From 2809917a93e83b18817160f3ec23f9d1a0cc462b Mon Sep 17 00:00:00 2001 From: Mihai Dinculescu Date: Sat, 29 Oct 2022 13:48:00 +0100 Subject: [PATCH] Add suport for system events --- CHANGELOG.md | 9 + FEATURES.md | 30 +- examples/Cargo.toml | 4 + examples/README.md | 6 + examples/src/system_events.rs | 76 +++++ simconnect-sdk-derive/src/lib.rs | 4 +- simconnect-sdk/build.rs | 55 ++-- simconnect-sdk/src/domain/event.rs | 21 -- simconnect-sdk/src/domain/events.rs | 305 ++++++++++++++++++++ simconnect-sdk/src/domain/mod.rs | 4 +- simconnect-sdk/src/domain/notification.rs | 10 +- simconnect-sdk/src/macros.rs | 6 +- simconnect-sdk/src/simconnect/base.rs | 35 ++- simconnect-sdk/src/simconnect/events.rs | 47 ++- simconnect-sdk/src/simconnect/facilities.rs | 29 +- simconnect-sdk/src/simconnect/objects.rs | 29 +- 16 files changed, 554 insertions(+), 116 deletions(-) create mode 100644 examples/src/system_events.rs delete mode 100644 simconnect-sdk/src/domain/event.rs create mode 100644 simconnect-sdk/src/domain/events.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ff7277b..f39c343 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ file. This change log follows the conventions of ## [Unreleased] +### Added + +- `Notification::SystemEvent`, `SystemEventRequest` and `SystemEvent` have been added. System Events can be subscribed to by using `SimConnect::subscribe_to_system_event` and unsubscribed from by using `SimConnect::unsubscribe_from_system_event`. + +### Changed + +- `Notification::Event` has been renamed to `Notification::ClientEvent`. +- `Event` has been renamed to `ClientEvent` and marked as `non_exhaustive`. + ## [v0.1.3] - 2022-10-24 ### Changed diff --git a/FEATURES.md b/FEATURES.md index d216695..ea58525 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -2,27 +2,27 @@ ## General -| Feature | Status | Comment | -| --------------------------------------- | ------- | ----------- | -| DispatchProc | | | -| SimConnect_Open | ✓ | | -| SimConnect_Close | ✓ | | -| SimConnect_CallDispatch | | | -| SimConnect_GetNextDispatch | ✓ | | -| SimConnect_RequestSystemState | | | -| SimConnect_MapClientEventToSimEvent | - | Coming soon | -| SimConnect_SubscribeToSystemEvent | | | -| SimConnect_SetSystemEventState | | | -| SimConnect_UnsubscribeFromSystemEvent | | | -| SimConnect_SetNotificationGroupPriority | - | Coming soon | +| Feature | Status | Comment | +| --------------------------------------- | ------- | ------- | +| DispatchProc | | | +| SimConnect_Open | ✓ | | +| SimConnect_Close | ✓ | | +| SimConnect_CallDispatch | | | +| SimConnect_GetNextDispatch | ✓ | | +| SimConnect_RequestSystemState | | | +| SimConnect_MapClientEventToSimEvent | - | WIP | +| SimConnect_SubscribeToSystemEvent | ✓ | | +| SimConnect_SetSystemEventState | | | +| SimConnect_UnsubscribeFromSystemEvent | ✓ | | +| SimConnect_SetNotificationGroupPriority | - | WIP | ## Events And Data | Feature | Status | Comment | | -------------------------------------------- | ------- | ----------------------------------- | | SimConnect_RequestDataOnSimObject | ✓ | Only for SIMCONNECT_OBJECT_ID_USER | -| SimConnect_RequestDataOnSimObjectType | - | Coming soon | -| SimConnect_AddClientEventToNotificationGroup | - | Coming soon | +| SimConnect_RequestDataOnSimObjectType | | | +| SimConnect_AddClientEventToNotificationGroup | - | WIP | | SimConnect_RemoveClientEvent | | | | SimConnect_TransmitClientEvent | | | | SimConnect_TransmitClientEvent_EX1 | | | diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 42af85c..a2233aa 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -27,6 +27,10 @@ path = "src/data_multiple_objects.rs" name = "facilities" path = "src/facilities.rs" +[[bin]] +name = "system_events" +path = "src/system_events.rs" + [dependencies] tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/examples/README.md b/examples/README.md index f6a2b0e..eaf289d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -44,3 +44,9 @@ cargo run --bin data_multiple_objects ```bash cargo run --bin facilities ``` + +## Receiving system events + +```bash +cargo run --bin system_events +``` diff --git a/examples/src/system_events.rs b/examples/src/system_events.rs new file mode 100644 index 0000000..7c5f1ee --- /dev/null +++ b/examples/src/system_events.rs @@ -0,0 +1,76 @@ +use simconnect_sdk::{Notification, SimConnect, SystemEvent, SystemEventRequest}; + +fn main() -> Result<(), Box> { + let client = SimConnect::new("System events example"); + + match client { + Ok(mut client) => loop { + let notification = client.get_next_dispatch()?; + + match notification { + Some(Notification::Open) => { + println!("Connection opened."); + + // After the connection is successfully open + // We request the system events we're interested in + client.subscribe_to_system_event(SystemEventRequest::FourSeconds)?; + client.subscribe_to_system_event(SystemEventRequest::AircraftLoaded)?; + client.subscribe_to_system_event(SystemEventRequest::Crashed)?; + client.subscribe_to_system_event(SystemEventRequest::FlightLoaded)?; + client.subscribe_to_system_event(SystemEventRequest::FlightPlanActivated)?; + client.subscribe_to_system_event(SystemEventRequest::FlightPlanDeactivated)?; + client.subscribe_to_system_event(SystemEventRequest::Pause)?; + client.subscribe_to_system_event(SystemEventRequest::Sim)?; + client.subscribe_to_system_event(SystemEventRequest::Sound)?; + client.subscribe_to_system_event(SystemEventRequest::View)?; + } + Some(Notification::SystemEvent(event)) => match event { + SystemEvent::FourSeconds => { + println!("FourSeconds ping received."); + + // After we have receive one FourSeconds event notification, we unsubscribe from it + client.unsubscribe_from_system_event(SystemEventRequest::FourSeconds)?; + println!("FourSeconds subscription stopped."); + } + SystemEvent::AircraftLoaded { file_name } => { + println!("AircraftLoaded: {file_name}."); + } + SystemEvent::Crashed => { + println!("Crashed."); + } + SystemEvent::FlightLoaded { file_name } => { + println!("FlightLoaded: {file_name}."); + } + SystemEvent::FlightPlanActivated { file_name } => { + println!("FlightPlanActivated: {file_name}."); + } + SystemEvent::FlightPlanDeactivated => { + println!("FlightPlanDeactivated."); + } + SystemEvent::Pause { state } => { + println!("Pause: {state}."); + } + SystemEvent::Sim { state } => { + println!("Sim: {state}."); + } + SystemEvent::Sound { state } => { + println!("Sound: {state}."); + } + SystemEvent::View { view } => { + println!("View: {view:?}."); + } + _ => {} + }, + _ => (), + } + + // sleep for about a frame to reduce CPU usage + std::thread::sleep(std::time::Duration::from_millis(16)); + }, + Err(e) => { + println!("Error: {e:?}") + } + } + + Ok(()) +} diff --git a/simconnect-sdk-derive/src/lib.rs b/simconnect-sdk-derive/src/lib.rs index d5f0a07..4528799 100644 --- a/simconnect-sdk-derive/src/lib.rs +++ b/simconnect-sdk-derive/src/lib.rs @@ -21,8 +21,8 @@ mod helpers; /// * `interval` - Optional. Defaults to `0`. The number of period events that should elapse between transmissions of the data. `0` means the data is transmitted every Period, `1` means that the data is transmitted every other Period, etc. /// /// # Field Arguments -/// * `name` - Required. The name of the field. One from . -/// * `unit` - Optional. The unit of the field. For `string`s and `bool`s it should be left out or be empty string. For numeric fields it should be one from . +/// * `name` - Required. The name of the field. One from . +/// * `unit` - Optional. The unit of the field. For `string`s and `bool`s it should be left out or be empty string. For numeric fields it should be one from . /// /// # Example /// diff --git a/simconnect-sdk/build.rs b/simconnect-sdk/build.rs index e2bff3b..997a080 100644 --- a/simconnect-sdk/build.rs +++ b/simconnect-sdk/build.rs @@ -21,37 +21,46 @@ fn main() { let bindings = builder .parse_callbacks(Box::new(bindgen::CargoCallbacks)) .clang_args(&["-x", "c++"]) - .allowlist_function("SimConnect_Open") - .allowlist_function("SimConnect_Close") - .allowlist_function("SimConnect_MapClientEventToSimEvent") .allowlist_function("SimConnect_AddClientEventToNotificationGroup") - .allowlist_function("SimConnect_SetNotificationGroupPriority") - .allowlist_function("SimConnect_CallDispatch") - .allowlist_function("SimConnect_GetNextDispatch") .allowlist_function("SimConnect_AddToDataDefinition") - .allowlist_function("SimConnect_RequestDataOnSimObject") + .allowlist_function("SimConnect_CallDispatch") .allowlist_function("SimConnect_ClearDataDefinition") - .allowlist_function("SimConnect_SubscribeToFacilities") - .allowlist_function("SimConnect_UnsubscribeToFacilities") + .allowlist_function("SimConnect_Close") + .allowlist_function("SimConnect_GetNextDispatch") + .allowlist_function("SimConnect_MapClientEventToSimEvent") + .allowlist_function("SimConnect_Open") + .allowlist_function("SimConnect_RequestDataOnSimObject") .allowlist_function("SimConnect_RequestFacilitiesList") - .allowlist_type("SIMCONNECT_RECV") - .allowlist_type("SIMCONNECT_RECV_ID") - .allowlist_type("SIMCONNECT_RECV_EVENT") - .allowlist_type("SIMCONNECT_RECV_SIMOBJECT_DATA") - .allowlist_type("SIMCONNECT_RECV_FACILITIES_LIST") - .allowlist_type("SIMCONNECT_RECV_AIRPORT_LIST") - .allowlist_type("SIMCONNECT_RECV_WAYPOINT_LIST") - .allowlist_type("SIMCONNECT_RECV_NDB_LIST") - .allowlist_type("SIMCONNECT_RECV_VOR_LIST") + .allowlist_function("SimConnect_SetNotificationGroupPriority") + .allowlist_function("SimConnect_SubscribeToFacilities") + .allowlist_function("SimConnect_SubscribeToSystemEvent") + .allowlist_function("SimConnect_UnsubscribeFromSystemEvent") + .allowlist_function("SimConnect_UnsubscribeToFacilities") .allowlist_type("SIMCONNECT_CLIENT_DATA_PERIOD") - .allowlist_type("SIMCONNECT_RECV_OPEN") + .allowlist_type("SIMCONNECT_EXCEPTION") + .allowlist_type("SIMCONNECT_RECV_AIRPORT_LIST") + .allowlist_type("SIMCONNECT_RECV_EVENT_FILENAME") + .allowlist_type("SIMCONNECT_RECV_EVENT_FRAME") + .allowlist_type("SIMCONNECT_RECV_EVENT") .allowlist_type("SIMCONNECT_RECV_EXCEPTION") + .allowlist_type("SIMCONNECT_RECV_FACILITIES_LIST") + .allowlist_type("SIMCONNECT_RECV_ID") + .allowlist_type("SIMCONNECT_RECV_NDB_LIST") + .allowlist_type("SIMCONNECT_RECV_OPEN") + .allowlist_type("SIMCONNECT_RECV_SIMOBJECT_DATA") + .allowlist_type("SIMCONNECT_RECV_VOR_LIST") + .allowlist_type("SIMCONNECT_RECV_WAYPOINT_LIST") + .allowlist_type("SIMCONNECT_RECV") .allowlist_var("SIMCONNECT_DATA_REQUEST_FLAG_CHANGED") - .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_NAV_SIGNAL") - .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_LOCALIZER") - .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE") - .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_DME") .allowlist_var("SIMCONNECT_OBJECT_ID_USER") + .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_DME") + .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE") + .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_LOCALIZER") + .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_NAV_SIGNAL") + .allowlist_var("SIMCONNECT_SOUND_SYSTEM_EVENT_DATA_MASTER") + .allowlist_var("SIMCONNECT_VIEW_SYSTEM_EVENT_DATA_COCKPIT_2D") + .allowlist_var("SIMCONNECT_VIEW_SYSTEM_EVENT_DATA_COCKPIT_VIRTUAL") + .allowlist_var("SIMCONNECT_VIEW_SYSTEM_EVENT_DATA_ORTHOGONAL") .generate() .expect("Unable to generate bindings"); diff --git a/simconnect-sdk/src/domain/event.rs b/simconnect-sdk/src/domain/event.rs deleted file mode 100644 index 69a4ad1..0000000 --- a/simconnect-sdk/src/domain/event.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::os::raw::c_char; - -/// SimConnect event. -/// As defined at -#[derive(Debug, Copy, Clone, num_enum::TryFromPrimitive)] -#[repr(u32)] -pub enum Event { - Brakes, - BrakesLeft, - AxisLeftBrakeSet, -} - -impl Event { - pub(crate) fn into_c_char(self) -> *const c_char { - match self { - Event::Brakes => "BRAKES\0".as_ptr() as *const c_char, - Event::BrakesLeft => "BRAKES_LEFT\0".as_ptr() as *const c_char, - Event::AxisLeftBrakeSet => "AXIS_LEFT_BRAKE_SET\0".as_ptr() as *const c_char, - } - } -} diff --git a/simconnect-sdk/src/domain/events.rs b/simconnect-sdk/src/domain/events.rs new file mode 100644 index 0000000..3007c1c --- /dev/null +++ b/simconnect-sdk/src/domain/events.rs @@ -0,0 +1,305 @@ +use std::os::raw::c_char; + +use crate::{bindings, fixed_c_str_to_string, SimConnectError}; + +/// SimConnect System Event Request. +#[derive(Debug, Copy, Clone, num_enum::TryFromPrimitive)] +#[repr(u32)] +#[non_exhaustive] +pub enum SystemEventRequest { + /// Request a notification every second. + OneSecond = 0, + /// Request a notification every four seconds. + FourSeconds, + /// Request notifications six times per second. This is the same rate that joystick movement events are transmitted. + SixTimesPerSecond, + /// Request a notification when the aircraft flight dynamics file is changed. These files have a .AIR extension. The filename is returned. + AircraftLoaded, + /// Request a notification if the user aircraft crashes. + Crashed, + /// Request a notification when the crash cut-scene has completed. + CrashReset, + /// Request a notification when a flight is loaded. Note that when a flight is ended, a default flight is typically loaded, so these events will occur when flights and missions are started and finished. The filename of the flight loaded is returned. + FlightLoaded, + /// Request a notification when a flight is saved correctly. The filename of the flight saved is returned. + FlightSaved, + /// Request a notification when a new flight plan is activated. The filename of the activated flight plan is returned. + FlightPlanActivated, + /// Request a notification when the active flight plan is de-activated. + FlightPlanDeactivated, + /// Request notifications every visual frame. + Frame, + /// Request notifications when the scenario is paused or unpaused, and also immediately returns the current pause state. + Pause, + /// Request a notification when the scenario is paused. + Paused, + /// Request notifications for every visual frame that the simulation is paused. + PauseFrame, + /// Request a notification when the user changes the position of their aircraft through a dialog. + PositionChanged, + /// Request notifications when the scenario is running or not, and also immediately returns the current state. + Sim, + /// The simulator is running. Typically the user is actively controlling the vehicle which is on the ground, underwater or in the air. + SimStart, + /// The simulator is not running. Typically the user is loading a scenario, navigating the user interface or in a dialog. + SimStop, + /// Requests a notification when the master sound switch is changed. This request will also return the current state of the master sound switch immediately. + Sound, + /// Request a notification when the flight is un-paused. + Unpaused, + /// Requests a notification when the user aircraft view is changed. This request will also return the current view immediately. + View, +} + +impl SystemEventRequest { + pub(crate) fn into_c_char(self) -> *const c_char { + match self { + SystemEventRequest::OneSecond => "1sec\0".as_ptr() as *const c_char, + SystemEventRequest::FourSeconds => "4sec\0".as_ptr() as *const c_char, + SystemEventRequest::SixTimesPerSecond => "6Hz\0".as_ptr() as *const c_char, + SystemEventRequest::AircraftLoaded => "AircraftLoaded\0".as_ptr() as *const c_char, + SystemEventRequest::Crashed => "Crashed\0".as_ptr() as *const c_char, + SystemEventRequest::CrashReset => "CrashReset\0".as_ptr() as *const c_char, + SystemEventRequest::FlightLoaded => "FlightLoaded\0".as_ptr() as *const c_char, + SystemEventRequest::FlightSaved => "FlightSaved\0".as_ptr() as *const c_char, + SystemEventRequest::FlightPlanActivated => { + "FlightPlanActivated\0".as_ptr() as *const c_char + } + SystemEventRequest::FlightPlanDeactivated => { + "FlightPlanDeactivated\0".as_ptr() as *const c_char + } + SystemEventRequest::Frame => "Frame\0".as_ptr() as *const c_char, + SystemEventRequest::Pause => "Pause\0".as_ptr() as *const c_char, + SystemEventRequest::Paused => "Paused\0".as_ptr() as *const c_char, + SystemEventRequest::PauseFrame => "PauseFrame\0".as_ptr() as *const c_char, + SystemEventRequest::PositionChanged => "PositionChanged\0".as_ptr() as *const c_char, + SystemEventRequest::Sim => "Sim\0".as_ptr() as *const c_char, + SystemEventRequest::SimStart => "SimStart\0".as_ptr() as *const c_char, + SystemEventRequest::SimStop => "SimStop\0".as_ptr() as *const c_char, + SystemEventRequest::Sound => "Sound\0".as_ptr() as *const c_char, + SystemEventRequest::Unpaused => "Unpaused\0".as_ptr() as *const c_char, + SystemEventRequest::View => "View\0".as_ptr() as *const c_char, + } + } +} + +/// Cockpit view type. +#[derive(Debug, Copy, Clone, num_enum::TryFromPrimitive)] +#[repr(u32)] +pub enum ViewType { + /// No cockpit view. + None = 0, + /// 2D Panels in cockpit view. + Cockpit2D = bindings::SIMCONNECT_VIEW_SYSTEM_EVENT_DATA_COCKPIT_2D, + /// Virtual (3D) panels in cockpit view. + CockpitVirtual = bindings::SIMCONNECT_VIEW_SYSTEM_EVENT_DATA_COCKPIT_VIRTUAL, + /// Orthogonal (map) view. + Orthogonal = bindings::SIMCONNECT_VIEW_SYSTEM_EVENT_DATA_ORTHOGONAL, +} + +/// SimConnect System Event Notification. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum SystemEvent { + /// A notification every second. + OneSecond, + /// A notification every four seconds. + FourSeconds, + /// A notification six times per second. This is the same rate that joystick movement events are transmitted. + SixTimesPerSecond, + /// A notification when the aircraft flight dynamics file is changed. These files have a .AIR extension. The filename is returned. + AircraftLoaded { + /// The returned filename. + file_name: String, + }, + /// A notification if the user aircraft crashes. + Crashed, + /// A notification when the crash cut-scene has completed. + CrashReset, + /// A notification when a flight is loaded. Note that when a flight is ended, a default flight is typically loaded, so these events will occur when flights and missions are started and finished. The filename of the flight loaded is returned. + FlightLoaded { + /// The returned filename. + file_name: String, + }, + /// A notification when a flight is saved correctly. The filename of the flight saved is returned. + FlightSaved { + /// The returned filename. + file_name: String, + }, + /// A notification when a new flight plan is activated. The filename of the activated flight plan is returned. + FlightPlanActivated { + /// The returned filename. + file_name: String, + }, + /// A notification when the active flight plan is de-activated. + FlightPlanDeactivated, + /// Notifications every visual frame. + Frame { + /// The visual frame rate in frames per second. + frame_rate: f32, + /// The simulation rate. For example if the simulation is running at four times normal speed -- 4X -- then `4.0` will be returned. + sim_speed: f32, + }, + /// Notifications when the scenario is paused or unpaused, and also immediately returns the current pause state. + Pause { + /// The current pause state (`true` = paused or `false` = unpaused). + state: bool, + }, + /// A notification when the scenario is paused. + Paused, + /// Notifications for every visual frame that the simulation is paused. + PauseFrame { + /// The visual frame rate in frames per second. + frame_rate: f32, + /// The simulation rate. For example if the simulation is running at four times normal speed -- 4X -- then 4.0 will be returned. + sim_speed: f32, + }, + /// A notification when the user changes the position of their aircraft through a dialog. + PositionChanged, + /// Notifications when the scenario is running or not, and also immediately returns the current state. + Sim { + /// The current state (`true` = running or `false` = not running). + state: bool, + }, + /// The simulator is running. Typically the user is actively controlling the vehicle which is on the ground, underwater or in the air. + SimStart, + /// The simulator is not running. Typically the user is loading a scenario, navigating the user interface or in a dialog. + SimStop, + /// A notification when the master sound switch is changed. This request will also return the current state of the master sound switch immediately. + Sound { + /// The current state of the master sound switch. `false` if the switch is off, `true` if the switch is on. + state: bool, + }, + /// A notification when the flight is un-paused. + Unpaused, + /// A notification when the user aircraft view is changed. This request will also return the current view immediately. + View { + /// The current cockpit view type. + view: ViewType, + }, +} + +impl TryFrom<&bindings::SIMCONNECT_RECV_EVENT> for SystemEvent { + type Error = SimConnectError; + + fn try_from(event: &bindings::SIMCONNECT_RECV_EVENT) -> Result { + let request = SystemEventRequest::try_from(event.uEventID) + .map_err(|_| SimConnectError::UnimplementedEventType(event.uEventID))?; + + match request { + SystemEventRequest::OneSecond => Ok(SystemEvent::OneSecond), + SystemEventRequest::FourSeconds => Ok(SystemEvent::FourSeconds), + SystemEventRequest::SixTimesPerSecond => Ok(SystemEvent::SixTimesPerSecond), + SystemEventRequest::Crashed => Ok(SystemEvent::Crashed), + SystemEventRequest::CrashReset => Ok(SystemEvent::CrashReset), + SystemEventRequest::FlightPlanDeactivated => Ok(SystemEvent::FlightPlanDeactivated), + SystemEventRequest::Pause => Ok(SystemEvent::Pause { + state: event.dwData == 1, + }), + SystemEventRequest::Paused => Ok(SystemEvent::Paused), + SystemEventRequest::PositionChanged => Ok(SystemEvent::PositionChanged), + SystemEventRequest::Sim => Ok(SystemEvent::Sim { + state: event.dwData == 1, + }), + SystemEventRequest::SimStart => Ok(SystemEvent::SimStart), + SystemEventRequest::SimStop => Ok(SystemEvent::SimStop), + SystemEventRequest::Sound => Ok(SystemEvent::Sound { + state: event.dwData == bindings::SIMCONNECT_SOUND_SYSTEM_EVENT_DATA_MASTER, + }), + SystemEventRequest::Unpaused => Ok(SystemEvent::Unpaused), + SystemEventRequest::View => Ok(SystemEvent::View { + view: ViewType::try_from(event.dwData).unwrap_or(ViewType::None), + }), + _ => Err(SimConnectError::UnimplementedEventType(event.uEventID)), + } + } +} + +impl TryFrom<&bindings::SIMCONNECT_RECV_EVENT_FILENAME> for SystemEvent { + type Error = SimConnectError; + + fn try_from(event: &bindings::SIMCONNECT_RECV_EVENT_FILENAME) -> Result { + let request = SystemEventRequest::try_from(event._base.uEventID) + .map_err(|_| SimConnectError::UnimplementedEventType(event._base.uEventID))?; + + match request { + SystemEventRequest::AircraftLoaded => { + let file_name = fixed_c_str_to_string(&event.szFileName); + Ok(SystemEvent::AircraftLoaded { file_name }) + } + SystemEventRequest::FlightLoaded => { + let file_name = fixed_c_str_to_string(&event.szFileName); + Ok(SystemEvent::FlightLoaded { file_name }) + } + SystemEventRequest::FlightSaved => { + let file_name = fixed_c_str_to_string(&event.szFileName); + Ok(SystemEvent::FlightSaved { file_name }) + } + SystemEventRequest::FlightPlanActivated => { + let file_name = fixed_c_str_to_string(&event.szFileName); + Ok(SystemEvent::FlightPlanActivated { file_name }) + } + _ => Err(SimConnectError::UnimplementedEventType( + event._base.uEventID, + )), + } + } +} + +impl TryFrom<&bindings::SIMCONNECT_RECV_EVENT_FRAME> for SystemEvent { + type Error = SimConnectError; + + fn try_from(event: &bindings::SIMCONNECT_RECV_EVENT_FRAME) -> Result { + let request = SystemEventRequest::try_from(event._base.uEventID) + .map_err(|_| SimConnectError::UnimplementedEventType(event._base.uEventID))?; + + match request { + SystemEventRequest::Frame => Ok(SystemEvent::Frame { + frame_rate: event.fFrameRate, + sim_speed: event.fSimSpeed, + }), + SystemEventRequest::PauseFrame => Ok(SystemEvent::PauseFrame { + frame_rate: event.fFrameRate, + sim_speed: event.fSimSpeed, + }), + _ => Err(SimConnectError::UnimplementedEventType( + event._base.uEventID, + )), + } + } +} + +pub(crate) const CLIENT_EVENT_START: u32 = 128; + +/// SimConnect Client Event. +/// +/// WIP. As defined by . +#[derive(Debug, Copy, Clone, num_enum::TryFromPrimitive)] +#[repr(u32)] +#[non_exhaustive] +pub enum ClientEvent { + // Aircraft Engine + /// Set throttles max. + ThrottleFull = CLIENT_EVENT_START, + // --------------- + // Aircraft Miscellaneous Systems + /// Increment brake pressure. Note: These are simulated spring-loaded toe brakes, which will bleed back to zero over time. + Brakes, + /// Increments left brake pressure. Note: This is a simulated spring-loaded toe brake, which will bleed back to zero over time. + BrakesLeft, + /// Sets left brake position from axis controller (e.g. joystick). -16383 (0 brakes) to +16383 (max brakes). + AxisLeftBrakeSet, +} + +impl ClientEvent { + pub(crate) fn into_c_char(self) -> *const c_char { + match self { + // Aircraft Engine + ClientEvent::ThrottleFull => "THROTTLE_FULL\0".as_ptr() as *const c_char, + // Aircraft Miscellaneous Systems + ClientEvent::Brakes => "BRAKES\0".as_ptr() as *const c_char, + ClientEvent::BrakesLeft => "BRAKES_LEFT\0".as_ptr() as *const c_char, + ClientEvent::AxisLeftBrakeSet => "AXIS_LEFT_BRAKE_SET\0".as_ptr() as *const c_char, + } + } +} diff --git a/simconnect-sdk/src/domain/mod.rs b/simconnect-sdk/src/domain/mod.rs index 313616a..badc7a6 100644 --- a/simconnect-sdk/src/domain/mod.rs +++ b/simconnect-sdk/src/domain/mod.rs @@ -1,6 +1,6 @@ mod condition; mod data_type; -mod event; +mod events; mod facilities; mod notification; mod notification_group; @@ -8,7 +8,7 @@ mod period; pub use condition::*; pub use data_type::*; -pub use event::*; +pub use events::*; pub use facilities::*; pub use notification::*; pub use notification_group::*; diff --git a/simconnect-sdk/src/domain/notification.rs b/simconnect-sdk/src/domain/notification.rs index 66eac65..cab51ba 100644 --- a/simconnect-sdk/src/domain/notification.rs +++ b/simconnect-sdk/src/domain/notification.rs @@ -1,4 +1,6 @@ -use crate::{Airport, Event, SimConnectError, SimConnectObjectExt, Waypoint, NDB, VOR}; +use crate::{ + Airport, ClientEvent, SimConnectError, SimConnectObjectExt, SystemEvent, Waypoint, NDB, VOR, +}; /// Notification received from SimConnect. #[derive(Debug)] @@ -6,8 +8,10 @@ use crate::{Airport, Event, SimConnectError, SimConnectObjectExt, Waypoint, NDB, pub enum Notification { /// SimConnect open Open, - /// SimConnect event - Event(Event), + /// SimConnect client event + ClientEvent(ClientEvent), + /// SimConnect system event + SystemEvent(SystemEvent), /// SimConnect object Object(Object), /// A list of [crate::Airport]. diff --git a/simconnect-sdk/src/macros.rs b/simconnect-sdk/src/macros.rs index 9d0bfa7..ecca6e0 100644 --- a/simconnect-sdk/src/macros.rs +++ b/simconnect-sdk/src/macros.rs @@ -1,8 +1,10 @@ macro_rules! success { ($hr:expr) => {{ let hr = $hr; - if hr != 0 { - return Err(SimConnectError::SimConnectError(hr)); + if hr == 0 { + Ok(()) + } else { + Err(SimConnectError::SimConnectError(hr)) } }}; } diff --git a/simconnect-sdk/src/simconnect/base.rs b/simconnect-sdk/src/simconnect/base.rs index bcf59a8..0dd020d 100644 --- a/simconnect-sdk/src/simconnect/base.rs +++ b/simconnect-sdk/src/simconnect/base.rs @@ -3,8 +3,9 @@ use std::{collections::HashMap, ffi::c_void}; use tracing::{error, span, trace, warn, Level}; use crate::{ - as_c_string, bindings, helpers::fixed_c_str_to_string, ok_if_fail, success, Airport, Event, - Notification, Object, SimConnectError, Waypoint, NDB, VOR, + as_c_string, bindings, helpers::fixed_c_str_to_string, ok_if_fail, success, Airport, + ClientEvent, Notification, Object, SimConnectError, SystemEvent, Waypoint, CLIENT_EVENT_START, + NDB, VOR, }; /// SimConnect SDK Client. @@ -102,7 +103,7 @@ impl SimConnect { std::ptr::null_mut(), 0, ) - }); + })?; Ok(Self { handle: std::ptr::NonNull::new(handle).ok_or_else(|| { @@ -158,10 +159,32 @@ impl SimConnect { let event: &bindings::SIMCONNECT_RECV_EVENT = unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_EVENT) }; - let event = Event::try_from(event.uEventID) - .map_err(|_| SimConnectError::UnimplementedEventType(event.uEventID))?; + if event.uEventID >= CLIENT_EVENT_START { + let event = ClientEvent::try_from(event.uEventID) + .map_err(|_| SimConnectError::UnimplementedEventType(event.uEventID))?; - Ok(Some(Notification::Event(event))) + Ok(Some(Notification::ClientEvent(event))) + } else { + let event = SystemEvent::try_from(event)?; + + Ok(Some(Notification::SystemEvent(event))) + } + } + bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EVENT_FILENAME => { + trace!("Received SIMCONNECT_RECV_EVENT_FILENAME"); + let event: &bindings::SIMCONNECT_RECV_EVENT_FILENAME = + unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_EVENT_FILENAME) }; + + let event = SystemEvent::try_from(event)?; + Ok(Some(Notification::SystemEvent(event))) + } + bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EVENT_FRAME => { + trace!("Received SIMCONNECT_RECV_EVENT_FRAME"); + let event: &bindings::SIMCONNECT_RECV_EVENT_FRAME = + unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_EVENT_FRAME) }; + + let event = SystemEvent::try_from(event)?; + Ok(Some(Notification::SystemEvent(event))) } bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_SIMOBJECT_DATA => { trace!("Received SIMCONNECT_RECV_SIMOBJECT_DATA"); diff --git a/simconnect-sdk/src/simconnect/events.rs b/simconnect-sdk/src/simconnect/events.rs index e066c9a..0ca301a 100644 --- a/simconnect-sdk/src/simconnect/events.rs +++ b/simconnect-sdk/src/simconnect/events.rs @@ -1,4 +1,7 @@ -use crate::{bindings, success, Event, NotificationGroup, SimConnect, SimConnectError}; +use crate::{ + bindings, success, ClientEvent, NotificationGroup, SimConnect, SimConnectError, + SystemEventRequest, +}; impl SimConnect { /// Associates a client defined event with a Microsoft Flight Simulator event name. @@ -7,7 +10,7 @@ impl SimConnect { #[tracing::instrument(name = "SimConnect::register_event", level = "debug", skip(self))] pub fn register_event( &self, - event: Event, + event: ClientEvent, notification_group: NotificationGroup, ) -> Result<(), SimConnectError> { success!(unsafe { @@ -16,7 +19,7 @@ impl SimConnect { event as u32, event.into_c_char(), ) - }); + })?; success!(unsafe { bindings::SimConnect_AddClientEventToNotificationGroup( @@ -25,7 +28,7 @@ impl SimConnect { event as u32, 0, ) - }); + })?; success!(unsafe { bindings::SimConnect_SetNotificationGroupPriority( @@ -33,8 +36,40 @@ impl SimConnect { notification_group as u32, 1, ) - }); + }) + } - Ok(()) + /// Request that a specific system event is notified to the client. + #[tracing::instrument( + name = "SimConnect::subscribe_to_system_event", + level = "debug", + skip(self) + )] + pub fn subscribe_to_system_event( + &mut self, + event: SystemEventRequest, + ) -> Result<(), SimConnectError> { + success!(unsafe { + bindings::SimConnect_SubscribeToSystemEvent( + self.handle.as_ptr(), + event as u32, + event.into_c_char(), + ) + }) + } + + /// Request that notifications are no longer received for the specified system event. + #[tracing::instrument( + name = "SimConnect::unsubscribe_from_system_event", + level = "debug", + skip(self) + )] + pub fn unsubscribe_from_system_event( + &mut self, + event: SystemEventRequest, + ) -> Result<(), SimConnectError> { + success!(unsafe { + bindings::SimConnect_UnsubscribeFromSystemEvent(self.handle.as_ptr(), event as u32) + }) } } diff --git a/simconnect-sdk/src/simconnect/facilities.rs b/simconnect-sdk/src/simconnect/facilities.rs index f76e6f2..03577c1 100644 --- a/simconnect-sdk/src/simconnect/facilities.rs +++ b/simconnect-sdk/src/simconnect/facilities.rs @@ -22,15 +22,13 @@ impl SimConnect { let type_name = facility_type.to_type_name(); let request_id = self.new_request_id(type_name)?; - unsafe { - success!(bindings::SimConnect_RequestFacilitiesList( + success!(unsafe { + bindings::SimConnect_RequestFacilitiesList( self.handle.as_ptr(), facility_type.into(), request_id, - )); - } - - Ok(()) + ) + }) } /// Request notifications when a facility of a certain type is added to the facilities cache. @@ -58,15 +56,13 @@ impl SimConnect { let type_name = facility_type.to_type_name(); let request_id = self.new_request_id(type_name)?; - unsafe { - success!(bindings::SimConnect_SubscribeToFacilities( + success!(unsafe { + bindings::SimConnect_SubscribeToFacilities( self.handle.as_ptr(), facility_type.into(), request_id, - )); - } - - Ok(()) + ) + }) } /// Request that notifications of additions to the facilities cache are not longer sent. @@ -84,12 +80,9 @@ impl SimConnect { ) -> Result<(), SimConnectError> { let type_name = facility_type.to_type_name(); - unsafe { - success!(bindings::SimConnect_UnsubscribeToFacilities( - self.handle.as_ptr(), - facility_type.into(), - )); - } + success!(unsafe { + bindings::SimConnect_UnsubscribeToFacilities(self.handle.as_ptr(), facility_type.into()) + })?; self.unregister_request_id_by_type_name(&type_name); diff --git a/simconnect-sdk/src/simconnect/objects.rs b/simconnect-sdk/src/simconnect/objects.rs index 0f600bf..f077034 100644 --- a/simconnect-sdk/src/simconnect/objects.rs +++ b/simconnect-sdk/src/simconnect/objects.rs @@ -26,12 +26,9 @@ impl SimConnect { .get(&type_name) .ok_or_else(|| SimConnectError::ObjectNotRegistered(type_name.clone()))?; - unsafe { - success!(bindings::SimConnect_ClearDataDefinition( - self.handle.as_ptr(), - *request_id, - )); - } + success!(unsafe { + bindings::SimConnect_ClearDataDefinition(self.handle.as_ptr(), *request_id) + })?; self.unregister_request_id_by_type_name(&type_name) .ok_or(SimConnectError::ObjectNotRegistered(type_name)) @@ -59,8 +56,8 @@ impl SimConnect { DataType::String => bindings::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_STRING256, }; - unsafe { - success!(bindings::SimConnect_AddToDataDefinition( + success!(unsafe { + bindings::SimConnect_AddToDataDefinition( self.handle.as_ptr(), request_id, as_c_string!(name), @@ -68,10 +65,8 @@ impl SimConnect { c_type, 0.0, u32::MAX, - )); - } - - Ok(()) + ) + }) } /// Request when the SimConnect client is to receive data values for a specific object. @@ -100,8 +95,8 @@ impl SimConnect { condition: Condition, interval: u32, ) -> Result<(), SimConnectError> { - unsafe { - success!(bindings::SimConnect_RequestDataOnSimObject( + success!(unsafe { + bindings::SimConnect_RequestDataOnSimObject( self.handle.as_ptr(), request_id, request_id, @@ -111,9 +106,7 @@ impl SimConnect { 0, interval, 0, - )); - } - - Ok(()) + ) + }) } }