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
index a5ce21d..5dc3322 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -113,6 +113,14 @@ csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
+# 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_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = false
+csharp_space_around_declaration_statements = do_not_ignore
+
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
indent_size = 2
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 198a629..834a4b2 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -76,11 +76,16 @@ jobs:
- name: Restore
run: dotnet restore PipeWire.NET.slnx
+ - name: Check formatting
+ run: |
+ dotnet tool restore
+ dotnet csharpier check .
+
- name: Build
run: dotnet build PipeWire.NET.slnx --no-restore -c Release
- name: Test (non-integration on host runner)
- run: dotnet test PipeWire.NET.slnx --settings tests.runsettings --no-build -c Release --coverage --coverage-settings coverage.settings.xml --coverage-output-format cobertura --coverage-output coverage.cobertura.xml
+ run: dotnet test --solution PipeWire.NET.slnx --settings tests.runsettings --no-build -c Release --coverage --coverage-settings coverage.settings.xml --coverage-output-format cobertura --coverage-output coverage.cobertura.xml
--filter "TestCategory!=Integration"
- name: Summarize coverage
@@ -191,7 +196,7 @@ jobs:
PIPEWIRE_DEBUG: "3"
PIPEWIRE_LOG: ${{ runner.temp }}/pw-native-nodaemon.log
timeout-minutes: 10
- run: dotnet test PipeWire.NET.slnx --settings tests.runsettings --no-build -c Release
+ run: dotnet test --solution PipeWire.NET.slnx --settings tests.runsettings --no-build -c Release
--filter "TestCategory=Integration&TestCategory!=RequiresDaemon&TestCategory!=PenTest&TestCategory!=KillsTheDaemon&TestCategory!=RequiresPatchedDaemon" --max-parallel-test-modules 1
- name: Name the test that hung
@@ -215,7 +220,7 @@ jobs:
run: |
source build/session.sh
pwnet_session_start
- pwnet_session_run dotnet test PipeWire.NET.slnx --settings tests.runsettings --no-build -c Release \
+ pwnet_session_run dotnet test --solution PipeWire.NET.slnx --settings tests.runsettings --no-build -c Release \
--filter "TestCategory=RequiresDaemon&TestCategory!=RequiresGpu&TestCategory!=RequiresGStreamer&TestCategory!=PenTest&TestCategory!=KillsTheDaemon&TestCategory!=RequiresPatchedDaemon" --max-parallel-test-modules 1
exit $PWNET_LAST_RC
@@ -294,7 +299,7 @@ jobs:
run: |
source build/session.sh
pwnet_session_start
- pwnet_session_run dotnet test PipeWire.NET.slnx --settings tests.runsettings --no-build -c Release \
+ pwnet_session_run dotnet test --solution PipeWire.NET.slnx --settings tests.runsettings --no-build -c Release \
--filter "TestCategory=RequiresGpu&TestCategory!=RequiresGStreamer" --max-parallel-test-modules 1
echo "::notice::DMA-BUF leg exit $PWNET_LAST_RC (non-gating: a hosted runner has no GPU to import with)"
@@ -307,7 +312,7 @@ jobs:
run: |
source build/session.sh
pwnet_session_start
- pwnet_session_run dotnet test PipeWire.NET.slnx --settings tests.runsettings --no-build -c Release \
+ pwnet_session_run dotnet test --solution PipeWire.NET.slnx --settings tests.runsettings --no-build -c Release \
--filter "TestCategory=RequiresGStreamer&TestCategory!=RequiresGpu&TestCategory!=PenTest" --max-parallel-test-modules 1
exit $PWNET_LAST_RC
@@ -360,7 +365,7 @@ jobs:
run: dotnet build PipeWire.NET.slnx -c Release
- name: Unit tests
- run: dotnet test PipeWire.NET.slnx --settings tests.runsettings --no-build -c Release --filter "TestCategory!=Integration"
+ run: dotnet test --solution PipeWire.NET.slnx --settings tests.runsettings --no-build -c Release --filter "TestCategory!=Integration"
aot_arm64:
name: Native AOT and ABI on arm64
@@ -399,7 +404,7 @@ jobs:
# The struct layouts and enum widths are what arm64 is here to disagree about, and these
# need no daemon.
run: |
- dotnet test PipeWire.NET.slnx --settings tests.runsettings --no-build -c Release --filter "TestCategory!=Integration"
+ dotnet test --solution PipeWire.NET.slnx --settings tests.runsettings --no-build -c Release --filter "TestCategory!=Integration"
- name: Native AOT publish and run
timeout-minutes: 20
@@ -514,7 +519,7 @@ jobs:
run: |
source build/session.sh
pwnet_session_start
- pwnet_session_run dotnet test PipeWire.NET.slnx --settings tests.runsettings -c Release --filter "TestCategory=RequiresDaemon&TestCategory!=RequiresGpu&TestCategory!=RequiresGStreamer&TestCategory!=PenTest&TestCategory!=KillsTheDaemon&TestCategory!=RequiresPatchedDaemon" --max-parallel-test-modules 1
+ pwnet_session_run dotnet test --solution PipeWire.NET.slnx --settings tests.runsettings -c Release --filter "TestCategory=RequiresDaemon&TestCategory!=RequiresGpu&TestCategory!=RequiresGStreamer&TestCategory!=PenTest&TestCategory!=KillsTheDaemon&TestCategory!=RequiresPatchedDaemon" --max-parallel-test-modules 1
if [ $PWNET_LAST_RC -eq 0 ]; then
echo "- result: passed" >> "$GITHUB_STEP_SUMMARY"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8c9343f..a8000e2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,7 +9,7 @@ dotnet build PipeWire.NET.slnx
dotnet test --filter "TestCategory!=Integration"
```
-The build treats warnings as errors and targets .NET 10 (and .NET 11 preview).
+The build treats warnings as errors and targets .NET 10 and .NET 11.
Integration tests need a running PipeWire daemon on Linux. Some use GStreamer to produce real
sources; install `gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 96ab58e..23691b5 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -7,9 +7,17 @@
-
-
-
+
+
+
diff --git a/PipeWire.NET.slnx b/PipeWire.NET.slnx
index 7b7a828..5aaddad 100644
--- a/PipeWire.NET.slnx
+++ b/PipeWire.NET.slnx
@@ -1,21 +1,21 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/global.json b/global.json
index abe2e1d..62939fe 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",
"rollForward": "latestFeature",
"allowPrerelease": true
},
diff --git a/samples/PipeWire.NET.SampleConsole/GraphCommands.cs b/samples/PipeWire.NET.SampleConsole/GraphCommands.cs
index 660212b..469f33c 100644
--- a/samples/PipeWire.NET.SampleConsole/GraphCommands.cs
+++ b/samples/PipeWire.NET.SampleConsole/GraphCommands.cs
@@ -1,7 +1,7 @@
using System.Globalization;
using System.Runtime.Versioning;
-using PipeWire.NET.Graph;
using PipeWire.NET;
+using PipeWire.NET.Graph;
namespace PipeWire.NET.SampleConsole;
@@ -12,14 +12,17 @@ internal static class GraphCommands
{
public static async Task ListAsync(CancellationToken cancellationToken)
{
- await using var session = await Session.ConnectAsync(
- "sample-list", cancellationToken).ConfigureAwait(false);
+ await using var session = await Session
+ .ConnectAsync("sample-list", cancellationToken)
+ .ConfigureAwait(false);
PipeWireGraphSnapshot graph = session.Registry.Current;
- Console.WriteLine($"Graph version {graph.Version}: " +
- $"{graph.Nodes.Length} nodes, {graph.Ports.Length} ports, " +
- $"{graph.Links.Length} links, {graph.Devices.Length} devices, " +
- $"{graph.Clients.Length} clients.");
+ Console.WriteLine(
+ $"Graph version {graph.Version}: "
+ + $"{graph.Nodes.Length} nodes, {graph.Ports.Length} ports, "
+ + $"{graph.Links.Length} links, {graph.Devices.Length} devices, "
+ + $"{graph.Clients.Length} clients."
+ );
Console.WriteLine("Nodes:");
foreach (PipeWireNode node in graph.Nodes)
@@ -28,17 +31,23 @@ public static async Task ListAsync(CancellationToken cancellationToken)
foreach (PipeWirePort _ in graph.GetPortsForNode(node.NodeId))
ports++;
- Console.WriteLine($" [{node.NodeId,4}] {node.Description ?? node.NodeName ?? ""}");
- Console.WriteLine($" class={node.MediaClass ?? "?"} " +
- $"media={node.Media} flow={node.Flow} ports={ports}");
+ Console.WriteLine(
+ $" [{node.NodeId, 4}] {node.Description ?? node.NodeName ?? ""}"
+ );
+ Console.WriteLine(
+ $" class={node.MediaClass ?? "?"} "
+ + $"media={node.Media} flow={node.Flow} ports={ports}"
+ );
}
if (graph.Devices.Length > 0)
{
Console.WriteLine("Devices:");
foreach (PipeWireDevice device in graph.Devices)
- Console.WriteLine($" [{device.Id,4}] {device.Description ?? device.DeviceName} " +
- $"({device.Api ?? "?"})");
+ Console.WriteLine(
+ $" [{device.Id, 4}] {device.Description ?? device.DeviceName} "
+ + $"({device.Api ?? "?"})"
+ );
}
if (graph.Links.Length > 0)
@@ -47,9 +56,11 @@ public static async Task ListAsync(CancellationToken cancellationToken)
foreach (PipeWireLink link in graph.Links)
{
(PipeWirePort? output, PipeWirePort? input) = graph.GetEndpoints(link);
- Console.WriteLine($" [{link.LinkId,4}] " +
- $"{output?.NodeId}:{output?.PortName ?? "?"} -> " +
- $"{input?.NodeId}:{input?.PortName ?? "?"}");
+ Console.WriteLine(
+ $" [{link.LinkId, 4}] "
+ + $"{output?.NodeId}:{output?.PortName ?? "?"} -> "
+ + $"{input?.NodeId}:{input?.PortName ?? "?"}"
+ );
}
}
@@ -65,35 +76,46 @@ public static async Task MonitorAsync(CancellationToken cancellationToken)
// a graph that can no longer change.
using var lost = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
- await using var session = await Session.ConnectAsync(
- "sample-monitor", lost, cancellationToken).ConfigureAwait(false);
+ await using var session = await Session
+ .ConnectAsync("sample-monitor", lost, cancellationToken)
+ .ConfigureAwait(false);
Console.WriteLine("Watching the graph; Ctrl+C stops.");
// A watcher hears about every global going away. A proxy hears about its own object only,
// which is the signal you get when you hold one object and never built a snapshot. Bound
// here against whatever sink is present so the sample shows both in one run.
- PipeWireNode? watched = session.Registry.Current.Nodes
- .FirstOrDefault(n => n.MediaClass == "Audio/Sink");
+ PipeWireNode? watched = session.Registry.Current.Nodes.FirstOrDefault(n =>
+ n.MediaClass == "Audio/Sink"
+ );
PipeWireNodeProxy? proxy = null;
if (watched is not null)
{
proxy = session.Registry.BindNode(watched.NodeId);
proxy.Removed += () =>
- Console.WriteLine($" the daemon destroyed node {watched.NodeId} ('{watched.NodeName}')");
+ Console.WriteLine(
+ $" the daemon destroyed node {watched.NodeId} ('{watched.NodeName}')"
+ );
- Console.WriteLine($"Also watching node {watched.NodeId} ('{watched.NodeName}') for removal.");
+ Console.WriteLine(
+ $"Also watching node {watched.NodeId} ('{watched.NodeName}') for removal."
+ );
}
try
{
- await foreach (PipeWireGraphSnapshot graph in session.Registry
- .WatchAsync(lost.Token).ConfigureAwait(false))
+ await foreach (
+ PipeWireGraphSnapshot graph in session
+ .Registry.WatchAsync(lost.Token)
+ .ConfigureAwait(false)
+ )
{
- Console.WriteLine($"[{graph.Version}] nodes={graph.Nodes.Length} " +
- $"ports={graph.Ports.Length} links={graph.Links.Length} " +
- $"devices={graph.Devices.Length} clients={graph.Clients.Length}");
+ Console.WriteLine(
+ $"[{graph.Version}] nodes={graph.Nodes.Length} "
+ + $"ports={graph.Ports.Length} links={graph.Links.Length} "
+ + $"devices={graph.Devices.Length} clients={graph.Clients.Length}"
+ );
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
@@ -104,7 +126,8 @@ public static async Task MonitorAsync(CancellationToken cancellationToken)
{
if (proxy is not null)
{
- if (proxy.IsRemoved) Console.WriteLine(" the watched node is gone.");
+ if (proxy.IsRemoved)
+ Console.WriteLine(" the watched node is gone.");
await proxy.DisposeAsync().ConfigureAwait(false);
}
}
@@ -122,9 +145,17 @@ public static async Task VolumeAsync(string[] args, CancellationToken cance
float setLevel = 0;
if (setRaw is not null)
{
- if (!float.TryParse(setRaw, NumberStyles.Float,
- CultureInfo.InvariantCulture, out setLevel)
- || float.IsNaN(setLevel) || setLevel < 0 || setLevel > 1)
+ if (
+ !float.TryParse(
+ setRaw,
+ NumberStyles.Float,
+ CultureInfo.InvariantCulture,
+ out setLevel
+ )
+ || float.IsNaN(setLevel)
+ || setLevel < 0
+ || setLevel > 1
+ )
{
Console.Error.WriteLine($"--set takes a volume from 0 to 1, not '{setRaw}'.");
return 2;
@@ -137,17 +168,20 @@ public static async Task VolumeAsync(string[] args, CancellationToken cance
return 2;
}
- await using var session = await Session.ConnectAsync(
- "sample-volume", cancellationToken).ConfigureAwait(false);
+ await using var session = await Session
+ .ConnectAsync("sample-volume", cancellationToken)
+ .ConfigureAwait(false);
PipeWireNode? node = target is null
? await DefaultSinkNodeAsync(session, cancellationToken).ConfigureAwait(false)
: FindNode(session, target);
if (node is null)
{
- Console.Error.WriteLine(target is null
- ? "No default sink in this session; name a node explicitly."
- : $"No node matches '{target}'.");
+ Console.Error.WriteLine(
+ target is null
+ ? "No default sink in this session; name a node explicitly."
+ : $"No node matches '{target}'."
+ );
return Program.NothingToDo;
}
@@ -179,13 +213,16 @@ public static async Task VolumeAsync(string[] args, CancellationToken cance
public static async Task DefaultsAsync(CancellationToken cancellationToken)
{
- await using var session = await Session.ConnectAsync(
- "sample-defaults", cancellationToken).ConfigureAwait(false);
+ await using var session = await Session
+ .ConnectAsync("sample-defaults", cancellationToken)
+ .ConfigureAwait(false);
PipeWireMetadataProxy? store = session.Registry.BindMetadata("default");
if (store is null)
{
- Console.Error.WriteLine("No 'default' metadata store; this session has no session manager.");
+ Console.Error.WriteLine(
+ "No 'default' metadata store; this session has no session manager."
+ );
return Program.NothingToDo;
}
@@ -196,7 +233,9 @@ public static async Task DefaultsAsync(CancellationToken cancellationToken)
Console.WriteLine($"Default source: {store.DefaultAudioSource?.NameValue ?? ""}");
Console.WriteLine($"Clock rate: {Render(store.ClockRate)} Hz");
Console.WriteLine($"Clock quantum: {Render(store.ClockQuantum)}");
- Console.WriteLine($"Quantum range: {Render(store.ClockMinQuantum)}..{Render(store.ClockMaxQuantum)}");
+ Console.WriteLine(
+ $"Quantum range: {Render(store.ClockMinQuantum)}..{Render(store.ClockMaxQuantum)}"
+ );
Console.WriteLine($"Forced rate: {Render(store.ClockForcedRate)}");
Console.WriteLine($"Forced quantum: {Render(store.ClockForcedQuantum)}");
}
@@ -206,7 +245,9 @@ public static async Task DefaultsAsync(CancellationToken cancellationToken)
// The default sink node: metadata names the node, the registry resolves it.
internal static async Task DefaultSinkNodeAsync(
- Session session, CancellationToken cancellationToken)
+ Session session,
+ CancellationToken cancellationToken
+ )
{
PipeWireMetadataProxy? store = session.Registry.BindMetadata("default");
if (store is null)
@@ -226,8 +267,7 @@ public static async Task DefaultsAsync(CancellationToken cancellationToken)
// Numeric id first, then a case-insensitive fragment of the node or description.
internal static PipeWireNode? FindNode(Session session, string target)
{
- if (uint.TryParse(target, NumberStyles.Integer,
- CultureInfo.InvariantCulture, out uint id))
+ if (uint.TryParse(target, NumberStyles.Integer, CultureInfo.InvariantCulture, out uint id))
{
foreach (PipeWireNode node in session.Registry.Nodes)
{
diff --git a/samples/PipeWire.NET.SampleConsole/PipeWire.NET.SampleConsole.csproj b/samples/PipeWire.NET.SampleConsole/PipeWire.NET.SampleConsole.csproj
index b58415f..821b658 100644
--- a/samples/PipeWire.NET.SampleConsole/PipeWire.NET.SampleConsole.csproj
+++ b/samples/PipeWire.NET.SampleConsole/PipeWire.NET.SampleConsole.csproj
@@ -2,9 +2,11 @@
Exe
net10.0;net11.0
+ true
- 10.0.11
+ 10.0.12
linux-x64;linux-arm64
false
-
+
CP0001
T:PipeWire.NET.AudioFrame
@@ -2442,4 +2445,4 @@
lib/net11.0/PipeWire.NET.dll
true
-
\ No newline at end of file
+
diff --git a/src/PipeWire.NET/Core/PipeWireContext.cs b/src/PipeWire.NET/Core/PipeWireContext.cs
index 1d9541c..d362730 100644
--- a/src/PipeWire.NET/Core/PipeWireContext.cs
+++ b/src/PipeWire.NET/Core/PipeWireContext.cs
@@ -1,10 +1,10 @@
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
-using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging;
-using System.Runtime.CompilerServices;
-using System.Globalization;
-using System.Runtime.InteropServices;
+using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Win32.SafeHandles;
using PipeWire.NET.Interop;
using PipeWire.NET.Spa;
@@ -39,7 +39,15 @@ public sealed partial class PipeWireContext : IDisposable, IAsyncDisposable
// A Lock scope held across Dispose is a contract violation, and the drain is what keeps that
// violation a hang at the call site rather than an unlock against a destroyed loop. Do not
// hold a scope across Dispose.
- private enum LifecycleState { Created, Starting, Running, Stopping, Disposed }
+ private enum LifecycleState
+ {
+ Created,
+ Starting,
+ Running,
+ Stopping,
+ Disposed,
+ }
+
private readonly Lock _lifecycle = new();
private volatile LifecycleState _state;
private int _activeLeases;
@@ -51,12 +59,12 @@ private enum LifecycleState { Created, Starting, Running, Stopping, Disposed }
// disposal is about to stop, so a caller waiting on one with no cancellation of its own would
// otherwise wait for a reply that can never come.
private readonly CancellationTokenSource _shutdown = new();
- private PipeWireLoopHandle? _loopHandle;
+ private PipeWireLoopHandle? _loopHandle;
private PipeWireContextHandle? _contextHandle;
- private PipeWireCoreHandle? _coreHandle;
+ private PipeWireCoreHandle? _coreHandle;
private readonly string _name;
- private volatile bool _started;
- private volatile bool _disposed;
+ private volatile bool _started;
+ private volatile bool _disposed;
// The connection's own listener, distinct from the per-request one a round trip attaches.
// A connection can die between requests - the daemon destroys our client, or the socket goes -
@@ -115,7 +123,8 @@ internal void ReportRefusedUnlock(int result)
internal unsafe void UnlockLoop(pw_thread_loop* loop)
{
int rc = Native.pw_thread_loop_unlock_checked(loop);
- if (rc < 0) ReportRefusedUnlock(rc);
+ if (rc < 0)
+ ReportRefusedUnlock(rc);
}
private long _refusedUnlocks;
@@ -156,7 +165,9 @@ public PipeWireContext(string name = "PipeWire.NET", ILoggerFactory? loggerFacto
///
///
private static readonly Lazy ProcessInit = new(
- InitOnce, LazyThreadSafetyMode.ExecutionAndPublication);
+ InitOnce,
+ LazyThreadSafetyMode.ExecutionAndPublication
+ );
private static unsafe bool InitOnce()
{
@@ -183,16 +194,19 @@ private static unsafe bool InitOnce()
private static unsafe Version? ParseLibraryVersion()
{
string? text = Marshal.PtrToStringUTF8((IntPtr)Native.pw_get_library_version());
- if (string.IsNullOrEmpty(text)) return null;
+ if (string.IsNullOrEmpty(text))
+ return null;
// "1.6.8", sometimes with a suffix. Only the leading two numbers are compared.
int firstDot = text.IndexOf('.', StringComparison.Ordinal);
- if (firstDot <= 0) return null;
+ if (firstDot <= 0)
+ return null;
int secondDot = text.IndexOf('.', firstDot + 1);
string minorText = secondDot < 0 ? text[(firstDot + 1)..] : text[(firstDot + 1)..secondDot];
- return int.TryParse(text[..firstDot], CultureInfo.InvariantCulture, out int major)
+ return
+ int.TryParse(text[..firstDot], CultureInfo.InvariantCulture, out int major)
&& int.TryParse(minorText, CultureInfo.InvariantCulture, out int minor)
? new Version(major, minor)
: null;
@@ -212,12 +226,14 @@ private static unsafe bool InitOnce()
private static void RequireASupportedLibrary()
{
Version? runtime = ParseLibraryVersion();
- if (runtime is null || runtime >= MinimumLibraryVersion) return;
+ if (runtime is null || runtime >= MinimumLibraryVersion)
+ return;
throw new PipeWireException(
$"libpipewire {runtime} is older than the {MinimumLibraryVersion} these bindings are "
- + "generated against, and the combination hangs rather than failing. Upgrade PipeWire, "
- + "or use a build of this library generated against the installed release.");
+ + "generated against, and the combination hangs rather than failing. Upgrade PipeWire, "
+ + "or use a build of this library generated against the installed release."
+ );
}
///
@@ -258,21 +274,26 @@ public void LoadModule(string name, string? args = null)
ArgumentException.ThrowIfNullOrEmpty(name);
if (_started)
- throw new InvalidOperationException("modules must be loaded before the context starts.");
+ throw new InvalidOperationException(
+ "modules must be loaded before the context starts."
+ );
_modules.Add((name, args));
}
private unsafe void LoadQueuedModules()
{
- if (_modules.Count == 0) return;
+ if (_modules.Count == 0)
+ return;
pw_context* context = _contextHandle!.Context;
foreach ((string name, string? args) in _modules)
{
ReadOnlySpan nameUtf8 = Encoding.UTF8.GetBytes(name + '\0');
- ReadOnlySpan argsUtf8 = args is null ? default : Encoding.UTF8.GetBytes(args + '\0');
+ ReadOnlySpan argsUtf8 = args is null
+ ? default
+ : Encoding.UTF8.GetBytes(args + '\0');
fixed (byte* n = nameUtf8)
fixed (byte* a = argsUtf8)
@@ -280,7 +301,9 @@ private unsafe void LoadQueuedModules()
if (Native.pw_context_load_module(context, (sbyte*)n, (sbyte*)a, null) is null)
{
throw new PipeWireInteropException(
- $"pw_context_load_module({name})", -NativeLibc.ENOENT);
+ $"pw_context_load_module({name})",
+ -NativeLibc.ENOENT
+ );
}
}
}
@@ -316,7 +339,8 @@ public unsafe int LoopDescriptor
get
{
PipeWireLoopHandle? loop = _loopHandle;
- if (loop is null || loop.IsInvalid || loop.IsClosed) return -1;
+ if (loop is null || loop.IsInvalid || loop.IsClosed)
+ return -1;
return Native.pw_loop_get_fd(Native.pw_thread_loop_get_loop(LoopHandle));
}
@@ -365,7 +389,8 @@ private void RequireExternalDriver()
{
throw new InvalidOperationException(
"this context runs its own loop thread; set DriveExternally before starting it to "
- + "drive the loop from your own.");
+ + "drive the loop from your own."
+ );
}
}
@@ -387,7 +412,8 @@ private unsafe void InitializeNative(string name)
pw_context* context = Native.pw_context_new(
Native.pw_thread_loop_get_loop(loop),
props: null,
- user_data_size: 0);
+ user_data_size: 0
+ );
if (context is null)
{
@@ -405,7 +431,8 @@ private unsafe void InitializeNative(string name)
// reaches zero.
private bool Admit()
{
- if (_state is LifecycleState.Stopping or LifecycleState.Disposed) return false;
+ if (_state is LifecycleState.Stopping or LifecycleState.Disposed)
+ return false;
Interlocked.Increment(ref _activeLeases);
_leasesDrained.Reset();
return true;
@@ -528,8 +555,11 @@ private Task StartAsyncCore(SafeHandle? handle, int rawFd, CancellationToken can
lock (_lifecycle)
{
ObjectDisposedException.ThrowIf(
- _disposed || _state is LifecycleState.Stopping or LifecycleState.Disposed, this);
- if (_state == LifecycleState.Running) return Task.CompletedTask;
+ _disposed || _state is LifecycleState.Stopping or LifecycleState.Disposed,
+ this
+ );
+ if (_state == LifecycleState.Running)
+ return Task.CompletedTask;
if (_state == LifecycleState.Created)
{
_state = LifecycleState.Starting;
@@ -550,7 +580,8 @@ private Task StartAsyncCore(SafeHandle? handle, int rawFd, CancellationToken can
_startSettled.Wait(cancellationToken);
lock (_lifecycle)
{
- if (_state == LifecycleState.Running) return Task.CompletedTask;
+ if (_state == LifecycleState.Running)
+ return Task.CompletedTask;
if (_state is LifecycleState.Stopping or LifecycleState.Disposed)
throw new ObjectDisposedException(nameof(PipeWireContext));
if (_state == LifecycleState.Created && Admit())
@@ -574,7 +605,8 @@ private Task StartAsyncCore(SafeHandle? handle, int rawFd, CancellationToken can
// Back to Created so a later start can retry: StartNative stops the loop thread
// it started, so nothing is left running. The settle event wakes any waiter,
// which re-verifies the state and takes over.
- if (_state == LifecycleState.Starting) _state = LifecycleState.Created;
+ if (_state == LifecycleState.Starting)
+ _state = LifecycleState.Created;
}
_startSettled.Set();
ReleaseLease();
@@ -633,9 +665,10 @@ private unsafe void StartNative(SafeHandle? handle, int rawFd)
// The descriptor the connect runs over: the duplicate's number, the caller's raw
// number, or none of them for the default daemon socket.
- int connectFd = duplicate is not null ? (int)duplicate.DangerousGetHandle()
- : rawFd >= 0 ? rawFd
- : -1;
+ int connectFd =
+ duplicate is not null ? (int)duplicate.DangerousGetHandle()
+ : rawFd >= 0 ? rawFd
+ : -1;
pw_core* core;
@@ -655,13 +688,32 @@ private unsafe void StartNative(SafeHandle? handle, int rawFd)
pw_properties* props = Native.pw_properties_new_dict(&native);
if (props is null)
- throw new PipeWireInteropException("pw_properties_new_dict", -NativeLibc.ENOMEM);
-
- core = connectFd < 0
- ? (RunInProcess
- ? Native.pw_context_connect_self(_contextHandle!.Context, props, user_data_size: 0)
- : Native.pw_context_connect(_contextHandle!.Context, props, user_data_size: 0))
- : Native.pw_context_connect_fd(_contextHandle!.Context, connectFd, props, user_data_size: 0);
+ throw new PipeWireInteropException(
+ "pw_properties_new_dict",
+ -NativeLibc.ENOMEM
+ );
+
+ core =
+ connectFd < 0
+ ? (
+ RunInProcess
+ ? Native.pw_context_connect_self(
+ _contextHandle!.Context,
+ props,
+ user_data_size: 0
+ )
+ : Native.pw_context_connect(
+ _contextHandle!.Context,
+ props,
+ user_data_size: 0
+ )
+ )
+ : Native.pw_context_connect_fd(
+ _contextHandle!.Context,
+ connectFd,
+ props,
+ user_data_size: 0
+ );
}
finally
{
@@ -675,15 +727,19 @@ private unsafe void StartNative(SafeHandle? handle, int rawFd)
// disposal closes it. A raw descriptor stays the caller's; nothing here closes it.
throw connectFd < 0
? new PipeWireConnectFailedException(
- "pw_context_connect", -NativeLibc.ENOENT,
+ "pw_context_connect",
+ -NativeLibc.ENOENT,
objectId: null,
- "ensure the PipeWire daemon is running (pipewire.service / wireplumber.service)")
+ "ensure the PipeWire daemon is running (pipewire.service / wireplumber.service)"
+ )
: new PipeWireConnectFailedException(
- "pw_context_connect_fd", -NativeLibc.ENOENT,
+ "pw_context_connect_fd",
+ -NativeLibc.ENOENT,
objectId: null,
"the fd must be a connected PipeWire socket (as returned by a portal "
- + "OpenPipeWireRemote request), not a plain file, and the daemon must be "
- + "reachable on it");
+ + "OpenPipeWireRemote request), not a plain file, and the daemon must be "
+ + "reachable on it"
+ );
}
// A successful connect hands the descriptor to PipeWire: pw_context_connect_fd puts
@@ -741,7 +797,8 @@ public unsafe bool TryLock(out LoopLock scope)
// admitted here, and the recheck after locking observes a disposal that began in between
// without taking the gate while the native lock is held.
PipeWireLoopHandle? loop = _loopHandle;
- if (loop is null) return false;
+ if (loop is null)
+ return false;
// A reference, not a validity check. Testing the handle and then reading its pointer is two
// steps, and disposal between them hands pw_thread_loop_lock a null pointer - which is a
@@ -758,7 +815,8 @@ public unsafe bool TryLock(out LoopLock scope)
return false;
}
- if (!referenced) return false;
+ if (!referenced)
+ return false;
lock (_lifecycle)
{
@@ -823,7 +881,8 @@ internal unsafe bool IsOnLoopThread
get
{
PipeWireLoopHandle? loop = _loopHandle;
- if (loop is null || loop.IsInvalid) return false;
+ if (loop is null || loop.IsInvalid)
+ return false;
bool referenced = false;
try
@@ -837,7 +896,8 @@ internal unsafe bool IsOnLoopThread
}
finally
{
- if (referenced) loop.DangerousRelease();
+ if (referenced)
+ loop.DangerousRelease();
}
}
}
@@ -871,7 +931,10 @@ internal unsafe pw_thread_loop* LoopHandle
/// refuses to bind one of these through this connection: module-metadata would stop reading the
/// connection until it answered a ping it can then never be heard answering.
///
- internal System.Collections.Concurrent.ConcurrentDictionary ServedStores { get; } = new();
+ internal System.Collections.Concurrent.ConcurrentDictionary<
+ Graph.PipeWireMetadataProvider,
+ byte
+ > ServedStores { get; } = new();
///
/// Changes this client's own properties on a live connection.
@@ -894,7 +957,8 @@ internal unsafe pw_thread_loop* LoopHandle
public unsafe int UpdateProperties(IReadOnlyDictionary properties)
{
ArgumentNullException.ThrowIfNull(properties);
- if (properties.Count == 0) return 0;
+ if (properties.Count == 0)
+ return 0;
// Sized from the input rather than a fixed scratch: a long value would otherwise lose its
// tail. UTF-8 is at most 4 bytes per char, plus a terminator for each key and value.
@@ -906,14 +970,16 @@ public unsafe int UpdateProperties(IReadOnlyDictionary propertie
var items = new spa_dict_item[properties.Count];
var builder = new SpaDictBuilder(scratch, items);
- foreach (KeyValuePair kv in properties) builder.Add(kv.Key, kv.Value);
+ foreach (KeyValuePair kv in properties)
+ builder.Add(kv.Key, kv.Value);
spa_dict native = builder.Build();
using (Lock())
{
pw_core* core = CoreHandle;
- if (core is null) throw new InvalidOperationException("the context is not connected.");
+ if (core is null)
+ throw new InvalidOperationException("the context is not connected.");
return Native.pw_core_update_properties(core, &native);
}
@@ -948,7 +1014,14 @@ private unsafe void WatchConnection(pw_core* core)
_watchEvents->error = &OnConnectionError;
_watchHook = (spa_hook*)NativeMemory.AllocZeroed((nuint)sizeof(spa_hook));
- if (Native.pw_core_add_listener(core, _watchHook, _watchEvents, (void*)GCHandle.ToIntPtr(_watchSelf)) >= 0)
+ if (
+ Native.pw_core_add_listener(
+ core,
+ _watchHook,
+ _watchEvents,
+ (void*)GCHandle.ToIntPtr(_watchSelf)
+ ) >= 0
+ )
return;
ReleaseConnectionWatch();
@@ -967,8 +1040,14 @@ private unsafe void ReleaseConnectionWatch()
if (_loopHandle is { IsInvalid: false } loop)
{
Native.pw_thread_loop_lock(loop.Loop);
- try { Native.spa_hook_remove(_watchHook); }
- finally { UnlockLoop(loop.Loop); }
+ try
+ {
+ Native.spa_hook_remove(_watchHook);
+ }
+ finally
+ {
+ UnlockLoop(loop.Loop);
+ }
}
else
{
@@ -981,8 +1060,13 @@ private unsafe void ReleaseConnectionWatch()
_watchHook = null;
}
- if (_watchEvents is not null) { NativeMemory.Free(_watchEvents); _watchEvents = null; }
- if (_watchSelf.IsAllocated) _watchSelf.Free();
+ if (_watchEvents is not null)
+ {
+ NativeMemory.Free(_watchEvents);
+ _watchEvents = null;
+ }
+ if (_watchSelf.IsAllocated)
+ _watchSelf.Free();
}
/// The daemon reporting an error against the connection, from the loop thread.
@@ -991,21 +1075,35 @@ private unsafe void ReleaseConnectionWatch()
/// an object that is gone - is an answer, and the connection carries on serving.
///
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
- private static unsafe void OnConnectionError(void* data, uint id, int seq, int res, sbyte* message)
+ private static unsafe void OnConnectionError(
+ void* data,
+ uint id,
+ int seq,
+ int res,
+ sbyte* message
+ )
{
// A native callback frame: an escaping exception aborts the process.
try
{
- if (data is null) return;
- if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireContext self) return;
- if (!IsConnectionFatal(res)) return;
+ if (data is null)
+ return;
+ if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireContext self)
+ return;
+ if (!IsConnectionFatal(res))
+ return;
var fault = new PipeWireConnectionClosedException(
- "connection", res, id, message is null ? null : DaemonText.String(message));
+ "connection",
+ res,
+ id,
+ message is null ? null : DaemonText.String(message)
+ );
// First one wins: the errno that ended it is the useful one, and a closing connection
// can report several.
- if (Interlocked.CompareExchange(ref self._fault, fault, null) is not null) return;
+ if (Interlocked.CompareExchange(ref self._fault, fault, null) is not null)
+ return;
SafeCallback.Raise(self.ConnectionLost, h => h(fault), _ => { });
}
@@ -1017,11 +1115,12 @@ private static unsafe void OnConnectionError(void* data, uint id, int seq, int r
}
///
- private static bool IsConnectionFatal(int result) => result is
- -NativeLibc.EPIPE or
- -NativeLibc.ECONNABORTED or
- -NativeLibc.ECONNRESET or
- -NativeLibc.ENOTCONN;
+ private static bool IsConnectionFatal(int result) =>
+ result
+ is -NativeLibc.EPIPE
+ or -NativeLibc.ECONNABORTED
+ or -NativeLibc.ECONNRESET
+ or -NativeLibc.ENOTCONN;
private void DisposeCore()
{
@@ -1034,13 +1133,15 @@ private void DisposeCore()
{
throw new InvalidOperationException(
"A PipeWire context cannot be disposed from its own loop thread: stopping the loop "
- + "joins that thread, so it would wait for itself. Dispose from the thread that "
- + "created it, or hand the disposal to another thread from the callback.");
+ + "joins that thread, so it would wait for itself. Dispose from the thread that "
+ + "created it, or hand the disposal to another thread from the callback."
+ );
}
lock (_lifecycle)
{
- if (_disposed) return;
+ if (_disposed)
+ return;
_disposed = true;
_state = LifecycleState.Stopping;
}
@@ -1104,8 +1205,11 @@ private unsafe void DisposeNative()
_loopHandle = null;
}
- [LoggerMessage(EventId = 34700, Level = LogLevel.Error,
- Message = "the loop refused an unlock ({Result}) on managed thread {ThreadId}; the loop mutex is still held by that thread and the next thread that needs it will wait for ever. At: {StackTrace}")]
+ [LoggerMessage(
+ EventId = 34700,
+ Level = LogLevel.Error,
+ Message = "the loop refused an unlock ({Result}) on managed thread {ThreadId}; the loop mutex is still held by that thread and the next thread that needs it will wait for ever. At: {StackTrace}"
+ )]
private partial void LogRefusedUnlock(int threadId, int result, string stackTrace);
///
@@ -1156,11 +1260,13 @@ internal void Release()
// however many copies were made and however often Dispose runs. An unbalanced unlock
// corrupts the native mutex the loop - and every scope after it - depends on, and a
// missing lease release wedges disposal's drain: it waits for a scope that is gone.
- if (Interlocked.Exchange(ref _released, 1) != 0) return;
+ if (Interlocked.Exchange(ref _released, 1) != 0)
+ return;
try
{
int rc = Native.pw_thread_loop_unlock_checked(_loop);
- if (rc < 0) _context.ReportRefusedUnlock(rc);
+ if (rc < 0)
+ _context.ReportRefusedUnlock(rc);
_handle.DangerousRelease();
}
finally
diff --git a/src/PipeWire.NET/Core/PipeWireMediaClass.cs b/src/PipeWire.NET/Core/PipeWireMediaClass.cs
index d7baff6..f74fdc9 100644
--- a/src/PipeWire.NET/Core/PipeWireMediaClass.cs
+++ b/src/PipeWire.NET/Core/PipeWireMediaClass.cs
@@ -24,19 +24,23 @@ public static class PipeWireMediaClass
public static PipeWireMediaKind ParseKind(string? mediaClass)
{
ReadOnlySpan s = mediaClass;
- if (s.IsEmpty) return PipeWireMediaKind.Unknown;
+ if (s.IsEmpty)
+ return PipeWireMediaKind.Unknown;
// "Stream/Output/Audio" names its medium last; everything else names it first.
ReadOnlySpan head = NextSegment(ref s);
if (head.Equals("Stream", StringComparison.Ordinal))
{
- _ = NextSegment(ref s); // Output | Input
+ _ = NextSegment(ref s); // Output | Input
head = NextSegment(ref s);
}
- if (head.Equals("Audio", StringComparison.Ordinal)) return PipeWireMediaKind.Audio;
- if (head.Equals("Video", StringComparison.Ordinal)) return PipeWireMediaKind.Video;
- if (head.Equals("Midi", StringComparison.Ordinal)) return PipeWireMediaKind.Midi;
+ if (head.Equals("Audio", StringComparison.Ordinal))
+ return PipeWireMediaKind.Audio;
+ if (head.Equals("Video", StringComparison.Ordinal))
+ return PipeWireMediaKind.Video;
+ if (head.Equals("Midi", StringComparison.Ordinal))
+ return PipeWireMediaKind.Midi;
return PipeWireMediaKind.Unknown;
}
@@ -44,7 +48,8 @@ public static PipeWireMediaKind ParseKind(string? mediaClass)
public static PipeWireMediaFlow ParseFlow(string? mediaClass)
{
ReadOnlySpan s = mediaClass;
- if (s.IsEmpty) return PipeWireMediaFlow.Unknown;
+ if (s.IsEmpty)
+ return PipeWireMediaFlow.Unknown;
ReadOnlySpan head = NextSegment(ref s);
@@ -52,27 +57,34 @@ public static PipeWireMediaFlow ParseFlow(string? mediaClass)
{
// An app's Output is the graph's Source, and vice versa.
ReadOnlySpan dir = NextSegment(ref s);
- if (dir.Equals("Output", StringComparison.Ordinal)) return PipeWireMediaFlow.Source;
- if (dir.Equals("Input", StringComparison.Ordinal)) return PipeWireMediaFlow.Sink;
+ if (dir.Equals("Output", StringComparison.Ordinal))
+ return PipeWireMediaFlow.Source;
+ if (dir.Equals("Input", StringComparison.Ordinal))
+ return PipeWireMediaFlow.Sink;
return PipeWireMediaFlow.Unknown;
}
// "Audio/Source", "Audio/Source/Virtual", "Video/Sink", "Midi/Bridge", ...
ReadOnlySpan role = NextSegment(ref s);
- if (role.Equals("Source", StringComparison.Ordinal)) return PipeWireMediaFlow.Source;
- if (role.Equals("Sink", StringComparison.Ordinal)) return PipeWireMediaFlow.Sink;
- if (role.Equals("Duplex", StringComparison.Ordinal)) return PipeWireMediaFlow.Duplex;
+ if (role.Equals("Source", StringComparison.Ordinal))
+ return PipeWireMediaFlow.Source;
+ if (role.Equals("Sink", StringComparison.Ordinal))
+ return PipeWireMediaFlow.Sink;
+ if (role.Equals("Duplex", StringComparison.Ordinal))
+ return PipeWireMediaFlow.Duplex;
// Bridge is treated as duplex. PipeWire does not say so directly, but a bridge exists to
// carry data both ways (Midi/Bridge is the common one), and it publishes input and output
// ports. Anything relying on this should check the ports rather than the class.
- if (role.Equals("Bridge", StringComparison.Ordinal)) return PipeWireMediaFlow.Duplex;
+ if (role.Equals("Bridge", StringComparison.Ordinal))
+ return PipeWireMediaFlow.Duplex;
return PipeWireMediaFlow.Unknown;
}
/// Takes the next /-separated segment, advancing .
private static ReadOnlySpan NextSegment(ref ReadOnlySpan rest)
{
- if (rest.IsEmpty) return default;
+ if (rest.IsEmpty)
+ return default;
int slash = rest.IndexOf('/');
if (slash < 0)
diff --git a/src/PipeWire.NET/Graph/MetadataReconciler.cs b/src/PipeWire.NET/Graph/MetadataReconciler.cs
index e4f582c..425ec05 100644
--- a/src/PipeWire.NET/Graph/MetadataReconciler.cs
+++ b/src/PipeWire.NET/Graph/MetadataReconciler.cs
@@ -1,6 +1,5 @@
using System.Collections.Concurrent;
-
namespace PipeWire.NET.Graph;
///
@@ -28,7 +27,10 @@ namespace PipeWire.NET.Graph;
///
internal sealed class MetadataReconciler(TimeSpan window, TimeProvider? time = null)
{
- private readonly ConcurrentDictionary<(uint Subject, string Key), List> _outstanding = new();
+ private readonly ConcurrentDictionary<
+ (uint Subject, string Key),
+ List
+ > _outstanding = new();
private readonly TimeProvider _time = time ?? TimeProvider.System;
private readonly TimeSpan _windowSpan = window;
@@ -82,8 +84,10 @@ internal PendingWrite NoteWrite(uint subject, string key, string? type, string?
lock (bucket)
{
- if (_outstanding.TryGetValue((subject, key), out List? current)
- && ReferenceEquals(current, bucket))
+ if (
+ _outstanding.TryGetValue((subject, key), out List? current)
+ && ReferenceEquals(current, bucket)
+ )
{
bucket.RemoveAll(e => e.Expired(now, _window));
bucket.Add(pending);
@@ -96,9 +100,11 @@ internal PendingWrite NoteWrite(uint subject, string key, string? type, string?
/// Drops a write that never landed, so it cannot suppress an echo that will not come.
internal void Forget(uint subject, string key, PendingWrite pending)
{
- if (!_outstanding.TryGetValue((subject, key), out List? bucket)) return;
+ if (!_outstanding.TryGetValue((subject, key), out List? bucket))
+ return;
- lock (bucket) bucket.Remove(pending);
+ lock (bucket)
+ bucket.Remove(pending);
DropIfEmpty(subject, key, bucket);
}
@@ -136,7 +142,8 @@ internal EchoAction Classify(uint subject, string key, string? type, string? val
// Our newest write coming home. The store raised it when it applied the write, so
// raising again here would report one change twice.
- if (bucket[^1].Matches(type, value)) return EchoAction.AlreadyKnown;
+ if (bucket[^1].Matches(type, value))
+ return EchoAction.AlreadyKnown;
// Records are deliberately not cleared when the newest is acknowledged: clearing
// discards the older entries, and echoes still in flight for those then match nothing
@@ -159,9 +166,11 @@ private void DropIfEmpty(uint subject, string key, List bucket)
// the bucket still present or re-adds it through GetOrAdd.
lock (bucket)
{
- if (bucket.Count != 0) return;
+ if (bucket.Count != 0)
+ return;
_outstanding.TryRemove(
- new KeyValuePair<(uint, string), List>((subject, key), bucket));
+ new KeyValuePair<(uint, string), List>((subject, key), bucket)
+ );
}
}
@@ -185,7 +194,8 @@ private void DropIfEmpty(uint subject, string key, List bucket)
///
internal void Settle(uint subject, string key)
{
- if (!_outstanding.TryGetValue((subject, key), out List? bucket)) return;
+ if (!_outstanding.TryGetValue((subject, key), out List? bucket))
+ return;
// The round trip proves every request issued before it was processed, so this is where the
// records in this bucket start ageing. Anything still unacknowledged after it was never
@@ -193,7 +203,8 @@ internal void Settle(uint subject, string key)
long now = Now();
lock (bucket)
{
- foreach (PendingWrite entry in bucket) entry.Acknowledge(now);
+ foreach (PendingWrite entry in bucket)
+ entry.Acknowledge(now);
}
DropIfEmpty(subject, key, bucket);
diff --git a/src/PipeWire.NET/Graph/PipeWireClient.cs b/src/PipeWire.NET/Graph/PipeWireClient.cs
index 788501e..da958ad 100644
--- a/src/PipeWire.NET/Graph/PipeWireClient.cs
+++ b/src/PipeWire.NET/Graph/PipeWireClient.cs
@@ -35,7 +35,8 @@ internal PipeWireClient(
string? SecurityLabel = null,
string? SecurityAppId = null,
string? SecurityInstanceId = null,
- PipeWireProperties? Properties = null)
+ PipeWireProperties? Properties = null
+ )
{
this.Properties = Properties ?? PipeWireProperties.Empty;
this.ObjectSerial = this.Properties.Serial;
diff --git a/src/PipeWire.NET/Graph/PipeWireClientProxy.cs b/src/PipeWire.NET/Graph/PipeWireClientProxy.cs
index 688ff96..b956693 100644
--- a/src/PipeWire.NET/Graph/PipeWireClientProxy.cs
+++ b/src/PipeWire.NET/Graph/PipeWireClientProxy.cs
@@ -49,8 +49,9 @@ public sealed partial class PipeWireClientProxy : IDisposable, IAsyncDisposable
private readonly PipeWireContext _ctx;
private readonly ILogger _logger;
- private readonly TaskCompletionSource _ready =
- new(TaskCreationOptions.RunContinuationsAsynchronously);
+ private readonly TaskCompletionSource _ready = new(
+ TaskCreationOptions.RunContinuationsAsynchronously
+ );
private BoundProxy? _bound;
private volatile bool _disposed;
@@ -74,12 +75,25 @@ private PipeWireClientProxy(PipeWireContext ctx, uint id, ILogger logger)
public uint Id { get; }
internal static unsafe PipeWireClientProxy Bind(
- PipeWireContext ctx, pw_registry* registry, uint id, uint version, ILogger logger,
- Action? propertiesObserved = null)
+ PipeWireContext ctx,
+ pw_registry* registry,
+ uint id,
+ uint version,
+ ILogger logger,
+ Action? propertiesObserved = null
+ )
{
- var control = new PipeWireClientProxy(ctx, id, logger) { PropertiesObserved = propertiesObserved };
+ var control = new PipeWireClientProxy(ctx, id, logger)
+ {
+ PropertiesObserved = propertiesObserved,
+ };
control._bound = BoundProxy.Bind(
- ctx, registry, id, PipeWireKeys.PW_TYPE_INTERFACE_Client, version, NativeConstants.PW_VERSION_CLIENT,
+ ctx,
+ registry,
+ id,
+ PipeWireKeys.PW_TYPE_INTERFACE_Client,
+ version,
+ NativeConstants.PW_VERSION_CLIENT,
sizeof(pw_client_events),
events =>
{
@@ -88,9 +102,15 @@ internal static unsafe PipeWireClientProxy Bind(
table->info = &OnInfoCallback;
table->permissions = &OnPermissionsCallback;
},
- static (proxy, hook, events, data) => Native.pw_client_add_listener(
- (pw_client*)proxy, (spa_hook*)hook, (pw_client_events*)events, (void*)data),
- control);
+ static (proxy, hook, events, data) =>
+ Native.pw_client_add_listener(
+ (pw_client*)proxy,
+ (spa_hook*)hook,
+ (pw_client_events*)events,
+ (void*)data
+ ),
+ control
+ );
control._bound.Removed = control.RaiseRemoved;
@@ -111,14 +131,19 @@ internal static unsafe PipeWireClientProxy Bind(
private void RaiseRemoved()
{
Action? handler = Removed;
- if (handler is null) return;
+ if (handler is null)
+ return;
// A native callback frame, so nothing may escape it.
- try { handler(); }
- catch (Exception) { /* a subscriber that throws must not reach the daemon */ }
+ try
+ {
+ handler();
+ }
+ catch (Exception)
+ { /* a subscriber that throws must not reach the daemon */
+ }
}
-
/// This client's properties, as the daemon last reported them.
///
/// Empty until the first info event arrives; waits for it. A
@@ -152,17 +177,26 @@ private static unsafe void OnInfoCallback(void* data, pw_client_info* info)
// An exception escaping a reverse P/Invoke aborts the process, so nothing here may throw.
try
{
- if (info is null) return;
- if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireClientProxy self) return;
- if (self._disposed) return;
+ if (info is null)
+ return;
+ if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireClientProxy self)
+ return;
+ if (self._disposed)
+ return;
if (info->props is not null)
{
PipeWireProperties properties = PipeWireProperties.From(info->props);
self._properties = properties;
- try { self.PropertiesObserved?.Invoke(self.Id, properties); }
- catch (Exception ex) { self.LogHandlerFaulted(self.Id, ex); }
+ try
+ {
+ self.PropertiesObserved?.Invoke(self.Id, properties);
+ }
+ catch (Exception ex)
+ {
+ self.LogHandlerFaulted(self.Id, ex);
+ }
}
self._ready.TrySetResult();
@@ -193,12 +227,16 @@ private static unsafe void OnInfoCallback(void* data, pw_client_info* info)
/// The proxy has been disposed.
/// Another read is already in flight.
public async Task> GetPermissionsAsync(
- uint index = 0, uint count = 64, CancellationToken cancellationToken = default)
+ uint index = 0,
+ uint count = 64,
+ CancellationToken cancellationToken = default
+ )
{
ObjectDisposedException.ThrowIf(_disposed, this);
var waiter = new TaskCompletionSource>(
- TaskCreationOptions.RunContinuationsAsynchronously);
+ TaskCreationOptions.RunContinuationsAsynchronously
+ );
lock (_permissionsGate)
{
@@ -211,7 +249,8 @@ public async Task> GetPermissionsAsync(
try
{
int res = Read(index, count);
- if (res < 0) throw new PipeWireException("pw_client_get_permissions", res, Id);
+ if (res < 0)
+ throw new PipeWireException("pw_client_get_permissions", res, Id);
return await waiter.Task.WaitAsync(cancellationToken).ConfigureAwait(false);
}
@@ -219,13 +258,15 @@ public async Task> GetPermissionsAsync(
{
lock (_permissionsGate)
{
- if (ReferenceEquals(_permissionsWaiter, waiter)) _permissionsWaiter = null;
+ if (ReferenceEquals(_permissionsWaiter, waiter))
+ _permissionsWaiter = null;
}
}
unsafe int Read(uint from, uint howMany)
{
- BoundProxy proxy = _bound ?? throw new ObjectDisposedException(nameof(PipeWireClientProxy));
+ BoundProxy proxy =
+ _bound ?? throw new ObjectDisposedException(nameof(PipeWireClientProxy));
using (_ctx.Lock())
return Native.pw_client_get_permissions((pw_client*)proxy.Object, from, howMany);
@@ -234,22 +275,32 @@ unsafe int Read(uint from, uint howMany)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe void OnPermissionsCallback(
- void* data, uint index, uint count, pw_permission* permissions)
+ void* data,
+ uint index,
+ uint count,
+ pw_permission* permissions
+ )
{
// An exception escaping a reverse P/Invoke aborts the process, so nothing here may throw.
try
{
- if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireClientProxy self) return;
+ if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireClientProxy self)
+ return;
var entries = ImmutableArray.CreateBuilder((int)count);
for (uint i = 0; i < count && permissions is not null; i++)
{
- entries.Add(new PipeWireObjectPermission(
- permissions[i].id, (PipeWirePermissions)permissions[i].permissions));
+ entries.Add(
+ new PipeWireObjectPermission(
+ permissions[i].id,
+ (PipeWirePermissions)permissions[i].permissions
+ )
+ );
}
TaskCompletionSource>? waiter;
- lock (self._permissionsGate) waiter = self._permissionsWaiter;
+ lock (self._permissionsGate)
+ waiter = self._permissionsWaiter;
waiter?.TrySetResult(entries.ToImmutable());
}
@@ -289,11 +340,15 @@ private static unsafe void OnPermissionsCallback(
/// The daemon refused.
public async Task UpdatePermissionsAsync(
ReadOnlyMemory permissions,
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (permissions.IsEmpty)
- throw new ArgumentException("at least one permission is required.", nameof(permissions));
+ throw new ArgumentException(
+ "at least one permission is required.",
+ nameof(permissions)
+ );
// Bits the daemon does not define are a caller mistake, not a forward-compatible extension:
// permission.h has held the same five since 0.3.77, and a stray bit is either a cast from
@@ -306,20 +361,23 @@ public async Task UpdatePermissionsAsync(
foreach (PipeWireObjectPermission entry in permissions.Span)
{
- if ((entry.Permissions & ~defined) == 0) continue;
+ if ((entry.Permissions & ~defined) == 0)
+ continue;
throw new ArgumentException(
$"object {entry.ObjectId} carries permission bits this library does not define: "
- + $"0x{(uint)(entry.Permissions & ~defined):x}.",
- nameof(permissions));
+ + $"0x{(uint)(entry.Permissions & ~defined):x}.",
+ nameof(permissions)
+ );
}
cancellationToken.ThrowIfCancellationRequested();
// Inside the round-trip: a permission change the daemon refuses is answered out of band,
// and a listener attached afterwards can miss it entirely.
- await CoreSync.RoundTripAsync(
- _ctx, () => Write(permissions.Span), cancellationToken).ConfigureAwait(false);
+ await CoreSync
+ .RoundTripAsync(_ctx, () => Write(permissions.Span), cancellationToken)
+ .ConfigureAwait(false);
}
///
@@ -342,7 +400,8 @@ await CoreSync.RoundTripAsync(
///
public Task ConfineToAsync(
ReadOnlySpan allowed,
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
var all = new PipeWireObjectPermission[allowed.Length + 1];
all[0] = new PipeWireObjectPermission(AnyObject, PipeWirePermissions.None);
@@ -356,8 +415,9 @@ public Task ConfineToAsync(
{
throw new ArgumentException(
"AnyObject is the default this method writes for you; it cannot also be a grant. "
- + "Use UpdatePermissionsAsync to set a default of your own.",
- nameof(allowed));
+ + "Use UpdatePermissionsAsync to set a default of your own.",
+ nameof(allowed)
+ );
}
}
@@ -373,20 +433,23 @@ public Task ConfineToAsync(
/// is .
public async Task UpdatePropertiesAsync(
IReadOnlyDictionary properties,
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
ArgumentNullException.ThrowIfNull(properties);
ObjectDisposedException.ThrowIf(_disposed, this);
- await CoreSync.RoundTripAsync(
- _ctx, () => WriteProperties(properties), cancellationToken).ConfigureAwait(false);
+ await CoreSync
+ .RoundTripAsync(_ctx, () => WriteProperties(properties), cancellationToken)
+ .ConfigureAwait(false);
}
private unsafe int Write(ReadOnlySpan permissions)
{
- Span native = permissions.Length <= 16
- ? stackalloc pw_permission[permissions.Length]
- : new pw_permission[permissions.Length];
+ Span native =
+ permissions.Length <= 16
+ ? stackalloc pw_permission[permissions.Length]
+ : new pw_permission[permissions.Length];
for (int i = 0; i < permissions.Length; i++)
{
@@ -403,7 +466,11 @@ private unsafe int Write(ReadOnlySpan permissions)
using (_ctx.Lock())
{
fixed (pw_permission* p = native)
- return Native.pw_client_update_permissions((pw_client*)proxy.Object, (uint)native.Length, p);
+ return Native.pw_client_update_permissions(
+ (pw_client*)proxy.Object,
+ (uint)native.Length,
+ p
+ );
}
}
@@ -416,12 +483,14 @@ private unsafe int WriteProperties(IReadOnlyDictionary propertie
// The dictionary holds raw pointers into these buffers, so they must not move between being
// filled and the native call reading them. A stackalloc cannot move; a plain array can, so
// the heap fallback allocates out of the pinned object heap rather than the normal one.
- Span scratch = bytes <= 1024
- ? stackalloc byte[bytes]
- : GC.AllocateUninitializedArray(bytes, pinned: true);
- Span items = properties.Count <= 32
- ? stackalloc spa_dict_item[properties.Count]
- : GC.AllocateArray(properties.Count, pinned: true);
+ Span scratch =
+ bytes <= 1024
+ ? stackalloc byte[bytes]
+ : GC.AllocateUninitializedArray(bytes, pinned: true);
+ Span items =
+ properties.Count <= 32
+ ? stackalloc spa_dict_item[properties.Count]
+ : GC.AllocateArray(properties.Count, pinned: true);
var builder = new SpaDictBuilder(scratch, items);
foreach ((string key, string value) in properties)
@@ -456,7 +525,8 @@ public ValueTask DisposeAsync()
private void DisposeCore()
{
- if (_disposed) return;
+ if (_disposed)
+ return;
_disposed = true;
_bound?.Dispose();
@@ -465,7 +535,10 @@ private void DisposeCore()
GC.SuppressFinalize(this);
}
- [LoggerMessage(EventId = 34700, Level = LogLevel.Warning,
- Message = "a properties handler for client {ClientId} threw")]
+ [LoggerMessage(
+ EventId = 34700,
+ Level = LogLevel.Warning,
+ Message = "a properties handler for client {ClientId} threw"
+ )]
private partial void LogHandlerFaulted(uint clientId, Exception exception);
}
diff --git a/src/PipeWire.NET/Graph/PipeWireCore.cs b/src/PipeWire.NET/Graph/PipeWireCore.cs
index 533771d..381262e 100644
--- a/src/PipeWire.NET/Graph/PipeWireCore.cs
+++ b/src/PipeWire.NET/Graph/PipeWireCore.cs
@@ -20,7 +20,8 @@ internal PipeWireCore(
string? CoreVersion,
string? HostName,
string? UserName,
- PipeWireProperties? Properties = null)
+ PipeWireProperties? Properties = null
+ )
{
this.Properties = Properties ?? PipeWireProperties.Empty;
this.ObjectSerial = this.Properties.Serial;
diff --git a/src/PipeWire.NET/Graph/PipeWireDevice.cs b/src/PipeWire.NET/Graph/PipeWireDevice.cs
index ab19439..0101d9f 100644
--- a/src/PipeWire.NET/Graph/PipeWireDevice.cs
+++ b/src/PipeWire.NET/Graph/PipeWireDevice.cs
@@ -29,7 +29,8 @@ internal PipeWireDevice(
uint? FactoryId,
uint? ClientId,
uint? ModuleId = null,
- PipeWireProperties? Properties = null)
+ PipeWireProperties? Properties = null
+ )
{
this.Properties = Properties ?? PipeWireProperties.Empty;
this.ObjectSerial = this.Properties.Serial;
diff --git a/src/PipeWire.NET/Graph/PipeWireDeviceProvider.cs b/src/PipeWire.NET/Graph/PipeWireDeviceProvider.cs
index e64d80b..36f8874 100644
--- a/src/PipeWire.NET/Graph/PipeWireDeviceProvider.cs
+++ b/src/PipeWire.NET/Graph/PipeWireDeviceProvider.cs
@@ -98,14 +98,18 @@ public static PipeWireDeviceProvider Create(
string name,
string description,
IReadOnlyDictionary>? parameters = null,
- IReadOnlyDictionary? properties = null)
+ IReadOnlyDictionary? properties = null
+ )
{
ArgumentNullException.ThrowIfNull(context);
ArgumentException.ThrowIfNullOrEmpty(name);
ArgumentException.ThrowIfNullOrEmpty(description);
var provider = new PipeWireDeviceProvider(
- context, name, context.LoggerFactory.CreateLogger($"PipeWire.NET.Device.{name}"));
+ context,
+ name,
+ context.LoggerFactory.CreateLogger($"PipeWire.NET.Device.{name}")
+ );
var props = ImmutableArray.CreateBuilder>();
props.Add(new(PipeWireKeys.PW_KEY_DEVICE_NAME, name));
@@ -116,7 +120,12 @@ public static PipeWireDeviceProvider Create(
{
foreach (KeyValuePair pair in properties)
{
- if (pair.Key is PipeWireKeys.PW_KEY_DEVICE_NAME or PipeWireKeys.PW_KEY_DEVICE_DESCRIPTION) continue;
+ if (
+ pair.Key
+ is PipeWireKeys.PW_KEY_DEVICE_NAME
+ or PipeWireKeys.PW_KEY_DEVICE_DESCRIPTION
+ )
+ continue;
props.Add(pair);
}
}
@@ -126,7 +135,9 @@ public static PipeWireDeviceProvider Create(
if (parameters is not null)
{
provider._params = parameters.ToImmutableDictionary(
- static p => p.Key, static p => p.Value);
+ static p => p.Key,
+ static p => p.Value
+ );
}
provider.Publish();
@@ -168,13 +179,17 @@ public static PipeWireDeviceProvider FromSpaFactory(
PipeWireContext context,
string factoryName,
IReadOnlyDictionary? properties = null,
- string? libraryName = null)
+ string? libraryName = null
+ )
{
ArgumentNullException.ThrowIfNull(context);
ArgumentException.ThrowIfNullOrEmpty(factoryName);
var provider = new PipeWireDeviceProvider(
- context, factoryName, context.LoggerFactory.CreateLogger($"PipeWire.NET.Device.{factoryName}"));
+ context,
+ factoryName,
+ context.LoggerFactory.CreateLogger($"PipeWire.NET.Device.{factoryName}")
+ );
pw_proxy* exported = SpaFactoryExport.Load(
context,
@@ -183,9 +198,14 @@ public static PipeWireDeviceProvider FromSpaFactory(
"device",
properties,
libraryName,
- out provider._spaHandle);
-
- provider._exported = new PipeWireProxyHandle(exported, context.LoopOwner, context.CoreOwner!);
+ out provider._spaHandle
+ );
+
+ provider._exported = new PipeWireProxyHandle(
+ exported,
+ context.LoopOwner,
+ context.CoreOwner!
+ );
provider.LogExported(factoryName);
return provider;
}
@@ -213,17 +233,19 @@ private void Publish()
_paramInfoCount = (uint)_params.Count;
if (_paramInfoCount > 0)
{
- _paramInfo = (spa_param_info*)NativeMemory.AllocZeroed(
- (nuint)(sizeof(spa_param_info) * (int)_paramInfoCount));
+ _paramInfo = (spa_param_info*)
+ NativeMemory.AllocZeroed((nuint)(sizeof(spa_param_info) * (int)_paramInfoCount));
int i = 0;
foreach (SpaParamType id in _params.Keys)
{
_paramInfo[i].id = (uint)id;
// Readable, and writable for the two a caller is expected to change.
- _paramInfo[i].flags = (uint)(id is SpaParamType.Profile or SpaParamType.Route
- ? SpaParamInfoFlags.Serial | SpaParamInfoFlags.ReadWrite
- : SpaParamInfoFlags.Serial | SpaParamInfoFlags.Read);
+ _paramInfo[i].flags = (uint)(
+ id is SpaParamType.Profile or SpaParamType.Route
+ ? SpaParamInfoFlags.Serial | SpaParamInfoFlags.ReadWrite
+ : SpaParamInfoFlags.Serial | SpaParamInfoFlags.Read
+ );
i++;
}
}
@@ -249,14 +271,14 @@ private void Publish()
var dict = new SpaDictBuilder(scratch, items);
foreach (KeyValuePair pair in _properties)
{
- if (dict.Count == items.Length) break;
+ if (dict.Count == items.Length)
+ break;
dict.Add(pair.Key, pair.Value);
}
spa_dict native = dict.Build();
- exported = Native.pw_core_export(
- _ctx.CoreHandle, _iface->type, &native, _iface, 0);
+ exported = Native.pw_core_export(_ctx.CoreHandle, _iface->type, &native, _iface, 0);
}
if (exported is null)
@@ -265,9 +287,9 @@ private void Publish()
throw new PipeWireInteropException(
"pw_core_export",
-38,
- daemonMessage:
- "the context has no export type for Device. libpipewire-module-client-device "
- + "registers it, and client.conf loads it unless module.client-device is off.");
+ daemonMessage: "the context has no export type for Device. libpipewire-module-client-device "
+ + "registers it, and client.conf loads it unless module.client-device is off."
+ );
}
_exported = new PipeWireProxyHandle(exported, _ctx.LoopOwner, _ctx.CoreOwner!);
@@ -325,7 +347,8 @@ public void SetParameter(SpaParamType parameter, ImmutableArray value
{
throw new InvalidOperationException(
$"'{_name}' is served by a SPA plugin, which answers its own parameters; "
- + "only a device built with Create has parameters this process can set.");
+ + "only a device built with Create has parameters this process can set."
+ );
}
_params = _params.SetItem(parameter, values);
@@ -334,7 +357,8 @@ public void SetParameter(SpaParamType parameter, ImmutableArray value
// loop thread reads while emitting, so toggling outside the lock races the read.
using (_ctx.Lock())
{
- if (_disposed || _listeners is null) return;
+ if (_disposed || _listeners is null)
+ return;
NoteParamsChanged(parameter);
EmitInfoLocked();
}
@@ -377,7 +401,8 @@ private void EmitInfoLocked()
var dict = new SpaDictBuilder(scratch, items);
foreach (KeyValuePair pair in _properties)
{
- if (dict.Count == items.Length) break;
+ if (dict.Count == items.Length)
+ break;
dict.Add(pair.Key, pair.Value);
}
@@ -390,13 +415,12 @@ private void EmitInfoLocked()
info.@params = _paramInfo;
info.n_params = _paramInfoCount;
- for (spa_list* node = _listeners->list.next;
- node != &_listeners->list;
- node = node->next)
+ for (spa_list* node = _listeners->list.next; node != &_listeners->list; node = node->next)
{
var hook = (spa_hook*)node;
var events = (spa_device_events*)hook->cb.funcs;
- if (events is null || events->info is null) continue;
+ if (events is null || events->info is null)
+ continue;
events->info(hook->cb.data, &info);
}
@@ -404,10 +428,15 @@ private void EmitInfoLocked()
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static int OnAddListener(
- void* obj, spa_hook* listener, spa_device_events* events, void* data)
+ void* obj,
+ spa_hook* listener,
+ spa_device_events* events,
+ void* data
+ )
{
PipeWireDeviceProvider? self = FromData(obj);
- if (self is null || listener is null) return -NativeLibc.EINVAL;
+ if (self is null || listener is null)
+ return -NativeLibc.EINVAL;
try
{
@@ -440,7 +469,8 @@ private static int OnAddListener(
private static int OnSync(void* obj, int seq)
{
PipeWireDeviceProvider? self = FromData(obj);
- if (self is null) return -NativeLibc.EINVAL;
+ if (self is null)
+ return -NativeLibc.EINVAL;
try
{
@@ -460,10 +490,17 @@ private static int OnSync(void* obj, int seq)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static int OnEnumParams(
- void* obj, int seq, uint id, uint start, uint num, spa_pod* filter)
+ void* obj,
+ int seq,
+ uint id,
+ uint start,
+ uint num,
+ spa_pod* filter
+ )
{
PipeWireDeviceProvider? self = FromData(obj);
- if (self is null) return -NativeLibc.EINVAL;
+ if (self is null)
+ return -NativeLibc.EINVAL;
try
{
@@ -493,7 +530,12 @@ private static int OnEnumParams(
result.next = index + 1;
result.param = (spa_pod*)p;
- self.EmitResult(seq, 0, (uint)NativeConstants.SPA_RESULT_TYPE_DEVICE_PARAMS, &result);
+ self.EmitResult(
+ seq,
+ 0,
+ (uint)NativeConstants.SPA_RESULT_TYPE_DEVICE_PARAMS,
+ &result
+ );
}
sent++;
@@ -512,13 +554,15 @@ private static int OnEnumParams(
/// Reads an enumeration filter pod, or null when there is none to apply.
private static unsafe SpaObject? ParseFilter(spa_pod* filter)
{
- if (filter is null) return null;
+ if (filter is null)
+ return null;
// The declared size is the caller's word, capped before a span is built over it the same
// way every other length off the wire is. An unparseable filter is ignored rather than
// failing the enumeration: the caller still gets the unfiltered set it would have had.
uint size = filter->size;
- if (size > MaxParamBytes) return null;
+ if (size > MaxParamBytes)
+ return null;
var bytes = new ReadOnlySpan(filter, (int)size + 8);
return SpaPod.TryParse(bytes, out SpaValue? parsed) && parsed is SpaObject o ? o : null;
@@ -538,7 +582,8 @@ private static bool MatchesFilter(SpaObject candidate, SpaObject filter) =>
private static int OnSetParam(void* obj, uint id, uint flags, spa_pod* param)
{
PipeWireDeviceProvider? self = FromData(obj);
- if (self is null) return -NativeLibc.EINVAL;
+ if (self is null)
+ return -NativeLibc.EINVAL;
try
{
@@ -597,7 +642,8 @@ private void RaiseParameterWritten(SpaParamType id, SpaObject? value) =>
ParameterWritten,
(Provider: this, Id: id, Value: value),
static (h, s) => h(s.Provider, s.Id, s.Value),
- static (s, ex) => s.Provider.LogCallbackThrew(nameof(ParameterWritten), ex));
+ static (s, ex) => s.Provider.LogCallbackThrew(nameof(ParameterWritten), ex)
+ );
/// How many listeners are attached. Diagnostics only.
private int ListenerCount
@@ -605,20 +651,24 @@ private int ListenerCount
get
{
int n = 0;
- for (spa_list* node = _listeners->list.next; node != &_listeners->list; node = node->next) n++;
+ for (
+ spa_list* node = _listeners->list.next;
+ node != &_listeners->list;
+ node = node->next
+ )
+ n++;
return n;
}
}
private void EmitResult(int seq, int res, uint type, void* result)
{
- for (spa_list* node = _listeners->list.next;
- node != &_listeners->list;
- node = node->next)
+ for (spa_list* node = _listeners->list.next; node != &_listeners->list; node = node->next)
{
var hook = (spa_hook*)node;
var events = (spa_device_events*)hook->cb.funcs;
- if (events is null || events->result is null) continue;
+ if (events is null || events->result is null)
+ continue;
events->result(hook->cb.data, seq, res, type, result);
}
@@ -626,7 +676,8 @@ private void EmitResult(int seq, int res, uint type, void* result)
private static PipeWireDeviceProvider? FromData(void* data)
{
- if (data is null) return null;
+ if (data is null)
+ return null;
try
{
@@ -653,7 +704,8 @@ public ValueTask DisposeAsync()
///
public void Dispose()
{
- if (_disposed) return;
+ if (_disposed)
+ return;
_disposed = true;
// Under the loop lock when it can be taken: the proxy destroy and the native frees below
@@ -687,7 +739,8 @@ private void ReleaseNative()
{
if (_iface is not null)
{
- if (_iface->type is not null) NativeMemory.Free(_iface->type);
+ if (_iface->type is not null)
+ NativeMemory.Free(_iface->type);
NativeMemory.Free(_iface);
_iface = null;
}
@@ -696,33 +749,59 @@ private void ReleaseNative()
// clearing it underneath a live proxy leaves the daemon dispatching through freed memory.
if (_spaHandle is not null)
{
- if (_spaHandle->clear is not null) _ = _spaHandle->clear(_spaHandle);
+ if (_spaHandle->clear is not null)
+ _ = _spaHandle->clear(_spaHandle);
_spaHandle = null;
}
- if (_methods is not null) { NativeMemory.Free(_methods); _methods = null; }
- if (_listeners is not null) { NativeMemory.Free(_listeners); _listeners = null; }
- if (_paramInfo is not null) { NativeMemory.Free(_paramInfo); _paramInfo = null; }
+ if (_methods is not null)
+ {
+ NativeMemory.Free(_methods);
+ _methods = null;
+ }
+ if (_listeners is not null)
+ {
+ NativeMemory.Free(_listeners);
+ _listeners = null;
+ }
+ if (_paramInfo is not null)
+ {
+ NativeMemory.Free(_paramInfo);
+ _paramInfo = null;
+ }
- if (_self.IsAllocated) _self.Free();
+ if (_self.IsAllocated)
+ _self.Free();
}
- [LoggerMessage(EventId = 34300, Level = LogLevel.Information,
- Message = "exported device {Name}; other clients can select its profiles")]
+ [LoggerMessage(
+ EventId = 34300,
+ Level = LogLevel.Information,
+ Message = "exported device {Name}; other clients can select its profiles"
+ )]
private partial void LogExported(string name);
- [LoggerMessage(EventId = 34302, Level = LogLevel.Debug,
- Message = "enum_params id={Id} start={Start} num={Num}")]
+ [LoggerMessage(
+ EventId = 34302,
+ Level = LogLevel.Debug,
+ Message = "enum_params id={Id} start={Start} num={Num}"
+ )]
private partial void LogEnumParams(uint id, uint start, uint num);
- [LoggerMessage(EventId = 34304, Level = LogLevel.Debug,
- Message = "enum_params sent {Sent} results to {Listeners} listeners")]
+ [LoggerMessage(
+ EventId = 34304,
+ Level = LogLevel.Debug,
+ Message = "enum_params sent {Sent} results to {Listeners} listeners"
+ )]
private partial void LogEnumSent(uint sent, int listeners);
[LoggerMessage(EventId = 34303, Level = LogLevel.Debug, Message = "a listener attached")]
private partial void LogListenerAttached();
- [LoggerMessage(EventId = 34301, Level = LogLevel.Error,
- Message = "a device provider callback ({Callback}) threw")]
+ [LoggerMessage(
+ EventId = 34301,
+ Level = LogLevel.Error,
+ Message = "a device provider callback ({Callback}) threw"
+ )]
private partial void LogCallbackThrew(string callback, Exception exception);
}
diff --git a/src/PipeWire.NET/Graph/PipeWireDeviceProxy.cs b/src/PipeWire.NET/Graph/PipeWireDeviceProxy.cs
index d059634..2bf5c27 100644
--- a/src/PipeWire.NET/Graph/PipeWireDeviceProxy.cs
+++ b/src/PipeWire.NET/Graph/PipeWireDeviceProxy.cs
@@ -31,28 +31,49 @@ private PipeWireDeviceProxy(PipeWireContext ctx, uint id, ILogger logger)
: base(ctx, id) => _logger = logger;
internal static unsafe PipeWireDeviceProxy Bind(
- PipeWireContext ctx, pw_registry* registry, uint id, uint version, ILogger logger,
- Action? propertiesObserved = null)
+ PipeWireContext ctx,
+ pw_registry* registry,
+ uint id,
+ uint version,
+ ILogger logger,
+ Action? propertiesObserved = null
+ )
{
// The observer is in place before the proxy is bound: the first info after a bind is the
// only one that carries the object's properties (later ones set no PROPS in their change
// mask, so their dictionary arrives empty), and it can arrive the moment the bind is sent.
// Assigned after Bind returned, a fast daemon's first info found no observer and the
// properties never reached the registry.
- var control = new PipeWireDeviceProxy(ctx, id, logger) { PropertiesObserved = propertiesObserved };
- control.Attach(BoundProxy.Bind(
- ctx, registry, id, PipeWireKeys.PW_TYPE_INTERFACE_Device, version, NativeConstants.PW_VERSION_DEVICE,
- sizeof(pw_device_events),
- events =>
- {
- var table = (pw_device_events*)events;
- table->version = NativeConstants.PW_VERSION_DEVICE_EVENTS;
- table->info = &OnInfoCallback;
- table->param = &OnParamCallback;
- },
- static (proxy, hook, events, data) => Native.pw_device_add_listener(
- (pw_device*)proxy, (spa_hook*)hook, (pw_device_events*)events, (void*)data),
- control));
+ var control = new PipeWireDeviceProxy(ctx, id, logger)
+ {
+ PropertiesObserved = propertiesObserved,
+ };
+ control.Attach(
+ BoundProxy.Bind(
+ ctx,
+ registry,
+ id,
+ PipeWireKeys.PW_TYPE_INTERFACE_Device,
+ version,
+ NativeConstants.PW_VERSION_DEVICE,
+ sizeof(pw_device_events),
+ events =>
+ {
+ var table = (pw_device_events*)events;
+ table->version = NativeConstants.PW_VERSION_DEVICE_EVENTS;
+ table->info = &OnInfoCallback;
+ table->param = &OnParamCallback;
+ },
+ static (proxy, hook, events, data) =>
+ Native.pw_device_add_listener(
+ (pw_device*)proxy,
+ (spa_hook*)hook,
+ (pw_device_events*)events,
+ (void*)data
+ ),
+ control
+ )
+ );
return control;
}
@@ -60,8 +81,8 @@ internal static unsafe PipeWireDeviceProxy Bind(
/// Every profile this device offers, such as the configurations of a sound card.
/// Abandons the wait.
public Task> EnumerateProfilesAsync(
- CancellationToken cancellationToken = default) =>
- EnumerateParametersAsync(SpaParamType.EnumProfile, cancellationToken);
+ CancellationToken cancellationToken = default
+ ) => EnumerateParametersAsync(SpaParamType.EnumProfile, cancellationToken);
/// The profile the device is currently using, or if it has none.
/// Abandons the wait.
@@ -95,27 +116,34 @@ public Task> EnumerateProfilesAsync(
///
///
public Task SetProfileAsync(
- int index, bool save = false, CancellationToken cancellationToken = default) =>
+ int index,
+ bool save = false,
+ CancellationToken cancellationToken = default
+ ) =>
SetParameterAsync(
SpaParamType.Profile,
- new SpaObject(SpaType.ObjectParamProfile, SpaParamType.Profile,
- [
- new SpaPodProperty(SpaParamProfile.Index, 0, new SpaInt(index)),
- new SpaPodProperty(SpaParamProfile.Save, 0, new SpaBool(save)),
- ]),
- cancellationToken);
+ new SpaObject(
+ SpaType.ObjectParamProfile,
+ SpaParamType.Profile,
+ [
+ new SpaPodProperty(SpaParamProfile.Index, 0, new SpaInt(index)),
+ new SpaPodProperty(SpaParamProfile.Save, 0, new SpaBool(save)),
+ ]
+ ),
+ cancellationToken
+ );
/// Every route the device offers: its jacks, speakers and microphones.
/// Abandons the wait.
public Task> EnumerateRoutesAsync(
- CancellationToken cancellationToken = default) =>
- EnumerateParametersAsync(SpaParamType.EnumRoute, cancellationToken);
+ CancellationToken cancellationToken = default
+ ) => EnumerateParametersAsync(SpaParamType.EnumRoute, cancellationToken);
/// The routes currently in use, one per direction the profile provides.
/// Abandons the wait.
public Task> GetActiveRoutesAsync(
- CancellationToken cancellationToken = default) =>
- EnumerateParametersAsync(SpaParamType.Route, cancellationToken);
+ CancellationToken cancellationToken = default
+ ) => EnumerateParametersAsync(SpaParamType.Route, cancellationToken);
///
/// Selects a route for one of the device's ports.
@@ -133,17 +161,24 @@ public Task> GetActiveRoutesAsync(
/// it: the daemon can still apply the change after this throws.
///
public Task SetRouteAsync(
- int routeIndex, int devicePort, bool save = false,
- CancellationToken cancellationToken = default) =>
+ int routeIndex,
+ int devicePort,
+ bool save = false,
+ CancellationToken cancellationToken = default
+ ) =>
SetParameterAsync(
SpaParamType.Route,
- new SpaObject(SpaType.ObjectParamRoute, SpaParamType.Route,
- [
- new SpaPodProperty(SpaParamRoute.Index, 0, new SpaInt(routeIndex)),
- new SpaPodProperty(SpaParamRoute.Device, 0, new SpaInt(devicePort)),
- new SpaPodProperty(SpaParamRoute.Save, 0, new SpaBool(save)),
- ]),
- cancellationToken);
+ new SpaObject(
+ SpaType.ObjectParamRoute,
+ SpaParamType.Route,
+ [
+ new SpaPodProperty(SpaParamRoute.Index, 0, new SpaInt(routeIndex)),
+ new SpaPodProperty(SpaParamRoute.Device, 0, new SpaInt(devicePort)),
+ new SpaPodProperty(SpaParamRoute.Save, 0, new SpaBool(save)),
+ ]
+ ),
+ cancellationToken
+ );
///
/// Sets the hardware volume and mute of a route.
@@ -176,49 +211,78 @@ public Task SetRouteVolumeAsync(
ReadOnlySpan channelVolumes,
bool muted,
bool save = false,
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
if (channelVolumes.IsEmpty)
- throw new ArgumentException("at least one channel volume is required.", nameof(channelVolumes));
+ throw new ArgumentException(
+ "at least one channel volume is required.",
+ nameof(channelVolumes)
+ );
var values = ImmutableArray.CreateBuilder(channelVolumes.Length);
foreach (float volume in channelVolumes)
{
if (float.IsNegative(volume) || float.IsNaN(volume))
- throw new ArgumentException("a channel volume must be a non-negative number.", nameof(channelVolumes));
+ throw new ArgumentException(
+ "a channel volume must be a non-negative number.",
+ nameof(channelVolumes)
+ );
values.Add(new SpaFloat(volume));
}
// The volume lives in a Props object nested inside the Route, because a route describes both
// which jack is selected and what its mixer is set to.
- var props = new SpaObject(SpaType.ObjectProps, SpaParamType.Props,
- [
- new SpaPodProperty(SpaProp.Mute, 0, new SpaBool(muted)),
- new SpaPodProperty(SpaProp.ChannelVolumes, 0,
- new SpaArray(SpaType.Float, values.MoveToImmutable())),
- ]);
+ var props = new SpaObject(
+ SpaType.ObjectProps,
+ SpaParamType.Props,
+ [
+ new SpaPodProperty(SpaProp.Mute, 0, new SpaBool(muted)),
+ new SpaPodProperty(
+ SpaProp.ChannelVolumes,
+ 0,
+ new SpaArray(SpaType.Float, values.MoveToImmutable())
+ ),
+ ]
+ );
return SetParameterAsync(
SpaParamType.Route,
- new SpaObject(SpaType.ObjectParamRoute, SpaParamType.Route,
- [
- new SpaPodProperty(SpaParamRoute.Index, 0, new SpaInt(routeIndex)),
- new SpaPodProperty(SpaParamRoute.Device, 0, new SpaInt(devicePort)),
- new SpaPodProperty(SpaParamRoute.Props, 0, props),
- new SpaPodProperty(SpaParamRoute.Save, 0, new SpaBool(save)),
- ]),
- cancellationToken);
+ new SpaObject(
+ SpaType.ObjectParamRoute,
+ SpaParamType.Route,
+ [
+ new SpaPodProperty(SpaParamRoute.Index, 0, new SpaInt(routeIndex)),
+ new SpaPodProperty(SpaParamRoute.Device, 0, new SpaInt(devicePort)),
+ new SpaPodProperty(SpaParamRoute.Props, 0, props),
+ new SpaPodProperty(SpaParamRoute.Save, 0, new SpaBool(save)),
+ ]
+ ),
+ cancellationToken
+ );
}
private protected override unsafe int EnumParamsNative(
- void* proxy, int seq, uint id, uint start, uint num, spa_pod* filter) =>
- Native.pw_device_enum_params((pw_device*)proxy, seq, id, start, num, filter);
+ void* proxy,
+ int seq,
+ uint id,
+ uint start,
+ uint num,
+ spa_pod* filter
+ ) => Native.pw_device_enum_params((pw_device*)proxy, seq, id, start, num, filter);
- private protected override unsafe int SetParamNative(void* proxy, uint id, uint flags, spa_pod* param) =>
- Native.pw_device_set_param((pw_device*)proxy, id, flags, param);
+ private protected override unsafe int SetParamNative(
+ void* proxy,
+ uint id,
+ uint flags,
+ spa_pod* param
+ ) => Native.pw_device_set_param((pw_device*)proxy, id, flags, param);
- private protected override unsafe int SubscribeParamsNative(void* proxy, uint* ids, uint count) =>
- Native.pw_device_subscribe_params((pw_device*)proxy, ids, count);
+ private protected override unsafe int SubscribeParamsNative(
+ void* proxy,
+ uint* ids,
+ uint count
+ ) => Native.pw_device_subscribe_params((pw_device*)proxy, ids, count);
private protected override void OnHandlerFaulted(Exception exception) =>
LogHandlerFaulted(Id, exception);
@@ -244,7 +308,13 @@ private static unsafe void OnInfoCallback(void* data, pw_device_info* info)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe void OnParamCallback(
- void* data, int seq, uint id, uint index, uint next, spa_pod* param)
+ void* data,
+ int seq,
+ uint id,
+ uint index,
+ uint next,
+ spa_pod* param
+ )
{
try
{
@@ -256,7 +326,10 @@ private static unsafe void OnParamCallback(
}
}
- [LoggerMessage(EventId = 33100, Level = LogLevel.Error,
- Message = "a ParameterChanged handler for device {DeviceId} threw")]
+ [LoggerMessage(
+ EventId = 33100,
+ Level = LogLevel.Error,
+ Message = "a ParameterChanged handler for device {DeviceId} threw"
+ )]
private partial void LogHandlerFaulted(uint deviceId, Exception exception);
}
diff --git a/src/PipeWire.NET/Graph/PipeWireExportedFormat.cs b/src/PipeWire.NET/Graph/PipeWireExportedFormat.cs
index 6ced05c..0423f0d 100644
--- a/src/PipeWire.NET/Graph/PipeWireExportedFormat.cs
+++ b/src/PipeWire.NET/Graph/PipeWireExportedFormat.cs
@@ -16,7 +16,12 @@ namespace PipeWire.NET.Graph;
[SupportedOSPlatform("linux")]
public sealed class PipeWireExportedFormat
{
- private PipeWireExportedFormat(int rate, int channels, SpaAudioFormat sampleFormat, int bytesPerSample)
+ private PipeWireExportedFormat(
+ int rate,
+ int channels,
+ SpaAudioFormat sampleFormat,
+ int bytesPerSample
+ )
{
Rate = rate;
Channels = channels;
@@ -95,7 +100,12 @@ internal int WriteFormat(Span destination, bool asEnum)
//
// The offered values lead each choice, so they stay the preferred answer, and whatever
// the peer settles on comes back through port_set_param and is recorded there.
- b.AddChoiceEnum(SpaFormat.AudioFormat, SampleFormat, SpaAudioFormat.F32Le, SpaAudioFormat.S16Le);
+ b.AddChoiceEnum(
+ SpaFormat.AudioFormat,
+ SampleFormat,
+ SpaAudioFormat.F32Le,
+ SpaAudioFormat.S16Le
+ );
b.AddChoiceRangeInt(SpaFormat.AudioRate, Rate, 1, MaxRate);
b.AddChoiceRangeInt(SpaFormat.AudioChannels, Channels, 1, MaxChannels);
}
@@ -136,16 +146,26 @@ internal int WriteFormat(Span destination, bool asEnum)
///
internal static PipeWireExportedFormat? FromPod(ReadOnlySpan pod)
{
- if (!SpaPod.TryParse(pod, out SpaValue? value) || value is not SpaObject format) return null;
-
- if (Fixed(format, SpaFormat.MediaType) is not SpaId mediaType
- || mediaType.Value != (uint)SpaMediaType.Audio) return null;
- if (Fixed(format, SpaFormat.MediaSubtype) is not SpaId subtype
- || subtype.Value != (uint)SpaMediaSubtype.Raw) return null;
-
- if (Fixed(format, SpaFormat.AudioFormat) is not SpaId sampleFormat) return null;
- if (Fixed(format, SpaFormat.AudioRate) is not SpaInt rate) return null;
- if (Fixed(format, SpaFormat.AudioChannels) is not SpaInt channels) return null;
+ if (!SpaPod.TryParse(pod, out SpaValue? value) || value is not SpaObject format)
+ return null;
+
+ if (
+ Fixed(format, SpaFormat.MediaType) is not SpaId mediaType
+ || mediaType.Value != (uint)SpaMediaType.Audio
+ )
+ return null;
+ if (
+ Fixed(format, SpaFormat.MediaSubtype) is not SpaId subtype
+ || subtype.Value != (uint)SpaMediaSubtype.Raw
+ )
+ return null;
+
+ if (Fixed(format, SpaFormat.AudioFormat) is not SpaId sampleFormat)
+ return null;
+ if (Fixed(format, SpaFormat.AudioRate) is not SpaInt rate)
+ return null;
+ if (Fixed(format, SpaFormat.AudioChannels) is not SpaInt channels)
+ return null;
// Only what WriteFormat offers. Anything else is a format this node never said it could make.
var settled = (SpaAudioFormat)sampleFormat.Value;
@@ -156,17 +176,20 @@ internal int WriteFormat(Span destination, bool asEnum)
_ => 0,
};
- if (bytesPerSample == 0) return null;
- if (rate.Value is < 1 or > MaxRate || channels.Value is < 1 or > MaxChannels) return null;
+ if (bytesPerSample == 0)
+ return null;
+ if (rate.Value is < 1 or > MaxRate || channels.Value is < 1 or > MaxChannels)
+ return null;
return new PipeWireExportedFormat(rate.Value, channels.Value, settled, bytesPerSample);
- static SpaValue? Fixed(SpaObject o, SpaKey key) => o.Find(key)?.Value switch
- {
- SpaChoice { Kind: SpaChoiceType.None } choice when !choice.Alternatives.IsDefaultOrEmpty
- => choice.Alternatives[0],
- var v => v,
- };
+ static SpaValue? Fixed(SpaObject o, SpaKey key) =>
+ o.Find(key)?.Value switch
+ {
+ SpaChoice { Kind: SpaChoiceType.None } choice
+ when !choice.Alternatives.IsDefaultOrEmpty => choice.Alternatives[0],
+ var v => v,
+ };
}
///
@@ -218,7 +241,12 @@ internal int WriteBuffers(Span destination)
b.PushObject(SpaType.ObjectParamBuffers, SpaParamType.Buffers);
b.AddChoiceRangeInt(SpaParamBuffers.Buffers, 2, 1, 32);
b.AddInt(SpaParamBuffers.Blocks, 1);
- b.AddChoiceRangeInt(SpaParamBuffers.Size, quantum * BytesPerFrame, BytesPerFrame, int.MaxValue);
+ b.AddChoiceRangeInt(
+ SpaParamBuffers.Size,
+ quantum * BytesPerFrame,
+ BytesPerFrame,
+ int.MaxValue
+ );
b.AddInt(SpaParamBuffers.Stride, BytesPerFrame);
b.Pop();
diff --git a/src/PipeWire.NET/Graph/PipeWireFactory.cs b/src/PipeWire.NET/Graph/PipeWireFactory.cs
index c37c2b6..9868291 100644
--- a/src/PipeWire.NET/Graph/PipeWireFactory.cs
+++ b/src/PipeWire.NET/Graph/PipeWireFactory.cs
@@ -20,7 +20,8 @@ internal PipeWireFactory(
string? TypeName,
uint? TypeVersion,
uint? ModuleId,
- PipeWireProperties? Properties = null)
+ PipeWireProperties? Properties = null
+ )
{
this.Properties = Properties ?? PipeWireProperties.Empty;
this.ObjectSerial = this.Properties.Serial;
diff --git a/src/PipeWire.NET/Graph/PipeWireFilter.cs b/src/PipeWire.NET/Graph/PipeWireFilter.cs
index 189641e..c8ae53f 100644
--- a/src/PipeWire.NET/Graph/PipeWireFilter.cs
+++ b/src/PipeWire.NET/Graph/PipeWireFilter.cs
@@ -104,7 +104,11 @@ private PipeWireFilter(PipeWireContext ctx, string name, ILogger logger)
/// A struct rather than an object, and a single delegate rather than an event, for the same
/// reason: neither may allocate on this thread.
///
- public delegate void ProcessHandler(PipeWireFilter filter, uint sampleCount, in PipeWireGraphClock clock);
+ public delegate void ProcessHandler(
+ PipeWireFilter filter,
+ uint sampleCount,
+ in PipeWireGraphClock clock
+ );
private PipeWireVideoCycle _videoCycle;
@@ -120,9 +124,13 @@ private PipeWireFilter(PipeWireContext ctx, string name, ILogger logger)
///
public PipeWireVideoCycle VideoCycle => _videoCycle;
-
/// Raised when the filter changes state, on the loop thread.
- public event Action? StateChanged;
+ public event Action<
+ PipeWireFilter,
+ PipeWireFilterState,
+ PipeWireFilterState,
+ string?
+ >? StateChanged;
/// The node id the filter was given, or before it has one.
///
@@ -141,7 +149,8 @@ public unsafe uint? NodeId
{
get
{
- if (_disposed || _handle is null || !_connected) return null;
+ if (_disposed || _handle is null || !_connected)
+ return null;
// Under the loop lock, like the stream's equivalent: pw_filter_* is called with the
// loop held, and without it this reads a field the loop thread is free to be writing
@@ -149,7 +158,8 @@ public unsafe uint? NodeId
uint id;
using (_ctx.Lock())
{
- if (_disposed || _handle is null) return null;
+ if (_disposed || _handle is null)
+ return null;
id = Native.pw_filter_get_node_id(_handle.Filter);
}
@@ -177,23 +187,29 @@ public Task WaitForNodeIdAsync(CancellationToken cancellationToken = defau
throw new InvalidOperationException("the filter is not connected.");
// Fast path: the id is often already there.
- if (NodeId is { } ready) return Task.FromResult(ready);
+ if (NodeId is { } ready)
+ return Task.FromResult(ready);
cancellationToken.ThrowIfCancellationRequested();
// Event-driven, not polled. The id arrives with the node's binding while state changes
// mark the progress around it, so every transition re-reads the live id rather than
// trusting any one event to carry it. Continuations run off the loop thread: completing
// inline would run a stranger's continuation with the native lock held.
- var waiter = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var waiter = new TaskCompletionSource(
+ TaskCreationOptions.RunContinuationsAsynchronously
+ );
Action? handler = null;
handler = (_, _, _, _) =>
{
- if (NodeId is { } id) waiter.TrySetResult(id);
+ if (NodeId is { } id)
+ waiter.TrySetResult(id);
};
CancellationTokenRegistration registration = cancellationToken.Register(
- static s => ((TaskCompletionSource)s!).TrySetCanceled(), waiter);
+ static s => ((TaskCompletionSource)s!).TrySetCanceled(),
+ waiter
+ );
StateChanged += handler;
_ = waiter.Task.ContinueWith(
@@ -204,7 +220,8 @@ public Task WaitForNodeIdAsync(CancellationToken cancellationToken = defau
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
- TaskScheduler.Default);
+ TaskScheduler.Default
+ );
// Re-check under subscription: the id may have arrived between the fast path and here.
if (NodeId is { } arrived)
@@ -217,18 +234,25 @@ public Task WaitForNodeIdAsync(CancellationToken cancellationToken = defau
// including assigning the id with no further state change to announce it - has been
// dispatched by the time this answers. The recheck then either finishes or waits for the
// next real transition; the caller's token is what ends a wait for an id that never comes.
- _ = CoreSync.RoundTripAsync(_ctx, cancellationToken).ContinueWith(
- t =>
- {
- if (t.IsFaulted)
- waiter.TrySetException(t.Exception?.InnerException
- ?? new InvalidOperationException("the node-id wait ended with its barrier."));
- else if (NodeId is { } id)
- waiter.TrySetResult(id);
- },
- CancellationToken.None,
- TaskContinuationOptions.ExecuteSynchronously,
- TaskScheduler.Default);
+ _ = CoreSync
+ .RoundTripAsync(_ctx, cancellationToken)
+ .ContinueWith(
+ t =>
+ {
+ if (t.IsFaulted)
+ waiter.TrySetException(
+ t.Exception?.InnerException
+ ?? new InvalidOperationException(
+ "the node-id wait ended with its barrier."
+ )
+ );
+ else if (NodeId is { } id)
+ waiter.TrySetResult(id);
+ },
+ CancellationToken.None,
+ TaskContinuationOptions.ExecuteSynchronously,
+ TaskScheduler.Default
+ );
return waiter.Task;
}
@@ -240,9 +264,11 @@ public unsafe PipeWireFilterState State
{
// Under the loop lock, like every other call into the filter: the state is read from
// structures the loop thread mutates as the filter transitions.
- if (_disposed || _handle is null || _handle.IsInvalid) return PipeWireFilterState.Unconnected;
+ if (_disposed || _handle is null || _handle.IsInvalid)
+ return PipeWireFilterState.Unconnected;
- if (!_ctx.TryLock(out PipeWireContext.LoopLock scope)) return PipeWireFilterState.Unconnected;
+ if (!_ctx.TryLock(out PipeWireContext.LoopLock scope))
+ return PipeWireFilterState.Unconnected;
using (scope)
{
@@ -264,12 +290,17 @@ public unsafe PipeWireFilterState State
public static unsafe PipeWireFilter Create(
PipeWireContext context,
string name,
- IReadOnlyDictionary? properties = null)
+ IReadOnlyDictionary? properties = null
+ )
{
ArgumentNullException.ThrowIfNull(context);
ArgumentException.ThrowIfNullOrEmpty(name);
- var filter = new PipeWireFilter(context, name, context.LoggerFactory.CreateLogger());
+ var filter = new PipeWireFilter(
+ context,
+ name,
+ context.LoggerFactory.CreateLogger()
+ );
try
{
using (context.Lock())
@@ -278,7 +309,8 @@ public static unsafe PipeWireFilter Create(
if (core is null)
{
throw new InvalidOperationException(
- "the context is not connected; call StartAsync before creating a filter.");
+ "the context is not connected; call StartAsync before creating a filter."
+ );
}
pw_filter* native;
@@ -292,7 +324,11 @@ public static unsafe PipeWireFilter Create(
try
{
- filter._handle = new PipeWireFilterHandle(native, context.LoopOwner, context.CoreOwner);
+ filter._handle = new PipeWireFilterHandle(
+ native,
+ context.LoopOwner,
+ context.CoreOwner
+ );
}
catch
{
@@ -303,7 +339,8 @@ public static unsafe PipeWireFilter Create(
throw;
}
- filter._events = (pw_filter_events*)NativeMemory.AllocZeroed((nuint)sizeof(pw_filter_events));
+ filter._events = (pw_filter_events*)
+ NativeMemory.AllocZeroed((nuint)sizeof(pw_filter_events));
filter._events->version = NativeConstants.PW_VERSION_FILTER_EVENTS;
filter._events->process = &OnProcessCallback;
filter._events->state_changed = &OnStateChangedCallback;
@@ -313,15 +350,19 @@ public static unsafe PipeWireFilter Create(
filter._hook = (spa_hook*)NativeMemory.AllocZeroed((nuint)sizeof(spa_hook));
// Weak: a strong self-handle roots the filter for the life of the process, so one dropped
- // without disposal leaks the native filter too.
- filter._self = GCHandle.Alloc(filter, GCHandleType.Weak);
+ // without disposal leaks the native filter too.
+ filter._self = GCHandle.Alloc(filter, GCHandleType.Weak);
// Handed over before the listener is attached, so a failure below still frees it and
// the free happens after pw_filter_destroy rather than racing it.
filter._handle!.OwnListener(filter._events, filter._hook, filter._self);
Native.pw_filter_add_listener(
- native, filter._hook, filter._events, (void*)GCHandle.ToIntPtr(filter._self));
+ native,
+ filter._hook,
+ filter._events,
+ (void*)GCHandle.ToIntPtr(filter._self)
+ );
}
return filter;
@@ -364,18 +405,26 @@ private static unsafe void OnCommandCallback(void* data, spa_command* command)
// An exception escaping a reverse P/Invoke aborts the process, so nothing here may throw.
try
{
- if (command is null) return;
- if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireFilter self) return;
- if (self._disposed) return;
+ if (command is null)
+ return;
+ if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireFilter self)
+ return;
+ if (self._disposed)
+ return;
// SPA_COMMAND_ID: the id only names a node command when the body says it is one, and a
// command of another type reuses the same numbers for different things.
- if (command->body.body.type != (uint)SpaType.CommandNode) return;
+ if (command->body.body.type != (uint)SpaType.CommandNode)
+ return;
Action? handler = self.CommandReceived;
- if (handler is null) return;
+ if (handler is null)
+ return;
- try { handler(self, (SpaNodeCommand)command->body.body.id); }
+ try
+ {
+ handler(self, (SpaNodeCommand)command->body.body.id);
+ }
catch (Exception ex)
{
Interlocked.Increment(ref self._processFaults);
@@ -399,7 +448,8 @@ public unsafe bool IsDriving
get
{
ObjectDisposedException.ThrowIf(_disposed, this);
- if (_handle is null || !_connected) return false;
+ if (_handle is null || !_connected)
+ return false;
using (_ctx.Lock())
return Native.pw_filter_is_driving(_handle.Filter);
@@ -427,7 +477,8 @@ public unsafe void RemovePort(PipeWireFilterPort port)
using (_ctx.Lock())
{
int res = Native.pw_filter_remove_port(port.PortData);
- if (res < 0) throw new PipeWireException("pw_filter_remove_port", res);
+ if (res < 0)
+ throw new PipeWireException("pw_filter_remove_port", res);
}
_ports.Remove(port);
@@ -447,11 +498,14 @@ public unsafe void RemovePort(PipeWireFilterPort port)
/// is null.
/// The daemon refused the update.
public unsafe int UpdateProperties(
- IReadOnlyDictionary properties, PipeWireFilterPort? port = null)
+ IReadOnlyDictionary properties,
+ PipeWireFilterPort? port = null
+ )
{
ObjectDisposedException.ThrowIf(_disposed, this);
ArgumentNullException.ThrowIfNull(properties);
- if (properties.Count == 0 || _handle is null) return 0;
+ if (properties.Count == 0 || _handle is null)
+ return 0;
// Sized from the input rather than a fixed scratch: a long value would otherwise lose its
// tail. UTF-8 is at most 4 bytes per char, plus a terminator for each key and value.
@@ -463,14 +517,16 @@ public unsafe int UpdateProperties(
spa_dict_item[] items = new spa_dict_item[properties.Count];
var builder = new SpaDictBuilder(scratch, items);
- foreach (KeyValuePair kv in properties) builder.Add(kv.Key, kv.Value);
+ foreach (KeyValuePair kv in properties)
+ builder.Add(kv.Key, kv.Value);
spa_dict native = builder.Build();
using (_ctx.Lock())
{
int res = Native.pw_filter_update_properties(_handle.Filter, port?.PortData, &native);
- if (res < 0) throw new PipeWireException("pw_filter_update_properties", res);
+ if (res < 0)
+ throw new PipeWireException("pw_filter_update_properties", res);
return res;
}
}
@@ -491,7 +547,9 @@ public unsafe void SetError(int result, string message)
ObjectDisposedException.ThrowIf(_disposed, this);
ArgumentNullException.ThrowIfNull(message);
if (_handle is null || !_connected)
- throw new InvalidOperationException("connect the filter before reporting an error on it.");
+ throw new InvalidOperationException(
+ "connect the filter before reporting an error on it."
+ );
using (_ctx.Lock())
{
@@ -519,10 +577,12 @@ public unsafe void TriggerProcess()
// whichever node actually drives, and a node that does not implement RequestProcess
// answers ENOTSUP and the daemon logs it - once per call, so a caller triggering at
// frame rate fills the log and changes nothing.
- if (!Native.pw_filter_is_driving(_handle.Filter)) return;
+ if (!Native.pw_filter_is_driving(_handle.Filter))
+ return;
int rc = Native.pw_filter_trigger_process(_handle.Filter);
- if (rc < 0) throw new PipeWireInteropException("pw_filter_trigger_process", rc);
+ if (rc < 0)
+ throw new PipeWireInteropException("pw_filter_trigger_process", rc);
}
}
@@ -544,7 +604,8 @@ public unsafe void SetActive(bool active)
using (_ctx.Lock())
{
int rc = Native.pw_filter_set_active(_handle.Filter, active);
- if (rc < 0) throw new PipeWireInteropException("pw_filter_set_active", rc);
+ if (rc < 0)
+ throw new PipeWireInteropException("pw_filter_set_active", rc);
}
}
@@ -636,20 +697,25 @@ public unsafe PipeWireFilterPort AddPort(
PipeWirePortDirection direction,
string name,
PipeWireDspFormat format,
- IReadOnlyDictionary? properties = null)
+ IReadOnlyDictionary? properties = null
+ )
{
ObjectDisposedException.ThrowIf(_disposed, this);
ArgumentException.ThrowIfNullOrEmpty(name);
if (_connected)
- throw new InvalidOperationException("ports must be added before the filter is connected.");
+ throw new InvalidOperationException(
+ "ports must be added before the filter is connected."
+ );
SpaDirection spaDirection = direction switch
{
PipeWirePortDirection.In => SpaDirection.Input,
PipeWirePortDirection.Out => SpaDirection.Output,
_ => throw new ArgumentException(
- "a filter port is an input or an output.", nameof(direction)),
+ "a filter port is an input or an output.",
+ nameof(direction)
+ ),
};
string dsp = format switch
@@ -678,8 +744,14 @@ public unsafe PipeWireFilterPort AddPort(
using (_ctx.Lock())
{
portData = Native.pw_filter_add_port(
- _handle!.Filter, spaDirection, PipeWireFilterPortFlags.MapBuffers,
- 0, BuildProperties(properties2), null, 0);
+ _handle!.Filter,
+ spaDirection,
+ PipeWireFilterPortFlags.MapBuffers,
+ 0,
+ BuildProperties(properties2),
+ null,
+ 0
+ );
}
if (portData is null)
@@ -698,7 +770,8 @@ public unsafe PipeWireFilterPort AddPort(
/// PipeWire refused the connection.
public async Task ConnectAsync(
PipeWireFilterFlags flags = PipeWireFilterFlags.RtProcess,
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
ObjectDisposedException.ThrowIf(_disposed, this);
cancellationToken.ThrowIfCancellationRequested();
@@ -708,7 +781,8 @@ public async Task ConnectAsync(
// also keeps a refusal from being answered before anything is listening for it.
// Marked connected only once the round-trip has actually succeeded: setting it first leaves
// a filter that refused to connect claiming it did.
- await CoreSync.RoundTripAsync(_ctx, () => ConnectNative(flags), cancellationToken)
+ await CoreSync
+ .RoundTripAsync(_ctx, () => ConnectNative(flags), cancellationToken)
.ConfigureAwait(false);
_connected = true;
@@ -721,7 +795,9 @@ private unsafe int ConnectNative(PipeWireFilterFlags flags)
return Native.pw_filter_connect(_handle!.Filter, flags, null, 0);
}
- private static unsafe pw_properties* BuildProperties(IReadOnlyDictionary? properties)
+ private static unsafe pw_properties* BuildProperties(
+ IReadOnlyDictionary? properties
+ )
{
if (properties is null || properties.Count == 0)
return null;
@@ -732,12 +808,14 @@ private unsafe int ConnectNative(PipeWireFilterFlags flags)
// pw_properties_new_dict copies what it is given, so these buffers only have to survive the
// call - but they must not move during it, which is why the heap fallback is pinned.
- Span scratch = bytes <= 512
- ? stackalloc byte[bytes]
- : GC.AllocateUninitializedArray(bytes, pinned: true);
- Span items = properties.Count <= 16
- ? stackalloc spa_dict_item[properties.Count]
- : GC.AllocateArray(properties.Count, pinned: true);
+ Span scratch =
+ bytes <= 512
+ ? stackalloc byte[bytes]
+ : GC.AllocateUninitializedArray(bytes, pinned: true);
+ Span items =
+ properties.Count <= 16
+ ? stackalloc spa_dict_item[properties.Count]
+ : GC.AllocateArray(properties.Count, pinned: true);
var builder = new SpaDictBuilder(scratch, items);
foreach ((string key, string value) in properties)
@@ -755,10 +833,12 @@ private static unsafe void OnProcessCallback(void* data, spa_io_position* positi
try
{
var self = (PipeWireFilter?)GCHandle.FromIntPtr((nint)data).Target;
- if (self is null || self._disposed) return;
+ if (self is null || self._disposed)
+ return;
ProcessHandler? callback = self.ProcessCallback;
- if (callback is null || position is null) return;
+ if (callback is null || position is null)
+ return;
// The whole clock, not just the duration. The position area is the only timing a filter
// is supposed to read - pw_filter_get_time is deprecated in favour of exactly this - so
@@ -766,8 +846,15 @@ private static unsafe void OnProcessCallback(void* data, spa_io_position* positi
// work or line it up against anything else on the graph.
spa_io_clock* c = &position->clock;
PipeWireGraphClock clock = new(
- c->nsec, c->position, c->duration,
- c->rate.num, c->rate.denom, c->delay, c->rate_diff, c->next_nsec);
+ c->nsec,
+ c->position,
+ c->duration,
+ c->rate.num,
+ c->rate.denom,
+ c->delay,
+ c->rate_diff,
+ c->next_nsec
+ );
// The graph's frame geometry for this cycle. A video port has no size of its own, so
// this is the only place a filter can learn how many pixels its buffer holds - and it
@@ -775,8 +862,12 @@ private static unsafe void OnProcessCallback(void* data, spa_io_position* positi
spa_io_video_size* v = &position->video;
self._videoCycle = new PipeWireVideoCycle(
(v->flags & (uint)SpaIoVideoSizeFlags.Valid) != 0,
- v->size.width, v->size.height, v->stride,
- v->framerate.num, v->framerate.denom);
+ v->size.width,
+ v->size.height,
+ v->stride,
+ v->framerate.num,
+ v->framerate.denom
+ );
// Invoked directly rather than through GetInvocationList: walking one allocates, which
// is why this is a single delegate and not an event.
@@ -807,16 +898,26 @@ private static unsafe void OnProcessCallback(void* data, spa_io_position* positi
}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
- private static unsafe void OnIoChangedCallback(void* data, void* portData, uint id, void* area, uint size)
+ private static unsafe void OnIoChangedCallback(
+ void* data,
+ void* portData,
+ uint id,
+ void* area,
+ uint size
+ )
{
try
{
var self = (PipeWireFilter?)GCHandle.FromIntPtr((nint)data).Target;
- if (self is null || self._disposed) return;
+ if (self is null || self._disposed)
+ return;
// The node's position area; port io areas are pw_filter's own business.
if (portData is null && id == (uint)SpaIoType.Position)
- self._position = area is not null && size >= (uint)sizeof(spa_io_position) ? (spa_io_position*)area : null;
+ self._position =
+ area is not null && size >= (uint)sizeof(spa_io_position)
+ ? (spa_io_position*)area
+ : null;
}
catch
{
@@ -825,13 +926,20 @@ private static unsafe void OnIoChangedCallback(void* data, void* portData, uint
}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
- private static unsafe void OnParamChangedCallback(void* data, void* portData, uint id, spa_pod* param)
+ private static unsafe void OnParamChangedCallback(
+ void* data,
+ void* portData,
+ uint id,
+ spa_pod* param
+ )
{
try
{
var self = (PipeWireFilter?)GCHandle.FromIntPtr((nint)data).Target;
- if (self is null || self._disposed || portData is null || param is null) return;
- if (id != (uint)SpaParamType.Format) return;
+ if (self is null || self._disposed || portData is null || param is null)
+ return;
+ if (id != (uint)SpaParamType.Format)
+ return;
PipeWireFilterPort? port = self._ports.Find(p => p.PortData == portData);
if (port is { Format: PipeWireDspFormat.Rgba32FloatVideo })
@@ -841,7 +949,8 @@ private static unsafe void OnParamChangedCallback(void* data, void* portData, ui
{
// A native frame on the loop thread: contained, and the port keeps whatever pool the
// graph gives it, which the pixel accessor refuses rather than overruns.
- if (GCHandle.FromIntPtr((nint)data).Target is PipeWireFilter self) self.LogVideoBuffersFailed(ex);
+ if (GCHandle.FromIntPtr((nint)data).Target is PipeWireFilter self)
+ self.LogVideoBuffersFailed(ex);
}
}
@@ -865,11 +974,15 @@ private static unsafe void OnParamChangedCallback(void* data, void* portData, ui
///
private unsafe void DeclareVideoBuffers(PipeWireFilterPort port)
{
- uint width = DefaultVideoWidth, height = DefaultVideoHeight;
+ uint width = DefaultVideoWidth,
+ height = DefaultVideoHeight;
spa_io_position* position = _position;
- if (position is not null
+ if (
+ position is not null
&& (position->video.flags & (uint)SpaIoVideoSizeFlags.Valid) != 0
- && position->video.size.width > 0 && position->video.size.height > 0)
+ && position->video.size.width > 0
+ && position->video.size.height > 0
+ )
{
width = position->video.size.width;
height = position->video.size.height;
@@ -896,8 +1009,10 @@ private unsafe void DeclareVideoBuffers(PipeWireFilterPort port)
rc = Native.pw_filter_update_params(_handle!.Filter, port.PortData, ¶m, 1);
}
- if (rc < 0) LogVideoBuffersRefused(port.Name, rc);
- else LogVideoBuffersDeclared(port.Name, width, height, size);
+ if (rc < 0)
+ LogVideoBuffersRefused(port.Name, rc);
+ else
+ LogVideoBuffersDeclared(port.Name, width, height, size);
}
// Upstream's default.video.width and height (settings.c), what the graph publishes when nothing
@@ -907,17 +1022,21 @@ private unsafe void DeclareVideoBuffers(PipeWireFilterPort port)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe void OnStateChangedCallback(
- void* data, PipeWireFilterState old, PipeWireFilterState state, sbyte* error)
+ void* data,
+ PipeWireFilterState old,
+ PipeWireFilterState state,
+ sbyte* error
+ )
{
try
{
var self = (PipeWireFilter?)GCHandle.FromIntPtr((nint)data).Target;
- if (self is null || self._disposed) return;
+ if (self is null || self._disposed)
+ return;
string? message = error is null
? null
- : Encoding.UTF8.GetString(
- DaemonText.Bytes((sbyte*)error));
+ : Encoding.UTF8.GetString(DaemonText.Bytes((sbyte*)error));
self.RaiseStateChanged(old, state, message);
}
@@ -927,12 +1046,20 @@ private static unsafe void OnStateChangedCallback(
}
}
- private void RaiseStateChanged(PipeWireFilterState old, PipeWireFilterState state, string? error)
+ private void RaiseStateChanged(
+ PipeWireFilterState old,
+ PipeWireFilterState state,
+ string? error
+ )
{
LogStateChanged(Name, old, state, error);
// Not the realtime path, so one throwing subscriber must not starve the rest.
- SafeCallback.Raise(StateChanged, h => h(this, old, state, error), ex => LogHandlerFaulted(Name, ex));
+ SafeCallback.Raise(
+ StateChanged,
+ h => h(this, old, state, error),
+ ex => LogHandlerFaulted(Name, ex)
+ );
}
///
@@ -945,7 +1072,8 @@ private void RaiseStateChanged(PipeWireFilterState old, PipeWireFilterState stat
///
public ValueTask DisposeAsync()
{
- if (_disposed) return ValueTask.CompletedTask;
+ if (_disposed)
+ return ValueTask.CompletedTask;
_disposed = true;
Release();
GC.SuppressFinalize(this);
@@ -965,9 +1093,12 @@ private unsafe void Release()
// over, so a throw in between leaves blocks and a GCHandle that nothing else will free.
if (!ownedByHandle)
{
- if (_hook is not null) NativeMemory.Free(_hook);
- if (_events is not null) NativeMemory.Free(_events);
- if (_self.IsAllocated) _self.Free();
+ if (_hook is not null)
+ NativeMemory.Free(_hook);
+ if (_events is not null)
+ NativeMemory.Free(_events);
+ if (_self.IsAllocated)
+ _self.Free();
}
_hook = null;
@@ -976,28 +1107,50 @@ private unsafe void Release()
_ports.Clear();
}
- [LoggerMessage(EventId = 33310, Level = LogLevel.Debug,
- Message = "video port '{Port}' asks for {Width}x{Height} frames: buffers of {Size} bytes")]
+ [LoggerMessage(
+ EventId = 33310,
+ Level = LogLevel.Debug,
+ Message = "video port '{Port}' asks for {Width}x{Height} frames: buffers of {Size} bytes"
+ )]
private partial void LogVideoBuffersDeclared(string port, uint width, uint height, int size);
- [LoggerMessage(EventId = 33311, Level = LogLevel.Warning,
- Message = "the graph refused video port '{Port}''s buffer size ({Result}); frames larger than its default pool will not be handed out")]
+ [LoggerMessage(
+ EventId = 33311,
+ Level = LogLevel.Warning,
+ Message = "the graph refused video port '{Port}''s buffer size ({Result}); frames larger than its default pool will not be handed out"
+ )]
private partial void LogVideoBuffersRefused(string port, int result);
- [LoggerMessage(EventId = 33312, Level = LogLevel.Error,
- Message = "declaring a video port's buffers threw")]
+ [LoggerMessage(
+ EventId = 33312,
+ Level = LogLevel.Error,
+ Message = "declaring a video port's buffers threw"
+ )]
private partial void LogVideoBuffersFailed(Exception exception);
- [LoggerMessage(EventId = 33300, Level = LogLevel.Debug,
- Message = "filter '{Name}' connected with {PortCount} port(s)")]
+ [LoggerMessage(
+ EventId = 33300,
+ Level = LogLevel.Debug,
+ Message = "filter '{Name}' connected with {PortCount} port(s)"
+ )]
private partial void LogConnected(string name, int portCount);
- [LoggerMessage(EventId = 33301, Level = LogLevel.Debug,
- Message = "filter '{Name}' state {Old} -> {State} {Error}")]
+ [LoggerMessage(
+ EventId = 33301,
+ Level = LogLevel.Debug,
+ Message = "filter '{Name}' state {Old} -> {State} {Error}"
+ )]
private partial void LogStateChanged(
- string name, PipeWireFilterState old, PipeWireFilterState state, string? error);
-
- [LoggerMessage(EventId = 33302, Level = LogLevel.Error,
- Message = "a StateChanged handler for filter '{Name}' threw")]
+ string name,
+ PipeWireFilterState old,
+ PipeWireFilterState state,
+ string? error
+ );
+
+ [LoggerMessage(
+ EventId = 33302,
+ Level = LogLevel.Error,
+ Message = "a StateChanged handler for filter '{Name}' threw"
+ )]
private partial void LogHandlerFaulted(string name, Exception exception);
}
diff --git a/src/PipeWire.NET/Graph/PipeWireFilterPort.cs b/src/PipeWire.NET/Graph/PipeWireFilterPort.cs
index ac7adf5..b01377f 100644
--- a/src/PipeWire.NET/Graph/PipeWireFilterPort.cs
+++ b/src/PipeWire.NET/Graph/PipeWireFilterPort.cs
@@ -28,8 +28,12 @@ public sealed unsafe class PipeWireFilterPort
private readonly PipeWireDspFormat _format;
internal PipeWireFilterPort(
- PipeWireFilter owner, void* portData, PipeWirePortDirection direction, string name,
- PipeWireDspFormat format)
+ PipeWireFilter owner,
+ void* portData,
+ PipeWirePortDirection direction,
+ string name,
+ PipeWireDspFormat format
+ )
{
_owner = owner;
_portData = portData;
@@ -85,7 +89,8 @@ public Span GetSamples(uint sampleCount)
// sequences get a typed accessor of their own when the sequence transport lands.
if (_format is not PipeWireDspFormat.MonoAudio)
throw new InvalidOperationException(
- $"port '{Name}' carries {_format}, not audio; GetSamples is audio-only.");
+ $"port '{Name}' carries {_format}, not audio; GetSamples is audio-only."
+ );
return DspBuffer(sampleCount);
}
@@ -118,7 +123,8 @@ public Span GetPixels(uint width, uint height)
if (_format is not PipeWireDspFormat.Rgba32FloatVideo)
throw new InvalidOperationException(
- $"port '{Name}' carries {_format}, not video; GetPixels is video-only.");
+ $"port '{Name}' carries {_format}, not video; GetPixels is video-only."
+ );
return DspBuffer(checked(width * height * 4));
}
@@ -146,7 +152,8 @@ public Span GetPixels(uint width, uint height)
private Span DspBuffer(uint floats)
{
pw_buffer* buf = Interop.Native.pw_filter_dequeue_buffer(_portData);
- if (buf is null) return default;
+ if (buf is null)
+ return default;
spa_buffer* sb = buf->buffer;
if (sb is null || sb->n_datas == 0 || sb->datas is null || sb->datas[0].data is null)
@@ -194,22 +201,27 @@ private Span DspBuffer(uint floats)
RequireSequencePort(nameof(ReadEvents));
pw_buffer* buf = Interop.Native.pw_filter_dequeue_buffer(_portData);
- if (buf is null) return null;
+ if (buf is null)
+ return null;
try
{
spa_buffer* sb = buf->buffer;
- if (sb is null || sb->n_datas == 0 || sb->datas is null) return null;
+ if (sb is null || sb->n_datas == 0 || sb->datas is null)
+ return null;
spa_data* d = &sb->datas[0];
- if (d->data is null || d->chunk is null) return null;
+ if (d->data is null || d->chunk is null)
+ return null;
uint size = d->chunk->size;
- if (size == 0 || size > d->maxsize) return null;
+ if (size == 0 || size > d->maxsize)
+ return null;
var pod = new ReadOnlySpan((byte*)d->data + d->chunk->offset, checked((int)size));
- return Spa.SpaPod.TryParse(pod, out Spa.SpaValue? parsed) && parsed is Spa.SpaSequence seq
+ return
+ Spa.SpaPod.TryParse(pod, out Spa.SpaValue? parsed) && parsed is Spa.SpaSequence seq
? seq
: null;
}
@@ -245,15 +257,18 @@ public bool WriteEvents(scoped ReadOnlySpan events)
RequireSequencePort(nameof(WriteEvents));
pw_buffer* buf = Interop.Native.pw_filter_dequeue_buffer(_portData);
- if (buf is null) return false;
+ if (buf is null)
+ return false;
try
{
spa_buffer* sb = buf->buffer;
- if (sb is null || sb->n_datas == 0 || sb->datas is null) return false;
+ if (sb is null || sb->n_datas == 0 || sb->datas is null)
+ return false;
spa_data* d = &sb->datas[0];
- if (d->data is null || d->chunk is null || d->maxsize == 0) return false;
+ if (d->data is null || d->chunk is null || d->maxsize == 0)
+ return false;
var target = new Span(d->data, checked((int)d->maxsize));
var sequence = new Spa.SpaSequence(0, [.. events]);
@@ -262,8 +277,9 @@ public bool WriteEvents(scoped ReadOnlySpan events)
{
throw new ArgumentException(
$"the events need {Spa.SpaPod.GetByteCount(sequence)} bytes, and the port's "
- + $"buffer holds {d->maxsize}.",
- nameof(events));
+ + $"buffer holds {d->maxsize}.",
+ nameof(events)
+ );
}
d->chunk->offset = 0;
@@ -284,6 +300,7 @@ private void RequireSequencePort(string member)
return;
throw new InvalidOperationException(
- $"port '{Name}' carries {_format}, not a sequence; {member} is for MIDI and control ports.");
+ $"port '{Name}' carries {_format}, not a sequence; {member} is for MIDI and control ports."
+ );
}
}
diff --git a/src/PipeWire.NET/Graph/PipeWireGlobalParser.cs b/src/PipeWire.NET/Graph/PipeWireGlobalParser.cs
index c9de185..d0f5827 100644
--- a/src/PipeWire.NET/Graph/PipeWireGlobalParser.cs
+++ b/src/PipeWire.NET/Graph/PipeWireGlobalParser.cs
@@ -28,8 +28,13 @@ internal static unsafe class PipeWireGlobalParser
/// having no name would hide it from the graph entirely.
///
internal static PipeWireNode ParseNode(
- uint id, PipeWirePermissions permissions, uint version, PipeWireProperties props) =>
- new(id,
+ uint id,
+ PipeWirePermissions permissions,
+ uint version,
+ PipeWireProperties props
+ ) =>
+ new(
+ id,
props.Text(PipeWireKeys.PW_KEY_NODE_NAME),
props.Text(PipeWireKeys.PW_KEY_NODE_DESCRIPTION),
props.Text(PipeWireKeys.SPA_KEY_MEDIA_CLASS),
@@ -42,15 +47,22 @@ internal static PipeWireNode ParseNode(
props.Text(PipeWireKeys.SPA_KEY_OBJECT_PATH),
props.Int(PipeWireKeys.PW_KEY_PRIORITY_DRIVER),
props.Int(PipeWireKeys.PW_KEY_PRIORITY_SESSION),
- props);
+ props
+ );
///
/// Builds a port, or explains why it cannot. A port needs an owning node and a direction; with
/// either missing there is nowhere to file it and no way to say which way it faces.
///
internal static bool TryParsePort(
- uint id, PipeWirePermissions permissions, uint version, PipeWireProperties props,
- out PipeWirePort? port, out string reason, out string? offendingValue)
+ uint id,
+ PipeWirePermissions permissions,
+ uint version,
+ PipeWireProperties props,
+ out PipeWirePort? port,
+ out string reason,
+ out string? offendingValue
+ )
{
port = null;
@@ -71,12 +83,14 @@ internal static bool TryParsePort(
}
port = new PipeWirePort(
- id, parsedNodeId,
+ id,
+ parsedNodeId,
props.Text(PipeWireKeys.PW_KEY_PORT_NAME),
parsedDirection,
props.Flag(PipeWireKeys.PW_KEY_PORT_MONITOR),
props.Id(PipeWireKeys.PW_KEY_PORT_ID),
- props.Flag(PipeWireKeys.PW_KEY_PORT_CONTROL) || parsedDirection is PipeWirePortDirection.Control,
+ props.Flag(PipeWireKeys.PW_KEY_PORT_CONTROL)
+ || parsedDirection is PipeWirePortDirection.Control,
props.Flag(PipeWireKeys.PW_KEY_PORT_PHYSICAL),
props.Flag(PipeWireKeys.PW_KEY_PORT_TERMINAL),
props.Text(PipeWireKeys.PW_KEY_FORMAT_DSP),
@@ -86,7 +100,8 @@ internal static bool TryParsePort(
props.Text(PipeWireKeys.SPA_KEY_OBJECT_PATH),
permissions,
version,
- props);
+ props
+ );
reason = string.Empty;
offendingValue = null;
@@ -98,23 +113,62 @@ internal static bool TryParsePort(
/// any of them describes no route.
///
internal static bool TryParseLink(
- uint id, PipeWirePermissions permissions, uint version, PipeWireProperties props,
- out PipeWireLink? link, out string reason, out string? offendingValue)
+ uint id,
+ PipeWirePermissions permissions,
+ uint version,
+ PipeWireProperties props,
+ out PipeWireLink? link,
+ out string reason,
+ out string? offendingValue
+ )
{
link = null;
- if (!TryReadId(props, PipeWireKeys.PW_KEY_LINK_OUTPUT_NODE, out uint outputNode, out reason, out offendingValue) ||
- !TryReadId(props, PipeWireKeys.PW_KEY_LINK_OUTPUT_PORT, out uint outputPort, out reason, out offendingValue) ||
- !TryReadId(props, PipeWireKeys.PW_KEY_LINK_INPUT_NODE, out uint inputNode, out reason, out offendingValue) ||
- !TryReadId(props, PipeWireKeys.PW_KEY_LINK_INPUT_PORT, out uint inputPort, out reason, out offendingValue))
+ if (
+ !TryReadId(
+ props,
+ PipeWireKeys.PW_KEY_LINK_OUTPUT_NODE,
+ out uint outputNode,
+ out reason,
+ out offendingValue
+ )
+ || !TryReadId(
+ props,
+ PipeWireKeys.PW_KEY_LINK_OUTPUT_PORT,
+ out uint outputPort,
+ out reason,
+ out offendingValue
+ )
+ || !TryReadId(
+ props,
+ PipeWireKeys.PW_KEY_LINK_INPUT_NODE,
+ out uint inputNode,
+ out reason,
+ out offendingValue
+ )
+ || !TryReadId(
+ props,
+ PipeWireKeys.PW_KEY_LINK_INPUT_PORT,
+ out uint inputPort,
+ out reason,
+ out offendingValue
+ )
+ )
return false;
link = new PipeWireLink(
- id, inputNode, inputPort, outputNode, outputPort, permissions, version,
+ id,
+ inputNode,
+ inputPort,
+ outputNode,
+ outputPort,
+ permissions,
+ version,
props.Flag(PipeWireKeys.PW_KEY_LINK_PASSIVE),
props.Id(PipeWireKeys.PW_KEY_FACTORY_ID),
props.Id(PipeWireKeys.PW_KEY_CLIENT_ID),
- props);
+ props
+ );
reason = string.Empty;
offendingValue = null;
return true;
@@ -125,8 +179,15 @@ internal static bool TryParseLink(
/// for having no name would hide a whole sound card from the graph.
///
internal static PipeWireDevice ParseDevice(
- uint id, PipeWirePermissions permissions, uint version, PipeWireProperties props) =>
- new(id, permissions, version,
+ uint id,
+ PipeWirePermissions permissions,
+ uint version,
+ PipeWireProperties props
+ ) =>
+ new(
+ id,
+ permissions,
+ version,
props.Text(PipeWireKeys.SPA_KEY_DEVICE_NAME),
props.Text(PipeWireKeys.SPA_KEY_DEVICE_DESCRIPTION),
props.Text(PipeWireKeys.SPA_KEY_DEVICE_NICK),
@@ -136,12 +197,20 @@ internal static PipeWireDevice ParseDevice(
props.Id(PipeWireKeys.PW_KEY_FACTORY_ID),
props.Id(PipeWireKeys.PW_KEY_CLIENT_ID),
props.Id(PipeWireKeys.PW_KEY_MODULE_ID),
- props);
+ props
+ );
/// Builds a client. Always succeeds; every field is optional.
internal static PipeWireClient ParseClient(
- uint id, PipeWirePermissions permissions, uint version, PipeWireProperties props) =>
- new(id, permissions, version,
+ uint id,
+ PipeWirePermissions permissions,
+ uint version,
+ PipeWireProperties props
+ ) =>
+ new(
+ id,
+ permissions,
+ version,
props.Text(PipeWireKeys.PW_KEY_APP_NAME),
props.Int(PipeWireKeys.PW_KEY_SEC_PID),
props.Id(PipeWireKeys.PW_KEY_SEC_UID),
@@ -153,46 +222,80 @@ internal static PipeWireClient ParseClient(
props.Text(PipeWireKeys.PW_KEY_SEC_LABEL),
props.Text(PipeWireKeys.PW_KEY_SEC_APP_ID),
props.Text(PipeWireKeys.PW_KEY_SEC_INSTANCE_ID),
- props);
+ props
+ );
/// Builds a factory. Always succeeds; every field is optional.
internal static PipeWireFactory ParseFactory(
- uint id, PipeWirePermissions permissions, uint version, PipeWireProperties props) =>
- new(id, permissions, version,
+ uint id,
+ PipeWirePermissions permissions,
+ uint version,
+ PipeWireProperties props
+ ) =>
+ new(
+ id,
+ permissions,
+ version,
props.Text(PipeWireKeys.PW_KEY_FACTORY_NAME),
props.Text(PipeWireKeys.PW_KEY_FACTORY_TYPE_NAME),
props.Id(PipeWireKeys.PW_KEY_FACTORY_TYPE_VERSION),
props.Id(PipeWireKeys.PW_KEY_MODULE_ID),
- props);
+ props
+ );
/// Builds a module. Always succeeds; every field is optional.
internal static PipeWireModule ParseModule(
- uint id, PipeWirePermissions permissions, uint version, PipeWireProperties props) =>
- new(id, permissions, version,
+ uint id,
+ PipeWirePermissions permissions,
+ uint version,
+ PipeWireProperties props
+ ) =>
+ new(
+ id,
+ permissions,
+ version,
props.Text(PipeWireKeys.PW_KEY_MODULE_NAME),
props.Text(PipeWireKeys.PW_KEY_MODULE_DESCRIPTION),
props.Text(PipeWireKeys.PW_KEY_MODULE_AUTHOR),
props.Text(PipeWireKeys.PW_KEY_MODULE_VERSION),
- props);
+ props
+ );
/// Builds a metadata store. Always succeeds; the name is optional.
internal static PipeWireMetadata ParseMetadata(
- uint id, PipeWirePermissions permissions, uint version, PipeWireProperties props) =>
- new(id, permissions, version, props.Text(PipeWireKeys.PW_KEY_METADATA_NAME),
+ uint id,
+ PipeWirePermissions permissions,
+ uint version,
+ PipeWireProperties props
+ ) =>
+ new(
+ id,
+ permissions,
+ version,
+ props.Text(PipeWireKeys.PW_KEY_METADATA_NAME),
props.Id(PipeWireKeys.PW_KEY_CLIENT_ID),
props.Id(PipeWireKeys.PW_KEY_FACTORY_ID),
props.Id(PipeWireKeys.PW_KEY_MODULE_ID),
- props);
+ props
+ );
/// Builds the core object. Always succeeds; every field is optional.
internal static PipeWireCore ParseCore(
- uint id, PipeWirePermissions permissions, uint version, PipeWireProperties props) =>
- new(id, permissions, version,
+ uint id,
+ PipeWirePermissions permissions,
+ uint version,
+ PipeWireProperties props
+ ) =>
+ new(
+ id,
+ permissions,
+ version,
props.Text(PipeWireKeys.PW_KEY_CORE_NAME),
props.Text(PipeWireKeys.PW_KEY_CORE_VERSION),
props.Text(PipeWireKeys.PW_KEY_APP_PROCESS_HOST),
props.Text(PipeWireKeys.PW_KEY_APP_PROCESS_USER),
- props);
+ props
+ );
///
/// Reads a property that names another object and must be there.
@@ -202,7 +305,12 @@ internal static PipeWireCore ParseCore(
/// value travels back so the refusal can name what the daemon actually sent.
///
private static bool TryReadId(
- PipeWireProperties props, string key, out uint value, out string reason, out string? offendingValue)
+ PipeWireProperties props,
+ string key,
+ out uint value,
+ out string reason,
+ out string? offendingValue
+ )
{
string? raw = props.GetValueOrDefault(key);
if (uint.TryParse(raw, out value))
@@ -225,11 +333,21 @@ internal static bool TryParseDirection(string? value, out PipeWirePortDirection
{
switch (value)
{
- case "in": direction = PipeWirePortDirection.In; return true;
- case "out": direction = PipeWirePortDirection.Out; return true;
- case "control": direction = PipeWirePortDirection.Control; return true;
- case "notify": direction = PipeWirePortDirection.Notify; return true;
- default: direction = default; return false;
+ case "in":
+ direction = PipeWirePortDirection.In;
+ return true;
+ case "out":
+ direction = PipeWirePortDirection.Out;
+ return true;
+ case "control":
+ direction = PipeWirePortDirection.Control;
+ return true;
+ case "notify":
+ direction = PipeWirePortDirection.Notify;
+ return true;
+ default:
+ direction = default;
+ return false;
}
}
@@ -245,18 +363,25 @@ internal static bool ParseBool(ReadOnlySpan value) =>
/// allocates. The span is valid only for the duration of the callback that supplied
/// - materialise it with to keep it.
///
- internal static bool TryReadValue(spa_dict* dict, ReadOnlySpan keyUtf8, out ReadOnlySpan value)
+ internal static bool TryReadValue(
+ spa_dict* dict,
+ ReadOnlySpan keyUtf8,
+ out ReadOnlySpan value
+ )
{
value = default;
- if (dict is null) return false;
+ if (dict is null)
+ return false;
int nItems = (int)dict->n_items;
- if (nItems == 0 || dict->items is null) return false;
+ if (nItems == 0 || dict->items is null)
+ return false;
for (int i = 0; i < nItems; i++)
{
spa_dict_item* item = dict->items + i;
- if (item->key is null) continue;
+ if (item->key is null)
+ continue;
if (!DaemonText.Bytes(item->key).SequenceEqual(keyUtf8))
continue;
@@ -264,7 +389,8 @@ internal static bool TryReadValue(spa_dict* dict, ReadOnlySpan keyUtf8, ou
// caller would turn the empty span it used to produce back into null anyway - so the
// three states the daemon can express (absent, present and null, present and empty)
// collapse to two here, and only the genuinely empty one reads as an empty string.
- if (item->value is null) return false;
+ if (item->value is null)
+ return false;
value = DaemonText.Bytes(item->value);
return true;
diff --git a/src/PipeWire.NET/Graph/PipeWireGraphClock.cs b/src/PipeWire.NET/Graph/PipeWireGraphClock.cs
index edb0283..03fcd50 100644
--- a/src/PipeWire.NET/Graph/PipeWireGraphClock.cs
+++ b/src/PipeWire.NET/Graph/PipeWireGraphClock.cs
@@ -31,4 +31,5 @@ public readonly record struct PipeWireGraphClock(
uint RateDen,
long Delay,
double RateDiff,
- ulong NextTimeNs);
+ ulong NextTimeNs
+);
diff --git a/src/PipeWire.NET/Graph/PipeWireGraphExtensions.cs b/src/PipeWire.NET/Graph/PipeWireGraphExtensions.cs
index 8bc5815..f09a45c 100644
--- a/src/PipeWire.NET/Graph/PipeWireGraphExtensions.cs
+++ b/src/PipeWire.NET/Graph/PipeWireGraphExtensions.cs
@@ -35,7 +35,8 @@ public bool IsStillIn(PipeWireGraphSnapshot graph)
{
ArgumentNullException.ThrowIfNull(graph);
- if (!graph.TryGetObject(held.Id, out IPipeWireObject? current)) return false;
+ if (!graph.TryGetObject(held.Id, out IPipeWireObject? current))
+ return false;
if (held.ObjectSerial is { } mine && current.ObjectSerial is { } theirs)
return mine == theirs;
@@ -55,11 +56,13 @@ public bool IsStillIn(PipeWireGraphSnapshot graph)
/// than being refused. Picking "the first input port" is the normal way to find a link
/// endpoint, and it has to not find that one.
///
- public bool IsDataInput => port.PortDirection is PipeWirePortDirection.In && !port.IsControl;
+ public bool IsDataInput =>
+ port.PortDirection is PipeWirePortDirection.In && !port.IsControl;
/// True for a port that carries media out of its node.
/// Excludes control ports, for the reason on the data input above.
- public bool IsDataOutput => port.PortDirection is PipeWirePortDirection.Out && !port.IsControl;
+ public bool IsDataOutput =>
+ port.PortDirection is PipeWirePortDirection.Out && !port.IsControl;
/// True for a notification port (port.direction=notify).
public bool IsNotify => port.PortDirection is PipeWirePortDirection.Notify;
@@ -84,7 +87,10 @@ public bool IsStillIn(PipeWireGraphSnapshot graph)
extension(PipeWireGraphSnapshot graph)
{
/// The ports of a node, in the given direction.
- public IEnumerable GetPortsForNode(uint nodeId, PipeWirePortDirection direction)
+ public IEnumerable GetPortsForNode(
+ uint nodeId,
+ PipeWirePortDirection direction
+ )
{
foreach (PipeWirePort port in graph.GetPortsForNode(nodeId))
if (port.PortDirection == direction)
@@ -110,7 +116,8 @@ public bool CanCaptureFrom(PipeWireNode node)
{
ArgumentNullException.ThrowIfNull(node);
foreach (PipeWirePort port in graph.GetPortsForNode(node.NodeId))
- if (port.IsDataOutput) return true;
+ if (port.IsDataOutput)
+ return true;
return false;
}
@@ -119,7 +126,8 @@ public bool CanSendTo(PipeWireNode node)
{
ArgumentNullException.ThrowIfNull(node);
foreach (PipeWirePort port in graph.GetPortsForNode(node.NodeId))
- if (port.IsDataInput) return true;
+ if (port.IsDataInput)
+ return true;
return false;
}
@@ -130,11 +138,19 @@ public bool CanSendTo(PipeWireNode node)
/// it in a loop.
///
public ImmutableArray GetVideoSources() =>
- [.. graph.Nodes.Where(n => n.Media is PipeWireMediaKind.Video && graph.CanCaptureFrom(n))];
+ [
+ .. graph.Nodes.Where(n =>
+ n.Media is PipeWireMediaKind.Video && graph.CanCaptureFrom(n)
+ ),
+ ];
/// Nodes carrying audio that media can actually be read from.
/// Includes sinks, which are readable through their monitor ports.
public ImmutableArray GetAudioSources() =>
- [.. graph.Nodes.Where(n => n.Media is PipeWireMediaKind.Audio && graph.CanCaptureFrom(n))];
+ [
+ .. graph.Nodes.Where(n =>
+ n.Media is PipeWireMediaKind.Audio && graph.CanCaptureFrom(n)
+ ),
+ ];
}
}
diff --git a/src/PipeWire.NET/Graph/PipeWireGraphSnapshot.cs b/src/PipeWire.NET/Graph/PipeWireGraphSnapshot.cs
index 1099e74..4628bd1 100644
--- a/src/PipeWire.NET/Graph/PipeWireGraphSnapshot.cs
+++ b/src/PipeWire.NET/Graph/PipeWireGraphSnapshot.cs
@@ -30,7 +30,8 @@ internal PipeWireGraphSnapshot(
IEnumerable nodes,
IEnumerable ports,
IEnumerable links,
- IEnumerable? others = null)
+ IEnumerable? others = null
+ )
{
Version = version;
Nodes = [.. nodes];
@@ -67,42 +68,78 @@ internal PipeWireGraphSnapshot(
private object? _objectsByIdLock;
private FrozenDictionary NodesById =>
- LazyInitializer.EnsureInitialized(ref _nodesById, ref _nodesByIdLock,
- () => Nodes.ToFrozenDictionary(static n => n.NodeId));
+ LazyInitializer.EnsureInitialized(
+ ref _nodesById,
+ ref _nodesByIdLock,
+ () => Nodes.ToFrozenDictionary(static n => n.NodeId)
+ );
private FrozenDictionary PortsById =>
- LazyInitializer.EnsureInitialized(ref _portsById, ref _portsByIdLock,
- () => Ports.ToFrozenDictionary(static p => p.PortId));
+ LazyInitializer.EnsureInitialized(
+ ref _portsById,
+ ref _portsByIdLock,
+ () => Ports.ToFrozenDictionary(static p => p.PortId)
+ );
private FrozenDictionary LinksById =>
- LazyInitializer.EnsureInitialized(ref _linksById, ref _linksByIdLock,
- () => Links.ToFrozenDictionary(static l => l.LinkId));
+ LazyInitializer.EnsureInitialized(
+ ref _linksById,
+ ref _linksByIdLock,
+ () => Links.ToFrozenDictionary(static l => l.LinkId)
+ );
private FrozenDictionary> LinksByNode =>
- LazyInitializer.EnsureInitialized(ref _linksByNode, ref _linksByNodeLock,
- () => Links.SelectMany(l => new[] { (Node: l.InputNodeId, Link: l), (Node: l.OutputNodeId, Link: l) })
- .GroupBy(static x => x.Node)
- .ToFrozenDictionary(static g => g.Key,
- static g => g.Select(static x => x.Link).Distinct().ToImmutableArray()));
+ LazyInitializer.EnsureInitialized(
+ ref _linksByNode,
+ ref _linksByNodeLock,
+ () =>
+ Links
+ .SelectMany(l =>
+ new[] { (Node: l.InputNodeId, Link: l), (Node: l.OutputNodeId, Link: l) }
+ )
+ .GroupBy(static x => x.Node)
+ .ToFrozenDictionary(
+ static g => g.Key,
+ static g => g.Select(static x => x.Link).Distinct().ToImmutableArray()
+ )
+ );
private FrozenDictionary> PortsByNode =>
- LazyInitializer.EnsureInitialized(ref _portsByNode, ref _portsByNodeLock,
- () => Ports.GroupBy(static p => p.NodeId)
- .ToFrozenDictionary(static g => g.Key, static g => g.ToImmutableArray()));
+ LazyInitializer.EnsureInitialized(
+ ref _portsByNode,
+ ref _portsByNodeLock,
+ () =>
+ Ports
+ .GroupBy(static p => p.NodeId)
+ .ToFrozenDictionary(static g => g.Key, static g => g.ToImmutableArray())
+ );
private FrozenDictionary> InputLinksByPort =>
- LazyInitializer.EnsureInitialized(ref _inputLinksByPort, ref _inputLinksByPortLock,
- () => Links.GroupBy(static l => l.InputPortId)
- .ToFrozenDictionary(static g => g.Key, static g => g.ToImmutableArray()));
+ LazyInitializer.EnsureInitialized(
+ ref _inputLinksByPort,
+ ref _inputLinksByPortLock,
+ () =>
+ Links
+ .GroupBy(static l => l.InputPortId)
+ .ToFrozenDictionary(static g => g.Key, static g => g.ToImmutableArray())
+ );
private FrozenDictionary> OutputLinksByPort =>
- LazyInitializer.EnsureInitialized(ref _outputLinksByPort, ref _outputLinksByPortLock,
- () => Links.GroupBy(static l => l.OutputPortId)
- .ToFrozenDictionary(static g => g.Key, static g => g.ToImmutableArray()));
+ LazyInitializer.EnsureInitialized(
+ ref _outputLinksByPort,
+ ref _outputLinksByPortLock,
+ () =>
+ Links
+ .GroupBy(static l => l.OutputPortId)
+ .ToFrozenDictionary(static g => g.Key, static g => g.ToImmutableArray())
+ );
private FrozenDictionary ObjectsById =>
- LazyInitializer.EnsureInitialized(ref _objectsById, ref _objectsByIdLock,
- () => Objects.ToFrozenDictionary(static o => o.Id));
+ LazyInitializer.EnsureInitialized(
+ ref _objectsById,
+ ref _objectsByIdLock,
+ () => Objects.ToFrozenDictionary(static o => o.Id)
+ );
///
/// Increases by one per published snapshot. A local counter for spotting missed publications;
@@ -142,8 +179,7 @@ internal PipeWireGraphSnapshot(
public ImmutableArray Modules => OfKind(ref _modules);
/// Every metadata store, such as default and settings.
- public ImmutableArray Metadata =>
- OfKind(ref _metadata);
+ public ImmutableArray Metadata => OfKind(ref _metadata);
/// The daemon core, or if it has not been seen yet.
public PipeWireCore? Core => Single();
@@ -176,17 +212,20 @@ private ImmutableArray OfKind(ref ImmutableArray cache)
where T : class, IPipeWireObject
{
ImmutableArray current = cache;
- if (!current.IsDefault) return current;
+ if (!current.IsDefault)
+ return current;
ImmutableInterlocked.InterlockedInitialize(ref cache, [.. Objects.OfType()]);
return cache;
}
- private T? Single() where T : class, IPipeWireObject
+ private T? Single()
+ where T : class, IPipeWireObject
{
foreach (IPipeWireObject candidate in Objects)
{
- if (candidate is T match) return match;
+ if (candidate is T match)
+ return match;
}
return null;
@@ -214,16 +253,20 @@ public bool TryGetLink(uint id, [NotNullWhen(true)] out PipeWireLink? value) =>
LinksById.TryGetValue(id, out value);
/// The device with this id, or if the graph has none.
- public PipeWireDevice? GetDevice(uint id) => ObjectsById.GetValueOrDefault(id) as PipeWireDevice;
+ public PipeWireDevice? GetDevice(uint id) =>
+ ObjectsById.GetValueOrDefault(id) as PipeWireDevice;
/// The client with this id, or if the graph has none.
- public PipeWireClient? GetClient(uint id) => ObjectsById.GetValueOrDefault(id) as PipeWireClient;
+ public PipeWireClient? GetClient(uint id) =>
+ ObjectsById.GetValueOrDefault(id) as PipeWireClient;
/// The factory with this id, or if the graph has none.
- public PipeWireFactory? GetFactory(uint id) => ObjectsById.GetValueOrDefault(id) as PipeWireFactory;
+ public PipeWireFactory? GetFactory(uint id) =>
+ ObjectsById.GetValueOrDefault(id) as PipeWireFactory;
/// The module with this id, or if the graph has none.
- public PipeWireModule? GetModule(uint id) => ObjectsById.GetValueOrDefault(id) as PipeWireModule;
+ public PipeWireModule? GetModule(uint id) =>
+ ObjectsById.GetValueOrDefault(id) as PipeWireModule;
/// The metadata store with this name, or if there is none.
/// The store name, such as default.
@@ -241,10 +284,11 @@ public bool TryGetLink(uint id, [NotNullWhen(true)] out PipeWireLink? value) =>
/// Looks up any object by id, whatever kind it is.
public bool TryGetObject(uint id, [NotNullWhen(true)] out IPipeWireObject? value)
{
- value = GetNode(id)
- ?? (IPipeWireObject?)GetPort(id)
- ?? GetLink(id)
- ?? ObjectsById.GetValueOrDefault(id);
+ value =
+ GetNode(id)
+ ?? (IPipeWireObject?)GetPort(id)
+ ?? GetLink(id)
+ ?? ObjectsById.GetValueOrDefault(id);
return value is not null;
}
diff --git a/src/PipeWire.NET/Graph/PipeWireLatency.cs b/src/PipeWire.NET/Graph/PipeWireLatency.cs
index 68af92b..0edf850 100644
--- a/src/PipeWire.NET/Graph/PipeWireLatency.cs
+++ b/src/PipeWire.NET/Graph/PipeWireLatency.cs
@@ -27,7 +27,8 @@ public sealed record PipeWireLatency(
int MinRate,
int MaxRate,
long MinNs,
- long MaxNs)
+ long MaxNs
+)
{
/// Reads one out of a SPA_PARAM_Latency object, or null if it is not one.
///
@@ -37,7 +38,8 @@ public sealed record PipeWireLatency(
///
public static PipeWireLatency? From(SpaObject? param)
{
- if (param is null || param.ObjectType != SpaType.ObjectParamLatency) return null;
+ if (param is null || param.ObjectType != SpaType.ObjectParamLatency)
+ return null;
return new PipeWireLatency(
(SpaDirection)Id(param, SpaParamLatency.Direction),
@@ -46,21 +48,25 @@ public sealed record PipeWireLatency(
Int(param, SpaParamLatency.MinRate),
Int(param, SpaParamLatency.MaxRate),
Long(param, SpaParamLatency.MinNs),
- Long(param, SpaParamLatency.MaxNs));
+ Long(param, SpaParamLatency.MaxNs)
+ );
}
/// This latency as the parameter object the daemon expects.
public SpaObject ToParameter() =>
- new(SpaType.ObjectParamLatency, SpaParamType.Latency,
- [
- new SpaPodProperty((uint)SpaParamLatency.Direction, 0, new SpaId((uint)Direction)),
- new SpaPodProperty((uint)SpaParamLatency.MinQuantum, 0, new SpaFloat(MinQuantum)),
- new SpaPodProperty((uint)SpaParamLatency.MaxQuantum, 0, new SpaFloat(MaxQuantum)),
- new SpaPodProperty((uint)SpaParamLatency.MinRate, 0, new SpaInt(MinRate)),
- new SpaPodProperty((uint)SpaParamLatency.MaxRate, 0, new SpaInt(MaxRate)),
- new SpaPodProperty((uint)SpaParamLatency.MinNs, 0, new SpaLong(MinNs)),
- new SpaPodProperty((uint)SpaParamLatency.MaxNs, 0, new SpaLong(MaxNs)),
- ]);
+ new(
+ SpaType.ObjectParamLatency,
+ SpaParamType.Latency,
+ [
+ new SpaPodProperty((uint)SpaParamLatency.Direction, 0, new SpaId((uint)Direction)),
+ new SpaPodProperty((uint)SpaParamLatency.MinQuantum, 0, new SpaFloat(MinQuantum)),
+ new SpaPodProperty((uint)SpaParamLatency.MaxQuantum, 0, new SpaFloat(MaxQuantum)),
+ new SpaPodProperty((uint)SpaParamLatency.MinRate, 0, new SpaInt(MinRate)),
+ new SpaPodProperty((uint)SpaParamLatency.MaxRate, 0, new SpaInt(MaxRate)),
+ new SpaPodProperty((uint)SpaParamLatency.MinNs, 0, new SpaLong(MinNs)),
+ new SpaPodProperty((uint)SpaParamLatency.MaxNs, 0, new SpaLong(MaxNs)),
+ ]
+ );
private static uint Id(SpaObject o, SpaParamLatency key) =>
o[(uint)key] is SpaId v ? v.Value : 0;
diff --git a/src/PipeWire.NET/Graph/PipeWireLink.cs b/src/PipeWire.NET/Graph/PipeWireLink.cs
index d861348..25c6e66 100644
--- a/src/PipeWire.NET/Graph/PipeWireLink.cs
+++ b/src/PipeWire.NET/Graph/PipeWireLink.cs
@@ -21,7 +21,8 @@ internal PipeWireLink(
bool IsPassive = false,
uint? FactoryId = null,
uint? ClientId = null,
- PipeWireProperties? Properties = null)
+ PipeWireProperties? Properties = null
+ )
{
this.Properties = Properties ?? PipeWireProperties.Empty;
this.ObjectSerial = this.Properties.Serial;
diff --git a/src/PipeWire.NET/Graph/PipeWireLinkBuilder.cs b/src/PipeWire.NET/Graph/PipeWireLinkBuilder.cs
index 2a0ce9c..d511401 100644
--- a/src/PipeWire.NET/Graph/PipeWireLinkBuilder.cs
+++ b/src/PipeWire.NET/Graph/PipeWireLinkBuilder.cs
@@ -27,7 +27,11 @@ public readonly struct PipeWireLinkBuilder
private readonly PipeWireObjectOptions _options;
internal PipeWireLinkBuilder(
- PipeWireRegistry registry, PipeWirePort output, PipeWirePort input, PipeWireObjectOptions options)
+ PipeWireRegistry registry,
+ PipeWirePort output,
+ PipeWirePort input,
+ PipeWireObjectOptions options
+ )
{
_registry = registry;
_output = output;
@@ -54,11 +58,19 @@ public PipeWireLinkBuilder WithProperty(string key, string value)
ArgumentNullException.ThrowIfNull(value);
PipeWireObjectOptions.ThrowIfReserved(key, forLink: true);
- ImmutableArray> existing =
- _options.Properties.IsDefault ? [] : _options.Properties;
+ ImmutableArray> existing = _options.Properties.IsDefault
+ ? []
+ : _options.Properties;
- return new(_registry, _output, _input,
- _options with { Properties = existing.Add(new(key, value)) });
+ return new(
+ _registry,
+ _output,
+ _input,
+ _options with
+ {
+ Properties = existing.Add(new(key, value)),
+ }
+ );
}
///
diff --git a/src/PipeWire.NET/Graph/PipeWireLinkProxy.cs b/src/PipeWire.NET/Graph/PipeWireLinkProxy.cs
index ef3c1d4..2c732c9 100644
--- a/src/PipeWire.NET/Graph/PipeWireLinkProxy.cs
+++ b/src/PipeWire.NET/Graph/PipeWireLinkProxy.cs
@@ -27,8 +27,9 @@ namespace PipeWire.NET.Graph;
public sealed partial class PipeWireLinkProxy : IDisposable, IAsyncDisposable
{
private readonly ILogger _logger;
- private readonly TaskCompletionSource _ready =
- new(TaskCreationOptions.RunContinuationsAsynchronously);
+ private readonly TaskCompletionSource _ready = new(
+ TaskCreationOptions.RunContinuationsAsynchronously
+ );
private BoundProxy? _bound;
private volatile bool _disposed;
@@ -53,7 +54,8 @@ private sealed record LinkSnapshot(
uint OutputNodeId,
uint OutputPortId,
uint InputNodeId,
- uint InputPortId);
+ uint InputPortId
+ );
/// The global id of the link this is bound to.
public uint LinkId { get; }
@@ -112,8 +114,13 @@ private sealed record LinkSnapshot(
public event Action? StateChanged;
internal static unsafe PipeWireLinkProxy Bind(
- PipeWireContext ctx, pw_registry* registry, uint id, uint version, ILogger logger,
- Action? propertiesObserved = null)
+ PipeWireContext ctx,
+ pw_registry* registry,
+ uint id,
+ uint version,
+ ILogger logger,
+ Action? propertiesObserved = null
+ )
{
// The observer is in place before the proxy is bound: the first info after a bind is the
// only one that carries the object's properties (later ones set no PROPS in their change
@@ -122,7 +129,12 @@ internal static unsafe PipeWireLinkProxy Bind(
// properties never reached the registry.
var control = new PipeWireLinkProxy(id, logger) { PropertiesObserved = propertiesObserved };
control._bound = BoundProxy.Bind(
- ctx, registry, id, PipeWireKeys.PW_TYPE_INTERFACE_Link, version, NativeConstants.PW_VERSION_LINK,
+ ctx,
+ registry,
+ id,
+ PipeWireKeys.PW_TYPE_INTERFACE_Link,
+ version,
+ NativeConstants.PW_VERSION_LINK,
sizeof(pw_link_events),
events =>
{
@@ -130,9 +142,15 @@ internal static unsafe PipeWireLinkProxy Bind(
table->version = NativeConstants.PW_VERSION_LINK_EVENTS;
table->info = &OnInfo;
},
- static (proxy, hook, events, data) => Native.pw_link_add_listener(
- (pw_link*)proxy, (spa_hook*)hook, (pw_link_events*)events, (void*)data),
- control);
+ static (proxy, hook, events, data) =>
+ Native.pw_link_add_listener(
+ (pw_link*)proxy,
+ (spa_hook*)hook,
+ (pw_link_events*)events,
+ (void*)data
+ ),
+ control
+ );
control._bound.Removed = control.RaiseRemoved;
@@ -153,14 +171,19 @@ internal static unsafe PipeWireLinkProxy Bind(
private void RaiseRemoved()
{
Action? handler = Removed;
- if (handler is null) return;
+ if (handler is null)
+ return;
// A native callback frame, so nothing may escape it.
- try { handler(); }
- catch (Exception) { /* a subscriber that throws must not reach the daemon */ }
+ try
+ {
+ handler();
+ }
+ catch (Exception)
+ { /* a subscriber that throws must not reach the daemon */
+ }
}
-
/// Waits for the daemon's first report about this link.
/// Abandons the wait.
///
@@ -174,19 +197,21 @@ public Task ReadyAsync(CancellationToken cancellationToken = default)
}
[System.Runtime.InteropServices.UnmanagedCallersOnly(
- CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
+ CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)]
+ )]
private static unsafe void OnInfo(void* data, pw_link_info* info)
{
// An exception escaping a reverse P/Invoke aborts the process, so nothing below may throw.
try
{
- if (data is null || info is null) return;
- if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireLinkProxy self) return;
- if (self._disposed) return;
+ if (data is null || info is null)
+ return;
+ if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireLinkProxy self)
+ return;
+ if (self._disposed)
+ return;
- string? error = info->error is null
- ? null
- : DaemonText.String(info->error);
+ string? error = info->error is null ? null : DaemonText.String(info->error);
// The error only means anything in the error state. Carrying a stale one alongside a
// recovered link would have a caller reporting a failure that is over.
@@ -196,7 +221,8 @@ private static unsafe void OnInfo(void* data, pw_link_info* info)
info->output_node_id,
info->output_port_id,
info->input_node_id,
- info->input_port_id);
+ info->input_port_id
+ );
self._snapshot = snapshot;
self._ready.TrySetResult();
@@ -211,15 +237,21 @@ private static unsafe void OnInfo(void* data, pw_link_info* info)
{
// Reached only if the marshalling above fails, which would mean the daemon sent
// something the struct does not describe.
- try { Console.Error.WriteLine($"PipeWire.NET link info callback faulted: {ex}"); }
- catch (IOException) { /* Deliberately not logged: nothing left that could report it. */ }
+ try
+ {
+ Console.Error.WriteLine($"PipeWire.NET link info callback faulted: {ex}");
+ }
+ catch (IOException)
+ { /* Deliberately not logged: nothing left that could report it. */
+ }
}
}
///
public void Dispose()
{
- if (_disposed) return;
+ if (_disposed)
+ return;
_disposed = true;
_ready.TrySetCanceled();
@@ -234,11 +266,17 @@ public ValueTask DisposeAsync()
return ValueTask.CompletedTask;
}
- [LoggerMessage(EventId = 34500, Level = LogLevel.Debug,
- Message = "link {LinkId} is {State}{Error}")]
+ [LoggerMessage(
+ EventId = 34500,
+ Level = LogLevel.Debug,
+ Message = "link {LinkId} is {State}{Error}"
+ )]
private partial void LogState(uint linkId, PipeWireLinkState state, string? error);
- [LoggerMessage(EventId = 34501, Level = LogLevel.Warning,
- Message = "a link state handler threw")]
+ [LoggerMessage(
+ EventId = 34501,
+ Level = LogLevel.Warning,
+ Message = "a link state handler threw"
+ )]
private partial void LogHandlerFaulted(Exception exception);
}
diff --git a/src/PipeWire.NET/Graph/PipeWireMetadata.cs b/src/PipeWire.NET/Graph/PipeWireMetadata.cs
index 44c88fc..7d55f43 100644
--- a/src/PipeWire.NET/Graph/PipeWireMetadata.cs
+++ b/src/PipeWire.NET/Graph/PipeWireMetadata.cs
@@ -26,7 +26,8 @@ internal PipeWireMetadata(
uint? ClientId = null,
uint? FactoryId = null,
uint? ModuleId = null,
- PipeWireProperties? Properties = null)
+ PipeWireProperties? Properties = null
+ )
{
this.Properties = Properties ?? PipeWireProperties.Empty;
this.ObjectSerial = this.Properties.Serial;
diff --git a/src/PipeWire.NET/Graph/PipeWireMetadataEntry.cs b/src/PipeWire.NET/Graph/PipeWireMetadataEntry.cs
index 7fa420a..53c89ac 100644
--- a/src/PipeWire.NET/Graph/PipeWireMetadataEntry.cs
+++ b/src/PipeWire.NET/Graph/PipeWireMetadataEntry.cs
@@ -31,14 +31,16 @@ public string? NameValue
{
get
{
- if (string.IsNullOrEmpty(Value)) return null;
+ if (string.IsNullOrEmpty(Value))
+ return null;
try
{
using var document = JsonDocument.Parse(Value);
- return document.RootElement.ValueKind == JsonValueKind.Object
- && document.RootElement.TryGetProperty("name", out JsonElement name)
- && name.ValueKind == JsonValueKind.String
+ return
+ document.RootElement.ValueKind == JsonValueKind.Object
+ && document.RootElement.TryGetProperty("name", out JsonElement name)
+ && name.ValueKind == JsonValueKind.String
? name.GetString()
: null;
}
diff --git a/src/PipeWire.NET/Graph/PipeWireMetadataProvider.cs b/src/PipeWire.NET/Graph/PipeWireMetadataProvider.cs
index ef8d3d8..a5c91ad 100644
--- a/src/PipeWire.NET/Graph/PipeWireMetadataProvider.cs
+++ b/src/PipeWire.NET/Graph/PipeWireMetadataProvider.cs
@@ -50,7 +50,10 @@ public sealed unsafe partial class PipeWireMetadataProvider : IDisposable, IAsyn
{
private readonly PipeWireContext _ctx;
private readonly ILogger _logger;
- private readonly ConcurrentDictionary<(uint Subject, string Key), PipeWireMetadataEntry> _entries = new();
+ private readonly ConcurrentDictionary<
+ (uint Subject, string Key),
+ PipeWireMetadataEntry
+ > _entries = new();
private PipeWireImplMetadataHandle? _handle;
@@ -90,7 +93,9 @@ private PipeWireMetadataProvider(PipeWireContext ctx, string name, ILogger logge
public string? Get(string key, uint subject = PipeWireMetadataProxy.SubjectCore)
{
ArgumentNullException.ThrowIfNull(key);
- return _entries.TryGetValue((subject, key), out PipeWireMetadataEntry? entry) ? entry.Value : null;
+ return _entries.TryGetValue((subject, key), out PipeWireMetadataEntry? entry)
+ ? entry.Value
+ : null;
}
///
@@ -129,12 +134,20 @@ private PipeWireMetadataProvider(PipeWireContext ctx, string name, ILogger logge
///
/// is null or empty.
/// The daemon refused to register it.
- public static PipeWireMetadataProvider Create(PipeWireContext ctx, string name, bool export = true)
+ public static PipeWireMetadataProvider Create(
+ PipeWireContext ctx,
+ string name,
+ bool export = true
+ )
{
ArgumentNullException.ThrowIfNull(ctx);
ArgumentException.ThrowIfNullOrEmpty(name);
- var provider = new PipeWireMetadataProvider(ctx, name, ctx.LoggerFactory.CreateLogger("PipeWire.NET.MetadataProvider"))
+ var provider = new PipeWireMetadataProvider(
+ ctx,
+ name,
+ ctx.LoggerFactory.CreateLogger("PipeWire.NET.MetadataProvider")
+ )
{
_export = export,
};
@@ -168,10 +181,17 @@ private void Start()
pw_impl_metadata* impl;
fixed (byte* n = nameUtf8)
impl = Native.pw_context_create_metadata(
- _ctx.ContextHandle, (sbyte*)n, Native.pw_properties_new_dict(&dict), 0);
+ _ctx.ContextHandle,
+ (sbyte*)n,
+ Native.pw_properties_new_dict(&dict),
+ 0
+ );
if (impl is null)
- throw new PipeWireInteropException("pw_context_create_metadata", -NativeLibc.ENOMEM);
+ throw new PipeWireInteropException(
+ "pw_context_create_metadata",
+ -NativeLibc.ENOMEM
+ );
_handle = new PipeWireImplMetadataHandle(impl, _ctx.LoopOwner);
@@ -185,13 +205,21 @@ private void Start()
_handle.OwnListener(_events, _hook, _self);
- Native.pw_impl_metadata_add_listener(impl, _hook, table, (void*)GCHandle.ToIntPtr(_self));
+ Native.pw_impl_metadata_add_listener(
+ impl,
+ _hook,
+ table,
+ (void*)GCHandle.ToIntPtr(_self)
+ );
// Register publishes a global in this client's own context. Exporting is what makes it
// reach the daemon, and through it every other client.
if (!_export)
{
- int rc = Native.pw_impl_metadata_register(impl, Native.pw_properties_new_dict(&dict));
+ int rc = Native.pw_impl_metadata_register(
+ impl,
+ Native.pw_properties_new_dict(&dict)
+ );
if (rc < 0)
throw new PipeWireInteropException("pw_impl_metadata_register", rc);
}
@@ -208,7 +236,9 @@ private void Start()
exportProps.Add(NativeConstants.PW_KEY_METADATA_NAME, Name);
spa_dict exportDict = exportProps.Build();
- ReadOnlySpan typeUtf8 = Encoding.UTF8.GetBytes(PipeWireKeys.PW_TYPE_INTERFACE_Metadata + '\0');
+ ReadOnlySpan typeUtf8 = Encoding.UTF8.GetBytes(
+ PipeWireKeys.PW_TYPE_INTERFACE_Metadata + '\0'
+ );
pw_proxy* exported = null;
if (_export)
@@ -220,12 +250,20 @@ private void Start()
pw_metadata* implementation = Native.pw_impl_metadata_get_implementation(impl);
if (implementation is null)
- throw new PipeWireInteropException("pw_impl_metadata_get_implementation", -NativeLibc.EINVAL);
+ throw new PipeWireInteropException(
+ "pw_impl_metadata_get_implementation",
+ -NativeLibc.EINVAL
+ );
fixed (byte* t = typeUtf8)
{
exported = Native.pw_core_export(
- _ctx.CoreHandle, (sbyte*)t, &exportDict, implementation, 0);
+ _ctx.CoreHandle,
+ (sbyte*)t,
+ &exportDict,
+ implementation,
+ 0
+ );
}
}
@@ -250,7 +288,8 @@ internal unsafe uint? ServedGlobalId
get
{
PipeWireProxyHandle? exported = _exported;
- if (exported is null || exported.IsInvalid) return null;
+ if (exported is null || exported.IsInvalid)
+ return null;
uint id = Native.pw_proxy_get_bound_id(exported.Proxy);
return id == NativeConstants.SPA_ID_INVALID ? null : id;
}
@@ -296,7 +335,8 @@ public void Set(
string key,
string? value,
string? type = null,
- uint subject = PipeWireMetadataProxy.SubjectCore)
+ uint subject = PipeWireMetadataProxy.SubjectCore
+ )
{
ArgumentNullException.ThrowIfNull(key);
ThrowIfContainsNul(key, nameof(key));
@@ -325,7 +365,9 @@ private void SetProperty(uint subject, string? key, string? type, string? value)
ReadOnlySpan keyUtf8 = key is null ? default : Encoding.UTF8.GetBytes(key + '\0');
ReadOnlySpan typeUtf8 = type is null ? default : Encoding.UTF8.GetBytes(type + '\0');
- ReadOnlySpan valueUtf8 = value is null ? default : Encoding.UTF8.GetBytes(value + '\0');
+ ReadOnlySpan valueUtf8 = value is null
+ ? default
+ : Encoding.UTF8.GetBytes(value + '\0');
// No round-trip: the store is ours, so the write lands in this process and the callback
// that updates the cache runs before this returns.
@@ -339,12 +381,15 @@ private void SetProperty(uint subject, string? key, string? type, string? value)
fixed (byte* v = valueUtf8)
{
int rc = Native.pw_impl_metadata_set_property(
- _handle.Metadata, subject,
+ _handle.Metadata,
+ subject,
key is null ? null : (sbyte*)k,
type is null ? null : (sbyte*)t,
- value is null ? null : (sbyte*)v);
+ value is null ? null : (sbyte*)v
+ );
- if (rc < 0) throw new PipeWireInteropException("pw_impl_metadata_set_property", rc);
+ if (rc < 0)
+ throw new PipeWireInteropException("pw_impl_metadata_set_property", rc);
}
}
}
@@ -370,15 +415,23 @@ public void Clear()
}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
- private static int OnPropertyCallback(void* data, uint subject, sbyte* key, sbyte* type, sbyte* value)
+ private static int OnPropertyCallback(
+ void* data,
+ uint subject,
+ sbyte* key,
+ sbyte* type,
+ sbyte* value
+ )
{
- if (data is null) return 0;
+ if (data is null)
+ return 0;
PipeWireMetadataProvider self;
try
{
// A freed handle throws out of the lookup, and this is a native frame.
- if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireMetadataProvider found) return 0;
+ if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireMetadataProvider found)
+ return 0;
self = found;
}
catch (Exception)
@@ -416,9 +469,12 @@ private void Apply(uint subject, string? key, string? type, string? value)
foreach ((uint Subject, string Key) existing in _entries.Keys)
{
- if (!everySubject && existing.Subject != subject) continue;
+ if (!everySubject && existing.Subject != subject)
+ continue;
if (_entries.TryRemove(existing, out PipeWireMetadataEntry? removed))
- Raise(new PipeWireMetadataEntry(removed.Subject, removed.Key, removed.Type, null));
+ Raise(
+ new PipeWireMetadataEntry(removed.Subject, removed.Key, removed.Type, null)
+ );
}
return;
@@ -426,8 +482,10 @@ private void Apply(uint subject, string? key, string? type, string? value)
var entry = new PipeWireMetadataEntry(subject, key, type, value);
- if (value is null) _entries.TryRemove((subject, key), out _);
- else _entries[(subject, key)] = entry;
+ if (value is null)
+ _entries.TryRemove((subject, key), out _);
+ else
+ _entries[(subject, key)] = entry;
Raise(entry);
}
@@ -449,7 +507,8 @@ public ValueTask DisposeAsync()
private void DisposeCore()
{
- if (_disposed) return;
+ if (_disposed)
+ return;
_disposed = true;
// The listener's memory goes with the handle, freed after the implementation is destroyed.
@@ -466,23 +525,40 @@ private void DisposeCore()
_entries.Clear();
}
- [LoggerMessage(EventId = 34203, Level = LogLevel.Information,
- Message = "exported metadata store {Name}; other clients can bind it")]
+ [LoggerMessage(
+ EventId = 34203,
+ Level = LogLevel.Information,
+ Message = "exported metadata store {Name}; other clients can bind it"
+ )]
private partial void LogExported(string name);
- [LoggerMessage(EventId = 34204, Level = LogLevel.Warning,
+ [LoggerMessage(
+ EventId = 34204,
+ Level = LogLevel.Warning,
Message = "metadata store {Name} could not be exported, so only this client can see it. "
- + "The context has no export type for Metadata, which libpipewire-module-metadata "
- + "registers and client.conf loads unless module.metadata is turned off.")]
+ + "The context has no export type for Metadata, which libpipewire-module-metadata "
+ + "registers and client.conf loads unless module.metadata is turned off."
+ )]
private partial void LogNotExported(string name);
- [LoggerMessage(EventId = 34200, Level = LogLevel.Information, Message = "serving metadata store {Name}")]
+ [LoggerMessage(
+ EventId = 34200,
+ Level = LogLevel.Information,
+ Message = "serving metadata store {Name}"
+ )]
private partial void LogRegistered(string name);
- [LoggerMessage(EventId = 34201, Level = LogLevel.Error, Message = "dispatching a metadata change failed")]
+ [LoggerMessage(
+ EventId = 34201,
+ Level = LogLevel.Error,
+ Message = "dispatching a metadata change failed"
+ )]
private partial void LogDispatchFailed(Exception ex);
- [LoggerMessage(EventId = 34202, Level = LogLevel.Error,
- Message = "an EntryChanged handler for store {Name} threw")]
+ [LoggerMessage(
+ EventId = 34202,
+ Level = LogLevel.Error,
+ Message = "an EntryChanged handler for store {Name} threw"
+ )]
private partial void LogHandlerFaulted(string name, Exception ex);
}
diff --git a/src/PipeWire.NET/Graph/PipeWireMetadataProxy.cs b/src/PipeWire.NET/Graph/PipeWireMetadataProxy.cs
index d07b50f..70e4faf 100644
--- a/src/PipeWire.NET/Graph/PipeWireMetadataProxy.cs
+++ b/src/PipeWire.NET/Graph/PipeWireMetadataProxy.cs
@@ -1,6 +1,6 @@
using System.Collections.Concurrent;
-using System.Globalization;
using System.Diagnostics;
+using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
@@ -38,7 +38,10 @@ public sealed partial class PipeWireMetadataProxy : IDisposable, IAsyncDisposabl
private readonly PipeWireContext _ctx;
private readonly ILogger _logger;
- private readonly ConcurrentDictionary<(uint Subject, string Key), PipeWireMetadataEntry> _entries = new();
+ private readonly ConcurrentDictionary<
+ (uint Subject, string Key),
+ PipeWireMetadataEntry
+ > _entries = new();
private readonly MetadataReconciler _reconciler = new(TimeSpan.FromSeconds(5));
@@ -67,11 +70,21 @@ private PipeWireMetadataProxy(PipeWireContext ctx, uint id, ILogger logger)
public event Action? EntryChanged;
internal static unsafe PipeWireMetadataProxy Bind(
- PipeWireContext ctx, pw_registry* registry, uint id, uint version, ILogger logger)
+ PipeWireContext ctx,
+ pw_registry* registry,
+ uint id,
+ uint version,
+ ILogger logger
+ )
{
var store = new PipeWireMetadataProxy(ctx, id, logger);
store._bound = BoundProxy.Bind(
- ctx, registry, id, PipeWireKeys.PW_TYPE_INTERFACE_Metadata, version, NativeConstants.PW_VERSION_METADATA,
+ ctx,
+ registry,
+ id,
+ PipeWireKeys.PW_TYPE_INTERFACE_Metadata,
+ version,
+ NativeConstants.PW_VERSION_METADATA,
sizeof(pw_metadata_events),
events =>
{
@@ -79,9 +92,15 @@ internal static unsafe PipeWireMetadataProxy Bind(
table->version = NativeConstants.PW_VERSION_METADATA_EVENTS;
table->property = &OnPropertyCallback;
},
- static (proxy, hook, events, data) => Native.pw_metadata_add_listener(
- (pw_metadata*)proxy, (spa_hook*)hook, (pw_metadata_events*)events, (void*)data),
- store);
+ static (proxy, hook, events, data) =>
+ Native.pw_metadata_add_listener(
+ (pw_metadata*)proxy,
+ (spa_hook*)hook,
+ (pw_metadata_events*)events,
+ (void*)data
+ ),
+ store
+ );
store._bound.Removed = store.RaiseRemoved;
@@ -102,14 +121,19 @@ internal static unsafe PipeWireMetadataProxy Bind(
private void RaiseRemoved()
{
Action? handler = Removed;
- if (handler is null) return;
+ if (handler is null)
+ return;
// A native callback frame, so nothing may escape it.
- try { handler(); }
- catch (Exception) { /* a subscriber that throws must not reach the daemon */ }
+ try
+ {
+ handler();
+ }
+ catch (Exception)
+ { /* a subscriber that throws must not reach the daemon */
+ }
}
-
///
/// Waits for the store to have sent everything it holds.
///
@@ -151,7 +175,9 @@ private static void ThrowIfContainsNul(string? text, string paramName)
public string? Get(string key, uint subject = SubjectCore)
{
ArgumentNullException.ThrowIfNull(key);
- return _entries.TryGetValue((subject, key), out PipeWireMetadataEntry? entry) ? entry.Value : null;
+ return _entries.TryGetValue((subject, key), out PipeWireMetadataEntry? entry)
+ ? entry.Value
+ : null;
}
///
@@ -186,7 +212,8 @@ public async Task SetAsync(
string? value,
string? type = null,
uint subject = SubjectCore,
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
ArgumentNullException.ThrowIfNull(key);
ThrowIfContainsNul(key, nameof(key));
@@ -227,7 +254,8 @@ public async Task SetAsync(
issued = true;
return Write(subject, key, type, value);
},
- cancellationToken);
+ cancellationToken
+ );
// Raised here, not when the echo lands. A write superseded before its echo arrives
// never gets one, and a caller should still be told about a change it made. Skipped
@@ -258,8 +286,10 @@ public async Task SetAsync(
// does not roll back - the request is already on its way and the daemon may still apply
// it, so the optimistic value remains the better guess there.
Rollback(subject, key, applied, previous, didApply);
- if (!issued) _reconciler.Forget(subject, key, pending);
- else _reconciler.Settle(subject, key);
+ if (!issued)
+ _reconciler.Forget(subject, key, pending);
+ else
+ _reconciler.Settle(subject, key);
throw;
}
catch
@@ -279,8 +309,10 @@ public async Task SetAsync(
// Records age from acknowledgement, and a write that went out and then failed will
// never get one. Starting the clock here is what keeps it from being kept for ever;
// it stays recognisable for the window, which is the whole reason it was not forgotten.
- if (!issued) _reconciler.Forget(subject, key, pending);
- else _reconciler.Settle(subject, key);
+ if (!issued)
+ _reconciler.Forget(subject, key, pending);
+ else
+ _reconciler.Settle(subject, key);
throw;
}
}
@@ -299,9 +331,11 @@ private void Rollback(
string key,
PipeWireMetadataEntry applied,
PipeWireMetadataEntry? previous,
- bool didApply)
+ bool didApply
+ )
{
- if (!didApply) return;
+ if (!didApply)
+ return;
lock (_clearGate)
{
@@ -310,9 +344,10 @@ private void Rollback(
bool stillOurs = now is null
? applied.Value is null
: string.Equals(now.Value, applied.Value, StringComparison.Ordinal)
- && string.Equals(now.Type, applied.Type, StringComparison.Ordinal);
+ && string.Equals(now.Type, applied.Type, StringComparison.Ordinal);
- if (!stillOurs) return;
+ if (!stillOurs)
+ return;
if (previous is null)
_entries.TryRemove((subject, key), out _);
@@ -400,8 +435,13 @@ private void Apply(PipeWireMetadataEntry entry)
public Task SetForcedQuantumAsync(int samples, CancellationToken cancellationToken = default)
{
ArgumentOutOfRangeException.ThrowIfNegative(samples);
- return SetAsync("clock.force-quantum", samples.ToString(CultureInfo.InvariantCulture),
- null, SubjectCore, cancellationToken);
+ return SetAsync(
+ "clock.force-quantum",
+ samples.ToString(CultureInfo.InvariantCulture),
+ null,
+ SubjectCore,
+ cancellationToken
+ );
}
/// Pins the graph to one sample rate, or releases it.
@@ -417,13 +457,19 @@ public Task SetForcedQuantumAsync(int samples, CancellationToken cancellationTok
public Task SetForcedRateAsync(int hz, CancellationToken cancellationToken = default)
{
ArgumentOutOfRangeException.ThrowIfNegative(hz);
- return SetAsync("clock.force-rate", hz.ToString(CultureInfo.InvariantCulture),
- null, SubjectCore, cancellationToken);
+ return SetAsync(
+ "clock.force-rate",
+ hz.ToString(CultureInfo.InvariantCulture),
+ null,
+ SubjectCore,
+ cancellationToken
+ );
}
/// Reads a settings value that is a bare integer, or null if it is absent or not one.
private int? Integer(string key) =>
- Find(key)?.Value is { } raw && int.TryParse(raw, CultureInfo.InvariantCulture, out int value)
+ Find(key)?.Value is { } raw
+ && int.TryParse(raw, CultureInfo.InvariantCulture, out int value)
? value
: null;
@@ -438,11 +484,19 @@ public Task SetForcedRateAsync(int hz, CancellationToken cancellationToken = def
/// objects come and go, so a default stored by id would drift onto a different device.
///
/// is null or empty.
- public Task SetDefaultAudioSinkAsync(string nodeName, CancellationToken cancellationToken = default)
+ public Task SetDefaultAudioSinkAsync(
+ string nodeName,
+ CancellationToken cancellationToken = default
+ )
{
ArgumentException.ThrowIfNullOrEmpty(nodeName);
- return SetAsync("default.audio.sink", NameJson(nodeName), "Spa:String:JSON",
- SubjectCore, cancellationToken);
+ return SetAsync(
+ "default.audio.sink",
+ NameJson(nodeName),
+ "Spa:String:JSON",
+ SubjectCore,
+ cancellationToken
+ );
}
/// Sets the default audio source by node name.
@@ -453,11 +507,19 @@ public Task SetDefaultAudioSinkAsync(string nodeName, CancellationToken cancella
///
///
/// is null or empty.
- public Task SetDefaultAudioSourceAsync(string nodeName, CancellationToken cancellationToken = default)
+ public Task SetDefaultAudioSourceAsync(
+ string nodeName,
+ CancellationToken cancellationToken = default
+ )
{
ArgumentException.ThrowIfNullOrEmpty(nodeName);
- return SetAsync("default.audio.source", NameJson(nodeName), "Spa:String:JSON",
- SubjectCore, cancellationToken);
+ return SetAsync(
+ "default.audio.source",
+ NameJson(nodeName),
+ "Spa:String:JSON",
+ SubjectCore,
+ cancellationToken
+ );
}
/// Removes every entry in the store.
@@ -512,7 +574,9 @@ private unsafe int Write(uint subject, string key, string? type, string? value)
{
ReadOnlySpan keyUtf8 = Encoding.UTF8.GetBytes(key + '\0');
ReadOnlySpan typeUtf8 = type is null ? default : Encoding.UTF8.GetBytes(type + '\0');
- ReadOnlySpan valueUtf8 = value is null ? default : Encoding.UTF8.GetBytes(value + '\0');
+ ReadOnlySpan valueUtf8 = value is null
+ ? default
+ : Encoding.UTF8.GetBytes(value + '\0');
// Referenced for the duration of the call. Destroying a proxy clears its pointer before it
// takes the loop lock, so the lock alone does not stop this becoming null mid-call.
@@ -527,9 +591,12 @@ private unsafe int Write(uint subject, string key, string? type, string? value)
fixed (byte* v = valueUtf8)
{
return Native.pw_metadata_set_property(
- (pw_metadata*)proxy.Object, subject, (sbyte*)k,
+ (pw_metadata*)proxy.Object,
+ subject,
+ (sbyte*)k,
type is null ? null : (sbyte*)t,
- value is null ? null : (sbyte*)v);
+ value is null ? null : (sbyte*)v
+ );
}
}
}
@@ -558,9 +625,12 @@ private void OnProperty(uint subject, string? key, string? type, string? value)
// that the entries went.
foreach ((uint Subject, string Key) existing in _entries.Keys)
{
- if (!everySubject && existing.Subject != subject) continue;
+ if (!everySubject && existing.Subject != subject)
+ continue;
if (_entries.TryRemove(existing, out PipeWireMetadataEntry? removed))
- Raise(new PipeWireMetadataEntry(removed.Subject, removed.Key, removed.Type, null));
+ Raise(
+ new PipeWireMetadataEntry(removed.Subject, removed.Key, removed.Type, null)
+ );
}
return;
@@ -569,38 +639,51 @@ private void OnProperty(uint subject, string? key, string? type, string? value)
var entry = new PipeWireMetadataEntry(subject, key, type, value);
MetadataReconciler.EchoAction action = _reconciler.Classify(subject, key, type, value);
- if (action == MetadataReconciler.EchoAction.Drop) return;
+ if (action == MetadataReconciler.EchoAction.Drop)
+ return;
lock (_clearGate)
{
// A clear landed between this event being dispatched and being handled, so whatever
// this reports is from before the store was emptied. Applying it now would put a
// cleared entry back, and the correcting event for that never comes.
- if (Volatile.Read(ref _epoch) != epoch) return;
+ if (Volatile.Read(ref _epoch) != epoch)
+ return;
Apply(entry);
}
// Outside the gate. Subscribers are user code running on the loop thread, and holding a
// lock across them lets one that waits on anything else stall every writer.
- if (action != MetadataReconciler.EchoAction.AlreadyKnown) Raise(entry);
+ if (action != MetadataReconciler.EchoAction.AlreadyKnown)
+ Raise(entry);
}
/// Reports one change to subscribers, isolating a handler that throws.
private void Raise(PipeWireMetadataEntry entry)
{
- SafeCallback.Raise(EntryChanged, h => h(this, entry), ex => LogHandlerFaulted(Id, entry.Key, ex));
+ SafeCallback.Raise(
+ EntryChanged,
+ h => h(this, entry),
+ ex => LogHandlerFaulted(Id, entry.Key, ex)
+ );
}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe int OnPropertyCallback(
- void* data, uint subject, sbyte* key, sbyte* type, sbyte* value)
+ void* data,
+ uint subject,
+ sbyte* key,
+ sbyte* type,
+ sbyte* value
+ )
{
// An exception escaping a reverse P/Invoke aborts the process, so nothing here may throw.
try
{
var self = (PipeWireMetadataProxy?)GCHandle.FromIntPtr((nint)data).Target;
- if (self is null || self._disposed) return 0;
+ if (self is null || self._disposed)
+ return 0;
self.OnProperty(subject, Utf8(key), Utf8(type), Utf8(value));
}
@@ -612,8 +695,7 @@ private static unsafe int OnPropertyCallback(
return 0;
static string? Utf8(sbyte* p) =>
- p is null ? null : Encoding.UTF8.GetString(
- DaemonText.Bytes((sbyte*)p));
+ p is null ? null : Encoding.UTF8.GetString(DaemonText.Bytes((sbyte*)p));
}
///
@@ -634,7 +716,8 @@ public ValueTask DisposeAsync()
private void DisposeCore()
{
- if (_disposed) return;
+ if (_disposed)
+ return;
_disposed = true;
_bound?.Dispose();
@@ -645,7 +728,10 @@ private void DisposeCore()
GC.SuppressFinalize(this);
}
- [LoggerMessage(EventId = 33200, Level = LogLevel.Error,
- Message = "an EntryChanged handler for store {StoreId} key {Key} threw")]
+ [LoggerMessage(
+ EventId = 33200,
+ Level = LogLevel.Error,
+ Message = "an EntryChanged handler for store {StoreId} key {Key} threw"
+ )]
private partial void LogHandlerFaulted(uint storeId, string key, Exception exception);
}
diff --git a/src/PipeWire.NET/Graph/PipeWireModule.cs b/src/PipeWire.NET/Graph/PipeWireModule.cs
index f56b08a..468ce7f 100644
--- a/src/PipeWire.NET/Graph/PipeWireModule.cs
+++ b/src/PipeWire.NET/Graph/PipeWireModule.cs
@@ -20,7 +20,8 @@ internal PipeWireModule(
string? Description,
string? Author,
string? ModuleVersion,
- PipeWireProperties? Properties = null)
+ PipeWireProperties? Properties = null
+ )
{
this.Properties = Properties ?? PipeWireProperties.Empty;
this.ObjectSerial = this.Properties.Serial;
diff --git a/src/PipeWire.NET/Graph/PipeWireModuleProxy.cs b/src/PipeWire.NET/Graph/PipeWireModuleProxy.cs
index 22c96b8..1196dfb 100644
--- a/src/PipeWire.NET/Graph/PipeWireModuleProxy.cs
+++ b/src/PipeWire.NET/Graph/PipeWireModuleProxy.cs
@@ -17,8 +17,9 @@ namespace PipeWire.NET.Graph;
[SupportedOSPlatform("linux")]
internal sealed class PipeWireModuleProxy : IDisposable
{
- private readonly TaskCompletionSource _info =
- new(TaskCreationOptions.RunContinuationsAsynchronously);
+ private readonly TaskCompletionSource _info = new(
+ TaskCreationOptions.RunContinuationsAsynchronously
+ );
private BoundProxy? _bound;
@@ -26,11 +27,20 @@ internal sealed class PipeWireModuleProxy : IDisposable
internal Task Properties => _info.Task;
internal static unsafe PipeWireModuleProxy Bind(
- PipeWireContext ctx, pw_registry* registry, uint id, uint version)
+ PipeWireContext ctx,
+ pw_registry* registry,
+ uint id,
+ uint version
+ )
{
var reader = new PipeWireModuleProxy();
reader._bound = BoundProxy.Bind(
- ctx, registry, id, PipeWireKeys.PW_TYPE_INTERFACE_Module, version, NativeConstants.PW_VERSION_MODULE,
+ ctx,
+ registry,
+ id,
+ PipeWireKeys.PW_TYPE_INTERFACE_Module,
+ version,
+ NativeConstants.PW_VERSION_MODULE,
sizeof(pw_module_events),
events =>
{
@@ -38,9 +48,15 @@ internal static unsafe PipeWireModuleProxy Bind(
table->version = NativeConstants.PW_VERSION_MODULE_EVENTS;
table->info = &OnInfoCallback;
},
- static (proxy, hook, events, data) => Native.pw_module_add_listener(
- (pw_module*)proxy, (spa_hook*)hook, (pw_module_events*)events, (void*)data),
- reader);
+ static (proxy, hook, events, data) =>
+ Native.pw_module_add_listener(
+ (pw_module*)proxy,
+ (spa_hook*)hook,
+ (pw_module_events*)events,
+ (void*)data
+ ),
+ reader
+ );
reader._bound.Removed = reader.RaiseRemoved;
@@ -61,31 +77,41 @@ internal static unsafe PipeWireModuleProxy Bind(
private void RaiseRemoved()
{
Action? handler = Removed;
- if (handler is null) return;
+ if (handler is null)
+ return;
// A native callback frame, so nothing may escape it.
- try { handler(); }
- catch (Exception) { /* a subscriber that throws must not reach the daemon */ }
+ try
+ {
+ handler();
+ }
+ catch (Exception)
+ { /* a subscriber that throws must not reach the daemon */
+ }
}
-
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe void OnInfoCallback(void* data, pw_module_info* info)
{
// A native callback frame: an escaping exception aborts the process.
try
{
- if (data is null || info is null) return;
- if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireModuleProxy self) return;
+ if (data is null || info is null)
+ return;
+ if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireModuleProxy self)
+ return;
// filename and args are not properties, but they are the two facts a reader of a module
// most often wants and they arrive nowhere else, so they travel as properties here.
var extra = new Dictionary(StringComparer.Ordinal);
- if (info->filename is not null) extra[PipeWireKeys.MODULE_FILENAME] = DaemonText.String(info->filename)!;
- if (info->args is not null) extra[PipeWireKeys.MODULE_ARGS] = DaemonText.String(info->args)!;
+ if (info->filename is not null)
+ extra[PipeWireKeys.MODULE_FILENAME] = DaemonText.String(info->filename)!;
+ if (info->args is not null)
+ extra[PipeWireKeys.MODULE_ARGS] = DaemonText.String(info->args)!;
self._info.TrySetResult(
- PipeWireProperties.From(info->props).MergedWith(PipeWireProperties.FromItems(extra)));
+ PipeWireProperties.From(info->props).MergedWith(PipeWireProperties.FromItems(extra))
+ );
}
catch (Exception)
{
diff --git a/src/PipeWire.NET/Graph/PipeWireNode.cs b/src/PipeWire.NET/Graph/PipeWireNode.cs
index 9cf2820..665facd 100644
--- a/src/PipeWire.NET/Graph/PipeWireNode.cs
+++ b/src/PipeWire.NET/Graph/PipeWireNode.cs
@@ -22,7 +22,8 @@ internal PipeWireNode(
string? ObjectPath = null,
int? DriverPriority = null,
int? SessionPriority = null,
- PipeWireProperties? Properties = null)
+ PipeWireProperties? Properties = null
+ )
{
this.Properties = Properties ?? PipeWireProperties.Empty;
this.ObjectSerial = this.Properties.Serial;
diff --git a/src/PipeWire.NET/Graph/PipeWireNodeBuilder.cs b/src/PipeWire.NET/Graph/PipeWireNodeBuilder.cs
index e960d1f..05dbf1e 100644
--- a/src/PipeWire.NET/Graph/PipeWireNodeBuilder.cs
+++ b/src/PipeWire.NET/Graph/PipeWireNodeBuilder.cs
@@ -27,7 +27,11 @@ public readonly struct PipeWireNodeBuilder
private readonly PipeWireObjectOptions _options;
internal PipeWireNodeBuilder(
- PipeWireRegistry registry, string description, string? name, PipeWireObjectOptions options)
+ PipeWireRegistry registry,
+ string description,
+ string? name,
+ PipeWireObjectOptions options
+ )
{
_registry = registry;
_description = description;
@@ -82,11 +86,19 @@ public PipeWireNodeBuilder WithProperty(string key, string value)
ArgumentNullException.ThrowIfNull(value);
PipeWireObjectOptions.ThrowIfReserved(key, forLink: false);
- ImmutableArray> existing =
- _options.Properties.IsDefault ? [] : _options.Properties;
-
- return new(_registry, _description, _name,
- _options with { Properties = existing.Add(new(key, value)) });
+ ImmutableArray> existing = _options.Properties.IsDefault
+ ? []
+ : _options.Properties;
+
+ return new(
+ _registry,
+ _description,
+ _name,
+ _options with
+ {
+ Properties = existing.Add(new(key, value)),
+ }
+ );
}
/// Sets media.class: whether the node is read from or written to.
@@ -135,8 +147,9 @@ public PipeWireNodeBuilder WithTarget(PipeWireNode node)
{
throw new ArgumentException(
$"node {node.NodeId} has no node.name, so there is nothing to target it by. "
- + "Target it by object.serial instead.",
- nameof(node));
+ + "Target it by object.serial instead.",
+ nameof(node)
+ );
}
return WithTarget(node.NodeName!);
diff --git a/src/PipeWire.NET/Graph/PipeWireNodeProvider.cs b/src/PipeWire.NET/Graph/PipeWireNodeProvider.cs
index 5bfc094..0da4bf6 100644
--- a/src/PipeWire.NET/Graph/PipeWireNodeProvider.cs
+++ b/src/PipeWire.NET/Graph/PipeWireNodeProvider.cs
@@ -54,12 +54,14 @@ public sealed unsafe partial class PipeWireNodeProvider : IDisposable, IAsyncDis
private GCHandle _self;
private spa_node* _node;
private spa_node_methods* _methods;
+
// What the graph installed for this node to call back into. `ready` and `reuse_buffer` stay
// unused deliberately - a null `ready` is how a node asks for synchronous operation, and the
// buffers to reuse go through the input port's io area - but `xrun` is how a node reports that
// it missed a cycle, and nothing else can report that on its behalf.
private spa_node_callbacks* _callbacks;
private void* _callbacksData;
+
// Every listener on this node, not just the one that exported it: the audio adapter that wraps
// it keeps one for good, and synchronous queries add and remove their own (see SpaHookList).
private spa_hook_list* _hooks;
@@ -119,7 +121,8 @@ private PipeWireNodeProvider(PipeWireContext ctx, string name, SpaDirection dire
public unsafe bool ReportXrun(ulong triggerMicroseconds, ulong delayMicroseconds)
{
spa_node_callbacks* callbacks = _callbacks;
- if (_disposed || callbacks is null || callbacks->xrun is null) return false;
+ if (_disposed || callbacks is null || callbacks->xrun is null)
+ return false;
return callbacks->xrun(_callbacksData, triggerMicroseconds, delayMicroseconds, null) >= 0;
}
@@ -211,7 +214,8 @@ public static PipeWireNodeProvider Create(
string name,
PipeWireExportedFormat format,
SpaDirection direction = SpaDirection.Output,
- IReadOnlyDictionary? properties = null)
+ IReadOnlyDictionary? properties = null
+ )
{
ArgumentNullException.ThrowIfNull(ctx);
ArgumentNullException.ThrowIfNull(format);
@@ -254,7 +258,8 @@ public static PipeWireNodeProvider FromSpaFactory(
PipeWireContext ctx,
string factoryName,
IReadOnlyDictionary? properties = null,
- string? libraryName = null)
+ string? libraryName = null
+ )
{
ArgumentNullException.ThrowIfNull(ctx);
ArgumentException.ThrowIfNullOrEmpty(factoryName);
@@ -267,11 +272,15 @@ public static PipeWireNodeProvider FromSpaFactory(
"node",
properties,
libraryName,
- out node._spaHandle);
+ out node._spaHandle
+ );
return node;
}
- private void Initialize(PipeWireExportedFormat format, IReadOnlyDictionary? properties)
+ private void Initialize(
+ PipeWireExportedFormat format,
+ IReadOnlyDictionary? properties
+ )
{
OfferedFormat = format;
@@ -323,7 +332,8 @@ private void Initialize(PipeWireExportedFormat format, IReadOnlyDictionary pair in properties) props[pair.Key] = pair.Value;
+ foreach (KeyValuePair pair in properties)
+ props[pair.Key] = pair.Value;
}
// Wrapped in the adapter, the way pw_stream wraps its own node for audio and video
@@ -354,13 +364,15 @@ private void Initialize(PipeWireExportedFormat format, IReadOnlyDictionary scratch = stackalloc byte[2048];
Span items = stackalloc spa_dict_item[24];
var dict = new SpaDictBuilder(scratch, items);
- foreach (KeyValuePair pair in props) dict.Add(pair.Key, pair.Value);
+ foreach (KeyValuePair pair in props)
+ dict.Add(pair.Key, pair.Value);
spa_dict built = dict.Build();
// The factory takes ownership of the properties, as stream.c's does.
@@ -368,8 +380,15 @@ private void Initialize(PipeWireExportedFormat format, IReadOnlyDictionary
public ValueTask DisposeAsync()
{
- if (_disposed) return ValueTask.CompletedTask;
+ if (_disposed)
+ return ValueTask.CompletedTask;
_disposed = true;
using (_ctx.Lock())
@@ -450,7 +471,8 @@ public ValueTask DisposeAsync()
///
private void DestroyAdapter()
{
- if (_adapter is null) return;
+ if (_adapter is null)
+ return;
Native.pw_impl_node_destroy(_adapter);
_adapter = null;
@@ -462,21 +484,36 @@ private void Cleanup()
// adapter was never created or has just been; taking the lock again is harmless (recursive).
if (_adapter is not null)
{
- using (_ctx.Lock()) DestroyAdapter();
+ using (_ctx.Lock())
+ DestroyAdapter();
}
// The factory's node lives in the handle, so clearing it is what actually releases the
// plugin's resources; the proxy only removed it from the graph.
if (_spaHandle is not null)
{
- if (_spaHandle->clear is not null) _ = _spaHandle->clear(_spaHandle);
+ if (_spaHandle->clear is not null)
+ _ = _spaHandle->clear(_spaHandle);
_spaHandle = null;
}
- if (_node is not null) { NativeMemory.Free(_node); _node = null; }
- if (_hooks is not null) { NativeMemory.Free(_hooks); _hooks = null; }
- if (_methods is not null) { NativeMemory.Free(_methods); _methods = null; }
- if (_self.IsAllocated) _self.Free();
+ if (_node is not null)
+ {
+ NativeMemory.Free(_node);
+ _node = null;
+ }
+ if (_hooks is not null)
+ {
+ NativeMemory.Free(_hooks);
+ _hooks = null;
+ }
+ if (_methods is not null)
+ {
+ NativeMemory.Free(_methods);
+ _methods = null;
+ }
+ if (_self.IsAllocated)
+ _self.Free();
}
private static PipeWireNodeProvider? From(void* data) =>
@@ -493,7 +530,8 @@ private static int OnAddListener(void* obj, spa_hook* hook, spa_node_events* eve
try
{
PipeWireNodeProvider? self = From(obj);
- if (self is null) return -NativeLibc.EINVAL;
+ if (self is null)
+ return -NativeLibc.EINVAL;
if (self._hooks is null || hook is null)
{
@@ -550,14 +588,16 @@ private void EmitInfo()
///
private void EmitNodeInfo()
{
- if (_hooks is null) return;
+ if (_hooks is null)
+ return;
Span scratch = stackalloc byte[4096];
Span items = stackalloc spa_dict_item[32];
var dict = new SpaDictBuilder(scratch, items);
foreach (KeyValuePair pair in _nodeProperties)
{
- if (dict.Count == items.Length) break;
+ if (dict.Count == items.Length)
+ break;
dict.Add(pair.Key, pair.Value);
}
@@ -579,7 +619,8 @@ private void EmitNodeInfo()
next = l->next;
var h = (spa_hook*)l;
var ev = (spa_node_events*)h->cb.funcs;
- if (ev is not null && ev->info is not null) ev->info(h->cb.data, &info);
+ if (ev is not null && ev->info is not null)
+ ev->info(h->cb.data, &info);
}
}
@@ -593,14 +634,27 @@ private void EmitNodeInfo()
///
private void EmitPortInfo()
{
- if (_hooks is null) return;
+ if (_hooks is null)
+ return;
bool formatSet = NegotiatedFormat is not null;
spa_param_info* paramInfo = stackalloc spa_param_info[5];
- paramInfo[0] = new spa_param_info { id = (uint)SpaParamType.EnumFormat, flags = (uint)SpaParamInfoFlags.Read };
- paramInfo[1] = new spa_param_info { id = (uint)SpaParamType.Meta, flags = (uint)SpaParamInfoFlags.Read };
- paramInfo[2] = new spa_param_info { id = (uint)SpaParamType.Io, flags = (uint)SpaParamInfoFlags.Read };
+ paramInfo[0] = new spa_param_info
+ {
+ id = (uint)SpaParamType.EnumFormat,
+ flags = (uint)SpaParamInfoFlags.Read,
+ };
+ paramInfo[1] = new spa_param_info
+ {
+ id = (uint)SpaParamType.Meta,
+ flags = (uint)SpaParamInfoFlags.Read,
+ };
+ paramInfo[2] = new spa_param_info
+ {
+ id = (uint)SpaParamType.Io,
+ flags = (uint)SpaParamInfoFlags.Read,
+ };
paramInfo[3] = new spa_param_info
{
id = (uint)SpaParamType.Format,
@@ -626,7 +680,8 @@ private void EmitPortInfo()
next = l->next;
var h = (spa_hook*)l;
var ev = (spa_node_events*)h->cb.funcs;
- if (ev is not null && ev->port_info is not null) ev->port_info(h->cb.data, _direction, PortId, &port);
+ if (ev is not null && ev->port_info is not null)
+ ev->port_info(h->cb.data, _direction, PortId, &port);
}
}
@@ -636,7 +691,8 @@ private static int OnSetCallbacks(void* obj, spa_node_callbacks* callbacks, void
try
{
PipeWireNodeProvider? self = From(obj);
- if (self is null) return -NativeLibc.EINVAL;
+ if (self is null)
+ return -NativeLibc.EINVAL;
self._callbacks = callbacks;
self._callbacksData = data;
@@ -649,11 +705,23 @@ private static int OnSetCallbacks(void* obj, spa_node_callbacks* callbacks, void
}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
- private static int OnEnumParams(void* obj, int seq, uint id, uint start, uint num, spa_pod* filter)
+ private static int OnEnumParams(
+ void* obj,
+ int seq,
+ uint id,
+ uint start,
+ uint num,
+ spa_pod* filter
+ )
{
try
{
- _ = obj; _ = seq; _ = id; _ = start; _ = num; _ = filter;
+ _ = obj;
+ _ = seq;
+ _ = id;
+ _ = start;
+ _ = num;
+ _ = filter;
return 0;
}
catch (Exception ex)
@@ -667,7 +735,10 @@ private static int OnSetParam(void* obj, uint id, uint flags, spa_pod* param)
{
try
{
- _ = obj; _ = id; _ = flags; _ = param;
+ _ = obj;
+ _ = id;
+ _ = flags;
+ _ = param;
return 0;
}
catch (Exception ex)
@@ -682,7 +753,8 @@ private static int OnSetIo(void* obj, uint id, void* area, nuint size)
try
{
PipeWireNodeProvider? self = From(obj);
- if (self is null) return -NativeLibc.EINVAL;
+ if (self is null)
+ return -NativeLibc.EINVAL;
// Return codes are spa/node/node.h's contract for set_io, and audioconvert's behaviour:
// -ENOSPC for an area too small to be the struct, -ENOENT for an id this node does not use.
@@ -722,7 +794,8 @@ private static int OnSendCommand(void* obj, spa_command* command)
{
try
{
- _ = obj; _ = command;
+ _ = obj;
+ _ = command;
return 0;
}
catch (Exception ex)
@@ -732,12 +805,20 @@ private static int OnSendCommand(void* obj, spa_command* command)
}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
- private static int OnPortSetIo(void* obj, SpaDirection direction, uint port, uint id, void* area, nuint size)
+ private static int OnPortSetIo(
+ void* obj,
+ SpaDirection direction,
+ uint port,
+ uint id,
+ void* area,
+ nuint size
+ )
{
try
{
PipeWireNodeProvider? self = From(obj);
- if (self is null) return -NativeLibc.EINVAL;
+ if (self is null)
+ return -NativeLibc.EINVAL;
// SPA_IO_Buffers: where the graph says which buffer is current and reads back what this
// node produced. Without it a source has nowhere to publish and simply never emits.
@@ -764,12 +845,21 @@ private static int OnPortSetIo(void* obj, SpaDirection direction, uint port, uin
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static int OnPortEnumParams(
- void* obj, int seq, SpaDirection direction, uint port, uint id, uint start, uint num, spa_pod* filter)
+ void* obj,
+ int seq,
+ SpaDirection direction,
+ uint port,
+ uint id,
+ uint start,
+ uint num,
+ spa_pod* filter
+ )
{
try
{
PipeWireNodeProvider? self = From(obj);
- if (self is null) return -NativeLibc.EINVAL;
+ if (self is null)
+ return -NativeLibc.EINVAL;
return self.EnumeratePortParams(seq, id, start, filter);
}
@@ -780,12 +870,20 @@ private static int OnPortEnumParams(
}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
- private static int OnPortSetParam(void* obj, SpaDirection direction, uint port, uint id, uint flags, spa_pod* param)
+ private static int OnPortSetParam(
+ void* obj,
+ SpaDirection direction,
+ uint port,
+ uint id,
+ uint flags,
+ spa_pod* param
+ )
{
try
{
PipeWireNodeProvider? self = From(obj);
- if (self is null) return -NativeLibc.EINVAL;
+ if (self is null)
+ return -NativeLibc.EINVAL;
// A null param clears the format, which is how the peer disconnects. The id comes from the
// generated enum rather than a literal: SPA_PARAM_ starts at Invalid = 0, so the values are
@@ -814,7 +912,9 @@ private static int OnPortSetParam(void* obj, SpaDirection direction, uint port,
// derived from them - a cycle sized off the offered format would then write the wrong
// number of bytes into a buffer the graph owns.
var settled = new ReadOnlySpan(
- param, checked((int)(param->size + (uint)sizeof(spa_pod))));
+ param,
+ checked((int)(param->size + (uint)sizeof(spa_pod)))
+ );
PipeWireExportedFormat? agreed = PipeWireExportedFormat.FromPod(settled);
if (agreed is null)
@@ -862,13 +962,21 @@ private struct IoUpdate
}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
- private static int DoSetBuffersIo(spa_loop* loop, bool async, uint seq, void* data, nuint size, void* userData)
+ private static int DoSetBuffersIo(
+ spa_loop* loop,
+ bool async,
+ uint seq,
+ void* data,
+ nuint size,
+ void* userData
+ )
{
// Runs under the data loop's lock, called from C: nothing may escape.
try
{
var update = (IoUpdate*)userData;
- if (From(update->Node) is { } self) self._io = update->Area;
+ if (From(update->Node) is { } self)
+ self._io = update->Area;
return 0;
}
catch (Exception ex)
@@ -879,12 +987,19 @@ private static int DoSetBuffersIo(spa_loop* loop, bool async, uint seq, void* da
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static int OnPortUseBuffers(
- void* obj, SpaDirection direction, uint port, uint flags, spa_buffer** buffers, uint count)
+ void* obj,
+ SpaDirection direction,
+ uint port,
+ uint flags,
+ spa_buffer** buffers,
+ uint count
+ )
{
try
{
PipeWireNodeProvider? self = From(obj);
- if (self is null) return -NativeLibc.EINVAL;
+ if (self is null)
+ return -NativeLibc.EINVAL;
// The count is what the data loop gates on, so it is published last when a pool arrives and
// first when one goes away. Assigning it before the array it describes leaves a window in
@@ -923,7 +1038,8 @@ private static int OnPortReuseBuffer(void* obj, uint port, uint bufferId)
try
{
PipeWireNodeProvider? self = From(obj);
- if (self is null) return -NativeLibc.EINVAL;
+ if (self is null)
+ return -NativeLibc.EINVAL;
// Upstream's export-source: reuse_buffer puts the buffer back on its free list. Same
// snapshot as RunCycle, and the same gate: a count that is set describes an array that
@@ -951,7 +1067,8 @@ private static int OnPortReuseBuffer(void* obj, uint port, uint bufferId)
private static int OnProcess(void* obj)
{
PipeWireNodeProvider? self = From(obj);
- if (self is null) return -NativeLibc.EINVAL;
+ if (self is null)
+ return -NativeLibc.EINVAL;
// This runs on the realtime data-loop thread, called from C. A managed exception cannot
// unwind through the native frames above it, so the runtime aborts the whole process
@@ -967,7 +1084,8 @@ private static int OnProcess(void* obj)
// Logged once per kind of failure, not per cycle: this is the realtime thread, and a handler
// that throws on every cycle would otherwise log at the graph's rate. Every occurrence is
// still in LastProcessError.
- if (self.LastProcessError?.GetType() != ex.GetType()) self.LogProcessFaulted(self._name, ex);
+ if (self.LastProcessError?.GetType() != ex.GetType())
+ self.LogProcessFaulted(self._name, ex);
self.LastProcessError = ex;
return -NativeLibc.EIO;
}
@@ -994,23 +1112,38 @@ private int RunCycle()
if (io is null || buffers is null || bufferCount == 0)
return consuming ? (int)SpaStatus.NeedData : (int)SpaStatus.Ok;
- if (consuming) return ConsumeCycle(io, buffers, bufferCount);
- if (ProduceTrace is not { } trace) return ProduceCycle(io, buffers, free, bufferCount);
+ if (consuming)
+ return ConsumeCycle(io, buffers, bufferCount);
+ if (ProduceTrace is not { } trace)
+ return ProduceCycle(io, buffers, free, bufferCount);
int entryStatus = io->status;
uint entryBuffer = io->buffer_id;
int result = ProduceCycle(io, buffers, free, bufferCount);
uint freeCount = 0;
- foreach (bool f in free) if (f) freeCount++;
+ foreach (bool f in free)
+ if (f)
+ freeCount++;
trace[ProduceTraceCount++ % trace.Length] = new ProduceCycleRecord(
- entryStatus, entryBuffer, result == (int)SpaStatus.HaveData ? io->buffer_id : uint.MaxValue,
- result, freeCount, bufferCount);
+ entryStatus,
+ entryBuffer,
+ result == (int)SpaStatus.HaveData ? io->buffer_id : uint.MaxValue,
+ result,
+ freeCount,
+ bufferCount
+ );
return result;
}
/// One source cycle as the io area saw it, for a test diagnosing lost or reused buffers.
internal readonly record struct ProduceCycleRecord(
- int EntryStatus, uint EntryBuffer, uint Published, int Result, uint FreeAfter, uint Pool);
+ int EntryStatus,
+ uint EntryBuffer,
+ uint Published,
+ int Result,
+ uint FreeAfter,
+ uint Pool
+ );
///
/// Set by a test before the node runs to record every source cycle, round-robin. Written only by
@@ -1031,20 +1164,24 @@ internal readonly record struct ProduceCycleRecord(
///
private int ConsumeCycle(spa_io_buffers* io, spa_buffer** buffers, uint bufferCount)
{
- if (io->status != (int)SpaStatus.HaveData) return (int)SpaStatus.NeedData;
+ if (io->status != (int)SpaStatus.HaveData)
+ return (int)SpaStatus.NeedData;
uint index = io->buffer_id;
- if (index >= bufferCount) return (int)SpaStatus.NeedData;
+ if (index >= bufferCount)
+ return (int)SpaStatus.NeedData;
try
{
HasProcessed = true;
spa_buffer* buffer = buffers[index];
- if (buffer is null || buffer->n_datas == 0 || buffer->datas is null) return (int)SpaStatus.NeedData;
+ if (buffer is null || buffer->n_datas == 0 || buffer->datas is null)
+ return (int)SpaStatus.NeedData;
spa_data* d = &buffer->datas[0];
- if (d->data is null || d->chunk is null || ProcessCallback is not { } handler) return (int)SpaStatus.NeedData;
+ if (d->data is null || d->chunk is null || ProcessCallback is not { } handler)
+ return (int)SpaStatus.NeedData;
// Only what the producer wrote, bounded by the mapping: the offset is clamped to the
// block and the size to what remains after it, as every upstream reader of a chunk
@@ -1087,9 +1224,15 @@ private int ConsumeCycle(spa_io_buffers* io, spa_buffer** buffers, uint bufferCo
/// -EPIPE for good.
///
///
- private int ProduceCycle(spa_io_buffers* io, spa_buffer** buffers, bool[] free, uint bufferCount)
+ private int ProduceCycle(
+ spa_io_buffers* io,
+ spa_buffer** buffers,
+ bool[] free,
+ uint bufferCount
+ )
{
- if (io->status == (int)SpaStatus.HaveData) return (int)SpaStatus.HaveData;
+ if (io->status == (int)SpaStatus.HaveData)
+ return (int)SpaStatus.HaveData;
if (io->buffer_id < bufferCount)
{
@@ -1101,19 +1244,23 @@ private int ProduceCycle(spa_io_buffers* io, spa_buffer** buffers, bool[] free,
for (uint i = 0; i < bufferCount; i++)
{
uint candidate = (_nextBuffer + i) % bufferCount;
- if (!free[candidate]) continue;
+ if (!free[candidate])
+ continue;
index = candidate;
break;
}
- if (index == uint.MaxValue) return -NativeLibc.EPIPE;
+ if (index == uint.MaxValue)
+ return -NativeLibc.EPIPE;
spa_buffer* buffer = buffers[index];
- if (buffer is null || buffer->n_datas == 0 || buffer->datas is null) return (int)SpaStatus.Ok;
+ if (buffer is null || buffer->n_datas == 0 || buffer->datas is null)
+ return (int)SpaStatus.Ok;
spa_data* d = &buffer->datas[0];
- if (d->data is null || d->chunk is null || ProcessCallback is not { } handler) return (int)SpaStatus.Ok;
+ if (d->data is null || d->chunk is null || ProcessCallback is not { } handler)
+ return (int)SpaStatus.Ok;
PipeWireExportedFormat? format = NegotiatedFormat ?? OfferedFormat;
@@ -1126,12 +1273,14 @@ private int ProduceCycle(spa_io_buffers* io, spa_buffer** buffers, bool[] free,
if (quantum > 0)
{
ulong wanted = quantum * (ulong)format.BytesPerFrame;
- if (wanted > 0 && wanted < cycleBytes) cycleBytes = (uint)wanted;
+ if (wanted > 0 && wanted < cycleBytes)
+ cycleBytes = (uint)wanted;
}
}
int written = handler(this, new Span(d->data, checked((int)cycleBytes)));
- if (written <= 0) return (int)SpaStatus.Ok;
+ if (written <= 0)
+ return (int)SpaStatus.Ok;
free[index] = false;
_nextBuffer = (index + 1) % bufferCount;
@@ -1159,31 +1308,48 @@ private int ProduceCycle(spa_io_buffers* io, spa_buffer** buffers, bool[] free,
/// means rather than having two notions of it. An unreadable filter is treated as no constraint,
/// as upstream does: refusing would drop the parameter on the strength of a pod we failed to parse.
///
- private static int ProjectParam(ReadOnlySpan candidate, spa_pod* filter, Span destination)
+ private static int ProjectParam(
+ ReadOnlySpan candidate,
+ spa_pod* filter,
+ Span destination
+ )
{
- if (!SpaPod.TryParse(candidate, out SpaValue? candidateValue) || candidateValue is not SpaObject offered)
+ if (
+ !SpaPod.TryParse(candidate, out SpaValue? candidateValue)
+ || candidateValue is not SpaObject offered
+ )
return 0;
var filterBytes = new ReadOnlySpan(
- filter, checked((int)(filter->size + (uint)sizeof(spa_pod))));
-
- if (!SpaPod.TryParse(filterBytes, out SpaValue? filterValue) || filterValue is not SpaObject wanted)
+ filter,
+ checked((int)(filter->size + (uint)sizeof(spa_pod)))
+ );
+
+ if (
+ !SpaPod.TryParse(filterBytes, out SpaValue? filterValue)
+ || filterValue is not SpaObject wanted
+ )
{
- if (candidate.Length > destination.Length) return 0;
+ if (candidate.Length > destination.Length)
+ return 0;
candidate.CopyTo(destination);
return candidate.Length;
}
SpaObject? narrowed = SpaPodProjection.Project(offered, wanted);
- if (narrowed is null) return 0;
+ if (narrowed is null)
+ return 0;
- return SpaPod.TryWrite(narrowed, destination, out int projectedLength) ? projectedLength : 0;
+ return SpaPod.TryWrite(narrowed, destination, out int projectedLength)
+ ? projectedLength
+ : 0;
}
/// Delivers one enumerated parameter to every listener, as spa_node_emit_result does.
private int EmitParam(int seq, uint id, uint start, ReadOnlySpan pod)
{
- if (_hooks is null) return 0;
+ if (_hooks is null)
+ return 0;
fixed (byte* p = pod)
{
@@ -1201,7 +1367,13 @@ private int EmitParam(int seq, uint id, uint start, ReadOnlySpan pod)
var h = (spa_hook*)l;
var ev = (spa_node_events*)h->cb.funcs;
if (ev is not null && ev->result is not null)
- ev->result(h->cb.data, seq, 0, (uint)NativeConstants.SPA_RESULT_TYPE_NODE_PARAMS, &result);
+ ev->result(
+ h->cb.data,
+ seq,
+ 0,
+ (uint)NativeConstants.SPA_RESULT_TYPE_NODE_PARAMS,
+ &result
+ );
}
}
@@ -1224,70 +1396,114 @@ private int EmitParam(int seq, uint id, uint start, ReadOnlySpan pod)
///
private int EnumeratePortParams(int seq, uint id, uint start, spa_pod* filter)
{
- if (start > 0) return 0;
+ if (start > 0)
+ return 0;
Span candidate = stackalloc byte[2048];
int length = (SpaParamType)id switch
{
SpaParamType.EnumFormat => OfferedFormat!.WriteFormat(candidate, asEnum: true),
- SpaParamType.Format => NegotiatedFormat is { } settled ? settled.WriteFormat(candidate, asEnum: false) : 0,
+ SpaParamType.Format => NegotiatedFormat is { } settled
+ ? settled.WriteFormat(candidate, asEnum: false)
+ : 0,
SpaParamType.Buffers => (NegotiatedFormat ?? OfferedFormat)!.WriteBuffers(candidate),
SpaParamType.Meta => PipeWireExportedFormat.WriteMetaHeader(candidate),
SpaParamType.Io => PipeWireExportedFormat.WriteIoBuffers(candidate),
_ => -1,
};
- if (length < 0) return -NativeLibc.ENOENT;
- if (length == 0) return 0;
+ if (length < 0)
+ return -NativeLibc.ENOENT;
+ if (length == 0)
+ return 0;
- if (filter is null) return EmitParam(seq, id, start, candidate[..length]);
+ if (filter is null)
+ return EmitParam(seq, id, start, candidate[..length]);
Span narrowed = stackalloc byte[2048];
int projected = ProjectParam(candidate[..length], filter, narrowed);
return projected > 0 ? EmitParam(seq, id, start, narrowed[..projected]) : 0;
}
- [LoggerMessage(EventId = 34600, Level = LogLevel.Error,
- Message = "exported node '{Name}': its {Callback} callback threw; the graph was answered -EIO")]
+ [LoggerMessage(
+ EventId = 34600,
+ Level = LogLevel.Error,
+ Message = "exported node '{Name}': its {Callback} callback threw; the graph was answered -EIO"
+ )]
private partial void LogCallbackFaulted(string name, string callback, Exception exception);
- [LoggerMessage(EventId = 34601, Level = LogLevel.Error,
- Message = "exported node '{Name}': the process handler threw; the cycle was dropped (later failures of the same kind are only recorded in LastProcessError)")]
+ [LoggerMessage(
+ EventId = 34601,
+ Level = LogLevel.Error,
+ Message = "exported node '{Name}': the process handler threw; the cycle was dropped (later failures of the same kind are only recorded in LastProcessError)"
+ )]
private partial void LogProcessFaulted(string name, Exception exception);
- [LoggerMessage(EventId = 34602, Level = LogLevel.Warning,
- Message = "exported node '{Name}': refused a listener (no hook list, or a null hook)")]
+ [LoggerMessage(
+ EventId = 34602,
+ Level = LogLevel.Warning,
+ Message = "exported node '{Name}': refused a listener (no hook list, or a null hook)"
+ )]
private partial void LogListenerRefused(string name);
- [LoggerMessage(EventId = 34603, Level = LogLevel.Debug,
- Message = "exported node '{Name}': io area {Io} is not one it uses")]
+ [LoggerMessage(
+ EventId = 34603,
+ Level = LogLevel.Debug,
+ Message = "exported node '{Name}': io area {Io} is not one it uses"
+ )]
private partial void LogIoRefused(string name, SpaIoType io);
- [LoggerMessage(EventId = 34604, Level = LogLevel.Warning,
- Message = "exported node '{Name}': io area {Io} is {Size} bytes, too small for the struct")]
+ [LoggerMessage(
+ EventId = 34604,
+ Level = LogLevel.Warning,
+ Message = "exported node '{Name}': io area {Io} is {Size} bytes, too small for the struct"
+ )]
private partial void LogIoAreaTooSmall(string name, SpaIoType io, nuint size);
- [LoggerMessage(EventId = 34605, Level = LogLevel.Debug,
- Message = "exported node '{Name}': parameter {Param} cannot be set on its port")]
+ [LoggerMessage(
+ EventId = 34605,
+ Level = LogLevel.Debug,
+ Message = "exported node '{Name}': parameter {Param} cannot be set on its port"
+ )]
private partial void LogParamRefused(string name, SpaParamType param);
- [LoggerMessage(EventId = 34606, Level = LogLevel.Warning,
- Message = "exported node '{Name}': refused a format it cannot carry")]
+ [LoggerMessage(
+ EventId = 34606,
+ Level = LogLevel.Warning,
+ Message = "exported node '{Name}': refused a format it cannot carry"
+ )]
private partial void LogFormatRefused(string name);
- [LoggerMessage(EventId = 34607, Level = LogLevel.Debug,
- Message = "exported node '{Name}': format settled on {SampleFormat} {Rate} Hz x{Channels}")]
- private partial void LogFormatSettled(string name, SpaAudioFormat sampleFormat, int rate, int channels);
-
- [LoggerMessage(EventId = 34608, Level = LogLevel.Debug,
- Message = "exported node '{Name}': format cleared")]
+ [LoggerMessage(
+ EventId = 34607,
+ Level = LogLevel.Debug,
+ Message = "exported node '{Name}': format settled on {SampleFormat} {Rate} Hz x{Channels}"
+ )]
+ private partial void LogFormatSettled(
+ string name,
+ SpaAudioFormat sampleFormat,
+ int rate,
+ int channels
+ );
+
+ [LoggerMessage(
+ EventId = 34608,
+ Level = LogLevel.Debug,
+ Message = "exported node '{Name}': format cleared"
+ )]
private partial void LogFormatCleared(string name);
- [LoggerMessage(EventId = 34609, Level = LogLevel.Debug,
- Message = "exported node '{Name}': the peer allocated {Count} buffers")]
+ [LoggerMessage(
+ EventId = 34609,
+ Level = LogLevel.Debug,
+ Message = "exported node '{Name}': the peer allocated {Count} buffers"
+ )]
private partial void LogBuffersArrived(string name, uint count);
- [LoggerMessage(EventId = 34610, Level = LogLevel.Warning,
- Message = "exported node '{Name}': the peer returned buffer {BufferId}, outside its pool of {Count}")]
+ [LoggerMessage(
+ EventId = 34610,
+ Level = LogLevel.Warning,
+ Message = "exported node '{Name}': the peer returned buffer {BufferId}, outside its pool of {Count}"
+ )]
private partial void LogReuseRefused(string name, uint bufferId, uint count);
}
diff --git a/src/PipeWire.NET/Graph/PipeWireNodeProxy.cs b/src/PipeWire.NET/Graph/PipeWireNodeProxy.cs
index 8264ee2..8873717 100644
--- a/src/PipeWire.NET/Graph/PipeWireNodeProxy.cs
+++ b/src/PipeWire.NET/Graph/PipeWireNodeProxy.cs
@@ -32,28 +32,49 @@ private PipeWireNodeProxy(PipeWireContext ctx, uint id, ILogger logger)
: base(ctx, id) => _logger = logger;
internal static unsafe PipeWireNodeProxy Bind(
- PipeWireContext ctx, pw_registry* registry, uint id, uint version, ILogger logger,
- Action? propertiesObserved = null)
+ PipeWireContext ctx,
+ pw_registry* registry,
+ uint id,
+ uint version,
+ ILogger logger,
+ Action? propertiesObserved = null
+ )
{
// The observer is in place before the proxy is bound: the first info after a bind is the
// only one that carries the object's properties (later ones set no PROPS in their change
// mask, so their dictionary arrives empty), and it can arrive the moment the bind is sent.
// Assigned after Bind returned, a fast daemon's first info found no observer and the
// properties never reached the registry.
- var control = new PipeWireNodeProxy(ctx, id, logger) { PropertiesObserved = propertiesObserved };
- control.Attach(BoundProxy.Bind(
- ctx, registry, id, PipeWireKeys.PW_TYPE_INTERFACE_Node, version, NativeConstants.PW_VERSION_NODE,
- sizeof(pw_node_events),
- events =>
- {
- var table = (pw_node_events*)events;
- table->version = NativeConstants.PW_VERSION_NODE_EVENTS;
- table->info = &OnInfoCallback;
- table->param = &OnParamCallback;
- },
- static (proxy, hook, events, data) => Native.pw_node_add_listener(
- (pw_node*)proxy, (spa_hook*)hook, (pw_node_events*)events, (void*)data),
- control));
+ var control = new PipeWireNodeProxy(ctx, id, logger)
+ {
+ PropertiesObserved = propertiesObserved,
+ };
+ control.Attach(
+ BoundProxy.Bind(
+ ctx,
+ registry,
+ id,
+ PipeWireKeys.PW_TYPE_INTERFACE_Node,
+ version,
+ NativeConstants.PW_VERSION_NODE,
+ sizeof(pw_node_events),
+ events =>
+ {
+ var table = (pw_node_events*)events;
+ table->version = NativeConstants.PW_VERSION_NODE_EVENTS;
+ table->info = &OnInfoCallback;
+ table->param = &OnParamCallback;
+ },
+ static (proxy, hook, events, data) =>
+ Native.pw_node_add_listener(
+ (pw_node*)proxy,
+ (spa_hook*)hook,
+ (pw_node_events*)events,
+ (void*)data
+ ),
+ control
+ )
+ );
return control;
}
@@ -82,7 +103,8 @@ internal static unsafe PipeWireNodeProxy Bind(
/// on top, and a device with a channel map reports per-channel values whatever that scalar says.
///
public async Task> GetChannelVolumesAsync(
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
SpaObject? props = await GetParameterOrNullAsync(SpaParamType.Props, cancellationToken)
.ConfigureAwait(false);
@@ -119,12 +141,20 @@ public Task SetVolumeAsync(float volume, CancellationToken cancellationToken = d
// NaN went into a float pod and reached the daemon as a volume.
ArgumentOutOfRangeException.ThrowIfNegative(volume);
if (float.IsNaN(volume))
- throw new ArgumentOutOfRangeException(nameof(volume), volume, "a volume must be a number.");
+ throw new ArgumentOutOfRangeException(
+ nameof(volume),
+ volume,
+ "a volume must be a number."
+ );
return SetParameterAsync(
SpaParamType.Props,
- new SpaObject(SpaType.ObjectProps, SpaParamType.Props,
- [new SpaPodProperty(SpaProp.Volume, 0, new SpaFloat(volume))]),
- cancellationToken);
+ new SpaObject(
+ SpaType.ObjectProps,
+ SpaParamType.Props,
+ [new SpaPodProperty(SpaProp.Volume, 0, new SpaFloat(volume))]
+ ),
+ cancellationToken
+ );
}
/// Sets the node's per-channel volumes.
@@ -147,27 +177,42 @@ public Task SetVolumeAsync(float volume, CancellationToken cancellationToken = d
///
/// is empty or holds a negative value.
public Task SetChannelVolumesAsync(
- ReadOnlySpan volumes, CancellationToken cancellationToken = default)
+ ReadOnlySpan volumes,
+ CancellationToken cancellationToken = default
+ )
{
if (volumes.IsEmpty)
- throw new ArgumentException("at least one channel volume is required.", nameof(volumes));
+ throw new ArgumentException(
+ "at least one channel volume is required.",
+ nameof(volumes)
+ );
var values = ImmutableArray.CreateBuilder(volumes.Length);
foreach (float volume in volumes)
{
if (float.IsNegative(volume) || float.IsNaN(volume))
- throw new ArgumentException("a channel volume must be a non-negative number.", nameof(volumes));
+ throw new ArgumentException(
+ "a channel volume must be a non-negative number.",
+ nameof(volumes)
+ );
values.Add(new SpaFloat(volume));
}
return SetParameterAsync(
SpaParamType.Props,
- new SpaObject(SpaType.ObjectProps, SpaParamType.Props,
- [
- new SpaPodProperty(SpaProp.ChannelVolumes, 0,
- new SpaArray(SpaType.Float, values.MoveToImmutable())),
- ]),
- cancellationToken);
+ new SpaObject(
+ SpaType.ObjectProps,
+ SpaParamType.Props,
+ [
+ new SpaPodProperty(
+ SpaProp.ChannelVolumes,
+ 0,
+ new SpaArray(SpaType.Float, values.MoveToImmutable())
+ ),
+ ]
+ ),
+ cancellationToken
+ );
}
/// Sets the per-channel volumes, checking the count against the node first.
@@ -190,7 +235,8 @@ public Task SetChannelVolumesAsync(
public Task SetChannelVolumesAsync(
ReadOnlySpan volumes,
bool matchChannelMap,
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
float[] copy = volumes.ToArray();
return matchChannelMap
@@ -200,23 +246,23 @@ public Task SetChannelVolumesAsync(
private async Task SetCheckedAsync(float[] volumes, CancellationToken cancellationToken)
{
- ImmutableArray map =
- await GetChannelMapAsync(cancellationToken).ConfigureAwait(false);
- ImmutableArray current =
- await GetChannelVolumesAsync(cancellationToken).ConfigureAwait(false);
+ ImmutableArray map = await GetChannelMapAsync(cancellationToken)
+ .ConfigureAwait(false);
+ ImmutableArray current = await GetChannelVolumesAsync(cancellationToken)
+ .ConfigureAwait(false);
if (volumes.Length != map.Length && volumes.Length != current.Length)
{
throw new ArgumentException(
$"{volumes.Length} volumes match neither the node's channel map ({map.Length}) "
- + $"nor the {current.Length} it currently holds.",
- nameof(volumes));
+ + $"nor the {current.Length} it currently holds.",
+ nameof(volumes)
+ );
}
await SetChannelVolumesAsync(volumes, cancellationToken).ConfigureAwait(false);
}
-
///
/// The channels this node actually has, in order, or empty if it does not say.
///
@@ -226,7 +272,8 @@ private async Task SetCheckedAsync(float[] volumes, CancellationToken cancellati
/// Unlike the volume array, this reflects the node rather than the last thing written to it.
///
public async Task> GetChannelMapAsync(
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
SpaObject? props = await GetParameterOrNullAsync(SpaParamType.Props, cancellationToken)
.ConfigureAwait(false);
@@ -237,7 +284,8 @@ public async Task> GetChannelMapAsync(
var channels = ImmutableArray.CreateBuilder(map.Items.Length);
foreach (SpaValue item in map.Items)
{
- if (item is SpaId id) channels.Add((SpaAudioChannel)id.Value);
+ if (item is SpaId id)
+ channels.Add((SpaAudioChannel)id.Value);
}
return channels.ToImmutable();
@@ -252,9 +300,13 @@ public async Task> GetChannelMapAsync(
public Task SetMutedAsync(bool muted, CancellationToken cancellationToken = default) =>
SetParameterAsync(
SpaParamType.Props,
- new SpaObject(SpaType.ObjectProps, SpaParamType.Props,
- [new SpaPodProperty(SpaProp.Mute, 0, new SpaBool(muted))]),
- cancellationToken);
+ new SpaObject(
+ SpaType.ObjectProps,
+ SpaParamType.Props,
+ [new SpaPodProperty(SpaProp.Mute, 0, new SpaBool(muted))]
+ ),
+ cancellationToken
+ );
///
/// The extra latency applied to this node, in nanoseconds, or if unset.
@@ -273,18 +325,25 @@ public Task SetMutedAsync(bool muted, CancellationToken cancellationToken = defa
/// Abandons the wait. The request is already on its way, so cancelling does not recall
/// it: the daemon can still apply the change after this throws.
///
- public Task SetLatencyOffsetAsync(long nanoseconds, CancellationToken cancellationToken = default) =>
+ public Task SetLatencyOffsetAsync(
+ long nanoseconds,
+ CancellationToken cancellationToken = default
+ ) =>
SetParameterAsync(
SpaParamType.Props,
- new SpaObject(SpaType.ObjectProps, SpaParamType.Props,
- [new SpaPodProperty(SpaProp.LatencyOffsetNsec, 0, new SpaLong(nanoseconds))]),
- cancellationToken);
+ new SpaObject(
+ SpaType.ObjectProps,
+ SpaParamType.Props,
+ [new SpaPodProperty(SpaProp.LatencyOffsetNsec, 0, new SpaLong(nanoseconds))]
+ ),
+ cancellationToken
+ );
/// The formats this node will accept, as offered for negotiation.
/// Abandons the wait.
public Task> EnumerateFormatsAsync(
- CancellationToken cancellationToken = default) =>
- EnumerateParametersAsync(SpaParamType.EnumFormat, cancellationToken);
+ CancellationToken cancellationToken = default
+ ) => EnumerateParametersAsync(SpaParamType.EnumFormat, cancellationToken);
///
/// Describes every property this node supports: its key, its type and the values it accepts.
@@ -295,8 +354,8 @@ public Task> EnumerateFormatsAsync(
/// so this says whether a volume slider should be shown.
///
public Task> EnumeratePropertyInfoAsync(
- CancellationToken cancellationToken = default) =>
- EnumerateParametersAsync(SpaParamType.PropInfo, cancellationToken);
+ CancellationToken cancellationToken = default
+ ) => EnumerateParametersAsync(SpaParamType.PropInfo, cancellationToken);
internal static ImmutableArray ReadFloatArray(SpaObject? props, SpaProp key)
{
@@ -306,21 +365,34 @@ internal static ImmutableArray ReadFloatArray(SpaObject? props, SpaProp k
var values = ImmutableArray.CreateBuilder(array.Items.Length);
foreach (SpaValue item in array.Items)
{
- if (item is SpaFloat value) values.Add(value.Value);
+ if (item is SpaFloat value)
+ values.Add(value.Value);
}
return values.ToImmutable();
}
private protected override unsafe int EnumParamsNative(
- void* proxy, int seq, uint id, uint start, uint num, spa_pod* filter) =>
- Native.pw_node_enum_params((pw_node*)proxy, seq, id, start, num, filter);
-
- private protected override unsafe int SetParamNative(void* proxy, uint id, uint flags, spa_pod* param) =>
- Native.pw_node_set_param((pw_node*)proxy, id, flags, param);
-
- private protected override unsafe int SubscribeParamsNative(void* proxy, uint* ids, uint count) =>
- Native.pw_node_subscribe_params((pw_node*)proxy, ids, count);
+ void* proxy,
+ int seq,
+ uint id,
+ uint start,
+ uint num,
+ spa_pod* filter
+ ) => Native.pw_node_enum_params((pw_node*)proxy, seq, id, start, num, filter);
+
+ private protected override unsafe int SetParamNative(
+ void* proxy,
+ uint id,
+ uint flags,
+ spa_pod* param
+ ) => Native.pw_node_set_param((pw_node*)proxy, id, flags, param);
+
+ private protected override unsafe int SubscribeParamsNative(
+ void* proxy,
+ uint* ids,
+ uint count
+ ) => Native.pw_node_subscribe_params((pw_node*)proxy, ids, count);
private protected override void OnHandlerFaulted(Exception exception) =>
LogHandlerFaulted(Id, exception);
@@ -346,7 +418,13 @@ private static unsafe void OnInfoCallback(void* data, pw_node_info* info)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe void OnParamCallback(
- void* data, int seq, uint id, uint index, uint next, spa_pod* param)
+ void* data,
+ int seq,
+ uint id,
+ uint index,
+ uint next,
+ spa_pod* param
+ )
{
// An exception escaping a reverse P/Invoke aborts the process, so nothing here may throw.
try
@@ -369,15 +447,20 @@ private static unsafe void OnParamCallback(
/// plain node reports nothing rather than failing.
///
public async Task> EnumeratePortConfigsAsync(
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
ImmutableArray raw = await EnumerateParametersAsync(
- SpaParamType.EnumPortConfig, cancellationToken).ConfigureAwait(false);
+ SpaParamType.EnumPortConfig,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
var configs = ImmutableArray.CreateBuilder(raw.Length);
foreach (SpaObject param in raw)
{
- if (PipeWirePortConfig.From(param) is { } config) configs.Add(config);
+ if (PipeWirePortConfig.From(param) is { } config)
+ configs.Add(config);
}
return configs.ToImmutable();
@@ -386,9 +469,12 @@ public async Task> EnumeratePortConfigsAsync(
/// The port layout this node is currently using.
/// Abandons the wait.
public async Task GetPortConfigAsync(
- CancellationToken cancellationToken = default) =>
+ CancellationToken cancellationToken = default
+ ) =>
PipeWirePortConfig.From(
- await GetParameterAsync(SpaParamType.PortConfig, cancellationToken).ConfigureAwait(false));
+ await GetParameterAsync(SpaParamType.PortConfig, cancellationToken)
+ .ConfigureAwait(false)
+ );
/// Reconfigures the node's ports.
/// The layout to apply.
@@ -405,7 +491,9 @@ public async Task> EnumeratePortConfigsAsync(
///
/// is null.
public Task SetPortConfigAsync(
- PipeWirePortConfig config, CancellationToken cancellationToken = default)
+ PipeWirePortConfig config,
+ CancellationToken cancellationToken = default
+ )
{
ArgumentNullException.ThrowIfNull(config);
return SetParameterAsync(SpaParamType.PortConfig, config.ToParameter(), cancellationToken);
@@ -417,9 +505,12 @@ public Task SetPortConfigAsync(
/// Abandons the wait.
/// The declared latency, or null when the node declares none.
public async Task GetProcessLatencyAsync(
- CancellationToken cancellationToken = default) =>
+ CancellationToken cancellationToken = default
+ ) =>
PipeWireProcessLatency.From(
- await GetParameterAsync(SpaParamType.ProcessLatency, cancellationToken).ConfigureAwait(false));
+ await GetParameterAsync(SpaParamType.ProcessLatency, cancellationToken)
+ .ConfigureAwait(false)
+ );
/// Declares what this node costs the graph to process.
/// The delay this node adds. See the type for which unit to use.
@@ -431,24 +522,35 @@ public Task SetPortConfigAsync(
///
/// is null.
public Task SetProcessLatencyAsync(
- PipeWireProcessLatency latency, CancellationToken cancellationToken = default)
+ PipeWireProcessLatency latency,
+ CancellationToken cancellationToken = default
+ )
{
ArgumentNullException.ThrowIfNull(latency);
- return SetParameterAsync(SpaParamType.ProcessLatency, latency.ToParameter(), cancellationToken);
+ return SetParameterAsync(
+ SpaParamType.ProcessLatency,
+ latency.ToParameter(),
+ cancellationToken
+ );
}
/// The metadata travelling with this node's stream, per direction.
/// Abandons the wait.
public async Task> GetTagsAsync(
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
- ImmutableArray raw =
- await EnumerateParametersAsync(SpaParamType.Tag, cancellationToken).ConfigureAwait(false);
+ ImmutableArray raw = await EnumerateParametersAsync(
+ SpaParamType.Tag,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
var tags = ImmutableArray.CreateBuilder(raw.Length);
foreach (SpaObject param in raw)
{
- if (PipeWireTag.From(param) is { } tag) tags.Add(tag);
+ if (PipeWireTag.From(param) is { } tag)
+ tags.Add(tag);
}
return tags.ToImmutable();
@@ -464,7 +566,10 @@ public Task SetTagAsync(PipeWireTag tag, CancellationToken cancellationToken = d
return SetParameterAsync(SpaParamType.Tag, tag.ToParameter(), cancellationToken);
}
- [LoggerMessage(EventId = 33000, Level = LogLevel.Error,
- Message = "a ParameterChanged handler for node {NodeId} threw")]
+ [LoggerMessage(
+ EventId = 33000,
+ Level = LogLevel.Error,
+ Message = "a ParameterChanged handler for node {NodeId} threw"
+ )]
private partial void LogHandlerFaulted(uint nodeId, Exception exception);
}
diff --git a/src/PipeWire.NET/Graph/PipeWireObjectOptions.cs b/src/PipeWire.NET/Graph/PipeWireObjectOptions.cs
index 3e4df7e..ed8ad99 100644
--- a/src/PipeWire.NET/Graph/PipeWireObjectOptions.cs
+++ b/src/PipeWire.NET/Graph/PipeWireObjectOptions.cs
@@ -27,7 +27,8 @@ namespace PipeWire.NET.Graph;
internal readonly record struct PipeWireObjectOptions(
bool Linger,
bool Passive,
- ImmutableArray> Properties = default)
+ ImmutableArray> Properties = default
+)
{
/// Keys that say what an object is rather than how it is configured.
///
@@ -41,8 +42,10 @@ internal readonly record struct PipeWireObjectOptions(
private static readonly string[] ReservedForLinks =
[
- PipeWireKeys.PW_KEY_LINK_OUTPUT_NODE, PipeWireKeys.PW_KEY_LINK_OUTPUT_PORT,
- PipeWireKeys.PW_KEY_LINK_INPUT_NODE, PipeWireKeys.PW_KEY_LINK_INPUT_PORT,
+ PipeWireKeys.PW_KEY_LINK_OUTPUT_NODE,
+ PipeWireKeys.PW_KEY_LINK_OUTPUT_PORT,
+ PipeWireKeys.PW_KEY_LINK_INPUT_NODE,
+ PipeWireKeys.PW_KEY_LINK_INPUT_PORT,
];
/// Refuses a key the caller does not get to set.
@@ -55,7 +58,8 @@ internal static void ThrowIfReserved(string key, bool forLink)
{
throw new ArgumentException(
$"'{key}' is decided by the creation call itself and cannot be set as a property.",
- nameof(key));
+ nameof(key)
+ );
}
}
@@ -63,14 +67,17 @@ private static bool Reserved(string key, bool forLink)
{
foreach (string reserved in ReservedForEverything)
{
- if (string.Equals(key, reserved, StringComparison.Ordinal)) return true;
+ if (string.Equals(key, reserved, StringComparison.Ordinal))
+ return true;
}
- if (!forLink) return false;
+ if (!forLink)
+ return false;
foreach (string reserved in ReservedForLinks)
{
- if (string.Equals(key, reserved, StringComparison.Ordinal)) return true;
+ if (string.Equals(key, reserved, StringComparison.Ordinal))
+ return true;
}
return false;
diff --git a/src/PipeWire.NET/Graph/PipeWireObjectPermission.cs b/src/PipeWire.NET/Graph/PipeWireObjectPermission.cs
index d57770d..fa8a4fe 100644
--- a/src/PipeWire.NET/Graph/PipeWireObjectPermission.cs
+++ b/src/PipeWire.NET/Graph/PipeWireObjectPermission.cs
@@ -13,4 +13,7 @@ namespace PipeWire.NET.Graph;
/// registry at all - it is hidden rather than refused, so a confined client observes a smaller graph
/// instead of an erroring one.
///
-public readonly record struct PipeWireObjectPermission(uint ObjectId, PipeWirePermissions Permissions);
+public readonly record struct PipeWireObjectPermission(
+ uint ObjectId,
+ PipeWirePermissions Permissions
+);
diff --git a/src/PipeWire.NET/Graph/PipeWireParameterInfo.cs b/src/PipeWire.NET/Graph/PipeWireParameterInfo.cs
index b242284..3cd703f 100644
--- a/src/PipeWire.NET/Graph/PipeWireParameterInfo.cs
+++ b/src/PipeWire.NET/Graph/PipeWireParameterInfo.cs
@@ -13,4 +13,8 @@ namespace PipeWire.NET.Graph;
/// enumerating the second is an error rather than an empty answer, which is what makes this worth
/// checking rather than guessing.
///
-public readonly record struct PipeWireParameterInfo(SpaParamType Parameter, bool CanRead, bool CanWrite);
+public readonly record struct PipeWireParameterInfo(
+ SpaParamType Parameter,
+ bool CanRead,
+ bool CanWrite
+);
diff --git a/src/PipeWire.NET/Graph/PipeWireParameterObject.cs b/src/PipeWire.NET/Graph/PipeWireParameterObject.cs
index ebb473b..e7817f5 100644
--- a/src/PipeWire.NET/Graph/PipeWireParameterObject.cs
+++ b/src/PipeWire.NET/Graph/PipeWireParameterObject.cs
@@ -32,11 +32,13 @@ namespace PipeWire.NET.Graph;
public abstract class PipeWireParameterObject : IDisposable, IAsyncDisposable
{
private readonly PipeWireContext _ctx;
+
// Concurrent rather than a dictionary behind a lock, and deliberately so. A param event is
// dispatched by the loop thread with the loop lock already held, so anything it waits on must
// never be held by a thread that is itself waiting for the loop lock.
private readonly ConcurrentDictionary> _answers = new();
private BoundProxy? _bound;
+
// The array reference, not the ImmutableArray wrapping it. A struct assignment has no atomicity
// guarantee and cannot be published with Volatile; the single reference inside it can.
private PipeWireParameterInfo[] _parameters = [];
@@ -75,7 +77,8 @@ public bool CanRead(SpaParamType parameter)
{
foreach (PipeWireParameterInfo info in Parameters)
{
- if (info.Parameter == parameter) return info.CanRead;
+ if (info.Parameter == parameter)
+ return info.CanRead;
}
return false;
@@ -87,7 +90,8 @@ public bool CanWrite(SpaParamType parameter)
{
foreach (PipeWireParameterInfo info in Parameters)
{
- if (info.Parameter == parameter) return info.CanWrite;
+ if (info.Parameter == parameter)
+ return info.CanWrite;
}
return false;
@@ -147,17 +151,25 @@ private protected void Attach(BoundProxy bound)
private void RaiseRemoved()
{
Action? handler = Removed;
- if (handler is null) return;
+ if (handler is null)
+ return;
// A native callback frame, so nothing may escape it.
- try { handler(); }
- catch (Exception ex) { OnHandlerFaulted(ex); }
+ try
+ {
+ handler();
+ }
+ catch (Exception ex)
+ {
+ OnHandlerFaulted(ex);
+ }
}
///
/// A number to hand to enum_params. Only for tracing: the protocol does not echo it.
///
- private int NextRequestTag() => Interlocked.Increment(ref _nextTag) & NativeConstants.SPA_ASYNC_SEQ_MASK;
+ private int NextRequestTag() =>
+ Interlocked.Increment(ref _nextTag) & NativeConstants.SPA_ASYNC_SEQ_MASK;
// Each takes the proxy pointer rather than reading it from the binding. Destroying a proxy
// clears that pointer before it takes the loop lock, so a pointer re-read inside the call could
@@ -165,10 +177,21 @@ private void RaiseRemoved()
/// Dispatches this interface's enum_params.
private protected abstract unsafe int EnumParamsNative(
- void* proxy, int seq, uint id, uint start, uint num, spa_pod* filter);
+ void* proxy,
+ int seq,
+ uint id,
+ uint start,
+ uint num,
+ spa_pod* filter
+ );
/// Dispatches this interface's set_param.
- private protected abstract unsafe int SetParamNative(void* proxy, uint id, uint flags, spa_pod* param);
+ private protected abstract unsafe int SetParamNative(
+ void* proxy,
+ uint id,
+ uint flags,
+ spa_pod* param
+ );
/// Dispatches this interface's subscribe_params.
private protected abstract unsafe int SubscribeParamsNative(void* proxy, uint* ids, uint count);
@@ -189,7 +212,9 @@ private protected abstract unsafe int EnumParamsNative(
/// "no values", so a caller asking for the wrong parameter finds out.
///
public async Task> EnumerateParametersAsync(
- SpaParamType parameter, CancellationToken cancellationToken = default)
+ SpaParamType parameter,
+ CancellationToken cancellationToken = default
+ )
{
ObjectDisposedException.ThrowIf(_disposed, this);
cancellationToken.ThrowIfCancellationRequested();
@@ -220,7 +245,10 @@ public async Task> EnumerateParametersAsync(
///
/// The binding has been disposed.
public Task> EnumerateParametersAsync(
- SpaParamType parameter, SpaObject filter, CancellationToken cancellationToken = default)
+ SpaParamType parameter,
+ SpaObject filter,
+ CancellationToken cancellationToken = default
+ )
{
ObjectDisposedException.ThrowIf(_disposed, this);
ArgumentNullException.ThrowIfNull(filter);
@@ -240,7 +268,10 @@ public Task> EnumerateParametersAsync(
}
private async Task> EnumerateFilteredAsync(
- SpaParamType parameter, nint filter, CancellationToken cancellationToken)
+ SpaParamType parameter,
+ nint filter,
+ CancellationToken cancellationToken
+ )
{
// The sequence number the answers carry is not the one handed to enum_params. The protocol
// replaces it with the connection's own message sequence and returns that, async-tagged, so
@@ -250,8 +281,7 @@ private async Task> EnumerateFilteredAsync(
// param the request produced. Without the barrier there is nothing to wait on: the protocol
// has no "that was the last one".
int key = 0;
- Task roundTrip = BeginEnumeration(
- parameter, filter, k => key = k, cancellationToken);
+ Task roundTrip = BeginEnumeration(parameter, filter, k => key = k, cancellationToken);
try
{
@@ -281,7 +311,9 @@ private async Task> EnumerateFilteredAsync(
/// .
///
private static bool IsNoSuchParameter(PipeWireRequestRefusedException ex) =>
- ex.Result == -NativeLibc.ENOENT && ex.ObjectId is { } id && id != NativeConstants.PW_ID_CORE;
+ ex.Result == -NativeLibc.ENOENT
+ && ex.ObjectId is { } id
+ && id != NativeConstants.PW_ID_CORE;
///
/// Like , but an object that does not have the parameter at all
@@ -293,7 +325,9 @@ private static bool IsNoSuchParameter(PipeWireRequestRefusedException ex) =>
/// an error. Every other failure, including an object that is gone, still throws.
///
private protected async Task GetParameterOrNullAsync(
- SpaParamType parameter, CancellationToken cancellationToken)
+ SpaParamType parameter,
+ CancellationToken cancellationToken
+ )
{
try
{
@@ -313,10 +347,12 @@ private static bool IsNoSuchParameter(PipeWireRequestRefusedException ex) =>
/// Which parameter to read.
/// Abandons the wait.
public async Task GetParameterAsync(
- SpaParamType parameter, CancellationToken cancellationToken = default)
+ SpaParamType parameter,
+ CancellationToken cancellationToken = default
+ )
{
- ImmutableArray all =
- await EnumerateParametersAsync(parameter, cancellationToken).ConfigureAwait(false);
+ ImmutableArray all = await EnumerateParametersAsync(parameter, cancellationToken)
+ .ConfigureAwait(false);
return all.IsDefaultOrEmpty ? null : all[0];
}
@@ -337,7 +373,10 @@ private static bool IsNoSuchParameter(PipeWireRequestRefusedException ex) =>
/// is .
/// The binding has been disposed.
public async Task SetParameterAsync(
- SpaParamType parameter, SpaObject value, CancellationToken cancellationToken = default)
+ SpaParamType parameter,
+ SpaObject value,
+ CancellationToken cancellationToken = default
+ )
{
ArgumentNullException.ThrowIfNull(value);
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -351,7 +390,10 @@ public async Task SetParameterAsync(
/// Issues a parameter write inside its round-trip, under a held reference.
private unsafe Task BeginWrite(
- SpaParamType parameter, byte[] pod, CancellationToken cancellationToken)
+ SpaParamType parameter,
+ byte[] pod,
+ CancellationToken cancellationToken
+ )
{
if (!Bound.TryUse(out BoundProxy.Use proxy))
throw new ObjectDisposedException(nameof(PipeWireParameterObject));
@@ -359,16 +401,24 @@ private unsafe Task BeginWrite(
using (proxy)
{
nint obj = (nint)proxy.Object;
- return CoreSync.RoundTripAsync(_ctx, () =>
- {
- fixed (byte* p = pod)
- return SetParamNative((void*)obj, (uint)parameter, 0, (spa_pod*)p);
- }, cancellationToken);
+ return CoreSync.RoundTripAsync(
+ _ctx,
+ () =>
+ {
+ fixed (byte* p = pod)
+ return SetParamNative((void*)obj, (uint)parameter, 0, (spa_pod*)p);
+ },
+ cancellationToken
+ );
}
}
private unsafe Task BeginEnumeration(
- SpaParamType parameter, nint filter, Action onKey, CancellationToken cancellationToken)
+ SpaParamType parameter,
+ nint filter,
+ Action onKey,
+ CancellationToken cancellationToken
+ )
{
if (!Bound.TryUse(out BoundProxy.Use proxy))
throw new ObjectDisposedException(nameof(PipeWireParameterObject));
@@ -385,31 +435,40 @@ private unsafe Task BeginEnumeration(
using (proxy)
{
nint obj = (nint)proxy.Object;
- return CoreSync.RoundTripAsync(_ctx, () =>
- {
- // 0 to uint.MaxValue: every value the object has of this parameter.
- // The filter travels as an IntPtr because a pointer cannot be captured; it is only
- // ever dereferenced here, synchronously, while the caller's pin is still held.
- int rc = EnumParamsNative(
- (void*)obj, NextRequestTag(), (uint)parameter, 0, uint.MaxValue,
- (spa_pod*)filter);
-
- // Filed while the loop lock is still held, so no param event for this request can
- // have been dispatched yet - the loop thread dispatches them, and it is blocked on
- // that lock until the scope closes.
- // Only when the daemon actually queued the request. A synchronous result has no
- // sequence, and filing a collector under key 0 makes OnParam route any seq-0 param
- // into this enumeration: the caller collects a parameter it did not ask for, and
- // the subscriber that should have been told about it is not.
- if (Native.SPA_RESULT_IS_ASYNC(rc))
+ return CoreSync.RoundTripAsync(
+ _ctx,
+ () =>
{
- int key = Native.SPA_RESULT_ASYNC_SEQ(rc);
- _answers[key] = [];
- onKey(key);
- }
-
- return rc;
- }, cancellationToken);
+ // 0 to uint.MaxValue: every value the object has of this parameter.
+ // The filter travels as an IntPtr because a pointer cannot be captured; it is only
+ // ever dereferenced here, synchronously, while the caller's pin is still held.
+ int rc = EnumParamsNative(
+ (void*)obj,
+ NextRequestTag(),
+ (uint)parameter,
+ 0,
+ uint.MaxValue,
+ (spa_pod*)filter
+ );
+
+ // Filed while the loop lock is still held, so no param event for this request can
+ // have been dispatched yet - the loop thread dispatches them, and it is blocked on
+ // that lock until the scope closes.
+ // Only when the daemon actually queued the request. A synchronous result has no
+ // sequence, and filing a collector under key 0 makes OnParam route any seq-0 param
+ // into this enumeration: the caller collects a parameter it did not ask for, and
+ // the subscriber that should have been told about it is not.
+ if (Native.SPA_RESULT_IS_ASYNC(rc))
+ {
+ int key = Native.SPA_RESULT_ASYNC_SEQ(rc);
+ _answers[key] = [];
+ onKey(key);
+ }
+
+ return rc;
+ },
+ cancellationToken
+ );
}
}
@@ -439,9 +498,10 @@ public unsafe void SubscribeParameters(params ReadOnlySpan paramet
{
ObjectDisposedException.ThrowIf(_disposed, this);
- Span ids = parameters.Length <= 16
- ? stackalloc uint[parameters.Length]
- : new uint[parameters.Length];
+ Span ids =
+ parameters.Length <= 16
+ ? stackalloc uint[parameters.Length]
+ : new uint[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
ids[i] = (uint)parameters[i];
@@ -477,19 +537,24 @@ public unsafe void SubscribeParameters(params ReadOnlySpan paramet
///
private protected unsafe void OnParam(int seq, spa_pod* param)
{
- if (param is null) return;
+ if (param is null)
+ return;
// The size is the producer's, and it is used to build the very span the parser then bounds
// itself against, so a lie here is one the parser cannot see through. Guarded the way
// PipeWireProfilerProxy guards the same shape.
uint size = *(uint*)param;
- if (size > int.MaxValue - 8) return;
+ if (size > int.MaxValue - 8)
+ return;
var bytes = new ReadOnlySpan(param, 8 + (int)size);
if (!SpaPod.TryParse(bytes, out SpaValue? value) || value is not SpaObject parsed)
return;
- bool filed = _answers.TryGetValue(Native.SPA_RESULT_ASYNC_SEQ(seq), out List? into);
+ bool filed = _answers.TryGetValue(
+ Native.SPA_RESULT_ASYNC_SEQ(seq),
+ out List? into
+ );
if (filed)
{
lock (into!)
@@ -519,7 +584,8 @@ private protected unsafe void OnInfo(spa_param_info* parameters, uint count)
described[i] = new PipeWireParameterInfo(
(SpaParamType)parameters![i].id,
((SpaParamInfoFlags)parameters[i].flags & SpaParamInfoFlags.Read) != 0,
- ((SpaParamInfoFlags)parameters[i].flags & SpaParamInfoFlags.Write) != 0);
+ ((SpaParamInfoFlags)parameters[i].flags & SpaParamInfoFlags.Write) != 0
+ );
}
Volatile.Write(ref _parameters, described);
@@ -542,18 +608,26 @@ private protected unsafe void OnInfo(spa_param_info* parameters, uint count)
private protected unsafe void OnInfoProperties(spa_dict* properties)
{
Action? observer = PropertiesObserved;
- if (observer is null || properties is null) return;
+ if (observer is null || properties is null)
+ return;
// A native callback frame, so nothing may escape it.
- try { observer(Id, PipeWireProperties.From(properties)); }
- catch (Exception ex) { OnHandlerFaulted(ex); }
+ try
+ {
+ observer(Id, PipeWireProperties.From(properties));
+ }
+ catch (Exception ex)
+ {
+ OnHandlerFaulted(ex);
+ }
}
/// Reports a subscriber that threw, where the logger is in scope.
private protected abstract void OnHandlerFaulted(Exception exception);
/// Resolves the instance a native callback belongs to, or null once it is gone.
- private protected static unsafe T? FromUserData(void* data) where T : PipeWireParameterObject
+ private protected static unsafe T? FromUserData(void* data)
+ where T : PipeWireParameterObject
{
var self = (T?)GCHandle.FromIntPtr((nint)data).Target;
return self is null || self._disposed ? null : self;
@@ -575,7 +649,8 @@ public ValueTask DisposeAsync()
private void DisposeCore()
{
- if (_disposed) return;
+ if (_disposed)
+ return;
_disposed = true;
// Anything still waiting for answers gets none; the round-trip it is awaiting completes or
@@ -585,5 +660,4 @@ private void DisposeCore()
GC.SuppressFinalize(this);
}
-
}
diff --git a/src/PipeWire.NET/Graph/PipeWirePort.cs b/src/PipeWire.NET/Graph/PipeWirePort.cs
index 7e42a22..2849fd9 100644
--- a/src/PipeWire.NET/Graph/PipeWirePort.cs
+++ b/src/PipeWire.NET/Graph/PipeWirePort.cs
@@ -27,7 +27,8 @@ internal PipeWirePort(
string? ObjectPath = null,
PipeWirePermissions Permissions = PipeWirePermissions.None,
uint InterfaceVersion = 0,
- PipeWireProperties? Properties = null)
+ PipeWireProperties? Properties = null
+ )
{
this.Properties = Properties ?? PipeWireProperties.Empty;
this.ObjectSerial = this.Properties.Serial;
diff --git a/src/PipeWire.NET/Graph/PipeWirePortConfig.cs b/src/PipeWire.NET/Graph/PipeWirePortConfig.cs
index f3569df..5727475 100644
--- a/src/PipeWire.NET/Graph/PipeWirePortConfig.cs
+++ b/src/PipeWire.NET/Graph/PipeWirePortConfig.cs
@@ -27,7 +27,8 @@ public sealed record PipeWirePortConfig(
SpaParamPortConfigMode Mode,
bool Monitor = false,
bool Control = false,
- SpaObject? Format = null)
+ SpaObject? Format = null
+)
{
/// Reads one out of a SPA_PARAM_PortConfig object, or null if it is not one.
///
@@ -36,14 +37,16 @@ public sealed record PipeWirePortConfig(
///
public static PipeWirePortConfig? From(SpaObject? param)
{
- if (param is null || param.ObjectType != SpaType.ObjectParamPortConfig) return null;
+ if (param is null || param.ObjectType != SpaType.ObjectParamPortConfig)
+ return null;
return new PipeWirePortConfig(
(SpaDirection)(param[(uint)SpaParamPortConfig.Direction] is SpaId d ? d.Value : 0),
(SpaParamPortConfigMode)(param[(uint)SpaParamPortConfig.Mode] is SpaId m ? m.Value : 0),
param[(uint)SpaParamPortConfig.Monitor] is SpaBool mon && mon.Value,
param[(uint)SpaParamPortConfig.Control] is SpaBool ctl && ctl.Value,
- param[(uint)SpaParamPortConfig.Format] as SpaObject);
+ param[(uint)SpaParamPortConfig.Format] as SpaObject
+ );
}
/// This configuration as the parameter object the daemon expects.
@@ -64,6 +67,10 @@ public SpaObject ToParameter()
if (Format is not null)
properties.Add(new SpaPodProperty((uint)SpaParamPortConfig.Format, 0, Format));
- return new SpaObject(SpaType.ObjectParamPortConfig, SpaParamType.PortConfig, [.. properties]);
+ return new SpaObject(
+ SpaType.ObjectParamPortConfig,
+ SpaParamType.PortConfig,
+ [.. properties]
+ );
}
}
diff --git a/src/PipeWire.NET/Graph/PipeWirePortProxy.cs b/src/PipeWire.NET/Graph/PipeWirePortProxy.cs
index fa59ddb..f536079 100644
--- a/src/PipeWire.NET/Graph/PipeWirePortProxy.cs
+++ b/src/PipeWire.NET/Graph/PipeWirePortProxy.cs
@@ -34,28 +34,49 @@ private PipeWirePortProxy(PipeWireContext ctx, uint id, ILogger logger)
: base(ctx, id) => _logger = logger;
internal static unsafe PipeWirePortProxy Bind(
- PipeWireContext ctx, pw_registry* registry, uint id, uint version, ILogger logger,
- Action? propertiesObserved = null)
+ PipeWireContext ctx,
+ pw_registry* registry,
+ uint id,
+ uint version,
+ ILogger logger,
+ Action? propertiesObserved = null
+ )
{
// The observer is in place before the proxy is bound: the first info after a bind is the
// only one that carries the object's properties (later ones set no PROPS in their change
// mask, so their dictionary arrives empty), and it can arrive the moment the bind is sent.
// Assigned after Bind returned, a fast daemon's first info found no observer and the
// properties never reached the registry.
- var control = new PipeWirePortProxy(ctx, id, logger) { PropertiesObserved = propertiesObserved };
- control.Attach(BoundProxy.Bind(
- ctx, registry, id, PipeWireKeys.PW_TYPE_INTERFACE_Port, version, NativeConstants.PW_VERSION_PORT,
- sizeof(pw_port_events),
- events =>
- {
- var table = (pw_port_events*)events;
- table->version = NativeConstants.PW_VERSION_PORT_EVENTS;
- table->info = &OnInfoCallback;
- table->param = &OnParamCallback;
- },
- static (proxy, hook, events, data) => Native.pw_port_add_listener(
- (pw_port*)proxy, (spa_hook*)hook, (pw_port_events*)events, (void*)data),
- control));
+ var control = new PipeWirePortProxy(ctx, id, logger)
+ {
+ PropertiesObserved = propertiesObserved,
+ };
+ control.Attach(
+ BoundProxy.Bind(
+ ctx,
+ registry,
+ id,
+ PipeWireKeys.PW_TYPE_INTERFACE_Port,
+ version,
+ NativeConstants.PW_VERSION_PORT,
+ sizeof(pw_port_events),
+ events =>
+ {
+ var table = (pw_port_events*)events;
+ table->version = NativeConstants.PW_VERSION_PORT_EVENTS;
+ table->info = &OnInfoCallback;
+ table->param = &OnParamCallback;
+ },
+ static (proxy, hook, events, data) =>
+ Native.pw_port_add_listener(
+ (pw_port*)proxy,
+ (spa_hook*)hook,
+ (pw_port_events*)events,
+ (void*)data
+ ),
+ control
+ )
+ );
return control;
}
@@ -67,8 +88,8 @@ internal static unsafe PipeWirePortProxy Bind(
/// negotiated format, and neither does one whose link is still negotiating.
///
public Task> EnumerateFormatsAsync(
- CancellationToken cancellationToken = default) =>
- EnumerateParametersAsync(SpaParamType.Format, cancellationToken);
+ CancellationToken cancellationToken = default
+ ) => EnumerateParametersAsync(SpaParamType.Format, cancellationToken);
/// Every format this port is willing to accept.
/// Abandons the wait.
@@ -78,8 +99,8 @@ public Task> EnumerateFormatsAsync(
/// .
///
public Task> EnumerateSupportedFormatsAsync(
- CancellationToken cancellationToken = default) =>
- EnumerateParametersAsync(SpaParamType.EnumFormat, cancellationToken);
+ CancellationToken cancellationToken = default
+ ) => EnumerateParametersAsync(SpaParamType.EnumFormat, cancellationToken);
/// The latency the graph has settled on for this port, in both directions.
/// Abandons the wait.
@@ -88,8 +109,8 @@ public Task> EnumerateSupportedFormatsAsync(
/// port's figure.
///
public Task> EnumerateLatencyAsync(
- CancellationToken cancellationToken = default) =>
- EnumerateParametersAsync(SpaParamType.Latency, cancellationToken);
+ CancellationToken cancellationToken = default
+ ) => EnumerateParametersAsync(SpaParamType.Latency, cancellationToken);
/// The settled latency of this port, read as a typed value per direction.
/// Abandons the wait.
@@ -100,15 +121,17 @@ public Task> EnumerateLatencyAsync(
/// something this version does not model.
///
public async Task> GetLatenciesAsync(
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
- ImmutableArray raw =
- await EnumerateLatencyAsync(cancellationToken).ConfigureAwait(false);
+ ImmutableArray raw = await EnumerateLatencyAsync(cancellationToken)
+ .ConfigureAwait(false);
var latencies = ImmutableArray.CreateBuilder(raw.Length);
foreach (SpaObject param in raw)
{
- if (PipeWireLatency.From(param) is { } latency) latencies.Add(latency);
+ if (PipeWireLatency.From(param) is { } latency)
+ latencies.Add(latency);
}
return latencies.ToImmutable();
@@ -117,23 +140,33 @@ public async Task> GetLatenciesAsync(
/// The metadata travelling through this port, per direction.
/// Abandons the wait.
public async Task> GetTagsAsync(
- CancellationToken cancellationToken = default)
+ CancellationToken cancellationToken = default
+ )
{
- ImmutableArray raw =
- await EnumerateParametersAsync(SpaParamType.Tag, cancellationToken).ConfigureAwait(false);
+ ImmutableArray raw = await EnumerateParametersAsync(
+ SpaParamType.Tag,
+ cancellationToken
+ )
+ .ConfigureAwait(false);
var tags = ImmutableArray.CreateBuilder(raw.Length);
foreach (SpaObject param in raw)
{
- if (PipeWireTag.From(param) is { } tag) tags.Add(tag);
+ if (PipeWireTag.From(param) is { } tag)
+ tags.Add(tag);
}
return tags.ToImmutable();
}
private protected override unsafe int EnumParamsNative(
- void* proxy, int seq, uint id, uint start, uint num, spa_pod* filter) =>
- Native.pw_port_enum_params((pw_port*)proxy, seq, id, start, num, filter);
+ void* proxy,
+ int seq,
+ uint id,
+ uint start,
+ uint num,
+ spa_pod* filter
+ ) => Native.pw_port_enum_params((pw_port*)proxy, seq, id, start, num, filter);
///
/// ENOTSUP, not a call. pw_port_methods has no set_param: a port's format is
@@ -142,13 +175,21 @@ private protected override unsafe int EnumParamsNative(
/// SetParameterAsync and hiding it would be worse, because a caller holding the base
/// type would walk straight past the override.
///
- private protected override unsafe int SetParamNative(void* proxy, uint id, uint flags, spa_pod* param) =>
- -95;
-
- private protected override unsafe int SubscribeParamsNative(void* proxy, uint* ids, uint count) =>
- Native.pw_port_subscribe_params((pw_port*)proxy, ids, count);
-
- private protected override void OnHandlerFaulted(Exception exception) => LogHandlerFaulted(exception);
+ private protected override unsafe int SetParamNative(
+ void* proxy,
+ uint id,
+ uint flags,
+ spa_pod* param
+ ) => -95;
+
+ private protected override unsafe int SubscribeParamsNative(
+ void* proxy,
+ uint* ids,
+ uint count
+ ) => Native.pw_port_subscribe_params((pw_port*)proxy, ids, count);
+
+ private protected override void OnHandlerFaulted(Exception exception) =>
+ LogHandlerFaulted(exception);
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe void OnInfoCallback(void* data, pw_port_info* info)
@@ -171,7 +212,13 @@ private static unsafe void OnInfoCallback(void* data, pw_port_info* info)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe void OnParamCallback(
- void* data, int seq, uint id, uint index, uint next, spa_pod* param)
+ void* data,
+ int seq,
+ uint id,
+ uint index,
+ uint next,
+ spa_pod* param
+ )
{
try
{
@@ -183,6 +230,10 @@ private static unsafe void OnParamCallback(
}
}
- [LoggerMessage(EventId = 34400, Level = LogLevel.Warning, Message = "a port parameter handler threw")]
+ [LoggerMessage(
+ EventId = 34400,
+ Level = LogLevel.Warning,
+ Message = "a port parameter handler threw"
+ )]
private partial void LogHandlerFaulted(Exception exception);
}
diff --git a/src/PipeWire.NET/Graph/PipeWireProcessLatency.cs b/src/PipeWire.NET/Graph/PipeWireProcessLatency.cs
index bdf091f..a1fb3bc 100644
--- a/src/PipeWire.NET/Graph/PipeWireProcessLatency.cs
+++ b/src/PipeWire.NET/Graph/PipeWireProcessLatency.cs
@@ -23,20 +23,25 @@ public sealed record PipeWireProcessLatency(float Quantum = 0f, int Rate = 0, lo
/// Reads one out of a SPA_PARAM_ProcessLatency object, or null if it is not one.
public static PipeWireProcessLatency? From(SpaObject? param)
{
- if (param is null || param.ObjectType != SpaType.ObjectParamProcessLatency) return null;
+ if (param is null || param.ObjectType != SpaType.ObjectParamProcessLatency)
+ return null;
return new PipeWireProcessLatency(
param[(uint)SpaParamProcessLatency.Quantum] is SpaFloat q ? q.Value : 0f,
param[(uint)SpaParamProcessLatency.Rate] is SpaInt r ? r.Value : 0,
- param[(uint)SpaParamProcessLatency.Ns] is SpaLong n ? n.Value : 0L);
+ param[(uint)SpaParamProcessLatency.Ns] is SpaLong n ? n.Value : 0L
+ );
}
/// This latency as the parameter object the daemon expects.
public SpaObject ToParameter() =>
- new(SpaType.ObjectParamProcessLatency, SpaParamType.ProcessLatency,
- [
- new SpaPodProperty((uint)SpaParamProcessLatency.Quantum, 0, new SpaFloat(Quantum)),
- new SpaPodProperty((uint)SpaParamProcessLatency.Rate, 0, new SpaInt(Rate)),
- new SpaPodProperty((uint)SpaParamProcessLatency.Ns, 0, new SpaLong(Ns)),
- ]);
+ new(
+ SpaType.ObjectParamProcessLatency,
+ SpaParamType.ProcessLatency,
+ [
+ new SpaPodProperty((uint)SpaParamProcessLatency.Quantum, 0, new SpaFloat(Quantum)),
+ new SpaPodProperty((uint)SpaParamProcessLatency.Rate, 0, new SpaInt(Rate)),
+ new SpaPodProperty((uint)SpaParamProcessLatency.Ns, 0, new SpaLong(Ns)),
+ ]
+ );
}
diff --git a/src/PipeWire.NET/Graph/PipeWireProfiler.cs b/src/PipeWire.NET/Graph/PipeWireProfiler.cs
index 89d5390..32aea31 100644
--- a/src/PipeWire.NET/Graph/PipeWireProfiler.cs
+++ b/src/PipeWire.NET/Graph/PipeWireProfiler.cs
@@ -16,7 +16,8 @@ internal PipeWireProfiler(
uint Id,
PipeWirePermissions Permissions,
uint InterfaceVersion,
- PipeWireProperties? Properties = null)
+ PipeWireProperties? Properties = null
+ )
{
this.Properties = Properties ?? PipeWireProperties.Empty;
this.ObjectSerial = this.Properties.Serial;
@@ -42,5 +43,4 @@ internal PipeWireProfiler(
///
public ulong? ObjectSerial { get; }
-
}
diff --git a/src/PipeWire.NET/Graph/PipeWireProfilerProxy.cs b/src/PipeWire.NET/Graph/PipeWireProfilerProxy.cs
index 8977d9f..1e8460c 100644
--- a/src/PipeWire.NET/Graph/PipeWireProfilerProxy.cs
+++ b/src/PipeWire.NET/Graph/PipeWireProfilerProxy.cs
@@ -42,7 +42,12 @@ private PipeWireProfilerProxy(uint id, ILogger logger)
public event Action? ProfileReceived;
internal static unsafe PipeWireProfilerProxy Bind(
- PipeWireContext ctx, pw_registry* registry, uint id, uint version, ILogger logger)
+ PipeWireContext ctx,
+ pw_registry* registry,
+ uint id,
+ uint version,
+ ILogger logger
+ )
{
// The profiler's protocol marshal lives in a module the client loads for itself; without
// it pw_proxy_new has nothing to build the proxy with and the bind returns null. Neither
@@ -51,7 +56,12 @@ internal static unsafe PipeWireProfilerProxy Bind(
var reader = new PipeWireProfilerProxy(id, logger);
reader._bound = BoundProxy.Bind(
- ctx, registry, id, PipeWireKeys.PW_TYPE_INTERFACE_Profiler, version, NativeConstants.PW_VERSION_PROFILER,
+ ctx,
+ registry,
+ id,
+ PipeWireKeys.PW_TYPE_INTERFACE_Profiler,
+ version,
+ NativeConstants.PW_VERSION_PROFILER,
sizeof(pw_profiler_events),
events =>
{
@@ -59,9 +69,15 @@ internal static unsafe PipeWireProfilerProxy Bind(
table->version = NativeConstants.PW_VERSION_PROFILER_EVENTS;
table->profile = &OnProfileCallback;
},
- (proxy, hook, events, data) => Native.pw_profiler_add_listener(
- (void*)proxy, (spa_hook*)hook, (pw_profiler_events*)events, (void*)data),
- reader);
+ (proxy, hook, events, data) =>
+ Native.pw_profiler_add_listener(
+ (void*)proxy,
+ (spa_hook*)hook,
+ (pw_profiler_events*)events,
+ (void*)data
+ ),
+ reader
+ );
reader._bound.Removed = reader.RaiseRemoved;
@@ -82,14 +98,19 @@ internal static unsafe PipeWireProfilerProxy Bind(
private void RaiseRemoved()
{
Action? handler = Removed;
- if (handler is null) return;
+ if (handler is null)
+ return;
// A native callback frame, so nothing may escape it.
- try { handler(); }
- catch (Exception) { /* a subscriber that throws must not reach the daemon */ }
+ try
+ {
+ handler();
+ }
+ catch (Exception)
+ { /* a subscriber that throws must not reach the daemon */
+ }
}
-
/// Loads the profiler extension module into this context, once.
/// Loading it twice is harmless; the module refcounts.
private static unsafe void EnsureProfilerModule(PipeWireContext ctx)
@@ -105,11 +126,13 @@ private static unsafe void EnsureProfilerModule(PipeWireContext ctx)
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static unsafe void OnProfileCallback(void* data, spa_pod* pod)
{
- if (data is null || pod is null) return;
+ if (data is null || pod is null)
+ return;
PipeWireProfilerProxy? self;
try
{
- if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireProfilerProxy found) return;
+ if (GCHandle.FromIntPtr((nint)data).Target is not PipeWireProfilerProxy found)
+ return;
self = found;
}
catch (Exception)
@@ -118,7 +141,8 @@ private static unsafe void OnProfileCallback(void* data, spa_pod* pod)
return;
}
- if (self._disposed) return;
+ if (self._disposed)
+ return;
try
{
@@ -153,12 +177,14 @@ private static unsafe void OnProfileCallback(void* data, spa_pod* pod)
internal static unsafe bool TryParseReport(
spa_pod* pod,
out ImmutableArray reports,
- out int size)
+ out int size
+ )
{
reports = [];
size = 0;
- if (pod is null) return false;
+ if (pod is null)
+ return false;
// Checked before the cast. A size near uint.MaxValue casts to a negative length, and
// the span constructor is the one place that would not tell us so.
@@ -171,7 +197,8 @@ internal static unsafe bool TryParseReport(
size = 8 + (int)pod->size;
var bytes = new ReadOnlySpan(pod, size);
- if (!SpaPod.TryParse(bytes, out SpaValue? value)) return false;
+ if (!SpaPod.TryParse(bytes, out SpaValue? value))
+ return false;
switch (value)
{
@@ -180,8 +207,7 @@ internal static unsafe bool TryParseReport(
return true;
case SpaStruct outer:
- ImmutableArray found =
- [.. outer.Fields.OfType()];
+ ImmutableArray found = [.. outer.Fields.OfType()];
reports = found;
return found.Length > 0;
@@ -207,21 +233,32 @@ public ValueTask DisposeAsync()
private void DisposeCore()
{
- if (_disposed) return;
+ if (_disposed)
+ return;
_disposed = true;
_bound?.Dispose();
_bound = null;
}
- [LoggerMessage(EventId = 34000, Level = LogLevel.Warning,
- Message = "a profiler report of {Size} bytes did not parse as an object")]
+ [LoggerMessage(
+ EventId = 34000,
+ Level = LogLevel.Warning,
+ Message = "a profiler report of {Size} bytes did not parse as an object"
+ )]
private partial void LogUnparsedReport(int size);
- [LoggerMessage(EventId = 34001, Level = LogLevel.Error, Message = "dispatching a profiler report failed")]
+ [LoggerMessage(
+ EventId = 34001,
+ Level = LogLevel.Error,
+ Message = "dispatching a profiler report failed"
+ )]
private partial void LogProfileDispatchFailed(Exception ex);
- [LoggerMessage(EventId = 34002, Level = LogLevel.Error,
- Message = "a ProfileReceived handler for profiler {ProfilerId} threw")]
+ [LoggerMessage(
+ EventId = 34002,
+ Level = LogLevel.Error,
+ Message = "a ProfileReceived handler for profiler {ProfilerId} threw"
+ )]
private partial void LogHandlerFaulted(uint profilerId, Exception ex);
}
diff --git a/src/PipeWire.NET/Graph/PipeWireProperties.cs b/src/PipeWire.NET/Graph/PipeWireProperties.cs
index 57f170b..dcf61d7 100644
--- a/src/PipeWire.NET/Graph/PipeWireProperties.cs
+++ b/src/PipeWire.NET/Graph/PipeWireProperties.cs
@@ -35,8 +35,7 @@ public sealed class PipeWireProperties : IReadOnlyDictionary
private PipeWireProperties(FrozenDictionary items) => _items = items;
/// An object the daemon sent no properties for.
- public static PipeWireProperties Empty { get; } =
- new(FrozenDictionary.Empty);
+ public static PipeWireProperties Empty { get; } = new(FrozenDictionary.Empty);
///
public string this[string key] => _items[key];
@@ -65,7 +64,9 @@ public bool TryGetValue(string key, [MaybeNullWhen(false)] out string value) =>
/// Reads a property that names another object, or null when it is absent or unusable.
internal uint? Id(string key) =>
- _items.TryGetValue(key, out string? raw) && uint.TryParse(raw, out uint value) ? value : null;
+ _items.TryGetValue(key, out string? raw) && uint.TryParse(raw, out uint value)
+ ? value
+ : null;
///
internal int? Int(string key) =>
@@ -77,7 +78,8 @@ internal bool Flag(string key) =>
/// Reads object.serial, which every object carries.
internal ulong? Serial =>
- _items.TryGetValue(PipeWireKeys.PW_KEY_OBJECT_SERIAL, out string? raw) && ulong.TryParse(raw, out ulong serial)
+ _items.TryGetValue(PipeWireKeys.PW_KEY_OBJECT_SERIAL, out string? raw)
+ && ulong.TryParse(raw, out ulong serial)
? serial
: null;
@@ -95,23 +97,29 @@ internal bool Flag(string key) =>
///
internal static unsafe PipeWireProperties From(spa_dict* dict)
{
- if (dict is null) return Empty;
+ if (dict is null)
+ return Empty;
int count = (int)dict->n_items;
- if (count <= 0 || dict->items is null) return Empty;
+ if (count <= 0 || dict->items is null)
+ return Empty;
var items = new Dictionary(count, StringComparer.Ordinal);
for (int i = 0; i < count; i++)
{
spa_dict_item* item = dict->items + i;
- if (item->key is null || item->value is null) continue;
+ if (item->key is null || item->value is null)
+ continue;
string key = Encoding.UTF8.GetString(DaemonText.Bytes(item->key));
- if (items.ContainsKey(key)) continue;
+ if (items.ContainsKey(key))
+ continue;
items[key] = Encoding.UTF8.GetString(DaemonText.Bytes(item->value));
}
- return items.Count == 0 ? Empty : new PipeWireProperties(items.ToFrozenDictionary(StringComparer.Ordinal));
+ return items.Count == 0
+ ? Empty
+ : new PipeWireProperties(items.ToFrozenDictionary(StringComparer.Ordinal));
}
/// Wraps properties this library assembled rather than read off the wire.
@@ -129,11 +137,14 @@ internal static PipeWireProperties FromItems(IReadOnlyDictionary
///
internal PipeWireProperties MergedWith(PipeWireProperties other)
{
- if (other.Count == 0) return this;
- if (Count == 0) return other;
+ if (other.Count == 0)
+ return this;
+ if (Count == 0)
+ return other;
var items = new Dictionary(_items, StringComparer.Ordinal);
- foreach (KeyValuePair pair in other._items) items[pair.Key] = pair.Value;
+ foreach (KeyValuePair pair in other._items)
+ items[pair.Key] = pair.Value;
return new PipeWireProperties(items.ToFrozenDictionary(StringComparer.Ordinal));
}
}
diff --git a/src/PipeWire.NET/Graph/PipeWireRegistry.cs b/src/PipeWire.NET/Graph/PipeWireRegistry.cs
index 4a8681d..d381220 100644
--- a/src/PipeWire.NET/Graph/PipeWireRegistry.cs
+++ b/src/PipeWire.NET/Graph/PipeWireRegistry.cs
@@ -91,9 +91,11 @@ private sealed class PublishWaiters
internal TaskCompletionSource Add()
{
var waiter = new TaskCompletionSource(
- TaskCreationOptions.RunContinuationsAsynchronously);
+ TaskCreationOptions.RunContinuationsAsynchronously
+ );
- lock (_waiters) _waiters.Add(waiter);
+ lock (_waiters)
+ _waiters.Add(waiter);
return waiter;
}
@@ -121,26 +123,31 @@ internal void FailAll(Exception error)
private TaskCompletionSource[] Snapshot()
{
- lock (_waiters) return [.. _waiters];
+ lock (_waiters)
+ return [.. _waiters];
}
}
private PipeWireGraphSnapshot _current = PipeWireGraphSnapshot.Empty;
private long _version;
- private PipeWireProxyHandle? _registryOwner;
+ private PipeWireProxyHandle? _registryOwner;
private unsafe pw_registry_events* _events;
+
// Unmanaged, not a field: pw_registry_add_listener retains this pointer and a `fixed` block only
// pins for its own duration. Same reasoning as PipeWireStreamCore.
- private unsafe spa_hook* _hook;
- private GCHandle _selfHandle;
+ private unsafe spa_hook* _hook;
+ private GCHandle _selfHandle;
private bool _listenerOwned;
- private volatile bool _disposed;
+ private volatile bool _disposed;
/// Signature for .
/// The registry that published the snapshot.
/// The graph as it stands after the change.
- public delegate void GraphChangedHandler(PipeWireRegistry sender, PipeWireGraphSnapshot snapshot);
+ public delegate void GraphChangedHandler(
+ PipeWireRegistry sender,
+ PipeWireGraphSnapshot snapshot
+ );
///
/// Raised after every graph change, carrying the snapshot that reflects it.
@@ -196,8 +203,11 @@ private unsafe void InitializeNative()
// get_registry / add_listener touch loop-owned objects -> hold the loop lock.
using (_ctx.Lock())
{
- pw_registry* registry =
- Native.pw_core_get_registry(_ctx.CoreHandle, NativeConstants.PW_VERSION_REGISTRY, 0);
+ pw_registry* registry = Native.pw_core_get_registry(
+ _ctx.CoreHandle,
+ NativeConstants.PW_VERSION_REGISTRY,
+ 0
+ );
// The registry is a proxy like any other, so it gets the same owner: that keeps the
// core and context alive long enough to destroy it properly rather than having it
@@ -205,13 +215,17 @@ private unsafe void InitializeNative()
// pointer lives, so the two cannot disagree about whether it is still valid.
if (registry is not null)
_registryOwner = new PipeWireProxyHandle(
- (pw_proxy*)registry, _ctx.LoopOwner, _ctx.CoreOwner);
+ (pw_proxy*)registry,
+ _ctx.LoopOwner,
+ _ctx.CoreOwner
+ );
if (registry is null)
throw new PipeWireInteropException("pw_core_get_registry", -NativeLibc.ENOMEM);
- _events = (pw_registry_events*)NativeMemory.AllocZeroed((nuint)sizeof(pw_registry_events));
- _events->version = NativeConstants.PW_VERSION_REGISTRY_EVENTS;
- _events->global = &OnGlobal;
+ _events = (pw_registry_events*)
+ NativeMemory.AllocZeroed((nuint)sizeof(pw_registry_events));
+ _events->version = NativeConstants.PW_VERSION_REGISTRY_EVENTS;
+ _events->global = &OnGlobal;
_events->global_remove = &OnGlobalRemove;
_hook = (spa_hook*)NativeMemory.AllocZeroed((nuint)sizeof(spa_hook));
@@ -223,8 +237,12 @@ private unsafe void InitializeNative()
_registryOwner!.OwnListener(_events, _hook, _selfHandle);
_listenerOwned = true;
- int rc = Native.pw_registry_add_listener(registry, _hook, _events,
- (void*)GCHandle.ToIntPtr(_selfHandle));
+ int rc = Native.pw_registry_add_listener(
+ registry,
+ _hook,
+ _events,
+ (void*)GCHandle.ToIntPtr(_selfHandle)
+ );
if (rc < 0)
throw new PipeWireInteropException("pw_registry_add_listener", rc);
}
@@ -248,8 +266,12 @@ private unsafe void UnwindNative()
foreach (uint id in _awaitingPublish.Keys)
{
if (_awaitingPublish.TryRemove(id, out PublishWaiters? waiters))
- waiters.FailAll(new ObjectDisposedException(nameof(PipeWireRegistry),
- $"the registry was disposed while object {id} was being created."));
+ waiters.FailAll(
+ new ObjectDisposedException(
+ nameof(PipeWireRegistry),
+ $"the registry was disposed while object {id} was being created."
+ )
+ );
}
// Safe in either disposal order. The handle chain - proxy holds core holds context holds
@@ -269,9 +291,12 @@ private unsafe void UnwindNative()
// and handing them over, and nothing else would ever free them.
if (!_listenerOwned)
{
- if (_hook is not null) NativeMemory.Free(_hook);
- if (_events is not null) NativeMemory.Free(_events);
- if (_selfHandle.IsAllocated) _selfHandle.Free();
+ if (_hook is not null)
+ NativeMemory.Free(_hook);
+ if (_events is not null)
+ NativeMemory.Free(_events);
+ if (_selfHandle.IsAllocated)
+ _selfHandle.Free();
}
_hook = null;
@@ -376,25 +401,33 @@ private void AbandonOwnership(uint id, PipeWireProxyHandle proxy)
// Collects rather than raises: the granular removals are announced only after Publish, so a
// handler inspecting Current already sees the snapshot the event describes. Raising first
// inverts that contract - the port or link is still indexed while its removal is announced.
- private void CascadeRemove(uint nodeId, out List removedLinks, out List removedPorts)
+ private void CascadeRemove(
+ uint nodeId,
+ out List removedLinks,
+ out List removedPorts
+ )
{
removedLinks = [];
removedPorts = [];
foreach ((uint portId, PipeWirePort port) in _ports)
{
- if (port.NodeId != nodeId) continue;
+ if (port.NodeId != nodeId)
+ continue;
foreach ((uint linkId, PipeWireLink link) in _links)
{
- if (link.OutputPortId != portId && link.InputPortId != portId) continue;
- if (!_links.TryRemove(linkId, out _)) continue;
+ if (link.OutputPortId != portId && link.InputPortId != portId)
+ continue;
+ if (!_links.TryRemove(linkId, out _))
+ continue;
LogRemoved("link", linkId);
removedLinks.Add(linkId);
}
- if (!_ports.TryRemove(portId, out _)) continue;
+ if (!_ports.TryRemove(portId, out _))
+ continue;
LogRemoved("port", portId);
removedPorts.Add(portId);
@@ -437,8 +470,7 @@ private void FailPublishWaiter(uint id, string reason)
{
if (_awaitingPublish.TryRemove(id, out PublishWaiters? waiters))
{
- waiters.FailAll(new PipeWireInteropException(
- "bind", -NativeLibc.EINVAL, id, reason));
+ waiters.FailAll(new PipeWireInteropException("bind", -NativeLibc.EINVAL, id, reason));
}
}
@@ -447,11 +479,16 @@ private void FailPublishWaiter(uint id, string reason)
/// published. Falls back to the live collections when the object arrived first.
///
private async Task AwaitPublishedAsync(
- uint id, Func lookup, CancellationToken cancellationToken) where T : class, IPipeWireObject
+ uint id,
+ Func lookup,
+ CancellationToken cancellationToken
+ )
+ where T : class, IPipeWireObject
{
// A connection already known to be gone cannot announce anything, so waiting on it is a
// hang with extra steps. The refusal names the fault that ended it.
- if (_ctx.ConnectionFault is { } dead) throw dead;
+ if (_ctx.ConnectionFault is { } dead)
+ throw dead;
// A registry that is already gone will never publish anything, and `bound` can arrive after
// disposal, so this has to be checked here rather than only when disposal runs.
@@ -477,26 +514,42 @@ private async Task AwaitPublishedAsync(
// Cancels this caller's wait and nobody else's. The context shutdown also ends the wait
// by surfacing an error: a publish that depends on a stopped loop never arrives.
- using (cancellationToken.UnsafeRegister(
- static s => ((TaskCompletionSource)s!).TrySetCanceled(), waiter))
- using (_ctx.Shutdown.UnsafeRegister(
- static s => ((TaskCompletionSource)s!).TrySetException(
- new ObjectDisposedException(nameof(PipeWireContext),
- "the context was disposed while an object publish was in flight.")), waiter))
+ using (
+ cancellationToken.UnsafeRegister(
+ static s => ((TaskCompletionSource)s!).TrySetCanceled(),
+ waiter
+ )
+ )
+ using (
+ _ctx.Shutdown.UnsafeRegister(
+ static s =>
+ ((TaskCompletionSource)s!).TrySetException(
+ new ObjectDisposedException(
+ nameof(PipeWireContext),
+ "the context was disposed while an object publish was in flight."
+ )
+ ),
+ waiter
+ )
+ )
{
try
{
IPipeWireObject published = await waiter.Task.ConfigureAwait(false);
- if (published is T typed) return typed;
+ if (published is T typed)
+ return typed;
// Ids recycle. Between the daemon answering with this id and the global arriving,
// the object can be destroyed and the number handed to something else, so the
// publish that satisfies this waiter is not always what was created. That is a
// lost race rather than a broken invariant, and it is reported as one.
throw new PipeWireException(
- "create", -NativeLibc.ENOENT, id,
+ "create",
+ -NativeLibc.ENOENT,
+ id,
$"global {id} arrived as {published.Kind} rather than {typeof(T).Name}; "
- + "the id was reused before the new object was observed");
+ + "the id was reused before the new object was observed"
+ );
}
finally
{
@@ -507,9 +560,13 @@ private async Task AwaitPublishedAsync(
/// Drops one waiter, and the id's entry once it was the last.
private void Unregister(
- uint id, PublishWaiters waiters, TaskCompletionSource waiter)
+ uint id,
+ PublishWaiters waiters,
+ TaskCompletionSource waiter
+ )
{
- if (!waiters.Remove(waiter)) return;
+ if (!waiters.Remove(waiter))
+ return;
// Conditional on identity: a waiter that registered after this one emptied the list holds a
// different instance, and removing by key alone would strand it.
@@ -519,7 +576,8 @@ private void Unregister(
///