first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
//
// AircraftDirVisitorBase.hxx - helper to traverse a heirarchy containing
// aircraft dirs
//
// Written by Curtis Olson, started August 1997.
//
// Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 FG_MAIN_AIRCRAFT_DIR_VISITOR_HXX
#define FG_MAIN_AIRCRAFT_DIR_VISITOR_HXX
#include <simgear/misc/sg_dir.hxx>
#include <simgear/misc/sg_path.hxx>
#include <Main/globals.hxx>
class AircraftDirVistorBase
{
public:
protected:
enum VisitResult {
VISIT_CONTINUE = 0,
VISIT_DONE,
VISIT_ERROR
};
AircraftDirVistorBase() :
_maxDepth(2)
{
}
VisitResult visitAircraftPaths()
{
const simgear::PathList& paths(globals->get_aircraft_paths());
simgear::PathList::const_iterator it = paths.begin();
for (; it != paths.end(); ++it) {
VisitResult vr = visitDir(*it, 0);
if (vr != VISIT_CONTINUE) {
return vr;
}
} // of aircraft paths iteration
// if we reach this point, search the default location (always last)
SGPath rootAircraft(globals->get_fg_root());
rootAircraft.append("Aircraft");
return visitDir(rootAircraft, 0);
}
VisitResult visitPath(const SGPath& path, unsigned int depth)
{
if (!path.exists()) {
return VISIT_ERROR;
}
return visit(path);
}
VisitResult visitDir(const simgear::Dir& d, unsigned int depth)
{
if (!d.exists()) {
SG_LOG(SG_GENERAL, SG_WARN, "visitDir: no such path:" << d.path());
return VISIT_CONTINUE;
}
if (depth >= _maxDepth) {
return VISIT_CONTINUE;
}
bool recurse = true;
simgear::PathList setFiles(d.children(simgear::Dir::TYPE_FILE, "-set.xml"));
simgear::PathList::iterator p;
for (p = setFiles.begin(); p != setFiles.end(); ++p) {
// if we found a -set.xml at this level, don't recurse any deeper
recurse = false;
VisitResult vr = visit(*p);
if (vr != VISIT_CONTINUE) {
return vr;
}
} // of -set.xml iteration
if (!recurse) {
return VISIT_CONTINUE;
}
simgear::PathList subdirs(d.children(simgear::Dir::TYPE_DIR | simgear::Dir::NO_DOT_OR_DOTDOT));
for (p = subdirs.begin(); p != subdirs.end(); ++p) {
VisitResult vr = visitDir(*p, depth + 1);
if (vr != VISIT_CONTINUE) {
return vr;
}
}
return VISIT_CONTINUE;
} // of visitDir method
virtual VisitResult visit(const SGPath& path) = 0;
private:
unsigned int _maxDepth;
};
#endif // of FG_MAIN_AIRCRAFT_DIR_VISITOR_HXX

125
src/Main/CMakeLists.txt Normal file
View File

@@ -0,0 +1,125 @@
# CMake module includes.
include(FlightGearComponent)
include(SetupFGFSBundle)
include(SetupFGFSEmbeddedResources)
include(SetupFGFSIncludes)
include(SetupFGFSLibraries)
include(SetupMSVCGrouping)
# Set up the Main FG file sources and headers (excluding bootstrap.cxx and its main() function).
if(MSVC)
set(MS_RESOURCE_FILE flightgear.rc)
endif(MSVC)
set(SOURCES
fg_commands.cxx
fg_init.cxx
fg_io.cxx
fg_os_common.cxx
fg_scene_commands.cxx
fg_props.cxx
FGInterpolator.cxx
globals.cxx
locale.cxx
logger.cxx
main.cxx
options.cxx
positioninit.cxx
screensaver_control.cxx
subsystemFactory.cxx
util.cxx
XLIFFParser.cxx
ErrorReporter.cxx
${MS_RESOURCE_FILE}
)
set(HEADERS
AircraftDirVisitorBase.hxx
fg_commands.hxx
fg_init.hxx
fg_io.hxx
fg_props.hxx
FGInterpolator.hxx
globals.hxx
locale.hxx
logger.hxx
main.hxx
options.hxx
positioninit.hxx
screensaver_control.hxx
subsystemFactory.hxx
util.hxx
XLIFFParser.hxx
ErrorReporter.hxx
sentryIntegration.hxx
)
flightgear_component(Main "${SOURCES}" "${HEADERS}" sentryIntegration.cxx)
# the main() function
set(MAIN_SOURCE
bootstrap.cxx
# we only want this file when building FGFS, so it can't be
# part of fgfsObjects
${CMAKE_SOURCE_DIR}/src/Scripting/NasalUnitTesting.cxx
)
# Set up the embedded resources.
setup_fgfs_embedded_resources()
# Sort the sources and headers for MSVC.
setup_msvc_grouping()
# souerces which are different for fgfs vs the test-suite
get_property(TEST_SOURCES GLOBAL PROPERTY FG_TEST_SOURCES)
# important we pass WIN32 here so the console is optional. Other
# platforms ignore this option. If a console is needed we allocate
# it manually via AllocConsole()
# similarly pass MACOSX_BUNDLE so we generate a .app on Mac
add_executable(fgfs
WIN32
MACOSX_BUNDLE
${MAIN_SOURCE}
${TEST_SOURCES}
$<TARGET_OBJECTS:fgfsObjects>
)
add_dependencies(fgfs buildId)
# explicitly disable automoc for main fgfs target
set_property(TARGET fgfs PROPERTY AUTOMOC OFF)
# MacOSX bundle packaging
if(APPLE)
setup_fgfs_bundle(fgfs)
endif()
# Set up the target links.
setup_fgfs_libraries(fgfs)
export_debug_symbols(fgfs)
# Additional search paths for includes.
setup_fgfs_includes(fgfs)
# this has to live here for compatability with older CMake versions
if (APPLE)
install(TARGETS fgfs BUNDLE DESTINATION .)
else()
install(TARGETS fgfs RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
if(ENABLE_METAR)
add_executable(metar metar_main.cxx)
target_link_libraries(metar SimGearScene)
install(TARGETS metar RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
# ensure run/debug paths are set automatically in Visual Studio
if (MSVC)
file(TO_NATIVE_PATH "${FG_QT_BIN_DIR}" _qt5_bin_dir_native)
file(TO_NATIVE_PATH "${FINAL_MSVC_3RDPARTY_DIR}/bin" _msvc_3rdparty_bin_dir)
file(TO_NATIVE_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}" _install_bin_dir)
set_property(TARGET fgfs PROPERTY
VS_GLOBAL_LocalDebuggerEnvironment "PATH=${_install_bin_dir};${_msvc_3rdparty_bin_dir};${_qt5_bin_dir_native}")
endif()

944
src/Main/ErrorReporter.cxx Normal file
View File

@@ -0,0 +1,944 @@
/*
* Copyright (C) 2021 James Turner
*
* This file is part of the program FlightGear.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "ErrorReporter.hxx"
#include <algorithm>
#include <ctime> // for strftime, etc
#include <deque>
#include <map>
#include <mutex>
#include <simgear/debug/ErrorReportingCallback.hxx>
#include <simgear/debug/LogCallback.hxx>
#include <simgear/timing/timestamp.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/structure/commands.hxx>
#include <GUI/MessageBox.hxx>
#include <GUI/dialog.hxx>
#include <GUI/new_gui.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Main/locale.hxx>
#include <Main/options.hxx>
#include <Main/sentryIntegration.hxx>
#include <Scripting/NasalClipboard.hxx> // clipboard access
using std::string;
namespace {
const double MinimumIntervalBetweenDialogs = 5.0;
const double NoNewErrorsTimeout = 8.0;
// map of context values; we allow a stack of values for
// cases such as sub-sub-model loading where we might process repeated
// nested model XML files
using PerThreadErrorContextStack = std::map<std::string, string_list>;
// context storage. This is per-thread so parallel osgDB threads don't
// confuse each other
static thread_local PerThreadErrorContextStack thread_errorContextStack;
/**
Define the aggregation points we use for errors, to direct the user towards the likely
source of problems.
*/
enum class Aggregation {
MainAircraft,
HangarAircraft, // handle hangar aircraft differently
CustomScenery,
TerraSync,
AddOn,
Scenario,
InputDevice,
FGData,
MultiPlayer,
Unknown, ///< error coudln't be attributed more specifcially
OutOfMemory, ///< seperate category to give it a custom message
Traffic,
ShadersEffects
};
// these should correspond to simgear::ErrorCode enum
// they map to translateable strings in fgdata/Translations/sys.xml
string_list static_errorIds = {
"error-missing-shader",
"error-loading-texture",
"error-xml-model-load",
"error-3D-model-load",
"error-btg-load",
"error-scenario-load",
"error-dialog-load",
"error-audio-fx-load",
"error-xml-load-command",
"error-aircraft-systems",
"error-input-device-config",
"error-ai-traffic-schedule",
"error-terrasync"};
string_list static_errorTypeIds = {
"error-type-unknown",
"error-type-not-found",
"error-type-out-of-memory",
"error-type-bad-header",
"error-type-bad-data",
"error-type-misconfigured",
"error-type-io",
"error-type-network"};
string_list static_categoryIds = {
"error-category-aircraft",
"error-category-aircraft-from-hangar",
"error-category-custom-scenery",
"error-category-terrasync",
"error-category-addon",
"error-category-scenario",
"error-category-input-device",
"error-category-fgdata",
"error-category-multiplayer",
"error-category-unknown",
"error-category-out-of-memory",
"error-category-traffic",
"error-category-shaders"};
class RecentLogCallback : public simgear::LogCallback
{
public:
RecentLogCallback() : simgear::LogCallback(SG_ALL, SG_INFO)
{
}
bool doProcessEntry(const simgear::LogEntry& e) override
{
ostringstream os;
if (e.file != nullptr) {
os << e.file << ":" << e.line << ":\t";
}
os << e.message;
std::lock_guard<std::mutex> g(_lock); // begin access to shared state
_recentLogEntries.push_back(os.str());
while (_recentLogEntries.size() > _preceedingLogMessageCount) {
_recentLogEntries.pop_front();
}
return true;
}
string_list copyRecentLogEntries() const
{
std::lock_guard<std::mutex> g(_lock); // begin access to shared state
string_list r(_recentLogEntries.begin(), _recentLogEntries.end());
return r;
}
private:
mutable std::mutex _lock;
size_t _preceedingLogMessageCount = 6;
using LogDeque = std::deque<string>;
LogDeque _recentLogEntries;
};
} // namespace
namespace flightgear {
class ErrorReporter::ErrorReporterPrivate
{
public:
bool _reportsDirty = false;
std::mutex _lock;
SGTimeStamp _nextShowTimeout;
bool _haveDonePostInit = false;
SGPropertyNode_ptr _enabledNode;
SGPropertyNode_ptr _displayNode;
SGPropertyNode_ptr _activeErrorNode;
SGPropertyNode_ptr _mpReportNode;
using ErrorContext = std::map<std::string, std::string>;
/**
strucutre representing a single error which has cocurred
*/
struct ErrorOcurrence {
simgear::ErrorCode code;
simgear::LoadFailure type;
string detailedInfo;
sg_location origin;
time_t when;
string_list log;
ErrorContext context;
bool hasContextKey(const std::string& key) const
{
return context.find(key) != context.end();
}
std::string getContextValue(const std::string& key) const
{
auto it = context.find(key);
if (it == context.end())
return {};
return it->second;
}
};
using OccurrenceVec = std::vector<ErrorOcurrence>;
std::unique_ptr<RecentLogCallback> _logCallback;
bool _logCallbackRegistered = false;
string _terrasyncPathPrefix;
string _fgdataPathPrefix;
string _aircraftDirectoryName;
/**
@brief hueristic to identify relative paths as origination from the main aircraft as opposed
to something else. This is based on containing the aircraft directory name.
*/
bool isMainAircraftPath(const std::string& path) const;
/**
structure representing one or more errors, aggregated together
*/
struct AggregateReport {
Aggregation type;
std::string parameter; ///< base on type, the specific point. For example the add-on ID, AI model ident or custom scenery path
SGTimeStamp lastErrorTime;
bool haveShownToUser = false;
OccurrenceVec errors;
bool isCritical = false;
bool addOccurence(const ErrorOcurrence& err);
};
using AggregateErrors = std::vector<AggregateReport>;
AggregateErrors _aggregated;
int _activeReportIndex = -1;
string_list _significantProperties; ///< properties we want to include in reports, for debugging
/**
find the appropriate agrgegate for an error, based on its context
*/
AggregateErrors::iterator getAggregateForOccurence(const ErrorOcurrence& oc);
AggregateErrors::iterator getAggregate(Aggregation ag, const std::string& param = {});
void collectError(simgear::LoadFailure type, simgear::ErrorCode code, const std::string& details, const sg_location& location)
{
ErrorOcurrence occurrence{code, type, details, location, time(nullptr), string_list(), ErrorContext()};
// snapshot the top of the context stacks into our occurence data
for (const auto& c : thread_errorContextStack) {
occurrence.context[c.first] = c.second.back();
}
occurrence.log = _logCallback->copyRecentLogEntries();
std::lock_guard<std::mutex> g(_lock); // begin access to shared state
auto it = getAggregateForOccurence(occurrence);
// add to the occurence, if it's not a duplicate
if (!it->addOccurence(occurrence)) {
return; // duplicate, nothing else to do
}
// log it once we know it's not a duplicate
SG_LOG(SG_GENERAL, SG_WARN, "Error:" << static_errorTypeIds.at(static_cast<int>(type)) << " from " << static_errorIds.at(static_cast<int>(code)) << "::" << details << "\n\t" << location.asString());
it->lastErrorTime.stamp();
_reportsDirty = true;
const auto ty = it->type;
// decide if it's a critical error or not
if ((ty == Aggregation::OutOfMemory) || (ty == Aggregation::InputDevice)) {
it->isCritical = true;
}
// aircraft errors are critical if they occur during initial
// aircraft load, otherwise we just show the warning
if (!_haveDonePostInit && (ty == Aggregation::MainAircraft)) {
it->isCritical = true;
}
if (code == simgear::ErrorCode::LoadEffectsShaders) {
it->isCritical = true;
}
}
void collectContext(const std::string& key, const std::string& value)
{
if (value == "POP") {
auto it = thread_errorContextStack.find(key);
assert(it != thread_errorContextStack.end());
assert(!it->second.empty());
it->second.pop_back();
if (it->second.empty()) {
thread_errorContextStack.erase(it);
}
} else {
thread_errorContextStack[key].push_back(value);
}
}
void presentErrorToUser(AggregateReport& report)
{
const int catId = static_cast<int>(report.type);
auto catLabel = globals->get_locale()->getLocalizedString(static_categoryIds.at(catId), "sys");
catLabel = simgear::strutils::replace(catLabel, "%VALUE%", report.parameter);
_displayNode->setStringValue("category", catLabel);
auto ns = globals->get_locale()->getLocalizedString("error-next-steps", "sys");
_displayNode->setStringValue("next-steps", ns);
// remove any existing error children
_displayNode->removeChildren("error");
ostringstream detailsTextStream;
// add all the discrete errors as child nodes with all their information
for (const auto& e : report.errors) {
SGPropertyNode_ptr errNode = _displayNode->addChild("error");
const auto em = globals->get_locale()->getLocalizedString(static_errorIds.at(static_cast<int>(e.code)), "sys");
errNode->setStringValue("message", em);
errNode->setIntValue("code", static_cast<int>(e.code));
const auto et = globals->get_locale()->getLocalizedString(static_errorTypeIds.at(static_cast<int>(e.type)), "sys");
errNode->setStringValue("type-message", et);
errNode->setIntValue("type", static_cast<int>(e.type));
errNode->setStringValue("details", e.detailedInfo);
detailsTextStream << em << ": " << et << "\n";
detailsTextStream << "(" << e.detailedInfo << ")\n";
if (e.origin.isValid()) {
errNode->setStringValue("location", e.origin.asString());
detailsTextStream << " from:" << e.origin.asString() << "\n";
}
detailsTextStream << "\n";
} // of errors within the report iteration
_displayNode->setStringValue("details-text", detailsTextStream.str());
_activeErrorNode->setBoolValue(true);
report.haveShownToUser = true;
// compute index; slightly clunky, find the report in _aggregated
auto it = std::find_if(_aggregated.begin(), _aggregated.end(), [report](const AggregateReport& a) {
if (a.type != report.type) return false;
return report.parameter.empty() ? true : report.parameter == a.parameter;
});
assert(it != _aggregated.end());
_activeReportIndex = static_cast<int>(std::distance(_aggregated.begin(), it));
_displayNode->setBoolValue("index", _activeReportIndex);
_displayNode->setBoolValue("have-next", _activeReportIndex < (int) _aggregated.size() - 1);
_displayNode->setBoolValue("have-previous", _activeReportIndex > 0);
}
void sendReportToSentry(AggregateReport& report)
{
const int catId = static_cast<int>(report.type);
flightgear::sentryReportUserError(static_categoryIds.at(catId), _displayNode->getStringValue());
}
void writeReportToStream(const AggregateReport& report, std::ostream& os) const;
void writeContextToStream(const ErrorOcurrence& error, std::ostream& os) const;
void writeLogToStream(const ErrorOcurrence& error, std::ostream& os) const;
void writeSignificantPropertiesToStream(std::ostream& os) const;
bool dismissReportCommand(const SGPropertyNode* args, SGPropertyNode*);
bool saveReportCommand(const SGPropertyNode* args, SGPropertyNode*);
bool showErrorReportCommand(const SGPropertyNode* args, SGPropertyNode*);
};
auto ErrorReporter::ErrorReporterPrivate::getAggregateForOccurence(const ErrorReporter::ErrorReporterPrivate::ErrorOcurrence& oc)
-> AggregateErrors::iterator
{
// all OOM errors go to a dedicated category. This is so we don't blame
// out of memory on the aircraft/scenery/etc, when it's not the underlying
// cause.
if (oc.type == simgear::LoadFailure::OutOfMemory) {
return getAggregate(Aggregation::OutOfMemory, {});
}
if (oc.hasContextKey("primary-aircraft")) {
const auto fullId = fgGetString("/sim/aircraft-id");
if (fullId != fgGetString("/sim/aircraft")) {
return getAggregate(Aggregation::MainAircraft, fullId);
}
return getAggregate(Aggregation::HangarAircraft, fullId);
}
if (oc.hasContextKey("multiplayer")) {
return getAggregate(Aggregation::MultiPlayer, {});
}
// traffic cases: need to handle errors in the traffic files (schedule, rwyuse)
// but also errors loading aircraft models associated with traffic
if (oc.code == simgear::ErrorCode::AITrafficSchedule) {
return getAggregate(Aggregation::Traffic, {});
}
if (oc.hasContextKey("traffic-aircraft-callsign")) {
return getAggregate(Aggregation::Traffic, {});
}
// all TerraSync coded errors go there: this is errors for the
// actual download process (eg, failed to write to disk)
if (oc.code == simgear::ErrorCode::TerraSync) {
return getAggregate(Aggregation::TerraSync, {});
}
if (oc.hasContextKey("terrain-stg") || oc.hasContextKey("btg")) {
// determine if it's custom scenery, TerraSync or FGData
// bucket is no use here, we need to check the BTG/XML/STG path etc.
// STG is probably the best bet. This ensures if a custom scenery
// STG references a model, XML or texture in FGData or TerraSync
// incorrectly, we attribute the error to the scenery, which is
// likely what we want/expect
auto path = oc.hasContextKey("terrain-stg") ? oc.getContextValue("terrain-stg") : oc.getContextValue("btg");
// custom scenery, find out the prefix
for (const auto& sceneryPath : globals->get_fg_scenery()) {
const auto pathStr = sceneryPath.utf8Str();
if (simgear::strutils::starts_with(path, pathStr)) {
return getAggregate(Aggregation::CustomScenery, pathStr);
}
}
// try generic paths
if (simgear::strutils::starts_with(path, _fgdataPathPrefix)) {
return getAggregate(Aggregation::FGData, {});
} else if (simgear::strutils::starts_with(path, _terrasyncPathPrefix)) {
return getAggregate(Aggregation::TerraSync, {});
}
// shouldn't ever happen
return getAggregate(Aggregation::CustomScenery, {});
}
if (oc.hasContextKey("scenario-path")) {
const auto scenarioPath = oc.getContextValue("scenario-path");
return getAggregate(Aggregation::Scenario, scenarioPath);
}
if (oc.hasContextKey("input-device")) {
return getAggregate(Aggregation::InputDevice, oc.getContextValue("input-device"));
}
// start guessing :)
// from this point on we're using less reliable inferences about where the
// error came from, trying to avoid 'unknown'
if (isMainAircraftPath(oc.origin.asString())) {
const auto fullId = fgGetString("/sim/aircraft-id");
if (fullId != fgGetString("/sim/aircraft")) {
return getAggregate(Aggregation::MainAircraft, fullId);
}
return getAggregate(Aggregation::HangarAircraft, fullId);
}
// GUI dialog errors often have no context
if (oc.code == simgear::ErrorCode::GUIDialog) {
// check if it's an add-on and use that
return getAggregate(Aggregation::FGData);
}
// becuase we report shader errors from the main thread, they don't
// get attributed. Collect them into their own category, which also
// means we can display a more specific message
if (oc.code == simgear::ErrorCode::LoadEffectsShaders) {
return getAggregate(Aggregation::ShadersEffects);
}
return getAggregate(Aggregation::Unknown);
}
auto ErrorReporter::ErrorReporterPrivate::getAggregate(Aggregation ag, const std::string& param)
-> AggregateErrors::iterator
{
auto it = std::find_if(_aggregated.begin(), _aggregated.end(), [ag, &param](const AggregateReport& a) {
if (a.type != ag) return false;
return param.empty() ? true : param == a.parameter;
});
if (it == _aggregated.end()) {
AggregateReport r;
r.type = ag;
r.parameter = param;
_aggregated.push_back(r);
it = _aggregated.end() - 1;
}
return it;
}
void ErrorReporter::ErrorReporterPrivate::writeReportToStream(const AggregateReport& report, std::ostream& os) const
{
os << "FlightGear " << VERSION << " error report, created at ";
{
char buf[64];
time_t now = time(nullptr);
strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now));
os << buf << endl;
}
os << "Category:" << static_categoryIds.at(static_cast<int>(report.type)) << endl;
if (!report.parameter.empty()) {
os << "\tParameter:" << report.parameter << endl;
}
os << endl; // insert a blank line after header data
int index = 1;
char whenBuf[64];
for (const auto& err : report.errors) {
os << "Error " << index++ << std::endl;
os << "\tcode:" << static_errorIds.at(static_cast<int>(err.code)) << endl;
os << "\ttype:" << static_errorTypeIds.at(static_cast<int>(err.type)) << endl;
strftime(whenBuf, sizeof(whenBuf), "%H:%M:%S GMT", gmtime(&err.when));
os << "\twhen:" << whenBuf << endl;
os << "\t" << err.detailedInfo << std::endl;
os << "\tlocation:" << err.origin.asString() << endl;
writeContextToStream(err, os);
writeLogToStream(err, os);
os << std::endl; // trailing blank line
}
os << "Command line / launcher / fgfsrc options" << endl;
for (auto o : Options::sharedInstance()->extractOptions()) {
os << "\t" << o << "\n";
}
os << endl;
writeSignificantPropertiesToStream(os);
}
void ErrorReporter::ErrorReporterPrivate::writeSignificantPropertiesToStream(std::ostream& os) const
{
os << "Properties:" << endl;
for (const auto& ps : _significantProperties) {
auto node = fgGetNode(ps);
if (!node) {
os << "\t" << ps << ": not defined\n";
} else {
os << "\t" << ps << ": " << node->getStringValue() << "\n";
}
}
os << endl;
}
bool ErrorReporter::ErrorReporterPrivate::dismissReportCommand(const SGPropertyNode* args, SGPropertyNode*)
{
std::lock_guard<std::mutex> g(_lock);
_activeErrorNode->setBoolValue(false);
if (args->getBoolValue("dont-show")) {
// TODO implement dont-show behaviour
}
// clear any values underneath displayNode?
_nextShowTimeout.stamp();
_reportsDirty = true; // set this so we check for another report to present
_activeReportIndex = -1;
return true;
}
bool ErrorReporter::ErrorReporterPrivate::showErrorReportCommand(const SGPropertyNode* args, SGPropertyNode*)
{
std::lock_guard<std::mutex> g(_lock);
if (_aggregated.empty()) {
return false;
}
const auto numAggregates = static_cast<int>(_aggregated.size());
if (args->getBoolValue("next")) {
_activeReportIndex++;
if (_activeReportIndex >= numAggregates) {
return false;
}
} else if (args->getBoolValue("previous")) {
if (_activeReportIndex < 1) {
return false;
}
_activeReportIndex--;
} else if (args->hasChild("index")) {
_activeReportIndex = args->getIntValue("index");
if ((_activeReportIndex < 0) || (_activeReportIndex >= numAggregates)) {
return false;
}
} else {
_activeReportIndex = 0;
}
auto& report = _aggregated.at(_activeReportIndex);
presentErrorToUser(report);
auto gui = globals->get_subsystem<NewGUI>();
if (!gui->getDialog("error-report")) {
gui->showDialog("error-report");
}
return true;
}
bool ErrorReporter::ErrorReporterPrivate::saveReportCommand(const SGPropertyNode* args, SGPropertyNode*)
{
if (_activeReportIndex < 0) {
return false;
}
const auto& report = _aggregated.at(_activeReportIndex);
const string where = args->getStringValue("where");
string when;
{
char buf[64];
time_t now = time(nullptr);
strftime(buf, sizeof(buf), "%Y%m%d", gmtime(&now));
when = buf;
}
if (where.empty() || (where == "!desktop")) {
SGPath p = SGPath::desktop() / ("flightgear_error_" + when + ".txt");
int uniqueCount = 2;
while (p.exists()) {
p = SGPath::desktop() / ("flightgear_error_" + when + "_" + std::to_string(uniqueCount++) + ".txt");
}
sg_ofstream f(p, std::ios_base::out);
writeReportToStream(report, f);
} else if (where == "!clipboard") {
std::ostringstream os;
writeReportToStream(report, os);
NasalClipboard::getInstance()->setText(os.str());
}
return true;
}
void ErrorReporter::ErrorReporterPrivate::writeContextToStream(const ErrorOcurrence& error, std::ostream& os) const
{
os << "\tcontext:\n";
for (const auto& c : error.context) {
os << "\t\t" << c.first << " = " << c.second << "\n";
}
}
void ErrorReporter::ErrorReporterPrivate::writeLogToStream(const ErrorOcurrence& error, std::ostream& os) const
{
os << "\tpreceeding log:\n";
for (const auto& c : error.log) {
os << "\t\t" << c << "\n";
}
}
bool ErrorReporter::ErrorReporterPrivate::AggregateReport::addOccurence(const ErrorOcurrence& err)
{
auto it = std::find_if(errors.begin(), errors.end(), [err](const ErrorOcurrence& ext) {
// check if the two occurences match for the purposes of
// de-duplication.
return (ext.code == err.code) &&
(ext.type == err.type) &&
(ext.detailedInfo == err.detailedInfo) &&
(ext.origin.asString() == err.origin.asString());
});
if (it != errors.end()) {
return false; // duplicate, don't add
}
errors.push_back(err);
lastErrorTime.stamp();
return true;
}
bool ErrorReporter::ErrorReporterPrivate::isMainAircraftPath(const std::string& path) const
{
const auto pos = path.find(_aircraftDirectoryName);
return pos != std::string::npos;
}
////////////////////////////////////////////
ErrorReporter::ErrorReporter() : d(new ErrorReporterPrivate)
{
d->_logCallback.reset(new RecentLogCallback);
// define significant properties
d->_significantProperties = {
"/sim/aircraft-id",
"/sim/aircraft-dir",
"/sim/rendering/gl-version",
"/sim/rendering/gl-renderer",
"/sim/rendering/gl-shading-language-version",
"/sim/rendering/max-texture-size",
"/sim/rendering/max-texture-units",
"/sim/rendering/shaders/skydome",
"/sim/rendering/shaders/water",
"/sim/rendering/shaders/model",
"/sim/rendering/shaders/landmass",
"/sim/rendering/shaders/vegetation-effects",
"/sim/rendering/shaders/transition",
"/sim/rendering/max-paged-lod",
"/sim/rendering/photoscenery/enabled",
"/sim/rendering/preset-description",
"/sim/rendering/multithreading-mode",
"/sim/rendering/multi-sample-buffers",
"/sim/rendering/multi-samples",
"/scenery/use-vpb"};
}
ErrorReporter::~ErrorReporter()
{
// if we are deleted withut being shutdown(), ensure we clean
// up our logging callback
if (d->_logCallbackRegistered) {
sglog().removeCallback(d->_logCallback.get());
}
}
void ErrorReporter::bind()
{
SGPropertyNode_ptr n = fgGetNode("/sim/error-report", true);
d->_enabledNode = n->getNode("enabled", true);
if (!d->_enabledNode->hasValue()) {
d->_enabledNode->setBoolValue(false); // default to off for now
}
d->_displayNode = n->getNode("display", true);
d->_activeErrorNode = n->getNode("active", true);
d->_mpReportNode = n->getNode("mp-report-enabled", true);
}
void ErrorReporter::unbind()
{
d->_enabledNode.clear();
d->_displayNode.clear();
d->_activeErrorNode.clear();
}
void ErrorReporter::preinit()
{
ErrorReporterPrivate* p = d.get();
simgear::setFailureCallback([p](simgear::LoadFailure type, simgear::ErrorCode code, const std::string& details, const sg_location& location) {
p->collectError(type, code, details, location);
});
simgear::setErrorContextCallback([p](const std::string& key, const std::string& value) {
p->collectContext(key, value);
});
sglog().addCallback(d->_logCallback.get());
d->_logCallbackRegistered = true;
}
void ErrorReporter::init()
{
// we want to disable errors in developer mode, but since self-compiled
// builds default to developer-mode=true, need an override so people
// can see errors if they want
const auto developerMode = fgGetBool("sim/developer-mode");
const auto disableInDeveloperMode = !d->_enabledNode->getParent()->getBoolValue("enable-in-developer-mode");
const auto dd = developerMode && disableInDeveloperMode;
if (dd || !d->_enabledNode) {
SG_LOG(SG_GENERAL, SG_INFO, "Error reporting disabled");
simgear::setFailureCallback(simgear::FailureCallback());
simgear::setErrorContextCallback(simgear::ContextCallback());
if (d->_logCallbackRegistered) {
sglog().removeCallback(d->_logCallback.get());
d->_logCallbackRegistered = false;
}
return;
}
globals->get_commands()->addCommand("dismiss-error-report", d.get(), &ErrorReporterPrivate::dismissReportCommand);
globals->get_commands()->addCommand("save-error-report-data", d.get(), &ErrorReporterPrivate::saveReportCommand);
globals->get_commands()->addCommand("show-error-report", d.get(), &ErrorReporterPrivate::showErrorReportCommand);
// cache these values here
d->_fgdataPathPrefix = globals->get_fg_root().utf8Str();
d->_terrasyncPathPrefix = globals->get_terrasync_dir().utf8Str();
const auto aircraftPath = SGPath::fromUtf8(fgGetString("/sim/aircraft-dir"));
d->_aircraftDirectoryName = aircraftPath.file();
}
void ErrorReporter::update(double dt)
{
bool showDialog = false;
bool showPopup = false;
bool havePendingReports = false;
// beginning of locked section
{
std::lock_guard<std::mutex> g(d->_lock);
if (!d->_enabledNode->getBoolValue()) {
return;
}
// we are into the update phase (postinit has ocurred). We treat errors
// after this point with lower severity, to avoid popups into a flight
d->_haveDonePostInit = true;
SGTimeStamp n = SGTimeStamp::now();
// ensure we pause between successive error dialogs
const auto timeSinceLastDialog = (n - d->_nextShowTimeout).toSecs();
if (timeSinceLastDialog < MinimumIntervalBetweenDialogs) {
return;
}
if (!d->_reportsDirty) {
return;
}
if (d->_activeReportIndex >= 0) {
return; // already showing a report
}
// check if any reports are due
// check if an error is current active
for (auto& report : d->_aggregated) {
if (report.type == Aggregation::MultiPlayer) {
if (!d->_mpReportNode->getBoolValue()) {
// mark it as shown, to supress it
report.haveShownToUser = true;
}
}
if (report.haveShownToUser) {
// unless we ever re-show?
continue;
}
const auto ageSec = (n - report.lastErrorTime).toSecs();
if (ageSec > NoNewErrorsTimeout) {
d->presentErrorToUser(report);
if (report.isCritical) {
showDialog = true;
} else {
showPopup = true;
}
d->sendReportToSentry(report);
// if we show one report, don't consider any others for now
break;
} else {
havePendingReports = true;
}
} // of active aggregates iteration
if (!havePendingReports) {
d->_reportsDirty = false;
}
} // end of locked section
if (flightgear::isHeadlessMode()) {
showDialog = false;
showPopup = false;
}
// do not call into another subsystem with our lock held,
// as this can trigger deadlocks
if (showDialog) {
auto gui = globals->get_subsystem<NewGUI>();
gui->showDialog("error-report");
// this needs a bit more thought, disabling for the now
#if 0
// pause the sim when showing the popup
SGPropertyNode_ptr pauseArgs(new SGPropertyNode);
pauseArgs->setBoolValue("force-pause", true);
globals->get_commands()->execute("do_pause", pauseArgs);
#endif
} else if (showPopup) {
SGPropertyNode_ptr popupArgs(new SGPropertyNode);
popupArgs->setIntValue("index", d->_activeReportIndex);
globals->get_commands()->execute("show-error-notification-popup", popupArgs, nullptr);
}
}
void ErrorReporter::shutdown()
{
if (d->_enabledNode) {
globals->get_commands()->removeCommand("dismiss-error-report");
globals->get_commands()->removeCommand("save-error-report-data");
globals->get_commands()->removeCommand("show-error-report");
// during a reset we don't want to touch the log callback; it was added in
// preinit, which does not get repeated on a reset
const bool inReset = fgGetBool("/sim/signals/reinit", false);
if (!inReset) {
sglog().removeCallback(d->_logCallback.get());
d->_logCallbackRegistered = false;
}
}
}
std::string ErrorReporter::threadSpecificContextValue(const std::string& key)
{
auto it = thread_errorContextStack.find(key);
if (it == thread_errorContextStack.end())
return {};
return it->second.back();
}
} // namespace flightgear

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2021 James Turner
*
* This file is part of the program FlightGear.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <memory>
#include <simgear/structure/subsystem_mgr.hxx>
namespace flightgear {
class ErrorReporter : public SGSubsystem
{
public:
ErrorReporter();
~ErrorReporter();
void preinit();
void init() override;
void bind() override;
void unbind() override;
void update(double dt) override;
void shutdown() override;
static const char* staticSubsystemClassId() { return "error-reporting"; }
static std::string threadSpecificContextValue(const std::string& key);
private:
class ErrorReporterPrivate;
std::unique_ptr<ErrorReporterPrivate> d;
};
} // namespace flightgear

View File

