From a6a5f09bf7a79018150abd2cf761467ef94b1fc2 Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 26 Aug 2026 12:24:41 +0200 Subject: [PATCH 01/13] feat(core)!: type the string-valued protocol enums on the wire Fields carrying ObsOutputState or ObsMediaInputAction were generated as strings, so every caller ran FromWireValue on the way in and ToWireValue on the way out. Which fields draw from an enum is not in the definition, which types them all as String, so the association is an explicit table like the numeric one. Both transports needed a converter: System.Text.Json can map the member names, but MessagePack's generator would have written the ordinal, which OBS rejects and which no round trip through this library alone would notice. Covered by tests that assert the bytes, not just the round trip. An unrecognised value maps to the enum's zero member rather than throwing, so a state added by a newer OBS does not fail the whole message. mediaState, monitorType, sceneItemBlendMode and inputKind carry fixed vocabularies too, but the protocol declares no enum for them, so they stay strings rather than ones this library would maintain by hand. Restores the helper index dropped in the README rewrite, folds the numbers and enums sections together now that neither needs conversion explained, and covers five more helpers in the example. --- .../Generation/Emitter.DtoGeneration.cs | 27 ++- .../Generation/Emitter.Helpers.cs | 7 +- .../Generation/Emitter.cs | 75 ++++++ .../Generation/StringEnumFieldTable.cs | 32 +++ .../Events/CustomEvent.EventPayload.g.cs | 2 +- .../InputAudioTracksChanged.EventPayload.g.cs | 2 +- .../Events/InputCreated.EventPayload.g.cs | 2 +- .../InputSettingsChanged.EventPayload.g.cs | 2 +- .../InputVolumeMeters.EventPayload.g.cs | 2 +- ...ediaInputActionTriggered.EventPayload.g.cs | 6 +- .../ProfileListChanged.EventPayload.g.cs | 2 +- .../RecordStateChanged.EventPayload.g.cs | 6 +- ...ReplayBufferStateChanged.EventPayload.g.cs | 6 +- ...eneCollectionListChanged.EventPayload.g.cs | 2 +- .../SceneItemListReindexed.EventPayload.g.cs | 2 +- ...ceneItemTransformChanged.EventPayload.g.cs | 2 +- .../Events/SceneListChanged.EventPayload.g.cs | 2 +- .../SourceFilterCreated.EventPayload.g.cs | 2 +- ...ourceFilterListReindexed.EventPayload.g.cs | 2 +- ...rceFilterSettingsChanged.EventPayload.g.cs | 2 +- .../StreamStateChanged.EventPayload.g.cs | 6 +- .../Events/VendorEvent.EventPayload.g.cs | 2 +- .../VirtualcamStateChanged.EventPayload.g.cs | 6 +- .../Generated/MediaInputAction.TypedEnum.g.cs | 28 +++ .../Generated/OutputState.TypedEnum.g.cs | 28 +++ .../Requests/CallVendorRequest.Request.g.cs | 2 +- .../Requests/CreateInput.Request.g.cs | 2 +- .../Requests/CreateSourceFilter.Request.g.cs | 2 +- .../TriggerMediaInputAction.Request.g.cs | 6 +- .../Responses/CallVendorRequest.Response.g.cs | 2 +- .../Responses/GetCanvasList.Response.g.cs | 2 +- .../GetCurrentSceneTransition.Response.g.cs | 2 +- .../Responses/GetGroupList.Response.g.cs | 2 +- .../GetGroupSceneItemList.Response.g.cs | 2 +- .../Responses/GetHotkeyList.Response.g.cs | 2 +- .../GetInputAudioTracks.Response.g.cs | 2 +- .../GetInputDefaultSettings.Response.g.cs | 2 +- .../Responses/GetInputKindList.Response.g.cs | 2 +- .../Responses/GetInputList.Response.g.cs | 2 +- ...tPropertiesListPropertyItems.Response.g.cs | 2 +- .../Responses/GetInputSettings.Response.g.cs | 2 +- .../Responses/GetMonitorList.Response.g.cs | 2 +- .../Responses/GetOutputList.Response.g.cs | 2 +- .../Responses/GetOutputSettings.Response.g.cs | 2 +- .../Responses/GetPersistentData.Response.g.cs | 2 +- .../Responses/GetProfileList.Response.g.cs | 2 +- .../GetSceneCollectionList.Response.g.cs | 2 +- .../Responses/GetSceneItemList.Response.g.cs | 2 +- .../GetSceneItemTransform.Response.g.cs | 2 +- .../Responses/GetSceneList.Response.g.cs | 2 +- .../GetSceneTransitionList.Response.g.cs | 2 +- .../Responses/GetSourceFilter.Response.g.cs | 2 +- ...tSourceFilterDefaultSettings.Response.g.cs | 2 +- .../GetSourceFilterKindList.Response.g.cs | 2 +- .../GetSourceFilterList.Response.g.cs | 2 +- .../GetStreamServiceSettings.Response.g.cs | 2 +- .../GetTransitionKindList.Response.g.cs | 2 +- .../Responses/GetVersion.Response.g.cs | 2 +- ObsWebSocket.Core/Groups/MediaInputsGroup.cs | 2 +- ObsWebSocket.Core/Groups/RecordGroup.cs | 2 +- ObsWebSocket.Core/Groups/StreamGroup.cs | 2 +- ObsWebSocket.Example/Worker.cs | 213 +++++++++++++++++- ObsWebSocket.Tests/ReadmeCompileCheck.cs | 26 ++- ObsWebSocket.Tests/WireEnumTests.cs | 98 ++++++++ README.md | 138 ++++++++++-- 65 files changed, 711 insertions(+), 95 deletions(-) create mode 100644 ObsWebSocket.Codegen.Tasks/Generation/StringEnumFieldTable.cs create mode 100644 ObsWebSocket.Tests/WireEnumTests.cs diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs index 3ace87f..7e7c969 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs @@ -525,6 +525,19 @@ ProtocolDefinition protocol continue; } + // A field mapped onto a protocol enum needs the wire value on both transports, not the + // member name and not the ordinal. It is never nullable: OBS always sends a value, and + // one it does not recognise maps onto the enum's zero member rather than onto null, so + // a caller never has to null check a state before switching on it. + string? propertyStringEnum = + associatedFieldDef?.ValueType == "String" + ? StringEnumFieldTable.MapStringEnum(originalName) + : null; + if (propertyStringEnum is not null) + { + propertyNullableSuffix = string.Empty; + } + PropertyGenInfo propInfo = new( propertyName, ToCamelCase(propertyName), @@ -561,6 +574,16 @@ ProtocolDefinition protocol mainBuilder.AppendLine($" [JsonPropertyName(\"{originalName}\")]"); mainBuilder.AppendLine($" [Key(\"{originalName}\")]"); + + if (propertyStringEnum is not null) + { + mainBuilder.AppendLine( + $" [JsonConverter(typeof({GeneratedEnumsNamespace}.{propertyStringEnum}JsonConverter))]" + ); + mainBuilder.AppendLine( + $" [MessagePackFormatter(typeof({GeneratedEnumsNamespace}.{propertyStringEnum}MessagePackFormatter))]" + ); + } mainBuilder.Append(" public "); if (isConsideredRequired) { @@ -648,7 +671,9 @@ out bool isRootOfNested // Append optional parameters WITH default null value constructorParams.AddRange( optionalParamsList.Select(p => - $"{p.CSharpType}{p.NullableSuffix} {p.ParamName} = null" + // A non-nullable optional parameter cannot default to null. That happens for + // the enum-typed fields, whose zero member is the "unknown" one anyway. + $"{p.CSharpType}{p.NullableSuffix} {p.ParamName} = {(p.NullableSuffix.Length == 0 ? "default" : "null")}" ) ); diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.Helpers.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.Helpers.cs index 60054c5..c373b9a 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.Helpers.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.Helpers.cs @@ -261,9 +261,14 @@ string parentDtoName } } + // A String field that carries a protocol enum is generated as that enum, with converters + // that map to and from the wire value on both transports. + string? stringEnum = + obsType == "String" ? StringEnumFieldTable.MapStringEnum(fieldName) : null; + string? mappedType = obsType switch { - "String" => "string", + "String" => stringEnum is null ? "string" : $"{GeneratedEnumsNamespace}.{stringEnum}", "Number" => numberType, "Boolean" => "bool", "Uuid" => "string", diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs index f05d358..9b7aa0b 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs @@ -338,6 +338,16 @@ private static string SnakeToPascalCase(string upperSnake) => string prefix = FindCommonMemberPrefix([.. members.Select(m => m.Member)]); + // The first member is the enum's zero value, which is what an unrecognised wire value + // falls back to. Both protocol enums name theirs sensibly (Unknown, None). + string firstMember = members[0].Member; + if (prefix.Length > 0 && firstMember.StartsWith(prefix, StringComparison.Ordinal)) + { + firstMember = firstMember.Substring(prefix.Length); + } + + string fallbackMember = SnakeToPascalCase(firstMember); + StringBuilder builder = BuildSourceHeader( "// Type: Typed Enum for a string-valued protocol enum" ); @@ -427,9 +437,74 @@ private static string SnakeToPascalCase(string upperSnake) => builder.AppendLine(" _ => null,"); builder.AppendLine(" };"); builder.AppendLine("}"); + builder.AppendLine(); + AppendWireConverters(builder, enumName, fallbackMember); return builder.ToString(); } + /// + /// Emits the converters that let a generated property be the enum itself rather than the wire + /// string, one for each transport. + /// + /// + /// Both map an unrecognised value onto the enum's zero member rather than throwing, so a state + /// added by a newer OBS degrades to an unknown value instead of failing the whole message. + /// + private static void AppendWireConverters( + StringBuilder builder, + string enumName, + string fallbackMember + ) + { + builder.AppendLine("/// "); + builder.AppendLine( + $"/// Reads and writes as the protocol string in JSON." + ); + builder.AppendLine("/// "); + builder.AppendLine( + $"public sealed class {enumName}JsonConverter : System.Text.Json.Serialization.JsonConverter<{enumName}>" + ); + builder.AppendLine("{"); + builder.AppendLine(" /// "); + builder.AppendLine( + $" public override {enumName} Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) =>" + ); + builder.AppendLine( + $" {enumName}Extensions.FromWireValue(reader.GetString()) ?? {enumName}.{fallbackMember};" + ); + builder.AppendLine(); + builder.AppendLine(" /// "); + builder.AppendLine( + $" public override void Write(System.Text.Json.Utf8JsonWriter writer, {enumName} value, System.Text.Json.JsonSerializerOptions options) =>" + ); + builder.AppendLine(" writer.WriteStringValue(value.ToWireValue());"); + builder.AppendLine("}"); + builder.AppendLine(); + builder.AppendLine("/// "); + builder.AppendLine( + $"/// Reads and writes as the protocol string in MessagePack." + ); + builder.AppendLine("/// "); + builder.AppendLine( + $"public sealed class {enumName}MessagePackFormatter : MessagePack.Formatters.IMessagePackFormatter<{enumName}>" + ); + builder.AppendLine("{"); + builder.AppendLine(" /// "); + builder.AppendLine( + $" public {enumName} Deserialize(ref MessagePack.MessagePackReader reader, MessagePack.MessagePackSerializerOptions options) =>" + ); + builder.AppendLine( + $" {enumName}Extensions.FromWireValue(reader.ReadString()) ?? {enumName}.{fallbackMember};" + ); + builder.AppendLine(); + builder.AppendLine(" /// "); + builder.AppendLine( + $" public void Serialize(ref MessagePack.MessagePackWriter writer, {enumName} value, MessagePack.MessagePackSerializerOptions options) =>" + ); + builder.AppendLine(" writer.Write(value.ToWireValue());"); + builder.AppendLine("}"); + } + /// /// Handles the case where the enum value kind could not be determined. Reports an error. /// diff --git a/ObsWebSocket.Codegen.Tasks/Generation/StringEnumFieldTable.cs b/ObsWebSocket.Codegen.Tasks/Generation/StringEnumFieldTable.cs new file mode 100644 index 0000000..836c6f0 --- /dev/null +++ b/ObsWebSocket.Codegen.Tasks/Generation/StringEnumFieldTable.cs @@ -0,0 +1,32 @@ +// ObsWebSocket.Codegen.Tasks/Generation/StringEnumFieldTable.cs +namespace ObsWebSocket.Codegen.Tasks.Generation; + +/// +/// Maps protocol fields that carry one of the string-valued protocol enums onto that enum, so the +/// generated property is the enum rather than a string the caller has to convert. +/// +/// +/// The definition types these fields as plain String and never says which enum they draw +/// from, so the association is written out here. Only fields backed by an enum the protocol +/// actually declares are listed: mediaState, monitorType, sceneItemBlendMode +/// and inputKind also carry fixed vocabularies, but the protocol declares no enum for them, +/// so they stay strings rather than being given one this library would have to maintain by hand. +/// +internal static class StringEnumFieldTable +{ + private static readonly Dictionary s_fieldToEnum = new(StringComparer.Ordinal) + { + // StreamStateChanged, RecordStateChanged, ReplayBufferStateChanged, VirtualcamStateChanged + ["outputState"] = "OutputState", + // TriggerMediaInputAction, and the event it raises + ["mediaAction"] = "MediaInputAction", + }; + + /// + /// Returns the enum type name a String field maps to, or when it + /// is an ordinary string. + /// + /// The protocol field name, matched case sensitively. + public static string? MapStringEnum(string fieldName) => + s_fieldToEnum.TryGetValue(fieldName, out string? enumName) ? enumName : null; +} diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CustomEvent.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CustomEvent.EventPayload.g.cs index 6540d45..3ef7478 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CustomEvent.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CustomEvent.EventPayload.g.cs @@ -39,7 +39,7 @@ public CustomEventPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CustomEventPayload(System.Text.Json.JsonElement? eventData = null) + public CustomEventPayload(System.Text.Json.JsonElement? eventData = default) { this.EventData = eventData; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioTracksChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioTracksChanged.EventPayload.g.cs index 560c00b..3bb169a 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioTracksChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioTracksChanged.EventPayload.g.cs @@ -53,7 +53,7 @@ public InputAudioTracksChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public InputAudioTracksChangedPayload(string? inputName = null, string? inputUuid = null, System.Collections.Generic.Dictionary? inputAudioTracks = null) + public InputAudioTracksChangedPayload(string? inputName = null, string? inputUuid = null, System.Collections.Generic.Dictionary? inputAudioTracks = default) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputCreated.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputCreated.EventPayload.g.cs index a57cb9d..6cc4c1d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputCreated.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputCreated.EventPayload.g.cs @@ -82,7 +82,7 @@ public InputCreatedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public InputCreatedPayload(long inputKindCaps, string? inputName = null, string? inputUuid = null, string? inputKind = null, string? unversionedInputKind = null, System.Text.Json.JsonElement? inputSettings = null, System.Text.Json.JsonElement? defaultInputSettings = null) + public InputCreatedPayload(long inputKindCaps, string? inputName = null, string? inputUuid = null, string? inputKind = null, string? unversionedInputKind = null, System.Text.Json.JsonElement? inputSettings = default, System.Text.Json.JsonElement? defaultInputSettings = default) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputSettingsChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputSettingsChanged.EventPayload.g.cs index 1cbd118..518423e 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputSettingsChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputSettingsChanged.EventPayload.g.cs @@ -55,7 +55,7 @@ public InputSettingsChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public InputSettingsChangedPayload(string? inputName = null, string? inputUuid = null, System.Text.Json.JsonElement? inputSettings = null) + public InputSettingsChangedPayload(string? inputName = null, string? inputUuid = null, System.Text.Json.JsonElement? inputSettings = default) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeMeters.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeMeters.EventPayload.g.cs index 1c231ca..db8e9ca 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeMeters.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeMeters.EventPayload.g.cs @@ -39,7 +39,7 @@ public InputVolumeMetersPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public InputVolumeMetersPayload(System.Collections.Generic.List? inputs = null) + public InputVolumeMetersPayload(System.Collections.Generic.List? inputs = default) { this.Inputs = inputs; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputActionTriggered.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputActionTriggered.EventPayload.g.cs index c279ad1..6e30ffc 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputActionTriggered.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputActionTriggered.EventPayload.g.cs @@ -43,7 +43,9 @@ public sealed partial record MediaInputActionTriggeredPayload /// [JsonPropertyName("mediaAction")] [Key("mediaAction")] - public string? MediaAction { get; init; } + [JsonConverter(typeof(ObsWebSocket.Core.Protocol.Generated.MediaInputActionJsonConverter))] + [MessagePackFormatter(typeof(ObsWebSocket.Core.Protocol.Generated.MediaInputActionMessagePackFormatter))] + public ObsWebSocket.Core.Protocol.Generated.MediaInputAction MediaAction { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +55,7 @@ public MediaInputActionTriggeredPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public MediaInputActionTriggeredPayload(string? inputName = null, string? inputUuid = null, string? mediaAction = null) + public MediaInputActionTriggeredPayload(string? inputName = null, string? inputUuid = null, ObsWebSocket.Core.Protocol.Generated.MediaInputAction mediaAction = default) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/ProfileListChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/ProfileListChanged.EventPayload.g.cs index 071862f..2331ca4 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/ProfileListChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/ProfileListChanged.EventPayload.g.cs @@ -39,7 +39,7 @@ public ProfileListChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public ProfileListChangedPayload(System.Collections.Generic.List? profiles = null) + public ProfileListChangedPayload(System.Collections.Generic.List? profiles = default) { this.Profiles = profiles; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/RecordStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/RecordStateChanged.EventPayload.g.cs index c255a07..d931b31 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/RecordStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/RecordStateChanged.EventPayload.g.cs @@ -43,7 +43,9 @@ public sealed partial record RecordStateChangedPayload /// [JsonPropertyName("outputState")] [Key("outputState")] - public string? OutputState { get; init; } + [JsonConverter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateJsonConverter))] + [MessagePackFormatter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateMessagePackFormatter))] + public ObsWebSocket.Core.Protocol.Generated.OutputState OutputState { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +56,7 @@ public RecordStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public RecordStateChangedPayload(bool outputActive, string? outputState = null, string? outputPath = null) + public RecordStateChangedPayload(bool outputActive, ObsWebSocket.Core.Protocol.Generated.OutputState outputState = default, string? outputPath = null) { this.OutputActive = outputActive; this.OutputState = outputState; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferStateChanged.EventPayload.g.cs index 4024dc7..5dae271 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferStateChanged.EventPayload.g.cs @@ -36,7 +36,9 @@ public sealed partial record ReplayBufferStateChangedPayload /// [JsonPropertyName("outputState")] [Key("outputState")] - public string? OutputState { get; init; } + [JsonConverter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateJsonConverter))] + [MessagePackFormatter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateMessagePackFormatter))] + public ObsWebSocket.Core.Protocol.Generated.OutputState OutputState { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -47,7 +49,7 @@ public ReplayBufferStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public ReplayBufferStateChangedPayload(bool outputActive, string? outputState = null) + public ReplayBufferStateChangedPayload(bool outputActive, ObsWebSocket.Core.Protocol.Generated.OutputState outputState = default) { this.OutputActive = outputActive; this.OutputState = outputState; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneCollectionListChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneCollectionListChanged.EventPayload.g.cs index c3d41de..35b75a9 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneCollectionListChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneCollectionListChanged.EventPayload.g.cs @@ -39,7 +39,7 @@ public SceneCollectionListChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneCollectionListChangedPayload(System.Collections.Generic.List? sceneCollections = null) + public SceneCollectionListChangedPayload(System.Collections.Generic.List? sceneCollections = default) { this.SceneCollections = sceneCollections; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemListReindexed.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemListReindexed.EventPayload.g.cs index cffcdb6..152a6d1 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemListReindexed.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemListReindexed.EventPayload.g.cs @@ -53,7 +53,7 @@ public SceneItemListReindexedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneItemListReindexedPayload(string? sceneName = null, string? sceneUuid = null, System.Collections.Generic.List? sceneItems = null) + public SceneItemListReindexedPayload(string? sceneName = null, string? sceneUuid = null, System.Collections.Generic.List? sceneItems = default) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs index bd2c2e0..20a42b5 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs @@ -61,7 +61,7 @@ public SceneItemTransformChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneItemTransformChangedPayload(int sceneItemId, string? sceneName = null, string? sceneUuid = null, ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub? sceneItemTransform = null) + public SceneItemTransformChangedPayload(int sceneItemId, string? sceneName = null, string? sceneUuid = null, ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub? sceneItemTransform = default) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneListChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneListChanged.EventPayload.g.cs index 98aa9a3..8ff306f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneListChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneListChanged.EventPayload.g.cs @@ -41,7 +41,7 @@ public SceneListChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneListChangedPayload(System.Collections.Generic.List? scenes = null) + public SceneListChangedPayload(System.Collections.Generic.List? scenes = default) { this.Scenes = scenes; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterCreated.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterCreated.EventPayload.g.cs index 7632912..386ce8c 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterCreated.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterCreated.EventPayload.g.cs @@ -75,7 +75,7 @@ public SourceFilterCreatedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SourceFilterCreatedPayload(int filterIndex, string? sourceName = null, string? filterName = null, string? filterKind = null, System.Text.Json.JsonElement? filterSettings = null, System.Text.Json.JsonElement? defaultFilterSettings = null) + public SourceFilterCreatedPayload(int filterIndex, string? sourceName = null, string? filterName = null, string? filterKind = null, System.Text.Json.JsonElement? filterSettings = default, System.Text.Json.JsonElement? defaultFilterSettings = default) { this.SourceName = sourceName; this.FilterName = filterName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterListReindexed.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterListReindexed.EventPayload.g.cs index 7e2ce55..30279e1 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterListReindexed.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterListReindexed.EventPayload.g.cs @@ -46,7 +46,7 @@ public SourceFilterListReindexedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SourceFilterListReindexedPayload(string? sourceName = null, System.Collections.Generic.List? filters = null) + public SourceFilterListReindexedPayload(string? sourceName = null, System.Collections.Generic.List? filters = default) { this.SourceName = sourceName; this.Filters = filters; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterSettingsChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterSettingsChanged.EventPayload.g.cs index 7d4bb98..9a5c963 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterSettingsChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterSettingsChanged.EventPayload.g.cs @@ -53,7 +53,7 @@ public SourceFilterSettingsChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SourceFilterSettingsChangedPayload(string? sourceName = null, string? filterName = null, System.Text.Json.JsonElement? filterSettings = null) + public SourceFilterSettingsChangedPayload(string? sourceName = null, string? filterName = null, System.Text.Json.JsonElement? filterSettings = default) { this.SourceName = sourceName; this.FilterName = filterName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/StreamStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/StreamStateChanged.EventPayload.g.cs index 6200c07..61f570f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/StreamStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/StreamStateChanged.EventPayload.g.cs @@ -36,7 +36,9 @@ public sealed partial record StreamStateChangedPayload /// [JsonPropertyName("outputState")] [Key("outputState")] - public string? OutputState { get; init; } + [JsonConverter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateJsonConverter))] + [MessagePackFormatter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateMessagePackFormatter))] + public ObsWebSocket.Core.Protocol.Generated.OutputState OutputState { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -47,7 +49,7 @@ public StreamStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public StreamStateChangedPayload(bool outputActive, string? outputState = null) + public StreamStateChangedPayload(bool outputActive, ObsWebSocket.Core.Protocol.Generated.OutputState outputState = default) { this.OutputActive = outputActive; this.OutputState = outputState; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/VendorEvent.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/VendorEvent.EventPayload.g.cs index 5ca8346..500dcbc 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/VendorEvent.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/VendorEvent.EventPayload.g.cs @@ -56,7 +56,7 @@ public VendorEventPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public VendorEventPayload(string? vendorName = null, string? eventType = null, System.Text.Json.JsonElement? eventData = null) + public VendorEventPayload(string? vendorName = null, string? eventType = null, System.Text.Json.JsonElement? eventData = default) { this.VendorName = vendorName; this.EventType = eventType; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/VirtualcamStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/VirtualcamStateChanged.EventPayload.g.cs index b92b4f9..34b4ac7 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/VirtualcamStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/VirtualcamStateChanged.EventPayload.g.cs @@ -36,7 +36,9 @@ public sealed partial record VirtualcamStateChangedPayload /// [JsonPropertyName("outputState")] [Key("outputState")] - public string? OutputState { get; init; } + [JsonConverter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateJsonConverter))] + [MessagePackFormatter(typeof(ObsWebSocket.Core.Protocol.Generated.OutputStateMessagePackFormatter))] + public ObsWebSocket.Core.Protocol.Generated.OutputState OutputState { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -47,7 +49,7 @@ public VirtualcamStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public VirtualcamStateChangedPayload(bool outputActive, string? outputState = null) + public VirtualcamStateChangedPayload(bool outputActive, ObsWebSocket.Core.Protocol.Generated.OutputState outputState = default) { this.OutputActive = outputActive; this.OutputState = outputState; diff --git a/ObsWebSocket.Core/Generated/Protocol/Generated/MediaInputAction.TypedEnum.g.cs b/ObsWebSocket.Core/Generated/Protocol/Generated/MediaInputAction.TypedEnum.g.cs index 7ca6202..f4a01e0 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Generated/MediaInputAction.TypedEnum.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Generated/MediaInputAction.TypedEnum.g.cs @@ -74,3 +74,31 @@ public static class MediaInputActionExtensions _ => null, }; } + +/// +/// Reads and writes as the protocol string in JSON. +/// +public sealed class MediaInputActionJsonConverter : System.Text.Json.Serialization.JsonConverter +{ + /// + public override MediaInputAction Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) => + MediaInputActionExtensions.FromWireValue(reader.GetString()) ?? MediaInputAction.None; + + /// + public override void Write(System.Text.Json.Utf8JsonWriter writer, MediaInputAction value, System.Text.Json.JsonSerializerOptions options) => + writer.WriteStringValue(value.ToWireValue()); +} + +/// +/// Reads and writes as the protocol string in MessagePack. +/// +public sealed class MediaInputActionMessagePackFormatter : MessagePack.Formatters.IMessagePackFormatter +{ + /// + public MediaInputAction Deserialize(ref MessagePack.MessagePackReader reader, MessagePack.MessagePackSerializerOptions options) => + MediaInputActionExtensions.FromWireValue(reader.ReadString()) ?? MediaInputAction.None; + + /// + public void Serialize(ref MessagePack.MessagePackWriter writer, MediaInputAction value, MessagePack.MessagePackSerializerOptions options) => + writer.Write(value.ToWireValue()); +} diff --git a/ObsWebSocket.Core/Generated/Protocol/Generated/OutputState.TypedEnum.g.cs b/ObsWebSocket.Core/Generated/Protocol/Generated/OutputState.TypedEnum.g.cs index 1bbac8c..1cef17a 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Generated/OutputState.TypedEnum.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Generated/OutputState.TypedEnum.g.cs @@ -86,3 +86,31 @@ public static class OutputStateExtensions _ => null, }; } + +/// +/// Reads and writes as the protocol string in JSON. +/// +public sealed class OutputStateJsonConverter : System.Text.Json.Serialization.JsonConverter +{ + /// + public override OutputState Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) => + OutputStateExtensions.FromWireValue(reader.GetString()) ?? OutputState.Unknown; + + /// + public override void Write(System.Text.Json.Utf8JsonWriter writer, OutputState value, System.Text.Json.JsonSerializerOptions options) => + writer.WriteStringValue(value.ToWireValue()); +} + +/// +/// Reads and writes as the protocol string in MessagePack. +/// +public sealed class OutputStateMessagePackFormatter : MessagePack.Formatters.IMessagePackFormatter +{ + /// + public OutputState Deserialize(ref MessagePack.MessagePackReader reader, MessagePack.MessagePackSerializerOptions options) => + OutputStateExtensions.FromWireValue(reader.ReadString()) ?? OutputState.Unknown; + + /// + public void Serialize(ref MessagePack.MessagePackWriter writer, OutputState value, MessagePack.MessagePackSerializerOptions options) => + writer.Write(value.ToWireValue()); +} diff --git a/ObsWebSocket.Core/Generated/Protocol/Requests/CallVendorRequest.Request.g.cs b/ObsWebSocket.Core/Generated/Protocol/Requests/CallVendorRequest.Request.g.cs index b7ec4f8..8b1cdb5 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Requests/CallVendorRequest.Request.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Requests/CallVendorRequest.Request.g.cs @@ -67,7 +67,7 @@ public CallVendorRequestRequestData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public CallVendorRequestRequestData(string vendorName, string requestType, System.Text.Json.JsonElement? requestData = null) + public CallVendorRequestRequestData(string vendorName, string requestType, System.Text.Json.JsonElement? requestData = default) { this.VendorName = vendorName; this.RequestType = requestType; diff --git a/ObsWebSocket.Core/Generated/Protocol/Requests/CreateInput.Request.g.cs b/ObsWebSocket.Core/Generated/Protocol/Requests/CreateInput.Request.g.cs index aff47e8..f22e769 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Requests/CreateInput.Request.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Requests/CreateInput.Request.g.cs @@ -108,7 +108,7 @@ public CreateInputRequestData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public CreateInputRequestData(string inputName, string inputKind, string? canvasUuid = null, string? sceneName = null, string? sceneUuid = null, System.Text.Json.JsonElement? inputSettings = null, bool? sceneItemEnabled = null) + public CreateInputRequestData(string inputName, string inputKind, string? canvasUuid = null, string? sceneName = null, string? sceneUuid = null, System.Text.Json.JsonElement? inputSettings = default, bool? sceneItemEnabled = null) { this.CanvasUuid = canvasUuid; this.SceneName = sceneName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Requests/CreateSourceFilter.Request.g.cs b/ObsWebSocket.Core/Generated/Protocol/Requests/CreateSourceFilter.Request.g.cs index c6e1632..578649f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Requests/CreateSourceFilter.Request.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Requests/CreateSourceFilter.Request.g.cs @@ -97,7 +97,7 @@ public CreateSourceFilterRequestData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public CreateSourceFilterRequestData(string filterName, string filterKind, string? canvasUuid = null, string? sourceName = null, string? sourceUuid = null, System.Text.Json.JsonElement? filterSettings = null) + public CreateSourceFilterRequestData(string filterName, string filterKind, string? canvasUuid = null, string? sourceName = null, string? sourceUuid = null, System.Text.Json.JsonElement? filterSettings = default) { this.CanvasUuid = canvasUuid; this.SourceName = sourceName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Requests/TriggerMediaInputAction.Request.g.cs b/ObsWebSocket.Core/Generated/Protocol/Requests/TriggerMediaInputAction.Request.g.cs index b8f067a..9751408 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Requests/TriggerMediaInputAction.Request.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Requests/TriggerMediaInputAction.Request.g.cs @@ -54,7 +54,9 @@ public sealed partial record TriggerMediaInputActionRequestData /// [JsonPropertyName("mediaAction")] [Key("mediaAction")] - public required string MediaAction { get; init; } + [JsonConverter(typeof(ObsWebSocket.Core.Protocol.Generated.MediaInputActionJsonConverter))] + [MessagePackFormatter(typeof(ObsWebSocket.Core.Protocol.Generated.MediaInputActionMessagePackFormatter))] + public required ObsWebSocket.Core.Protocol.Generated.MediaInputAction MediaAction { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -65,7 +67,7 @@ public TriggerMediaInputActionRequestData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public TriggerMediaInputActionRequestData(string mediaAction, string? inputName = null, string? inputUuid = null) + public TriggerMediaInputActionRequestData(ObsWebSocket.Core.Protocol.Generated.MediaInputAction mediaAction, string? inputName = null, string? inputUuid = null) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/CallVendorRequest.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/CallVendorRequest.Response.g.cs index 4db8e0a..3e0a706 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/CallVendorRequest.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/CallVendorRequest.Response.g.cs @@ -56,7 +56,7 @@ public CallVendorRequestResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CallVendorRequestResponseData(string? vendorName = null, string? requestType = null, System.Text.Json.JsonElement? responseData = null) + public CallVendorRequestResponseData(string? vendorName = null, string? requestType = null, System.Text.Json.JsonElement? responseData = default) { this.VendorName = vendorName; this.RequestType = requestType; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCanvasList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCanvasList.Response.g.cs index 90b57fe..dd59387 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCanvasList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCanvasList.Response.g.cs @@ -39,7 +39,7 @@ public GetCanvasListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetCanvasListResponseData(System.Collections.Generic.List? canvases = null) + public GetCanvasListResponseData(System.Collections.Generic.List? canvases = default) { this.Canvases = canvases; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentSceneTransition.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentSceneTransition.Response.g.cs index 54f270a..7c1ef9a 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentSceneTransition.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentSceneTransition.Response.g.cs @@ -82,7 +82,7 @@ public GetCurrentSceneTransitionResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public GetCurrentSceneTransitionResponseData(bool transitionFixed, bool transitionConfigurable, string? transitionName = null, string? transitionUuid = null, string? transitionKind = null, int? transitionDuration = null, System.Text.Json.JsonElement? transitionSettings = null) + public GetCurrentSceneTransitionResponseData(bool transitionFixed, bool transitionConfigurable, string? transitionName = null, string? transitionUuid = null, string? transitionKind = null, int? transitionDuration = null, System.Text.Json.JsonElement? transitionSettings = default) { this.TransitionName = transitionName; this.TransitionUuid = transitionUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupList.Response.g.cs index 7a59dd6..fa1bd5b 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupList.Response.g.cs @@ -41,7 +41,7 @@ public GetGroupListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetGroupListResponseData(System.Collections.Generic.List? groups = null) + public GetGroupListResponseData(System.Collections.Generic.List? groups = default) { this.Groups = groups; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupSceneItemList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupSceneItemList.Response.g.cs index faa6d2a..3276769 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupSceneItemList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupSceneItemList.Response.g.cs @@ -43,7 +43,7 @@ public GetGroupSceneItemListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetGroupSceneItemListResponseData(System.Collections.Generic.List? sceneItems = null) + public GetGroupSceneItemListResponseData(System.Collections.Generic.List? sceneItems = default) { this.SceneItems = sceneItems; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetHotkeyList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetHotkeyList.Response.g.cs index 51b51f8..71b2bdd 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetHotkeyList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetHotkeyList.Response.g.cs @@ -41,7 +41,7 @@ public GetHotkeyListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetHotkeyListResponseData(System.Collections.Generic.List? hotkeys = null) + public GetHotkeyListResponseData(System.Collections.Generic.List? hotkeys = default) { this.Hotkeys = hotkeys; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioTracks.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioTracks.Response.g.cs index 3de8e33..9e730b6 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioTracks.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioTracks.Response.g.cs @@ -39,7 +39,7 @@ public GetInputAudioTracksResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputAudioTracksResponseData(System.Collections.Generic.Dictionary? inputAudioTracks = null) + public GetInputAudioTracksResponseData(System.Collections.Generic.Dictionary? inputAudioTracks = default) { this.InputAudioTracks = inputAudioTracks; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDefaultSettings.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDefaultSettings.Response.g.cs index 3cae6e2..a6b73fa 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDefaultSettings.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDefaultSettings.Response.g.cs @@ -39,7 +39,7 @@ public GetInputDefaultSettingsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputDefaultSettingsResponseData(System.Text.Json.JsonElement? defaultInputSettings = null) + public GetInputDefaultSettingsResponseData(System.Text.Json.JsonElement? defaultInputSettings = default) { this.DefaultInputSettings = defaultInputSettings; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputKindList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputKindList.Response.g.cs index d926cd5..09063e0 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputKindList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputKindList.Response.g.cs @@ -39,7 +39,7 @@ public GetInputKindListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputKindListResponseData(System.Collections.Generic.List? inputKinds = null) + public GetInputKindListResponseData(System.Collections.Generic.List? inputKinds = default) { this.InputKinds = inputKinds; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputList.Response.g.cs index fe8a6f7..6d78a2f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputList.Response.g.cs @@ -39,7 +39,7 @@ public GetInputListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputListResponseData(System.Collections.Generic.List? inputs = null) + public GetInputListResponseData(System.Collections.Generic.List? inputs = default) { this.Inputs = inputs; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputPropertiesListPropertyItems.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputPropertiesListPropertyItems.Response.g.cs index 22f90f1..7f62e7a 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputPropertiesListPropertyItems.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputPropertiesListPropertyItems.Response.g.cs @@ -41,7 +41,7 @@ public GetInputPropertiesListPropertyItemsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputPropertiesListPropertyItemsResponseData(System.Collections.Generic.List? propertyItems = null) + public GetInputPropertiesListPropertyItemsResponseData(System.Collections.Generic.List? propertyItems = default) { this.PropertyItems = propertyItems; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputSettings.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputSettings.Response.g.cs index bf2cd85..34de41a 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputSettings.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputSettings.Response.g.cs @@ -48,7 +48,7 @@ public GetInputSettingsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputSettingsResponseData(System.Text.Json.JsonElement? inputSettings = null, string? inputKind = null) + public GetInputSettingsResponseData(System.Text.Json.JsonElement? inputSettings = default, string? inputKind = null) { this.InputSettings = inputSettings; this.InputKind = inputKind; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetMonitorList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetMonitorList.Response.g.cs index c7bf0ae..74ea14c 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetMonitorList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetMonitorList.Response.g.cs @@ -39,7 +39,7 @@ public GetMonitorListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetMonitorListResponseData(System.Collections.Generic.List? monitors = null) + public GetMonitorListResponseData(System.Collections.Generic.List? monitors = default) { this.Monitors = monitors; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputList.Response.g.cs index f09911e..38c9b55 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputList.Response.g.cs @@ -39,7 +39,7 @@ public GetOutputListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetOutputListResponseData(System.Collections.Generic.List? outputs = null) + public GetOutputListResponseData(System.Collections.Generic.List? outputs = default) { this.Outputs = outputs; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputSettings.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputSettings.Response.g.cs index 6f7aff4..ad059ff 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputSettings.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputSettings.Response.g.cs @@ -39,7 +39,7 @@ public GetOutputSettingsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetOutputSettingsResponseData(System.Text.Json.JsonElement? outputSettings = null) + public GetOutputSettingsResponseData(System.Text.Json.JsonElement? outputSettings = default) { this.OutputSettings = outputSettings; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetPersistentData.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetPersistentData.Response.g.cs index 123f822..3372fd2 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetPersistentData.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetPersistentData.Response.g.cs @@ -39,7 +39,7 @@ public GetPersistentDataResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetPersistentDataResponseData(System.Text.Json.JsonElement? slotValue = null) + public GetPersistentDataResponseData(System.Text.Json.JsonElement? slotValue = default) { this.SlotValue = slotValue; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetProfileList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetProfileList.Response.g.cs index cbf1325..4820cf6 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetProfileList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetProfileList.Response.g.cs @@ -46,7 +46,7 @@ public GetProfileListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetProfileListResponseData(string? currentProfileName = null, System.Collections.Generic.List? profiles = null) + public GetProfileListResponseData(string? currentProfileName = null, System.Collections.Generic.List? profiles = default) { this.CurrentProfileName = currentProfileName; this.Profiles = profiles; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneCollectionList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneCollectionList.Response.g.cs index 43feb17..57abae9 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneCollectionList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneCollectionList.Response.g.cs @@ -46,7 +46,7 @@ public GetSceneCollectionListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneCollectionListResponseData(string? currentSceneCollectionName = null, System.Collections.Generic.List? sceneCollections = null) + public GetSceneCollectionListResponseData(string? currentSceneCollectionName = null, System.Collections.Generic.List? sceneCollections = default) { this.CurrentSceneCollectionName = currentSceneCollectionName; this.SceneCollections = sceneCollections; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemList.Response.g.cs index 6e5f373..9dadb01 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemList.Response.g.cs @@ -41,7 +41,7 @@ public GetSceneItemListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneItemListResponseData(System.Collections.Generic.List? sceneItems = null) + public GetSceneItemListResponseData(System.Collections.Generic.List? sceneItems = default) { this.SceneItems = sceneItems; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemTransform.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemTransform.Response.g.cs index 3a58313..a452d9d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemTransform.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemTransform.Response.g.cs @@ -41,7 +41,7 @@ public GetSceneItemTransformResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneItemTransformResponseData(ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub? sceneItemTransform = null) + public GetSceneItemTransformResponseData(ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub? sceneItemTransform = default) { this.SceneItemTransform = sceneItemTransform; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneList.Response.g.cs index 8088553..35528a2 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneList.Response.g.cs @@ -67,7 +67,7 @@ public GetSceneListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneListResponseData(string? currentProgramSceneName = null, string? currentProgramSceneUuid = null, string? currentPreviewSceneName = null, string? currentPreviewSceneUuid = null, System.Collections.Generic.List? scenes = null) + public GetSceneListResponseData(string? currentProgramSceneName = null, string? currentProgramSceneUuid = null, string? currentPreviewSceneName = null, string? currentPreviewSceneUuid = null, System.Collections.Generic.List? scenes = default) { this.CurrentProgramSceneName = currentProgramSceneName; this.CurrentProgramSceneUuid = currentProgramSceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneTransitionList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneTransitionList.Response.g.cs index ea28080..c65245b 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneTransitionList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneTransitionList.Response.g.cs @@ -60,7 +60,7 @@ public GetSceneTransitionListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneTransitionListResponseData(string? currentSceneTransitionName = null, string? currentSceneTransitionUuid = null, string? currentSceneTransitionKind = null, System.Collections.Generic.List? transitions = null) + public GetSceneTransitionListResponseData(string? currentSceneTransitionName = null, string? currentSceneTransitionUuid = null, string? currentSceneTransitionKind = null, System.Collections.Generic.List? transitions = default) { this.CurrentSceneTransitionName = currentSceneTransitionName; this.CurrentSceneTransitionUuid = currentSceneTransitionUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilter.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilter.Response.g.cs index ef6669c..ab2375a 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilter.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilter.Response.g.cs @@ -61,7 +61,7 @@ public GetSourceFilterResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public GetSourceFilterResponseData(bool filterEnabled, int filterIndex, string? filterKind = null, System.Text.Json.JsonElement? filterSettings = null) + public GetSourceFilterResponseData(bool filterEnabled, int filterIndex, string? filterKind = null, System.Text.Json.JsonElement? filterSettings = default) { this.FilterEnabled = filterEnabled; this.FilterIndex = filterIndex; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterDefaultSettings.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterDefaultSettings.Response.g.cs index baec898..3a8582f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterDefaultSettings.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterDefaultSettings.Response.g.cs @@ -39,7 +39,7 @@ public GetSourceFilterDefaultSettingsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSourceFilterDefaultSettingsResponseData(System.Text.Json.JsonElement? defaultFilterSettings = null) + public GetSourceFilterDefaultSettingsResponseData(System.Text.Json.JsonElement? defaultFilterSettings = default) { this.DefaultFilterSettings = defaultFilterSettings; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterKindList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterKindList.Response.g.cs index 7e3c343..62337d1 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterKindList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterKindList.Response.g.cs @@ -41,7 +41,7 @@ public GetSourceFilterKindListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSourceFilterKindListResponseData(System.Collections.Generic.List? sourceFilterKinds = null) + public GetSourceFilterKindListResponseData(System.Collections.Generic.List? sourceFilterKinds = default) { this.SourceFilterKinds = sourceFilterKinds; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterList.Response.g.cs index c45922f..a469a32 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterList.Response.g.cs @@ -39,7 +39,7 @@ public GetSourceFilterListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSourceFilterListResponseData(System.Collections.Generic.List? filters = null) + public GetSourceFilterListResponseData(System.Collections.Generic.List? filters = default) { this.Filters = filters; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamServiceSettings.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamServiceSettings.Response.g.cs index 387481f..2010980 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamServiceSettings.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamServiceSettings.Response.g.cs @@ -46,7 +46,7 @@ public GetStreamServiceSettingsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetStreamServiceSettingsResponseData(string? streamServiceType = null, System.Text.Json.JsonElement? streamServiceSettings = null) + public GetStreamServiceSettingsResponseData(string? streamServiceType = null, System.Text.Json.JsonElement? streamServiceSettings = default) { this.StreamServiceType = streamServiceType; this.StreamServiceSettings = streamServiceSettings; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetTransitionKindList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetTransitionKindList.Response.g.cs index e68225d..b65f895 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetTransitionKindList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetTransitionKindList.Response.g.cs @@ -41,7 +41,7 @@ public GetTransitionKindListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetTransitionKindListResponseData(System.Collections.Generic.List? transitionKinds = null) + public GetTransitionKindListResponseData(System.Collections.Generic.List? transitionKinds = default) { this.TransitionKinds = transitionKinds; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetVersion.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetVersion.Response.g.cs index 56eb552..0221d1b 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetVersion.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetVersion.Response.g.cs @@ -82,7 +82,7 @@ public GetVersionResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public GetVersionResponseData(int rpcVersion, string? obsVersion = null, string? obsWebSocketVersion = null, System.Collections.Generic.List? availableRequests = null, System.Collections.Generic.List? supportedImageFormats = null, string? platform = null, string? platformDescription = null) + public GetVersionResponseData(int rpcVersion, string? obsVersion = null, string? obsWebSocketVersion = null, System.Collections.Generic.List? availableRequests = default, System.Collections.Generic.List? supportedImageFormats = default, string? platform = null, string? platformDescription = null) { this.ObsVersion = obsVersion; this.ObsWebSocketVersion = obsWebSocketVersion; diff --git a/ObsWebSocket.Core/Groups/MediaInputsGroup.cs b/ObsWebSocket.Core/Groups/MediaInputsGroup.cs index 0c4f224..c6c1046 100644 --- a/ObsWebSocket.Core/Groups/MediaInputsGroup.cs +++ b/ObsWebSocket.Core/Groups/MediaInputsGroup.cs @@ -42,7 +42,7 @@ await client new TriggerMediaInputActionRequestData { InputName = inputName, - MediaAction = action.ToWireValue(), + MediaAction = action, }, cancellationToken ) diff --git a/ObsWebSocket.Core/Groups/RecordGroup.cs b/ObsWebSocket.Core/Groups/RecordGroup.cs index 86566e8..5ca831a 100644 --- a/ObsWebSocket.Core/Groups/RecordGroup.cs +++ b/ObsWebSocket.Core/Groups/RecordGroup.cs @@ -59,7 +59,7 @@ public readonly partial struct RecordGroup try { RecordStateChangedEventArgs ev = await waitTask.ConfigureAwait(false); - return OutputStateExtensions.FromWireValue(ev.EventData.OutputState); + return ev.EventData.OutputState; } catch (TimeoutException) { diff --git a/ObsWebSocket.Core/Groups/StreamGroup.cs b/ObsWebSocket.Core/Groups/StreamGroup.cs index 74ef2b7..f3f5eb3 100644 --- a/ObsWebSocket.Core/Groups/StreamGroup.cs +++ b/ObsWebSocket.Core/Groups/StreamGroup.cs @@ -58,7 +58,7 @@ public readonly partial struct StreamGroup try { StreamStateChangedEventArgs ev = await waitTask.ConfigureAwait(false); - return OutputStateExtensions.FromWireValue(ev.EventData.OutputState); + return ev.EventData.OutputState; } catch (TimeoutException) { diff --git a/ObsWebSocket.Example/Worker.cs b/ObsWebSocket.Example/Worker.cs index dada299..95d005d 100644 --- a/ObsWebSocket.Example/Worker.cs +++ b/ObsWebSocket.Example/Worker.cs @@ -2364,6 +2364,214 @@ await client .ConfigureAwait(false) ); + results.Add( + await TrySettingsCheckAsync( + "Preview scene helpers", + async () => + { + // Preview only exists in Studio Mode, so turn it on for the check and + // put it back however it was found. + GetStudioModeEnabledResponseData studio = await client + .Ui.GetStudioModeEnabledAsync(cancellationToken) + .ConfigureAwait(false); + if (!studio.StudioModeEnabled) + { + await client + .Ui.SetStudioModeEnabledAsync(new(true), cancellationToken) + .ConfigureAwait(false); + } + + try + { + // Every switch waits for the event confirming it. OBS points + // Preview at the Program scene while enabling Studio Mode, and + // that lands after StudioModeStateChanged, so a switch that does + // not wait for its own confirmation gets silently undone. + await client + .Scenes.SwitchPreviewSceneAndWaitAsync( + sceneName, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + GetCurrentPreviewSceneResponseData preview = await client + .Scenes.GetCurrentPreviewSceneAsync(cancellationToken) + .ConfigureAwait(false); + + // The plain overload, confirmed by waiting on the event directly. + Task back = + client.WaitForEventAsync( + e => + string.Equals( + e.EventData.SceneName, + originalScene, + StringComparison.Ordinal + ), + TimeSpan.FromSeconds(5), + cancellationToken + ); + await client + .Scenes.SwitchPreviewSceneAsync( + originalScene, + cancellationToken + ) + .ConfigureAwait(false); + _ = await back.ConfigureAwait(false); + + GetCurrentPreviewSceneResponseData restored = await client + .Scenes.GetCurrentPreviewSceneAsync(cancellationToken) + .ConfigureAwait(false); + + bool ok = + string.Equals( + preview.SceneName, + sceneName, + StringComparison.Ordinal + ) + && string.Equals( + restored.SceneName, + originalScene, + StringComparison.Ordinal + ); + + return ( + ok, + $"wanted {sceneName} got {preview.SceneName}, " + + $"then wanted {originalScene} got {restored.SceneName}" + ); + } + finally + { + if (!studio.StudioModeEnabled) + { + await client + .Ui.SetStudioModeEnabledAsync(new(false), cancellationToken) + .ConfigureAwait(false); + } + } + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "SourceExistsAsync", + async () => + { + bool present = await client + .Sources.SourceExistsAsync(inputName, cancellationToken) + .ConfigureAwait(false); + bool absent = await client + .Sources.SourceExistsAsync("__absent__", cancellationToken) + .ConfigureAwait(false); + + return (present && !absent, $"present={present}, absent={absent}"); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "SetInputMutesAsync", + async () => + { + GetInputMuteResponseData before = await client + .Inputs.GetInputMuteAsync( + new(inputName: inputName), + cancellationToken + ) + .ConfigureAwait(false); + + // One real input beside one that does not exist, so the returned + // results have to show a success next to a failure. + BatchResults muteResults = await client + .Inputs.SetInputMutesAsync( + [(inputName, !before.InputMuted), ("__absent__", true)], + cancellationToken + ) + .ConfigureAwait(false); + + GetInputMuteResponseData after = await client + .Inputs.GetInputMuteAsync( + new(inputName: inputName), + cancellationToken + ) + .ConfigureAwait(false); + + await client + .Inputs.SetInputMutesAsync( + [(inputName, before.InputMuted)], + cancellationToken + ) + .ConfigureAwait(false); + + bool ok = + muteResults.Count == 2 + && muteResults[0].RequestStatus.Result + && !muteResults[1].RequestStatus.Result + && after.InputMuted == !before.InputMuted; + + return ( + ok, + $"{muteResults.Count} results, muted {before.InputMuted} -> {after.InputMuted}" + ); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Transition settings read", + async () => + { + GetCurrentSceneTransitionResponseData current = await client + .Transitions.GetCurrentSceneTransitionAsync(cancellationToken) + .ConfigureAwait(false); + // The typed Get*SettingsAsync helpers deserialize into a settings + // record; the generated request is the way to read the raw JSON. + // A transition with nothing to configure, such as Fade, legitimately + // reports no settings, so the name and kind are what is asserted. + JsonElement? settings = current.TransitionSettings; + + return ( + !string.IsNullOrEmpty(current.TransitionName) + && !string.IsNullOrEmpty(current.TransitionKind), + $"{current.TransitionName} ({current.TransitionKind}), " + + $"settings {(settings is null ? "none" : "present")}" + ); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Canvas screenshot helper", + async () => + { + byte[]? bytes = await client + .Sources.GetSourceScreenshotOnCanvasBytesAsync( + sceneName, + "png", + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + + bool png = + bytes is { Length: > 8 } + && bytes[0] == 0x89 + && bytes[1] == 0x50 + && bytes[2] == 0x4E + && bytes[3] == 0x47; + + return (png, $"{bytes?.Length ?? 0} bytes at canvas size"); + } + ) + .ConfigureAwait(false) + ); + results.Add( await TrySettingsCheckAsync( "Typed exception on a rejected request", @@ -3365,9 +3573,7 @@ private void OnInputCreated(object? sender, InputCreatedEventArgs e) => private void OnStreamStateChanged(object? sender, StreamStateChangedEventArgs e) { - // The wire value is a string; OutputStateExtensions.FromWireValue turns it into the - // typed enum so it can be matched instead of compared against protocol constants. - string description = OutputStateExtensions.FromWireValue(e.EventData.OutputState) switch + string description = e.EventData.OutputState switch { OutputState.Starting => "starting up", OutputState.Started => "live", @@ -3377,7 +3583,6 @@ private void OnStreamStateChanged(object? sender, StreamStateChangedEventArgs e) OutputState.Reconnected => "reconnected", OutputState.Paused => "paused", OutputState.Unknown => "in an unknown state", - null => $"reporting an unrecognised state ({e.EventData.OutputState})", _ => "in an unhandled state", }; diff --git a/ObsWebSocket.Tests/ReadmeCompileCheck.cs b/ObsWebSocket.Tests/ReadmeCompileCheck.cs index 5906d2d..2490aac 100644 --- a/ObsWebSocket.Tests/ReadmeCompileCheck.cs +++ b/ObsWebSocket.Tests/ReadmeCompileCheck.cs @@ -210,12 +210,12 @@ internal static void TypedEnums(ObsWebSocketClient client) { client.StreamStateChanged += (_, e) => { - string what = OutputStateExtensions.FromWireValue(e.EventData.OutputState) switch + string what = e.EventData.OutputState switch { OutputState.Started => "live", OutputState.Starting or OutputState.Reconnecting => "coming up", OutputState.Stopped or OutputState.Stopping => "going down", - null => $"unrecognised ({e.EventData.OutputState})", + OutputState.Unknown => "in an unrecognised state", _ => "in between", }; @@ -307,11 +307,31 @@ await client.SceneItems.SetSceneItemIndexAsync( _ = $"{bytes} {volume}"; } + internal static async Task DroppingToTheWireAsync( + ObsWebSocketClient client, + CancellationToken ct + ) + { + string wire = MediaInputAction.Restart.ToWireValue(); + + System.Text.Json.JsonElement? raw = + await client.CallAsyncValue( + "SomeNewRequest", + new { someField = 1 }, + cancellationToken: ct + ); + _ = $"{wire} {raw}"; + } + internal static void HostIntegration( Microsoft.Extensions.Hosting.IHostApplicationBuilder builder ) { - _ = builder.AddObsWebSocketClient("obs").WithAutoConnect().WithHealthCheck(); + _ = builder + .AddObsWebSocketClient("obs") + .WithAutoConnect() + .WithHealthCheck() + .WithReconnectPipeline(); } internal static void TelemetryAndKeyedRegistration(IServiceCollection services) diff --git a/ObsWebSocket.Tests/WireEnumTests.cs b/ObsWebSocket.Tests/WireEnumTests.cs new file mode 100644 index 0000000..31b0bee --- /dev/null +++ b/ObsWebSocket.Tests/WireEnumTests.cs @@ -0,0 +1,98 @@ +using System.Text.Json; +using MessagePack; +using ObsWebSocket.Core.Protocol.Events; +using ObsWebSocket.Core.Protocol.Generated; +using ObsWebSocket.Core.Protocol.Requests; +using ObsWebSocket.Core.Serialization; + +namespace ObsWebSocket.Tests; + +/// +/// Protocol enums that travel as strings are generated as C# enums, so the value on the wire has +/// to stay the protocol string on both transports. MessagePack would otherwise write the ordinal, +/// which OBS rejects, and which no round trip through this library alone would notice. +/// +[TestClass] +public sealed class WireEnumTests +{ + [TestMethod] + public void Json_WritesTheProtocolStringNotTheMemberName() + { + TriggerMediaInputActionRequestData request = new( + mediaAction: MediaInputAction.Stop, + inputName: "Stinger" + ); + + string json = JsonSerializer.Serialize( + request, + ObsWebSocketJsonContext.Default.TriggerMediaInputActionRequestData + ); + + StringAssert.Contains(json, "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP"); + Assert.IsFalse(json.Contains("\"Stop\"", StringComparison.Ordinal), "member name leaked"); + } + + [TestMethod] + public void MsgPack_WritesTheProtocolStringNotTheOrdinal() + { + TriggerMediaInputActionRequestData request = new( + mediaAction: MediaInputAction.Stop, + inputName: "Stinger" + ); + + byte[] bytes = MessagePackSerializer.Serialize( + request, + MsgPackMessageSerializer.s_msgPackOptions + ); + + // Converting to JSON shows what actually landed in the buffer. + string asJson = MessagePackSerializer.ConvertToJson( + bytes, + MsgPackMessageSerializer.s_msgPackOptions + ); + + StringAssert.Contains(asJson, "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP"); + } + + [TestMethod] + public void MsgPack_ReadsTheProtocolStringBackIntoTheEnum() + { + StreamStateChangedPayload payload = new( + outputActive: true, + outputState: OutputState.Reconnecting + ); + + byte[] bytes = MessagePackSerializer.Serialize( + payload, + MsgPackMessageSerializer.s_msgPackOptions + ); + StringAssert.Contains( + MessagePackSerializer.ConvertToJson(bytes, MsgPackMessageSerializer.s_msgPackOptions), + "OBS_WEBSOCKET_OUTPUT_RECONNECTING" + ); + + StreamStateChangedPayload read = + MessagePackSerializer.Deserialize( + bytes, + MsgPackMessageSerializer.s_msgPackOptions + ); + + Assert.AreEqual(OutputState.Reconnecting, read.OutputState); + } + + [TestMethod] + public void UnrecognisedWireValue_FallsBackToTheZeroMemberRatherThanThrowing() + { + // A state added by a newer OBS must not fail the whole message. + string json = """{"outputActive":true,"outputState":"OBS_WEBSOCKET_OUTPUT_FUTURE_STATE"}"""; + + StreamStateChangedPayload? read = JsonSerializer.Deserialize( + json, + ObsWebSocketJsonContext.Default.StreamStateChangedPayload + ); + + Assert.IsNotNull(read); + Assert.AreEqual(OutputState.Unknown, read.OutputState); + Assert.IsTrue(read.OutputActive); + } +} diff --git a/README.md b/README.md index 964fc22..6010885 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,75 @@ protocol definition, so a refresh that recategorises a request moves it here too `WaitForEventAsync` and `CallBatchAsync` stay directly on the client, since neither belongs to one category. +## The helper set + +Alongside the generated request per protocol request, each group carries conveniences for things +that otherwise take several calls or a lookup. Every typed settings helper has two overloads: an +implicit one for library-registered types, and an explicit one taking a `JsonTypeInfo` for +consumer-provided types. Use the explicit overload to stay AOT-safe. + +**Settings read and write** + +| Helper | Notes | +|---|---| +| `Inputs.GetInputSettingsAsync` / `SetInputSettingsAsync` | Input settings; Set supports `overlay` | +| `Inputs.GetInputDefaultSettingsAsync` | Defaults for a given input kind | +| `Filters.GetSourceFilterSettingsAsync` / `SetSourceFilterSettingsAsync` | Filter settings; Set supports `overlay` | +| `Filters.GetSourceFilterDefaultSettingsAsync` | Defaults for a given filter kind | +| `Transitions.GetCurrentSceneTransitionSettingsAsync` / `SetCurrentSceneTransitionSettingsAsync` | Transition settings | +| `Outputs.GetOutputSettingsAsync` / `SetOutputSettingsAsync` | Output settings | +| `Config.GetStreamServiceSettingsAsync` / `SetStreamServiceSettingsAsync` | Stream service settings | + +Most take optional parameters ahead of the cancellation token, so pass it as `cancellationToken: ct`. + +**Scenes and scene items** + +- `Scenes.SwitchProgramSceneAsync(scene, ct)` and `Scenes.SwitchPreviewSceneAsync(scene, ct)` switch + a scene. Optional `transitionName` and `transitionDurationMs` apply to that switch only. +- `Scenes.SwitchProgramSceneAndWaitAsync` and `Scenes.SwitchPreviewSceneAndWaitAsync` do the same + and wait for the event confirming it. +- `SceneItems.SetSceneItemEnabledAsync(scene, sourceName, isEnabled, ct)` returns the resulting + state. Leave `isEnabled` null to toggle. An overload takes the numeric item id instead. +- `SceneItems.FindSceneItemIdAsync(scene, sourceName, ct)` returns `int?`, null rather than throwing + when the item is not in the scene. +- `Sources.SourceExistsAsync(name, ct)` and `Scenes.SceneExistsAsync(name, ct)` check existence. + +**Inputs and filters** + +- `Inputs.SetInputTextAsync(name, text, ct)` is shorthand for updating text source content. +- `Inputs.SetInputVolumeDbAsync(name, db, ct)` and `Inputs.SetInputVolumeMulAsync(name, mul, ct)` + each pick one unit. The underlying request accepts either and fails when given neither. +- `Inputs.SetInputMutesAsync(inputMutes, ct)` sets many mute states in one batch and returns the + results, so a caller sees which inputs OBS rejected. +- `Inputs.CreateInputAsync(kind, name, settings, ...)` creates an input with typed settings. +- `Filters.CreateSourceFilterAsync(source, filterName, kind, settings, ct)` adds a typed filter. + +**Media** + +- `MediaInputs.PlayMediaAsync`, `PauseMediaAsync`, `StopMediaAsync` and `RestartMediaAsync` are + shorthands over `TriggerMediaActionAsync(name, MediaInputAction, ct)`. + +**Screenshots** + +- `Sources.GetSourceScreenshotBytesAsync(source, ...)` returns decoded image bytes. +- `Sources.GetSourceScreenshotOnCanvasBytesAsync(source, ...)` does the same at canvas dimensions. +- `Sources.SaveSourceScreenshotToFileAsync(source, filePath, ...)` writes straight to disk. + +**Outputs** + +- `Record.SetRecordActiveAndWaitAsync(activate, timeout, ct)`, + `Stream.SetStreamActiveAndWaitAsync(...)` and `Outputs.SetVirtualCamActiveAndWaitAsync(...)` start + or stop the output and wait for OBS to confirm, returning the resulting `OutputState`. +- `Record.IsRecordActiveAsync(ct)`, `Stream.IsStreamActiveAsync(ct)` and + `Outputs.IsVirtualCamActiveAsync(ct)` read current state. + +**Application state** + +- `Config.EnsureProfileActiveAsync(name, ct)` and `Config.EnsureSceneCollectionActiveAsync(name, ct)` + switch only if needed, returning whether the target is active rather than throwing when it does + not exist. +- `General.TriggerHotkeyAsync(hotkeyName, ct)` fires a hotkey by name. + ## Observing events Every OBS event is exposed as an async sequence on its category group. The stream subscribes for the @@ -250,54 +319,64 @@ batch.Add("SetInputSettings", myJsonElement); > so it cannot be corrected here; references refuse to resolve on such a batch and say why. See > [#16](https://github.com/Agash/ObsWebSocket/issues/16). -## Typed protocol enums +## Protocol types + +The protocol definition is looser than C#: it has one numeric type because JSON does, and it types +enum-valued fields as plain strings. The generated surface narrows both, so callers get the C# type +rather than the wire representation. -Protocol enums that travel as strings have a real C# enum, so states can be matched rather than -compared against constants: +**Numbers.** A scene item id and a volume multiplier are both `Number` with a `>= 0` restriction, so +which ones are integral is not recoverable from the definition. Fields holding whole numbers are +generated as `int` or `long`, from an explicit list in the generator rather than a rule over field +names, so a volume can never be truncated by a naming coincidence: + +```csharp +int id = await client.SceneItems.FindSceneItemIdAsync("Intro", "Logo", ct) ?? throw new(...); +await client.SceneItems.SetSceneItemIndexAsync(new(sceneItemId: id, sceneItemIndex: 0, sceneName: "Intro"), ct); + +long bytes = (await client.Stream.GetStreamStatusAsync(ct)).OutputBytes; +double volume = (await client.Inputs.GetInputVolumeAsync(new("Mic"), ct)).InputVolumeMul; +``` + +**Enums.** Fields carrying a protocol enum are that enum, on both the read and the write side, so +there is nothing to convert at the call site: ```csharp client.StreamStateChanged += (_, e) => { - string what = OutputStateExtensions.FromWireValue(e.EventData.OutputState) switch + string what = e.EventData.OutputState switch { OutputState.Started => "live", OutputState.Starting or OutputState.Reconnecting => "coming up", OutputState.Stopped or OutputState.Stopping => "going down", - null => $"unrecognised ({e.EventData.OutputState})", + OutputState.Unknown => "in a state this build does not recognise", _ => "in between", }; - - Console.WriteLine($"Stream is {what}"); }; -``` -Media transport works the same way, with shorthands for the common actions: - -```csharp -await client.MediaInputs.PlayMediaAsync("Stinger", ct); await client.MediaInputs.TriggerMediaActionAsync("Stinger", MediaInputAction.Restart, ct); ``` -The wire constants remain available as `const` strings on `ObsOutputState` and `ObsMediaInputAction`, -and `ToWireValue()` converts an enum back. +A value OBS sends that this build does not know maps to the enum's zero member rather than throwing, +so a state added by a newer OBS does not fail the whole message. -## Numbers +This covers the enums the protocol declares. `mediaState`, `monitorType`, `sceneItemBlendMode` and +`inputKind` carry fixed vocabularies too, but the protocol types them as strings and never lists +their values, so they stay strings rather than being given an enum this library would have to keep +correct by hand. -The protocol has a single `Number` type because JSON does, so a scene item id and a volume -multiplier look identical in the definition. Fields that hold whole numbers are generated as `int` -or `long` rather than `double`: +**Dropping to the wire.** Nothing above is a wall. `ToWireValue()` and `FromWireValue()` convert +either way, the wire values remain as `const` strings, and `CallAsync` sends a request the generated +surface does not model at all: ```csharp -int id = await client.SceneItems.FindSceneItemIdAsync("Intro", "Logo", ct) ?? throw new(...); -await client.SceneItems.SetSceneItemIndexAsync(new(sceneItemId: id, sceneItemIndex: 0, sceneName: "Intro"), ct); +string wire = MediaInputAction.Restart.ToWireValue(); // OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART -long bytes = (await client.Stream.GetStreamStatusAsync(ct)).OutputBytes; -double volume = (await client.Inputs.GetInputVolumeAsync(new("Mic"), ct)).InputVolumeMul; +// CallAsync for a reference type response, CallAsyncValue for a value type such as JsonElement. +JsonElement? raw = await client.CallAsyncValue( + "SomeNewRequest", new { someField = 1 }, cancellationToken: ct); ``` -Which fields those are is an explicit list in the generator, not a rule over field names, so a -volume can never be silently truncated by a naming coincidence. - ## Host integration ```csharp @@ -391,6 +470,15 @@ Reconnect delays grow by `ReconnectBackoffMultiplier`, are capped at `MaxReconne jitter so several clients recovering from one outage do not retry in lockstep. Authentication failures are never retried, since they cannot succeed on a second attempt. +`WithReconnectPipeline()` registers the default pipeline explicitly, which is worth doing when a +host has its own resilience configuration and you want this client's to be visible alongside it: + +```csharp +builder.AddObsWebSocketClient("obs") + .WithAutoConnect() + .WithReconnectPipeline(); +``` + To replace the policy outright rather than tune those options, register your own pipeline under `ObsWebSocketResilience.ReconnectPipelineKey` after adding the client. From 85f93104bbf55d2e111214e01e7607923ec1c739 Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 26 Aug 2026 12:39:48 +0200 Subject: [PATCH 02/13] feat(core): put the classic event handlers on their category group Moving the streams onto the groups left the events themselves flat, so half of observing an event was grouped and half was not: client.Scenes.SceneCreatedStream() beside client.SceneCreated. The handler is what most callers actually write, so the inconsistency was in the more used half. Events cannot go in a C# extension block, which is what the earlier reasoning stopped at. The groups are ordinary structs, and a struct can declare an event whose explicit accessors forward to the client. That detail matters: the property hands out a fresh struct on every access, so a field-like event would add the handler to a temporary and lose it. Tested for removal as well as addition, since a remove that quietly did nothing would leak every handler. The client keeps its events for the low-level path, and connection lifecycle events stay there because they belong to no category. --- .../Generation/Emitter.EventStreams.cs | 47 +- .../ObsWebSocketClient.EventStreams.g.cs | 660 ++++++++++++++++++ ObsWebSocket.Example/Worker.cs | 28 +- ObsWebSocket.Tests/GroupEventTests.cs | 76 ++ ObsWebSocket.Tests/ReadmeCompileCheck.cs | 4 +- README.md | 14 +- 6 files changed, 802 insertions(+), 27 deletions(-) create mode 100644 ObsWebSocket.Tests/GroupEventTests.cs diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.EventStreams.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.EventStreams.cs index b48f3a6..c000cf3 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.EventStreams.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.EventStreams.cs @@ -94,15 +94,48 @@ IGrouping group in protocol string eventArgsTypeName = $"{GeneratedEventArgsNamespace}.{eventName}EventArgs"; + string subscriptionRemark = string.IsNullOrWhiteSpace( + eventDef.EventSubscription + ) + ? string.Empty + : $" /// Requires the {System.Security.SecurityElement.Escape(eventDef.EventSubscription)} subscription."; + + string descriptionLine = string.IsNullOrWhiteSpace(eventDef.Description) + ? string.Empty + : $" /// {FlattenDescription(eventDef.Description)}"; + + // The classic handler, on the group rather than only on the client. Explicit + // accessors are what make this work: the group is a struct the property hands + // out fresh, so a field-like event would add to a temporary and lose it. + builder.AppendLine(" /// "); + builder.AppendLine($" /// Occurs when OBS raises {eventName}."); + if (descriptionLine.Length > 0) + { + builder.AppendLine(descriptionLine); + } + + builder.AppendLine(" /// "); + if (subscriptionRemark.Length > 0) + { + builder.AppendLine(subscriptionRemark); + } + + builder.AppendLine( + $" public event EventHandler<{eventArgsTypeName}>? {eventName}" + ); + builder.AppendLine(" {"); + builder.AppendLine($" add => client.{eventName} += value;"); + builder.AppendLine($" remove => client.{eventName} -= value;"); + builder.AppendLine(" }"); + builder.AppendLine(); + builder.AppendLine(" /// "); builder.AppendLine( $" /// Streams {eventName} events as they arrive." ); - if (!string.IsNullOrWhiteSpace(eventDef.Description)) + if (descriptionLine.Length > 0) { - builder.AppendLine( - $" /// {FlattenDescription(eventDef.Description)}" - ); + builder.AppendLine(descriptionLine); } builder.AppendLine(" /// "); @@ -112,11 +145,9 @@ IGrouping group in protocol builder.AppendLine( " /// Ends the enumeration and unsubscribes." ); - if (!string.IsNullOrWhiteSpace(eventDef.EventSubscription)) + if (subscriptionRemark.Length > 0) { - builder.AppendLine( - $" /// Requires the {System.Security.SecurityElement.Escape(eventDef.EventSubscription)} subscription." - ); + builder.AppendLine(subscriptionRemark); } builder.AppendLine( diff --git a/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.EventStreams.g.cs b/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.EventStreams.g.cs index ace3b09..89894d1 100644 --- a/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.EventStreams.g.cs +++ b/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.EventStreams.g.cs @@ -18,6 +18,17 @@ namespace ObsWebSocket.Core; /// public readonly partial struct CanvasesGroup { + /// + /// Occurs when OBS raises CanvasCreated. + /// A new canvas has been created. + /// + /// Requires the Canvases subscription. + public event EventHandler? CanvasCreated + { + add => client.CanvasCreated += value; + remove => client.CanvasCreated -= value; + } + /// /// Streams CanvasCreated events as they arrive. /// A new canvas has been created. @@ -37,6 +48,17 @@ public readonly partial struct CanvasesGroup cancellationToken); } + /// + /// Occurs when OBS raises CanvasRemoved. + /// A canvas has been removed. + /// + /// Requires the Canvases subscription. + public event EventHandler? CanvasRemoved + { + add => client.CanvasRemoved += value; + remove => client.CanvasRemoved -= value; + } + /// /// Streams CanvasRemoved events as they arrive. /// A canvas has been removed. @@ -56,6 +78,17 @@ public readonly partial struct CanvasesGroup cancellationToken); } + /// + /// Occurs when OBS raises CanvasNameChanged. + /// The name of a canvas has changed. + /// + /// Requires the Canvases subscription. + public event EventHandler? CanvasNameChanged + { + add => client.CanvasNameChanged += value; + remove => client.CanvasNameChanged -= value; + } + /// /// Streams CanvasNameChanged events as they arrive. /// The name of a canvas has changed. @@ -84,6 +117,17 @@ public readonly partial struct CanvasesGroup /// public readonly partial struct ConfigGroup { + /// + /// Occurs when OBS raises CurrentSceneCollectionChanging. + /// The current scene collection has begun changing. Note: We recommend using this event to trigger a pause of all polling requests, as performing any requests during a scene collection change is considered undefined behavior and can cause crashes! + /// + /// Requires the Config subscription. + public event EventHandler? CurrentSceneCollectionChanging + { + add => client.CurrentSceneCollectionChanging += value; + remove => client.CurrentSceneCollectionChanging -= value; + } + /// /// Streams CurrentSceneCollectionChanging events as they arrive. /// The current scene collection has begun changing. Note: We recommend using this event to trigger a pause of all polling requests, as performing any requests during a scene collection change is considered undefined behavior and can cause crashes! @@ -103,6 +147,17 @@ public readonly partial struct ConfigGroup cancellationToken); } + /// + /// Occurs when OBS raises CurrentSceneCollectionChanged. + /// The current scene collection has changed. Note: If polling has been paused during `CurrentSceneCollectionChanging`, this is the que to restart polling. + /// + /// Requires the Config subscription. + public event EventHandler? CurrentSceneCollectionChanged + { + add => client.CurrentSceneCollectionChanged += value; + remove => client.CurrentSceneCollectionChanged -= value; + } + /// /// Streams CurrentSceneCollectionChanged events as they arrive. /// The current scene collection has changed. Note: If polling has been paused during `CurrentSceneCollectionChanging`, this is the que to restart polling. @@ -122,6 +177,17 @@ public readonly partial struct ConfigGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneCollectionListChanged. + /// The scene collection list has changed. + /// + /// Requires the Config subscription. + public event EventHandler? SceneCollectionListChanged + { + add => client.SceneCollectionListChanged += value; + remove => client.SceneCollectionListChanged -= value; + } + /// /// Streams SceneCollectionListChanged events as they arrive. /// The scene collection list has changed. @@ -141,6 +207,17 @@ public readonly partial struct ConfigGroup cancellationToken); } + /// + /// Occurs when OBS raises CurrentProfileChanging. + /// The current profile has begun changing. + /// + /// Requires the Config subscription. + public event EventHandler? CurrentProfileChanging + { + add => client.CurrentProfileChanging += value; + remove => client.CurrentProfileChanging -= value; + } + /// /// Streams CurrentProfileChanging events as they arrive. /// The current profile has begun changing. @@ -160,6 +237,17 @@ public readonly partial struct ConfigGroup cancellationToken); } + /// + /// Occurs when OBS raises CurrentProfileChanged. + /// The current profile has changed. + /// + /// Requires the Config subscription. + public event EventHandler? CurrentProfileChanged + { + add => client.CurrentProfileChanged += value; + remove => client.CurrentProfileChanged -= value; + } + /// /// Streams CurrentProfileChanged events as they arrive. /// The current profile has changed. @@ -179,6 +267,17 @@ public readonly partial struct ConfigGroup cancellationToken); } + /// + /// Occurs when OBS raises ProfileListChanged. + /// The profile list has changed. + /// + /// Requires the Config subscription. + public event EventHandler? ProfileListChanged + { + add => client.ProfileListChanged += value; + remove => client.ProfileListChanged -= value; + } + /// /// Streams ProfileListChanged events as they arrive. /// The profile list has changed. @@ -207,6 +306,17 @@ public readonly partial struct ConfigGroup /// public readonly partial struct FiltersGroup { + /// + /// Occurs when OBS raises SourceFilterListReindexed. + /// A source's filter list has been reindexed. + /// + /// Requires the Filters subscription. + public event EventHandler? SourceFilterListReindexed + { + add => client.SourceFilterListReindexed += value; + remove => client.SourceFilterListReindexed -= value; + } + /// /// Streams SourceFilterListReindexed events as they arrive. /// A source's filter list has been reindexed. @@ -226,6 +336,17 @@ public readonly partial struct FiltersGroup cancellationToken); } + /// + /// Occurs when OBS raises SourceFilterCreated. + /// A filter has been added to a source. + /// + /// Requires the Filters subscription. + public event EventHandler? SourceFilterCreated + { + add => client.SourceFilterCreated += value; + remove => client.SourceFilterCreated -= value; + } + /// /// Streams SourceFilterCreated events as they arrive. /// A filter has been added to a source. @@ -245,6 +366,17 @@ public readonly partial struct FiltersGroup cancellationToken); } + /// + /// Occurs when OBS raises SourceFilterRemoved. + /// A filter has been removed from a source. + /// + /// Requires the Filters subscription. + public event EventHandler? SourceFilterRemoved + { + add => client.SourceFilterRemoved += value; + remove => client.SourceFilterRemoved -= value; + } + /// /// Streams SourceFilterRemoved events as they arrive. /// A filter has been removed from a source. @@ -264,6 +396,17 @@ public readonly partial struct FiltersGroup cancellationToken); } + /// + /// Occurs when OBS raises SourceFilterNameChanged. + /// The name of a source filter has changed. + /// + /// Requires the Filters subscription. + public event EventHandler? SourceFilterNameChanged + { + add => client.SourceFilterNameChanged += value; + remove => client.SourceFilterNameChanged -= value; + } + /// /// Streams SourceFilterNameChanged events as they arrive. /// The name of a source filter has changed. @@ -283,6 +426,17 @@ public readonly partial struct FiltersGroup cancellationToken); } + /// + /// Occurs when OBS raises SourceFilterSettingsChanged. + /// An source filter's settings have changed (been updated). + /// + /// Requires the Filters subscription. + public event EventHandler? SourceFilterSettingsChanged + { + add => client.SourceFilterSettingsChanged += value; + remove => client.SourceFilterSettingsChanged -= value; + } + /// /// Streams SourceFilterSettingsChanged events as they arrive. /// An source filter's settings have changed (been updated). @@ -302,6 +456,17 @@ public readonly partial struct FiltersGroup cancellationToken); } + /// + /// Occurs when OBS raises SourceFilterEnableStateChanged. + /// A source filter's enable state has changed. + /// + /// Requires the Filters subscription. + public event EventHandler? SourceFilterEnableStateChanged + { + add => client.SourceFilterEnableStateChanged += value; + remove => client.SourceFilterEnableStateChanged -= value; + } + /// /// Streams SourceFilterEnableStateChanged events as they arrive. /// A source filter's enable state has changed. @@ -330,6 +495,17 @@ public readonly partial struct FiltersGroup /// public readonly partial struct GeneralGroup { + /// + /// Occurs when OBS raises ExitStarted. + /// OBS has begun the shutdown process. + /// + /// Requires the General subscription. + public event EventHandler? ExitStarted + { + add => client.ExitStarted += value; + remove => client.ExitStarted -= value; + } + /// /// Streams ExitStarted events as they arrive. /// OBS has begun the shutdown process. @@ -349,6 +525,17 @@ public readonly partial struct GeneralGroup cancellationToken); } + /// + /// Occurs when OBS raises VendorEvent. + /// An event has been emitted from a vendor. A vendor is a unique name registered by a third-party plugin or script, which allows for custom requests and events to be added to obs-websocket. If a plugin or script implements vendor requests or events, documentation is expected to be provided with them. + /// + /// Requires the Vendors subscription. + public event EventHandler? VendorEvent + { + add => client.VendorEvent += value; + remove => client.VendorEvent -= value; + } + /// /// Streams VendorEvent events as they arrive. /// An event has been emitted from a vendor. A vendor is a unique name registered by a third-party plugin or script, which allows for custom requests and events to be added to obs-websocket. If a plugin or script implements vendor requests or events, documentation is expected to be provided with them. @@ -368,6 +555,17 @@ public readonly partial struct GeneralGroup cancellationToken); } + /// + /// Occurs when OBS raises CustomEvent. + /// Custom event emitted by `BroadcastCustomEvent`. + /// + /// Requires the General subscription. + public event EventHandler? CustomEvent + { + add => client.CustomEvent += value; + remove => client.CustomEvent -= value; + } + /// /// Streams CustomEvent events as they arrive. /// Custom event emitted by `BroadcastCustomEvent`. @@ -396,6 +594,17 @@ public readonly partial struct GeneralGroup /// public readonly partial struct InputsGroup { + /// + /// Occurs when OBS raises InputCreated. + /// An input has been created. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputCreated + { + add => client.InputCreated += value; + remove => client.InputCreated -= value; + } + /// /// Streams InputCreated events as they arrive. /// An input has been created. @@ -415,6 +624,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputRemoved. + /// An input has been removed. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputRemoved + { + add => client.InputRemoved += value; + remove => client.InputRemoved -= value; + } + /// /// Streams InputRemoved events as they arrive. /// An input has been removed. @@ -434,6 +654,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputNameChanged. + /// The name of an input has changed. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputNameChanged + { + add => client.InputNameChanged += value; + remove => client.InputNameChanged -= value; + } + /// /// Streams InputNameChanged events as they arrive. /// The name of an input has changed. @@ -453,6 +684,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputSettingsChanged. + /// 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. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputSettingsChanged + { + add => client.InputSettingsChanged += value; + remove => client.InputSettingsChanged -= value; + } + /// /// Streams InputSettingsChanged events as they arrive. /// 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. @@ -472,6 +714,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputActiveStateChanged. + /// An input's active state has changed. When an input is active, it means it's being shown by the program feed. + /// + /// Requires the InputActiveStateChanged subscription. + public event EventHandler? InputActiveStateChanged + { + add => client.InputActiveStateChanged += value; + remove => client.InputActiveStateChanged -= value; + } + /// /// Streams InputActiveStateChanged events as they arrive. /// An input's active state has changed. When an input is active, it means it's being shown by the program feed. @@ -491,6 +744,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputShowStateChanged. + /// An input's show state has changed. When an input is showing, it means it's being shown by the preview or a dialog. + /// + /// Requires the InputShowStateChanged subscription. + public event EventHandler? InputShowStateChanged + { + add => client.InputShowStateChanged += value; + remove => client.InputShowStateChanged -= value; + } + /// /// Streams InputShowStateChanged events as they arrive. /// An input's show state has changed. When an input is showing, it means it's being shown by the preview or a dialog. @@ -510,6 +774,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputMuteStateChanged. + /// An input's mute state has changed. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputMuteStateChanged + { + add => client.InputMuteStateChanged += value; + remove => client.InputMuteStateChanged -= value; + } + /// /// Streams InputMuteStateChanged events as they arrive. /// An input's mute state has changed. @@ -529,6 +804,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputVolumeChanged. + /// An input's volume level has changed. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputVolumeChanged + { + add => client.InputVolumeChanged += value; + remove => client.InputVolumeChanged -= value; + } + /// /// Streams InputVolumeChanged events as they arrive. /// An input's volume level has changed. @@ -548,6 +834,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputAudioBalanceChanged. + /// The audio balance value of an input has changed. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputAudioBalanceChanged + { + add => client.InputAudioBalanceChanged += value; + remove => client.InputAudioBalanceChanged -= value; + } + /// /// Streams InputAudioBalanceChanged events as they arrive. /// The audio balance value of an input has changed. @@ -567,6 +864,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputAudioSyncOffsetChanged. + /// The sync offset of an input has changed. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputAudioSyncOffsetChanged + { + add => client.InputAudioSyncOffsetChanged += value; + remove => client.InputAudioSyncOffsetChanged -= value; + } + /// /// Streams InputAudioSyncOffsetChanged events as they arrive. /// The sync offset of an input has changed. @@ -586,6 +894,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputAudioTracksChanged. + /// The audio tracks of an input have changed. + /// + /// Requires the Inputs subscription. + public event EventHandler? InputAudioTracksChanged + { + add => client.InputAudioTracksChanged += value; + remove => client.InputAudioTracksChanged -= value; + } + /// /// Streams InputAudioTracksChanged events as they arrive. /// The audio tracks of an input have changed. @@ -605,6 +924,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputAudioMonitorTypeChanged. + /// The monitor type of an input has changed. Available types are: - `OBS_MONITORING_TYPE_NONE` - `OBS_MONITORING_TYPE_MONITOR_ONLY` - `OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT` + /// + /// Requires the Inputs subscription. + public event EventHandler? InputAudioMonitorTypeChanged + { + add => client.InputAudioMonitorTypeChanged += value; + remove => client.InputAudioMonitorTypeChanged -= value; + } + /// /// Streams InputAudioMonitorTypeChanged events as they arrive. /// The monitor type of an input has changed. Available types are: - `OBS_MONITORING_TYPE_NONE` - `OBS_MONITORING_TYPE_MONITOR_ONLY` - `OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT` @@ -624,6 +954,17 @@ public readonly partial struct InputsGroup cancellationToken); } + /// + /// Occurs when OBS raises InputVolumeMeters. + /// A high-volume event providing volume levels of all active inputs every 50 milliseconds. + /// + /// Requires the InputVolumeMeters subscription. + public event EventHandler? InputVolumeMeters + { + add => client.InputVolumeMeters += value; + remove => client.InputVolumeMeters -= value; + } + /// /// Streams InputVolumeMeters events as they arrive. /// A high-volume event providing volume levels of all active inputs every 50 milliseconds. @@ -652,6 +993,17 @@ public readonly partial struct InputsGroup /// public readonly partial struct MediaInputsGroup { + /// + /// Occurs when OBS raises MediaInputPlaybackStarted. + /// A media input has started playing. + /// + /// Requires the MediaInputs subscription. + public event EventHandler? MediaInputPlaybackStarted + { + add => client.MediaInputPlaybackStarted += value; + remove => client.MediaInputPlaybackStarted -= value; + } + /// /// Streams MediaInputPlaybackStarted events as they arrive. /// A media input has started playing. @@ -671,6 +1023,17 @@ public readonly partial struct MediaInputsGroup cancellationToken); } + /// + /// Occurs when OBS raises MediaInputPlaybackEnded. + /// A media input has finished playing. + /// + /// Requires the MediaInputs subscription. + public event EventHandler? MediaInputPlaybackEnded + { + add => client.MediaInputPlaybackEnded += value; + remove => client.MediaInputPlaybackEnded -= value; + } + /// /// Streams MediaInputPlaybackEnded events as they arrive. /// A media input has finished playing. @@ -690,6 +1053,17 @@ public readonly partial struct MediaInputsGroup cancellationToken); } + /// + /// Occurs when OBS raises MediaInputActionTriggered. + /// An action has been performed on an input. + /// + /// Requires the MediaInputs subscription. + public event EventHandler? MediaInputActionTriggered + { + add => client.MediaInputActionTriggered += value; + remove => client.MediaInputActionTriggered -= value; + } + /// /// Streams MediaInputActionTriggered events as they arrive. /// An action has been performed on an input. @@ -718,6 +1092,17 @@ public readonly partial struct MediaInputsGroup /// public readonly partial struct OutputsGroup { + /// + /// Occurs when OBS raises StreamStateChanged. + /// The state of the stream output has changed. + /// + /// Requires the Outputs subscription. + public event EventHandler? StreamStateChanged + { + add => client.StreamStateChanged += value; + remove => client.StreamStateChanged -= value; + } + /// /// Streams StreamStateChanged events as they arrive. /// The state of the stream output has changed. @@ -737,6 +1122,17 @@ public readonly partial struct OutputsGroup cancellationToken); } + /// + /// Occurs when OBS raises RecordStateChanged. + /// The state of the record output has changed. + /// + /// Requires the Outputs subscription. + public event EventHandler? RecordStateChanged + { + add => client.RecordStateChanged += value; + remove => client.RecordStateChanged -= value; + } + /// /// Streams RecordStateChanged events as they arrive. /// The state of the record output has changed. @@ -756,6 +1152,17 @@ public readonly partial struct OutputsGroup cancellationToken); } + /// + /// Occurs when OBS raises RecordFileChanged. + /// The record output has started writing to a new file. For example, when a file split happens. + /// + /// Requires the Outputs subscription. + public event EventHandler? RecordFileChanged + { + add => client.RecordFileChanged += value; + remove => client.RecordFileChanged -= value; + } + /// /// Streams RecordFileChanged events as they arrive. /// The record output has started writing to a new file. For example, when a file split happens. @@ -775,6 +1182,17 @@ public readonly partial struct OutputsGroup cancellationToken); } + /// + /// Occurs when OBS raises ReplayBufferStateChanged. + /// The state of the replay buffer output has changed. + /// + /// Requires the Outputs subscription. + public event EventHandler? ReplayBufferStateChanged + { + add => client.ReplayBufferStateChanged += value; + remove => client.ReplayBufferStateChanged -= value; + } + /// /// Streams ReplayBufferStateChanged events as they arrive. /// The state of the replay buffer output has changed. @@ -794,6 +1212,17 @@ public readonly partial struct OutputsGroup cancellationToken); } + /// + /// Occurs when OBS raises VirtualcamStateChanged. + /// The state of the virtualcam output has changed. + /// + /// Requires the Outputs subscription. + public event EventHandler? VirtualcamStateChanged + { + add => client.VirtualcamStateChanged += value; + remove => client.VirtualcamStateChanged -= value; + } + /// /// Streams VirtualcamStateChanged events as they arrive. /// The state of the virtualcam output has changed. @@ -813,6 +1242,17 @@ public readonly partial struct OutputsGroup cancellationToken); } + /// + /// Occurs when OBS raises ReplayBufferSaved. + /// The replay buffer has been saved. + /// + /// Requires the Outputs subscription. + public event EventHandler? ReplayBufferSaved + { + add => client.ReplayBufferSaved += value; + remove => client.ReplayBufferSaved -= value; + } + /// /// Streams ReplayBufferSaved events as they arrive. /// The replay buffer has been saved. @@ -841,6 +1281,17 @@ public readonly partial struct OutputsGroup /// public readonly partial struct SceneItemsGroup { + /// + /// Occurs when OBS raises SceneItemCreated. + /// A scene item has been created. + /// + /// Requires the SceneItems subscription. + public event EventHandler? SceneItemCreated + { + add => client.SceneItemCreated += value; + remove => client.SceneItemCreated -= value; + } + /// /// Streams SceneItemCreated events as they arrive. /// A scene item has been created. @@ -860,6 +1311,17 @@ public readonly partial struct SceneItemsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneItemRemoved. + /// A scene item has been removed. This event is not emitted when the scene the item is in is removed. + /// + /// Requires the SceneItems subscription. + public event EventHandler? SceneItemRemoved + { + add => client.SceneItemRemoved += value; + remove => client.SceneItemRemoved -= value; + } + /// /// Streams SceneItemRemoved events as they arrive. /// A scene item has been removed. This event is not emitted when the scene the item is in is removed. @@ -879,6 +1341,17 @@ public readonly partial struct SceneItemsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneItemListReindexed. + /// A scene's item list has been reindexed. + /// + /// Requires the SceneItems subscription. + public event EventHandler? SceneItemListReindexed + { + add => client.SceneItemListReindexed += value; + remove => client.SceneItemListReindexed -= value; + } + /// /// Streams SceneItemListReindexed events as they arrive. /// A scene's item list has been reindexed. @@ -898,6 +1371,17 @@ public readonly partial struct SceneItemsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneItemEnableStateChanged. + /// A scene item's enable state has changed. + /// + /// Requires the SceneItems subscription. + public event EventHandler? SceneItemEnableStateChanged + { + add => client.SceneItemEnableStateChanged += value; + remove => client.SceneItemEnableStateChanged -= value; + } + /// /// Streams SceneItemEnableStateChanged events as they arrive. /// A scene item's enable state has changed. @@ -917,6 +1401,17 @@ public readonly partial struct SceneItemsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneItemLockStateChanged. + /// A scene item's lock state has changed. + /// + /// Requires the SceneItems subscription. + public event EventHandler? SceneItemLockStateChanged + { + add => client.SceneItemLockStateChanged += value; + remove => client.SceneItemLockStateChanged -= value; + } + /// /// Streams SceneItemLockStateChanged events as they arrive. /// A scene item's lock state has changed. @@ -936,6 +1431,17 @@ public readonly partial struct SceneItemsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneItemSelected. + /// A scene item has been selected in the Ui. + /// + /// Requires the SceneItems subscription. + public event EventHandler? SceneItemSelected + { + add => client.SceneItemSelected += value; + remove => client.SceneItemSelected -= value; + } + /// /// Streams SceneItemSelected events as they arrive. /// A scene item has been selected in the Ui. @@ -955,6 +1461,17 @@ public readonly partial struct SceneItemsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneItemTransformChanged. + /// The transform/crop of a scene item has changed. + /// + /// Requires the SceneItemTransformChanged subscription. + public event EventHandler? SceneItemTransformChanged + { + add => client.SceneItemTransformChanged += value; + remove => client.SceneItemTransformChanged -= value; + } + /// /// Streams SceneItemTransformChanged events as they arrive. /// The transform/crop of a scene item has changed. @@ -983,6 +1500,17 @@ public readonly partial struct SceneItemsGroup /// public readonly partial struct ScenesGroup { + /// + /// Occurs when OBS raises SceneCreated. + /// A new scene has been created. + /// + /// Requires the Scenes subscription. + public event EventHandler? SceneCreated + { + add => client.SceneCreated += value; + remove => client.SceneCreated -= value; + } + /// /// Streams SceneCreated events as they arrive. /// A new scene has been created. @@ -1002,6 +1530,17 @@ public readonly partial struct ScenesGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneRemoved. + /// A scene has been removed. + /// + /// Requires the Scenes subscription. + public event EventHandler? SceneRemoved + { + add => client.SceneRemoved += value; + remove => client.SceneRemoved -= value; + } + /// /// Streams SceneRemoved events as they arrive. /// A scene has been removed. @@ -1021,6 +1560,17 @@ public readonly partial struct ScenesGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneNameChanged. + /// The name of a scene has changed. + /// + /// Requires the Scenes subscription. + public event EventHandler? SceneNameChanged + { + add => client.SceneNameChanged += value; + remove => client.SceneNameChanged -= value; + } + /// /// Streams SceneNameChanged events as they arrive. /// The name of a scene has changed. @@ -1040,6 +1590,17 @@ public readonly partial struct ScenesGroup cancellationToken); } + /// + /// Occurs when OBS raises CurrentProgramSceneChanged. + /// The current program scene has changed. + /// + /// Requires the Scenes subscription. + public event EventHandler? CurrentProgramSceneChanged + { + add => client.CurrentProgramSceneChanged += value; + remove => client.CurrentProgramSceneChanged -= value; + } + /// /// Streams CurrentProgramSceneChanged events as they arrive. /// The current program scene has changed. @@ -1059,6 +1620,17 @@ public readonly partial struct ScenesGroup cancellationToken); } + /// + /// Occurs when OBS raises CurrentPreviewSceneChanged. + /// The current preview scene has changed. + /// + /// Requires the Scenes subscription. + public event EventHandler? CurrentPreviewSceneChanged + { + add => client.CurrentPreviewSceneChanged += value; + remove => client.CurrentPreviewSceneChanged -= value; + } + /// /// Streams CurrentPreviewSceneChanged events as they arrive. /// The current preview scene has changed. @@ -1078,6 +1650,17 @@ public readonly partial struct ScenesGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneListChanged. + /// The list of scenes has changed. TODO: Make OBS fire this event when scenes are reordered. + /// + /// Requires the Scenes subscription. + public event EventHandler? SceneListChanged + { + add => client.SceneListChanged += value; + remove => client.SceneListChanged -= value; + } + /// /// Streams SceneListChanged events as they arrive. /// The list of scenes has changed. TODO: Make OBS fire this event when scenes are reordered. @@ -1106,6 +1689,17 @@ public readonly partial struct ScenesGroup /// public readonly partial struct TransitionsGroup { + /// + /// Occurs when OBS raises CurrentSceneTransitionChanged. + /// The current scene transition has changed. + /// + /// Requires the Transitions subscription. + public event EventHandler? CurrentSceneTransitionChanged + { + add => client.CurrentSceneTransitionChanged += value; + remove => client.CurrentSceneTransitionChanged -= value; + } + /// /// Streams CurrentSceneTransitionChanged events as they arrive. /// The current scene transition has changed. @@ -1125,6 +1719,17 @@ public readonly partial struct TransitionsGroup cancellationToken); } + /// + /// Occurs when OBS raises CurrentSceneTransitionDurationChanged. + /// The current scene transition duration has changed. + /// + /// Requires the Transitions subscription. + public event EventHandler? CurrentSceneTransitionDurationChanged + { + add => client.CurrentSceneTransitionDurationChanged += value; + remove => client.CurrentSceneTransitionDurationChanged -= value; + } + /// /// Streams CurrentSceneTransitionDurationChanged events as they arrive. /// The current scene transition duration has changed. @@ -1144,6 +1749,17 @@ public readonly partial struct TransitionsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneTransitionStarted. + /// A scene transition has started. + /// + /// Requires the Transitions subscription. + public event EventHandler? SceneTransitionStarted + { + add => client.SceneTransitionStarted += value; + remove => client.SceneTransitionStarted -= value; + } + /// /// Streams SceneTransitionStarted events as they arrive. /// A scene transition has started. @@ -1163,6 +1779,17 @@ public readonly partial struct TransitionsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneTransitionEnded. + /// A scene transition has completed fully. Note: Does not appear to trigger when the transition is interrupted by the user. + /// + /// Requires the Transitions subscription. + public event EventHandler? SceneTransitionEnded + { + add => client.SceneTransitionEnded += value; + remove => client.SceneTransitionEnded -= value; + } + /// /// Streams SceneTransitionEnded events as they arrive. /// A scene transition has completed fully. Note: Does not appear to trigger when the transition is interrupted by the user. @@ -1182,6 +1809,17 @@ public readonly partial struct TransitionsGroup cancellationToken); } + /// + /// Occurs when OBS raises SceneTransitionVideoEnded. + /// A scene transition's video has completed fully. Useful for stinger transitions to tell when the video *actually* ends. `SceneTransitionEnded` only signifies the cut point, not the completion of transition playback. Note: Appears to be called by every transition, regardless of relevance. + /// + /// Requires the Transitions subscription. + public event EventHandler? SceneTransitionVideoEnded + { + add => client.SceneTransitionVideoEnded += value; + remove => client.SceneTransitionVideoEnded -= value; + } + /// /// Streams SceneTransitionVideoEnded events as they arrive. /// A scene transition's video has completed fully. Useful for stinger transitions to tell when the video *actually* ends. `SceneTransitionEnded` only signifies the cut point, not the completion of transition playback. Note: Appears to be called by every transition, regardless of relevance. @@ -1210,6 +1848,17 @@ public readonly partial struct TransitionsGroup /// public readonly partial struct UiGroup { + /// + /// Occurs when OBS raises StudioModeStateChanged. + /// Studio mode has been enabled or disabled. + /// + /// Requires the Ui subscription. + public event EventHandler? StudioModeStateChanged + { + add => client.StudioModeStateChanged += value; + remove => client.StudioModeStateChanged -= value; + } + /// /// Streams StudioModeStateChanged events as they arrive. /// Studio mode has been enabled or disabled. @@ -1229,6 +1878,17 @@ public readonly partial struct UiGroup cancellationToken); } + /// + /// Occurs when OBS raises ScreenshotSaved. + /// A screenshot has been saved. Note: Triggered for the screenshot feature available in `Settings -> Hotkeys -> Screenshot Output` ONLY. Applications using `Get/SaveSourceScreenshot` should implement a `CustomEvent` if this kind of inter-client communication is desired. + /// + /// Requires the Ui subscription. + public event EventHandler? ScreenshotSaved + { + add => client.ScreenshotSaved += value; + remove => client.ScreenshotSaved -= value; + } + /// /// Streams ScreenshotSaved events as they arrive. /// A screenshot has been saved. Note: Triggered for the screenshot feature available in `Settings -> Hotkeys -> Screenshot Output` ONLY. Applications using `Get/SaveSourceScreenshot` should implement a `CustomEvent` if this kind of inter-client communication is desired. diff --git a/ObsWebSocket.Example/Worker.cs b/ObsWebSocket.Example/Worker.cs index 95d005d..13f1ade 100644 --- a/ObsWebSocket.Example/Worker.cs +++ b/ObsWebSocket.Example/Worker.cs @@ -58,13 +58,13 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) _obsClient.AuthenticationFailure += OnObsAuthenticationFailure; // --- Subscribe to Specific OBS Events --- - _obsClient.CurrentProgramSceneChanged += OnCurrentProgramSceneChanged; - _obsClient.InputMuteStateChanged += OnInputMuteStateChanged; - _obsClient.StudioModeStateChanged += OnStudioModeStateChanged; - _obsClient.InputCreated += OnInputCreated; - _obsClient.StreamStateChanged += OnStreamStateChanged; - _obsClient.SceneCreated += OnSceneCreated; - _obsClient.SourceFilterCreated += OnSourceFilterCreated; + _obsClient.Scenes.CurrentProgramSceneChanged += OnCurrentProgramSceneChanged; + _obsClient.Inputs.InputMuteStateChanged += OnInputMuteStateChanged; + _obsClient.Ui.StudioModeStateChanged += OnStudioModeStateChanged; + _obsClient.Inputs.InputCreated += OnInputCreated; + _obsClient.Outputs.StreamStateChanged += OnStreamStateChanged; + _obsClient.Scenes.SceneCreated += OnSceneCreated; + _obsClient.Filters.SourceFilterCreated += OnSourceFilterCreated; _logger.LogInformation("Example Worker running."); _logger.LogInformation( @@ -150,14 +150,14 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) _obsClient.Disconnected -= OnObsDisconnected; _obsClient.ConnectionFailed -= OnObsConnectionFailed; _obsClient.AuthenticationFailure -= OnObsAuthenticationFailure; - _obsClient.CurrentProgramSceneChanged -= OnCurrentProgramSceneChanged; - _obsClient.InputMuteStateChanged -= OnInputMuteStateChanged; - _obsClient.StudioModeStateChanged -= OnStudioModeStateChanged; + _obsClient.Scenes.CurrentProgramSceneChanged -= OnCurrentProgramSceneChanged; + _obsClient.Inputs.InputMuteStateChanged -= OnInputMuteStateChanged; + _obsClient.Ui.StudioModeStateChanged -= OnStudioModeStateChanged; // Unsubscribe new handlers - _obsClient.InputCreated -= OnInputCreated; - _obsClient.StreamStateChanged -= OnStreamStateChanged; - _obsClient.SceneCreated -= OnSceneCreated; - _obsClient.SourceFilterCreated -= OnSourceFilterCreated; + _obsClient.Inputs.InputCreated -= OnInputCreated; + _obsClient.Outputs.StreamStateChanged -= OnStreamStateChanged; + _obsClient.Scenes.SceneCreated -= OnSceneCreated; + _obsClient.Filters.SourceFilterCreated -= OnSourceFilterCreated; // Ensure disconnection on exit if (_obsClient.IsConnected) diff --git a/ObsWebSocket.Tests/GroupEventTests.cs b/ObsWebSocket.Tests/GroupEventTests.cs new file mode 100644 index 0000000..52a8b2c --- /dev/null +++ b/ObsWebSocket.Tests/GroupEventTests.cs @@ -0,0 +1,76 @@ +using ObsWebSocket.Core; +using ObsWebSocket.Core.Events.Generated; + +namespace ObsWebSocket.Tests; + +/// +/// The category groups are structs the property hands out fresh on every access, so subscribing +/// through one has to reach the client's own delegate list rather than a temporary. Removal +/// matters most: a remove that silently did nothing would leak every handler. +/// +[TestClass] +public sealed class GroupEventTests +{ + private static Delegate[] Handlers(ObsWebSocketClient client, string eventName) => + TestUtils.GetPrivateField(client, eventName)?.GetInvocationList() ?? []; + + [TestMethod] + public void AddAndRemoveThroughTheGroup_ReachTheClientsList() + { + (ObsWebSocketClient client, _, _) = TestUtils.SetupConnectedClientForceState(); + static void Handler(object? sender, CurrentProgramSceneChangedEventArgs e) { } + + Assert.AreEqual(0, Handlers(client, "CurrentProgramSceneChanged").Length); + + client.Scenes.CurrentProgramSceneChanged += Handler; + Assert.AreEqual( + 1, + Handlers(client, "CurrentProgramSceneChanged").Length, + "adding through the group should reach the client" + ); + + client.Scenes.CurrentProgramSceneChanged -= Handler; + Assert.AreEqual( + 0, + Handlers(client, "CurrentProgramSceneChanged").Length, + "removing through the group must not leave the handler attached" + ); + } + + [TestMethod] + public void TheGroupAndTheClientShareOneSubscriptionList() + { + (ObsWebSocketClient client, _, _) = TestUtils.SetupConnectedClientForceState(); + static void Handler(object? sender, CurrentProgramSceneChangedEventArgs e) { } + + client.CurrentProgramSceneChanged += Handler; + client.Scenes.CurrentProgramSceneChanged -= Handler; + Assert.AreEqual( + 0, + Handlers(client, "CurrentProgramSceneChanged").Length, + "a handler added on the client should be removable through the group" + ); + + client.Scenes.CurrentProgramSceneChanged += Handler; + client.CurrentProgramSceneChanged -= Handler; + Assert.AreEqual( + 0, + Handlers(client, "CurrentProgramSceneChanged").Length, + "a handler added through the group should be removable on the client" + ); + } + + [TestMethod] + public void EveryCategoryGroupCarriesItsEvents() + { + // The point of the change: a caller never has to know that some events sit on the client + // and some on a group. + (ObsWebSocketClient client, _, _) = TestUtils.SetupConnectedClientForceState(); + + Assert.IsNotNull(typeof(ScenesGroup).GetEvent(nameof(client.SceneCreated))); + Assert.IsNotNull(typeof(InputsGroup).GetEvent(nameof(client.InputCreated))); + Assert.IsNotNull(typeof(OutputsGroup).GetEvent(nameof(client.StreamStateChanged))); + Assert.IsNotNull(typeof(SceneItemsGroup).GetEvent(nameof(client.SceneItemCreated))); + Assert.IsNotNull(typeof(UiGroup).GetEvent(nameof(client.StudioModeStateChanged))); + } +} diff --git a/ObsWebSocket.Tests/ReadmeCompileCheck.cs b/ObsWebSocket.Tests/ReadmeCompileCheck.cs index 2490aac..2f8bf02 100644 --- a/ObsWebSocket.Tests/ReadmeCompileCheck.cs +++ b/ObsWebSocket.Tests/ReadmeCompileCheck.cs @@ -164,7 +164,7 @@ var e in client.Scenes.CurrentProgramSceneChangedStream(cancellationToken: ct) break; } - client.CurrentProgramSceneChanged += (_, e) => _ = e.EventData.SceneName; + client.Scenes.CurrentProgramSceneChanged += (_, e) => _ = e.EventData.SceneName; _ = await client.WaitForEventAsync(ct); _ = await client.WaitForEventAsync( @@ -208,7 +208,7 @@ internal static async Task TypedBatchAsync(ObsWebSocketClient client, Cancellati internal static void TypedEnums(ObsWebSocketClient client) { - client.StreamStateChanged += (_, e) => + client.Outputs.StreamStateChanged += (_, e) => { string what = e.EventData.OutputState switch { diff --git a/README.md b/README.md index 6010885..2d8ebef 100644 --- a/README.md +++ b/README.md @@ -172,13 +172,21 @@ await foreach (var e in client.Scenes.CurrentProgramSceneChangedStream(cancellat Streams buffer a bounded number of events and drop the oldest when a consumer falls behind, so a slow loop cannot stall the receive loop. Pass `capacity` to change that. -The classic events still work, including alongside a stream over the same event: +The classic handler sits on the same group, so subscribing and streaming read alike and there is no +second place to look: ```csharp -client.CurrentProgramSceneChanged += (_, e) => +client.Scenes.CurrentProgramSceneChanged += (_, e) => Console.WriteLine($"Program scene is now {e.EventData.SceneName}"); ``` +Both work over the same event at once. The group's event is the client's event, so a handler added +through one can be removed through the other; `client.CurrentProgramSceneChanged` remains for the +low-level path, the way `CallAsync` remains alongside the generated requests. + +Connection lifecycle events stay on the client, since `Connected`, `Disconnected`, +`ConnectionFailed` and `AuthenticationFailure` belong to no protocol category. + To wait for a single occurrence, use `WaitForEventAsync`. It subscribes before returning, so you can start the wait and then trigger the action without racing it: @@ -342,7 +350,7 @@ double volume = (await client.Inputs.GetInputVolumeAsync(new("Mic"), ct)).InputV there is nothing to convert at the call site: ```csharp -client.StreamStateChanged += (_, e) => +client.Outputs.StreamStateChanged += (_, e) => { string what = e.EventData.OutputState switch { From 2e69385d10e3fa7aeeaa6c61926c9cfbbf0aa04e Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 26 Aug 2026 12:42:22 +0200 Subject: [PATCH 03/13] test: stop the suite oversubscribing the thread pool MSTest defaults to one worker per processor, and dotnet test runs every target framework at once, so the suite asked for several times the machine's parallelism. The tests that suffered were the ones asserting a timeout: their continuations wait on a saturated pool, so they failed on a busy machine rather than on a defect, and raising one test's budget only moved the failure to the next one. --- ObsWebSocket.Tests/MSTestSettings.cs | 7 ++++++- ObsWebSocket.Tests/ObsWebSocketClientConnectionTests.cs | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ObsWebSocket.Tests/MSTestSettings.cs b/ObsWebSocket.Tests/MSTestSettings.cs index aaf278c..654d907 100644 --- a/ObsWebSocket.Tests/MSTestSettings.cs +++ b/ObsWebSocket.Tests/MSTestSettings.cs @@ -1 +1,6 @@ -[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] +// Method-level parallelism with a fixed worker count. Left to its default, MSTest runs one worker +// per processor, and `dotnet test` runs every target framework at once, so the suite asks for +// several times the machine's parallelism. The tests that suffer are the ones asserting a timeout: +// their continuations wait on a thread pool that is saturated, and they fail on a busy machine +// rather than on a defect. +[assembly: Parallelize(Workers = 4, Scope = ExecutionScope.MethodLevel)] diff --git a/ObsWebSocket.Tests/ObsWebSocketClientConnectionTests.cs b/ObsWebSocket.Tests/ObsWebSocketClientConnectionTests.cs index 295b10f..989c819 100644 --- a/ObsWebSocket.Tests/ObsWebSocketClientConnectionTests.cs +++ b/ObsWebSocket.Tests/ObsWebSocketClientConnectionTests.cs @@ -714,8 +714,10 @@ await Assert.ThrowsExactlyAsync(() => mockFactory.Verify(f => f.CreateConnection(), Times.Exactly(maxAttempts)); } + // Its own two signal waits total five seconds in the worst case, so the shared six second + // budget left no headroom and the test failed on a busy machine rather than on a defect. [TestMethod] - [Timeout(TestTimeout)] + [Timeout(20_000)] public async Task DisconnectAsync_DuringRetry_StopsRetriesAndDisconnectsGracefully() { // Arrange From cd026d220cc5a673f32de0da0e48a8d90c160df8 Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 26 Aug 2026 13:32:19 +0200 Subject: [PATCH 04/13] refactor(core)!: one status on the exception, and drop the empty record ObsWebSocketRequestException carried both Status and StatusCode for the same thing. The record added nothing on a thrown exception: Result is false by definition, Comment is already on the exception, and Code is the number behind StatusCode, which casting to int still recovers. Only StatusCode remains. A request whose fields are all optional needed an empty record at the call site, so GetSceneListAsync(new(), ct) became GetSceneListAsync(ct). Requests offering a name or a uuid are excluded: the protocol marks both optional because either will do, but one of the pair has to be supplied, so a no argument call would fail at runtime rather than at compile time. --- .../Generation/Emitter.cs | 51 +++++++++++++++ .../Client/ObsWebSocketClient.Extensions.g.cs | 62 +++++++++++++++++++ ObsWebSocket.Core/Groups/ScenesGroup.cs | 2 +- ObsWebSocket.Core/Groups/SourcesGroup.cs | 2 +- .../ObsWebSocketRequestException.cs | 13 ++-- ObsWebSocket.Example/Worker.cs | 15 ++--- ObsWebSocket.Tests/BatchResultTests.cs | 2 +- ObsWebSocket.Tests/ReadmeCompileCheck.cs | 4 +- README.md | 2 +- 9 files changed, 132 insertions(+), 21 deletions(-) diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs index 9b7aa0b..912b65d 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs @@ -889,6 +889,35 @@ IGrouping group in protocol ); } + /// + /// Whether every field of a request is optional, so an empty record is a valid payload. + /// + private static bool AllFieldsOptional(RequestDefinition reqDef) => + reqDef.RequestFields is { Count: > 0 } + && reqDef.RequestFields.TrueForAll(f => f.ValueOptional.GetValueOrDefault(false)); + + /// + /// Whether a request identifies its target by either a name or a uuid. The protocol marks both + /// optional because either will do, but one of them has to be supplied. + /// + private static bool HasNameOrUuidChoice(RequestDefinition reqDef) + { + if (reqDef.RequestFields is null) + { + return false; + } + + HashSet names = new( + reqDef.RequestFields.Select(f => f.ValueName), + StringComparer.Ordinal + ); + + return names.Any(n => + n.EndsWith("Uuid", StringComparison.Ordinal) + && names.Contains(string.Concat(n.AsSpan(0, n.Length - 4), "Name")) + ); + } + /// /// Generates the source code for a single request extension method. /// @@ -999,6 +1028,28 @@ RequestDefinition reqDef $" {awaitPrefix}await client.{baseCallMethod}<{responseDtoType}>({requestTypeStringLiteral}, {callParams}, cancellationToken: cancellationToken).ConfigureAwait(false);" ); builder.AppendLine(" }"); + + // A request whose fields are all optional still needs an empty record at the call site, + // which reads as ceremony for the ones that take nothing meaningful. Emit an overload + // for those. Requests offering a name or a uuid are excluded: exactly one of the pair is + // required in practice, so a no argument call would fail at runtime. + if (hasRequestData && AllFieldsOptional(reqDef) && !HasNameOrUuidChoice(reqDef)) + { + builder.AppendLine(); + builder.AppendLine(" /// "); + AppendMultiLineXmlDoc(builder, reqDef.Description, " ///"); + builder.AppendLine(" /// "); + builder.AppendLine( + " /// A token to cancel the asynchronous operation." + ); + builder.AppendLine( + " /// Sends the request with no fields set, since all of them are optional." + ); + builder.AppendLine( + $" public {returnType} {methodName}(CancellationToken cancellationToken = default) =>" + ); + builder.AppendLine($" {methodName}(new {requestDtoType}(), cancellationToken);"); + } } #endregion diff --git a/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.Extensions.g.cs b/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.Extensions.g.cs index d122759..1449f1b 100644 --- a/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.Extensions.g.cs +++ b/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.Extensions.g.cs @@ -297,6 +297,16 @@ public async Task SetVideoSettingsAsync(ObsWebSocket.Core.Protocol.Requests.SetV await client.CallAsync("SetVideoSettings", requestData, cancellationToken: cancellationToken).ConfigureAwait(false); } + /// + /// Sets the current video settings. + /// + /// Note: Fields must be specified in pairs. For example, you cannot set only `baseWidth` without needing to specify `baseHeight`. + /// + /// A token to cancel the asynchronous operation. + /// Sends the request with no fields set, since all of them are optional. + public Task SetVideoSettingsAsync(CancellationToken cancellationToken = default) => + SetVideoSettingsAsync(new ObsWebSocket.Core.Protocol.Requests.SetVideoSettingsRequestData(), cancellationToken); + /// /// Gets the current stream service settings (stream destination). /// @@ -719,6 +729,16 @@ public async Task TriggerHotkeyByKeySequenceAsync(ObsWebSocket.Core.Protocol.Req await client.CallAsync("TriggerHotkeyByKeySequence", requestData, cancellationToken: cancellationToken).ConfigureAwait(false); } + /// + /// Triggers a hotkey using a sequence of keys. + /// + /// Note: Hotkey functionality in obs-websocket comes as-is, and we do not guarantee support if things are broken. In 9/10 usages of hotkey requests, there exists a better, more reliable method via other requests. + /// + /// A token to cancel the asynchronous operation. + /// Sends the request with no fields set, since all of them are optional. + public Task TriggerHotkeyByKeySequenceAsync(CancellationToken cancellationToken = default) => + TriggerHotkeyByKeySequenceAsync(new ObsWebSocket.Core.Protocol.Requests.TriggerHotkeyByKeySequenceRequestData(), cancellationToken); + /// /// Sleeps for a time duration or number of frames. Only available in request batches with types `SERIAL_REALTIME` or `SERIAL_FRAME`. /// @@ -738,6 +758,14 @@ public async Task SleepAsync(ObsWebSocket.Core.Protocol.Requests.SleepRequestDat await client.CallAsync("Sleep", requestData, cancellationToken: cancellationToken).ConfigureAwait(false); } + /// + /// Sleeps for a time duration or number of frames. Only available in request batches with types `SERIAL_REALTIME` or `SERIAL_FRAME`. + /// + /// A token to cancel the asynchronous operation. + /// Sends the request with no fields set, since all of them are optional. + public Task SleepAsync(CancellationToken cancellationToken = default) => + SleepAsync(new ObsWebSocket.Core.Protocol.Requests.SleepRequestData(), cancellationToken); + } /// @@ -765,6 +793,14 @@ public readonly partial struct InputsGroup(ObsWebSocketClient client) return await client.CallRequiredAsync("GetInputList", requestData, cancellationToken: cancellationToken).ConfigureAwait(false); } + /// + /// Gets an array of all inputs in OBS. + /// + /// A token to cancel the asynchronous operation. + /// Sends the request with no fields set, since all of them are optional. + public Task GetInputListAsync(CancellationToken cancellationToken = default) => + GetInputListAsync(new ObsWebSocket.Core.Protocol.Requests.GetInputListRequestData(), cancellationToken); + /// /// Gets an array of all available input kinds in OBS. /// @@ -784,6 +820,14 @@ public readonly partial struct InputsGroup(ObsWebSocketClient client) return await client.CallRequiredAsync("GetInputKindList", requestData, cancellationToken: cancellationToken).ConfigureAwait(false); } + /// + /// Gets an array of all available input kinds in OBS. + /// + /// A token to cancel the asynchronous operation. + /// Sends the request with no fields set, since all of them are optional. + public Task GetInputKindListAsync(CancellationToken cancellationToken = default) => + GetInputKindListAsync(new ObsWebSocket.Core.Protocol.Requests.GetInputKindListRequestData(), cancellationToken); + /// /// Gets the names of all special inputs. /// @@ -1914,6 +1958,16 @@ public async Task CreateRecordChapterAsync(ObsWebSocket.Core.Protocol.Requests.C await client.CallAsync("CreateRecordChapter", requestData, cancellationToken: cancellationToken).ConfigureAwait(false); } + /// + /// Adds a new chapter marker to the file currently being recorded. + /// + /// Note: As of OBS 30.2.0, the only file format supporting this feature is Hybrid MP4. + /// + /// A token to cancel the asynchronous operation. + /// Sends the request with no fields set, since all of them are optional. + public Task CreateRecordChapterAsync(CancellationToken cancellationToken = default) => + CreateRecordChapterAsync(new ObsWebSocket.Core.Protocol.Requests.CreateRecordChapterRequestData(), cancellationToken); + } /// @@ -2316,6 +2370,14 @@ public readonly partial struct ScenesGroup(ObsWebSocketClient client) return await client.CallRequiredAsync("GetSceneList", requestData, cancellationToken: cancellationToken).ConfigureAwait(false); } + /// + /// Gets an array of scenes in OBS. + /// + /// A token to cancel the asynchronous operation. + /// Sends the request with no fields set, since all of them are optional. + public Task GetSceneListAsync(CancellationToken cancellationToken = default) => + GetSceneListAsync(new ObsWebSocket.Core.Protocol.Requests.GetSceneListRequestData(), cancellationToken); + /// /// Gets an array of all groups in OBS. /// diff --git a/ObsWebSocket.Core/Groups/ScenesGroup.cs b/ObsWebSocket.Core/Groups/ScenesGroup.cs index 4ffc9f7..a91028f 100644 --- a/ObsWebSocket.Core/Groups/ScenesGroup.cs +++ b/ObsWebSocket.Core/Groups/ScenesGroup.cs @@ -226,7 +226,7 @@ public async Task SceneExistsAsync( client.EnsureConnected(); GetSceneListResponseData? scenes = await client - .Scenes.GetSceneListAsync(new GetSceneListRequestData(), cancellationToken) + .Scenes.GetSceneListAsync(cancellationToken) .ConfigureAwait(false); return scenes?.Scenes?.Any(s => diff --git a/ObsWebSocket.Core/Groups/SourcesGroup.cs b/ObsWebSocket.Core/Groups/SourcesGroup.cs index 898a9dc..d812f79 100644 --- a/ObsWebSocket.Core/Groups/SourcesGroup.cs +++ b/ObsWebSocket.Core/Groups/SourcesGroup.cs @@ -57,7 +57,7 @@ public async Task SourceExistsAsync( // Check scenes if not found in inputs ObsWebSocket.Core.Protocol.Responses.GetSceneListResponseData? sceneListResponse = await client - .Scenes.GetSceneListAsync(new(), cancellationToken: cancellationToken) + .Scenes.GetSceneListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); return sceneListResponse?.Scenes?.Any(s => string.Equals(s.SceneName, sourceName, StringComparison.Ordinal) diff --git a/ObsWebSocket.Core/ObsWebSocketRequestException.cs b/ObsWebSocket.Core/ObsWebSocketRequestException.cs index dbfe5a9..8988a82 100644 --- a/ObsWebSocket.Core/ObsWebSocketRequestException.cs +++ b/ObsWebSocket.Core/ObsWebSocketRequestException.cs @@ -25,7 +25,7 @@ public ObsWebSocketRequestException( { RequestType = requestType; RequestId = requestId; - Status = status; + StatusCode = status is null ? null : (RequestStatusCode)status.Code; Comment = comment; } @@ -49,16 +49,13 @@ public ObsWebSocketRequestException(string message, Exception innerException) /// The identifier of the rejected request. public string RequestId { get; } = string.Empty; - /// The status OBS reported, when one was available. - public RequestStatus? Status { get; } - /// The comment OBS attached, if any. public string? Comment { get; } /// - /// The status OBS reported as - /// , so a handler can - /// match on the reason rather than on the text of . + /// The reason OBS gave for rejecting the request, so a handler matches on the reason rather + /// than on the text of . A code this build does not know still + /// round trips, since the enum carries the number: cast it back to . /// /// /// @@ -66,7 +63,7 @@ public ObsWebSocketRequestException(string message, Exception innerException) /// when (ex.StatusCode is RequestStatusCode.ResourceNotFound) { } /// /// - public RequestStatusCode? StatusCode => Status is null ? null : (RequestStatusCode)Status.Code; + public RequestStatusCode? StatusCode { get; } } /// diff --git a/ObsWebSocket.Example/Worker.cs b/ObsWebSocket.Example/Worker.cs index 13f1ade..d0725ec 100644 --- a/ObsWebSocket.Example/Worker.cs +++ b/ObsWebSocket.Example/Worker.cs @@ -704,7 +704,7 @@ await _obsClient.MediaInputs.TriggerMediaActionAsync( { // Typed failure carries the protocol status, so no message matching. UiWarn( - $"OBS rejected {ex.RequestType} with code {ex.Status?.Code}: {ex.Comment}" + $"OBS rejected {ex.RequestType} with code {(int?)ex.StatusCode}: {ex.Comment}" ); } @@ -810,7 +810,7 @@ version is null ); GetSceneListResponseData? scenes = await cycleClient - .Scenes.GetSceneListAsync(new(), cancellationToken) + .Scenes.GetSceneListAsync(cancellationToken) .ConfigureAwait(false); if (scenes?.Scenes is null || scenes.Scenes.Count == 0) { @@ -825,7 +825,7 @@ version is null int sceneCount = scenes?.Scenes?.Count ?? 0; GetInputListResponseData? inputs = await cycleClient - .Inputs.GetInputListAsync(new GetInputListRequestData(), cancellationToken) + .Inputs.GetInputListAsync(cancellationToken) .ConfigureAwait(false); if (inputs?.Inputs is null || inputs.Inputs.Count == 0) { @@ -1337,7 +1337,7 @@ CancellationToken cancellationToken string inputName = $"__obsws_input_{suffix}"; GetSceneListResponseData? sceneList = await client - .Scenes.GetSceneListAsync(new GetSceneListRequestData(), cancellationToken) + .Scenes.GetSceneListAsync(cancellationToken) .ConfigureAwait(false); string originalScene = sceneList?.CurrentProgramSceneName ?? string.Empty; @@ -1755,7 +1755,7 @@ await TrySettingsCheckAsync( } catch (ObsWebSocketRequestException ex) { - caught = $"code {ex.Status?.Code}"; + caught = $"code {(int?)ex.StatusCode}"; } // TryGet reports the failure without throwing. @@ -2592,8 +2592,9 @@ await TrySettingsCheckAsync( catch (ObsWebSocketRequestException ex) { return ( - ex.Status?.Code == 600 && ex.RequestType == "GetSceneItemList", - $"{ex.RequestType} code {ex.Status?.Code}" + ex.StatusCode == RequestStatusCode.ResourceNotFound + && ex.RequestType == "GetSceneItemList", + $"{ex.RequestType} code {(int?)ex.StatusCode}" ); } } diff --git a/ObsWebSocket.Tests/BatchResultTests.cs b/ObsWebSocket.Tests/BatchResultTests.cs index f0d7685..ac18787 100644 --- a/ObsWebSocket.Tests/BatchResultTests.cs +++ b/ObsWebSocket.Tests/BatchResultTests.cs @@ -90,7 +90,7 @@ public void GetRequiredData_FailedRequest_ThrowsCarryingStatus() result.GetRequiredData() ); - Assert.AreEqual(600, ex.Status?.Code); + Assert.AreEqual(600, (int?)ex.StatusCode); Assert.AreEqual("GetSceneItemList", ex.RequestType); } diff --git a/ObsWebSocket.Tests/ReadmeCompileCheck.cs b/ObsWebSocket.Tests/ReadmeCompileCheck.cs index 2f8bf02..74c1826 100644 --- a/ObsWebSocket.Tests/ReadmeCompileCheck.cs +++ b/ObsWebSocket.Tests/ReadmeCompileCheck.cs @@ -268,7 +268,7 @@ internal static async Task TypedErrorsAsync(ObsWebSocketClient client, Cancellat } catch (ObsWebSocketRequestException ex) { - _ = $"{ex.RequestType} failed with {ex.Status?.Code}: {ex.Comment}"; + _ = $"{ex.RequestType} failed with {(int?)ex.StatusCode}: {ex.Comment}"; } catch (ObsWebSocketTimeoutException) { @@ -378,7 +378,7 @@ internal static async Task StudioModeAsync(ObsWebSocketClient client, Cancellati } catch (ObsWebSocketRequestException ex) { - _ = $"{ex.RequestType} failed with {ex.Status?.Code}: {ex.Comment}"; + _ = $"{ex.RequestType} failed with {(int?)ex.StatusCode}: {ex.Comment}"; } catch (ObsWebSocketTimeoutException) { } } diff --git a/README.md b/README.md index 2d8ebef..f9ad237 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ conveniences this library adds all sit in the group their category owns, so ther reach anything: ```csharp -await client.Scenes.GetSceneListAsync(new(), ct); // generated request +await client.Scenes.GetSceneListAsync(ct); // generated request await client.Scenes.SwitchProgramSceneAndWaitAsync("Intro", cancellationToken: ct); // convenience await client.Scenes.CurrentProgramSceneChangedStream(cancellationToken: ct); // event stream await client.Inputs.SetInputVolumeDbAsync("Mic", -6, ct); From bd87818c94f334c43d66de7077426e1989ccd764 Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 26 Aug 2026 13:37:07 +0200 Subject: [PATCH 05/13] revert(core): drop the no-argument request overloads The rule behind them was wrong. "Every field is optional" does not mean omitting everything does anything useful: SetVideoSettings documents each field as not changed when omitted, so the overload was a write that silently did nothing, TriggerHotkeyByKeySequence pressed no key, and Sleep had no duration and is only valid inside a serial batch. A structural rule does derive the three harmless cases without a list, by requiring response fields as well, but it only reaches GetSceneList, GetInputList and GetInputKindList. Not worth a codegen branch, so those keep their empty record. --- .../Generation/Emitter.cs | 51 --------------- .../Client/ObsWebSocketClient.Extensions.g.cs | 62 ------------------- ObsWebSocket.Core/Groups/ScenesGroup.cs | 2 +- ObsWebSocket.Core/Groups/SourcesGroup.cs | 2 +- ObsWebSocket.Example/Worker.cs | 6 +- README.md | 2 +- 6 files changed, 6 insertions(+), 119 deletions(-) diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs index 912b65d..9b7aa0b 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.cs @@ -889,35 +889,6 @@ IGrouping group in protocol ); } - /// - /// Whether every field of a request is optional, so an empty record is a valid payload. - /// - private static bool AllFieldsOptional(RequestDefinition reqDef) => - reqDef.RequestFields is { Count: > 0 } - && reqDef.RequestFields.TrueForAll(f => f.ValueOptional.GetValueOrDefault(false)); - - /// - /// Whether a request identifies its target by either a name or a uuid. The protocol marks both - /// optional because either will do, but one of them has to be supplied. - /// - private static bool HasNameOrUuidChoice(RequestDefinition reqDef) - { - if (reqDef.RequestFields is null) - { - return false; - } - - HashSet names = new( - reqDef.RequestFields.Select(f => f.ValueName), - StringComparer.Ordinal - ); - - return names.Any(n => - n.EndsWith("Uuid", StringComparison.Ordinal) - && names.Contains(string.Concat(n.AsSpan(0, n.Length - 4), "Name")) - ); - } - /// /// Generates the source code for a single request extension method. /// @@ -1028,28 +999,6 @@ RequestDefinition reqDef $" {awaitPrefix}await client.{baseCallMethod}<{responseDtoType}>({requestTypeStringLiteral}, {callParams}, cancellationToken: cancellationToken).ConfigureAwait(false);" ); builder.AppendLine(" }"); - - // A request whose fields are all optional still needs an empty record at the call site, - // which reads as ceremony for the ones that take nothing meaningful. Emit an overload - // for those. Requests offering a name or a uuid are excluded: exactly one of the pair is - // required in practice, so a no argument call would fail at runtime. - if (hasRequestData && AllFieldsOptional(reqDef) && !HasNameOrUuidChoice(reqDef)) - { - builder.AppendLine(); - builder.AppendLine(" /// "); - AppendMultiLineXmlDoc(builder, reqDef.Description, " ///"); - builder.AppendLine(" /// "); - builder.AppendLine( - " /// A token to cancel the asynchronous operation." - ); - builder.AppendLine( - " /// Sends the request with no fields set, since all of them are optional." - ); - builder.AppendLine( - $" public {returnType} {methodName}(CancellationToken cancellationToken = default) =>" - ); - builder.AppendLine($" {methodName}(new {requestDtoType}(), cancellationToken);"); - } } #endregion diff --git a/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.Extensions.g.cs b/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.Extensions.g.cs index 1449f1b..d122759 100644 --- a/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.Extensions.g.cs +++ b/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.Extensions.g.cs @@ -297,16 +297,6 @@ public async Task SetVideoSettingsAsync(ObsWebSocket.Core.Protocol.Requests.SetV await client.CallAsync("SetVideoSettings", requestData, cancellationToken: cancellationToken).ConfigureAwait(false); } - /// - /// Sets the current video settings. - /// - /// Note: Fields must be specified in pairs. For example, you cannot set only `baseWidth` without needing to specify `baseHeight`. - /// - /// A token to cancel the asynchronous operation. - /// Sends the request with no fields set, since all of them are optional. - public Task SetVideoSettingsAsync(CancellationToken cancellationToken = default) => - SetVideoSettingsAsync(new ObsWebSocket.Core.Protocol.Requests.SetVideoSettingsRequestData(), cancellationToken); - /// /// Gets the current stream service settings (stream destination). /// @@ -729,16 +719,6 @@ public async Task TriggerHotkeyByKeySequenceAsync(ObsWebSocket.Core.Protocol.Req await client.CallAsync("TriggerHotkeyByKeySequence", requestData, cancellationToken: cancellationToken).ConfigureAwait(false); } - /// - /// Triggers a hotkey using a sequence of keys. - /// - /// Note: Hotkey functionality in obs-websocket comes as-is, and we do not guarantee support if things are broken. In 9/10 usages of hotkey requests, there exists a better, more reliable method via other requests. - /// - /// A token to cancel the asynchronous operation. - /// Sends the request with no fields set, since all of them are optional. - public Task TriggerHotkeyByKeySequenceAsync(CancellationToken cancellationToken = default) => - TriggerHotkeyByKeySequenceAsync(new ObsWebSocket.Core.Protocol.Requests.TriggerHotkeyByKeySequenceRequestData(), cancellationToken); - /// /// Sleeps for a time duration or number of frames. Only available in request batches with types `SERIAL_REALTIME` or `SERIAL_FRAME`. /// @@ -758,14 +738,6 @@ public async Task SleepAsync(ObsWebSocket.Core.Protocol.Requests.SleepRequestDat await client.CallAsync("Sleep", requestData, cancellationToken: cancellationToken).ConfigureAwait(false); } - /// - /// Sleeps for a time duration or number of frames. Only available in request batches with types `SERIAL_REALTIME` or `SERIAL_FRAME`. - /// - /// A token to cancel the asynchronous operation. - /// Sends the request with no fields set, since all of them are optional. - public Task SleepAsync(CancellationToken cancellationToken = default) => - SleepAsync(new ObsWebSocket.Core.Protocol.Requests.SleepRequestData(), cancellationToken); - } /// @@ -793,14 +765,6 @@ public readonly partial struct InputsGroup(ObsWebSocketClient client) return await client.CallRequiredAsync("GetInputList", requestData, cancellationToken: cancellationToken).ConfigureAwait(false); } - /// - /// Gets an array of all inputs in OBS. - /// - /// A token to cancel the asynchronous operation. - /// Sends the request with no fields set, since all of them are optional. - public Task GetInputListAsync(CancellationToken cancellationToken = default) => - GetInputListAsync(new ObsWebSocket.Core.Protocol.Requests.GetInputListRequestData(), cancellationToken); - /// /// Gets an array of all available input kinds in OBS. /// @@ -820,14 +784,6 @@ public readonly partial struct InputsGroup(ObsWebSocketClient client) return await client.CallRequiredAsync("GetInputKindList", requestData, cancellationToken: cancellationToken).ConfigureAwait(false); } - /// - /// Gets an array of all available input kinds in OBS. - /// - /// A token to cancel the asynchronous operation. - /// Sends the request with no fields set, since all of them are optional. - public Task GetInputKindListAsync(CancellationToken cancellationToken = default) => - GetInputKindListAsync(new ObsWebSocket.Core.Protocol.Requests.GetInputKindListRequestData(), cancellationToken); - /// /// Gets the names of all special inputs. /// @@ -1958,16 +1914,6 @@ public async Task CreateRecordChapterAsync(ObsWebSocket.Core.Protocol.Requests.C await client.CallAsync("CreateRecordChapter", requestData, cancellationToken: cancellationToken).ConfigureAwait(false); } - /// - /// Adds a new chapter marker to the file currently being recorded. - /// - /// Note: As of OBS 30.2.0, the only file format supporting this feature is Hybrid MP4. - /// - /// A token to cancel the asynchronous operation. - /// Sends the request with no fields set, since all of them are optional. - public Task CreateRecordChapterAsync(CancellationToken cancellationToken = default) => - CreateRecordChapterAsync(new ObsWebSocket.Core.Protocol.Requests.CreateRecordChapterRequestData(), cancellationToken); - } /// @@ -2370,14 +2316,6 @@ public readonly partial struct ScenesGroup(ObsWebSocketClient client) return await client.CallRequiredAsync("GetSceneList", requestData, cancellationToken: cancellationToken).ConfigureAwait(false); } - /// - /// Gets an array of scenes in OBS. - /// - /// A token to cancel the asynchronous operation. - /// Sends the request with no fields set, since all of them are optional. - public Task GetSceneListAsync(CancellationToken cancellationToken = default) => - GetSceneListAsync(new ObsWebSocket.Core.Protocol.Requests.GetSceneListRequestData(), cancellationToken); - /// /// Gets an array of all groups in OBS. /// diff --git a/ObsWebSocket.Core/Groups/ScenesGroup.cs b/ObsWebSocket.Core/Groups/ScenesGroup.cs index a91028f..8d370b6 100644 --- a/ObsWebSocket.Core/Groups/ScenesGroup.cs +++ b/ObsWebSocket.Core/Groups/ScenesGroup.cs @@ -226,7 +226,7 @@ public async Task SceneExistsAsync( client.EnsureConnected(); GetSceneListResponseData? scenes = await client - .Scenes.GetSceneListAsync(cancellationToken) + .Scenes.GetSceneListAsync(new(), cancellationToken) .ConfigureAwait(false); return scenes?.Scenes?.Any(s => diff --git a/ObsWebSocket.Core/Groups/SourcesGroup.cs b/ObsWebSocket.Core/Groups/SourcesGroup.cs index d812f79..898a9dc 100644 --- a/ObsWebSocket.Core/Groups/SourcesGroup.cs +++ b/ObsWebSocket.Core/Groups/SourcesGroup.cs @@ -57,7 +57,7 @@ public async Task SourceExistsAsync( // Check scenes if not found in inputs ObsWebSocket.Core.Protocol.Responses.GetSceneListResponseData? sceneListResponse = await client - .Scenes.GetSceneListAsync(cancellationToken: cancellationToken) + .Scenes.GetSceneListAsync(new(), cancellationToken: cancellationToken) .ConfigureAwait(false); return sceneListResponse?.Scenes?.Any(s => string.Equals(s.SceneName, sourceName, StringComparison.Ordinal) diff --git a/ObsWebSocket.Example/Worker.cs b/ObsWebSocket.Example/Worker.cs index d0725ec..63d56b9 100644 --- a/ObsWebSocket.Example/Worker.cs +++ b/ObsWebSocket.Example/Worker.cs @@ -810,7 +810,7 @@ version is null ); GetSceneListResponseData? scenes = await cycleClient - .Scenes.GetSceneListAsync(cancellationToken) + .Scenes.GetSceneListAsync(new(), cancellationToken) .ConfigureAwait(false); if (scenes?.Scenes is null || scenes.Scenes.Count == 0) { @@ -825,7 +825,7 @@ version is null int sceneCount = scenes?.Scenes?.Count ?? 0; GetInputListResponseData? inputs = await cycleClient - .Inputs.GetInputListAsync(cancellationToken) + .Inputs.GetInputListAsync(new(), cancellationToken) .ConfigureAwait(false); if (inputs?.Inputs is null || inputs.Inputs.Count == 0) { @@ -1337,7 +1337,7 @@ CancellationToken cancellationToken string inputName = $"__obsws_input_{suffix}"; GetSceneListResponseData? sceneList = await client - .Scenes.GetSceneListAsync(cancellationToken) + .Scenes.GetSceneListAsync(new(), cancellationToken) .ConfigureAwait(false); string originalScene = sceneList?.CurrentProgramSceneName ?? string.Empty; diff --git a/README.md b/README.md index f9ad237..2d8ebef 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ conveniences this library adds all sit in the group their category owns, so ther reach anything: ```csharp -await client.Scenes.GetSceneListAsync(ct); // generated request +await client.Scenes.GetSceneListAsync(new(), ct); // generated request await client.Scenes.SwitchProgramSceneAndWaitAsync("Intro", cancellationToken: ct); // convenience await client.Scenes.CurrentProgramSceneChangedStream(cancellationToken: ct); // event stream await client.Inputs.SetInputVolumeDbAsync("Mic", -6, ct); From 50ee7d7f31f19500ad6bc720117a066fe4ec9ef0 Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 26 Aug 2026 13:54:23 +0200 Subject: [PATCH 06/13] docs: cover parallelism and the low level path The parallel note said which execution type to avoid and stopped there. It now says what a caller who picks it anyway can still do: only the labelling is wrong, requestStatus and responseData come from the same object, so Raw plus GetData recovers every payload with its own status, and what is unknowable is which request produced which row. Verified against OBS by comparing the recovered set against the serial truth. Concurrent requests are documented as the answer when results have to be attributed, since the client multiplexes on request id. Adds a low level section for CallAsync, CallAsyncValue and a hand rolled batch, and three example checks covering all of it. --- ObsWebSocket.Example/Worker.cs | 168 +++++++++++++++++++++++ ObsWebSocket.Tests/ReadmeCompileCheck.cs | 77 +++++++++++ README.md | 124 ++++++++++++++--- 3 files changed, 353 insertions(+), 16 deletions(-) diff --git a/ObsWebSocket.Example/Worker.cs b/ObsWebSocket.Example/Worker.cs index 63d56b9..d3b4abd 100644 --- a/ObsWebSocket.Example/Worker.cs +++ b/ObsWebSocket.Example/Worker.cs @@ -2572,6 +2572,174 @@ await TrySettingsCheckAsync( .ConfigureAwait(false) ); + results.Add( + await TrySettingsCheckAsync( + "Parallel batch, verdict without attribution", + async () => + { + // A parallel batch of writes is the case Parallel is actually good + // for: OBS mispairs the rows, but a verdict over all of them does not + // depend on which row is which. + ObsBatchBuilder par = new(); + _ = par.Inputs.SetInputMute( + new SetInputMuteRequestData(inputName: inputName, inputMuted: true) + ); + _ = par.Inputs.SetInputMute( + new SetInputMuteRequestData(inputName: inputName, inputMuted: false) + ); + + BatchResults ok = await client + .CallBatchAsync( + par, + executionType: RequestBatchExecutionType.Parallel, + haltOnFailure: false, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + + // Same again with one request that cannot succeed, so the count of + // failures is checked as well as the all-succeeded verdict. + ObsBatchBuilder mixed = new(); + _ = mixed.Inputs.SetInputMute( + new SetInputMuteRequestData(inputName: inputName, inputMuted: false) + ); + _ = mixed.Inputs.SetInputMute( + new SetInputMuteRequestData( + inputName: "__absent__", + inputMuted: true + ) + ); + + BatchResults partial = await client + .CallBatchAsync( + mixed, + executionType: RequestBatchExecutionType.Parallel, + haltOnFailure: false, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + + int failures = partial.GetFailures().Count(); + + return ( + ok.AllSucceeded() && !partial.AllSucceeded() && failures == 1, + $"all-ok verdict {ok.AllSucceeded()}, mixed verdict " + + $"{partial.AllSucceeded()} with {failures} failure(s)" + ); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Concurrent requests keep their own results", + async () => + { + // The answer to "how do I run things in parallel" once a parallel + // batch is ruled out for reads. The client multiplexes on request id. + Task version = client.General.GetVersionAsync( + cancellationToken + ); + Task video = + client.Config.GetVideoSettingsAsync(cancellationToken); + Task itemsHere = + client.SceneItems.GetSceneItemListAsync( + new GetSceneItemListRequestData(sceneName: sceneName), + cancellationToken + ); + Task itemsThere = + client.SceneItems.GetSceneItemListAsync( + new GetSceneItemListRequestData(sceneName: originalScene), + cancellationToken + ); + + await Task.WhenAll(version, video, itemsHere, itemsThere) + .ConfigureAwait(false); + + // Each answer has to match what the same request returns on its own. + GetVersionResponseData serialVersion = await client + .General.GetVersionAsync(cancellationToken) + .ConfigureAwait(false); + GetSceneItemListResponseData serialHere = await client + .SceneItems.GetSceneItemListAsync( + new GetSceneItemListRequestData(sceneName: sceneName), + cancellationToken + ) + .ConfigureAwait(false); + + bool ok = + string.Equals( + version.Result.ObsVersion, + serialVersion.ObsVersion, + StringComparison.Ordinal + ) + && video.Result.FpsNumerator > 0 + && itemsHere.Result.SceneItems?.Count + == serialHere.SceneItems?.Count; + + return ( + ok, + $"v={version.Result.ObsVersion}, fps={video.Result.FpsNumerator}, " + + $"items {itemsHere.Result.SceneItems?.Count} here vs " + + $"{itemsThere.Result.SceneItems?.Count} there" + ); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Low level Add and raw results", + async () => + { + // Hand rolled batch: Add covers anything the generated methods do not, + // and the raw payload helpers read it back. + ObsBatchBuilder raw = new(); + _ = raw.Add("GetVersion"); + _ = raw.Add("GetStats"); + + BatchResults rawResults = await client + .CallBatchAsync( + raw, + executionType: RequestBatchExecutionType.SerialRealtime, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + + GetVersionResponseData? v = rawResults + .Raw[0] + .GetData(); + + // And the same request without any batch at all. + GetVersionResponseData? direct = await client + .CallAsync( + "GetVersion", + null, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + + bool ok = + rawResults.Count == 2 + && v is not null + && direct is not null + && string.Equals( + v.ObsVersion, + direct.ObsVersion, + StringComparison.Ordinal + ); + + return ( + ok, + $"raw batch {rawResults.Count}, CallAsync {direct?.ObsVersion}" + ); + } + ) + .ConfigureAwait(false) + ); + results.Add( await TrySettingsCheckAsync( "Typed exception on a rejected request", diff --git a/ObsWebSocket.Tests/ReadmeCompileCheck.cs b/ObsWebSocket.Tests/ReadmeCompileCheck.cs index 74c1826..95d6e23 100644 --- a/ObsWebSocket.Tests/ReadmeCompileCheck.cs +++ b/ObsWebSocket.Tests/ReadmeCompileCheck.cs @@ -323,6 +323,83 @@ CancellationToken ct _ = $"{wire} {raw}"; } + internal static async Task ParallelRecoveryAsync( + ObsWebSocketClient client, + ObsBatchBuilder batch, + CancellationToken ct + ) + { + BatchResults results = await client.CallBatchAsync( + batch, + executionType: RequestBatchExecutionType.Parallel, + haltOnFailure: false, + cancellationToken: ct + ); + + foreach (RequestResponsePayload row in results.Raw) + { + if (!row.RequestStatus.Result) + { + _ = $"one request failed with {row.RequestStatus.Code}"; + continue; + } + + GetSceneItemListResponseData? data = row.GetData(); + _ = data?.SceneItems?.Count; + } + + _ = results.AllSucceeded(); + _ = results.GetFailures().Count(); + } + + internal static async Task ConcurrentRequestsAsync( + ObsWebSocketClient client, + string[] sceneNames, + CancellationToken ct + ) + { + Task version = client.General.GetVersionAsync(ct); + Task stats = client.General.GetStatsAsync(ct); + Task[] perScene = + [ + .. sceneNames.Select(n => + client.SceneItems.GetSceneItemListAsync(new(sceneName: n), ct) + ), + ]; + + await Task.WhenAll([version, stats, .. perScene.Cast()]); + _ = version.Result.ObsVersion; + } + + internal static async Task LowLevelAsync(ObsWebSocketClient client, CancellationToken ct) + { + GetVersionResponseData? v = await client.CallAsync( + "GetVersion", + null, + cancellationToken: ct + ); + + System.Text.Json.JsonElement? raw = + await client.CallAsyncValue( + "SomeNewRequest", + new { someField = 1 }, + cancellationToken: ct + ); + + List> results = await client.CallBatchAsync( + [new BatchRequestItem("GetVersion", null), new BatchRequestItem("GetStats", null)], + executionType: RequestBatchExecutionType.SerialRealtime, + cancellationToken: ct + ); + + foreach (RequestResponsePayload result in results) + { + _ = result.GetData(); + } + + _ = $"{v?.ObsVersion} {raw}"; + } + internal static void HostIntegration( Microsoft.Extensions.Hosting.IHostApplicationBuilder builder ) diff --git a/README.md b/README.md index 2d8ebef..0c9d1bc 100644 --- a/README.md +++ b/README.md @@ -321,11 +321,111 @@ batch.Add("GetStats"); batch.Add("SetInputSettings", myJsonElement); ``` -> `RequestBatchExecutionType.Parallel` is best avoided when you care about the results. OBS collects -> them in completion order but labels them from the submission order, so every result carries -> another request's `responseData` and `requestStatus`. That happens before the response leaves OBS, -> so it cannot be corrected here; references refuse to resolve on such a batch and say why. See -> [#16](https://github.com/Agash/ObsWebSocket/issues/16). +### Running requests in parallel + +`RequestBatchExecutionType.Parallel` works, but OBS mislabels what comes back. It collects results +in completion order and labels them from the submission order, so on any one row the +`requestType` and `requestId` belong to a different request than the `requestStatus` and +`responseData` beside them. That happens before the response leaves OBS, so it cannot be corrected +here. See [#16](https://github.com/Agash/ObsWebSocket/issues/16). + +Only the labelling is wrong. `requestStatus` and `responseData` come from the same object, so each +row's status does belong to the payload beside it; it is the `requestType` and `requestId` on that +row that name a different request. `Get` and the indexer therefore throw rather than hand back data +under the wrong reference, and `TryGet` reports `false`. + +Nothing is lost, though, and `Raw` still reaches all of it. `GetData` reads the payload without +consulting the label, so every response is recoverable as a set: + +```csharp +BatchResults results = await client.CallBatchAsync( + batch, executionType: RequestBatchExecutionType.Parallel, haltOnFailure: false, cancellationToken: ct); + +foreach (RequestResponsePayload row in results.Raw) +{ + if (!row.RequestStatus.Result) + { + Console.WriteLine($"one request failed with {row.RequestStatus.Code}"); + continue; // the code is right, the requestType naming it is not + } + + // Correct data, from one of the requests in the batch. Which one is not knowable. + GetSceneItemListResponseData? data = row.GetData(); +} +``` + +That is usable when the batch is homogeneous and the order does not matter, when the payload +identifies itself, or when you only need the values in aggregate. It is not usable when you need to +know which request an answer came from. + +Anything that does not depend on which row is which stays exact: + +```csharp +ObsBatchBuilder batch = new(); +foreach (string input in inputs) +{ + _ = batch.Inputs.SetInputMute(new(inputName: input, inputMuted: true)); +} + +BatchResults results = await client.CallBatchAsync( + batch, executionType: RequestBatchExecutionType.Parallel, cancellationToken: ct); + +bool everythingWorked = results.AllSucceeded(); // reliable: order does not change the verdict +int failureCount = results.GetFailures().Count(); // reliable count, unreliable names +``` + +So `Parallel` suits a set of writes you want applied as fast as possible, where you only need to +know whether they all took. It does not suit reading anything back. + +When you need results attributed, use concurrent requests rather than a parallel batch. The client +multiplexes on the request id, so anything in flight at once is matched back to its own caller: + +```csharp +Task version = client.General.GetVersionAsync(ct); +Task stats = client.General.GetStatsAsync(ct); +Task[] perScene = +[ + .. sceneNames.Select(n => client.SceneItems.GetSceneItemListAsync(new(sceneName: n), ct)), +]; + +await Task.WhenAll([version, stats, .. perScene.Cast()]); + +Console.WriteLine(version.Result.ObsVersion); // each result belongs to its own request +``` + +That costs one round trip per request rather than one for the set. Use a serial batch when the round +trip is what you are saving, and concurrent requests when you need the answers attributed. + +## Dropping to the low level + +Nothing above is a wall. Every generated request is a thin wrapper over the same primitives, and +they stay available for a request this build does not model, an OBS newer than this library, or a +vendor plugin: + +```csharp +// A request with a reference type response. +GetVersionResponseData? v = await client.CallAsync("GetVersion", null, cancellationToken: ct); + +// A value type response, JsonElement included. CallAsync is constrained to classes, so a struct +// response goes through CallAsyncValue. +JsonElement? raw = await client.CallAsyncValue( + "SomeNewRequest", new { someField = 1 }, cancellationToken: ct); + +// A batch assembled by hand, without the typed builder. +List> results = await client.CallBatchAsync( + [new BatchRequestItem("GetVersion", null), new BatchRequestItem("GetStats", null)], + executionType: RequestBatchExecutionType.SerialRealtime, + cancellationToken: ct); + +foreach (RequestResponsePayload result in results) +{ + GetVersionResponseData? data = result.GetData(); +} +``` + +The same applies to events and enums: `client.SceneCreated` remains alongside +`client.Scenes.SceneCreated`, and `ToWireValue()` / `FromWireValue()` convert an enum to and from +the protocol string when you are building a payload by hand. ## Protocol types @@ -373,17 +473,9 @@ This covers the enums the protocol declares. `mediaState`, `monitorType`, `scene their values, so they stay strings rather than being given an enum this library would have to keep correct by hand. -**Dropping to the wire.** Nothing above is a wall. `ToWireValue()` and `FromWireValue()` convert -either way, the wire values remain as `const` strings, and `CallAsync` sends a request the generated -surface does not model at all: - -```csharp -string wire = MediaInputAction.Restart.ToWireValue(); // OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART - -// CallAsync for a reference type response, CallAsyncValue for a value type such as JsonElement. -JsonElement? raw = await client.CallAsyncValue( - "SomeNewRequest", new { someField = 1 }, cancellationToken: ct); -``` +The wire values also remain as `const` strings on `ObsOutputState` and `ObsMediaInputAction`, and +`ToWireValue()` converts an enum back, for payloads built by hand. See +[Dropping to the low level](#dropping-to-the-low-level). ## Host integration From f9f5fe69a1b31f7fd166d9e83a840bb775fea6f1 Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 26 Aug 2026 13:56:17 +0200 Subject: [PATCH 07/13] docs: warn that mixed types in a parallel batch are unrecoverable Reading a payload as the wrong record throws on JSON, so probing types works there, but MessagePack maps by key name and quietly returns an object with every unmatched property left at its default. A reading of cpu=0.00 is indistinguishable from a real one, so a heterogeneous parallel batch cannot be sorted out afterwards on that transport. --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0c9d1bc..01cbb35 100644 --- a/README.md +++ b/README.md @@ -354,9 +354,20 @@ foreach (RequestResponsePayload row in results.Raw) } ``` -That is usable when the batch is homogeneous and the order does not matter, when the payload -identifies itself, or when you only need the values in aggregate. It is not usable when you need to -know which request an answer came from. +That works when every request in the batch returns the **same** type, so it does not matter which +row is which, and when the order is not what you needed. + +A parallel batch of **different** request types is a different matter, and the transport decides +whether it is merely awkward or actively unsafe: + +- On JSON, a payload read as the wrong record throws `ObsWebSocketSerializationException`, so you + can try each type you expect and let the mismatch tell you. Ugly, but sound. +- On MessagePack it is **not detectable**. The format maps by key name, so reading a payload as the + wrong record quietly leaves every unmatched property at its default and returns an object. A + reading of `cpu=0.00, memory=0.0` is indistinguishable from a genuine one. + +So do not mix request types in a parallel batch and expect to sort the results out afterwards. Use a +serial batch, or concurrent requests. Anything that does not depend on which row is which stays exact: From a68ddaf20681bd11d8088dcf7f6d413fb8c015af Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 26 Aug 2026 14:09:33 +0200 Subject: [PATCH 08/13] feat(core): reject a batch payload read as the wrong record Reading a payload as a record it did not come from was silent. On MessagePack the format maps by key name, so every unmatched property was left at its default and a fabricated reading of cpu=0.00 looked genuine. JSON was no better for the 42 of 72 response records that have no required member for it to miss. Codegen emits the field names OBS sends per record, and GetData checks a payload carries at least one of them before deserializing. Response records almost never share field names, so this catches the mistake on both transports and they now fail alike. It rejects rather than identifies: partial overlap still passes, and the five shapes shared by more than one record cannot be told apart. Those records are field for field identical, so reading one as another gives the right values, and a test pins that so the check cannot regress it. --- .../Generation/Emitter.PayloadSchema.cs | 102 +++++++++++++++++ .../Generation/ProtocolCodeGenerator.cs | 1 + ObsWebSocket.Core/BatchResultExtensions.cs | 2 + .../ObsWebSocketPayloadSchema.g.cs | 99 +++++++++++++++++ .../Serialization/PayloadShape.cs | 104 ++++++++++++++++++ ObsWebSocket.Tests/PayloadShapeTests.cs | 77 +++++++++++++ README.md | 24 ++-- 7 files changed, 398 insertions(+), 11 deletions(-) create mode 100644 ObsWebSocket.Codegen.Tasks/Generation/Emitter.PayloadSchema.cs create mode 100644 ObsWebSocket.Core/Generated/Serialization/ObsWebSocketPayloadSchema.g.cs create mode 100644 ObsWebSocket.Core/Serialization/PayloadShape.cs create mode 100644 ObsWebSocket.Tests/PayloadShapeTests.cs diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.PayloadSchema.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.PayloadSchema.cs new file mode 100644 index 0000000..52216ac --- /dev/null +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.PayloadSchema.cs @@ -0,0 +1,102 @@ +// ObsWebSocket.Codegen.Tasks/Generation/Emitter.PayloadSchema.cs +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace ObsWebSocket.Codegen.Tasks.Generation; + +/// +/// Emits the wire keys each response record expects, so a payload can be checked against the type +/// it is about to be read as. +/// +internal static partial class Emitter +{ + /// + /// Generates a lookup from response record name to the field names OBS sends for it. + /// + /// + /// Reading a payload as the wrong record is silent on MessagePack, which maps by key name and + /// leaves everything unmatched at its default, and silent on JSON too for the records that + /// happen to have no required member. Response records almost never share field names, so + /// checking that a payload carries at least one key the target record knows catches the + /// mistake on both transports. + /// + /// The source production context. + /// The parsed protocol definition. + public static void GeneratePayloadSchema( + SourceProductionContext context, + ProtocolDefinition protocol + ) + { + if (protocol.Requests is null || protocol.Requests.Count == 0) + { + return; + } + + StringBuilder builder = BuildSourceHeader("// Wire keys per response record"); + builder.AppendLine("using System;"); + builder.AppendLine("using System.Collections.Generic;"); + builder.AppendLine(); + builder.AppendLine("namespace ObsWebSocket.Core.Serialization;"); + builder.AppendLine(); + builder.AppendLine("/// "); + builder.AppendLine( + "/// The field names OBS sends for each response record, used to reject a payload being" + ); + builder.AppendLine("/// read as a record it did not come from."); + builder.AppendLine("/// "); + builder.AppendLine("internal static class ObsWebSocketPayloadSchema"); + builder.AppendLine("{"); + builder.AppendLine( + " private static readonly Dictionary s_keys = new(StringComparer.Ordinal)" + ); + builder.AppendLine(" {"); + + foreach (RequestDefinition reqDef in protocol.Requests) + { + List fields = reqDef.ResponseFields ?? []; + if (fields.Count == 0) + { + continue; + } + + // Nested fields arrive as "parent.child"; only the outermost name is a map key. + HashSet keys = new(StringComparer.Ordinal); + foreach (FieldDefinition field in fields) + { + string name = field.ValueName; + int dot = name.IndexOf('.'); + _ = keys.Add(dot >= 0 ? name.Substring(0, dot) : name); + } + + string recordName = $"{SanitizeIdentifier(reqDef.RequestType)}ResponseData"; + string list = string.Join( + ", ", + keys.OrderBy(k => k, StringComparer.Ordinal).Select(k => $"\"{k}\"") + ); + builder.AppendLine($" [\"{recordName}\"] = [{list}],"); + } + + builder.AppendLine(" };"); + builder.AppendLine(); + builder.AppendLine(" /// "); + builder.AppendLine( + " /// The keys a response record expects, or an empty span when the record is unknown" + ); + builder.AppendLine(" /// to the schema, in which case no check is possible."); + builder.AppendLine(" /// "); + builder.AppendLine( + " /// The record's type name." + ); + builder.AppendLine(" public static string[] KnownKeys(string responseTypeName) =>"); + builder.AppendLine( + " s_keys.TryGetValue(responseTypeName, out string[]? keys) ? keys : [];" + ); + builder.AppendLine("}"); + + context.AddSource( + "ObsWebSocketPayloadSchema.g.cs", + SourceText.From(builder.ToString(), Encoding.UTF8) + ); + } +} diff --git a/ObsWebSocket.Codegen.Tasks/Generation/ProtocolCodeGenerator.cs b/ObsWebSocket.Codegen.Tasks/Generation/ProtocolCodeGenerator.cs index 65192a7..edd2006 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/ProtocolCodeGenerator.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/ProtocolCodeGenerator.cs @@ -40,6 +40,7 @@ IReadOnlyList Diagnostics Emitter.GenerateEnums(context, protocol); Emitter.GenerateRequestDtos(context, protocol); Emitter.GenerateResponseDtos(context, protocol); + Emitter.GeneratePayloadSchema(context, protocol); Emitter.GenerateClientExtensions(context, protocol); Emitter.GenerateEventPayloads(context, protocol); Emitter.GenerateEventArgs(context, protocol); diff --git a/ObsWebSocket.Core/BatchResultExtensions.cs b/ObsWebSocket.Core/BatchResultExtensions.cs index c7e40c3..9c9e1ad 100644 --- a/ObsWebSocket.Core/BatchResultExtensions.cs +++ b/ObsWebSocket.Core/BatchResultExtensions.cs @@ -45,6 +45,8 @@ public static class BatchResultExtensions return already; } + PayloadShape.EnsurePlausible(result.ResponseData); + // The MessagePack transport hands back the raw payload bytes, the JSON transport a // JsonElement, so a batch result has to be read according to which produced it. if (result.ResponseData is ReadOnlyMemory packed) diff --git a/ObsWebSocket.Core/Generated/Serialization/ObsWebSocketPayloadSchema.g.cs b/ObsWebSocket.Core/Generated/Serialization/ObsWebSocketPayloadSchema.g.cs new file mode 100644 index 0000000..f782d10 --- /dev/null +++ b/ObsWebSocket.Core/Generated/Serialization/ObsWebSocketPayloadSchema.g.cs @@ -0,0 +1,99 @@ +// +// Wire keys per response record +#nullable enable + +using System; +using System.Collections.Generic; + +namespace ObsWebSocket.Core.Serialization; + +/// +/// The field names OBS sends for each response record, used to reject a payload being +/// read as a record it did not come from. +/// +internal static class ObsWebSocketPayloadSchema +{ + private static readonly Dictionary s_keys = new(StringComparer.Ordinal) + { + ["GetCanvasListResponseData"] = ["canvases"], + ["GetPersistentDataResponseData"] = ["slotValue"], + ["GetSceneCollectionListResponseData"] = ["currentSceneCollectionName", "sceneCollections"], + ["GetProfileListResponseData"] = ["currentProfileName", "profiles"], + ["GetProfileParameterResponseData"] = ["defaultParameterValue", "parameterValue"], + ["GetVideoSettingsResponseData"] = ["baseHeight", "baseWidth", "fpsDenominator", "fpsNumerator", "outputHeight", "outputWidth"], + ["GetStreamServiceSettingsResponseData"] = ["streamServiceSettings", "streamServiceType"], + ["GetRecordDirectoryResponseData"] = ["recordDirectory"], + ["GetSourceFilterKindListResponseData"] = ["sourceFilterKinds"], + ["GetSourceFilterListResponseData"] = ["filters"], + ["GetSourceFilterDefaultSettingsResponseData"] = ["defaultFilterSettings"], + ["GetSourceFilterResponseData"] = ["filterEnabled", "filterIndex", "filterKind", "filterSettings"], + ["GetVersionResponseData"] = ["availableRequests", "obsVersion", "obsWebSocketVersion", "platform", "platformDescription", "rpcVersion", "supportedImageFormats"], + ["GetStatsResponseData"] = ["activeFps", "availableDiskSpace", "averageFrameRenderTime", "cpuUsage", "memoryUsage", "outputSkippedFrames", "outputTotalFrames", "renderSkippedFrames", "renderTotalFrames", "webSocketSessionIncomingMessages", "webSocketSessionOutgoingMessages"], + ["CallVendorRequestResponseData"] = ["requestType", "responseData", "vendorName"], + ["GetHotkeyListResponseData"] = ["hotkeys"], + ["GetInputListResponseData"] = ["inputs"], + ["GetInputKindListResponseData"] = ["inputKinds"], + ["GetSpecialInputsResponseData"] = ["desktop1", "desktop2", "mic1", "mic2", "mic3", "mic4"], + ["CreateInputResponseData"] = ["inputUuid", "sceneItemId"], + ["GetInputDefaultSettingsResponseData"] = ["defaultInputSettings"], + ["GetInputSettingsResponseData"] = ["inputKind", "inputSettings"], + ["GetInputMuteResponseData"] = ["inputMuted"], + ["ToggleInputMuteResponseData"] = ["inputMuted"], + ["GetInputVolumeResponseData"] = ["inputVolumeDb", "inputVolumeMul"], + ["GetInputAudioBalanceResponseData"] = ["inputAudioBalance"], + ["GetInputAudioSyncOffsetResponseData"] = ["inputAudioSyncOffset"], + ["GetInputAudioMonitorTypeResponseData"] = ["monitorType"], + ["GetInputAudioTracksResponseData"] = ["inputAudioTracks"], + ["GetInputDeinterlaceModeResponseData"] = ["inputDeinterlaceMode"], + ["GetInputDeinterlaceFieldOrderResponseData"] = ["inputDeinterlaceFieldOrder"], + ["GetInputPropertiesListPropertyItemsResponseData"] = ["propertyItems"], + ["GetMediaInputStatusResponseData"] = ["mediaCursor", "mediaDuration", "mediaState"], + ["GetVirtualCamStatusResponseData"] = ["outputActive"], + ["ToggleVirtualCamResponseData"] = ["outputActive"], + ["GetReplayBufferStatusResponseData"] = ["outputActive"], + ["ToggleReplayBufferResponseData"] = ["outputActive"], + ["GetLastReplayBufferReplayResponseData"] = ["savedReplayPath"], + ["GetOutputListResponseData"] = ["outputs"], + ["GetOutputStatusResponseData"] = ["outputActive", "outputBytes", "outputCongestion", "outputDuration", "outputReconnecting", "outputSkippedFrames", "outputTimecode", "outputTotalFrames"], + ["ToggleOutputResponseData"] = ["outputActive"], + ["GetOutputSettingsResponseData"] = ["outputSettings"], + ["GetRecordStatusResponseData"] = ["outputActive", "outputBytes", "outputDuration", "outputPaused", "outputTimecode"], + ["ToggleRecordResponseData"] = ["outputActive"], + ["StopRecordResponseData"] = ["outputPath"], + ["GetSceneItemListResponseData"] = ["sceneItems"], + ["GetGroupSceneItemListResponseData"] = ["sceneItems"], + ["GetSceneItemIdResponseData"] = ["sceneItemId"], + ["GetSceneItemSourceResponseData"] = ["sourceName", "sourceUuid"], + ["CreateSceneItemResponseData"] = ["sceneItemId"], + ["DuplicateSceneItemResponseData"] = ["sceneItemId"], + ["GetSceneItemTransformResponseData"] = ["sceneItemTransform"], + ["GetSceneItemEnabledResponseData"] = ["sceneItemEnabled"], + ["GetSceneItemLockedResponseData"] = ["sceneItemLocked"], + ["GetSceneItemIndexResponseData"] = ["sceneItemIndex"], + ["GetSceneItemBlendModeResponseData"] = ["sceneItemBlendMode"], + ["GetSceneListResponseData"] = ["currentPreviewSceneName", "currentPreviewSceneUuid", "currentProgramSceneName", "currentProgramSceneUuid", "scenes"], + ["GetGroupListResponseData"] = ["groups"], + ["GetCurrentProgramSceneResponseData"] = ["currentProgramSceneName", "currentProgramSceneUuid", "sceneName", "sceneUuid"], + ["GetCurrentPreviewSceneResponseData"] = ["currentPreviewSceneName", "currentPreviewSceneUuid", "sceneName", "sceneUuid"], + ["CreateSceneResponseData"] = ["sceneUuid"], + ["GetSceneSceneTransitionOverrideResponseData"] = ["transitionDuration", "transitionName"], + ["GetSourceActiveResponseData"] = ["videoActive", "videoShowing"], + ["GetSourceScreenshotResponseData"] = ["imageData"], + ["GetStreamStatusResponseData"] = ["outputActive", "outputBytes", "outputCongestion", "outputDuration", "outputReconnecting", "outputSkippedFrames", "outputTimecode", "outputTotalFrames"], + ["ToggleStreamResponseData"] = ["outputActive"], + ["GetTransitionKindListResponseData"] = ["transitionKinds"], + ["GetSceneTransitionListResponseData"] = ["currentSceneTransitionKind", "currentSceneTransitionName", "currentSceneTransitionUuid", "transitions"], + ["GetCurrentSceneTransitionResponseData"] = ["transitionConfigurable", "transitionDuration", "transitionFixed", "transitionKind", "transitionName", "transitionSettings", "transitionUuid"], + ["GetCurrentSceneTransitionCursorResponseData"] = ["transitionCursor"], + ["GetStudioModeEnabledResponseData"] = ["studioModeEnabled"], + ["GetMonitorListResponseData"] = ["monitors"], + }; + + /// + /// The keys a response record expects, or an empty span when the record is unknown + /// to the schema, in which case no check is possible. + /// + /// The record's type name. + public static string[] KnownKeys(string responseTypeName) => + s_keys.TryGetValue(responseTypeName, out string[]? keys) ? keys : []; +} diff --git a/ObsWebSocket.Core/Serialization/PayloadShape.cs b/ObsWebSocket.Core/Serialization/PayloadShape.cs new file mode 100644 index 0000000..e1ee279 --- /dev/null +++ b/ObsWebSocket.Core/Serialization/PayloadShape.cs @@ -0,0 +1,104 @@ +using System.Text.Json; +using MessagePack; + +namespace ObsWebSocket.Core.Serialization; + +/// +/// Checks a batch payload against the record it is about to be read as. +/// +/// +/// Reading a payload as the wrong record is silent on MessagePack, which maps by key name and +/// leaves everything unmatched at its default, and silent on JSON too for the response records that +/// have no required member, which is most of them. Response records almost never share field +/// names, so a payload carrying none of the target's keys did not come from that request. +/// +/// This rejects rather than identifies. Five response shapes are shared by more than one request, +/// but those records are field for field identical, so reading one as another yields the right +/// values anyway. A payload that overlaps the target only partly still passes, which is the +/// remaining gap. +/// +/// +internal static class PayloadShape +{ + /// + /// Throws when a payload carries none of the fields expects. + /// + /// The record the payload is about to be read as. + /// The transport shaped payload. + /// Thrown when the payload cannot be that record. + public static void EnsurePlausible(object responseData) + where TResponse : class + { + string[] expected = ObsWebSocketPayloadSchema.KnownKeys(typeof(TResponse).Name); + if (expected.Length == 0) + { + return; + } + + bool anyMatch = responseData switch + { + JsonElement json => MatchesJson(json, expected), + ReadOnlyMemory packed => MatchesMsgPack(packed, expected), + _ => true, + }; + + if (!anyMatch) + { + throw new ObsWebSocketSerializationException( + $"This payload carries none of the fields {typeof(TResponse).Name} expects " + + $"({string.Join(", ", expected)}), so it came from a different request. Under " + + "RequestBatchExecutionType.Parallel OBS labels each result with another " + + "request's type, which is the usual cause." + ); + } + } + + private static bool MatchesJson(JsonElement json, string[] expected) + { + if (json.ValueKind != JsonValueKind.Object) + { + return true; + } + + bool sawAny = false; + foreach (JsonProperty property in json.EnumerateObject()) + { + sawAny = true; + if (Array.IndexOf(expected, property.Name) >= 0) + { + return true; + } + } + + // An empty object tells us nothing either way. + return !sawAny; + } + + private static bool MatchesMsgPack(ReadOnlyMemory packed, string[] expected) + { + MessagePackReader reader = new(packed); + if (reader.NextMessagePackType != MessagePackType.Map) + { + return true; + } + + int count = reader.ReadMapHeader(); + if (count == 0) + { + return true; + } + + for (int i = 0; i < count; i++) + { + string? key = reader.ReadString(); + if (key is not null && Array.IndexOf(expected, key) >= 0) + { + return true; + } + + reader.Skip(); + } + + return false; + } +} diff --git a/ObsWebSocket.Tests/PayloadShapeTests.cs b/ObsWebSocket.Tests/PayloadShapeTests.cs new file mode 100644 index 0000000..5b652b8 --- /dev/null +++ b/ObsWebSocket.Tests/PayloadShapeTests.cs @@ -0,0 +1,77 @@ +using System.Text.Json; +using MessagePack; +using ObsWebSocket.Core; +using ObsWebSocket.Core.Protocol; +using ObsWebSocket.Core.Protocol.Responses; +using ObsWebSocket.Core.Serialization; + +namespace ObsWebSocket.Tests; + +/// +/// Reading a batch payload as the wrong response record used to be silent on MessagePack, which +/// maps by key name and leaves everything unmatched at its default, so a fabricated reading of +/// cpu=0 looked genuine. JSON was silent too for the response records with no required member, +/// which is most of them. +/// +[TestClass] +public sealed class PayloadShapeTests +{ + private static RequestResponsePayload Row(object payload) => + new("GetVersion", "id", new RequestStatus(true, 100), payload); + + [TestMethod] + public void MsgPack_ReadingAPayloadAsTheWrongRecord_Throws() + { + GetVersionResponseData version = new() { RpcVersion = 1, ObsVersion = "32.2.2" }; + byte[] packed = MessagePackSerializer.Serialize( + version, + MsgPackMessageSerializer.s_msgPackOptions + ); + + RequestResponsePayload row = Row(new ReadOnlyMemory(packed)); + + _ = Assert.ThrowsExactly(() => + row.GetData() + ); + } + + [TestMethod] + public void Json_ReadingAPayloadAsTheWrongRecord_Throws() + { + // GetSceneListResponseData has no required member, so System.Text.Json alone accepted this. + using JsonDocument doc = JsonDocument.Parse("""{"cpuUsage":0.5,"memoryUsage":300.0}"""); + RequestResponsePayload row = Row(doc.RootElement.Clone()); + + _ = Assert.ThrowsExactly(() => + row.GetData() + ); + } + + [TestMethod] + public void ReadingAPayloadAsItsOwnRecord_StillWorks() + { + GetVersionResponseData version = new() { RpcVersion = 1, ObsVersion = "32.2.2" }; + byte[] packed = MessagePackSerializer.Serialize( + version, + MsgPackMessageSerializer.s_msgPackOptions + ); + + GetVersionResponseData? read = Row(new ReadOnlyMemory(packed)) + .GetData(); + + Assert.AreEqual("32.2.2", read?.ObsVersion); + } + + [TestMethod] + public void RecordsWithAnIdenticalShape_AreStillInterchangeable() + { + // GetInputMute and ToggleInputMute are both a single inputMuted field, so reading one as + // the other gives the right value. The check must not reject that. + using JsonDocument doc = JsonDocument.Parse("""{"inputMuted":true}"""); + RequestResponsePayload row = Row(doc.RootElement.Clone()); + + ToggleInputMuteResponseData? read = row.GetData(); + + Assert.IsTrue(read?.InputMuted); + } +} diff --git a/README.md b/README.md index 01cbb35..af5ec97 100644 --- a/README.md +++ b/README.md @@ -357,17 +357,19 @@ foreach (RequestResponsePayload row in results.Raw) That works when every request in the batch returns the **same** type, so it does not matter which row is which, and when the order is not what you needed. -A parallel batch of **different** request types is a different matter, and the transport decides -whether it is merely awkward or actively unsafe: - -- On JSON, a payload read as the wrong record throws `ObsWebSocketSerializationException`, so you - can try each type you expect and let the mismatch tell you. Ugly, but sound. -- On MessagePack it is **not detectable**. The format maps by key name, so reading a payload as the - wrong record quietly leaves every unmatched property at its default and returns an object. A - reading of `cpu=0.00, memory=0.0` is indistinguishable from a genuine one. - -So do not mix request types in a parallel batch and expect to sort the results out afterwards. Use a -serial batch, or concurrent requests. +A parallel batch of **different** request types is harder, because nothing on a row tells you which +type its payload really is. `GetData` will not invent an answer, though: it checks the payload +against the fields `T` expects and throws `ObsWebSocketSerializationException` when the payload +carries none of them. So you can try each type you expect and let the mismatch tell you. + +That check rejects rather than identifies. Two records that share field names cannot be told apart +this way, and a payload overlapping the target only partly still passes with the rest of the +properties left at their defaults. Where two records are field for field identical, which happens +for five shapes including `GetInputMute` and `ToggleInputMute`, reading one as the other gives the +right values anyway. + +So a heterogeneous parallel batch is possible to unpick but never reliable. Use a serial batch, or +concurrent requests. Anything that does not depend on which row is which stays exact: From 10b154087d00d19da9656f2bce49b8fe4df8be13 Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 26 Aug 2026 14:49:40 +0200 Subject: [PATCH 09/13] feat(core)!: stop responses and events being nullable by default Every read of a response went through a "!" or a "?.", 73 of them in the example alone. The cause was that the protocol never marks a response or event field optional: all 152 response fields and all 149 event fields carry a null valueOptional, so the generator fell back to the C# type and made every string and array nullable while value types became required. That split came from C#, not from OBS. The prose is where the protocol records a genuinely absent field, and it is the same signal that fixed GetMediaInputStatus. Strings and arrays are non-nullable unless the description says the field can be null. Response records go from 57% nullable to 16%, events to 7%, and every one that remains is either documented as null or a settings blob where nullable distinguishes absent from empty. The hand-written stubs had no protocol definition to derive from, so they were checked against the payloads a live OBS 32.2.2 actually returns. That found isGroup really is null for a non-group item, and that crops, alignments and indices arrive as integers rather than floats. Safe against older OBS: no field has ever been added to a request or event that already existed, so an older build never sends a partial payload for a request it supports, it rejects the request instead. A side effect worth naming: MessagePack does enforce required members, so both transports now reject a payload missing them. The earlier finding that MessagePack is never strict held only for records that had no required member to miss. Also fixes a README example that could not have worked: CallAsync request data must be a JsonElement or a type the serializer context knows, and an anonymous object throws at runtime. The compile check could not catch it because it only compiles. Renames ObsWebSocketClientHelpers to ObsWebSocketClientOperations, since it holds the client level operations rather than helpers. --- .../Generation/Emitter.DtoGeneration.cs | 37 +++++-- .../Generation/Emitter.WaitForEvent.cs | 4 +- .../ObsWebSocketClient.WaitForEvent.g.cs | 2 +- .../Events/CanvasCreated.EventPayload.g.cs | 7 +- .../CanvasNameChanged.EventPayload.g.cs | 9 +- .../Events/CanvasRemoved.EventPayload.g.cs | 7 +- ...rrentPreviewSceneChanged.EventPayload.g.cs | 7 +- .../CurrentProfileChanged.EventPayload.g.cs | 5 +- .../CurrentProfileChanging.EventPayload.g.cs | 5 +- ...rrentProgramSceneChanged.EventPayload.g.cs | 7 +- ...ntSceneCollectionChanged.EventPayload.g.cs | 5 +- ...tSceneCollectionChanging.EventPayload.g.cs | 5 +- ...ntSceneTransitionChanged.EventPayload.g.cs | 7 +- .../InputActiveStateChanged.EventPayload.g.cs | 6 +- ...InputAudioBalanceChanged.EventPayload.g.cs | 6 +- ...tAudioMonitorTypeChanged.EventPayload.g.cs | 9 +- ...utAudioSyncOffsetChanged.EventPayload.g.cs | 6 +- .../InputAudioTracksChanged.EventPayload.g.cs | 7 +- .../Events/InputCreated.EventPayload.g.cs | 10 +- .../InputMuteStateChanged.EventPayload.g.cs | 6 +- .../Events/InputNameChanged.EventPayload.g.cs | 9 +- .../Events/InputRemoved.EventPayload.g.cs | 7 +- .../InputSettingsChanged.EventPayload.g.cs | 7 +- .../InputShowStateChanged.EventPayload.g.cs | 6 +- .../InputVolumeChanged.EventPayload.g.cs | 6 +- .../InputVolumeMeters.EventPayload.g.cs | 5 +- ...ediaInputActionTriggered.EventPayload.g.cs | 7 +- .../MediaInputPlaybackEnded.EventPayload.g.cs | 7 +- ...ediaInputPlaybackStarted.EventPayload.g.cs | 7 +- .../ProfileListChanged.EventPayload.g.cs | 5 +- .../RecordFileChanged.EventPayload.g.cs | 5 +- .../ReplayBufferSaved.EventPayload.g.cs | 5 +- ...eneCollectionListChanged.EventPayload.g.cs | 5 +- .../Events/SceneCreated.EventPayload.g.cs | 6 +- .../Events/SceneItemCreated.EventPayload.g.cs | 10 +- ...neItemEnableStateChanged.EventPayload.g.cs | 6 +- .../SceneItemListReindexed.EventPayload.g.cs | 9 +- ...ceneItemLockStateChanged.EventPayload.g.cs | 6 +- .../Events/SceneItemRemoved.EventPayload.g.cs | 10 +- .../SceneItemSelected.EventPayload.g.cs | 6 +- ...ceneItemTransformChanged.EventPayload.g.cs | 6 +- .../Events/SceneListChanged.EventPayload.g.cs | 5 +- .../Events/SceneNameChanged.EventPayload.g.cs | 9 +- .../Events/SceneRemoved.EventPayload.g.cs | 6 +- .../SceneTransitionEnded.EventPayload.g.cs | 7 +- .../SceneTransitionStarted.EventPayload.g.cs | 7 +- ...ceneTransitionVideoEnded.EventPayload.g.cs | 7 +- .../Events/ScreenshotSaved.EventPayload.g.cs | 5 +- .../SourceFilterCreated.EventPayload.g.cs | 8 +- ...FilterEnableStateChanged.EventPayload.g.cs | 6 +- ...ourceFilterListReindexed.EventPayload.g.cs | 7 +- .../SourceFilterNameChanged.EventPayload.g.cs | 9 +- .../SourceFilterRemoved.EventPayload.g.cs | 7 +- ...rceFilterSettingsChanged.EventPayload.g.cs | 7 +- .../Events/VendorEvent.EventPayload.g.cs | 7 +- .../Responses/CallVendorRequest.Response.g.cs | 7 +- .../Responses/CreateInput.Response.g.cs | 4 +- .../Responses/CreateScene.Response.g.cs | 5 +- .../Responses/GetCanvasList.Response.g.cs | 5 +- .../GetCurrentPreviewScene.Response.g.cs | 11 +- .../GetCurrentProgramScene.Response.g.cs | 11 +- .../GetCurrentSceneTransition.Response.g.cs | 8 +- .../Responses/GetGroupList.Response.g.cs | 5 +- .../GetGroupSceneItemList.Response.g.cs | 5 +- .../Responses/GetHotkeyList.Response.g.cs | 5 +- .../GetInputAudioMonitorType.Response.g.cs | 5 +- ...etInputDeinterlaceFieldOrder.Response.g.cs | 5 +- .../GetInputDeinterlaceMode.Response.g.cs | 5 +- .../Responses/GetInputKindList.Response.g.cs | 5 +- .../Responses/GetInputList.Response.g.cs | 5 +- ...tPropertiesListPropertyItems.Response.g.cs | 5 +- .../Responses/GetInputSettings.Response.g.cs | 5 +- .../GetLastReplayBufferReplay.Response.g.cs | 5 +- .../GetMediaInputStatus.Response.g.cs | 5 +- .../Responses/GetMonitorList.Response.g.cs | 5 +- .../Responses/GetOutputList.Response.g.cs | 5 +- .../Responses/GetOutputStatus.Response.g.cs | 4 +- .../Responses/GetProfileList.Response.g.cs | 7 +- .../GetRecordDirectory.Response.g.cs | 5 +- .../Responses/GetRecordStatus.Response.g.cs | 4 +- .../GetSceneCollectionList.Response.g.cs | 7 +- .../GetSceneItemBlendMode.Response.g.cs | 5 +- .../Responses/GetSceneItemList.Response.g.cs | 5 +- .../GetSceneItemSource.Response.g.cs | 7 +- .../Responses/GetSceneList.Response.g.cs | 5 +- .../GetSceneTransitionList.Response.g.cs | 5 +- .../Responses/GetSourceFilter.Response.g.cs | 4 +- .../GetSourceFilterKindList.Response.g.cs | 5 +- .../GetSourceFilterList.Response.g.cs | 5 +- .../GetSourceScreenshot.Response.g.cs | 5 +- .../Responses/GetSpecialInputs.Response.g.cs | 15 +-- .../GetStreamServiceSettings.Response.g.cs | 5 +- .../Responses/GetStreamStatus.Response.g.cs | 4 +- .../GetTransitionKindList.Response.g.cs | 5 +- .../Responses/GetVersion.Response.g.cs | 14 +-- .../Responses/StopRecord.Response.g.cs | 5 +- ObsWebSocket.Core/Groups/ConfigGroup.cs | 4 +- ObsWebSocket.Core/Groups/FiltersGroup.cs | 8 +- ObsWebSocket.Core/Groups/InputsGroup.cs | 8 +- ObsWebSocket.Core/Groups/OutputsGroup.cs | 4 +- ObsWebSocket.Core/Groups/TransitionsGroup.cs | 4 +- ...WebSocketClientOperations.Conveniences.cs} | 2 +- ...per.cs => ObsWebSocketClientOperations.cs} | 2 +- .../Protocol/Common/StubTypes.cs | 104 +++++++++--------- ObsWebSocket.Example/Worker.cs | 4 +- ObsWebSocket.Tests/BatchResultTests.cs | 12 +- ObsWebSocket.Tests/EventStreamTests.cs | 5 +- ObsWebSocket.Tests/ObsWebSocketClientTests.cs | 2 +- ObsWebSocket.Tests/PayloadShapeTests.cs | 4 +- ObsWebSocket.Tests/ReadmeCompileCheck.cs | 14 ++- ObsWebSocket.Tests/SerializerBehaviorTests.cs | 69 ++++++++++-- ObsWebSocket.Tests/TestUtils.cs | 19 ++++ ObsWebSocket.Tests/TypedSettingsTests.cs | 10 +- README.md | 10 +- 114 files changed, 546 insertions(+), 375 deletions(-) rename ObsWebSocket.Core/{ObsWebSocketClient.Helper.Convenience.cs => ObsWebSocketClientOperations.Conveniences.cs} (99%) rename ObsWebSocket.Core/{ObsWebSocketClient.Helper.cs => ObsWebSocketClientOperations.cs} (97%) diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs index 7e7c969..eb217ec 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs @@ -489,23 +489,36 @@ ProtocolDefinition protocol } else // Response, Event, Nested { - isConsideredRequired = !typeIsInherentlyNullable && !csharpType.EndsWith("?"); + // The protocol never marks a response or event field optional: all 152 + // response fields and all 149 event fields carry a null valueOptional. The + // only place it records that a field can be absent is the prose, which is + // therefore the signal, rather than whether C# happens to make the type + // nullable. Without this a string OBS always sends still arrives nullable and + // every read needs a "!". + bool proseAllowsNull = DescriptionAllowsNull( + associatedFieldDef.ValueDescription + ); - // Some fields are only ever null in a particular state, which the protocol - // records in the description rather than in valueOptional. Deserializing - // those into a non-nullable value type fails outright when it happens. - if ( - isConsideredRequired - && isValueType - && DescriptionAllowsNull(associatedFieldDef.ValueDescription) - ) + if (proseAllowsNull) { isConsideredRequired = false; } - - if (csharpType.StartsWith("List<") || csharpType.StartsWith("Dictionary<")) + else if (isValueType) { - isConsideredRequired = false; + isConsideredRequired = !csharpType.EndsWith("?"); + } + else + { + // Strings and arrays are always sent. A dictionary or a JsonElement is a + // settings bag that genuinely may not be there, so those stay nullable. + // The array mapper bakes the "?" into the type it returns, so a list has + // to have it stripped here rather than merely left off the suffix. + bool isList = csharpType.Contains("List<"); + isConsideredRequired = csharpType == "string" || isList; + if (isList && csharpType.EndsWith("?", StringComparison.Ordinal)) + { + csharpType = csharpType.Substring(0, csharpType.Length - 1); + } } } propertyNullableSuffix = diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.WaitForEvent.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.WaitForEvent.cs index 2c05508..f3e7460 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.WaitForEvent.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.WaitForEvent.cs @@ -46,7 +46,7 @@ ProtocolDefinition protocol "/// Contains generated helper methods for the ." ); builder.AppendLine("/// "); - builder.AppendLine("public static partial class ObsWebSocketClientHelpers"); + builder.AppendLine("public static partial class ObsWebSocketClientOperations"); builder.AppendLine("{"); // Generate the WaitForEventAsync method signature and documentation @@ -283,7 +283,7 @@ ProtocolDefinition protocol builder.AppendLine(" }"); // End WaitForEventAsync method // Close class and namespace - builder.AppendLine("}"); // End ObsWebSocketClientHelpers class + builder.AppendLine("}"); // End ObsWebSocketClientOperations class // File-scoped namespace is assumed, no closing brace needed here // Add the generated source file to the compilation diff --git a/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.WaitForEvent.g.cs b/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.WaitForEvent.g.cs index 79ea6b9..50db1a3 100644 --- a/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.WaitForEvent.g.cs +++ b/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.WaitForEvent.g.cs @@ -15,7 +15,7 @@ namespace ObsWebSocket.Core; // Add to ObsWebSocket.Core namespace /// /// Contains generated helper methods for the . /// -public static partial class ObsWebSocketClientHelpers +public static partial class ObsWebSocketClientOperations { /// /// Asynchronously waits for a specific OBS event of type that satisfies a predicate condition. diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CanvasCreated.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CanvasCreated.EventPayload.g.cs index b9c2593..67fa8c1 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CanvasCreated.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CanvasCreated.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record CanvasCreatedPayload /// [JsonPropertyName("canvasName")] [Key("canvasName")] - public string? CanvasName { get; init; } + public required string CanvasName { get; init; } /// /// UUID of the new canvas /// [JsonPropertyName("canvasUuid")] [Key("canvasUuid")] - public string? CanvasUuid { get; init; } + public required string CanvasUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public CanvasCreatedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CanvasCreatedPayload(string? canvasName = null, string? canvasUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CanvasCreatedPayload(string canvasName, string canvasUuid) { this.CanvasName = canvasName; this.CanvasUuid = canvasUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CanvasNameChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CanvasNameChanged.EventPayload.g.cs index 892daee..82dc87d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CanvasNameChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CanvasNameChanged.EventPayload.g.cs @@ -29,21 +29,21 @@ public sealed partial record CanvasNameChangedPayload /// [JsonPropertyName("canvasName")] [Key("canvasName")] - public string? CanvasName { get; init; } + public required string CanvasName { get; init; } /// /// UUID of the canvas /// [JsonPropertyName("canvasUuid")] [Key("canvasUuid")] - public string? CanvasUuid { get; init; } + public required string CanvasUuid { get; init; } /// /// Old name of the canvas /// [JsonPropertyName("oldCanvasName")] [Key("oldCanvasName")] - public string? OldCanvasName { get; init; } + public required string OldCanvasName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public CanvasNameChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CanvasNameChangedPayload(string? canvasUuid = null, string? oldCanvasName = null, string? canvasName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CanvasNameChangedPayload(string canvasUuid, string oldCanvasName, string canvasName) { this.CanvasUuid = canvasUuid; this.OldCanvasName = oldCanvasName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CanvasRemoved.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CanvasRemoved.EventPayload.g.cs index 871ecb0..dd9f151 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CanvasRemoved.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CanvasRemoved.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record CanvasRemovedPayload /// [JsonPropertyName("canvasName")] [Key("canvasName")] - public string? CanvasName { get; init; } + public required string CanvasName { get; init; } /// /// UUID of the removed canvas /// [JsonPropertyName("canvasUuid")] [Key("canvasUuid")] - public string? CanvasUuid { get; init; } + public required string CanvasUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public CanvasRemovedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CanvasRemovedPayload(string? canvasName = null, string? canvasUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CanvasRemovedPayload(string canvasName, string canvasUuid) { this.CanvasName = canvasName; this.CanvasUuid = canvasUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentPreviewSceneChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentPreviewSceneChanged.EventPayload.g.cs index 5617e41..3f44b74 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentPreviewSceneChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentPreviewSceneChanged.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record CurrentPreviewSceneChangedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene that was switched to /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public CurrentPreviewSceneChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CurrentPreviewSceneChangedPayload(string? sceneName = null, string? sceneUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CurrentPreviewSceneChangedPayload(string sceneName, string sceneUuid) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanged.EventPayload.g.cs index 1c0d6b3..f426b44 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanged.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record CurrentProfileChangedPayload /// [JsonPropertyName("profileName")] [Key("profileName")] - public string? ProfileName { get; init; } + public required string ProfileName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public CurrentProfileChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CurrentProfileChangedPayload(string? profileName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CurrentProfileChangedPayload(string profileName) { this.ProfileName = profileName; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanging.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanging.EventPayload.g.cs index 55d23d2..75d2175 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanging.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProfileChanging.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record CurrentProfileChangingPayload /// [JsonPropertyName("profileName")] [Key("profileName")] - public string? ProfileName { get; init; } + public required string ProfileName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public CurrentProfileChangingPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CurrentProfileChangingPayload(string? profileName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CurrentProfileChangingPayload(string profileName) { this.ProfileName = profileName; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProgramSceneChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProgramSceneChanged.EventPayload.g.cs index 78c6388..75b86f7 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProgramSceneChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentProgramSceneChanged.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record CurrentProgramSceneChangedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene that was switched to /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public CurrentProgramSceneChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CurrentProgramSceneChangedPayload(string? sceneName = null, string? sceneUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CurrentProgramSceneChangedPayload(string sceneName, string sceneUuid) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanged.EventPayload.g.cs index b9fbe31..739e6f7 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanged.EventPayload.g.cs @@ -31,7 +31,7 @@ public sealed partial record CurrentSceneCollectionChangedPayload /// [JsonPropertyName("sceneCollectionName")] [Key("sceneCollectionName")] - public string? SceneCollectionName { get; init; } + public required string SceneCollectionName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public CurrentSceneCollectionChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CurrentSceneCollectionChangedPayload(string? sceneCollectionName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CurrentSceneCollectionChangedPayload(string sceneCollectionName) { this.SceneCollectionName = sceneCollectionName; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanging.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanging.EventPayload.g.cs index a1b6c61..bbfd793 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanging.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneCollectionChanging.EventPayload.g.cs @@ -32,7 +32,7 @@ public sealed partial record CurrentSceneCollectionChangingPayload /// [JsonPropertyName("sceneCollectionName")] [Key("sceneCollectionName")] - public string? SceneCollectionName { get; init; } + public required string SceneCollectionName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -42,7 +42,8 @@ public CurrentSceneCollectionChangingPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CurrentSceneCollectionChangingPayload(string? sceneCollectionName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CurrentSceneCollectionChangingPayload(string sceneCollectionName) { this.SceneCollectionName = sceneCollectionName; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneTransitionChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneTransitionChanged.EventPayload.g.cs index 202876a..3cd48bf 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneTransitionChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/CurrentSceneTransitionChanged.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record CurrentSceneTransitionChangedPayload /// [JsonPropertyName("transitionName")] [Key("transitionName")] - public string? TransitionName { get; init; } + public required string TransitionName { get; init; } /// /// UUID of the new transition /// [JsonPropertyName("transitionUuid")] [Key("transitionUuid")] - public string? TransitionUuid { get; init; } + public required string TransitionUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public CurrentSceneTransitionChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CurrentSceneTransitionChangedPayload(string? transitionName = null, string? transitionUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CurrentSceneTransitionChangedPayload(string transitionName, string transitionUuid) { this.TransitionName = transitionName; this.TransitionUuid = transitionUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputActiveStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputActiveStateChanged.EventPayload.g.cs index c4e0cea..0ae0d39 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputActiveStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputActiveStateChanged.EventPayload.g.cs @@ -31,14 +31,14 @@ public sealed partial record InputActiveStateChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// Whether the input is active @@ -56,7 +56,7 @@ public InputActiveStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public InputActiveStateChangedPayload(bool videoActive, string? inputName = null, string? inputUuid = null) + public InputActiveStateChangedPayload(string inputName, string inputUuid, bool videoActive) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioBalanceChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioBalanceChanged.EventPayload.g.cs index aac65ae..6c1e63c 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioBalanceChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioBalanceChanged.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record InputAudioBalanceChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +54,7 @@ public InputAudioBalanceChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public InputAudioBalanceChangedPayload(double inputAudioBalance, string? inputName = null, string? inputUuid = null) + public InputAudioBalanceChangedPayload(string inputName, string inputUuid, double inputAudioBalance) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioMonitorTypeChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioMonitorTypeChanged.EventPayload.g.cs index 2cf962d..cdb1e9e 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioMonitorTypeChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioMonitorTypeChanged.EventPayload.g.cs @@ -35,21 +35,21 @@ public sealed partial record InputAudioMonitorTypeChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// New monitor type of the input /// [JsonPropertyName("monitorType")] [Key("monitorType")] - public string? MonitorType { get; init; } + public required string MonitorType { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -59,7 +59,8 @@ public InputAudioMonitorTypeChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public InputAudioMonitorTypeChangedPayload(string? inputName = null, string? inputUuid = null, string? monitorType = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public InputAudioMonitorTypeChangedPayload(string inputName, string inputUuid, string monitorType) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioSyncOffsetChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioSyncOffsetChanged.EventPayload.g.cs index 027ebf4..6355bd2 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioSyncOffsetChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioSyncOffsetChanged.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record InputAudioSyncOffsetChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +54,7 @@ public InputAudioSyncOffsetChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public InputAudioSyncOffsetChangedPayload(int inputAudioSyncOffset, string? inputName = null, string? inputUuid = null) + public InputAudioSyncOffsetChangedPayload(string inputName, string inputUuid, int inputAudioSyncOffset) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioTracksChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioTracksChanged.EventPayload.g.cs index 3bb169a..0250260 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioTracksChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputAudioTracksChanged.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record InputAudioTracksChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public InputAudioTracksChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public InputAudioTracksChangedPayload(string? inputName = null, string? inputUuid = null, System.Collections.Generic.Dictionary? inputAudioTracks = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public InputAudioTracksChangedPayload(string inputName, string inputUuid, System.Collections.Generic.Dictionary? inputAudioTracks = default) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputCreated.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputCreated.EventPayload.g.cs index 6cc4c1d..5f098e3 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputCreated.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputCreated.EventPayload.g.cs @@ -36,7 +36,7 @@ public sealed partial record InputCreatedPayload /// [JsonPropertyName("inputKind")] [Key("inputKind")] - public string? InputKind { get; init; } + public required string InputKind { get; init; } /// /// Bitflag value for the caps that an input supports. See obs_source_info.output_flags in the libobs docs @@ -50,7 +50,7 @@ public sealed partial record InputCreatedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// The settings configured to the input when it was created @@ -64,14 +64,14 @@ public sealed partial record InputCreatedPayload /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// The unversioned kind of input (aka no `_v2` stuff) /// [JsonPropertyName("unversionedInputKind")] [Key("unversionedInputKind")] - public string? UnversionedInputKind { get; init; } + public required string UnversionedInputKind { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -82,7 +82,7 @@ public InputCreatedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public InputCreatedPayload(long inputKindCaps, string? inputName = null, string? inputUuid = null, string? inputKind = null, string? unversionedInputKind = null, System.Text.Json.JsonElement? inputSettings = default, System.Text.Json.JsonElement? defaultInputSettings = default) + public InputCreatedPayload(string inputName, string inputUuid, string inputKind, string unversionedInputKind, long inputKindCaps, System.Text.Json.JsonElement? inputSettings = default, System.Text.Json.JsonElement? defaultInputSettings = default) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputMuteStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputMuteStateChanged.EventPayload.g.cs index 9af23b7..fae8006 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputMuteStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputMuteStateChanged.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record InputMuteStateChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +54,7 @@ public InputMuteStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public InputMuteStateChangedPayload(bool inputMuted, string? inputName = null, string? inputUuid = null) + public InputMuteStateChangedPayload(string inputName, string inputUuid, bool inputMuted) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputNameChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputNameChanged.EventPayload.g.cs index fb8163b..e305c8e 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputNameChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputNameChanged.EventPayload.g.cs @@ -29,21 +29,21 @@ public sealed partial record InputNameChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// Old name of the input /// [JsonPropertyName("oldInputName")] [Key("oldInputName")] - public string? OldInputName { get; init; } + public required string OldInputName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public InputNameChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public InputNameChangedPayload(string? inputUuid = null, string? oldInputName = null, string? inputName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public InputNameChangedPayload(string inputUuid, string oldInputName, string inputName) { this.InputUuid = inputUuid; this.OldInputName = oldInputName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputRemoved.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputRemoved.EventPayload.g.cs index c433e84..5512e96 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputRemoved.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputRemoved.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record InputRemovedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public InputRemovedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public InputRemovedPayload(string? inputName = null, string? inputUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public InputRemovedPayload(string inputName, string inputUuid) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputSettingsChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputSettingsChanged.EventPayload.g.cs index 518423e..68fd0f6 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputSettingsChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputSettingsChanged.EventPayload.g.cs @@ -31,7 +31,7 @@ public sealed partial record InputSettingsChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// New settings object of the input @@ -45,7 +45,7 @@ public sealed partial record InputSettingsChangedPayload /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -55,7 +55,8 @@ public InputSettingsChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public InputSettingsChangedPayload(string? inputName = null, string? inputUuid = null, System.Text.Json.JsonElement? inputSettings = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public InputSettingsChangedPayload(string inputName, string inputUuid, System.Text.Json.JsonElement? inputSettings = default) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputShowStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputShowStateChanged.EventPayload.g.cs index ceb21db..59def4d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputShowStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputShowStateChanged.EventPayload.g.cs @@ -31,14 +31,14 @@ public sealed partial record InputShowStateChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// Whether the input is showing @@ -56,7 +56,7 @@ public InputShowStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public InputShowStateChangedPayload(bool videoShowing, string? inputName = null, string? inputUuid = null) + public InputShowStateChangedPayload(string inputName, string inputUuid, bool videoShowing) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeChanged.EventPayload.g.cs index 677cdfb..0dad780 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeChanged.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record InputVolumeChangedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// New volume level in dB @@ -61,7 +61,7 @@ public InputVolumeChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public InputVolumeChangedPayload(double inputVolumeMul, double inputVolumeDb, string? inputName = null, string? inputUuid = null) + public InputVolumeChangedPayload(string inputName, string inputUuid, double inputVolumeMul, double inputVolumeDb) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeMeters.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeMeters.EventPayload.g.cs index db8e9ca..35bb10f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeMeters.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/InputVolumeMeters.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record InputVolumeMetersPayload /// [JsonPropertyName("inputs")] [Key("inputs")] - public System.Collections.Generic.List? Inputs { get; init; } + public required System.Collections.Generic.List Inputs { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public InputVolumeMetersPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public InputVolumeMetersPayload(System.Collections.Generic.List? inputs = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public InputVolumeMetersPayload(System.Collections.Generic.List inputs) { this.Inputs = inputs; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputActionTriggered.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputActionTriggered.EventPayload.g.cs index 6e30ffc..2172bf3 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputActionTriggered.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputActionTriggered.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record MediaInputActionTriggeredPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// Action performed on the input. See `ObsMediaInputAction` enum @@ -55,7 +55,8 @@ public MediaInputActionTriggeredPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public MediaInputActionTriggeredPayload(string? inputName = null, string? inputUuid = null, ObsWebSocket.Core.Protocol.Generated.MediaInputAction mediaAction = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public MediaInputActionTriggeredPayload(string inputName, string inputUuid, ObsWebSocket.Core.Protocol.Generated.MediaInputAction mediaAction = default) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackEnded.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackEnded.EventPayload.g.cs index aa100b7..20e0076 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackEnded.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackEnded.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record MediaInputPlaybackEndedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public MediaInputPlaybackEndedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public MediaInputPlaybackEndedPayload(string? inputName = null, string? inputUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public MediaInputPlaybackEndedPayload(string inputName, string inputUuid) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackStarted.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackStarted.EventPayload.g.cs index 0fe00ba..ef8b3cf 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackStarted.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/MediaInputPlaybackStarted.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record MediaInputPlaybackStartedPayload /// [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// /// UUID of the input /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public MediaInputPlaybackStartedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public MediaInputPlaybackStartedPayload(string? inputName = null, string? inputUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public MediaInputPlaybackStartedPayload(string inputName, string inputUuid) { this.InputName = inputName; this.InputUuid = inputUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/ProfileListChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/ProfileListChanged.EventPayload.g.cs index 2331ca4..efd2f8e 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/ProfileListChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/ProfileListChanged.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record ProfileListChangedPayload /// [JsonPropertyName("profiles")] [Key("profiles")] - public System.Collections.Generic.List? Profiles { get; init; } + public required System.Collections.Generic.List Profiles { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public ProfileListChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public ProfileListChangedPayload(System.Collections.Generic.List? profiles = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public ProfileListChangedPayload(System.Collections.Generic.List profiles) { this.Profiles = profiles; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/RecordFileChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/RecordFileChanged.EventPayload.g.cs index 21dfe13..f07709b 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/RecordFileChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/RecordFileChanged.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record RecordFileChangedPayload /// [JsonPropertyName("newOutputPath")] [Key("newOutputPath")] - public string? NewOutputPath { get; init; } + public required string NewOutputPath { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public RecordFileChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public RecordFileChangedPayload(string? newOutputPath = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public RecordFileChangedPayload(string newOutputPath) { this.NewOutputPath = newOutputPath; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferSaved.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferSaved.EventPayload.g.cs index 0eaa21f..83bbcda 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferSaved.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/ReplayBufferSaved.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record ReplayBufferSavedPayload /// [JsonPropertyName("savedReplayPath")] [Key("savedReplayPath")] - public string? SavedReplayPath { get; init; } + public required string SavedReplayPath { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public ReplayBufferSavedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public ReplayBufferSavedPayload(string? savedReplayPath = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public ReplayBufferSavedPayload(string savedReplayPath) { this.SavedReplayPath = savedReplayPath; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneCollectionListChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneCollectionListChanged.EventPayload.g.cs index 35b75a9..274ed52 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneCollectionListChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneCollectionListChanged.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record SceneCollectionListChangedPayload /// [JsonPropertyName("sceneCollections")] [Key("sceneCollections")] - public System.Collections.Generic.List? SceneCollections { get; init; } + public required System.Collections.Generic.List SceneCollections { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public SceneCollectionListChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneCollectionListChangedPayload(System.Collections.Generic.List? sceneCollections = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SceneCollectionListChangedPayload(System.Collections.Generic.List sceneCollections) { this.SceneCollections = sceneCollections; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneCreated.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneCreated.EventPayload.g.cs index 096fb2b..3843803 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneCreated.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneCreated.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record SceneCreatedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the new scene /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +54,7 @@ public SceneCreatedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneCreatedPayload(bool isGroup, string? sceneName = null, string? sceneUuid = null) + public SceneCreatedPayload(string sceneName, string sceneUuid, bool isGroup) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemCreated.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemCreated.EventPayload.g.cs index 76dad2f..406822e 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemCreated.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemCreated.EventPayload.g.cs @@ -43,28 +43,28 @@ public sealed partial record SceneItemCreatedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene the item was added to /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// /// Name of the underlying source (input/scene) /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// /// UUID of the underlying source (input/scene) /// [JsonPropertyName("sourceUuid")] [Key("sourceUuid")] - public string? SourceUuid { get; init; } + public required string SourceUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -75,7 +75,7 @@ public SceneItemCreatedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneItemCreatedPayload(int sceneItemId, int sceneItemIndex, string? sceneName = null, string? sceneUuid = null, string? sourceName = null, string? sourceUuid = null) + public SceneItemCreatedPayload(string sceneName, string sceneUuid, string sourceName, string sourceUuid, int sceneItemId, int sceneItemIndex) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemEnableStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemEnableStateChanged.EventPayload.g.cs index 48a8e4e..99acf10 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemEnableStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemEnableStateChanged.EventPayload.g.cs @@ -43,14 +43,14 @@ public sealed partial record SceneItemEnableStateChangedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene the item is in /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -61,7 +61,7 @@ public SceneItemEnableStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneItemEnableStateChangedPayload(int sceneItemId, bool sceneItemEnabled, string? sceneName = null, string? sceneUuid = null) + public SceneItemEnableStateChangedPayload(string sceneName, string sceneUuid, int sceneItemId, bool sceneItemEnabled) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemListReindexed.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemListReindexed.EventPayload.g.cs index 152a6d1..51570c5 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemListReindexed.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemListReindexed.EventPayload.g.cs @@ -29,21 +29,21 @@ public sealed partial record SceneItemListReindexedPayload /// [JsonPropertyName("sceneItems")] [Key("sceneItems")] - public System.Collections.Generic.List? SceneItems { get; init; } + public required System.Collections.Generic.List SceneItems { get; init; } /// /// Name of the scene /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public SceneItemListReindexedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneItemListReindexedPayload(string? sceneName = null, string? sceneUuid = null, System.Collections.Generic.List? sceneItems = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SceneItemListReindexedPayload(string sceneName, string sceneUuid, System.Collections.Generic.List sceneItems) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemLockStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemLockStateChanged.EventPayload.g.cs index 6ce7abd..b13fc77 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemLockStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemLockStateChanged.EventPayload.g.cs @@ -43,14 +43,14 @@ public sealed partial record SceneItemLockStateChangedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene the item is in /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -61,7 +61,7 @@ public SceneItemLockStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneItemLockStateChangedPayload(int sceneItemId, bool sceneItemLocked, string? sceneName = null, string? sceneUuid = null) + public SceneItemLockStateChangedPayload(string sceneName, string sceneUuid, int sceneItemId, bool sceneItemLocked) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemRemoved.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemRemoved.EventPayload.g.cs index 4a4293e..5d69c8b 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemRemoved.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemRemoved.EventPayload.g.cs @@ -38,28 +38,28 @@ public sealed partial record SceneItemRemovedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene the item was removed from /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// /// Name of the underlying source (input/scene) /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// /// UUID of the underlying source (input/scene) /// [JsonPropertyName("sourceUuid")] [Key("sourceUuid")] - public string? SourceUuid { get; init; } + public required string SourceUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -70,7 +70,7 @@ public SceneItemRemovedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneItemRemovedPayload(int sceneItemId, string? sceneName = null, string? sceneUuid = null, string? sourceName = null, string? sourceUuid = null) + public SceneItemRemovedPayload(string sceneName, string sceneUuid, string sourceName, string sourceUuid, int sceneItemId) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemSelected.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemSelected.EventPayload.g.cs index 762d26b..defb464 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemSelected.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemSelected.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record SceneItemSelectedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene the item is in /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +54,7 @@ public SceneItemSelectedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneItemSelectedPayload(int sceneItemId, string? sceneName = null, string? sceneUuid = null) + public SceneItemSelectedPayload(string sceneName, string sceneUuid, int sceneItemId) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs index 20a42b5..a893eca 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs @@ -43,14 +43,14 @@ public sealed partial record SceneItemTransformChangedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// The UUID of the scene the item is in /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -61,7 +61,7 @@ public SceneItemTransformChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneItemTransformChangedPayload(int sceneItemId, string? sceneName = null, string? sceneUuid = null, ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub? sceneItemTransform = default) + public SceneItemTransformChangedPayload(string sceneName, string sceneUuid, int sceneItemId, ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub? sceneItemTransform = default) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneListChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneListChanged.EventPayload.g.cs index 8ff306f..e54479f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneListChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneListChanged.EventPayload.g.cs @@ -31,7 +31,7 @@ public sealed partial record SceneListChangedPayload /// [JsonPropertyName("scenes")] [Key("scenes")] - public System.Collections.Generic.List? Scenes { get; init; } + public required System.Collections.Generic.List Scenes { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public SceneListChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneListChangedPayload(System.Collections.Generic.List? scenes = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SceneListChangedPayload(System.Collections.Generic.List scenes) { this.Scenes = scenes; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneNameChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneNameChanged.EventPayload.g.cs index c664e25..0b08add 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneNameChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneNameChanged.EventPayload.g.cs @@ -29,21 +29,21 @@ public sealed partial record SceneNameChangedPayload /// [JsonPropertyName("oldSceneName")] [Key("oldSceneName")] - public string? OldSceneName { get; init; } + public required string OldSceneName { get; init; } /// /// New name of the scene /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the scene /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public SceneNameChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneNameChangedPayload(string? sceneUuid = null, string? oldSceneName = null, string? sceneName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SceneNameChangedPayload(string sceneUuid, string oldSceneName, string sceneName) { this.SceneUuid = sceneUuid; this.OldSceneName = oldSceneName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneRemoved.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneRemoved.EventPayload.g.cs index 5e17f20..e40bb42 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneRemoved.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneRemoved.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record SceneRemovedPayload /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// UUID of the removed scene /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +54,7 @@ public SceneRemovedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneRemovedPayload(bool isGroup, string? sceneName = null, string? sceneUuid = null) + public SceneRemovedPayload(string sceneName, string sceneUuid, bool isGroup) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionEnded.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionEnded.EventPayload.g.cs index 43902d7..f57cf2d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionEnded.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionEnded.EventPayload.g.cs @@ -31,14 +31,14 @@ public sealed partial record SceneTransitionEndedPayload /// [JsonPropertyName("transitionName")] [Key("transitionName")] - public string? TransitionName { get; init; } + public required string TransitionName { get; init; } /// /// Scene transition UUID /// [JsonPropertyName("transitionUuid")] [Key("transitionUuid")] - public string? TransitionUuid { get; init; } + public required string TransitionUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -48,7 +48,8 @@ public SceneTransitionEndedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneTransitionEndedPayload(string? transitionName = null, string? transitionUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SceneTransitionEndedPayload(string transitionName, string transitionUuid) { this.TransitionName = transitionName; this.TransitionUuid = transitionUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionStarted.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionStarted.EventPayload.g.cs index 00d0c4e..8b4d0a0 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionStarted.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionStarted.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record SceneTransitionStartedPayload /// [JsonPropertyName("transitionName")] [Key("transitionName")] - public string? TransitionName { get; init; } + public required string TransitionName { get; init; } /// /// Scene transition UUID /// [JsonPropertyName("transitionUuid")] [Key("transitionUuid")] - public string? TransitionUuid { get; init; } + public required string TransitionUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public SceneTransitionStartedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneTransitionStartedPayload(string? transitionName = null, string? transitionUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SceneTransitionStartedPayload(string transitionName, string transitionUuid) { this.TransitionName = transitionName; this.TransitionUuid = transitionUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionVideoEnded.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionVideoEnded.EventPayload.g.cs index 2e65463..5930f1c 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionVideoEnded.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneTransitionVideoEnded.EventPayload.g.cs @@ -34,14 +34,14 @@ public sealed partial record SceneTransitionVideoEndedPayload /// [JsonPropertyName("transitionName")] [Key("transitionName")] - public string? TransitionName { get; init; } + public required string TransitionName { get; init; } /// /// Scene transition UUID /// [JsonPropertyName("transitionUuid")] [Key("transitionUuid")] - public string? TransitionUuid { get; init; } + public required string TransitionUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -51,7 +51,8 @@ public SceneTransitionVideoEndedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SceneTransitionVideoEndedPayload(string? transitionName = null, string? transitionUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SceneTransitionVideoEndedPayload(string transitionName, string transitionUuid) { this.TransitionName = transitionName; this.TransitionUuid = transitionUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/ScreenshotSaved.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/ScreenshotSaved.EventPayload.g.cs index 9c332a3..5bb48d2 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/ScreenshotSaved.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/ScreenshotSaved.EventPayload.g.cs @@ -33,7 +33,7 @@ public sealed partial record ScreenshotSavedPayload /// [JsonPropertyName("savedScreenshotPath")] [Key("savedScreenshotPath")] - public string? SavedScreenshotPath { get; init; } + public required string SavedScreenshotPath { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -43,7 +43,8 @@ public ScreenshotSavedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public ScreenshotSavedPayload(string? savedScreenshotPath = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public ScreenshotSavedPayload(string savedScreenshotPath) { this.SavedScreenshotPath = savedScreenshotPath; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterCreated.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterCreated.EventPayload.g.cs index 386ce8c..ad9b2b4 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterCreated.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterCreated.EventPayload.g.cs @@ -43,14 +43,14 @@ public sealed partial record SourceFilterCreatedPayload /// [JsonPropertyName("filterKind")] [Key("filterKind")] - public string? FilterKind { get; init; } + public required string FilterKind { get; init; } /// /// Name of the filter /// [JsonPropertyName("filterName")] [Key("filterName")] - public string? FilterName { get; init; } + public required string FilterName { get; init; } /// /// The settings configured to the filter when it was created @@ -64,7 +64,7 @@ public sealed partial record SourceFilterCreatedPayload /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -75,7 +75,7 @@ public SourceFilterCreatedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SourceFilterCreatedPayload(int filterIndex, string? sourceName = null, string? filterName = null, string? filterKind = null, System.Text.Json.JsonElement? filterSettings = default, System.Text.Json.JsonElement? defaultFilterSettings = default) + public SourceFilterCreatedPayload(string sourceName, string filterName, string filterKind, int filterIndex, System.Text.Json.JsonElement? filterSettings = default, System.Text.Json.JsonElement? defaultFilterSettings = default) { this.SourceName = sourceName; this.FilterName = filterName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterEnableStateChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterEnableStateChanged.EventPayload.g.cs index d78a96e..984c893 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterEnableStateChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterEnableStateChanged.EventPayload.g.cs @@ -36,14 +36,14 @@ public sealed partial record SourceFilterEnableStateChangedPayload /// [JsonPropertyName("filterName")] [Key("filterName")] - public string? FilterName { get; init; } + public required string FilterName { get; init; } /// /// Name of the source the filter is on /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -54,7 +54,7 @@ public SourceFilterEnableStateChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SourceFilterEnableStateChangedPayload(bool filterEnabled, string? sourceName = null, string? filterName = null) + public SourceFilterEnableStateChangedPayload(string sourceName, string filterName, bool filterEnabled) { this.SourceName = sourceName; this.FilterName = filterName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterListReindexed.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterListReindexed.EventPayload.g.cs index 30279e1..7c77152 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterListReindexed.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterListReindexed.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record SourceFilterListReindexedPayload /// [JsonPropertyName("filters")] [Key("filters")] - public System.Collections.Generic.List? Filters { get; init; } + public required System.Collections.Generic.List Filters { get; init; } /// /// Name of the source /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public SourceFilterListReindexedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SourceFilterListReindexedPayload(string? sourceName = null, System.Collections.Generic.List? filters = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SourceFilterListReindexedPayload(string sourceName, System.Collections.Generic.List filters) { this.SourceName = sourceName; this.Filters = filters; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterNameChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterNameChanged.EventPayload.g.cs index d561018..3b19ce2 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterNameChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterNameChanged.EventPayload.g.cs @@ -29,21 +29,21 @@ public sealed partial record SourceFilterNameChangedPayload /// [JsonPropertyName("filterName")] [Key("filterName")] - public string? FilterName { get; init; } + public required string FilterName { get; init; } /// /// Old name of the filter /// [JsonPropertyName("oldFilterName")] [Key("oldFilterName")] - public string? OldFilterName { get; init; } + public required string OldFilterName { get; init; } /// /// The source the filter is on /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public SourceFilterNameChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SourceFilterNameChangedPayload(string? sourceName = null, string? oldFilterName = null, string? filterName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SourceFilterNameChangedPayload(string sourceName, string oldFilterName, string filterName) { this.SourceName = sourceName; this.OldFilterName = oldFilterName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterRemoved.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterRemoved.EventPayload.g.cs index a4a9aa1..c8c0d6f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterRemoved.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterRemoved.EventPayload.g.cs @@ -29,14 +29,14 @@ public sealed partial record SourceFilterRemovedPayload /// [JsonPropertyName("filterName")] [Key("filterName")] - public string? FilterName { get; init; } + public required string FilterName { get; init; } /// /// Name of the source the filter was on /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public SourceFilterRemovedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SourceFilterRemovedPayload(string? sourceName = null, string? filterName = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SourceFilterRemovedPayload(string sourceName, string filterName) { this.SourceName = sourceName; this.FilterName = filterName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterSettingsChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterSettingsChanged.EventPayload.g.cs index 9a5c963..005eb19 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterSettingsChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SourceFilterSettingsChanged.EventPayload.g.cs @@ -29,7 +29,7 @@ public sealed partial record SourceFilterSettingsChangedPayload /// [JsonPropertyName("filterName")] [Key("filterName")] - public string? FilterName { get; init; } + public required string FilterName { get; init; } /// /// New settings object of the filter @@ -43,7 +43,7 @@ public sealed partial record SourceFilterSettingsChangedPayload /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public SourceFilterSettingsChangedPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public SourceFilterSettingsChangedPayload(string? sourceName = null, string? filterName = null, System.Text.Json.JsonElement? filterSettings = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public SourceFilterSettingsChangedPayload(string sourceName, string filterName, System.Text.Json.JsonElement? filterSettings = default) { this.SourceName = sourceName; this.FilterName = filterName; diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/VendorEvent.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/VendorEvent.EventPayload.g.cs index 500dcbc..fa22054 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/VendorEvent.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/VendorEvent.EventPayload.g.cs @@ -39,14 +39,14 @@ public sealed partial record VendorEventPayload /// [JsonPropertyName("eventType")] [Key("eventType")] - public string? EventType { get; init; } + public required string EventType { get; init; } /// /// Name of the vendor emitting the event /// [JsonPropertyName("vendorName")] [Key("vendorName")] - public string? VendorName { get; init; } + public required string VendorName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -56,7 +56,8 @@ public VendorEventPayload() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public VendorEventPayload(string? vendorName = null, string? eventType = null, System.Text.Json.JsonElement? eventData = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public VendorEventPayload(string vendorName, string eventType, System.Text.Json.JsonElement? eventData = default) { this.VendorName = vendorName; this.EventType = eventType; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/CallVendorRequest.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/CallVendorRequest.Response.g.cs index 3e0a706..27be420 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/CallVendorRequest.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/CallVendorRequest.Response.g.cs @@ -32,7 +32,7 @@ public sealed partial record CallVendorRequestResponseData /// [JsonPropertyName("requestType")] [Key("requestType")] - public string? RequestType { get; init; } + public required string RequestType { get; init; } /// /// Object containing appropriate response data. {} if request does not provide any response data @@ -46,7 +46,7 @@ public sealed partial record CallVendorRequestResponseData /// [JsonPropertyName("vendorName")] [Key("vendorName")] - public string? VendorName { get; init; } + public required string VendorName { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -56,7 +56,8 @@ public CallVendorRequestResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CallVendorRequestResponseData(string? vendorName = null, string? requestType = null, System.Text.Json.JsonElement? responseData = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CallVendorRequestResponseData(string vendorName, string requestType, System.Text.Json.JsonElement? responseData = default) { this.VendorName = vendorName; this.RequestType = requestType; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/CreateInput.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/CreateInput.Response.g.cs index c032cd3..feaba6e 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/CreateInput.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/CreateInput.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record CreateInputResponseData /// [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// /// ID of the newly created scene item @@ -47,7 +47,7 @@ public CreateInputResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public CreateInputResponseData(int sceneItemId, string? inputUuid = null) + public CreateInputResponseData(string inputUuid, int sceneItemId) { this.InputUuid = inputUuid; this.SceneItemId = sceneItemId; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/CreateScene.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/CreateScene.Response.g.cs index 03c84ce..dc5a303 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/CreateScene.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/CreateScene.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record CreateSceneResponseData /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public CreateSceneResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public CreateSceneResponseData(string? sceneUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public CreateSceneResponseData(string sceneUuid) { this.SceneUuid = sceneUuid; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCanvasList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCanvasList.Response.g.cs index dd59387..4c86d8c 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCanvasList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCanvasList.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetCanvasListResponseData /// [JsonPropertyName("canvases")] [Key("canvases")] - public System.Collections.Generic.List? Canvases { get; init; } + public required System.Collections.Generic.List Canvases { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetCanvasListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetCanvasListResponseData(System.Collections.Generic.List? canvases = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetCanvasListResponseData(System.Collections.Generic.List canvases) { this.Canvases = canvases; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentPreviewScene.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentPreviewScene.Response.g.cs index 11154ae..b5d17f7 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentPreviewScene.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentPreviewScene.Response.g.cs @@ -33,28 +33,28 @@ public sealed partial record GetCurrentPreviewSceneResponseData /// [JsonPropertyName("currentPreviewSceneName")] [Key("currentPreviewSceneName")] - public string? CurrentPreviewSceneName { get; init; } + public required string CurrentPreviewSceneName { get; init; } /// /// Current preview scene UUID /// [JsonPropertyName("currentPreviewSceneUuid")] [Key("currentPreviewSceneUuid")] - public string? CurrentPreviewSceneUuid { get; init; } + public required string CurrentPreviewSceneUuid { get; init; } /// /// Current preview scene name /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// Current preview scene UUID /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -64,7 +64,8 @@ public GetCurrentPreviewSceneResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetCurrentPreviewSceneResponseData(string? sceneName = null, string? sceneUuid = null, string? currentPreviewSceneName = null, string? currentPreviewSceneUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetCurrentPreviewSceneResponseData(string sceneName, string sceneUuid, string currentPreviewSceneName, string currentPreviewSceneUuid) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentProgramScene.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentProgramScene.Response.g.cs index 8702319..85c179d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentProgramScene.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentProgramScene.Response.g.cs @@ -33,28 +33,28 @@ public sealed partial record GetCurrentProgramSceneResponseData /// [JsonPropertyName("currentProgramSceneName")] [Key("currentProgramSceneName")] - public string? CurrentProgramSceneName { get; init; } + public required string CurrentProgramSceneName { get; init; } /// /// Current program scene UUID (Deprecated) /// [JsonPropertyName("currentProgramSceneUuid")] [Key("currentProgramSceneUuid")] - public string? CurrentProgramSceneUuid { get; init; } + public required string CurrentProgramSceneUuid { get; init; } /// /// Current program scene name /// [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// /// Current program scene UUID /// [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -64,7 +64,8 @@ public GetCurrentProgramSceneResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetCurrentProgramSceneResponseData(string? sceneName = null, string? sceneUuid = null, string? currentProgramSceneName = null, string? currentProgramSceneUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetCurrentProgramSceneResponseData(string sceneName, string sceneUuid, string currentProgramSceneName, string currentProgramSceneUuid) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentSceneTransition.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentSceneTransition.Response.g.cs index 7c1ef9a..2e1ebc2 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentSceneTransition.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetCurrentSceneTransition.Response.g.cs @@ -50,14 +50,14 @@ public sealed partial record GetCurrentSceneTransitionResponseData /// [JsonPropertyName("transitionKind")] [Key("transitionKind")] - public string? TransitionKind { get; init; } + public required string TransitionKind { get; init; } /// /// Name of the transition /// [JsonPropertyName("transitionName")] [Key("transitionName")] - public string? TransitionName { get; init; } + public required string TransitionName { get; init; } /// /// Object of settings for the transition. `null` if transition is not configurable @@ -71,7 +71,7 @@ public sealed partial record GetCurrentSceneTransitionResponseData /// [JsonPropertyName("transitionUuid")] [Key("transitionUuid")] - public string? TransitionUuid { get; init; } + public required string TransitionUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -82,7 +82,7 @@ public GetCurrentSceneTransitionResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public GetCurrentSceneTransitionResponseData(bool transitionFixed, bool transitionConfigurable, string? transitionName = null, string? transitionUuid = null, string? transitionKind = null, int? transitionDuration = null, System.Text.Json.JsonElement? transitionSettings = default) + public GetCurrentSceneTransitionResponseData(string transitionName, string transitionUuid, string transitionKind, bool transitionFixed, bool transitionConfigurable, int? transitionDuration = null, System.Text.Json.JsonElement? transitionSettings = default) { this.TransitionName = transitionName; this.TransitionUuid = transitionUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupList.Response.g.cs index fa1bd5b..6320ecc 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupList.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetGroupListResponseData /// [JsonPropertyName("groups")] [Key("groups")] - public System.Collections.Generic.List? Groups { get; init; } + public required System.Collections.Generic.List Groups { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public GetGroupListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetGroupListResponseData(System.Collections.Generic.List? groups = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetGroupListResponseData(System.Collections.Generic.List groups) { this.Groups = groups; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupSceneItemList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupSceneItemList.Response.g.cs index 3276769..7a8e22e 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupSceneItemList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetGroupSceneItemList.Response.g.cs @@ -33,7 +33,7 @@ public sealed partial record GetGroupSceneItemListResponseData /// [JsonPropertyName("sceneItems")] [Key("sceneItems")] - public System.Collections.Generic.List? SceneItems { get; init; } + public required System.Collections.Generic.List SceneItems { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -43,7 +43,8 @@ public GetGroupSceneItemListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetGroupSceneItemListResponseData(System.Collections.Generic.List? sceneItems = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetGroupSceneItemListResponseData(System.Collections.Generic.List sceneItems) { this.SceneItems = sceneItems; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetHotkeyList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetHotkeyList.Response.g.cs index 71b2bdd..8341aaf 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetHotkeyList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetHotkeyList.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetHotkeyListResponseData /// [JsonPropertyName("hotkeys")] [Key("hotkeys")] - public System.Collections.Generic.List? Hotkeys { get; init; } + public required System.Collections.Generic.List Hotkeys { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public GetHotkeyListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetHotkeyListResponseData(System.Collections.Generic.List? hotkeys = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetHotkeyListResponseData(System.Collections.Generic.List hotkeys) { this.Hotkeys = hotkeys; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioMonitorType.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioMonitorType.Response.g.cs index 530b1fa..8229b39 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioMonitorType.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputAudioMonitorType.Response.g.cs @@ -35,7 +35,7 @@ public sealed partial record GetInputAudioMonitorTypeResponseData /// [JsonPropertyName("monitorType")] [Key("monitorType")] - public string? MonitorType { get; init; } + public required string MonitorType { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -45,7 +45,8 @@ public GetInputAudioMonitorTypeResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputAudioMonitorTypeResponseData(string? monitorType = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetInputAudioMonitorTypeResponseData(string monitorType) { this.MonitorType = monitorType; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceFieldOrder.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceFieldOrder.Response.g.cs index f8d3540..2b8a903 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceFieldOrder.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceFieldOrder.Response.g.cs @@ -36,7 +36,7 @@ public sealed partial record GetInputDeinterlaceFieldOrderResponseData /// [JsonPropertyName("inputDeinterlaceFieldOrder")] [Key("inputDeinterlaceFieldOrder")] - public string? InputDeinterlaceFieldOrder { get; init; } + public required string InputDeinterlaceFieldOrder { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public GetInputDeinterlaceFieldOrderResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputDeinterlaceFieldOrderResponseData(string? inputDeinterlaceFieldOrder = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetInputDeinterlaceFieldOrderResponseData(string inputDeinterlaceFieldOrder) { this.InputDeinterlaceFieldOrder = inputDeinterlaceFieldOrder; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceMode.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceMode.Response.g.cs index 81cc25a..05fbbe8 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceMode.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputDeinterlaceMode.Response.g.cs @@ -43,7 +43,7 @@ public sealed partial record GetInputDeinterlaceModeResponseData /// [JsonPropertyName("inputDeinterlaceMode")] [Key("inputDeinterlaceMode")] - public string? InputDeinterlaceMode { get; init; } + public required string InputDeinterlaceMode { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -53,7 +53,8 @@ public GetInputDeinterlaceModeResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputDeinterlaceModeResponseData(string? inputDeinterlaceMode = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetInputDeinterlaceModeResponseData(string inputDeinterlaceMode) { this.InputDeinterlaceMode = inputDeinterlaceMode; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputKindList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputKindList.Response.g.cs index 09063e0..6d26527 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputKindList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputKindList.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetInputKindListResponseData /// [JsonPropertyName("inputKinds")] [Key("inputKinds")] - public System.Collections.Generic.List? InputKinds { get; init; } + public required System.Collections.Generic.List InputKinds { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetInputKindListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputKindListResponseData(System.Collections.Generic.List? inputKinds = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetInputKindListResponseData(System.Collections.Generic.List inputKinds) { this.InputKinds = inputKinds; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputList.Response.g.cs index 6d78a2f..ea28a4b 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputList.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetInputListResponseData /// [JsonPropertyName("inputs")] [Key("inputs")] - public System.Collections.Generic.List? Inputs { get; init; } + public required System.Collections.Generic.List Inputs { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetInputListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputListResponseData(System.Collections.Generic.List? inputs = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetInputListResponseData(System.Collections.Generic.List inputs) { this.Inputs = inputs; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputPropertiesListPropertyItems.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputPropertiesListPropertyItems.Response.g.cs index 7f62e7a..4998a08 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputPropertiesListPropertyItems.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputPropertiesListPropertyItems.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetInputPropertiesListPropertyItemsResponseData /// [JsonPropertyName("propertyItems")] [Key("propertyItems")] - public System.Collections.Generic.List? PropertyItems { get; init; } + public required System.Collections.Generic.List PropertyItems { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public GetInputPropertiesListPropertyItemsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputPropertiesListPropertyItemsResponseData(System.Collections.Generic.List? propertyItems = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetInputPropertiesListPropertyItemsResponseData(System.Collections.Generic.List propertyItems) { this.PropertyItems = propertyItems; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputSettings.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputSettings.Response.g.cs index 34de41a..3d5a27c 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputSettings.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetInputSettings.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetInputSettingsResponseData /// [JsonPropertyName("inputKind")] [Key("inputKind")] - public string? InputKind { get; init; } + public required string InputKind { get; init; } /// /// Object of settings for the input @@ -48,7 +48,8 @@ public GetInputSettingsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetInputSettingsResponseData(System.Text.Json.JsonElement? inputSettings = default, string? inputKind = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetInputSettingsResponseData(string inputKind, System.Text.Json.JsonElement? inputSettings = default) { this.InputSettings = inputSettings; this.InputKind = inputKind; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetLastReplayBufferReplay.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetLastReplayBufferReplay.Response.g.cs index 303f27a..49ee625 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetLastReplayBufferReplay.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetLastReplayBufferReplay.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetLastReplayBufferReplayResponseData /// [JsonPropertyName("savedReplayPath")] [Key("savedReplayPath")] - public string? SavedReplayPath { get; init; } + public required string SavedReplayPath { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetLastReplayBufferReplayResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetLastReplayBufferReplayResponseData(string? savedReplayPath = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetLastReplayBufferReplayResponseData(string savedReplayPath) { this.SavedReplayPath = savedReplayPath; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetMediaInputStatus.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetMediaInputStatus.Response.g.cs index 54d974d..5555c1d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetMediaInputStatus.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetMediaInputStatus.Response.g.cs @@ -54,7 +54,7 @@ public sealed partial record GetMediaInputStatusResponseData /// [JsonPropertyName("mediaState")] [Key("mediaState")] - public string? MediaState { get; init; } + public required string MediaState { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -64,7 +64,8 @@ public GetMediaInputStatusResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetMediaInputStatusResponseData(string? mediaState = null, long? mediaDuration = null, long? mediaCursor = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetMediaInputStatusResponseData(string mediaState, long? mediaDuration = null, long? mediaCursor = null) { this.MediaState = mediaState; this.MediaDuration = mediaDuration; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetMonitorList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetMonitorList.Response.g.cs index 74ea14c..cfe1115 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetMonitorList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetMonitorList.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetMonitorListResponseData /// [JsonPropertyName("monitors")] [Key("monitors")] - public System.Collections.Generic.List? Monitors { get; init; } + public required System.Collections.Generic.List Monitors { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetMonitorListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetMonitorListResponseData(System.Collections.Generic.List? monitors = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetMonitorListResponseData(System.Collections.Generic.List monitors) { this.Monitors = monitors; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputList.Response.g.cs index 38c9b55..3c4f07a 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputList.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetOutputListResponseData /// [JsonPropertyName("outputs")] [Key("outputs")] - public System.Collections.Generic.List? Outputs { get; init; } + public required System.Collections.Generic.List Outputs { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetOutputListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetOutputListResponseData(System.Collections.Generic.List? outputs = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetOutputListResponseData(System.Collections.Generic.List outputs) { this.Outputs = outputs; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputStatus.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputStatus.Response.g.cs index 9b0b776..bb89ae2 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputStatus.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetOutputStatus.Response.g.cs @@ -71,7 +71,7 @@ public sealed partial record GetOutputStatusResponseData /// [JsonPropertyName("outputTimecode")] [Key("outputTimecode")] - public string? OutputTimecode { get; init; } + public required string OutputTimecode { get; init; } /// /// Total number of frames delivered by the output's process @@ -89,7 +89,7 @@ public GetOutputStatusResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public GetOutputStatusResponseData(bool outputActive, bool outputReconnecting, long outputDuration, double outputCongestion, long outputBytes, int outputSkippedFrames, int outputTotalFrames, string? outputTimecode = null) + public GetOutputStatusResponseData(bool outputActive, bool outputReconnecting, string outputTimecode, long outputDuration, double outputCongestion, long outputBytes, int outputSkippedFrames, int outputTotalFrames) { this.OutputActive = outputActive; this.OutputReconnecting = outputReconnecting; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetProfileList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetProfileList.Response.g.cs index 4820cf6..8c6a5db 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetProfileList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetProfileList.Response.g.cs @@ -29,14 +29,14 @@ public sealed partial record GetProfileListResponseData /// [JsonPropertyName("currentProfileName")] [Key("currentProfileName")] - public string? CurrentProfileName { get; init; } + public required string CurrentProfileName { get; init; } /// /// Array of all available profiles /// [JsonPropertyName("profiles")] [Key("profiles")] - public System.Collections.Generic.List? Profiles { get; init; } + public required System.Collections.Generic.List Profiles { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public GetProfileListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetProfileListResponseData(string? currentProfileName = null, System.Collections.Generic.List? profiles = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetProfileListResponseData(string currentProfileName, System.Collections.Generic.List profiles) { this.CurrentProfileName = currentProfileName; this.Profiles = profiles; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordDirectory.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordDirectory.Response.g.cs index 7a0a39f..28cb753 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordDirectory.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordDirectory.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetRecordDirectoryResponseData /// [JsonPropertyName("recordDirectory")] [Key("recordDirectory")] - public string? RecordDirectory { get; init; } + public required string RecordDirectory { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetRecordDirectoryResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetRecordDirectoryResponseData(string? recordDirectory = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetRecordDirectoryResponseData(string recordDirectory) { this.RecordDirectory = recordDirectory; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordStatus.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordStatus.Response.g.cs index 5dbe7bc..7f0499f 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordStatus.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetRecordStatus.Response.g.cs @@ -57,7 +57,7 @@ public sealed partial record GetRecordStatusResponseData /// [JsonPropertyName("outputTimecode")] [Key("outputTimecode")] - public string? OutputTimecode { get; init; } + public required string OutputTimecode { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -68,7 +68,7 @@ public GetRecordStatusResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public GetRecordStatusResponseData(bool outputActive, bool outputPaused, long outputDuration, long outputBytes, string? outputTimecode = null) + public GetRecordStatusResponseData(bool outputActive, bool outputPaused, string outputTimecode, long outputDuration, long outputBytes) { this.OutputActive = outputActive; this.OutputPaused = outputPaused; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneCollectionList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneCollectionList.Response.g.cs index 57abae9..be07285 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneCollectionList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneCollectionList.Response.g.cs @@ -29,14 +29,14 @@ public sealed partial record GetSceneCollectionListResponseData /// [JsonPropertyName("currentSceneCollectionName")] [Key("currentSceneCollectionName")] - public string? CurrentSceneCollectionName { get; init; } + public required string CurrentSceneCollectionName { get; init; } /// /// Array of all available scene collections /// [JsonPropertyName("sceneCollections")] [Key("sceneCollections")] - public System.Collections.Generic.List? SceneCollections { get; init; } + public required System.Collections.Generic.List SceneCollections { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public GetSceneCollectionListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneCollectionListResponseData(string? currentSceneCollectionName = null, System.Collections.Generic.List? sceneCollections = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSceneCollectionListResponseData(string currentSceneCollectionName, System.Collections.Generic.List sceneCollections) { this.CurrentSceneCollectionName = currentSceneCollectionName; this.SceneCollections = sceneCollections; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemBlendMode.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemBlendMode.Response.g.cs index 9b835d9..678af56 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemBlendMode.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemBlendMode.Response.g.cs @@ -41,7 +41,7 @@ public sealed partial record GetSceneItemBlendModeResponseData /// [JsonPropertyName("sceneItemBlendMode")] [Key("sceneItemBlendMode")] - public string? SceneItemBlendMode { get; init; } + public required string SceneItemBlendMode { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -51,7 +51,8 @@ public GetSceneItemBlendModeResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneItemBlendModeResponseData(string? sceneItemBlendMode = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSceneItemBlendModeResponseData(string sceneItemBlendMode) { this.SceneItemBlendMode = sceneItemBlendMode; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemList.Response.g.cs index 9dadb01..00ecc2a 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemList.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetSceneItemListResponseData /// [JsonPropertyName("sceneItems")] [Key("sceneItems")] - public System.Collections.Generic.List? SceneItems { get; init; } + public required System.Collections.Generic.List SceneItems { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public GetSceneItemListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneItemListResponseData(System.Collections.Generic.List? sceneItems = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSceneItemListResponseData(System.Collections.Generic.List sceneItems) { this.SceneItems = sceneItems; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemSource.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemSource.Response.g.cs index 16eb829..16616b1 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemSource.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemSource.Response.g.cs @@ -29,14 +29,14 @@ public sealed partial record GetSceneItemSourceResponseData /// [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// /// UUID of the source associated with the scene item /// [JsonPropertyName("sourceUuid")] [Key("sourceUuid")] - public string? SourceUuid { get; init; } + public required string SourceUuid { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public GetSceneItemSourceResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneItemSourceResponseData(string? sourceName = null, string? sourceUuid = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSceneItemSourceResponseData(string sourceName, string sourceUuid) { this.SourceName = sourceName; this.SourceUuid = sourceUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneList.Response.g.cs index 35528a2..a293190 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneList.Response.g.cs @@ -57,7 +57,7 @@ public sealed partial record GetSceneListResponseData /// [JsonPropertyName("scenes")] [Key("scenes")] - public System.Collections.Generic.List? Scenes { get; init; } + public required System.Collections.Generic.List Scenes { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -67,7 +67,8 @@ public GetSceneListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneListResponseData(string? currentProgramSceneName = null, string? currentProgramSceneUuid = null, string? currentPreviewSceneName = null, string? currentPreviewSceneUuid = null, System.Collections.Generic.List? scenes = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSceneListResponseData(System.Collections.Generic.List scenes, string? currentProgramSceneName = null, string? currentProgramSceneUuid = null, string? currentPreviewSceneName = null, string? currentPreviewSceneUuid = null) { this.CurrentProgramSceneName = currentProgramSceneName; this.CurrentProgramSceneUuid = currentProgramSceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneTransitionList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneTransitionList.Response.g.cs index c65245b..18a4fe1 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneTransitionList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneTransitionList.Response.g.cs @@ -50,7 +50,7 @@ public sealed partial record GetSceneTransitionListResponseData /// [JsonPropertyName("transitions")] [Key("transitions")] - public System.Collections.Generic.List? Transitions { get; init; } + public required System.Collections.Generic.List Transitions { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -60,7 +60,8 @@ public GetSceneTransitionListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneTransitionListResponseData(string? currentSceneTransitionName = null, string? currentSceneTransitionUuid = null, string? currentSceneTransitionKind = null, System.Collections.Generic.List? transitions = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSceneTransitionListResponseData(System.Collections.Generic.List transitions, string? currentSceneTransitionName = null, string? currentSceneTransitionUuid = null, string? currentSceneTransitionKind = null) { this.CurrentSceneTransitionName = currentSceneTransitionName; this.CurrentSceneTransitionUuid = currentSceneTransitionUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilter.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilter.Response.g.cs index ab2375a..a965564 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilter.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilter.Response.g.cs @@ -43,7 +43,7 @@ public sealed partial record GetSourceFilterResponseData /// [JsonPropertyName("filterKind")] [Key("filterKind")] - public string? FilterKind { get; init; } + public required string FilterKind { get; init; } /// /// Settings object associated with the filter @@ -61,7 +61,7 @@ public GetSourceFilterResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public GetSourceFilterResponseData(bool filterEnabled, int filterIndex, string? filterKind = null, System.Text.Json.JsonElement? filterSettings = default) + public GetSourceFilterResponseData(bool filterEnabled, int filterIndex, string filterKind, System.Text.Json.JsonElement? filterSettings = default) { this.FilterEnabled = filterEnabled; this.FilterIndex = filterIndex; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterKindList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterKindList.Response.g.cs index 62337d1..be71509 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterKindList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterKindList.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetSourceFilterKindListResponseData /// [JsonPropertyName("sourceFilterKinds")] [Key("sourceFilterKinds")] - public System.Collections.Generic.List? SourceFilterKinds { get; init; } + public required System.Collections.Generic.List SourceFilterKinds { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public GetSourceFilterKindListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSourceFilterKindListResponseData(System.Collections.Generic.List? sourceFilterKinds = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSourceFilterKindListResponseData(System.Collections.Generic.List sourceFilterKinds) { this.SourceFilterKinds = sourceFilterKinds; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterList.Response.g.cs index a469a32..8e48091 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceFilterList.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record GetSourceFilterListResponseData /// [JsonPropertyName("filters")] [Key("filters")] - public System.Collections.Generic.List? Filters { get; init; } + public required System.Collections.Generic.List Filters { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public GetSourceFilterListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSourceFilterListResponseData(System.Collections.Generic.List? filters = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSourceFilterListResponseData(System.Collections.Generic.List filters) { this.Filters = filters; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceScreenshot.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceScreenshot.Response.g.cs index f055243..7374ff4 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceScreenshot.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSourceScreenshot.Response.g.cs @@ -34,7 +34,7 @@ public sealed partial record GetSourceScreenshotResponseData /// [JsonPropertyName("imageData")] [Key("imageData")] - public string? ImageData { get; init; } + public required string ImageData { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -44,7 +44,8 @@ public GetSourceScreenshotResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSourceScreenshotResponseData(string? imageData = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSourceScreenshotResponseData(string imageData) { this.ImageData = imageData; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSpecialInputs.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSpecialInputs.Response.g.cs index 714b455..4181309 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSpecialInputs.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSpecialInputs.Response.g.cs @@ -29,42 +29,42 @@ public sealed partial record GetSpecialInputsResponseData /// [JsonPropertyName("desktop1")] [Key("desktop1")] - public string? Desktop1 { get; init; } + public required string Desktop1 { get; init; } /// /// Name of the Desktop Audio 2 input /// [JsonPropertyName("desktop2")] [Key("desktop2")] - public string? Desktop2 { get; init; } + public required string Desktop2 { get; init; } /// /// Name of the Mic/Auxiliary Audio input /// [JsonPropertyName("mic1")] [Key("mic1")] - public string? Mic1 { get; init; } + public required string Mic1 { get; init; } /// /// Name of the Mic/Auxiliary Audio 2 input /// [JsonPropertyName("mic2")] [Key("mic2")] - public string? Mic2 { get; init; } + public required string Mic2 { get; init; } /// /// Name of the Mic/Auxiliary Audio 3 input /// [JsonPropertyName("mic3")] [Key("mic3")] - public string? Mic3 { get; init; } + public required string Mic3 { get; init; } /// /// Name of the Mic/Auxiliary Audio 4 input /// [JsonPropertyName("mic4")] [Key("mic4")] - public string? Mic4 { get; init; } + public required string Mic4 { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -74,7 +74,8 @@ public GetSpecialInputsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSpecialInputsResponseData(string? desktop1 = null, string? desktop2 = null, string? mic1 = null, string? mic2 = null, string? mic3 = null, string? mic4 = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSpecialInputsResponseData(string desktop1, string desktop2, string mic1, string mic2, string mic3, string mic4) { this.Desktop1 = desktop1; this.Desktop2 = desktop2; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamServiceSettings.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamServiceSettings.Response.g.cs index 2010980..4a01c1d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamServiceSettings.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamServiceSettings.Response.g.cs @@ -36,7 +36,7 @@ public sealed partial record GetStreamServiceSettingsResponseData /// [JsonPropertyName("streamServiceType")] [Key("streamServiceType")] - public string? StreamServiceType { get; init; } + public required string StreamServiceType { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -46,7 +46,8 @@ public GetStreamServiceSettingsResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetStreamServiceSettingsResponseData(string? streamServiceType = null, System.Text.Json.JsonElement? streamServiceSettings = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetStreamServiceSettingsResponseData(string streamServiceType, System.Text.Json.JsonElement? streamServiceSettings = default) { this.StreamServiceType = streamServiceType; this.StreamServiceSettings = streamServiceSettings; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamStatus.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamStatus.Response.g.cs index c8119b0..47912f4 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamStatus.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetStreamStatus.Response.g.cs @@ -71,7 +71,7 @@ public sealed partial record GetStreamStatusResponseData /// [JsonPropertyName("outputTimecode")] [Key("outputTimecode")] - public string? OutputTimecode { get; init; } + public required string OutputTimecode { get; init; } /// /// Total number of frames delivered by the output's process @@ -89,7 +89,7 @@ public GetStreamStatusResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public GetStreamStatusResponseData(bool outputActive, bool outputReconnecting, long outputDuration, double outputCongestion, long outputBytes, int outputSkippedFrames, int outputTotalFrames, string? outputTimecode = null) + public GetStreamStatusResponseData(bool outputActive, bool outputReconnecting, string outputTimecode, long outputDuration, double outputCongestion, long outputBytes, int outputSkippedFrames, int outputTotalFrames) { this.OutputActive = outputActive; this.OutputReconnecting = outputReconnecting; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetTransitionKindList.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetTransitionKindList.Response.g.cs index b65f895..0bc3ca9 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetTransitionKindList.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetTransitionKindList.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetTransitionKindListResponseData /// [JsonPropertyName("transitionKinds")] [Key("transitionKinds")] - public System.Collections.Generic.List? TransitionKinds { get; init; } + public required System.Collections.Generic.List TransitionKinds { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public GetTransitionKindListResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetTransitionKindListResponseData(System.Collections.Generic.List? transitionKinds = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetTransitionKindListResponseData(System.Collections.Generic.List transitionKinds) { this.TransitionKinds = transitionKinds; } diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetVersion.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetVersion.Response.g.cs index 0221d1b..3bdc83c 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetVersion.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetVersion.Response.g.cs @@ -29,35 +29,35 @@ public sealed partial record GetVersionResponseData /// [JsonPropertyName("availableRequests")] [Key("availableRequests")] - public System.Collections.Generic.List? AvailableRequests { get; init; } + public required System.Collections.Generic.List AvailableRequests { get; init; } /// /// Current OBS Studio version /// [JsonPropertyName("obsVersion")] [Key("obsVersion")] - public string? ObsVersion { get; init; } + public required string ObsVersion { get; init; } /// /// Current obs-websocket version /// [JsonPropertyName("obsWebSocketVersion")] [Key("obsWebSocketVersion")] - public string? ObsWebSocketVersion { get; init; } + public required string ObsWebSocketVersion { get; init; } /// /// Name of the platform. Usually `windows`, `macos`, or `ubuntu` (linux flavor). Not guaranteed to be any of those /// [JsonPropertyName("platform")] [Key("platform")] - public string? Platform { get; init; } + public required string Platform { get; init; } /// /// Description of the platform, like `Windows 10 (10.0)` /// [JsonPropertyName("platformDescription")] [Key("platformDescription")] - public string? PlatformDescription { get; init; } + public required string PlatformDescription { get; init; } /// /// Current latest obs-websocket RPC version @@ -71,7 +71,7 @@ public sealed partial record GetVersionResponseData /// [JsonPropertyName("supportedImageFormats")] [Key("supportedImageFormats")] - public System.Collections.Generic.List? SupportedImageFormats { get; init; } + public required System.Collections.Generic.List SupportedImageFormats { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -82,7 +82,7 @@ public GetVersionResponseData() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public GetVersionResponseData(int rpcVersion, string? obsVersion = null, string? obsWebSocketVersion = null, System.Collections.Generic.List? availableRequests = default, System.Collections.Generic.List? supportedImageFormats = default, string? platform = null, string? platformDescription = null) + public GetVersionResponseData(string obsVersion, string obsWebSocketVersion, int rpcVersion, System.Collections.Generic.List availableRequests, System.Collections.Generic.List supportedImageFormats, string platform, string platformDescription) { this.ObsVersion = obsVersion; this.ObsWebSocketVersion = obsWebSocketVersion; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/StopRecord.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/StopRecord.Response.g.cs index 04d885d..d79896b 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/StopRecord.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/StopRecord.Response.g.cs @@ -29,7 +29,7 @@ public sealed partial record StopRecordResponseData /// [JsonPropertyName("outputPath")] [Key("outputPath")] - public string? OutputPath { get; init; } + public required string OutputPath { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -39,7 +39,8 @@ public StopRecordResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public StopRecordResponseData(string? outputPath = null) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public StopRecordResponseData(string outputPath) { this.OutputPath = outputPath; } diff --git a/ObsWebSocket.Core/Groups/ConfigGroup.cs b/ObsWebSocket.Core/Groups/ConfigGroup.cs index db8d7cf..fc1cc8c 100644 --- a/ObsWebSocket.Core/Groups/ConfigGroup.cs +++ b/ObsWebSocket.Core/Groups/ConfigGroup.cs @@ -57,7 +57,7 @@ public readonly partial struct ConfigGroup public Task GetStreamServiceSettingsAsync(CancellationToken cancellationToken = default) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Config.GetStreamServiceSettingsAsync(typeInfo, cancellationToken); } @@ -112,7 +112,7 @@ public Task SetStreamServiceSettingsAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Config.SetStreamServiceSettingsAsync( streamServiceType, settings, diff --git a/ObsWebSocket.Core/Groups/FiltersGroup.cs b/ObsWebSocket.Core/Groups/FiltersGroup.cs index f70830c..e164b64 100644 --- a/ObsWebSocket.Core/Groups/FiltersGroup.cs +++ b/ObsWebSocket.Core/Groups/FiltersGroup.cs @@ -99,7 +99,7 @@ public readonly partial struct FiltersGroup ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Filters.GetSourceFilterSettingsAsync( sourceName, filterName, @@ -183,7 +183,7 @@ public Task SetSourceFilterSettingsAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Filters.SetSourceFilterSettingsAsync( sourceName, filterName, @@ -270,7 +270,7 @@ public Task CreateSourceFilterAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Filters.CreateSourceFilterAsync( sourceName, filterName, @@ -329,7 +329,7 @@ public Task CreateSourceFilterAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Filters.GetSourceFilterDefaultSettingsAsync( filterKind, typeInfo, diff --git a/ObsWebSocket.Core/Groups/InputsGroup.cs b/ObsWebSocket.Core/Groups/InputsGroup.cs index e68272b..5307ff0 100644 --- a/ObsWebSocket.Core/Groups/InputsGroup.cs +++ b/ObsWebSocket.Core/Groups/InputsGroup.cs @@ -185,7 +185,7 @@ public async Task SetInputMutesAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Inputs.GetInputSettingsAsync(inputName, typeInfo, cancellationToken); } @@ -258,7 +258,7 @@ public Task SetInputSettingsAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Inputs.SetInputSettingsAsync( inputName, settings, @@ -355,7 +355,7 @@ public Task SetInputSettingsAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Inputs.CreateInputAsync( inputKind, inputName, @@ -416,7 +416,7 @@ public Task SetInputSettingsAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Inputs.GetInputDefaultSettingsAsync(inputKind, typeInfo, cancellationToken); } diff --git a/ObsWebSocket.Core/Groups/OutputsGroup.cs b/ObsWebSocket.Core/Groups/OutputsGroup.cs index 51ff2d5..21fcce1 100644 --- a/ObsWebSocket.Core/Groups/OutputsGroup.cs +++ b/ObsWebSocket.Core/Groups/OutputsGroup.cs @@ -67,7 +67,7 @@ public readonly partial struct OutputsGroup ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Outputs.GetOutputSettingsAsync(outputName, typeInfo, cancellationToken); } @@ -122,7 +122,7 @@ public Task SetOutputSettingsAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Outputs.SetOutputSettingsAsync( outputName, settings, diff --git a/ObsWebSocket.Core/Groups/TransitionsGroup.cs b/ObsWebSocket.Core/Groups/TransitionsGroup.cs index 15bd666..61238eb 100644 --- a/ObsWebSocket.Core/Groups/TransitionsGroup.cs +++ b/ObsWebSocket.Core/Groups/TransitionsGroup.cs @@ -59,7 +59,7 @@ public readonly partial struct TransitionsGroup ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Transitions.GetCurrentSceneTransitionSettingsAsync( typeInfo, cancellationToken @@ -116,7 +116,7 @@ public Task SetCurrentSceneTransitionSettingsAsync( ) where T : class { - JsonTypeInfo typeInfo = ObsWebSocketClientHelpers.GetRegisteredTypeInfo(); + JsonTypeInfo typeInfo = ObsWebSocketClientOperations.GetRegisteredTypeInfo(); return client.Transitions.SetCurrentSceneTransitionSettingsAsync( settings, typeInfo, diff --git a/ObsWebSocket.Core/ObsWebSocketClient.Helper.Convenience.cs b/ObsWebSocket.Core/ObsWebSocketClientOperations.Conveniences.cs similarity index 99% rename from ObsWebSocket.Core/ObsWebSocketClient.Helper.Convenience.cs rename to ObsWebSocket.Core/ObsWebSocketClientOperations.Conveniences.cs index 6b993f1..c38ec47 100644 --- a/ObsWebSocket.Core/ObsWebSocketClient.Helper.Convenience.cs +++ b/ObsWebSocket.Core/ObsWebSocketClientOperations.Conveniences.cs @@ -8,7 +8,7 @@ namespace ObsWebSocket.Core; /// The client level conveniences: waiting for an event, and sending a batch. Neither belongs to /// one protocol category, so both stay on the client rather than on a category group. /// -public static partial class ObsWebSocketClientHelpers +public static partial class ObsWebSocketClientOperations { extension(ObsWebSocketClient client) { diff --git a/ObsWebSocket.Core/ObsWebSocketClient.Helper.cs b/ObsWebSocket.Core/ObsWebSocketClientOperations.cs similarity index 97% rename from ObsWebSocket.Core/ObsWebSocketClient.Helper.cs rename to ObsWebSocket.Core/ObsWebSocketClientOperations.cs index df5762b..2dbafb7 100644 --- a/ObsWebSocket.Core/ObsWebSocketClient.Helper.cs +++ b/ObsWebSocket.Core/ObsWebSocketClientOperations.cs @@ -9,7 +9,7 @@ namespace ObsWebSocket.Core; /// WaitForEventAsync. Everything category scoped lives on the category group instead, so /// client.Scenes, client.Inputs and the rest are where those methods are found. /// -public static partial class ObsWebSocketClientHelpers +public static partial class ObsWebSocketClientOperations { private static readonly JsonSerializerOptions s_helperJsonOptions = CreateHelperOptions(); diff --git a/ObsWebSocket.Core/Protocol/Common/StubTypes.cs b/ObsWebSocket.Core/Protocol/Common/StubTypes.cs index a5da120..c2fadd3 100644 --- a/ObsWebSocket.Core/Protocol/Common/StubTypes.cs +++ b/ObsWebSocket.Core/Protocol/Common/StubTypes.cs @@ -14,17 +14,17 @@ public sealed class SceneStub /// Scene name. [JsonPropertyName("sceneName")] [Key("sceneName")] - public string? SceneName { get; init; } + public required string SceneName { get; init; } /// Scene UUID. [JsonPropertyName("sceneUuid")] [Key("sceneUuid")] - public string? SceneUuid { get; init; } + public required string SceneUuid { get; init; } /// Scene index position. [JsonPropertyName("sceneIndex")] [Key("sceneIndex")] - public double? SceneIndex { get; init; } + public required int SceneIndex { get; init; } /// Captures any extra fields not explicitly defined in the stub. [IgnoreMember] @@ -48,126 +48,126 @@ public sealed class SceneItemTransformStub /// [JsonPropertyName("positionX")] [Key("positionX")] - public double? PositionX { get; init; } + public required double PositionX { get; init; } /// /// Position X value. /// [JsonPropertyName("positionY")] [Key("positionY")] - public double? PositionY { get; init; } + public required double PositionY { get; init; } /// /// Rotation value. /// [JsonPropertyName("rotation")] [Key("rotation")] - public double? Rotation { get; init; } + public required double Rotation { get; init; } /// /// Scale X value. /// [JsonPropertyName("scaleX")] [Key("scaleX")] - public double? ScaleX { get; init; } + public required double ScaleX { get; init; } /// /// Scale Y value. /// [JsonPropertyName("scaleY")] [Key("scaleY")] - public double? ScaleY { get; init; } + public required double ScaleY { get; init; } /// /// Width value. /// [JsonPropertyName("width")] [Key("width")] - public double? Width { get; init; } + public required double Width { get; init; } /// /// Height value. /// [JsonPropertyName("height")] [Key("height")] - public double? Height { get; init; } + public required double Height { get; init; } /// /// Source width value. /// [JsonPropertyName("sourceWidth")] [Key("sourceWidth")] - public double? SourceWidth { get; init; } + public required double SourceWidth { get; init; } /// /// Source height value. /// [JsonPropertyName("sourceHeight")] [Key("sourceHeight")] - public double? SourceHeight { get; init; } + public required double SourceHeight { get; init; } /// /// Alignment value. /// [JsonPropertyName("alignment")] [Key("alignment")] - public double? Alignment { get; init; } + public required int Alignment { get; init; } /// /// Bounds type value. /// [JsonPropertyName("boundsType")] [Key("boundsType")] - public string? BoundsType { get; init; } + public required string BoundsType { get; init; } /// /// Bounds alignment value. /// [JsonPropertyName("boundsAlignment")] [Key("boundsAlignment")] - public double? BoundsAlignment { get; init; } + public required int BoundsAlignment { get; init; } /// /// Bounds width value. /// [JsonPropertyName("boundsWidth")] [Key("boundsWidth")] - public double? BoundsWidth { get; init; } + public required double BoundsWidth { get; init; } /// /// Bounds height value. /// [JsonPropertyName("boundsHeight")] [Key("boundsHeight")] - public double? BoundsHeight { get; init; } + public required double BoundsHeight { get; init; } /// /// Crop left value. /// [JsonPropertyName("cropLeft")] [Key("cropLeft")] - public double? CropLeft { get; init; } + public required int CropLeft { get; init; } /// /// Crop top value. /// [JsonPropertyName("cropTop")] [Key("cropTop")] - public double? CropTop { get; init; } + public required int CropTop { get; init; } /// /// Crop right value. /// [JsonPropertyName("cropRight")] [Key("cropRight")] - public double? CropRight { get; init; } + public required int CropRight { get; init; } /// /// Crop bottom value. /// [JsonPropertyName("cropBottom")] [Key("cropBottom")] - public double? CropBottom { get; init; } + public required int CropBottom { get; init; } /// Captures any extra fields not explicitly defined in the stub. [IgnoreMember] @@ -189,32 +189,32 @@ public sealed class SceneItemStub /// Scene item ID. [JsonPropertyName("sceneItemId")] [Key("sceneItemId")] - public double? SceneItemId { get; init; } + public required int SceneItemId { get; init; } /// Scene item index position. [JsonPropertyName("sceneItemIndex")] [Key("sceneItemIndex")] - public double? SceneItemIndex { get; init; } + public required int SceneItemIndex { get; init; } /// Name of the source associated with the scene item. [JsonPropertyName("sourceName")] [Key("sourceName")] - public string? SourceName { get; init; } + public required string SourceName { get; init; } /// UUID of the source associated with the scene item. [JsonPropertyName("sourceUuid")] [Key("sourceUuid")] - public string? SourceUuid { get; init; } + public required string SourceUuid { get; init; } /// Whether the scene item is enabled (visible). [JsonPropertyName("sceneItemEnabled")] [Key("sceneItemEnabled")] - public bool? SceneItemEnabled { get; init; } + public required bool SceneItemEnabled { get; init; } /// Whether the scene item is locked. [JsonPropertyName("sceneItemLocked")] [Key("sceneItemLocked")] - public bool? SceneItemLocked { get; init; } + public required bool SceneItemLocked { get; init; } /// Whether the source is a group. [JsonPropertyName("isGroup")] @@ -224,7 +224,7 @@ public sealed class SceneItemStub /// Transform data for the scene item. [JsonPropertyName("sceneItemTransform")] [Key("sceneItemTransform")] - public SceneItemTransformStub? SceneItemTransform { get; init; } // Made nullable for safety + public required SceneItemTransformStub SceneItemTransform { get; init; } // Made nullable for safety /// Captures any extra fields not explicitly defined in the stub. [IgnoreMember] @@ -246,22 +246,22 @@ public sealed class FilterStub /// Filter name. [JsonPropertyName("filterName")] [Key("filterName")] - public string? FilterName { get; init; } + public required string FilterName { get; init; } /// Filter kind. [JsonPropertyName("filterKind")] [Key("filterKind")] - public string? FilterKind { get; init; } + public required string FilterKind { get; init; } /// Filter index position. [JsonPropertyName("filterIndex")] [Key("filterIndex")] - public double? FilterIndex { get; init; } + public required int FilterIndex { get; init; } /// Whether the filter is enabled. [JsonPropertyName("filterEnabled")] [Key("filterEnabled")] - public bool? FilterEnabled { get; init; } + public required bool FilterEnabled { get; init; } /// Filter settings object. [JsonPropertyName("filterSettings")] @@ -288,22 +288,22 @@ public sealed class InputStub /// Input name. [JsonPropertyName("inputName")] [Key("inputName")] - public string? InputName { get; init; } + public required string InputName { get; init; } /// Input UUID. [JsonPropertyName("inputUuid")] [Key("inputUuid")] - public string? InputUuid { get; init; } + public required string InputUuid { get; init; } /// Input kind. [JsonPropertyName("inputKind")] [Key("inputKind")] - public string? InputKind { get; init; } + public required string InputKind { get; init; } /// Unversioned input kind. [JsonPropertyName("unversionedInputKind")] [Key("unversionedInputKind")] - public string? UnversionedInputKind { get; init; } + public required string UnversionedInputKind { get; init; } /// Captures any extra fields not explicitly defined in the stub. [IgnoreMember] @@ -325,27 +325,27 @@ public sealed class TransitionStub /// Transition name. [JsonPropertyName("transitionName")] [Key("transitionName")] - public string? TransitionName { get; init; } + public required string TransitionName { get; init; } /// Transition UUID. [JsonPropertyName("transitionUuid")] [Key("transitionUuid")] - public string? TransitionUuid { get; init; } + public required string TransitionUuid { get; init; } /// Transition kind. [JsonPropertyName("transitionKind")] [Key("transitionKind")] - public string? TransitionKind { get; init; } + public required string TransitionKind { get; init; } /// Whether the transition is configurable. [JsonPropertyName("transitionConfigurable")] [Key("transitionConfigurable")] - public bool? TransitionConfigurable { get; init; } + public required bool TransitionConfigurable { get; init; } /// Whether the transition duration is fixed. [JsonPropertyName("transitionFixed")] [Key("transitionFixed")] - public bool? TransitionFixed { get; init; } + public required bool TransitionFixed { get; init; } /// Captures any extra fields not explicitly defined in the stub. [IgnoreMember] @@ -367,27 +367,27 @@ public sealed class OutputStub /// Output name. [JsonPropertyName("outputName")] [Key("outputName")] - public string? OutputName { get; init; } + public required string OutputName { get; init; } /// Output kind. [JsonPropertyName("outputKind")] [Key("outputKind")] - public string? OutputKind { get; init; } + public required string OutputKind { get; init; } /// Whether the output is active. [JsonPropertyName("outputActive")] [Key("outputActive")] - public bool? OutputActive { get; init; } + public required bool OutputActive { get; init; } /// Output width. [JsonPropertyName("outputWidth")] [Key("outputWidth")] - public double? OutputWidth { get; init; } + public required int OutputWidth { get; init; } /// Output height. [JsonPropertyName("outputHeight")] [Key("outputHeight")] - public double? OutputHeight { get; init; } + public required int OutputHeight { get; init; } /// Output settings. [JsonPropertyName("outputSettings")] @@ -414,32 +414,32 @@ public sealed class MonitorStub /// Monitor name. [JsonPropertyName("monitorName")] [Key("monitorName")] - public string? MonitorName { get; init; } + public required string MonitorName { get; init; } /// Monitor index. [JsonPropertyName("monitorIndex")] [Key("monitorIndex")] - public double? MonitorIndex { get; init; } + public required int MonitorIndex { get; init; } /// Monitor width. [JsonPropertyName("monitorWidth")] [Key("monitorWidth")] - public double? MonitorWidth { get; init; } + public required int MonitorWidth { get; init; } /// Monitor height. [JsonPropertyName("monitorHeight")] [Key("monitorHeight")] - public double? MonitorHeight { get; init; } + public required int MonitorHeight { get; init; } /// Monitor position X. [JsonPropertyName("monitorPositionX")] [Key("monitorPositionX")] - public double? MonitorPositionX { get; init; } + public required int MonitorPositionX { get; init; } /// Monitor position Y. [JsonPropertyName("monitorPositionY")] [Key("monitorPositionY")] - public double? MonitorPositionY { get; init; } + public required int MonitorPositionY { get; init; } /// Captures any extra fields not explicitly defined in the stub. [IgnoreMember] diff --git a/ObsWebSocket.Example/Worker.cs b/ObsWebSocket.Example/Worker.cs index d3b4abd..d83ad41 100644 --- a/ObsWebSocket.Example/Worker.cs +++ b/ObsWebSocket.Example/Worker.cs @@ -454,7 +454,9 @@ await _obsClient.Filters.GetSourceFilterListAsync( _ = table.AddColumn("Enabled"); foreach (Core.Protocol.Common.FilterStub filterElement in filterList.Filters) { - string filterIndex = filterElement.FilterIndex?.ToString() ?? "N/A"; + string filterIndex = filterElement.FilterIndex.ToString( + System.Globalization.CultureInfo.InvariantCulture + ); string filterName = Markup.Escape(filterElement.FilterName ?? "N/A") ?? "N/A"; string filterKind = diff --git a/ObsWebSocket.Tests/BatchResultTests.cs b/ObsWebSocket.Tests/BatchResultTests.cs index ac18787..64e902d 100644 --- a/ObsWebSocket.Tests/BatchResultTests.cs +++ b/ObsWebSocket.Tests/BatchResultTests.cs @@ -47,10 +47,7 @@ private static RequestResponsePayload MsgPackResult(string type, object [TestMethod] public void GetRequiredData_JsonPayload_Deserializes() { - RequestResponsePayload result = JsonResult( - "GetVersion", - new GetVersionResponseData { ObsVersion = "32.2.2", RpcVersion = 1 } - ); + RequestResponsePayload result = JsonResult("GetVersion", TestUtils.SampleVersion()); Assert.AreEqual("32.2.2", result.GetRequiredData().ObsVersion); } @@ -61,7 +58,7 @@ public void GetRequiredData_MsgPackPayload_Deserializes() // The MessagePack transport hands back raw payload bytes rather than a JsonElement. RequestResponsePayload result = MsgPackResult( "GetVersion", - new GetVersionResponseData { ObsVersion = "32.2.2", RpcVersion = 1 } + TestUtils.SampleVersion() ); Assert.AreEqual("32.2.2", result.GetRequiredData().ObsVersion); @@ -113,10 +110,7 @@ public void AllSucceededAndGetFailures_ReflectStatuses() { List> results = [ - JsonResult( - "GetVersion", - new GetVersionResponseData { ObsVersion = "1", RpcVersion = 1 } - ), + JsonResult("GetVersion", TestUtils.SampleVersion("1")), new("GetStats", "GetStats_1", new RequestStatus(false, 604, "nope"), null), ]; diff --git a/ObsWebSocket.Tests/EventStreamTests.cs b/ObsWebSocket.Tests/EventStreamTests.cs index fe9080b..37447cd 100644 --- a/ObsWebSocket.Tests/EventStreamTests.cs +++ b/ObsWebSocket.Tests/EventStreamTests.cs @@ -34,7 +34,10 @@ public void Raise(string sceneName) => Fired?.Invoke( this, new CurrentProgramSceneChangedEventArgs( - new CurrentProgramSceneChangedPayload(sceneName: sceneName) + new CurrentProgramSceneChangedPayload( + sceneName: sceneName, + sceneUuid: Guid.NewGuid().ToString() + ) ) ); } diff --git a/ObsWebSocket.Tests/ObsWebSocketClientTests.cs b/ObsWebSocket.Tests/ObsWebSocketClientTests.cs index b7a90a0..5c75bd9 100644 --- a/ObsWebSocket.Tests/ObsWebSocketClientTests.cs +++ b/ObsWebSocket.Tests/ObsWebSocketClientTests.cs @@ -149,7 +149,7 @@ public async Task CallBatchAsync_ValidRequest_SendsCorrectBatchMessage() List requests = [request1, request2]; // Expected Response Payloads (for simulation) - GetVersionResponseData response1Data = new(1, "v1", "v5", [], [], "windows", "windows 11"); + GetVersionResponseData response1Data = new("v1", "v5", 1, [], [], "windows", "windows 11"); RequestResponsePayload response1 = new( "GetVersion", "batch1_0", diff --git a/ObsWebSocket.Tests/PayloadShapeTests.cs b/ObsWebSocket.Tests/PayloadShapeTests.cs index 5b652b8..1c9a328 100644 --- a/ObsWebSocket.Tests/PayloadShapeTests.cs +++ b/ObsWebSocket.Tests/PayloadShapeTests.cs @@ -22,7 +22,7 @@ private static RequestResponsePayload Row(object payload) => [TestMethod] public void MsgPack_ReadingAPayloadAsTheWrongRecord_Throws() { - GetVersionResponseData version = new() { RpcVersion = 1, ObsVersion = "32.2.2" }; + GetVersionResponseData version = TestUtils.SampleVersion(); byte[] packed = MessagePackSerializer.Serialize( version, MsgPackMessageSerializer.s_msgPackOptions @@ -50,7 +50,7 @@ public void Json_ReadingAPayloadAsTheWrongRecord_Throws() [TestMethod] public void ReadingAPayloadAsItsOwnRecord_StillWorks() { - GetVersionResponseData version = new() { RpcVersion = 1, ObsVersion = "32.2.2" }; + GetVersionResponseData version = TestUtils.SampleVersion(); byte[] packed = MessagePackSerializer.Serialize( version, MsgPackMessageSerializer.s_msgPackOptions diff --git a/ObsWebSocket.Tests/ReadmeCompileCheck.cs b/ObsWebSocket.Tests/ReadmeCompileCheck.cs index 95d6e23..86eaf08 100644 --- a/ObsWebSocket.Tests/ReadmeCompileCheck.cs +++ b/ObsWebSocket.Tests/ReadmeCompileCheck.cs @@ -314,10 +314,15 @@ CancellationToken ct { string wire = MediaInputAction.Restart.ToWireValue(); + // Request data has to be a JsonElement or a type the serializer context knows. An + // anonymous object compiles and then throws at runtime, so the README does not use one. + using System.Text.Json.JsonDocument body = System.Text.Json.JsonDocument.Parse( + """{"someField":1}""" + ); System.Text.Json.JsonElement? raw = await client.CallAsyncValue( "SomeNewRequest", - new { someField = 1 }, + body.RootElement, cancellationToken: ct ); _ = $"{wire} {raw}"; @@ -379,10 +384,15 @@ internal static async Task LowLevelAsync(ObsWebSocketClient client, Cancellation cancellationToken: ct ); + // Request data has to be a JsonElement or a type the serializer context knows. An + // anonymous object compiles and then throws at runtime, so the README does not use one. + using System.Text.Json.JsonDocument body = System.Text.Json.JsonDocument.Parse( + """{"someField":1}""" + ); System.Text.Json.JsonElement? raw = await client.CallAsyncValue( "SomeNewRequest", - new { someField = 1 }, + body.RootElement, cancellationToken: ct ); diff --git a/ObsWebSocket.Tests/SerializerBehaviorTests.cs b/ObsWebSocket.Tests/SerializerBehaviorTests.cs index 10a67f4..2bdd592 100644 --- a/ObsWebSocket.Tests/SerializerBehaviorTests.cs +++ b/ObsWebSocket.Tests/SerializerBehaviorTests.cs @@ -122,7 +122,7 @@ public void MsgPackSerializer_DeserializePayload_WithComplexFilterBytes_Deserial Assert.AreEqual("Color Correction", filters[0].FilterName); Assert.AreEqual("color_filter_v2", filters[0].FilterKind); Assert.AreEqual(0, filters[0].FilterIndex); - Assert.IsTrue(filters[0].FilterEnabled ?? false); + Assert.IsTrue(filters[0].FilterEnabled); Assert.IsTrue(filters[0].FilterSettings.HasValue); JsonElement filterSettings = filters[0].FilterSettings.GetValueOrDefault(); Assert.AreEqual(0.8d, filterSettings.GetProperty("opacity").GetDouble(), 0.0001d); @@ -387,8 +387,8 @@ public void MsgPackSerializer_DeserializePayload_SceneItemList_WithTransformAndE Assert.AreEqual("Camera", sceneItems[0].SourceName); Core.Protocol.Common.SceneItemTransformStub? transform = sceneItems[0].SceneItemTransform; Assert.IsNotNull(transform); - Assert.IsTrue(transform.Width.HasValue); - Assert.AreEqual(1920d, transform.Width.Value, 0.001d); + Assert.AreNotEqual(0, transform.Width); + Assert.AreEqual(1920d, transform.Width, 0.001d); Dictionary? extensionData = sceneItems[0].ExtensionData; Assert.IsNotNull(extensionData); Assert.AreEqual("group-A", extensionData["customGroup"].GetString()); @@ -448,7 +448,7 @@ public void MsgPackSerializer_SerializeThenDeserialize_FilterPayload_RoundTripsW Assert.AreEqual("Limiter", roundTrip.Filters[1].FilterName); Assert.AreEqual("limiter_filter_v2", roundTrip.Filters[1].FilterKind); Assert.AreEqual(1, roundTrip.Filters[1].FilterIndex); - Assert.IsFalse(roundTrip.Filters[1].FilterEnabled ?? true); + Assert.IsFalse(roundTrip.Filters[1].FilterEnabled); Assert.IsFalse(roundTrip.Filters[1].FilterSettings.HasValue); } @@ -592,6 +592,53 @@ private static byte[] BuildTriggerHotkeyNestedPayloadBytes() return [.. buffer.WrittenSpan]; } + /// + /// Writes a scene item transform with every field OBS sends. The stub names them required, so + /// a partial map is no longer a payload OBS would produce. + /// + private static void WriteTransform(ref MessagePackWriter writer, double width, double height) + { + writer.WriteMapHeader(19); + writer.Write("alignment"); + writer.Write(5); + writer.Write("boundsAlignment"); + writer.Write(0); + writer.Write("boundsHeight"); + writer.Write(0d); + writer.Write("boundsType"); + writer.Write("OBS_BOUNDS_NONE"); + writer.Write("boundsWidth"); + writer.Write(0d); + writer.Write("cropBottom"); + writer.Write(0); + writer.Write("cropLeft"); + writer.Write(0); + writer.Write("cropRight"); + writer.Write(0); + writer.Write("cropToBounds"); + writer.Write(false); + writer.Write("cropTop"); + writer.Write(0); + writer.Write("height"); + writer.Write(height); + writer.Write("positionX"); + writer.Write(0d); + writer.Write("positionY"); + writer.Write(0d); + writer.Write("rotation"); + writer.Write(0d); + writer.Write("scaleX"); + writer.Write(1d); + writer.Write("scaleY"); + writer.Write(1d); + writer.Write("sourceHeight"); + writer.Write(height); + writer.Write("sourceWidth"); + writer.Write(width); + writer.Write("width"); + writer.Write(width); + } + private static byte[] BuildSceneItemListPayloadBytes() { ArrayBufferWriter buffer = new(); @@ -600,19 +647,21 @@ private static byte[] BuildSceneItemListPayloadBytes() writer.WriteMapHeader(1); writer.Write("sceneItems"); writer.WriteArrayHeader(1); - writer.WriteMapHeader(6); + writer.WriteMapHeader(9); writer.Write("sceneItemId"); writer.Write(42); + writer.Write("sceneItemIndex"); + writer.Write(0); writer.Write("sourceName"); writer.Write("Camera"); + writer.Write("sourceUuid"); + writer.Write("11111111-2222-3333-4444-555555555555"); writer.Write("sceneItemEnabled"); writer.Write(true); + writer.Write("sceneItemLocked"); + writer.Write(false); writer.Write("sceneItemTransform"); - writer.WriteMapHeader(2); - writer.Write("width"); - writer.Write(1920d); - writer.Write("height"); - writer.Write(1080d); + WriteTransform(ref writer, 1920d, 1080d); writer.Write("customGroup"); writer.Write("group-A"); writer.Write("customArray"); diff --git a/ObsWebSocket.Tests/TestUtils.cs b/ObsWebSocket.Tests/TestUtils.cs index 9e772cc..f0c18f3 100644 --- a/ObsWebSocket.Tests/TestUtils.cs +++ b/ObsWebSocket.Tests/TestUtils.cs @@ -228,6 +228,25 @@ Mock mockConnection /// /// Invokes the private ProcessIncomingMessage method on the client instance using reflection. /// + /// + /// A fully populated GetVersion response. Response records name every field OBS always sends + /// as required, so a test that only cares about one of them still has to supply the rest. + /// + /// The version to report. + internal static ObsWebSocket.Core.Protocol.Responses.GetVersionResponseData SampleVersion( + string obsVersion = "32.2.2" + ) => + new() + { + ObsVersion = obsVersion, + ObsWebSocketVersion = "5.7.0", + RpcVersion = 1, + AvailableRequests = [], + SupportedImageFormats = [], + Platform = "windows", + PlatformDescription = "Windows 11", + }; + internal static void InvokeProcessIncomingMessage( ObsWebSocketClient client, object messageObject diff --git a/ObsWebSocket.Tests/TypedSettingsTests.cs b/ObsWebSocket.Tests/TypedSettingsTests.cs index c115a18..fd3c6bf 100644 --- a/ObsWebSocket.Tests/TypedSettingsTests.cs +++ b/ObsWebSocket.Tests/TypedSettingsTests.cs @@ -831,7 +831,10 @@ Mock mockConnection BrowserSourceSettings settings = new(Url: "https://create.test", Width: 800, Height: 600); JsonElement? capturedSettings = null; - CreateInputResponseData responseDto = new(sceneItemId: 42); + CreateInputResponseData responseDto = new( + inputUuid: Guid.NewGuid().ToString(), + sceneItemId: 42 + ); _ = mockConnection .Setup(ws => @@ -913,7 +916,10 @@ Mock mockConnection .Default .TestConsumerSettings; JsonElement? capturedSettings = null; - CreateInputResponseData responseDto = new(sceneItemId: 7); + CreateInputResponseData responseDto = new( + inputUuid: Guid.NewGuid().ToString(), + sceneItemId: 7 + ); _ = mockConnection .Setup(ws => diff --git a/README.md b/README.md index af5ec97..1734c13 100644 --- a/README.md +++ b/README.md @@ -421,8 +421,14 @@ GetVersionResponseData? v = await client.CallAsync("GetV // A value type response, JsonElement included. CallAsync is constrained to classes, so a struct // response goes through CallAsyncValue. -JsonElement? raw = await client.CallAsyncValue( - "SomeNewRequest", new { someField = 1 }, cancellationToken: ct); +JsonElement? raw = await client.CallAsyncValue("GetStats", null, cancellationToken: ct); + +// Request data must be a JsonElement or a type the library's serializer context knows, because +// the payload is written through a source generated context. An anonymous object has no metadata +// there and throws ObsWebSocketSerializationException at runtime. +using JsonDocument body = JsonDocument.Parse("""{"someField":1}"""); +JsonElement? answer = await client.CallAsyncValue( + "SomeNewRequest", body.RootElement, cancellationToken: ct); // A batch assembled by hand, without the typed builder. List> results = await client.CallBatchAsync( From 59d785c0f84340b9f4d3f5784df861cd997251c0 Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 26 Aug 2026 15:05:06 +0200 Subject: [PATCH 10/13] docs: correct the README against what actually runs Rereading it found three things that could not work. The grouped surface example awaited an IAsyncEnumerable, which does not compile. The errors example still read ex.Status?.Code, removed when Status collapsed into StatusCode. The example app section described a fraction of the suite it now runs. The low level examples are verified against OBS 32.2.2 rather than asserted: CallAsync with a reference type response, CallAsyncValue with none, CallAsyncValue with a JsonElement body, and the anonymous object that throws as documented. WaitForEventAsync threw the BCL TimeoutException while a request timeout threw ObsWebSocketTimeoutException, so catch (ObsWebSocketException) did not cover a wait that timed out, and the doc comment claimed it did. One timeout type now. sceneItemTransform is a concrete stub for an object OBS always sends rather than a settings bag, so it follows the other non-nullable fields, and the dead null assertions the analyzer flagged are gone. Four of those were narrowing genuinely nullable members and are restored. --- .../Generation/Emitter.DtoGeneration.cs | 15 +++++-- .../Generation/Emitter.WaitForEvent.cs | 4 +- .../ObsWebSocketClient.WaitForEvent.g.cs | 2 +- ...ceneItemTransformChanged.EventPayload.g.cs | 4 +- .../GetSceneItemTransform.Response.g.cs | 5 ++- .../ObsWebSocketClientIntegrationTests.cs | 41 ++----------------- ObsWebSocket.Tests/ReadmeCompileCheck.cs | 31 ++++++++++++++ README.md | 20 +++++---- 8 files changed, 68 insertions(+), 54 deletions(-) diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs index eb217ec..cbfa145 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.DtoGeneration.cs @@ -511,11 +511,18 @@ ProtocolDefinition protocol { // Strings and arrays are always sent. A dictionary or a JsonElement is a // settings bag that genuinely may not be there, so those stay nullable. - // The array mapper bakes the "?" into the type it returns, so a list has - // to have it stripped here rather than merely left off the suffix. + // The array and stub mappers bake the "?" into the type they return, so + // those have to have it stripped here rather than merely left off the + // suffix. A stub is a concrete record for an object OBS always sends, + // unlike a JsonElement settings bag. bool isList = csharpType.Contains("List<"); - isConsideredRequired = csharpType == "string" || isList; - if (isList && csharpType.EndsWith("?", StringComparison.Ordinal)) + bool isStub = + csharpType.EndsWith("Stub?", StringComparison.Ordinal) + || csharpType.EndsWith("Stub", StringComparison.Ordinal); + isConsideredRequired = csharpType == "string" || isList || isStub; + if ( + (isList || isStub) && csharpType.EndsWith("?", StringComparison.Ordinal) + ) { csharpType = csharpType.Substring(0, csharpType.Length - 1); } diff --git a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.WaitForEvent.cs b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.WaitForEvent.cs index f3e7460..bd62e0a 100644 --- a/ObsWebSocket.Codegen.Tasks/Generation/Emitter.WaitForEvent.cs +++ b/ObsWebSocket.Codegen.Tasks/Generation/Emitter.WaitForEvent.cs @@ -253,7 +253,9 @@ ProtocolDefinition protocol ); builder.AppendLine(" tcs.TrySetCanceled(linkedCts.Token);"); builder.AppendLine( - " throw new TimeoutException($\"Timed out after {timeout} waiting for {typeof(TEventArgs).Name}.\");" + // The library's own timeout type, so catching ObsWebSocketException covers a wait + // that timed out as well as a request that did. + " throw new ObsWebSocketTimeoutException($\"Timed out after {timeout} waiting for {typeof(TEventArgs).Name}.\");" ); builder.AppendLine(" }"); builder.AppendLine(" catch (Exception ex)"); diff --git a/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.WaitForEvent.g.cs b/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.WaitForEvent.g.cs index 50db1a3..3c21fd2 100644 --- a/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.WaitForEvent.g.cs +++ b/ObsWebSocket.Core/Generated/Client/ObsWebSocketClient.WaitForEvent.g.cs @@ -1320,7 +1320,7 @@ public static async Task WaitForEventAsync( { client._logger.LogDebug("WaitForEventAsync<{EventType}> timed out after {Timeout}.", typeof(TEventArgs).Name, timeout); tcs.TrySetCanceled(linkedCts.Token); - throw new TimeoutException($"Timed out after {timeout} waiting for {typeof(TEventArgs).Name}."); + throw new ObsWebSocketTimeoutException($"Timed out after {timeout} waiting for {typeof(TEventArgs).Name}."); } catch (Exception ex) { diff --git a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs index a893eca..636a70d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Events/SceneItemTransformChanged.EventPayload.g.cs @@ -36,7 +36,7 @@ public sealed partial record SceneItemTransformChangedPayload /// [JsonPropertyName("sceneItemTransform")] [Key("sceneItemTransform")] - public ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub? SceneItemTransform { get; init; } + public required ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub SceneItemTransform { get; init; } /// /// The name of the scene the item is in @@ -61,7 +61,7 @@ public SceneItemTransformChangedPayload() { } /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] - public SceneItemTransformChangedPayload(string sceneName, string sceneUuid, int sceneItemId, ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub? sceneItemTransform = default) + public SceneItemTransformChangedPayload(string sceneName, string sceneUuid, int sceneItemId, ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub sceneItemTransform) { this.SceneName = sceneName; this.SceneUuid = sceneUuid; diff --git a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemTransform.Response.g.cs b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemTransform.Response.g.cs index a452d9d..dad352d 100644 --- a/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemTransform.Response.g.cs +++ b/ObsWebSocket.Core/Generated/Protocol/Responses/GetSceneItemTransform.Response.g.cs @@ -31,7 +31,7 @@ public sealed partial record GetSceneItemTransformResponseData /// [JsonPropertyName("sceneItemTransform")] [Key("sceneItemTransform")] - public ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub? SceneItemTransform { get; init; } + public required ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub SceneItemTransform { get; init; } /// Initializes a new instance for deserialization via . [JsonConstructor] @@ -41,7 +41,8 @@ public GetSceneItemTransformResponseData() { } /// Initializes a new instance with all properties specified. /// Parameters are ordered with required properties first, then optional properties (with defaults). Follows protocol definition order where possible. /// - public GetSceneItemTransformResponseData(ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub? sceneItemTransform = default) + [System.Diagnostics.CodeAnalysis.SetsRequiredMembers] + public GetSceneItemTransformResponseData(ObsWebSocket.Core.Protocol.Common.SceneItemTransformStub sceneItemTransform) { this.SceneItemTransform = sceneItemTransform; } diff --git a/ObsWebSocket.Tests/ObsWebSocketClientIntegrationTests.cs b/ObsWebSocket.Tests/ObsWebSocketClientIntegrationTests.cs index 3ae7f22..0bfa367 100644 --- a/ObsWebSocket.Tests/ObsWebSocketClientIntegrationTests.cs +++ b/ObsWebSocket.Tests/ObsWebSocketClientIntegrationTests.cs @@ -150,7 +150,6 @@ public async Task ConnectDisconnect_ValidUri_Succeeds() Assert.IsTrue(client.IsConnected, "Client should be connected after ConnectAsync."); Assert.IsTrue(connectedEventFired, "Connected event should have fired."); - Assert.IsNotNull(client.NegotiatedRpcVersion, "NegotiatedRpcVersion should be set."); Assert.IsTrue(client.NegotiatedRpcVersion >= 1, "NegotiatedRpcVersion should be >= 1."); Assert.IsNotNull( client.CurrentEventSubscriptions, @@ -198,7 +197,6 @@ public async Task GetVersion_ReturnsValidData() "ObsWebSocketVersion should not be empty." ); Assert.IsGreaterThanOrEqualTo(version.RpcVersion, 1, "RpcVersion should be >= 1."); - Assert.IsNotNull(version.AvailableRequests, "AvailableRequests should not be null."); Assert.IsNotEmpty(version.AvailableRequests, "AvailableRequests should not be empty."); } @@ -355,7 +353,6 @@ public async Task GetSceneList_ReturnsSceneStubs() GetSceneListResponseData? response = await client.Scenes.GetSceneListAsync(new()); Assert.IsNotNull(response, "GetSceneList response was null."); - Assert.IsNotNull(response.Scenes, "Scenes list was null."); Assert.IsNotEmpty(response.Scenes, "Expected at least one scene in the list."); // Find the test scene using the SceneStub @@ -397,7 +394,6 @@ public async Task GetInputList_ReturnsInputStubs() ); // Empty request data Assert.IsNotNull(response, "GetInputList response was null."); - Assert.IsNotNull(response.Inputs, "Inputs list was null."); Assert.IsNotEmpty(response.Inputs, "Expected at least one input in the list."); // Find the test input @@ -457,16 +453,9 @@ await client.SceneItems.GetSceneItemTransformAsync( ); Assert.IsNotNull(transformResponse, "GetSceneItemTransform response was null."); - Assert.IsNotNull(transformResponse.SceneItemTransform, "SceneItemTransform data was null."); // Validate some core transform properties SceneItemTransformStub transform = transformResponse.SceneItemTransform; - Assert.IsNotNull(transform.PositionX, "PositionX should have a value."); - Assert.IsNotNull(transform.PositionY, "PositionY should have a value."); - Assert.IsNotNull(transform.ScaleX, "ScaleX should have a value."); - Assert.IsNotNull(transform.ScaleY, "ScaleY should have a value."); - Assert.IsNotNull(transform.Width, "Width should have a value."); - Assert.IsNotNull(transform.Height, "Height should have a value."); Trace.WriteLine( $"Transform for Item {sceneItemId}: Pos=({transform.PositionX},{transform.PositionY}), Scale=({transform.ScaleX},{transform.ScaleY}), Size=({transform.Width}x{transform.Height})" ); @@ -497,7 +486,7 @@ public async Task GetInputAudioTracks_ReturnsDictionary() } Assert.IsNotNull(response, "GetInputAudioTracks response was null."); - Assert.IsNotNull(response.InputAudioTracks, "InputAudioTracks dictionary was null."); + Assert.IsNotNull(response.InputAudioTracks, "Audio tracks object was null."); Assert.IsNotEmpty(response.InputAudioTracks, "Expected at least one audio track."); // Check if common tracks exist (OBS usually has 6) @@ -549,7 +538,6 @@ public async Task GetInputSettings_TextGDI_ReturnsJsonElement() } Assert.IsNotNull(response, "GetInputSettings response was null."); - Assert.IsNotNull(response.InputSettings, "InputSettings (JsonElement?) was null."); Assert.AreEqual( "text_gdiplus_v3", response.InputKind, @@ -557,6 +545,7 @@ public async Task GetInputSettings_TextGDI_ReturnsJsonElement() ); // Verify kind // Demonstrate deserializing the JsonElement + Assert.IsNotNull(response.InputSettings, "Input settings were null."); JsonElement settingsElement = response.InputSettings.Value; Assert.AreEqual( JsonValueKind.Object, @@ -569,10 +558,8 @@ public async Task GetInputSettings_TextGDI_ReturnsJsonElement() TestUtils.s_jsonSerializerOptions ); Assert.IsNotNull(textSettings, "Failed to deserialize settings element."); - Assert.IsNotNull(textSettings.Text, "Expected 'text' property in settings."); Trace.WriteLine($"Text GDI+ Settings 'text' property: {textSettings.Text}"); - Assert.IsNotNull(textSettings.Font, "Expected 'font' property in settings."); - Trace.WriteLine($"Text GDI+ Settings 'font.face': {textSettings.Font.Face}"); + Trace.WriteLine($"Text GDI+ Settings 'font.face': {textSettings.Font?.Face}"); } [TestMethod, TestCategory("Integration")] @@ -600,7 +587,6 @@ public async Task GetSourceFilterList_ReturnsFilterStubs() } Assert.IsNotNull(response, "GetSourceFilterList response was null."); - Assert.IsNotNull(response.Filters, "Filters list was null."); FilterStub? testFilter = response.Filters.FirstOrDefault(f => f.FilterName == s_testOptions.TestFilterName @@ -613,8 +599,6 @@ public async Task GetSourceFilterList_ReturnsFilterStubs() string.IsNullOrWhiteSpace(testFilter.FilterKind), "Filter kind should not be empty." ); - Assert.IsNotNull(testFilter.FilterIndex, "Filter index should have a value."); - Assert.IsNotNull(testFilter.FilterEnabled, "Filter enabled should have a value."); Trace.WriteLine( $"Found Test Filter Stub: Name={testFilter.FilterName}, Kind={testFilter.FilterKind}, Index={testFilter.FilterIndex}, Enabled={testFilter.FilterEnabled}" ); @@ -644,7 +628,6 @@ public async Task GetTransitionList_ReturnsTransitionStubs() await client.Transitions.GetSceneTransitionListAsync(); Assert.IsNotNull(response, "GetSceneTransitionList response was null."); - Assert.IsNotNull(response.Transitions, "Transitions list was null."); Assert.IsNotEmpty(response.Transitions, "Expected at least one transition."); Assert.IsFalse( string.IsNullOrWhiteSpace(response.CurrentSceneTransitionName), @@ -661,11 +644,6 @@ public async Task GetTransitionList_ReturnsTransitionStubs() string.IsNullOrWhiteSpace(firstTransition.TransitionKind), "Transition kind should not be empty." ); - Assert.IsNotNull( - firstTransition.TransitionConfigurable, - "Transition configurable flag should exist." - ); - Assert.IsNotNull(firstTransition.TransitionFixed, "Transition fixed flag should exist."); Trace.WriteLine($"Current Transition: {response.CurrentSceneTransitionName}"); Trace.WriteLine( $"First Transition Stub: Name={firstTransition.TransitionName}, Kind={firstTransition.TransitionKind}" @@ -684,7 +662,6 @@ public async Task GetOutputList_ReturnsOutputStubs() GetOutputListResponseData? response = await client.Outputs.GetOutputListAsync(); Assert.IsNotNull(response, "GetOutputList response was null."); - Assert.IsNotNull(response.Outputs, "Outputs list was null."); Assert.IsNotEmpty( response.Outputs, "Expected at least one output (e.g., Simple Output or Advanced)." @@ -700,7 +677,6 @@ public async Task GetOutputList_ReturnsOutputStubs() string.IsNullOrWhiteSpace(firstOutput.OutputKind), "Output kind should not be empty." ); - Assert.IsNotNull(firstOutput.OutputActive, "Output active flag should exist."); // Note: Width/Height/Settings might be null depending on the output type and state Trace.WriteLine( $"First Output Stub: Name={firstOutput.OutputName}, Kind={firstOutput.OutputKind}, Active={firstOutput.OutputActive}" @@ -719,7 +695,6 @@ public async Task GetMonitorList_ReturnsMonitorStubs() GetMonitorListResponseData? response = await client.Ui.GetMonitorListAsync(); Assert.IsNotNull(response, "GetMonitorList response was null."); - Assert.IsNotNull(response.Monitors, "Monitors list was null."); // Cannot assert count > 0 as user might have no monitors, but list should exist. if (response.Monitors.Count > 0) @@ -730,11 +705,6 @@ public async Task GetMonitorList_ReturnsMonitorStubs() string.IsNullOrWhiteSpace(firstMonitor.MonitorName), "Monitor name should not be empty." ); - Assert.IsNotNull(firstMonitor.MonitorIndex, "Monitor index should exist."); - Assert.IsNotNull(firstMonitor.MonitorWidth, "Monitor width should exist."); - Assert.IsNotNull(firstMonitor.MonitorHeight, "Monitor height should exist."); - Assert.IsNotNull(firstMonitor.MonitorPositionX, "Monitor position X should exist."); - Assert.IsNotNull(firstMonitor.MonitorPositionY, "Monitor position Y should exist."); Trace.WriteLine( $"First Monitor Stub: Name={firstMonitor.MonitorName}, Index={firstMonitor.MonitorIndex}, Res=({firstMonitor.MonitorWidth}x{firstMonitor.MonitorHeight})" ); @@ -838,11 +808,8 @@ public async Task GetInputDefaultSettings_ReturnsJsonElement_CanDeserialize() } Assert.IsNotNull(response, "GetInputDefaultSettings response was null."); - Assert.IsNotNull( - response.DefaultInputSettings, - "DefaultInputSettings (JsonElement?) was null." - ); + Assert.IsNotNull(response.DefaultInputSettings, "Default input settings were null."); JsonElement settingsElement = response.DefaultInputSettings.Value; Assert.AreEqual( JsonValueKind.Object, diff --git a/ObsWebSocket.Tests/ReadmeCompileCheck.cs b/ObsWebSocket.Tests/ReadmeCompileCheck.cs index 86eaf08..93b06e4 100644 --- a/ObsWebSocket.Tests/ReadmeCompileCheck.cs +++ b/ObsWebSocket.Tests/ReadmeCompileCheck.cs @@ -410,6 +410,37 @@ internal static async Task LowLevelAsync(ObsWebSocketClient client, Cancellation _ = $"{v?.ObsVersion} {raw}"; } + internal static async Task GroupedSurfaceAsync(ObsWebSocketClient client, CancellationToken ct) + { + await client.Scenes.GetSceneListAsync(new(), ct); + await client.Scenes.SwitchProgramSceneAndWaitAsync("Intro", cancellationToken: ct); + await client.Inputs.SetInputVolumeDbAsync("Mic", -6, ct); + await client.SceneItems.SetSceneItemEnabledAsync("Intro", "Logo", false, ct); + + client.Scenes.CurrentProgramSceneChanged += (_, e) => { }; + await foreach ( + var e in client.Scenes.CurrentProgramSceneChangedStream(cancellationToken: ct) + ) + { + break; + } + } + + internal static async Task TimeoutTypeAsync(ObsWebSocketClient client, CancellationToken ct) + { + try + { + _ = await client.WaitForEventAsync( + TimeSpan.FromSeconds(5), + ct + ); + } + catch (ObsWebSocketTimeoutException) + { + // One catch covers a request timeout and a wait timeout alike. + } + } + internal static void HostIntegration( Microsoft.Extensions.Hosting.IHostApplicationBuilder builder ) diff --git a/README.md b/README.md index 1734c13..c039980 100644 --- a/README.md +++ b/README.md @@ -76,9 +76,11 @@ reach anything: ```csharp await client.Scenes.GetSceneListAsync(new(), ct); // generated request await client.Scenes.SwitchProgramSceneAndWaitAsync("Intro", cancellationToken: ct); // convenience -await client.Scenes.CurrentProgramSceneChangedStream(cancellationToken: ct); // event stream await client.Inputs.SetInputVolumeDbAsync("Mic", -6, ct); await client.SceneItems.SetSceneItemEnabledAsync("Intro", "Logo", false, ct); + +client.Scenes.CurrentProgramSceneChanged += (_, e) => { }; // classic event +await foreach (var e in client.Scenes.CurrentProgramSceneChangedStream(cancellationToken: ct)) { break; } ``` The groups are `Canvases`, `Config`, `Filters`, `General`, `Inputs`, `MediaInputs`, `Outputs`, @@ -200,7 +202,8 @@ var intro = await client.WaitForEventAsync( ); ``` -It throws `TimeoutException` when the wait elapses. +It throws `ObsWebSocketTimeoutException` when the wait elapses, the same type a request +timeout raises, so one `catch (ObsWebSocketException)` covers both. ## Common use cases @@ -557,7 +560,7 @@ try } catch (ObsWebSocketRequestException ex) { - Console.WriteLine($"{ex.RequestType} failed with {ex.Status?.Code}: {ex.Comment}"); + Console.WriteLine($"{ex.RequestType} failed with {ex.StatusCode}: {ex.Comment}"); } catch (ObsWebSocketTimeoutException) { @@ -633,10 +636,13 @@ identically on either, and the validation suite exercises both. - **One-shot mode**: `ObsWebSocket.Example run-transport-tests` `run-transport-tests` creates its own scene and input, so it does not depend on a particular OBS -layout, and removes them afterwards. On each transport it asserts real values for the settings modes, -event streams and buffering, `WaitForEventAsync`, the typed batch builder including duplicate request -types, partial failure and truncation, typed protocol enums, screenshots, and the scene, input, -volume and output helpers. +layout, and removes them afterwards. It runs the same checks on JSON and on MessagePack, asserting +real values rather than that a call returned: the three settings modes, event streams and their +buffering, `WaitForEventAsync`, the typed batch builder including duplicate request types, partial +failure and truncation, a parallel batch and what survives its mispairing, concurrent requests +keeping their own results, the low level `Add` and `CallAsync` path, typed protocol enums, integer +fields round tripping in both directions, screenshots in memory and on disk, and the scene, preview, +input, mute, volume, media, transition and output helpers. ## Native AOT From 82d4d029aaa63410234d61bac63e77665c39ce3e Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 26 Aug 2026 15:18:49 +0200 Subject: [PATCH 11/13] fix(core): read a list of JsonElement over MessagePack An array whose item type the protocol does not state is generated as List, and nothing in the resolver chain could build a formatter for one. GetCanvasList therefore could not be read at all on MessagePack while JSON read it fine, and the swallowed FormatterNotRegisteredException surfaced only as "OBS reported success but returned no payload". Found by covering the Canvases category in the example, which was the one category the validation run never touched. Also closes the gaps that audit turned up: the request shape with neither a payload nor a response, the generated nested request record, the monitor, output and transition stubs, the classic += handler on the validation client, and a check that sends one request six ways, including a hand built JsonElement body and the anonymous object that is refused. --- .../MsgPackJsonElementResolver.cs | 57 ++++ ObsWebSocket.Example/Worker.cs | 318 ++++++++++++++++++ ObsWebSocket.Tests/JsonElementListTests.cs | 63 ++++ 3 files changed, 438 insertions(+) create mode 100644 ObsWebSocket.Tests/JsonElementListTests.cs diff --git a/ObsWebSocket.Core/Serialization/MsgPackJsonElementResolver.cs b/ObsWebSocket.Core/Serialization/MsgPackJsonElementResolver.cs index b8547c7..970b990 100644 --- a/ObsWebSocket.Core/Serialization/MsgPackJsonElementResolver.cs +++ b/ObsWebSocket.Core/Serialization/MsgPackJsonElementResolver.cs @@ -16,6 +16,11 @@ private MsgPackJsonElementResolver() { } ? (IMessagePackFormatter)(object)JsonElementFormatter.Instance : typeof(T) == typeof(JsonElement?) ? (IMessagePackFormatter)(object)NullableJsonElementFormatter.Instance + // An array the protocol does not give an item type for becomes List, and + // nothing else in the resolver chain knows how to build one, so GetCanvasList could not + // be read at all over MessagePack. + : typeof(T) == typeof(List) + ? (IMessagePackFormatter)(object)JsonElementListFormatter.Instance : null; internal sealed class JsonElementFormatter : IMessagePackFormatter @@ -101,4 +106,56 @@ MessagePackSerializerOptions options ? null : JsonElementFormatter.Instance.Deserialize(ref reader, options); } + + /// + /// Reads and writes a list of , which is what an array whose item + /// type the protocol does not state is generated as. + /// + internal sealed class JsonElementListFormatter : IMessagePackFormatter?> + { + public static readonly JsonElementListFormatter Instance = new(); + + /// + public void Serialize( + ref MessagePackWriter writer, + List? value, + MessagePackSerializerOptions options + ) + { + if (value is null) + { + writer.WriteNil(); + return; + } + + writer.WriteArrayHeader(value.Count); + foreach (JsonElement item in value) + { + JsonElementFormatter.Instance.Serialize(ref writer, item, options); + } + } + + /// + public List? Deserialize( + ref MessagePackReader reader, + MessagePackSerializerOptions options + ) + { + if (reader.TryReadNil()) + { + return null; + } + + options.Security.DepthStep(ref reader); + int count = reader.ReadArrayHeader(); + List items = new(count); + for (int i = 0; i < count; i++) + { + items.Add(JsonElementFormatter.Instance.Deserialize(ref reader, options)); + } + + reader.Depth--; + return items; + } + } } diff --git a/ObsWebSocket.Example/Worker.cs b/ObsWebSocket.Example/Worker.cs index d83ad41..5fc8e34 100644 --- a/ObsWebSocket.Example/Worker.cs +++ b/ObsWebSocket.Example/Worker.cs @@ -14,6 +14,7 @@ using ObsWebSocket.Core.Protocol.Common; using ObsWebSocket.Core.Protocol.Common.FilterSettings; using ObsWebSocket.Core.Protocol.Common.InputSettings; +using ObsWebSocket.Core.Protocol.Common.NestedTypes; using ObsWebSocket.Core.Protocol.Generated; using ObsWebSocket.Core.Protocol.Requests; using ObsWebSocket.Core.Protocol.Responses; @@ -2742,6 +2743,323 @@ await TrySettingsCheckAsync( .ConfigureAwait(false) ); + results.Add( + await TrySettingsCheckAsync( + "Canvases category", + async () => + { + // The only request in its category, and the one stub type nothing + // else reaches. + GetCanvasListResponseData canvases = await client + .Canvases.GetCanvasListAsync(cancellationToken) + .ConfigureAwait(false); + + return ( + canvases.Canvases.Count > 0, + $"{canvases.Canvases.Count} canvas(es)" + ); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Request with neither payload", + async () => + { + // The one generated shape nothing else exercises: no request data and + // no response. Pointing Preview at the scene already in Program makes + // the transition a no-op, so the check is safe to run. + GetStudioModeEnabledResponseData studio = await client + .Ui.GetStudioModeEnabledAsync(cancellationToken) + .ConfigureAwait(false); + if (!studio.StudioModeEnabled) + { + await client + .Ui.SetStudioModeEnabledAsync(new(true), cancellationToken) + .ConfigureAwait(false); + } + + try + { + GetSceneListResponseData before = await client + .Scenes.GetSceneListAsync(new(), cancellationToken) + .ConfigureAwait(false); + string program = before.CurrentProgramSceneName!; + + // Plain switch, not the waiting variant: OBS raises no + // CurrentPreviewSceneChanged when the preview is already that + // scene, which enabling Studio Mode has just made it. + await client + .Scenes.SwitchPreviewSceneAsync(program, cancellationToken) + .ConfigureAwait(false); + + await client + .Transitions.TriggerStudioModeTransitionAsync(cancellationToken) + .ConfigureAwait(false); + + GetSceneListResponseData after = await client + .Scenes.GetSceneListAsync(new(), cancellationToken) + .ConfigureAwait(false); + + return ( + string.Equals( + after.CurrentProgramSceneName, + program, + StringComparison.Ordinal + ), + $"transitioned, program still '{after.CurrentProgramSceneName}'" + ); + } + finally + { + if (!studio.StudioModeEnabled) + { + await client + .Ui.SetStudioModeEnabledAsync(new(false), cancellationToken) + .ConfigureAwait(false); + } + } + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Nested request object", + async () => + { + // keyModifiers is the one generated nested record, a distinct shape + // from the flat request payloads. F13 is not bound by default, so the + // press does nothing. + await client + .General.TriggerHotkeyByKeySequenceAsync( + new TriggerHotkeyByKeySequenceRequestData( + keyId: "OBS_KEY_F13", + keyModifiers: new TriggerHotkeyByKeySequenceRequestData_KeyModifiers( + shift: false, + control: true, + alt: false, + command: false + ) + ), + cancellationToken + ) + .ConfigureAwait(false); + + return (true, "nested keyModifiers accepted"); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Remaining stub types", + async () => + { + // Monitor, output and transition stubs are generated the same way as + // the scene and input ones, so one read each covers the shape. + GetMonitorListResponseData monitors = await client + .Ui.GetMonitorListAsync(cancellationToken) + .ConfigureAwait(false); + GetSceneTransitionListResponseData transitions = await client + .Transitions.GetSceneTransitionListAsync(cancellationToken) + .ConfigureAwait(false); + GetOutputListResponseData outputs = await client + .Outputs.GetOutputListAsync(cancellationToken) + .ConfigureAwait(false); + + bool ok = + monitors.Monitors.Count > 0 + && transitions.Transitions.Count > 0 + && outputs.Outputs.Count > 0 + && monitors.Monitors[0].MonitorWidth > 0 + && !string.IsNullOrEmpty(transitions.Transitions[0].TransitionName) + && !string.IsNullOrEmpty(outputs.Outputs[0].OutputName); + + return ( + ok, + $"{monitors.Monitors.Count} monitor(s) first {monitors.Monitors[0].MonitorWidth}px, " + + $"{transitions.Transitions.Count} transition(s), {outputs.Outputs.Count} output(s)" + ); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Classic handler and subscriptions", + async () => + { + // The stream path is covered above; this is the += path, plus the + // negotiated subscription flags the client reports. + TaskCompletionSource seen = new( + TaskCreationOptions.RunContinuationsAsynchronously + ); + string? observed = null; + void Handler(object? sender, SceneCreatedEventArgs e) + { + observed = e.EventData.SceneName; + _ = seen.TrySetResult(); + } + + client.Scenes.SceneCreated += Handler; + string probe = $"__obsws_handler_{Guid.NewGuid():N}"[..24]; + try + { + await client + .Scenes.CreateSceneAsync( + new CreateSceneRequestData(sceneName: probe), + cancellationToken + ) + .ConfigureAwait(false); + await seen + .Task.WaitAsync(TimeSpan.FromSeconds(5), cancellationToken) + .ConfigureAwait(false); + } + finally + { + client.Scenes.SceneCreated -= Handler; + await client + .Scenes.RemoveSceneAsync( + new RemoveSceneRequestData(sceneName: probe), + CancellationToken.None + ) + .ConfigureAwait(false); + } + + EventSubscription? subs = client.CurrentEventSubscriptions; + + return ( + string.Equals(observed, probe, StringComparison.Ordinal) + && subs is not null + && subs.Value.HasFlag(EventSubscription.Scenes), + $"handler saw '{observed}', subscriptions {subs}" + ); + } + ) + .ConfigureAwait(false) + ); + + results.Add( + await TrySettingsCheckAsync( + "Every way of sending a request", + async () => + { + // One request, reached six ways, so no path is left unexercised. + // 1. The generated request on its category group. + GetVersionResponseData viaGroup = await client + .General.GetVersionAsync(cancellationToken) + .ConfigureAwait(false); + + // 2. The low level typed call, for a reference type response. + GetVersionResponseData? viaCall = await client + .CallAsync( + "GetVersion", + null, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + + // 3. The low level untyped call, for a value type response. + JsonElement? viaValue = await client + .CallAsyncValue( + "GetVersion", + null, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + string? viaRawJson = viaValue?.GetProperty("obsVersion").GetString(); + + // 4. A hand built JsonElement as the request payload. + using JsonDocument requestBody = JsonDocument.Parse( + $$"""{"sceneName":"{{sceneName}}"}""" + ); + JsonElement? viaJsonBody = await client + .CallAsyncValue( + "GetSceneItemList", + requestBody.RootElement, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + int itemsViaJsonBody = + viaJsonBody?.GetProperty("sceneItems").GetArrayLength() ?? -1; + + // 5. The typed batch builder. + ObsBatchBuilder builder = new(); + BatchRef batched = builder.General.GetVersion(); + BatchResults built = await client + .CallBatchAsync( + builder, + executionType: RequestBatchExecutionType.SerialRealtime, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + string viaBatch = built.Get(batched).ObsVersion; + + // 6. A hand rolled batch item, with a JsonElement payload. + using JsonDocument batchBody = JsonDocument.Parse( + $$"""{"sceneName":"{{sceneName}}"}""" + ); + List> viaRawBatch = await client + .CallBatchAsync( + [ + new BatchRequestItem("GetVersion", null), + new BatchRequestItem( + "GetSceneItemList", + batchBody.RootElement + ), + ], + executionType: RequestBatchExecutionType.SerialRealtime, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + string? viaRawBatchVersion = viaRawBatch[0] + .GetData() + ?.ObsVersion; + + // And the one shape that is not supported, asserted as unsupported: + // an anonymous object has no metadata in the serializer context. + string anonymous; + try + { + _ = await client + .CallAsyncValue( + "GetSceneItemList", + new { sceneName }, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + anonymous = "unexpectedly accepted"; + } + catch (ObsWebSocketSerializationException) + { + anonymous = "refused"; + } + + string expected = viaGroup.ObsVersion; + bool allAgree = + viaCall?.ObsVersion == expected + && viaRawJson == expected + && viaBatch == expected + && viaRawBatchVersion == expected + && itemsViaJsonBody >= 0 + && anonymous == "refused"; + + return ( + allAgree, + $"six paths agree on {expected}, JsonElement body read " + + $"{itemsViaJsonBody} item(s), anonymous object {anonymous}" + ); + } + ) + .ConfigureAwait(false) + ); + results.Add( await TrySettingsCheckAsync( "Typed exception on a rejected request", diff --git a/ObsWebSocket.Tests/JsonElementListTests.cs b/ObsWebSocket.Tests/JsonElementListTests.cs new file mode 100644 index 0000000..0207a73 --- /dev/null +++ b/ObsWebSocket.Tests/JsonElementListTests.cs @@ -0,0 +1,63 @@ +using System.Text.Json; +using MessagePack; +using ObsWebSocket.Core.Protocol.Responses; +using ObsWebSocket.Core.Serialization; + +namespace ObsWebSocket.Tests; + +/// +/// An array whose item type the protocol does not state is generated as a list of +/// . Nothing in the MessagePack resolver chain could build a formatter for +/// one, so GetCanvasList could not be read at all on that transport while JSON read it fine. +/// +[TestClass] +public sealed class JsonElementListTests +{ + [TestMethod] + public void MsgPack_RoundTripsAListOfJsonElement() + { + using JsonDocument doc = JsonDocument.Parse( + """{"canvasName":"Main","canvasVideoSettings":{"baseWidth":1920,"fpsNumerator":30}}""" + ); + GetCanvasListResponseData original = new() { Canvases = [doc.RootElement.Clone()] }; + + byte[] packed = MessagePackSerializer.Serialize( + original, + MsgPackMessageSerializer.s_msgPackOptions + ); + GetCanvasListResponseData read = + MessagePackSerializer.Deserialize( + packed, + MsgPackMessageSerializer.s_msgPackOptions + ); + + Assert.AreEqual(1, read.Canvases.Count); + Assert.AreEqual("Main", read.Canvases[0].GetProperty("canvasName").GetString()); + Assert.AreEqual( + 1920, + read.Canvases[0].GetProperty("canvasVideoSettings").GetProperty("baseWidth").GetInt32(), + "a nested object inside the element has to survive too" + ); + } + + [TestMethod] + public void MsgPack_EmptyListRoundTrips() + { + GetCanvasListResponseData original = new() { Canvases = [] }; + + byte[] packed = MessagePackSerializer.Serialize( + original, + MsgPackMessageSerializer.s_msgPackOptions + ); + + Assert.AreEqual( + 0, + MessagePackSerializer + .Deserialize( + packed, + MsgPackMessageSerializer.s_msgPackOptions + ) + .Canvases.Count + ); + } +} From c0f4f4a8fdf0ce655576fe6b8cf80cac78034082 Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 26 Aug 2026 15:33:30 +0200 Subject: [PATCH 12/13] feat(core): accept a consumer JsonTypeInfo on the low level calls The typed settings helpers have taken a JsonTypeInfo since v0.3, so a consumer can serialize a type this library does not model. CallAsync, CallAsyncValue and CallRequiredAsync did not, which left hand building a JsonElement as the only way to send an unmodelled payload, and made the low level path the one place a consumer context did not work. The optional parameter is a non generic JsonTypeInfo, so no call site gains a type argument and existing named calls are unaffected. Measured the alternatives before settling on this. Only two ways of producing a JsonElement are AOT safe: JsonDocument.Parse, and SerializeToElement with a JsonTypeInfo. SerializeToElement without one, and the JsonNode and JsonObject routes, all carry IL2026 and IL3050, so recommending them would have broken the AOT target. The example now sends one request seven ways and asserts all seven agree, the seventh being a consumer context. --- ObsWebSocket.Core/ObsWebSocketClient.cs | 61 ++++++++++++++++++++----- ObsWebSocket.Example/Worker.cs | 36 ++++++++++++++- README.md | 21 +++++++-- 3 files changed, 101 insertions(+), 17 deletions(-) diff --git a/ObsWebSocket.Core/ObsWebSocketClient.cs b/ObsWebSocket.Core/ObsWebSocketClient.cs index fe52b64..b07c92e 100644 --- a/ObsWebSocket.Core/ObsWebSocketClient.cs +++ b/ObsWebSocket.Core/ObsWebSocketClient.cs @@ -1,4 +1,4 @@ -using System.Buffers; +using System.Buffers; using System.Collections.Concurrent; using System.Collections.Frozen; using System.Diagnostics; @@ -6,6 +6,7 @@ using System.Net.WebSockets; using System.Runtime.CompilerServices; using System.Text.Json; +using System.Text.Json.Serialization.Metadata; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using ObsWebSocket.Core.Events; @@ -321,6 +322,11 @@ await SendMessageAsync( /// The expected response data type. /// The OBS request type string. /// The request payload, or . + /// + /// Metadata for , for a type this library does not know. + /// Supplying it from your own JsonSerializerContext keeps the call AOT safe and + /// avoids hand building a . + /// /// Optional override for the request timeout. /// A token to cancel the operation. /// The response data. @@ -331,11 +337,18 @@ await SendMessageAsync( public async Task CallRequiredAsync( string requestType, object? requestData = null, + JsonTypeInfo? requestTypeInfo = null, int? timeoutMs = null, CancellationToken cancellationToken = default ) where TResponse : class => - await CallAsync(requestType, requestData, timeoutMs, cancellationToken) + await CallAsync( + requestType, + requestData, + requestTypeInfo, + timeoutMs, + cancellationToken + ) .ConfigureAwait(false) ?? throw new ObsWebSocketException( $"OBS reported success for '{requestType}' but returned no {typeof(TResponse).Name} payload." @@ -348,6 +361,11 @@ await CallAsync(requestType, requestData, timeoutMs, cancellationToke /// The expected type of the response data payload (must be a reference type). /// The OBS WebSocket request type string. /// Optional data payload for the request. Should be serializable to the format expected by OBS for the request type. + /// + /// Metadata for , for a type this library does not know. + /// Supplying it from your own JsonSerializerContext keeps the call AOT safe and + /// avoids hand building a . + /// /// Optional timeout in milliseconds to wait for the response. Defaults to . /// A token to cancel the asynchronous operation. /// @@ -361,6 +379,7 @@ await CallAsync(requestType, requestData, timeoutMs, cancellationToke public async Task CallAsync( string requestType, object? requestData = null, + JsonTypeInfo? requestTypeInfo = null, int? timeoutMs = null, CancellationToken cancellationToken = default ) @@ -403,7 +422,7 @@ await SendMessageAsync( new RequestPayload( requestType, requestId, - SerializeRequestData(requestType, requestData) + SerializeRequestData(requestType, requestData, requestTypeInfo) ), linkedCts.Token ) @@ -469,6 +488,11 @@ await SendMessageAsync( /// The expected type of the response data payload (must be a value type). /// The OBS WebSocket request type string. /// Optional data payload for the request. Should be serializable to the format expected by OBS for the request type. + /// + /// Metadata for , for a type this library does not know. + /// Supplying it from your own JsonSerializerContext keeps the call AOT safe and + /// avoids hand building a . + /// /// Optional timeout in milliseconds to wait for the response. Defaults to . /// A token to cancel the asynchronous operation. /// @@ -482,6 +506,7 @@ await SendMessageAsync( public async Task CallAsyncValue( string requestType, object? requestData = null, + JsonTypeInfo? requestTypeInfo = null, int? timeoutMs = null, CancellationToken cancellationToken = default ) @@ -510,7 +535,7 @@ await SendMessageAsync( new RequestPayload( requestType, requestId, - SerializeRequestData(requestType, requestData) + SerializeRequestData(requestType, requestData, requestTypeInfo) ), linkedCts.Token ) @@ -2179,7 +2204,16 @@ private TPayload ExtractPayloadFromHandshake(object messageObject, str /// In Native AOT, arbitrary objects passed through the batch API path may fail if they are not registered /// in . /// - private static JsonElement? SerializeRequestData(string requestContext, object? requestData) + /// + /// Metadata for , for a type this library does not know. + /// Supplying it from your own JsonSerializerContext keeps the call AOT safe and + /// avoids hand building a . + /// + private static JsonElement? SerializeRequestData( + string requestContext, + object? requestData, + JsonTypeInfo? requestTypeInfo = null + ) { if (requestData is null) { @@ -2188,12 +2222,17 @@ private TPayload ExtractPayloadFromHandshake(object messageObject, str try { - return requestData is JsonElement element - ? element - : JsonSerializer.SerializeToElement( - requestData, - s_payloadJsonOptions.GetTypeInfo(requestData.GetType()) - ); + if (requestData is JsonElement element) + { + return element; + } + + // A caller passing metadata from their own context can send a type this library has + // never heard of, without hand rolling a JsonDocument, and stays AOT safe doing it. + return JsonSerializer.SerializeToElement( + requestData, + requestTypeInfo ?? s_payloadJsonOptions.GetTypeInfo(requestData.GetType()) + ); } catch (InvalidOperationException ex) { diff --git a/ObsWebSocket.Example/Worker.cs b/ObsWebSocket.Example/Worker.cs index 5fc8e34..1786d2a 100644 --- a/ObsWebSocket.Example/Worker.cs +++ b/ObsWebSocket.Example/Worker.cs @@ -3022,6 +3022,20 @@ await TrySettingsCheckAsync( .GetData() ?.ObsVersion; + // 7. A consumer's own JsonSerializerContext, so a type this library + // has never heard of is sent without hand building a JsonElement. + JsonElement? viaConsumerContext = await client + .CallAsyncValue( + "GetSceneItemList", + new ConsumerSceneRequest(sceneName), + ExampleRequestContext.Default.ConsumerSceneRequest, + cancellationToken: cancellationToken + ) + .ConfigureAwait(false); + int itemsViaContext = + viaConsumerContext?.GetProperty("sceneItems").GetArrayLength() + ?? -1; + // And the one shape that is not supported, asserted as unsupported: // an anonymous object has no metadata in the serializer context. string anonymous; @@ -3048,12 +3062,14 @@ await TrySettingsCheckAsync( && viaBatch == expected && viaRawBatchVersion == expected && itemsViaJsonBody >= 0 + && itemsViaContext == itemsViaJsonBody && anonymous == "refused"; return ( allAgree, - $"six paths agree on {expected}, JsonElement body read " - + $"{itemsViaJsonBody} item(s), anonymous object {anonymous}" + $"seven paths agree on {expected}, JsonElement body and consumer " + + $"context both read {itemsViaJsonBody} item(s), anonymous " + + $"object {anonymous}" ); } ) @@ -4124,3 +4140,19 @@ internal sealed record WorkerGainDbSettings([property: JsonPropertyName("db")] d DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault )] internal sealed partial class WorkerSettingsJsonContext : JsonSerializerContext { } + +/// +/// A request payload this library has no metadata for, sent with the consumer's own context. +/// +/// The scene to list items for. +internal sealed record ConsumerSceneRequest( + [property: System.Text.Json.Serialization.JsonPropertyName("sceneName")] string SceneName +); + +/// +/// The consumer side serializer context, the AOT safe way to describe a payload the library does +/// not model. +/// +[System.Text.Json.Serialization.JsonSerializable(typeof(ConsumerSceneRequest))] +internal sealed partial class ExampleRequestContext + : System.Text.Json.Serialization.JsonSerializerContext; diff --git a/README.md b/README.md index c039980..1b35124 100644 --- a/README.md +++ b/README.md @@ -426,11 +426,20 @@ GetVersionResponseData? v = await client.CallAsync("GetV // response goes through CallAsyncValue. JsonElement? raw = await client.CallAsyncValue("GetStats", null, cancellationToken: ct); -// Request data must be a JsonElement or a type the library's serializer context knows, because -// the payload is written through a source generated context. An anonymous object has no metadata -// there and throws ObsWebSocketSerializationException at runtime. -using JsonDocument body = JsonDocument.Parse("""{"someField":1}"""); +// Request data is written through a source generated context, so it must be a JsonElement, a type +// the library knows, or a type you supply metadata for. An anonymous object has no metadata +// anywhere and throws ObsWebSocketSerializationException. + +// Your own type, with your own context. AOT safe, and nothing to hand build. +[JsonSerializable(typeof(MyRequest))] +internal sealed partial class MyContext : JsonSerializerContext; + JsonElement? answer = await client.CallAsyncValue( + "SomeNewRequest", new MyRequest(1), MyContext.Default.MyRequest, cancellationToken: ct); + +// Or a JsonElement built by hand, when a one-off payload does not deserve a type. +using JsonDocument body = JsonDocument.Parse("""{"someField":1}"""); +JsonElement? viaElement = await client.CallAsyncValue( "SomeNewRequest", body.RootElement, cancellationToken: ct); // A batch assembled by hand, without the typed builder. @@ -445,6 +454,10 @@ foreach (RequestResponsePayload result in results) } ``` +Those two are the AOT-safe ways to build a payload. `JsonSerializer.SerializeToElement` without a +`JsonTypeInfo`, and the `JsonNode` and `JsonObject` routes, all work at runtime but carry `IL2026` +and `IL3050`, so they are not options under Native AOT. + The same applies to events and enums: `client.SceneCreated` remains alongside `client.Scenes.SceneCreated`, and `ToWireValue()` / `FromWireValue()` convert an enum to and from the protocol string when you are building a payload by hand. From c03f490975c6b59ad50a6db078e28b8c2bce3585 Mon Sep 17 00:00:00 2001 From: Agash Date: Wed, 26 Aug 2026 15:38:32 +0200 Subject: [PATCH 13/13] test: guard the consumer context snippet the README now shows The README gained a preferred way to send an unmodelled payload without a matching compile check, which is exactly how the two broken snippets this PR already fixed got in. Every API the README references now appears in the check: 27 code blocks, 28 distinct references, none unguarded. --- ObsWebSocket.Tests/ReadmeCompileCheck.cs | 34 ++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/ObsWebSocket.Tests/ReadmeCompileCheck.cs b/ObsWebSocket.Tests/ReadmeCompileCheck.cs index 93b06e4..bec11b9 100644 --- a/ObsWebSocket.Tests/ReadmeCompileCheck.cs +++ b/ObsWebSocket.Tests/ReadmeCompileCheck.cs @@ -11,8 +11,13 @@ namespace ObsWebSocket.Tests; [JsonSerializable(typeof(OverlaySettings))] +[JsonSerializable(typeof(MyRequest))] internal partial class MyContext : JsonSerializerContext { } +/// A request payload the library does not model, described by the consumer's context. +/// An arbitrary field. +internal sealed record MyRequest([property: JsonPropertyName("someField")] int SomeField); + internal sealed record OverlaySettings( [property: JsonPropertyName("url")] string? Url = null, [property: JsonPropertyName("css")] string? Css = null @@ -441,6 +446,35 @@ internal static async Task TimeoutTypeAsync(ObsWebSocketClient client, Cancellat } } + internal static async Task ConsumerContextPayloadAsync( + ObsWebSocketClient client, + CancellationToken ct + ) + { + // The preferred way to send a payload the library does not model: your own type and your + // own context, which is AOT safe and needs nothing hand built. + System.Text.Json.JsonElement? answer = + await client.CallAsyncValue( + "SomeNewRequest", + new MyRequest(1), + MyContext.Default.MyRequest, + cancellationToken: ct + ); + + // The alternative, for a one-off payload that does not deserve a type. + using System.Text.Json.JsonDocument body = System.Text.Json.JsonDocument.Parse( + """{"someField":1}""" + ); + System.Text.Json.JsonElement? viaElement = + await client.CallAsyncValue( + "SomeNewRequest", + body.RootElement, + cancellationToken: ct + ); + + _ = $"{answer} {viaElement}"; + } + internal static void HostIntegration( Microsoft.Extensions.Hosting.IHostApplicationBuilder builder )