refs #366 cleanup

* Removed obsolete signal from IContextSimulator
* Removed connectToSimulator() method from IContextSimulator as it is no
longer relevant
* CSimulatorComponent updates automatically when simulator disconnects
* FS9 & FSX drivers update (including FSCommon)
This commit is contained in:
Michał Garapich
2015-03-23 19:56:59 +01:00
committed by Roland Winklmeier
parent 63e48ae332
commit 4d4acb20bc
22 changed files with 68 additions and 122 deletions

View File

@@ -65,10 +65,8 @@ namespace BlackCore
virtual ~IContextSimulator() {} virtual ~IContextSimulator() {}
signals: signals:
//! Simulator started or stopped
void startedChanged(bool startedChanged);
//! Simulator combined status //! Simulator combined status
//! \sa ISimulator::SimulatorStatus
void simulatorStatusChanged(int status); void simulatorStatusChanged(int status);
//! Only a limited number of aircraft displayed //! Only a limited number of aircraft displayed
@@ -84,7 +82,6 @@ namespace BlackCore
void ownAircraftModelChanged(BlackMisc::Simulation::CSimulatedAircraft aircraft); void ownAircraftModelChanged(BlackMisc::Simulation::CSimulatedAircraft aircraft);
public slots: public slots:
//! Return list of available simulator plugins //! Return list of available simulator plugins
virtual BlackSim::CSimulatorPluginInfoList getAvailableSimulatorPlugins() const = 0; virtual BlackSim::CSimulatorPluginInfoList getAvailableSimulatorPlugins() const = 0;
@@ -93,14 +90,9 @@ namespace BlackCore
virtual bool isConnected() const = 0; virtual bool isConnected() const = 0;
//! Can we connect? //! Can we connect?
//! \todo Remove?
virtual bool canConnect() const = 0; virtual bool canConnect() const = 0;
//! Connect to simulator
virtual bool connectToSimulator() = 0;
//! Connect to simulator (asynchronous version)
virtual void asyncConnectToSimulator() = 0;
//! Disconnect from simulator //! Disconnect from simulator
virtual bool disconnectFromSimulator() = 0; virtual bool disconnectFromSimulator() = 0;

View File