@@ -0,0 +1,42 @@
// Property interpolation manager for SGPropertyNodes
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#include "FGInterpolator.hxx"
#include "fg_props.hxx"
#include <simgear/scene/util/ColorInterpolator.hxx>
//------------------------------------------------------------------------------
FGInterpolator::FGInterpolator()
{
addInterpolatorFactory<simgear::ColorInterpolator>("color");
setRealtimeProperty( fgGetNode("/sim/time/delta-realtime-sec", true) );
SGPropertyNode::setInterpolationMgr(this);
}
//------------------------------------------------------------------------------
FGInterpolator::~FGInterpolator()
{
if( SGPropertyNode::getInterpolationMgr() == this )
SGPropertyNode::setInterpolationMgr(0);
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGInterpolator> registrantFGInterpolator(
SGSubsystemMgr::INIT);

View File

@@ -0,0 +1,34 @@
///@file Property interpolation manager for SGPropertyNodes
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
#ifndef FG_INTERPOLATOR_HXX_
#define FG_INTERPOLATOR_HXX_
#include <simgear/props/PropertyInterpolationMgr.hxx>
class FGInterpolator : public simgear::PropertyInterpolationMgr
{
public:
FGInterpolator();
~FGInterpolator();
// Subsystem identification.
static const char* staticSubsystemClassId() { return "prop-interpolator"; }
};
#endif /* FG_INTERPOLATOR_HXX_ */

90
src/Main/README Normal file
View File

@@ -0,0 +1,90 @@
Last updated $Date$
This directory contains the main routine for FlightGear, together with
various utility classes and functions that do not fit in anywhere
else, and subsystems that have not yet graduated to having their own
directories (because the maintainers are lazy, not because the
subsystems are not important).
fg_commands.cxx
fg_commands.hxx
This module defines a global function to register FlightGear's
built-in commands.
fg_init.cxx
fg_init.hxx
This module defines global functions to initialize for the various
subsystems. Code is gradually being moved out of here and into the
subsystems' own init() methods, so this module will eventually
disappear.
fg_io.cxx
fg_io.hxx
This module defines high-level global I/O functions for FlightGear.
I don't know how widely these are used, yet.
fg_props.cxx
fg_props.hxx
This module provides a FlightGear layer over the SimGear property
manager. This module performs two roles: it provides global
convenience functions for accessing the FlightGear property tree,
and it ties some properties to its own static functions. The second
role has mostly disappeared, since subsystems now tie their own
properties in their bind() methods, and eventually this module will
contain only the convenience functions.
fgfs.cxx
fgfs.hxx
This module defines FGSubsystem, the abstract base class (or
interface) for subsystems in FlightGear. Most of the important
subsystems already extend this class, and eventually, all subsystems
will.
globals.cxx
globals.hxx
This module defines FGGlobals, a class that holds pointers to
subsystems and other variables used by a FlightGear session. Many
global variables have migrated into here, and eventually, all will,
so that the program can maintain state for more than one session at
once.
logger.cxx
logger.hxx
This module defines the FGLogger subsystem, which allows the user to
log any property value to any number of CSV files, somewhat like an
overenthusiastic flight data recorder.
main.cxx
This file defines the main() function for FlightGear as well as the
top-level loop. Currently, there is a still lot of code in here
that belongs in the individual subsystems' init() and update()
methods; eventually, this will be a short, simple file defining the
top-level flow logic of the program.
options.cxx
options.hxx
This module defines global functions for parsing command-line
options and transferring them to the property tree.
runfgfs.bat
runfgfs.bat.in
This is a batch file for launching FlightGear under MSWindows.
runfgfs.bat is autogenerated from runfgfs.bat.in, so any changes
should be made to the latter file.
splash.cxx
splash.hxx
This module defines global functions for displaying and updating the
splash screen.
viewer.cxx
viewer.hxx
This module defines the FGViewer subsystem, which calculates the
viewpoint in world coordinates and provides transformation matrices
for the scenery code. FGViewer has methods that allow the program
to rotate or move the current view direction.
viewmgr.cxx
viewmgr.hxx
This module defines the FGViewMgr subsystem, which manages all of
the different views available in FlightGear.

144
src/Main/XLIFFParser.cxx Normal file
View File

@@ -0,0 +1,144 @@
// XLIFFParser.cxx -- parse an XLIFF 1.2 XML file
////
// Copyright (C) 2018 James Turner <james@flightgear.org>
//
// 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 "config.h"
#include "XLIFFParser.hxx"
#include <cstring>
#include <string>
// simgear
#include <simgear/debug/logstream.hxx>
#include <simgear/props/props.hxx>
#include <simgear/misc/strutils.hxx>
using namespace flightgear;
XLIFFParser::XLIFFParser(SGPropertyNode_ptr lroot) :
_localeRoot(lroot)
{
}
void XLIFFParser::startXML()
{
}
void XLIFFParser::endXML()
{
}
void XLIFFParser::startElement(const char *name, const XMLAttributes &atts)
{
_text.clear();
std::string tag(name);
if (tag == "trans-unit") {
_unitId = atts.getValue("id");
if (_unitId.empty()) {
SG_LOG(SG_GENERAL, SG_WARN, "XLIFF trans-unit with missing ID: line "
<< getLine() << " of " << getPath());
}
_source.clear();
_target.clear();
const char* ac = atts.getValue("approved");
if (!ac || !strcmp(ac, "")) {
_approved = false;
} else {
_approved = simgear::strutils::to_bool(std::string{ac});
}
} else if (tag == "group") {
_resource = atts.getValue("resname");
if (_resource.empty()) {
SG_LOG(SG_GENERAL, SG_WARN, "XLIFF group with missing resname: line "
<< getLine() << " of " << getPath());
} else {
_resourceNode = _localeRoot->getChild(_resource, 0, true /* create */);
}
}
}
void XLIFFParser::endElement(const char* name)
{
std::string tag(name);
if (tag == "source") {
_source = _text;
} else if (tag == "target") {
_target = _text;
} else if (tag == "trans-unit") {
finishTransUnit();
}
}
void XLIFFParser::finishTransUnit()
{
if (!_resourceNode) {
SG_LOG(SG_GENERAL, SG_WARN, "XLIFF trans-unit without enclosing resource group: line "
<< getLine() << " of " << getPath());
return;
}
if (!_approved || _target.empty()) {
// skip un-approved or missing translations
return;
}
const auto slashPos = _unitId.find('/');
const auto indexPos = _unitId.find(':');
if (slashPos == std::string::npos) {
SG_LOG(SG_GENERAL, SG_WARN, "XLIFF trans-unit id without resource: '" <<
_unitId << "' at line " << getLine() << " of " << getPath());
return;
}
const auto res = _unitId.substr(0, slashPos);
if (res != _resource) {
// this implies the <group> node resname doesn't match the
// id resource prefix. For now just warn and skip, we could decide
// that one or the other takes precedence here?
SG_LOG(SG_GENERAL, SG_WARN, "XLIFF trans-unit with inconsistent resource: line "
<< getLine() << " of " << getPath());
return;
}
const auto id = _unitId.substr(slashPos + 1, indexPos - (slashPos + 1));
const int index = std::stoi(_unitId.substr(indexPos+1));
_resourceNode->getNode(id, index, true)->setStringValue(_target);
}
void XLIFFParser::data (const char * s, int len)
{
_text += std::string(s, static_cast<size_t>(len));
}
void XLIFFParser::pi (const char * target, const char * data)
{
SG_UNUSED(target);
SG_UNUSED(data);
//cout << "Processing instruction " << target << ' ' << data << endl;
}
void XLIFFParser::warning (const char * message, int line, int column) {
SG_LOG(SG_GENERAL, SG_WARN, "Warning: " << message << " (" << line << ',' << column << ')');
}

54
src/Main/XLIFFParser.hxx Normal file
View File

@@ -0,0 +1,54 @@
// XLIFFParser.hxx -- parse an XLIFF 1.2 XML file
////
// Copyright (C) 2018 James Turner <james@flightgear.org>
//
// 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 <simgear/xml/easyxml.hxx>
#include <string>
#include <simgear/props/propsfwd.hxx>
namespace flightgear
{
class XLIFFParser : public XMLVisitor
{
public:
XLIFFParser(SGPropertyNode_ptr lroot);
protected:
void startXML () override;
void endXML () override;
void startElement (const char * name, const XMLAttributes &atts) override;
void endElement (const char * name) override;
void data (const char * s, int len) override;
void pi (const char * target, const char * data) override;
void warning (const char * message, int line, int column) override;
private:
void finishTransUnit();
SGPropertyNode_ptr _localeRoot;
SGPropertyNode_ptr _resourceNode;
std::string _text;
std::string _unitId, _resource;
std::string _source, _target;
bool _approved = false;
};
}

429
src/Main/bootstrap.cxx Executable file
View File

@@ -0,0 +1,429 @@
// bootstrap.cxx -- bootstrap routines: main()
//
// Written by Curtis Olson, started May 1997.
//
// Copyright (C) 1997 - 2002 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 <config.h>
#ifdef HAVE_WINDOWS_H
#include <windows.h>
#endif
#if defined(__linux__)
// set link for setting _GNU_SOURCE before including fenv.h
// http://man7.org/linux/man-pages/man3/fenv.3.html
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <fenv.h>
#endif
#ifndef _WIN32
# include <unistd.h> // for gethostname()
#endif
#include <iostream>
#include <cerrno>
#include <csignal>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <clocale>
#include <simgear/compiler.h>
#include <simgear/structure/exception.hxx>
#include <simgear/scene/tgdb/GroundLightManager.hxx>
#include <osg/BufferObject>
#include <osg/Texture>
#include <osg/Version>
#include <osgText/Font>
#include "main.hxx"
#include <GUI/MessageBox.hxx>
#include <Main/fg_init.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <Main/options.hxx>
#include <Main/sentryIntegration.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Viewer/fgviewer.hxx>
#include <flightgearBuildId.h>
#include "fg_os.hxx"
#if defined(HAVE_QT)
#include <GUI/QtLauncher.hxx>
#endif
using std::cerr;
using std::endl;
std::string homedir;
std::string hostname;
// forward declaration.
void fgExitCleanup();
static void initFPE(bool enableExceptions);
#if defined(__linux__)
static void handleFPE(int);
static void
initFPE (bool fpeAbort)
{
if (fpeAbort) {
int except = fegetexcept();
feenableexcept(except | FE_DIVBYZERO | FE_INVALID);
} else {
signal(SIGFPE, handleFPE);
}
}
static void handleFPE(int)
{
feclearexcept(FE_ALL_EXCEPT);
SG_LOG(SG_GENERAL, SG_ALERT, "Floating point interrupt (SIGFPE)");
signal(SIGFPE, handleFPE);
}
#elif defined (SG_WINDOWS)
static void initFPE(bool fpeAbort)
{
// Enable floating-point exceptions for Windows
if (fpeAbort) {
// set following link for what this does (note it does set SSE
// flags too, it's not just for the x87 FPU)
// http://msdn.microsoft.com/en-us/library/e9b52ceh.aspx
_control87( _EM_INEXACT, _MCW_EM );
}
}
#else
static void initFPE(bool)
{
// Ignore floating-point exceptions on FreeBSD, OS-X, other Unices
signal(SIGFPE, SIG_IGN);
}
#endif
#if defined(SG_WINDOWS)
int main ( int argc, char **argv );
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
// convert wchar_t args to UTF-8 which is what we expect cross-platform
int numArgs = 0;
LPWSTR* wideArgs = CommandLineToArgvW(GetCommandLineW(), &numArgs);
std::vector<char*> utf8Args;
utf8Args.resize(numArgs);
for (int a = 0; a < numArgs; ++a) {
const auto s = simgear::strutils::convertWStringToUtf8(wideArgs[a]);
// note we leak these (strudp calls malloc) but not a big concern
utf8Args[a] = strdup(s.c_str());
}
main(numArgs, utf8Args.data());
}
// see https://stackoverflow.com/questions/35572792/setlocale-stuck-on-windows
// for why we need this
bool checkUniversalCRTVersion()
{
DWORD dwSize = 0;
VS_FIXEDFILEINFO *pFileInfo = NULL;
UINT puLenFileInfo = 0;
// Get the version information for the file requested
dwSize = GetFileVersionInfoSize("ucrtbase.dll", NULL);
if (dwSize == 0) {
return false;
}
std::vector<BYTE> versionInfo;
versionInfo.resize(dwSize);
if (!GetFileVersionInfo("ucrtbase.dll", 0, dwSize, versionInfo.data())) {
return false;
}
if (!VerQueryValue(versionInfo.data(), TEXT("\\"), (LPVOID*)&pFileInfo, &puLenFileInfo)) {
return false;
}
const WORD majorVersion = pFileInfo->dwFileVersionMS >> 16;
const WORD minorVersion = pFileInfo->dwFileVersionMS & 0xffff;
const WORD buildVersion = pFileInfo->dwFileVersionLS >> 16;
const WORD releaseVersion = pFileInfo->dwFileVersionLS & 0xffff;
// char buffer[256];
// snprintf(buffer, 256, "File Version: %d.%d.%d.%d\n",
// majorVersion, minorVersion, buildVersion, releaseVersion);
// OutputDebugString(buffer);
return (buildVersion > 10586);
}
#endif
#if defined(__GNUC__)
#include <execinfo.h>
#include <cxxabi.h>
void segfault_handler(int signo) {
fprintf(stderr, "Error: caught signal %d:\n", signo);
#ifndef __OpenBSD__
void *array[128];
size_t size;
size = backtrace(array, 128);
if (size) {
char** list = backtrace_symbols(array, size);
size_t fnlen = 256;
char* fname = (char*)malloc(fnlen);
for (size_t i=1; i<size; i++) {
char *begin = 0, *offset = 0, *end = 0;
for (char *p = list[i]; *p; ++p) {
if (*p == '(') begin = p;
else if (*p == '+') offset = p;
else if (*p == ')' && offset) {
end = p;
break;
}
}
if (begin && offset && end && begin<offset) {
*begin++ = '\0'; *offset++ = '\0'; *end = '\0';
int status;
char* ret = abi::__cxa_demangle(begin, fname, &fnlen, &status);
if (status == 0) {
fname = ret;
fprintf(stderr, " %s : %s+%s\n", list[i], fname, offset);
}
else
fprintf(stderr, " %s : %s()+%s\n", list[i], begin, offset);
}
else
fprintf(stderr, " %s\n", list[i]);
}
free(fname);
free(list);
}
#endif
std::abort();
}
#endif
[[noreturn]] static void fg_terminate()
{
cerr << "Running FlightGear's terminate handler. The program is going to "
"exit due to a fatal error condition, sorry." << std::endl;
std::abort();
}
// Detect SSE2 support for x86, it is always available for x86_64
#if defined(__i386__)
# if defined(SG_WINDOWS)
# include <intrin.h>
# define get_cpuid(a,b) __cpuid(a,b)
# else
# include <cpuid.h>
# define get_cpuid(a,b) __cpuid(b,a[0],a[1],a[2],a[3])
# endif
# define CPUID_GETFEATURES 1
# define CPUID_FEAT_EDX_SSE2 (1 << 26)
bool detectSIMD()
{
static int regs[4] = {0,0,0,0};
get_cpuid(regs, CPUID_GETFEATURES);
return (regs[3] & CPUID_FEAT_EDX_SSE2);
}
#else
bool detectSIMD() { return true; }
#endif
int _bootstrap_OSInit;
// Main entry point; catch any exceptions that have made it this far.
int main ( int argc, char **argv )
{
// we don't want to accidently show a GUI box and block startup in
// non_GUI setups, so check this value early here, before options are
// processed
const bool headless = flightgear::Options::checkForArg(argc, argv, "disable-gui");
flightgear::setHeadlessMode(headless);
#ifdef ENABLE_SIMD
if (!detectSIMD()) {
flightgear::fatalMessageBoxThenExit(
"Fatal error",
"SSE2 support not detected, but this version of FlightGear requires "
"SSE2 hardware support.");
}
#endif
#if defined(SG_WINDOWS)
// Don't show blocking "no disk in drive" error messages on Windows 7,
// silently return errors to application instead.
// See Microsoft MSDN #ms680621: "GUI apps should specify SEM_NOOPENFILEERRORBOX"
SetErrorMode(SEM_NOOPENFILEERRORBOX);
hostname = ::getenv( "COMPUTERNAME" );
if (!checkUniversalCRTVersion()) {
flightgear::fatalMessageBoxThenExit(
"Fatal error",
"The Microsoft Universal CRT on this computer is too old to run FlightGear. "
"Please use Windows Update to install a more recent Universal CRT version.");
}
#else
// Unix(alike) systems
char _hostname[256];
gethostname(_hostname, 256);
hostname = _hostname;
signal(SIGPIPE, SIG_IGN);
#endif
_bootstrap_OSInit = 0;
#if defined(HAVE_SENTRY)
const bool noSentry = flightgear::Options::checkForArg(argc, argv, "disable-sentry");
if (!noSentry) {
flightgear::initSentry();
}
#endif
// if we're not using the normal crash-reported, install our
// custom segfault handler on Linux, in debug builds.
// NB On OpenBSD this seems to lose info about where the signal
// happened, so is disabled.
#if !defined(SG_WINDOWS) && !defined(NDEBUG) && !defined(__OpenBSD__)
if (!flightgear::isSentryEnabled()) {
signal(SIGSEGV, segfault_handler);
}
#endif
initFPE(flightgear::Options::checkForArg(argc, argv, "enable-fpe"));
// pick up all user locale settings, but force C locale for numerical/sorting
// conversions because we have lots of code which assumes standard
// formatting
setlocale(LC_ALL, "");
setlocale(LC_NUMERIC, "C");
setlocale(LC_COLLATE, "C");
if (flightgear::Options::checkForArg(argc, argv, "uninstall")) {
return fgUninstall();
}
bool fgviewer = flightgear::Options::checkForArg(argc, argv, "fgviewer");
int exitStatus = EXIT_FAILURE;
try {
// http://code.google.com/p/flightgear-bugs/issues/detail?id=1231
// ensure sglog is inited before atexit() is registered, so logging
// is possible inside fgExitCleanup
sglog();
#if OSG_VERSION_LESS_THAN(3, 5, 0)
// similar to above, ensure some static maps inside OSG exist before
// we register our at-exit handler, otherwise the statics are gone
// when fg_terminate runs, which causes crashes.
osg::Texture::getTextureObjectManager(0);
osg::GLBufferObjectManager::getGLBufferObjectManager(0);
// ensure this is called early (and hence deleted) late,
// otherwise fgExitCleanup crahses: see
// Sentry FLIGHTEAR-M68
osgText::Font::getDefaultFont();
#endif
std::set_terminate(fg_terminate);
atexit(fgExitCleanup);
if (fgviewer) {
exitStatus = fgviewerMain(argc, argv);
} else {
exitStatus = fgMainInit(argc, argv);
}
} catch (const sg_throwable &t) {
std::string info;
if (std::strlen(t.getOrigin()) != 0)
info = std::string("received from ") + t.getOrigin();
flightgear::fatalMessageBoxWithoutExit(
"Fatal exception", t.getFormattedMessage(), info);
} catch (const std::bad_alloc&) {
flightgear::fatalMessageBoxWithoutExit("Out of memory",
"FlightGear ran out of memory and must exit.", "Consider adjusting your settings or closing other applications.");
} catch (const std::exception &e ) {
flightgear::fatalMessageBoxWithoutExit("Fatal exception", e.what());
} catch (const std::string &s) {
flightgear::fatalMessageBoxWithoutExit("Fatal exception", s);
} catch (const flightgear::FatalErrorException&) {
// we already showed the message box, just carry on to exit
} catch (const char *s) {
std::cerr << "Fatal error (const char*): " << s << std::endl;
} catch (...) {
flightgear::sentryReportException("Unknown main loop exception");
std::cerr << "Unknown exception in the main loop. Aborting..." << std::endl;
if (errno)
perror("Possible cause");
}
#if defined(HAVE_QT)
flightgear::shutdownQtApp();
#endif
return exitStatus;
}
// do some clean up on exit. Specifically we want to delete the sound-manager,
// so OpenAL device and context are released cleanly
void fgExitCleanup() {
if (_bootstrap_OSInit != 0) {
fgSetMouseCursor(MOUSE_CURSOR_POINTER);
fgOSCloseWindow();
}
flightgear::NavDataCache::shutdown();
// you might imagine we'd call shutdownQtApp here, but it's not safe to do
// so in an atexit handler, and crashes on Mac. Thiago states this explicitly:
// https://bugreports.qt.io/browse/QTBUG-48709
// on the common exit path globals is already deleted, and NULL,
// so this only happens on error paths.
delete globals;
// avoid crash on exit (https://sourceforge.net/p/flightgear/codetickets/1935/)
simgear::GroundLightManager::instance()->getRunwayLightStateSet()->clear();
simgear::GroundLightManager::instance()->getTaxiLightStateSet()->clear();
simgear::GroundLightManager::instance()->getGroundLightStateSet()->clear();
simgear::shutdownLogging();
flightgear::shutdownSentry();
}

1131
src/Main/fg_commands.cxx Normal file

File diff suppressed because it is too large Load Diff

15
src/Main/fg_commands.hxx Normal file
View File

@@ -0,0 +1,15 @@
// fg_commands.hxx - built-in commands for FlightGear.
#ifndef __FG_COMMANDS_HXX
#define __FG_COMMANDS_HXX
/**
* Initialize the built-in commands.
*/
void fgInitCommands ();
void fgInitSceneCommands();
// end of fg_commands.hxx
#endif

1539
src/Main/fg_init.cxx Executable file

File diff suppressed because it is too large Load Diff

103
src/Main/fg_init.hxx Normal file
View File

@@ -0,0 +1,103 @@
//
// fg_init.hxx -- Flight Gear top level initialization routines
//
// Written by Curtis Olson, started August 1997.
//
// Copyright (C) 1997 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 _FG_INIT_HXX
#define _FG_INIT_HXX
#include <string>
#include <simgear/misc/sg_path.hxx>
// forward decls
class SGPropertyNode;
class SGPath;
// Return the current base package version
std::string fgBasePackageVersion(const SGPath& path);
SGPath fgHomePath();
enum InitHomeResult
{
InitHomeOkay,
InitHomeReadOnly,
InitHomeExplicitReadOnly,
InitHomeAbort
};
InitHomeResult fgInitHome();
void fgShutdownHome();
void fgDeleteLockFile();
// Read in configuration (file and command line)
int fgInitConfig ( int argc, char **argv, bool reinit );
void fgInitAircraftPaths(bool reinit);
/**
* @brief
*
* @param reinit : is this a second(+) call of the function, i.e after reset
* @param didUseLauncher : allow adjusting UI feedback if we used the launcher or not
* @return int : an Options result to indicate if we should continue, quit, etc
*/
int fgInitAircraft(bool reinit, bool didUseLauncher);
// log various settings / configuration state
void fgOutputSettings();
// Initialize the localization
SGPropertyNode *fgInitLocale(const char *language);
// Init navaids and waypoints
bool fgInitNav ();
// General house keeping initializations
bool fgInitGeneral ();
// Create all the subsystems needed by the sim
void fgCreateSubsystems(bool duringReset);
// called after the subsystems have been bound and initialised,
// to peform final init
void fgPostInitSubsystems();
// Re-position: when only location is changing, we can do considerably
// less work than a full re-init.
void fgStartReposition();
void fgStartNewReset();
// setup the package system including the global root
void fgInitPackageRoot();
// wipe FG_HOME. (The removing of the program data is assumed to be done
// by the real installer).
int fgUninstall();
#endif // _FG_INIT_HXX

615
src/Main/fg_io.cxx Normal file
View File

@@ -0,0 +1,615 @@
// fg_io.cxx -- higher level I/O channel management routines
//
// Written by Curtis Olson, started November 1999.
//
// Copyright (C) 1999 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 "config.h"
#include <simgear/compiler.h>
#include <cstdlib> // atoi()
#include <string>
#include <algorithm>
#include <simgear/debug/logstream.hxx>
#include <simgear/io/iochannel.hxx>
#include <simgear/io/sg_file.hxx>
#include <simgear/io/sg_serial.hxx>
#include <simgear/io/sg_socket.hxx>
#include <simgear/io/sg_socket_udp.hxx>
#include <simgear/math/sg_types.hxx>
#include <simgear/timing/timestamp.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/structure/commands.hxx>
#include <Network/protocol.hxx>
#include <Network/ATC-Main.hxx>
#include <Network/atlas.hxx>
#include <Network/AV400.hxx>
#include <Network/AV400Sim.hxx>
#include <Network/AV400WSim.hxx>
#include <Network/flarm.hxx>
#include <Network/garmin.hxx>
#include <Network/igc.hxx>
#include <Network/joyclient.hxx>
#include <Network/jsclient.hxx>
#include <Network/native.hxx>
#include <Network/native_ctrls.hxx>
#include <Network/native_fdm.hxx>
#include <Network/native_gui.hxx>
#include <Network/opengc.hxx>
#include <Network/nmea.hxx>
#include <Network/props.hxx>
#include <Network/pve.hxx>
#include <Network/ray.hxx>
#include <Network/rul.hxx>
#include <Network/generic.hxx>
#if FG_HAVE_DDS
#include <simgear/io/SGDataDistributionService.hxx>
#include <Network/dds_props.hxx>
#endif
#if FG_HAVE_HLA
#include <Network/HLA/hla.hxx>
#endif
#include "globals.hxx"
#include "fg_io.hxx"
using std::atoi;
using std::string;
using std::to_string;
// configure a port based on the config string
FGProtocol*
FGIO::parse_port_config( const string& config, bool& o_ok )
{
SG_LOG( SG_IO, SG_INFO, "Parse I/O channel request: " << config );
string_list tokens = simgear::strutils::split( config, "," );
if (tokens.empty())
{
SG_LOG( SG_IO, SG_ALERT,
"Port configuration error: empty config string" );
o_ok = false;
return nullptr;
}
return parse_port_config(tokens, o_ok);
}
FGProtocol*
FGIO::parse_port_config( const string_list& tokens, bool& o_ok )
{
o_ok = false;
const string protocol = tokens[0];
SG_LOG( SG_IO, SG_INFO, " protocol = " << protocol );
FGProtocol *io = nullptr;
try
{
if ( protocol == "atcsim" ) {
FGATCMain *atcsim = new FGATCMain;
atcsim->set_hz( 30 );
if ( tokens.size() != 6 ) {
SG_LOG( SG_IO, SG_ALERT, "Usage: --atcsim=[no-]pedals,"
<< "input0_config,input1_config,"
<< "output0_config,output1_config,file.nas" );
delete atcsim;
return NULL;
}
if ( tokens[1] == "no-pedals" ) {
fgSetBool( "/input/atcsim/ignore-pedal-controls", true );
} else {
fgSetBool( "/input/atcsim/ignore-pedal-controls", false );
}
atcsim->set_path_names(tokens[2], tokens[3], tokens[4], tokens[5]);
o_ok = true;
return atcsim;
} else if ( protocol == "atlas" ) {
io = new FGAtlas;
} else if ( protocol == "opengc" ) {
io = new FGOpenGC;
} else if ( protocol == "AV400" ) {
io = new FGAV400;
} else if ( protocol == "AV400Sim" ) {
io = new FGAV400Sim;
} else if ( protocol == "AV400WSimA" ) {
io = new FGAV400WSimA;
} else if ( protocol == "AV400WSimB" ) {
io = new FGAV400WSimB;
} else if ( protocol == "flarm" ) {
io = new FGFlarm();
} else if ( protocol == "garmin" ) {
io = new FGGarmin();
} else if ( protocol == "igc" ) {
io = new IGCProtocol;
} else if ( protocol == "joyclient" ) {
io = new FGJoyClient;
} else if ( protocol == "jsclient" ) {
io = new FGJsClient;
} else if ( protocol == "native" ) {
io = new FGNative;
} else if ( protocol == "native-ctrls" ) {
io = new FGNativeCtrls;
} else if ( protocol == "native-fdm" ) {
io = new FGNativeFDM;
} else if ( protocol == "native-gui" ) {
io = new FGNativeGUI;
} else if ( protocol == "nmea" ) {
io = new FGNMEA();
#if FG_HAVE_DDS
} else if ( protocol == "dds-props") {
io = new FGDDSProps;
#endif
} else if ( protocol == "props" || protocol == "telnet" ) {
io = new FGProps( tokens );
o_ok = true;
return io;
} else if ( protocol == "pve" ) {
io = new FGPVE;
} else if ( protocol == "ray" ) {
io = new FGRAY;
} else if ( protocol == "rul" ) {
io = new FGRUL;
} else if ( protocol == "generic" ) {
FGGeneric *generic = new FGGeneric( tokens );
if (!generic->getInitOk())
{
// failed to initialize (i.e. invalid configuration)
delete generic;
return NULL;
}
io = generic;
} else if ( protocol == "multiplay" ) {
if ( tokens.size() != 5 ) {
SG_LOG( SG_IO, SG_ALERT, "Ignoring invalid --multiplay option "
"(4 arguments expected: --multiplay=dir,hz,hostname,port)" );
return NULL;
}
string dir = tokens[1];
int rate = atoi(tokens[2].c_str());
string host = tokens[3];
short port = atoi(tokens[4].c_str());
// multiplay used to be handled by an FGProtocol, but no longer. This code
// retains compatibility with existing command-line syntax
if (dir == "in") {
fgSetInt("/sim/multiplay/rxport", port);
fgSetString("/sim/multiplay/rxhost", host.c_str());
} else if (dir == "out") {
fgSetInt("/sim/multiplay/txport", port);
fgSetString("/sim/multiplay/txhost", host.c_str());
fgSetInt("/sim/multiplay/tx-rate-hz", rate);
}
o_ok = true;
return NULL;
}
#if FG_HAVE_HLA
else if ( protocol == "hla" ) {
o_ok = true;
return new FGHLA(tokens);
}
else if ( protocol == "hla-local" ) {
// This is just about to bring up some defaults
if (tokens.size() != 2) {
SG_LOG( SG_IO, SG_ALERT, "Ignoring invalid --hla-local option "
"(one argument expected: --hla-local=<federationname>" );
return NULL;
}
std::vector<std::string> HLA_tokens (tokens);
HLA_tokens.insert(HLA_tokens.begin(), "");
HLA_tokens.insert(HLA_tokens.begin(), "60");
HLA_tokens.insert(HLA_tokens.begin(), "bi");
HLA_tokens.push_back("fg-local.xml");
o_ok = true;
return new FGHLA(HLA_tokens);
}
#endif
else {
return NULL;
}
}
catch (FGProtocolConfigError& err)
{
SG_LOG( SG_IO, SG_ALERT, "Port configuration error: " << err.what() );
delete io;
return NULL;
}
if (tokens.size() < 4) {
#if FG_HAVE_DDS
SG_LOG( SG_IO, SG_ALERT, "Too few arguments for network protocol. At least 3 arguments required. " <<
"Usage: --" << protocol <<
"=(file|socket|serial|dds), (in|out|bi), hertz");
#else
SG_LOG( SG_IO, SG_ALERT, "Too few arguments for network protocol. At least 3 arguments required. " <<
"Usage: --" << protocol <<
"=(file|socket|serial), (in|out|bi), hertz");
#endif
delete io;
return NULL;
}
string medium = tokens[1];
SG_LOG( SG_IO, SG_INFO, " medium = " << medium );
string direction = tokens[2];
io->set_direction( direction );
SG_LOG( SG_IO, SG_INFO, " direction = " << direction );
string hertz_str = tokens[3];
double hertz = atof( hertz_str.c_str() );
io->set_hz( hertz );
SG_LOG( SG_IO, SG_INFO, " hertz = " << hertz );
// name
const auto name = generateName(protocol);
io->set_name(name);
SG_LOG(SG_IO, SG_INFO, " name = " << name);
if ( medium == "serial" ) {
if ( tokens.size() < 6) {
SG_LOG( SG_IO, SG_ALERT, "Too few arguments for serial communications. " <<
"Usage --" << protocol << "=serial, (in|out|bi), hertz, device, baudrate");
delete io;
return NULL;
}
// device name
string device = tokens[4];
SG_LOG( SG_IO, SG_INFO, " device = " << device );
// baud
string baud = tokens[5];
SG_LOG( SG_IO, SG_INFO, " baud = " << baud );
SGSerial *ch = new SGSerial( device, baud );
io->set_io_channel( ch );
if ( protocol == "AV400WSimB" ) {
if ( tokens.size() < 7 ) {
SG_LOG( SG_IO, SG_ALERT, "Missing second hz for AV400WSimB.");
delete io;
return NULL;
}
FGAV400WSimB *fgavb = static_cast<FGAV400WSimB*>(io);
string hz2_str = tokens[6];
double hz2 = atof(hz2_str.c_str());
fgavb->set_hz2(hz2);
}
} else if ( medium == "file" ) {
// file name
if ( tokens.size() < 5) {
SG_LOG( SG_IO, SG_ALERT, "Too few arguments for file I/O. " <<
"Usage --" << protocol << "=file, (in|out), hertz, filename (,repeat)");
delete io;
return NULL;
}
string file = tokens[4];
SG_LOG( SG_IO, SG_INFO, " file name = " << file );
int repeat = 1;
if (tokens.size() >= 7 && tokens[6] == "repeat") {
if (tokens.size() >= 8) {
repeat = atoi(tokens[7].c_str());
FGGeneric* generic = dynamic_cast<FGGeneric*>(io);
if (generic)
generic->setExitOnError(true);
} else {
repeat = -1;
}
}
SGFile *ch = new SGFile( file, repeat );
io->set_io_channel( ch );
} else if ( medium == "socket" ) {
if ( tokens.size() < 7) {
SG_LOG( SG_IO, SG_ALERT, "Too few arguments for socket communications. " <<
"Usage --" << protocol << "=socket, (in|out|bi), hertz, hostname, port, (tcp|udp)");
delete io;
return NULL;
}
string hostname = tokens[4];
string port = tokens[5];
string style = tokens[6];
SG_LOG( SG_IO, SG_INFO, " hostname = " << hostname );
SG_LOG( SG_IO, SG_INFO, " port = " << port );
SG_LOG( SG_IO, SG_INFO, " style = " << style );
if (hertz <= 0) {
SG_LOG(SG_IO, SG_ALERT, "Non-Positive Hz rate may block generic I/O ");
}
io->set_io_channel( new SGSocket( hostname, port, style ) );
}
#if FG_HAVE_DDS
else if ( medium == "dds") {
io->set_io_channel( new SG_DDS_Topic() );
}
#endif
else
{
SG_LOG( SG_IO, SG_ALERT, "Unknown transport medium \"" << medium << "\" for \"" << protocol << "\"");
delete io;
return nullptr;
}
if (io) o_ok = true;
return io;
}
// step through the port config streams (from fgOPTIONS) and setup
// serial port channels for each
void
FGIO::init()
{
// SG_LOG( SG_IO, SG_INFO, "I/O Channel initialization, " <<
// globals->get_channel_options_list()->size() << " requests." );
_realDeltaTime = fgGetNode("/sim/time/delta-realtime-sec");
// we could almost do this in a single step except pushing a valid
// port onto the port list copies the structure and destroys the
// original, which closes the port and frees up the fd ... doh!!!
for (const auto& config : *(globals->get_channel_options_list())) {
bool ok;
FGProtocol* p = add_channel(config, ok);
SG_LOG( SG_IO, SG_DEBUG, "add_channel() with config=" << config << " => ok=" << ok << " p=" << p);
if (ok) {
if (p) {
addToPropertyTree(p->get_name(), config);
}
}
else {
SG_LOG( SG_IO, SG_ALERT, "add_channel() failed. config=" << config);
}
} // of channel options iteration
auto cmdMgr = globals->get_commands();
cmdMgr->addCommand("add-io-channel", this, &FGIO::commandAddChannel);
cmdMgr->addCommand("remove-io-channel", this, &FGIO::commandRemoveChannel);
}
// add another I/O channel
FGProtocol* FGIO::add_channel(const string& config, bool& o_ok)
{
// parse the configuration string and store the results in the
// appropriate FGIOChannel structure
FGProtocol *p = parse_port_config( config, o_ok );
if (!o_ok)
{
SG_LOG(SG_IO, SG_ALERT, "Failed to parse config=" << config);
return nullptr;
}
if (!p) {
return nullptr;
}
p->open();
if ( !p->is_enabled() ) {
SG_LOG( SG_IO, SG_ALERT, "I/O Channel config failed." );
delete p;
return nullptr;
}
io_channels.push_back( p );
return p;
}
void
FGIO::reinit()
{
SG_LOG(SG_IO, SG_INFO, "FGIO::reinit()");
std::for_each(io_channels.begin(), io_channels.end(), [](FGProtocol* p) {
SG_LOG(SG_IO, SG_INFO, "Restarting channel \"" << p->get_name() << "\"");
p->reinit();
});
}
// process any IO channel work
void
FGIO::update( double /* delta_time_sec */ )
{
// use wall-clock, not simulation, delta time, so that network
// protocols update when the simulation is paused
// see http://code.google.com/p/flightgear-bugs/issues/detail?id=125
double delta_time_sec = _realDeltaTime->getDoubleValue();
ProtocolVec::iterator i = io_channels.begin();
ProtocolVec::iterator end = io_channels.end();
for (; i != end; ++i ) {
FGProtocol* p = *i;
if (!p->is_enabled()) {
continue;
}
p->dec_count_down( delta_time_sec );
double dt = 1 / p->get_hz();
if ( p->get_count_down() < 0.33 * dt ) {
p->process();
p->inc_count();
while ( p->get_count_down() < 0.33 * dt ) {
p->inc_count_down( dt );
}
} // of channel processing
} // of io_channels iteration
}
void
FGIO::shutdown()
{
ProtocolVec::iterator i = io_channels.begin();
ProtocolVec::iterator end = io_channels.end();
for (; i != end; ++i )
{
FGProtocol *p = *i;
if ( p->is_enabled() ) {
p->close();
}
SG_LOG(SG_IO, SG_INFO, "Shutting down channel \"" << p->get_name() << "\"");
delete p;
}
io_channels.clear();
auto cmdMgr = globals->get_commands();
cmdMgr->removeCommand("add-io-channel");
cmdMgr->removeCommand("remove-io-channel");
}
void
FGIO::bind()
{
}
void
FGIO::unbind()
{
}
bool FGIO::isMultiplayerRequested()
{
// launcher sets these properties directly, as does the in-sim dialog
std::string txAddress = fgGetString("/sim/multiplay/txhost");
if (!txAddress.empty()) return true;
// check the channel options list for a multiplay setting - this
// is easier than checking the raw Options arguments, but works before
// this subsytem is actually created.
auto channels = globals->get_channel_options_list();
if (!channels)
return false; // happens running tests
auto it = std::find_if(channels->begin(), channels->end(),
[](const std::string& channelOption)
{ return (channelOption.find("multiplay") == 0); });
return it != channels->end();
}
bool FGIO::commandAddChannel(const SGPropertyNode * arg, SGPropertyNode * root)
{
if (!arg->hasChild("config")) {
SG_LOG(SG_NETWORK, SG_WARN, "add-io-channel: missing 'config' argument");
return false;
}
string name = arg->getStringValue("name");
const string config = arg->getStringValue("config");
bool ok;
auto protocol = add_channel(config, ok);
if (!ok) {
SG_LOG(SG_NETWORK, SG_WARN, "add-io-channel: adding channel failed");
return false;
}
if (!protocol) {
return true;
}
if (!name.empty()) {
const string validName = simgear::strutils::makeStringSafeForPropertyName(name);
if (name.compare(validName) != 0) {
SG_LOG(SG_IO, SG_WARN, "add-io-channel: replaced illegal characters: " << name << " -> " << validName);
}
if (!validName.empty()) {
auto it = std::find_if(io_channels.begin(), io_channels.end(), [&validName](const FGProtocol* proto) {
return proto->get_name() == validName;
});
if (it != io_channels.end()) {
SG_LOG(SG_IO, SG_WARN, "add-io-channel: channel name \"" << validName << "\" already exists, using " << protocol->get_name());
} else {
// set custom name instead of auto-generated name:
SG_LOG(SG_IO, SG_INFO, "add-io-channel: setting name to \"" << validName << "\"");
protocol->set_name(validName);
}
}
}
// add entry to /io/channels/<name>
addToPropertyTree(protocol->get_name(), config);
return true;
}
bool FGIO::commandRemoveChannel(const SGPropertyNode * arg, SGPropertyNode * root)
{
if (!arg->hasChild("name")) {
SG_LOG(SG_NETWORK, SG_WARN, "remove-io-channel: missing 'name' argument");
}
const string name = arg->getStringValue("name");
auto it = find_if(io_channels.begin(), io_channels.end(),
[name](const FGProtocol* proto)
{ return proto->get_name() == name; });
if (it == io_channels.end()) {
SG_LOG(SG_NETWORK, SG_WARN, "remove-io-channel: no channel with name:" + name);
return false;
}
removeFromPropertyTree(name);
FGProtocol* p = *it;
if (p->is_enabled()) {
p->close();
}
delete p;
io_channels.erase(it);
return true;
}
string
FGIO::generateName(const string protocol)
{
string name;
// Find first unused name:
for (int i = 1; i < 1000; i++) {
name = protocol + "-" + to_string(i);
auto it = find_if(io_channels.begin(), io_channels.end(),
[name](const FGProtocol* proto) { return proto->get_name() == name; });
if (it == io_channels.end()) {
break;
}
}
return name;
}
void FGIO::addToPropertyTree(const string name, const string config)
{
auto channelNode = fgGetNode("/io/channels/" + name, true);
channelNode->setStringValue("config", config);
channelNode->setStringValue("name", name);
}
void FGIO::removeFromPropertyTree(const string name)
{
auto node = fgGetNode("/io/channels");
if (node->hasChild(name)) {
node->removeChild(name);
}
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGIO> registrantFGIO;

84
src/Main/fg_io.hxx Normal file
View File

@@ -0,0 +1,84 @@
// fg_io.hxx -- Higher level I/O management routines
//
// Written by Curtis Olson, started November 1999.
//
// Copyright (C) 1999 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 _FG_IO_HXX
#define _FG_IO_HXX
#include <simgear/compiler.h>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/props.hxx>
#include <vector>
#include <string>
class FGProtocol;
class FGIO : public SGSubsystem
{
public:
FGIO() = default;
~FGIO() = default;
// Subsystem API.
void bind() override;
void init() override;
void reinit() override;
void shutdown() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "io"; }
/**
* helper to determine early in startup, if MP will be used.
* This information is needed in the position-init code, to adjust the
* start position off active runways.
*/
static bool isMultiplayerRequested();
private:
FGProtocol* add_channel(const std::string& config, bool& o_ok);
FGProtocol* parse_port_config( const std::string& cfgstr, bool& o_ok );
FGProtocol* parse_port_config( const string_list& tokens, bool& o_ok );
void addToPropertyTree(const string name, const string config);
void removeFromPropertyTree(const string name);
string generateName(const string protocol);
private:
// define the global I/O channel list
//io_container global_io_list;
typedef std::vector< FGProtocol* > ProtocolVec;
ProtocolVec io_channels;
SGPropertyNode_ptr _realDeltaTime;
bool commandAddChannel(const SGPropertyNode * arg, SGPropertyNode * root);
bool commandRemoveChannel(const SGPropertyNode * arg, SGPropertyNode * root);
};
#endif // _FG_IO_HXX

96
src/Main/fg_os.hxx Normal file
View File

