use std::ffi::CString;
use std::ffi::NulError;
use std::ops::DerefMut;
use std::os::raw::{c_int, c_void};
use xplm_sys::*;
#[derive(Debug)]
pub struct Command {
id: XPLMCommandRef,
}
impl Command {
pub fn find(name: &str) -> Result<Self, CommandFindError> {
let name_c = CString::new(name)?;
let command_ref = unsafe { XPLMFindCommand(name_c.as_ptr()) };
if !command_ref.is_null() {
Ok(Command { id: command_ref })
} else {
Err(CommandFindError::NotFound)
}
}
pub fn trigger(&mut self) {
unsafe {
XPLMCommandOnce(self.id);
}
}
pub fn hold_down(&mut self) -> CommandHold {
unsafe {
XPLMCommandBegin(self.id);
}
CommandHold { command: self }
}
fn release(&mut self) {
unsafe {
XPLMCommandEnd(self.id);
}
}
}
#[derive(Debug)]
pub struct CommandHold<'a> {
command: &'a mut Command,
}
impl<'a> Drop for CommandHold<'a> {
fn drop(&mut self) {
self.command.release();
}
}
#[derive(thiserror::Error, Debug)]
pub enum CommandFindError {
#[error("Null byte in command name")]
Null(#[from] NulError),
#[error("Command not found")]
NotFound,
}
pub trait CommandHandler: 'static {
fn command_begin(&mut self);
fn command_continue(&mut self);
fn command_end(&mut self);
}
pub struct OwnedCommand {
data: Box<OwnedCommandData>,
callback: XPLMCommandCallback_f,
}
impl OwnedCommand {
pub fn new<H: CommandHandler>(
name: &str,
description: &str,
handler: H,
) -> Result<Self, CommandCreateError> {
let mut data = Box::new(OwnedCommandData::new(name, description, handler)?);
let data_ptr: *mut OwnedCommandData = data.deref_mut();
unsafe {
XPLMRegisterCommandHandler(
data.id,
Some(command_handler::<H>),
1,
data_ptr as *mut c_void,
);
}
Ok(OwnedCommand {
data,
callback: Some(command_handler::<H>),
})
}
}
impl Drop for OwnedCommand {
fn drop(&mut self) {
let data_ptr: *mut OwnedCommandData = self.data.deref_mut();
unsafe {
XPLMUnregisterCommandHandler(self.data.id, self.callback, 1, data_ptr as *mut c_void);
}
}
}
struct OwnedCommandData {
id: XPLMCommandRef,
handler: Box<dyn CommandHandler>,
}
impl OwnedCommandData {
pub fn new<H: CommandHandler>(
name: &str,
description: &str,
handler: H,
) -> Result<Self, CommandCreateError> {
let name_c = CString::new(name)?;
let description_c = CString::new(description)?;
Ok(OwnedCommandData {
id: unsafe { XPLMCreateCommand(name_c.as_ptr(), description_c.as_ptr()) },
handler: Box::new(handler),
})
}
}
unsafe extern "C" fn command_handler<H: CommandHandler>(
_: XPLMCommandRef,
phase: XPLMCommandPhase,
refcon: *mut c_void,
) -> c_int {
let data = refcon as *mut OwnedCommandData;
let handler: *mut dyn CommandHandler = (*data).handler.deref_mut();
let handler = handler as *mut H;
if phase == xplm_CommandBegin as i32 {
(*handler).command_begin();
} else if phase == xplm_CommandContinue as i32 {
(*handler).command_continue();
} else if phase == xplm_CommandEnd as i32 {
(*handler).command_end();
}
0
}
#[derive(thiserror::Error, Debug)]
pub enum CommandCreateError {
#[error("Null byte in Command name")]
Null(#[from] NulError),
#[deprecated(note = "commands persist between plugin reload - not an error if already exists")]
#[error("Command exists already")]
Exists,
}