@@ -36,9 +36,8 @@ namespace BlackCore
m_mapper(new QSignalMapper(this)) m_mapper(new QSignalMapper(this))
{ {
findSimulatorPlugins(); findSimulatorPlugins();
// Maps listener instance
connect(m_mapper, static_cast<void (QSignalMapper::*)(QObject *)>(&QSignalMapper::mapped), this, &CContextSimulator::ps_simulatorStarted); connect(m_mapper, static_cast<void (QSignalMapper::*)(QObject *)>(&QSignalMapper::mapped), this, &CContextSimulator::ps_simulatorStarted);
// do not load plugin here, as it depends on settings
// it has to be guaranteed the settings are alredy loaded
} }
CContextSimulator::~CContextSimulator() CContextSimulator::~CContextSimulator()
@@ -111,26 +110,6 @@ namespace BlackCore
return m_simulator->simulator->canConnect(); return m_simulator->simulator->canConnect();
} }
bool CContextSimulator::connectToSimulator()
{
if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; }
if (!m_simulator)
return false;
Q_ASSERT(m_simulator->simulator);
return m_simulator->simulator->connectTo();
}
void CContextSimulator::asyncConnectToSimulator()
{
if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; }
if (!m_simulator || m_canConnectResult.isRunning())
return; // already checking
Q_ASSERT(m_simulator->simulator);
m_simulator->simulator->asyncConnectTo();
}
bool CContextSimulator::disconnectFromSimulator() bool CContextSimulator::disconnectFromSimulator()
{ {
if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; }
@@ -210,7 +189,7 @@ namespace BlackCore
return; return;
} }
Q_ASSERT(m_simulator->simulator); Q_ASSERT(m_simulator->simulator);
m_simulator->reloadInstalledModels(); m_simulator->simulator->reloadInstalledModels();
} }
CAircraftIcao CContextSimulator::getIcaoForModelString(const QString &modelString) const CAircraftIcao CContextSimulator::getIcaoForModelString(const QString &modelString) const
@@ -419,10 +398,10 @@ namespace BlackCore
settingsChanged(static_cast<uint>(IContextSettings::SettingsSimulator)); settingsChanged(static_cast<uint>(IContextSettings::SettingsSimulator));
// try to connect // try to connect
asyncConnectToSimulator(); m_simulator->simulator->asyncConnectTo();
// info about what is going on // info about what is going on
CLogMessage(this).info("Simulator plugin loaded: %1") << this->m_simulator->getSimulatorInfo().toQString(true); CLogMessage(this).info("Simulator plugin loaded: %1") << this->m_simulator->info.toQString(true);
return true; return true;
} }
@@ -567,7 +546,7 @@ namespace BlackCore
m_simulator->simulator->removeRemoteAircraft(callsign); m_simulator->simulator->removeRemoteAircraft(callsign);
} }
void CContextSimulator::ps_onConnectionStatusChanged(ISimulator::ConnectionStatus status) void CContextSimulator::ps_onSimulatorStatusChanged(int status)
{ {
Q_ASSERT(m_simulator); Q_ASSERT(m_simulator);
Q_ASSERT(m_simulator->simulator); Q_ASSERT(m_simulator->simulator);

View File

@@ -56,12 +56,6 @@ namespace BlackCore
//! \copydoc IContextSimulator::canConnect //! \copydoc IContextSimulator::canConnect
virtual bool canConnect() const override; virtual bool canConnect() const override;
//! \copydoc IContextSimulator::connectTo
virtual bool connectToSimulator() override;
//! \copydoc IContextSimulator::asyncConnectTo
virtual void asyncConnectToSimulator() override;
//! \copydoc IContextSimulator::disconnectFrom //! \copydoc IContextSimulator::disconnectFrom
virtual bool disconnectFromSimulator() override; virtual bool disconnectFromSimulator() override;

View File

@@ -69,16 +69,6 @@ namespace BlackCore
return m_dBusInterface->callDBusRet<bool>(QLatin1Literal("canConnect")); return m_dBusInterface->callDBusRet<bool>(QLatin1Literal("canConnect"));
} }
bool CContextSimulatorProxy::connectToSimulator()
{
return m_dBusInterface->callDBusRet<bool>(QLatin1Literal("connectToSimulator"));
}
void CContextSimulatorProxy::asyncConnectToSimulator()
{
m_dBusInterface->callDBus(QLatin1Literal("asyncConnectToSimulator"));
}
bool CContextSimulatorProxy::disconnectFromSimulator() bool CContextSimulatorProxy::disconnectFromSimulator()
{ {
return m_dBusInterface->callDBusRet<bool>(QLatin1Literal("disconnectFromSimulator")); return m_dBusInterface->callDBusRet<bool>(QLatin1Literal("disconnectFromSimulator"));

View File

@@ -53,12 +53,6 @@ namespace BlackCore
//! \copydoc IContextSimulator::canConnect //! \copydoc IContextSimulator::canConnect
virtual bool canConnect() const override; virtual bool canConnect() const override;
//! \copydoc IContextSimulator::connectTo
virtual bool connectToSimulator() override;
//! \copydoc IContextSimulator::asyncConnectTo
virtual void asyncConnectToSimulator() override;
//! \copydoc IContextSimulator::disconnectFrom //! \copydoc IContextSimulator::disconnectFrom
virtual bool disconnectFromSimulator() override; virtual bool disconnectFromSimulator() override;

View File

@@ -259,9 +259,9 @@ namespace BlackCore
VatSimType CNetworkVatlib::convertToSimType(CSimulatorPluginInfo &simInfo) VatSimType CNetworkVatlib::convertToSimType(CSimulatorPluginInfo &simInfo)
{ {
/* TODO Define recognized simulators somewhere */ /* TODO Define recognized simulators somewhere */
if (simInfo.simulator() == "fs9" || simInfo.simulator() == "fsx") { if (simInfo.getSimulator() == "fs9" || simInfo.getSimulator() == "fsx") {
return vatSimTypeMSCFS; return vatSimTypeMSCFS;
} else if (simInfo.simulator() == "xplane") { } else if (simInfo.getSimulator() == "xplane") {
return vatSimTypeXPLANE; return vatSimTypeXPLANE;
} else { } else {
return vatSimTypeUnknown; return vatSimTypeUnknown;

View File

@@ -85,13 +85,17 @@ namespace BlackGui
return; return;
} }
const QString searchFor = plugin.name();
for (int i = 0; i < this->ui->cb_Plugins->count(); ++i) for (int i = 0; i < this->ui->cb_Plugins->count(); ++i)
{ {
const QString t = ui->cb_Plugins->itemText(i); QVariant data = this->ui->cb_Plugins->itemData(i);
if (t.indexOf(searchFor, 0, Qt::CaseInsensitive) >= 0) Q_ASSERT(data.canConvert<CSimulatorPluginInfo>());
CSimulatorPluginInfo p = data.value<CSimulatorPluginInfo>();
if (p.getName() == plugin.getName())
{ {
if (i == this->ui->cb_Plugins->currentIndex()) return; if (i == this->ui->cb_Plugins->currentIndex())
return;
this->ui->cb_Plugins->setCurrentIndex(i); this->ui->cb_Plugins->setCurrentIndex(i);
break; break;
} }

View File

@@ -62,13 +62,22 @@ namespace BlackGui
void CSimulatorComponent::update() void CSimulatorComponent::update()
{ {
Q_ASSERT(getIContextSimulator());
if (!this->isVisibleWidget()) return; // no updates on invisible widgets if (!this->isVisibleWidget()) return; // no updates on invisible widgets
if (!this->getIContextOwnAircraft()) return; if (!this->getIContextOwnAircraft()) return;
if (!this->getIContextSimulator()->isSimulating())
{ if (!this->getIContextSimulator()->isConnected()) {
if (this->rowCount() == 1) { return; } addOrUpdateByName("info", tr("No simulator available"), CIcons::StandardIconWarning16);
this->clear(); return;
this->addOrUpdateByName("info", "sim not running", CIcons::StandardIconWarning16); }
if (!this->getIContextSimulator()->isSimulating()) {
this->addOrUpdateByName("info",
tr("Simulator (%1) not yet running").arg(
getIContextSimulator()->getSimulatorInfo().getSimulator()
),
CIcons::StandardIconWarning16);
return; return;
} }
@@ -118,6 +127,8 @@ namespace BlackGui
this->m_updateTimer->startTimer(intervalMs); this->m_updateTimer->startTimer(intervalMs);
} else { } else {
this->stopTimer(); this->stopTimer();
clear();
update();
} }
} }

View File

@@ -17,7 +17,7 @@ namespace BlackSim
bool CSimulatorPluginInfoList::supportsSimulator(const QString &simulator) bool CSimulatorPluginInfoList::supportsSimulator(const QString &simulator)
{ {
return std::find_if(begin(), end(), [&simulator](const CSimulatorPluginInfo &info) { return std::find_if(begin(), end(), [&simulator](const CSimulatorPluginInfo &info) {
return info.simulator() == simulator; return info.getSimulator() == simulator;
}) != end(); }) != end();
} }

View File

@@ -15,8 +15,10 @@ using namespace BlackMisc;
namespace BlackSim namespace BlackSim
{ {
CSimulatorPluginInfo::CSimulatorPluginInfo(const QJsonObject &json) void CSimulatorPluginInfo::convertFromJson(const QJsonObject &json)
{ {
if (json["IID"].toString() != QStringLiteral("org.swift.pilotclient.BlackCore.SimulatorInterface")) if (json["IID"].toString() != QStringLiteral("org.swift.pilotclient.BlackCore.SimulatorInterface"))
{ {
return; return;
@@ -33,13 +35,7 @@ namespace BlackSim
return; return;
} }
m_name = data["name"].toString(); CValueObject::convertFromJson(data);
m_simulator = data["simulator"].toString();
if (data["description"].isString())
{
m_description = data["description"].toString();
}
m_valid = true; m_valid = true;
} }

View File

@@ -23,7 +23,7 @@ namespace BlackSim
/** /**
* The _name_ property identifies the plugin itself and must be uniqe. * The _name_ property identifies the plugin itself and must be uniqe.
*/ */
Q_PROPERTY(QString name READ name) Q_PROPERTY(QString getName READ getName)
/** /**
* The _simulator_ property specifies which simulator the plugin handles. * The _simulator_ property specifies which simulator the plugin handles.
@@ -31,19 +31,18 @@ namespace BlackSim
* Swift enables some features for particular simulators. Currently recognized are: * Swift enables some features for particular simulators. Currently recognized are:
* fsx, fs9, xplane * fsx, fs9, xplane
*/ */
Q_PROPERTY(QString simulator READ simulator) Q_PROPERTY(QString getSimulator READ getSimulator)
/** /**
* The _description_ property provides a short, human-readable description of the plugin. * The _description_ property provides a short, human-readable description of the plugin.
*/ */
Q_PROPERTY(QString description READ description) Q_PROPERTY(QString getDescription READ getDescription)
public: public:
//! Default constructor //! Default constructor
CSimulatorPluginInfo() = default; CSimulatorPluginInfo() = default;
//! This constructor takes a JSON object that comes with the plugin metadata virtual void convertFromJson(const QJsonObject &json) override;
CSimulatorPluginInfo(const QJsonObject& json);
//! Unspecified simulator //! Unspecified simulator
bool isUnspecified() const; bool isUnspecified() const;
@@ -63,13 +62,13 @@ namespace BlackSim
//! * provides plugin name; //! * provides plugin name;
//! * specifies simulator it handles. //! * specifies simulator it handles.
//! Unspecified sim is considered as invalid. //! Unspecified sim is considered as invalid.
inline bool isValid() const { return m_valid; } bool isValid() const { return m_valid; }
inline bool operator==(const CSimulatorPluginInfo& other) { return name() == other.name(); } bool operator==(const CSimulatorPluginInfo &other) { return getName() == other.getName(); }
inline const QString& name() const { return m_name; } const QString &getName() const { return m_name; }
inline const QString& simulator() const { return m_simulator; } const QString &getSimulator() const { return m_simulator; }
inline const QString& description() const { return m_description; } const QString &getDescription() const { return m_description; }
protected: protected:
//! \copydoc CValueObject::convertToQString //! \copydoc CValueObject::convertToQString
@@ -86,7 +85,10 @@ namespace BlackSim
} }
BLACK_DECLARE_TUPLE_CONVERSION(BlackSim::CSimulatorPluginInfo, ( BLACK_DECLARE_TUPLE_CONVERSION(BlackSim::CSimulatorPluginInfo, (
o.m_name, attr(o.m_name),
attr(o.m_simulator),
attr(o.m_description),
attr(o.m_valid),
attr(o.m_simsetup, flags<DisabledForComparison>()) attr(o.m_simsetup, flags<DisabledForComparison>())
)) ))
Q_DECLARE_METATYPE(BlackSim::CSimulatorPluginInfo) Q_DECLARE_METATYPE(BlackSim::CSimulatorPluginInfo)

View File

@@ -47,7 +47,7 @@ namespace BlackSimPlugin
{ {
CSimulatorFs9::CSimulatorFs9(IOwnAircraftProvider *ownAircraftProvider, CSimulatorFs9::CSimulatorFs9(IOwnAircraftProvider *ownAircraftProvider,
IRemoteAircraftProvider *remoteAircraftProvider, QObject *parent) : IRemoteAircraftProvider *remoteAircraftProvider, QObject *parent) :
CSimulatorFsCommon(CSimulatorPluginInfo::FS9(), ownAircraftProvider, remoteAircraftProvider, parent) CSimulatorFsCommon(ownAircraftProvider, remoteAircraftProvider, parent)
{ {
connect(lobbyClient.data(), &CLobbyClient::disconnected, this, std::bind(&CSimulatorFs9::simulatorStatusChanged, this, 0)); connect(lobbyClient.data(), &CLobbyClient::disconnected, this, std::bind(&CSimulatorFs9::simulatorStatusChanged, this, 0));
connect(fs9Host.data(), &CFs9Host::customPacketReceived, this, &CSimulatorFs9::ps_processFs9Message); connect(fs9Host.data(), &CFs9Host::customPacketReceived, this, &CSimulatorFs9::ps_processFs9Message);
@@ -293,7 +293,7 @@ namespace BlackSimPlugin
if (m_lobbyConnected && fs9Host->isConnected()) if (m_lobbyConnected && fs9Host->isConnected())
{ {
emit simulatorStarted(m_simulatorInfo); emit simulatorStarted();
m_lobbyConnected = false; m_lobbyConnected = false;
} }
}); });
@@ -339,11 +339,6 @@ namespace BlackSimPlugin
return new CSimulatorFs9(ownAircraftProvider, remoteAircraftProvider, parent); return new CSimulatorFs9(ownAircraftProvider, remoteAircraftProvider, parent);
} }
BlackSim::CSimulatorPluginInfo CSimulatorFs9Factory::getSimulatorInfo() const
{
return CSimulatorPluginInfo::FS9();
}
BlackCore::ISimulatorListener *CSimulatorFs9Factory::createListener(QObject *parent) BlackCore::ISimulatorListener *CSimulatorFs9Factory::createListener(QObject *parent)
{ {
return new CSimulatorFs9Listener(parent); return new CSimulatorFs9Listener(parent);

View File

@@ -130,7 +130,6 @@ namespace BlackSimPlugin
QTimer* m_timer = nullptr; QTimer* m_timer = nullptr;
bool m_lobbyConnected = false; bool m_lobbyConnected = false;
const BlackSim::CSimulatorPluginInfo m_simulatorInfo = BlackSim::CSimulatorPluginInfo::FS9();
}; };
@@ -152,9 +151,6 @@ namespace BlackSimPlugin
BlackMisc::Simulation::IRemoteAircraftProvider *remoteAircraftProvider, BlackMisc::Simulation::IRemoteAircraftProvider *remoteAircraftProvider,
QObject *parent) override; QObject *parent) override;
//! Simulator info
virtual BlackSim::CSimulatorPluginInfo getSimulatorInfo() const override;
//! \copydoc BlackCore::ISimulatorFactory::createListener //! \copydoc BlackCore::ISimulatorFactory::createListener
virtual BlackCore::ISimulatorListener *createListener(QObject *parent = nullptr) override; virtual BlackCore::ISimulatorListener *createListener(QObject *parent = nullptr) override;