@@ -0,0 +1,96 @@
#ifndef _FG_OS_HXX
#define _FG_OS_HXX
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string>
#include <vector>
#include <osg/ref_ptr>
#include <osg/Camera>
#include <osg/GraphicsContext>
enum { MOUSE_BUTTON_LEFT,
MOUSE_BUTTON_MIDDLE,
MOUSE_BUTTON_RIGHT };
enum { MOUSE_BUTTON_DOWN,
MOUSE_BUTTON_UP };
enum { MOUSE_CURSOR_NONE,
MOUSE_CURSOR_POINTER,
MOUSE_CURSOR_WAIT,
MOUSE_CURSOR_CROSSHAIR,
MOUSE_CURSOR_LEFTRIGHT,
MOUSE_CURSOR_TOPSIDE,
MOUSE_CURSOR_BOTTOMSIDE,
MOUSE_CURSOR_LEFTSIDE,
MOUSE_CURSOR_RIGHTSIDE,
MOUSE_CURSOR_TOPLEFT,
MOUSE_CURSOR_TOPRIGHT,
MOUSE_CURSOR_BOTTOMLEFT,
MOUSE_CURSOR_BOTTOMRIGHT,
};
enum { KEYMOD_NONE = 0,
KEYMOD_RELEASED = 1, // Not a mod key, indicates "up" action
KEYMOD_SHIFT = 2,
KEYMOD_CTRL = 4,
KEYMOD_ALT = 8,
KEYMOD_META = 16,
KEYMOD_SUPER = 32,
KEYMOD_HYPER = 64,
KEYMOD_MAX = 128 };
// A note on key codes: none are defined here. FlightGear has no
// hard-coded interpretations of codes other than modifier keys, so we
// can get away with that. The only firm requirement is that the
// codes passed to the fgKeyHandler function be correctly interpreted
// by the PUI library. Users who need to hard-code key codes
// (probably not a good idea in any case) can use the pu.hxx header
// for definitions.
//
// OS integration functions
//
void fgOSInit(int* argc, char** argv);
void fgOSOpenWindow(bool stencil);
void fgOSCloseWindow();
void fgOSFullScreen();
int fgOSMainLoop();
void fgOSExit(int code);
void fgOSResetProperties();
void fgSetMouseCursor(int cursor);
int fgGetMouseCursor();
void fgWarpMouse(int x, int y);
int fgGetKeyModifiers();
//
// Callbacks and registration API
//
namespace osg { class Camera; class GraphicsContext; }
namespace osgGA { class GUIEventAdapter; }
typedef void (*fgIdleHandler)();
typedef void (*fgDrawHandler)();
typedef void (*fgWindowResizeHandler)(int w, int h);
typedef void (*fgKeyHandler)(int key, int keymod, int mousex, int mousey);
typedef void (*fgMouseClickHandler)(int button, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter*);
typedef void (*fgMouseMotionHandler)(int x, int y, const osgGA::GUIEventAdapter*);
void fgRegisterIdleHandler(fgIdleHandler func);
void fgRegisterDrawHandler(fgDrawHandler func);
void fgRegisterWindowResizeHandler(fgWindowResizeHandler func);
void fgRegisterKeyHandler(fgKeyHandler func);
void fgRegisterMouseClickHandler(fgMouseClickHandler func);
void fgRegisterMouseMotionHandler(fgMouseMotionHandler func);
#endif // _FG_OS_HXX

57
src/Main/fg_os_common.cxx Normal file
View File

@@ -0,0 +1,57 @@
// fg_os_common.cxx -- common functions for fg_os interface
// implemented as an osgViewer
//
// Copyright (C) 2007 Tim Moore timoore@redhat.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.
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <osg/GraphicsContext>
#include <Viewer/renderer.hxx>
#include <Viewer/FGEventHandler.hxx>
#include "fg_os.hxx"
#include "globals.hxx"
// fg_os callback registration APIs
//
// Event handling and scene graph update is all handled by a
// manipulator. See FGEventHandler.cpp
void fgRegisterIdleHandler(fgIdleHandler func)
{
globals->get_renderer()->getEventHandler()->setIdleHandler(func);
}
void fgRegisterKeyHandler(fgKeyHandler func)
{
globals->get_renderer()->getEventHandler()->setKeyHandler(func);
}
void fgRegisterMouseClickHandler(fgMouseClickHandler func)
{
globals->get_renderer()->getEventHandler()->setMouseClickHandler(func);
}
void fgRegisterMouseMotionHandler(fgMouseMotionHandler func)
{
globals->get_renderer()->getEventHandler()->setMouseMotionHandler(func);
}

740
src/Main/fg_props.cxx Normal file
View File

