1use std::ffi::CString;
2use std::ffi::NulError;
3use std::ops::DerefMut;
4use std::os::raw::{c_int, c_void};
5
6use xplm_sys::*;
7
8#[derive(Debug)]
10pub struct Command {
11 id: XPLMCommandRef,
13}
14
15impl Command {
16 pub fn find(name: &str) -> Result<Self, CommandFindError> {
20 let name_c = CString::new(name)?;
21 let command_ref = unsafe { XPLMFindCommand(name_c.as_ptr()) };
22 if !command_ref.is_null() {
23 Ok(Command { id: command_ref })
24 } else {
25 Err(CommandFindError::NotFound)
26 }
27 }
28
29 pub fn trigger(&mut self) {
33 unsafe {
34 XPLMCommandOnce(self.id);
35 }
36 }
37
38 pub fn hold_down(&mut self) -> CommandHold {
42 unsafe {
43 XPLMCommandBegin(self.id);
44 }
45 CommandHold { command: self }
46 }
47
48 fn release(&mut self) {
50 unsafe {
51 XPLMCommandEnd(self.id);
52 }
53 }
54}
55
56#[derive(Debug)]
60pub struct CommandHold<'a> {
61 command: &'a mut Command,
63}
64
65impl<'a> Drop for CommandHold<'a> {
66 fn drop(&mut self) {
67 self.command.release();
68 }
69}
70
71#[derive(thiserror::Error, Debug)]
73pub enum CommandFindError {
74 #[error("Null byte in command name")]
76 Null(#[from] NulError),
77
78 #[error("Command not found")]
80 NotFound,
81}
82
83pub trait CommandHandler: 'static {
85 fn command_begin(&mut self);
87 fn command_continue(&mut self);
89 fn command_end(&mut self);
91}
92
93pub struct OwnedCommand {
95 data: Box<OwnedCommandData>,
97 callback: XPLMCommandCallback_f,
99}
100
101impl OwnedCommand {
102 pub fn new<H: CommandHandler>(
107 name: &str,
108 description: &str,
109 handler: H,
110 ) -> Result<Self, CommandCreateError> {
111 let mut data = Box::new(OwnedCommandData::new(name, description, handler)?);
112 let data_ptr: *mut OwnedCommandData = data.deref_mut();
113 unsafe {
114 XPLMRegisterCommandHandler(
115 data.id,
116 Some(command_handler::<H>),
117 1,
118 data_ptr as *mut c_void,
119 );
120 }
121 Ok(OwnedCommand {
122 data,
123 callback: Some(command_handler::<H>),
124 })
125 }
126}
127
128impl Drop for OwnedCommand {
129 fn drop(&mut self) {
130 let data_ptr: *mut OwnedCommandData = self.data.deref_mut();
131 unsafe {
132 XPLMUnregisterCommandHandler(self.data.id, self.callback, 1, data_ptr as *mut c_void);
133 }
134 }
135}
136
137struct OwnedCommandData {
139 id: XPLMCommandRef,
141 handler: Box<dyn CommandHandler>,
143}
144
145impl OwnedCommandData {
146 pub fn new<H: CommandHandler>(
147 name: &str,
148 description: &str,
149 handler: H,
150 ) -> Result<Self, CommandCreateError> {
151 let name_c = CString::new(name)?;
152 let description_c = CString::new(description)?;
153
154 Ok(OwnedCommandData {
155 id: unsafe { XPLMCreateCommand(name_c.as_ptr(), description_c.as_ptr()) },
156 handler: Box::new(handler),
157 })
158 }
159}
160
161unsafe extern "C" fn command_handler<H: CommandHandler>(
163 _: XPLMCommandRef,
164 phase: XPLMCommandPhase,
165 refcon: *mut c_void,
166) -> c_int {
167 let data = refcon as *mut OwnedCommandData;
168 let handler: *mut dyn CommandHandler = (*data).handler.deref_mut();
169 let handler = handler as *mut H;
170 if phase == xplm_CommandBegin as i32 {
171 (*handler).command_begin();
172 } else if phase == xplm_CommandContinue as i32 {
173 (*handler).command_continue();
174 } else if phase == xplm_CommandEnd as i32 {
175 (*handler).command_end();
176 }
177 0
179}
180
181#[derive(thiserror::Error, Debug)]
183pub enum CommandCreateError {
184 #[error("Null byte in Command name")]
186 Null(#[from] NulError),
187
188 #[deprecated(note = "commands persist between plugin reload - not an error if already exists")]
190 #[error("Command exists already")]
191 Exists,
192}