FlightGear next
fg_commands.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: fg_commands.hxx
3 * SPDX-FileComment: built-in commands for FlightGear.
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include <config.h>
8
9#include <string.h> // strcmp()
10
11#include <simgear/compiler.h>
12
13#include <fstream>
14#include <optional>
15#include <string>
16#include <vector>
17
18#include <simgear/debug/ErrorReportingCallback.hxx>
19#include <simgear/debug/logstream.hxx>
20#include <simgear/io/iostreams/sgstream.hxx>
21#include <simgear/math/sg_random.hxx>
22#include <simgear/props/props.hxx>
23#include <simgear/props/props_io.hxx>
24#include <simgear/sg_inlines.h>
25#include <simgear/structure/commands.hxx>
26#include <simgear/structure/event_mgr.hxx>
27#include <simgear/structure/exception.hxx>
28#include <simgear/timing/sg_time.hxx>
29
31
32#include <FDM/flight.hxx>
33#include <Aircraft/replay.hxx>
37#include <Viewer/viewmgr.hxx>
38#include <Viewer/view.hxx>
39#include <Viewer/VRManager.hxx>
42#include <GUI/gui.h>
44
45#include "fg_init.hxx"
46#include "fg_io.hxx"
47#include "fg_os.hxx"
48#include "fg_commands.hxx"
49#include "fg_props.hxx"
50#include "globals.hxx"
51#include "logger.hxx"
52#include "main.hxx"
53#include "positioninit.hxx"
54
55#if FG_HAVE_GPERFTOOLS
56# include <google/profiler.h>
57#endif
58
59using std::string;
60using std::ifstream;
61using std::ofstream;
62
63
65// Static helper functions.
67
68
69static inline SGPropertyNode *
70get_prop (const SGPropertyNode * arg, SGPropertyNode * root)
71{
72 if (root != nullptr)
73 {
74 SGPropertyNode *rv = nullptr;
75 rv = root->getNode(arg->getStringValue("property[0]", "/null"), true);
76 if (rv == nullptr)
77 {
78 rv = root->getNode(arg->getStringValue("property[0]", "/null"), true);
79 return fgGetNode(arg->getStringValue("property[0]", "/null"), true);
80 }
81 return rv;
82 }
83 return fgGetNode(arg->getStringValue("property[0]", "/null"), true);
84}
85
86static inline SGPropertyNode *
87get_prop2 (const SGPropertyNode * arg, SGPropertyNode * root)
88{
89 if (root != nullptr)
90 {
91 SGPropertyNode *rv = nullptr;
92 rv = root->getNode(arg->getStringValue("property[1]", "/null"), true);
93 if (rv == nullptr)
94 {
95 rv = root->getNode(arg->getStringValue("property[1]", "/null"), true);
96 return fgGetNode(arg->getStringValue("property[1]", "/null"), true);
97 }
98 return rv;
99 }
100 return fgGetNode(arg->getStringValue("property[1]", "/null"), true);
101}
102
103
107static void
108split_value (double full_value, const char * mask,
109 double * unmodifiable, double * modifiable)
110{
111 if (!strcmp("integer", mask)) {
112 *modifiable = (full_value < 0 ? ceil(full_value) : floor (full_value));
113 *unmodifiable = full_value - *modifiable;
114 } else if (!strcmp("decimal", mask)) {
115 *unmodifiable = (full_value < 0 ? ceil(full_value) : floor(full_value));
116 *modifiable = full_value - *unmodifiable;
117 } else {
118 if (strcmp("all", mask))
119 SG_LOG(SG_GENERAL, SG_ALERT, "Bad value " << mask << " for mask;"
120 << " assuming 'all'");
121 *unmodifiable = 0;
122 *modifiable = full_value;
123 }
124}
125
135template <class T>
136static std::optional<T>
137getValueIndirect(const SGPropertyNode* node, const std::string& name, const std::string& indirectName = {})
138{
139 auto indirectNode = node->getChild(indirectName.empty() ? (name + "-prop") : indirectName);
140 if (indirectNode) {
141 const auto resolvedNode = fgGetNode(indirectNode->getStringValue());
142 if (resolvedNode) {
143 return resolvedNode->getValue<T>();
144 }
145
146 // if the path is not valid, warn
147 SG_LOG(SG_GENERAL, SG_DEV_WARN, "getValueIndirect: property:" << indirectNode->getNameString() << " has value '" << indirectNode->getStringValue() << "' which was not found in the global property tree. Falling back to "
148 "value defined by argument '"
149 << name << "'");
150
151 // deliberate fall through here, so we use the value from the direct prop
152 }
153
154 auto directNode = node->getChild(name);
155 if (directNode)
156 return directNode->getValue<T>();
157
158 return {};
159}
160
164static double
165limit_value(double value, const SGPropertyNode* arg)
166{
167 const auto minv = getValueIndirect<double>(arg, "min");
168 const auto maxv = getValueIndirect<double>(arg, "max");
169
170 // we can wrap only if requested, and
171 const bool canWrap = (minv && maxv);
172 const bool wantsWrap = arg->getBoolValue("wrap");
173 const bool wrap = canWrap && wantsWrap;
174 if (wantsWrap && !canWrap) {
175 SG_LOG(SG_GENERAL, SG_DEV_WARN, "limit_value: wrap requested, but no min/max values defined");
176 }
177
178 if (wrap) { // wrap such that min <= x < max
179 const double resolution = arg->getDoubleValue("resolution");
180 if (resolution > 0.0) {
181 // snap to (min + N*resolution), taking special care to handle imprecision
182 int n = (int)floor((value - minv.value()) / resolution + 0.5);
183 int steps = (int)floor((maxv.value() - minv.value()) / resolution + 0.5);
184 SG_NORMALIZE_RANGE(n, 0, steps);
185 return minv.value() + resolution * n;
186 } else {
187 // plain circular wrapping
188 return SGMiscd::normalizePeriodic(minv.value(), maxv.value(), value);
189 }
190 } else { // clamp such that min <= x <= max
191 if (minv && (value < minv.value())) {
192 return minv.value();
193 }
194
195 if (maxv && (value > maxv.value())) {
196 return maxv.value();
197 }
198 }
199
200 return value; // return unmodified value
201}
202
203static bool
204compare_values (SGPropertyNode * value1, SGPropertyNode * value2)
205{
206 switch (value1->getType()) {
207 case simgear::props::BOOL:
208 return (value1->getBoolValue() == value2->getBoolValue());
209 case simgear::props::INT:
210 return (value1->getIntValue() == value2->getIntValue());
211 case simgear::props::LONG:
212 return (value1->getLongValue() == value2->getLongValue());
213 case simgear::props::FLOAT:
214 return (value1->getFloatValue() == value2->getFloatValue());
215 case simgear::props::DOUBLE:
216 return (value1->getDoubleValue() == value2->getDoubleValue());
217 default:
218 return value1->getStringValue() == value2->getStringValue();
219 }
220}
221
222
223
225// Command implementations.
227
228
232static bool
233do_null (const SGPropertyNode * arg, SGPropertyNode * root)
234{
235 return true;
236}
237
241static bool
242do_nasal (const SGPropertyNode * arg, SGPropertyNode * root)
243{
244 auto nasalSys = globals->get_subsystem<FGNasalSys>();
245 if (!nasalSys) {
246 SG_LOG(SG_GUI, SG_ALERT, "do_nasal command: Nasal subsystem not found");
247 return false;
248 }
249
250 return nasalSys->handleCommand(arg, root);
251}
252
256static bool
257do_replay (const SGPropertyNode * arg, SGPropertyNode * root)
258{
259 auto r = globals->get_subsystem<FGReplay>();
260 return r->start();
261}
262
266static bool
267do_pause (const SGPropertyNode * arg, SGPropertyNode * root)
268{
269 bool forcePause = arg->getBoolValue("force-pause", false );
270 bool forcePlay = arg->getBoolValue("force-play", false );
271
272 bool paused = fgGetBool("/sim/freeze/master",true) || fgGetBool("/sim/freeze/clock",true);
273
274 if(forcePause) paused = false;
275 if(forcePlay) paused = true;
276
277 if (paused && (fgGetInt("/sim/freeze/replay-state",0)>0))
278 {
279 do_replay(NULL, nullptr);
280 }
281 else
282 {
283 fgSetBool("/sim/freeze/master",!paused);
284 fgSetBool("/sim/freeze/clock",!paused);
285 }
286
288 return true;
289}
290
297static bool
298do_load (const SGPropertyNode * arg, SGPropertyNode * root)
299{
300 SGPath file(arg->getStringValue("file", "fgfs.sav").c_str());
301
302 if (file.extension() != "sav")
303 file.concat(".sav");
304
305 const SGPath validated_path = SGPath(file).validate(false);
306 if (validated_path.isNull()) {
307 SG_LOG(SG_IO, SG_ALERT, "load: reading '" << file << "' denied "
308 "(unauthorized access)");
309 return false;
310 }
311
312 sg_ifstream input(validated_path);
313 if (input.good() && fgLoadFlight(input)) {
314 input.close();
315 SG_LOG(SG_INPUT, SG_INFO, "Restored flight from " << file);
316 return true;
317 } else {
318 SG_LOG(SG_INPUT, SG_WARN, "Cannot load flight from " << file);
319 return false;
320 }
321}
322
323
330static bool
331do_save (const SGPropertyNode * arg, SGPropertyNode * root)
332{
333 SGPath file(arg->getStringValue("file", "fgfs.sav").c_str());
334
335 if (file.extension() != "sav")
336 file.concat(".sav");
337
338 const SGPath validated_path = SGPath(file).validate(true);
339 if (validated_path.isNull()) {
340 SG_LOG(SG_IO, SG_ALERT, "save: writing '" << file << "' denied "
341 "(unauthorized access)");
342 return false;
343 }
344
345 bool write_all = arg->getBoolValue("write-all", false);
346 SG_LOG(SG_INPUT, SG_INFO, "Saving flight");
347 sg_ofstream output(validated_path);
348 if (output.good() && fgSaveFlight(output, write_all)) {
349 output.close();
350 SG_LOG(SG_INPUT, SG_INFO, "Saved flight to " << file);
351 return true;
352 } else {
353 SG_LOG(SG_INPUT, SG_ALERT, "Cannot save flight to " << file);
354 return false;
355 }
356}
357
362static bool
363do_save_tape (const SGPropertyNode * arg, SGPropertyNode * root)
364{
365 auto replay = globals->get_subsystem<FGReplay>();
366 replay->saveTape(arg);
367
368 return true;
369}
370
374static bool
375do_load_tape (const SGPropertyNode * arg, SGPropertyNode * root)
376{
377 auto replay = globals->get_subsystem<FGReplay>();
378 replay->loadTape(arg);
379
380 return true;
381}
382
383static void
384do_view_next(bool do_it)
385{
386 // Only switch view if really requested to do so (and not for example while
387 // reset/reposition where /command/view/next is set to false)
388 if( do_it )
389 {
390 globals->get_current_view()->setHeadingOffset_deg(0.0);
391 globals->get_viewmgr()->next_view();
392 }
393}
394
395static void
396do_view_prev(bool do_it)
397{
398 if( do_it )
399 {
400 globals->get_current_view()->setHeadingOffset_deg(0.0);
401 globals->get_viewmgr()->prev_view();
402 }
403}
404
408static bool
409do_view_cycle (const SGPropertyNode * arg, SGPropertyNode * root)
410{
411 globals->get_current_view()->setHeadingOffset_deg(0.0);
412 globals->get_viewmgr()->next_view();
413 return true;
414}
415
416
420static bool
421do_view_push (const SGPropertyNode * arg, SGPropertyNode * root)
422{
423 SG_LOG(SG_GENERAL, SG_DEBUG, "do_view_push() called");
424 globals->get_viewmgr()->view_push();
425 return true;
426}
427
428
432static bool
433do_view_clone (const SGPropertyNode * arg, SGPropertyNode * root)
434{
435 SG_LOG(SG_GENERAL, SG_DEBUG, "do_view_clone() called");
436 globals->get_viewmgr()->clone_current_view(arg);
437 return true;
438}
439
440
444static bool
445do_view_last_pair (const SGPropertyNode * arg, SGPropertyNode * root)
446{
447 SG_LOG(SG_GENERAL, SG_DEBUG, "do_view_last_pair() called");
448 globals->get_viewmgr()->clone_last_pair(arg);
449 return true;
450}
451
452
456static bool
457do_view_last_pair_double (const SGPropertyNode * arg, SGPropertyNode * root)
458{
459 SG_LOG(SG_GENERAL, SG_DEBUG, "do_view_last_pair_double() called");
460 globals->get_viewmgr()->clone_last_pair_double(arg);
461 return true;
462}
463
464
468static bool
469do_view_new (const SGPropertyNode * arg, SGPropertyNode * root)
470{
471 SG_LOG(SG_GENERAL, SG_ALERT, "do_view_new() called");
472 globals->get_viewmgr()->view_new(arg);
473 return true;
474}
475
491static bool
492do_video_start (const SGPropertyNode * arg, SGPropertyNode * root)
493{
494 auto view_mgr = globals->get_subsystem<FGViewMgr>();
495 if (!view_mgr) return false;
496 view_mgr->video_start(
497 arg->getStringValue("name"),
498 arg->getStringValue("codec"),
499 arg->getDoubleValue("quality", -1),
500 arg->getDoubleValue("speed", -1),
501 arg->getIntValue("bitrate", 0)
502 );
503 return true;
504}
505
509static bool
510do_video_stop (const SGPropertyNode * arg, SGPropertyNode * root)
511{
512 auto view_mgr = globals->get_subsystem<FGViewMgr>();
513 if (!view_mgr) return false;
514 view_mgr->video_stop();
515 return true;
516}
517
523static bool
524do_property_toggle (const SGPropertyNode * arg, SGPropertyNode * root)
525{
526 SGPropertyNode * prop = get_prop(arg, root);
527 return prop->setBoolValue(!prop->getBoolValue());
528}
529
530
538static bool
539do_property_assign (const SGPropertyNode * arg, SGPropertyNode * root)
540{
541 SGPropertyNode * prop = get_prop(arg,root);
542 const SGPropertyNode * value = arg->getNode("value");
543
544 if (value != 0)
545 return prop->setUnspecifiedValue(value->getStringValue());
546 else
547 {
548 const SGPropertyNode * prop2 = get_prop2(arg,root);
549 if (prop2)
550 return prop->setUnspecifiedValue(prop2->getStringValue());
551 else
552 return false;
553 }
554}
555
556
577static bool
578do_property_adjust (const SGPropertyNode * arg, SGPropertyNode * root)
579{
580 SGPropertyNode * prop = get_prop(arg,root);
581
582 double amount = 0;
583 if (arg->hasValue("step"))
584 amount = arg->getDoubleValue("step");
585 else
586 amount = (arg->getDoubleValue("factor", 1.0)
587 * arg->getDoubleValue("offset"));
588
589 double unmodifiable, modifiable;
590 split_value(prop->getDoubleValue(), arg->getStringValue("mask", "all").c_str(),
591 &unmodifiable, &modifiable);
592 modifiable += amount;
593 modifiable = limit_value(modifiable, arg);
594
595 prop->setDoubleValue(unmodifiable + modifiable);
596
597 return true;
598}
599
600
615static bool
616do_property_multiply (const SGPropertyNode * arg, SGPropertyNode * root)
617{
618 SGPropertyNode * prop = get_prop(arg,root);
619 auto factorValue = getValueIndirect<double>(arg, "factor");
620 if (!factorValue) {
621 SG_LOG(SG_GENERAL, SG_DEV_WARN, "property-multiply: missing factor/factor-prop argument");
622 return false;
623 }
624
625 double unmodifiable, modifiable;
626 split_value(prop->getDoubleValue(), arg->getStringValue("mask", "all").c_str(),
627 &unmodifiable, &modifiable);
628 modifiable *= factorValue.value();
629 modifiable = limit_value(modifiable, arg);
630
631 prop->setDoubleValue(unmodifiable + modifiable);
632
633 return true;
634}
635
636
643static bool
644do_property_swap (const SGPropertyNode * arg, SGPropertyNode * root)
645{
646 SGPropertyNode * prop1 = get_prop(arg,root);
647 SGPropertyNode * prop2 = get_prop2(arg,root);
648
649 // FIXME: inefficient
650 const string & tmp = prop1->getStringValue();
651 return (prop1->setUnspecifiedValue(prop2->getStringValue()) &&
652 prop2->setUnspecifiedValue(tmp.c_str()));
653}
654
655
664static bool
665do_property_scale (const SGPropertyNode * arg, SGPropertyNode * root)
666{
667 SGPropertyNode * prop = get_prop(arg,root);
668 double setting = arg->getDoubleValue("setting");
669 double offset = arg->getDoubleValue("offset", 0.0);
670 double factor = arg->getDoubleValue("factor", 1.0);
671 bool squared = arg->getBoolValue("squared", false);
672 int power = arg->getIntValue("power", (squared ? 2 : 1));
673
674 int sign = (setting < 0 ? -1 : 1);
675
676 switch (power) {
677 case 1:
678 break;
679 case 2:
680 setting = setting * setting * sign;
681 break;
682 case 3:
683 setting = setting * setting * setting;
684 break;
685 case 4:
686 setting = setting * setting * setting * setting * sign;
687 break;
688 default:
689 setting = pow(setting, power);
690 if ((power % 2) == 0)
691 setting *= sign;
692 break;
693 }
694
695 return prop->setDoubleValue((setting + offset) * factor);
696}
697
698
708static bool
709do_property_cycle (const SGPropertyNode * arg, SGPropertyNode * root)
710{
711 SGPropertyNode * prop = get_prop(arg,root);
712 std::vector<SGPropertyNode_ptr> values = arg->getChildren("value");
713
714 bool wrap = arg->getBoolValue("wrap", true);
715 // compatible with knob/pick animations
716 int offset = arg->getIntValue("offset", 1);
717
718 int selection = -1;
719 int nSelections = values.size();
720
721 if (nSelections < 1) {
722 SG_LOG(SG_GENERAL, SG_ALERT, "No values for property-cycle");
723 return false;
724 }
725
726 // Try to find the current selection
727 for (int i = 0; i < nSelections; i++) {
728 if (compare_values(prop, values[i])) {
729 selection = i;
730 break;
731 }
732 }
733
734 if (selection < 0) { // default to first selection
735 selection = 0;
736 } else {
737 selection += offset;
738 if (wrap) {
739 selection = (selection + nSelections) % nSelections;
740 } else {
741 SG_CLAMP_RANGE(selection, 0, nSelections - 1);
742 }
743 }
744
745 prop->setUnspecifiedValue(values[selection]->getStringValue());
746 return true;
747}
748
749
757static bool
758do_property_randomize (const SGPropertyNode * arg, SGPropertyNode * root)
759{
760 SGPropertyNode * prop = get_prop(arg,root);
761 double min = arg->getDoubleValue("min", DBL_MIN);
762 double max = arg->getDoubleValue("max", DBL_MAX);
763 prop->setDoubleValue(sg_random() * (max - min) + min);
764 return true;
765}
766
785static bool
786do_property_interpolate (const SGPropertyNode * arg, SGPropertyNode * root)
787{
788 SGPropertyNode * prop = get_prop(arg,root);
789 if( !prop )
790 return false;
791
792 simgear::PropertyList time_nodes = arg->getChildren("time");
793 simgear::PropertyList rate_nodes = arg->getChildren("rate");
794
795 if( !time_nodes.empty() && !rate_nodes.empty() )
796 // mustn't specify time and rate
797 return false;
798
799 simgear::PropertyList::size_type num_times = time_nodes.empty()
800 ? rate_nodes.size()
801 : time_nodes.size();
802
803 simgear::PropertyList value_nodes = arg->getChildren("value");
804 if( value_nodes.empty() )
805 {
806 simgear::PropertyList prop_nodes = arg->getChildren("property");
807
808 // must have one more property node
809 if( prop_nodes.size() != num_times + 1 )
810 return false;
811
812 value_nodes.reserve(num_times);
813 for( size_t i = 1; i < prop_nodes.size(); ++i )
814 value_nodes.push_back( fgGetNode(prop_nodes[i]->getStringValue()) );
815 }
816
817 // must match
818 if( value_nodes.size() != num_times )
819 return false;
820
821 std::vector<double> deltas;
822 deltas.reserve(num_times);
823
824 if( !time_nodes.empty() )
825 {
826 for( size_t i = 0; i < num_times; ++i )
827 deltas.push_back( time_nodes[i]->getDoubleValue() );
828 }
829 else
830 {
831 for( size_t i = 0; i < num_times; ++i )
832 {
833 // TODO calculate delta based on property type
834 double delta = value_nodes[i]->getDoubleValue()
835 - ( i > 0
836 ? value_nodes[i - 1]->getDoubleValue()
837 : prop->getDoubleValue()
838 );
839 deltas.push_back( fabs(delta / rate_nodes[i]->getDoubleValue()) );
840 }
841 }
842
843 return prop->interpolate
844 (
845 arg->getStringValue("type", "numeric"),
846 value_nodes,
847 deltas,
848 arg->getStringValue("easing", "linear")
849 );
850}
851
856static bool
857do_data_logging_commit (const SGPropertyNode * arg, SGPropertyNode * root)
858{
859 auto log = globals->get_subsystem<FGLogger>();
860 log->reinit();
861 return true;
862}
863
867static bool
868do_log_level (const SGPropertyNode * arg, SGPropertyNode * root)
869{
870 sglog().setLogLevels( SG_ALL, (sgDebugPriority)arg->getIntValue() );
871
872 return true;
873}
874
886
887static bool
888do_load_xml_to_proptree(const SGPropertyNode * arg, SGPropertyNode * root)
889{
890 SGPath file(arg->getStringValue("filename"));
891 if (file.isNull())
892 return false;
893
894 if (file.extension() != "xml")
895 file.concat(".xml");
896
897 // some Nasal uses loadxml to also speculatively probe for existence of
898 // files. This flag allows us not to be noisy in the logs, in that case.
899 const bool quiet = arg->getBoolValue("quiet", false);
900
901 std::string icao = arg->getStringValue("icao");
902 if (icao.empty()) {
903 if (file.isRelative()) {
904 SGPath absPath = globals->resolve_maybe_aircraft_path(file.utf8Str());
905 if (!absPath.isNull())
906 file = absPath;
907 else
908 {
909 if (!quiet) {
910 SG_LOG(SG_IO, SG_ALERT, "loadxml: Cannot find XML property file '" << file << "'.");
911 simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::XMLLoadCommand,
912 "loadxml: no such file:" + file.utf8Str(), file);
913 }
914 return false;
915 }
916 }
917 } else {
918 if (!XMLLoader::findAirportData(icao, file.utf8Str(), file)) {
919 if (!quiet) {
920 SG_LOG(SG_IO, SG_INFO, "loadxml: failed to find airport data for " << file << " at ICAO:" << icao);
921 simgear::reportFailure(simgear::LoadFailure::NotFound, simgear::ErrorCode::XMLLoadCommand,
922 "loadxml: no airport data file for:" + icao, file);
923 }
924 return false;
925 }
926 }
927
928 if (!file.exists()) {
929 if (!quiet) {
930 SG_LOG(SG_IO, SG_WARN, "loadxml: no such file:" << file);
931 }
932 return false;
933 }
934
935 const SGPath validated_path = SGPath(file).validate(false);
936 if (validated_path.isNull()) {
937 SG_LOG(SG_IO, quiet ? SG_DEV_WARN : SG_ALERT, "loadxml: reading '" << file << "' denied "
938 "(unauthorized directory - authorization no longer follows symlinks; to authorize reading additional directories, pass them to --allow-nasal-read)");
939 return false;
940 }
941
942 SGPropertyNode *targetnode;
943 if (arg->hasValue("targetnode"))
944 targetnode = fgGetNode(arg->getStringValue("targetnode"), true);
945 else
946 targetnode = const_cast<SGPropertyNode *>(arg)->getNode("data", true);
947
948 try {
949 readProperties(validated_path, targetnode, true);
950 } catch (const sg_exception &e) {
951 if (!quiet) {
952 simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::XMLLoadCommand,
953 "loadxml exception:" + e.getFormattedMessage(), e.getLocation());
954 }
955 SG_LOG(SG_IO, quiet ? SG_DEV_WARN : SG_WARN, "loadxml exception: " << e.getFormattedMessage());
956 return false;
957 }
958
959 return true;
960}
961
962static bool
963do_load_xml_from_url(const SGPropertyNode * arg, SGPropertyNode * root)
964{
965 auto http = globals->get_subsystem<FGHTTPClient>();
966 if (!http) {
967 SG_LOG(SG_IO, SG_ALERT, "xmlhttprequest: HTTP client not running");
968 return false;
969 }
970
971 std::string url(arg->getStringValue("url"));
972 if (url.empty())
973 return false;
974
975 SGPropertyNode *targetnode;
976 if (arg->hasValue("targetnode"))
977 targetnode = fgGetNode(arg->getStringValue("targetnode"), true);
978 else
979 targetnode = const_cast<SGPropertyNode *>(arg)->getNode("data", true);
980
981 RemoteXMLRequest* req = new RemoteXMLRequest(url, targetnode);
982
983 if (arg->hasChild("body"))
984 req->setBodyData(arg->getChild("body"));
985
986// connect up optional reporting properties
987 if (arg->hasValue("complete"))
988 req->setCompletionProp(fgGetNode(arg->getStringValue("complete"), true));
989 if (arg->hasValue("failure"))
990 req->setFailedProp(fgGetNode(arg->getStringValue("failure"), true));
991 if (arg->hasValue("status"))
992 req->setStatusProp(fgGetNode(arg->getStringValue("status"), true));
993
994 http->makeRequest(req);
995 return true;
996}
997
998
1011
1012static bool
1013do_save_xml_from_proptree(const SGPropertyNode * arg, SGPropertyNode * root)
1014{
1015 SGPath file(arg->getStringValue("filename"));
1016 if (file.isNull())
1017 return false;
1018
1019 if (file.extension() != "xml")
1020 file.concat(".xml");
1021
1022 const SGPath validated_path = SGPath(file).validate(true);
1023 if (validated_path.isNull()) {
1024 SG_LOG(SG_IO, SG_ALERT, "savexml: writing to '" << file << "' denied "
1025 "(unauthorized directory - authorization no longer follows symlinks)");
1026 return false;
1027 }
1028
1029 SGPropertyNode *sourcenode;
1030 if (arg->hasValue("sourcenode"))
1031 sourcenode = fgGetNode(arg->getStringValue("sourcenode"), true);
1032 else if (arg->getNode("data", false))
1033 sourcenode = const_cast<SGPropertyNode *>(arg)->getNode("data");
1034 else
1035 return false;
1036
1037 try {
1038 writeProperties (validated_path, sourcenode, true);
1039 } catch (const sg_exception &e) {
1040 SG_LOG(SG_IO, SG_WARN, "savexml: " << e.getFormattedMessage());
1041 return false;
1042 }
1043
1044 return true;
1045}
1046
1047// Optional profiling commands using gperftools:
1048// http://code.google.com/p/gperftools/
1049
1050#if !FG_HAVE_GPERFTOOLS
1051static void
1053{
1054 SG_LOG
1055 (
1056 SG_GENERAL,
1057 SG_ALERT,
1058 "No profiling support! Install gperftools and reconfigure/rebuild fgfs."
1059 );
1060}
1061#endif
1062
1063static bool
1064do_profiler_start(const SGPropertyNode *arg, SGPropertyNode *root)
1065{
1066#if FG_HAVE_GPERFTOOLS
1067 const char *filename = arg->getStringValue("filename", "fgfs.profile");
1068 ProfilerStart(filename);
1069 return true;
1070#else
1072 return false;
1073#endif
1074}
1075
1076static bool
1077do_profiler_stop(const SGPropertyNode *arg, SGPropertyNode *root)
1078{
1079#if FG_HAVE_GPERFTOOLS
1080 ProfilerStop();
1081 return true;
1082#else
1084 return false;
1085#endif
1086}
1087
1088static bool do_reload_nasal_module(const SGPropertyNode* arg, SGPropertyNode*)
1089{
1090 auto nasalSys = globals->get_subsystem<FGNasalSys>();
1091 if (!nasalSys) {
1092 SG_LOG(SG_GUI, SG_ALERT, "reloadModuleFromFile command: Nasal subsystem not found");
1093 return false;
1094 }
1095
1096 return nasalSys->reloadModuleFromFile(arg->getStringValue("module"));
1097}
1098
1099// VR related commands
1100
1101#ifndef ENABLE_OSGXR
1102
1103static bool
1104no_vr_support(const SGPropertyNode* arg, SGPropertyNode* root)
1105{
1106 SG_LOG(SG_GENERAL, SG_ALERT,
1107 "No VR support! Rebuild fgfs with VR enabled.");
1108 return false;
1109}
1110
1111#define do_vr_recenter no_vr_support
1112
1113#else // ENABLE_OSGXR
1114
1115static bool
1116do_vr_recenter(const SGPropertyNode* arg, SGPropertyNode* root)
1117{
1118 return flightgear::VRManager::instance()->recenter();
1119}
1120
1121#endif // ENABLE_OSGXR
1122
1123
1125// Command setup.
1127
1128
1135static struct {
1136 const char * name;
1137 SGCommandMgr::command_t command;
1138} built_ins[] = {
1139 {"null", do_null},
1140 {"nasal", do_nasal},
1141 {"nasal-reload", do_reload_nasal_module}, // avoid conflict with modules.nas which defines 'nasal-module-reload'
1142 {"pause", do_pause},
1143 {"load", do_load},
1144 {"save", do_save},
1145 {"save-tape", do_save_tape},
1146 {"load-tape", do_load_tape},
1147 {"view-cycle", do_view_cycle},
1148 {"view-push", do_view_push},
1149 {"view-clone", do_view_clone},
1150 {"view-last-pair", do_view_last_pair},
1151 {"view-last-pair-double", do_view_last_pair_double},
1152 {"view-new", do_view_new},
1153 /*
1154 { "set-sea-level-air-temp-degc", do_set_sea_level_degc },
1155 { "set-outside-air-temp-degc", do_set_oat_degc },
1156 { "set-dewpoint-sea-level-air-temp-degc", do_set_dewpoint_sea_level_degc },
1157 { "set-dewpoint-temp-degc", do_set_dewpoint_degc },
1158 */
1159 {"property-toggle", do_property_toggle},
1160 {"property-assign", do_property_assign},
1161 {"property-adjust", do_property_adjust},
1162 {"property-multiply", do_property_multiply},
1163 {"property-swap", do_property_swap},
1164 {"property-scale", do_property_scale},
1165 {"property-cycle", do_property_cycle},
1166 {"property-randomize", do_property_randomize},
1167 {"property-interpolate", do_property_interpolate},
1168 {"data-logging-commit", do_data_logging_commit},
1169 {"log-level", do_log_level},
1170 {"replay", do_replay},
1171 /*
1172 { "decrease-visibility", do_decrease_visibility },
1173 { "increase-visibility", do_increase_visibility },
1174 */
1175 {"loadxml", do_load_xml_to_proptree},
1176 {"savexml", do_save_xml_from_proptree},
1177 {"xmlhttprequest", do_load_xml_from_url},
1178
1179 {"profiler-start", do_profiler_start},
1180 {"profiler-stop", do_profiler_stop},
1181
1182 {"video-start", do_video_start},
1183 {"video-stop", do_video_stop},
1184
1185 {"vr-recenter", do_vr_recenter},
1186
1187 {0, 0} // zero-terminated
1189
1190
1196void
1198{
1199 // set our property root as the implicit default root for the
1200 // command managr
1201 SGCommandMgr::instance()->setImplicitRoot(globals->get_props());
1202
1203 SG_LOG(SG_GENERAL, SG_BULK, "Initializing basic built-in commands:");
1204 for (int i = 0; built_ins[i].name != 0; i++) {
1205 SG_LOG(SG_GENERAL, SG_BULK, " " << built_ins[i].name);
1206 globals->get_commands()->addCommand(built_ins[i].name,
1208 }
1209
1210 typedef bool (*dummy)();
1211 fgTie( "/command/view/next", dummy(0), do_view_next );
1212 fgTie( "/command/view/prev", dummy(0), do_view_prev );
1213
1214 globals->get_props()->setValueReadOnly( "/sim/debug/profiler-available",
1215 bool(FG_HAVE_GPERFTOOLS) );
1216}
1217
1218// end of fg_commands.cxx
#define min(X, Y)
static FGNasalSys * nasalSys
Definition NasalSys.cxx:82
#define i(x)
Log any property values to any number of CSV files.
Definition logger.hxx:21
void reinit() override
Definition logger.cxx:120
void video_stop()
Definition viewmgr.cxx:481
bool video_start(const std::string &name="", const std::string &codec="", double quality=-1, double speed=-1, int bitrate=0)
Definition viewmgr.cxx:364
void setStatusProp(SGPropertyNode_ptr p)
void setCompletionProp(SGPropertyNode_ptr p)
void setFailedProp(SGPropertyNode_ptr p)
static bool findAirportData(const std::string &aICAO, const std::string &aFileName, SGPath &aPath)
Search the scenery for a file name of the form: I/C/A/ICAO.filename.xml and return the corresponding ...
static bool do_property_swap(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: swap two property values.
static struct @067026177365276227015073311113211264353134373250 built_ins[]
Table of built-in commands.
static bool do_load_xml_from_url(const SGPropertyNode *arg, SGPropertyNode *root)
static void split_value(double full_value, const char *mask, double *unmodifiable, double *modifiable)
Get a double value and split it as required.
static bool do_property_randomize(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: randomize a numeric property value.
static bool do_view_clone(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: clone view.
static bool do_nasal(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: run a Nasal script.
static bool do_save(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: save flight.
static bool do_load_tape(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: load flight recorder tape.
static bool do_view_new(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: double view last pair.
static bool do_property_toggle(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: toggle a bool property value.
static double limit_value(double value, const SGPropertyNode *arg)
Clamp or wrap a value as specified.
static bool do_replay(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: replay the FDR buffer.
static bool do_view_push(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: view-push.
static bool do_video_start(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: video-start.
SGCommandMgr::command_t command
static bool do_save_xml_from_proptree(const SGPropertyNode *arg, SGPropertyNode *root)
An fgcommand to allow saving of xml files via nasal, the file's structure will be determined based on...
static bool do_view_last_pair_double(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: double view last pair.
static bool do_save_tape(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: save flight recorder tape.
static SGPropertyNode * get_prop2(const SGPropertyNode *arg, SGPropertyNode *root)
static bool do_reload_nasal_module(const SGPropertyNode *arg, SGPropertyNode *)
static void do_view_next(bool do_it)
static bool do_load_xml_to_proptree(const SGPropertyNode *arg, SGPropertyNode *root)
An fgcommand to allow loading of xml files via nasal, the xml file's structure will be made available...
static bool do_property_cycle(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: cycle a property through a set of values.
static bool do_data_logging_commit(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: reinit the data logging system based on the current contents of the /logger tree.
const char * name
static bool compare_values(SGPropertyNode *value1, SGPropertyNode *value2)
static bool do_property_adjust(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: increment or decrement a property value.
static bool do_null(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: do nothing.
static bool no_vr_support(const SGPropertyNode *arg, SGPropertyNode *root)
static void do_view_prev(bool do_it)
static bool do_pause(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: pause/unpause the sim.
static bool do_property_assign(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: assign a value to a property.
static bool do_profiler_stop(const SGPropertyNode *arg, SGPropertyNode *root)
static std::optional< T > getValueIndirect(const SGPropertyNode *node, const std::string &name, const std::string &indirectName={})
Retrive a typed value from a node, either from <foo> directly or indirectly.
static bool do_video_stop(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: video-stop.
static bool do_load(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: load flight.
static SGPropertyNode * get_prop(const SGPropertyNode *arg, SGPropertyNode *root)
static bool do_view_last_pair(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: view last pair.
static bool do_property_interpolate(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: interpolate a property value over time.
static bool do_property_scale(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: Set a property to an axis or other moving input.
#define do_vr_recenter
void fgInitCommands()
Initialize the default built-in commands.
static bool do_profiler_start(const SGPropertyNode *arg, SGPropertyNode *root)
static bool do_property_multiply(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: multiply a property value.
static bool do_view_cycle(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: cycle view.
static bool do_log_level(const SGPropertyNode *arg, SGPropertyNode *root)
Built-in command: set log level (0 ... 7)
static void no_profiling_support()
bool fgSaveFlight(std::ostream &output, bool write_all)
Save the current state of the simulator to a stream.
Definition fg_props.cxx:424
bool fgLoadFlight(std::istream &input)
Restore the current state of the simulator from a stream.
Definition fg_props.cxx:448
int fgGetInt(const char *name, int defaultValue)
Get an int value for a property.
Definition fg_props.cxx:532
void fgTie(const char *name, V(*getter)(), void(*setter)(V)=0, bool useDefault=true)
Tie a property to a pair of simple functions.
Definition fg_props.hxx:751
FGGlobals * globals
Definition globals.cxx:142
void syncPausePopupState()
synchronize /sim/freeze properties with visiblity of the popup-dialog which informs the user
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
Definition proptest.cpp:25
bool fgSetBool(char const *name, bool val)
Set a bool value for a property.
Definition proptest.cpp:24
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
bool replay(FGReplayInternal &self, double time)
Replay a saved frame based on time, interpolate from the two nearest saved frames.
bool start(bool new_tape=false)
Start replay session.
Definition replay.cxx:67