@@ -0,0 +1,740 @@
// fg_props.cxx -- support for FlightGear properties.
//
// Written by David Megginson, started 2000.
//
// Copyright (C) 2000, 2001 David Megginson - david@megginson.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 "config.h"
#include <simgear/compiler.h>
#include <simgear/structure/exception.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/timing/sg_time.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/scene/model/particles.hxx>
#include <simgear/sound/soundmgr.hxx>
#include <GUI/gui.h>
#include "globals.hxx"
#include "fg_props.hxx"
static bool frozen = false; // FIXME: temporary
using std::string;
////////////////////////////////////////////////////////////////////////
// Default property bindings (not yet handled by any module).
////////////////////////////////////////////////////////////////////////
struct LogClassMapping {
sgDebugClass c;
string name;
LogClassMapping(sgDebugClass cc, string nname) { c = cc; name = nname; }
};
LogClassMapping log_class_mappings [] = {
LogClassMapping(SG_NONE, "none"),
LogClassMapping(SG_TERRAIN, "terrain"),
LogClassMapping(SG_ASTRO, "astro"),
LogClassMapping(SG_FLIGHT, "flight"),
LogClassMapping(SG_INPUT, "input"),
LogClassMapping(SG_GL, "gl"),
LogClassMapping(SG_VIEW, "view"),
LogClassMapping(SG_COCKPIT, "cockpit"),
LogClassMapping(SG_GENERAL, "general"),
LogClassMapping(SG_MATH, "math"),
LogClassMapping(SG_EVENT, "event"),
LogClassMapping(SG_AIRCRAFT, "aircraft"),
LogClassMapping(SG_AUTOPILOT, "autopilot"),
LogClassMapping(SG_IO, "io"),
LogClassMapping(SG_CLIPPER, "clipper"),
LogClassMapping(SG_NETWORK, "network"),
LogClassMapping(SG_INSTR, "instrumentation"),
LogClassMapping(SG_ATC, "atc"),
LogClassMapping(SG_NASAL, "nasal"),
LogClassMapping(SG_SYSTEMS, "systems"),
LogClassMapping(SG_AI, "ai"),
LogClassMapping(SG_ENVIRONMENT, "environment"),
LogClassMapping(SG_SOUND, "sound"),
LogClassMapping(SG_NAVAID, "navaid"),
LogClassMapping(SG_GUI, "gui"),
LogClassMapping(SG_TERRASYNC, "terrasync"),
LogClassMapping(SG_UNDEFD, "")
};
/**
* Get the logging classes.
*/
// XXX Making the result buffer be global is a band-aid that hopefully
// delays its destruction 'til after its last use.
namespace
{
string loggingResult;
}
static const char *
getLoggingClasses ()
{
sgDebugClass classes = sglog().get_log_classes();
loggingResult.clear();
for (int i = 0; log_class_mappings[i].c != SG_UNDEFD; i++) {
if ((classes&log_class_mappings[i].c) > 0) {
if (!loggingResult.empty())
loggingResult += '|';
loggingResult += log_class_mappings[i].name;
}
}
return loggingResult.c_str();
}
static void
addLoggingClass (const string &name)
{
sgDebugClass classes = sglog().get_log_classes();
for (int i = 0; log_class_mappings[i].c != SG_UNDEFD; i++) {
if (name == log_class_mappings[i].name) {
sglog().set_log_classes(sgDebugClass(classes|log_class_mappings[i].c));
return;
}
}
SG_LOG(SG_GENERAL, SG_WARN, "Unknown logging class: " << name);
}
/**
* Set the logging classes.
*/
void
setLoggingClasses (const char * c)
{
string classes = c;
sglog().set_log_classes(SG_NONE);
if (classes == "none") {
SG_LOG(SG_GENERAL, SG_INFO, "Disabled all logging classes");
return;
}
if (classes.empty() || classes == "all") { // default
sglog().set_log_classes(SG_ALL);
SG_LOG(SG_GENERAL, SG_INFO, "Enabled all logging classes: "
<< getLoggingClasses());
return;
}
string rest = classes;
string name = "";
string::size_type sep = rest.find('|');
if (sep == string::npos)
sep = rest.find(',');
while (sep != string::npos) {
name = rest.substr(0, sep);
rest = rest.substr(sep+1);
addLoggingClass(name);
sep = rest.find('|');
if (sep == string::npos)
sep = rest.find(',');
}
addLoggingClass(rest);
SG_LOG(SG_GENERAL, SG_INFO, "Set logging classes to "
<< getLoggingClasses());
}
/**
* Get the logging priority.
*/
static const char *
getLoggingPriority ()
{
switch (sglog().get_log_priority()) {
case SG_BULK:
return "bulk";
case SG_DEBUG:
return "debug";
case SG_INFO:
return "info";
case SG_WARN:
return "warn";
case SG_ALERT:
case SG_POPUP:
return "alert";
default:
SG_LOG(SG_GENERAL, SG_WARN, "Internal: Unknown logging priority number: "
<< sglog().get_log_priority());
return "unknown";
}
}
/**
* Set the logging priority.
*/
void
setLoggingPriority (const char * p)
{
if (p == 0)
return;
string priority = p;
if (priority.empty()) {
sglog().set_log_priority(SG_INFO);
} else {
try {
sglog().set_log_priority(logstream::priorityFromString(priority));
} catch (std::exception& e) {
SG_LOG(SG_GENERAL, SG_WARN, "Unknown logging priority: " << priority);
}
}
SG_LOG(SG_GENERAL, SG_DEBUG, "Logging priority is " << getLoggingPriority());
}
/**
* Return the current frozen state.
*/
static bool
getFreeze ()
{
return frozen;
}
/**
* Set the current frozen state.
*/
static void
setFreeze (bool f)
{
frozen = f;
// Pause the particle system
auto p = simgear::ParticlesGlobalManager::instance();
p->setFrozen(f);
}
/**
* Return the number of milliseconds elapsed since simulation started.
*/
static double
getElapsedTime_sec ()
{
return globals->get_sim_time_sec();
}
/**
* Return the current Zulu time.
*/
static const char *
getDateString ()
{
static char buf[64]; // FIXME
SGTime * st = globals->get_time_params();
if (!st) {
buf[0] = 0;
return buf;
}
struct tm * t = st->getGmt();
sprintf(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d",
t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec);
return buf;
}
/**
* Set the current Zulu time.
*/
static void
setDateString (const char * date_string)
{
SGTime * st = globals->get_time_params();
struct tm * current_time = st->getGmt();
struct tm new_time;
// Scan for basic ISO format
// YYYY-MM-DDTHH:MM:SS
new_time.tm_isdst = 0;
int ret = sscanf(date_string, "%d-%d-%dT%d:%d:%d",
&(new_time.tm_year), &(new_time.tm_mon),
&(new_time.tm_mday), &(new_time.tm_hour),
&(new_time.tm_min), &(new_time.tm_sec));
// Be pretty picky about this, so
// that strange things don't happen
// if the save file has been edited
// by hand.
if (ret != 6) {
SG_LOG(SG_INPUT, SG_WARN, "Date/time string " << date_string
<< " not in YYYY-MM-DDTHH:MM:SS format; skipped");
return;
}
// OK, it looks like we got six
// values, one way or another.
new_time.tm_year -= 1900;
new_time.tm_mon -= 1;
// Now, tell flight gear to use
// the new time. This was far
// too difficult, by the way.
long int warp =
mktime(&new_time) - mktime(current_time) + globals->get_warp();
fgSetInt("/sim/time/warp", warp);
}
/**
* Return the GMT as a string.
*/
static const char *
getGMTString ()
{
static char buf[16];
SGTime * st = globals->get_time_params();
if (!st) {
buf[0] = 0;
return buf;
}
struct tm *t = st->getGmt();
snprintf(buf, 16, "%.2d:%.2d:%.2d",
t->tm_hour, t->tm_min, t->tm_sec);
return buf;
}
////////////////////////////////////////////////////////////////////////
// Tie the properties.
////////////////////////////////////////////////////////////////////////
SGConstPropertyNode_ptr FGProperties::_longDeg;
SGConstPropertyNode_ptr FGProperties::_latDeg;
SGConstPropertyNode_ptr FGProperties::_lonLatformat;
using namespace simgear;
const char* FGProperties::getLongitudeString ()
{
const double d = _longDeg->getDoubleValue();
auto format = static_cast<strutils::LatLonFormat>(_lonLatformat->getIntValue());
const char c = d < 0.0 ? 'W' : 'E';
static char longitudeBuffer[64];
const auto s = strutils::formatLatLonValueAsString(d, format, c);
memcpy(longitudeBuffer, s.c_str(), s.size() + 1);
return longitudeBuffer;
}
const char* FGProperties::getLatitudeString ()
{
const double d = _latDeg->getDoubleValue();
auto format = static_cast<strutils::LatLonFormat>(_lonLatformat->getIntValue());
const char c = d < 0.0 ? 'S' : 'N';
static char latitudeBuffer[64];
const auto s = strutils::formatLatLonValueAsString(d, format, c);
memcpy(latitudeBuffer, s.c_str(), s.size() + 1);
return latitudeBuffer;
}
FGProperties::FGProperties ()
{
}
FGProperties::~FGProperties ()
{
}
void
FGProperties::init ()
{
}
static SGPropertyNode_ptr initDoubleNode(const std::string& path, const double v)
{
auto r = fgGetNode(path, true);
if (r->getType() == simgear::props::NONE) {
r->setDoubleValue(v);
}
return r;
}
void
FGProperties::bind ()
{
_longDeg = fgGetNode("/position/longitude-deg", true);
_latDeg = fgGetNode("/position/latitude-deg", true);
_lonLatformat = fgGetNode("/sim/lon-lat-format", true);
_offset = fgGetNode("/sim/time/local-offset", true);
// utc date/time
_uyear = fgGetNode("/sim/time/utc/year", true);
_umonth = fgGetNode("/sim/time/utc/month", true);
_uday = fgGetNode("/sim/time/utc/day", true);
_uhour = fgGetNode("/sim/time/utc/hour", true);
_umin = fgGetNode("/sim/time/utc/minute", true);
_usec = fgGetNode("/sim/time/utc/second", true);
_uwday = fgGetNode("/sim/time/utc/weekday", true);
_udsec = fgGetNode("/sim/time/utc/day-seconds", true);
// real local date/time
_ryear = fgGetNode("/sim/time/real/year", true);
_rmonth = fgGetNode("/sim/time/real/month", true);
_rday = fgGetNode("/sim/time/real/day", true);
_rhour = fgGetNode("/sim/time/real/hour", true);
_rmin = fgGetNode("/sim/time/real/minute", true);
_rsec = fgGetNode("/sim/time/real/second", true);
_rwday = fgGetNode("/sim/time/real/weekday", true);
_tiedProperties.setRoot(globals->get_props());
// Simulation
_tiedProperties.Tie("/sim/logging/priority", getLoggingPriority, setLoggingPriority);
_tiedProperties.Tie("/sim/logging/classes", getLoggingClasses, setLoggingClasses);
_tiedProperties.Tie("/sim/freeze/master", getFreeze, setFreeze);
_tiedProperties.Tie<double>("/sim/time/elapsed-sec", getElapsedTime_sec);
_tiedProperties.Tie("/sim/time/gmt", getDateString, setDateString);
fgSetArchivable("/sim/time/gmt");
_tiedProperties.Tie<const char*>("/sim/time/gmt-string", getGMTString);
// Position
_tiedProperties.Tie<const char*>("/position/latitude-string", getLatitudeString);
_tiedProperties.Tie<const char*>("/position/longitude-string", getLongitudeString);
_headingMagnetic = initDoubleNode("/orientation/heading-magnetic-deg", 0.0);
_trackMagnetic = initDoubleNode("/orientation/track-magnetic-deg", 0.0);
_magVar = initDoubleNode("/environment/magnetic-variation-deg", 0.0);
_trueHeading = initDoubleNode("/orientation/heading-deg", 0.0);
_trueTrack = initDoubleNode("/orientation/track-deg", 0.0);
}
void
FGProperties::unbind ()
{
_tiedProperties.Untie();
// drop static references to properties
_longDeg = 0;
_latDeg = 0;
_lonLatformat = 0;
}
void
FGProperties::update (double dt)
{
_offset->setIntValue(globals->get_time_params()->get_local_offset());
// utc date/time
struct tm *u = globals->get_time_params()->getGmt();
_uyear->setIntValue(u->tm_year + 1900);
_umonth->setIntValue(u->tm_mon + 1);
_uday->setIntValue(u->tm_mday);
_uhour->setIntValue(u->tm_hour);
_umin->setIntValue(u->tm_min);
_usec->setIntValue(u->tm_sec);
_uwday->setIntValue(u->tm_wday);
_udsec->setIntValue(u->tm_hour * 3600 + u->tm_min * 60 + u->tm_sec);
// real local date/time
time_t real = time(0);
struct tm *r = localtime(&real);
_ryear->setIntValue(r->tm_year + 1900);
_rmonth->setIntValue(r->tm_mon + 1);
_rday->setIntValue(r->tm_mday);
_rhour->setIntValue(r->tm_hour);
_rmin->setIntValue(r->tm_min);
_rsec->setIntValue(r->tm_sec);
_rwday->setIntValue(r->tm_wday);
const double magvar = _magVar->getDoubleValue();
const auto hdgMag = SGMiscd::normalizePeriodic(0, 360.0, _trueHeading->getDoubleValue() - magvar);
_headingMagnetic->setDoubleValue(hdgMag);
const auto trackMag = SGMiscd::normalizePeriodic(0, 360.0, _trueTrack->getDoubleValue() - magvar);
_trackMagnetic->setDoubleValue(trackMag);
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGProperties> registrantFGProperties;
////////////////////////////////////////////////////////////////////////
// Save and restore.
////////////////////////////////////////////////////////////////////////
/**
* Save the current state of the simulator to a stream.
*/
bool
fgSaveFlight (std::ostream &output, bool write_all)
{
fgSetBool("/sim/presets/onground", false);
fgSetArchivable("/sim/presets/onground");
fgSetBool("/sim/presets/trim", false);
fgSetArchivable("/sim/presets/trim");
fgSetString("/sim/presets/speed-set", "UVW");
fgSetArchivable("/sim/presets/speed-set");
try {
writeProperties(output, globals->get_props(), write_all);
} catch (const sg_exception &e) {
guiErrorMessage("Error saving flight: ", e);
return false;
}
return true;
}
/**
* Restore the current state of the simulator from a stream.
*/
bool
fgLoadFlight (std::istream &input)
{
SGPropertyNode props;
try {
readProperties(input, &props);
} catch (const sg_exception &e) {
guiErrorMessage("Error reading saved flight: ", e);
return false;
}
fgSetBool("/sim/presets/onground", false);
fgSetBool("/sim/presets/trim", false);
fgSetString("/sim/presets/speed-set", "UVW");
copyProperties(&props, globals->get_props());
return true;
}
bool
fgLoadProps (const std::string& path, SGPropertyNode * props, bool in_fg_root, int default_mode)
{
SGPath fullpath;
if (in_fg_root) {
SGPath loadpath(globals->get_fg_root());
loadpath.append(path);
fullpath = loadpath;
} else {
fullpath = SGPath::fromUtf8(path);
}
try {
readProperties(fullpath, props, default_mode);
} catch (const sg_exception &e) {
guiErrorMessage("Error reading properties: ", e);
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////////
// Property convenience functions.
////////////////////////////////////////////////////////////////////////
SGPropertyNode *
fgGetNode (const char * path, bool create)
{
return globals->get_props()->getNode(path, create);
}
SGPropertyNode *
fgGetNode (const char * path, int index, bool create)
{
return globals->get_props()->getNode(path, index, create);
}
bool
fgHasNode (const char * path)
{
return (fgGetNode(path, false) != 0);
}
void
fgAddChangeListener (SGPropertyChangeListener * listener, const char * path)
{
fgGetNode(path, true)->addChangeListener(listener);
}
void
fgAddChangeListener (SGPropertyChangeListener * listener,
const char * path, int index)
{
fgGetNode(path, index, true)->addChangeListener(listener);
}
bool
fgGetBool (const char * name, bool defaultValue)
{
return globals->get_props()->getBoolValue(name, defaultValue);
}
int
fgGetInt (const char * name, int defaultValue)
{
return globals->get_props()->getIntValue(name, defaultValue);
}
long
fgGetLong (const char * name, long defaultValue)
{
return globals->get_props()->getLongValue(name, defaultValue);
}
float
fgGetFloat (const char * name, float defaultValue)
{
return globals->get_props()->getFloatValue(name, defaultValue);
}
double
fgGetDouble (const char * name, double defaultValue)
{
return globals->get_props()->getDoubleValue(name, defaultValue);
}
std::string
fgGetString (const char * name, const char * defaultValue)
{
return globals->get_props()->getStringValue(name, defaultValue);
}
bool
fgSetBool (const char * name, bool val)
{
return globals->get_props()->setBoolValue(name, val);
}
bool
fgSetInt (const char * name, int val)
{
return globals->get_props()->setIntValue(name, val);
}
bool
fgSetLong (const char * name, long val)
{
return globals->get_props()->setLongValue(name, val);
}
bool
fgSetFloat (const char * name, float val)
{
return globals->get_props()->setFloatValue(name, val);
}
bool
fgSetDouble (const char * name, double val)
{
return globals->get_props()->setDoubleValue(name, val);
}
bool
fgSetString (const char * name, const char * val)
{
return globals->get_props()->setStringValue(name, val);
}
void
fgSetArchivable (const char * name, bool state)
{
SGPropertyNode * node = globals->get_props()->getNode(name);
if (node == 0)
SG_LOG(SG_GENERAL, SG_DEBUG,
"Attempt to set archive flag for non-existant property "
<< name);
else
node->setAttribute(SGPropertyNode::ARCHIVE, state);
}
void
fgSetReadable (const char * name, bool state)
{
SGPropertyNode * node = globals->get_props()->getNode(name);
if (node == 0)
SG_LOG(SG_GENERAL, SG_DEBUG,
"Attempt to set read flag for non-existant property "
<< name);
else
node->setAttribute(SGPropertyNode::READ, state);
}
void
fgSetWritable (const char * name, bool state)
{
SGPropertyNode * node = globals->get_props()->getNode(name);
if (node == 0)
SG_LOG(SG_GENERAL, SG_DEBUG,
"Attempt to set write flag for non-existant property "
<< name);
else
node->setAttribute(SGPropertyNode::WRITE, state);
}
void
fgUntie(const char * name)
{
SGPropertyNode* node = globals->get_props()->getNode(name);
if (!node) {
SG_LOG(SG_GENERAL, SG_WARN, "fgUntie: unknown property " << name);
return;
}
if (!node->isTied()) {
return;
}
if (!node->untie()) {
SG_LOG(SG_GENERAL, SG_WARN, "Failed to untie property " << name);
}
}
void fgUntieIfDefined(const std::string& name)
{
SGPropertyNode* node = globals->get_props()->getNode(name);
if (!node) {
return;
}
if (!node->isTied()) {
return;
}
if (!node->untie()) {
SG_LOG(SG_GENERAL, SG_WARN, "Failed to untie property " << name);
}
}
// end of fg_props.cxx

854
src/Main/fg_props.hxx Normal file
View File

@@ -0,0 +1,854 @@
// fg_props.hxx - Declarations and inline methods for property handling.
// Written by David Megginson, started 2000.
//
// This file is in the Public Domain, and comes with no warranty.
#ifndef __FG_PROPS_HXX
#define __FG_PROPS_HXX 1
#include <iosfwd>
#include <algorithm>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/tiedpropertylist.hxx>
#include <Main/globals.hxx>
////////////////////////////////////////////////////////////////////////
// Property management.
////////////////////////////////////////////////////////////////////////
class FGProperties : public SGSubsystem
{
public:
FGProperties ();
virtual ~FGProperties ();
// Subsystem API.
void bind() override;
void init() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "properties"; }
private:
simgear::TiedPropertyList _tiedProperties;
static const char* getLatitudeString ();
static const char* getLongitudeString ();
static SGConstPropertyNode_ptr _longDeg, _latDeg, _lonLatformat;
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;
};
/**
* Save a flight to disk.
*
* This function saves all of the archivable properties to a stream
* so that the current flight can be restored later.
*
* @param output The output stream to write the XML save file to.
* @param write_all If true, write all properties rather than
* just the ones flagged as archivable.
* @return true if the flight was saved successfully.
*/
extern bool fgSaveFlight (std::ostream &output, bool write_all = false);
/**
* Load a flight from disk.
*
* This function loads an XML save file from a stream to restore
* a flight.
*
* @param input The input stream to read the XML from.
* @return true if the flight was restored successfully.
*/
extern bool fgLoadFlight (std::istream &input);
/**
* Load properties from a file.
*
* @param file The relative or absolute filename.
* @param props The property node to load the properties into.
* @param in_fg_root If true, look for the file relative to
* $FG_ROOT; otherwise, look for the file relative to the
* current working directory.
* @return true if the properties loaded successfully, false
* otherwise.
*/
extern bool fgLoadProps (const std::string& path, SGPropertyNode * props,
bool in_fg_root = true, int default_mode = 0);
void setLoggingClasses (const char * c);
void setLoggingPriority (const char * p);
////////////////////////////////////////////////////////////////////////
// Convenience functions for getting property values.
////////////////////////////////////////////////////////////////////////
/**
* Get a property node.
*
* @param path The path of the node, relative to root.
* @param create true to create the node if it doesn't exist.
* @return The node, or 0 if none exists and none was created.
*/
extern SGPropertyNode * fgGetNode (const char * path, bool create = false);
/**
* Get a property node.
*
* @param path The path of the node, relative to root.
* @param create true to create the node if it doesn't exist.
* @return The node, or 0 if none exists and none was created.
*/
inline SGPropertyNode * fgGetNode (const std::string & path, bool create = false)
{
return fgGetNode(path.c_str(), create );
}
/**
* Get a property node with separate index.
*
* This method separates the index from the path string, to make it
* easier to iterate through multiple components without using sprintf
* to add indices. For example, fgGetNode("foo[1]/bar", 3) will
* return the same result as fgGetNode("foo[1]/bar[3]").
*
* @param path The path of the node, relative to root.
* @param index The index for the last member of the path (overrides
* any given in the string).
* @param create true to create the node if it doesn't exist.
* @return The node, or 0 if none exists and none was created.
*/
extern SGPropertyNode * fgGetNode (const char * path,
int index, bool create = false);
/**
* Get a property node with separate index.
*
* This method separates the index from the path string, to make it
* easier to iterate through multiple components without using sprintf
* to add indices. For example, fgGetNode("foo[1]/bar", 3) will
* return the same result as fgGetNode("foo[1]/bar[3]").
*
* @param path The path of the node, relative to root.
* @param index The index for the last member of the path (overrides
* any given in the string).
* @param create true to create the node if it doesn't exist.
* @return The node, or 0 if none exists and none was created.
*/
inline SGPropertyNode * fgGetNode (const std::string & path,
int index, bool create = false)
{
return fgGetNode(path.c_str(), index, create );
}
/**
* Test whether a given node exists.
*
* @param path The path of the node, relative to root.
* @return true if the node exists, false otherwise.
*/
extern bool fgHasNode (const char * path);
/**
* Test whether a given node exists.
*
* @param path The path of the node, relative to root.
* @return true if the node exists, false otherwise.
*/
inline bool fgHasNode (const std::string & path)
{
return fgHasNode( path.c_str() );
}
/**
* Add a listener to a node.
*
* @param listener The listener to add to the node.
* @param path The path of the node, relative to root.
* @param index The index for the last member of the path (overrides
* any given in the string).
*/
extern void fgAddChangeListener (SGPropertyChangeListener * listener,
const char * path);
/**
* Add a listener to a node.
*
* @param listener The listener to add to the node.
* @param path The path of the node, relative to root.
* @param index The index for the last member of the path (overrides
* any given in the string).
*/
inline void fgAddChangeListener (SGPropertyChangeListener * listener,
const std::string & path)
{
fgAddChangeListener( listener, path.c_str() );
}
/**
* Add a listener to a node.
*
* @param listener The listener to add to the node.
* @param path The path of the node, relative to root.
* @param index The index for the last member of the path (overrides
* any given in the string).
*/
extern void fgAddChangeListener (SGPropertyChangeListener * listener,
const char * path, int index);
/**
* Add a listener to a node.
*
* @param listener The listener to add to the node.
* @param path The path of the node, relative to root.
* @param index The index for the last member of the path (overrides
* any given in the string).
*/
inline void fgAddChangeListener (SGPropertyChangeListener * listener,
const std::string & path, int index)
{
fgAddChangeListener( listener, path.c_str(), index );
}
/**
* Get a bool value for a property.
*
* This method is convenient but inefficient. It should be used
* infrequently (i.e. for initializing, loading, saving, etc.),
* not in the main loop. If you need to get a value frequently,
* it is better to look up the node itself using fgGetNode and then
* use the node's getBoolValue() method, to avoid the lookup overhead.
*
* @param name The property name.
* @param defaultValue The default value to return if the property
* does not exist.
* @return The property's value as a bool, or the default value provided.
*/
extern bool fgGetBool (const char * name, bool defaultValue = false);
/**
* Get a bool value for a property.
*
* This method is convenient but inefficient. It should be used
* infrequently (i.e. for initializing, loading, saving, etc.),
* not in the main loop. If you need to get a value frequently,
* it is better to look up the node itself using fgGetNode and then
* use the node's getBoolValue() method, to avoid the lookup overhead.
*
* @param name The property name.
* @param defaultValue The default value to return if the property
* does not exist.
* @return The property's value as a bool, or the default value provided.
*/
inline bool fgGetBool (const std::string & name, bool defaultValue = false)
{
return fgGetBool( name.c_str(), defaultValue );
}
/**
* Get an int value for a property.
*
* This method is convenient but inefficient. It should be used
* infrequently (i.e. for initializing, loading, saving, etc.),
* not in the main loop. If you need to get a value frequently,
* it is better to look up the node itself using fgGetNode and then
* use the node's getIntValue() method, to avoid the lookup overhead.
*
* @param name The property name.
* @param defaultValue The default value to return if the property
* does not exist.
* @return The property's value as an int, or the default value provided.
*/
extern int fgGetInt (const char * name, int defaultValue = 0);
/**
* Get an int value for a property.
*
* This method is convenient but inefficient. It should be used
* infrequently (i.e. for initializing, loading, saving, etc.),
* not in the main loop. If you need to get a value frequently,
* it is better to look up the node itself using fgGetNode and then
* use the node's getIntValue() method, to avoid the lookup overhead.
*
* @param name The property name.
* @param defaultValue The default value to return if the property
* does not exist.
* @return The property's value as an int, or the default value provided.
*/
inline int fgGetInt (const std::string & name, int defaultValue = 0)
{
return fgGetInt( name.c_str(), defaultValue );
}
/**
* Get a long value for a property.
*
* This method is convenient but inefficient. It should be used
* infrequently (i.e. for initializing, loading, saving, etc.),
* not in the main loop. If you need to get a value frequently,
* it is better to look up the node itself using fgGetNode and then
* use the node's getLongValue() method, to avoid the lookup overhead.
*
* @param name The property name.
* @param defaultValue The default value to return if the property
* does not exist.
* @return The property's value as a long, or the default value provided.
*/
extern long fgGetLong (const char * name, long defaultValue = 0L);
/**
* Get a long value for a property.
*
* This method is convenient but inefficient. It should be used
* infrequently (i.e. for initializing, loading, saving, etc.),
* not in the main loop. If you need to get a value frequently,
* it is better to look up the node itself using fgGetNode and then
* use the node's getLongValue() method, to avoid the lookup overhead.
*
* @param name The property name.
* @param defaultValue The default value to return if the property
* does not exist.
* @return The property's value as a long, or the default value provided.
*/
inline long fgGetLong (const std::string & name, long defaultValue = 0L)
{
return fgGetLong( name.c_str(), defaultValue );
}
/**
* Get a float value for a property.
*
* This method is convenient but inefficient. It should be used
* infrequently (i.e. for initializing, loading, saving, etc.),
* not in the main loop. If you need to get a value frequently,
* it is better to look up the node itself using fgGetNode and then
* use the node's getFloatValue() method, to avoid the lookup overhead.
*
* @param name The property name.
* @param defaultValue The default value to return if the property
* does not exist.
* @return The property's value as a float, or the default value provided.
*/
extern float fgGetFloat (const char * name, float defaultValue = 0.0);
/**
* Get a float value for a property.
*
* This method is convenient but inefficient. It should be used
* infrequently (i.e. for initializing, loading, saving, etc.),
* not in the main loop. If you need to get a value frequently,
* it is better to look up the node itself using fgGetNode and then
* use the node's getFloatValue() method, to avoid the lookup overhead.
*
* @param name The property name.
* @param defaultValue The default value to return if the property
* does not exist.
* @return The property's value as a float, or the default value provided.
*/
inline float fgGetFloat (const std::string & name, float defaultValue = 0.0)
{
return fgGetFloat( name.c_str(), defaultValue );
}
/**
* Get a double value for a property.
*
* This method is convenient but inefficient. It should be used
* infrequently (i.e. for initializing, loading, saving, etc.),
* not in the main loop. If you need to get a value frequently,
* it is better to look up the node itself using fgGetNode and then
* use the node's getDoubleValue() method, to avoid the lookup overhead.
*
* @param name The property name.
* @param defaultValue The default value to return if the property
* does not exist.
* @return The property's value as a double, or the default value provided.
*/
extern double fgGetDouble (const char * name, double defaultValue = 0.0);
/**
* Get a double value for a property.
*
* This method is convenient but inefficient. It should be used
* infrequently (i.e. for initializing, loading, saving, etc.),
* not in the main loop. If you need to get a value frequently,
* it is better to look up the node itself using fgGetNode and then
* use the node's getDoubleValue() method, to avoid the lookup overhead.
*
* @param name The property name.
* @param defaultValue The default value to return if the property
* does not exist.
* @return The property's value as a double, or the default value provided.
*/
inline double fgGetDouble (const std::string & name, double defaultValue = 0.0)
{
return fgGetDouble( name.c_str(), defaultValue );
}
/**
* Get a string value for a property.
*
* This method is convenient but inefficient. It should be used
* infrequently (i.e. for initializing, loading, saving, etc.),
* not in the main loop. If you need to get a value frequently,
* it is better to look up the node itself using fgGetNode and then
* use the node's getStringValue() method, to avoid the lookup overhead.
*
* @param name The property name.
* @param defaultValue The default value to return if the property
* does not exist.
* @return The property's value as a string, or the default value provided.
*/
extern std::string fgGetString (const char * name,
const char * defaultValue = "");
/**
* Get a string value for a property.
*
* This method is convenient but inefficient. It should be used
* infrequently (i.e. for initializing, loading, saving, etc.),
* not in the main loop. If you need to get a value frequently,
* it is better to look up the node itself using fgGetNode and then
* use the node's getStringValue() method, to avoid the lookup overhead.
*
* @param name The property name.
* @param defaultValue The default value to return if the property
* does not exist.
* @return The property's value as a string, or the default value provided.
*/
inline std::string fgGetString (const std::string & name,
const std::string & defaultValue = std::string(""))
{
return fgGetString( name.c_str(), defaultValue.c_str() );
}
/**
* Set a bool value for a property.
*
* Assign a bool value to a property. If the property does not
* yet exist, it will be created and its type will be set to
* BOOL; if it has a type of UNKNOWN, the type will also be set to
* BOOL; otherwise, the bool value will be converted to the property's
* type.
*
* @param name The property name.
* @param val The new value for the property.
* @return true if the assignment succeeded, false otherwise.
*/
extern bool fgSetBool (const char * name, bool val);
/**
* Set a bool value for a property.
*
* Assign a bool value to a property. If the property does not
* yet exist, it will be created and its type will be set to
* BOOL; if it has a type of UNKNOWN, the type will also be set to
* BOOL; otherwise, the bool value will be converted to the property's
* type.
*
* @param name The property name.
* @param val The new value for the property.
* @return true if the assignment succeeded, false otherwise.
*/
inline bool fgSetBool (const std::string & name, bool val)
{
return fgSetBool( name.c_str(), val );
}
/**
* Set an int value for a property.
*
* Assign an int value to a property. If the property does not
* yet exist, it will be created and its type will be set to
* INT; if it has a type of UNKNOWN, the type will also be set to
* INT; otherwise, the bool value will be converted to the property's
* type.
*
* @param name The property name.
* @param val The new value for the property.
* @return true if the assignment succeeded, false otherwise.
*/
extern bool fgSetInt (const char * name, int val);
/**
* Set an int value for a property.
*
* Assign an int value to a property. If the property does not
* yet exist, it will be created and its type will be set to
* INT; if it has a type of UNKNOWN, the type will also be set to
* INT; otherwise, the bool value will be converted to the property's
* type.
*
* @param name The property name.
* @param val The new value for the property.
* @return true if the assignment succeeded, false otherwise.
*/
inline bool fgSetInt (const std::string & name, int val)
{
return fgSetInt( name.c_str(), val );
}
/**
* Set a long value for a property.
*
* Assign a long value to a property. If the property does not
* yet exist, it will be created and its type will be set to
* LONG; if it has a type of UNKNOWN, the type will also be set to
* LONG; otherwise, the bool value will be converted to the property's
* type.
*
* @param name The property name.
* @param val The new value for the property.
* @return true if the assignment succeeded, false otherwise.
*/
extern bool fgSetLong (const char * name, long val);
/**
* Set a long value for a property.
*
* Assign a long value to a property. If the property does not
* yet exist, it will be created and its type will be set to
* LONG; if it has a type of UNKNOWN, the type will also be set to
* LONG; otherwise, the bool value will be converted to the property's
* type.
*
* @param name The property name.
* @param val The new value for the property.
* @return true if the assignment succeeded, false otherwise.
*/
inline bool fgSetLong (const std::string & name, long val)
{
return fgSetLong( name.c_str(), val );
}
/**
* Set a float value for a property.
*
* Assign a float value to a property. If the property does not
* yet exist, it will be created and its type will be set to
* FLOAT; if it has a type of UNKNOWN, the type will also be set to
* FLOAT; otherwise, the bool value will be converted to the property's
* type.
*
* @param name The property name.
* @param val The new value for the property.
* @return true if the assignment succeeded, false otherwise.
*/
extern bool fgSetFloat (const char * name, float val);
/**
* Set a float value for a property.
*
* Assign a float value to a property. If the property does not
* yet exist, it will be created and its type will be set to
* FLOAT; if it has a type of UNKNOWN, the type will also be set to
* FLOAT; otherwise, the bool value will be converted to the property's
* type.
*
* @param name The property name.
* @param val The new value for the property.
* @return true if the assignment succeeded, false otherwise.
*/
inline bool fgSetFloat (const std::string & name, float val)
{
return fgSetFloat( name.c_str(), val );
}
/**
* Set a double value for a property.
*
* Assign a double value to a property. If the property does not
* yet exist, it will be created and its type will be set to
* DOUBLE; if it has a type of UNKNOWN, the type will also be set to
* DOUBLE; otherwise, the double value will be converted to the property's
* type.
*
* @param name The property name.
* @param val The new value for the property.
* @return true if the assignment succeeded, false otherwise.
*/
extern bool fgSetDouble (const char * name, double val);
/**
* Set a double value for a property.
*
* Assign a double value to a property. If the property does not
* yet exist, it will be created and its type will be set to
* DOUBLE; if it has a type of UNKNOWN, the type will also be set to
* DOUBLE; otherwise, the double value will be converted to the property's
* type.
*
* @param name The property name.
* @param val The new value for the property.
* @return true if the assignment succeeded, false otherwise.
*/
inline bool fgSetDouble (const std::string & name, double val)
{
return fgSetDouble( name.c_str(), val );
}
/**
* Set a string value for a property.
*
* Assign a string value to a property. If the property does not
* yet exist, it will be created and its type will be set to
* STRING; if it has a type of UNKNOWN, the type will also be set to
* STRING; otherwise, the string value will be converted to the property's
* type.
*
* @param name The property name.
* @param val The new value for the property.
* @return true if the assignment succeeded, false otherwise.
*/
extern bool fgSetString (const char * name, const char * val);
/**
* Set a string value for a property.
*
* Assign a string value to a property. If the property does not
* yet exist, it will be created and its type will be set to
* STRING; if it has a type of UNKNOWN, the type will also be set to
* STRING; otherwise, the string value will be converted to the property's
* type.
*
* @param name The property name.
* @param val The new value for the property.
* @return true if the assignment succeeded, false otherwise.
*/
inline bool fgSetString (const std::string & name, const std::string & val)
{
return fgSetString( name.c_str(), val.c_str() );
}
////////////////////////////////////////////////////////////////////////
// Convenience functions for setting property attributes.
////////////////////////////////////////////////////////////////////////
/**
* Set the state of the archive attribute for a property.
*
* If the archive attribute is true, the property will be written
* when a flight is saved; if it is false, the property will be
* skipped.
*
* A warning message will be printed if the property does not exist.
*
* @param name The property name.
* @param state The state of the archive attribute (defaults to true).
*/
extern void fgSetArchivable (const char * name, bool state = true);
/**
* Set the state of the read attribute for a property.
*
* If the read attribute is true, the property value will be readable;
* if it is false, the property value will always be the default value
* for its type.
*
* A warning message will be printed if the property does not exist.
*
* @param name The property name.
* @param state The state of the read attribute (defaults to true).
*/
extern void fgSetReadable (const char * name, bool state = true);
/**
* Set the state of the write attribute for a property.
*
* If the write attribute is true, the property value may be modified
* (depending on how it is tied); if the write attribute is false, the
* property value may not be modified.
*
* A warning message will be printed if the property does not exist.
*
* @param name The property name.
* @param state The state of the write attribute (defaults to true).
*/
extern void fgSetWritable (const char * name, bool state = true);
////////////////////////////////////////////////////////////////////////
// Convenience functions for tying properties, with logging.
////////////////////////////////////////////////////////////////////////
/**
* Untie a property from an external data source.
*
* Classes should use this function to release control of any
* properties they are managing.
*/
extern void fgUntie (const char * name);
/**
@brfief variant of the above which doesn't warn if the property does not exist
*/
void fgUntieIfDefined(const std::string& name);
/**
* Tie a property to a pair of simple functions.
*
* Every time the property value is queried, the getter (if any) will
* be invoked; every time the property value is modified, the setter
* (if any) will be invoked. The getter can be 0 to make the property
* unreadable, and the setter can be 0 to make the property
* unmodifiable.
*
* @param name The property name to tie (full path).
* @param getter The getter function, or 0 if the value is unreadable.
* @param setter The setter function, or 0 if the value is unmodifiable.
* @param useDefault true if the setter should be invoked with any existing
* property value should be; false if the old value should be
* discarded; defaults to true.
*/
template <class V>
inline void
fgTie (const char * name, V (*getter)(), void (*setter)(V) = 0,
bool useDefault = true)
{
if (!globals->get_props()->tie(name, SGRawValueFunctions<V>(getter, setter),
useDefault))
SG_LOG(SG_GENERAL, SG_DEV_WARN,
"Failed to tie property " << name << " to functions");
}
/**
* Tie a property to a pair of indexed functions.
*
* Every time the property value is queried, the getter (if any) will
* be invoked with the index provided; every time the property value
* is modified, the setter (if any) will be invoked with the index
* provided. The getter can be 0 to make the property unreadable, and
* the setter can be 0 to make the property unmodifiable.
*
* @param name The property name to tie (full path).
* @param index The integer argument to pass to the getter and
* setter functions.
* @param getter The getter function, or 0 if the value is unreadable.
* @param setter The setter function, or 0 if the value is unmodifiable.
* @param useDefault true if the setter should be invoked with any existing
* property value should be; false if the old value should be
* discarded; defaults to true.
*/
template <class V>
inline void
fgTie (const char * name, int index, V (*getter)(int),
void (*setter)(int, V) = 0, bool useDefault = true)
{
if (!globals->get_props()->tie(name,
SGRawValueFunctionsIndexed<V>(index,
getter,
setter),
useDefault))
SG_LOG(SG_GENERAL, SG_DEV_WARN,
"Failed to tie property " << name << " to indexed functions");
}
/**
* Tie a property to a pair of object methods.
*
* Every time the property value is queried, the getter (if any) will
* be invoked; every time the property value is modified, the setter
* (if any) will be invoked. The getter can be 0 to make the property
* unreadable, and the setter can be 0 to make the property
* unmodifiable.
*
* @param name The property name to tie (full path).
* @param obj The object whose methods should be invoked.
* @param getter The object's getter method, or 0 if the value is
* unreadable.
* @param setter The object's setter method, or 0 if the value is
* unmodifiable.
* @param useDefault true if the setter should be invoked with any existing
* property value should be; false if the old value should be
* discarded; defaults to true.
*/
template <class T, class V>
inline void
fgTie (const char * name, T * obj, V (T::*getter)() const,
void (T::*setter)(V) = 0, bool useDefault = true)
{
if (!globals->get_props()->tie(name,
SGRawValueMethods<T,V>(*obj, getter, setter),
useDefault))
SG_LOG(SG_GENERAL, SG_DEV_WARN,
"Failed to tie property " << name << " to object methods");
}
/**
* Tie a property to a pair of indexed object methods.
*
* Every time the property value is queried, the getter (if any) will
* be invoked with the index provided; every time the property value
* is modified, the setter (if any) will be invoked with the index
* provided. The getter can be 0 to make the property unreadable, and
* the setter can be 0 to make the property unmodifiable.
*
* @param name The property name to tie (full path).
* @param obj The object whose methods should be invoked.
* @param index The integer argument to pass to the getter and
* setter methods.
* @param getter The getter method, or 0 if the value is unreadable.
* @param setter The setter method, or 0 if the value is unmodifiable.
* @param useDefault true if the setter should be invoked with any existing
* property value should be; false if the old value should be
* discarded; defaults to true.
*/
template <class T, class V>
inline void
fgTie (const char * name, T * obj, int index,
V (T::*getter)(int) const, void (T::*setter)(int, V) = 0,
bool useDefault = true)
{
if (!globals->get_props()->tie(name,
SGRawValueMethodsIndexed<T,V>(*obj,
index,
getter,
setter),
useDefault))
SG_LOG(SG_GENERAL, SG_DEV_WARN,
"Failed to tie property " << name << " to indexed object methods");
}
#endif // __FG_PROPS_HXX

View File

@@ -0,0 +1,578 @@
// fg_scene_commands.cxx - internal FGFS commands.
#include "config.h"
#include <string.h> // strcmp()
#include <simgear/compiler.h>
#include <string>
#include <fstream>
#include <simgear/sg_inlines.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/math/sg_random.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/scene/material/mat.hxx>
#include <simgear/scene/material/matlib.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/props/props.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/event_mgr.hxx>
#include <simgear/sound/soundmgr.hxx>
#include <simgear/timing/sg_time.hxx>
#include <Network/RemoteXMLRequest.hxx>
#include <FDM/flight.hxx>
#include <GUI/gui.h>
#include <GUI/new_gui.hxx>
#include <GUI/dialog.hxx>
#include <Aircraft/replay.hxx>
#include <Scenery/scenery.hxx>
#include <Scripting/NasalSys.hxx>
#include <Sound/sample_queue.hxx>
#include <Airports/xmlloader.hxx>
#include <Network/HTTPClient.hxx>
#include <Viewer/CameraGroup.hxx>
#include <Viewer/viewmgr.hxx>
#include <Viewer/view.hxx>
#include <Environment/presets.hxx>
#include <Navaids/NavDataCache.hxx>
#include "fg_init.hxx"
#include "fg_io.hxx"
#include "fg_os.hxx"
#include "fg_commands.hxx"
#include "fg_props.hxx"
#include "globals.hxx"
#include "logger.hxx"
#include "util.hxx"
#include "main.hxx"
#include "positioninit.hxx"
#if FG_HAVE_GPERFTOOLS
# include <google/profiler.h>
#endif
#if defined(HAVE_QT)
#include <GUI/QtLauncher.hxx>
#endif
////////////////////////////////////////////////////////////////////////
// Command implementations.
////////////////////////////////////////////////////////////////////////
/**
* Built-in command: exit FlightGear.
*
* status: the exit status to return to the operating system (defaults to 0)
*/
static bool
do_exit (const SGPropertyNode * arg, SGPropertyNode * root)
{
SG_LOG(SG_INPUT, SG_INFO, "Program exit requested.");
fgSetBool("/sim/signals/exit", true);
globals->saveUserSettings();
fgOSExit(arg->getIntValue("status", 0));
return true;
}
/**
* Reset FlightGear (Shift-Escape or Menu->File->Reset)
*/
static bool
do_reset (const SGPropertyNode * arg, SGPropertyNode * root)
{
fgResetIdleState();
return true;
}
/**
* Change aircraft
*/
static bool
do_switch_aircraft (const SGPropertyNode * arg, SGPropertyNode * root)
{
fgSetString("/sim/aircraft", arg->getStringValue("aircraft"));
// start a reset
fgResetIdleState();
return true;
}
/**
*/
static bool
do_reposition (const SGPropertyNode * arg, SGPropertyNode * root)
{
fgStartReposition();
return true;
}
/**
* Built-in command: (re)load the panel.
*
* path (optional): the file name to load the panel from
* (relative to FG_ROOT). Defaults to the value of /sim/panel/path,
* and if that's unspecified, to "Panels/Default/default.xml".
*/
static bool
do_panel_load (const SGPropertyNode * arg, SGPropertyNode * root)
{
string panel_path = arg->getStringValue("path");
if (!panel_path.empty()) {
// write to the standard property, which will force a load
fgSetString("/sim/panel/path", panel_path.c_str());
}
return true;
}
/**
* Built-in command: (re)load preferences.
*
* path (optional): the file name to load the panel from (relative
* to FG_ROOT). Defaults to "preferences.xml".
*/
static bool
do_preferences_load (const SGPropertyNode * arg, SGPropertyNode * root)
{
// disabling this command which was formerly used to reload 'preferences.xml'
// reloading the defaults doesn't make sense (better to reset the simulator),
// and loading arbitrary XML docs into the property-tree can be done via
// the standard load-xml command.
SG_LOG(SG_GENERAL, SG_ALERT, "preferences-load command is deprecated and non-functional");
return false;
}
/**
* An fgcommand to toggle fullscreen mode.
* No parameters.
*/
static bool
do_toggle_fullscreen(const SGPropertyNode*, SGPropertyNode*)
{
fgOSFullScreen();
return true;
}
/**
* Built-in command: capture screen.
*/
static bool
do_screen_capture(const SGPropertyNode*, SGPropertyNode*)
{
return fgDumpSnapShot();
}
static bool
do_reload_shaders (const SGPropertyNode*, SGPropertyNode*)
{
simgear::reload_shaders();
return true;
}
static bool
do_dump_scene_graph (const SGPropertyNode*, SGPropertyNode*)
{
fgDumpSceneGraph();
return true;
}
static bool
do_dump_terrain_branch (const SGPropertyNode*, SGPropertyNode*)
{
fgDumpTerrainBranch();
double lon_deg = fgGetDouble("/position/longitude-deg");
double lat_deg = fgGetDouble("/position/latitude-deg");
SGGeod geodPos = SGGeod::fromDegFt(lon_deg, lat_deg, 0.0);
SGVec3d zero = SGVec3d::fromGeod(geodPos);
SG_LOG(SG_INPUT, SG_INFO, "Model parameters:");
SG_LOG(SG_INPUT, SG_INFO, "Center: " << zero.x() << ", " << zero.y() << ", " << zero.z() );
SG_LOG(SG_INPUT, SG_INFO, "Rotation: " << lat_deg << ", " << lon_deg );
return true;
}
static bool
do_print_visible_scene_info(const SGPropertyNode*, SGPropertyNode*)
{
fgPrintVisibleSceneInfoCommand();
return true;
}
/**
* Built-in command: hires capture screen.
*/
static bool
do_hires_screen_capture (const SGPropertyNode * arg, SGPropertyNode * root)
{
fgHiResDump();
return true;
}
/**
* Reload all Compositor instances in the default CameraGroup.
*/
static bool
do_reload_compositor(const SGPropertyNode*, SGPropertyNode*)
{
flightgear::reloadCompositors(flightgear::CameraGroup::getDefault());
return true;
}
/**
* Reload the tile cache.
*/
static bool
do_tile_cache_reload (const SGPropertyNode * arg, SGPropertyNode * root)
{
SGPropertyNode *master_freeze = fgGetNode("/sim/freeze/master");
bool freeze = master_freeze->getBoolValue();
SG_LOG(SG_INPUT, SG_INFO, "ReIniting TileCache");
if ( !freeze ) {
master_freeze->setBoolValue(true);
}
globals->get_subsystem("scenery")->reinit();
if ( !freeze ) {
master_freeze->setBoolValue(false);
}
return true;
}
/**
* Reload the materials definition
*/
static bool
do_materials_reload (const SGPropertyNode * arg, SGPropertyNode * root)
{
SG_LOG(SG_INPUT, SG_INFO, "Reloading Materials");
SGMaterialLib* new_matlib = new SGMaterialLib;
SGPath mpath( globals->get_fg_root() );
mpath.append( fgGetString("/sim/rendering/materials-file") );
bool loaded = new_matlib->load(globals->get_fg_root(),
mpath,
globals->get_props());
if ( ! loaded ) {
SG_LOG( SG_GENERAL, SG_ALERT,
"Error loading materials file " << mpath );
return false;
}
globals->set_matlib(new_matlib);
FGScenery* scenery = static_cast<FGScenery*>(globals->get_scenery());
scenery->materialLibChanged();
return true;
}
/**
* Built-in command: Add a dialog to the GUI system. Does *not*
* display the dialog. The property node should have the same format
* as a dialog XML configuration. It must include:
*
* name: the name of the GUI dialog for future reference.
*/
static bool
do_dialog_new (const SGPropertyNode * arg, SGPropertyNode * root)
{
NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
if (!gui) {
return false;
}
// Note the casting away of const: this is *real*. Doing a
// "dialog-apply" command later on will mutate this property node.
// I'm not convinced that this isn't the Right Thing though; it
// allows client to create a node, pass it to dialog-new, and get
// the values back from the dialog by reading the same node.
// Perhaps command arguments are not as "const" as they would
// seem?
gui->newDialog((SGPropertyNode*)arg);
return true;
}
/**
* Built-in command: Show an XML-configured dialog.
*
* dialog-name: the name of the GUI dialog to display.
*/
static bool
do_dialog_show (const SGPropertyNode * arg, SGPropertyNode * root)
{
auto gui = globals->get_subsystem<NewGUI>();
gui->showDialog(arg->getStringValue("dialog-name"));
return true;
}
/**
* Built-in command: Show an XML-configured dialog.
*
* dialog-name: the name of the GUI dialog to display.
*/
static bool
do_dialog_toggle (const SGPropertyNode * arg, SGPropertyNode * root)
{
auto gui = globals->get_subsystem<NewGUI>();
gui->toggleDialog(arg->getStringValue("dialog-name"));
return true;
}
/**
* Built-in Command: Hide the active XML-configured dialog.
*/
static bool
do_dialog_close (const SGPropertyNode * arg, SGPropertyNode * root)
{
auto gui = globals->get_subsystem<NewGUI>();
if(arg->hasValue("dialog-name"))
return gui->closeDialog(arg->getStringValue("dialog-name"));
return gui->closeActiveDialog();
}
/**
* Update a value in the active XML-configured dialog.
*
* object-name: The name of the GUI object(s) (all GUI objects if omitted).
*/
static bool
do_dialog_update (const SGPropertyNode * arg, SGPropertyNode * root)
{
auto gui = globals->get_subsystem<NewGUI>();
FGDialog * dialog;
if (arg->hasValue("dialog-name"))
dialog = gui->getDialog(arg->getStringValue("dialog-name"));
else
dialog = gui->getActiveDialog();
if (dialog != 0) {
dialog->updateValues(arg->getStringValue("object-name"));
return true;
} else {
return false;
}
}
static bool
do_open_browser (const SGPropertyNode * arg, SGPropertyNode * root)
{
string path;
if (arg->hasValue("path"))
path = arg->getStringValue("path");
else
if (arg->hasValue("url"))
path = arg->getStringValue("url");
else
return false;
return openBrowser(path);
}
static bool
do_open_launcher(const SGPropertyNode*, SGPropertyNode*)
{
#if defined(HAVE_QT)
bool ok = flightgear::runInAppLauncherDialog();
if (ok) {
// start a full reset
fgResetIdleState();
}
return true; // don't report failure
#else
return false;
#endif
}
/**
* Apply a value in the active XML-configured dialog.
*
* object-name: The name of the GUI object(s) (all GUI objects if omitted).
*/
static bool
do_dialog_apply (const SGPropertyNode * arg, SGPropertyNode * root)
{
auto gui = globals->get_subsystem<NewGUI>();
FGDialog* dialog = nullptr;
if (arg->hasValue("dialog-name"))
dialog = gui->getDialog(arg->getStringValue("dialog-name"));
else
dialog = gui->getActiveDialog();
if (dialog) {
dialog->applyValues(arg->getStringValue("object-name"));
return true;
} else {
return false;
}
}
/**
* Redraw GUI (applying new widget colors). Doesn't reload the dialogs,
* unlike reinit().
*/
static bool
do_gui_redraw (const SGPropertyNode * arg, SGPropertyNode * root)
{
NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
gui->redraw();
return true;
}
/**
* Adds model to the scenery. The path to the added branch (/models/model[*])
* is returned in property "property".
*/
static bool
do_add_model (const SGPropertyNode * arg, SGPropertyNode * root)
{
SGPropertyNode * model = fgGetNode("models", true);
int i;
for (i = 0; model->hasChild("model",i); i++);
model = model->getChild("model", i, true);
copyProperties(arg, model);
if (model->hasValue("elevation-m"))
model->setDoubleValue("elevation-ft", model->getDoubleValue("elevation-m")
* SG_METER_TO_FEET);
model->getNode("load", true);
model->removeChildren("load");
const_cast<SGPropertyNode *>(arg)->setStringValue("property", model->getPath());
return true;
}
/**
* Built-in command: commit presets (read from in /sim/presets/)
*/
static bool
do_presets_commit (const SGPropertyNode * arg, SGPropertyNode * root)
{
if (fgGetBool("/sim/initialized", false)) {
fgResetIdleState();
} else {
// Nasal can trigger this during initial init, which confuses
// the logic in ReInitSubsystems, since initial state has not been
// saved at that time. Short-circuit everything here.
flightgear::initPosition();
}
return true;
}
static bool
do_press_cockpit_button (const SGPropertyNode * arg, SGPropertyNode * root)
{
const string prefix = arg->getStringValue("prefix");
if (arg->getBoolValue("guarded") && fgGetDouble((prefix + "-guard").c_str()) < 1)
return true;
string prop = prefix + "-button";
double value;
if (arg->getBoolValue("latching"))
value = fgGetDouble(prop.c_str()) > 0 ? 0 : 1;
else
value = 1;
fgSetDouble(prop.c_str(), value);
fgSetBool(arg->getStringValue("discrete"), value > 0);
return true;
}
static bool
do_release_cockpit_button (const SGPropertyNode * arg, SGPropertyNode * root)
{
const string prefix = arg->getStringValue("prefix");
if (arg->getBoolValue("guarded")) {
string prop = prefix + "-guard";
if (fgGetDouble(prop.c_str()) < 1) {
fgSetDouble(prop.c_str(), 1);
return true;
}
}
if (! arg->getBoolValue("latching")) {
fgSetDouble((prefix + "-button").c_str(), 0);
fgSetBool(arg->getStringValue("discrete"), false);
}
return true;
}
////////////////////////////////////////////////////////////////////////
// Command setup.
////////////////////////////////////////////////////////////////////////
/**
* Table of built-in commands.
*
* New commands do not have to be added here; any module in the application
* can add a new command using globals->get_commands()->addCommand(...).
*/
static struct {
const char * name;
SGCommandMgr::command_t command;
} built_ins [] = {
{ "exit", do_exit },
{ "reset", do_reset },
{ "reposition", do_reposition },
{ "switch-aircraft", do_switch_aircraft },
{ "panel-load", do_panel_load },
{ "preferences-load", do_preferences_load },
{ "toggle-fullscreen", do_toggle_fullscreen },
{ "screen-capture", do_screen_capture },
{ "hires-screen-capture", do_hires_screen_capture },
{ "tile-cache-reload", do_tile_cache_reload },
{ "dialog-new", do_dialog_new },
{ "dialog-show", do_dialog_show },
{ "dialog-toggle", do_dialog_toggle },
{ "dialog-close", do_dialog_close },
{ "dialog-update", do_dialog_update },
{ "dialog-apply", do_dialog_apply },
{ "open-browser", do_open_browser },
{ "gui-redraw", do_gui_redraw },
{ "add-model", do_add_model },
{ "presets-commit", do_presets_commit },
{ "press-cockpit-button", do_press_cockpit_button },
{ "release-cockpit-button", do_release_cockpit_button },
{ "dump-scenegraph", do_dump_scene_graph },
{ "dump-terrainbranch", do_dump_terrain_branch },
{ "print-visible-scene", do_print_visible_scene_info },
{ "reload-shaders", do_reload_shaders },
{ "reload-materials", do_materials_reload },
{ "reload-compositor", do_reload_compositor },
{ "open-launcher", do_open_launcher },
{ 0, 0 } // zero-terminated
};
/**
* Initialize the default built-in commands.
*
* Other commands may be added by other parts of the application.
*/
void
fgInitSceneCommands ()
{
SG_LOG(SG_GENERAL, SG_BULK, "Initializing scene built-in commands:");
for (int i = 0; built_ins[i].name != 0; i++) {
SG_LOG(SG_GENERAL, SG_BULK, " " << built_ins[i].name);
globals->get_commands()->addCommand(built_ins[i].name,
built_ins[i].command);
}
}

BIN
src/Main/flightgear.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

1
src/Main/flightgear.rc Normal file
View File

@@ -0,0 +1 @@
FLIGHTGEAR ICON "flightgear.ico"

990
src/Main/globals.cxx Normal file
View File

@@ -0,0 +1,990 @@
// globals.cxx -- Global state that needs to be shared among the sim modules
//
// Written by Curtis Olson, started July 2000.
//
// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 <config.h>
#include <algorithm>
#include <osgViewer/Viewer>
#include <osgDB/Registry>
#include <simgear/structure/commands.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/timing/sg_time.hxx>
#include <simgear/ephemeris/ephemeris.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/structure/event_mgr.hxx>
#include <simgear/misc/ResourceManager.hxx>
#include <simgear/props/propertyObject.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/props/AtomicChangeListener.hxx>
#include <simgear/scene/model/modellib.hxx>
#include <simgear/package/Root.hxx>
#include <Add-ons/AddonResourceProvider.hxx>
#include <Aircraft/controls.hxx>
#include <Airports/runways.hxx>
#include <Autopilot/route_mgr.hxx>
#include <Navaids/navlist.hxx>
#include <GUI/gui.h>
#include <Main/sentryIntegration.hxx>
#include <Viewer/viewmgr.hxx>
#include <Scenery/scenery.hxx>
#include <Scenery/tilemgr.hxx>
#include <Viewer/renderer.hxx>
#include <GUI/FGFontCache.hxx>
#include <GUI/MessageBox.hxx>
#include <simgear/sound/soundmgr.hxx>
#include <simgear/scene/material/matlib.hxx>
#include "globals.hxx"
#include "locale.hxx"
#include "fg_props.hxx"
#include "fg_io.hxx"
class AircraftResourceProvider : public simgear::ResourceProvider
{
public:
AircraftResourceProvider() :
simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_HIGH)
{
}
virtual SGPath resolve(const std::string& aResource, SGPath&) const
{
string_list pieces(sgPathBranchSplit(aResource));
if ((pieces.size() < 3) || (pieces.front() != "Aircraft")) {
return SGPath(); // not an Aircraft path
}
// test against the aircraft-dir property
const std::string aircraftDir = fgGetString("/sim/aircraft-dir");
string_list aircraftDirPieces(sgPathBranchSplit(aircraftDir));
if (!aircraftDirPieces.empty() && (aircraftDirPieces.back() == pieces[1])) {
// current aircraft-dir matches resource aircraft
SGPath r(aircraftDir);
for (unsigned int i=2; i<pieces.size(); ++i) {
r.append(pieces[i]);
}
if (r.exists()) {
return r;
} else {
// Stop here, otherwise we could end up returning a resource that
// belongs to an unrelated version of the same aircraft (from a
// different aircraft directory).
return SGPath();
}
}
// try each aircraft dir in turn
std::string res(aResource, 9); // resource path with 'Aircraft/' removed
const PathList& dirs(globals->get_aircraft_paths());
PathList::const_iterator it = dirs.begin();
for (; it != dirs.end(); ++it) {
SGPath p(*it);
p.append(res);
if (p.exists()) {
return p;
}
} // of aircraft path iteration
return SGPath(); // not found
}
};
class CurrentAircraftDirProvider : public simgear::ResourceProvider
{
public:
CurrentAircraftDirProvider() :
simgear::ResourceProvider(simgear::ResourceManager::PRIORITY_HIGH)
{
}
virtual SGPath resolve(const std::string& aResource, SGPath&) const
{
SGPath p = SGPath::fromUtf8(fgGetString("/sim/aircraft-dir"));
p.append(aResource);
return p.exists() ? p : SGPath();
}
};
////////////////////////////////////////////////////////////////////////
// Implementation of FGGlobals.
////////////////////////////////////////////////////////////////////////
// global global :-)
FGGlobals *globals = NULL;
// Constructor
FGGlobals::FGGlobals() :
renderer( new FGRenderer ),
subsystem_mgr( new SGSubsystemMgr ),
event_mgr( new SGEventMgr ),
sim_time_sec( 0.0 ),
fg_root( "" ),
fg_home( "" ),
time_params( NULL ),
commands( SGCommandMgr::instance() ),
channel_options_list( NULL ),
initial_waypoints( NULL ),
channellist( NULL ),
haveUserSettings(false)
{
SGPropertyNode* root = new SGPropertyNode;
props = SGPropertyNode_ptr(root);
locale = new FGLocale(props);
auto resMgr = simgear::ResourceManager::instance();
resMgr->addProvider(new AircraftResourceProvider());
resMgr->addProvider(new CurrentAircraftDirProvider());
resMgr->addProvider(new flightgear::addons::ResourceProvider());
initProperties();
}
void FGGlobals::initProperties()
{
simgear::PropertyObjectBase::setDefaultRoot(props);
positionLon = props->getNode("position/longitude-deg", true);
positionLat = props->getNode("position/latitude-deg", true);
positionAlt = props->getNode("position/altitude-ft", true);
viewLon = props->getNode("sim/current-view/viewer-lon-deg", true);
viewLat = props->getNode("sim/current-view/viewer-lat-deg", true);
viewAlt = props->getNode("sim/current-view/viewer-elev-ft", true);
referenceOffsetX = props->getNode("sim/model/reference-offset-x", true);
referenceOffsetY = props->getNode("sim/model/reference-offset-y", true);
referenceOffsetZ = props->getNode("sim/model/reference-offset-z", true);
orientPitch = props->getNode("orientation/pitch-deg", true);
orientHeading = props->getNode("orientation/heading-deg", true);
orientRoll = props->getNode("orientation/roll-deg", true);
}
// Destructor
FGGlobals::~FGGlobals()
{
// save user settings (unless already saved)
saveUserSettings();
// stop OSG threading first, to avoid thread races while we tear down
// scene-graph pieces
// there are some scenarios where renderer is already gone.
osg::ref_ptr<osgViewer::ViewerBase> vb;
if (renderer) {
vb = renderer->getViewerBase();
if (vb) {
// https://code.google.com/p/flightgear-bugs/issues/detail?id=1291
// explicitly stop trheading before we delete the renderer or
// viewMgr (which ultimately holds refs to the CameraGroup, and
// GraphicsContext)
vb->stopThreading();
}
}
subsystem_mgr->shutdown();
subsystem_mgr->unbind();
// don't cancel the pager until after shutdown, since AIModels (and
// potentially others) can queue delete requests on the pager.
osgViewer::View* v = renderer->getView();
if (v && v->getDatabasePager()) {
v->getDatabasePager()->cancel();
v->getDatabasePager()->clear();
}
osgDB::Registry::instance()->clearObjectCache();
if (subsystem_mgr->get_subsystem(FGScenery::staticSubsystemClassId())) {
subsystem_mgr->remove(FGScenery::staticSubsystemClassId());
}
// renderer touches subsystems during its destruction
set_renderer(nullptr);
FGFontCache::shutdown();
fgCancelSnapShot();
delete subsystem_mgr;
subsystem_mgr = nullptr; // important so ::get_subsystem returns NULL
vb = nullptr;
set_matlib(NULL);
delete time_params;
delete channel_options_list;
delete initial_waypoints;
delete channellist;
// delete commands before we release the property root
// this avoids crash where commands might be storing a propery
// ref/pointer.
// see https://sentry.io/organizations/flightgear/issues/1890563449
delete commands;
commands = nullptr;
simgear::PropertyObjectBase::setDefaultRoot(NULL);
simgear::SGModelLib::resetPropertyRoot();
delete locale;
locale = NULL;
cleanupListeners();
props.clear();
delete simgear::ResourceManager::instance();
}
// set the fg_root path
void FGGlobals::set_fg_root (const SGPath &root) {
SGPath tmp(root);
fg_root = tmp.realpath();
// append /data to root if it exists
tmp.append( "data" );
tmp.append( "version" );
if ( tmp.exists() ) {
fgGetNode("BAD_FG_ROOT", true)->setStringValue(fg_root.utf8Str());
fg_root.append("data");
fgGetNode("GOOD_FG_ROOT", true)->setStringValue(fg_root.utf8Str());
SG_LOG(SG_GENERAL, SG_ALERT, "***\n***\n*** Warning: changing bad FG_ROOT/--fg-root to '"
<< fg_root << "'\n***\n***");
}
// deliberately not a tied property, for SGPath::validate() security
// write-protect to avoid accidents
SGPropertyNode *n = fgGetNode("/sim", true);
n->removeChild("fg-root", 0);
n = n->getChild("fg-root", 0, true);
n->setStringValue(fg_root.utf8Str());
n->setAttribute(SGPropertyNode::WRITE, false);
simgear::ResourceManager::instance()->addBasePath(fg_root,
simgear::ResourceManager::PRIORITY_DEFAULT);
}
// set the fg_home path
void FGGlobals::set_fg_home (const SGPath &home)
{
fg_home = home.realpath();
}
void FGGlobals::set_texture_cache_dir(const SGPath &textureCache)
{
texture_cache_dir = textureCache.realpath();
auto node = fgGetNode("/sim/rendering/texture-cache/dir", true);
node->setAttribute(SGPropertyNode::WRITE, true);
node->setStringValue(textureCache.utf8Str());
node->setAttribute(SGPropertyNode::WRITE, false);
}
PathList FGGlobals::get_data_paths() const
{
PathList r(additional_data_paths);
r.push_back(fg_root);
r.insert(r.end(), _dataPathsAfterFGRoot.begin(), _dataPathsAfterFGRoot.end());
return r;
}
PathList FGGlobals::get_data_paths(const std::string& suffix) const
{
PathList r;
for (SGPath p : get_data_paths()) {
p.append(suffix);
if (p.exists()) {
r.push_back(p);
}
}
return r;
}
void FGGlobals::append_data_path(const SGPath& path, bool afterFGRoot)
{
if (!path.exists()) {
SG_LOG(SG_GENERAL, SG_WARN, "adding non-existant data path:" << path);
}
using RM = simgear::ResourceManager;
auto resManager = RM::instance();
if (afterFGRoot) {
_dataPathsAfterFGRoot.push_back(path);
// after FG_ROOT
resManager->addBasePath(path, static_cast<RM::Priority>(RM::PRIORITY_DEFAULT - 10));
} else {
additional_data_paths.push_back(path);
// after NORMAL prioirty, but ahead of FG_ROOT
resManager->addBasePath(path, static_cast<RM::Priority>(RM::PRIORITY_DEFAULT + 10));
}
}
SGPath FGGlobals::findDataPath(const std::string& pathSuffix) const
{
for (SGPath p : additional_data_paths) {
p.append(pathSuffix);
if (p.exists()) {
return p;
}
}
SGPath rootPath(fg_root);
rootPath.append(pathSuffix);
if (rootPath.exists()) {
return rootPath;
}
for (SGPath p : _dataPathsAfterFGRoot) {
p.append(pathSuffix);
if (p.exists()) {
return p;
}
}
return SGPath{};
}
void FGGlobals::append_fg_scenery (const PathList &paths)
{
for (const SGPath& path : paths) {
append_fg_scenery(path);
}
}
void FGGlobals::append_fg_scenery (const SGPath &path)
{
SGPropertyNode* sim = fgGetNode("/sim", true);
// find first unused fg-scenery property in /sim
int propIndex = 0;
while (sim->getChild("fg-scenery", propIndex) != NULL) {
++propIndex;
}
SGPath abspath(path.realpath());
if (!abspath.exists()) {
SG_LOG(SG_GENERAL, SG_WARN, "scenery path not found:" << abspath);
return;
}
// check for duplicates
PathList::const_iterator ex = std::find(fg_scenery.begin(), fg_scenery.end(), abspath);
if (ex != fg_scenery.end()) {
SG_LOG(SG_GENERAL, SG_INFO, "skipping duplicate add of scenery path:" << abspath);
return;
}
// tell the ResouceManager about the scenery path
// needed to load Models from this scenery path
simgear::ResourceManager::instance()->addBasePath(abspath, simgear::ResourceManager::PRIORITY_DEFAULT);
fg_scenery.push_back(abspath);
extra_read_allowed_paths.push_back(abspath);
// make scenery dirs available to Nasal
SGPropertyNode* n = sim->getChild("fg-scenery", propIndex++, true);
n->setStringValue(abspath.utf8Str());
n->setAttribute(SGPropertyNode::WRITE, false);
// temporary fix so these values survive reset
n->setAttribute(SGPropertyNode::PRESERVE, true);
}
void FGGlobals::append_read_allowed_paths(const SGPath &path)
{
SGPath abspath(path.realpath());
if (!abspath.exists()) {
SG_LOG(SG_IO, SG_DEBUG, "read-allowed path not found:" << abspath);
return;
}
extra_read_allowed_paths.push_back(abspath);
}
void FGGlobals::clear_fg_scenery()
{
fg_scenery.clear();
fgGetNode("/sim", true)->removeChildren("fg-scenery");
}
// The 'path' argument to this method must come from trustworthy code, because
// the method grants read permissions to Nasal code for many files beneath
// 'path'. In particular, don't call this method with a 'path' value taken
// from the property tree or any other Nasal-writable place.
void FGGlobals::set_download_dir(const SGPath& path)
{
SGPath abspath(path.realpath());
download_dir = abspath;
append_read_allowed_paths(abspath / "Aircraft");
append_read_allowed_paths(abspath / "AI");
append_read_allowed_paths(abspath / "Liveries");
// If in use, abspath / TerraSync will be added to 'extra_read_allowed_paths'
// by FGGlobals::append_fg_scenery(), as any scenery path.
SGPropertyNode *n = fgGetNode("/sim/paths/download-dir", true);
n->setAttribute(SGPropertyNode::WRITE, true);
n->setStringValue(abspath.utf8Str());
n->setAttribute(SGPropertyNode::WRITE, false);
}
// The 'path' argument to this method must come from trustworthy code, because
// the method grants read permissions to Nasal code for all files beneath
// 'path'. In particular, don't call this method with a 'path' value taken
// from the property tree or any other Nasal-writable place.
void FGGlobals::set_terrasync_dir(const SGPath &path)
{
if (terrasync_dir.realpath() != SGPath(fgGetString("/sim/terrasync/scenery-dir")).realpath()) {
// if they don't match, /sim/terrasync/scenery-dir has been set by something else
SG_LOG(SG_GENERAL, SG_WARN, "/sim/terrasync/scenery-dir is no longer stored across runs: if you wish to keep using a non-standard Terrasync directory, use --terrasync-dir or the launcher's settings");
}
SGPath abspath(path.realpath());
terrasync_dir = abspath;
// deliberately not a tied property, for SGPath::validate() security
// write-protect to avoid accidents
SGPropertyNode *n = fgGetNode("/sim/terrasync/scenery-dir", true);
n->setAttribute(SGPropertyNode::WRITE, true);
n->setStringValue(abspath.utf8Str());
n->setAttribute(SGPropertyNode::WRITE, false);
// don't add it to fg_scenery yet, as we want it ordered after explicit --fg-scenery
}
void FGGlobals::set_catalog_aircraft_path(const SGPath& path)
{
catalog_aircraft_dir = path;
}
PathList FGGlobals::get_aircraft_paths() const
{
PathList r;
if (!catalog_aircraft_dir.isNull()) {
r.push_back(catalog_aircraft_dir);
}
r.insert(r.end(), fg_aircraft_dirs.begin(), fg_aircraft_dirs.end());
return r;
}
void FGGlobals::append_aircraft_path(const SGPath& path)
{
SGPath dirPath(path);
if (!dirPath.exists()) {
SG_LOG(SG_GENERAL, SG_WARN, "aircraft path not found:" << path);
return;
}
SGPath acSubdir(dirPath);
acSubdir.append("Aircraft");
if (acSubdir.exists()) {
SG_LOG(
SG_GENERAL,
SG_WARN,
"Specified an aircraft-dir with an 'Aircraft' subdirectory:" << dirPath
<< ", will instead use child directory:" << acSubdir
);
dirPath = acSubdir;
}
fg_aircraft_dirs.push_back(dirPath.realpath());
extra_read_allowed_paths.push_back(dirPath.realpath());
}
void FGGlobals::append_aircraft_paths(const PathList& paths)
{
for (unsigned int p = 0; p<paths.size(); ++p) {
append_aircraft_path(paths[p]);
}
}
SGPath FGGlobals::resolve_aircraft_path(const std::string& branch) const
{
return simgear::ResourceManager::instance()->findPath(branch);
}
SGPath FGGlobals::resolve_maybe_aircraft_path(const std::string& branch) const
{
return simgear::ResourceManager::instance()->findPath(branch);
}
SGPath FGGlobals::resolve_resource_path(const std::string& branch) const
{
return simgear::ResourceManager::instance()
->findPath(branch, SGPath(fgGetString("/sim/aircraft-dir")));
}
FGRenderer *
FGGlobals::get_renderer () const
{
return renderer;
}
void FGGlobals::set_renderer(FGRenderer *render)
{
if (render == renderer) {
return;
}
delete renderer;
renderer = render;
}
SGSubsystemMgr *
FGGlobals::get_subsystem_mgr () const
{
return subsystem_mgr;
}
SGSubsystem *
FGGlobals::get_subsystem (const char * name) const
{
if (!subsystem_mgr) {
return NULL;
}
return subsystem_mgr->get_subsystem(name);
}
void
FGGlobals::add_subsystem (const char * name,
SGSubsystem * subsystem,
SGSubsystemMgr::GroupType type,
double min_time_sec)
{
subsystem_mgr->add(name, subsystem, type, min_time_sec);
}
SGEventMgr *
FGGlobals::get_event_mgr () const
{
return event_mgr;
}
SGGeod
FGGlobals::get_aircraft_position() const
{
return SGGeod::fromDegFt(positionLon->getDoubleValue(),
positionLat->getDoubleValue(),
positionAlt->getDoubleValue());
}
SGVec3d
FGGlobals::get_aircraft_position_cart() const
{
return SGVec3d::fromGeod(get_aircraft_position());
}
void FGGlobals::get_aircraft_orientation(double& heading, double& pitch, double& roll)
{
heading = orientHeading->getDoubleValue();
pitch = orientPitch->getDoubleValue();
roll = orientRoll->getDoubleValue();
}
SGGeod
FGGlobals::get_view_position() const
{
return SGGeod::fromDegFt(viewLon->getDoubleValue(),
viewLat->getDoubleValue(),
viewAlt->getDoubleValue());
}
SGVec3d
FGGlobals::get_view_position_cart() const
{
return SGVec3d::fromGeod(get_view_position());
}
SGVec3d FGGlobals::get_ownship_reference_position_cart() const
{
SGVec3d pos = get_aircraft_position_cart();
if (referenceOffsetX)
pos[0] += referenceOffsetX->getDoubleValue();
if (referenceOffsetY)
pos[1] += referenceOffsetY->getDoubleValue();
if (referenceOffsetZ)
pos[2] += referenceOffsetZ->getDoubleValue();
return pos;
}
static void treeDumpRefCounts(int depth, SGPropertyNode* nd)
{
for (int i=0; i<nd->nChildren(); ++i) {
SGPropertyNode* cp = nd->getChild(i);
if (SGReferenced::count(cp) > 1) {
SG_LOG(SG_GENERAL, SG_INFO, "\t" << cp->getPath() << " refcount:" << SGReferenced::count(cp));
}
treeDumpRefCounts(depth + 1, cp);
}
}
static void treeClearAliases(SGPropertyNode* nd)
{
if (nd->isAlias()) {
nd->unalias();
}
for (int i=0; i<nd->nChildren(); ++i) {
SGPropertyNode* cp = nd->getChild(i);
treeClearAliases(cp);
}
}
void
FGGlobals::resetPropertyRoot()
{
delete locale;
// avoid a warning when we processOptions after reset
terrasync_dir = SGPath{};
cleanupListeners();
// we don't strictly need to clear these (they will be reset when we
// initProperties again), but trying to reduce false-positives when dumping
// ref-counts.
positionLon.clear();
positionLat.clear();
positionAlt.clear();
viewLon.clear();
viewLat.clear();
viewAlt.clear();
orientPitch.clear();
orientHeading.clear();
orientRoll.clear();
// clear aliases so ref-counts are accurate when dumped
treeClearAliases(props);
SG_LOG(SG_GENERAL, SG_INFO, "root props refcount:" << props.getNumRefs());
treeDumpRefCounts(0, props);
//BaseStackSnapshot::dumpAll(std::cout);
props = new SGPropertyNode;
initProperties();
locale = new FGLocale(props);
// remove /sim/fg-root before writing to prevent hijacking
SGPropertyNode *n = props->getNode("/sim", true);
n->removeChild("fg-root", 0);
n = n->getChild("fg-root", 0, true);
n->setStringValue(fg_root.utf8Str());
n->setAttribute(SGPropertyNode::WRITE, false);
}
static std::string autosaveName()
{
std::ostringstream os;
string_list versionParts = simgear::strutils::split(VERSION, ".");
if (versionParts.size() < 2) {
return "autosave.xml";
}
os << "autosave_" << versionParts[0] << "_" << versionParts[1] << ".xml";
return os.str();
}
SGPath FGGlobals::autosaveFilePath(SGPath userDataPath) const
{
if (userDataPath.isNull()) {
userDataPath = get_fg_home();
}
return simgear::Dir(userDataPath).file(autosaveName());
}
static void deleteProperties(SGPropertyNode* props, const string_list& blacklist)
{
const std::string path(props->getPath());
auto it = std::find_if(blacklist.begin(), blacklist.end(), [path](const std::string& black)
{ return simgear::strutils::matchPropPathToTemplate(path, black); });
if (it != blacklist.end()) {
SGPropertyNode* pr = props->getParent();
pr->removeChild(props);
return;
}
// recurse
for (int c=0; c < props->nChildren(); ++c) {
deleteProperties(props->getChild(c), blacklist);
}
}
using VersionPair = std::pair<int, int>;
static bool operator<(const VersionPair& a, const VersionPair& b)
{
if (a.first == b.first) {
return a.second < b.second;
}
return a.first < b.first;
}
static void tryAutosaveMigration(const SGPath& userDataPath, SGPropertyNode* props)
{
const string_list versionParts = simgear::strutils::split(VERSION, ".");
if (versionParts.size() < 2) {
return;
}
simgear::Dir userDataDir(userDataPath);
SGPath migratePath;
VersionPair foundVersion(0, 0);
const VersionPair currentVersion(simgear::strutils::to_int(versionParts[0]),
simgear::strutils::to_int(versionParts[1]));
for (auto previousSave : userDataDir.children(simgear::Dir::TYPE_FILE, ".xml")) {
const std::string base = previousSave.file_base();
VersionPair v;
// extract version from name
const int matches = ::sscanf(base.c_str(), "autosave_%d_%d", &v.first, &v.second);
if (matches != 2) {
continue;
}
if (currentVersion < v) {
// ignore autosaves from more recent versions; this happens when
// running unsable and stable at the same time
continue;
}
if (v.first < 2000) {
// ignore autosaves from older versions, too much change to deal
// with.
continue;
}
if (foundVersion < v) {
foundVersion = v;
migratePath = previousSave;
}
}
if (!migratePath.exists()) {
return;
}
SG_LOG(SG_GENERAL, SG_INFO, "Migrating old autosave:" << migratePath);
SGPropertyNode oldProps;
try {
readProperties(migratePath, &oldProps, SGPropertyNode::USERARCHIVE);
} catch (sg_exception& e) {
SG_LOG(SG_GENERAL, SG_WARN, "failed to read previous user settings:" << e.getMessage()
<< "(from " << e.getOrigin() << ")");
return;
}
// read migration blacklist
string_list blacklist;
SGPropertyNode_ptr blacklistNode = fgGetNode("/sim/autosave-migration/blacklist", true);
for (auto node : blacklistNode->getChildren("path")) {
blacklist.push_back(node->getStringValue());
}
// apply migration filters for each property in turn
deleteProperties(&oldProps, blacklist);
// manual migrations
// migrate pre-2019.1 sense of /sim/mouse/invert-mouse-wheel
if (foundVersion.first < 2019) {
SGPropertyNode_ptr wheelNode = oldProps.getNode("/sim/mouse/invert-mouse-wheel");
if (wheelNode) {
wheelNode->setBoolValue(!wheelNode->getBoolValue());
}
}
// copy remaining props out
copyProperties(&oldProps, props);
// we can't inform the user yet, becuase embedded resources and the locale
// are not done. So we set a flag and check it once those things are done.
fgSetBool("/sim/autosave-migration/did-migrate", true);
}
// Load user settings from the autosave file (normally in $FG_HOME)
void
FGGlobals::loadUserSettings(SGPath userDataPath)
{
if (userDataPath.isNull()) {
userDataPath = get_fg_home();
}
// Remember that we have (tried) to load any existing autosave file
haveUserSettings = true;
SGPath autosaveFile = autosaveFilePath(userDataPath);
SGPropertyNode autosave;
if (autosaveFile.exists()) {
SG_LOG(SG_INPUT, SG_INFO,
"Reading user settings from " << autosaveFile);
try {
readProperties(autosaveFile, &autosave, SGPropertyNode::USERARCHIVE);
} catch (sg_exception& e) {
SG_LOG(SG_INPUT, SG_WARN, "failed to read user settings:" << e.getMessage()
<< "(from " << e.getOrigin() << ")");
}
} else {
tryAutosaveMigration(userDataPath, &autosave);
}
/* Before 2020-03-10, we could save portions of the /ai/models/ tree, which
confuses things when loaded again. So delete any such items if they have
been loaded. */
SGPropertyNode* ai = autosave.getNode("ai");
if (ai) {
ai->removeChildren("models");
}
copyProperties(&autosave, globals->get_props());
}
// Save user settings to the autosave file.
//
// When calling this method, make sure the value of 'userDataPath' is
// trustworthy. In particular, make sure it can't be influenced by Nasal code,
// not even indirectly via a Nasal-writable place such as the property tree.
//
// Note: the default value, which causes the autosave file to be written to
// $FG_HOME, is safe---if not, it would be a bug.
void
FGGlobals::saveUserSettings(SGPath userDataPath)
{
if (userDataPath.isNull()) userDataPath = get_fg_home();
// only save settings when we have (tried) to load the previous
// settings (otherwise user data was lost)
if (!haveUserSettings)
return;
if (fgGetBool("/sim/startup/save-on-exit")) {
// don't save settings more than once on shutdown
haveUserSettings = false;
SGPath autosaveFile = autosaveFilePath(userDataPath);
autosaveFile.create_dir( 0700 );
SG_LOG(SG_IO, SG_INFO, "Saving user settings to " << autosaveFile);
try {
writeProperties(autosaveFile, globals->get_props(), false, SGPropertyNode::USERARCHIVE);
} catch (const sg_exception &e) {
guiErrorMessage("Error writing autosave:", e);
}
SG_LOG(SG_INPUT, SG_DEBUG, "Finished Saving user settings");
}
}
long int FGGlobals::get_warp() const
{
return fgGetInt("/sim/time/warp");
}
void FGGlobals::set_warp( long int w )
{
fgSetInt("/sim/time/warp", w);
}
long int FGGlobals::get_warp_delta() const
{
return fgGetInt("/sim/time/warp-delta");
}
void FGGlobals::set_warp_delta( long int d )
{
fgSetInt("/sim/time/warp-delta", d);
}
FGScenery* FGGlobals::get_scenery () const
{
return get_subsystem<FGScenery>();
}
FGViewMgr *FGGlobals::get_viewmgr() const
{
return get_subsystem<FGViewMgr>();
}
flightgear::View* FGGlobals::get_current_view () const
{
FGViewMgr* vm = get_viewmgr();
return vm ? vm->get_current_view() : 0;
}
void FGGlobals::set_matlib( SGMaterialLib *m )
{
matlib = m;
}
FGControls *FGGlobals::get_controls() const
{
return get_subsystem<FGControls>();
}
void FGGlobals::addListenerToCleanup(SGPropertyChangeListener* l)
{
_listeners_to_cleanup.push_back(l);
}
void FGGlobals::cleanupListeners()
{
SGPropertyChangeListenerVec::iterator i = _listeners_to_cleanup.begin();
for (; i != _listeners_to_cleanup.end(); ++i) {
delete *i;
}
_listeners_to_cleanup.clear();
simgear::AtomicChangeListener::clearPendingChanges();
}
simgear::pkg::Root* FGGlobals::packageRoot()
{
return _packageRoot.get();
}
void FGGlobals::setPackageRoot(const SGSharedPtr<simgear::pkg::Root>& p)
{
_packageRoot = p;
}
bool FGGlobals::is_headless()
{
return flightgear::isHeadlessMode();
}
void FGGlobals::set_headless(bool mode)
{
flightgear::setHeadlessMode(mode);
}
// end of globals.cxx

422
src/Main/globals.hxx Normal file
View File

@@ -0,0 +1,422 @@
// globals.hxx -- Global state that needs to be shared among the sim modules
//
// Written by Curtis Olson, started July 2000.
//
// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 _GLOBALS_HXX
#define _GLOBALS_HXX
#include <simgear/compiler.h>
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/misc/sg_path.hxx>
#include <vector>
#include <string>
#include <memory>
typedef std::vector<std::string> string_list;
typedef std::vector<SGPath> PathList;
// Forward declarations
// This file is included, directly or indirectly, almost everywhere in
// FlightGear, so if any of its dependencies changes, most of the sim
// has to be recompiled. Using these forward declarations helps us to
// avoid including a lot of header files (and thus, a lot of
// dependencies). Since Most of the methods simply set or return
// pointers, we don't need to know anything about the class details
// anyway.
class SGCommandMgr;
class SGMaterialLib;
class SGPropertyNode;
class SGTime;
class SGEventMgr;
class SGSubsystemMgr;
class SGSubsystem;
class FGControls;
class FGTACANList;
class FGLocale;
class FGRouteMgr;
class FGScenery;
class FGViewMgr;
class FGRenderer;
namespace simgear { namespace pkg {
class Root;
}}
namespace flightgear
{
class View;
}
/**
* Bucket for subsystem pointers representing the sim's state.
*/
class FGGlobals
{
private:
// properties, destroy last
SGPropertyNode_ptr props;
// localization
FGLocale* locale;
FGRenderer *renderer;
SGSubsystemMgr *subsystem_mgr;
SGEventMgr *event_mgr;
// Number of milliseconds elapsed since the start of the program.
double sim_time_sec;
// Root of FlightGear data tree
SGPath fg_root;
/**
* locations to search for (non-scenery) data.
*/
PathList additional_data_paths;
PathList _dataPathsAfterFGRoot; ///< paths with a /lower/ priority than FGRoot
// Users home directory for data
SGPath fg_home;
SGPath texture_cache_dir;
// Download directory
SGPath download_dir;
// Terrasync directory
SGPath terrasync_dir;
// Roots of FlightGear scenery tree
PathList fg_scenery;
// Paths Nasal is allowed to read
PathList extra_read_allowed_paths;
std::string browser;
// Time structure
SGTime *time_params;
// Material properties library
SGSharedPtr<SGMaterialLib> matlib;
SGCommandMgr *commands;
// list of serial port-like configurations
string_list *channel_options_list;
// A list of initial waypoints that are read from the command line
// and or flight-plan file during initialization
string_list *initial_waypoints;
// Navigational Aids
FGTACANList *channellist;
/// roots of Aircraft trees
PathList fg_aircraft_dirs;
SGPath catalog_aircraft_dir;
bool haveUserSettings;
SGPropertyNode_ptr positionLon, positionLat, positionAlt;
SGPropertyNode_ptr viewLon, viewLat, viewAlt;
SGPropertyNode_ptr orientHeading, orientPitch, orientRoll;
SGPropertyNode_ptr referenceOffsetX, referenceOffsetY, referenceOffsetZ;
/**
* helper to initialise standard properties on a new property tree
*/
void initProperties();
void cleanupListeners();
typedef std::vector<SGPropertyChangeListener*> SGPropertyChangeListenerVec;
SGPropertyChangeListenerVec _listeners_to_cleanup;
SGSharedPtr<simgear::pkg::Root> _packageRoot;
public:
FGGlobals();
virtual ~FGGlobals();
virtual FGRenderer *get_renderer () const;
void set_renderer(FGRenderer* render);
SGSubsystemMgr *get_subsystem_mgr () const;
SGSubsystem *get_subsystem (const char * name) const;
template<class T>
T* get_subsystem() const
{
return dynamic_cast<T*>(get_subsystem(T::staticSubsystemClassId()));
}
void add_subsystem (const char * name,
SGSubsystem * subsystem,
SGSubsystemMgr::GroupType type,
double min_time_sec = 0);
template<class T>
T* add_new_subsystem (SGSubsystemMgr::GroupType type, double min_time_sec = 0)
{
T* sub = new T;
add_subsystem(T::staticSubsystemClassId(), sub, type, min_time_sec);
return sub;
}
SGEventMgr *get_event_mgr () const;
inline double get_sim_time_sec () const { return sim_time_sec; }
inline void inc_sim_time_sec (double dt) { sim_time_sec += dt; }
inline void set_sim_time_sec (double t) { sim_time_sec = t; }
const SGPath &get_fg_root () const { return fg_root; }
void set_fg_root(const SGPath&root);
void set_texture_cache_dir(const SGPath&textureCache);
/**
* Get list of data locations. fg_root is always the final item in the
* result.
*/
PathList get_data_paths() const;
/**
* Get data locations which contain the file path suffix. Eg pass ing
* 'AI/Traffic' to get all data paths which define <path>/AI/Traffic subdir
*/
PathList get_data_paths(const std::string& suffix) const;
void append_data_path(const SGPath& path, bool afterFGRoot = false);
/**
* Given a path suffix (eg 'Textures' or 'AI/Traffic'), find the
* first data directory which defines it.
*/
SGPath findDataPath(const std::string& pathSuffix) const;
const SGPath &get_fg_home () const { return fg_home; }
void set_fg_home (const SGPath &home);
const SGPath &get_download_dir () const { return download_dir; }
// The 'path' argument to set_download_dir() must come from trustworthy
// code, because the method grants read permissions to Nasal code for many
// files beneath 'path'. In particular, don't call this method with a
// 'path' value taken from the property tree or any other Nasal-writable
// place.
void set_download_dir (const SGPath &path);
const SGPath &get_texture_cache_dir() const { return texture_cache_dir; }
const SGPath &get_terrasync_dir () const { return terrasync_dir; }
// The 'path' argument to set_terrasync_dir() must come from trustworthy
// code, because the method grants read permissions to Nasal code for all
// files beneath 'path'. In particular, don't call this method with a
// 'path' value taken from the property tree or any other Nasal-writable
// place.
void set_terrasync_dir (const SGPath &path);
const PathList &get_fg_scenery () const { return fg_scenery; }
const PathList &get_extra_read_allowed_paths () const { return extra_read_allowed_paths; }
/**
* Add a scenery directory
*
* This also makes the path Nasal-readable:
* to avoid can-read-any-file security holes, do NOT call this on paths
* obtained from the property tree or other Nasal-writable places
*/
void append_fg_scenery (const SGPath &scenery);
void append_fg_scenery (const PathList &scenery);
void clear_fg_scenery();
/**
* Allow Nasal to read a path
*
* To avoid can-read-any-file security holes, do NOT call this on paths
* obtained from the property tree or other Nasal-writable places
*
* Only works during initial load (before fgInitAllowedPaths)
*/
void append_read_allowed_paths (const SGPath &path);
/**
* specify a path we'll prepend to the aircraft paths list if non-empty.
* This is used with packaged aircraft, to ensure their catalog (and hence,
* dependency packages) are found correctly.
*/
void set_catalog_aircraft_path(const SGPath& path);
PathList get_aircraft_paths() const;
/**
* Add an aircraft directory
*
* This also makes the path Nasal-readable:
* to avoid can-read-any-file security holes, do NOT call this on paths
* obtained from the property tree or other Nasal-writable places
*/
void append_aircraft_path(const SGPath& path);
void append_aircraft_paths(const PathList& path);
/**
* Given a path to an aircraft-related resource file, resolve it
* against the appropriate root. This means looking at the location
* defined by /sim/aircraft-dir, and then aircraft_path in turn,
* finishing with fg_root/Aircraft.
*
* if the path could not be resolved, an empty path is returned.
*/
SGPath resolve_aircraft_path(const std::string& branch) const;
/**
* Same as above, but test for non 'Aircraft/' branch paths, and
* always resolve them against fg_root.
*/
SGPath resolve_maybe_aircraft_path(const std::string& branch) const;
/**
* Search in the following directories:
*
* 1. Root directory of current aircraft (defined by /sim/aircraft-dir)
* 2. All aircraft directories if branch starts with Aircraft/
* 3. fg_data directory
*/
SGPath resolve_resource_path(const std::string& branch) const;
inline const std::string &get_browser () const { return browser; }
void set_browser (const std::string &b) { browser = b; }
long int get_warp() const;
void set_warp( long int w );
long int get_warp_delta() const;
void set_warp_delta( long int d );
inline SGTime *get_time_params() const { return time_params; }
inline void set_time_params( SGTime *t ) { time_params = t; }
inline SGMaterialLib *get_matlib() const
{
return matlib;
}
void set_matlib( SGMaterialLib *m );
inline SGPropertyNode *get_props () { return props; }
/**
* @brief reset the property tree to new, empty tree. Ensure all
* subsystems are shutdown and unbound before call this.
*/
void resetPropertyRoot();
inline FGLocale* get_locale () { return locale; }
inline SGCommandMgr *get_commands () { return commands; }
SGGeod get_aircraft_position() const;
SGVec3d get_aircraft_position_cart() const;
void get_aircraft_orientation(double& heading, double& pitch, double& roll);
SGGeod get_view_position() const;
SGVec3d get_view_position_cart() const;
SGVec3d get_ownship_reference_position_cart() const;
inline string_list *get_channel_options_list () {
return channel_options_list;
}
inline void set_channel_options_list( string_list *l ) {
channel_options_list = l;
}
inline string_list *get_initial_waypoints () {
return initial_waypoints;
}
inline void set_initial_waypoints (string_list *list) {
initial_waypoints = list;
}
FGViewMgr *get_viewmgr() const;
flightgear::View *get_current_view() const;
FGControls *get_controls() const;
FGScenery * get_scenery () const;
inline FGTACANList *get_channellist() const { return channellist; }
inline void set_channellist( FGTACANList *c ) { channellist = c; }
/**
* Return an SGPath instance for the autosave file under 'userDataPath',
* which defaults to $FG_HOME.
*/
SGPath autosaveFilePath(SGPath userDataPath = SGPath()) const;
/**
* Load user settings from the autosave file under 'userDataPath',
* which defaults to $FG_HOME.
*/
void loadUserSettings(SGPath userDatapath = SGPath());
/**
* Save user settings to the autosave file under 'userDataPath', which
* defaults to $FG_HOME.
*
* When calling this method, make sure the value of 'userDataPath' is
* trustworthy. In particular, make sure it can't be influenced by Nasal
* code, not even indirectly via a Nasal-writable place such as the
* property tree.
*
* Note: the default value is safe---if not, it would be a bug.
*/
void saveUserSettings(SGPath userDatapath = SGPath());
void addListenerToCleanup(SGPropertyChangeListener* l);
simgear::pkg::Root* packageRoot();
void setPackageRoot(const SGSharedPtr<simgear::pkg::Root>& p);
/**
* A runtime headless mode for running without a GUI.
*/
bool is_headless();
void set_headless(bool mode);
};
extern FGGlobals *globals;
#endif // _GLOBALS_HXX

622
src/Main/locale.cxx Normal file
View File

@@ -0,0 +1,622 @@
// locale.cxx -- FlightGear Localization Support
//
// Written by Thorsten Brehm, started April 2012.
//
// Copyright (C) 2012 Thorsten Brehm - brehmt (at) 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
#include <config.h>
#ifdef HAVE_WINDOWS_H
#include <windows.h>
#endif
#include <cstdio>
#include <cstring> // std::strlen()
#include <cstddef> // std::size_t
#include <cassert>
#include <simgear/misc/strutils.hxx>
#include <simgear/props/props.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/exception.hxx>
#include "fg_props.hxx"
#include "locale.hxx"
#include "XLIFFParser.hxx"
#include <Add-ons/AddonMetadataParser.hxx>
using std::vector;
using std::string;
namespace strutils = simgear::strutils;
FGLocale::FGLocale(SGPropertyNode* root) :
_intl(root->getNode("/sim/intl", 0, true)),
_defaultLocale(_intl->getChild("locale", 0, true))
{
}
FGLocale::~FGLocale()
{
}
// Static method
string FGLocale::removeEncodingPart(const string& locale)
{
string res;
std::size_t pos = locale.find('.');
if (pos != string::npos)
{
assert(pos > 0);
res = locale.substr(0, pos);
} else {
res = locale;
}
return res;
}
string removeLocalePart(const string& locale)
{
std::size_t pos = locale.find('_');
if (pos == string::npos) {
pos = locale.find('-');
}
if (pos == string::npos)
return {};
return locale.substr(0, pos);
}
#ifdef _WIN32
string_list
FGLocale::getUserLanguages()
{
unsigned long bufSize = 128;
wchar_t* localeNameBuf = reinterpret_cast<wchar_t*>(alloca(bufSize));
unsigned long numLanguages = 0;
bool ok = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages,
localeNameBuf, &bufSize);
if (!ok) {
// if we have a lot of languages, can fail, allocate a bigger
// buffer
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
bufSize = 0;
GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages,
nullptr, &bufSize);
localeNameBuf = reinterpret_cast<wchar_t*>(alloca(bufSize));
ok = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numLanguages,
localeNameBuf, &bufSize);
}
}
if (!ok) {
SG_LOG(SG_GENERAL, SG_WARN, "Failed to detected user locale via GetUserPreferredUILanguages");
return{};
}
string_list result;
result.reserve(numLanguages);
for (unsigned int l = 0; l < numLanguages; ++l) {
std::wstring ws(localeNameBuf);
if (ws.empty())
break;
// skip to next string, past this string and trailing NULL
localeNameBuf += (ws.size() + 1);
result.push_back(simgear::strutils::convertWStringToUtf8(ws));
SG_LOG(SG_GENERAL, SG_INFO, "User langauge " << l << ":" << result.back());
}
return result;
}
#elif __APPLE__
// implemented in CocoaHelpers.mm
#else
/**
* Determine locale/language settings on Linux/Unix.
*/
string_list
FGLocale::getUserLanguages()
{
string_list result;
const char* langEnv = ::getenv("LANG");
if (langEnv) {
// Remove character encoding from the locale spec, i.e. "de_DE.UTF-8"
// becomes "de_DE". This is for consistency with the Windows and MacOS
// implementations of this method.
result.push_back(removeEncodingPart(langEnv));
}
return result;
}
#endif
// Search property tree for matching locale description
SGPropertyNode*
FGLocale::findLocaleNode(const string& localeSpec)
{
SGPropertyNode* node = nullptr;
// Remove the character encoding part of the locale spec, i.e.,
// "de_DE.utf8" => "de_DE"
string language = removeEncodingPart(localeSpec);
SG_LOG(SG_GENERAL, SG_DEBUG,
"Searching language resource for locale: '" << language << "'");
// search locale using full string
vector<SGPropertyNode_ptr> localeList = _intl->getChildren("locale");
for (size_t i = 0; i < localeList.size(); i++)
{
vector<SGPropertyNode_ptr> langList = localeList[i]->getChildren("lang");
for (size_t j = 0; j < langList.size(); j++)
{
if (!language.compare(langList[j]->getStringValue()))
{
SG_LOG(SG_GENERAL, SG_INFO, "Found language resource for: " << language);
return localeList[i];
}
}
}
// try country's default resource, i.e. "de_DE" => "de"
const auto justTheLanguage = removeLocalePart(language);
if (!justTheLanguage.empty()) {
node = findLocaleNode(justTheLanguage);
if (node)
return node;
}
return nullptr;
}
// Select the language. When no language is given (nullptr),
// a default is determined matching the system locale.
bool FGLocale::selectLanguage(const std::string& language)
{
_languages = getUserLanguages();
if (_languages.empty()) {
// Use plain C locale if nothing is available.
SG_LOG(SG_GENERAL, SG_WARN, "Unable to detect system language" );
_languages.push_back("C");
}
// if we were passed a language option, try it first
if (!language.empty()) {
const auto normalizedLang = strutils::replace(language, "-", "_");
_languages.insert(_languages.begin(), normalizedLang);
}
_currentLocaleString = removeEncodingPart(_languages.front());
if (_currentLocaleString == "C") {
_currentLocaleString.clear();
}
_currentLocale = nullptr;
for (const string& lang : _languages) {
SG_LOG(SG_GENERAL, SG_DEBUG,
"Trying to find locale for '" << lang << "'");
_currentLocale = findLocaleNode(lang);
if (_currentLocale) {
SG_LOG(SG_GENERAL, SG_DEBUG,
"Found locale for '" << lang << "' at '" <<
_currentLocale->getPath() << "'");
break;
}
}
if (_currentLocale && _currentLocale->hasChild("xliff")) {
parseXLIFF(_currentLocale);
}
// load resource for system messages (translations for fgfs internal messages)
loadResource("sys");
loadResource("atc");
loadResource("tips");
loadResource("weather-scenarios");
_inited = true;
if (!_currentLocale && !_currentLocaleString.empty()) {
SG_LOG(SG_GENERAL, SG_WARN,
"System locale not found or no internationalization settings specified in defaults.xml. Using default (en).");
return false;
}
return true;
}
void FGLocale::clear()
{
_inited = false;
_currentLocaleString.clear();
_languages.clear();
if (_currentLocale && (_currentLocale != _defaultLocale)) {
// remove loaded strings, so we don't duplicate
_currentLocale->removeChild("strings");
}
_currentLocale.clear();
}
// Return the preferred language according to user choice and/or settings
// (e.g., 'fr_FR', or the empty string if nothing could be found).
std::string
FGLocale::getPreferredLanguage() const
{
return _currentLocaleString;
}
void FGLocale::parseXLIFF(SGPropertyNode* localeNode)
{
SGPath path(globals->get_fg_root());
SGPath xliffPath = path / localeNode->getStringValue("xliff");
if (!xliffPath.exists()) {
SG_LOG(SG_GENERAL, SG_ALERT, "No XLIFF file at " << xliffPath);
} else {
SG_LOG(SG_GENERAL, SG_INFO, "Loading XLIFF file at " << xliffPath);
SGPropertyNode_ptr stringNode = localeNode->getNode("strings", 0, true);
try {
flightgear::XLIFFParser visitor(stringNode);
readXML(xliffPath, visitor);
} catch (sg_io_exception& ex) {
SG_LOG(SG_GENERAL, SG_WARN, "failure parsing XLIFF: " << path <<
"\n\t" << ex.getMessage() << "\n\tat:" << ex.getLocation().asString());
} catch (sg_exception& ex) {
SG_LOG(SG_GENERAL, SG_WARN, "failure parsing XLIFF: " << path <<
"\n\t" << ex.getMessage());
}
}
}
// Load strings for requested resource and locale.
// Result is stored below "strings" in the property tree of the given locale.
bool
FGLocale::loadResource(SGPropertyNode* localeNode, const char* resource)
{
SGPath path( globals->get_fg_root() );
// already loaded
if (localeNode->hasChild("xliff")) {
return true;
}
SGPropertyNode* stringNode = localeNode->getNode("strings", 0, true);
SGPropertyNode* resourceNode = stringNode->getNode(resource);
if (!resourceNode)
return false;
if (resourceNode->getBoolValue("__loaded")) {
// already loaded previously
return true;
}
string path_str = resourceNode->getStringValue();
if (path_str.empty())
{
SG_LOG(SG_GENERAL, SG_WARN, "No path in " << stringNode->getPath() << "/" << resource << ".");
return false;
}
path.append(path_str);
SG_LOG(SG_GENERAL, SG_INFO, "Reading localized strings for '" <<
localeNode->getStringValue("lang", "<none>")
<<"' from " << path);
// load the actual file
try
{
readProperties(path, resourceNode);
} catch (const sg_exception &e)
{
SG_LOG(SG_GENERAL, SG_ALERT, "Unable to read the localized strings from " << path <<
". Error: " << e.getFormattedMessage());
return false;
}
// set marker to indicate the data has been loaded
resourceNode->setBoolValue("__loaded", true);
return true;
}
// Load strings for requested resource (for current and default locale).
// Result is stored below "strings" in the property tree of the locales.
bool
FGLocale::loadResource(const char* resource)
{
// load defaults first
bool Ok = loadResource(_defaultLocale, resource);
// also load language specific resource, unless identical
if ((_currentLocale != nullptr) && (_defaultLocale != _currentLocale))
{
Ok &= loadResource(_currentLocale, resource);
}
return Ok;
}
std::string
FGLocale::innerGetLocalizedString(SGPropertyNode* localeNode, const char* id, const char* context, int index) const
{
SGPropertyNode *n = localeNode->getNode("strings",0, true)->getNode(context);
if (!n) {
return std::string();
}
n = n->getNode(id, index);
if (n && n->hasValue()) {
return std::string(n->getStringValue());
}
return std::string();
}
std::string
FGLocale::getLocalizedString(const std::string& id, const char* resource, const std::string& defaultValue)
{
return getLocalizedString(id.c_str(), resource, defaultValue.c_str());
}
std::string
FGLocale::getLocalizedString(const char* id, const char* resource, const char* Default)
{
assert(_inited);
if (id && resource)
{
std::string s;
if (_currentLocale) {
s = innerGetLocalizedString(_currentLocale, id, resource, 0);
if (!s.empty()) {
return s;
}
}
if (_defaultLocale) {
s = innerGetLocalizedString(_defaultLocale, id, resource, 0);
if (!s.empty()) {
return s;
}
}
}
return (Default == nullptr) ? std::string() : std::string(Default);
}
std::string
FGLocale::getLocalizedStringWithIndex(const char* id, const char* resource, unsigned int index) const
{
assert(_inited);
if (id && resource) {
std::string s;
if (_currentLocale) {
s = innerGetLocalizedString(_currentLocale, id, resource, index);
if (!s.empty()) {
return s;
}
}
if (_defaultLocale) {
s = innerGetLocalizedString(_defaultLocale, id, resource, index);
if (!s.empty()) {
return s;
}
}
}
return std::string();
}
simgear::PropertyList
FGLocale::getLocalizedStrings(SGPropertyNode *localeNode, const char* id, const char* context)
{
SGPropertyNode *n = localeNode->getNode("strings",0, true)->getNode(context);
if (n)
{
return n->getChildren(id);
}
return simgear::PropertyList();
}
size_t FGLocale::getLocalizedStringCount(const char* id, const char* resource) const
{
assert(_inited);
if (_currentLocale) {
SGPropertyNode* resourceNode = _currentLocale->getNode("strings",0, true)->getNode(resource);
if (resourceNode) {
const size_t count = resourceNode->getChildren(id).size();
if (count > 0) {
return count;
}
}
}
if (_defaultLocale) {
auto resourceNode = _defaultLocale->getNode("strings", 0, true)->getNode(resource);
if (!resourceNode)
return 0;
size_t count = resourceNode->getChildren(id).size();
if (count > 0) {
return count;
}
}
return 0;
}
simgear::PropertyList
FGLocale::getLocalizedStrings(const char* id, const char* resource)
{
assert(_inited);
if (id && resource)
{
if (_currentLocale)
{
simgear::PropertyList s = getLocalizedStrings(_currentLocale, id, resource);
if (! s.empty())
return s;
}
if (_defaultLocale)
{
simgear::PropertyList s = getLocalizedStrings(_defaultLocale, id, resource);
if (! s.empty())
return s;
}
}
return simgear::PropertyList();
}
// Check for localized font
std::string
FGLocale::getDefaultFont(const char* fallbackFont)
{
assert(_inited);
std::string font;
if (_currentLocale)
{
font = _currentLocale->getStringValue("font", "");
if (!font.empty())
return font;
}
if (_defaultLocale)
{
font = _defaultLocale->getStringValue("font", "");
if (!font.empty())
return font;
}
return fallbackFont;
}
std::string FGLocale::localizedPrintf(const char* id, const char* resource, ... )
{
va_list args;
va_start(args, resource);
string r = vlocalizedPrintf(id, resource, args);
va_end(args);
return r;
}
std::string FGLocale::vlocalizedPrintf(const char* id, const char* resource, va_list args)
{
assert(_inited);
std::string format = getLocalizedString(id, resource);
int len = ::vsnprintf(nullptr, 0, format.c_str(), args);
char* buf = (char*) alloca(len);
::vsprintf(buf, format.c_str(), args);
return std::string(buf);
}
// Simple UTF8 to Latin1 encoder.
void FGLocale::utf8toLatin1(string& s)
{
size_t pos = 0;
// map '0xc3..' utf8 characters to Latin1
while ((string::npos != (pos = s.find('\xc3',pos)))&&
(pos+1 < s.size()))
{
char c='*';
unsigned char p = s[pos+1];
if ((p>=0x80)&&(p<0xc0))
c = 0x40 + p;
char v[2];
v[0]=c;
v[1]=0;
s.replace(pos, 2, v);
pos++;
}
#ifdef DEBUG_ENCODING
printf("'%s': ", s.c_str());
for (pos = 0;pos<s.size();pos++)
printf("%02x ", (unsigned char) s[pos]);
printf("\n");
#endif
// hack: also map some Latin2 characters to plain-text ASCII
pos = 0;
while ((string::npos != (pos = s.find('\xc5',pos)))&&
(pos+1 < s.size()))
{
char c='*';
unsigned char p = s[pos+1];
switch(p)
{
case 0x82:c='l';break;
case 0x9a:c='S';break;
case 0x9b:c='s';break;
case 0xba:c='z';break;
case 0xbc:c='z';break;
}
char v[2];
v[0]=c;
v[1]=0;
s.replace(pos, 2, v);
pos++;
}
}
std::string fgTrMsg(const char* key)
{
return globals->get_locale()->getLocalizedString(key, "message");
}
std::string fgTrPrintfMsg(const char* key, ...)
{
va_list args;
va_start(args, key);
string r = globals->get_locale()->vlocalizedPrintf(key, "message", args);
va_end(args);
return r;
}
SGPropertyNode_ptr FGLocale::selectLanguageNode(SGPropertyNode* langs) const
{
if (!langs)
return {};
for (auto l : _languages) {
// Only accept the hyphen separator in PropertyList node names between
// language and territory
const auto langNoEncoding = strutils::replace(removeEncodingPart(l),
"_", "-");
if (langs->hasChild(langNoEncoding)) {
return langs->getChild(langNoEncoding);
}
const auto justLang = removeLocalePart(langNoEncoding);
if (langs->hasChild(justLang)) {
return langs->getChild(justLang);
}
}
return {};
}

