use std::{collections::HashMap, ffi::c_void}; use crate::{ as_c_string, bindings, helpers::fixed_c_str_to_string, ok_if_fail, success, Airport, Event, Notification, Object, SimConnectError, Waypoint, NDB, VOR, }; /// SimConnect SDK Client. /// /// # Example /// /// ```no_run /// use simconnect_sdk::{Notification, SimConnect, SimConnectObject}; /// /// /// A data structure that will be used to receive data from SimConnect. /// #[derive(Debug, Clone, SimConnectObject)] /// #[simconnect(period = "second")] /// struct GpsData { /// #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] /// lat: f64, /// #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] /// lon: f64, /// #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] /// alt: f64, /// } /// /// fn main() -> Result<(), Box> { /// let client = SimConnect::new("Simple Program"); /// /// match client { /// Ok(mut client) => { /// let mut notifications_received = 0; /// /// loop { /// let notification = client.get_next_dispatch()?; /// /// match notification { /// Some(Notification::Open) => { /// println!("Connection opened."); /// /// // After the connection is successfully open, we register the struct /// client.register_object::()?; /// } /// Some(Notification::Object(data)) => { /// if let Ok(gps_data) = GpsData::try_from(&data) { /// println!("{gps_data:?}"); /// /// notifications_received += 1; /// /// // After we have received 10 notifications, we unregister the struct /// if notifications_received > 10 { /// client.unregister_object::()?; /// println!("Subscription stopped."); /// break; /// } /// } /// } /// _ => (), /// } /// /// // sleep for about a frame to reduce CPU usage /// std::thread::sleep(std::time::Duration::from_millis(16)); /// } /// } /// Err(e) => { /// println!("Error: {e:?}") /// } /// } /// /// Ok(()) /// } /// ``` #[derive(Debug)] pub struct SimConnect { pub(super) handle: std::ptr::NonNull, pub(super) next_request_id: u32, pub(super) registered_objects: HashMap, } impl SimConnect { /// Create a new SimConnect SDK client. #[tracing::instrument(name = "SimConnect::new")] pub fn new(name: &str) -> Result { let mut handle = std::ptr::null_mut(); success!(unsafe { bindings::SimConnect_Open( &mut handle, as_c_string!(name), std::ptr::null_mut(), 0, std::ptr::null_mut(), 0, ) }); Ok(Self { handle: std::ptr::NonNull::new(handle).ok_or_else(|| { SimConnectError::UnexpectedError( "SimConnect_Open returned null pointer on success".to_string(), ) })?, next_request_id: 0, registered_objects: HashMap::new(), }) } /// Receive the next SimConnect message. /// /// # Remarks /// This is a non-blocking function. If there are no messages to receive, it will return None immediately. /// When called in a loop, it is recommended to use a short sleep time. pub fn get_next_dispatch(&self) -> Result, SimConnectError> { let mut data_buf: *mut bindings::SIMCONNECT_RECV = std::ptr::null_mut(); let mut size_buf: bindings::DWORD = 32; let size_buf_pointer: *mut bindings::DWORD = &mut size_buf; unsafe { ok_if_fail!( bindings::SimConnect_GetNextDispatch( self.handle.as_ptr(), &mut data_buf, size_buf_pointer ), None ); }; let result = match unsafe { (*data_buf).dwID as i32 } { bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_OPEN => Some(Notification::Open), bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_QUIT => Some(Notification::Quit), bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EVENT => { let event = unsafe { *(data_buf as *const bindings::SIMCONNECT_RECV_EVENT) }; let event = Event::try_from(event.uEventID) .map_err(|_| SimConnectError::SimConnectUnrecognizedEvent(event.uEventID))?; Some(Notification::Event(event)) } bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_SIMOBJECT_DATA => { let event: &bindings::SIMCONNECT_RECV_SIMOBJECT_DATA = unsafe { std::mem::transmute_copy( &(data_buf as *const bindings::SIMCONNECT_RECV_SIMOBJECT_DATA), ) }; let type_name = self.get_type_name_by_request_id(event.dwDefineID); match type_name { Some(type_name) => { let data = Object { type_name, data_addr: std::ptr::addr_of!(event.dwData), }; Some(Notification::Object(data)) } _ => None, } } bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_AIRPORT_LIST => { let event: &bindings::SIMCONNECT_RECV_AIRPORT_LIST = unsafe { std::mem::transmute_copy( &(data_buf as *const bindings::SIMCONNECT_RECV_AIRPORT_LIST), ) }; let data = (0..event._base.dwArraySize as usize) .map(|i| { // `rgData` is defined as a 1-element array, but it is actually a variable-length array. let record = unsafe { event.rgData.get_unchecked(i) }; Airport { icao: fixed_c_str_to_string(&record.Icao), lat: record.Latitude, lon: record.Longitude, alt: record.Altitude, } }) .collect::>(); Some(Notification::AirportList(data)) } bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_WAYPOINT_LIST => { let event: &bindings::SIMCONNECT_RECV_WAYPOINT_LIST = unsafe { std::mem::transmute_copy( &(data_buf as *const bindings::SIMCONNECT_RECV_WAYPOINT_LIST), ) }; let data = (0..event._base.dwArraySize as usize) .map(|i| { // `rgData` is defined as a 1-element array, but it is actually a variable-length array. let record = unsafe { event.rgData.get_unchecked(i) }; Waypoint { icao: fixed_c_str_to_string(&record._base.Icao), lat: record._base.Latitude, lon: record._base.Longitude, alt: record._base.Altitude, mag_var: record.fMagVar, } }) .collect::>(); Some(Notification::WaypointList(data)) } bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_NDB_LIST => { let event: &bindings::SIMCONNECT_RECV_NDB_LIST = unsafe { std::mem::transmute_copy( &(data_buf as *const bindings::SIMCONNECT_RECV_NDB_LIST), ) }; let data = (0..event._base.dwArraySize as usize) .map(|i| { // `rgData` is defined as a 1-element array, but it is actually a variable-length array. let record = unsafe { event.rgData.get_unchecked(i) }; NDB { icao: fixed_c_str_to_string(&record._base._base.Icao), lat: record._base._base.Latitude, lon: record._base._base.Longitude, alt: record._base._base.Altitude, mag_var: record._base.fMagVar, frequency: record.fFrequency, } }) .collect::>(); Some(Notification::NdbList(data)) } bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_VOR_LIST => { let event: &bindings::SIMCONNECT_RECV_VOR_LIST = unsafe { std::mem::transmute_copy( &(data_buf as *const bindings::SIMCONNECT_RECV_VOR_LIST), ) }; let data = (0..event._base.dwArraySize as usize) .map(|i| { // `rgData` is defined as a 1-element array, but it is actually a variable-length array. let record = unsafe { event.rgData.get_unchecked(i) }; let has_nav_signal = record.Flags & bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_NAV_SIGNAL == bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_NAV_SIGNAL; let has_localizer = record.Flags & bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_LOCALIZER == bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_LOCALIZER; let has_glide_slope = record.Flags & bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE == bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE; let has_dme = record.Flags & bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_DME == bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_DME; VOR { icao: fixed_c_str_to_string(&record._base._base._base.Icao), lat: record._base._base._base.Latitude, lon: record._base._base._base.Longitude, alt: record._base._base._base.Altitude, mag_var: record._base._base.fMagVar, has_nav_signal, has_localizer, has_glide_slope, has_dme, localizer: if has_localizer { Some(record.fLocalizer) } else { None }, glide_lat: if has_nav_signal { Some(record.GlideLat) } else { None }, glide_lon: if has_nav_signal { Some(record.GlideLon) } else { None }, glide_alt: if has_nav_signal { Some(record.GlideAlt) } else { None }, glide_slope_angle: if has_glide_slope { Some(record.fGlideSlopeAngle) } else { None }, frequency: if has_dme { Some(record._base.fFrequency) } else { None }, } }) .collect::>(); Some(Notification::VorList(data)) } bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EXCEPTION => { let event = unsafe { *(data_buf as *const bindings::SIMCONNECT_RECV_EXCEPTION) }; Some(Notification::Exception(event.dwException)) } bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_NULL => None, _ => panic!("Got unrecognized notification: {}", unsafe { (*data_buf).dwID as i32 }), }; Ok(result) } /// Register a Request ID in the internal state so that the user doesn't have to manually manage Request IDs. #[tracing::instrument(name = "SimConnect::new_request_id")] pub(super) fn new_request_id(&mut self, type_name: String) -> Result { if self.registered_objects.contains_key(&type_name) { return Err(SimConnectError::ObjectAlreadyRegistered(type_name)); } let mut request_id = self.next_request_id; self.next_request_id += 1; // when `next_request_id` overflows some ids might still be in use // so we need to find the next available one while self.registered_objects.values().any(|id| *id == request_id) { request_id = self.next_request_id; self.next_request_id += 1; } self.registered_objects.insert(type_name, request_id); Ok(request_id) } /// Unregister a Request ID in the internal state so that the user doesn't have to manually manage Request IDs. #[tracing::instrument(name = "SimConnect::unregister_request_id_by_type_name")] pub(super) fn unregister_request_id_by_type_name(&mut self, type_name: &str) -> Option { self.registered_objects.remove(type_name) } /// Get the Type Name of a Request ID. #[tracing::instrument(name = "SimConnect::get_type_name_by_request_id")] pub(super) fn get_type_name_by_request_id(&self, request_id: u32) -> Option { self.registered_objects .iter() .find(|(_, v)| **v == request_id) .map(|(k, _)| k.clone()) } } impl Drop for SimConnect { fn drop(&mut self) { let _ = unsafe { bindings::SimConnect_Close(self.handle.as_ptr()) }; } }