From 4cbea1b48afd5fa8921632057fc4c57b2418350a Mon Sep 17 00:00:00 2001
From: Jumar Macato <16554748+jmacato@users.noreply.github.com>
Date: Sat, 8 Jun 2024 02:17:00 +0800
Subject: [PATCH] add tmds.dbus.protocol
---
Avalonia.Desktop.slnf | 3 +-
Avalonia.sln | 7 +
.../Avalonia.FreeDesktop.csproj | 4 +-
.../Tmds.DBus.Protocol/ActionException.cs | 17 +
src/Linux/Tmds.DBus.Protocol/Address.cs | 220 +++
src/Linux/Tmds.DBus.Protocol/AddressReader.cs | 191 +++
src/Linux/Tmds.DBus.Protocol/Array.cs | 88 ++
.../ClientConnectionOptions.cs | 36 +
.../Tmds.DBus.Protocol/ClientSetupResult.cs | 19 +
.../Tmds.DBus.Protocol/CloseSafeHandle.cs | 17 +
.../Tmds.DBus.Protocol/ConnectException.cs | 10 +
.../Tmds.DBus.Protocol/Connection.DBus.cs | 112 ++
src/Linux/Tmds.DBus.Protocol/Connection.cs | 335 +++++
.../Tmds.DBus.Protocol/ConnectionOptions.cs | 7 +
src/Linux/Tmds.DBus.Protocol/Constants.cs | 7 +
.../Tmds.DBus.Protocol/DBusConnection.cs | 1290 +++++++++++++++++
.../Tmds.DBus.Protocol/DBusEnvironment.cs | 50 +
src/Linux/Tmds.DBus.Protocol/DBusException.cs | 15 +
src/Linux/Tmds.DBus.Protocol/DBusType.cs | 23 +
src/Linux/Tmds.DBus.Protocol/Dict.cs | 89 ++
.../DisconnectedException.cs | 6 +
.../Tmds.DBus.Protocol/DisposableMessage.cs | 18 +
src/Linux/Tmds.DBus.Protocol/Feature.cs | 14 +
src/Linux/Tmds.DBus.Protocol/GlobalUsings.cs | 14 +
src/Linux/Tmds.DBus.Protocol/IDBusWritable.cs | 6 +
.../Tmds.DBus.Protocol/IMessageStream.cs | 14 +
.../Tmds.DBus.Protocol/IMethodHandler.cs | 13 +
.../IMethodHandlerDictionary.cs | 9 +
.../Tmds.DBus.Protocol/IntrospectionXml.cs | 51 +
src/Linux/Tmds.DBus.Protocol/MatchRule.cs | 120 ++
src/Linux/Tmds.DBus.Protocol/Message.cs | 242 ++++
src/Linux/Tmds.DBus.Protocol/MessageBuffer.cs | 43 +
.../Tmds.DBus.Protocol/MessageBufferPool.cs | 42 +
src/Linux/Tmds.DBus.Protocol/MessageFlags.cs | 10 +
src/Linux/Tmds.DBus.Protocol/MessageHeader.cs | 14 +
src/Linux/Tmds.DBus.Protocol/MessagePool.cs | 27 +
src/Linux/Tmds.DBus.Protocol/MessageStream.cs | 403 +++++
src/Linux/Tmds.DBus.Protocol/MessageType.cs | 9 +
.../Tmds.DBus.Protocol/MessageWriter.Array.cs | 271 ++++
.../Tmds.DBus.Protocol/MessageWriter.Basic.cs | 234 +++
.../MessageWriter.Dictionary.cs | 128 ++
.../MessageWriter.Handle.cs | 28 +
.../MessageWriter.Header.cs | 215 +++
.../MessageWriter.IntrospectionXml.cs | 75 +
.../MessageWriter.Struct.cs | 299 ++++
.../MessageWriter.Variant.Dynamic.cs | 77 +
.../MessageWriter.Variant.cs | 9 +
.../MessageWriter.WriteT.Dynamic.cs | 994 +++++++++++++
.../MessageWriter.WriteT.cs | 82 ++
src/Linux/Tmds.DBus.Protocol/MessageWriter.cs | 193 +++
src/Linux/Tmds.DBus.Protocol/MethodContext.cs | 97 ++
.../Netstandard2_0Extensions.cs | 214 +++
.../Netstandard2_1Extensions.cs | 53 +
src/Linux/Tmds.DBus.Protocol/ObjectPath.cs | 16 +
src/Linux/Tmds.DBus.Protocol/ObserverFlags.cs | 12 +
.../Tmds.DBus.Protocol/PathNodeDictionary.cs | 311 ++++
.../Tmds.DBus.Protocol/PlatformDetection.cs | 19 +
.../DynamicallyAccessedMemberTypes.cs | 25 +
.../DynamicallyAccessedMembersAttribute.cs | 30 +
.../Polyfill/Nerdbank.Streams.Sequence.cs | 530 +++++++
.../Polyfill/NullableAttributes.cs | 144 ++
.../RequiresUnreferencedCodeAttribute.cs | 23 +
.../Polyfill/SequenceReader.cs | 417 ++++++
.../Polyfill/SequenceReaderExtensions.cs | 194 +++
.../UnconditionalSuppressMessageAttribute.cs | 32 +
.../Tmds.DBus.Protocol/ProtocolConstants.cs | 86 ++
.../Tmds.DBus.Protocol/ProtocolException.cs | 7 +
src/Linux/Tmds.DBus.Protocol/Reader.Array.cs | 166 +++
src/Linux/Tmds.DBus.Protocol/Reader.Basic.cs | 132 ++
.../Tmds.DBus.Protocol/Reader.Dictionary.cs | 48 +
src/Linux/Tmds.DBus.Protocol/Reader.Handle.cs | 36 +
.../Reader.ReadT.Dynamic.cs | 1045 +++++++++++++
src/Linux/Tmds.DBus.Protocol/Reader.ReadT.cs | 77 +
src/Linux/Tmds.DBus.Protocol/Reader.Struct.cs | 309 ++++
.../Reader.Variant.Dynamic.cs | 8 +
.../Tmds.DBus.Protocol/Reader.Variant.cs | 161 ++
src/Linux/Tmds.DBus.Protocol/Reader.cs | 90 ++
src/Linux/Tmds.DBus.Protocol/Signature.cs | 12 +
.../Tmds.DBus.Protocol/SignatureReader.cs | 271 ++++
.../Tmds.DBus.Protocol/SocketExtensions.cs | 232 +++
.../StringBuilderExtensions.cs | 24 +
src/Linux/Tmds.DBus.Protocol/Strings.cs | 29 +
src/Linux/Tmds.DBus.Protocol/Struct.cs | 469 ++++++
src/Linux/Tmds.DBus.Protocol/ThrowHelper.cs | 32 +
.../Tmds.DBus.Protocol.csproj | 22 +
.../Tmds.DBus.Protocol/TypeModel.Dynamic.cs | 134 ++
src/Linux/Tmds.DBus.Protocol/TypeModel.cs | 359 +++++
.../Tmds.DBus.Protocol/UnixFdCollection.cs | 244 ++++
src/Linux/Tmds.DBus.Protocol/Utf8Span.cs | 20 +
src/Linux/Tmds.DBus.Protocol/Variant.cs | 451 ++++++
.../Tmds.DBus.Protocol/VariantExtensions.cs | 41 +
src/Linux/Tmds.DBus.Protocol/VariantValue.cs | 795 ++++++++++
.../Tmds.DBus.Protocol/VariantValueType.cs | 30 +
93 files changed, 13643 insertions(+), 4 deletions(-)
create mode 100644 src/Linux/Tmds.DBus.Protocol/ActionException.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Address.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/AddressReader.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Array.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/ClientConnectionOptions.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/ClientSetupResult.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/CloseSafeHandle.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/ConnectException.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Connection.DBus.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Connection.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/ConnectionOptions.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Constants.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/DBusConnection.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/DBusEnvironment.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/DBusException.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/DBusType.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Dict.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/DisconnectedException.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/DisposableMessage.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Feature.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/GlobalUsings.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/IDBusWritable.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/IMessageStream.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/IMethodHandler.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/IMethodHandlerDictionary.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/IntrospectionXml.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MatchRule.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Message.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageBuffer.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageBufferPool.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageFlags.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageHeader.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessagePool.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageStream.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageType.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageWriter.Array.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageWriter.Basic.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageWriter.Dictionary.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageWriter.Handle.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageWriter.Header.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageWriter.IntrospectionXml.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageWriter.Struct.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageWriter.Variant.Dynamic.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageWriter.Variant.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageWriter.WriteT.Dynamic.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageWriter.WriteT.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MessageWriter.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/MethodContext.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Netstandard2_0Extensions.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Netstandard2_1Extensions.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/ObjectPath.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/ObserverFlags.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/PathNodeDictionary.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/PlatformDetection.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Polyfill/DynamicallyAccessedMemberTypes.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Polyfill/DynamicallyAccessedMembersAttribute.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Polyfill/Nerdbank.Streams.Sequence.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Polyfill/NullableAttributes.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Polyfill/RequiresUnreferencedCodeAttribute.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Polyfill/SequenceReader.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Polyfill/SequenceReaderExtensions.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Polyfill/UnconditionalSuppressMessageAttribute.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/ProtocolConstants.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/ProtocolException.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Reader.Array.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Reader.Basic.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Reader.Dictionary.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Reader.Handle.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Reader.ReadT.Dynamic.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Reader.ReadT.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Reader.Struct.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Reader.Variant.Dynamic.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Reader.Variant.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Reader.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Signature.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/SignatureReader.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/SocketExtensions.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/StringBuilderExtensions.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Strings.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Struct.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/ThrowHelper.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Tmds.DBus.Protocol.csproj
create mode 100644 src/Linux/Tmds.DBus.Protocol/TypeModel.Dynamic.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/TypeModel.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/UnixFdCollection.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Utf8Span.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/Variant.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/VariantExtensions.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/VariantValue.cs
create mode 100644 src/Linux/Tmds.DBus.Protocol/VariantValueType.cs
diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf
index 35eeab33ac..b32d6e3d70 100644
--- a/Avalonia.Desktop.slnf
+++ b/Avalonia.Desktop.slnf
@@ -30,15 +30,16 @@
"src\\Avalonia.MicroCom\\Avalonia.MicroCom.csproj",
"src\\Avalonia.Native\\Avalonia.Native.csproj",
"src\\Avalonia.OpenGL\\Avalonia.OpenGL.csproj",
- "src\\Avalonia.Vulkan\\Avalonia.Vulkan.csproj",
"src\\Avalonia.ReactiveUI\\Avalonia.ReactiveUI.csproj",
"src\\Avalonia.Remote.Protocol\\Avalonia.Remote.Protocol.csproj",
"src\\Avalonia.Themes.Fluent\\Avalonia.Themes.Fluent.csproj",
"src\\Avalonia.Themes.Simple\\Avalonia.Themes.Simple.csproj",
+ "src\\Avalonia.Vulkan\\Avalonia.Vulkan.csproj",
"src\\Avalonia.X11\\Avalonia.X11.csproj",
"src\\Headless\\Avalonia.Headless.Vnc\\Avalonia.Headless.Vnc.csproj",
"src\\Headless\\Avalonia.Headless\\Avalonia.Headless.csproj",
"src\\Linux\\Avalonia.LinuxFramebuffer\\Avalonia.LinuxFramebuffer.csproj",
+ "src\\Linux\\Tmds.DBus.Protocol\\Tmds.DBus.Protocol.csproj",
"src\\Linux\\Tmds.DBus.SourceGenerator\\Tmds.DBus.SourceGenerator.csproj",
"src\\Markup\\Avalonia.Markup.Xaml.Loader\\Avalonia.Markup.Xaml.Loader.csproj",
"src\\Markup\\Avalonia.Markup.Xaml\\Avalonia.Markup.Xaml.csproj",
diff --git a/Avalonia.sln b/Avalonia.sln
index 395a6fe559..5e98d26cc6 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -304,6 +304,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.RenderTests.WpfCom
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tmds.DBus.SourceGenerator", "src\Linux\Tmds.DBus.SourceGenerator\Tmds.DBus.SourceGenerator.csproj", "{12AE6CBC-C0A1-4BEF-AED6-81E566AAE7EB}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tmds.DBus.Protocol", "src\Linux\Tmds.DBus.Protocol\Tmds.DBus.Protocol.csproj", "{9A7672D7-77B9-4D50-AF22-6C5049C4712B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -708,6 +710,10 @@ Global
{12AE6CBC-C0A1-4BEF-AED6-81E566AAE7EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12AE6CBC-C0A1-4BEF-AED6-81E566AAE7EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{12AE6CBC-C0A1-4BEF-AED6-81E566AAE7EB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9A7672D7-77B9-4D50-AF22-6C5049C4712B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9A7672D7-77B9-4D50-AF22-6C5049C4712B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9A7672D7-77B9-4D50-AF22-6C5049C4712B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9A7672D7-77B9-4D50-AF22-6C5049C4712B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -796,6 +802,7 @@ Global
{DA5F1FF9-4259-4C54-B443-85CFA226EE6A} = {9CCA131B-DE95-4D44-8788-C3CAE28574CD}
{9AE1B827-21AC-4063-AB22-C8804B7F931E} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{12AE6CBC-C0A1-4BEF-AED6-81E566AAE7EB} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
+ {9A7672D7-77B9-4D50-AF22-6C5049C4712B} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
index 536d9238a2..e45b11fec1 100644
--- a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
+++ b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
@@ -13,12 +13,12 @@
-
+
@@ -51,8 +51,6 @@
-
-
diff --git a/src/Linux/Tmds.DBus.Protocol/ActionException.cs b/src/Linux/Tmds.DBus.Protocol/ActionException.cs
new file mode 100644
index 0000000000..48b9225306
--- /dev/null
+++ b/src/Linux/Tmds.DBus.Protocol/ActionException.cs
@@ -0,0 +1,17 @@
+namespace Tmds.DBus.Protocol;
+
+public static class ActionException
+{
+ // Exception used when the IDisposable returned by AddMatchAsync gets disposed.
+ public static bool IsObserverDisposed(Exception exception)
+ => object.ReferenceEquals(exception, DBusConnection.ObserverDisposedException);
+
+ // Exception used when the Connection gets disposed.
+ public static bool IsConnectionDisposed(Exception exception)
+ // note: Connection.DisposedException is only ever used as an InnerException of DisconnectedException,
+ // so we directly check for that.
+ => object.ReferenceEquals(exception?.InnerException, Connection.DisposedException);
+
+ public static bool IsDisposed(Exception exception)
+ => IsObserverDisposed(exception) || IsConnectionDisposed(exception);
+}
\ No newline at end of file
diff --git a/src/Linux/Tmds.DBus.Protocol/Address.cs b/src/Linux/Tmds.DBus.Protocol/Address.cs
new file mode 100644
index 0000000000..c43b7ca38e
--- /dev/null
+++ b/src/Linux/Tmds.DBus.Protocol/Address.cs
@@ -0,0 +1,220 @@
+using System.IO.MemoryMappedFiles;
+
+namespace Tmds.DBus.Protocol;
+
+public static class Address
+{
+ private static bool _systemAddressResolved = false;
+ private static string? _systemAddress = null;
+ private static bool _sessionAddressResolved = false;
+ private static string? _sessionAddress = null;
+
+ public static string? System
+ {
+ get
+ {
+ if (_systemAddressResolved)
+ {
+ return _systemAddress;
+ }
+
+ _systemAddress = Environment.GetEnvironmentVariable("DBUS_SYSTEM_BUS_ADDRESS");
+
+ if (string.IsNullOrEmpty(_systemAddress) && !PlatformDetection.IsWindows())
+ {
+ _systemAddress = "unix:path=/var/run/dbus/system_bus_socket";
+ }
+
+ _systemAddressResolved = true;
+ return _systemAddress;
+ }
+ }
+
+ public static string? Session
+ {
+ get
+ {
+ if (_sessionAddressResolved)
+ {
+ return _sessionAddress;
+ }
+
+ _sessionAddress = Environment.GetEnvironmentVariable("DBUS_SESSION_BUS_ADDRESS");
+
+ if (string.IsNullOrEmpty(_sessionAddress))
+ {
+ if (PlatformDetection.IsWindows())
+ {
+ _sessionAddress = GetSessionBusAddressFromSharedMemory();
+ }
+ else
+ {
+ _sessionAddress = GetSessionBusAddressFromX11();
+ }
+ }
+
+ _sessionAddressResolved = true;
+ return _sessionAddress;
+ }
+ }
+
+ private static string? GetSessionBusAddressFromX11()
+ {
+ if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DISPLAY")))
+ {
+ var display = XOpenDisplay(null);
+ if (display == IntPtr.Zero)
+ {
+ return null;
+ }
+ string username;
+ unsafe
+ {
+ const int BufLen = 1024;
+ byte* stackBuf = stackalloc byte[BufLen];
+ Passwd passwd;
+ IntPtr result;
+ getpwuid_r(getuid(), out passwd, stackBuf, BufLen, out result);
+ if (result != IntPtr.Zero)
+ {
+ username = Marshal.PtrToStringAnsi(passwd.Name)!;
+ }
+ else
+ {
+ return null;
+ }
+ }
+ var machineId = DBusEnvironment.MachineId.Replace("-", string.Empty);
+ var selectionName = $"_DBUS_SESSION_BUS_SELECTION_{username}_{machineId}";
+ var selectionAtom = XInternAtom(display, selectionName, false);
+ if (selectionAtom == IntPtr.Zero)
+ {
+ return null;
+ }
+ var owner = XGetSelectionOwner(display, selectionAtom);
+ if (owner == IntPtr.Zero)
+ {
+ return null;
+ }
+ var addressAtom = XInternAtom(display, "_DBUS_SESSION_BUS_ADDRESS", false);
+ if (addressAtom == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ IntPtr actualReturnType;
+ IntPtr actualReturnFormat;
+ IntPtr nrItemsReturned;
+ IntPtr bytesAfterReturn;
+ IntPtr propReturn;
+
+ int rv = XGetWindowProperty(display, owner, addressAtom, 0, 1024, false, (IntPtr)31 /* XA_STRING */,
+ out actualReturnType, out actualReturnFormat, out nrItemsReturned, out bytesAfterReturn, out propReturn);
+ string? address = rv == 0 ? Marshal.PtrToStringAnsi(propReturn) : null;
+ if (propReturn != IntPtr.Zero)
+ {
+ XFree(propReturn);
+ }
+
+ XCloseDisplay(display);
+
+ return address;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ private static string? GetSessionBusAddressFromSharedMemory()
+ {
+ string? result = ReadSharedMemoryString("DBusDaemonAddressInfo", 255);
+ if (string.IsNullOrEmpty(result))
+ {
+ result = ReadSharedMemoryString("DBusDaemonAddressInfoDebug", 255);
+ }
+ return result;
+ }
+
+ private static string? ReadSharedMemoryString(string id, long maxlen = -1)
+ {
+ if (!PlatformDetection.IsWindows())
+ {
+ return null;
+ }
+ MemoryMappedFile? shmem;
+ try
+ {
+ shmem = MemoryMappedFile.OpenExisting(id);
+ }
+ catch
+ {
+ shmem = null;
+ }
+ if (shmem == null)
+ {
+ return null;
+ }
+
+ MemoryMappedViewStream s = shmem.CreateViewStream();
+ long len = s.Length;
+ if (maxlen >= 0 && len > maxlen)
+ {
+ len = maxlen;
+ }
+ if (len == 0)
+ {
+ return string.Empty;
+ }
+ if (len > int.MaxValue)
+ {
+ len = int.MaxValue;
+ }
+ byte[] bytes = new byte[len];
+ int count = s.Read(bytes, 0, (int)len);
+ if (count <= 0)
+ {
+ return string.Empty;
+ }
+
+ count = 0;
+ while (count < len && bytes[count] != 0)
+ {
+ count++;
+ }
+
+ return Encoding.UTF8.GetString(bytes, 0, count);
+ }
+
+ struct Passwd
+ {
+ public IntPtr Name;
+ public IntPtr Password;
+ public uint UserID;
+ public uint GroupID;
+ public IntPtr UserInfo;
+ public IntPtr HomeDir;
+ public IntPtr Shell;
+ }
+
+ [DllImport("libc")]
+ private static extern unsafe int getpwuid_r(uint uid, out Passwd pwd, byte* buf, int bufLen, out IntPtr result);
+ [DllImport("libc")]
+ private static extern uint getuid();
+
+ [DllImport("libX11")]
+ private static extern IntPtr XOpenDisplay(string? name);
+ [DllImport("libX11")]
+ private static extern int XCloseDisplay(IntPtr display);
+ [DllImport("libX11")]
+ private static extern IntPtr XInternAtom(IntPtr display, string atom_name, bool only_if_exists);
+ [DllImport("libX11")]
+ private static extern int XGetWindowProperty(IntPtr display, IntPtr w, IntPtr property,
+ int long_offset, int long_length, bool delete, IntPtr req_type,
+ out IntPtr actual_type_return, out IntPtr actual_format_return,
+ out IntPtr nitems_return, out IntPtr bytes_after_return, out IntPtr prop_return);
+ [DllImport("libX11")]
+ private static extern int XFree(IntPtr data);
+ [DllImport("libX11")]
+ private static extern IntPtr XGetSelectionOwner(IntPtr display, IntPtr Atom);
+}
diff --git a/src/Linux/Tmds.DBus.Protocol/AddressReader.cs b/src/Linux/Tmds.DBus.Protocol/AddressReader.cs
new file mode 100644
index 0000000000..7ccf3fa168
--- /dev/null
+++ b/src/Linux/Tmds.DBus.Protocol/AddressReader.cs
@@ -0,0 +1,191 @@
+namespace Tmds.DBus.Protocol;
+
+static class AddressParser
+{
+ public struct AddressEntry
+ {
+ internal string String { get; }
+ internal int Offset { get; }
+ internal int Count { get; }
+
+ internal AddressEntry(string s, int offset, int count) =>
+ (String, Offset, Count) = (s, offset, count);
+
+ internal ReadOnlySpan AsSpan() => String.AsSpan(Offset, Count);
+
+ public override string ToString() => AsSpan().AsString();
+ }
+
+ public static bool TryGetNextEntry(string addresses, ref AddressEntry address)
+ {
+ int offset = address.String is null ? 0 : address.Offset + address.Count + 1;
+ if (offset >= addresses.Length - 1)
+ {
+ return false;
+ }
+ ReadOnlySpan span = addresses.AsSpan().Slice(offset);
+ int length = span.IndexOf(';');
+ if (length == -1)
+ {
+ length = span.Length;
+ }
+ address = new AddressEntry(addresses, offset, length);
+ return true;
+ }
+
+ public static bool IsType(AddressEntry address, string type)
+ {
+ ReadOnlySpan span = address.AsSpan();
+ return span.Length > type.Length && span[type.Length] == ':' && span.StartsWith(type.AsSpan());
+ }
+
+ public static void ParseTcpProperties(AddressEntry address, out string host, out int? port, out Guid guid)
+ {
+ host = null!;
+ port = null;
+ guid = default;
+ ReadOnlySpan properties = GetProperties(address);
+ while (TryParseProperty(ref properties, out ReadOnlySpan key, out ReadOnlySpan value))
+ {
+ if (key.SequenceEqual("host".AsSpan()))
+ {
+ host = Unescape(value);
+ }
+ else if (key.SequenceEqual("port".AsSpan()))
+ {
+ port = int.Parse(Unescape(value));
+ }
+ else if (key.SequenceEqual("guid".AsSpan()))
+ {
+ guid = Guid.ParseExact(Unescape(value), "N");
+ }
+ }
+ if (host is null)
+ {
+ host = "localhost";
+ }
+ }
+
+ public static void ParseUnixProperties(AddressEntry address, out string path, out Guid guid)
+ {
+ path = null!;
+ bool isAbstract = false;
+ guid = default;
+ ReadOnlySpan properties = GetProperties(address);
+ while (TryParseProperty(ref properties, out ReadOnlySpan key, out ReadOnlySpan value))
+ {
+ if (key.SequenceEqual("path".AsSpan()))
+ {
+ path = Unescape(value);
+ }
+ else if (key.SequenceEqual("abstract".AsSpan()))
+ {
+ isAbstract = true;
+ path = Unescape(value);
+ }
+ else if (key.SequenceEqual("guid".AsSpan()))
+ {
+ guid = Guid.ParseExact(Unescape(value), "N");
+ }
+ }
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentException("path");
+ }
+ if (isAbstract)
+ {
+ path = (char)'\0' + path;
+ }
+ }
+
+ private static ReadOnlySpan GetProperties(AddressEntry address)
+ {
+ ReadOnlySpan span = address.AsSpan();
+ int colonPos = span.IndexOf(':');
+ if (colonPos == -1)
+ {
+ throw new FormatException("No colon found.");
+ }
+ return span.Slice(colonPos + 1);
+ }
+
+ public static bool TryParseProperty(ref ReadOnlySpan properties, out ReadOnlySpan key, out ReadOnlySpan value)
+ {
+ if (properties.Length == 0)
+ {
+ key = default;
+ value = default;
+ return false;
+ }
+ int end = properties.IndexOf(',');
+ ReadOnlySpan property;
+ if (end == -1)
+ {
+ property = properties;
+ properties = default;
+ }
+ else
+ {
+ property = properties.Slice(0, end);
+ properties = properties.Slice(end + 1);
+ }
+ int equalPos = property.IndexOf('=');
+ if (equalPos == -1)
+ {
+ throw new FormatException("No equals sign found.");
+ }
+ key = property.Slice(0, equalPos);
+ value = property.Slice(equalPos + 1);
+ return true;
+ }
+
+ private static string Unescape(ReadOnlySpan value)
+ {
+ if (!value.Contains("%".AsSpan(), StringComparison.Ordinal))
+ {
+ return value.AsString();
+ }
+ Span unescaped = stackalloc char[Constants.StackAllocCharThreshold];
+ int pos = 0;
+ for (int i = 0; i < value.Length;)
+ {
+ char c = value[i++];
+ if (c != '%')
+ {
+ unescaped[pos++] = c;
+ }
+ else if (i + 2 < value.Length)
+ {
+ int a = FromHexChar(value[i++]);
+ int b = FromHexChar(value[i++]);
+ if (a == -1 || b == -1)
+ {
+ throw new FormatException("Invalid hex char.");
+ }
+ unescaped[pos++] = (char)((a << 4) + b);
+ }
+ else
+ {
+ throw new FormatException("Escape sequence is too short.");
+ }
+ }
+ return unescaped.Slice(0, pos).AsString();
+
+ static int FromHexChar(char c)
+ {
+ if (c >= '0' && c <= '9')
+ {
+ return c - '0';
+ }
+ if (c >= 'A' && c <= 'F')
+ {
+ return c - 'A' + 10;
+ }
+ if (c >= 'a' && c <= 'f')
+ {
+ return c - 'a' + 10;
+ }
+ return -1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Linux/Tmds.DBus.Protocol/Array.cs b/src/Linux/Tmds.DBus.Protocol/Array.cs
new file mode 100644
index 0000000000..42ab3d5fcf
--- /dev/null
+++ b/src/Linux/Tmds.DBus.Protocol/Array.cs
@@ -0,0 +1,88 @@
+using System.Collections;
+
+namespace Tmds.DBus.Protocol;
+
+// Using obsolete generic write members
+#pragma warning disable CS0618
+
+public sealed class Array : IDBusWritable, IList
+ where T : notnull
+{
+ private readonly List _values;
+
+ public Array() :
+ this(new List())
+ { }
+
+ public Array(int capacity) :
+ this(new List(capacity))
+ { }
+
+ public Array(IEnumerable collection) :
+ this(new List(collection))
+ { }
+
+ private Array(List values)
+ {
+ TypeModel.EnsureSupportedVariantType();
+ _values = values;
+ }
+
+ public void Add(T item)
+ => _values.Add(item);
+
+ public void Clear()
+ => _values.Clear();
+
+ public int Count => _values.Count;
+
+ bool ICollection.IsReadOnly
+ => false;
+
+ public T this[int index]
+ {
+ get => _values[index];
+ set => _values[index] = value;
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => _values.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => _values.GetEnumerator();
+
+ public int IndexOf(T item)
+ => _values.IndexOf(item);
+
+ public void Insert(int index, T item)
+ => _values.Insert(index, item);
+
+ public void RemoveAt(int index)
+ => _values.RemoveAt(index);
+
+ public bool Contains(T item)
+ => _values.Contains(item);
+
+ public void CopyTo(T[] array, int arrayIndex)
+ => _values.CopyTo(array, arrayIndex);
+
+ public bool Remove(T item)
+ => _values.Remove(item);
+
+ public Variant AsVariant()
+ => Variant.FromArray(this);
+
+ public static implicit operator Variant(Array value)
+ => value.AsVariant();
+
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // this is a supported variant type.
+ void IDBusWritable.WriteTo(ref MessageWriter writer)
+ {
+#if NET5_0_OR_GREATER
+ Span span = CollectionsMarshal.AsSpan(_values);
+ writer.WriteArray(span);
+#else
+ writer.WriteArray(_values);
+#endif
+ }
+}
diff --git a/src/Linux/Tmds.DBus.Protocol/ClientConnectionOptions.cs b/src/Linux/Tmds.DBus.Protocol/ClientConnectionOptions.cs
new file mode 100644
index 0000000000..129947db9d
--- /dev/null
+++ b/src/Linux/Tmds.DBus.Protocol/ClientConnectionOptions.cs
@@ -0,0 +1,36 @@
+namespace Tmds.DBus.Protocol;
+
+public class ClientConnectionOptions : ConnectionOptions
+{
+ private string _address;
+
+ public ClientConnectionOptions(string address)
+ {
+ if (address == null)
+ throw new ArgumentNullException(nameof(address));
+ _address = address;
+ }
+
+ protected ClientConnectionOptions()
+ {
+ _address = string.Empty;
+ }
+
+ public bool AutoConnect { get; set; }
+
+ internal bool IsShared { get; set; }
+
+ protected internal virtual ValueTask SetupAsync(CancellationToken cancellationToken)
+ {
+ return new ValueTask(
+ new ClientSetupResult(_address)
+ {
+ SupportsFdPassing = true,
+ UserId = DBusEnvironment.UserId,
+ MachineId = DBusEnvironment.MachineId
+ });
+ }
+
+ protected internal virtual void Teardown(object? token)
+ { }
+}
\ No newline at end of file
diff --git a/src/Linux/Tmds.DBus.Protocol/ClientSetupResult.cs b/src/Linux/Tmds.DBus.Protocol/ClientSetupResult.cs
new file mode 100644
index 0000000000..d375d7d66e
--- /dev/null
+++ b/src/Linux/Tmds.DBus.Protocol/ClientSetupResult.cs
@@ -0,0 +1,19 @@
+namespace Tmds.DBus.Protocol;
+
+public class ClientSetupResult
+{
+ public ClientSetupResult(string address)
+ {
+ ConnectionAddress = address ?? throw new ArgumentNullException(nameof(address));
+ }
+
+ public string ConnectionAddress { get; }
+
+ public object? TeardownToken { get; set; }
+
+ public string? UserId { get; set; }
+
+ public string? MachineId { get; set; }
+
+ public bool SupportsFdPassing { get; set; }
+}
\ No newline at end of file
diff --git a/src/Linux/Tmds.DBus.Protocol/CloseSafeHandle.cs b/src/Linux/Tmds.DBus.Protocol/CloseSafeHandle.cs
new file mode 100644
index 0000000000..1e92132ada
--- /dev/null
+++ b/src/Linux/Tmds.DBus.Protocol/CloseSafeHandle.cs
@@ -0,0 +1,17 @@
+namespace Tmds.DBus.Protocol;
+
+sealed class CloseSafeHandle : SafeHandle
+{
+ public CloseSafeHandle() :
+ base(new IntPtr(-1), ownsHandle: true)
+ { }
+
+ public override bool IsInvalid
+ => handle == new IntPtr(-1);
+
+ protected override bool ReleaseHandle()
+ => close(handle.ToInt32()) == 0;
+
+ [DllImport("libc", SetLastError = true)]
+ internal static extern int close(int fd);
+}
diff --git a/src/Linux/Tmds.DBus.Protocol/ConnectException.cs b/src/Linux/Tmds.DBus.Protocol/ConnectException.cs
new file mode 100644
index 0000000000..f035990402
--- /dev/null
+++ b/src/Linux/Tmds.DBus.Protocol/ConnectException.cs
@@ -0,0 +1,10 @@
+namespace Tmds.DBus.Protocol;
+
+public class ConnectException : Exception
+{
+ public ConnectException(string message) : base(message)
+ { }
+
+ public ConnectException(string message, Exception innerException) : base(message, innerException)
+ { }
+}
\ No newline at end of file
diff --git a/src/Linux/Tmds.DBus.Protocol/Connection.DBus.cs b/src/Linux/Tmds.DBus.Protocol/Connection.DBus.cs
new file mode 100644
index 0000000000..4ceceb5c70
--- /dev/null
+++ b/src/Linux/Tmds.DBus.Protocol/Connection.DBus.cs
@@ -0,0 +1,112 @@
+using System.Threading.Channels;
+
+namespace Tmds.DBus.Protocol;
+
+public partial class Connection
+{
+ public const string DBusObjectPath = "/org/freedesktop/DBus";
+ public const string DBusServiceName = "org.freedesktop.DBus";
+ public const string DBusInterface = "org.freedesktop.DBus";
+
+ public Task ListServicesAsync()
+ {
+ return CallMethodAsync(CreateMessage(), (Message m, object? s) => m.GetBodyReader().ReadArrayOfString());
+ MessageBuffer CreateMessage()
+ {
+ using var writer = GetMessageWriter();
+ writer.WriteMethodCallHeader(
+ destination: DBusServiceName,
+ path: DBusObjectPath,
+ @interface: DBusInterface,
+ member: "ListNames");
+ return writer.CreateMessage();
+ }
+ }
+
+ public Task ListActivatableServicesAsync()
+ {
+ return CallMethodAsync(CreateMessage(), (Message m, object? s) => m.GetBodyReader().ReadArrayOfString());
+ MessageBuffer CreateMessage()
+ {
+ using var writer = GetMessageWriter();
+ writer.WriteMethodCallHeader(
+ destination: DBusServiceName,
+ path: DBusObjectPath,
+ @interface: DBusInterface,
+ member: "ListActivatableNames");
+ return writer.CreateMessage();
+ }
+ }
+
+ public async Task BecomeMonitorAsync(Action handler, IEnumerable? rules = null)
+ {
+ if (_connectionOptions.IsShared)
+ {
+ throw new InvalidOperationException("Cannot become monitor on a shared connection.");
+ }
+
+ DBusConnection connection = await ConnectCoreAsync().ConfigureAwait(false);
+ await connection.BecomeMonitorAsync(handler, rules).ConfigureAwait(false);
+ }
+
+ public static async IAsyncEnumerable MonitorBusAsync(string address, IEnumerable? rules = null, [EnumeratorCancellation]CancellationToken ct = default)
+ {
+ ct.ThrowIfCancellationRequested();
+
+ var channel = Channel.CreateUnbounded(
+ new UnboundedChannelOptions()
+ {
+ AllowSynchronousContinuations = true,
+ SingleReader = true,
+ SingleWriter = true,
+ }
+ );
+
+ using var connection = new Connection(address);
+ using CancellationTokenRegistration ctr =
+#if NETCOREAPP3_1_OR_GREATER
+ ct.UnsafeRegister(c => ((Connection)c!).Dispose(), connection);
+#else
+ ct.Register(c => ((Connection)c!).Dispose(), connection);
+#endif
+ try
+ {
+ await connection.ConnectAsync().ConfigureAwait(false);
+
+ await connection.BecomeMonitorAsync(
+ (Exception? ex, DisposableMessage message) =>
+ {
+ if (ex is not null)
+ {
+ if (ct.IsCancellationRequested)
+ {
+ ex = new OperationCanceledException(ct);
+ }
+ channel.Writer.TryComplete(ex);
+ return;
+ }
+
+ if (!channel.Writer.TryWrite(message))
+ {
+ message.Dispose();
+ }
+ },
+ rules
+ ).ConfigureAwait(false);
+ }
+ catch
+ {
+ ct.ThrowIfCancellationRequested();
+
+ throw;
+ }
+
+ while (await channel.Reader.WaitToReadAsync().ConfigureAwait(false))
+ {
+ if (channel.Reader.TryRead(out DisposableMessage msg))
+ {
+ yield return msg;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Linux/Tmds.DBus.Protocol/Connection.cs b/src/Linux/Tmds.DBus.Protocol/Connection.cs
new file mode 100644
index 0000000000..973c40738e
--- /dev/null
+++ b/src/Linux/Tmds.DBus.Protocol/Connection.cs
@@ -0,0 +1,335 @@
+namespace Tmds.DBus.Protocol;
+
+public delegate T MessageValueReader(Message message, object? state);
+
+public partial class Connection : IDisposable
+{
+ internal static readonly Exception DisposedException = new ObjectDisposedException(typeof(Connection).FullName);
+ private static Connection? s_systemConnection;
+ private static Connection? s_sessionConnection;
+
+ public static Connection System => s_systemConnection ?? CreateConnection(ref s_systemConnection, Address.System);
+ public static Connection Session => s_sessionConnection ?? CreateConnection(ref s_sessionConnection, Address.Session);
+
+ public string? UniqueName => GetConnection().UniqueName;
+
+ enum ConnectionState
+ {
+ Created,
+ Connecting,
+ Connected,
+ Disconnected
+ }
+
+ private readonly object _gate = new object();
+ private readonly ClientConnectionOptions _connectionOptions;
+ private DBusConnection? _connection;
+ private CancellationTokenSource? _connectCts;
+ private Task? _connectingTask;
+ private ClientSetupResult? _setupResult;
+ private ConnectionState _state;
+ private bool _disposed;
+ private int _nextSerial;
+
+ public Connection(string address) :
+ this(new ClientConnectionOptions(address))
+ { }
+
+ public Connection(ConnectionOptions connectionOptions)
+ {
+ if (connectionOptions == null)
+ throw new ArgumentNullException(nameof(connectionOptions));
+
+ _connectionOptions = (ClientConnectionOptions)connectionOptions;
+ }
+
+ // For tests.
+ internal void Connect(IMessageStream stream)
+ {
+ _connection = new DBusConnection(this, DBusEnvironment.MachineId);
+ _connection.Connect(stream);
+ _state = ConnectionState.Connected;
+ }
+
+ public async ValueTask ConnectAsync()
+ {
+ await ConnectCoreAsync(explicitConnect: true).ConfigureAwait(false);
+ }
+
+ private ValueTask ConnectCoreAsync(bool explicitConnect = false)
+ {
+ lock (_gate)
+ {
+ ThrowHelper.ThrowIfDisposed(_disposed, this);
+
+ ConnectionState state = _state;
+
+ if (state == ConnectionState.Connected)
+ {
+ return new ValueTask(_connection!);
+ }
+
+ if (!_connectionOptions.AutoConnect)
+ {
+ DBusConnection? connection = _connection;
+ if (!explicitConnect && _state == ConnectionState.Disconnected && connection is not null)
+ {
+ throw new DisconnectedException(connection.DisconnectReason);
+ }
+
+ if (!explicitConnect || _state != ConnectionState.Created)
+ {
+ throw new InvalidOperationException("Can only connect once using an explicit call.");
+ }
+ }
+
+ if (state == ConnectionState.Connecting)
+ {
+ return new ValueTask(_connectingTask!);
+ }
+
+ _state = ConnectionState.Connecting;
+ _connectingTask = DoConnectAsync();
+
+ return new ValueTask(_connectingTask);
+ }
+ }
+
+ private async Task DoConnectAsync()
+ {
+ Debug.Assert(Monitor.IsEntered(_gate));
+
+ DBusConnection? connection = null;
+ try
+ {
+ _connectCts = new();
+ _setupResult = await _connectionOptions.SetupAsync(_connectCts.Token).ConfigureAwait(false);
+ connection = _connection = new DBusConnection(this, _setupResult.MachineId ?? DBusEnvironment.MachineId);
+
+ await connection.ConnectAsync(_setupResult.ConnectionAddress, _setupResult.UserId, _setupResult.SupportsFdPassing, _connectCts.Token).ConfigureAwait(false);
+
+ lock (_gate)
+ {
+ ThrowHelper.ThrowIfDisposed(_disposed, this);
+
+ if (_connection == connection && _state == ConnectionState.Connecting)
+ {
+ _connectingTask = null;
+ _connectCts = null;
+ _state = ConnectionState.Connected;
+ }
+ else
+ {
+ throw new DisconnectedException(connection.DisconnectReason);
+ }
+ }
+
+ return connection;
+ }
+ catch (Exception exception)
+ {
+ Disconnect(exception, connection);
+
+ // Prefer throwing ObjectDisposedException.
+ ThrowHelper.ThrowIfDisposed(_disposed, this);
+
+ // Throw DisconnectedException or ConnectException.
+ if (exception is DisconnectedException || exception is ConnectException)
+ {
+ throw;
+ }
+ else
+ {
+ throw new ConnectException(exception.Message, exception);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ lock (_gate)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+ _disposed = true;
+ }
+
+ Disconnect(DisposedException);
+ }
+
+ internal void Disconnect(Exception disconnectReason, DBusConnection? trigger = null)
+ {
+ DBusConnection? connection;
+ ClientSetupResult? setupResult;
+ CancellationTokenSource? connectCts;
+ lock (_gate)
+ {
+ if (trigger is not null && trigger != _connection)
+ {
+ // Already disconnected from this stream.
+ return;
+ }
+
+ ConnectionState state = _state;
+ if (state == ConnectionState.Disconnected)
+ {
+ return;
+ }
+
+ _state = ConnectionState.Disconnected;
+
+ connection = _connection;
+ setupResult = _setupResult;
+ connectCts = _connectCts;
+
+ _connectingTask = null;
+ _setupResult = null;
+ _connectCts = null;
+
+ if (connection is not null)
+ {
+ connection.DisconnectReason = disconnectReason;
+ }
+ }
+
+ connectCts?.Cancel();
+ connection?.Dispose();
+ if (setupResult != null)
+ {
+ _connectionOptions.Teardown(setupResult.TeardownToken);
+ }
+ }
+
+ public async Task CallMethodAsync(MessageBuffer message)
+ {
+ DBusConnection connection;
+ try
+ {
+ connection = await ConnectCoreAsync().ConfigureAwait(false);
+ }
+ catch
+ {
+ message.ReturnToPool();
+ throw;
+ }
+ await connection.CallMethodAsync(message).ConfigureAwait(false);
+ }
+
+ public async Task CallMethodAsync(MessageBuffer message, MessageValueReader reader, object? readerState = null)
+ {
+ DBusConnection connection;
+ try
+ {
+ connection = await ConnectCoreAsync().ConfigureAwait(false);
+ }
+ catch
+ {
+ message.ReturnToPool();
+ throw;
+ }
+ return await connection.CallMethodAsync(message, reader, readerState).ConfigureAwait(false);
+ }
+
+ [Obsolete("Use an overload that accepts ObserverFlags.")]
+ public ValueTask AddMatchAsync(MatchRule rule, MessageValueReader reader, Action handler, object? readerState = null, object? handlerState = null, bool emitOnCapturedContext = true, bool subscribe = true)
+ => AddMatchAsync(rule, reader, handler, readerState, handlerState, emitOnCapturedContext, ObserverFlags.EmitOnDispose | (!subscribe ? ObserverFlags.NoSubscribe : default));
+
+ public ValueTask AddMatchAsync(MatchRule rule, MessageValueReader reader, Action handler, ObserverFlags flags, object? readerState = null, object? handlerState = null, bool emitOnCapturedContext = true)
+ => AddMatchAsync(rule, reader, handler, readerState, handlerState, emitOnCapturedContext, flags);
+
+ public ValueTask AddMatchAsync(MatchRule rule, MessageValueReader reader, Action handler, object? readerState, object? handlerState, bool emitOnCapturedContext, ObserverFlags flags)
+ => AddMatchAsync(rule, reader, handler, readerState, handlerState, emitOnCapturedContext ? SynchronizationContext.Current : null, flags);
+
+ public async ValueTask AddMatchAsync(MatchRule rule, MessageValueReader reader, Action handler, object? readerState , object? handlerState, SynchronizationContext? synchronizationContext, ObserverFlags flags)
+ {
+ DBusConnection connection = await ConnectCoreAsync().ConfigureAwait(false);
+ return await connection.AddMatchAsync(synchronizationContext, rule, reader, handler, readerState, handlerState, flags).ConfigureAwait(false);
+ }
+
+ public void AddMethodHandler(IMethodHandler methodHandler)
+ => UpdateMethodHandlers((dictionary, handler) => dictionary.AddMethodHandler(handler), methodHandler);
+
+ public void AddMethodHandlers(IReadOnlyList methodHandlers)
+ => UpdateMethodHandlers((dictionary, handlers) => dictionary.AddMethodHandlers(handlers), methodHandlers);
+
+ public void RemoveMethodHandler(string path)
+ => UpdateMethodHandlers((dictionary, path) => dictionary.RemoveMethodHandler(path), path);
+
+ public void RemoveMethodHandlers(IEnumerable paths)
+ => UpdateMethodHandlers((dictionary, paths) => dictionary.RemoveMethodHandlers(paths), paths);
+
+ private void UpdateMethodHandlers(Action update, T state)
+ => GetConnection().UpdateMethodHandlers(update, state);
+
+ private static Connection CreateConnection(ref Connection? field, string? address)
+ {
+ address = address ?? "unix:";
+ var connection = Volatile.Read(ref field);
+ if (connection is not null)
+ {
+ return connection;
+ }
+ var newConnection = new Connection(new ClientConnectionOptions(address) { AutoConnect = true, IsShared = true });
+ connection = Interlocked.CompareExchange(ref field, newConnection, null);
+ if (connection != null)
+ {
+ newConnection.Dispose();
+ return connection;
+ }
+ return newConnection;
+ }
+
+ public MessageWriter GetMessageWriter() => new MessageWriter(MessageBufferPool.Shared, GetNextSerial());
+
+ public bool TrySendMessage(MessageBuffer message)
+ {
+ DBusConnection? connection = GetConnection(ifConnected: true);
+ if (connection is null)
+ {
+ message.ReturnToPool();
+ return false;
+ }
+ connection.SendMessage(message);
+ return true;
+ }
+
+ public Task DisconnectedAsync()
+ {
+ DBusConnection connection = GetConnection();
+ return connection.DisconnectedAsync();
+ }
+
+ private DBusConnection GetConnection() => GetConnection(ifConnected: false)!;
+
+ private DBusConnection? GetConnection(bool ifConnected)
+ {
+ lock (_gate)
+ {
+ ThrowHelper.ThrowIfDisposed(_disposed, this);
+
+ if (_connectionOptions.AutoConnect)
+ {
+ throw new InvalidOperationException("Method cannot be used on autoconnect connections.");
+ }
+
+ ConnectionState state = _state;
+
+ if (state == ConnectionState.Created ||
+ state == ConnectionState.Connecting)
+ {
+ throw new InvalidOperationException("Connect before using this method.");
+ }
+
+ if (ifConnected && state != ConnectionState.Connected)
+ {
+ return null;
+ }
+
+ return _connection;
+ }
+ }
+
+ internal uint GetNextSerial() => (uint)Interlocked.Increment(ref _nextSerial);
+}
\ No newline at end of file
diff --git a/src/Linux/Tmds.DBus.Protocol/ConnectionOptions.cs b/src/Linux/Tmds.DBus.Protocol/ConnectionOptions.cs
new file mode 100644
index 0000000000..40c6e7a9f0
--- /dev/null
+++ b/src/Linux/Tmds.DBus.Protocol/ConnectionOptions.cs
@@ -0,0 +1,7 @@
+namespace Tmds.DBus.Protocol;
+
+public abstract class ConnectionOptions
+{
+ internal ConnectionOptions()
+ { }
+}
\ No newline at end of file
diff --git a/src/Linux/Tmds.DBus.Protocol/Constants.cs b/src/Linux/Tmds.DBus.Protocol/Constants.cs
new file mode 100644
index 0000000000..9382ef13d3
--- /dev/null
+++ b/src/Linux/Tmds.DBus.Protocol/Constants.cs
@@ -0,0 +1,7 @@
+namespace Tmds.DBus.Protocol;
+
+static class Constants
+{
+ public const int StackAllocByteThreshold = 512;
+ public const int StackAllocCharThreshold = StackAllocByteThreshold / 2;
+}
\ No newline at end of file
diff --git a/src/Linux/Tmds.DBus.Protocol/DBusConnection.cs b/src/Linux/Tmds.DBus.Protocol/DBusConnection.cs
new file mode 100644
index 0000000000..3ddcbb5a9d
--- /dev/null
+++ b/src/Linux/Tmds.DBus.Protocol/DBusConnection.cs
@@ -0,0 +1,1290 @@
+using System.Net;
+using System.Net.Sockets;
+using System.Threading.Tasks.Sources;
+
+#pragma warning disable VSTHRD100 // Avoid "async void" methods
+
+namespace Tmds.DBus.Protocol;
+
+class DBusConnection : IDisposable
+{
+ private delegate void MessageReceivedHandler(Exception? exception, Message message, object? state);
+
+ sealed class MyValueTaskSource : IValueTaskSource, IValueTaskSource
+ {
+ private ManualResetValueTaskSourceCore _core;
+ private volatile bool _continuationSet;
+
+ public void SetResult(T result)
+ {
+ // Ensure we complete the Task from the read loop.
+ SpinWait wait = new();
+ while (!_continuationSet)
+ {
+ wait.SpinOnce();
+ }
+ _core.SetResult(result);
+ }
+
+ public void SetException(Exception exception) => _core.SetException(exception);
+
+ public ValueTaskSourceStatus GetStatus(short token) => _core.GetStatus(token);
+
+ public void OnCompleted(Action