184
src/Main/locale.hxx Normal file
View File

@@ -0,0 +1,184 @@
// locale.hxx -- FlightGear Localization Support
//
// Written by Thorsten Brehm, started April 2012.
//
// Copyright (C) 2012 Thorsten Brehm - brehmt (at) 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef __FGLOCALE_HXX
#define __FGLOCALE_HXX
#include <string>
#include <cstdarg> // for va_start/_end
#include <simgear/props/propsfwd.hxx>
#include <simgear/misc/strutils.hxx>
// forward decls
class SGPath;
///////////////////////////////////////////////////////////////////////////////
// FGLocale //////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
class FGLocale
{
public:
FGLocale(SGPropertyNode* root);
virtual ~FGLocale();
/**
* Select the locale's primary language. When no language is given
* (nullptr), a default is determined matching the system locale.
*/
bool selectLanguage(const std::string& language = {});
/** Return the preferred language according to user choice and/or settings.
*
* Examples: 'fr_CA', 'de_DE'... or the empty string if nothing could be
* found.
*
* Note that this is not necessarily the same as the last value passed to
* selectLanguage(), assuming it was non-null and non-empty, because the
* latter may have an encoding specifier, while values returned by
* getPreferredLanguage() never have that.
*/
std::string getPreferredLanguage() const;
/**
* Load strings for requested resource, i.e. "menu", "options", "dialogs".
* Loads data for current and default locale (the latter is the fallback).
* Result is stored below the "strings" node in the property tree of the
* respective locale.
*/
bool loadResource (const char* resource);
/**
* Obtain a single string from the localized resource matching the given identifier.
* Selected context refers to "menu", "options", "dialog" etc.
*/
std::string getLocalizedString(const char* id, const char* resource,
const char* Default = nullptr);
std::string getLocalizedString(const std::string& id, const char* resource, const std::string& defaultValue = {});
/**
* Obtain a list of strings from the localized resource matching the given identifier.
* Selected context refers to "menu", "options", "dialog" etc.
* Returns a list of (string) properties.
*/
simgear::PropertyList getLocalizedStrings(const char* id, const char* resource);
/**
* Obtain a single string from the resource matching an identifier and ID.
*/
std::string getLocalizedStringWithIndex(const char* id, const char* resource, unsigned int index) const;
/**
* Return the number of strings matching a resource
*/
size_t getLocalizedStringCount(const char* id, const char* resource) const;
/**
* Obtain default font for current locale.
*/
std::string getDefaultFont (const char* fallbackFont);
/**
* Obtain a message string, from a localized resource ID, and use it as
* a printf format string.
*/
std::string localizedPrintf(const char* id, const char* resource, ... );
std::string vlocalizedPrintf(const char* id, const char* resource, va_list args);
/**
* Simple UTF8 to Latin1 encoder.
*/
static void utf8toLatin1 (std::string& s);
/**
* reset all data in the locale. This is needed to allow the launcher to use the code,
without disturbing the main behaviour. Afteer calling this you can do
selectLangauge again without problems.
*/
void clear();
/**
@ brief given a node with children corresponding to different language / locale codes,
select one based on the user preferred langauge
*/
SGPropertyNode_ptr selectLanguageNode(SGPropertyNode* langs) const;
protected:
/**
* Find property node matching given language.
*/
SGPropertyNode* findLocaleNode (const std::string& language);
/**
* Load resource data for given locale node.
*/
bool loadResource (SGPropertyNode* localeNode, const char* resource);
/**
* Obtain a single string from locale node matching the given identifier and context.
*/
std::string innerGetLocalizedString(SGPropertyNode* localeNode, const char* id, const char* context, int index) const;
/**
* Obtain a list of strings from locale node matching the given identifier and context.
*/
simgear::PropertyList getLocalizedStrings(SGPropertyNode *localeNode, const char* id, const char* context);
/**
* Obtain user's default language setting.
*/
string_list getUserLanguages();
SGPropertyNode_ptr _intl;
SGPropertyNode_ptr _currentLocale;
SGPropertyNode_ptr _defaultLocale;
std::string _currentLocaleString;
/**
* Parse an XLIFF 1.2 file into the standard property structure underneath
* the provided locale node
*/
void parseXLIFF(SGPropertyNode* node);
private:
/** Return a new string with the character encoding part of the locale
* spec removed., i.e., "de_DE.UTF-8" becomes "de_DE". If there is no
* such part, return a copy of the input string.
*/
static std::string removeEncodingPart(const std::string& locale);
// this is the ordered list of languages to try. It's the same as
// returned by getUserLanguages(), except if the user has used
// --language to override, that will be the first item.
string_list _languages;
bool _inited = false;
};
// global translation wrappers
std::string fgTrMsg(const char* key);
std::string fgTrPrintfMsg(const char* key, ...);
#endif // __FGLOCALE_HXX

