1
0
Fork 0

Merge branch 'master' into electric-engine

This commit is contained in:
Vit Hanousek 2020-04-11 09:15:21 +02:00
commit 30528a9d6c
75 changed files with 1268 additions and 1145 deletions

View file

@ -23,11 +23,7 @@ add_library(hidapi STATIC
)
if(WIN32)
find_library(SETUP_API_LIB Setupapi)
if (NOT SETUP_API_LIB)
message(FATAL_ERROR "Failed to find Setupapi.lib")
endif()
target_link_libraries(hidapi ${SETUP_API_LIB})
target_link_libraries(hidapi PUBLIC SetupApi)
elseif(APPLE)
find_library(IOKIT_FRAMEWORK IOKit)
target_link_libraries(hidapi ${IOKIT_FRAMEWORK})

View file

@ -76,7 +76,7 @@ include(GNUInstallDirs)
# System detection/default settings
include( DetectDistro )
include( DetectBrowser )
include( ExportDebugSymbols )
set(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "add a postfix, usually d on windows")
set(CMAKE_RELEASE_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows")
set(CMAKE_RELWITHDEBINFO_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows")
@ -123,11 +123,7 @@ IF(APPLE)
find_library(COCOA_LIBRARY Cocoa)
list(APPEND PLATFORM_LIBS ${COCOA_LIBRARY} ${CORESERVICES_LIBRARY})
elseif(WIN32)
find_library(SETUP_API_LIB Setupapi)
if (SETUP_API_LIB)
set(EVENT_INPUT_DEFAULT 1)
endif()
set(EVENT_INPUT_DEFAULT 1)
list(APPEND PLATFORM_LIBS "Shlwapi.lib")
set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION "bin")
@ -265,14 +261,7 @@ if(EVENT_INPUT)
message(STATUS "event-based input enabled. Using ${UDEV_LIBRARIES}")
endif()
else()
# Windows
if (NOT SETUP_API_LIB)
message(WARNING "Setupapi.lib not found, HID/event input is disabled")
set(ENABLE_HID_INPUT 0)
set(EVENT_INPUT 0)
else()
add_definitions(-DWITH_EVENTINPUT)
endif()
add_definitions(-DWITH_EVENTINPUT)
endif()
if (ENABLE_HID_INPUT)
@ -282,7 +271,15 @@ if(EVENT_INPUT)
endif(EVENT_INPUT)
if (ENABLE_SWIFT)
# DBUS
# DBUS
# our local FindDBus.cmake file uses pkg-config on non-Windows
# we want to ensure our local prefixes are searched, so set this
set(PKG_CONFIG_USE_CMAKE_PREFIX_PATH 1)
# unfortunately CMAKE_INSTALL_PREFIX is not searched, so add that manually
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_INSTALL_PREFIX})
find_package(DBus)
#libevent
@ -317,6 +314,13 @@ find_package(OpenSceneGraph 3.2.0 REQUIRED
osgGA
)
find_package(sentry QUIET)
if (TARGET sentry::sentry)
message(STATUS "Sentry.io crash reporting enabled")
set(HAVE_SENTRY 1)
endif()
if (MSVC)
find_package(CrashRpt)
if (CRASHRPT_FOUND)
@ -415,10 +419,7 @@ endif()
if(ENABLE_RTI)
message(STATUS "RTI: ENABLED")
find_package(RTI)
if(RTI_FOUND)
set(FG_HAVE_HLA 1)
endif(RTI_FOUND)
set(FG_HAVE_HLA 1)
else()
message(STATUS "RTI: DISABLED")
endif(ENABLE_RTI)

View file

@ -48,6 +48,9 @@ if (MSVC AND MSVC_3RDPARTY_ROOT)
set (CMAKE_LIBRARY_PATH ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/lib ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenScenegraph/lib ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenRTI/lib ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/SimGear/lib )
set (CMAKE_INCLUDE_PATH ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/include ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenScenegraph/include ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/OpenRTI/include ${MSVC_3RDPARTY_ROOT}/install/${OSG_MSVC}/SimGear/include)
# ensure 3rdparty/lib/cmake is searched
list(APPEND CMAKE_PREFIX_PATH ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR})
if(NOT BOOST_INCLUDEDIR)
# if this variable was not set by the user, set it to 3rdparty root's
# parent dir, which is the normal location for people using our

View file

@ -0,0 +1,22 @@
# placehodler target for other ones to depend upon
add_custom_target(
debug_symbols
)
function(export_debug_symbols target)
if (APPLE)
add_custom_target(${target}.dSYM
COMMENT "Generating dSYM files for ${target}"
COMMAND dsymutil --out=${target}.dSYM $<TARGET_FILE:${target}>
DEPENDS $<TARGET_FILE:${target}>
)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${target}.dSYM DESTINATION symbols OPTIONAL)
add_dependencies(debug_symbols ${target}.dSYM)
endif()
endfunction()

View file

@ -7,7 +7,7 @@ function(setup_fgfs_bundle target)
# in our local CMakeModules dir
set_target_properties(${target} PROPERTIES
MACOSX_BUNDLE_INFO_PLIST FlightGearBundleInfo.plist.in
MACOSX_BUNDLE_GUI_IDENTIFIER "org.flightgear.FlightGear"
MACOSX_BUNDLE_GUI_IDENTIFIER "org.flightgear.mac-nightly"
MACOSX_BUNDLE_SHORT_VERSION_STRING ${FLIGHTGEAR_VERSION}
MACOSX_BUNDLE_LONG_VERSION_STRING "FlightGear ${FLIGHTGEAR_VERSION} Nightly"
MACOSX_BUNDLE_BUNDLE_VERSION ${FLIGHTGEAR_VERSION}

View file

@ -7,7 +7,7 @@ function(setup_fgfs_libraries target)
#message(STATUS "SG libs ${SIMGEAR_LIBRARIES}")
if(RTI_FOUND)
set(HLA_LIBRARIES ${RTI_LIBRARIES})
set(HLA_LIBRARIES ${RTI_LDFLAGS})
else()
set(HLA_LIBRARIES "")
endif()

View file

@ -20,15 +20,12 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <config.h>
#include <string.h>
#include <simgear/compiler.h>
#include <boost/foreach.hpp>
#include <string>
#include <osg/ref_ptr>
@ -67,11 +64,9 @@ public:
}
~FGAIModelData()
{
}
~FGAIModelData() = default;
virtual FGAIModelData* clone() const { return new FGAIModelData(); }
FGAIModelData* clone() const override { return new FGAIModelData(); }
/** osg callback, thread-safe */
void modelLoaded(const std::string& path, SGPropertyNode *prop, osg::Node *n)

View file

