diff --git a/3rdparty/hidapi/CMakeLists.txt b/3rdparty/hidapi/CMakeLists.txt index 06fc8a352..f4d69cad3 100644 --- a/3rdparty/hidapi/CMakeLists.txt +++ b/3rdparty/hidapi/CMakeLists.txt @@ -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}) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3818b1110..456199ab3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/CMakeModules/ConfigureMsvc3rdParty.cmake b/CMakeModules/ConfigureMsvc3rdParty.cmake index 1651d2f79..5cfdc9dcd 100644 --- a/CMakeModules/ConfigureMsvc3rdParty.cmake +++ b/CMakeModules/ConfigureMsvc3rdParty.cmake @@ -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 diff --git a/CMakeModules/ExportDebugSymbols.cmake b/CMakeModules/ExportDebugSymbols.cmake new file mode 100644 index 000000000..e5861fb46 --- /dev/null +++ b/CMakeModules/ExportDebugSymbols.cmake @@ -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 $ + DEPENDS $ + ) + + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${target}.dSYM DESTINATION symbols OPTIONAL) + + add_dependencies(debug_symbols ${target}.dSYM) + endif() + +endfunction() \ No newline at end of file diff --git a/CMakeModules/SetupFGFSBundle.cmake b/CMakeModules/SetupFGFSBundle.cmake index 92efc3e2e..156c8b189 100644 --- a/CMakeModules/SetupFGFSBundle.cmake +++ b/CMakeModules/SetupFGFSBundle.cmake @@ -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} diff --git a/CMakeModules/SetupFGFSLibraries.cmake b/CMakeModules/SetupFGFSLibraries.cmake index 3eba04915..d0d44ea82 100644 --- a/CMakeModules/SetupFGFSLibraries.cmake +++ b/CMakeModules/SetupFGFSLibraries.cmake @@ -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() diff --git a/src/AIModel/AIBase.cxx b/src/AIModel/AIBase.cxx index 140a872a8..d1954cbdf 100644 --- a/src/AIModel/AIBase.cxx +++ b/src/AIModel/AIBase.cxx @@ -20,15 +20,12 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -#ifdef HAVE_CONFIG_H -# include -#endif +#include #include #include -#include #include #include @@ -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) diff --git a/src/AIModel/AICarrier.cxx b/src/AIModel/AICarrier.cxx index 617b532f0..e50065adb 100644 --- a/src/AIModel/AICarrier.cxx +++ b/src/AIModel/AICarrier.cxx @@ -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 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 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::findCarrierByNameOrPennant(const std::stri if (!aiManager) { return {}; } - + for (const auto& aiObject : aiManager->get_ai_list()) { if (aiObject->isa(FGAIBase::otCarrier)) { SGSharedPtr c = static_cast(aiObject.get()); @@ -664,7 +664,7 @@ SGSharedPtr 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); diff --git a/src/Airports/groundnetwork.cxx b/src/Airports/groundnetwork.cxx index 30ee31aef..564c17f77 100644 --- a/src/Airports/groundnetwork.cxx +++ b/src/Airports/groundnetwork.cxx @@ -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); }); diff --git a/src/Cockpit/NavDisplay.cxx b/src/Cockpit/NavDisplay.cxx index 9885d81ff..4810b28c9 100644 --- a/src/Cockpit/NavDisplay.cxx +++ b/src/Cockpit/NavDisplay.cxx @@ -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 enable; string_set required_states; @@ -459,7 +459,7 @@ NavDisplay::NavDisplay(SGPropertyNode *node) : SGPropertyNode* symbol; map 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) diff --git a/src/Environment/metarproperties.cxx b/src/Environment/metarproperties.cxx index 436224551..a75ee1aa0 100644 --- a/src/Environment/metarproperties.cxx +++ b/src/Environment/metarproperties.cxx @@ -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 m ) _hour = m->getHour(); _minute = m->getMinute(); _cavok = m->getCAVOK(); + _tiedProperties.fireValueChanged(); _metarValidNode->setBoolValue(true); _description = m->getDescription(-1); } diff --git a/src/FDM/YASim/FGFDM.cpp b/src/FDM/YASim/FGFDM.cpp index 3440dd1aa..63fb20b95 100644 --- a/src/FDM/YASim/FGFDM.cpp +++ b/src/FDM/YASim/FGFDM.cpp @@ -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); } diff --git a/src/FDM/YASim/YASim.cxx b/src/FDM/YASim/YASim.cxx index 60c2eb425..9e4b50ba0 100644 --- a/src/FDM/YASim/YASim.cxx +++ b/src/FDM/YASim/YASim.cxx @@ -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; inumGear(); 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 diff --git a/src/GUI/AircraftModel.cxx b/src/GUI/AircraftModel.cxx index 22c8c301b..ec474602a 100644 --- a/src/GUI/AircraftModel.cxx +++ b/src/GUI/AircraftModel.cxx @@ -38,6 +38,7 @@ #include #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(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(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(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(i)); + modelIndex = index(rowOffset + static_cast(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); -} diff --git a/src/GUI/AircraftModel.hxx b/src/GUI/AircraftModel.hxx index 5590f5cf7..bee5134e5 100644 --- a/src/GUI/AircraftModel.hxx +++ b/src/GUI/AircraftModel.hxx @@ -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 m_favourites; int m_cachedLocalAircraftCount = 0; }; diff --git a/src/GUI/AircraftSearchFilterModel.cxx b/src/GUI/AircraftSearchFilterModel.cxx index f4b5f18bc..6b5aac3d5 100644 --- a/src/GUI/AircraftSearchFilterModel.cxx +++ b/src/GUI/AircraftSearchFilterModel.cxx @@ -4,6 +4,8 @@ #include #include "AircraftModel.hxx" +#include "FavouriteAircraftData.hxx" + #include 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(); } diff --git a/src/GUI/AircraftSearchFilterModel.hxx b/src/GUI/AircraftSearchFilterModel.hxx index d42e73c0a..d0103c65f 100644 --- a/src/GUI/AircraftSearchFilterModel.hxx +++ b/src/GUI/AircraftSearchFilterModel.hxx @@ -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; diff --git a/src/GUI/BaseDiagram.cxx b/src/GUI/BaseDiagram.cxx index 1d6cce7f8..f1b359186 100644 --- a/src/GUI/BaseDiagram.cxx +++ b/src/GUI/BaseDiagram.cxx @@ -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(); diff --git a/src/GUI/BaseDiagram.hxx b/src/GUI/BaseDiagram.hxx index c28ef36f1..3d367952e 100644 --- a/src/GUI/BaseDiagram.hxx +++ b/src/GUI/BaseDiagram.hxx @@ -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 ¢er); 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; diff --git a/src/GUI/CMakeLists.txt b/src/GUI/CMakeLists.txt index a3aed0468..186090339 100644 --- a/src/GUI/CMakeLists.txt +++ b/src/GUI/CMakeLists.txt @@ -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 diff --git a/src/GUI/CarrierDiagram.cxx b/src/GUI/CarrierDiagram.cxx new file mode 100644 index 000000000..bb9dd6352 --- /dev/null +++ b/src/GUI/CarrierDiagram.cxx @@ -0,0 +1,148 @@ +// CarrierDiagram.cxx - part of GUI launcher using Qt5 +// +// Written by Stuart Buchanan, started April 2020. +// +// Copyright (C) 2022 Stuart Buchanan +// +// 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 + +#include +#include +#include +#include + +#include + +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)); + } +} diff --git a/src/GUI/CarrierDiagram.hxx b/src/GUI/CarrierDiagram.hxx new file mode 100644 index 000000000..c4f8af5c1 --- /dev/null +++ b/src/GUI/CarrierDiagram.hxx @@ -0,0 +1,80 @@ +// CarrierDiagram.hxx - part of GUI launcher using Qt5 +// +// Written by Stuart Buchanan, started April 2020. +// +// Copyright (C) 2022 Stuart Buchanan +// +// 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 +#include + +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 diff --git a/src/GUI/CarriersLocationModel.cxx b/src/GUI/CarriersLocationModel.cxx index 9cebe1246..d60368de5 100644 --- a/src/GUI/CarriersLocationModel.cxx +++ b/src/GUI/CarriersLocationModel.cxx @@ -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 CarriersLocationModel::roleNames() const result[NameRole] = "name"; result[IconRole] = "icon"; result[TypeRole] = "type"; + result[DescriptionRole] = "description"; result[NavFrequencyRole] = "frequency"; return result; } diff --git a/src/GUI/CarriersLocationModel.hxx b/src/GUI/CarriersLocationModel.hxx index 2bc4fa9d8..11a936b04 100644 --- a/src/GUI/CarriersLocationModel.hxx +++ b/src/GUI/CarriersLocationModel.hxx @@ -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; diff --git a/src/GUI/CatalogListModel.cxx b/src/GUI/CatalogListModel.cxx index 7704727e7..057089147 100644 --- a/src/GUI/CatalogListModel.cxx +++ b/src/GUI/CatalogListModel.cxx @@ -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)"); diff --git a/src/GUI/FavouriteAircraftData.cxx b/src/GUI/FavouriteAircraftData.cxx new file mode 100644 index 000000000..435a8d81d --- /dev/null +++ b/src/GUI/FavouriteAircraftData.cxx @@ -0,0 +1,62 @@ +#include "FavouriteAircraftData.hxx" + +#include + +#include + +static std::unique_ptr 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); +} diff --git a/src/GUI/FavouriteAircraftData.hxx b/src/GUI/FavouriteAircraftData.hxx new file mode 100644 index 000000000..0f87d28fa --- /dev/null +++ b/src/GUI/FavouriteAircraftData.hxx @@ -0,0 +1,31 @@ +#ifndef FAVOURITEAIRCRAFTDATA_HXX +#define FAVOURITEAIRCRAFTDATA_HXX + +#include +#include +#include + +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 m_favourites; + +}; + +#endif // FAVOURITEAIRCRAFTDATA_HXX diff --git a/src/GUI/LauncherController.cxx b/src/GUI/LauncherController.cxx index fb2fe59e7..4a1b40cfb 100644 --- a/src/GUI/LauncherController.cxx +++ b/src/GUI/LauncherController.cxx @@ -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("FlightGear", 1, 0, "PixmapImage"); qmlRegisterType("FlightGear", 1, 0, "AirportDiagram"); + qmlRegisterType("FlightGear", 1, 0, "CarrierDiagram"); qmlRegisterType("FlightGear", 1, 0, "NavaidDiagram"); qmlRegisterType("FlightGear", 1, 0, "RouteDiagram"); qmlRegisterType("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 diff --git a/src/GUI/LocalAircraftCache.cxx b/src/GUI/LocalAircraftCache.cxx index a3d88ae20..32fb0f5c9 100644 --- a/src/GUI/LocalAircraftCache.cxx +++ b/src/GUI/LocalAircraftCache.cxx @@ -39,9 +39,6 @@ static quint32 CACHE_VERSION = 12; -const int STANDARD_THUMBNAIL_HEIGHT = 128; -//const int STANDARD_THUMBNAIL_WIDTH = 172; - AircraftItem::AircraftItem() { } diff --git a/src/GUI/LocationController.cxx b/src/GUI/LocationController.cxx index 2e1a05eff..ac934e878 100644 --- a/src/GUI/LocationController.cxx +++ b/src/GUI/LocationController.cxx @@ -31,6 +31,7 @@ #include #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(); } 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(); } // 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(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)); diff --git a/src/GUI/LocationController.hxx b/src/GUI/LocationController.hxx index 1310b0ea3..81737aaa8 100644 --- a/src/GUI/LocationController.hxx +++ b/src/GUI/LocationController.hxx @@ -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; diff --git a/src/GUI/ModelDataExtractor.cxx b/src/GUI/ModelDataExtractor.cxx index b34ba80ca..b222a02c2 100644 --- a/src/GUI/ModelDataExtractor.cxx +++ b/src/GUI/ModelDataExtractor.cxx @@ -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 {}; } diff --git a/src/GUI/QmlAircraftInfo.cxx b/src/GUI/QmlAircraftInfo.cxx index 8c2da779b..7c07baa14 100644 --- a/src/GUI/QmlAircraftInfo.cxx +++ b/src/GUI/QmlAircraftInfo.cxx @@ -14,6 +14,7 @@ #include
#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("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" diff --git a/src/GUI/QmlAircraftInfo.hxx b/src/GUI/QmlAircraftInfo.hxx index ddab2a3e8..434898849 100644 --- a/src/GUI/QmlAircraftInfo.hxx +++ b/src/GUI/QmlAircraftInfo.hxx @@ -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(); diff --git a/src/GUI/assets/aircraft-carrier-icon.svg b/src/GUI/assets/aircraft-carrier-icon.svg new file mode 100644 index 000000000..e594f579c --- /dev/null +++ b/src/GUI/assets/aircraft-carrier-icon.svg @@ -0,0 +1,113 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/src/GUI/assets/icons8-airport-50.png b/src/GUI/assets/icons8-airport-50.png new file mode 100644 index 000000000..e83ae59f8 Binary files /dev/null and b/src/GUI/assets/icons8-airport-50.png differ diff --git a/src/GUI/qml/AircraftDetailsView.qml b/src/GUI/qml/AircraftDetailsView.qml index 7863f4d5e..c0d21eba0 100644 --- a/src/GUI/qml/AircraftDetailsView.qml +++ b/src/GUI/qml/AircraftDetailsView.qml @@ -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 { diff --git a/src/GUI/qml/Location.qml b/src/GUI/qml/Location.qml index 9fb4ed5f9..7a03ff937 100644 --- a/src/GUI/qml/Location.qml +++ b/src/GUI/qml/Location.qml @@ -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" + } } } diff --git a/src/GUI/qml/LocationCarrierView.qml b/src/GUI/qml/LocationCarrierView.qml index ab4d21bca..b51c446d3 100644 --- a/src/GUI/qml/LocationCarrierView.qml +++ b/src/GUI/qml/LocationCarrierView.qml @@ -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 } diff --git a/src/GUI/qml/SettingControl.qml b/src/GUI/qml/SettingControl.qml index e469340dd..a17304c03 100644 --- a/src/GUI/qml/SettingControl.qml +++ b/src/GUI/qml/SettingControl.qml @@ -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 } } diff --git a/src/GUI/resources.qrc b/src/GUI/resources.qrc index 79ee6f85a..97b9edbca 100644 --- a/src/GUI/resources.qrc +++ b/src/GUI/resources.qrc @@ -155,6 +155,7 @@ assets/icons8-menu.svg assets/icons8-hide-50.png assets/icons8-cargo-ship-50.png - assets/aircraft-carrier-icon.png + assets/icons8-airport-50.png + assets/aircraft-carrier-icon.svg diff --git a/src/Include/config_cmake.h.in b/src/Include/config_cmake.h.in index 2dbdc4427..b55bc29a3 100644 --- a/src/Include/config_cmake.h.in +++ b/src/Include/config_cmake.h.in @@ -70,3 +70,5 @@ #cmakedefine ENABLE_COMPOSITOR #cmakedefine ENABLE_SWIFT + +#cmakedefine HAVE_SENTRY diff --git a/src/Input/CMakeLists.txt b/src/Input/CMakeLists.txt index 680991e9d..758172281 100644 --- a/src/Input/CMakeLists.txt +++ b/src/Input/CMakeLists.txt @@ -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() diff --git a/src/Input/FGEventInput.cxx b/src/Input/FGEventInput.cxx index 4b0a17aea..08c455f0b 100644 --- a/src/Input/FGEventInput.cxx +++ b/src/Input/FGEventInput.cxx @@ -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 diff --git a/src/Input/FGHIDEventInput.cxx b/src/Input/FGHIDEventInput.cxx index 4422a73ea..d86c45091 100644 --- a/src/Input/FGHIDEventInput.cxx +++ b/src/Input/FGHIDEventInput.cxx @@ -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_rawXMLDescriptor; + // all sets which will be send on the next update() call. std::set _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> 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> 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; diff --git a/src/Input/FGMacOSXEventInput.cxx b/src/Input/FGMacOSXEventInput.cxx deleted file mode 100644 index d7f6a33f4..000000000 --- a/src/Input/FGMacOSXEventInput.cxx +++ /dev/null @@ -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 fgmacosx 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. -// -// $Id$ - -#include "FGMacOSXEventInput.hxx" - -#include - -#include - -#include -#include -#include - -#include - -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(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(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 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(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(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(::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 NameCountMap; - NameCountMap nameCounts; - - std::set 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(reportId)); - } - - size_t len = d.size(); - const uint8_t* bytes = (const uint8_t*) d.data(); - - std::stringstream ss; - for (int i=0; i(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); - } - -} diff --git a/src/Input/FGMacOSXEventInput.hxx b/src/Input/FGMacOSXEventInput.hxx deleted file mode 100644 index 4dd2ecd4d..000000000 --- a/src/Input/FGMacOSXEventInput.hxx +++ /dev/null @@ -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 fgmacosx 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. -// -// $Id$ - -#ifndef __FGMACOSXEVENTINPUT_HXX_ -#define __FGMACOSXEVENTINPUT_HXX_ - -#include -#include -#include -#include - -#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 d; -}; - -#endif diff --git a/src/Input/FGMouseInput.cxx b/src/Input/FGMouseInput.cxx index 2e2930079..66c101fae 100644 --- a/src/Input/FGMouseInput.cxx +++ b/src/Input/FGMouseInput.cxx @@ -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(); } diff --git a/src/Input/input.cxx b/src/Input/input.cxx index d13586fbd..9549a9c75 100644 --- a/src/Input/input.cxx +++ b/src/Input/input.cxx @@ -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 diff --git a/src/Main/CMakeLists.txt b/src/Main/CMakeLists.txt index 391446f73..fad846170 100644 --- a/src/Main/CMakeLists.txt +++ b/src/Main/CMakeLists.txt @@ -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 $ DESTINATION fgfs.app/Contents/MacOS OPTIONAL) + else() + # install in the bin-dir, next to the application binary + install(FILES $ DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) + endif() +endif() + if(ENABLE_METAR) add_executable(metar metar_main.cxx) target_link_libraries(metar diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx index a1b3460bc..90f860a3c 100644 --- a/src/Main/fg_init.cxx +++ b/src/Main/fg_init.cxx @@ -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( diff --git a/src/Main/fg_io.cxx b/src/Main/fg_io.cxx index 702d8b139..a5c8a4d2f 100644 --- a/src/Main/fg_io.cxx +++ b/src/Main/fg_io.cxx @@ -216,11 +216,12 @@ FGIO::parse_port_config( const string_list& tokens ) "(one argument expected: --hla-local=" ); 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 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 { diff --git a/src/Main/fg_props.cxx b/src/Main/fg_props.cxx index eee6d0006..d0e2bead6 100644 --- a/src/Main/fg_props.cxx +++ b/src/Main/fg_props.cxx @@ -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("/position/latitude-string", getLatitudeString); _tiedProperties.Tie("/position/longitude-string", getLongitudeString); - // Orientation - _tiedProperties.Tie("/orientation/heading-magnetic-deg", getHeadingMag); - _tiedProperties.Tie("/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); } diff --git a/src/Main/fg_props.hxx b/src/Main/fg_props.hxx index 6399ecbbd..894f69e4d 100644 --- a/src/Main/fg_props.hxx +++ b/src/Main/fg_props.hxx @@ -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(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(*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"); } diff --git a/src/Main/options.cxx b/src/Main/options.cxx index ea292a55d..729bad95f 100644 --- a/src/Main/options.cxx +++ b/src/Main/options.cxx @@ -21,9 +21,7 @@ // $Id$ -#ifdef HAVE_CONFIG_H -# include -#endif +#include #include #include @@ -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 }, diff --git a/src/Main/positioninit.cxx b/src/Main/positioninit.cxx index d53b4990a..42503eee8 100644 --- a/src/Main/positioninit.cxx +++ b/src/Main/positioninit.cxx @@ -52,8 +52,8 @@ using std::string; namespace flightgear { - - + + enum InitPosResult { ExactPosition, VicinityPosition, @@ -180,7 +180,9 @@ std::tuple 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 ca // Set current_options lon/lat given an aircraft carrier id static InitPosResult setFinalPosFromCarrier( const string& carrier, const string& posid ) { - + SGSharedPtr 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 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 diff --git a/src/MultiPlayer/multiplaymgr.cxx b/src/MultiPlayer/multiplaymgr.cxx index 56cb1d67d..7376d914d 100644 --- a/src/MultiPlayer/multiplaymgr.cxx +++ b/src/MultiPlayer/multiplaymgr.cxx @@ -2449,9 +2449,9 @@ FGMultiplayMgr::addMultiplayer(const std::string& callsign, } } - /* Copy values from our local /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 /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; diff --git a/src/Scenery/tilemgr.cxx b/src/Scenery/tilemgr.cxx index a29a6aa4b..2c3f8f0a4 100644 --- a/src/Scenery/tilemgr.cxx +++ b/src/Scenery/tilemgr.cxx @@ -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 viewer(globals->get_renderer()->getViewer()); - viewer->getDatabasePager()->setTargetMaximumNumberOfPageLOD(v); + int v = prop->getIntValue(); + osg::ref_ptr 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(): diff --git a/src/Scripting/NasalPositioned.cxx b/src/Scripting/NasalPositioned.cxx index 3219b9909..6066954d5 100644 --- a/src/Scripting/NasalPositioned.cxx +++ b/src/Scripting/NasalPositioned.cxx @@ -21,6 +21,7 @@ #include "config.h" #include +#include #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(); _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 }, diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index 711a53c13..bd1819127 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -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, diff --git a/src/Scripting/NasalSys.hxx b/src/Scripting/NasalSys.hxx index 8b3e3fe23..30d848d8d 100644 --- a/src/Scripting/NasalSys.hxx +++ b/src/Scripting/NasalSys.hxx @@ -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, diff --git a/src/Traffic/TrafficMgr.cxx b/src/Traffic/TrafficMgr.cxx index 15b070f72..40dff6cd3 100644 --- a/src/Traffic/TrafficMgr.cxx +++ b/src/Traffic/TrafficMgr.cxx @@ -505,7 +505,7 @@ void FGTrafficManager::shutdown() bool FGTrafficManager::doDataSync() { - simgear::SGTerraSync* terraSync = static_cast(globals->get_subsystem("terrasync")); + auto terraSync = globals->get_subsystem(); bool doDataSync = fgGetBool("/sim/terrasync/ai-data-enabled"); if (doDataSync && terraSync) { if (!trafficSyncRequested) { diff --git a/src/Viewer/view.cxx b/src/Viewer/view.cxx index cb640ef75..268727f6f 100644 --- a/src/Viewer/view.cxx +++ b/src/Viewer/view.cxx @@ -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")) { diff --git a/utils/fgai/CMakeLists.txt b/utils/fgai/CMakeLists.txt index 5c6f56924..9a68538b3 100644 --- a/utils/fgai/CMakeLists.txt +++ b/utils/fgai/CMakeLists.txt @@ -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}) diff --git a/utils/fgcom/positions.hxx b/utils/fgcom/positions.hxx old mode 100644 new mode 100755 diff --git a/utils/fgpanel/ApplicationProperties.cxx b/utils/fgpanel/ApplicationProperties.cxx index 2224e0730..7f02348b6 100644 --- a/utils/fgpanel/ApplicationProperties.cxx +++ b/utils/fgpanel/ApplicationProperties.cxx @@ -16,7 +16,6 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // - #ifdef _WIN32 # include // for getcwd() #else // !_WIN32 diff --git a/utils/fgpanel/CMakeLists.txt b/utils/fgpanel/CMakeLists.txt index 96a192ba9..13a4b4433 100644 --- a/utils/fgpanel/CMakeLists.txt +++ b/utils/fgpanel/CMakeLists.txt @@ -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) diff --git a/utils/fgpanel/FGPanel.cxx b/utils/fgpanel/FGPanel.cxx index b89e18926..3ca58ea34 100644 --- a/utils/fgpanel/FGPanel.cxx +++ b/utils/fgpanel/FGPanel.cxx @@ -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" diff --git a/utils/fgpanel/FGPanel.hxx b/utils/fgpanel/FGPanel.hxx index 718e3dc9b..9f186d887 100644 --- a/utils/fgpanel/FGPanel.hxx +++ b/utils/fgpanel/FGPanel.hxx @@ -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 (); diff --git a/utils/fgpanel/FGPanelApplication.cxx b/utils/fgpanel/FGPanelApplication.cxx index 6073ebbbe..d8ff4727e 100644 --- a/utils/fgpanel/FGPanelApplication.cxx +++ b/utils/fgpanel/FGPanelApplication.cxx @@ -36,7 +36,6 @@ #include #elif defined (_GLES2) #include -#include "GLES_utils.hxx" #else #include // Must be included before #include diff --git a/utils/fgpanel/FGPanelProtocol.hxx b/utils/fgpanel/FGPanelProtocol.hxx index df818300b..33e72122c 100644 --- a/utils/fgpanel/FGPanelProtocol.hxx +++ b/utils/fgpanel/FGPanelProtocol.hxx @@ -28,8 +28,7 @@ class PropertySetter; typedef vector PropertySetterVector; -class FGPanelProtocol : public SGSubsystem -{ +class FGPanelProtocol : public SGSubsystem { public: FGPanelProtocol (SGPropertyNode_ptr a_Root); virtual ~FGPanelProtocol (); diff --git a/utils/fgpanel/FGTextLayer.cxx b/utils/fgpanel/FGTextLayer.cxx index 8ca9de1fe..caf50bd1f 100644 --- a/utils/fgpanel/FGTextLayer.cxx +++ b/utils/fgpanel/FGTextLayer.cxx @@ -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" diff --git a/utils/fgpanel/README.RPi b/utils/fgpanel/README.RPi index ac56fc14b..6d3f6dd3a 100644 --- a/utils/fgpanel/README.RPi +++ b/utils/fgpanel/README.RPi @@ -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 ======== diff --git a/utils/fgpanel/panel_io.hxx b/utils/fgpanel/panel_io.hxx index d24db4e0f..fef6e3ec7 100644 --- a/utils/fgpanel/panel_io.hxx +++ b/utils/fgpanel/panel_io.hxx @@ -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"; } diff --git a/utils/fgviewer/CMakeLists.txt b/utils/fgviewer/CMakeLists.txt index f98f85626..dd690cecd 100644 --- a/utils/fgviewer/CMakeLists.txt +++ b/utils/fgviewer/CMakeLists.txt @@ -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})