170
src/Main/logger.cxx Normal file
View File

@@ -0,0 +1,170 @@
// logger.cxx - log properties.
// Written by David Megginson, started 2002.
//
// This file is in the Public Domain, and comes with no warranty.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "logger.hxx"
#include <ios>
#include <string>
#include <cstdlib>
#include <simgear/debug/logstream.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include "fg_props.hxx"
#include "globals.hxx"
using std::string;
using std::endl;
////////////////////////////////////////////////////////////////////////
// Implementation of FGLogger
////////////////////////////////////////////////////////////////////////
void
FGLogger::init ()
{
SGPropertyNode * logging = fgGetNode("/logging");
if (logging == 0)
return;
std::vector<SGPropertyNode_ptr> children = logging->getChildren("log");
_logs.reserve(children.size());
for (const auto& child: children) {
if (!child->getBoolValue("enabled", false))
continue;
_logs.emplace_back(new Log());
Log &log = *_logs.back();
string filename = child->getStringValue("filename");
if (filename.empty()) {
filename = "fg_log.csv";
child->setStringValue("filename", filename.c_str());
}
// Security: the path comes from the global Property Tree; it *must* be
// validated before we overwrite the file.
const SGPath authorizedPath = SGPath::fromUtf8(filename).validate(
/* write */ true);
if (authorizedPath.isNull()) {
const string propertyPath = child->getChild("filename")
->getPath(/* simplify */ true);
const string msg =
"The FGLogger logging system, via the '" + propertyPath + "' property, "
"was asked to write to '" + filename + "', however this path is not "
"authorized for writing anymore for security reasons. " +
"Please choose another location, for instance in the $FG_HOME/Export "
"folder (" + (globals->get_fg_home() / "Export").utf8Str() + ").";
SG_LOG(SG_GENERAL, SG_ALERT, msg);
exit(EXIT_FAILURE);
}
string delimiter = child->getStringValue("delimiter");
if (delimiter.empty()) {
delimiter = ",";
child->setStringValue("delimiter", delimiter.c_str());
}
log.interval_ms = child->getLongValue("interval-ms");
log.last_time_ms = globals->get_sim_time_sec() * 1000;
log.delimiter = delimiter.c_str()[0];
// Security: use the return value of SGPath::validate()
log.output.reset(new sg_ofstream(authorizedPath, std::ios_base::out));
if ( !(*log.output) ) {
SG_LOG(SG_GENERAL, SG_ALERT, "Cannot write log to " << filename);
_logs.pop_back();
continue;
}
//
// Process the individual entries (Time is automatic).
//
std::vector<SGPropertyNode_ptr> entries = child->getChildren("entry");
(*log.output) << "Time";
for (unsigned int j = 0; j < entries.size(); j++) {
SGPropertyNode * entry = entries[j];
//
// Set up defaults.
//
if (!entry->hasValue("property")) {
entry->setBoolValue("enabled", false);
continue;
}
if (!entry->getBoolValue("enabled"))
continue;
SGPropertyNode * node =
fgGetNode(entry->getStringValue("property"), true);
log.nodes.push_back(node);
(*log.output) << log.delimiter
<< entry->getStringValue("title", node->getPath().c_str());
}
(*log.output) << endl;
}
}
void
FGLogger::reinit ()
{
_logs.clear();
init();
}
void
FGLogger::bind ()
{
}
void
FGLogger::unbind ()
{
}
void
FGLogger::update (double dt)
{
double sim_time_sec = globals->get_sim_time_sec();
double sim_time_ms = sim_time_sec * 1000;
for (unsigned int i = 0; i < _logs.size(); i++) {
while ((sim_time_ms - _logs[i]->last_time_ms) >= _logs[i]->interval_ms) {
_logs[i]->last_time_ms += _logs[i]->interval_ms;
(*_logs[i]->output) << sim_time_sec;
for (unsigned int j = 0; j < _logs[i]->nodes.size(); j++) {
(*_logs[i]->output) << _logs[i]->delimiter
<< _logs[i]->nodes[j]->getStringValue();
}
(*_logs[i]->output) << endl;
}
}
}
////////////////////////////////////////////////////////////////////////
// Implementation of FGLogger::Log
////////////////////////////////////////////////////////////////////////
FGLogger::Log::Log ()
: interval_ms(0),
last_time_ms(-999999.0),
delimiter(',')
{
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGLogger> registrantFGLogger;
// end of logger.cxx

50
src/Main/logger.hxx Normal file
View File

@@ -0,0 +1,50 @@
// logger.hxx - log properties.
// Written by David Megginson, started 2002.
//
// This file is in the Public Domain, and comes with no warranty.
#ifndef __LOGGER_HXX
#define __LOGGER_HXX 1
#include <memory>
#include <vector>
#include <simgear/compiler.h>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/props.hxx>
/**
* Log any property values to any number of CSV files.
*/
class FGLogger : public SGSubsystem
{
public:
// Subsystem API.
void bind() override;
void init() override;
void reinit() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "logger"; }
private:
/**
* A single instance of a log file (the logger can contain many).
*/
struct Log {
Log ();
std::vector<SGPropertyNode_ptr> nodes;
std::unique_ptr<sg_ofstream> output;
long interval_ms;
double last_time_ms;
char delimiter;
};
std::vector< std::unique_ptr<Log> > _logs;
};
#endif // __LOGGER_HXX

825
src/Main/main.cxx Executable file
View File

@@ -0,0 +1,825 @@
// main.cxx -- top level sim routines
//
// Written by Curtis Olson, started May 1997.
//
// Copyright (C) 1997 - 2002 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 <config.h>
#include <simgear/compiler.h>
#include <simgear/props/props_io.hxx>
#include <iostream>
#include <osg/Camera>
#include <osg/GraphicsContext>
#include <osgDB/Registry>
// Class references
#include <simgear/canvas/VGInitOperation.hxx>
#include <simgear/debug/logdelta.hxx>
#include <simgear/emesary/Emesary.hxx>
#include <simgear/emesary/notifications.hxx>
#include <simgear/io/raw_socket.hxx>
#include <simgear/math/SGMath.hxx>
#include <simgear/math/sg_random.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/nasal/NasalEmesaryInterface.hxx>
#include <simgear/props/AtomicChangeListener.hxx>
#include <simgear/props/props.hxx>
#include <simgear/scene/material/Effect.hxx>
#include <simgear/scene/material/matlib.hxx>
#include <simgear/scene/model/modellib.hxx>
#include <simgear/scene/tsync/terrasync.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/timing/sg_time.hxx>
#include <Add-ons/AddonManager.hxx>
#include <GUI/MessageBox.hxx>
#include <GUI/gui.h>
#include <Main/locale.hxx>
#include <Model/panelnode.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Scenery/scenery.hxx>
#include <Sound/soundmanager.hxx>
#include <Time/TimeManager.hxx>
#include <Viewer/CameraGroup.hxx>
#include <Viewer/GraphicsPresets.hxx>
#include <Viewer/WindowSystemAdapter.hxx>
#include <Viewer/renderer.hxx>
#include <Viewer/splash.hxx>
#include <flightgearBuildId.h>
#include "fg_commands.hxx"
#include "fg_init.hxx"
#include "fg_io.hxx"
#include "fg_os.hxx"
#include "fg_props.hxx"
#include "main.hxx"
#include "options.hxx"
#include "positioninit.hxx"
#include "screensaver_control.hxx"
#include "subsystemFactory.hxx"
#include "util.hxx"
#include <Main/ErrorReporter.hxx>
#include <Main/sentryIntegration.hxx>
#include <simgear/embedded_resources/EmbeddedResourceManager.hxx>
#include <EmbeddedResources/FlightGear-resources.hxx>
#if defined(HAVE_QT)
#include <GUI/QtLauncher.hxx>
#endif
#ifdef __OpenBSD__
#include <sys/resource.h>
#endif
using namespace flightgear;
using std::cerr;
using std::vector;
// The atexit() function handler should know when the graphical subsystem
// is initialized.
extern int _bootstrap_OSInit;
static SGPropertyNode_ptr frame_signal;
#ifdef NASAL_BACKGROUND_GC_THREAD
static SGPropertyNode_ptr nasal_gc_threaded;
static SGPropertyNode_ptr nasal_gc_threaded_wait;
static SGSharedPtr<simgear::Notifications::MainLoopNotification> mln_begin(new simgear::Notifications::MainLoopNotification(simgear::Notifications::MainLoopNotification::Type::Begin));
static SGSharedPtr<simgear::Notifications::MainLoopNotification> mln_end(new simgear::Notifications::MainLoopNotification(simgear::Notifications::MainLoopNotification::Type::End));
static SGSharedPtr<simgear::Notifications::MainLoopNotification> mln_started(new simgear::Notifications::MainLoopNotification(simgear::Notifications::MainLoopNotification::Type::Started));
static SGSharedPtr<simgear::Notifications::MainLoopNotification> mln_stopped(new simgear::Notifications::MainLoopNotification(simgear::Notifications::MainLoopNotification::Type::Stopped));
static SGSharedPtr<simgear::Notifications::NasalGarbageCollectionConfigurationNotification> ngccn;
#endif
// This method is usually called after OSG has finished rendering a frame in what OSG calls an idle handler and
// is reposonsible for invoking all of the relevant per frame processing; most of which is handled by subsystems.
static void fgMainLoop( void )
{
#ifdef NASAL_BACKGROUND_GC_THREAD
//
// the Nasal GC will automatically run when (during allocation) it discovers that more space is needed.
// This has a cost of between 5ms and 50ms (depending on the amount of currently active Nasal).
// The result is unscheduled and unpredictable pauses during normal operation when the garbage collector
// runs; which typically occurs at intervals between 1sec and 20sec.
//
// The solution to this, which overall increases CPU load, is to use a thread to do this; as Nasal is thread safe
// so what we do is to launch the garbage collection at the end of the main loop and then wait for completion at the start of the
// next main loop.
// So although the overall CPU is increased it has little effect on the frame rate; if anything it is an overall benefit
// as there are no unscheduled long duration frames.
//
// The implementation appears to work fine without waiting for completion at the start of the frame - so
// this wait at the start can be disabled by setting the property /sim/nasal-gc-threaded-wait to false.
// first we see if the config has changed. The notification will return true from SetActive/SetWait when the
// value has been changed - and thus we notify the Nasal system that it should configure itself accordingly.
auto use_threaded_gc = nasal_gc_threaded->getBoolValue();
auto threaded_wait = nasal_gc_threaded_wait->getBoolValue();
bool notify_gc_config = false;
notify_gc_config = ngccn->SetActive(use_threaded_gc);
notify_gc_config |= ngccn->SetWait(threaded_wait);
if (notify_gc_config)
simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(ngccn);
simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(mln_begin);
#endif
if (sglog().has_popup()) {
std::string s = sglog().get_popup();
flightgear::modalMessageBox("Alert", s, "");
}
frame_signal->fireValueChanged();
auto timeManager = globals->get_subsystem<TimeManager>();
// compute simulated time (allowing for pause, warp, etc) and
// real elapsed time
double sim_dt, real_dt;
timeManager->computeTimeDeltas(sim_dt, real_dt);
// update all subsystems
globals->get_subsystem_mgr()->update(sim_dt);
// flush commands waiting in the queue
SGCommandMgr::instance()->executedQueuedCommands();
simgear::AtomicChangeListener::fireChangeListeners();
#ifdef NASAL_BACKGROUND_GC_THREAD
simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(mln_end);
#endif
}
static void initTerrasync()
{
// add the terrasync root as a data path so data can be retrieved from it
// (even if we are in read-only mode)
SGPath terraSyncDir(globals->get_terrasync_dir());
globals->append_data_path(terraSyncDir, false /* = ahead of FG_ROOT */);
if (fgGetBool("/sim/fghome-readonly", false)) {
SG_LOG(SG_GENERAL, SG_ALERT, "initTerrasync() failing because /sim/fghome-readonly is true");
return;
}
// make fg-root dir available so existing Scenery data can be copied, and
// hence not downloaded again.
fgSetString("/sim/terrasync/installation-dir", (globals->get_fg_root() / "Scenery").utf8Str());
simgear::SGTerraSync* terra_sync = new simgear::SGTerraSync();
terra_sync->setRoot(globals->get_props());
globals->add_subsystem("terrasync", terra_sync, SGSubsystemMgr::GENERAL);
terra_sync->bind();
terra_sync->init();
if (fgGetBool("/sim/terrasync/enabled")) {
flightgear::addSentryTag("terrasync", "enabled");
}
}
static void fgSetVideoOptions()
{
SGPath userDataPath = globals->get_fg_home();
SGPath autosaveFile = globals->autosaveFilePath(userDataPath);
if (autosaveFile.exists()) return;
std::string vendor = fgGetString("/sim/rendering/gl-vendor");
SGPath path(globals->get_fg_root());
path.append("Video");
path.append(vendor);
if (path.exists())
{
std::string renderer = fgGetString("/sim/rendering/gl-renderer");
size_t pos = renderer.find("x86/");
if (pos == std::string::npos) {
pos = renderer.find('/');
}
if (pos == std::string::npos) {
pos = renderer.find(" (");
}
if (pos != std::string::npos) {
renderer = renderer.substr(0, pos);
}
path.append(renderer+".xml");
if (path.exists()) {
SG_LOG(SG_INPUT, SG_INFO, "Reading video settings from " << path);
try {
SGPropertyNode *r_prop = fgGetNode("/sim/rendering");
readProperties(path, r_prop);
} catch (sg_exception& e) {
SG_LOG(SG_INPUT, SG_WARN, "failed to read video settings:" << e.getMessage()
<< "(from " << e.getOrigin() << ")");
}
}
}
}
static void checkOpenGLVersion()
{
flightgear::addSentryTag("gl-version", fgGetString("/sim/rendering/gl-version"));
flightgear::addSentryTag("gl-renderer", fgGetString("/sim/rendering/gl-vendor"));
flightgear::addSentryTag("gl-vendor", fgGetString("/sim/rendering/gl-renderer"));
#if defined(SG_MAC)
// Mac users can't upgrade their drivers, so complaining about
// versions doesn't help them much
return;
#endif
// format of these strings is not standardised, so be careful about
// parsing them.
std::string versionString(fgGetString("/sim/rendering/gl-version"));
string_list parts = simgear::strutils::split(versionString);
if (parts.size() == 3) {
if (parts[1].find("NVIDIA") != std::string::npos) {
// driver version number, dot-seperared
string_list driverVersion = simgear::strutils::split(parts[2], ".");
if (!driverVersion.empty()) {
int majorDriverVersion = simgear::strutils::to_int(driverVersion[0]);
if (majorDriverVersion < 300) {
std::ostringstream ss;
ss << "Please upgrade to at least version 300 of the nVidia drivers (installed version is " << parts[2] << ")";
flightgear::modalMessageBox("Outdated graphics drivers",
"FlightGear has detected outdated drivers for your graphics card.",
ss.str());
}
}
} // of NVIDIA-style version string
} // of three parts
}
namespace flightgear {
void registerMainLoop()
{
// stash current frame signal property
frame_signal = fgGetNode("/sim/signals/frame", true);
#ifdef NASAL_BACKGROUND_GC_THREAD
nasal_gc_threaded = fgGetNode("/sim/nasal-gc-threaded", true);
nasal_gc_threaded_wait = fgGetNode("/sim/nasal-gc-threaded-wait", true);
#endif
// init the Emesary receiver for Nasal
nasal::initMainLoopRecipient();
fgRegisterIdleHandler( fgMainLoop );
}
void unregisterMainLoopProperties()
{
nasal::shutdownMainLoopRecipient();
frame_signal.reset();
#ifdef NASAL_BACKGROUND_GC_THREAD
nasal_gc_threaded.reset();
nasal_gc_threaded_wait.reset();
#endif
}
} // namespace flightgear
// This is the top level master main function that is registered as
// our idle function
// The first few passes take care of initialization things (a couple
// per pass) and once everything has been initialized fgMainLoop from
// then on.
static int idle_state = 0;
static void fgIdleFunction ( void ) {
// Specify our current idle function state. This is used to run all
// our initializations out of the idle callback so that we can get a
// splash screen up and running right away.
if ( idle_state == 0 ) {
auto camera = flightgear::getGUICamera(flightgear::CameraGroup::getDefault());
if (guiInit(camera->getGraphicsContext())) {
checkOpenGLVersion();
fgSetVideoOptions();
idle_state+=2;
fgSplashProgress("loading-aircraft-list");
fgSetBool("/sim/rendering/initialized", true);
}
} else if ( idle_state == 2 ) {
initTerrasync();
idle_state++;
fgSplashProgress("loading-nav-dat");
} else if ( idle_state == 3 ) {
bool done = fgInitNav();
if (done) {
++idle_state;
fgSplashProgress("init-scenery");
}
} else if ( idle_state == 4 ) {
idle_state++;
globals->add_new_subsystem<TimeManager>(SGSubsystemMgr::INIT);
// Do some quick general initializations
if( !fgInitGeneral()) {
throw sg_exception("General initialization failed");
}
// now we have commands up
flightgear::delayedSentryInit();
////////////////////////////////////////////////////////////////////
// Initialize the property-based built-in commands
////////////////////////////////////////////////////////////////////
fgInitCommands();
fgInitSceneCommands();
flightgear::registerSubsystemCommands(globals->get_commands());
////////////////////////////////////////////////////////////////////
// Initialize the material manager
////////////////////////////////////////////////////////////////////
globals->set_matlib( new SGMaterialLib );
simgear::SGModelLib::setPanelFunc(FGPanelNode::load);
} else if (( idle_state == 5 ) || (idle_state == 2005)) {
idle_state+=2;
flightgear::initPosition();
simgear::SGModelLib::init(globals->get_fg_root().utf8Str(), globals->get_props());
auto timeManager = globals->get_subsystem<TimeManager>();
timeManager->init();
////////////////////////////////////////////////////////////////////
// Initialize the TG scenery subsystem.
////////////////////////////////////////////////////////////////////
globals->add_new_subsystem<FGScenery>(SGSubsystemMgr::DISPLAY);
globals->get_scenery()->init();
globals->get_scenery()->bind();
fgSplashProgress("creating-subsystems");
} else if (( idle_state == 7 ) || (idle_state == 2007)) {
bool isReset = (idle_state == 2007);
idle_state = 8; // from the next state on, reset & startup are identical
SGTimeStamp st;
st.stamp();
try {
fgCreateSubsystems(isReset);
} catch (std::exception& e) {
// attempt to trace location of illegal argument / invalid string
// position errors on startup
flightgear::sentryReportException(string{"Creating subsystems: caught:"} + e.what());
throw;
}
SG_LOG(SG_GENERAL, SG_INFO, "Creating subsystems took:" << st.elapsedMSec());
fgSplashProgress("binding-subsystems");
} else if ( idle_state == 8 ) {
idle_state++;
SGTimeStamp st;
st.stamp();
globals->get_subsystem_mgr()->bind();
SG_LOG(SG_GENERAL, SG_INFO, "Binding subsystems took:" << st.elapsedMSec());
fgSplashProgress("init-subsystems");
} else if ( idle_state == 9 ) {
SGSubsystem::InitStatus status = globals->get_subsystem_mgr()->incrementalInit();
if ( status == SGSubsystem::INIT_DONE) {
++idle_state;
fgSplashProgress("finishing-subsystems");
} else {
fgSplashProgress("init-subsystems");
}
} else if ( idle_state == 10 ) {
idle_state = 900;
fgPostInitSubsystems();
fgSplashProgress("finalize-position");
} else if ( idle_state == 900 ) {
idle_state = 1000;
// setup OpenGL view parameters
globals->get_renderer()->setupView();
globals->get_renderer()->resize( fgGetInt("/sim/startup/xsize"),
fgGetInt("/sim/startup/ysize") );
WindowSystemAdapter::getWSA()->windows[0]->gc->add(
new simgear::canvas::VGInitOperation()
);
int session = fgGetInt("/sim/session",0);
session++;
fgSetInt("/sim/session",session);
}
if ( idle_state == 1000 ) {
sglog().setStartupLoggingEnabled(false);
// We've finished all our initialization steps, from now on we
// run the main loop.
fgSetBool("sim/sceneryloaded", false);
flightgear::registerMainLoop();
#ifdef NASAL_BACKGROUND_GC_THREAD
ngccn = new simgear::Notifications::NasalGarbageCollectionConfigurationNotification(nasal_gc_threaded->getBoolValue(), nasal_gc_threaded_wait->getBoolValue());
simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(ngccn);
simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(mln_started);
#endif
flightgear::addSentryBreadcrumb("entering main loop", "info");
}
if ( idle_state == 2000 ) {
flightgear::addSentryBreadcrumb("starting reset", "info");
fgStartNewReset();
idle_state = 2005;
}
}
void fgResetIdleState()
{
idle_state = 2000;
fgRegisterIdleHandler( &fgIdleFunction );
}
void fgInitSecureMode()
{
bool secureMode = true;
if (Options::sharedInstance()->isOptionSet("allow-nasal-from-sockets")) {
SG_LOG(SG_GENERAL, SG_MANDATORY_INFO, "\n!! Network connections allowed to use Nasal !!\n"
"Network connections will be allowed full access to the simulator \n"
"including running arbitrary scripts. Ensure you have adequate security\n"
"(such as a firewall which blocks external connections).\n");
secureMode = false;
}
// it's by design that we overwrite any existing property tree value
// here - this prevents an aircraft or add-on setting the property
// value underneath us, eg in their -set.xml
SGPropertyNode_ptr secureFlag = fgGetNode("/sim/secure-flag", true);
secureFlag->setBoolValue(secureMode);
secureFlag->setAttributes(SGPropertyNode::READ |
SGPropertyNode::PRESERVE |
SGPropertyNode::PROTECTED);
}
// this hack is needed to avoid weird viewport sizing within OSG on Windows.
// still required as of March 2017, sad times.
// see for example https://sourceforge.net/p/flightgear/codetickets/1958/
static void ATIScreenSizeHack()
{
osg::ref_ptr<osg::Camera> hackCam = new osg::Camera;
hackCam->setRenderOrder(osg::Camera::PRE_RENDER);
int prettyMuchAnyInt = 1;
hackCam->setViewport(0, 0, prettyMuchAnyInt, prettyMuchAnyInt);
globals->get_renderer()->addCamera(hackCam, false);
}
// Propose NVIDIA Optimus / AMD Xpress to use high-end GPU
#if defined(SG_WINDOWS)
extern "C" {
_declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
_declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
static void rotateOldLogFiles()
{
const int maxLogCount = 10;
const auto homePath = globals->get_fg_home();
for (int i = maxLogCount; i > 0; --i) {
const auto name = "fgfs_" + std::to_string(i - 1) + ".log";
SGPath curLogFile = homePath / name;
if (curLogFile.exists()) {
auto newName = "fgfs_" + std::to_string(i) + ".log";
curLogFile.rename(homePath / newName);
}
}
SGPath p = homePath / "fgfs.log";
if (!p.exists())
return;
SGPath log0Path = homePath / "fgfs_0.log";
if (!p.rename(log0Path)) {
std::cerr << "Failed to rename " << p.str() << " to " << log0Path.str() << std::endl;
}
}
static void logToHome(const std::string& pri)
{
sgDebugPriority fileLogLevel = SG_INFO;
// https://sourceforge.net/p/flightgear/codetickets/2100/
if (!pri.empty()) {
try {
fileLogLevel = std::min(fileLogLevel, logstream::priorityFromString(pri));
} catch (std::exception& ) {
// let's not worry about this, and just log at INFO
}
}
SGPath logPath = globals->get_fg_home();
logPath.append("fgfs.log");
if (logPath.exists()) {
rotateOldLogFiles();
}
sglog().logToFile(logPath, SG_ALL, fileLogLevel);
}
struct SGLogDeltasListener : SGPropertyChangeListener
{
void valueChanged(SGPropertyNode* node) override
{
std::string value = node->getStringValue();
std::cerr << __FILE__ << ":" << __LINE__ << ": sglogdeltas value=" << value << "\n";
logDeltaSet(value.c_str());
}
};
static SGLogDeltasListener s_sglogdeltas_listener;
// Main top level initialization
int fgMainInit( int argc, char **argv )
{
sglog().setLogLevels( SG_ALL, SG_WARN );
sglog().setStartupLoggingEnabled(true);
globals = new FGGlobals;
auto initHomeResult = fgInitHome();
if (initHomeResult == InitHomeAbort) {
flightgear::fatalMessageBoxThenExit("Unable to create lock file",
"Flightgear was unable to create the lock file in FG_HOME");
}
#if defined(HAVE_QT)
flightgear::initApp(argc, argv);
#endif
// check if the launcher is requested, since it affects config file parsing
bool showLauncher = flightgear::Options::checkForArg(argc, argv, "launcher");
// an Info.plist bundle can't define command line arguments, but it can set
// environment variables. This avoids needed a wrapper shell-script on OS-X.
showLauncher |= (::getenv("FG_LAUNCHER") != nullptr);
#if defined(HAVE_QT)
if (showLauncher && (initHomeResult == InitHomeReadOnly)) {
// show this message early, if we can
auto r = flightgear::showLockFileDialog();
if (r == flightgear::LockFileReset) {
SG_LOG( SG_GENERAL, SG_MANDATORY_INFO, "Deleting lock file at user request");
flightgear::addSentryBreadcrumb("deleting lock-file at user request", "info");
fgDeleteLockFile();
fgSetBool("/sim/fghome-readonly", false);
} else if (r == flightgear::LockFileQuit) {
return EXIT_SUCCESS;
}
}
#endif
{
SGPropertyNode* sglogdeltas = globals->get_props()->getNode("/sim/sg-log-deltas", true /*create*/);
assert(sglogdeltas);
sglogdeltas->addChangeListener(&s_sglogdeltas_listener, false /*initial*/);
const char* sglogdeltas_value = getenv("SG_LOG_DELTAS");
if (sglogdeltas_value) {
sglogdeltas->setStringValue(sglogdeltas_value);
}
}
globals->get_props()->getNode("/sim", true /*create*/)->setAttribute(SGPropertyNode::VALUE_CHANGED_DOWN, true);
{
SGPropertyNode* active = globals->get_props()->getNode("/sim/property-locking/active", true /*create*/);
SGPropertyNode* verbose = globals->get_props()->getNode("/sim/property-locking/verbose", true /*create*/);
SGPropertyNode* timing = globals->get_props()->getNode("/sim/property-locking/timing", true /*create*/);
SGPropertyNode* parent_listeners = globals->get_props()->getNode("/sim/property-locking/parent_listeners", true /*create*/);
SGPropertyLockControl(active, verbose, timing, parent_listeners);
}
const bool readOnlyFGHome = fgGetBool("/sim/fghome-readonly");
if (!readOnlyFGHome) {
// now home is initialised, we can log to a file inside it
const auto level = flightgear::Options::getArgValue(argc, argv, "--log-level");
logToHome(level);
}
if (readOnlyFGHome) {
flightgear::addSentryTag("fghome-readonly", "true");
}
std::string version(FLIGHTGEAR_VERSION);
SG_LOG( SG_GENERAL, SG_INFO, "FlightGear: Version " << version );
SG_LOG( SG_GENERAL, SG_INFO, "FlightGear: Build Type " << FG_BUILD_TYPE );
SG_LOG( SG_GENERAL, SG_INFO, "Built with " << SG_COMPILER_STR);
SG_LOG( SG_GENERAL, SG_INFO, "Jenkins number/ID " << JENKINS_BUILD_NUMBER << ":"
<< JENKINS_BUILD_ID);
flightgear::addSentryTag("osg-version", osgGetVersion());
#ifdef __OpenBSD__
{
/* OpenBSD defaults to a small maximum data segment, which can cause
flightgear to crash with SIGBUS, so output a warning if this is likely.
*/
struct rlimit rlimit;
int e = getrlimit(RLIMIT_DATA, &rlimit);
if (e) {
SG_LOG( SG_GENERAL, SG_INFO, "This is OpenBSD; getrlimit() failed: " << strerror(errno));
}
else {
unsigned long long required = 4ULL * (1ULL<<30);
if (rlimit.rlim_cur < required) {
SG_LOG( SG_GENERAL, SG_POPUP, ""
<< "Max data segment (" << rlimit.rlim_cur << "bytes) too small.\n"
<< "This can cause Flightgear to crash due to SIGBUS.\n"
<< "E.g. increase with 'ulimit -d " << required/1024 << "'."
);
}
}
}
#endif
// seed the random number generator
sg_srandom_time();
string_list *col = new string_list;
globals->set_channel_options_list( col );
if (showLauncher) {
// to minimise strange interactions when launcher and config files
// set overlaping options, we disable the default files. Users can
// still explicitly request config files via --config options if they choose.
flightgear::Options::sharedInstance()->setShouldLoadDefaultConfig(false);
}
// Load the configuration parameters. (Command line options
// override config file options. Config file options override
// defaults.)
int configResult = fgInitConfig(argc, argv, false);
if (configResult == flightgear::FG_OPTIONS_ERROR) {
return EXIT_FAILURE;
} else if (configResult == flightgear::FG_OPTIONS_EXIT) {
return EXIT_SUCCESS;
}
bool didUseLauncher = false; /* <didUseLauncher> is set but unused. */
#if defined(HAVE_QT)
if (showLauncher) {
flightgear::addSentryBreadcrumb("starting launcher", "info");
if (!flightgear::runLauncherDialog()) {
return EXIT_SUCCESS;
}
didUseLauncher = true;
flightgear::addSentryBreadcrumb("completed launcher", "info");
}
#else
if (showLauncher) {
SG_LOG(SG_GENERAL, SG_ALERT, "\n!Launcher requested, but FlightGear was compiled without Qt support!\n");
}
#endif
fgInitSecureMode();
fgInitAircraftPaths(false);
auto errorManager = globals->add_new_subsystem<flightgear::ErrorReporter>(SGSubsystemMgr::GENERAL);
errorManager->preinit();
configResult = fgInitAircraft(false, didUseLauncher);
if (configResult == flightgear::FG_OPTIONS_ERROR) {
return EXIT_FAILURE;
} else if ((configResult == flightgear::FG_OPTIONS_EXIT) ||
(configResult == flightgear::FG_OPTIONS_SHOW_AIRCRAFT))
{
return EXIT_SUCCESS;
}
addons::AddonManager::createInstance();
configResult = flightgear::Options::sharedInstance()->processOptions();
if (configResult == flightgear::FG_OPTIONS_ERROR) {
return EXIT_FAILURE;
} else if (configResult == flightgear::FG_OPTIONS_EXIT) {
return EXIT_SUCCESS;
}
// Set the lists of allowed paths for cases where a path comes from an
// untrusted source, such as the global property tree (this uses $FG_HOME
// and other paths set by Options::processOptions()).
fgInitAllowedPaths();
const auto& resMgr = simgear::EmbeddedResourceManager::createInstance();
initFlightGearEmbeddedResources();
// The language was set in processOptions()
const std::string locale = globals->get_locale()->getPreferredLanguage();
// Must always be done after all resources have been added to 'resMgr'
resMgr->selectLocale(locale);
SG_LOG(SG_GENERAL, SG_INFO,
"EmbeddedResourceManager: selected locale '" << locale << "'");
if (fgGetBool("/sim/autosave-migration/did-migrate", false)) {
// inform the user we did migration. This is the earliest point
// we can do it, since now the locale is set
auto locale = globals->get_locale();
const auto title = locale->getLocalizedString("settings-migration-title", "sys", "Settings migrated");
const auto msg = locale->getLocalizedString("settings-migration-text", "sys",
"Saved settings were migrated from a previous version of FlightGear. "
"If you encounter any problems when using the system, try restoring "
"the default settings, before reporting a problem. "
"Saved settings can affect the appearance, performance and features of the simulator.");
flightgear::modalMessageBox(title, msg);
}
// Copy the property nodes for the menus added by registered add-ons
addons::AddonManager::instance()->addAddonMenusToFGMenubar();
auto presets = globals->add_new_subsystem<flightgear::GraphicsPresets>(SGSubsystemMgr::DISPLAY);
presets->applyInitialPreset();
// Initialize the Window/Graphics environment.
fgOSInit(&argc, argv);
_bootstrap_OSInit++;
fgRegisterIdleHandler( &fgIdleFunction );
// Initialize sockets (WinSock needs this)
simgear::Socket::initSockets();
// Clouds3D requires an alpha channel
fgOSOpenWindow(true /* request stencil buffer */);
fgOSResetProperties();
fntInit();
globals->get_renderer()->preinit();
if (fgGetBool("/sim/ati-viewport-hack", true)) {
SG_LOG(SG_GENERAL, SG_WARN, "Enabling ATI/AMD viewport hack");
flightgear::addSentryTag("ati-viewport-hack", "enabled");
ATIScreenSizeHack();
}
fgOutputSettings();
//try to disable the screensaver
fgOSDisableScreensaver();
// pass control off to the master event handler
int result = fgOSMainLoop();
flightgear::unregisterMainLoopProperties();
fgOSCloseWindow();
fgShutdownHome();
const bool requestLauncherRestart = fgGetBool("/sim/restart-launcher-on-exit");
#ifdef NASAL_BACKGROUND_GC_THREAD
simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(mln_stopped);
#endif
simgear::clearEffectCache();
// clean up here; ensure we null globals to avoid
// confusing the atexit() handler
delete globals;
globals = nullptr;
// delete the NavCache here. This will cause the destruction of many cached
// objects (eg, airports, navaids, runways).
delete flightgear::NavDataCache::instance();
#if defined(HAVE_QT)
if (requestLauncherRestart) {
string_list originalArgs;
for (int arg = 1; arg < argc; ++arg) {
originalArgs.push_back(argv[arg]);
}
flightgear::addSentryBreadcrumb("Requested to restart launcher", "info");
flightgear::startLaunchOnExit(originalArgs);
}
#endif
return result;
}

37
src/Main/main.hxx Normal file
View File

@@ -0,0 +1,37 @@
// main.hxx -- top level sim routines
//
// Written by Curtis Olson, started May 1997.
//
// Copyright (C) 1997 - 2002 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 __FG_MAIN_HXX
#define __FG_MAIN_HXX 1
int fgMainInit( int argc, char **argv );
void fgResetIdleState();
extern std::string hostname;
namespace flightgear {
void registerMainLoop();
void unregisterMainLoopProperties();
} // namespace flightgear
#endif

409
src/Main/metar_main.cxx Normal file
View File

@@ -0,0 +1,409 @@
// metar interface class demo
//
// Written by Melchior FRANZ, started December 2003.
//
// Copyright (C) 2003 Melchior FRANZ - mfranz@aon.at
//
// 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 <iomanip>
#include <sstream>
#include <iostream>
#include <string.h>
#include <time.h>
#include <cstdlib>
#include <cstdio>
#include <simgear/environment/metar.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/io/HTTPClient.hxx>
#include <simgear/io/HTTPMemoryRequest.hxx>
#include <simgear/io/raw_socket.hxx>
#include <simgear/timing/timestamp.hxx>
#include <simgear/misc/strutils.hxx>
using namespace std;
using namespace simgear;
// text color
#if defined(__linux__) || defined(__sun) || defined(__CYGWIN__) || defined( __FreeBSD__ ) || defined( __OpenBSD__ ) || defined ( sgi )
# define R "\033[31;1m" // red
# define G "\033[32;1m" // green
# define Y "\033[33;1m" // yellow
# define B "\033[34;1m" // blue
# define M "\033[35;1m" // magenta
# define C "\033[36;1m" // cyan
# define W "\033[37;1m" // white
# define N "\033[m" // normal
#else
# define R ""
# define G ""
# define Y ""
# define B ""
# define M ""
# define C ""
# define W ""
# define N ""
#endif
const char *azimuthName(double d)
{
const char *dir[] = {
"N", "NNE", "NE", "ENE",
"E", "ESE", "SE", "SSE",
"S", "SSW", "SW", "WSW",
"W", "WNW", "NW", "NNW"
};
d += 11.25;
while (d < 0)
d += 360;
while (d >= 360)
d -= 360;
return dir[int(d / 22.5)];
}
// round double to 10^g
double rnd(double r, int g = 0)
{
double f = pow(10.0, g);
return f * floor(r / f + 0.5);
}
ostream& operator<<(ostream& s, const SGMetarVisibility& v)
{
ostringstream buf;
int m = v.getModifier();
const char *mod;
if (m == SGMetarVisibility::GREATER_THAN)
mod = ">=";
else if (m == SGMetarVisibility::LESS_THAN)
mod = "<";
else
mod = "";
buf << mod;
double dist = rnd(v.getVisibility_m(), 1);
if (dist < 1000.0)
buf << rnd(dist, 1) << " m";
else
buf << rnd(dist / 1000.0, -1) << " km";
const char *dir = "";
int i;
if ((i = v.getDirection()) != -1) {
dir = azimuthName(i);
buf << " " << dir;
}
buf << "\t\t\t\t\t" << mod << rnd(v.getVisibility_sm(), -1) << " US-miles " << dir;
return s << buf.str();
}
void printReport(SGMetar *m)
{
cout << m->getDescription(0);
}
void printArgs(SGMetar *m, double airport_elevation)
{
#define NaN SGMetarNaN
vector<string> args;
char buf[256];
int i;
// ICAO id
sprintf(buf, "--airport=%s ", m->getId());
args.push_back(buf);
// report time
sprintf(buf, "--start-date-gmt=%4d:%02d:%02d:%02d:%02d:00 ",
m->getYear(), m->getMonth(), m->getDay(),
m->getHour(), m->getMinute());
args.push_back(buf);
// cloud layers
const char *coverage_string[5] = {
"clear", "few", "scattered", "broken", "overcast"
};
vector<SGMetarCloud> cv = m->getClouds();
vector<SGMetarCloud>::iterator cloud;
for (i = 0, cloud = cv.begin(); i < 5; i++) {
int coverage = 0;
double altitude = -99999;
if (cloud != cv.end()) {
coverage = cloud->getCoverage();
altitude = coverage ? cloud->getAltitude_ft() + airport_elevation : -99999;
cloud++;
}
sprintf(buf, "--prop:/environment/clouds/layer[%d]/coverage=%s ", i, coverage_string[coverage]);
args.push_back(buf);
sprintf(buf, "--prop:/environment/clouds/layer[%d]/elevation-ft=%.0lf ", i, altitude);
args.push_back(buf);
sprintf(buf, "--prop:/environment/clouds/layer[%d]/thickness-ft=500 ", i);
args.push_back(buf);
}
// environment (temperature, dewpoint, visibility, pressure)
// metar sets don't provide aloft information; we have to
// set the same values for all boundary levels
int wind_dir = m->getWindDir();
double visibility = m->getMinVisibility().getVisibility_m();
double dewpoint = m->getDewpoint_C();
double temperature = m->getTemperature_C();
double pressure = m->getPressure_inHg();
double wind_speed = m->getWindSpeed_kt();
double elevation = -100;
for (i = 0; i < 3; i++, elevation += 2000.0) {
sprintf(buf, "--prop:/environment/config/boundary/entry[%d]/", i);
int pos = strlen(buf);
sprintf(&buf[pos], "elevation-ft=%.0lf", elevation);
args.push_back(buf);
sprintf(&buf[pos], "turbulence-norm=%.0lf", 0.0);
args.push_back(buf);
if (visibility != NaN) {
sprintf(&buf[pos], "visibility-m=%.0lf", visibility);
args.push_back(buf);
}
if (temperature != NaN) {
sprintf(&buf[pos], "temperature-degc=%.0lf", temperature);
args.push_back(buf);
}
if (dewpoint != NaN) {
sprintf(&buf[pos], "dewpoint-degc=%.0lf", dewpoint);
args.push_back(buf);
}
if (pressure != NaN) {
sprintf(&buf[pos], "pressure-sea-level-inhg=%.0lf", pressure);
args.push_back(buf);
}
if (wind_dir != NaN) {
sprintf(&buf[pos], "wind-from-heading-deg=%d", wind_dir);
args.push_back(buf);
}
if (wind_speed != NaN) {
sprintf(&buf[pos], "wind-speed-kt=%.0lf", wind_speed);
args.push_back(buf);
}
}
// wind dir@speed
int range_from = m->getWindRangeFrom();
int range_to = m->getWindRangeTo();
double gust_speed = m->getGustSpeed_kt();
if (wind_speed != NaN && wind_dir != -1) {
strcpy(buf, "--wind=");
if (range_from != -1 && range_to != -1)
sprintf(&buf[strlen(buf)], "%d:%d", range_from, range_to);
else
sprintf(&buf[strlen(buf)], "%d", wind_dir);
sprintf(&buf[strlen(buf)], "@%.0lf", wind_speed);
if (gust_speed != NaN)
sprintf(&buf[strlen(buf)], ":%.0lf", gust_speed);
args.push_back(buf);
}
// output everything
//cout << "fgfs" << endl;
vector<string>::iterator arg;
for (i = 0, arg = args.begin(); arg != args.end(); i++, arg++) {
cout << "\t" << *arg << endl;
}
cout << endl;
#undef NaN
}
void getproxy(string& host, string& port)
{
host = "";
port = "80";
const char *p = getenv("http_proxy");
if (!p)
return;
while (isspace(*p))
p++;
if (!strncmp(p, "http://", 7))
p += 7;
if (!*p)
return;
char s[256], *t;
strncpy(s, p, 255);
s[255] = '\0';
for (t = s + strlen(s); t > s; t--)
if (!isspace(t[-1]) && t[-1] != '/')
break;
*t = '\0';
t = strchr(s, ':');
if (t) {
*t++ = '\0';
port = t;
}
host = s;
}
void usage()
{
printf(
"Usage: metar [-v] [-e elevation] [-r|-c] <list of ICAO airport ids or METAR strings>\n"
" metar -h\n"
"\n"
" -h|--help show this help\n"
" -v|--verbose verbose output\n"
" -r|--report print report (default)\n"
" -c|--command-line print command line\n"
" -e E|--elevation E set airport elevation to E meters\n"
" (added to cloud bases in command line mode)\n"
" -s|--string <METAR string>\n"
"Environment:\n"
" http_proxy set proxy in the form \"http://host:port/\"\n"
"\n"
"Examples:\n"
" $ metar ksfo koak\n"
" $ metar -c ksfo -r ksfo\n"
" $ metar -s \"LOWL 161500Z 19004KT 160V240 9999 FEW035 SCT300 29/23 Q1006 NOSIG\"\n"
" $ fgfs `metar -e 183 -c loww`\n"
" $ http_proxy=http://localhost:3128/ metar ksfo\n"
"\n"
);
}
int main(int argc, char *argv[])
{
bool report = true;
bool verbose = false;
double elevation = 0.0;
if (argc <= 1) {
usage();
return 0;
}
string proxy_host, proxy_port;
getproxy(proxy_host, proxy_port);
Socket::initSockets();
HTTP::Client http;
http.setProxy(proxy_host, atoi(proxy_port.c_str()));
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help"))
usage();
else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
verbose = true;
else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--report"))
report = true;
else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--command-line"))
report = false;
else if (!strcmp(argv[i], "-e") || !strcmp(argv[i], "--elevation")) {
if (++i >= argc) {
cerr << "-e option used without elevation" << endl;
return 1;
}
elevation = strtod(argv[i], 0);
}
else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--string")) {
if (++i >= argc) {
cerr << "-s option used with out string\n";
return 1;
}
const char* string = argv[i];
SGMetar metar(string);
printReport(&metar);
}
else {
static bool shown = false;
if (verbose && !shown) {
cerr << "Proxy host: '" << proxy_host << "'" << endl;
cerr << "Proxy port: '" << proxy_port << "'" << endl << endl;
shown = true;
}
try
{
static const std::string NOAA_BASE_URL =
"https://tgftp.nws.noaa.gov/data/observations/metar/stations/";
HTTP::MemoryRequest* mr = new HTTP::MemoryRequest
(
NOAA_BASE_URL + strutils::uppercase(argv[i]) + ".TXT"
);
HTTP::Request_ptr own(mr);
http.makeRequest(mr);
// spin until the request completes, fails or times out
SGTimeStamp start(SGTimeStamp::now());
while (start.elapsedMSec() < 8000) {
http.update();
if( mr->isComplete() )
break;
SGTimeStamp::sleepForMSec(1);
}
if( !mr->isComplete() )
throw sg_io_exception("metar download timed out");
if( mr->responseCode() != 200 )
{
std::cerr << "metar download failed: "
<< mr->url()
<< " (" << mr->responseCode()
<< " " << mr->responseReason() << ")"
<< std::endl;
throw sg_io_exception("metar download failed");
}
SGMetar *m = new SGMetar(mr->responseBody());
//SGMetar *m = new SGMetar("2004/01/11 01:20\nLOWG 110120Z AUTO VRB01KT 0050 1600N R35/0600 FG M06/M06 Q1019 88//////\n");
if (verbose) {
cerr << G "INPUT: " << m->getDataString() << "" N << endl;
const auto unused = m->getUnparsedData();
if (!unused.empty())
cerr << R "UNUSED: " << unused << "" N << endl;
}
if (report)
printReport(m);
else
printArgs(m, elevation);
delete m;
} catch (const sg_io_exception& e) {
cerr << R "ERROR: " << e.getFormattedMessage().c_str() << "" N << endl << endl;
}
}
}
return 0;
}