View File

@@ -1,4 +1,5 @@
{ {
"short_name" : "FS9", "name" : "swift_generic_fs9",
"full_name" : "Microsoft Flight Simulator 2004" "simulator" : "fs9",
"description" : "Microsoft Flight Simulator 2004"
} }

View File

@@ -26,8 +26,8 @@ namespace BlackSimPlugin
{ {
namespace FsCommon namespace FsCommon
{ {
CSimulatorFsCommon::CSimulatorFsCommon(const BlackSim::CSimulatorPluginInfo &simInfo, BlackMisc::Simulation::IOwnAircraftProvider *ownAircraftProvider, IRemoteAircraftProvider *renderedAircraftProvider, QObject *parent) : CSimulatorFsCommon::CSimulatorFsCommon(BlackMisc::Simulation::IOwnAircraftProvider *ownAircraftProvider, IRemoteAircraftProvider *renderedAircraftProvider, QObject *parent) :
CSimulatorCommon(simInfo, ownAircraftProvider, renderedAircraftProvider, parent), CSimulatorCommon(ownAircraftProvider, renderedAircraftProvider, parent),
m_fsuipc(new FsCommon::CFsuipc()) m_fsuipc(new FsCommon::CFsuipc())
{ {
// hack to init mapper // hack to init mapper

View File

@@ -81,7 +81,6 @@ namespace BlackSimPlugin
protected: protected:
//! Constructor //! Constructor
CSimulatorFsCommon( CSimulatorFsCommon(
const BlackSim::CSimulatorPluginInfo &simInfo,
BlackMisc::Simulation::IOwnAircraftProvider *ownAircraftProvider, BlackMisc::Simulation::IOwnAircraftProvider *ownAircraftProvider,
BlackMisc::Simulation::IRemoteAircraftProvider *renderedAircraftProvider, BlackMisc::Simulation::IRemoteAircraftProvider *renderedAircraftProvider,
QObject *parent = nullptr); QObject *parent = nullptr);

View File

@@ -39,7 +39,7 @@ namespace BlackSimPlugin
namespace Fsx namespace Fsx
{ {
CSimulatorFsx::CSimulatorFsx(IOwnAircraftProvider *ownAircraftProvider, IRemoteAircraftProvider *remoteAircraftProvider, QObject *parent) : CSimulatorFsx::CSimulatorFsx(IOwnAircraftProvider *ownAircraftProvider, IRemoteAircraftProvider *remoteAircraftProvider, QObject *parent) :
CSimulatorFsCommon(CSimulatorPluginInfo::FSX(), ownAircraftProvider, remoteAircraftProvider, parent) CSimulatorFsCommon(ownAircraftProvider, remoteAircraftProvider, parent)
{ {
Q_ASSERT(ownAircraftProvider); Q_ASSERT(ownAircraftProvider);
Q_ASSERT(remoteAircraftProvider); Q_ASSERT(remoteAircraftProvider);
@@ -820,7 +820,7 @@ namespace BlackSimPlugin
SimConnect_Close(hSimConnect); SimConnect_Close(hSimConnect);
if (result == S_OK) if (result == S_OK)
emit simulatorStarted(m_simulatorInfo); emit simulatorStarted();
}); });
} }

View File

@@ -195,6 +195,7 @@ namespace BlackSimPlugin
uint m_nextObjID = 1; //!< object ID TODO: also used as request id, where to we place other request ids as for facilities uint m_nextObjID = 1; //!< object ID TODO: also used as request id, where to we place other request ids as for facilities
QHash<BlackMisc::Aviation::CCallsign, CSimConnectObject> m_simConnectObjects; QHash<BlackMisc::Aviation::CCallsign, CSimConnectObject> m_simConnectObjects;
QFutureWatcher<bool> m_watcherConnect; QFutureWatcher<bool> m_watcherConnect;
BlackSim::CSimulatorPluginInfo m_simulatorInfo;
// statistics // statistics
qint64 m_statsUpdateAircraftTimeTotal = 0; qint64 m_statsUpdateAircraftTimeTotal = 0;
@@ -218,7 +219,6 @@ namespace BlackSimPlugin
private: private:
QTimer* m_timer; QTimer* m_timer;
const BlackSim::CSimulatorPluginInfo m_simulatorInfo = BlackSim::CSimulatorPluginInfo::FSX();
}; };
} }

