1#![allow(clippy::too_many_arguments)]
2
3use crate::sys;
4use std::any::TypeId;
5use std::collections::HashMap;
6use std::pin::Pin;
7
8pub use sys::SIMCONNECT_OBJECT_ID_USER;
9
10pub use msfs_derive::sim_connect_client_data_definition as client_data_definition;
11pub use msfs_derive::sim_connect_data_definition as data_definition;
12
13pub type DataXYZ = sys::SIMCONNECT_DATA_XYZ;
14pub type InitPosition = sys::SIMCONNECT_DATA_INITPOSITION;
15
16pub trait DataDefinition: 'static {
18 #[doc(hidden)]
19 const DEFINITIONS: &'static [(&'static str, &'static str, f32, sys::SIMCONNECT_DATATYPE)];
20}
21
22pub trait ClientDataDefinition: 'static {
24 #[doc(hidden)]
25 fn get_definitions() -> Vec<(usize, usize, f32)>;
26}
27
28#[allow(dead_code)]
30#[derive(Debug)]
31pub struct HResult(sys::HRESULT);
32impl std::fmt::Display for HResult {
33 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
34 std::fmt::Debug::fmt(self, fmt)
35 }
36}
37impl std::error::Error for HResult {}
38
39pub type Result<T> = std::result::Result<T, HResult>;
40#[inline(always)]
41fn map_err(result: sys::HRESULT) -> Result<()> {
42 if result >= 0 {
43 Ok(())
44 } else {
45 Err(HResult(result))
46 }
47}
48
49type SimConnectCallback<'a> = dyn FnMut(&mut SimConnect, SimConnectRecv) + 'a;
50
51pub struct SimConnect<'a> {
53 handle: sys::HANDLE,
54 callback: Box<SimConnectCallback<'a>>,
55 data_definitions: HashMap<TypeId, sys::SIMCONNECT_DATA_DEFINITION_ID>,
56 client_data_definitions: HashMap<TypeId, sys::SIMCONNECT_CLIENT_DATA_DEFINITION_ID>,
57 event_id_counter: sys::DWORD,
58 client_data_id_counter: sys::DWORD,
59}
60
61impl std::fmt::Debug for SimConnect<'_> {
62 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
63 fmt.debug_struct("SimConnect").finish()
64 }
65}
66
67impl<'a> SimConnect<'a> {
68 pub fn open<F>(name: &str, callback: F) -> Result<Pin<Box<SimConnect<'a>>>>
70 where
71 F: FnMut(&mut SimConnect, SimConnectRecv) + 'a,
72 {
73 unsafe {
74 let mut handle = 0;
75 let name = std::ffi::CString::new(name).unwrap();
76 map_err(sys::SimConnect_Open(
77 &mut handle,
78 name.as_ptr(),
79 std::ptr::null_mut(),
80 0,
81 0,
82 0,
83 ))?;
84 debug_assert!(handle != 0);
85 let mut sim = Box::pin(SimConnect {
86 handle,
87 callback: Box::new(callback),
88 data_definitions: HashMap::new(),
89 client_data_definitions: HashMap::new(),
90 event_id_counter: 0,
91 client_data_id_counter: 0,
92 });
93 sim.call_dispatch()?;
94 Ok(sim)
95 }
96 }
97
98 pub fn call_dispatch(&mut self) -> Result<()> {
100 unsafe {
101 map_err(sys::SimConnect_CallDispatch(
102 self.handle,
103 Some(dispatch_cb),
104 self as *mut SimConnect as *mut std::ffi::c_void,
105 ))
106 }
107 }
108
109 fn get_define_id<T: DataDefinition>(&mut self) -> Result<sys::SIMCONNECT_DATA_DEFINITION_ID> {
110 let handle = self.handle;
111 SimConnect::get_id::<T, _, _>(
112 &mut self.data_definitions,
113 |define_id: sys::SIMCONNECT_DATA_DEFINITION_ID| {
114 for (datum_name, units_type, epsilon, datatype) in T::DEFINITIONS {
120 let datum_name = std::ffi::CString::new(*datum_name).unwrap();
121 let units_type = std::ffi::CString::new(*units_type).unwrap();
122 unsafe {
123 map_err(sys::SimConnect_AddToDataDefinition(
124 handle,
125 define_id,
126 datum_name.as_ptr(),
127 units_type.as_ptr(),
128 *datatype,
129 *epsilon,
130 sys::SIMCONNECT_UNUSED,
131 ))?;
132 }
133 }
134 Ok(())
135 },
136 )
137 }
138
139 fn get_client_data_define_id<T: ClientDataDefinition>(
140 &mut self,
141 ) -> Result<sys::SIMCONNECT_CLIENT_DATA_DEFINITION_ID> {
142 let handle = self.handle;
143 SimConnect::get_id::<T, _, _>(&mut self.client_data_definitions, |define_id| {
144 let mut padding = usize::MAX;
153 for (offset, size, epsilon) in T::get_definitions() {
154 padding = padding.min(std::mem::size_of::<T>() - (offset + size));
155 unsafe {
156 map_err(sys::SimConnect_AddToClientDataDefinition(
157 handle,
158 define_id,
159 offset as sys::DWORD,
160 size as sys::DWORD,
161 epsilon,
162 sys::SIMCONNECT_UNUSED,
163 ))?;
164 }
165 }
166 if padding > 0 && padding != usize::MAX {
167 unsafe {
168 map_err(sys::SimConnect_AddToClientDataDefinition(
169 handle,
170 define_id,
171 (std::mem::size_of::<T>() - padding) as sys::DWORD,
172 padding as sys::DWORD,
173 0.0,
174 sys::SIMCONNECT_UNUSED,
175 ))?;
176 }
177 }
178 Ok(())
179 })
180 }
181
182 fn get_id<T: 'static, U: std::convert::TryFrom<usize> + Copy, F: Fn(U) -> Result<()>>(
183 map: &mut HashMap<TypeId, U>,
184 insert_fn: F,
185 ) -> Result<U> {
186 let key = TypeId::of::<T>();
187 let maybe_id = U::try_from(map.len()).unwrap_or_else(|_| unreachable!());
188 match map.entry(key) {
189 std::collections::hash_map::Entry::Vacant(entry) => {
190 insert_fn(maybe_id)?;
191 entry.insert(maybe_id);
192 Ok(maybe_id)
193 }
194 std::collections::hash_map::Entry::Occupied(entry) => Ok(*entry.get()),
195 }
196 }
197
198 pub fn set_data_on_sim_object<T: DataDefinition>(
200 &mut self,
201 object_id: sys::SIMCONNECT_OBJECT_ID,
202 data: &T,
203 ) -> Result<()> {
204 let define_id = self.get_define_id::<T>()?;
205 unsafe {
206 map_err(sys::SimConnect_SetDataOnSimObject(
207 self.handle,
208 define_id,
209 object_id,
210 0,
211 0,
212 std::mem::size_of_val(data) as sys::DWORD,
213 data as *const T as *mut std::ffi::c_void,
214 ))
215 }
216 }
217
218 pub fn request_data_on_sim_object_type<T: DataDefinition>(
221 &mut self,
222 request_id: sys::SIMCONNECT_DATA_REQUEST_ID,
223 radius: sys::DWORD,
224 r#type: sys::SIMCONNECT_SIMOBJECT_TYPE,
225 ) -> Result<()> {
226 let define_id = self.get_define_id::<T>()?;
227 unsafe {
228 map_err(sys::SimConnect_RequestDataOnSimObjectType(
229 self.handle,
230 request_id,
231 define_id,
232 radius,
233 r#type,
234 ))
235 }
236 }
237
238 pub fn request_data_on_sim_object<T: DataDefinition>(
240 &mut self,
241 request_id: sys::SIMCONNECT_DATA_REQUEST_ID,
242 object_id: sys::SIMCONNECT_OBJECT_ID,
243 period: Period,
244 ) -> Result<()> {
245 let define_id = self.get_define_id::<T>()?;
246
247 unsafe {
248 map_err(sys::SimConnect_RequestDataOnSimObject(
249 self.handle,
250 request_id,
251 define_id,
252 object_id,
253 period as sys::SIMCONNECT_PERIOD,
254 sys::SIMCONNECT_DATA_REQUEST_FLAG_CHANGED,
255 0,
256 0,
257 0,
258 ))
259 }
260 }
261
262 pub fn map_client_event_to_sim_event(
265 &mut self,
266 event_name: &str,
267 mask: bool,
268 ) -> Result<sys::DWORD> {
269 let event_id = self.event_id_counter;
270 self.event_id_counter += 1;
271 let event_name = std::ffi::CString::new(event_name).unwrap();
272
273 unsafe {
274 map_err(sys::SimConnect_MapClientEventToSimEvent(
275 self.handle,
276 event_id,
277 event_name.as_ptr(),
278 ))?;
279
280 map_err(sys::SimConnect_AddClientEventToNotificationGroup(
281 self.handle,
282 0,
283 event_id,
284 mask.into(),
285 ))?;
286
287 map_err(sys::SimConnect_SetNotificationGroupPriority(
288 self.handle,
289 0,
290 sys::SIMCONNECT_GROUP_PRIORITY_HIGHEST_MASKABLE,
291 ))?;
292 }
293 Ok(event_id)
294 }
295
296 pub fn transmit_client_event(
298 &mut self,
299 object_id: sys::SIMCONNECT_OBJECT_ID,
300 event_id: sys::DWORD,
301 data: sys::DWORD,
302 ) -> Result<()> {
303 unsafe {
304 map_err(sys::SimConnect_TransmitClientEvent(
305 self.handle,
306 object_id,
307 event_id,
308 data,
309 0,
310 0,
311 ))
312 }
313 }
314
315 pub fn transmit_client_event_ex1(
316 &mut self,
317 object_id: sys::SIMCONNECT_OBJECT_ID,
318 event_id: sys::DWORD,
319 data: [sys::DWORD; 5],
320 ) -> Result<()> {
321 unsafe {
322 map_err(sys::SimConnect_TransmitClientEvent_EX1(
323 self.handle,
324 object_id,
325 event_id,
326 0,
327 0,
328 data[0],
329 data[1],
330 data[2],
331 data[3],
332 data[4],
333 ))
334 }
335 }
336
337 fn get_client_data_id(&mut self, name: &str) -> Result<sys::SIMCONNECT_CLIENT_DATA_ID> {
338 let client_id = self.client_data_id_counter;
339 self.client_data_id_counter += 1;
340 let name = std::ffi::CString::new(name).unwrap();
341
342 unsafe {
343 map_err(sys::SimConnect_MapClientDataNameToID(
344 self.handle,
345 name.as_ptr(),
346 client_id,
347 ))?;
348 }
349 Ok(client_id)
350 }
351
352 pub fn create_client_data<T: ClientDataDefinition>(
356 &mut self,
357 name: &str,
358 ) -> Result<ClientDataArea<T>> {
359 let client_id = self.get_client_data_id(name)?;
360 unsafe {
361 map_err(sys::SimConnect_CreateClientData(
362 self.handle,
363 client_id,
364 std::mem::size_of::<T>() as sys::DWORD,
365 0,
366 ))?;
367 }
368 Ok(ClientDataArea {
369 client_id,
370 phantom: std::marker::PhantomData,
371 })
372 }
373
374 pub fn get_client_area<T: ClientDataDefinition>(
377 &mut self,
378 name: &str,
379 ) -> Result<ClientDataArea<T>> {
380 let client_id = self.get_client_data_id(name)?;
381 Ok(ClientDataArea {
382 client_id,
383 phantom: std::marker::PhantomData,
384 })
385 }
386
387 pub fn request_client_data<T: ClientDataDefinition>(
391 &mut self,
392 request_id: sys::SIMCONNECT_DATA_REQUEST_ID,
393 name: &str,
394 ) -> Result<()> {
395 let define_id = self.get_client_data_define_id::<T>()?;
396 let client_id = self.get_client_data_id(name)?;
397 unsafe {
398 map_err(sys::SimConnect_RequestClientData(
399 self.handle,
400 client_id,
401 request_id,
402 define_id,
403 sys::SIMCONNECT_CLIENT_DATA_PERIOD_SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET,
404 sys::SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_CHANGED,
405 0,
406 0,
407 0,
408 ))?;
409 }
410 Ok(())
411 }
412
413 pub fn set_client_data<T: ClientDataDefinition>(
416 &mut self,
417 area: &ClientDataArea<T>,
418 data: &T,
419 ) -> Result<()> {
420 unsafe {
421 map_err(sys::SimConnect_SetClientData(
422 self.handle,
423 area.client_id,
424 self.get_client_data_define_id::<T>()?,
425 0,
426 0,
427 std::mem::size_of::<T>() as sys::DWORD,
428 data as *const _ as *mut std::ffi::c_void,
429 ))?;
430 }
431 Ok(())
432 }
433
434 pub fn ai_create_non_atc_aircraft(
435 &mut self,
436 container_title: &str,
437 tail_number: &str,
438 init_position: sys::SIMCONNECT_DATA_INITPOSITION,
439 request_id: sys::SIMCONNECT_DATA_REQUEST_ID,
440 ) -> Result<()> {
441 let container_title = std::ffi::CString::new(container_title).unwrap();
442 let tail_number = std::ffi::CString::new(tail_number).unwrap();
443
444 unsafe {
445 map_err(sys::SimConnect_AICreateNonATCAircraft(
446 self.handle,
447 container_title.as_ptr(),
448 tail_number.as_ptr(),
449 init_position,
450 request_id,
451 ))?;
452 }
453 Ok(())
454 }
455
456 pub fn ai_create_parked_atc_aircraft(
457 &mut self,
458 container_title: &str,
459 tail_number: &str,
460 icao: &str,
461 request_id: sys::SIMCONNECT_DATA_REQUEST_ID,
462 ) -> Result<()> {
463 let container_title = std::ffi::CString::new(container_title).unwrap();
464 let tail_number = std::ffi::CString::new(tail_number).unwrap();
465 let icao = std::ffi::CString::new(icao).unwrap();
466
467 unsafe {
468 map_err(sys::SimConnect_AICreateParkedATCAircraft(
469 self.handle,
470 container_title.as_ptr(),
471 tail_number.as_ptr(),
472 icao.as_ptr(),
473 request_id,
474 ))?;
475 }
476 Ok(())
477 }
478
479 pub fn ai_remove_object(
480 &mut self,
481 object_id: sys::SIMCONNECT_OBJECT_ID,
482 request_id: sys::SIMCONNECT_DATA_REQUEST_ID,
483 ) -> Result<()> {
484 unsafe {
485 map_err(sys::SimConnect_AIRemoveObject(
486 self.handle,
487 object_id,
488 request_id,
489 ))?;
490 }
491 Ok(())
492 }
493
494 pub fn subscribe_to_system_event(&mut self, system_event_name: &str) -> Result<sys::DWORD> {
495 let event_id = self.event_id_counter;
496 self.event_id_counter += 1;
497 let system_event_name = std::ffi::CString::new(system_event_name).unwrap();
498
499 unsafe {
500 map_err(sys::SimConnect_SubscribeToSystemEvent(
501 self.handle,
502 event_id,
503 system_event_name.as_ptr(),
504 ))?;
505 }
506 Ok(event_id)
507 }
508
509 pub fn unsubscribe_from_system_event(
510 &mut self,
511 event_id: sys::SIMCONNECT_CLIENT_EVENT_ID,
512 ) -> Result<()> {
513 unsafe {
514 map_err(sys::SimConnect_UnsubscribeFromSystemEvent(
515 self.handle,
516 event_id,
517 ))?;
518 }
519 Ok(())
520 }
521
522 pub fn set_system_event_state(
523 &mut self,
524 event_id: sys::SIMCONNECT_CLIENT_EVENT_ID,
525 on: bool,
526 ) -> Result<()> {
527 let state = on.into();
528 unsafe {
529 map_err(sys::SimConnect_SetSystemEventState(
530 self.handle,
531 event_id,
532 state,
533 ))?;
534 }
535 Ok(())
536 }
537
538 pub fn load_flight(&mut self, flight_file_path: &str) -> Result<()> {
540 let flight_file_path = std::ffi::CString::new(flight_file_path).unwrap();
541
542 unsafe {
543 map_err(sys::SimConnect_FlightLoad(
544 self.handle,
545 flight_file_path.as_ptr(),
546 ))?;
547 }
548 Ok(())
549 }
550
551 pub fn save_flight(
553 &mut self,
554 flight_file_path: &str,
555 title: Option<&str>,
556 description: Option<&str>,
557 ) -> Result<()> {
558 let flight_file_path = std::ffi::CString::new(flight_file_path).unwrap();
559 let title = title.map(|x| std::ffi::CString::new(x).unwrap());
560 let description = description.map(|x| std::ffi::CString::new(x).unwrap());
561
562 unsafe {
563 map_err(sys::SimConnect_FlightSave(
564 self.handle,
565 flight_file_path.as_ptr(),
566 title
567 .as_ref()
568 .map(|x| x.as_ptr())
569 .unwrap_or(std::ptr::null()),
570 description
571 .as_ref()
572 .map(|x| x.as_ptr())
573 .unwrap_or(std::ptr::null()),
574 0,
575 ))?;
576 }
577 Ok(())
578 }
579
580 pub fn load_flight_plan(&mut self, flight_plan_file_path: &str) -> Result<()> {
582 let flight_plan_file_path = std::ffi::CString::new(flight_plan_file_path).unwrap();
583
584 unsafe {
585 map_err(sys::SimConnect_FlightPlanLoad(
586 self.handle,
587 flight_plan_file_path.as_ptr(),
588 ))?;
589 }
590 Ok(())
591 }
592}
593
594impl Drop for SimConnect<'_> {
595 fn drop(&mut self) {
596 unsafe {
597 map_err(sys::SimConnect_Close(self.handle)).expect("SimConnect_Close");
598 }
599 }
600}
601
602macro_rules! recv {
603 ($V:ident) => {
604 $V! {
605 (
606 SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EXCEPTION,
607 SIMCONNECT_RECV_EXCEPTION,
608 Exception
609 ),
610 (
611 SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_OPEN,
612 SIMCONNECT_RECV_OPEN,
613 Open
614 ),
615 (
616 SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_QUIT,
617 SIMCONNECT_RECV_QUIT,
618 Quit
619 ),
620 (
621 SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EVENT,
622 SIMCONNECT_RECV_EVENT,
623 Event
624 ),
625 (
626 SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EVENT_EX1,
627 SIMCONNECT_RECV_EVENT_EX1,
628 EventEx1
629 ),
630 (
631 SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_SIMOBJECT_DATA,
632 SIMCONNECT_RECV_SIMOBJECT_DATA,
633 SimObjectData
634 ),
635 (
636 SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_CLIENT_DATA,
637 SIMCONNECT_RECV_CLIENT_DATA,
638 ClientData
639 ),
640 (
641 SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID,
642 SIMCONNECT_RECV_ASSIGNED_OBJECT_ID,
643 AssignedObjectId
644 ),
645 }
646 };
647}
648
649extern "C" fn dispatch_cb(
650 recv: *mut sys::SIMCONNECT_RECV,
651 _cb_data: sys::DWORD,
652 p_context: *mut std::ffi::c_void,
653) {
654 macro_rules! recv_cb {
655 ($( ($ID:ident, $T:ident, $E:ident), )*) => {
656 unsafe {
657 match (*recv).dwID as sys::SIMCONNECT_RECV_ID {
658 sys::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_NULL => Some(SimConnectRecv::Null),
659 $(
660 sys::$ID => Some(SimConnectRecv::$E(&*(recv as *mut sys::$T))),
661 )*
662 sys::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE => {
663 Some(SimConnectRecv::SimObjectData(&*(recv as *mut sys::SIMCONNECT_RECV_SIMOBJECT_DATA)))
664 }
665 _ => None,
666 }
667 }
668 }
669 }
670 let recv = recv!(recv_cb);
671
672 if let Some(recv) = recv {
673 let sim = unsafe { &mut *(p_context as *mut SimConnect) };
674 (sim.callback)(unsafe { &mut *(p_context as *mut SimConnect) }, recv);
675 }
676}
677
678macro_rules! recv_enum {
679 ($( ($ID:ident, $T:ident, $E:ident), )*) => {
680 #[derive(Debug)]
682 pub enum SimConnectRecv<'a> {
683 Null,
684 $(
685 $E(&'a sys::$T),
686 )*
687 }
688 }
689}
690recv!(recv_enum);
691
692impl sys::SIMCONNECT_RECV_EVENT {
693 pub fn id(&self) -> sys::DWORD {
695 self.uEventID
696 }
697
698 pub fn data(&self) -> sys::DWORD {
700 self.dwData
701 }
702}
703
704impl sys::SIMCONNECT_RECV_EVENT_EX1 {
705 pub fn id(&self) -> sys::DWORD {
707 self.uEventID
708 }
709
710 pub fn data(&self) -> [sys::DWORD; 5] {
712 [
713 self.dwData0,
714 self.dwData1,
715 self.dwData2,
716 self.dwData3,
717 self.dwData4,
718 ]
719 }
720}
721
722impl sys::SIMCONNECT_RECV_ASSIGNED_OBJECT_ID {
723 pub fn id(&self) -> sys::DWORD {
724 self.dwRequestID
725 }
726
727 pub fn object_id(&self) -> sys::DWORD {
728 self.dwObjectID
729 }
730}
731
732impl sys::SIMCONNECT_RECV_SIMOBJECT_DATA {
733 pub fn id(&self) -> sys::DWORD {
735 self.dwRequestID
736 }
737
738 pub fn into<T: DataDefinition>(&self, sim: &SimConnect) -> Option<&T> {
740 let define_id = sim.data_definitions[&TypeId::of::<T>()];
741 if define_id == self.dwDefineID {
742 Some(unsafe { &*(std::ptr::addr_of!(self.dwData) as *const T) })
744 } else {
745 None
746 }
747 }
748}
749
750impl sys::SIMCONNECT_RECV_CLIENT_DATA {
751 pub fn id(&self) -> sys::DWORD {
753 self._base.dwRequestID
754 }
755
756 pub fn into<T: ClientDataDefinition>(&self, sim: &SimConnect) -> Option<&T> {
758 let define_id = sim.client_data_definitions[&TypeId::of::<T>()];
759 if define_id == self._base.dwDefineID {
760 Some(unsafe { &*(std::ptr::addr_of!(self._base.dwData) as *const T) })
762 } else {
763 None
764 }
765 }
766}
767
768#[derive(Debug)]
770pub enum Period {
771 Never = sys::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_NEVER as isize,
773 Once = sys::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_ONCE as isize,
777 VisualFrame = sys::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_VISUAL_FRAME as isize,
779 SimFrame = sys::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_SIM_FRAME as isize,
782 Second = sys::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_SECOND as isize,
784}
785
786pub struct ClientDataArea<T: ClientDataDefinition> {
789 client_id: sys::SIMCONNECT_CLIENT_DATA_ID,
790 phantom: std::marker::PhantomData<T>,
791}