3303
src/Main/options.cxx Normal file

File diff suppressed because it is too large Load Diff

216
src/Main/options.hxx Normal file
View File

@@ -0,0 +1,216 @@
// options.hxx -- class to handle command line options
//
// Written by Curtis Olson, started April 1998.
//
// Copyright (C) 1998 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 _OPTIONS_HXX
#define _OPTIONS_HXX
#include <memory>
#include <string>
#include <simgear/misc/strutils.hxx>
// forward decls
class SGPath;
namespace flightgear
{
/**
* return the default platform dependant download directory.
* This must be a user-writeable location, the question is if it should
* be a user visible location. On Windows we default to a subdir of
* Documents (FlightGear), on Unixes we default to FG_HOME, which is
* typically invisible.
*/
SGPath defaultDownloadDir();
/// option processing can have various result values
/// depending on what the user requested. Note processOptions only
/// returns a subset of these.
enum OptionResult
{
FG_OPTIONS_OK = 0,
FG_OPTIONS_HELP,
FG_OPTIONS_ERROR,
FG_OPTIONS_EXIT,
FG_OPTIONS_VERBOSE_HELP,
FG_OPTIONS_SHOW_AIRCRAFT,
FG_OPTIONS_SHOW_SOUND_DEVICES,
FG_OPTIONS_NO_DEFAULT_CONFIG
};
class Options
{
private:
Options();
public:
static Options* sharedInstance();
/**
* Delete the entire options object. Use with a degree of care, no code
* should ever be caching the Options pointer but this has not actually been
* checked across the whole code :)
*/
static void reset();
~Options();
/**
* pass command line arguments, read default config files
*/
OptionResult init(int argc, char* argv[], const SGPath& appDataPath);
/**
* parse a config file (eg, .fgfsrc)
*/
void readConfig(const SGPath& path);
/**
* read the value for an option, if it has been set
*/
std::string valueForOption(const std::string& key, const std::string& defValue = std::string()) const;
/**
* return all values for a multi-valued option
*/
string_list valuesForOption(const std::string& key) const;
/**
* check if a particular option has been set (so far)
*/
bool isOptionSet(const std::string& key) const;
/**
* set an option value, assuming it is not already set (or multiple values
* are permitted)
* This can be used to inject option values, eg based upon environment variables
*/
int addOption(const std::string& key, const std::string& value);
/**
* set an option, overwriting any existing value which might be set
*/
int setOption(const std::string& key, const std::string& value);
void clearOption(const std::string& key);
/**
* apply option values to the simulation state
* (set properties, etc).
*/
OptionResult processOptions();
/**
* process command line options relating to scenery / aircraft / data paths
*/
void initPaths();
/**
* init the aircraft options
*/
OptionResult initAircraft();
/**
* should defualt configuration files be loaded and processed or not?
* There's many configuration files we have historically read by default
* on startup - fgfs.rc in various places and so on.
* --no-default-config allows this behaviour to be changed, so only
* expicitly listed files are read Expose
* the value of the option here.
*/
bool shouldLoadDefaultConfig() const;
/**
* when using the built-in launcher, we disable the default config files.
* explicitly loaded confg files are still permitted.
*/
void setShouldLoadDefaultConfig(bool load);
/**
* check if the arguments array contains a particular string (with a '--' or
* '-' prefix).
* Used by early startup code before Options object is created
*/
static bool checkForArg(int argc, char* argv[], const char* arg);
/**
* @brief getArgValue - get the value of an argument if it exists, or
* an empty string otherwise
* @param argc
* @param argv
* @param checkArg : arg to look for, with '--' prefix
* @return value following '=' until the next white space
*/
static std::string getArgValue(int argc, char* argv[], const char* checkArg);
SGPath platformDefaultRoot() const;
/**
* @brief extractOptions - extract the currently set options as
* a string array. This can be used to examine what options were
* requested / set so far.
* @return
*/
string_list extractOptions() const;
/**
@brief the actual download dir in use, which may be the default or a user-supplied value
*/
SGPath actualDownloadDir();
private:
void showUsage() const;
void showVersion() const;
// Write info such as FG version, FG_ROOT, FG_HOME, scenery paths, aircraft
// paths, etc. to stdout in JSON format, using the UTF-8 encoding.
void printJSONReport() const;
// The 'fromConfigFile' parameter indicates whether the option comes from a
// config file or directly from the command line.
int parseOption(const std::string& s, bool fromConfigFile);
void processArgResult(int result);
/**
* Setup the root base, and check it's valid. If
* the root package was not found or is the incorrect version,
* returns FG_OPTIONS_ERROR. Argv/argv
* are passed since we might potentially show a GUI dialog at this point
* to help the user our (finding a base package), and hence need to init Qt.
*/
OptionResult setupRoot(int argc, char** argv);
class OptionsPrivate;
std::unique_ptr<OptionsPrivate> p;
};
} // of namespace flightgear
void fgSetDefaults();
#endif /* _OPTIONS_HXX */

872
src/Main/positioninit.cxx Normal file
View File

