diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..9e028ef
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,13 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "csharpier": {
+ "version": "1.3.0",
+ "commands": [
+ "csharpier"
+ ],
+ "rollForward": false
+ }
+ }
+}
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..5e95c9a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,45 @@
+root = true
+
+
+[*.cs]
+# Formatting follows CSharpier; `dotnet csharpier check` enforces it in CI. These keys keep the IDE
+# formatter consistent with it, and IDE0055 is off because some layouts, such as a multi-line
+# constructor initializer before an empty body, cannot be expressed to it.
+dotnet_diagnostic.IDE0055.severity = none
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = false
+csharp_indent_switch_labels = true
+csharp_indent_labels = one_less_than_current
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = false
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = do_not_ignore
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1bf5d8a..51589a2 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -44,11 +44,16 @@ jobs:
dotnet-version: |
9.0.x
10.0.x
- 11.0.100-preview.7.26381.103
+ 11.0.100-rc.1.26425.128
- name: Restore dependencies
run: dotnet restore WaveLink.Client.slnx
+ - name: Check formatting
+ run: |
+ dotnet tool restore
+ dotnet csharpier check .
+
- name: Clean solution
run: dotnet clean WaveLink.Client.slnx --configuration Release
@@ -57,7 +62,7 @@ jobs:
- name: Run tests
run: >
- dotnet test WaveLink.Client.slnx
+ dotnet test --solution WaveLink.Client.slnx
--configuration Release
--no-build
-- --coverage --coverage-settings coverage.settings.xml --coverage-output-format cobertura --coverage-output coverage.cobertura.xml
diff --git a/global.json b/global.json
index 6827f37..0f28551 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "11.0.100-preview.7.26381.103",
+ "version": "11.0.100-rc.1.26425.128",
"allowPrerelease": true,
"rollForward": "latestPreview"
},
diff --git a/samples/WaveLink.Console/Program.cs b/samples/WaveLink.Console/Program.cs
index 84b86c2..7b9fe9a 100644
--- a/samples/WaveLink.Console/Program.cs
+++ b/samples/WaveLink.Console/Program.cs
@@ -3,8 +3,10 @@
WaveLinkClient client = new();
client.Disconnected += (_, _) => Console.WriteLine("Disconnected.");
-client.FocusedAppChanged += (_, e) => Console.WriteLine($"Focused app: {e.Name} ({e.Id}) -> channel {e.Channel?.Id}");
-client.LevelMeterChanged += (_, e) => Console.WriteLine($"Level meters update: channels={e.Channels?.Count ?? 0}");
+client.FocusedAppChanged += (_, e) =>
+ Console.WriteLine($"Focused app: {e.Name} ({e.Id}) -> channel {e.Channel?.Id}");
+client.LevelMeterChanged += (_, e) =>
+ Console.WriteLine($"Level meters update: channels={e.Channels?.Count ?? 0}");
await client.ConnectAsync();
@@ -17,13 +19,20 @@
await client.GetMixesAsync();
// Subscribe to focused app changes and all channel meters
-await client.SetSubscriptionAsync(new SetSubscriptionParams
-{
- FocusedAppChanged = new SubscriptionToggle { IsEnabled = true },
- LevelMeterChanged = new LevelMeterSubscription { IsEnabled = true, Type = "channel", Id = "all" }
-});
+await client.SetSubscriptionAsync(
+ new SetSubscriptionParams
+ {
+ FocusedAppChanged = new SubscriptionToggle { IsEnabled = true },
+ LevelMeterChanged = new LevelMeterSubscription
+ {
+ IsEnabled = true,
+ Type = "channel",
+ Id = "all",
+ },
+ }
+);
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
-await client.DisposeAsync();
\ No newline at end of file
+await client.DisposeAsync();
diff --git a/samples/WaveLink.Console/WaveLink.Console.csproj b/samples/WaveLink.Console/WaveLink.Console.csproj
index 5553f33..0f40b4e 100644
--- a/samples/WaveLink.Console/WaveLink.Console.csproj
+++ b/samples/WaveLink.Console/WaveLink.Console.csproj
@@ -1,15 +1,13 @@
Exe
- net10.0
-
- 10.0.11
+ net11.0
+ true
preview
enable
enable
- true
- false
+ true
+ false
diff --git a/src/WaveLink.Client/JsonRpcModels.cs b/src/WaveLink.Client/JsonRpcModels.cs
index 8eb6fac..40b9e7f 100644
--- a/src/WaveLink.Client/JsonRpcModels.cs
+++ b/src/WaveLink.Client/JsonRpcModels.cs
@@ -73,4 +73,4 @@ public sealed record JsonRpcNotification
/// The parameters for the method call, or null for no parameters.
[JsonPropertyName("params")]
public JsonElement? Params { get; init; }
-}
\ No newline at end of file
+}
diff --git a/src/WaveLink.Client/Models.cs b/src/WaveLink.Client/Models.cs
index 08bd5b3..4bed471 100644
--- a/src/WaveLink.Client/Models.cs
+++ b/src/WaveLink.Client/Models.cs
@@ -8,16 +8,20 @@ namespace WaveLink.Client;
public sealed record ApplicationInfo
{
/// The application ID.
- [JsonPropertyName("appID")] public required string AppId { get; set; }
+ [JsonPropertyName("appID")]
+ public required string AppId { get; set; }
/// The application name.
- [JsonPropertyName("name")] public string? Name { get; set; }
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
/// The interface revision supported by the server.
- [JsonPropertyName("interfaceRevision")] public required int InterfaceRevision { get; set; }
+ [JsonPropertyName("interfaceRevision")]
+ public required int InterfaceRevision { get; set; }
/// Additional properties returned by the server.
- [JsonExtensionData] public Dictionary? ExtensionData { get; set; }
+ [JsonExtensionData]
+ public Dictionary? ExtensionData { get; set; }
}
// Input devices
@@ -25,105 +29,132 @@ public sealed record ApplicationInfo
public sealed record InputDevicesResult
{
/// List of input devices.
- [JsonPropertyName("inputDevices")] public required List InputDevices { get; set; }
+ [JsonPropertyName("inputDevices")]
+ public required List InputDevices { get; set; }
/// Additional properties returned by the server.
- [JsonExtensionData] public Dictionary? ExtensionData { get; set; }
+ [JsonExtensionData]
+ public Dictionary? ExtensionData { get; set; }
}
/// Represents an input device and its inputs.
public sealed record InputDevice
{
/// Unique identifier for this input device.
- [JsonPropertyName("id")] public required string Id { get; set; }
+ [JsonPropertyName("id")]
+ public required string Id { get; set; }
/// Display name of the input device.
- [JsonPropertyName("name")] public string? Name { get; set; }
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
/// Indicates whether this is a Wave Link device.
- [JsonPropertyName("isWaveDevice")] public bool? IsWaveDevice { get; set; }
+ [JsonPropertyName("isWaveDevice")]
+ public bool? IsWaveDevice { get; set; }
/// List of inputs on this device.
- [JsonPropertyName("inputs")] public required List Inputs { get; set; }
+ [JsonPropertyName("inputs")]
+ public required List Inputs { get; set; }
/// Additional properties returned by the server.
- [JsonExtensionData] public Dictionary? ExtensionData { get; set; }
+ [JsonExtensionData]
+ public Dictionary? ExtensionData { get; set; }
}
/// Represents a single input on an input device.
public sealed record Input
{
/// Unique identifier for this input.
- [JsonPropertyName("id")] public required string Id { get; set; }
+ [JsonPropertyName("id")]
+ public required string Id { get; set; }
/// Display name of the input.
- [JsonPropertyName("name")] public string? Name { get; set; }
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
/// Indicates whether the input is muted.
- [JsonPropertyName("isMuted")] public bool? IsMuted { get; set; }
+ [JsonPropertyName("isMuted")]
+ public bool? IsMuted { get; set; }
/// Indicates whether the hardware gain lock is on (if supported).
- [JsonPropertyName("isGainLockOn")] public bool? IsGainLockOn { get; set; }
+ [JsonPropertyName("isGainLockOn")]
+ public bool? IsGainLockOn { get; set; }
/// Gain settings for this input.
- [JsonPropertyName("gain")] public Gain? Gain { get; set; }
+ [JsonPropertyName("gain")]
+ public Gain? Gain { get; set; }
/// Microphone to PC audio mix settings for this input.
- [JsonPropertyName("micPcMix")] public MicPcMix? MicPcMix { get; set; }
+ [JsonPropertyName("micPcMix")]
+ public MicPcMix? MicPcMix { get; set; }
/// List of software effects applied to this input.
- [JsonPropertyName("effects")] public List? Effects { get; set; }
+ [JsonPropertyName("effects")]
+ public List? Effects { get; set; }
/// List of hardware DSP effects applied to this input.
- [JsonPropertyName("dspEffects")] public List? DspEffects { get; set; }
+ [JsonPropertyName("dspEffects")]
+ public List? DspEffects { get; set; }
/// Additional properties returned by the server.
- [JsonExtensionData] public Dictionary? ExtensionData { get; set; }
+ [JsonExtensionData]
+ public Dictionary? ExtensionData { get; set; }
}
/// Represents gain information for an input.
public sealed record Gain
{
/// The gain value normalized to the range 0..1.
- [JsonPropertyName("value")] public required double Value { get; set; }
+ [JsonPropertyName("value")]
+ public required double Value { get; set; }
/// The maximum gain value in dB.
- [JsonPropertyName("max")] public double? Max { get; set; }
+ [JsonPropertyName("max")]
+ public double? Max { get; set; }
/// Additional properties returned by the server.
- [JsonExtensionData] public Dictionary? ExtensionData { get; set; }
+ [JsonExtensionData]
+ public Dictionary? ExtensionData { get; set; }
}
/// Represents mic/PC mix value and related metadata.
public sealed record MicPcMix
{
/// The mic/PC mix value normalized to the range 0..1.
- [JsonPropertyName("value")] public required double Value { get; set; }
+ [JsonPropertyName("value")]
+ public required double Value { get; set; }
/// Indicates whether the mix direction is inverted.
- [JsonPropertyName("isInverted")] public bool? IsInverted { get; set; }
+ [JsonPropertyName("isInverted")]
+ public bool? IsInverted { get; set; }
/// Additional properties returned by the server.
- [JsonExtensionData] public Dictionary? ExtensionData { get; set; }
+ [JsonExtensionData]
+ public Dictionary? ExtensionData { get; set; }
}
/// Represents an audio effect available on an input.
public sealed record Effect
{
/// Unique identifier for this effect.
- [JsonPropertyName("id")] public required string Id { get; set; }
+ [JsonPropertyName("id")]
+ public required string Id { get; set; }
/// Display name of the effect.
- [JsonPropertyName("name")] public string? Name { get; set; }
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
/// Indicates whether the effect is currently enabled.
- [JsonPropertyName("isEnabled")] public bool? IsEnabled { get; set; }
+ [JsonPropertyName("isEnabled")]
+ public bool? IsEnabled { get; set; }
/// Indicates whether the effect is supported by the device.
- [JsonPropertyName("isSupported")] public bool? IsSupported { get; set; }
+ [JsonPropertyName("isSupported")]
+ public bool? IsSupported { get; set; }
/// Additional properties returned by the server.
- [JsonExtensionData] public Dictionary? ExtensionData { get; set; }
+ [JsonExtensionData]
+ public Dictionary? ExtensionData { get; set; }
}
// Output devices
@@ -131,64 +162,80 @@ public sealed record Effect
public sealed record OutputDevicesResult
{
/// The current main output selection.
- [JsonPropertyName("mainOutput")] public required MainOutput MainOutput { get; set; }
+ [JsonPropertyName("mainOutput")]
+ public required MainOutput MainOutput { get; set; }
/// List of output devices.
- [JsonPropertyName("outputDevices")] public required List OutputDevices { get; set; }
+ [JsonPropertyName("outputDevices")]
+ public required List OutputDevices { get; set; }
/// Additional properties returned by the server.
- [JsonExtensionData] public Dictionary? ExtensionData { get; set; }
+ [JsonExtensionData]
+ public Dictionary? ExtensionData { get; set; }
}
/// Identifies the selected main output.
public sealed record MainOutput
{
/// The ID of the output device containing the main output.
- [JsonPropertyName("outputDeviceId")] public required string OutputDeviceId { get; set; }
+ [JsonPropertyName("outputDeviceId")]
+ public required string OutputDeviceId { get; set; }
/// The ID of the main output on the device.
- [JsonPropertyName("outputId")] public required string OutputId { get; set; }
+ [JsonPropertyName("outputId")]
+ public required string OutputId { get; set; }
/// Additional properties returned by the server.
- [JsonExtensionData] public Dictionary? ExtensionData { get; set; }
+ [JsonExtensionData]
+ public Dictionary? ExtensionData { get; set; }
}
/// Represents an output device and its outputs.
public sealed record OutputDevice
{
/// Unique identifier for this output device.
- [JsonPropertyName("id")] public required string Id { get; set; }
+ [JsonPropertyName("id")]
+ public required string Id { get; set; }
/// Display name of the output device.
- [JsonPropertyName("name")] public string? Name { get; set; }
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
/// Indicates whether this is a Wave Link device.
- [JsonPropertyName("isWaveDevice")] public bool? IsWaveDevice { get; set; }
+ [JsonPropertyName("isWaveDevice")]
+ public bool? IsWaveDevice { get; set; }
/// List of outputs on this device.
- [JsonPropertyName("outputs")] public required List
diff --git a/src/WaveLink.Client/WaveLinkClient.cs b/src/WaveLink.Client/WaveLinkClient.cs
index ad62a83..41fa1c0 100644
--- a/src/WaveLink.Client/WaveLinkClient.cs
+++ b/src/WaveLink.Client/WaveLinkClient.cs
@@ -22,7 +22,8 @@ public sealed class WaveLinkClient(WaveLinkClientOptions? options = null) : IAsy
private readonly WaveLinkClientOptions _options = options ?? new WaveLinkClientOptions();
private readonly ClientWebSocket _ws = new();
private readonly CancellationTokenSource _cts = new();
- private readonly ConcurrentDictionary> _pending = new();
+ private readonly ConcurrentDictionary> _pending =
+ new();
private Task? _recvLoop;
private int _nextId;
@@ -69,7 +70,10 @@ public sealed class WaveLinkClient(WaveLinkClientOptions? options = null) : IAsy
public event EventHandler? InputDeviceChanged;
/// Raised when the main output or output devices list changes.
- public event EventHandler<(MainOutput mainOutput, IReadOnlyList outputDevices)>? OutputDevicesChanged;
+ public event EventHandler<(
+ MainOutput mainOutput,
+ IReadOnlyList outputDevices
+ )>? OutputDevicesChanged;
/// Raised when a single output device is added or updated.
public event EventHandler? OutputDeviceChanged;
@@ -106,10 +110,14 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
return;
}
- int port = _options.PortOverride ?? await DiscoverPortAsync(cancellationToken).ConfigureAwait(false);
+ int port =
+ _options.PortOverride
+ ?? await DiscoverPortAsync(cancellationToken).ConfigureAwait(false);
Uri uri = new($"ws://127.0.0.1:{port}");
- using CancellationTokenSource timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+ using CancellationTokenSource timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(
+ cancellationToken
+ );
timeoutCts.CancelAfter(_options.ConnectTimeout);
_ws.Options.SetRequestHeader("Origin", _options.OriginHeader);
@@ -126,17 +134,22 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
_recvLoop = Task.Run(() => ReceiveLoopAsync(_cts.Token), CancellationToken.None);
// Basic validation handshake
- ApplicationInfo info = await GetApplicationInfoAsync(cancellationToken).ConfigureAwait(false);
+ ApplicationInfo info = await GetApplicationInfoAsync(cancellationToken)
+ .ConfigureAwait(false);
ApplicationInfo = info;
if (!string.Equals(info.AppId, "EWL", StringComparison.Ordinal))
{
- throw new WaveLinkException($"Connected server returned unexpected appID '{info.AppId}'.");
+ throw new WaveLinkException(
+ $"Connected server returned unexpected appID '{info.AppId}'."
+ );
}
if (info.InterfaceRevision < 1)
{
- throw new WaveLinkException($"Connected server returned unsupported interfaceRevision {info.InterfaceRevision}.");
+ throw new WaveLinkException(
+ $"Connected server returned unsupported interfaceRevision {info.InterfaceRevision}."
+ );
}
}
@@ -148,8 +161,18 @@ public async ValueTask DisposeAsync()
await _cts.CancelAsync().ConfigureAwait(false);
if (_ws.State is WebSocketState.Open or WebSocketState.CloseReceived)
{
- try { await _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Dispose", CancellationToken.None).ConfigureAwait(false); }
- catch { /* ignore */ }
+ try
+ {
+ await _ws.CloseAsync(
+ WebSocketCloseStatus.NormalClosure,
+ "Dispose",
+ CancellationToken.None
+ )
+ .ConfigureAwait(false);
+ }
+ catch
+ { /* ignore */
+ }
}
}
finally
@@ -170,13 +193,23 @@ public Task GetApplicationInfoAsync(CancellationToken ct = defa
/// Send plugin information to the server.
public Task SetPluginInfoAsync(SetPluginInfoParams info, CancellationToken ct = default)
{
- return CallVoidAsync("setPluginInfo", info, WaveLinkJsonContext.Default.SetPluginInfoParams, ct);
+ return CallVoidAsync(
+ "setPluginInfo",
+ info,
+ WaveLinkJsonContext.Default.SetPluginInfoParams,
+ ct
+ );
}
/// Retrieve the list of input devices and update the cached state.
public async Task GetInputDevicesAsync(CancellationToken ct = default)
{
- InputDevicesResult result = await CallAsync("getInputDevices", WaveLinkJsonContext.Default.InputDevicesResult, ct).ConfigureAwait(false);
+ InputDevicesResult result = await CallAsync(
+ "getInputDevices",
+ WaveLinkJsonContext.Default.InputDevicesResult,
+ ct
+ )
+ .ConfigureAwait(false);
_inputDevices = result.InputDevices ?? [];
InputDevicesChanged?.Invoke(this, _inputDevices);
return result;
@@ -185,13 +218,23 @@ public async Task GetInputDevicesAsync(CancellationToken ct
/// Update an input device configuration.
public Task SetInputDeviceAsync(SetInputDeviceParams p, CancellationToken ct = default)
{
- return CallVoidAsync("setInputDevice", p, WaveLinkJsonContext.Default.SetInputDeviceParams, ct);
+ return CallVoidAsync(
+ "setInputDevice",
+ p,
+ WaveLinkJsonContext.Default.SetInputDeviceParams,
+ ct
+ );
}
/// Retrieve the list of output devices and update the cached state.
public async Task GetOutputDevicesAsync(CancellationToken ct = default)
{
- OutputDevicesResult result = await CallAsync("getOutputDevices", WaveLinkJsonContext.Default.OutputDevicesResult, ct).ConfigureAwait(false);
+ OutputDevicesResult result = await CallAsync(
+ "getOutputDevices",
+ WaveLinkJsonContext.Default.OutputDevicesResult,
+ ct
+ )
+ .ConfigureAwait(false);
MainOutput = result.MainOutput;
_outputDevices = result.OutputDevices ?? [];
OutputDevicesChanged?.Invoke(this, (result.MainOutput, _outputDevices));
@@ -201,13 +244,23 @@ public async Task GetOutputDevicesAsync(CancellationToken c
/// Update an output device configuration.
public Task SetOutputDeviceAsync(SetOutputDeviceParams p, CancellationToken ct = default)
{
- return CallVoidAsync("setOutputDevice", p, WaveLinkJsonContext.Default.SetOutputDeviceParams, ct);
+ return CallVoidAsync(
+ "setOutputDevice",
+ p,
+ WaveLinkJsonContext.Default.SetOutputDeviceParams,
+ ct
+ );
}
/// Retrieve the list of channels and update the cached state.
public async Task GetChannelsAsync(CancellationToken ct = default)
{
- ChannelsResult result = await CallAsync("getChannels", WaveLinkJsonContext.Default.ChannelsResult, ct).ConfigureAwait(false);
+ ChannelsResult result = await CallAsync(
+ "getChannels",
+ WaveLinkJsonContext.Default.ChannelsResult,
+ ct
+ )
+ .ConfigureAwait(false);
_channels = result.Channels ?? [];
ChannelsChanged?.Invoke(this, _channels);
return result;
@@ -228,7 +281,12 @@ public Task AddToChannelAsync(AddToChannelParams p, CancellationToken ct = defau
/// Retrieve the list of mixes and update the cached state.
public async Task GetMixesAsync(CancellationToken ct = default)
{
- MixesResult result = await CallAsync("getMixes", WaveLinkJsonContext.Default.MixesResult, ct).ConfigureAwait(false);
+ MixesResult result = await CallAsync(
+ "getMixes",
+ WaveLinkJsonContext.Default.MixesResult,
+ ct
+ )
+ .ConfigureAwait(false);
_mixes = result.Mixes ?? [];
MixesChanged?.Invoke(this, _mixes);
return result;
@@ -241,9 +299,18 @@ public Task SetMixAsync(SetMixParams p, CancellationToken ct = default)
}
/// Subscribe or unsubscribe to server notifications.
- public Task SetSubscriptionAsync(SetSubscriptionParams p, CancellationToken ct = default)
+ public Task SetSubscriptionAsync(
+ SetSubscriptionParams p,
+ CancellationToken ct = default
+ )
{
- return CallAsync("setSubscription", p, WaveLinkJsonContext.Default.SetSubscriptionParams, WaveLinkJsonContext.Default.SetSubscriptionResult, ct);
+ return CallAsync(
+ "setSubscription",
+ p,
+ WaveLinkJsonContext.Default.SetSubscriptionParams,
+ WaveLinkJsonContext.Default.SetSubscriptionResult,
+ ct
+ );
}
#endregion
@@ -251,9 +318,12 @@ public Task SetSubscriptionAsync(SetSubscriptionParams p,
#region Streams (IAsyncEnumerable)
/// Streams real-time updates for input devices.
- public async IAsyncEnumerable StreamInputDeviceChangesAsync([EnumeratorCancellation] CancellationToken ct = default)
+ public async IAsyncEnumerable StreamInputDeviceChangesAsync(
+ [EnumeratorCancellation] CancellationToken ct = default
+ )
{
- Channel channel = System.Threading.Channels.Channel.CreateUnbounded();
+ Channel channel =
+ System.Threading.Channels.Channel.CreateUnbounded();
void handler(object? _, InputDevice e)
{
_ = channel.Writer.TryWrite(e);
@@ -262,7 +332,9 @@ void handler(object? _, InputDevice e)
InputDeviceChanged += handler;
try
{
- await foreach (InputDevice? item in channel.Reader.ReadAllAsync(ct).ConfigureAwait(false))
+ await foreach (
+ InputDevice? item in channel.Reader.ReadAllAsync(ct).ConfigureAwait(false)
+ )
{
yield return item;
}
@@ -274,9 +346,12 @@ void handler(object? _, InputDevice e)
}
/// Streams real-time updates for level meters.
- public async IAsyncEnumerable StreamLevelMetersAsync([EnumeratorCancellation] CancellationToken ct = default)
+ public async IAsyncEnumerable StreamLevelMetersAsync(
+ [EnumeratorCancellation] CancellationToken ct = default
+ )
{
- Channel channel = System.Threading.Channels.Channel.CreateUnbounded();
+ Channel channel =
+ System.Threading.Channels.Channel.CreateUnbounded();
void handler(object? _, LevelMeterChangedParams e)
{
_ = channel.Writer.TryWrite(e);
@@ -285,7 +360,11 @@ void handler(object? _, LevelMeterChangedParams e)
LevelMeterChanged += handler;
try
{
- await foreach (LevelMeterChangedParams? item in channel.Reader.ReadAllAsync(ct).ConfigureAwait(false))
+ await foreach (
+ LevelMeterChangedParams? item in channel
+ .Reader.ReadAllAsync(ct)
+ .ConfigureAwait(false)
+ )
{
yield return item;
}
@@ -297,9 +376,12 @@ void handler(object? _, LevelMeterChangedParams e)
}
/// Streams real-time updates for focused app changes.
- public async IAsyncEnumerable StreamFocusedAppChangesAsync([EnumeratorCancellation] CancellationToken ct = default)
+ public async IAsyncEnumerable StreamFocusedAppChangesAsync(
+ [EnumeratorCancellation] CancellationToken ct = default
+ )
{
- Channel channel = System.Threading.Channels.Channel.CreateUnbounded();
+ Channel channel =
+ System.Threading.Channels.Channel.CreateUnbounded();
void handler(object? _, FocusedAppChangedParams e)
{
_ = channel.Writer.TryWrite(e);
@@ -308,7 +390,11 @@ void handler(object? _, FocusedAppChangedParams e)
FocusedAppChanged += handler;
try
{
- await foreach (FocusedAppChangedParams? item in channel.Reader.ReadAllAsync(ct).ConfigureAwait(false))
+ await foreach (
+ FocusedAppChangedParams? item in channel
+ .Reader.ReadAllAsync(ct)
+ .ConfigureAwait(false)
+ )
{
yield return item;
}
@@ -324,68 +410,133 @@ void handler(object? _, FocusedAppChangedParams e)
#region Convenience Helpers
/// Convenience: set the level for a specific output on a device.
- public Task SetOutputLevelAsync(string outputDeviceId, string outputId, double level0to1, CancellationToken ct = default)
+ public Task SetOutputLevelAsync(
+ string outputDeviceId,
+ string outputId,
+ double level0to1,
+ CancellationToken ct = default
+ )
{
- return SetOutputDeviceAsync(new SetOutputDeviceParams
- {
- OutputDevice = new OutputDeviceUpdate
+ return SetOutputDeviceAsync(
+ new SetOutputDeviceParams
{
- Id = outputDeviceId,
- Outputs = [new() { Id = outputId, Level = Clamp01(level0to1) }]
- }
- }, ct);
+ OutputDevice = new OutputDeviceUpdate
+ {
+ Id = outputDeviceId,
+ Outputs = [new() { Id = outputId, Level = Clamp01(level0to1) }],
+ },
+ },
+ ct
+ );
}
/// Convenience: set mute state for a specific output on a device.
- public Task SetOutputMuteAsync(string outputDeviceId, string outputId, bool isMuted, CancellationToken ct = default)
+ public Task SetOutputMuteAsync(
+ string outputDeviceId,
+ string outputId,
+ bool isMuted,
+ CancellationToken ct = default
+ )
{
- return SetOutputDeviceAsync(new SetOutputDeviceParams
- {
- OutputDevice = new OutputDeviceUpdate
+ return SetOutputDeviceAsync(
+ new SetOutputDeviceParams
{
- Id = outputDeviceId,
- Outputs = [new() { Id = outputId, IsMuted = isMuted }]
- }
- }, ct);
+ OutputDevice = new OutputDeviceUpdate
+ {
+ Id = outputDeviceId,
+ Outputs = [new() { Id = outputId, IsMuted = isMuted }],
+ },
+ },
+ ct
+ );
}
/// Convenience: set the main output.
- public Task SetMainOutputAsync(string outputDeviceId, string outputId, CancellationToken ct = default)
+ public Task SetMainOutputAsync(
+ string outputDeviceId,
+ string outputId,
+ CancellationToken ct = default
+ )
{
- return SetOutputDeviceAsync(new SetOutputDeviceParams
- {
- MainOutput = new MainOutput { OutputDeviceId = outputDeviceId, OutputId = outputId }
- }, ct);
+ return SetOutputDeviceAsync(
+ new SetOutputDeviceParams
+ {
+ MainOutput = new MainOutput
+ {
+ OutputDeviceId = outputDeviceId,
+ OutputId = outputId,
+ },
+ },
+ ct
+ );
}
/// Convenience: set mute state for a specific input on a device.
- public Task SetInputMuteAsync(string inputDeviceId, string inputId, bool isMuted, CancellationToken ct = default)
+ public Task SetInputMuteAsync(
+ string inputDeviceId,
+ string inputId,
+ bool isMuted,
+ CancellationToken ct = default
+ )
{
- return SetInputDeviceAsync(new SetInputDeviceParams
- {
- Id = inputDeviceId,
- Inputs = [new() { Id = inputId, IsMuted = isMuted }]
- }, ct);
+ return SetInputDeviceAsync(
+ new SetInputDeviceParams
+ {
+ Id = inputDeviceId,
+ Inputs = [new() { Id = inputId, IsMuted = isMuted }],
+ },
+ ct
+ );
}
/// Convenience: set normalized gain for a specific input.
- public Task SetInputGainNormalizedAsync(string inputDeviceId, string inputId, double value0to1, CancellationToken ct = default)
+ public Task SetInputGainNormalizedAsync(
+ string inputDeviceId,
+ string inputId,
+ double value0to1,
+ CancellationToken ct = default
+ )
{
- return SetInputDeviceAsync(new SetInputDeviceParams
- {
- Id = inputDeviceId,
- Inputs = [new() { Id = inputId, Gain = new GainValue { Value = Clamp01(value0to1) } }]
- }, ct);
+ return SetInputDeviceAsync(
+ new SetInputDeviceParams
+ {
+ Id = inputDeviceId,
+ Inputs =
+ [
+ new()
+ {
+ Id = inputId,
+ Gain = new GainValue { Value = Clamp01(value0to1) },
+ },
+ ],
+ },
+ ct
+ );
}
/// Convenience: set normalized mic/PC mix for a specific input.
- public Task SetMicPcMixNormalizedAsync(string inputDeviceId, string inputId, double value0to1, CancellationToken ct = default)
+ public Task SetMicPcMixNormalizedAsync(
+ string inputDeviceId,
+ string inputId,
+ double value0to1,
+ CancellationToken ct = default
+ )
{
- return SetInputDeviceAsync(new SetInputDeviceParams
- {
- Id = inputDeviceId,
- Inputs = [new() { Id = inputId, MicPcMix = new MicPcMixValue { Value = Clamp01(value0to1) } }]
- }, ct);
+ return SetInputDeviceAsync(
+ new SetInputDeviceParams
+ {
+ Id = inputDeviceId,
+ Inputs =
+ [
+ new()
+ {
+ Id = inputId,
+ MicPcMix = new MicPcMixValue { Value = Clamp01(value0to1) },
+ },
+ ],
+ },
+ ct
+ );
}
/// Clamp a value to the range [0,1].
@@ -421,7 +572,9 @@ private async Task DiscoverPortAsync(CancellationToken ct)
}
}
- throw new WaveLinkException($"Unable to discover Wave Link WebSocket port (ws-info.json missing and scan {_options.MinPort}-{_options.MaxPort} failed).");
+ throw new WaveLinkException(
+ $"Unable to discover Wave Link WebSocket port (ws-info.json missing and scan {_options.MinPort}-{_options.MaxPort} failed)."
+ );
}
private static string? GetDefaultWsInfoFilePath()
@@ -437,16 +590,31 @@ private async Task DiscoverPortAsync(CancellationToken ct)
if (appData.EndsWith("Roaming", StringComparison.OrdinalIgnoreCase))
{
string baseDir = appData[..^"Roaming".Length].TrimEnd('\\', '/');
- return Path.Combine(baseDir, "Local", "Packages", "Elgato.WaveLink_g54w8ztgkx496", "LocalState", "ws-info.json");
+ return Path.Combine(
+ baseDir,
+ "Local",
+ "Packages",
+ "Elgato.WaveLink_g54w8ztgkx496",
+ "LocalState",
+ "ws-info.json"
+ );
}
string? localAppData = Environment.GetEnvironmentVariable("LOCALAPPDATA");
if (!string.IsNullOrWhiteSpace(localAppData))
{
- return Path.Combine(localAppData, "Packages", "Elgato.WaveLink_g54w8ztgkx496", "LocalState", "ws-info.json");
+ return Path.Combine(
+ localAppData,
+ "Packages",
+ "Elgato.WaveLink_g54w8ztgkx496",
+ "LocalState",
+ "ws-info.json"
+ );
}
}
- catch { /* ignored */ }
+ catch
+ { /* ignored */
+ }
return null;
}
@@ -460,13 +628,21 @@ private async Task DiscoverPortAsync(CancellationToken ct)
}
using FileStream stream = File.OpenRead(path);
- using JsonDocument doc = await JsonDocument.ParseAsync(stream, cancellationToken: ct).ConfigureAwait(false);
- if (doc.RootElement.TryGetProperty("port", out JsonElement portEl) && portEl.ValueKind == JsonValueKind.Number && portEl.TryGetInt32(out int port))
+ using JsonDocument doc = await JsonDocument
+ .ParseAsync(stream, cancellationToken: ct)
+ .ConfigureAwait(false);
+ if (
+ doc.RootElement.TryGetProperty("port", out JsonElement portEl)
+ && portEl.ValueKind == JsonValueKind.Number
+ && portEl.TryGetInt32(out int port)
+ )
{
return port;
}
}
- catch { /* ignored */ }
+ catch
+ { /* ignored */
+ }
return null;
}
@@ -475,13 +651,17 @@ private async Task CanConnectAsync(int port, CancellationToken ct)
using ClientWebSocket ws = new();
ws.Options.SetRequestHeader("Origin", _options.OriginHeader);
- using CancellationTokenSource timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
+ using CancellationTokenSource timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(
+ ct
+ );
timeoutCts.CancelAfter(TimeSpan.FromMilliseconds(700));
try
{
- await ws.ConnectAsync(new Uri($"ws://127.0.0.1:{port}"), timeoutCts.Token).ConfigureAwait(false);
- await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "probe", CancellationToken.None).ConfigureAwait(false);
+ await ws.ConnectAsync(new Uri($"ws://127.0.0.1:{port}"), timeoutCts.Token)
+ .ConfigureAwait(false);
+ await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "probe", CancellationToken.None)
+ .ConfigureAwait(false);
return true;
}
catch
@@ -491,28 +671,52 @@ private async Task CanConnectAsync(int port, CancellationToken ct)
}
// AOT-safe typed calls
- private async Task CallAsync(string method, JsonTypeInfo responseTypeInfo, CancellationToken ct)
+ private async Task CallAsync(
+ string method,
+ JsonTypeInfo responseTypeInfo,
+ CancellationToken ct
+ )
{
- JsonElement? resultEl = await CallCoreAsync