@ -72,7 +72,7 @@ void FGAICarrier::readFromScenario(SGPropertyNode* scFileNode) {
_flolsPosOffset(2) = - flols->getDoubleValue("z-offset-m", 0);
_flolsHeadingOffsetDeg = flols->getDoubleValue("heading-offset-deg", 0.0);
_flolsApproachAngle = flols->getDoubleValue("glidepath-angle-deg", 3.0);
_flolsApproachAngle = flols->getDoubleValue("glidepath-angle-deg", 3.5);
} else
_flolsPosOffset = SGVec3d::zeros();
@ -621,7 +621,7 @@ std::pair<bool, SGGeod> FGAICarrier::initialPositionForCarrier(const std::string
// this is actually a three-layer search (we want the scenario with the
// carrier with the correct penanant or name. Sometimes an XPath for
// properties would be quite handy :)
for (auto s : fgGetNode("/sim/ai/scenarios")->getChildren("scenario")) {
auto carriers = s->getChildren("carrier");
auto it = std::find_if(carriers.begin(), carriers.end(),
@ -636,16 +636,16 @@ std::pair<bool, SGGeod> FGAICarrier::initialPositionForCarrier(const std::string
if (it == carriers.end()) {
continue;
}
// mark the scenario for loading (which will happen in post-init of the AIManager)
fgGetNode("/sim/ai/")->addChild("scenario")->setStringValue(s->getStringValue("id"));
// read out the initial-position
SGGeod geod = SGGeod::fromDeg((*it)->getDoubleValue("longitude"),
(*it)->getDoubleValue("latitude"));
return std::make_pair(true, geod);
} // of scenarios iteration
return std::make_pair(false, SGGeod());
}
@ -655,7 +655,7 @@ SGSharedPtr<FGAICarrier> FGAICarrier::findCarrierByNameOrPennant(const std::stri
if (!aiManager) {
return {};
}
for (const auto& aiObject : aiManager->get_ai_list()) {
if (aiObject->isa(FGAIBase::otCarrier)) {
SGSharedPtr<FGAICarrier> c = static_cast<FGAICarrier*>(aiObject.get());
@ -664,7 +664,7 @@ SGSharedPtr<FGAICarrier> FGAICarrier::findCarrierByNameOrPennant(const std::stri
}
}
} // of all objects iteration
return {};
}
@ -673,19 +673,22 @@ void FGAICarrier::extractCarriersFromScenario(SGPropertyNode_ptr xmlNode, SGProp
for (auto c : xmlNode->getChildren("entry")) {
if (c->getStringValue("type") != std::string("carrier"))
continue;
const std::string name = c->getStringValue("name");
const std::string pennant = c->getStringValue("pennant-number");
if (name.empty() && pennant.empty()) {
continue;
}
SGPropertyNode_ptr carrierNode = scenario->addChild("carrier");
// extract the initial position from the scenario
carrierNode->setDoubleValue("longitude", c->getDoubleValue("longitude"));
carrierNode->setDoubleValue("latitude", c->getDoubleValue("latitude"));
// A description of the carrier is also available from the entry. Primarily for use by the launcher
carrierNode->setStringValue("description", c->getStringValue("description"));
// the find code above just looks for anything called a name (so alias
// are possible, for example)
if (!name.empty()) carrierNode->addChild("name")->setStringValue(name);

View file

@ -234,6 +234,13 @@ FGTaxiNodeRef FGGroundNetwork::findNearestNodeOffRunway(const SGGeod& aGeod, FGR
[runwayLine, cartPos, marginMSqr] (const FGTaxiNodeRef& a)
{
if (a->getIsOnRunway()) return false;
// exclude parking positions from consideration. This helps to
// exclude airports whose ground nets only list parking positions,
// since these typically produce bad results. See discussion in
// https://sourceforge.net/p/flightgear/codetickets/2110/
if (a->type() == FGPositioned::PARKING) return false;
return (distSqr(runwayLine, a->cart()) >= marginMSqr);
});

View file

@ -153,7 +153,7 @@ public:
_nd(nd)
{}
virtual void valueChanged (SGPropertyNode * prop)
void valueChanged (SGPropertyNode * prop) override
{
_nd->invalidatePositionedCache();
}
@ -168,7 +168,7 @@ public:
_nd(nd)
{}
virtual void valueChanged (SGPropertyNode * prop)
void valueChanged (SGPropertyNode * prop) override
{
_nd->forceUpdate();
}
@ -256,7 +256,7 @@ public:
// record instances for limiting by count
int instanceCount;
private:
SymbolDef* definition;
SymbolDef* definition = nullptr;
std::unique_ptr<SGCondition> enable;
string_set required_states;
@ -459,7 +459,7 @@ NavDisplay::NavDisplay(SGPropertyNode *node) :
SGPropertyNode* symbol;
map<string, SymbolDef*> definitionDict;
for (int i = 0; (symbol = symbolsNode->getChild("symbol", i)) != NULL; ++i) {
for (int i = 0; (symbol = symbolsNode->getChild("symbol", i)) != nullptr; ++i) {
SymbolDef* def = new SymbolDef;
if (!def->initFromNode(symbol, this)) {
delete def;
@ -539,7 +539,7 @@ NavDisplay::init ()
}
// no mipmap or else alpha will mix with pixels on the border of shapes, ruining the effect
_symbolTexture = SGLoadTexture2D(tpath, NULL, false, false);
_symbolTexture = SGLoadTexture2D(tpath, nullptr, false, false);
_odg = new FGODGauge;
_odg->setSize(_Instrument->getIntValue("texture-size", 512));
@ -958,7 +958,7 @@ public:
double minRunwayLengthFt;
virtual bool pass(FGPositioned* aPos) const
bool pass(FGPositioned* aPos) const override
{
if (aPos->type() == FGPositioned::FIX) {
string ident(aPos->ident());
@ -979,11 +979,13 @@ public:
return _owner->isPositionedShown(aPos);
}
virtual FGPositioned::Type minType() const {
FGPositioned::Type minType() const override
{
return FGPositioned::AIRPORT;
}
virtual FGPositioned::Type maxType() const {
FGPositioned::Type maxType() const override
{
return FGPositioned::OBSTACLE;
}
@ -1105,7 +1107,7 @@ FGNavRecord* NavDisplay::processNavRadio(const SGPropertyNode_ptr& radio)
FGNavRecord* nav = FGNavList::findByFreq(mhz, _pos, FGNavList::navFilter());
if (!nav || (nav->ident() != radio->getStringValue("nav-id"))) {
// station was not found
return NULL;
return nullptr;
}
@ -1367,11 +1369,11 @@ void NavDisplay::computeAIStates(const SGPropertyNode* ai, string_set& states)
SymbolInstance* NavDisplay::addSymbolInstance(const osg::Vec2& proj, double heading, SymbolDef* def, SGPropertyNode* vars)
{
if (isProjectedClipped(proj)) {
return NULL;
return nullptr;
}
if ((def->limitCount > 0) && (def->instanceCount >= def->limitCount)) {
return NULL;
return nullptr;
}
++def->instanceCount;
@ -1416,28 +1418,28 @@ void NavDisplay::addTestSymbols()
double dummy;
SGGeodesy::direct(_pos, 45.0, 20.0 * SG_NM_TO_METER, a1, dummy);
addTestSymbol("airport", "", a1, 0.0, NULL);
addTestSymbol("airport", "", a1, 0.0, nullptr);
SGGeodesy::direct(_pos, 95.0, 40.0 * SG_NM_TO_METER, a1, dummy);
addTestSymbol("vor", "", a1, 0.0, NULL);
addTestSymbol("vor", "", a1, 0.0, nullptr);
SGGeodesy::direct(_pos, 120, 80.0 * SG_NM_TO_METER, a1, dummy);
addTestSymbol("airport", "destination", a1, 0.0, NULL);
addTestSymbol("airport", "destination", a1, 0.0, nullptr);
SGGeodesy::direct(_pos, 80.0, 20.0 * SG_NM_TO_METER, a1, dummy);
addTestSymbol("fix", "", a1, 0.0, NULL);
addTestSymbol("fix", "", a1, 0.0, nullptr);
SGGeodesy::direct(_pos, 140.0, 20.0 * SG_NM_TO_METER, a1, dummy);
addTestSymbol("fix", "", a1, 0.0, NULL);
addTestSymbol("fix", "", a1, 0.0, nullptr);
SGGeodesy::direct(_pos, 110.0, 10.0 * SG_NM_TO_METER, a1, dummy);
addTestSymbol("fix", "", a1, 0.0, NULL);
addTestSymbol("fix", "", a1, 0.0, nullptr);
SGGeodesy::direct(_pos, 110.0, 5.0 * SG_NM_TO_METER, a1, dummy);
addTestSymbol("fix", "", a1, 0.0, NULL);
addTestSymbol("fix", "", a1, 0.0, nullptr);
}
void NavDisplay::addRule(SymbolRule* r)

View file

@ -184,6 +184,9 @@ MetarProperties::MetarProperties( SGPropertyNode_ptr rootNode ) :
_tiedProperties.Tie("decoded", this, &MetarProperties::get_decoded );
_tiedProperties.Tie("cavok", &_cavok );
_tiedProperties.Tie("description", this, &MetarProperties::get_description );
// mark proeprties as listener-safe, we invoke valueChanged explicitly
_tiedProperties.setAttribute(SGPropertyNode::LISTENER_SAFE, true);
}
MetarProperties::~MetarProperties()
@ -422,6 +425,7 @@ void MetarProperties::setMetar( SGSharedPtr<FGMetar> m )
_hour = m->getHour();
_minute = m->getMinute();
_cavok = m->getCAVOK();
_tiedProperties.fireValueChanged();
_metarValidNode->setBoolValue(true);
_description = m->getDescription(-1);
}

View file

@ -204,9 +204,9 @@ void FGFDM::init()
fgSetDouble(buf, CM2GALS * _airplane.getTankCapacity(i)/density);
}
// This has a nasty habit of being false at startup. That's not
// good.
fgSetBool("/controls/gear/gear-down", true);
if (_yasimN->getBoolValue("respect-external-gear-state") == false) {
fgSetBool("/controls/gear/gear-down", true);
}
_airplane.getModel()->setTurbulence(_turb);
}

View file

@ -196,8 +196,10 @@ void YASim::init()
// Are we at ground level? If so, lift the plane up so the gear
// clear the ground.
bool respect_external_gear_state = fgGetBool("/fdm/yasim/respect-external-gear-state");
double runway_altitude = get_Runway_altitude();
if(get_Altitude() - runway_altitude < 50) {
bool gear_state = fgGetBool("/controls/gear/gear-down");
fgSetBool("/controls/gear/gear-down", false);
float minGearZ = 1e18;
for(int i=0; i<airplane->numGear(); i++) {
@ -209,9 +211,9 @@ void YASim::init()
}
_set_Altitude(runway_altitude - minGearZ*M2FT);
// ground start-up: gear down
fgSetBool("/controls/gear/gear-down", true);
fgSetBool("/controls/gear/gear-down", respect_external_gear_state ? gear_state : true);
}
else
else if (! respect_external_gear_state)
{
// airborne start-up: gear up
fgSetBool("/controls/gear/gear-down", false);
@ -300,7 +302,7 @@ void YASim::copyToYASim(bool copyState)
atmo.setDensity(dens);
atmo.setTemperature(temp);
atmo.setPressure(pressure);
// Convert and set:
Model* model = _fdm->getAirplane()->getModel();
yasim::State s;
@ -380,9 +382,9 @@ void YASim::copyToYASim(bool copyState)
// These are set below:
// _set_Accels_Local
// _set_Accels_Body
// _set_Accels_CG_Body
// _set_Accels_CG_Body
// _set_Accels_Pilot_Body
// _set_Accels_CG_Body_N
// _set_Accels_CG_Body_N
// _set_Velocities_Local
// _set_Velocities_Ground
// _set_Velocities_Body

View file

@ -38,6 +38,7 @@
#include <Include/version.h>
#include "QmlAircraftInfo.hxx"
#include "FavouriteAircraftData.hxx"
using namespace simgear::pkg;
@ -125,7 +126,7 @@ private:
return QModelIndex();
}
int offset = std::distance(m_model->m_packages.begin(), it);
const int offset = static_cast<int>(std::distance(m_model->m_packages.begin(), it));
return m_model->index(offset + m_model->m_cachedLocalAircraftCount);
}
@ -138,6 +139,8 @@ void PackageDelegate::catalogRefreshed(CatalogRef aCatalog, StatusCode aReason)
// nothing to do
} else if ((aReason == STATUS_REFRESHED) || (aReason == STATUS_SUCCESS)) {
m_model->refreshPackages();
} else if (aReason == FAIL_VERSION) {
// silent about this
} else {
qWarning() << "failed refresh of"
<< QString::fromStdString(aCatalog->url()) << ":" << aReason << endl;
@ -158,8 +161,6 @@ AircraftItemModel::AircraftItemModel(QObject* pr) :
this, &AircraftItemModel::onScanAddedItems);
connect(cache, &LocalAircraftCache::cleared,
this, &AircraftItemModel::onLocalCacheCleared);
loadFavourites();
}
AircraftItemModel::~AircraftItemModel()
@ -244,7 +245,7 @@ QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
if (role == AircraftIsFavouriteRole) {
// recursive call here, hope that's okay
const auto uri = data(index, AircraftURIRole).toUrl();
return m_favourites.contains(uri);
return FavouriteAircraftData::instance()->isFavourite(uri);
}
if (row >= m_cachedLocalAircraftCount) {
@ -338,7 +339,7 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, const DelegateSta
QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, const DelegateState& state, int role) const
{
if (role >= AircraftVariantDescriptionRole) {
int variantIndex = role - AircraftVariantDescriptionRole;
const unsigned int variantIndex = static_cast<unsigned int>(role - AircraftVariantDescriptionRole);
QString desc = QString::fromStdString(item->nameForVariant(variantIndex));
if (desc.isEmpty()) {
desc = tr("Missing description for: %1").arg(QString::fromStdString(item->id()));
@ -431,17 +432,11 @@ bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value,
emit dataChanged(index, index);
return true;
} else if (role == AircraftIsFavouriteRole) {
bool f = value.toBool();
const auto uri = data(index, AircraftURIRole).toUrl();
const auto cur = m_favourites.contains(uri);
if (f && !cur) {
m_favourites.append(uri);
} else if (!f && cur) {
m_favourites.removeOne(uri);
bool changed = FavouriteAircraftData::instance()->setFavourite(uri, value.toBool());
if (changed) {
emit dataChanged(index, index);
}
saveFavourites();
emit dataChanged(index, index);
}
return false;
@ -493,9 +488,10 @@ QModelIndex AircraftItemModel::indexOfAircraftURI(QUrl uri) const
PackageRef pkg = m_packageRoot->getPackageById(ident.toStdString());
if (pkg) {
for (size_t i=0; i < m_packages.size(); ++i) {
if (m_packages[i] == pkg) {
return index(rowOffset + i);
const auto numPackages = m_packages.size();
for (unsigned int i=0; i < numPackages; ++i) {
if (m_packages.at(i) == pkg) {
return index(static_cast<int>(rowOffset + i));
}
} // of linear package scan
}
@ -542,7 +538,7 @@ void AircraftItemModel::selectVariantForAircraftURI(QUrl uri)
if (pkg) {
for (size_t i=0; i < m_packages.size(); ++i) {
if (m_packages[i] == pkg) {
modelIndex = index(rowOffset + static_cast<int>(i));
modelIndex = index(rowOffset + static_cast<int>(i));
variantIndex = pkg->indexOfVariant(ident.toStdString());
break;
}
@ -663,21 +659,3 @@ bool AircraftItemModel::isIndexRunnable(const QModelIndex& index) const
return !ex->isDownloading();
}
void AircraftItemModel::loadFavourites()
{
m_favourites.clear();
QSettings settings;
Q_FOREACH(auto v, settings.value("favourite-aircraft").toList()) {
m_favourites.append(v.toUrl());
}
}
void AircraftItemModel::saveFavourites()
{
QVariantList favs;
Q_FOREACH(auto u, m_favourites) {
favs.append(u);
}
QSettings settings;
settings.setValue("favourite-aircraft", favs);
}

View file

@ -146,9 +146,6 @@ private:
void installSucceeded(QModelIndex index);
void installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason);
void loadFavourites();
void saveFavourites();
private:
PackageDelegate* m_delegate = nullptr;
@ -156,8 +153,6 @@ private:
simgear::pkg::RootRef m_packageRoot;
simgear::pkg::PackageList m_packages;
QVector<QUrl> m_favourites;
int m_cachedLocalAircraftCount = 0;
};

View file

@ -4,6 +4,8 @@
#include <QDebug>
#include "AircraftModel.hxx"
#include "FavouriteAircraftData.hxx"
#include <simgear/package/Package.hxx>
AircraftProxyModel::AircraftProxyModel(QObject *pr, QAbstractItemModel * source) :
@ -121,6 +123,13 @@ void AircraftProxyModel::setShowFavourites(bool e)
return;
m_onlyShowFavourites = e;
if (e) {
setDynamicSortFilter(false);
connect(FavouriteAircraftData::instance(), &FavouriteAircraftData::changed,
[this]() {
this->invalidate();
});
}
invalidate();
}

View file

@ -68,7 +68,7 @@ protected:
private:
bool filterAircraft(const QModelIndex& sourceIndex) const;
bool m_ratingsFilter = true;
bool m_ratingsFilter = false;
bool m_onlyShowInstalled = false;
bool m_onlyShowWithUpdate = false;
bool m_onlyShowFavourites = false;

View file

@ -155,6 +155,25 @@ void BaseDiagram::paintAirplaneIcon(QPainter* painter, const SGGeod& geod, int h
painter->restore();
}
void BaseDiagram::paintCarrierIcon(QPainter* painter, const SGGeod& geod, int headingDeg)
{
QPointF pos = project(geod);
QPixmap pix(":/svg/aircraft-carrier");
pos = m_viewportTransform.map(pos);
painter->save();
painter->setWorldTransform(m_baseDeviceTransform);
painter->translate(pos.x(), pos.y());
painter->rotate(headingDeg);
painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
QRect carrierIconRect = pix.rect();
carrierIconRect.moveCenter(QPoint(0,0));
painter->drawPixmap(carrierIconRect, pix);
painter->restore();
}
void BaseDiagram::paintPolygonData(QPainter* painter)
{
QTransform invT = m_viewportTransform.inverted();

View file

@ -57,7 +57,7 @@ public:
void setAircraftType(LauncherController::AircraftType type);
QRect rect() const;
Q_INVOKABLE void resetZoom();
protected:
void paint(QPainter* p) override;
@ -78,7 +78,7 @@ protected:
void extendBounds(const QPointF& p, double radiusM = 1.0);
QPointF project(const SGGeod& geod) const;
QTransform transform() const;
void clearIgnoredNavaids();
void addIgnoredNavaid(FGPositionedRef pos);
@ -98,6 +98,7 @@ protected:
static SGGeod unproject(const QPointF &xy, const SGGeod &center);
void paintAirplaneIcon(QPainter *painter, const SGGeod &geod, int headingDeg);
void paintCarrierIcon(QPainter *painter, const SGGeod &geod, int headingDeg);
void paintAirways(QPainter* painter, const FGPositionedList& navs);
QPointF projectedPosition(PositionedID pid) const;

View file

@ -84,6 +84,8 @@ if (HAVE_QT)
BaseDiagram.hxx
AirportDiagram.cxx
AirportDiagram.hxx
CarrierDiagram.cxx
CarrierDiagram.hxx
NavaidDiagram.cxx
NavaidDiagram.hxx
SetupRootDialog.cxx
@ -128,6 +130,8 @@ if (HAVE_QT)
PathListModel.hxx
CarriersLocationModel.cxx
CarriersLocationModel.hxx
FavouriteAircraftData.cxx
FavouriteAircraftData.hxx
${uic_sources}
${qrc_sources}
${qml_sources})
@ -137,7 +141,7 @@ if (HAVE_QT)
target_include_directories(fglauncher PRIVATE ${PROJECT_BINARY_DIR}/src/GUI)
add_library(fgqmlui QQuickDrawable.cxx
QQuickDrawable.hxx
QQuickDrawable.hxx
QtQuickFGCanvasItem.cxx
QtQuickFGCanvasItem.hxx
PropertyItemModel.cxx

148
src/GUI/CarrierDiagram.cxx Normal file
View file

@ -0,0 +1,148 @@
// CarrierDiagram.cxx - part of GUI launcher using Qt5
//
// Written by Stuart Buchanan, started April 2020.
//
// Copyright (C) 2022 Stuart Buchanan <stuart13@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "CarrierDiagram.hxx"
#include <limits>
#include <QPainter>
#include <QDebug>
#include <QVector2D>
#include <QMouseEvent>
#include <Navaids/NavDataCache.hxx>
CarrierDiagram::CarrierDiagram(QQuickItem* pr) :
BaseDiagram(pr)
{
}
void CarrierDiagram::setGeod(QmlGeod geod)
{
setGeod(geod.geod());
}
QmlGeod CarrierDiagram::geod() const
{
return QmlGeod(m_geod);
}
void CarrierDiagram::setGeod(const SGGeod &geod)
{
m_Carrier.clear();
m_geod = geod;
m_projectionCenter = m_geod;
recomputeBounds(true);
emit locationChanged();
}
void CarrierDiagram::setOffsetEnabled(bool offset)
{
if (m_offsetEnabled == offset)
return;
m_offsetEnabled = offset;
recomputeBounds(true);
emit offsetChanged();
}
void CarrierDiagram::setAbeam(bool abeam)
{
if (m_abeam == abeam)
return;
m_abeam = abeam;
recomputeBounds(true);
emit offsetChanged();
}
void CarrierDiagram::setOffsetDistance(QuantityValue distanceNm)
{
if (distanceNm == m_offsetDistance)
return;
m_offsetDistance = distanceNm;
update();
emit offsetChanged();
}
void CarrierDiagram::paintContents(QPainter *painter)
{
QPointF base = project(m_geod);
SGGeod carrierPos = m_geod;
SGGeod aircraftPos = m_geod;
float aircraft_heading = 0;
if (m_offsetEnabled) {
// We don't actually know the eventual orientation of the carrier,
// so we place the aircraft relative to the aircraft carrier icon,
// which has the angled flight deck at 80 degrees (e.g. just N or E)
//
// There are two case:
// - On finals, which will be at -100 degrees, aircraft heading 80
// - Abeam on a left hand circuit, so offset 0 degrees,
// aircraft heading downwind -90 (note that this is parallel to the
// _carrier_, not the angled flightdeck)
float offset_heading = -100;
aircraft_heading = 80;
if (m_abeam) {
offset_heading = 0;
aircraft_heading = -90;
}
double d = m_offsetDistance.convertToUnit(Units::Kilometers).value * 1000;
SGGeod offsetGeod = SGGeodesy::direct(m_geod, offset_heading, d);
QPointF offset = project(offsetGeod);
QPen pen(Qt::green);
pen.setCosmetic(true);
painter->setPen(pen);
painter->drawLine(base, offset);
aircraftPos = offsetGeod;
} else {
// We're at a parking position or on the catapults, so simply rotate to
// match the carrier heading - E
aircraft_heading = 90;
}
paintCarrierIcon(painter, carrierPos, 0.0);
paintAirplaneIcon(painter, aircraftPos, aircraft_heading);
}
void CarrierDiagram::doComputeBounds()
{
extendBounds(project(m_geod));
// project four points around the base location at 20nm to give some
// coverage
for (int i=0; i<4; ++i) {
SGGeod pt = SGGeodesy::direct(m_geod, i * 90, SG_NM_TO_METER * 20.0);
extendBounds(project(pt));
}
if (m_offsetEnabled) {
double d = m_offsetDistance.convertToUnit(Units::Kilometers).value * 1000;
float offset_heading = m_abeam ? 0 : -100; // See above for explanation
SGGeod offsetPos = SGGeodesy::direct(m_geod, offset_heading, d);
extendBounds(project(offsetPos));
}
}

View file

@ -0,0 +1,80 @@
// CarrierDiagram.hxx - part of GUI launcher using Qt5
//
// Written by Stuart Buchanan, started April 2020.
//
// Copyright (C) 2022 Stuart Buchanan <stuart13@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef GUI_CARRIER_DIAGRAM_HXX
#define GUI_CARRIER_DIAGRAM_HXX
#include "BaseDiagram.hxx"
#include "QmlPositioned.hxx"
#include "UnitsModel.hxx"
#include <Navaids/navrecord.hxx>
#include <simgear/math/sg_geodesy.hxx>
class CarrierDiagram : public BaseDiagram
{
Q_OBJECT
Q_PROPERTY(QmlGeod geod READ geod WRITE setGeod NOTIFY locationChanged)
Q_PROPERTY(bool offsetEnabled READ isOffsetEnabled WRITE setOffsetEnabled NOTIFY offsetChanged)
Q_PROPERTY(bool abeam READ isAbeam WRITE setAbeam NOTIFY offsetChanged)
Q_PROPERTY(QuantityValue offsetDistance READ offsetDistance WRITE setOffsetDistance NOTIFY offsetChanged)
public:
CarrierDiagram(QQuickItem* pr = nullptr);
//void setCarrier(qlonglong nav);
//qlonglong navaid() const;
void setGeod(QmlGeod geod);
QmlGeod geod() const;
void setGeod(const SGGeod& geod);
bool isOffsetEnabled() const
{ return m_offsetEnabled; }
bool isAbeam() const
{ return m_abeam; }
void setOffsetEnabled(bool offset);
void setAbeam(bool abeam);
void setOffsetDistance(QuantityValue distance);
QuantityValue offsetDistance() const
{ return m_offsetDistance; }
signals:
void locationChanged();
void offsetChanged();
protected:
void paintContents(QPainter *) override;
void doComputeBounds() override;
private:
FGPositionedRef m_Carrier;
SGGeod m_geod;
bool m_offsetEnabled = false;
bool m_abeam = false;
QuantityValue m_offsetDistance;
};
#endif // of GUI_CARRIER_DIAGRAM_HXX

View file

@ -12,7 +12,7 @@ CarriersLocationModel::CarriersLocationModel(QObject *parent)
SGPropertyNode_ptr localRoot(new SGPropertyNode);
FGAIManager::registerScenarios(localRoot);
// this code encodes some scenario structre, sorry
// this code encodes some scenario structure, sorry
for (auto s : localRoot->getNode("sim/ai/scenarios")->getChildren("scenario")) {
const std::string scenarioId = s->getStringValue("id");
for (auto c : s->getChildren("carrier")) {
@ -26,6 +26,7 @@ void CarriersLocationModel::processCarrier(const string &scenario, SGPropertyNod
const auto name = QString::fromStdString(carrierNode->getStringValue("name"));
const auto pennant = QString::fromStdString(carrierNode->getStringValue("pennant-number"));
const auto tacan = QString::fromStdString(carrierNode->getStringValue("TACAN-channel-ID"));
const auto desc = QString::fromStdString(carrierNode->getStringValue("description"));
SGGeod geod = SGGeod::fromDeg(carrierNode->getDoubleValue("longitude"),
carrierNode->getDoubleValue("latitude"));
@ -38,6 +39,7 @@ void CarriersLocationModel::processCarrier(const string &scenario, SGPropertyNod
QString::fromStdString(scenario),
pennant,
name,
desc,
geod,
tacan,
parkings
@ -65,6 +67,8 @@ QVariant CarriersLocationModel::data(const QModelIndex &index, int role) const
case NameRole: return c.mName;
// case GeodRole: return QVariant::fromValue(c.mInitialLocation);
case IdentRole: return c.mCallsign;
case DescriptionRole: return c.mDescription;
case TypeRole: return "Carrier";
case IconRole: return QPixmap(":/svg/aircraft-carrier");
default:
break;
@ -83,6 +87,7 @@ QHash<int, QByteArray> CarriersLocationModel::roleNames() const
result[NameRole] = "name";
result[IconRole] = "icon";
result[TypeRole] = "type";
result[DescriptionRole] = "description";
result[NavFrequencyRole] = "frequency";
return result;
}

View file

@ -30,7 +30,8 @@ public:
NameRole = Qt::UserRole + 4,
IconRole = Qt::UserRole + 5,
TypeRole = Qt::UserRole + 6,
NavFrequencyRole = Qt::UserRole + 7
NavFrequencyRole = Qt::UserRole + 7,
DescriptionRole = Qt::UserRole + 8
};
int indexOf(const QString name) const;
@ -46,6 +47,7 @@ private:
QString mScenario; // scenario ID for loading
QString mCallsign; // pennant-number
QString mName;
QString mDescription;
SGGeod mInitialLocation;
// icon?
QString mTACAN;

View file

@ -111,7 +111,7 @@ QVariant CatalogListModel::data(const QModelIndex& index, int role) const
desc = tr("The catalog data was not found on the server at the expected location (URL)");
break;
case Delegate::FAIL_VERSION:
desc = tr("The catalog is not comaptible with the version of FlightGear");
desc = tr("The catalog is not compatible with the version of FlightGear");
break;
case Delegate::FAIL_HTTP_FORBIDDEN:
desc = tr("The catalog server is blocking access from some reason (forbidden)");

View file

@ -0,0 +1,62 @@
#include "FavouriteAircraftData.hxx"
#include <QSettings>
#include <memory>
static std::unique_ptr<FavouriteAircraftData> static_instance;
FavouriteAircraftData *FavouriteAircraftData::instance()
{
if (!static_instance) {
static_instance.reset(new FavouriteAircraftData);
}
return static_instance.get();
}
bool FavouriteAircraftData::isFavourite(QUrl u) const
{
return m_favourites.contains(u);
}
bool FavouriteAircraftData::setFavourite(QUrl u, bool b)
{
const auto cur = m_favourites.contains(u);
if (b == cur)
return false;
if (b && !cur) {
m_favourites.append(u);
} else if (!b && cur) {
m_favourites.removeOne(u);
}
emit changed(u);
saveFavourites();
return true;
}
FavouriteAircraftData::FavouriteAircraftData()
{
loadFavourites();
}
void FavouriteAircraftData::loadFavourites()
{
m_favourites.clear();
QSettings settings;
Q_FOREACH(auto v, settings.value("favourite-aircraft").toList()) {
m_favourites.append(v.toUrl());
}
}
void FavouriteAircraftData::saveFavourites()
{
QVariantList favs;
Q_FOREACH(auto u, m_favourites) {
favs.append(u);
}
QSettings settings;
settings.setValue("favourite-aircraft", favs);
}

View file

@ -0,0 +1,31 @@
#ifndef FAVOURITEAIRCRAFTDATA_HXX
#define FAVOURITEAIRCRAFTDATA_HXX
#include <QObject>
#include <QUrl>
#include <QVector>
class FavouriteAircraftData : public QObject
{
Q_OBJECT
public:
static FavouriteAircraftData* instance();
bool isFavourite(QUrl u) const;
bool setFavourite(QUrl u, bool b);
signals:
void changed(QUrl u);
private:
FavouriteAircraftData();
void loadFavourites();
void saveFavourites();
QVector<QUrl> m_favourites;
};
#endif // FAVOURITEAIRCRAFTDATA_HXX

View file

@ -45,6 +45,7 @@
#include "QmlPositioned.hxx"
#include "PixmapImageItem.hxx"
#include "AirportDiagram.hxx"
#include "CarrierDiagram.hxx"
#include "NavaidDiagram.hxx"
#include "RouteDiagram.hxx"
#include "QmlRadioButtonHelper.hxx"
@ -168,6 +169,7 @@ void LauncherController::initQML()
qmlRegisterType<PixmapImageItem>("FlightGear", 1, 0, "PixmapImage");
qmlRegisterType<AirportDiagram>("FlightGear", 1, 0, "AirportDiagram");
qmlRegisterType<CarrierDiagram>("FlightGear", 1, 0, "CarrierDiagram");
qmlRegisterType<NavaidDiagram>("FlightGear", 1, 0, "NavaidDiagram");
qmlRegisterType<RouteDiagram>("FlightGear", 1, 0, "RouteDiagram");
qmlRegisterType<QmlRadioButtonGroup>("FlightGear", 1, 0, "RadioButtonGroup");
@ -389,6 +391,10 @@ QString LauncherController::selectAircraftStateAutomatically()
}
}
if (m_location->isCarrier() && m_location->isAirborneLocation() && m_selectedAircraftInfo->hasState("carrier-approach")) {
return "carrier-approach";
}
if (m_location->isAirborneLocation() && m_selectedAircraftInfo->hasState("approach")) {
return "approach";
}
@ -400,10 +406,14 @@ QString LauncherController::selectAircraftStateAutomatically()
if (m_selectedAircraftInfo->hasState("parking")) {
return "parking";
}
} else {
// also try 'engines-running'?
if (m_selectedAircraftInfo->hasState("take-off"))
return "take-off";
}
if (m_location->isCarrier() && m_selectedAircraftInfo->hasState("carrier-take-off")) {
return "carrier-take-off";
}
if (m_selectedAircraftInfo->hasState("take-off")) {
return "take-off";
}
return {}; // failed to compute, give up

View file

@ -39,9 +39,6 @@
static quint32 CACHE_VERSION = 12;
const int STANDARD_THUMBNAIL_HEIGHT = 128;
//const int STANDARD_THUMBNAIL_WIDTH = 172;
AircraftItem::AircraftItem()
{
}

View file

@ -31,6 +31,7 @@
#include <simgear/structure/exception.hxx>
#include "AirportDiagram.hxx"
#include "CarrierDiagram.hxx"
#include "NavaidDiagram.hxx"
#include "LaunchConfig.hxx"
#include "DefaultAircraftLocator.hxx"
@ -226,6 +227,7 @@ void LocationController::clearLocation()
{
m_locationIsLatLon = false;
m_locationIsCarrier = false;
m_abeam = false;
m_location.clear();
m_carrierName.clear();
m_airportLocation.clear();
@ -365,8 +367,8 @@ void LocationController::showHistoryInSearchModel()
const std::string tutorialICAO = "PHTO"; // C172P tutorial aiurport
// remove them from the recent locations
auto it = std::remove_if(locs.begin(), locs.end(),
[defaultICAO, tutorialICAO](FGPositionedRef pos)
auto it = std::remove_if(locs.begin(), locs.end(),
[defaultICAO, tutorialICAO](FGPositionedRef pos)
{
return (pos->ident() == defaultICAO) || (pos->ident() == tutorialICAO);
});
@ -507,6 +509,12 @@ void LocationController::setUseCarrierFLOLS(bool useCarrierFLOLS)
emit configChanged();
}
void LocationController::setAbeam(bool abeam)
{
m_abeam = abeam;
emit configChanged();
}
void LocationController::restoreLocation(QVariantMap l)
{
clearLocation();
@ -533,6 +541,7 @@ void LocationController::restoreLocation(QVariantMap l)
setCarrierLocation(l.value("carrier").toString());
if (l.contains("carrier-flols")) {
setUseCarrierFLOLS(l.value("carrier-flols").toBool());
setAbeam(l.value("abeam").toBool());
// overwrite value form above, intentionally
m_offsetDistance = l.value("location-carrier-flols-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
} else if (l.contains("carrier-parking")) {
@ -576,6 +585,7 @@ void LocationController::restoreLocation(QVariantMap l)
}
m_onFinal = l.value("location-on-final").toBool();
setAbeam(l.value("abeam").toBool());
m_offsetDistance = l.value("location-apt-final-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
} // of location is an airport
} catch (const sg_exception&) {
@ -593,7 +603,7 @@ bool LocationController::shouldStartPaused() const
if (m_useCarrierFLOLS) {
return true;
}
if (!m_location) {
return false; // defaults to on-ground at the default airport
}
@ -619,6 +629,7 @@ QVariantMap LocationController::saveLocation() const
if (m_useCarrierFLOLS) {
locationSet.insert("carrier-flols", true);
locationSet.insert("location-carrier-flols-distance", QVariant::fromValue(m_offsetDistance));
locationSet.insert("abeam", m_abeam);
} else if (!m_carrierParking.isEmpty()) {
locationSet.insert("carrier-parking", m_carrierParking);
}
@ -628,6 +639,7 @@ QVariantMap LocationController::saveLocation() const
if (m_airportLocation) {
locationSet.insert("location-on-final", m_onFinal);
locationSet.insert("location-apt-final-distance", QVariant::fromValue(m_offsetDistance));
locationSet.insert("abeam", m_abeam);
if (m_useActiveRunway) {
locationSet.insert("location-apt-runway", "ACTIVE");
} else if (m_useAvailableParking) {
@ -675,7 +687,7 @@ void LocationController::setLocationProperties()
"runway-requested" << "navaid-id" << "offset-azimuth-deg" <<
"offset-distance-nm" << "glideslope-deg" <<
"speed-set" << "on-ground" << "airspeed-kt" <<
"airport-id" << "runway" << "parkpos" << "carrier";
"airport-id" << "runway" << "parkpos" << "carrier" << "abeam";
Q_FOREACH(QString s, props) {
SGPropertyNode* c = presets->getChild(s.toStdString());
@ -709,7 +721,8 @@ void LocationController::setLocationProperties()
// treat the FLOLS as a runway, for the purposes of communication with position-init
fgSetString("/sim/presets/runway", "FLOLS");
fgSetDouble("/sim/presets/offset-distance-nm", m_offsetDistance.convertToUnit(Units::NauticalMiles).value);
fgSetBool("/sim/presets/abeam", m_abeam);
applyAltitude();
applyAirspeed();
} else if (!m_carrierParking.isEmpty()) {
fgSetString("/sim/presets/parkpos", m_carrierParking.toStdString());
@ -730,6 +743,7 @@ void LocationController::setLocationProperties()
fgSetString("/sim/presets/airport-id", m_airportLocation->ident());
fgSetBool("/sim/presets/on-ground", true);
fgSetBool("/sim/presets/airport-requested", true);
fgSetBool("/sim/presets/abeam", m_abeam);
const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY));
const bool atParking = (m_detailLocation && (m_detailLocation->type() == FGPositioned::PARKING));
@ -792,11 +806,11 @@ void LocationController::setLocationProperties()
default:
break;
}
// set disambiguation property
globals->get_props()->setIntValue("/sim/presets/navaid-id",
static_cast<int>(m_location->guid()));
applyPositionOffset();
applyAltitude();
applyAirspeed();
@ -847,7 +861,7 @@ void LocationController::applyAltitude()
switch (m_altitude.unit) {
default:
qWarning() << Q_FUNC_INFO << "unsupported altitdue unit";
qWarning() << Q_FUNC_INFO << "unsupported altitude unit";
break;
case Units::FeetMSL:
m_config->setArg("altitude", QString::number(m_altitude.value));
@ -915,6 +929,9 @@ void LocationController::onCollectConfig()
m_config->setArg("runway", QStringLiteral("FLOLS"));
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
m_config->setArg("offset-distance", QString::number(offsetNm));
if (m_abeam) m_config->setArg("carrier-abeam", QStringLiteral("true"));
applyAltitude();
applyAirspeed();
}
@ -1029,6 +1046,8 @@ QString compassPointFromHeading(int heading)
QString LocationController::description() const
{
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
if (!m_location) {
if (m_locationIsLatLon) {
const auto s = simgear::strutils::formatGeodAsString(m_geodLocation,
@ -1039,7 +1058,15 @@ QString LocationController::description() const
if (m_locationIsCarrier) {
QString pennant = m_carriersModel->pennantForIndex(m_carriersModel->indexOf(m_carrierName));
return tr("on carrier %1 (%2)").arg(m_carrierName).arg(pennant);
QString locationToCarrier;
if (m_abeam) {
locationToCarrier = tr("%1nm abeam").arg(offsetNm);
} else if (m_useCarrierFLOLS) {
locationToCarrier = tr("on %1nm final to").arg(offsetNm);
} else {
locationToCarrier = tr("on deck at %1 on").arg(m_carrierParking);
}
return tr("%1 carrier %2 (%3)").arg(locationToCarrier).arg(m_carrierName).arg(pennant);
}
return tr("No location selected");
@ -1049,7 +1076,6 @@ QString LocationController::description() const
name = QString::fromStdString(m_location->name());
name = fixNavaidName(name);
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
if (m_airportLocation) {
const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY));

View file

@ -75,6 +75,7 @@ class LocationController : public QObject
Q_PROPERTY(QStringList carrierParkings READ carrierParkings NOTIFY baseLocationChanged)
Q_PROPERTY(bool useCarrierFLOLS READ useCarrierFLOLS WRITE setUseCarrierFLOLS NOTIFY configChanged)
Q_PROPERTY(QString carrierParking READ carrierParking WRITE setCarrierParking NOTIFY configChanged)
Q_PROPERTY(bool abeam READ abeam WRITE setAbeam NOTIFY configChanged)
// allow collecting the location properties to be disabled, if the
@ -189,6 +190,11 @@ public:
return m_useCarrierFLOLS;
}
bool abeam() const
{
return m_abeam;
}
public slots:
void setOffsetRadial(QuantityValue offsetRadial);
@ -204,6 +210,8 @@ public slots:
void setUseCarrierFLOLS(bool useCarrierFLOLS);
void setAbeam(bool abeam);
Q_SIGNALS:
void descriptionChanged();
void offsetChanged();
@ -261,6 +269,7 @@ private:
bool m_speedEnabled = false;
bool m_altitudeEnabled = false;
bool m_skipFromArgs = false;
bool m_abeam;
bool m_useCarrierFLOLS = false;
QString m_carrierParking;

View file

@ -43,7 +43,10 @@ QVariant ModelDataExtractor::data() const
return m_rawModel.property(uIndex).toVariant();
}
qWarning() << "Unable to convert model data:" << m_rawModel.toString();
if (!m_rawModel.isUndefined() && !m_rawModel.isNull()) {
qWarning() << "Unable to convert model data:" << m_rawModel.toString();
}
return {};
}

View file

@ -14,6 +14,7 @@
#include <Main/globals.hxx>
#include "LocalAircraftCache.hxx"
#include "FavouriteAircraftData.hxx"
using namespace simgear::pkg;
@ -36,6 +37,8 @@ public:
}
protected:
void finishInstall(InstallRef aInstall, StatusCode aReason) override;
void catalogRefreshed(CatalogRef, StatusCode) override
{
}
@ -55,19 +58,9 @@ protected:
}
}
void finishInstall(InstallRef aInstall, StatusCode aReason) override
{
Q_UNUSED(aReason);
if (aInstall->package() == p->packageRef()) {
p->_cachedProps.reset();
p->checkForStates();
p->infoChanged();
}
}
void installStatusChanged(InstallRef aInstall, StatusCode aReason) override
{
Q_UNUSED(aReason);
Q_UNUSED(aReason)
if (aInstall->package() == p->packageRef()) {
p->downloadChanged();
}
@ -85,6 +78,16 @@ private:
QmlAircraftInfo* p;
};
void QmlAircraftInfo::Delegate::finishInstall(InstallRef aInstall, StatusCode aReason)
{
Q_UNUSED(aReason)
if (aInstall->package() == p->packageRef()) {
p->_cachedProps.reset();
p->checkForStates();
p->infoChanged();
}
}
////////////////////////////////////////////////////////////////////////////
@ -109,7 +112,7 @@ static AircraftStateVec readAircraftStates(const SGPath& setXMLPath)
// malformed include or XML, just bail
return {};
}
if (!root->getNode("sim/state")) {
return {};
}
@ -141,7 +144,7 @@ QString humanNameFromStateTag(const std::string& tag)
{
if (tag == "approach") return QObject::tr("On approach");
if ((tag == "take-off") || (tag == "takeoff"))
return QObject::tr("Ready for Take-off");
return QObject::tr("Ready for take-off");
if ((tag == "parked") || (tag == "parking") || (tag == "cold-and-dark"))
return QObject::tr("Parked, cold & dark");
if (tag == "auto")
@ -150,6 +153,10 @@ QString humanNameFromStateTag(const std::string& tag)
return QObject::tr("Cruise");
if (tag == "taxi")
return QObject::tr("Ready to taxi");
if (tag == "carrier-approach")
return QObject::tr("On approach to a carrier");
if (tag == "carrier-take-off")
return QObject::tr("Ready for catapult launch");
qWarning() << Q_FUNC_INFO << "add translation / string for" << QString::fromStdString(tag);
// no mapping, let's use the tag directly
@ -299,6 +306,8 @@ QmlAircraftInfo::QmlAircraftInfo(QObject *parent)
, _delegate(new Delegate(this))
{
qmlRegisterUncreatableType<StatesModel>("FlightGear.Launcher", 1, 0, "StatesModel", "no");
connect(FavouriteAircraftData::instance(), &FavouriteAircraftData::changed,
this, &QmlAircraftInfo::onFavouriteChanged);
}
QmlAircraftInfo::~QmlAircraftInfo()
@ -441,7 +450,7 @@ QVariantList QmlAircraftInfo::previews() const
for (auto p : previews) {
SGPath localPreviewPath = ex->path() / p.path;
if (!localPreviewPath.exists()) {
qWarning() << "missing local preview" << QString::fromStdString(localPreviewPath.utf8Str());
// this happens when the aircraft is being installed, for example
continue;
}
result.append(QUrl::fromLocalFile(QString::fromStdString(localPreviewPath.utf8Str())));
@ -667,6 +676,7 @@ void QmlAircraftInfo::setUri(QUrl u)
emit uriChanged();
emit infoChanged();
emit downloadChanged();
emit favouriteChanged();
}
void QmlAircraftInfo::setVariant(quint32 variant)
@ -691,6 +701,19 @@ void QmlAircraftInfo::setVariant(quint32 variant)
emit variantChanged(_variant);
}
void QmlAircraftInfo::setFavourite(bool favourite)
{
FavouriteAircraftData::instance()->setFavourite(uri(), favourite);
}
void QmlAircraftInfo::onFavouriteChanged(QUrl u)
{
if (u != uri())
return;
emit favouriteChanged();
}
QVariant QmlAircraftInfo::packageAircraftStatus(simgear::pkg::PackageRef p)
{
if (p->hasTag("needs-maintenance")) {
@ -876,5 +899,9 @@ bool QmlAircraftInfo::hasTag(QString tag) const
return false;
}
#include "QmlAircraftInfo.moc"
bool QmlAircraftInfo::favourite() const
{
return FavouriteAircraftData::instance()->isFavourite(uri());
}
#include "QmlAircraftInfo.moc"

View file

@ -59,6 +59,7 @@ class QmlAircraftInfo : public QObject
Q_PROPERTY(bool hasStates READ hasStates NOTIFY infoChanged)
Q_PROPERTY(StatesModel* statesModel READ statesModel NOTIFY infoChanged)
Q_PROPERTY(bool favourite READ favourite WRITE setFavourite NOTIFY favouriteChanged)
public:
explicit QmlAircraftInfo(QObject *parent = nullptr);
virtual ~QmlAircraftInfo();
@ -127,11 +128,14 @@ public:
Q_INVOKABLE bool isAltitudeBelowLimits(QuantityValue speed) const;
Q_INVOKABLE bool hasTag(QString tag) const;
bool favourite() const;
signals:
void uriChanged();
void infoChanged();
void downloadChanged();
void variantChanged(quint32 variant);
void favouriteChanged();
public slots:
@ -139,6 +143,10 @@ public slots:
void setVariant(quint32 variant);
void setFavourite(bool favourite);
private slots:
void onFavouriteChanged(QUrl u);
private:
AircraftItemPtr resolveItem() const;
void checkForStates();

View file

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="87.149193"
height="37.111004"
viewBox="0 0 461.16447 196.37906"
version="1.1"
id="svg8"
inkscape:export-filename="/home/stuart/bitmap.png"
inkscape:export-xdpi="2.8"
inkscape:export-ydpi="2.8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="aircraft-carrier-icon.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="82.435073"
inkscape:cy="-17.019439"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="2493"
inkscape:window-height="1385"
inkscape:window-x="67"
inkscape:window-y="27"
inkscape:window-maximized="1"
fit-margin-top="5"
fit-margin-left="5"
fit-margin-right="5"
fit-margin-bottom="5"
units="px"
scale-x="10" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
style="display:inline"
transform="translate(319.16852,-98.581295)">
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#009bff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -290.00765,177.53052 7.30519,71.36096 55.25252,16.03618 215.076885,1.06907 30.407428,-31.5378 94.930487,-12.2944 -1.4833,-45.43582 -94.559656,-10.69078 -31.890713,-38.48681 -219.155941,2.13815 -8.15808,34.74504 z"
id="Outline"
inkscape:connector-curvature="0"
inkscape:label="#path826"
sodipodi:nodetypes="cccccccccccc" />
<path
style="fill:#009bff;fill-opacity:1;fill-rule:evenodd;stroke:#009bff;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -82.210854,265.48522 0.200595,-46.99016 h -24.786801 l -0.73387,13.23009 -59.39478,-0.54281 -0.6013,33.61805 z"
id="Island"
inkscape:connector-curvature="0"
inkscape:label="#path828"
sodipodi:nodetypes="ccccccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#009aff;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -286.7815,192.23034 4.44986,40.62497"
id="path832"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#009aff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -14.968809,127.55112 -271.812691,64.67922 2.97333,48.86685 300.730074,-75.05926"
id="LandingMarks"
inkscape:connector-curvature="0"
inkscape:label="#path834"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#009aff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -47.355536,198.09657 100.08338,193.60644"
id="Cat1"
inkscape:connector-curvature="0"
inkscape:label="#path836" />
<path
style="fill:none;fill-rule:evenodd;stroke:#009aff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M -44.09832,226.88876 102.04272,209.30242"
id="Cat2"
inkscape:connector-curvature="0"
inkscape:label="#path836"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#009aff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:29.99999994, 4.99999999;stroke-dashoffset:0;stroke-opacity:1"
d="M -273.57231,212.56627 -18.148692,149.13964"
id="Centerline"
inkscape:connector-curvature="0"
inkscape:label="#path853"
sodipodi:nodetypes="cc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -155,59 +155,71 @@ Rectangle {
visible: aircraft.previews.length > 0
}
Grid {
id: ratingGrid
anchors.left: parent.left
Row {
height: ratingGrid.height
width: parent.width
spacing: Style.strutSize
visible: aircraft.ratings !== undefined
rows: 2
columns: 3
rowSpacing: Style.margin
columnSpacing: Style.margin
StyledText {
id: ratingsLabel
text: qsTr("Ratings:")
}
AircraftRating {
title: qsTr("Flight model")
Binding on value {
when: aircraft.ratings !== undefined
value: aircraft.ratings[0]
FavouriteToggleButton {
checked: aircraft.favourite
onToggle: {
aircraft.favourite = on;
}
}
AircraftRating {
title: qsTr("Systems")
Binding on value {
when: aircraft.ratings !== undefined
value: aircraft.ratings[1]
}
}
Grid {
id: ratingGrid
Item {
width: ratingsLabel.width
height: 1
} // placeholder
visible: aircraft.ratings !== undefined
AircraftRating {
title: qsTr("Cockpit")
Binding on value {
when: aircraft.ratings !== undefined
value: aircraft.ratings[2]
}
}
rows: 2
columns: 3
rowSpacing: Style.margin
columnSpacing: Style.margin
AircraftRating {
title: qsTr("Exterior")
Binding on value {
when: aircraft.ratings !== undefined
value: aircraft.ratings[3]
StyledText {
id: ratingsLabel
text: qsTr("Ratings:")
}
}
AircraftRating {
title: qsTr("Flight model")
Binding on value {
when: aircraft.ratings !== undefined
value: aircraft.ratings[0]
}
}
AircraftRating {
title: qsTr("Systems")
Binding on value {
when: aircraft.ratings !== undefined
value: aircraft.ratings[1]
}
}
Item {
width: ratingsLabel.width
height: 1
} // placeholder
AircraftRating {
title: qsTr("Cockpit")
Binding on value {
when: aircraft.ratings !== undefined
value: aircraft.ratings[2]
}
}
AircraftRating {
title: qsTr("Exterior")
Binding on value {
when: aircraft.ratings !== undefined
value: aircraft.ratings[3]
}
}
} // of rating grid
}
StyledText {

View file

@ -109,6 +109,10 @@ Item {
return "%1 - %2 (%3 MHz)".arg(model.ident).arg(model.name).arg(freq);
}
if (model.type === "Carrier") {
return "%1 - %2\n%3".arg(model.ident).arg(model.name).arg(model.description);
}
// general case
return "%1 - %2".arg(model.ident).arg(model.name);
}
@ -225,7 +229,13 @@ Item {
icon: "qrc:///svg/icon-carrier"
onClicked: {
root.showCarriers = true;
root.showCarriers = ! root.showCarriers;
if (root.showCarriers) {
this.icon = "qrc:///svg/icon-airport"
} else {
this.icon = "qrc:///svg/icon-carrier"
}
}
}

View file

@ -6,14 +6,13 @@ import "."
Item {
property alias geod: diagram.geod
NavaidDiagram {
CarrierDiagram {
id: diagram
anchors.fill: parent
offsetEnabled: _location.offsetEnabled
offsetBearing: _location.offsetRadial
offsetEnabled: _location.useCarrierFLOLS
offsetDistance: _location.offsetDistance
heading: _location.heading
abeam: _location.abeam
}
Component.onCompleted: {
@ -22,7 +21,9 @@ Item {
function syncUIFromController()
{
if (_location.useCarrierFLOLS) {
if (_location.abeam) {
abeamRadio.select()
} else if (_location.useCarrierFLOLS) {
flolsRadio.select()
} else {
parkingRadio.select();
@ -46,7 +47,7 @@ Item {
height: selectionGrid.height + Style.margin * 2
// set opacity here only, so we don't make the whole summary pannel translucent
// set opacity here only, so we don't make the whole summary panel translucent
Rectangle {
id: background
anchors.fill: parent
@ -76,7 +77,7 @@ Item {
font.pixelSize: Style.headingFontPixelSize
}
// on FLOLS offset
// on final approach
Row {
anchors.left: parent.left
anchors.leftMargin: Style.margin
@ -89,9 +90,12 @@ Item {
anchors.verticalCenter: parent.verticalCenter
group: radioGroup
onClicked: {
if (selected) _location.useCarrierFLOLS = selected
if (selected) {
_location.useCarrierFLOLS = selected;
_location.abeam = false;
}
}
selected: _location.useCarrierFLOLS
selected: _location.useCarrierFLOLS && (!_location.abeam)
}
StyledText {
@ -101,6 +105,47 @@ Item {
}
}
// Abeam the FLOLS
Row {
anchors.left: parent.left
anchors.leftMargin: Style.margin
anchors.right: parent.right
anchors.rightMargin: Style.margin
spacing: Style.margin
RadioButton {
id: abeamRadio
anchors.verticalCenter: parent.verticalCenter
group: radioGroup
onClicked: {
if (selected) _location.abeam = _location.useCarrierFLOLS = true
}
selected: _location.abeam
}
StyledText {
text: qsTr("Abeam carrier at 180 degrees")
anchors.verticalCenter: parent.verticalCenter
enabled: abeamRadio.selected
}
}
// Offset selection
readonly property bool offsetEnabled: (flolsRadio.selected || abeamRadio.selected)
Row {
anchors.left: parent.left
anchors.leftMargin: Style.margin
anchors.right: parent.right
anchors.rightMargin: Style.margin
spacing: Style.margin
Item {
height: 1; width: Style.strutSize
}
NumericalEdit {
id: offsetNmEdit
quantity: _location.offsetDistance
@ -109,13 +154,13 @@ Item {
unitsMode: Units.Distance
live: true
anchors.verticalCenter: parent.verticalCenter
enabled: flolsRadio.selected
enabled: selectionGrid.offsetEnabled
}
StyledText {
text: qsTr(" from the FLOLS (aka the ball)")
anchors.verticalCenter: parent.verticalCenter
enabled: flolsRadio.selected
enabled: selectionGrid.offsetEnabled
}
Item {
@ -124,7 +169,7 @@ Item {
ToggleSwitch {
id: airspeedToggle
enabled: flolsRadio.selected
enabled: selectionGrid.offsetEnabled
checked: _location.speedEnabled
onCheckedChanged: _location.speedEnabled = checked;
anchors.verticalCenter: parent.verticalCenter
@ -134,12 +179,23 @@ Item {
id: airspeedSpinbox
label: qsTr("Airspeed:")
unitsMode: Units.SpeedWithoutMach
enabled: _location.speedEnabled && flolsRadio.selected
enabled: _location.speedEnabled && selectionGrid.offsetEnabled
quantity: _location.airspeed
onCommit: _location.airspeed = newValue
anchors.verticalCenter: parent.verticalCenter
}
} // of FLOLS row
Item {
height: 1; width: Style.strutSize
}
LocationAltitudeRow
{
enabled: selectionGrid.offsetEnabled
width: parent.width
}
} // of Offset selection
// parking row
Row {
@ -159,10 +215,11 @@ Item {
onClicked: {
if (selected) parkingChoice.setLocation();
}
selected : (! _location.abeam) && (! _location.useCarrierFLOLS)
}
StyledText {
text: qsTr("Parking")
text: qsTr("On deck")
anchors.verticalCenter: parent.verticalCenter
enabled: parkingRadio.selected
}

View file

@ -90,8 +90,9 @@ Item {
var defaultValue = ("defaultValue" in root) ? root.defaultValue : undefined;
var rawValue = _config.getValueForKey("", root.setting, defaultValue);
// console.warn("restoring state for " + root.setting + ", got raw value " + rawValue + " with type " + typeof(rawValue))
if (rawValue !== undefined) {
// console.warn("restoring state for " + root.setting + ", got raw value " + rawValue + " with type " + typeof(rawValue))
if (rawValue !== defaultValue) {
setValue(rawValue);
}
}
@ -99,6 +100,6 @@ Item {
function setValue(newValue)
{
// hook method so controls can override
this.value = newValue
root.value = newValue
}
}

View file

@ -155,6 +155,7 @@
<file alias="icon-list-view">assets/icons8-menu.svg</file>
<file alias="icon-hide">assets/icons8-hide-50.png</file>
<file alias="icon-carrier">assets/icons8-cargo-ship-50.png</file>
<file alias="aircraft-carrier">assets/aircraft-carrier-icon.png</file>
<file alias="icon-airport">assets/icons8-airport-50.png</file>
<file alias="aircraft-carrier">assets/aircraft-carrier-icon.svg</file>
</qresource>
</RCC>

View file

@ -70,3 +70,5 @@
#cmakedefine ENABLE_COMPOSITOR
#cmakedefine ENABLE_SWIFT
#cmakedefine HAVE_SENTRY

View file

@ -1,8 +1,7 @@
include(FlightGearComponent)
IF(APPLE)
set(EVENT_INPUT_SOURCES FGMacOSXEventInput.cxx)
set(EVENT_INPUT_HEADERS FGMacOSXEventInput.hxx)
# no Mac implemention, use HID
elseif(WIN32)
# no Win32 specific implementation, at least for now
else()

View file

@ -176,12 +176,12 @@ void FGAxisEvent::fire( FGEventData & eventData )
// We need a copy of the FGEventData struct to set the new value and to avoid side effects
FGEventData ed = eventData;
if( fabs(ed.value) < deadband )
ed.value = 0.0;
if( minRange != maxRange )
ed.value = 2.0*(eventData.value-minRange)/(maxRange-minRange)-1.0;
if( fabs(ed.value) < deadband )
ed.value = 0.0;
if (interpolater) {
if ((ed.value < 0.0) && mirrorInterpolater) {
// mirror the positive interpolation for negative values

View file

@ -312,12 +312,16 @@ private:
std::string _hidPath;
hid_device* _device = nullptr;
bool _haveNumberedReports = false;
bool _debugRaw = false;
/// set if we parsed the device description our XML
/// instead of from the USB data. Useful on Windows where the data
/// is inaccessible, or devices with broken descriptors
bool _haveLocalDescriptor = false;
/// allow specifying the descriptor as hex bytes in XML
std::vector<uint8_t>_rawXMLDescriptor;
// all sets which will be send on the next update() call.
std::set<Report*> _dirtyReports;
};
@ -384,6 +388,17 @@ void FGHIDDevice::Configure(SGPropertyNode_ptr node)
defineReport(report);
}
}
if (node->hasChild("hid-raw-descriptor")) {
_rawXMLDescriptor = simgear::strutils::decodeHex(node->getStringValue("hid-raw-descriptor"));
if (debugEvents) {
SG_LOG(SG_INPUT, SG_INFO, GetUniqueName() << " will configure using XML-defined raw HID descriptor");
}
}
if (node->getBoolValue("hid-debug-raw")) {
_debugRaw = true;
}
}
bool FGHIDDevice::Open()
@ -395,6 +410,19 @@ bool FGHIDDevice::Open()
return false;
}
#if !defined(SG_WINDOWS)
if (_rawXMLDescriptor.empty()) {
_rawXMLDescriptor.resize(2048);
int descriptorSize = hid_get_descriptor(_device, _rawXMLDescriptor.data(), _rawXMLDescriptor.size());
if (descriptorSize <= 0) {
SG_LOG(SG_INPUT, SG_WARN, "HID: " << GetUniqueName() << " failed to read HID descriptor");
return false;
}
_rawXMLDescriptor.resize(descriptorSize);
}
#endif
if (!_haveLocalDescriptor) {
bool ok = parseUSBHIDDescriptor();
if (!ok)
@ -422,26 +450,22 @@ bool FGHIDDevice::Open()
bool FGHIDDevice::parseUSBHIDDescriptor()
{
#if defined(SG_WINDOWS)
SG_LOG(SG_INPUT, SG_ALERT, GetUniqueName() << ": on Windows, there is no way to extract the UDB-HID report descriptor. "
<< "\nPlease supply the report descriptor in the device XML configuration.");
return false;
#endif
unsigned char reportDescriptor[1024];
int descriptorSize = hid_get_descriptor(_device, reportDescriptor, 1024);
if (descriptorSize <= 0) {
SG_LOG(SG_INPUT, SG_WARN, "HID: " << GetUniqueName() << " failed to read HID descriptor");
if (_rawXMLDescriptor.empty()) {
SG_LOG(SG_INPUT, SG_ALERT, GetUniqueName() << ": on Windows, there is no way to extract the UDB-HID report descriptor. "
<< "\nPlease supply the report descriptor in the device XML configuration.");
SG_LOG(SG_INPUT, SG_ALERT, "See this page:<> for information on extracting the report descriptor on Windows");
return false;
}
#endif
if (debugEvents) {
if (_debugRaw) {
SG_LOG(SG_INPUT, SG_INFO, "\nHID: descriptor for:" << GetUniqueName());
{
std::ostringstream byteString;
for (int i=0; i<descriptorSize; ++i) {
byteString << hexTable[reportDescriptor[i] >> 4];
byteString << hexTable[reportDescriptor[i] & 0x0f];
for (auto i=0; i<_rawXMLDescriptor.size(); ++i) {
byteString << hexTable[_rawXMLDescriptor[i] >> 4];
byteString << hexTable[_rawXMLDescriptor[i] & 0x0f];
byteString << " ";
}
SG_LOG(SG_INPUT, SG_INFO, "\tbytes: " << byteString.str());
@ -449,7 +473,7 @@ bool FGHIDDevice::parseUSBHIDDescriptor()
}
hid_item* rootItem = nullptr;
hid_parse_reportdesc(reportDescriptor, descriptorSize, &rootItem);
hid_parse_reportdesc(_rawXMLDescriptor.data(), _rawXMLDescriptor.size(), &rootItem);
if (debugEvents) {
SG_LOG(SG_INPUT, SG_INFO, "\nHID: scan for:" << GetUniqueName());
}
@ -645,6 +669,18 @@ void FGHIDDevice::sendReport(Report* report) const
}
reportLength /= 8;
if (_debugRaw) {
std::ostringstream byteString;
for (size_t i=0; i<reportLength; ++i) {
byteString << hexTable[reportBytes[i] >> 4];
byteString << hexTable[reportBytes[i] & 0x0f];
byteString << " ";
}
SG_LOG(SG_INPUT, SG_INFO, "sending bytes: " << byteString.str());
}
// send the data, based on the report type
if (report->type == HID::ReportType::Feature) {
hid_send_feature_report(_device, reportBytes, reportLength + 1);
@ -663,7 +699,7 @@ void FGHIDDevice::processInputReport(Report* report, unsigned char* data,
size_t length,
double dt, int keyModifiers)
{
if (debugEvents) {
if (_debugRaw) {
SG_LOG(SG_INPUT, SG_INFO, GetName() << " FGHIDDeivce received input report:" << (int) report->number << ", len=" << length);
{
std::ostringstream byteString;
@ -698,7 +734,7 @@ void FGHIDDevice::processInputReport(Report* report, unsigned char* data,
if (!item->event)
continue;
if (debugEvents) {
if (_debugRaw) {
SG_LOG(SG_INPUT, SG_INFO, "\titem:" << item->name << " = " << value);
}
@ -713,7 +749,7 @@ void FGHIDDevice::SendFeatureReport(unsigned int reportId, const std::string& da
return;
}
if (debugEvents) {
if (_debugRaw) {
SG_LOG(SG_INPUT, SG_INFO, GetName() << ": FGHIDDevice: Sending feature report:" << (int) reportId << ", len=" << data.size());
{
std::ostringstream byteString;

View file

@ -1,650 +0,0 @@
// FGMacOSXEventInput.cxx -- handle event driven input devices for Mac OS X
//
// Written by Tatsuhiro Nishioka, started Aug. 2009.
//
// Copyright (C) 2009 Tasuhiro Nishioka, tat <dot> fgmacosx <at> gmail <dot> com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#include "FGMacOSXEventInput.hxx"
#include <simgear/sg_inlines.h>
#include <cstdlib>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/hid/IOHIDLib.h>
#include <IOKit/HID/IOHIDKeys.h>
#include <simgear/structure/exception.hxx>
static std::string nameForUsage(uint32_t usagePage, uint32_t usage)
{
if (usagePage == kHIDPage_GenericDesktop) {
switch (usage) {
case kHIDUsage_GD_Joystick: return "joystick";
case kHIDUsage_GD_Wheel: return "wheel";
case kHIDUsage_GD_Dial: return "dial";
case kHIDUsage_GD_Hatswitch: return "hat";
case kHIDUsage_GD_Slider: return "slider";
case kHIDUsage_GD_Rx: return "x-rotate";
case kHIDUsage_GD_Ry: return "y-rotate";
case kHIDUsage_GD_Rz: return "z-rotate";
case kHIDUsage_GD_X: return "x-translate";
case kHIDUsage_GD_Y: return "y-translate";
case kHIDUsage_GD_Z: return "z-translate";
default:
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID generic desktop usage:" << usage);
}
} else if (usagePage == kHIDPage_Simulation) {
switch (usage) {
default:
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID simulation usage:" << usage);
}
} else if (usagePage == kHIDPage_Consumer) {
switch (usage) {
default:
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID consumer usage:" << usage);
}
} else if (usagePage == kHIDPage_AlphanumericDisplay) {
switch (usage) {
case kHIDUsage_AD_AlphanumericDisplay: return "alphanumeric";
case kHIDUsage_AD_CharacterReport: return "character-report";
case kHIDUsage_AD_DisplayData: return "display-data";
case 0x46: return "display-brightness";
default:
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID alphanumeric usage:" << usage);
}
} else if (usagePage == kHIDPage_LEDs) {
switch (usage) {
case kHIDUsage_LED_GenericIndicator: return "led-misc";
case kHIDUsage_LED_Pause: return "led-pause";
default:
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID LED usage:" << usage);
}
} else if (usagePage == kHIDPage_Button) {
std::stringstream os;
os << "button-" << usage;
return os.str();
} else if (usagePage >= kHIDPage_VendorDefinedStart) {
return "vendor";
} else {
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID usage page:" << usagePage);
}
return "unknown";
}
class FGMacOSXEventInputPrivate
{
public:
IOHIDManagerRef hidManager = nullptr;
FGMacOSXEventInput* p = nullptr;
double currentDt = 0.0;
int currentModifiers = 0;
void matchedDevice(IOHIDDeviceRef device);
void removedDevice(IOHIDDeviceRef device);
void iterateDevices(CFSetRef matchingSet);
std::string getDeviceStringProperty(IOHIDDeviceRef device, CFStringRef hidProp);
bool getDeviceIntProperty(IOHIDDeviceRef device, CFStringRef hidProp, int& value);
};
static void deviceMatchingCallback(
void * inContext, // context from IOHIDManagerRegisterDeviceMatchingCallback
IOReturn inResult, // the result of the matching operation
void * inSender, // the IOHIDManagerRef for the new device
IOHIDDeviceRef inIOHIDDeviceRef // the new HID device
)
{
// printf("%s(context: %p, result: %p, sender: %p, device: %p).\n",
// __PRETTY_FUNCTION__, inContext, (void *) inResult, inSender, (void*) inIOHIDDeviceRef);
FGMacOSXEventInputPrivate* p = static_cast<FGMacOSXEventInputPrivate*>(inContext);
p->matchedDevice(inIOHIDDeviceRef);
} // Handle_DeviceMatchingCallback
static void deviceRemovalCallback(
void * inContext, // context from IOHIDManagerRegisterDeviceMatchingCallback
IOReturn inResult, // the result of the matching operation
void * inSender, // the IOHIDManagerRef for the new device
IOHIDDeviceRef inIOHIDDeviceRef // the new HID device
)
{
// printf("%s(context: %p, result: %p, sender: %p, device: %p).\n",
// __PRETTY_FUNCTION__, inContext, (void *) inResult, inSender, (void*) inIOHIDDeviceRef);
FGMacOSXEventInputPrivate* p = static_cast<FGMacOSXEventInputPrivate*>(inContext);
p->removedDevice(inIOHIDDeviceRef);
} // Handle_DeviceMatchingCallback
//
// FGMacOSXInputDevice implementation
//
//
// FGMacOSXInputDevice
// Mac OS X specific FGInputDevice
//
class FGMacOSXInputDevice : public FGInputDevice {
public:
FGMacOSXInputDevice(IOHIDDeviceRef hidRef,
FGMacOSXEventInputPrivate* subsys);
virtual ~FGMacOSXInputDevice();
bool Open() override;
void Close() override;
virtual void update(double dt);
virtual const char *TranslateEventName(FGEventData &eventData);
virtual void Send( const char * eventName, double value );
virtual void SendFeatureReport(unsigned int reportId, const std::string& data);
virtual void AddHandledEvent( FGInputEvent_ptr handledEvent );
void drainQueue();
void handleValue(IOHIDValueRef value);
private:
void buildElementNameDictionary();
std::string nameForHIDElement(IOHIDElementRef element) const;
IOHIDDeviceRef _hid = nullptr;
IOHIDQueueRef _queue = nullptr;
FGMacOSXEventInputPrivate* _subsystem;
typedef std::map<std::string, IOHIDElementRef> NameElementDict;
NameElementDict namedElements;
};
#if 0
static void valueAvailableCallback(void * inContext, // context from IOHIDQueueRegisterValueAvailableCallback
IOReturn inResult, // the inResult
void * inSender // IOHIDQueueRef of the queue
) {
FGMacOSXInputDevice* dev = static_cast<FGMacOSXInputDevice*>(inContext);
dev->drainQueue();
} // Handle_ValueAvailableCallback
#endif
//
// FGMacOSXEventInput implementation
//
FGMacOSXEventInput::FGMacOSXEventInput() :
FGEventInput(),
d(new FGMacOSXEventInputPrivate)
{
d->p = this; // store back pointer to outer object on pimpl
SG_LOG(SG_INPUT, SG_DEBUG, "FGMacOSXEventInput created");
}
FGMacOSXEventInput::~FGMacOSXEventInput()
{
}
void FGMacOSXEventInput::init()
{
// everything is deffered until postinit since we need Nasal
}
void FGMacOSXEventInput::postinit()
{
d->hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
// set the HID device matching dictionary
IOHIDManagerSetDeviceMatching( d->hidManager, NULL /* all devices */);
IOHIDManagerRegisterDeviceMatchingCallback(d->hidManager, deviceMatchingCallback, d.get());
IOHIDManagerRegisterDeviceRemovalCallback(d->hidManager, deviceRemovalCallback, d.get());
IOHIDManagerOpen(d->hidManager, kIOHIDOptionsTypeNone);
IOHIDManagerScheduleWithRunLoop(d->hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
}
void FGMacOSXEventInput::shutdown()
{
FGEventInput::shutdown();
if (d->hidManager) {
IOHIDManagerClose(d->hidManager, kIOHIDOptionsTypeNone);
IOHIDManagerUnscheduleFromRunLoop(d->hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
CFRelease(d->hidManager);
}
}
//
// read all elements in each input device
//
void FGMacOSXEventInput::update(double dt)
{
d->currentDt = dt;
d->currentModifiers = fgGetKeyModifiers();
FGEventInput::update(dt);
}
void FGMacOSXEventInputPrivate::matchedDevice(IOHIDDeviceRef device)
{
std::string productName = getDeviceStringProperty(device, CFSTR(kIOHIDProductKey));
std::string manufacturer = getDeviceStringProperty(device, CFSTR(kIOHIDManufacturerKey));
std::string serial = getDeviceStringProperty(device, CFSTR(kIOHIDSerialNumberKey));
// filter out keyboard and mouse devices : this is especially important for
// Catalina TCC hardening to avoid secuirty alerts
int usagePage, usage;
getDeviceIntProperty(device, CFSTR(kIOHIDPrimaryUsagePageKey), usagePage);
getDeviceIntProperty(device, CFSTR(kIOHIDPrimaryUsageKey), usage);
if (usagePage == kHIDPage_GenericDesktop) {
if ((usage == kHIDUsage_GD_Keyboard) || (usage == kHIDUsage_GD_Mouse)) {
SG_LOG(SG_INPUT, SG_INFO, "MacOSX-EventInput: skipping device:" << productName << "( from " << manufacturer << ") becuase it is a keyboard or mouse");
return;
}
}
SG_LOG(SG_INPUT, SG_DEBUG, "MacOSX-EventInput: matched device:" << productName << "( from " << manufacturer << ")");
// allocate a Mac input device, and add to the base class to see if we have
// a config
FGMacOSXInputDevice* macInputDevice = new FGMacOSXInputDevice(device, this);
macInputDevice->SetName(manufacturer + " " + productName);
macInputDevice->SetSerialNumber(serial);
p->AddDevice(macInputDevice);
}
void FGMacOSXEventInputPrivate::removedDevice(IOHIDDeviceRef device)
{
std::string productName = getDeviceStringProperty(device, CFSTR(kIOHIDProductKey));
std::string manufacturer = getDeviceStringProperty(device, CFSTR(kIOHIDManufacturerKey));
// see if we have an entry for the device
}
std::string FGMacOSXEventInputPrivate::getDeviceStringProperty(IOHIDDeviceRef device, CFStringRef hidProp)
{
CFStringRef prop = (CFStringRef) IOHIDDeviceGetProperty(device, hidProp);
if ((prop == nullptr)|| (CFGetTypeID(prop) != CFStringGetTypeID())) {
return std::string();
}
size_t len = CFStringGetLength(prop);
if (len == 0) {
return std::string();
}
char* buffer = static_cast<char*>(malloc(len + 1)); // + 1 for the null byte
Boolean ok = CFStringGetCString(prop, buffer, len + 1, kCFStringEncodingUTF8);
if (!ok) {
SG_LOG(SG_INPUT, SG_WARN, "string conversion failed");
}
std::string result(buffer, len);
free(buffer);
return result;
}
bool FGMacOSXEventInputPrivate::getDeviceIntProperty(IOHIDDeviceRef device, CFStringRef hidProp, int& value)
{
CFTypeRef prop = IOHIDDeviceGetProperty(device, hidProp);
if (CFGetTypeID(prop) != CFNumberGetTypeID()) {
return false;
}
int32_t v;
Boolean result = CFNumberGetValue((CFNumberRef) prop, kCFNumberSInt32Type, &v);
value = v;
return result;
}
void FGMacOSXEventInputPrivate::iterateDevices(CFSetRef matchingSet)
{
size_t numDevices = CFSetGetCount(matchingSet);
IOHIDDeviceRef* devs = static_cast<IOHIDDeviceRef*>(::malloc(numDevices * sizeof(IOHIDDeviceRef)));
CFSetGetValues(matchingSet, (const void **) devs);
for (size_t i=0; i < numDevices; ++i) {
matchedDevice(devs[i]);
}
free(devs);
}
FGMacOSXInputDevice::FGMacOSXInputDevice(IOHIDDeviceRef hidRef,
FGMacOSXEventInputPrivate* subsys)
{
_hid = hidRef;
CFRetain(_hid);
_subsystem = subsys;
}
FGMacOSXInputDevice::~FGMacOSXInputDevice()
{
if (_queue) {
CFRelease(_queue);
}
CFRelease(_hid);
}
bool FGMacOSXInputDevice::Open()
{
IOReturn result = IOHIDDeviceOpen(_hid, kIOHIDOptionsTypeNone);
if (result != kIOReturnSuccess) {
return false;
}
IOHIDDeviceScheduleWithRunLoop(_hid, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
// IOHIDQueueRegisterValueAvailableCallback(_queue, valueAvailableCallback, this);
CFIndex maxDepth = 128;
_queue = IOHIDQueueCreate(kCFAllocatorDefault, _hid, maxDepth, kIOHIDOptionsTypeNone);
IOHIDQueueScheduleWithRunLoop(_queue, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDQueueStart(_queue);
return true;
}
void FGMacOSXInputDevice::buildElementNameDictionary()
{
// copy all elements from the device
CFArrayRef elements = IOHIDDeviceCopyMatchingElements(_hid, NULL,
kIOHIDOptionsTypeNone);
CFIndex count = CFArrayGetCount(elements);
typedef std::map<std::string, unsigned int> NameCountMap;
NameCountMap nameCounts;
std::set<IOHIDElementCookie> seenCookies;
for (int i = 0; i < count; ++i) {
IOHIDElementRef element = (IOHIDElementRef) CFArrayGetValueAtIndex(elements, i);
IOHIDElementCookie cookie = IOHIDElementGetCookie(element);
if (seenCookies.find(cookie) != seenCookies.end()) {
// skip duplicate match of this element;
continue;
}
seenCookies.insert(cookie);
// bool isWrapping = IOHIDElementIsWrapping(element);
IOHIDElementType ty = IOHIDElementGetType(element);
if (ty == kIOHIDElementTypeCollection)
{
continue;
}
uint32_t page = IOHIDElementGetUsagePage(element);
uint32_t usage = IOHIDElementGetUsage(element);
bool isRelative = IOHIDElementIsRelative(element);
// compute the name for the element
std::string name = nameForUsage(page, usage);
if (isRelative) {
name = "rel-" + name; // prefix relative elements
}
NameCountMap::iterator it = nameCounts.find(name);
unsigned int nameCount;
std::string finalName = name;
if (it == nameCounts.end()) {
nameCounts[name] = nameCount = 1;
} else {
// check if we have a collison but different element types, eg
// input & feature. In which case, prefix the feature one since
// we assume it's the input one which is more interesting.
IOHIDElementRef other = namedElements[name];
IOHIDElementType otherTy = IOHIDElementGetType(other);
if (otherTy != ty) {
// types mismatch
if (otherTy == kIOHIDElementTypeFeature) {
namedElements[name] = element;
element = other;
finalName = "feature-" + name;
} else if (ty == kIOHIDElementTypeFeature) {
finalName = "feature-" + name;
}
nameCount = 1;
} else {
// duplicate element, append ordinal suffix
std::stringstream os;
os << name << "-" << it->second;
finalName = os.str();
nameCount = ++(it->second);
}
}
CFRetain(element);
namedElements[finalName] = element;
if (nameCount == 2) {
// we have more than one entry for this name, so ensures
// the first item is availabe with the -0 suffix
std::stringstream os;
os << name << "-0";
namedElements[os.str()] = namedElements[name];
CFRetain(namedElements[os.str()]);
}
}
// HID debugging code
#if 0
NameElementDict::const_iterator it;
for (it = namedElements.begin(); it != namedElements.end(); ++it) {
int report = IOHIDElementGetReportID(it->second);
int reportCount = IOHIDElementGetReportCount(it->second);
int reportSize = IOHIDElementGetReportSize(it->second);
CFTypeRef sizeProp = IOHIDElementGetProperty(it->second, CFSTR(kIOHIDElementDuplicateIndexKey));
if (sizeProp) {
CFShow(sizeProp);
}
bool isArray = IOHIDElementIsArray(it->second);
if (isArray) {
SG_LOG(SG_INPUT, SG_INFO, "YYYYYYYYYYYYYYYYYY");
}
SG_LOG(SG_INPUT, SG_INFO, "\t" << it->first << " report ID " << report << " /count=" << reportCount
<< " ;sz=" << reportSize);
}
#endif
CFRelease(elements);
}
void FGMacOSXInputDevice::Close()
{
// leaking these otherwise we get a crash shutting down the HID-manager
// object. Don't understand why that should be the case
#if 0
for (auto it : namedElements) {
CFRelease(it.second);
}
#endif
namedElements.clear();
IOHIDQueueStop(_queue);
IOHIDQueueUnscheduleFromRunLoop(_queue, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDDeviceUnscheduleFromRunLoop(_hid, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDDeviceClose(_hid, kIOHIDOptionsTypeNone);
}
void FGMacOSXInputDevice::AddHandledEvent( FGInputEvent_ptr handledEvent )
{
SG_LOG(SG_INPUT, SG_DEBUG, "adding event:" << handledEvent->GetName());
if (namedElements.empty()) {
buildElementNameDictionary();
}
NameElementDict::iterator it = namedElements.find(handledEvent->GetName());
if (it == namedElements.end()) {
SG_LOG(SG_INPUT, SG_WARN, "device does not have any element with name:" << handledEvent->GetName());
return;
}
IOHIDQueueAddElement(_queue, it->second);
FGInputDevice::AddHandledEvent(handledEvent);
}
void FGMacOSXInputDevice::update(double dt)
{
SG_UNUSED(dt);
drainQueue();
FGInputDevice::update(dt);
}
void FGMacOSXInputDevice::drainQueue()
{
do {
IOHIDValueRef valueRef = IOHIDQueueCopyNextValueWithTimeout( _queue, 0. );
if ( !valueRef ) break;
handleValue(valueRef);
CFRelease( valueRef );
} while ( 1 ) ;
}
void FGMacOSXInputDevice::handleValue(IOHIDValueRef value)
{
IOHIDElementRef element = IOHIDValueGetElement(value);
CFIndex val;
// need report count to know if we have to handle this specially
int reportCount = IOHIDElementGetReportCount(element);
if (reportCount > 1) {
// for a repeated element, we need to read the final value of
// the data bytes (even though this will be the lowest numbered name
// for this element.
int bitSize = IOHIDElementGetReportSize(element);
const uint8_t* bytes= IOHIDValueGetBytePtr(value);
size_t byteLen = IOHIDValueGetLength(value);
uint8_t finalByte = bytes[byteLen - 1];
if (bitSize == 8) {
val = (int8_t) finalByte; // force sign extension
} else if (bitSize == 4) {
int8_t b = finalByte >> 4; // high nibble
if (b & 0x08) {
// manually sign extend
b |= 0xf0; // set all bits except the low nibble
}
val = b;
} else {
throw sg_io_exception("Unhandled bit size in MacoSXHID");
}
} else {
val = IOHIDValueGetIntegerValue(value);
}
// supress spurious 0-valued relative events
std::string name = nameForHIDElement(element);
if ((name.find("rel-") == 0) && (val == 0)) {
return;
}
FGMacOSXEventData eventData(name, val,
_subsystem->currentDt,
_subsystem->currentModifiers);
HandleEvent(eventData);
}
std::string FGMacOSXInputDevice::nameForHIDElement(IOHIDElementRef element) const
{
NameElementDict::const_iterator it;
for (it = namedElements.begin(); it != namedElements.end(); ++it) {
if (it->second == element) {
return it->first;
}
}
throw sg_exception("Unknown HID element");
}
const char *FGMacOSXInputDevice::TranslateEventName(FGEventData &eventData)
{
FGMacOSXEventData &macEvent = (FGMacOSXEventData &)eventData;
return macEvent.name.c_str();
}
//
// Outputs value to an writable element (like LEDElement)
//
void FGMacOSXInputDevice::Send(const char *eventName, double value)
{
NameElementDict::const_iterator it = namedElements.find(eventName);
if (it == namedElements.end()) {
SG_LOG(SG_INPUT, SG_WARN, "FGMacOSXInputDevice::Send: unknown element:" << eventName);
return;
}
CFIndex cfVal = value;
uint64_t timestamp = 0;
IOHIDValueRef valueRef = IOHIDValueCreateWithIntegerValue(kCFAllocatorDefault,
it->second, timestamp, cfVal);
IOHIDDeviceSetValue(_hid, it->second, valueRef);
CFRelease(valueRef);
}
void FGMacOSXInputDevice::SendFeatureReport(unsigned int reportId, const std::string& data)
{
string d(data);
if (reportId > 0) {
// prefix with report number
d.insert(d.begin(), static_cast<char>(reportId));
}
size_t len = d.size();
const uint8_t* bytes = (const uint8_t*) d.data();
std::stringstream ss;
for (int i=0; i<len; ++i) {
ss << static_cast<int>(bytes[i]);
ss << ":";
}
SG_LOG(SG_INPUT, SG_INFO, "report " << reportId << " sending " << ss.str());
IOReturn res = IOHIDDeviceSetReport(_hid,
kIOHIDReportTypeFeature,
reportId, /* Report ID*/
bytes, len);
if (res != kIOReturnSuccess) {
SG_LOG(SG_INPUT, SG_WARN, "failed to send feature report" << reportId);
}
}

View file

@ -1,67 +0,0 @@
// FGMacOSXEventInput.hxx -- handle event driven input devices for Mac OS X
//
// Written by Tatsuhiro Nishioka, started Aug. 2009.
//
// Copyright (C) 2009 Tasuhiro Nishioka, tat <dot> fgmacosx <at> gmail <dot> com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef __FGMACOSXEVENTINPUT_HXX_
#define __FGMACOSXEVENTINPUT_HXX_
#include <string>
#include <vector>
#include <map>
#include <memory>
#include "FGEventInput.hxx"
class FGMacOSXEventInputPrivate;
struct FGMacOSXEventData : public FGEventData {
FGMacOSXEventData(std::string name, double value, double dt, int modifiers) :
FGEventData(value, dt, modifiers), name(name) {}
std::string name;
};
//
// Mac OS X specific FGEventInput
//
class FGMacOSXEventInput : public FGEventInput
{
public:
FGMacOSXEventInput();
virtual ~FGMacOSXEventInput();
// Subsystem API.
void init() override;
void postinit() override;
void shutdown() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "input-event"; }
private:
friend class FGMacOSXEventInputPrivate;
std::unique_ptr<FGMacOSXEventInputPrivate> d;
};
#endif

View file

@ -561,7 +561,7 @@ void FGMouseInput::doMouseClick (int b, int updown, int x, int y, bool mainWindo
SGSceneryPicks pickList = globals->get_renderer()->pick(windowPos);
if( updown != MOUSE_BUTTON_DOWN )
if( updown == MOUSE_BUTTON_UP )
{
// Execute the mouse up event in any case, may be we should
// stop processing here?
@ -572,7 +572,7 @@ void FGMouseInput::doMouseClick (int b, int updown, int x, int y, bool mainWindo
{
SGPickCallbackPtr& cb = callbacks.front();
const SGSceneryPick* pick = getPick(pickList, cb);
cb->buttonReleased(ea->getModKeyMask(), *ea, pick ? &pick->info : 0);
cb->buttonReleased(ea->getModKeyMask(), *ea, pick ? &pick->info : nullptr);
callbacks.pop_front();
}

View file

@ -38,10 +38,9 @@
#ifdef WITH_EVENTINPUT
#if defined ( SG_MAC )
#include "FGMacOSXEventInput.hxx"
#define INPUTEVENT_CLASS FGMacOSXEventInput
// we use HID
#elif defined(SG_WINDOWS)
// we use HID
#else
#include "FGLinuxEventInput.hxx"
#define INPUTEVENT_CLASS FGLinuxEventInput

View file

@ -101,6 +101,7 @@ endif()
# Set up the target links.
setup_fgfs_libraries(fgfs)
export_debug_symbols(fgfs)
if (APPLE)
install(TARGETS fgfs BUNDLE DESTINATION .)
@ -108,6 +109,21 @@ else()
install(TARGETS fgfs RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
if (TARGET sentry::sentry)
# disabling this for now until Sentry-native fixes their CMake export :)
# target_link_libraries(fgfs sentry::sentry)
endif()
if (TARGET sentry_crashpad::handler)
if (APPLE)
# install inside the bundle
install(FILES $<TARGET_FILE:sentry_crashpad::handler> DESTINATION fgfs.app/Contents/MacOS OPTIONAL)
else()
# install in the bin-dir, next to the application binary
install(FILES $<TARGET_FILE:sentry_crashpad::handler> DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
endif()
endif()
if(ENABLE_METAR)
add_executable(metar metar_main.cxx)
target_link_libraries(metar

View file

@ -389,7 +389,27 @@ private:
} else {
SG_LOG(SG_AIRCRAFT, SG_DEV_ALERT, "Aircraft does not specify a minimum FG version: please add one at /sim/minimum-fg-version");
}
auto compatNodes = globals->get_props()->getNode("/sim")->getChildren("compatible-fg-version");
if (!compatNodes.empty()) {
bool showCompatWarning = true;
// if we have at least one compatibility node, then it needs to match
for (const auto& cn : compatNodes) {
const auto v = cn->getStringValue();
if (simgear::strutils::compareVersionToWildcard(FLIGHTGEAR_VERSION, v)) {
showCompatWarning = false;
break;
}
}
if (showCompatWarning) {
flightgear::modalMessageBox("Aircraft not compatible with this version",
"The selected aircraft has not been checked for compatability with this version of FlightGear (" FLIGHTGEAR_VERSION "). "
"Some aircraft features might not work, or might be displayed incorrectly.");
}
}
return true;
}
@ -628,7 +648,7 @@ int fgInitConfig ( int argc, char **argv, bool reinit )
#endif
// Read global defaults from $FG_ROOT/defaults
SG_LOG(SG_GENERAL, SG_INFO, "Reading global defaults");
SG_LOG(SG_GENERAL, SG_DEBUG, "Reading global defaults");
SGPath defaultsXML = globals->get_fg_root() / "defaults.xml";
if (!defaultsXML.exists()) {
flightgear::fatalMessageBoxThenExit(

View file

@ -216,11 +216,12 @@ FGIO::parse_port_config( const string_list& tokens )
"(one argument expected: --hla-local=<federationname>" );
return NULL;
}
tokens.insert(tokens.begin(), "");
tokens.insert(tokens.begin(), "60");
tokens.insert(tokens.begin(), "bi");
tokens.push_back("fg-local.xml");
return new FGHLA(tokens);
std::vector<std::string> HLA_tokens (tokens);
HLA_tokens.insert(HLA_tokens.begin(), "");
HLA_tokens.insert(HLA_tokens.begin(), "60");
HLA_tokens.insert(HLA_tokens.begin(), "bi");
HLA_tokens.push_back("fg-local.xml");
return new FGHLA(HLA_tokens);
}
#endif
else {

View file

@ -326,28 +326,6 @@ getGMTString ()
return buf;
}
/**
* Return the current heading in degrees.
*/
static double
getHeadingMag ()
{
double magheading = fgGetDouble("/orientation/heading-deg") -
fgGetDouble("/environment/magnetic-variation-deg");
return SGMiscd::normalizePeriodic(0, 360, magheading );
}
/**
* Return the current track in degrees.
*/
static double
getTrackMag ()
{
double magtrack = fgGetDouble("/orientation/track-deg") -
fgGetDouble("/environment/magnetic-variation-deg");
return SGMiscd::normalizePeriodic(0, 360, magtrack );
}
////////////////////////////////////////////////////////////////////////
// Tie the properties.
////////////////////////////////////////////////////////////////////////
@ -439,9 +417,11 @@ FGProperties::bind ()
_tiedProperties.Tie<const char*>("/position/latitude-string", getLatitudeString);
_tiedProperties.Tie<const char*>("/position/longitude-string", getLongitudeString);
// Orientation
_tiedProperties.Tie<double>("/orientation/heading-magnetic-deg", getHeadingMag);
_tiedProperties.Tie<double>("/orientation/track-magnetic-deg", getTrackMag);
_headingMagnetic = fgGetNode("/orientation/heading-magnetic-deg", true);
_trackMagnetic = fgGetNode("/orientation/track-magnetic-deg", true);
_magVar = fgGetNode("/environment/magnetic-variation-deg", true);
_trueHeading = fgGetNode("/orientation/heading-deg", true);
_trueTrack = fgGetNode("/orientation/track-deg", true);
}
void
@ -481,6 +461,10 @@ FGProperties::update (double dt)
_rmin->setIntValue(r->tm_min);
_rsec->setIntValue(r->tm_sec);
_rwday->setIntValue(r->tm_wday);
const double magvar = _magVar->getDoubleValue();
_headingMagnetic->setDoubleValue(_trueHeading->getDoubleValue() - magvar);
_trackMagnetic->setDoubleValue(_trueTrack->getDoubleValue() - magvar);
}

View file

@ -43,6 +43,10 @@ private:
SGPropertyNode_ptr _offset;
SGPropertyNode_ptr _uyear, _umonth, _uday, _uhour, _umin, _usec, _uwday, _udsec;
SGPropertyNode_ptr _ryear, _rmonth, _rday, _rhour, _rmin, _rsec, _rwday, _rdsec;
SGPropertyNode_ptr _headingMagnetic, _trackMagnetic;
SGPropertyNode_ptr _magVar;
SGPropertyNode_ptr _trueHeading, _trueTrack;
};
@ -736,7 +740,7 @@ fgTie (const char * name, V (*getter)(), void (*setter)(V) = 0,
{
if (!globals->get_props()->tie(name, SGRawValueFunctions<V>(getter, setter),
useDefault))
SG_LOG(SG_GENERAL, SG_WARN,
SG_LOG(SG_GENERAL, SG_DEV_WARN,
"Failed to tie property " << name << " to functions");
}
@ -769,7 +773,7 @@ fgTie (const char * name, int index, V (*getter)(int),
getter,
setter),
useDefault))
SG_LOG(SG_GENERAL, SG_WARN,
SG_LOG(SG_GENERAL, SG_DEV_WARN,
"Failed to tie property " << name << " to indexed functions");
}
@ -801,7 +805,7 @@ fgTie (const char * name, T * obj, V (T::*getter)() const,
if (!globals->get_props()->tie(name,
SGRawValueMethods<T,V>(*obj, getter, setter),
useDefault))
SG_LOG(SG_GENERAL, SG_WARN,
SG_LOG(SG_GENERAL, SG_DEV_WARN,
"Failed to tie property " << name << " to object methods");
}
@ -837,7 +841,7 @@ fgTie (const char * name, T * obj, int index,
getter,
setter),
useDefault))
SG_LOG(SG_GENERAL, SG_WARN,
SG_LOG(SG_GENERAL, SG_DEV_WARN,
"Failed to tie property " << name << " to indexed object methods");
}

View file

@ -21,9 +21,7 @@
// $Id$
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <config.h>
#include <simgear/compiler.h>
#include <simgear/structure/exception.hxx>
@ -165,7 +163,7 @@ void fgSetDefaults ()
fgSetDouble("/sim/presets/offset-distance-nm", 0.0);
fgSetBool("/sim/presets/runway-requested", false);
fgSetBool("/sim/presets/onground", true);
fgSetBool("/sim/presets/trim", false);
@ -1421,22 +1419,22 @@ fgOptScenario( const char *arg )
// make absolute
path = simgear::Dir::current().path() / arg;
}
// create description node
auto n = FGAIManager::registerScenarioFile(globals->get_props(), path);
if (!n) {
SG_LOG(SG_GENERAL, SG_WARN, "failed to read scenario file at:" << path);
return FG_OPTIONS_ERROR;
}
// also set the /sim/ai/scenario entry so we load it on startup
name = path.file_base();
}
// add the 'load it' node
SGPropertyNode_ptr ai_node = fgGetNode( "/sim/ai", true );
ai_node->addChild("scenario")->setStringValue(name);
return FG_OPTIONS_OK;
}
@ -1680,6 +1678,7 @@ struct OptionDesc {
{"ndb", true, OPTION_FUNC, "", false, "", fgOptNDB },
{"ndb-frequency", true, OPTION_DOUBLE, "/sim/presets/ndb-freq", false, "", fgOptVOR },
{"carrier", true, OPTION_FUNC, "", false, "", fgOptCarrier },
{"carrier-abeam", true, OPTION_BOOL, "/sim/presets/carrier-abeam", true, "", 0 },
{"parkpos", true, OPTION_FUNC, "", false, "", fgOptParkpos },
{"fix", true, OPTION_FUNC, "", false, "", fgOptFIX },
{"offset-distance", true, OPTION_DOUBLE, "/sim/presets/offset-distance-nm", false, "", 0 },
@ -1719,6 +1718,7 @@ struct OptionDesc {
{"notrim", false, OPTION_BOOL, "/sim/presets/trim", false, "", 0 },
{"on-ground", false, OPTION_BOOL, "/sim/presets/onground", true, "", 0 },
{"in-air", false, OPTION_BOOL, "/sim/presets/onground", false, "", 0 },
{"disable-hold-short", false, OPTION_BOOL, "/sim/presets/mp-hold-short-override", true, "", 0 },
{"fog-disable", false, OPTION_STRING, "/sim/rendering/fog", false, "disabled", 0 },
{"fog-fastest", false, OPTION_STRING, "/sim/rendering/fog", false, "fastest", 0 },
{"fog-nicest", false, OPTION_STRING, "/sim/rendering/fog", false, "nicest", 0 },

View file

@ -52,8 +52,8 @@ using std::string;
namespace flightgear
{
enum InitPosResult {
ExactPosition,
VicinityPosition,
@ -180,7 +180,9 @@ std::tuple<SGGeod, double> runwayStartPos(FGRunwayRef runway)
double startOffset = fgGetDouble("/sim/airport/runways/start-offset-m", 5.0);
SGGeod pos = runway->pointOnCenterline(startOffset);
if (FGIO::isMultiplayerRequested() && (fabs(offsetNm) <0.1)) {
const bool overrideHoldShort = fgGetBool("/sim/presets/mp-hold-short-override", false);
if (!overrideHoldShort && FGIO::isMultiplayerRequested() && (fabs(offsetNm) <0.1)) {
SG_LOG( SG_GENERAL, SG_WARN, "Requested to start on " << runway->airport()->ident() << "/" <<
runway->ident() << ", MP is enabled so computing hold short position to avoid runway incursion");
@ -417,7 +419,7 @@ static InitPosResult setInitialPosFromCarrier( const string& carrier )
SG_LOG( SG_GENERAL, SG_DEBUG, "Initial carrier pos = " << initialPos.second );
return VicinityPosition;
}
SG_LOG( SG_GENERAL, SG_ALERT, "Failed to locate aircraft carrier = " << carrier );
return Failure;
}
@ -450,33 +452,33 @@ static InitPosResult checkCarrierSceneryLoaded(const SGSharedPtr<FGAICarrier> ca
// Set current_options lon/lat given an aircraft carrier id
static InitPosResult setFinalPosFromCarrier( const string& carrier, const string& posid )
{
SGSharedPtr<FGAICarrier> carrierRef = FGAICarrier::findCarrierByNameOrPennant(carrier);
if (!carrierRef) {
SG_LOG( SG_GENERAL, SG_ALERT, "Failed to locate aircraft carrier = "
<< carrier );
return Failure;
}
auto res = checkCarrierSceneryLoaded(carrierRef);
if (res != VicinityPosition) {
return res; // either failrue or keep waiting for scenery load
}
SGGeod geodPos;
double heading;
SGVec3d uvw;
if (carrierRef->getParkPosition(posid, geodPos, heading, uvw)) {
////////
double lon = geodPos.getLongitudeDeg();
double lat = geodPos.getLatitudeDeg();
double alt = geodPos.getElevationFt() + 2.0;
SG_LOG( SG_GENERAL, SG_INFO, "Attempting to set starting position for "
<< carrier << " at lat = " << lat << ", lon = " << lon
<< ", alt = " << alt << ", heading = " << heading);
fgSetDouble("/sim/presets/longitude-deg", lon);
fgSetDouble("/sim/presets/latitude-deg", lat);
fgSetDouble("/sim/presets/altitude-ft", alt);
@ -485,7 +487,7 @@ static InitPosResult setFinalPosFromCarrier( const string& carrier, const string
fgSetDouble("/position/latitude-deg", lat);
fgSetDouble("/position/altitude-ft", alt);
fgSetDouble("/orientation/heading-deg", heading);
fgSetString("/sim/presets/speed-set", "UVW");
fgSetDouble("/velocities/uBody-fps", uvw(0));
fgSetDouble("/velocities/vBody-fps", uvw(1));
@ -493,9 +495,9 @@ static InitPosResult setFinalPosFromCarrier( const string& carrier, const string
fgSetDouble("/sim/presets/uBody-fps", uvw(0));
fgSetDouble("/sim/presets/vBody-fps", uvw(1));
fgSetDouble("/sim/presets/wBody-fps", uvw(2));
fgSetBool("/sim/presets/onground", true);
/////////
return ExactPosition;
}
@ -503,7 +505,7 @@ static InitPosResult setFinalPosFromCarrier( const string& carrier, const string
return Failure;
}
static InitPosResult setFinalPosFromCarrierFLOLS(const string& carrier)
static InitPosResult setFinalPosFromCarrierFLOLS(const string& carrier, bool abeam)
{
SGSharedPtr<FGAICarrier> carrierRef = FGAICarrier::findCarrierByNameOrPennant(carrier);
if (!carrierRef) {
@ -529,16 +531,29 @@ static InitPosResult setFinalPosFromCarrierFLOLS(const string& carrier)
double gs = SGMiscd::deg2rad(carrierRef->getFLOLFSGlidepathAngleDeg());
const double od = fgGetDouble("/sim/presets/offset-distance-nm");
// start position, but with altitude not set
SGGeod startPos = SGGeodesy::direct(flolsPosition, headingToFLOLS + 180, od * SG_NM_TO_METER);
// start position
SGGeod startPos;
if (abeam) {
// If we're starting from the abeam position, we are opposite the FLOLS, downwind on a left hand circuit
startPos = SGGeodesy::direct(flolsPosition, headingToFLOLS - 90, od * SG_NM_TO_METER);
} else {
startPos = SGGeodesy::direct(flolsPosition, headingToFLOLS + 180, od * SG_NM_TO_METER);
}
const double offsetFt = od * SG_NM_TO_METER * SG_METER_TO_FEET;
startPos.setElevationFt(fabs(offsetFt*tan(gs)) + flolsElevationFt);
double alt = fgGetDouble("/sim/presets/altitude-ft");
if (alt < 0.0f) {
// No altitude set, so base on glideslope
const double offsetFt = od * SG_NM_TO_METER * SG_METER_TO_FEET;
startPos.setElevationFt(fabs(offsetFt*tan(gs)) + flolsElevationFt);
} else {
startPos.setElevationFt(alt);
}
fgSetDouble("/sim/presets/longitude-deg", startPos.getLongitudeDeg());
fgSetDouble("/sim/presets/latitude-deg", startPos.getLatitudeDeg());
fgSetDouble("/sim/presets/altitude-ft", startPos.getElevationFt());
fgSetDouble("/sim/presets/heading-deg", headingToFLOLS);
fgSetDouble("/sim/presets/heading-deg", abeam ? (headingToFLOLS - 180) : headingToFLOLS);
fgSetDouble("/position/longitude-deg", startPos.getLongitudeDeg());
fgSetDouble("/position/latitude-deg", startPos.getLatitudeDeg());
fgSetDouble("/position/altitude-ft", startPos.getElevationFt());
@ -576,21 +591,21 @@ bool initPosition()
globals->get_event_mgr()->addTask("finalizePosition", &finalizePosition, 0.1);
global_callbackRegistered = true;
}
double gs = SGMiscd::deg2rad(fgGetDouble("/sim/presets/glideslope-deg"));
double od = fgGetDouble("/sim/presets/offset-distance-nm");
double alt = fgGetDouble("/sim/presets/altitude-ft");
bool set_pos = false;
// If glideslope is specified, then calculate offset-distance or
// altitude relative to glide slope if either of those was not
// specified.
if ( fabs( gs ) > 0.01 ) {
fgSetDistOrAltFromGlideSlope();
}
// If we have an explicit, in-range lon/lat, don't change it, just use it.
// If not, check for an airport-id and use that.
// If not, default to the middle of the KSFO field.
@ -604,7 +619,7 @@ bool initPosition()
{
set_pos = true;
}
string apt = fgGetString("/sim/presets/airport-id");
const bool apt_req = fgGetBool("/sim/presets/airport-requested");
string rwy_no = fgGetString("/sim/presets/runway");
@ -616,31 +631,31 @@ bool initPosition()
string carrier = fgGetString("/sim/presets/carrier");
string parkpos = fgGetString("/sim/presets/parkpos");
string fix = fgGetString("/sim/presets/fix");
// the launcher sets this to precisely identify a navaid
PositionedID navaidId = fgGetInt("/sim/presets/navaid-id");
SGPropertyNode *hdg_preset = fgGetNode("/sim/presets/heading-deg", true);
double hdg = hdg_preset->getDoubleValue();
// save some start parameters, so that we can later say what the
// user really requested. TODO generalize that and move it to options.cxx
static bool start_options_saved = false;
if (!start_options_saved) {
start_options_saved = true;
SGPropertyNode *opt = fgGetNode("/sim/startup/options", true);
opt->setDoubleValue("latitude-deg", lat_deg);
opt->setDoubleValue("longitude-deg", lon_deg);
opt->setDoubleValue("heading-deg", hdg);
opt->setStringValue("airport", apt.c_str());
opt->setStringValue("runway", rwy_no.c_str());
}
if (hdg > 9990.0) {
hdg = fgGetDouble("/environment/config/boundary/entry/wind-from-heading-deg", 270);
}
if ( !set_pos && !carrier.empty() ) {
// an aircraft carrier is requested
const auto result = setInitialPosFromCarrier( carrier );
@ -649,7 +664,7 @@ bool initPosition()
set_pos = true;
}
}
if (apt_req && !rwy_req) {
// ensure that if the users asks for a specific airport, but not a runway,
// presumably because they want automatic selection, we do not look
@ -657,7 +672,7 @@ bool initPosition()
// likely missing.
rwy_no.clear();
}
if ( !set_pos && !apt.empty() && !parkpos.empty() ) {
// An airport + parking position is requested
// since this depends on parking, which is part of dynamics, and hence
@ -670,7 +685,7 @@ bool initPosition()
set_pos = true;
}
}
if ( !set_pos && !apt.empty() && !rwy_no.empty() ) {
// An airport + runway is requested
if ( fgSetPosFromAirportIDandRwy( apt, rwy_no, rwy_req ) ) {
@ -681,7 +696,7 @@ bool initPosition()
set_pos = true;
}
}
if ( !set_pos && !apt.empty() ) {
// An airport is requested (find runway closest to hdg)
if ( setPosFromAirportIDandHdg( apt, hdg ) ) {
@ -692,31 +707,31 @@ bool initPosition()
set_pos = true;
}
}
if (hdg_preset->getDoubleValue() > 9990.0)
hdg_preset->setDoubleValue(hdg);
if ( !set_pos && !vor.empty() ) {
// a VOR is requested
if ( fgSetPosFromNAV( vor, vor_freq, FGPositioned::VOR, navaidId ) ) {
set_pos = true;
}
}
if ( !set_pos && !ndb.empty() ) {
// an NDB is requested
if ( fgSetPosFromNAV( ndb, ndb_freq, FGPositioned::NDB, navaidId ) ) {
set_pos = true;
}
}
if ( !set_pos && !fix.empty() ) {
// a Fix is requested
if ( fgSetPosFromFix( fix, navaidId ) ) {
set_pos = true;
}
}
if ( !set_pos ) {
const std::string defaultAirportId = fgGetString("/sim/presets/airport-id");
const FGAirport* airport = fgFindAirportID(defaultAirportId);
@ -733,27 +748,27 @@ bool initPosition()
<< "') seems to be unknown.");
}
}
fgSetDouble( "/position/longitude-deg",
fgGetDouble("/sim/presets/longitude-deg") );
fgSetDouble( "/position/latitude-deg",
fgGetDouble("/sim/presets/latitude-deg") );
fgSetDouble( "/orientation/heading-deg", hdg_preset->getDoubleValue());
// determine if this should be an on-ground or in-air start
if ((fabs(gs) > 0.01 || fabs(od) > 0.1 || alt > 0.1) && carrier.empty()) {
fgSetBool("/sim/presets/onground", false);
} else {
fgSetBool("/sim/presets/onground", true);
}
fgSetBool("/sim/position-finalized", false);
// Initialize the longitude, latitude and altitude to the initial position
fgSetDouble("/position/altitude-ft", fgGetDouble("/sim/presets/altitude-ft"));
fgSetDouble("/position/longitude-deg", fgGetDouble("/sim/presets/longitude-deg"));
fgSetDouble("/position/latitude-deg", fgGetDouble("/sim/presets/latitude-deg"));
return true;
}
@ -820,13 +835,14 @@ void finalizePosition()
std::string runway = fgGetString("/sim/presets/runway");
std::string apt = fgGetString("/sim/presets/airport-id");
const bool rwy_req = fgGetBool("/sim/presets/runway-requested");
const bool abeam = fgGetBool("/sim/presets/carrier-abeam");
if (!carrier.empty())
{
const bool atFLOLS = rwy_req && (runway == "FLOLS");
const bool atFLOLS = rwy_req && (runway == "FLOLS" || parkpos == "FLOLS");
InitPosResult carrierResult;
if (atFLOLS) {
carrierResult = setFinalPosFromCarrierFLOLS(carrier);
carrierResult = setFinalPosFromCarrierFLOLS(carrier, abeam);
} else {
carrierResult = setFinalPosFromCarrier(carrier, parkpos);
}
@ -843,7 +859,7 @@ void finalizePosition()
done = true;
}
}
} else if (!apt.empty() && !parkpos.empty()) {
// parking position depends on ATC / dynamics code to assign spaces,
// so we wait until this point to initialise

View file

@ -2449,9 +2449,9 @@ FGMultiplayMgr::addMultiplayer(const std::string& callsign,
}
}
/* Copy values from our local <set>/sim/view[]/config/* into global
/ai/models/multiplayer/set/sim/view[]/config/ so that we have view offsets
available for this multiplayer aircraft. */
// Copy values from our local <set>/sim/view[]/config/* into global
// /ai/models/multiplayer/set/sim/view[]/config/ so that we have view offsets
// available for this multiplayer aircraft.
SGPropertyNode* global_set = mp->_getProps()->addChild("set");
SGPropertyNode* global_sim = global_set->addChild("sim");
double sim_chase_distance_m = -25;

View file

@ -62,7 +62,12 @@ public:
_manager(manager),
_useVBOsProp(fgGetNode("/sim/rendering/use-vbos", true)),
_enableCacheProp(fgGetNode("/sim/tile-cache/enable", true)),
_pagedLODMaximumProp(fgGetNode("/sim/rendering/max-paged-lod", true))
_pagedLODMaximumProp(fgGetNode("/sim/rendering/max-paged-lod", true)),
_lodDetailed(fgGetNode("/sim/rendering/static-lod/detailed", true)),
_lodRoughDelta(fgGetNode("/sim/rendering/static-lod/rough-delta", true)),
_lodBareDelta(fgGetNode("/sim/rendering/static-lod/bare-delta", true)),
_lodRough(fgGetNode("/sim/rendering/static-lod/rough", true)),
_lodBare(fgGetNode("/sim/rendering/static-lod/bare", true))
{
_useVBOsProp->addChangeListener(this, true);
@ -78,6 +83,9 @@ public:
_pagedLODMaximumProp->setIntValue(current);
}
_pagedLODMaximumProp->addChangeListener(this, true);
_lodDetailed->addChangeListener(this, true);
_lodBareDelta->addChangeListener(this, true);
_lodRoughDelta->addChangeListener(this, true);
}
~TileManagerListener()
@ -85,6 +93,9 @@ public:
_useVBOsProp->removeChangeListener(this);
_enableCacheProp->removeChangeListener(this);
_pagedLODMaximumProp->removeChangeListener(this);
_lodDetailed->removeChangeListener(this);
_lodBareDelta->removeChangeListener(this);
_lodRoughDelta->removeChangeListener(this);
}
virtual void valueChanged(SGPropertyNode* prop)
@ -92,13 +103,20 @@ public:
if (prop == _useVBOsProp) {
bool useVBOs = prop->getBoolValue();
_manager->_options->setPluginStringData("SimGear::USE_VBOS",
useVBOs ? "ON" : "OFF");
useVBOs ? "ON" : "OFF");
} else if (prop == _enableCacheProp) {
_manager->_enableCache = prop->getBoolValue();
} else if (prop == _pagedLODMaximumProp) {
int v = prop->getIntValue();
osg::ref_ptr<osgViewer::Viewer> viewer(globals->get_renderer()->getViewer());
viewer->getDatabasePager()->setTargetMaximumNumberOfPageLOD(v);
int v = prop->getIntValue();
osg::ref_ptr<osgViewer::Viewer> viewer(globals->get_renderer()->getViewer());
viewer->getDatabasePager()->setTargetMaximumNumberOfPageLOD(v);
} else if (prop == _lodDetailed || prop == _lodBareDelta || prop == _lodRoughDelta) {
// compatibility with earlier versions; set the static lod ranges appropriately as otherwise (bad) self managed
// LOD on scenery with range animations doesn't work.
// see also /sim/rendering/enable-range-lod-animations - which is false by default in > 2019.2 which also fixes
// the scenery but in a more efficient way.
_lodBare->setDoubleValue(_lodDetailed->getDoubleValue() + _lodRoughDelta->getDoubleValue());
_lodRough->setDoubleValue(_lodBare->getDoubleValue() + _lodBareDelta->getDoubleValue());
}
}
@ -106,7 +124,13 @@ private:
FGTileMgr* _manager;
SGPropertyNode_ptr _useVBOsProp,
_enableCacheProp,
_pagedLODMaximumProp;
_pagedLODMaximumProp,
_lodDetailed,
_lodRoughDelta,
_lodBareDelta,
_lodRough,
_lodBare
;
};
FGTileMgr::FGTileMgr():

View file

@ -21,6 +21,7 @@
#include "config.h"
#include <cstring>
#include <algorithm>
#include "NasalPositioned.hxx"
@ -2446,7 +2447,8 @@ private:
class NasalFPDelegateFactory : public FlightPlan::DelegateFactory
{
public:
NasalFPDelegateFactory(naRef code)
NasalFPDelegateFactory(naRef code, const std::string& id) :
_id(id)
{
_nasal = globals->get_subsystem<FGNasalSys>();
_func = code;
@ -2474,7 +2476,11 @@ public:
naFreeContext(ctx);
return result;
}
const std::string& id() const
{ return _id; }
private:
const std::string _id;
FGNasalSys* _nasal;
naRef _func;
int _gcSaveKey;
@ -2499,10 +2505,45 @@ static naRef f_registerFPDelegate(naContext c, naRef me, int argc, naRef* args)
if ((argc < 1) || !naIsFunc(args[0])) {
naRuntimeError(c, "non-function argument to registerFlightPlanDelegate");
}
NasalFPDelegateFactory* factory = new NasalFPDelegateFactory(args[0]);
FlightPlan::registerDelegateFactory(factory);
const std::string delegateId = (argc > 1) ? naStr_data(args[1]) : std::string{};
if (!delegateId.empty()) {
auto it = std::find_if(static_nasalDelegateFactories.begin(), static_nasalDelegateFactories.end(),
[delegateId](NasalFPDelegateFactory* delegate) {
return delegate->id() == delegateId;
});
if (it != static_nasalDelegateFactories.end()) {
naRuntimeError(c, "duplicate delegate ID at registerFlightPlanDelegate: %s", delegateId.c_str());
}
}
NasalFPDelegateFactory* factory = new NasalFPDelegateFactory(args[0], delegateId);
FlightPlan::registerDelegateFactory(factory);
static_nasalDelegateFactories.push_back(factory);
return naNil();
return naNil();
}
static naRef f_unregisterFPDelegate(naContext c, naRef me, int argc, naRef* args)
{
if ((argc < 1) || !naIsString(args[0])) {
naRuntimeError(c, "non-string argument to unregisterFlightPlanDelegate");
}
const std::string delegateId = naStr_data(args[0]);
auto it = std::find_if(static_nasalDelegateFactories.begin(), static_nasalDelegateFactories.end(),
[delegateId](NasalFPDelegateFactory* delegate) {
return delegate->id() == delegateId;
});
if (it == static_nasalDelegateFactories.end()) {
SG_LOG(SG_NASAL, SG_DEV_WARN, "f_unregisterFPDelegate: no de;egate with ID:" << delegateId);
return naNil();
}
FlightPlan::unregisterDelegateFactory(*it);
static_nasalDelegateFactories.erase(it);
return naNil();
}
static WayptRef wayptFromArg(naRef arg)
@ -3295,6 +3336,7 @@ static struct { const char* name; naCFunction func; } funcs[] = {
{ "flightplan", f_flightplan },
{ "createFlightplan", f_createFlightplan },
{ "registerFlightPlanDelegate", f_registerFPDelegate },
{ "unregisterFlightPlanDelegate", f_unregisterFPDelegate},
{ "createWP", f_createWP },
{ "createWPFrom", f_createWPFrom },
{ "createViaTo", f_createViaTo },

View file

@ -955,6 +955,17 @@ naRef FGNasalSys::cmdArgGhost()
return propNodeGhost(_cmdArg);
}
void FGNasalSys::initLogLevelConstants()
{
hashset(_globals, "LOG_BULK", naNum(SG_BULK));
hashset(_globals, "LOG_WARN", naNum(SG_WARN));
hashset(_globals, "LOG_DEBUG", naNum(SG_DEBUG));
hashset(_globals, "LOG_INFO", naNum(SG_INFO));
hashset(_globals, "LOG_ALERT", naNum(SG_ALERT));
hashset(_globals, "DEV_WARN", naNum(SG_DEV_WARN));
hashset(_globals, "DEV_ALERT", naNum(SG_DEV_ALERT));
}
void FGNasalSys::setCmdArg(SGPropertyNode* aNode)
{
_cmdArg = aNode;
@ -983,6 +994,8 @@ void FGNasalSys::init()
hashset(_globals, "thread", naInit_thread(_context));
hashset(_globals, "utf8", naInit_utf8(_context));
initLogLevelConstants();
// Add our custom extension functions:
for(i=0; funcs[i].name; i++)
hashset(_globals, funcs[i].name,

View file

@ -161,6 +161,18 @@ public:
simgear::BufferedLogCallback* log() const
{ return _log.get(); }
private:
void initLogLevelConstants();
void loadPropertyScripts();
void loadPropertyScripts(SGPropertyNode* n);
void loadScriptDirectory(simgear::Dir nasalDir);
void addModule(std::string moduleName, simgear::PathList scripts);
static void logError(naContext);
naRef parse(naContext ctx, const char* filename, const char* buf, int len,
std::string& errors);
naRef genPropsModule();
private:
//friend class FGNasalScript;
friend class FGNasalListener;
@ -180,15 +192,6 @@ private:
static int _listenerId;
void loadPropertyScripts();
void loadPropertyScripts(SGPropertyNode* n);
void loadScriptDirectory(simgear::Dir nasalDir);
void addModule(std::string moduleName, simgear::PathList scripts);
static void logError(naContext);
naRef parse(naContext ctx, const char* filename, const char* buf, int len,
std::string& errors);
naRef genPropsModule();
bool _inited;
naContext _context;
naRef _globals,

View file

@ -505,7 +505,7 @@ void FGTrafficManager::shutdown()
bool FGTrafficManager::doDataSync()
{
simgear::SGTerraSync* terraSync = static_cast<simgear::SGTerraSync*>(globals->get_subsystem("terrasync"));
auto terraSync = globals->get_subsystem<simgear::SGTerraSync>();
bool doDataSync = fgGetBool("/sim/terrasync/ai-data-enabled");
if (doDataSync && terraSync) {
if (!trafficSyncRequested) {

View file

@ -171,7 +171,7 @@ View* View::createFromProperties(SGPropertyNode_ptr config, int view_index)
double fov_deg = config->getDoubleValue("default-field-of-view-deg");
double near_m = config->getDoubleValue("ground-level-nearplane-m");
View* v = 0;
View* v = nullptr;
// supporting two types "lookat" = 1 and "lookfrom" = 0
const char *type = config->getParent()->getStringValue("type");
if (!strcmp(type, "lookat")) {

View file

@ -21,7 +21,7 @@ target_link_libraries(fgai
SimGearCore SimGearScene
${OPENSCENEGRAPH_LIBRARIES}
${OPENGL_LIBRARIES}
${RTI_LIBRARIES}
${RTI_LDFLAGS}
)
install(TARGETS fgai RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})

0
utils/fgcom/positions.hxx Normal file → Executable file
View file

View file

@ -16,7 +16,6 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifdef _WIN32
# include <direct.h> // for getcwd()
#else // !_WIN32

View file

@ -1,13 +1,26 @@
find_package(PNG)
find_package(OpenGL)
find_package(GLEW)
find_package(Freetype)
find_package(PNG QUIET)
find_package(OpenGL QUIET)
find_package(Freetype QUIET)
if ((NOT PNG_FOUND) OR (NOT OPENGL_FOUND) OR (NOT GLEW_FOUND) OR (NOT FREETYPE_FOUND))
find_package(GLUT QUIET)
find_package(GLEW QUIET)
if(NOT ${GLUT_FOUND})
message(WARNING "GLUT NOT found, can't build FGPanel")
set(WITH_FGPANEL 0)
return()
endif()
if(NOT ${GLEW_FOUND})
message(WARNING "GLEW NOT found, can't build FGPanel")
set(WITH_FGPANEL 0)
return()
endif()
if((NOT PNG_FOUND) OR (NOT OPENGL_FOUND) OR (NOT FREETYPE_FOUND))
message(WARNING "FGPanel enabled, but some dependencies are missing")
message(STATUS "libPNG: ${PNG_FOUND}")
message(STATUS "OpenGL: ${OPENGL_FOUND}")
message(STATUS "GLEW: ${GLEW_FOUND}")
message(STATUS "Freetype: ${FREETYPE_FOUND}")
return()
endif()
@ -18,21 +31,7 @@ find_path(BCMHOST_INCLUDE_DIR
NO_DEFAULT_PATH
)
include_directories(
${FREETYPE_INCLUDE_DIRS}
${PNG_INCLUDE_DIR}
)
if(${BCMHOST_INCLUDE_DIR} STREQUAL "BCMHOST_INCLUDE_DIR-NOTFOUND")
else()
# CMAKE > 3.1 : target_sources(fgpanel
set(TARGET_SOURCES
GLES_utils.cxx
GLES_utils.hxx
)
endif()
add_executable(fgpanel
set(TARGET_SOURCES
main.cxx
ApplicationProperties.hxx
ApplicationProperties.cxx
@ -74,45 +73,71 @@ add_executable(fgpanel
panel_io.hxx
GL_utils.cxx
GL_utils.hxx
${TARGET_SOURCES}
)
add_executable(fgpanel ${TARGET_SOURCES})
target_link_libraries(fgpanel
SimGearCore
${PNG_LIBRARIES}
${FREETYPE_LIBRARIES}
${OPENGL_LIBRARIES}
${GLUT_LIBRARIES}
${GLEW_LIBRARIES}
)
if(${BCMHOST_INCLUDE_DIR} STREQUAL "BCMHOST_INCLUDE_DIR-NOTFOUND")
find_package(GLUT REQUIRED)
if(GLUT_FOUND)
message(STATUS "found GLUT inc ${GLUT_INCLUDE_DIR}, lib ${GLUT_LIBRARIES} ")
if (MSVC)
add_definitions( -DFREEGLUT_LIB_PRAGMAS=0 )
endif ()
target_include_directories(fgpanel PUBLIC
${FREETYPE_INCLUDE_DIRS}
${PNG_INCLUDE_DIR}
${GLEW_INCLUDE_DIRS}
)
target_link_libraries(fgpanel
${OPENGL_LIBRARIES}
${GLUT_LIBRARIES}
GLEW::GLEW
if(MSVC)
target_compile_definitions(fgpanel PUBLIC
-DFREEGLUT_LIB_PRAGMAS=0
)
else(GLUT_FOUND)
message(STATUS "glut NOT found, can't build fgpanel")
endif(GLUT_FOUND)
else()
endif(MSVC)
if(BCMHOST_INCLUDE_DIR)
message(STATUS "found Raspberry Pi")
target_link_libraries(fgpanel
-lGLESv2 -lEGL -lm -lbcm_host -L/opt/vc/lib
add_executable(fgpanel-egl ${TARGET_SOURCES}
GLES_utils.cxx
GLES_utils.hxx
)
include_directories(
target_include_directories(fgpanel-egl PUBLIC
${FREETYPE_INCLUDE_DIRS}
${PNG_INCLUDE_DIR}
${BCMHOST_INCLUDE_DIR}
${BCMHOST_INCLUDE_DIR}/interface/vcos/pthreads
${BCMHOST_INCLUDE_DIR}/interface/vmcs_host/linux
)
add_definitions(-D_GLES2 -D_RPI)
endif()
target_link_libraries(fgpanel-egl
SimGearCore
${PNG_LIBRARIES}
${FREETYPE_LIBRARIES}
-lbrcmGLESv2 -lbrcmEGL -lm -lbcm_host -L/opt/vc/lib
)
install(TARGETS fgpanel RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
target_compile_definitions(fgpanel-egl PUBLIC
-D_GLES2 -D_RPI
)
target_compile_definitions(fgpanel PUBLIC
-D_RPI
)
message(STATUS "FGPanel (Raspberry Pi) : ENABLED")
install(TARGETS fgpanel-egl RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
else(BCMHOST_INCLUDE_DIR)
message(STATUS "FGPanel (Raspberry Pi) : DISABLED")
endif(BCMHOST_INCLUDE_DIR)
if(WITH_FGPANEL)
message(STATUS "FGPanel : ENABLED")
install(TARGETS fgpanel RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
else(WITH_FGPANEL)
message(WARNING "FGPanel : DISABLED")
endif(WITH_FGPANEL)

View file

@ -89,12 +89,13 @@ void
FGPanel::init () {
// Textured Layer Shaders
const char V_Textured_Layer_Shader_Str[] =
#ifdef _GLES2
#ifdef _RPI
"#version 100 \n"
"attribute vec4 a_position; \n"
"attribute vec2 a_tex_coord; \n"
"varying vec2 v_tex_coord; \n"
#else
"#version 330 \n"
"#version 130 \n"
"in vec4 a_position; \n"
"in vec2 a_tex_coord; \n"
"out vec2 v_tex_coord; \n"
@ -106,13 +107,13 @@ FGPanel::init () {
"} \n";
const char F_Textured_Layer_Shader_Str[] =
#ifdef _GLES2
#ifdef _RPI
"#version 100 \n"
"precision mediump float; \n"
"varying vec2 v_tex_coord; \n"
#else
"#version 330 \n"
"#version 130 \n"
"in vec2 v_tex_coord; \n"
"out vec4 gl_FragColor; \n"
#endif
"uniform sampler2D u_texture; \n"
"void main () { \n"

View file

@ -51,8 +51,7 @@ using namespace std;
* redraw themselves when necessary, and will pass mouse clicks on to
* the appropriate instruments for processing.
*/
class FGPanel : public SGSubsystem
{
class FGPanel : public SGSubsystem {
public:
FGPanel (const SGPropertyNode_ptr root);
virtual ~FGPanel ();

View file

@ -36,7 +36,6 @@
#include <GLUT/glut.h>
#elif defined (_GLES2)
#include <GLES2/gl2.h>
#include "GLES_utils.hxx"
#else
#include <GL/glew.h> // Must be included before <GL/gl.h>
#include <GL/gl.h>

View file

@ -28,8 +28,7 @@ class PropertySetter;
typedef vector<PropertySetter*> PropertySetterVector;
class FGPanelProtocol : public SGSubsystem
{
class FGPanelProtocol : public SGSubsystem {
public:
FGPanelProtocol (SGPropertyNode_ptr a_Root);
virtual ~FGPanelProtocol ();

View file

@ -35,26 +35,32 @@ GLint FGTextLayer::Text_Layer_Color_Loc (0);
bool
FGTextLayer::Init () {
const char V_Text_Layer_Shader_Str[] =
#ifdef _GLES2
#ifdef _RPI
"#version 100 \n"
"attribute vec4 a_position; \n"
"attribute vec2 a_tex_coord; \n"
"varying vec2 v_tex_coord; \n"
#else
"#version 330 \n"
"#version 130 \n"
"in vec4 a_position; \n"
"in vec2 a_tex_coord; \n"
"out vec2 v_tex_coord; \n"
#endif
"uniform mat4 u_mvp_matrix; \n"
"varying vec2 v_tex_coord; \n"
"void main () { \n"
" gl_Position = u_mvp_matrix * a_position; \n"
" v_tex_coord = a_tex_coord; \n"
"} \n";
const char F_Text_Layer_Shader_Str[] =
#ifdef _GLES2
#ifdef _RPI
"#version 100 \n"
"precision mediump float; \n"
#endif
"varying vec2 v_tex_coord; \n"
#else
"#version 130 \n"
"in vec2 v_tex_coord; \n"
#endif
"uniform sampler2D u_texture; \n"
"uniform vec4 u_color; \n"
"void main () { \n"

View file

@ -15,10 +15,10 @@ INSTALLATION
The source code of FGPanel can easily be adapted to other embedded devices supporting OpenGL ES 2.0.
3. Build FGPanel only (don't build all components on the Raspberry Pi as this will take ages!!!):
make -- fgpanel
make -- fgpanel-egl fgpanel
USAGE
=====
USAGE (fgpanel-egl)
===================
1. Increase the amount of GPU memory by editing the /boot/config.txt file.
Add the following line (this is for Raspberry Pi model 1B with 512 Mb of memory):
@ -30,10 +30,21 @@ USAGE
3. Stop X server. FGPanel runs in the console (Linux framebuffer).
4. Start FGPanel as usual but as ROOT or use the 'sudo' command (see README):
sudo -- utils/fgpanel/fgpanel --fg-root=/path/to/fg/data --panel=Aircraft/MyAircraft/Panels/MyPanel.xml
sudo -- utils/fgpanel/fgpanel-egl --fg-root=/path/to/fg/data --panel=Aircraft/MyAircraft/Panels/MyPanel.xml
ROOT privileges are required to access the GPU of the Raspberry Pi.
USAGE (fgpanel)
===================
1. Activate the Full KMS driver on the Raspberry Pi using the raspi-config program:
Advanced Options -> GL Driver -> Full KMS
2. Reboot. The Full KMS driver should be activated.
3. When X server is running, start FGPanel as usual (see README):
utils/fgpanel/fgpanel --fg-root=/path/to/fg/data --panel=Aircraft/MyAircraft/Panels/MyPanel.xml
FEATURES
========

View file

@ -31,8 +31,7 @@
#include "FGPanel.hxx"
class FGReadablePanel : public FGPanel
{
class FGReadablePanel : public FGPanel {
public:
// Subsystem identification.
static const char* staticSubsystemClassId() { return "readable-panel"; }

View file

@ -35,7 +35,7 @@ if(RTI_FOUND)
HLAWindowDrawable.cxx
HLAWindowDrawableClass.cxx
)
set(FGVIEWER_RTI_LIBRARIES ${RTI_LIBRARIES})
set(FGVIEWER_RTI_LIBRARIES ${RTI_LDFLAGS})
else()
set(FGVIEWER_RTI_LIBRARIES "")
set(FGVIEWER_RTI_SOURCES "")
@ -47,9 +47,9 @@ if(X11_FOUND)
endif()
target_link_libraries(fgviewer
SimGearScene SimGearCore
SimGearScene SimGearCore
${OPENGL_LIBRARIES}
${FGVIEWER_RTI_LIBRARIES}
${FGVIEWER_RTI_LIBRARIES}
${GDAL_LIBRARY}
)
install(TARGETS fgviewer RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})