@@ -0,0 +1,872 @@
// positioninit.cxx - helpers relating to setting initial aircraft position
//
// Copyright (C) 2012 James Turner zakalawe@mac.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "config.h"
#include "positioninit.hxx"
#include <osgViewer/Viewer>
#include <osg/PagedLOD>
// simgear
#include <simgear/misc/strutils.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/structure/event_mgr.hxx>
#include <simgear/scene/model/CheckSceneryVisitor.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include "globals.hxx"
#include "fg_props.hxx"
#include "fg_io.hxx"
#include <Navaids/navlist.hxx>
#include <Airports/runways.hxx>
#include <Airports/airport.hxx>
#include <Airports/dynamics.hxx>
#include <Airports/groundnetwork.hxx>
#include <AIModel/AIManager.hxx>
#include <AIModel/AICarrier.hxx>
#include <AIModel/AIAircraft.hxx>
#include <AIModel/AIFlightPlan.hxx>
#include <Scenery/scenery.hxx>
#include <GUI/MessageBox.hxx>
#include <Viewer/renderer.hxx>
using std::endl;
using std::string;
namespace flightgear
{
enum InitPosResult {
ExactPosition,
VicinityPosition,
ContinueWaiting,
Failure
};
/// to avoid blocking when metar-fetch is enabled, but the network is
/// unresponsive, we need a timeout value. This value is reset on initPosition,
/// and tracked through each call to finalizePosition.
static SGTimeStamp global_finalizeTime;
static bool global_callbackRegistered = false;
void finalizePosition();
static void setInitialPosition(const SGGeod& aPos, double aHeadingDeg)
{
// presets
fgSetDouble("/sim/presets/longitude-deg", aPos.getLongitudeDeg() );
fgSetDouble("/sim/presets/latitude-deg", aPos.getLatitudeDeg() );
fgSetDouble("/sim/presets/heading-deg", aHeadingDeg );
// other code depends on the actual values being set ...
fgSetDouble("/position/longitude-deg", aPos.getLongitudeDeg() );
fgSetDouble("/position/latitude-deg", aPos.getLatitudeDeg() );
fgSetDouble("/orientation/heading-deg", aHeadingDeg );
}
static void fgApplyStartOffset(const SGGeod& aStartPos, double aHeading, double aTargetHeading = HUGE_VAL)
{
SGGeod startPos(aStartPos);
if (aTargetHeading == HUGE_VAL) {
aTargetHeading = aHeading;
}
if ( fabs( fgGetDouble("/sim/presets/offset-distance-nm") ) > SG_EPSILON ) {
double offsetDistance = fgGetDouble("/sim/presets/offset-distance-nm");
offsetDistance *= SG_NM_TO_METER;
double offsetAzimuth = aHeading;
if ( fabs(fgGetDouble("/sim/presets/offset-azimuth-deg")) > SG_EPSILON ) {
offsetAzimuth = fgGetDouble("/sim/presets/offset-azimuth-deg");
aHeading = aTargetHeading;
}
startPos = SGGeodesy::direct(startPos, offsetAzimuth + 180, offsetDistance);
}
setInitialPosition(startPos, aHeading);
}
std::tuple<SGGeod, double> runwayStartPos(FGRunwayRef runway)
{
fgSetString("/sim/atc/runway", runway->ident().c_str());
double offsetNm = fgGetDouble("/sim/presets/offset-distance-nm");
double startOffset = fgGetDouble("/sim/airport/runways/start-offset-m", 5.0);
SGGeod pos = runway->pointOnCenterline(startOffset);
const bool overrideHoldShort = fgGetBool("/sim/presets/mp-hold-short-override", false);
if (!overrideHoldShort && FGIO::isMultiplayerRequested() && (fabs(offsetNm) <0.1)) {
SG_LOG( SG_GENERAL, SG_MANDATORY_INFO, "Requested to start on " << runway->airport()->ident() << "/" <<
runway->ident() << ", MP is enabled so computing hold short position to avoid runway incursion");
FGGroundNetwork* groundNet = runway->airport()->groundNetwork();
if (groundNet) {
// add a margin, try to keep the entire aeroplane comfortable off the
// runway.
double margin = startOffset + (runway->widthM() * 1.5);
FGTaxiNodeRef taxiNode = groundNet->findNearestNodeOffRunway(pos, runway, margin);
if (taxiNode) {
// set this so multiplayer.nas can inform the user
fgSetBool("/sim/presets/avoided-mp-runway", true);
return std::make_tuple(taxiNode->geod(), SGGeodesy::courseDeg(taxiNode->geod(), pos));
}
else {
// if we couldn't find a suitable taxi-node, give up. Guessing a position
// causes too much pain (starting in the water or similar bad things)
SG_LOG( SG_GENERAL, SG_ALERT, "Unable to position off runway because groundnet has no taxi node.");
}
}
else {
SG_LOG( SG_GENERAL, SG_ALERT, "Unable to position off runway because no groundnet.");
}
}
return std::make_tuple(pos, runway->headingDeg());
}
// Set current_options lon/lat given an airport id and heading (degrees)
static bool setPosFromAirportIDandHdg( const string& id, double tgt_hdg ) {
if ( id.empty() )
return false;
// set initial position from runway and heading
SG_LOG( SG_GENERAL, SG_INFO,
"Attempting to set starting position from airport code "
<< id << " heading " << tgt_hdg );
const FGAirport* apt = fgFindAirportID(id);
if (!apt) return false;
SGGeod startPos;
double heading = tgt_hdg;
if (apt->type() == FGPositioned::HELIPORT) {
if (apt->numHelipads() > 0) {
startPos = apt->getHelipadByIndex(0)->geod();
} else {
startPos = apt->geod();
}
} else {
FGRunway* r = apt->findBestRunwayForHeading(tgt_hdg);
std::tie(startPos, heading) = runwayStartPos(r);
}
fgApplyStartOffset(startPos, heading);
return true;
}
static bool airportParkingSetVicinity(const string& id)
{
const FGAirport* apt = fgFindAirportID(id);
if (!apt) {
SG_LOG( SG_GENERAL, SG_ALERT, "Failed to find airport " << id );
return false;
}
setInitialPosition(apt->geod(), 0.0);
return true;
}
// Set current_options lon/lat given an airport id and parkig position name
static bool finalizePositionForParkpos( const string& id, const string& parkpos )
{
auto aiManager = globals->get_subsystem<FGAIManager>();
if (!aiManager || !aiManager->getUserAircraft()) {
SG_LOG( SG_GENERAL, SG_ALERT, "finalizePositionForParkpos: >> failed to find AI manager / user aircraft");
return false;
}
auto userAIFP = aiManager->getUserAircraft()->GetFlightPlan();
if (!userAIFP) {
SG_LOG( SG_GENERAL, SG_ALERT, "finalizePositionForParkpos: >> failed to find user aircraft AI flight-plan");
return false;
}
auto pkr = userAIFP->getParkingGate();
if (!pkr) {
SG_LOG( SG_GENERAL, SG_ALERT,
"Failed to find a parking at airport " << id << ":" << parkpos);
return false;
}
fgSetString("/sim/presets/parkpos", pkr->getName());
fgApplyStartOffset(pkr->geod(), pkr->getHeading());
return true;
}
// Set current_options lon/lat given an airport id and runway number
static bool fgSetPosFromAirportIDandRwy( const string& id, const string& rwy, bool rwy_req ) {
if ( id.empty() )
return false;
// set initial position from airport and runway number
SG_LOG( SG_GENERAL, SG_INFO,
"Attempting to set starting position for "
<< id << ":" << rwy );
const FGAirport* apt = fgFindAirportID(id);
if (!apt) {
SG_LOG( SG_GENERAL, SG_ALERT, "Failed to find airport:" << id);
return false;
}
if (apt->hasRunwayWithIdent(rwy)) {
FGRunway* r(apt->getRunwayByIdent(rwy));
SGGeod startPos;
double heading;
std::tie(startPos, heading) = runwayStartPos(r);
fgApplyStartOffset(startPos, heading);
return true;
} else if (apt->hasHelipadWithIdent(rwy)) {
FGHelipad* h(apt->getHelipadByIdent(rwy));
fgApplyStartOffset(h->geod(), h->headingDeg());
return true;
}
if (rwy_req) {
flightgear::modalMessageBox("Runway not available", "Runway/helipad "
+ rwy + " not found at airport " + apt->getId()
+ " - " + apt->getName() );
} else {
SG_LOG( SG_GENERAL, SG_INFO,
"Failed to find runway/helipad " << rwy <<
" at airport " << id << ". Using default runway." );
}
return false;
}
static void fgSetDistOrAltFromGlideSlope()
{
string apt_id = fgGetString("/sim/presets/airport-id");
double gs = SGMiscd::deg2rad(fgGetDouble("/sim/presets/glideslope-deg"));
double od = fgGetDouble("/sim/presets/offset-distance-nm");
double alt = fgGetDouble("/sim/presets/altitude-ft");
double apt_elev = 0.0;
if ( ! apt_id.empty() ) {
apt_elev = fgGetAirportElev( apt_id );
if ( apt_elev < -9990.0 ) {
apt_elev = 0.0;
}
} else {
apt_elev = 0.0;
}
if( fabs(gs) > 0.01 && fabs(od) > 0.1 && alt < -9990 ) {
// set altitude from glideslope and offset-distance
od *= SG_NM_TO_METER * SG_METER_TO_FEET;
alt = fabs(od*tan(gs)) + apt_elev;
fgSetDouble("/sim/presets/altitude-ft", alt);
fgSetBool("/sim/presets/onground", false);
SG_LOG( SG_GENERAL, SG_INFO, "Calculated altitude as: "
<< alt << " ft" );
} else if( fabs(gs) > 0.01 && alt > 0 && fabs(od) < 0.1) {
// set offset-distance from glideslope and altitude
od = (alt - apt_elev) / tan(gs);
od *= -1*SG_FEET_TO_METER * SG_METER_TO_NM;
fgSetDouble("/sim/presets/offset-distance-nm", od);
fgSetBool("/sim/presets/onground", false);
SG_LOG( SG_GENERAL, SG_INFO, "Calculated offset distance as: "
<< od << " nm" );
} else if( fabs(gs) > 0.01 ) {
SG_LOG( SG_GENERAL, SG_ALERT,
"Glideslope given but not altitude or offset-distance." );
SG_LOG( SG_GENERAL, SG_ALERT, "Resetting glideslope to zero" );
fgSetDouble("/sim/presets/glideslope-deg", 0);
fgSetBool("/sim/presets/onground", true);
}
}
// Set current_options lon/lat given a Nav ID or GUID
static bool fgSetPosFromNAV( const string& id,
const double& freq,
FGPositioned::Type type,
PositionedID guid)
{
FGNavRecordRef nav;
if (guid != 0) {
nav = FGPositioned::loadById<FGNavRecord>(guid);
if (!nav)
return false;
} else {
FGNavList::TypeFilter filter(type);
const nav_list_type navlist = FGNavList::findByIdentAndFreq( id.c_str(), freq, &filter );
if (navlist.empty()) {
SG_LOG( SG_GENERAL, SG_ALERT, "Failed to locate NAV = "
<< id << ":" << freq );
return false;
}
if( navlist.size() > 1 ) {
std::ostringstream buf;
buf << "Ambigous NAV-ID: '" << id << "'. Specify id and frequency. Available stations:" << endl;
for( const auto& nav : navlist ) {
// NDB stored in kHz, VOR stored in MHz * 100 :-P
double factor = nav->type() == FGPositioned::NDB ? 1.0 : 1/100.0;
string unit = nav->type() == FGPositioned::NDB ? "kHz" : "MHz";
buf << nav->ident() << " "
<< std::setprecision(5) << static_cast<double>(nav->get_freq() * factor) << " "
<< nav->get_lat() << "/" << nav->get_lon()
<< endl;
}
SG_LOG( SG_GENERAL, SG_ALERT, buf.str() );
return false;
}
// nav list must be of length 1
nav = navlist[0];
}
fgApplyStartOffset(nav->geod(), fgGetDouble("/sim/presets/heading-deg"));
return true;
}
static InitPosResult setInitialPosFromCarrier( const string& carrier )
{
const auto initialPos = FGAICarrier::initialPositionForCarrier(carrier);
if (initialPos.first) {
// set these so scenery system has a vicinity to work with, and
// so our PagedLOD is loaded
fgSetDouble("/sim/presets/longitude-deg", initialPos.second.getLongitudeDeg());
fgSetDouble("/sim/presets/latitude-deg", initialPos.second.getLatitudeDeg());
const std::string carrier = fgGetString("/sim/presets/carrier");
const std::string carrierpos = fgGetString("/sim/presets/carrier-position");
const std::string parkpos = fgGetString("/sim/presets/parkpos");
if (!carrier.empty())
{
std::string cpos = simgear::strutils::lowercase(carrierpos);
if (cpos == "flols" || cpos == "abeam")
fgSetInt("/sim/presets/carrier-course", 3);// base=1, launch=2, recovery=3
else if (parkpos.find("cat") != std::string::npos)
fgSetInt("/sim/presets/carrier-course", 2);// base=1, launch=2, recovery=3
else
fgSetInt("/sim/presets/carrier-course", 1); // base
}
else
fgSetInt("/sim/presets/carrier-course", 0); // not defined.
SG_LOG( SG_GENERAL, SG_DEBUG, "Initial carrier pos = " << initialPos.second << " course " << fgGetInt("/sim/presets/carrier-course"));
return VicinityPosition;
}
SG_LOG( SG_GENERAL, SG_ALERT, "Failed to locate aircraft carrier = " << carrier );
return Failure;
}
static InitPosResult checkCarrierSceneryLoaded(const SGSharedPtr<FGAICarrier> carrierRef)
{
SGVec3d cartPos = carrierRef->getCartPos();
auto framestamp = globals->get_renderer()->getFrameStamp();
simgear::CheckSceneryVisitor csnv(globals->get_scenery()->getPager(),
toOsg(cartPos),
100.0 /* range in metres */,
framestamp);
// currently the PagedLODs will not be loaded by the DatabasePager
// while the splashscreen is there, so CheckSceneryVisitor force-loads
// missing objects in the main thread
carrierRef->getSceneBranch()->accept(csnv);
if (!csnv.isLoaded()) {
return ContinueWaiting;
}
// and then wait for the load to actually be synced to the main thread
if (!carrierRef->modelLoaded()) {
return ContinueWaiting;
}
return VicinityPosition;
}
// Set current_options lon/lat given an aircraft carrier id
static InitPosResult setFinalPosFromCarrier( const string& carrier, const string& posid )
{
SGSharedPtr<FGAICarrier> carrierRef = FGAICarrier::findCarrierByNameOrPennant(carrier);
if (!carrierRef) {
SG_LOG( SG_GENERAL, SG_ALERT, "Failed to locate aircraft carrier = "
<< carrier );
return Failure;
}
auto res = checkCarrierSceneryLoaded(carrierRef);
if (res != VicinityPosition) {
return res; // either failrue or keep waiting for scenery load
}
SGGeod geodPos;
double heading;
SGVec3d uvw;
if (carrierRef->getParkPosition(posid, geodPos, heading, uvw)) {
////////
double lon = geodPos.getLongitudeDeg();
double lat = geodPos.getLatitudeDeg();
double alt = geodPos.getElevationFt() + 2.0;
SG_LOG( SG_GENERAL, SG_INFO, "Attempting to set starting position for "
<< carrier << " at lat = " << lat << ", lon = " << lon
<< ", alt = " << alt << ", heading = " << heading);
fgSetDouble("/sim/presets/longitude-deg", lon);
fgSetDouble("/sim/presets/latitude-deg", lat);
fgSetDouble("/sim/presets/altitude-ft", alt);
fgSetDouble("/sim/presets/heading-deg", heading);
fgSetDouble("/position/longitude-deg", lon);
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));
fgSetDouble("/velocities/wBody-fps", uvw(2));
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;
}
SG_LOG( SG_GENERAL, SG_ALERT, "Failed to locate aircraft carrier = " << carrier );
return Failure;
}
static InitPosResult setFinalPosFromCarrierFLOLS(const string& carrier, bool abeam)
{
SGSharedPtr<FGAICarrier> carrierRef = FGAICarrier::findCarrierByNameOrPennant(carrier);
if (!carrierRef) {
SG_LOG( SG_GENERAL, SG_ALERT, "Failed to locate aircraft carrier = "
<< carrier );
return Failure;
}
auto res = checkCarrierSceneryLoaded(carrierRef);
if (res != VicinityPosition) {
SG_LOG(SG_GENERAL, SG_DEBUG, "carrier scenery not yet loaded");
return res; // either failure or keep waiting for scenery load
}
SGGeod flolsPosition;
double headingToFLOLS;
if (!carrierRef->getFLOLSPositionHeading(flolsPosition, headingToFLOLS)) {
SG_LOG( SG_GENERAL, SG_ALERT, "Unable to compiute FLOLS position for carrier = "
<< carrier );
return Failure;
}
const auto flolsElevationFt = flolsPosition.getElevationFt();
double gs = SGMiscd::deg2rad(carrierRef->getFLOLFSGlidepathAngleDeg());
const double od = fgGetDouble("/sim/presets/offset-distance-nm");
// 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);
}
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", abeam ? (headingToFLOLS - 180) : headingToFLOLS);
fgSetDouble("/position/longitude-deg", startPos.getLongitudeDeg());
fgSetDouble("/position/latitude-deg", startPos.getLatitudeDeg());
fgSetDouble("/position/altitude-ft", startPos.getElevationFt());
fgSetDouble("/orientation/heading-deg", headingToFLOLS);
fgSetBool("/sim/presets/onground", false);
return ExactPosition;
}
// Set current_options lon/lat given a fix ident and GUID
static bool fgSetPosFromFix( const string& id, PositionedID guid )
{
FGPositionedRef fix;
if (guid != 0) {
fix = FGPositioned::loadById<FGPositioned>(guid);
} else {
FGPositioned::TypeFilter fixFilter(FGPositioned::FIX);
fix = FGPositioned::findFirstWithIdent(id, &fixFilter);
}
if (!fix) {
SG_LOG( SG_GENERAL, SG_ALERT, "Failed to locate fix = " << id );
return false;
}
fgApplyStartOffset(fix->geod(), fgGetDouble("/sim/presets/heading-deg"));
return true;
}
// Set the initial position based on presets (or defaults)
bool initPosition()
{
global_finalizeTime = SGTimeStamp(); // reset to invalid
if (!global_callbackRegistered) {
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;
// clear this value, so we don't preserve an old value and confuse
// the ATC manager. We will set it again if it's valid
fgSetString("/sim/atc/runway", "");
// 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.
// The default values for lon/lat are deliberately out of range
// so that the airport-id can take effect; valid lon/lat will
// override airport-id, however.
double lon_deg = fgGetDouble("/sim/presets/longitude-deg");
double lat_deg = fgGetDouble("/sim/presets/latitude-deg");
if ( lon_deg >= -180.0 && lon_deg <= 180.0
&& lat_deg >= -90.0 && lat_deg <= 90.0 )
{
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");
bool rwy_req = fgGetBool("/sim/presets/runway-requested");
string vor = fgGetString("/sim/presets/vor-id");
double vor_freq = fgGetDouble("/sim/presets/vor-freq");
string ndb = fgGetString("/sim/presets/ndb-id");
double ndb_freq = fgGetDouble("/sim/presets/ndb-freq");
string carrier = fgGetString("/sim/presets/carrier");
string parkpos = fgGetString("/sim/presets/parkpos");
string fix = fgGetString("/sim/presets/fix");
const auto tacan = fgGetString("/sim/presets/tacan-id");
// 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();
double original_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 );
if (result != Failure) {
// we at least found the carrier
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
// for the default runway (from $FGDATA/location-preset.xml) which is
// 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
// also depends on ATC (the ground controller), we need to defer this
// until position finalisation
// the rest of the work happens in finalizePosFromParkpos
if ( airportParkingSetVicinity( apt ) ) {
set_pos = true;
}
}
if ( !set_pos && !apt.empty() && !rwy_no.empty() ) {
// An airport + runway is requested
if ( fgSetPosFromAirportIDandRwy( apt, rwy_no, rwy_req ) ) {
set_pos = true;
}
}
if ( !set_pos && !apt.empty() ) {
// An airport is requested (find runway closest to hdg)
if ( setPosFromAirportIDandHdg( apt, hdg ) ) {
set_pos = true;
}
}
// if an airport ID was requested, set closest-airport-id
// and tower based upon it.
if (!apt.empty() && set_pos) {
// set tower position
fgSetString("/sim/airport/closest-airport-id", apt.c_str());
fgSetString("/sim/tower/airport-id", apt.c_str());
}
if (original_hdg < 9990.0) {
// The user-set heading may be overridden by the setPosFromAirportID above.
hdg_preset->setDoubleValue(original_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 & !tacan.empty()) {
// we don't record TACANs in the NavCache right now, instead we
// record DMEs, some of which have TACAN in the name.
if (fgSetPosFromNAV(tacan, 0.0, FGPositioned::DME, navaidId)) {
set_pos = true;
}
}
if ( !set_pos ) {
const std::string defaultAirportId = fgGetString("/sim/presets/airport-id");
const FGAirport* airport = fgFindAirportID(defaultAirportId);
if( airport ) {
const SGGeod & airportGeod = airport->geod();
fgSetDouble("/sim/presets/longitude-deg", airportGeod.getLongitudeDeg());
fgSetDouble("/sim/presets/latitude-deg", airportGeod.getLatitudeDeg());
} else {
// So, the default airport is unknown? We are in serious trouble.
// Let's hope KSFO still exists somehow
fgSetDouble("/sim/presets/longitude-deg", -122.374843);
fgSetDouble("/sim/presets/latitude-deg", 37.619002);
SG_LOG(SG_GENERAL, SG_ALERT, "Sorry, the default airport ('" << defaultAirportId
<< "') 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;
}
bool finalizeMetar()
{
double hdg = fgGetDouble( "/environment/metar/base-wind-dir-deg", 9999.0 );
string apt = fgGetString("/sim/presets/airport-id");
double strthdg = fgGetDouble( "/sim/startup/options/heading-deg", 9999.0 );
string parkpos = fgGetString( "/sim/presets/parkpos" );
bool onground = fgGetBool( "/sim/presets/onground", false );
const bool rwy_req = fgGetBool("/sim/presets/runway-requested");
// this logic is taken from former startup.nas
bool needMetar = (hdg < 360.0) &&
!apt.empty() &&
(strthdg > 360.0) &&
!rwy_req &&
onground &&
parkpos.empty();
if (needMetar) {
// timeout so we don't spin forever if the network is down
if (global_finalizeTime.elapsedMSec() > fgGetInt("/sim/startup/metar-fetch-timeout-msec", 6000)) {
SG_LOG(SG_GENERAL, SG_WARN, "finalizePosition: timed out waiting for METAR fetch");
return true;
}
if (fgGetBool( "/environment/metar/failure" )) {
SG_LOG(SG_ENVIRONMENT, SG_INFO, "metar download failed, not waiting");
return true;
}
if (!fgGetBool( "/environment/metar/valid" )) {
return false;
}
SG_LOG(SG_ENVIRONMENT, SG_INFO,
"Using METAR for runway selection: '" << fgGetString("/environment/metar/data") << "'" );
setPosFromAirportIDandHdg( apt, hdg );
// fall through to return true
} // of need-metar case
return true;
}
void finalizePosition()
{
// first call to finalize after an initPosition call
if (global_finalizeTime.get_usec() == 0) {
global_finalizeTime = SGTimeStamp::now();
}
bool done = true;
/* Scenarios require Nasal, so FGAIManager loads the scenarios,
* including its models such as a/c carriers, in its 'postinit',
* which is the very last thing we do.
* flightgear::initPosition is called very early in main.cxx/fgIdleFunction,
* one of the first things we do, long before scenarios/carriers are
* loaded. => When requested "initial preset position" relates to a
* carrier, recalculate the 'initial' position here
*/
const std::string carrier = fgGetString("/sim/presets/carrier");
const std::string carrierpos = fgGetString("/sim/presets/carrier-position");
const std::string parkpos = fgGetString("/sim/presets/parkpos");
const std::string runway = fgGetString("/sim/presets/runway");
const std::string apt = fgGetString("/sim/presets/airport-id");
if (!carrier.empty())
{
/* /sim/presets/carrier-position can take a number of different values
- name of a parking/cataputt
- FLOLS - to position on final approach
- abeam - on an abeam position from the FLOLS, heading downwind
- <empty> indicating a start position of a catapult
*/
// Convert to lower case to simplify comparison
std::string cpos = simgear::strutils::lowercase(carrierpos);
const bool inair = (cpos == "flols") || (cpos == "abeam");
const bool abeam = (cpos == "abeam");
InitPosResult carrierResult;
if (inair) {
carrierResult = setFinalPosFromCarrierFLOLS(carrier, abeam);
} else {
// We don't simply use cpos as it is lower case, and the
// search against parking/catapult positions is case-sensitive.
carrierResult = setFinalPosFromCarrier(carrier, carrierpos);
}
if (carrierResult == ExactPosition) {
done = true;
} else if (carrierResult == Failure) {
SG_LOG(SG_GENERAL, SG_ALERT, "secondary carrier init failed");
done = true;
} else {
done = false;
// 60 second timeout on waiting for the carrier to load
if (global_finalizeTime.elapsedMSec() > 60000) {
SG_LOG(SG_GENERAL, SG_ALERT, "Timeout waiting for carrier scenery to load, will start on the water.");
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
bool ok = finalizePositionForParkpos(apt, parkpos);
if (!ok) {
SG_LOG(SG_GENERAL, SG_WARN, "finalizePositionForParkPos failed, reverting to best runway");
// clear this so finalizeMetar works as expected
fgSetString("/sim/presets/parkpos", "");
finalizeMetar();
}
} else {
done = finalizeMetar();
}
fgSetBool("/sim/position-finalized", done);
if (done) {
globals->get_event_mgr()->removeTask("finalizePosition");
global_callbackRegistered = false;
}
}
} // of namespace flightgear

39
src/Main/positioninit.hxx Normal file
View File

@@ -0,0 +1,39 @@
// positioninit.hxx - helpers relating to setting initial aircraft position
//
// Copyright (C) 2012 James Turner zakalawe@mac.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef FG_POSITION_INIT_HXX
#define FG_POSITION_INIT_HXX
#include <string>
namespace flightgear
{
// Set the initial position based on presets (or defaults)
bool initPosition();
// Listen to /sim/tower/airport-id and set tower view position accordingly
void initTowerLocationListener();
// allow this to be manually invoked for position init testing
void finalizePosition();
} // of namespace flightgear
#endif // of FG_POSITION_INIT_HXX

23
src/Main/runfgfs.bat.in Normal file
View File

@@ -0,0 +1,23 @@
REM @ECHO OFF
REM Skip ahead to CONT1 if FG_ROOT has a value
IF NOT %FG_ROOT%.==. GOTO CONT1
SET FG_ROOT=.
:CONT1
REM Check for the existance of the executable
IF NOT EXIST %FG_ROOT%\BIN\FGFS.EXE GOTO ERROR1
REM Now that FG_ROOT has been set, run the program
ECHO FG_ROOT = %FG_ROOT%
%FG_ROOT%\BIN\FGFS.EXE %1 %2 %3 %4 %5 %6 %7 %8 %9
GOTO END
:ERROR1
ECHO Cannot find %FG_ROOT%\BIN\FGFS.EXE
GOTO END
:END

89
src/Main/runfgfs.in Executable file
View File

@@ -0,0 +1,89 @@
#!/usr/bin/perl
#
# runfg -- front end for setting up the FG_ROOT env variable and launching
# the fg executable.
#
# Written by Curtis Olson, started September 1997.
#
# Copyright (C) 1997 - 1998 Curtis L. Olson - http://www.flightgear.org/~curt
#
# 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
#
# $Id$
#---------------------------------------------------------------------------
$prefix = "@prefix@";
# print "-> $prefix\n";
# potential names of Flight Gear executable to try
@files = ( "fgfs", "fgfs.exe" );
# search for the executable
# potential paths where the executable may be found
@paths = ( ".", "Simulator/Main", $prefix );
$savepath = "";
$savefile = "";
foreach $path (@paths) {
foreach $file (@files) {
# print "'$savepath'\n";
if ( $savepath eq "" ) {
# don't search again if we've already found one
# print "checking $path" . "bin/$file and $path" . "$file\n";
if ( -x "$path/bin/$file" ) {
$saveprefix = $path;
$savepath = "$path/bin";
$savefile = "$file";
} elsif ( -x "$path/$file" ) {
$saveprefix = $path;
$savepath = "$path";
$savefile = "$file";
}
} else {
# print "skipping $path/bin/$file and $path/$file\n";
}
}
}
die "Cannot locate program.\n" if ( $savepath eq "" );
# search for the "FlightGear" root directory
@paths = ( $prefix, $saveprefix, $ENV{HOME} );
$fg_root = "";
foreach $path (@paths) {
# print "trying $path\n";
if ( $fg_root eq "" ) {
if ( -d "$path/FlightGear" ) {
$fg_root = "$path/FlightGear";
} elsif ( -d "$path/share/FlightGear" ) {
$fg_root = "$path/share/FlightGear";
}
}
}
die "Cannot locate FG root directory (data)\n" if ( $fg_root eq "" );
# run Flight Gear
print "Running $savepath/$savefile --fg-root=$fg_root @ARGV\n";
exec("$savepath/$savefile --fg-root=$fg_root @ARGV");
#---------------------------------------------------------------------------

View File

@@ -0,0 +1,69 @@
// screensaver_control.cxx -- disable the screensaver
//
// Written by Rebecca Palmer, December 2013.
//
// Copyright (C) 2013 Rebecca Palmer
//
// 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 <config.h>
#include <simgear/compiler.h>
#ifdef HAVE_DBUS
#include <dbus/dbus.h>//Uses the low-level libdbus rather than GDBus/QtDBus to avoid adding more dependencies than necessary. http://dbus.freedesktop.org/doc/api/html/index.html
#endif
/** Attempt to disable the screensaver.
*
* Screensavers/powersavers often do not monitor the joystick, and it is hence advisable to disable them while FlightGear is running.
* This function always exists, but currently only actually does anything on Linux, where it will disable gnome-screensaver/kscreensaver until FlightGear exits.
*
* The following might be useful to anyone wishing to add Windows support:
* http://msdn.microsoft.com/en-us/library/windows/desktop/aa373233%28v=vs.85%29.aspx
* http://msdn.microsoft.com/en-us/library/windows/desktop/aa373208%28v=vs.85%29.aspx (While this documentation says it only disables powersave, it is elsewhere reported to also disable screensaver)
* http://msdn.microsoft.com/en-us/library/windows/desktop/dd405534%28v=vs.85%29.aspx
*/
void fgOSDisableScreensaver()
{
#if defined(HAVE_DBUS) && defined(SG_UNIX) && !defined(SG_MAC)
DBusConnection *dbus_connection;
DBusMessage *dbus_inhibit_screenlock;
unsigned int window_id=1000;//fake-it doesn't seem to care
unsigned int inhibit_idle=8;//8=idle inhibit flag
const char *app_name="org.flightgear";
const char *inhibit_reason="Uses joystick input";
// REVIEW: Memory Leak - 2,056 bytes in 1 blocks are still reachable
dbus_connection=dbus_bus_get(DBUS_BUS_SESSION,NULL);
dbus_connection_set_exit_on_disconnect(dbus_connection,FALSE);//Don't close us if we lose the DBus connection
//Two possible interfaces; we send on both, as that is easier than trying to determine which will work
//GNOME: https://people.gnome.org/~mccann/gnome-session/docs/gnome-session.html
dbus_inhibit_screenlock=dbus_message_new_method_call("org.gnome.SessionManager","/org/gnome/SessionManager","org.gnome.SessionManager","Inhibit");
// REVIEW: Memory Leak - 352 bytes in 1 blocks are indirectly lost
dbus_message_append_args(dbus_inhibit_screenlock,DBUS_TYPE_STRING,&app_name,DBUS_TYPE_UINT32,&window_id,DBUS_TYPE_STRING,&inhibit_reason,DBUS_TYPE_UINT32,&inhibit_idle,DBUS_TYPE_INVALID);
dbus_connection_send(dbus_connection,dbus_inhibit_screenlock,NULL);
//KDE, GNOME 3.6+: http://standards.freedesktop.org/idle-inhibit-spec/0.1/re01.html
dbus_inhibit_screenlock=dbus_message_new_method_call("org.freedesktop.ScreenSaver","/ScreenSaver","org.freedesktop.ScreenSaver","Inhibit");
// REVIEW: Memory Leak - 320 bytes in 1 blocks are still reachable
dbus_message_append_args(dbus_inhibit_screenlock,DBUS_TYPE_STRING,&app_name,DBUS_TYPE_STRING,&inhibit_reason,DBUS_TYPE_INVALID);
dbus_connection_send(dbus_connection,dbus_inhibit_screenlock,NULL);
dbus_connection_flush(dbus_connection);
//Currently ignores the reply; it would need to read it if we wanted to determine whether we've succeeded and/or allow explicitly re-enabling the screensaver
//Don't disconnect, that ends the inhibition
#endif
}

View File

@@ -0,0 +1,24 @@
// screensaver_control.hxx -- disable the screensaver
//
// Written by Rebecca Palmer, December 2013.
//
// Copyright (C) 2013 Rebecca Palmer
//
// 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$
void fgOSDisableScreensaver();

View File

@@ -0,0 +1,577 @@
// sentryIntegration.cxx - Interface with Sentry.io crash reporting
//
// Copyright (C) 2020 James Turner james@flightgear.org
//
// 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 "config.h"
#include "sentryIntegration.hxx"
#include <cstring> // for strcmp
#include <simgear/debug/LogCallback.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/debug/ErrorReportingCallback.hxx>
#include <simgear/debug/Reporting.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/props/props.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <Main/fg_init.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <flightgearBuildId.h>
using namespace std;
bool doesStringMatchPrefixes(const std::string& s, const std::initializer_list<const char*>& prefixes)
{
if (s.empty())
return false;
for (auto c : prefixes) {
if (s.find(c) == 0)
return true;
}
return false;
}
auto OSG_messageWhitelist = {
"PNG lib warning : iCCP: known incorrect sRGB profile",
"PNG lib warning : iCCP: profile 'ICC Profile': 1000000h: invalid rendering intent",
"osgDB ac3d reader: detected surface with less than 3",
"osgDB ac3d reader: detected line with less than 2",
"Detected particle system using segment(s) with less than 2 vertices"
};
auto exception_messageWhitelist = {
"position is invalid, NaNs", ///< avoid spam when NaNs occur
"bad AI flight plan", ///< adjusting logic to avoid this is tricky
"couldn't find shader", ///< handled seperately
/// supress noise from user-entered METAR values : we special case
/// when live metar fails to parse
"metar data bogus",
"metar data incomplete"
};
// we don't want sentry enabled for the test suite
#if defined(HAVE_SENTRY) && !defined(BUILDING_TESTSUITE)
static bool static_sentryEnabled = false;
#include <sentry.h>
namespace {
// this callback is invoked whenever an instance of sg_throwable is created.
// therefore we can log the stack trace at that point
void sentryTraceSimgearThrow(const std::string& msg, const std::string& origin, const sg_location& loc)
{
if (!static_sentryEnabled)
return;
if (doesStringMatchPrefixes(msg, exception_messageWhitelist)) {
return;
}
sentry_value_t exc = sentry_value_new_object();
sentry_value_set_by_key(exc, "type", sentry_value_new_string("Exception"));
string message = msg;
sentry_value_t info = sentry_value_new_object();
if (!origin.empty()) {
sentry_value_set_by_key(info, "origin", sentry_value_new_string(origin.c_str()));
}
if (loc.isValid()) {
const auto ls = loc.asString();
sentry_value_set_by_key(info, "location", sentry_value_new_string(ls.c_str()));
}
sentry_set_context("what", info);
sentry_value_set_by_key(exc, "value", sentry_value_new_string(message.c_str()));
sentry_value_t event = sentry_value_new_event();
sentry_value_set_by_key(event, "exception", exc);
sentry_event_value_add_stacktrace(event, nullptr, 0);
sentry_capture_event(event);
}
class SentryLogCallback : public simgear::LogCallback
{
public:
SentryLogCallback() : simgear::LogCallback(SG_ALL, SG_WARN)
{
}
bool doProcessEntry(const simgear::LogEntry& e) override
{
// we need original priority here, so we don't record MANDATORY_INFO
// or DEV_ messages, which would get noisy.
const auto op = e.originalPriority;
if ((op != SG_WARN) && (op != SG_ALERT)) {
return true;
}
if ((e.debugClass == SG_OSG) && doesStringMatchPrefixes(e.message, OSG_messageWhitelist)) {
return true;
}
if (e.message == _lastLoggedMessage) {
_lastLoggedCount++;
return true;
}
if (_lastLoggedCount > 0) {
flightgear::addSentryBreadcrumb("(repeats " + std::to_string(_lastLoggedCount) + " times)", "info");
_lastLoggedCount = 0;
}
_lastLoggedMessage = e.message;
flightgear::addSentryBreadcrumb(e.message, (op == SG_WARN) ? "warning" : "error");
return true;
}
private:
std::string _lastLoggedMessage;
int _lastLoggedCount = 0;
};
const auto missingShaderPrefix = string{"Missing shader"};
string_list anon_missingShaderList;
bool isNewMissingShader(const std::string& path)
{
auto it = std::find(anon_missingShaderList.begin(), anon_missingShaderList.end(), path);
if (it != anon_missingShaderList.end()) {
return false;
}
anon_missingShaderList.push_back(path);
return true;
}
void sentrySimgearReportCallback(const string& msg, const string& more, bool isFatal)
{
// don't duplicate reports for missing shaders, once per sessions
// is sufficient
using simgear::strutils::starts_with;
if (starts_with(msg, missingShaderPrefix)) {
if (!isNewMissingShader(more)) {
return;
}
}
sentry_value_t exc = sentry_value_new_object();
if (isFatal) {
sentry_value_set_by_key(exc, "type", sentry_value_new_string("Fatal Error"));
} else {
sentry_value_set_by_key(exc, "type", sentry_value_new_string("Exception"));
}
sentry_value_set_by_key(exc, "value", sentry_value_new_string(msg.c_str()));
sentry_value_t event = sentry_value_new_event();
sentry_value_set_by_key(event, "exception", exc);
sentry_event_value_add_stacktrace(event, nullptr, 0);
sentry_capture_event(event);
}
void sentryReportBadAlloc()
{
if (simgear::ReportBadAllocGuard::isSet()) {
sentry_value_t sentryMessage = sentry_value_new_object();
sentry_value_set_by_key(sentryMessage, "type", sentry_value_new_string("Fatal Error"));
sentry_value_set_by_key(sentryMessage, "formatted", sentry_value_new_string("bad allocation"));
sentry_value_t event = sentry_value_new_event();
sentry_value_set_by_key(event, "message", sentryMessage);
sentry_event_value_add_stacktrace(event, nullptr, 0);
sentry_capture_event(event);
}
throw std::bad_alloc(); // allow normal processing
}
} // namespace
namespace flightgear
{
bool sentryReportCommand(const SGPropertyNode* args, SGPropertyNode* root)
{
if (!static_sentryEnabled) {
SG_LOG(SG_GENERAL, SG_WARN, "Sentry.io not enabled at startup");
return false;
}
sentry_value_t exc = sentry_value_new_object();
sentry_value_set_by_key(exc, "type", sentry_value_new_string("Report"));
const string message = args->getStringValue("message");
sentry_value_set_by_key(exc, "value", sentry_value_new_string(message.c_str()));
sentry_value_t event = sentry_value_new_event();
sentry_value_set_by_key(event, "exception", exc);
// capture the C++ stack-trace. Probably not that useful but can't hurt
sentry_event_value_add_stacktrace(event, nullptr, 0);
sentry_capture_event(event);
return true;
}
bool sentrySendError(const SGPropertyNode* args, SGPropertyNode* root)
{
if (!static_sentryEnabled) {
SG_LOG(SG_GENERAL, SG_WARN, "Sentry.io not enabled at startup");
return false;
}
try {
throw sg_io_exception("Invalid flurlbe", sg_location("/Some/dummy/path/bar.txt", 100, 200));
} catch (sg_exception& e) {
SG_LOG(SG_GENERAL, SG_WARN, "caught dummy exception");
}
return true;
}
void initSentry()
{
sentry_options_t *options = sentry_options_new();
// API key is defined in config.h, set in an environment variable prior
// to running CMake, so it can be customised. Env var at build time is:
// FLIGHTGEAR_SENTRY_API_KEY
sentry_options_set_dsn(options, SENTRY_API_KEY);
if (strcmp(FG_BUILD_TYPE, "Dev") == 0) {
sentry_options_set_release(options, "flightgear-dev@" FLIGHTGEAR_VERSION);
} else {
sentry_options_set_release(options, "flightgear@" FLIGHTGEAR_VERSION);
}
const auto buildString = std::to_string(JENKINS_BUILD_NUMBER);
sentry_options_set_dist(options, buildString.c_str());
// for dev / nightly builds, put Sentry in debug mode
if (strcmp(FG_BUILD_TYPE, "Release")) {
sentry_options_set_debug(options, 1);
}
SGPath dataPath = fgHomePath() / "sentry_db";
#if defined(SG_WINDOWS)
const auto homePathString = dataPath.wstr();
sentry_options_set_database_pathw(options, homePathString.c_str());
const auto logPath = (fgHomePath() / "fgfs.log").wstr();
sentry_options_add_attachmentw(options, logPath.c_str());
#else
const auto homePathString = dataPath.utf8Str();
sentry_options_set_database_path(options, homePathString.c_str());
const auto logPath = (fgHomePath() / "fgfs.log").utf8Str();
sentry_options_add_attachment(options, logPath.c_str());
#endif
const auto uuidPath = fgHomePath() / "sentry_uuid.txt";
bool generateUuid = true;
std::string uuid;
if (uuidPath.exists()) {
sg_ifstream f(uuidPath);
std::getline(f, uuid);
// if we read enough bytes, that this is a valid UUID, then accept it
if ( uuid.length() >= 36) {
generateUuid = false;
}
}
// we need to generate a new UUID
if (generateUuid) {
// use the Sentry APi to generate one
sentry_uuid_t su = sentry_uuid_new_v4();
char bytes[38];
sentry_uuid_as_string(&su, bytes);
bytes[37] = 0;
uuid = std::string{bytes};
// write it back to disk for next time
sg_ofstream f(uuidPath);
f << uuid << endl;
}
if (sentry_init(options) == 0) {
static_sentryEnabled = true;
sentry_value_t user = sentry_value_new_object();
sentry_value_t userUuidV = sentry_value_new_string(uuid.c_str());
sentry_value_set_by_key(user, "id", userUuidV);
sentry_set_user(user);
sglog().addCallback(new SentryLogCallback);
setThrowCallback(sentryTraceSimgearThrow);
simgear::setErrorReportCallback(sentrySimgearReportCallback);
std::set_new_handler(sentryReportBadAlloc);
} else {
SG_LOG(SG_GENERAL, SG_WARN, "Failed to init Sentry reporting");
static_sentryEnabled = false;
}
}
void delayedSentryInit()
{
if (!static_sentryEnabled)
return;
// allow the user to opt-out of sentry.io features
if (!fgGetBool("/sim/startup/sentry-crash-reporting-enabled", true)) {
SG_LOG(SG_GENERAL, SG_INFO, "Disabling Sentry.io reporting");
sentry_shutdown();
static_sentryEnabled = false;
return;
}
globals->get_commands()->addCommand("sentry-report", &sentryReportCommand);
globals->get_commands()->addCommand("sentry-exception", &sentrySendError);
}
void shutdownSentry()
{
if (static_sentryEnabled) {
sentry_shutdown();
static_sentryEnabled = false;
}
}
bool isSentryEnabled()
{
return static_sentryEnabled;
}
void addSentryBreadcrumb(const std::string& msg, const std::string& level)
{
if (!static_sentryEnabled)
return;
sentry_value_t crumb = sentry_value_new_breadcrumb("default", msg.c_str());
sentry_value_set_by_key(crumb, "level", sentry_value_new_string(level.c_str()));
sentry_add_breadcrumb(crumb);
}
void addSentryTag(const char* tag, const char* value)
{
if (!static_sentryEnabled)
return;
if (!tag || !value)
return;
sentry_set_tag(tag, value);
}
void updateSentryTag(const std::string& tag, const std::string& value)
{
if (tag.empty() || value.empty())
return;
if (!static_sentryEnabled)
return;
sentry_remove_tag(tag.c_str());
sentry_set_tag(tag.c_str(), value.c_str());
}
void sentryReportNasalError(const std::string& msg, const string_list& stack)
{
if (!static_sentryEnabled)
return;
#if 0
sentry_value_t exc = sentry_value_new_object();
sentry_value_set_by_key(exc, "type", sentry_value_new_string("Exception"));
sentry_value_set_by_key(exc, "value", sentry_value_new_string(msg.c_str()));
sentry_value_t stackData = sentry_value_new_list();
for (const auto& nasalFrame : stack) {
sentry_value_append(stackData, sentry_value_new_string(nasalFrame.c_str()));
}
sentry_value_set_by_key(exc, "stack", stackData);
sentry_value_t event = sentry_value_new_event();
sentry_value_set_by_key(event, "exception", exc);
// add the Nasal stack trace data
// capture the C++ stack-trace. Probably not that useful but can't hurt
sentry_event_value_add_stacktrace(event, nullptr, 0);
sentry_capture_event(event);
#endif
}
void sentryReportException(const std::string& msg, const std::string& location)
{
if (!static_sentryEnabled)
return;
sentry_value_t exc = sentry_value_new_object();
sentry_value_set_by_key(exc, "type", sentry_value_new_string("Exception"));
sentry_value_t info = sentry_value_new_object();
if (!location.empty()) {
sentry_value_set_by_key(info, "location", sentry_value_new_string(location.c_str()));
}
sentry_set_context("what", info);
sentry_value_set_by_key(exc, "value", sentry_value_new_string(msg.c_str()));
sentry_value_t event = sentry_value_new_event();
sentry_value_set_by_key(event, "exception", exc);
// capture the C++ stack-trace. Probably not that useful but can't hurt
sentry_event_value_add_stacktrace(event, nullptr, 0);
sentry_capture_event(event);
}
void sentryReportFatalError(const std::string& msg, const std::string& more)
{
if (!static_sentryEnabled)
return;
sentry_value_t sentryMessage = sentry_value_new_object();
sentry_value_set_by_key(sentryMessage, "type", sentry_value_new_string("Fatal Error"));
sentry_value_t info = sentry_value_new_object();
if (!more.empty()) {
sentry_value_set_by_key(info, "more", sentry_value_new_string(more.c_str()));
}
sentry_set_context("what", info);
sentry_value_set_by_key(sentryMessage, "formatted", sentry_value_new_string(msg.c_str()));
sentry_value_t event = sentry_value_new_event();
sentry_value_set_by_key(event, "message", sentryMessage);
sentry_event_value_add_stacktrace(event, nullptr, 0);
sentry_capture_event(event);
}
void sentryReportUserError(const std::string& aggregate, const std::string& details)
{
if (!static_sentryEnabled)
return;
sentry_value_t sentryMessage = sentry_value_new_object();
sentry_value_set_by_key(sentryMessage, "type", sentry_value_new_string("Error"));
sentry_value_t info = sentry_value_new_object();
sentry_value_set_by_key(info, "details", sentry_value_new_string(details.c_str()));
sentry_set_context("what", info);
sentry_value_t event = sentry_value_new_event();
sentry_value_set_by_key(event, "message", sentry_value_new_string(aggregate.c_str()));
sentry_capture_event(event);
}
} // of namespace
#else
// stubs for non-sentry case
namespace flightgear
{
void initSentry()
{
}
void shutdownSentry()
{
}
void delayedSentryInit()
{
}
bool isSentryEnabled()
{
return false;
}
void addSentryBreadcrumb(const std::string&, const std::string&)
{
}
void addSentryTag(const char*, const char*)
{
}
void updateSentryTag(const std::string&, const std::string&)
{
}
void sentryReportNasalError(const std::string&, const string_list&)
{
}
void sentryReportException(const std::string&, const std::string&)
{
}
void sentryReportFatalError(const std::string&, const std::string&)
{
}
void sentryReportUserError(const std::string&, const std::string&)
{
}
} // of namespace
#endif
// common helpers
namespace flightgear
{
void addSentryTag(const std::string& tag, const std::string& value)
{
if (tag.empty() || value.empty())
return;
addSentryTag(tag.c_str(), value.c_str());
}
} // of namespace flightgear

View File

@@ -0,0 +1,53 @@
// sentryIntegration.hxx - Interface with Sentry.io crash reporting
//
// Copyright (C) 2020 James Turner james@flightgear.org
//
// 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.
#pragma once
#include <string>
#include <simgear/misc/strutils.hxx>
namespace flightgear
{
void initSentry();
void shutdownSentry();
void delayedSentryInit();
bool isSentryEnabled();
void addSentryBreadcrumb(const std::string& msg, const std::string& level);
void addSentryTag(const char* tag, const char* value);
void addSentryTag(const std::string& tag, const std::string& value);
void updateSentryTag(const std::string& tag, const std::string& value);
void sentryReportNasalError(const std::string& msg, const string_list& stack);
void sentryReportException(const std::string& msg, const std::string& location = {});
void sentryReportFatalError(const std::string& msg, const std::string& more = {});
void sentryReportUserError(const std::string& aggregate, const std::string& details);
} // of namespace flightgear

View File

@@ -0,0 +1,330 @@
// subsystemFactory.cxx - factory for subsystems
//
// Copyright (C) 2012 James Turner zakalawe@mac.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.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "subsystemFactory.hxx"
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/structure/event_mgr.hxx>
#include <Main/globals.hxx>
#include <Sound/soundmanager.hxx>
// subsystem includes
#include <Aircraft/controls.hxx>
#include <Aircraft/FlightHistory.hxx>
#include <Main/fg_props.hxx>
#include <Main/FGInterpolator.hxx>
#include <Main/fg_io.hxx>
#include <FDM/fdm_shell.hxx>
#include <Environment/environment_mgr.hxx>
#include <Environment/ephemeris.hxx>
#include <Instrumentation/instrument_mgr.hxx>
#include <Instrumentation/HUD/HUD.hxx>
#include <Systems/system_mgr.hxx>
#include <Autopilot/route_mgr.hxx>
#include <Autopilot/autopilotgroup.hxx>
#include <Traffic/TrafficMgr.hxx>
#include <Network/fgcom.hxx>
#include <Network/HTTPClient.hxx>
#include <Cockpit/cockpitDisplayManager.hxx>
#include <GUI/new_gui.hxx>
#include <Main/logger.hxx>
#include <ATC/atc_mgr.hxx>
#include <AIModel/AIManager.hxx>
#include <MultiPlayer/multiplaymgr.hxx>
#if defined(ENABLE_SWIFT)
# include <Network/Swift/swift_connection.hxx>
#endif
#include <AIModel/submodel.hxx>
#include <Aircraft/controls.hxx>
#include <Input/input.hxx>
#include <Aircraft/replay.hxx>
#include <Sound/voice.hxx>
#include <Canvas/canvas_mgr.hxx>
#include <Canvas/gui_mgr.hxx>
#include <Time/light.hxx>
#include <Viewer/viewmgr.hxx>
#include <Model/acmodel.hxx>
#include <Model/modelmgr.hxx>
using std::vector;
namespace flightgear
{
SGSubsystem* createSubsystemByName(const std::string& name)
{
#define MAKE_SUB(cl, n) \
if (name == n) return new cl;
MAKE_SUB(FGSoundManager, "sound");
MAKE_SUB(FGInterpolator, "prop-interpolator")
MAKE_SUB(FGProperties, "properties");
MAKE_SUB(FGHTTPClient, "http");
MAKE_SUB(FDMShell, "flight");
MAKE_SUB(FGEnvironmentMgr, "environment");
MAKE_SUB(Ephemeris, "ephemeris");
MAKE_SUB(FGSystemMgr, "systems");
MAKE_SUB(FGInstrumentMgr, "instrumentation");
MAKE_SUB(HUD, "hud");
MAKE_SUB(flightgear::CockpitDisplayManager, "cockpit-displays");
MAKE_SUB(FGRouteMgr, "route-manager");
MAKE_SUB(FGIO, "io");
MAKE_SUB(FGLogger, "logger");
MAKE_SUB(NewGUI, "gui");
MAKE_SUB(CanvasMgr, "Canvas");
MAKE_SUB(GUIMgr, "CanvasGUI");
MAKE_SUB(FGATCManager, "ATC");
MAKE_SUB(FGMultiplayMgr, "mp");
MAKE_SUB(FGAIManager, "ai-model");
MAKE_SUB(FGSubmodelMgr, "submodel-mgr");
MAKE_SUB(FGTrafficManager, "traffic-manager");
MAKE_SUB(FGControls, "controls");
MAKE_SUB(FGInput, "input");
MAKE_SUB(FGReplay, "replay");
MAKE_SUB(FGFlightHistory, "history");
#ifdef ENABLE_AUDIO_SUPPORT
MAKE_SUB(FGVoiceMgr, "voice");
#endif
#ifdef ENABLE_IAX
MAKE_SUB(FGCom, "fgcom");
#endif
#ifdef ENABLE_SWIFT
MAKE_SUB(SwiftConnection, "swift");
#endif
MAKE_SUB(FGLight, "tides");
MAKE_SUB(FGLight, "lighting");
MAKE_SUB(FGAircraftModel, "aircraft-model");
MAKE_SUB(FGModelMgr, "model-manager");
MAKE_SUB(FGViewMgr, "view-manager");
#undef MAKE_SUB
throw sg_range_exception("unknown subsystem:" + name);
}
SGSubsystemMgr::GroupType mapGroupNameToType(const std::string& s)
{
if (s == "init") return SGSubsystemMgr::INIT;
if (s == "general") return SGSubsystemMgr::GENERAL;
if (s == "fdm") return SGSubsystemMgr::FDM;
if (s == "post-fdm") return SGSubsystemMgr::POST_FDM;
if (s == "display") return SGSubsystemMgr::DISPLAY;
if (s == "sound") return SGSubsystemMgr::SOUND;
SG_LOG(SG_GENERAL, SG_ALERT, "unrecognized subsystem group:" << s);
return SGSubsystemMgr::GENERAL;
}
static SGSubsystem* getSubsystem(const SGPropertyNode* arg, bool create)
{
std::string subsystem(arg->getStringValue("subsystem"));
std::string name = arg->getStringValue("name");
if (name.empty()) {
// default name is simply the subsytem's name
name = subsystem;
}
SGSubsystem* sys = globals->get_subsystem_mgr()->get_subsystem(name);
if (!create)
return sys;
if( subsystem.empty() ) {
SG_LOG( SG_GENERAL,
SG_ALERT,
"do_add_subsystem: no subsystem/name supplied" );
return 0;
}
if (sys) {
SG_LOG( SG_GENERAL,
SG_ALERT,
"do_add_subsystem: duplicate subsystem name:" << name );
return 0;
}
std::string groupname = arg->getStringValue("group");
SGSubsystemMgr::GroupType group = SGSubsystemMgr::GENERAL;
if (!groupname.empty()) {
group = mapGroupNameToType(groupname);
}
try {
sys = createSubsystemByName(subsystem);
} catch (sg_exception& e) {
SG_LOG( SG_GENERAL,
SG_ALERT,
"subsystem creation failed:" << name
<< ":" << e.getFormattedMessage() );
return 0;
}
bool doInit = arg->getBoolValue("do-bind-init", false);
if (doInit) {
sys->bind();
sys->init();
}
double minTime = arg->getDoubleValue("min-time-sec", 0.0);
globals->get_subsystem_mgr()
->add(name.c_str(), sys, group, minTime);
return sys;
}
static bool
do_check_subsystem_running(const SGPropertyNode * arg, SGPropertyNode * root)
{
return getSubsystem(arg, false) != 0;
}
static bool
do_add_subsystem (const SGPropertyNode * arg, SGPropertyNode * root)
{
return getSubsystem(arg, true) != 0;
}
static bool do_remove_subsystem(const SGPropertyNode * arg, SGPropertyNode * root)
{
std::string name = arg->getStringValue("subsystem");
SGSubsystem* instance = globals->get_subsystem_mgr()->get_subsystem(name);
if (!instance) {
SG_LOG(SG_GENERAL, SG_ALERT, "do_remove_subsystem: unknown subsytem:" << name);
return false;
}
// is it safe to always call these? let's assume so!
instance->shutdown();
instance->unbind();
// unplug from the manager (this also deletes the instance!)
globals->get_subsystem_mgr()->remove(name.c_str());
return true;
}
/**
* Built-in command: reinitialize one or more subsystems.
*
* subsystem[*]: the name(s) of the subsystem(s) to reinitialize; if
* none is specified, reinitialize all of them.
*/
static bool
do_reinit (const SGPropertyNode * arg, SGPropertyNode * root)
{
bool result = true;
vector<SGPropertyNode_ptr> subsystems = arg->getChildren("subsystem");
if (subsystems.empty()) {
globals->get_subsystem_mgr()->reinit();
} else {
for ( unsigned int i = 0; i < subsystems.size(); i++ ) {
std::string name = subsystems[i]->getStringValue();
SGSubsystem * subsystem = globals->get_subsystem(name.c_str());
if (subsystem == 0) {
result = false;
SG_LOG( SG_GENERAL, SG_ALERT,
"Subsystem " << name << " not found" );
} else {
subsystem->reinit();
}
}
}
globals->get_event_mgr()->reinit();
return result;
}
/**
* Built-in command: suspend one or more subsystems.
*
* subsystem[*] - the name(s) of the subsystem(s) to suspend.
*/
static bool
do_suspend (const SGPropertyNode * arg, SGPropertyNode * root)
{
bool result = true;
vector<SGPropertyNode_ptr> subsystems = arg->getChildren("subsystem");
for ( unsigned int i = 0; i < subsystems.size(); i++ ) {
std::string name = subsystems[i]->getStringValue();
SGSubsystem * subsystem = globals->get_subsystem(name.c_str());
if (subsystem == 0) {
result = false;
SG_LOG(SG_GENERAL, SG_ALERT, "Subsystem " << name << " not found");
} else {
subsystem->suspend();
}
}
return result;
}
/**
* Built-in command: suspend one or more subsystems.
*
* subsystem[*] - the name(s) of the subsystem(s) to suspend.
*/
static bool
do_resume (const SGPropertyNode * arg, SGPropertyNode * root)
{
bool result = true;
vector<SGPropertyNode_ptr> subsystems = arg->getChildren("subsystem");
for ( unsigned int i = 0; i < subsystems.size(); i++ ) {
std::string name = subsystems[i]->getStringValue();
SGSubsystem * subsystem = globals->get_subsystem(name.c_str());
if (subsystem == 0) {
result = false;
SG_LOG(SG_GENERAL, SG_ALERT, "Subsystem " << name << " not found");
} else {
subsystem->resume();
}
}
return result;
}
static struct {
const char * name;
SGCommandMgr::command_t command;
} built_ins [] = {
{ "add-subsystem", do_add_subsystem },
{ "remove-subsystem", do_remove_subsystem },
{ "subsystem-running", do_check_subsystem_running },
{ "reinit", do_reinit },
{ "suspend", do_suspend },
{ "resume", do_resume },
{ 0, 0 } // zero-terminated
};
void registerSubsystemCommands(SGCommandMgr* cmdMgr)
{
for (int i = 0; built_ins[i].name != 0; i++) {
cmdMgr->addCommand(built_ins[i].name, built_ins[i].command);
}
}
} // of namepace flightgear

View File

@@ -0,0 +1,44 @@
// subsystemFactory.hxx - factory for subsystems
//
// Copyright (C) 2012 James Turner zakalawe@mac.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef FG_SUBSYSTEM_FACTORY_HXX
#define FG_SUBSYSTEM_FACTORY_HXX
#include <string>
// forward decls
class SGCommandMgr;
class SGSubsystem;
namespace flightgear
{
/**
* create a subsystem by name, and return it.
* Will throw an exception if the subsytem name is invalid, or
* if the subsytem could not be created for some other reason.
* ownership of the subsystem passes to the caller
*/
SGSubsystem* createSubsystemByName(const std::string& name);
void registerSubsystemCommands(SGCommandMgr* cmdMgr);
} // of namespace flightgear
#endif // of FG_POSITION_INIT_HXX

166
src/Main/util.cxx Normal file
View File

@@ -0,0 +1,166 @@
// util.cxx - general-purpose utility functions.
// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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$
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/compiler.h>
#include <cmath>
#include <cstdlib>
#include <vector>
#include <simgear/debug/logstream.hxx>
#include <simgear/math/SGLimits.hxx>
#include <simgear/math/SGMisc.hxx>
#include <GUI/MessageBox.hxx>
#include "fg_io.hxx"
#include "fg_props.hxx"
#include "globals.hxx"
#include "util.hxx"
#ifdef OSG_LIBRARY_STATIC
#include "osgDB/Registry"
#endif
using std::vector;
// Originally written by Alex Perry.
double
fgGetLowPass (double current, double target, double timeratio)
{
if ( timeratio < 0.0 ) {
if ( timeratio < -1.0 ) {
// time went backwards; kill the filter
current = target;
} else {
// ignore mildly negative time
}
} else if ( timeratio < 0.2 ) {
// Normal mode of operation; fast
// approximation to exp(-timeratio)
current = current * (1.0 - timeratio) + target * timeratio;
} else if ( timeratio > 5.0 ) {
// Huge time step; assume filter has settled
current = target;
} else {
// Moderate time step; non linear response
double keep = exp(-timeratio);
current = current * keep + target * (1.0 - keep);
}
return current;
}
/**
* Allowed paths here are absolute, and may contain _one_ *,
* which matches any string
*/
void fgInitAllowedPaths()
{
if(SGPath("ygjmyfvhhnvdoesnotexist").realpath().utf8Str() == "ygjmyfvhhnvdoesnotexist"){
// Abort in case this is used with older versions of realpath()
// that don't normalize non-existent files, as that would be a security
// hole.
flightgear::fatalMessageBoxThenExit(
"Nasal initialization error",
"Version mismatch - please update simgear");
}
SGPath::clearListOfAllowedPaths(false); // clear list of read-allowed paths
SGPath::clearListOfAllowedPaths(true); // clear list of write-allowed paths
PathList read_paths = globals->get_extra_read_allowed_paths();
read_paths.push_back(globals->get_fg_root());
read_paths.push_back(globals->get_fg_home());
for (const auto& path: read_paths) {
// If we get the initialization order wrong, better to have an
// obvious error than a can-read-everything security hole...
if (path.isNull()) {
flightgear::fatalMessageBoxThenExit(
"Nasal initialization error",
"Empty string in FG_ROOT, FG_HOME, FG_AIRCRAFT, FG_SCENERY or "
"--allow-nasal-read, or fgInitAllowedPaths() called too early");
}
SGPath::addAllowedPathPattern(path.realpath().utf8Str() + "/*",
false /* write */);
SGPath::addAllowedPathPattern(path.realpath().utf8Str(), false);
}
const std::string fg_home = globals->get_fg_home().realpath().utf8Str();
SGPath::addAllowedPathPattern(fg_home + "/*.sav", true /* write */);
SGPath::addAllowedPathPattern(fg_home + "/*.log", true);
SGPath::addAllowedPathPattern(fg_home + "/cache/*", true);
SGPath::addAllowedPathPattern(fg_home + "/Export/*", true);
SGPath::addAllowedPathPattern(fg_home + "/state/*.xml", true);
SGPath::addAllowedPathPattern(fg_home + "/aircraft-data/*.xml", true);
SGPath::addAllowedPathPattern(fg_home + "/Wildfire/*.xml", true);
SGPath::addAllowedPathPattern(fg_home + "/runtime-jetways/*.xml", true);
SGPath::addAllowedPathPattern(fg_home + "/Input/Joysticks/*.xml", true);
// Check that it works
const std::string homePath = globals->get_fg_home().utf8Str();
if (! SGPath(homePath + "/../no.log").validate(true).isNull() ||
! SGPath(homePath + "/no.logt").validate(true).isNull() ||
! SGPath(homePath + "/nolog").validate(true).isNull() ||
! SGPath(homePath + "no.log").validate(true).isNull() ||
! SGPath(homePath + "\\..\\no.log").validate(false).isNull() ||
SGPath(homePath + "/aircraft-data/yes..xml").validate(true).isNull() ||
SGPath(homePath + "/.\\yes.bmp").validate(false).isNull()) {
flightgear::fatalMessageBoxThenExit(
"Nasal initialization error",
"The FG_HOME directory must not be inside any of the FG_ROOT, "
"FG_AIRCRAFT, FG_SCENERY or --allow-nasal-read directories",
"(check that you have not accidentally included an extra ':', "
"as an empty part means the current directory)");
}
}
std::string generateAuthorsText(SGPropertyNode* authors)
{
std::string result;
for (auto a : authors->getChildren("author")) {
const std::string name = a->getStringValue("name");
if (name.empty())
continue;
if (!result.empty())
result += ", ";
result += a->getStringValue("name");
}
return result;
}
std::string flightgear::getAircraftAuthorsText()
{
const auto authorsNode = fgGetNode("sim/authors");
if (authorsNode) {
// we have structured authors data
return generateAuthorsText(authorsNode);
}
// if we hit this point, there is no strucutred authors data
return fgGetString("/sim/author");
}
// end of util.cxx

55
src/Main/util.hxx Normal file
View File

@@ -0,0 +1,55 @@
// util.hxx - general-purpose utility functions.
// Copyright (C) 2002 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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 __UTIL_HXX
#define __UTIL_HXX 1
#include <string>
#include <simgear/misc/sg_path.hxx>
/**
* Move a value towards a target.
*
* This function was originally written by Alex Perry.
*
* @param current The current value.
* @param target The target value.
* @param timeratio The percentage of smoothing time that has passed
* (elapsed time/smoothing time)
* @return The new value.
*/
double fgGetLowPass (double current, double target, double timeratio);
/**
* Set the read and write lists of allowed paths patterns for SGPath::validate()
*/
void fgInitAllowedPaths();
namespace flightgear
{
/**
* @brief getAircraftAuthorsText - get the aircraft authors as a single
* string value. This will combine the new (structured) authors data if
* present, otherwise just return the old plain string
* @return
*/
std::string getAircraftAuthorsText();
}
#endif // __UTIL_HXX