This sample demonstrates how to intercept and handle crashes in a way that plays nicely with the X-Plane crash reporter.
The core idea is to intercept all crashes by installing appropriate crash handlers and then filtering out crashes that weren’t caused by the plugin. To do so, we track both the threads created by us at runtime, as well as checking whether the plugin is active if we crash on the main thread. If the crash wasn’t caused by the plugin, the crash is forwarded to the next crash handler (potentially X-Plane, or another plugin) which then gets a chance to process the crash.
If the crash is caused by the plugin, we eat the crash to not falsely trigger X-Planes crash reporter and pollute the X-Plane crash database. This example also writes a minidump file on Windows and a simple backtrace on macOS and Linux. Production code might want to integrate more sophisticated crash handling
#include "XPLMDataAccess.h"
#include "XPLMMenus.h"
#include "XPLMProcessing.h"
#include "XPLMPlugin.h"
// This option enables support for multi threaded crash handling.
// It requires C++11 or newer
#define SUPPORT_BACKGROUND_THREADS 0
#include <cstring>
#include <cstdlib>
#if SUPPORT_BACKGROUND_THREADS
#include <atomic>
#include <set>
#include <thread>
#include <mutex>
#endif
#if APL || LIN
#include <signal.h>
#include <execinfo.h>
#include <fcntl.h>
#include <unistd.h>
#endif
/*
* This plugin demonstrates how to intercept and handle crashes in a way that plays nicely with the X-Plane crash reporter.
* The core idea is to intercept all crashes by installing appropriate crash handlers and then filtering out crashes that weren't caused
* by our plugin. To do so, we track both the threads created by us at runtime, as well as checking whether our plugin is active when we crash on the main thread.
* If the crash wasn't caused by us, the crash is forwarded to the next crash handler (potentially X-Plane, or another plugin) which then gets a chance
* to process the crash.
* If the crash is caused by us, we eat the crash to not falsely trigger X-Planes crash reporter and pollute the X-Plane crash database. This example
* also writes a minidump file on Windows and a simple backtrace on macOS and Linux. Production code might want to integrate more sophisticated crash handling
*/
#if SUPPORT_BACKGROUND_THREADS
static std::thread::id s_main_thread;
static std::atomic_flag s_thread_lock;
static std::set<std::thread::id> s_known_threads;
#endif
static XPLMPluginID s_my_plugin_id;
// Function called when we detect a crash that was caused by us
void handle_crash(void *context);
#if APL || LIN
static struct sigaction s_prev_sigsegv = {};
static struct sigaction s_prev_sigabrt = {};
static struct sigaction s_prev_sigfpe = {};
static struct sigaction s_prev_sigint = {};
static struct sigaction s_prev_sigill = {};
static struct sigaction s_prev_sigterm = {};
static void handle_posix_sig(int sig, siginfo_t *siginfo, void *context);
#endif
#if IBM
static LPTOP_LEVEL_EXCEPTION_FILTER s_previous_windows_exception_handler;
LONG WINAPI handle_windows_exception(EXCEPTION_POINTERS *ei);
#endif
#if SUPPORT_BACKGROUND_THREADS
// Registers the calling thread with the crash handler. We use this to figure out if a crashed thread belongs to us when we later try to figure out if we caused a crash
void register_thread_for_crash_handler()
{
while(s_thread_lock.test_and_set(std::memory_order_acquire))
{}
s_known_threads.insert(std::this_thread::get_id());
s_thread_lock.clear(std::memory_order_release);
}
// Unregisters the calling thread from the crash handler. MUST be called at the end of thread that was registered via register_thread_for_crash_handler()
void unregister_thread_from_crash_handler()
{
while(s_thread_lock.test_and_set(std::memory_order_acquire))
{}
s_known_threads.erase(std::this_thread::get_id());
s_thread_lock.clear(std::memory_order_release);
}
#endif
// Registers the global crash handler. Should be called from XPluginStart
void register_crash_handler()
{
#if SUPPORT_BACKGROUND_THREADS
s_main_thread = std::this_thread::get_id();
#endif
s_my_plugin_id = XPLMGetMyID();
#if APL || LIN
struct sigaction sig_action = { .sa_sigaction = handle_posix_sig };
sigemptyset(&sig_action.sa_mask);
#if LIN
static uint8_t alternate_stack[SIGSTKSZ];
stack_t ss = {
.ss_sp = (void*)alternate_stack,
.ss_size = SIGSTKSZ,
.ss_flags = 0
};
sigaltstack(&ss, NULL);
sig_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
#else
sig_action.sa_flags = SA_SIGINFO;
#endif
sigaction(SIGSEGV, &sig_action, &s_prev_sigsegv);
sigaction(SIGABRT, &sig_action, &s_prev_sigabrt);
sigaction(SIGFPE, &sig_action, &s_prev_sigfpe);
sigaction(SIGINT, &sig_action, &s_prev_sigint);
sigaction(SIGILL, &sig_action, &s_prev_sigill);
sigaction(SIGTERM, &sig_action, &s_prev_sigterm);
#endif
#if IBM
// Load the debug helper library into the process already, this way we don't have to hit the dynamic loader
// in an exception context where it's potentially unsafe to do so.
HMODULE module = ::GetModuleHandleA("dbghelp.dll");
if(!module)
module = ::LoadLibraryA("dbghelp.dll");
(void)module;
s_previous_windows_exception_handler = SetUnhandledExceptionFilter(handle_windows_exception);
#endif
}
// Unregisters the global crash handler. You need to call this in XPluginStop so we can clean up after ourselves
void unregister_crash_handler()
{
#if APL || LIN
sigaction(SIGSEGV, &s_prev_sigsegv, NULL);
sigaction(SIGABRT, &s_prev_sigabrt, NULL);
sigaction(SIGFPE, &s_prev_sigfpe, NULL);
sigaction(SIGINT, &s_prev_sigint, NULL);
sigaction(SIGILL, &s_prev_sigill, NULL);
sigaction(SIGTERM, &s_prev_sigterm, NULL);
#endif
#if IBM
SetUnhandledExceptionFilter(s_previous_windows_exception_handler);
#endif
}
#if SUPPORT_BACKGROUND_THREADS
// A RAII helper class to register and unregister threads to participate in crash detection
class StThreadCrashCookie
{
public:
StThreadCrashCookie()
{
register_thread_for_crash_handler();
}
~StThreadCrashCookie()
{
unregister_thread_from_crash_handler();
}
};
#endif
// Predicates that returns true if a thread is caused by us
// The main idea is to check the plugin ID if we are on the main thread,
// if not, we check if the current thread is known to be from us.
// Returns false if the crash was caused by code that didn't come from our plugin
bool is_us_executing()
{
#if SUPPORT_BACKGROUND_THREADS
const std::thread::id thread_id = std::this_thread::get_id();
if(thread_id == s_main_thread)
{
// Check if the plugin executing is our plugin.
// XPLMGetMyID() will return the ID of the currently executing plugin. If this is us, then it will return the plugin ID that we have previously stashed away
return (s_my_plugin_id == XPLMGetMyID());
}
if(s_thread_lock.test_and_set(std::memory_order_acquire))
{
// We couldn't acquire our lock. In this case it's better if we just say it's not us so we don't eat the exception
return false;
}
const bool is_our_thread = (s_known_threads.find(thread_id) != s_known_threads.end());
s_thread_lock.clear(std::memory_order_release);
return is_our_thread;
#else
return (s_my_plugin_id == XPLMGetMyID());
#endif
}
#if APL || LIN
static void handle_posix_sig(int sig, siginfo_t *siginfo, void *context)
{
if(is_us_executing())
{
static bool has_called_out = false;
if(!has_called_out)
{
has_called_out = true;
handle_crash((void *)sig);
}
abort();
}
// Forward the signal to the other handlers
#define FORWARD_SIGNAL(sigact) \
do { \
if((sigact)->sa_sigaction && ((sigact)->sa_flags & SA_SIGINFO)) \
(sigact)->sa_sigaction(sig, siginfo, context); \
else if((sigact)->sa_handler) \
(sigact)->sa_handler(sig); \
} while (0)
switch(sig)
{
case SIGSEGV:
FORWARD_SIGNAL(&s_prev_sigsegv);
break;
case SIGABRT:
FORWARD_SIGNAL(&s_prev_sigabrt);
break;
case SIGFPE:
FORWARD_SIGNAL(&s_prev_sigfpe);
break;
case SIGILL:
FORWARD_SIGNAL(&s_prev_sigill);
break;
case SIGTERM:
FORWARD_SIGNAL(&s_prev_sigterm);
break;
}
#undef FORWARD_SIGNAL
abort();
}
#endif
#if IBM
LONG WINAPI handle_windows_exception(EXCEPTION_POINTERS *ei)
{
if(is_us_executing())
{
handle_crash(ei);
return EXCEPTION_CONTINUE_SEARCH;
}
if(s_previous_windows_exception_handler)
return s_previous_windows_exception_handler(ei);
return EXCEPTION_CONTINUE_SEARCH;
}
#endif
// Runtime
#if IBM
void write_mini_dump(PEXCEPTION_POINTERS exception_pointers);
#endif
void handle_crash(void *context)
{
#if APL || LIN
// NOTE: This is definitely NOT production code
// backtrace and backtrace_symbols are NOT signal handler safe and are just put in here for demonstration purposes
// A better alternative would be to use something like libunwind here
void *frames[64];
int frame_count = backtrace(frames, 64);
char **names = backtrace_symbols(frames, frame_count);
const int fd = open("backtrace.txt", O_CREAT | O_RDWR | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if(fd >= 0)
{
for(int i = 0; i < frame_count; ++ i)
{
write(fd, names[i], strlen(names[i]));
write(fd, "\n", 1);
}
close(fd);
}
#endif
#if IBM
// Create a mini-dump file that can be later opened up in Visual Studio or WinDbg to do post mortem debugging
write_mini_dump((PEXCEPTION_POINTERS)context);
#endif
}
#if IBM
#include <DbgHelp.h>
typedef BOOL(WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile,
MINIDUMP_TYPE DumpType,
CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
void write_mini_dump(PEXCEPTION_POINTERS exception_pointers)
{
HMODULE module = ::GetModuleHandleA("dbghelp.dll");
if(!module)
module = ::LoadLibraryA("dbghelp.dll");
if(module)
{
const MINIDUMPWRITEDUMP pDump = MINIDUMPWRITEDUMP(::GetProcAddress(module, "MiniDumpWriteDump"));
if(pDump)
{
// Create dump file
const HANDLE handle = ::CreateFileA("crash_dump.dmp", GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if(handle != INVALID_HANDLE_VALUE)
{
MINIDUMP_EXCEPTION_INFORMATION exception_information = {};
exception_information.ThreadId = ::GetCurrentThreadId();
exception_information.ExceptionPointers = exception_pointers;
exception_information.ClientPointers = false;
pDump(GetCurrentProcess(), GetCurrentProcessId(), handle, MiniDumpNormal, &exception_information, nullptr, nullptr);
::CloseHandle(handle);
}
}
}
}
#endif
#if SUPPORT_BACKGROUND_THREADS
static std::thread s_background_thread;
static std::atomic<bool> s_background_thread_shutdown(false);
static std::atomic<bool> s_background_thread_want_crash(false);
void background_thread_func()
{
StThreadCrashCookie thread_cookie; // Make sure our thread is registered with the crash handling system
while(!s_background_thread_shutdown.load(std::memory_order_acquire))
{
if(s_background_thread_want_crash.load(std::memory_order_acquire))
{
s_background_thread_want_crash = false;
int *death_is_a_destination = NULL;
*death_is_a_destination = 1;
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
#endif
// User Interface
#define MENU_ITEM_CRASH_MAIN_THREAD (void *)0x1
#define MENU_ITEM_CRASH_BACKGROUND_THREAD (void *)0x2
void menu_handler(void *inMenuRef, void *inItemRef)
{
if(inItemRef == MENU_ITEM_CRASH_MAIN_THREAD)
{
int *death_is_a_destination = NULL;
*death_is_a_destination = 1;
}
#if SUPPORT_BACKGROUND_THREADS
else if(inItemRef == MENU_ITEM_CRASH_BACKGROUND_THREAD)
{
s_background_thread_want_crash = true;
}
#endif
}
// Plugin functions
PLUGIN_API int XPluginStart(char* name, char* sig, char* desc)
{
strcpy(name, "Crash Handling example");
strcpy(sig, "com.laminarresearch.example.crash-handling");
strcpy(desc, "Example plugin for crash handling");
register_crash_handler();
#if SUPPORT_BACKGROUND_THREADS
s_background_thread = std::thread(&background_thread_func);
#endif
const int plugin_menu_item = XPLMAppendMenuItem(XPLMFindPluginsMenu(), "Crash Handler", NULL, 0);
const XPLMMenuID menu = XPLMCreateMenu("Crash Handler", XPLMFindPluginsMenu(), plugin_menu_item, menu_handler, NULL);
XPLMAppendMenuItem(menu, "Crash main thread", MENU_ITEM_CRASH_MAIN_THREAD, 0);
#if SUPPORT_BACKGROUND_THREADS
XPLMAppendMenuItem(menu, "Crash background thread", MENU_ITEM_CRASH_BACKGROUND_THREAD, 0);
#endif
return 1;
}
PLUGIN_API void XPluginStop(void)
{
#if SUPPORT_BACKGROUND_THREADS
// Tell the background thread to shutdown and then wait for it
s_background_thread_shutdown.store(true, std::memory_order_release);
s_background_thread.join();
#endif
unregister_crash_handler();
}
PLUGIN_API int XPluginEnable(void)
{
return 1;
}
PLUGIN_API void XPluginDisable(void)
{
}
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID from, int msg, void* param)
{}
This code example demonstrates a way to provide traffic information about airplanes to X-Plane and third-party traffic consumers like aircraft instruments or an EFB connected to ADSB-out.
A provider of traffic would use XPLMInstance to place 3d object of aircraft in the world, and then use TCAS override to tell the user about the other airplanes in this airspace. A maximum of 64 planes, so one user and 63 targets, can be supplied at any time, regardless of the user’s setting for AI plane count.
#include "XPLMProcessing.h"
#include "XPLMDataAccess.h"
#include "XPLMPlanes.h"
#include "XPLMUtilities.h"
#include "XPLMPlugin.h"
#include <cassert>
#include <cstring>
/* This plugin creates four traffic targets that will fly circles around the users' plane. These traffic
* targets exist purely as TCAS targets, not as 3D objects, as such would usually be placed by XPLMInstance
*/
// for convenience, I'm going to define the altitude in feet and the distance in nm and convert later
static const float fttomtr = 0.3048f;
static const float nmtomtr = 1852.f;
// how many targets this plugin generates.
static const std::size_t TARGETS = 4;
// datarefs we are going to write to
static XPLMDataRef id = NULL;
static XPLMDataRef flt_id = NULL;
static XPLMDataRef brg = NULL;
static XPLMDataRef dis = NULL;
static XPLMDataRef alt = NULL;
static XPLMDataRef override = NULL;
// datarefs for our own plane
static XPLMDataRef psi = NULL;
static XPLMDataRef ele = NULL;
// whether our plugin is in charge
static bool plugin_owns_tcas = false;
static int ids[TARGETS] = { 0XA51B64, 0XAB90C2, 0XADCB98, 0XA08DB8 }; // Required: unique ID for the target. Must be 24bit number.
static char tailnum[TARGETS][8] = {"N428X", "N844X", "N98825", "N1349Z"}; // Optional: Flight ID is item 7 of the ICAO flightplan. So it can be the tailnumber OR the flightnumber! Cannot be longer than 7 chars+nullbyte!
// the initial position of our four targets. We'll place them directly north, east, etc. of us
// at various altitudes and distances between 3 and 6 nautical miles
static float absbrgs[] = { 0, 90, 180, 270 };
static float absalts[] = { 1000, 1500, 2000, 4000 };
static float dists[] = { 6*nmtomtr, 5*nmtomtr, 4*nmtomtr, 3*nmtomtr };
// this flightloop callback will be called every frame to update the targets
float floop_cb(float elapsed1, float elapsed2, int ctr, void* refcon)
{
static float relbrgs[TARGETS];
static float relalts[TARGETS];
// make some targets change altitude
absalts[0] += 400 * elapsed1 / 60.f; // target is climbing 400fpm
absalts[3] -= 600 * elapsed1 / 60.f; // target descending 600fpm
for (int i = 0; i < TARGETS; ++i)
{
// targets are just flying perfect circles around the user.
absbrgs[i] += (i+1) * elapsed1; // this just makes the targets fly circles of varying speed
relbrgs[i] = absbrgs[i] - XPLMGetDataf(psi); // convert to relative position for TCAS dataref. Use true_psi, not hpath or something else
relalts[i] = absalts[i] * fttomtr - XPLMGetDatad(ele); // convert to relative position for TCAS dataref. Use elevation, not local_y!
}
// if we are in charge, we can write four targets to the four TCAS datarefs, starting at index 1
// Note this dataref write would do nothing if we hadn't acquired the planes and set override_TCAS
if (plugin_owns_tcas)
{
// These relative coordinates, or the absolute x/y/z double coordinates must be updated to keep the target flying, obviously.
// X-Plane will forget about your target if you don't update it for 10 consecutive frames.
XPLMSetDatavf(brg, relbrgs, 1, TARGETS);
XPLMSetDatavf(dis, dists, 1, TARGETS);
XPLMSetDatavf(alt, relalts, 1, TARGETS);
// You could also update sim/cockpit2/tcas/targets/position/double/plane1_x, plane1_y, etc.. In which case X-Plane would update the relative bearings for you
// So for one target, you can write either absolute coorindates or relative bearings, but not both!
// For mulitple targets, you can update some targets in relative mode, and others in absolute mode.
}
// be sure to be called every frame. A target not updated for 10 successive frames will be dropped.
return -1;
}
// A simple reset we will call every minute to reset the targets to their initial position and altitude
float reset_cb(float elapsed1, float elapsed2, int ctr, void* refcon)
{
absbrgs[0] = 0;
absbrgs[1] = 90;
absbrgs[2] = 180;
absbrgs[3] = 270;
absalts[0] = 1000;
absalts[3] = 4000;
return 60; // call me again in a minute
}
// we call this function when we succesfully acquired the AI planes.
// Arthur says: BRILLIANT! My Jets Now!
void my_planes_now()
{
XPLMDebugString("TCAS test plugin now has the AI planes!\n");
XPLMSetDatai(override, 1); // If you try to set this dataref when not owning the planes, it will fail!
// query the array size. This might change with X-Plane updates.
std::size_t max_targets = XPLMGetDatavi(id, NULL, 0, 0);
assert(TARGETS < max_targets);
XPLMSetActiveAircraftCount(TARGETS); // This will give you four targets, even if the user's AI plane count is set to 0. This can be as high as 63!
plugin_owns_tcas = true;
// As long as you keep updating the positions, X-Plane will remember the ID.
// These IDs can be used by other plugins to keep track of your aircraft if you shuffle slots.
// Note that the ID cannot be left 0! X-Plane will not update your target's dependent datarefs if it has no ID!!
// If you haven't updated a target for 10 frames, X-Plane will forget it and reset the ID of the slot to 0.
XPLMSetDatavi(id, ids, 1, TARGETS);
// Each target can have a 7 ASCII character flight ID, usually the tailnumber or flightnumber
// it consists of an 8 byte character array, which is null terminated.
// The array is 64*8 bytes long, and the first 8 bytes are the user's tailnumber obviously.
// Note that this is, unlike the Mode-S ID, totally optional.
// But it is nice to see the tainumber on the map obviously!
for (int i = 1; i <= TARGETS; ++i)
XPLMSetDatab(flt_id, tailnum[i - 1], i * 8, strnlen(tailnum[i-1], 8)); // copy at most 8 characters, but not more than we actually have.
// start updating
XPLMRegisterFlightLoopCallback(floop_cb, 1, NULL);
XPLMRegisterFlightLoopCallback(reset_cb, 60, NULL);
}
// we call this function when we want to give up on controlling the AI planes
// For example, a network plugin would do this as soon as you disconnect from your multiplayer session!
void someone_elses_planes_now()
{
// stop updating
XPLMUnregisterFlightLoopCallback(floop_cb, NULL);
XPLMUnregisterFlightLoopCallback(reset_cb, NULL);
// relinquish control
plugin_owns_tcas = false;
XPLMSetDatai(override, 0); // order is important! Relinquish the override first
XPLMReleasePlanes(); // Then release the AI planes to another plugin! Note that another plugins AcquirePlanes callback function might be called here synchronously
}
// this is a callback that will be called by X-Plane, if we asked for planes, but another plugin had the planes at the time
// but now this other plugin has given up the planes. They essentially yielded control to us. So the planes are up for grabs again!!
void retry_acquiring_planes(void*)
{
if (!XPLMAcquirePlanes(NULL, &retry_acquiring_planes, NULL))
{
// Damn, someone else cut in the queue before us!
// this can happen if more than two plugins are all competing for AI.
XPLMDebugString("TCAS test plugin could not get the AI planes, even after the previous plugin gave them up. We are waiting for the next chance\n");
}
else
{
// Acquisition succeded.
my_planes_now();
}
}
PLUGIN_API int XPluginStart(char* name, char* sig, char* desc)
{
strcpy(name, "TCAS override test");
strcpy(sig, "com.laminarresearch.test.tcas");
strcpy(desc, "Test plugin for TCAS override datarefs");
return 1;
}
PLUGIN_API void XPluginStop(void)
{
}
PLUGIN_API int XPluginEnable(void)
{
psi = XPLMFindDataRef("sim/flightmodel/position/true_psi");
ele = XPLMFindDataRef("sim/flightmodel/position/elevation");
// these datarefs were read-only until 11.50
brg = XPLMFindDataRef("sim/cockpit2/tcas/indicators/relative_bearing_degs");
dis = XPLMFindDataRef("sim/cockpit2/tcas/indicators/relative_distance_mtrs");
alt = XPLMFindDataRef("sim/cockpit2/tcas/indicators/relative_altitude_mtrs");
// these datarefs are new to 11.50
id = XPLMFindDataRef("sim/cockpit2/tcas/targets/modeS_id"); // array of 64 integers
flt_id = XPLMFindDataRef("sim/cockpit2/tcas/targets/flight_id"); // array of 64*8 bytes
// this dataref can only be set if we own the AI planes!
override = XPLMFindDataRef("sim/operation/override/override_TCAS");
/// STOP
/// DROP AND LISTEN
// In a real application, you would only do the next step if you are immediately ready to supply traffic.
// I.e. if you are connected to a session if you are a multiplayer plugin. Don't preemptively acquire the traffic
// just because you might connect to a session some time later!
// So this piecce of code is probably not going to be in XPluginEnable for you.
// It is going to be wherever you are actually done establishing your traffic source!
// try to acquire planes. If a different plugin has them, this will fail.
// If the other plugin releases them, our callback will be called.
if (!XPLMAcquirePlanes(NULL, &retry_acquiring_planes, NULL))
{
// If acquisition has failed, gather some intelligence on who currently owns the planes
int total, active;
XPLMPluginID controller;
char who[256];
XPLMCountAircraft(&total, &active, &controller);
XPLMGetPluginInfo(controller, who, NULL, NULL, NULL);
XPLMDebugString("TCAS test plugin could not get the AI planes, because ");
XPLMDebugString(who);
XPLMDebugString(" owns the AI planes now. We'll get them when he relinquishes control.\n");
// Note that the retry callback will be called when this other guy gives up the planes.
}
else
{
// Acquisition succeded.
my_planes_now();
}
return 1;
}
PLUGIN_API void XPluginDisable(void)
{
someone_elses_planes_now();
}
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID from, int msg, void* param)
{
if (msg == XPLM_MSG_RELEASE_PLANES)
{
// Some other plugin wants the AI planes. Since this is just a dummy traffic provider, we yield
// to a real traffic provider. Deactivate myself now!
// If this was send to a VATSIM plugin while the user is connected, of course it would just ignore this message.
someone_elses_planes_now();
char name[256];
XPLMGetPluginInfo(from, name, NULL, NULL, NULL);
XPLMDebugString("TCAS test plugin has given up control of the AI planes to ");
XPLMDebugString(name);
XPLMDebugString("\n");
}
}
This example draws a 2-d label over the center of gravity of the user’s aircraft. It demonstrates how to implement 2-d OpenGL drawing over the screen (using a drawing callback) that matches locations in the 3-d world without using 3-d drawing callbacks. This technique works with OpenGL, Vulkan, or Metal drivers for X-Plane but requires X-Plane 11.50 or newer for datarefs.
We sometimes call this technique “coach marks” because it involves drawing markings over the 3-d world that match the 3-d world in location but do not interact with the depth buffer.
#include "XPLMDisplay.h"
#include "XPLMGraphics.h"
#include "XPLMDefs.h"
#include "XPLMUtilities.h"
#include "XPLMDataAccess.h"
#include <string.h>
/* This example plugin demonstrates how to use a 2-d drawing callback to draw
* to the screen in a way that matches the 3-d coordinate system. Add-ons that
* need to add 3-d labels, coach marks, or other non-3d graphics that "match"
* the real world can use this technique to draw on with Metal and Vulkan. */
// Datarefs for the aircraft position.
static XPLMDataRef s_pos_x = NULL;
static XPLMDataRef s_pos_y = NULL;
static XPLMDataRef s_pos_z = NULL;
// Transform matrices - we will use these to figure out where we shuold should have drawn.
static XPLMDataRef s_matrix_wrl = NULL;
static XPLMDataRef s_matrix_proj = NULL;
static XPLMDataRef s_screen_width = NULL;
static XPLMDataRef s_screen_height = NULL;
// 4x4 matrix transform of an XYZW coordinate - this matches OpenGL matrix conventions.
static void mult_matrix_vec(float dst[4], const float m[16], const float v[4])
{
dst[0] = v[0] * m[0] + v[1] * m[4] + v[2] * m[8] + v[3] * m[12];
dst[1] = v[0] * m[1] + v[1] * m[5] + v[2] * m[9] + v[3] * m[13];
dst[2] = v[0] * m[2] + v[1] * m[6] + v[2] * m[10] + v[3] * m[14];
dst[3] = v[0] * m[3] + v[1] * m[7] + v[2] * m[11] + v[3] * m[15];
}
// This drawing callback will draw a label to the screen where the
static int DrawCallback1(XPLMDrawingPhase inPhase, int inIsBefore, void * inRefcon)
{
// Read the ACF's OpengL coordinates
float acf_wrl[4] = {
XPLMGetDataf(s_pos_x),
XPLMGetDataf(s_pos_y),
XPLMGetDataf(s_pos_z),
1.0f };
float mv[16], proj[16];
// Read the model view and projection matrices from this frame
XPLMGetDatavf(s_matrix_wrl,mv,0,16);
XPLMGetDatavf(s_matrix_proj,proj,0,16);
float acf_eye[4], acf_ndc[4];
// Simulate the OpenGL transformation to get screen coordinates.
mult_matrix_vec(acf_eye, mv, acf_wrl);
mult_matrix_vec(acf_ndc, proj, acf_eye);
acf_ndc[3] = 1.0f / acf_ndc[3];
acf_ndc[0] *= acf_ndc[3];
acf_ndc[1] *= acf_ndc[3];
acf_ndc[2] *= acf_ndc[3];
float screen_w = XPLMGetDatai(s_screen_width);
float screen_h = XPLMGetDatai(s_screen_height);
float final_x = screen_w * (acf_ndc[0] * 0.5f + 0.5f);
float final_y = screen_h * (acf_ndc[1] * 0.5f + 0.5f);
// Now we have something in screen coordinates, which we can then draw a label on.
XPLMDrawTranslucentDarkBox(final_x - 5, final_y + 10, final_x + 100, final_y - 10);
float colWHT[] = { 1.0, 1.0, 1.0 };
XPLMDrawString(colWHT, final_x, final_y, "TEST STRING 1", NULL, xplmFont_Basic);
return 1;
}
PLUGIN_API int XPluginStart(char * outName, char * outSig, char * outDesc)
{
strcpy(outName,"Example label drawing");
strcpy(outSig,"com.laminar.example_label_drawing");
strcpy(outDesc,"A plugin that shows how to draw a 3-d-referenced label in 2-d");
XPLMRegisterDrawCallback(DrawCallback1, xplm_Phase_Window, 0, NULL);
s_pos_x = XPLMFindDataRef("sim/flightmodel/position/local_x");
s_pos_y = XPLMFindDataRef("sim/flightmodel/position/local_y");
s_pos_z = XPLMFindDataRef("sim/flightmodel/position/local_z");
// These datarefs are valid to read from a 2-d drawing callback and describe the state
// of the underlying 3-d drawing environment the 2-d drawing is layered on top of.
s_matrix_wrl = XPLMFindDataRef("sim/graphics/view/world_matrix");
s_matrix_proj = XPLMFindDataRef("sim/graphics/view/projection_matrix_3d");
// This describes the size of the current monitor at the time we draw.
s_screen_width = XPLMFindDataRef("sim/graphics/view/window_width");
s_screen_height = XPLMFindDataRef("sim/graphics/view/window_height");
return 1;
}
PLUGIN_API int XPluginEnable()
{
return 1;
}
PLUGIN_API void XPluginStop()
{
}
PLUGIN_API void XPluginDisable()
{
}
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMessage, void * inParam )
{
}
The following sample code, modeled after the X‑Plane 11 Window API sample, will create a “modern”-style window. Note that as of X‑Plane 11.20, control over windows in VR is quite limited—you can move a window in and out of VR and change the window’s size, but that’s about it.
Note that some of the APIs used here are new to X-Plane 11.20b5—if you get a runtime error, ensure you’re on the latest release.
This sample is based on the XPLM300 window sample. It demonstrates the use of the modelview, viewport, and projection matrix datarefs (new to X-Plane 11.10) for applying scissoring to new-style plugin-created windows.
If you build the plugin, but comment out the call the glScissor(), you’ll see the fill pattern goes all the way to the edge of the green rectangle.
#include "XPLMDisplay.h"
#include "XPLMGraphics.h"
#include <string.h>
#if IBM
#include <windows.h>
#endif
#if LIN
#include <GL/gl.h>
#elif __GNUC__
#include <OpenGL/gl.h>
#else
#include <GL/gl.h>
#endif
#ifndef XPLM300
#error This is made to be compiled against the XPLM300 SDK
#endif
// An opaque handle to the window we will create
static XPLMWindowID g_window;
// Callbacks we will register when we create our window
void draw_hello_world(XPLMWindowID in_window_id, void * in_refcon);
int dummy_mouse_handler(XPLMWindowID in_window_id, int x, int y, int is_down, void * in_refcon) { return 0; }
XPLMCursorStatus dummy_cursor_status_handler(XPLMWindowID in_window_id, int x, int y, void * in_refcon) { return xplm_CursorDefault; }
int dummy_wheel_handler(XPLMWindowID in_window_id, int x, int y, int wheel, int clicks, void * in_refcon) { return 0; }
void dummy_key_handler(XPLMWindowID in_window_id, char key, XPLMKeyFlags flags, char virtual_key, void * in_refcon, int losing_focus) { }
PLUGIN_API int XPluginStart(
char * outName,
char * outSig,
char * outDesc)
{
strcpy(outName, "HelloWorld3Plugin");
strcpy(outSig, "xpsdk.examples.helloworld3plugin");
strcpy(outDesc, "A Hello World plug-in for the XPLM300 SDK.");
XPLMCreateWindow_t params;
params.structSize = sizeof(params);
params.visible = 1;
params.drawWindowFunc = draw_hello_world;
// Note on "dummy" handlers:
// Even if we don't want to handle these events, we have to register a "do-nothing" callback for them
params.handleMouseClickFunc = dummy_mouse_handler;
params.handleRightClickFunc = dummy_mouse_handler;
params.handleMouseWheelFunc = dummy_wheel_handler;
params.handleKeyFunc = dummy_key_handler;
params.handleCursorFunc = dummy_cursor_status_handler;
params.refcon = NULL;
params.layer = xplm_WindowLayerFloatingWindows;
// Opt-in to styling our window like an X-Plane 11 native window
// If you're on XPLM300, not XPLM301, swap this enum for the literal value 1.
params.decorateAsFloatingWindow = xplm_WindowDecorationRoundRectangle;
// Set the window's initial bounds
// Note that we're not guaranteed that the main monitor's lower left is at (0, 0)...
// We'll need to query for the global desktop bounds!
int left, bottom, right, top;
XPLMGetScreenBoundsGlobal(&left, &top, &right, &bottom);
params.left = left + 50;
params.bottom = bottom + 150;
params.right = params.left + 200;
params.top = params.bottom + 200;
g_window = XPLMCreateWindowEx(¶ms);
// Position the window as a "free" floating window, which the user can drag around
XPLMSetWindowPositioningMode(g_window, xplm_WindowPositionFree, -1);
// Limit resizing our window: maintain a minimum width/height of 100 boxels and a max width/height of 300 boxels
XPLMSetWindowResizingLimits(g_window, 200, 200, 300, 300);
XPLMSetWindowTitle(g_window, "Sample Window");
return g_window != NULL;
}
PLUGIN_API void XPluginStop(void)
{
// Since we created the window, we'll be good citizens and clean it up
XPLMDestroyWindow(g_window);
g_window = NULL;
}
PLUGIN_API void XPluginDisable(void) { }
PLUGIN_API int XPluginEnable(void) { return 1; }
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void * inParam) { }
void draw_hello_world(XPLMWindowID in_window_id, void * in_refcon)
{
// Mandatory: We *must* set the OpenGL state before drawing
// (we can't make any assumptions about it)
XPLMSetGraphicsState(
0 /* no fog */,
0 /* 0 texture units */,
0 /* no lighting */,
0 /* no alpha testing */,
1 /* do alpha blend */,
1 /* do depth testing */,
0 /* no depth writing */
);
int l, t, r, b;
XPLMGetWindowGeometry(in_window_id, &l, &t, &r, &b);
float col_white[] = {1.0, 1.0, 1.0}; // red, green, blue
XPLMDrawString(col_white, l + 10, t - 20, "Hello world!", NULL, xplmFont_Proportional);
}
This is a compatibility wrapper for using the new XPLMInstance APIs in plugins that support old versions of X-Plane. It consists of 2 files (a .h and a .cpp)
xplm_instance.h
/**
* This is a backward-compatibility wrapper for the new XPLM300
* instancing APIs.
*
* It can be used to provide compatibility with plugins that support
* both X-Plane 10 and 11. X-Plane 10 plugins will not see the performance
* benefit, of course, but this saves you from needing two separate
* implementations.
*/
#ifndef xplm_instance_h
#define xplm_instance_h
#include <XPLMScenery.h>
typedef void * XPLMInstanceRef;
XPLMInstanceRefXPLMCreateInstance(XPLMObjectRef obj, const char * drefs[]);
void XPLMDestroyInstance(XPLMInstanceRef inst);
// Data is consecutive floats, one for each dataref
void XPLMInstanceSetPosition(
XPLMInstanceRef inst,
const XPLMDrawInfo_t * position,
const float * data);
// These are boiler plate needed only for the 'lib' implementation of the above APIs.
void XPLMInstanceInit();
void XPLMInstanceCleanup();
#endif /* xplm_instance_h */
This sample shows off the new instancing API, used for efficiently drawing objects without needing to write OpenGL.
Note: We’ve created a compatibility wrapper for X-Plane 10 plugins that want to migrate to the new instancing API. This is useful if your plugin supports both X-Plane 10 and 11, and you want to use the same code for both—just set up the compatibility wrapper for the X-Plane 10 version only, and you’ll get the performance benefits of the new API in your X-Plane 11 version.
#include "XPLMMenus.h"
#include <string.h>
#if IBM
#include <windows.h>
#endif
#ifndef XPLM300
#error This is made to be compiled against the XPLM300 SDK
#endif
int g_menu_container_idx; // The index of our menu item in the Plugins menu
XPLMMenuID g_menu_id; // The menu container we'll append all our menu items to
void menu_handler(void *, void *);
PLUGIN_API int XPluginStart(
char * outName,
char * outSig,
char * outDesc)
{
strcpy(outName, "MenuPlugin");
strcpy(outSig, "xpsdk.examples.menuplugin");
strcpy(outDesc, "A sample plug-in that demonstrates and exercises the X-Plane menu API.");
g_menu_container_idx = XPLMAppendMenuItem(XPLMFindPluginsMenu(), "Sample Menu", 0, 0);
g_menu_id = XPLMCreateMenu("Sample Menu", XPLMFindPluginsMenu(), g_menu_container_idx, menu_handler, NULL);
XPLMAppendMenuItem(g_menu_id, "Toggle Settings", (void *)"Menu Item 1", 1);
XPLMAppendMenuSeparator(g_menu_id);
XPLMAppendMenuItem(g_menu_id, "Toggle Shortcuts", (void *)"Menu Item 2", 1);
XPLMAppendMenuItemWithCommand(g_menu_id, "Toggle Flight Configuration (Command-Based)", XPLMFindCommand("sim/operation/toggle_flight_config"));
// Changed your mind? You can destroy the submenu you created with XPLMDestroyMenu(),
// then remove the "Sample Menu" item from the "Plugins" menu with XPLMRemoveMenuItem().
//XPLMDestroyMenu(g_menu_id);
//XPLMRemoveMenuItem(XPLMFindPluginsMenu(), g_menu_container_idx);
XPLMMenuID aircraft_menu = XPLMFindAircraftMenu();
if(aircraft_menu) // This will be NULL unless this plugin was loaded with an aircraft (i.e., it was located in the current aircraft's "plugins" subdirectory)
{
XPLMAppendMenuItemWithCommand(aircraft_menu, "Toggle Settings (Command-Based)", XPLMFindCommand("sim/operation/toggle_settings_window"));
}
return 1;
}
PLUGIN_API void XPluginStop(void)
{
// Since we created this menu, we'll be good citizens and clean it up as well
XPLMDestroyMenu(g_menu_id);
// If we were able to add a command to the aircraft menu, it will be automatically removed for us when we're unloaded
}
PLUGIN_API void XPluginDisable(void) { }
PLUGIN_API int XPluginEnable(void) { return 1; }
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void * inParam) { }
void menu_handler(void * in_menu_ref, void * in_item_ref)
{
if(!strcmp((const char *)in_item_ref, "Menu Item 1"))
{
XPLMCommandOnce(XPLMFindCommand("sim/operation/toggle_settings_window"));
}
else if(!strcmp((const char *)in_item_ref, "Menu Item 2"))
{
XPLMCommandOnce(XPLMFindCommand("sim/operation/toggle_key_shortcuts_window"));
}
}
#include "XPLMDisplay.h"
#include "XPLMGraphics.h"
#include <string.h>
#include <stdio.h>
#if IBM
#include <windows.h>
#endif
#if LIN
#include <GL/gl.h>
#elif __GNUC__
#include <OpenGL/gl.h>
#else
#include <GL/gl.h>
#endif
#ifndef XPLM300
#error This is made to be compiled against the XPLM300 SDK
#endif
static XPLMWindowID g_window;
void draw(XPLMWindowID in_window_id, void * in_refcon);
int dummy_mouse_handler(XPLMWindowID in_window_id, int x, int y, int is_down, void * in_refcon) { return 0; }
XPLMCursorStatus dummy_cursor_status_handler(XPLMWindowID in_window_id, int x, int y, void * in_refcon) { return xplm_CursorDefault; }
int dummy_wheel_handler(XPLMWindowID in_window_id, int x, int y, int wheel, int clicks, void * in_refcon) { return 0; }
void dummy_key_handler(XPLMWindowID in_window_id, char key, XPLMKeyFlags flags, char virtual_key, void * in_refcon, int losing_focus) { }
PLUGIN_API int XPluginStart(
char * outName,
char * outSig,
char * outDesc)
{
strcpy(outName, "GravitySamplePlugin");
strcpy(outSig, "xpsdk.examples.gravitysampleplugin");
strcpy(outDesc, "A test plug-in that demonstrates the gravity features of the X-Plane 11 GUI plugin API.");
// We're not guaranteed that the main monitor's lower left is at (0, 0)... we'll need to query for the global desktop bounds!
int global_desktop_bounds[4]; // left, bottom, right, top
XPLMGetScreenBoundsGlobal(&global_desktop_bounds[0], &global_desktop_bounds[3], &global_desktop_bounds[2], &global_desktop_bounds[1]);
XPLMCreateWindow_t params;
params.structSize = sizeof(params);
// Set the window bounds such that we stretch the full *width* of the global desktop, and cover the top 200 bx
params.left = global_desktop_bounds[0];
params.bottom = global_desktop_bounds[3] - 200;
params.right = global_desktop_bounds[2];
params.top = global_desktop_bounds[3];
params.visible = 1;
params.drawWindowFunc = draw;
params.handleMouseClickFunc = dummy_mouse_handler;
params.handleRightClickFunc = dummy_mouse_handler;
params.handleMouseWheelFunc = dummy_wheel_handler;
params.handleKeyFunc = dummy_key_handler;
params.handleCursorFunc = dummy_cursor_status_handler;
params.refcon = NULL;
params.layer = xplm_WindowLayerFlightOverlay; // stick our window beneath all floating windows (like the X-Plane 11 map)
params.decorateAsFloatingWindow = 0;
g_window = XPLMCreateWindowEx(¶ms);
XPLMSetWindowPositioningMode(g_window, xplm_WindowPositionFree, -1);
// As the X-Plane window resizes, glue our left and right edges to the sides of the screen
// (causing our width to grow and shrink to match the window size), but keep a constant
// height for our window (with the same y position relative to the window's top).
XPLMSetWindowGravity(g_window, 0, 1, 1, 1);
return (g_window != NULL);
}
PLUGIN_API void XPluginStop(void)
{
// Since we created the window, we'll be good citizens and clean it up
XPLMDestroyWindow(g_window);
g_window = NULL;
}
PLUGIN_API void XPluginDisable(void) { }
PLUGIN_API int XPluginEnable(void) { return 1; }
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void * inParam) { }
void draw(XPLMWindowID in_window_id, void * in_refcon)
{
XPLMSetGraphicsState(
0 /* no fog */,
0 /* 0 texture units */,
0 /* no lighting */,
0 /* no alpha testing */,
1 /* do alpha blend */,
1 /* do depth testing */,
0 /* no depth writing */
);
int b[4];
XPLMGetWindowGeometry(in_window_id, &b[0], &b[3], &b[2], &b[1]);
// Draw our window's translucent background overlay
XPLMDrawTranslucentDarkBox(b[0], b[3], b[2], b[1]);
// Display the window bounds (centered within the window)
char scratch_buffer[150];
sprintf(scratch_buffer, "Window bounds: %d %d %d %d", b[0], b[1], b[2], b[3]);
float col_white[] = {1.0, 1.0, 1.0};
int text_width = XPLMMeasureString(xplmFont_Proportional, scratch_buffer, strlen(scratch_buffer));
float text_midpoint_x = (b[2] + b[0]) / 2;
XPLMDrawString(col_white, text_midpoint_x - text_width / 2, (b[3] + b[1]) / 2, scratch_buffer, NULL, xplmFont_Proportional);
}