first commit
This commit is contained in:
120
src/Main/AircraftDirVisitorBase.hxx
Normal file
120
src/Main/AircraftDirVisitorBase.hxx
Normal 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
125
src/Main/CMakeLists.txt
Normal 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
944
src/Main/ErrorReporter.cxx
Normal 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, ¶m](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
|
||||
55
src/Main/ErrorReporter.hxx
Normal file
55
src/Main/ErrorReporter.hxx
Normal 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
|
||||
42
src/Main/FGInterpolator.cxx
Normal file
42
src/Main/FGInterpolator.cxx
Normal 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);
|
||||
34
src/Main/FGInterpolator.hxx
Normal file
34
src/Main/FGInterpolator.hxx
Normal 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
90
src/Main/README
Normal 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
144
src/Main/XLIFFParser.cxx
Normal 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
54
src/Main/XLIFFParser.hxx
Normal 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
429
src/Main/bootstrap.cxx
Executable 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
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
15
src/Main/fg_commands.hxx
Normal 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
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
103
src/Main/fg_init.hxx
Normal 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
615
src/Main/fg_io.cxx
Normal 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
84
src/Main/fg_io.hxx
Normal 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
96
src/Main/fg_os.hxx
Normal 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
57
src/Main/fg_os_common.cxx
Normal 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
740
src/Main/fg_props.cxx
Normal 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
854
src/Main/fg_props.hxx
Normal 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
|
||||
|
||||
578
src/Main/fg_scene_commands.cxx
Normal file
578
src/Main/fg_scene_commands.cxx
Normal 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
BIN
src/Main/flightgear.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
1
src/Main/flightgear.rc
Normal file
1
src/Main/flightgear.rc
Normal file
@@ -0,0 +1 @@
|
||||
FLIGHTGEAR ICON "flightgear.ico"
|
||||
990
src/Main/globals.cxx
Normal file
990
src/Main/globals.cxx
Normal 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
422
src/Main/globals.hxx
Normal 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
622
src/Main/locale.cxx
Normal 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
184
src/Main/locale.hxx
Normal 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
170
src/Main/logger.cxx
Normal 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
50
src/Main/logger.hxx
Normal 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
825
src/Main/main.cxx
Executable 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
37
src/Main/main.hxx
Normal 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
409
src/Main/metar_main.cxx
Normal 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
3303
src/Main/options.cxx
Normal file
File diff suppressed because it is too large
Load Diff
216
src/Main/options.hxx
Normal file
216
src/Main/options.hxx
Normal 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
872
src/Main/positioninit.cxx
Normal 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
39
src/Main/positioninit.hxx
Normal 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
23
src/Main/runfgfs.bat.in
Normal 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
89
src/Main/runfgfs.in
Executable 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");
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
69
src/Main/screensaver_control.cxx
Normal file
69
src/Main/screensaver_control.cxx
Normal 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
|
||||
}
|
||||
24
src/Main/screensaver_control.hxx
Normal file
24
src/Main/screensaver_control.hxx
Normal 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();
|
||||
577
src/Main/sentryIntegration.cxx
Normal file
577
src/Main/sentryIntegration.cxx
Normal 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
|
||||
53
src/Main/sentryIntegration.hxx
Normal file
53
src/Main/sentryIntegration.hxx
Normal 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
|
||||
|
||||
330
src/Main/subsystemFactory.cxx
Normal file
330
src/Main/subsystemFactory.cxx
Normal 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
|
||||
44
src/Main/subsystemFactory.hxx
Normal file
44
src/Main/subsystemFactory.hxx
Normal 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
166
src/Main/util.cxx
Normal 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
55
src/Main/util.hxx
Normal 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
|
||||
Reference in New Issue
Block a user