diff --git a/src/SimConnect.NET/Events/SimSystemEventReceivedEventArgs.cs b/src/SimConnect.NET/Events/SimSystemEventReceivedEventArgs.cs new file mode 100644 index 0000000..1b92f3d --- /dev/null +++ b/src/SimConnect.NET/Events/SimSystemEventReceivedEventArgs.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) BARS. All rights reserved. +// + +namespace SimConnect.NET.Events +{ + /// + /// Provides data for an event that is raised when a Simconnect system event is raised. + /// + /// + /// Initializes a new instance of the class with the specified event identifier and. + /// associated data. + /// + /// The unique identifier for the system event. + /// The data associated with the system event. + public class SimSystemEventReceivedEventArgs(uint eventId, uint data) : EventArgs + { + /// + /// Gets the unique identifier for the event. + /// + public uint EventId { get; } = eventId; + + /// + /// Gets the data associated with the event. + /// + public uint Data { get; } = data; + } +} diff --git a/src/SimConnect.NET/SimConnectClient.cs b/src/SimConnect.NET/SimConnectClient.cs index c5b6d3f..2516dc6 100644 --- a/src/SimConnect.NET/SimConnectClient.cs +++ b/src/SimConnect.NET/SimConnectClient.cs @@ -2,7 +2,6 @@ // Copyright (c) BARS. All rights reserved. // -using System; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -70,6 +69,11 @@ public SimConnectClient(string applicationName = "SimConnect.NET Client") /// public event EventHandler? RawMessageReceived; + /// + /// Occurs when a subscribed event is fired. + /// + public event EventHandler? SystemEventReceived; + /// /// Gets a value indicating whether the client is connected to SimConnect. /// @@ -345,6 +349,40 @@ public async Task DisconnectAsync() } } + /// + /// Subscribes to a specific simulator system event. + /// + /// The name of the system event (e.g., "SimStart", "4Sec", "Crashed"). + /// A user-defined ID to identify this subscription. + /// Cancellation token for the operation. + /// A task representing the event. + /// Thrown when a sim connection wasn't found. + /// Thrown when the event wasn't subscribed. + public async Task SubscribeToEventAsync(string systemEventName, uint systemEventId, CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(this.disposed, nameof(SimConnectClient)); + + if (!this.isConnected) + { + throw new InvalidOperationException("Not connected to SimConnect."); + } + + await Task.Run( + () => + { + var result = SimConnectNative.SimConnect_SubscribeToSystemEvent( + this.simConnectHandle, + systemEventId, + systemEventName); + + if (result != (int)SimConnectError.None) + { + throw new SimConnectException($"Failed to subscribe to event {systemEventName}: {(SimConnectError)result}", (SimConnectError)result); + } + }, + cancellationToken).ConfigureAwait(false); + } + /// /// Processes the next SimConnect message. /// @@ -419,6 +457,9 @@ public async Task ProcessNextMessageAsync(CancellationToken cancellationTo case SimConnectRecvId.VorList: case SimConnectRecvId.NdbList: break; + case SimConnectRecvId.Event: + this.ProcessSystemEvent(ppData); + break; default: this.simVarManager?.ProcessReceivedData(ppData, pcbData); break; @@ -543,6 +584,29 @@ private void ProcessOpen(IntPtr ppData) } } + /// + /// Processes a system event message from SimConnect. + /// + /// Pointer to the received Event data. + private void ProcessSystemEvent(IntPtr ppData) + { + try + { + var recvEvent = Marshal.PtrToStructure(ppData); + + this.SystemEventReceived?.Invoke(this, new SimSystemEventReceivedEventArgs(recvEvent.EventId, recvEvent.Data)); + + if (SimConnectLogger.IsLevelEnabled(SimConnectLogger.LogLevel.Debug)) + { + SimConnectLogger.Debug($"System Event Received: ID={recvEvent.EventId} Data={recvEvent.Data}"); + } + } + catch (Exception ex) when (!ExceptionHelper.IsCritical(ex)) + { + SimConnectLogger.Error("Error processing system event", ex); + } + } + /// /// Starts the background message processing loop. /// diff --git a/tests/SimConnect.NET.Tests.Net8/Tests/SystemEventSubscriptionTests.cs b/tests/SimConnect.NET.Tests.Net8/Tests/SystemEventSubscriptionTests.cs new file mode 100644 index 0000000..f124d37 --- /dev/null +++ b/tests/SimConnect.NET.Tests.Net8/Tests/SystemEventSubscriptionTests.cs @@ -0,0 +1,70 @@ +// +// Copyright (c) BARS. All rights reserved. +// + +namespace SimConnect.NET.Tests.Net8.Tests +{ + internal class SystemEventSubscriptionTests : ISimConnectTest + { + public string Name => "SystemEventSubscription"; + + public string Description => "Tests system event subscription"; + + public string Category => "System Event"; + + public async Task RunAsync(SimConnectClient client, CancellationToken cancellationToken = default) + { + try + { + if (!client.IsConnected) + { + Console.WriteLine(" ❌ Client should already be connected"); + return false; + } + + Console.WriteLine(" ✅ Connection status verified"); + + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + cts.CancelAfter(TimeSpan.FromSeconds(15)); + + bool testEventReceived = false; + client.SystemEventReceived += (sender, e) => + { + switch (e.EventId) + { + case 100: + Console.WriteLine("4 seconds has passed!"); + testEventReceived = true; + break; + } + }; + + await client.SubscribeToEventAsync("4sec", 100, cts.Token); + + Console.WriteLine("Listening for events..."); + + while (!testEventReceived && !cts.Token.IsCancellationRequested) + { + await Task.Delay(500, cts.Token); + } + if (!testEventReceived) + { + Console.WriteLine(" ❌ Did not receive expected system event"); + return false; + } + Console.WriteLine(" ✅ Received expected system event"); + return true; + } + catch (OperationCanceledException) + { + Console.WriteLine(" ❌ Connection test timed out"); + return false; + } + catch (Exception ex) + { + Console.WriteLine($" ❌ Connection test failed: {ex.Message}"); + return false; + } + } + } +} diff --git a/tests/SimConnect.NET.Tests.Net8/Tests/TestRunner.cs b/tests/SimConnect.NET.Tests.Net8/Tests/TestRunner.cs index 7f2c83d..512f3aa 100644 --- a/tests/SimConnect.NET.Tests.Net8/Tests/TestRunner.cs +++ b/tests/SimConnect.NET.Tests.Net8/Tests/TestRunner.cs @@ -31,6 +31,7 @@ public TestRunner() new InputEventTests(), new InputEventValueTests(), new PerformanceTests(), + new SystemEventSubscriptionTests(), }; } @@ -143,6 +144,9 @@ private static TestOptions ParseArguments(string[] args) case "--verbose": options.Verbose = true; break; + case "--test-events": + options.Categories.Add("System Event"); + break; } }