View File

@@ -1,4 +1,5 @@
{ {
"short_name" : "FSX", "name" : "swift_generic_fsx",
"full_name" : "Microsoft Flight Simulator X (2006)" "simulator" : "fsx",
"description" : "Microsoft Flight Simulator X (2006)"
} }

View File

@@ -24,11 +24,6 @@ namespace BlackSimPlugin
return new CSimulatorFsx(ownAircraftProvider, renderedAircraftProvider, parent); return new CSimulatorFsx(ownAircraftProvider, renderedAircraftProvider, parent);
} }
BlackSim::CSimulatorPluginInfo CSimulatorFsxFactory::getSimulatorInfo() const
{
return BlackSim::CSimulatorPluginInfo::FSX();
}
BlackCore::ISimulatorListener *CSimulatorFsxFactory::createListener(QObject *parent) BlackCore::ISimulatorListener *CSimulatorFsxFactory::createListener(QObject *parent)
{ {
return new CSimulatorFsxListener(parent); return new CSimulatorFsxListener(parent);

View File

@@ -38,9 +38,6 @@ namespace BlackSimPlugin
BlackMisc::Simulation::IRemoteAircraftProvider *renderedAircraftProvider, BlackMisc::Simulation::IRemoteAircraftProvider *renderedAircraftProvider,
QObject *parent) override; QObject *parent) override;
//! \copydoc BlackCore::ISimulatorFactory::getSimulatorInfo
virtual BlackSim::CSimulatorPluginInfo getSimulatorInfo() const override;
//! \copydoc BlackCore::ISimulatorFactory::getListener //! \copydoc BlackCore::ISimulatorFactory::getListener
virtual BlackCore::ISimulatorListener *createListener(QObject *parent = nullptr) override; virtual BlackCore::ISimulatorListener *createListener(QObject *parent = nullptr) override;
}; };

View File

@@ -1,5 +1,5 @@
{ {
"name" : "swift_generic_xplane", "name" : "swift_generic_xplane",
"simulator" : "X-Plane", "simulator" : "xplane",
"description" : "X-Plane support via the xbus plugin" "description" : "X-Plane support via the xbus plugin"
} }