1 // options.cxx -- class to handle command line options
2 //
3 // Written by Curtis Olson, started April 1998.
4 //
5 // Copyright (C) 1998 Curtis L. Olson - http://www.flightgear.org/~curt
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 //
21 // $Id$
22
23
24 #include <config.h>
25
26 #include <simgear/compiler.h>
27 #include <simgear/structure/exception.hxx>
28 #include <simgear/debug/logstream.hxx>
29 #include <simgear/timing/sg_time.hxx>
30 #include <simgear/misc/sg_dir.hxx>
31
32 #include <3rdparty/cjson/cJSON.h>
33
34 #include <cmath> // rint()
35 #include <cstdio>
36 #include <cstdlib> // atof(), atoi()
37 #include <cstring> // strcmp()
38 #include <algorithm>
39 #include <map>
40
41 #include <iostream>
42 #include <string>
43 #include <sstream>
44
45 #include <simgear/math/sg_random.h>
46 #include <simgear/props/props_io.hxx>
47 #include <simgear/io/iostreams/sgstream.hxx>
48 #include <simgear/misc/sg_path.hxx>
49 #include <simgear/misc/sg_dir.hxx>
50 #include <simgear/scene/material/mat.hxx>
51 #include <simgear/sound/soundmgr.hxx>
52 #include <simgear/misc/strutils.hxx>
53 #include <Autopilot/route_mgr.hxx>
54 #include <Aircraft/replay.hxx>
55 #include <Aircraft/initialstate.hxx>
56
57 #include <GUI/gui.h>
58 #include <GUI/MessageBox.hxx>
59
60 #if defined(HAVE_QT)
61 #include <GUI/QtLauncher.hxx>
62 #include <GUI/SetupRootDialog.hxx>
63 #endif
64
65 #include <AIModel/AIManager.hxx>
66 #include <Add-ons/AddonManager.hxx>
67 #include <Main/locale.hxx>
68 #include <Navaids/NavDataCache.hxx>
69 #include "globals.hxx"
70 #include "fg_init.hxx"
71 #include "fg_props.hxx"
72 #include "options.hxx"
73 #include "util.hxx"
74 #include "main.hxx"
75 #include "locale.hxx"
76 #include <Viewer/view.hxx>
77 #include <Viewer/viewmgr.hxx>
78 #include <Environment/presets.hxx>
79 #include <Network/http/httpd.hxx>
80 #include "AircraftDirVisitorBase.hxx"
81
82 #include <osg/Version>
83 #include <Include/version.h>
84 #include <Include/build.h>
85 #include <simgear/version.h>
86
87 using std::string;
88 using std::sort;
89 using std::cout;
90 using std::cerr;
91 using std::endl;
92 using std::vector;
93 using std::cin;
94
95 using namespace flightgear;
96
97 #define NEW_DEFAULT_MODEL_HZ 120
98
99 static flightgear::Options* shared_instance = nullptr;
100
101 static double
atof(const string & str)102 atof( const string& str )
103 {
104 return ::atof( str.c_str() );
105 }
106
107 static int
atoi(const string & str)108 atoi( const string& str )
109 {
110 return ::atoi( str.c_str() );
111 }
112
113 static int fgSetupProxy( const char *arg );
114
115 /**
116 * Set a few fail-safe default property values.
117 *
118 * These should all be set in $FG_ROOT/defaults.xml, but just
119 * in case, we provide some initial sane values here. This method
120 * should be invoked *before* reading any init files.
121 */
fgSetDefaults()122 void fgSetDefaults ()
123 {
124
125 // Position (deliberately out of range)
126 fgSetDouble("/position/longitude-deg", 9999.0);
127 fgSetDouble("/position/latitude-deg", 9999.0);
128 fgSetDouble("/position/altitude-ft", -9999.0);
129
130 // Orientation
131 fgSetDouble("/orientation/heading-deg", 9999.0);
132 fgSetDouble("/orientation/roll-deg", 0.0);
133 fgSetDouble("/orientation/pitch-deg", 0.424);
134
135 // Velocities
136 fgSetDouble("/velocities/uBody-fps", 0.0);
137 fgSetDouble("/velocities/vBody-fps", 0.0);
138 fgSetDouble("/velocities/wBody-fps", 0.0);
139 fgSetDouble("/velocities/speed-north-fps", 0.0);
140 fgSetDouble("/velocities/speed-east-fps", 0.0);
141 fgSetDouble("/velocities/speed-down-fps", 0.0);
142 fgSetDouble("/velocities/airspeed-kt", 0.0);
143 fgSetDouble("/velocities/mach", 0.0);
144
145 // Presets
146 fgSetDouble("/sim/presets/longitude-deg", 9999.0);
147 fgSetDouble("/sim/presets/latitude-deg", 9999.0);
148 fgSetDouble("/sim/presets/altitude-ft", -9999.0);
149
150 fgSetDouble("/sim/presets/heading-deg", 9999.0);
151 fgSetDouble("/sim/presets/roll-deg", 0.0);
152 fgSetDouble("/sim/presets/pitch-deg", 0.424);
153
154 fgSetString("/sim/presets/speed-set", "knots");
155 fgSetDouble("/sim/presets/airspeed-kt", 0.0);
156 fgSetDouble("/sim/presets/mach", 0.0);
157 fgSetDouble("/sim/presets/uBody-fps", 0.0);
158 fgSetDouble("/sim/presets/vBody-fps", 0.0);
159 fgSetDouble("/sim/presets/wBody-fps", 0.0);
160 fgSetDouble("/sim/presets/speed-north-fps", 0.0);
161 fgSetDouble("/sim/presets/speed-east-fps", 0.0);
162 fgSetDouble("/sim/presets/speed-down-fps", 0.0);
163 fgSetDouble("/sim/presets/offset-distance-nm", 0.0);
164
165 fgSetBool("/sim/presets/runway-requested", false);
166
167 fgSetBool("/sim/presets/onground", true);
168 fgSetBool("/sim/presets/trim", false);
169
170 // Miscellaneous
171 fgSetBool("/sim/startup/splash-screen", true);
172 // we want mouse-pointer to have an undefined value if nothing is
173 // specified so we can do the right thing for voodoo-1/2 cards.
174 // fgSetString("/sim/startup/mouse-pointer", "disabled");
175 fgSetBool("/controls/flight/auto-coordination", false);
176 fgSetString("/sim/logging/priority", "alert");
177
178 // Features
179 fgSetBool("/sim/hud/color/antialiased", false);
180 fgSetBool("/sim/hud/enable3d[1]", true);
181 fgSetBool("/sim/hud/visibility[1]", false);
182 fgSetBool("/sim/panel/visibility", true);
183 fgSetBool("/sim/sound/enabled", true);
184 fgSetBool("/sim/sound/working", true);
185 fgSetBool("/sim/fgcom/enabled", false);
186
187 // Flight Model options
188 fgSetString("/sim/flight-model", "jsb");
189 fgSetString("/sim/aero", "c172");
190 fgSetInt("/sim/model-hz", NEW_DEFAULT_MODEL_HZ);
191 fgSetDouble("/sim/speed-up", 1.0);
192
193 // Scenery
194 fgSetString("/sim/scenery/engine", "tilecache");
195
196 // ( scenery = pagedLOD )
197 fgSetString("/sim/scenery/lod-levels", "1 3 5 7 9");
198 fgSetString("/sim/scenery/lod-res", "1");
199 fgSetString("/sim/scenery/lod-texturing", "bluemarble");
200
201 // Rendering options
202 fgSetString("/sim/rendering/fog", "nicest");
203 fgSetBool("/environment/clouds/status", true);
204 fgSetBool("/sim/startup/fullscreen", false);
205 fgSetBool("/sim/rendering/shading", true);
206 fgTie( "/sim/rendering/filtering", SGGetTextureFilter, SGSetTextureFilter, false);
207 fgSetInt("/sim/rendering/filtering", 1);
208 fgSetBool("/sim/rendering/wireframe", false);
209 fgSetBool("/sim/rendering/horizon-effect", false);
210 fgSetBool("/sim/rendering/distance-attenuation", false);
211 fgSetBool("/sim/rendering/specular-highlight", true);
212 fgSetString("/sim/rendering/materials-file", "materials.xml");
213 fgSetInt("/sim/startup/xsize", 1024);
214 fgSetInt("/sim/startup/ysize", 768);
215 fgSetInt("/sim/rendering/bits-per-pixel", 32);
216 fgSetString("/sim/view-mode", "pilot");
217 fgSetDouble("/sim/current-view/heading-offset-deg", 0);
218
219 // HUD options
220 fgSetString("/sim/startup/units", "feet");
221 fgSetString("/sim/hud/frame-stat-type", "tris");
222
223 // Time options
224 fgSetInt("/sim/startup/time-offset", 0);
225 fgSetString("/sim/startup/time-offset-type", "system-offset");
226 fgSetLong("/sim/time/cur-time-override", 0);
227
228 // Freeze options
229 fgSetBool("/sim/freeze/master", false);
230 fgSetBool("/sim/freeze/position", false);
231 fgSetBool("/sim/freeze/clock", false);
232 fgSetBool("/sim/freeze/fuel", false);
233
234 fgSetString("/sim/multiplay/callsign", "callsign");
235 fgSetString("/sim/multiplay/rxhost", "");
236 fgSetString("/sim/multiplay/txhost", "");
237 fgSetInt("/sim/multiplay/rxport", 0);
238 fgSetInt("/sim/multiplay/txport", 0);
239
240 SGPropertyNode* v = globals->get_props()->getNode("/sim/version", true);
241 v->setValueReadOnly("flightgear", FLIGHTGEAR_VERSION);
242 v->setValueReadOnly("simgear", SG_STRINGIZE(SIMGEAR_VERSION));
243 v->setValueReadOnly("openscenegraph", osgGetVersion());
244 #if OSG_VERSION_LESS_THAN(3,5,2)
245 v->setValueReadOnly("openscenegraph-thread-safe-reference-counting",
246 osg::Referenced::getThreadSafeReferenceCounting());
247 #endif
248 v->setValueReadOnly("revision", REVISION);
249 v->setValueReadOnly("build-number", JENKINS_BUILD_NUMBER);
250 v->setValueReadOnly("build-id", JENKINS_BUILD_ID);
251 v->setValueReadOnly("hla-support", bool(FG_HAVE_HLA));
252 v->setValueReadOnly("build-type", FG_BUILD_TYPE);
253 #ifdef ENABLE_COMPOSITOR
254 static constexpr bool compositor_support = true;
255 #else
256 static constexpr bool compositor_support = false;
257 #endif
258 v->setValueReadOnly("compositor-support", compositor_support);
259
260 char* envp = ::getenv( "http_proxy" );
261 if( envp != nullptr )
262 fgSetupProxy( envp );
263 }
264
265 ///////////////////////////////////////////////////////////////////////////////
266 // helper object to implement the --show-aircraft command.
267 // resides here so we can share the fgFindAircraftInDir template above,
268 // and hence ensure this command lists exectly the same aircraft as the normal
269 // loading path.
270 class ShowAircraft : public AircraftDirVistorBase
271 {
272 public:
ShowAircraft()273 ShowAircraft()
274 {
275 _minStatus = getNumMaturity(fgGetString("/sim/aircraft-min-status", "all"));
276 }
277
278
show(const vector<SGPath> & path_list)279 void show(const vector<SGPath> & path_list)
280 {
281 for (vector<SGPath>::const_iterator p = path_list.begin();
282 p != path_list.end(); ++p)
283 visitDir(*p, 0);
284
285 simgear::requestConsole(); // ensure console is shown on Windows
286
287 std::sort(_aircraft.begin(), _aircraft.end(), ciLessLibC());
288 cout << "Available aircraft:" << endl;
289 for ( unsigned int i = 0; i < _aircraft.size(); i++ ) {
290 cout << _aircraft[i] << endl;
291 }
292 }
293
294 private:
visit(const SGPath & path)295 virtual VisitResult visit(const SGPath& path)
296 {
297 SGPropertyNode root;
298 try {
299 readProperties(path, &root);
300 } catch (sg_exception& ) {
301 return VISIT_CONTINUE;
302 }
303
304 int maturity = 0;
305 string descStr(" ");
306 descStr += path.file();
307 // trim common suffix from file names
308 int nPos = descStr.rfind("-set.xml");
309 if (nPos == (int)(descStr.size() - 8)) {
310 descStr.resize(nPos);
311 }
312
313 SGPropertyNode *node = root.getNode("sim");
314 if (node) {
315 SGPropertyNode* desc = node->getNode("description");
316 // if a status tag is found, read it in
317 if (node->hasValue("status")) {
318 maturity = getNumMaturity(node->getStringValue("status"));
319 }
320
321 if (desc) {
322 if (descStr.size() <= 27+3) {
323 descStr.append(29+3-descStr.size(), ' ');
324 } else {
325 descStr += '\n';
326 descStr.append( 32, ' ');
327 }
328 descStr += desc->getStringValue();
329 }
330 } // of have 'sim' node
331
332 if (maturity >= _minStatus) {
333 _aircraft.push_back(descStr);
334 }
335
336 return VISIT_CONTINUE;
337 }
338
339
getNumMaturity(const char * str)340 int getNumMaturity(const char * str)
341 {
342 // Changes should also be reflected in $FG_ROOT/options.xml
343 const char* levels[] = {"alpha","beta","early-production","production"};
344
345 if (!strcmp(str, "all")) {
346 return 0;
347 }
348
349 for (size_t i=0; i<(sizeof(levels)/sizeof(levels[0]));i++)
350 if (strcmp(str,levels[i])==0)
351 return i;
352
353 return 0;
354 }
355
356 // recommended in Meyers, Effective STL when internationalization and embedded
357 // NULLs aren't an issue. Much faster than the STL or Boost lex versions.
358 struct ciLessLibC : public std::binary_function<string, string, bool>
359 {
operator ()ShowAircraft::ciLessLibC360 bool operator()(const std::string &lhs, const std::string &rhs) const
361 {
362 return strcasecmp(lhs.c_str(), rhs.c_str()) < 0 ? 1 : 0;
363 }
364 };
365
366 int _minStatus;
367 string_list _aircraft;
368 };
369
370 /*
371 * Search in the current directory, and in on directory deeper
372 * for <aircraft>-set.xml configuration files and show the aircaft name
373 * and the contents of the<description> tag in a sorted manner.
374 *
375 * @parampath the directory to search for configuration files
376 */
fgShowAircraft(const vector<SGPath> & path_list)377 void fgShowAircraft(const vector<SGPath> &path_list)
378 {
379 ShowAircraft s;
380 s.show(path_list);
381
382 #ifdef _MSC_VER
383 cout << "Hit a key to continue..." << endl;
384 std::cin.get();
385 #endif
386 }
387
388
389 static bool
parse_wind(const string & wind,double * min_hdg,double * max_hdg,double * speed,double * gust)390 parse_wind (const string &wind, double * min_hdg, double * max_hdg,
391 double * speed, double * gust)
392 {
393 string::size_type pos = wind.find('@');
394 if (pos == string::npos)
395 return false;
396 string dir = wind.substr(0, pos);
397 string spd = wind.substr(pos+1);
398 pos = dir.find(':');
399 if (pos == string::npos) {
400 *min_hdg = *max_hdg = atof(dir.c_str());
401 } else {
402 *min_hdg = atof(dir.substr(0,pos).c_str());
403 *max_hdg = atof(dir.substr(pos+1).c_str());
404 }
405 pos = spd.find(':');
406 if (pos == string::npos) {
407 *speed = *gust = atof(spd.c_str());
408 } else {
409 *speed = atof(spd.substr(0,pos).c_str());
410 *gust = atof(spd.substr(pos+1).c_str());
411 }
412 return true;
413 }
414
415 static bool
parseIntValue(char ** ppParserPos,int * pValue,int min,int max,const char * field,const char * argument)416 parseIntValue(char** ppParserPos, int* pValue,int min, int max, const char* field, const char* argument)
417 {
418 if ( !strlen(*ppParserPos) )
419 return true;
420
421 char num[256];
422 int i = 0;
423
424 while ( isdigit((*ppParserPos)[0]) && (i<255) )
425 {
426 num[i] = (*ppParserPos)[0];
427 (*ppParserPos)++;
428 i++;
429 }
430 num[i] = '\0';
431
432 switch ((*ppParserPos)[0])
433 {
434 case 0:
435 break;
436 case ':':
437 (*ppParserPos)++;
438 break;
439 default:
440 SG_LOG(SG_GENERAL, SG_ALERT, "Illegal character in time string for " << field << ": '" <<
441 (*ppParserPos)[0] << "'.");
442 // invalid field - skip rest of string to avoid further errors
443 while ((*ppParserPos)[0])
444 (*ppParserPos)++;
445 return false;
446 }
447
448 if (i<=0)
449 return true;
450
451 int value = atoi(num);
452 if ((value < min)||(value > max))
453 {
454 SG_LOG(SG_GENERAL, SG_ALERT, "Invalid " << field << " in '" << argument <<
455 "'. Valid range is " << min << "-" << max << ".");
456 return false;
457 }
458 else
459 {
460 *pValue = value;
461 return true;
462 }
463 }
464
465 // parse a time string ([+/-]%f[:%f[:%f]]) into hours
466 static double
parse_time(const string & time_in)467 parse_time(const string& time_in) {
468 char *time_str, num[256];
469 double hours, minutes, seconds;
470 double result = 0.0;
471 int sign = 1;
472 int i;
473
474 time_str = (char *)time_in.c_str();
475
476 // printf("parse_time(): %s\n", time_str);
477
478 // check for sign
479 if ( strlen(time_str) ) {
480 if ( time_str[0] == '+' ) {
481 sign = 1;
482 time_str++;
483 } else if ( time_str[0] == '-' ) {
484 sign = -1;
485 time_str++;
486 }
487 }
488 // printf("sign = %d\n", sign);
489
490 // get hours
491 if ( strlen(time_str) ) {
492 i = 0;
493 while ( (time_str[0] != ':') && (time_str[0] != '\0') ) {
494 num[i] = time_str[0];
495 time_str++;
496 i++;
497 }
498 if ( time_str[0] == ':' ) {
499 time_str++;
500 }
501 num[i] = '\0';
502 hours = atof(num);
503 // printf("hours = %.2lf\n", hours);
504
505 result += hours;
506 }
507
508 // get minutes
509 if ( strlen(time_str) ) {
510 i = 0;
511 while ( (time_str[0] != ':') && (time_str[0] != '\0') ) {
512 num[i] = time_str[0];
513 time_str++;
514 i++;
515 }
516 if ( time_str[0] == ':' ) {
517 time_str++;
518 }
519 num[i] = '\0';
520 minutes = atof(num);
521 // printf("minutes = %.2lf\n", minutes);
522
523 result += minutes / 60.0;
524 }
525
526 // get seconds
527 if ( strlen(time_str) ) {
528 i = 0;
529 while ( (time_str[0] != ':') && (time_str[0] != '\0') ) {
530 num[i] = time_str[0];
531 time_str++;
532 i++;
533 }
534 num[i] = '\0';
535 seconds = atof(num);
536 // printf("seconds = %.2lf\n", seconds);
537
538 result += seconds / 3600.0;
539 }
540
541 SG_LOG( SG_GENERAL, SG_INFO, " parse_time() = " << sign * result );
542
543 return(sign * result);
544 }
545
546 // parse a date string (yyyy:mm:dd:hh:mm:ss) into a time_t (seconds)
547 static long int
parse_date(const string & date,const char * timeType)548 parse_date( const string& date, const char* timeType)
549 {
550 struct tm gmt,*pCurrentTime;
551 int year,month,day,hour,minute,second;
552 char *argument, *date_str;
553
554 SGTime CurrentTime;
555 CurrentTime.update(SGGeod(),0,0);
556
557 // FIXME This should obtain system/aircraft/GMT time depending on timeType
558 pCurrentTime = CurrentTime.getGmt();
559
560 // initialize all fields with current time
561 year = pCurrentTime->tm_year + 1900;
562 month = pCurrentTime->tm_mon + 1;
563 day = pCurrentTime->tm_mday;
564 hour = pCurrentTime->tm_hour;
565 minute = pCurrentTime->tm_min;
566 second = pCurrentTime->tm_sec;
567
568 argument = (char *)date.c_str();
569 date_str = argument;
570
571 // start with parsing year
572 if (!strlen(date_str) ||
573 !parseIntValue(&date_str,&year,0,9999,"year",argument))
574 {
575 return -1;
576 }
577
578 if (year < 1970)
579 {
580 SG_LOG(SG_GENERAL, SG_ALERT, "Invalid year '" << year << "'. Use 1970 or later.");
581 return -1;
582 }
583
584 parseIntValue(&date_str, &month, 1, 12, "month", argument);
585 parseIntValue(&date_str, &day, 1, 31, "day", argument);
586 parseIntValue(&date_str, &hour, 0, 23, "hour", argument);
587 parseIntValue(&date_str, &minute, 0, 59, "minute", argument);
588 parseIntValue(&date_str, &second, 0, 59, "second", argument);
589
590 gmt.tm_sec = second;
591 gmt.tm_min = minute;
592 gmt.tm_hour = hour;
593 gmt.tm_mday = day;
594 gmt.tm_mon = month - 1;
595 gmt.tm_year = year -1900;
596 gmt.tm_isdst = 0; // ignore daylight savings time for the moment
597
598 time_t theTime = sgTimeGetGMT( gmt.tm_year, gmt.tm_mon, gmt.tm_mday,
599 gmt.tm_hour, gmt.tm_min, gmt.tm_sec );
600
601 SG_LOG(SG_GENERAL, SG_INFO, "Configuring startup time to " << ctime(&theTime));
602
603 return (theTime);
604 }
605
606
607 // parse angle in the form of [+/-]ddd:mm:ss into degrees
608 static double
parse_degree(const string & degree_str)609 parse_degree( const string& degree_str) {
610 double result = parse_time( degree_str );
611
612 // printf("Degree = %.4f\n", result);
613
614 return(result);
615 }
616
617
618 // parse time offset string into seconds
619 static long int
parse_time_offset(const string & time_str)620 parse_time_offset( const string& time_str) {
621 long int result;
622
623 // printf("time offset = %s\n", time_str);
624
625 #ifdef HAVE_RINT
626 result = (int)rint(parse_time(time_str) * 3600.0);
627 #else
628 result = (int)(parse_time(time_str) * 3600.0);
629 #endif
630
631 // printf("parse_time_offset(): %d\n", result);
632
633 return( result );
634 }
635
636
637 // Parse --fov=x.xx type option
638 static double
parse_fov(const string & arg)639 parse_fov( const string& arg ) {
640 double fov = atof(arg);
641
642 if ( fov < FG_FOV_MIN ) { fov = FG_FOV_MIN; }
643 if ( fov > FG_FOV_MAX ) { fov = FG_FOV_MAX; }
644
645 fgSetDouble("/sim/view[0]/config/default-field-of-view-deg", fov);
646
647 // printf("parse_fov(): result = %.4f\n", fov);
648
649 return fov;
650 }
651
652
653 // Parse I/O channel option
654 //
655 // Format is "--protocol=medium,direction,hz,medium_options,..."
656 //
657 // protocol = { native, nmea, garmin, AV400, AV400Sim, fgfs, rul, pve, etc. }
658 // medium = { serial, socket, file, etc. }
659 // direction = { in, out, bi }
660 // hz = number of times to process channel per second (floating
661 // point values are ok.
662 //
663 // Serial example "--nmea=serial,dir,hz,device,baud" where
664 //
665 // device = OS device name of serial line to be open()'ed
666 // baud = {300, 1200, 2400, ..., 230400}
667 //
668 // Socket example "--native=socket,dir,hz,machine,port,style" where
669 //
670 // machine = machine name or ip address if client (leave empty if server)
671 // port = port, leave empty to let system choose
672 // style = tcp or udp
673 //
674 // File example "--garmin=file,dir,hz,filename" where
675 //
676 // filename = file system file name
677
678 static bool
add_channel(const string & type,const string & channel_str)679 add_channel( const string& type, const string& channel_str ) {
680 // This check is necessary to prevent fgviewer from segfaulting when given
681 // weird options. (It doesn't run the full initialization)
682 if(!globals->get_channel_options_list())
683 {
684 SG_LOG(SG_GENERAL, SG_ALERT, "Option " << type << "=" << channel_str
685 << " ignored.");
686 return false;
687 }
688 SG_LOG(SG_GENERAL, SG_INFO, "Channel string = " << channel_str );
689 globals->get_channel_options_list()->push_back( type + "," + channel_str );
690 return true;
691 }
692
693 static void
clearLocation()694 clearLocation ()
695 {
696 fgSetString("/sim/presets/airport-id", "");
697 fgSetString("/sim/presets/vor-id", "");
698 fgSetString("/sim/presets/ndb-id", "");
699 fgSetString("/sim/presets/carrier", "");
700 fgSetString("/sim/presets/parkpos", "");
701 fgSetString("/sim/presets/carrier-position", "");
702 fgSetString("/sim/presets/fix", "");
703 }
704
705 /*
706 Using --addon=/foo/bar does:
707 - register the add-on with the AddonManager (enabling, among other things,
708 add-on-specific resources for simgear::ResourceManager);
709 - load /foo/bar/addon-config.xml into the Global Property Tree;
710 - add /foo/bar to the list of aircraft paths to provide read access:
711 - set various properties related to the add-on under /addons;
712 - load /foo/bar/addon-main.nas into namespace __addon[ADDON_ID]__
713 (see $FG_ROOT/Nasal/addons.nas);
714 - call the main() function defined in that file.
715
716 For more details, see $FG_ROOT/Docs/README.add-ons.
717 */
718 static int
fgOptAddon(const char * arg)719 fgOptAddon(const char *arg)
720 {
721 const SGPath addonPath = SGPath::fromUtf8(arg);
722 const auto& addonManager = addons::AddonManager::instance();
723
724 try {
725 addonManager->registerAddon(addonPath);
726 } catch (const sg_exception &e) {
727 string msg = "Error registering an add-on: " + e.getFormattedMessage();
728 SG_LOG(SG_GENERAL, SG_ALERT, msg);
729 flightgear::fatalMessageBoxThenExit(
730 "FlightGear", "Unable to register an add-on.", msg);
731 }
732
733 return FG_OPTIONS_OK;
734 }
735
736 static int
fgOptAdditionalDataDir(const char * arg)737 fgOptAdditionalDataDir(const char* arg)
738 {
739 const SGPath dataPath = SGPath::fromUtf8(arg);
740 if (!dataPath.exists()) {
741 SG_LOG(SG_GENERAL, SG_ALERT, "--data path not found:'" << dataPath << "'");
742 flightgear::fatalMessageBoxWithoutExit(
743 "FlightGear", "Data path not found: '" + dataPath.utf8Str() + "'.");
744 return FG_OPTIONS_EXIT;
745 }
746
747 globals->append_data_path(dataPath, false /* = before FG_ROOT */);
748 return FG_OPTIONS_OK;
749 }
750
751 static int
fgOptVOR(const char * arg)752 fgOptVOR( const char * arg )
753 {
754 clearLocation();
755 fgSetString("/sim/presets/vor-id", arg);
756 return FG_OPTIONS_OK;
757 }
758
759 static int
fgOptNDB(const char * arg)760 fgOptNDB( const char * arg )
761 {
762 clearLocation();
763 fgSetString("/sim/presets/ndb-id", arg);
764 return FG_OPTIONS_OK;
765 }
766
767 static int
fgOptCarrier(const char * arg)768 fgOptCarrier( const char * arg )
769 {
770 clearLocation();
771 fgSetString("/sim/presets/carrier", arg);
772 return FG_OPTIONS_OK;
773 }
774
775 static int
fgOptCarrierPos(const char * arg)776 fgOptCarrierPos( const char * arg )
777 {
778 fgSetString("/sim/presets/carrier-position", arg);
779 return FG_OPTIONS_OK;
780 }
781
782 static int
fgOptParkpos(const char * arg)783 fgOptParkpos( const char * arg )
784 {
785 fgSetString("/sim/presets/parkpos", arg);
786 fgSetBool("/sim/presets/parking-requested", true);
787 return FG_OPTIONS_OK;
788 }
789
790 static int
fgOptFIX(const char * arg)791 fgOptFIX( const char * arg )
792 {
793 clearLocation();
794 fgSetString("/sim/presets/fix", arg);
795 return FG_OPTIONS_OK;
796 }
797
798 static int
fgOptLon(const char * arg)799 fgOptLon( const char *arg )
800 {
801 clearLocation();
802 fgSetDouble("/sim/presets/longitude-deg", parse_degree( arg ));
803 fgSetDouble("/position/longitude-deg", parse_degree( arg ));
804 return FG_OPTIONS_OK;
805 }
806
807 static int
fgOptLat(const char * arg)808 fgOptLat( const char *arg )
809 {
810 clearLocation();
811 fgSetDouble("/sim/presets/latitude-deg", parse_degree( arg ));
812 fgSetDouble("/position/latitude-deg", parse_degree( arg ));
813 return FG_OPTIONS_OK;
814 }
815
816 static int
fgOptAltitude(const char * arg)817 fgOptAltitude( const char *arg )
818 {
819 fgSetBool("/sim/presets/onground", false);
820 if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
821 fgSetDouble("/sim/presets/altitude-ft", atof( arg ));
822 else
823 fgSetDouble("/sim/presets/altitude-ft",
824 atof( arg ) * SG_METER_TO_FEET);
825 return FG_OPTIONS_OK;
826 }
827
828 static int
fgOptUBody(const char * arg)829 fgOptUBody( const char *arg )
830 {
831 fgSetString("/sim/presets/speed-set", "UVW");
832 if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
833 fgSetDouble("/sim/presets/uBody-fps", atof( arg ));
834 else
835 fgSetDouble("/sim/presets/uBody-fps",
836 atof( arg ) * SG_METER_TO_FEET);
837 return FG_OPTIONS_OK;
838 }
839
840 static int
fgOptVBody(const char * arg)841 fgOptVBody( const char *arg )
842 {
843 fgSetString("/sim/presets/speed-set", "UVW");
844 if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
845 fgSetDouble("/sim/presets/vBody-fps", atof( arg ));
846 else
847 fgSetDouble("/sim/presets/vBody-fps",
848 atof( arg ) * SG_METER_TO_FEET);
849 return FG_OPTIONS_OK;
850 }
851
852 static int
fgOptWBody(const char * arg)853 fgOptWBody( const char *arg )
854 {
855 fgSetString("/sim/presets/speed-set", "UVW");
856 if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
857 fgSetDouble("/sim/presets/wBody-fps", atof(arg));
858 else
859 fgSetDouble("/sim/presets/wBody-fps",
860 atof(arg) * SG_METER_TO_FEET);
861 return FG_OPTIONS_OK;
862 }
863
864 static int
fgOptVNorth(const char * arg)865 fgOptVNorth( const char *arg )
866 {
867 fgSetString("/sim/presets/speed-set", "NED");
868 if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
869 fgSetDouble("/sim/presets/speed-north-fps", atof( arg ));
870 else
871 fgSetDouble("/sim/presets/speed-north-fps",
872 atof( arg ) * SG_METER_TO_FEET);
873 return FG_OPTIONS_OK;
874 }
875
876 static int
fgOptVEast(const char * arg)877 fgOptVEast( const char *arg )
878 {
879 fgSetString("/sim/presets/speed-set", "NED");
880 if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
881 fgSetDouble("/sim/presets/speed-east-fps", atof(arg));
882 else
883 fgSetDouble("/sim/presets/speed-east-fps",
884 atof(arg) * SG_METER_TO_FEET);
885 return FG_OPTIONS_OK;
886 }
887
888 static int
fgOptVDown(const char * arg)889 fgOptVDown( const char *arg )
890 {
891 fgSetString("/sim/presets/speed-set", "NED");
892 if ( !strcmp(fgGetString("/sim/startup/units"), "feet") )
893 fgSetDouble("/sim/presets/speed-down-fps", atof(arg));
894 else
895 fgSetDouble("/sim/presets/speed-down-fps",
896 atof(arg) * SG_METER_TO_FEET);
897 return FG_OPTIONS_OK;
898 }
899
900 static int
fgOptVc(const char * arg)901 fgOptVc( const char *arg )
902 {
903 // fgSetString("/sim/presets/speed-set", "knots");
904 // fgSetDouble("/velocities/airspeed-kt", atof(arg.substr(5)));
905 fgSetString("/sim/presets/speed-set", "knots");
906 fgSetDouble("/sim/presets/airspeed-kt", atof(arg));
907 return FG_OPTIONS_OK;
908 }
909
910 static int
fgOptMach(const char * arg)911 fgOptMach( const char *arg )
912 {
913 fgSetString("/sim/presets/speed-set", "mach");
914 fgSetDouble("/sim/presets/mach", atof(arg));
915 return FG_OPTIONS_OK;
916 }
917
918 static int
fgOptRoc(const char * arg)919 fgOptRoc( const char *arg )
920 {
921 fgSetDouble("/sim/presets/vertical-speed-fps", atof(arg)/60);
922 return FG_OPTIONS_OK;
923 }
924
925 static int
fgOptFgScenery(const char * arg)926 fgOptFgScenery( const char *arg )
927 {
928 globals->append_fg_scenery(SGPath::pathsFromUtf8(arg));
929 return FG_OPTIONS_OK;
930 }
931
932 static int
fgOptEnhancedLighting(const char * arg)933 fgOptEnhancedLighting( const char *arg )
934 {
935 SG_LOG(SG_ALL,SG_ALERT,
936 "the options --enable-enhanced-lighting and "
937 "--disable-enhanced-lighting are no longer supported and will be "
938 "removed in a future FlightGear version! Please do not use them");
939 return FG_OPTIONS_EXIT;
940 }
941
942 static int
fgOptAllowNasalRead(const char * arg)943 fgOptAllowNasalRead( const char *arg )
944 {
945 PathList paths = SGPath::pathsFromUtf8(arg);
946 if(paths.size() == 0) {
947 SG_LOG(SG_GENERAL, SG_WARN, "--allow-nasal-read requires a list of directories to allow");
948 }
949 for( PathList::const_iterator it = paths.begin(); it != paths.end(); ++it ) {
950 globals->append_read_allowed_paths(*it);
951 }
952 return FG_OPTIONS_OK;
953 }
954
955 static int
fgOptFov(const char * arg)956 fgOptFov( const char *arg )
957 {
958 parse_fov( arg );
959 return FG_OPTIONS_OK;
960 }
961
962 static int
fgOptGeometry(const char * arg)963 fgOptGeometry( const char *arg )
964 {
965 bool geometry_ok = true;
966 int xsize = 0, ysize = 0;
967 string geometry = arg;
968 string::size_type i = geometry.find('x');
969
970 if (i != string::npos) {
971 xsize = atoi(geometry.substr(0, i));
972 ysize = atoi(geometry.substr(i+1));
973 } else {
974 geometry_ok = false;
975 }
976
977 if ( xsize <= 0 || ysize <= 0 ) {
978 xsize = 640;
979 ysize = 480;
980 geometry_ok = false;
981 }
982
983 if ( !geometry_ok ) {
984 SG_LOG( SG_GENERAL, SG_ALERT, "Unknown geometry: " << geometry );
985 SG_LOG( SG_GENERAL, SG_ALERT,
986 "Setting geometry to " << xsize << 'x' << ysize << '\n');
987 } else {
988 SG_LOG( SG_GENERAL, SG_INFO,
989 "Setting geometry to " << xsize << 'x' << ysize << '\n');
990 fgSetInt("/sim/startup/xsize", xsize);
991 fgSetInt("/sim/startup/ysize", ysize);
992 }
993 return FG_OPTIONS_OK;
994 }
995
996 static int
fgOptBpp(const char * arg)997 fgOptBpp( const char *arg )
998 {
999 string bits_per_pix = arg;
1000 if ( bits_per_pix == "16" ) {
1001 fgSetInt("/sim/rendering/bits-per-pixel", 16);
1002 } else if ( bits_per_pix == "24" ) {
1003 fgSetInt("/sim/rendering/bits-per-pixel", 24);
1004 } else if ( bits_per_pix == "32" ) {
1005 fgSetInt("/sim/rendering/bits-per-pixel", 32);
1006 } else {
1007 SG_LOG(SG_GENERAL, SG_ALERT, "Unsupported bpp " << bits_per_pix);
1008 }
1009 return FG_OPTIONS_OK;
1010 }
1011
1012 static int
fgOptTimeOffset(const char * arg)1013 fgOptTimeOffset( const char *arg )
1014 {
1015 fgSetLong("/sim/startup/time-offset",
1016 parse_time_offset( arg ));
1017 fgSetString("/sim/startup/time-offset-type", "system-offset");
1018 return FG_OPTIONS_OK;
1019 }
1020
1021 static int
fgOptStartDateSys(const char * arg)1022 fgOptStartDateSys( const char *arg )
1023 {
1024 long int theTime = parse_date( arg, "system" );
1025 if (theTime>=0)
1026 {
1027 fgSetLong("/sim/startup/time-offset", theTime);
1028 fgSetString("/sim/startup/time-offset-type", "system");
1029 }
1030 return FG_OPTIONS_OK;
1031 }
1032
1033 static int
fgOptStartDateLat(const char * arg)1034 fgOptStartDateLat( const char *arg )
1035 {
1036 long int theTime = parse_date( arg, "latitude" );
1037 if (theTime>=0)
1038 {
1039 fgSetLong("/sim/startup/time-offset", theTime);
1040 fgSetString("/sim/startup/time-offset-type", "latitude");
1041 }
1042 return FG_OPTIONS_OK;
1043 }
1044
1045 static int
fgOptStartDateGmt(const char * arg)1046 fgOptStartDateGmt( const char *arg )
1047 {
1048 long int theTime = parse_date( arg, "gmt" );
1049 if (theTime>=0)
1050 {
1051 fgSetLong("/sim/startup/time-offset", theTime);
1052 fgSetString("/sim/startup/time-offset-type", "gmt");
1053 }
1054 return FG_OPTIONS_OK;
1055 }
1056
1057 static int
fgOptJpgHttpd(const char * arg)1058 fgOptJpgHttpd( const char * arg )
1059 {
1060 SG_LOG(SG_ALL,SG_ALERT,
1061 "the option --jpg-httpd is no longer supported! Please use --httpd instead."
1062 " URL for the screenshot within the new httpd is http://YourFgServer:xxxx/screenshot");
1063 return FG_OPTIONS_EXIT;
1064 }
1065
1066 static int
fgOptHttpd(const char * arg)1067 fgOptHttpd( const char * arg )
1068 {
1069 // port may be any valid address:port notation
1070 // like 127.0.0.1:8080
1071 // or just the port 8080
1072 string port = simgear::strutils::strip(string(arg));
1073 if( port.empty() ) return FG_OPTIONS_ERROR;
1074 fgSetString( string(flightgear::http::PROPERTY_ROOT).append("/options/listening-port").c_str(), port );
1075 return FG_OPTIONS_OK;
1076 }
1077
1078 static int
fgSetupProxy(const char * arg)1079 fgSetupProxy( const char *arg )
1080 {
1081 string options = simgear::strutils::strip( arg );
1082 string host, port, auth;
1083 string::size_type pos;
1084
1085 // this is NURLP - NURLP is not an url parser
1086 if( simgear::strutils::starts_with( options, "http://" ) )
1087 options = options.substr( 7 );
1088 if( simgear::strutils::ends_with( options, "/" ) )
1089 options = options.substr( 0, options.length() - 1 );
1090
1091 host = port = auth = "";
1092 if ((pos = options.find("@")) != string::npos)
1093 auth = options.substr(0, pos++);
1094 else
1095 pos = 0;
1096
1097 host = options.substr(pos, options.size());
1098 if ((pos = host.find(":")) != string::npos) {
1099 port = host.substr(++pos, host.size());
1100 host.erase(--pos, host.size());
1101 }
1102
1103 fgSetString("/sim/presets/proxy/host", host.c_str());
1104 fgSetString("/sim/presets/proxy/port", port.c_str());
1105 fgSetString("/sim/presets/proxy/authentication", auth.c_str());
1106
1107 return FG_OPTIONS_OK;
1108 }
1109
1110 static int
fgOptTraceRead(const char * arg)1111 fgOptTraceRead( const char *arg )
1112 {
1113 string name = arg;
1114 SG_LOG(SG_GENERAL, SG_INFO, "Tracing reads for property " << name);
1115 fgGetNode(name.c_str(), true)
1116 ->setAttribute(SGPropertyNode::TRACE_READ, true);
1117 return FG_OPTIONS_OK;
1118 }
1119
1120 static int
fgOptLogLevel(const char * arg)1121 fgOptLogLevel( const char *arg )
1122 {
1123 fgSetString("/sim/logging/priority", arg);
1124 setLoggingPriority(arg);
1125
1126 return FG_OPTIONS_OK;
1127 }
1128
1129 static int
fgOptLogClasses(const char * arg)1130 fgOptLogClasses( const char *arg )
1131 {
1132 fgSetString("/sim/logging/classes", arg);
1133 setLoggingClasses (arg);
1134
1135 return FG_OPTIONS_OK;
1136 }
1137
1138 static int
fgOptLogDir(const char * arg)1139 fgOptLogDir(const char* arg)
1140 {
1141 SGPath dirPath;
1142 if (!strcmp(arg, "desktop")) {
1143 dirPath = SGPath::desktop();
1144 } else {
1145 dirPath = SGPath::fromUtf8(arg);
1146 }
1147
1148 if (!dirPath.isDir()) {
1149 SG_LOG(SG_GENERAL, SG_ALERT, "cannot find logging location " << dirPath);
1150 return FG_OPTIONS_ERROR;
1151 }
1152
1153 if (!dirPath.canWrite()) {
1154 SG_LOG(SG_GENERAL, SG_ALERT, "cannot write to logging location " << dirPath);
1155 return FG_OPTIONS_ERROR;
1156 }
1157
1158 // generate the log file name
1159 SGPath logFile;
1160 {
1161 char fileNameBuffer[100];
1162 time_t now;
1163 time(&now);
1164 strftime(fileNameBuffer, 99, "FlightGear_%F", localtime(&now));
1165
1166 unsigned int logsTodayCount = 0;
1167 while (true) {
1168 std::ostringstream os;
1169 os << fileNameBuffer << "_" << logsTodayCount++ << ".log";
1170 logFile = dirPath / os.str();
1171 if (!logFile.exists()) {
1172 break;
1173 }
1174 }
1175 }
1176
1177 sglog().logToFile(logFile, sglog().get_log_classes(), sglog().get_log_priority());
1178
1179 return FG_OPTIONS_OK;
1180 }
1181
1182 static int
fgOptTraceWrite(const char * arg)1183 fgOptTraceWrite( const char *arg )
1184 {
1185 string name = arg;
1186 SG_LOG(SG_GENERAL, SG_INFO, "Tracing writes for property " << name);
1187 fgGetNode(name.c_str(), true)
1188 ->setAttribute(SGPropertyNode::TRACE_WRITE, true);
1189 return FG_OPTIONS_OK;
1190 }
1191
1192 static int
fgOptViewOffset(const char * arg)1193 fgOptViewOffset( const char *arg )
1194 {
1195 // $$$ begin - added VS Renganathan, 14 Oct 2K
1196 // for multi-window outside window imagery
1197 string woffset = arg;
1198 double default_view_offset = 0.0;
1199 if ( woffset == "LEFT" ) {
1200 default_view_offset = SGD_PI * 0.25;
1201 } else if ( woffset == "RIGHT" ) {
1202 default_view_offset = SGD_PI * 1.75;
1203 } else if ( woffset == "CENTER" ) {
1204 default_view_offset = 0.00;
1205 } else {
1206 default_view_offset = atof( woffset.c_str() ) * SGD_DEGREES_TO_RADIANS;
1207 }
1208 /* apparently not used (CLO, 11 Jun 2002)
1209 FGViewer *pilot_view =
1210 (FGViewer *)globals->get_viewmgr()->get_view( 0 ); */
1211 // this will work without calls to the viewer...
1212 fgSetDouble( "/sim/current-view/heading-offset-deg",
1213 default_view_offset * SGD_RADIANS_TO_DEGREES );
1214 // $$$ end - added VS Renganathan, 14 Oct 2K
1215 return FG_OPTIONS_OK;
1216 }
1217
1218 static int
fgOptVisibilityMeters(const char * arg)1219 fgOptVisibilityMeters( const char *arg )
1220 {
1221 Environment::Presets::VisibilitySingleton::instance()->preset( atof( arg ) );
1222 return FG_OPTIONS_OK;
1223 }
1224
1225 static int
fgOptVisibilityMiles(const char * arg)1226 fgOptVisibilityMiles( const char *arg )
1227 {
1228 Environment::Presets::VisibilitySingleton::instance()->preset( atof( arg ) * 5280.0 * SG_FEET_TO_METER );
1229 return FG_OPTIONS_OK;
1230 }
1231
1232 static int
fgOptMetar(const char * arg)1233 fgOptMetar(const char *arg)
1234 {
1235 // The given METAR string cannot be effective without disabling
1236 // real weather fetching.
1237 fgSetBool("/environment/realwx/enabled", false);
1238 // The user-supplied METAR string
1239 fgSetString("/environment/metar/data", arg);
1240
1241 return FG_OPTIONS_OK;
1242 }
1243 static int
fgOptConsole(const char * arg)1244 fgOptConsole(const char *arg)
1245 {
1246 static bool already_done = false;
1247 if (!already_done)
1248 {
1249 already_done = true;
1250 simgear::requestConsole();
1251 }
1252 return FG_OPTIONS_OK;
1253 }
1254
1255 static int
fgOptRandomWind(const char * arg)1256 fgOptRandomWind( const char *arg )
1257 {
1258 double min_hdg = sg_random() * 360.0;
1259 double max_hdg = min_hdg + (20 - sqrt(sg_random() * 400));
1260 double speed = sg_random() * sg_random() * 40;
1261 double gust = speed + (10 - sqrt(sg_random() * 100));
1262 Environment::Presets::WindSingleton::instance()->preset(min_hdg, max_hdg, speed, gust);
1263 return FG_OPTIONS_OK;
1264 }
1265
1266 static int
fgOptWind(const char * arg)1267 fgOptWind( const char *arg )
1268 {
1269 double min_hdg = 0.0, max_hdg = 0.0, speed = 0.0, gust = 0.0;
1270 if (!parse_wind( arg, &min_hdg, &max_hdg, &speed, &gust)) {
1271 SG_LOG( SG_GENERAL, SG_ALERT, "bad wind value " << arg );
1272 return FG_OPTIONS_ERROR;
1273 }
1274 Environment::Presets::WindSingleton::instance()->preset(min_hdg, max_hdg, speed, gust);
1275 return FG_OPTIONS_OK;
1276 }
1277
1278 static int
fgOptTurbulence(const char * arg)1279 fgOptTurbulence( const char *arg )
1280 {
1281 Environment::Presets::TurbulenceSingleton::instance()->preset( atof(arg) );
1282 return FG_OPTIONS_OK;
1283 }
1284
1285 static int
fgOptCeiling(const char * arg)1286 fgOptCeiling( const char *arg )
1287 {
1288 double elevation, thickness;
1289 string spec = arg;
1290 string::size_type pos = spec.find(':');
1291 if (pos == string::npos) {
1292 elevation = atof(spec.c_str());
1293 thickness = 2000;
1294 } else {
1295 elevation = atof(spec.substr(0, pos).c_str());
1296 thickness = atof(spec.substr(pos + 1).c_str());
1297 }
1298 Environment::Presets::CeilingSingleton::instance()->preset( elevation, thickness );
1299 return FG_OPTIONS_OK;
1300 }
1301
1302 static int
fgOptWp(const char * arg)1303 fgOptWp( const char *arg )
1304 {
1305 string_list *waypoints = globals->get_initial_waypoints();
1306 if (!waypoints) {
1307 waypoints = new string_list;
1308 globals->set_initial_waypoints(waypoints);
1309 }
1310 waypoints->push_back(arg);
1311 return FG_OPTIONS_OK;
1312 }
1313
1314 static bool
parse_colon(const string & s,double * val1,double * val2)1315 parse_colon (const string &s, double * val1, double * val2)
1316 {
1317 string::size_type pos = s.find(':');
1318 if (pos == string::npos) {
1319 *val2 = atof(s);
1320 return false;
1321 } else {
1322 *val1 = atof(s.substr(0, pos).c_str());
1323 *val2 = atof(s.substr(pos+1).c_str());
1324 return true;
1325 }
1326 }
1327
1328
1329 static int
fgOptFailure(const char * arg)1330 fgOptFailure( const char * arg )
1331 {
1332 string a = arg;
1333 if (a == "pitot") {
1334 fgSetBool("/systems/pitot/serviceable", false);
1335 } else if (a == "static") {
1336 fgSetBool("/systems/static/serviceable", false);
1337 } else if (a == "vacuum") {
1338 fgSetBool("/systems/vacuum/serviceable", false);
1339 } else if (a == "electrical") {
1340 fgSetBool("/systems/electrical/serviceable", false);
1341 } else {
1342 SG_LOG(SG_INPUT, SG_ALERT, "Unknown failure mode: " << a);
1343 return FG_OPTIONS_ERROR;
1344 }
1345
1346 return FG_OPTIONS_OK;
1347 }
1348
1349
1350 static int
fgOptNAV1(const char * arg)1351 fgOptNAV1( const char * arg )
1352 {
1353 double radial, freq;
1354 if (parse_colon(arg, &radial, &freq))
1355 fgSetDouble("/instrumentation/nav[0]/radials/selected-deg", radial);
1356 fgSetDouble("/instrumentation/nav[0]/frequencies/selected-mhz", freq);
1357 return FG_OPTIONS_OK;
1358 }
1359
1360 static int
fgOptNAV2(const char * arg)1361 fgOptNAV2( const char * arg )
1362 {
1363 double radial, freq;
1364 if (parse_colon(arg, &radial, &freq))
1365 fgSetDouble("/instrumentation/nav[1]/radials/selected-deg", radial);
1366 fgSetDouble("/instrumentation/nav[1]/frequencies/selected-mhz", freq);
1367 return FG_OPTIONS_OK;
1368 }
1369
1370 static int
fgOptADF(const char * arg)1371 fgOptADF( const char * arg )
1372 {
1373 SG_LOG(SG_ALL,SG_ALERT,
1374 "the option --adf is no longer supported! Please use --adf1 "
1375 "instead.");
1376 return FG_OPTIONS_EXIT;
1377 }
1378
1379 static int
fgOptADF1(const char * arg)1380 fgOptADF1( const char * arg )
1381 {
1382 double rot, freq;
1383 if (parse_colon(arg, &rot, &freq))
1384 fgSetDouble("/instrumentation/adf[0]/rotation-deg", rot);
1385 fgSetDouble("/instrumentation/adf[0]/frequencies/selected-khz", freq);
1386 return FG_OPTIONS_OK;
1387 }
1388
1389 static int
fgOptADF2(const char * arg)1390 fgOptADF2( const char * arg )
1391 {
1392 double rot, freq;
1393 if (parse_colon(arg, &rot, &freq))
1394 fgSetDouble("/instrumentation/adf[1]/rotation-deg", rot);
1395 fgSetDouble("/instrumentation/adf[1]/frequencies/selected-khz", freq);
1396 return FG_OPTIONS_OK;
1397 }
1398
1399 static int
fgOptDME(const char * arg)1400 fgOptDME( const char *arg )
1401 {
1402 string opt = arg;
1403 if (opt == "nav1") {
1404 fgSetInt("/instrumentation/dme/switch-position", 1);
1405 fgSetString("/instrumentation/dme/frequencies/source",
1406 "/instrumentation/nav[0]/frequencies/selected-mhz");
1407 } else if (opt == "nav2") {
1408 fgSetInt("/instrumentation/dme/switch-position", 3);
1409 fgSetString("/instrumentation/dme/frequencies/source",
1410 "/instrumentation/nav[1]/frequencies/selected-mhz");
1411 } else {
1412 double frequency = atof(arg);
1413 if (frequency==0.0)
1414 {
1415 SG_LOG(SG_INPUT, SG_ALERT, "Invalid DME frequency: '" << arg << "'.");
1416 return FG_OPTIONS_ERROR;
1417 }
1418 fgSetInt("/instrumentation/dme/switch-position", 2);
1419 fgSetString("/instrumentation/dme/frequencies/source",
1420 "/instrumentation/dme/frequencies/selected-mhz");
1421 fgSetDouble("/instrumentation/dme/frequencies/selected-mhz", frequency);
1422 }
1423 return FG_OPTIONS_OK;
1424 }
1425
1426 static int
fgOptLivery(const char * arg)1427 fgOptLivery( const char *arg )
1428 {
1429 string opt = arg;
1430 string livery_path = "livery/" + opt;
1431 fgSetString("/sim/model/texture-path", livery_path.c_str() );
1432 return FG_OPTIONS_OK;
1433 }
1434
1435 static int
fgOptScenario(const char * arg)1436 fgOptScenario( const char *arg )
1437 {
1438 SGPath path(arg);
1439 std::string name(arg);
1440 if (path.exists()) {
1441 if (path.isRelative()) {
1442 // make absolute
1443 path = simgear::Dir::current().path() / arg;
1444 }
1445
1446 // create description node
1447 auto n = FGAIManager::registerScenarioFile(globals->get_props(), path);
1448 if (!n) {
1449 SG_LOG(SG_GENERAL, SG_WARN, "failed to read scenario file at:" << path);
1450 return FG_OPTIONS_ERROR;
1451 }
1452
1453 // also set the /sim/ai/scenario entry so we load it on startup
1454 name = path.file_base();
1455 }
1456
1457 // add the 'load it' node
1458 SGPropertyNode_ptr ai_node = fgGetNode( "/sim/ai", true );
1459 ai_node->addChild("scenario")->setStringValue(name);
1460
1461 return FG_OPTIONS_OK;
1462 }
1463
1464 static int
fgOptAirport(const char * arg)1465 fgOptAirport( const char *arg )
1466 {
1467 fgSetString("/sim/presets/airport-id", arg );
1468 fgSetBool("/sim/presets/airport-requested", true );
1469 return FG_OPTIONS_OK;
1470 }
1471
1472 static int
fgOptRunway(const char * arg)1473 fgOptRunway( const char *arg )
1474 {
1475 fgSetString("/sim/presets/runway", arg );
1476 fgSetBool("/sim/presets/runway-requested", true );
1477 return FG_OPTIONS_OK;
1478 }
1479
1480 static int
fgOptCallSign(const char * arg)1481 fgOptCallSign(const char * arg)
1482 {
1483 int i;
1484 char callsign[11];
1485 strncpy(callsign,arg,10);
1486 callsign[10]=0;
1487 for (i=0;callsign[i];i++)
1488 {
1489 char c = callsign[i];
1490 if (c >= 'A' && c <= 'Z') continue;
1491 if (c >= 'a' && c <= 'z') continue;
1492 if (c >= '0' && c <= '9') continue;
1493 if (c == '-' || c == '_') continue;
1494 // convert any other illegal characters
1495 callsign[i]='-';
1496 }
1497 fgSetString("sim/multiplay/callsign", callsign );
1498 return FG_OPTIONS_OK;
1499 }
1500
1501 static int
fgOptIgnoreAutosave(const char * arg)1502 fgOptIgnoreAutosave(const char* arg)
1503 {
1504 fgSetBool("/sim/startup/ignore-autosave", true);
1505 // don't overwrite autosave on exit
1506 fgSetBool("/sim/startup/save-on-exit", false);
1507 return FG_OPTIONS_OK;
1508 }
1509
1510 static int
fgOptEnableFreeze(const char * arg)1511 fgOptEnableFreeze(const char* arg)
1512 {
1513 fgSetBool("/sim/freeze/master", true);
1514 fgSetBool("/sim/freeze/clock", true);
1515 return FG_OPTIONS_OK;
1516 }
1517
1518 static int
fgOptDisableFreeze(const char * arg)1519 fgOptDisableFreeze(const char* arg)
1520 {
1521 fgSetBool("/sim/freeze/master", false);
1522 fgSetBool("/sim/freeze/clock", false);
1523 return FG_OPTIONS_OK;
1524 }
1525
1526 // Set a property for the --prop: option. Syntax: --prop:[<type>:]<name>=<value>
1527 // <type> can be "double" etc. but also only the first letter "d".
1528 // Examples: --prop:alpha=1 --prop:bool:beta=true --prop:d:gamma=0.123
1529 static int
fgOptSetProperty(const char * raw)1530 fgOptSetProperty(const char* raw)
1531 {
1532 string arg(raw);
1533 string::size_type pos = arg.find('=');
1534 if (pos == arg.npos || pos == 0 || pos + 1 == arg.size())
1535 return FG_OPTIONS_ERROR;
1536
1537 string name = arg.substr(0, pos);
1538 string value = arg.substr(pos + 1);
1539 string type;
1540 pos = name.find(':');
1541
1542 if (pos != name.npos && pos != 0 && pos + 1 != name.size()) {
1543 type = name.substr(0, pos);
1544 name = name.substr(pos + 1);
1545 }
1546 SGPropertyNode *n = fgGetNode(name.c_str(), true);
1547
1548 bool writable = n->getAttribute(SGPropertyNode::WRITE);
1549 if (!writable)
1550 n->setAttribute(SGPropertyNode::WRITE, true);
1551
1552 bool ret = false;
1553 if (type.empty())
1554 ret = n->setUnspecifiedValue(value.c_str());
1555 else if (type == "s" || type == "string")
1556 ret = n->setStringValue(value.c_str());
1557 else if (type == "d" || type == "double")
1558 ret = n->setDoubleValue(strtod(value.c_str(), 0));
1559 else if (type == "f" || type == "float")
1560 ret = n->setFloatValue(atof(value.c_str()));
1561 else if (type == "l" || type == "long")
1562 ret = n->setLongValue(strtol(value.c_str(), 0, 0));
1563 else if (type == "i" || type == "int")
1564 ret = n->setIntValue(atoi(value.c_str()));
1565 else if (type == "b" || type == "bool")
1566 ret = n->setBoolValue(value == "true" || atoi(value.c_str()) != 0);
1567
1568 if (!writable)
1569 n->setAttribute(SGPropertyNode::WRITE, false);
1570 return ret ? FG_OPTIONS_OK : FG_OPTIONS_ERROR;
1571 }
1572
1573 static int
fgOptLoadTape(const char * arg)1574 fgOptLoadTape(const char* arg)
1575 {
1576 // load a flight recorder tape but wait until the fdm is initialized
1577 class DelayedTapeLoader : SGPropertyChangeListener {
1578 public:
1579 DelayedTapeLoader( const char * tape ) :
1580 _tape(SGPath::fromUtf8(tape))
1581 {
1582 SGPropertyNode_ptr n = fgGetNode("/sim/signals/fdm-initialized", true);
1583 n->addChangeListener( this );
1584 }
1585
1586 virtual ~ DelayedTapeLoader() {}
1587
1588 virtual void valueChanged(SGPropertyNode * node)
1589 {
1590 node->removeChangeListener( this );
1591
1592 // tell the replay subsystem to load the tape
1593 FGReplay* replay = globals->get_subsystem<FGReplay>();
1594 SGPropertyNode_ptr arg = new SGPropertyNode();
1595 arg->setStringValue("tape", _tape.utf8Str() );
1596 arg->setBoolValue( "same-aircraft", 0 );
1597 replay->loadTape(arg);
1598
1599 delete this; // commence suicide
1600 }
1601 private:
1602 SGPath _tape;
1603
1604 };
1605
1606 new DelayedTapeLoader(arg);
1607 return FG_OPTIONS_OK;
1608 }
1609
fgOptDisableGUI(const char *)1610 static int fgOptDisableGUI(const char*)
1611 {
1612 globals->set_headless(true);
1613 return FG_OPTIONS_OK;
1614 }
1615
1616 /*
1617 option has_param type property b_param s_param func
1618
1619 where:
1620 option : name of the option
1621 has_param : option is --name=value if true or --name if false
1622 type : OPTION_BOOL - property is a boolean
1623 OPTION_STRING - property is a string
1624 OPTION_DOUBLE - property is a double
1625 OPTION_INT - property is an integer
1626 OPTION_CHANNEL - name of option is the name of a channel
1627 OPTION_FUNC - the option trigger a function
1628 b_param : if type==OPTION_BOOL,
1629 value set to the property (has_param is false for boolean)
1630 s_param : if type==OPTION_STRING,
1631 value set to the property if has_param is false
1632 func : function called if type==OPTION_FUNC. if has_param is true,
1633 the value is passed to the function as a string, otherwise,
1634 s_param is passed.
1635
1636 For OPTION_DOUBLE and OPTION_INT, the parameter value is converted into a
1637 double or an integer and set to the property.
1638
1639 For OPTION_CHANNEL, add_channel is called with the parameter value as the
1640 argument.
1641 */
1642
1643 enum OptionType { OPTION_BOOL = 0, OPTION_STRING, OPTION_DOUBLE, OPTION_INT, OPTION_CHANNEL, OPTION_FUNC, OPTION_IGNORE };
1644 const int OPTION_MULTI = 1 << 17;
1645
1646 struct OptionDesc {
1647 const char *option;
1648 bool has_param;
1649 int type;
1650 const char *property;
1651 bool b_param;
1652 const char *s_param;
1653 int (*func)( const char * );
1654 } fgOptionArray[] = {
1655
1656 {"language", true, OPTION_IGNORE, "", false, "", 0 },
1657 {"console", false, OPTION_FUNC, "", false, "", fgOptConsole },
1658 {"launcher", false, OPTION_IGNORE, "", false, "", 0 },
1659 {"enable-sentry", false, OPTION_BOOL, "/sim/startup/sentry-crash-reporting-enabled", true, "", nullptr },
1660 {"disable-sentry", false, OPTION_BOOL, "/sim/startup/sentry-crash-reporting-enabled", false, "", nullptr },
1661 {"allow-nasal-from-sockets", false, OPTION_IGNORE, "", false, "", 0 },
1662 {"disable-rembrandt", false, OPTION_BOOL, "/sim/rendering/rembrandt/enabled", false, "", 0 },
1663 {"enable-rembrandt", false, OPTION_BOOL, "/sim/rendering/rembrandt/enabled", true, "", 0 },
1664 {"renderer", true, OPTION_STRING, "/sim/rendering/rembrandt/renderer", false, "", 0 },
1665 {"compositor", true, OPTION_STRING, "/sim/rendering/default-compositor", false, "", 0 },
1666 {"disable-splash-screen", false, OPTION_BOOL, "/sim/startup/splash-screen", false, "", 0 },
1667 {"enable-splash-screen", false, OPTION_BOOL, "/sim/startup/splash-screen", true, "", 0 },
1668 {"disable-mouse-pointer", false, OPTION_STRING, "/sim/startup/mouse-pointer", false, "disabled", 0 },
1669 {"enable-mouse-pointer", false, OPTION_STRING, "/sim/startup/mouse-pointer", false, "enabled", 0 },
1670 {"disable-random-objects", false, OPTION_BOOL, "/sim/rendering/random-objects", false, "", 0 },
1671 {"enable-random-objects", false, OPTION_BOOL, "/sim/rendering/random-objects", true, "", 0 },
1672 {"disable-random-vegetation", false, OPTION_BOOL, "/sim/rendering/random-vegetation", false, "", 0 },
1673 {"enable-random-vegetation", false, OPTION_BOOL, "/sim/rendering/random-vegetation", true, "", 0 },
1674 {"disable-random-buildings", false, OPTION_BOOL, "/sim/rendering/random-buildings", false, "", 0 },
1675 {"enable-random-buildings", false, OPTION_BOOL, "/sim/rendering/random-buildings", true, "", 0 },
1676 {"disable-real-weather-fetch", false, OPTION_BOOL, "/environment/realwx/enabled", false, "", 0 },
1677 {"enable-real-weather-fetch", false, OPTION_BOOL, "/environment/realwx/enabled", true, "", 0 },
1678 {"metar", true, OPTION_FUNC, "", false, "", fgOptMetar },
1679 {"disable-ai-models", false, OPTION_BOOL, "/sim/ai/enabled", false, "", 0 },
1680 {"enable-ai-models", false, OPTION_BOOL, "/sim/ai/enabled", true, "", 0 },
1681 {"disable-ai-traffic", false, OPTION_BOOL, "/sim/traffic-manager/enabled", false, "", 0 },
1682 {"enable-ai-traffic", false, OPTION_BOOL, "/sim/traffic-manager/enabled", true, "", 0 },
1683 {"disable-freeze", false, OPTION_FUNC, "", false, "", fgOptDisableFreeze },
1684 {"enable-freeze", false, OPTION_FUNC, "", true, "", fgOptEnableFreeze },
1685 {"disable-fuel-freeze", false, OPTION_BOOL, "/sim/freeze/fuel", false, "", 0 },
1686 {"enable-fuel-freeze", false, OPTION_BOOL, "/sim/freeze/fuel", true, "", 0 },
1687 {"disable-clock-freeze", false, OPTION_BOOL, "/sim/freeze/clock", false, "", 0 },
1688 {"enable-clock-freeze", false, OPTION_BOOL, "/sim/freeze/clock", true, "", 0 },
1689 {"disable-hud-3d", false, OPTION_BOOL, "/sim/hud/enable3d[1]", false, "", 0 },
1690 {"enable-hud-3d", false, OPTION_BOOL, "/sim/hud/enable3d[1]", true, "", 0 },
1691 {"disable-anti-alias-hud", false, OPTION_BOOL, "/sim/hud/color/antialiased", false, "", 0 },
1692 {"enable-anti-alias-hud", false, OPTION_BOOL, "/sim/hud/color/antialiased", true, "", 0 },
1693 {"disable-auto-coordination", false, OPTION_BOOL, "/controls/flight/auto-coordination", false, "", 0 },
1694 {"enable-auto-coordination", false, OPTION_BOOL, "/controls/flight/auto-coordination", true, "", 0 },
1695 {"browser-app", true, OPTION_STRING, "/sim/startup/browser-app", false, "", 0 },
1696 {"disable-hud", false, OPTION_BOOL, "/sim/hud/visibility[1]", false, "", 0 },
1697 {"enable-hud", false, OPTION_BOOL, "/sim/hud/visibility[1]", true, "", 0 },
1698 {"disable-panel", false, OPTION_BOOL, "/sim/panel/visibility", false, "", 0 },
1699 {"enable-panel", false, OPTION_BOOL, "/sim/panel/visibility", true, "", 0 },
1700 {"disable-sound", false, OPTION_BOOL, "/sim/sound/working", false, "", 0 },
1701 {"enable-sound", false, OPTION_BOOL, "/sim/sound/working", true, "", 0 },
1702 {"sound-device", true, OPTION_STRING, "/sim/sound/device-name", false, "", 0 },
1703 {"airport", true, OPTION_FUNC, "", false, "", fgOptAirport },
1704 {"runway", true, OPTION_FUNC, "", false, "", fgOptRunway },
1705 {"vor", true, OPTION_FUNC, "", false, "", fgOptVOR },
1706 {"vor-frequency", true, OPTION_DOUBLE, "/sim/presets/vor-freq", false, "", fgOptVOR },
1707 {"ndb", true, OPTION_FUNC, "", false, "", fgOptNDB },
1708 {"ndb-frequency", true, OPTION_DOUBLE, "/sim/presets/ndb-freq", false, "", fgOptVOR },
1709 {"carrier", true, OPTION_FUNC, "", false, "", fgOptCarrier },
1710 {"carrier-position", true, OPTION_FUNC, "", false, "", fgOptCarrierPos },
1711 {"fix", true, OPTION_FUNC, "", false, "", fgOptFIX },
1712 {"offset-distance", true, OPTION_DOUBLE, "/sim/presets/offset-distance-nm", false, "", 0 },
1713 {"offset-azimuth", true, OPTION_DOUBLE, "/sim/presets/offset-azimuth-deg", false, "", 0 },
1714 {"lon", true, OPTION_FUNC, "", false, "", fgOptLon },
1715 {"lat", true, OPTION_FUNC, "", false, "", fgOptLat },
1716 {"altitude", true, OPTION_FUNC, "", false, "", fgOptAltitude },
1717 {"uBody", true, OPTION_FUNC, "", false, "", fgOptUBody },
1718 {"vBody", true, OPTION_FUNC, "", false, "", fgOptVBody },
1719 {"wBody", true, OPTION_FUNC, "", false, "", fgOptWBody },
1720 {"vNorth", true, OPTION_FUNC, "", false, "", fgOptVNorth },
1721 {"vEast", true, OPTION_FUNC, "", false, "", fgOptVEast },
1722 {"vDown", true, OPTION_FUNC, "", false, "", fgOptVDown },
1723 {"vc", true, OPTION_FUNC, "", false, "", fgOptVc },
1724 {"mach", true, OPTION_FUNC, "", false, "", fgOptMach },
1725 {"heading", true, OPTION_DOUBLE, "/sim/presets/heading-deg", false, "", 0 },
1726 {"roll", true, OPTION_DOUBLE, "/sim/presets/roll-deg", false, "", 0 },
1727 {"pitch", true, OPTION_DOUBLE, "/sim/presets/pitch-deg", false, "", 0 },
1728 {"glideslope", true, OPTION_DOUBLE, "/sim/presets/glideslope-deg", false, "", 0 },
1729 {"roc", true, OPTION_FUNC, "", false, "", fgOptRoc },
1730 {"fg-root", true, OPTION_IGNORE, "", false, "", 0 },
1731 {"fg-scenery", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptFgScenery },
1732 {"terrain-engine", true, OPTION_STRING, "/sim/scenery/engine", false, "tilecache", 0 },
1733 {"lod-levels", true, OPTION_STRING, "/sim/scenery/lod-levels", false, "1 3 5 7", 0 },
1734 {"lod-res", true, OPTION_STRING, "/sim/scenery/lod-res", false, "1", 0 },
1735 {"lod-texturing", true, OPTION_STRING, "/sim/scenery/lod-texturing", false, "bluemarble", 0 },
1736 {"lod-range-mult", true, OPTION_STRING, "/sim/scenery/lod-range-mult", false, "2", 0 },
1737 {"fg-aircraft", true, OPTION_IGNORE | OPTION_MULTI, "", false, "", 0 },
1738 {"fdm", true, OPTION_STRING, "/sim/flight-model", false, "", 0 },
1739 {"aero", true, OPTION_STRING, "/sim/aero", false, "", 0 },
1740 {"aircraft-dir", true, OPTION_IGNORE, "", false, "", 0 },
1741 {"state", true, OPTION_IGNORE, "", false, "", 0 },
1742 {"model-hz", true, OPTION_INT, "/sim/model-hz", false, "", 0 },
1743 {"max-fps", true, OPTION_DOUBLE, "/sim/frame-rate-throttle-hz", false, "", 0 },
1744 {"speed", true, OPTION_DOUBLE, "/sim/speed-up", false, "", 0 },
1745 {"trim", false, OPTION_BOOL, "/sim/presets/trim", true, "", 0 },
1746 {"notrim", false, OPTION_BOOL, "/sim/presets/trim", false, "", 0 },
1747 {"on-ground", false, OPTION_BOOL, "/sim/presets/onground", true, "", 0 },
1748 {"in-air", false, OPTION_BOOL, "/sim/presets/onground", false, "", 0 },
1749 {"disable-hold-short", false, OPTION_BOOL, "/sim/presets/mp-hold-short-override", true, "", 0 },
1750 {"fog-disable", false, OPTION_STRING, "/sim/rendering/fog", false, "disabled", 0 },
1751 {"fog-fastest", false, OPTION_STRING, "/sim/rendering/fog", false, "fastest", 0 },
1752 {"fog-nicest", false, OPTION_STRING, "/sim/rendering/fog", false, "nicest", 0 },
1753 {"disable-horizon-effect", false, OPTION_BOOL, "/sim/rendering/horizon-effect", false, "", 0 },
1754 {"enable-horizon-effect", false, OPTION_BOOL, "/sim/rendering/horizon-effect", true, "", 0 },
1755 {"disable-enhanced-lighting", false, OPTION_FUNC, "", false, "", fgOptEnhancedLighting },
1756 {"enable-enhanced-lighting", false, OPTION_FUNC, "", false, "", fgOptEnhancedLighting },
1757 {"disable-distance-attenuation", false, OPTION_BOOL, "/sim/rendering/distance-attenuation", false, "", 0 },
1758 {"enable-distance-attenuation", false, OPTION_BOOL, "/sim/rendering/distance-attenuation", true, "", 0 },
1759 {"disable-specular-highlight", false, OPTION_BOOL, "/sim/rendering/specular-highlight", false, "", 0 },
1760 {"enable-specular-highlight", false, OPTION_BOOL, "/sim/rendering/specular-highlight", true, "", 0 },
1761 {"disable-clouds", false, OPTION_BOOL, "/environment/clouds/status", false, "", 0 },
1762 {"enable-clouds", false, OPTION_BOOL, "/environment/clouds/status", true, "", 0 },
1763 {"disable-clouds3d", false, OPTION_BOOL, "/sim/rendering/clouds3d-enable", false, "", 0 },
1764 {"enable-clouds3d", false, OPTION_BOOL, "/sim/rendering/clouds3d-enable", true, "", 0 },
1765 {"fov", true, OPTION_FUNC, "", false, "", fgOptFov },
1766 {"aspect-ratio-multiplier", true, OPTION_DOUBLE, "/sim/current-view/aspect-ratio-multiplier", false, "", 0 },
1767 {"disable-fullscreen", false, OPTION_BOOL, "/sim/startup/fullscreen", false, "", 0 },
1768 {"enable-fullscreen", false, OPTION_BOOL, "/sim/startup/fullscreen", true, "", 0 },
1769 {"disable-save-on-exit", false, OPTION_BOOL, "/sim/startup/save-on-exit", false, "", 0 },
1770 {"enable-save-on-exit", false, OPTION_BOOL, "/sim/startup/save-on-exit", true, "", 0 },
1771 {"read-only", false, OPTION_BOOL, "/sim/fghome-readonly", true, "", 0 },
1772 {"ignore-autosave", false, OPTION_FUNC, "", false, "", fgOptIgnoreAutosave },
1773 {"restore-defaults", false, OPTION_BOOL, "/sim/startup/restore-defaults", true, "", 0 },
1774 {"shading-flat", false, OPTION_BOOL, "/sim/rendering/shading", false, "", 0 },
1775 {"shading-smooth", false, OPTION_BOOL, "/sim/rendering/shading", true, "", 0 },
1776 {"texture-filtering", false, OPTION_INT, "/sim/rendering/filtering", 1, "", 0 },
1777 {"disable-wireframe", false, OPTION_BOOL, "/sim/rendering/wireframe", false, "", 0 },
1778 {"enable-wireframe", false, OPTION_BOOL, "/sim/rendering/wireframe", true, "", 0 },
1779 {"materials-file", true, OPTION_STRING, "/sim/rendering/materials-file", false, "", 0 },
1780 {"disable-terrasync", false, OPTION_BOOL, "/sim/terrasync/enabled", false, "", 0 },
1781 {"enable-terrasync", false, OPTION_BOOL, "/sim/terrasync/enabled", true, "", 0 },
1782 {"terrasync-dir", true, OPTION_IGNORE, "", false, "", 0 },
1783 {"download-dir", true, OPTION_IGNORE, "", false, "", 0 },
1784 {"texture-cache-dir", true, OPTION_IGNORE, "", false, "", 0 },
1785 {"enable-texture-cache", false, OPTION_BOOL, "/sim/rendering/texture-cache/cache-enabled", true, "", 0 },
1786 {"disable-texture-cache", false, OPTION_BOOL, "/sim/rendering/texture-cache/cache-enabled", false, "", 0 },
1787 {"allow-nasal-read", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptAllowNasalRead },
1788 {"geometry", true, OPTION_FUNC, "", false, "", fgOptGeometry },
1789 {"bpp", true, OPTION_FUNC, "", false, "", fgOptBpp },
1790 {"units-feet", false, OPTION_STRING, "/sim/startup/units", false, "feet", 0 },
1791 {"units-meters", false, OPTION_STRING, "/sim/startup/units", false, "meters", 0 },
1792 {"timeofday", true, OPTION_STRING, "/sim/startup/time-offset-type", false, "noon", 0 },
1793 {"season", true, OPTION_STRING, "/sim/startup/season", false, "summer", 0 },
1794 {"time-offset", true, OPTION_FUNC, "", false, "", fgOptTimeOffset },
1795 {"time-match-real", false, OPTION_STRING, "/sim/startup/time-offset-type", false, "system-offset", 0 },
1796 {"time-match-local", false, OPTION_STRING, "/sim/startup/time-offset-type", false, "latitude-offset", 0 },
1797 {"start-date-sys", true, OPTION_FUNC, "", false, "", fgOptStartDateSys },
1798 {"start-date-lat", true, OPTION_FUNC, "", false, "", fgOptStartDateLat },
1799 {"start-date-gmt", true, OPTION_FUNC, "", false, "", fgOptStartDateGmt },
1800 {"hud-tris", false, OPTION_STRING, "/sim/hud/frame-stat-type", false, "tris", 0 },
1801 {"hud-culled", false, OPTION_STRING, "/sim/hud/frame-stat-type", false, "culled", 0 },
1802 {"atcsim", true, OPTION_CHANNEL, "", false, "dummy", 0 },
1803 {"atlas", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1804 {"httpd", true, OPTION_FUNC , "", false, "", fgOptHttpd },
1805 {"jpg-httpd", true, OPTION_FUNC, "", false, "", fgOptJpgHttpd },
1806 {"native", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1807 {"native-ctrls", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1808 {"native-fdm", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1809 {"native-gui", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1810 {"opengc", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1811 {"AV400", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1812 {"AV400Sim", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1813 {"AV400WSimA", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1814 {"AV400WSimB", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1815 {"flarm", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1816 {"garmin", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1817 {"igc", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1818 {"nmea", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1819 {"generic", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1820 {"props", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1821 {"telnet", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1822 {"pve", true, OPTION_CHANNEL, "", false, "", 0 },
1823 {"ray", true, OPTION_CHANNEL, "", false, "", 0 },
1824 {"rul", true, OPTION_CHANNEL, "", false, "", 0 },
1825 {"joyclient", true, OPTION_CHANNEL, "", false, "", 0 },
1826 {"jsclient", true, OPTION_CHANNEL, "", false, "", 0 },
1827 {"proxy", true, OPTION_FUNC, "", false, "", fgSetupProxy },
1828 {"callsign", true, OPTION_FUNC, "", false, "", fgOptCallSign},
1829 {"multiplay", true, OPTION_CHANNEL | OPTION_MULTI, "", false, "", 0 },
1830 #if FG_HAVE_HLA
1831 {"hla", true, OPTION_CHANNEL, "", false, "", 0 },
1832 {"hla-local", true, OPTION_CHANNEL, "", false, "", 0 },
1833 #endif
1834 {"trace-read", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptTraceRead },
1835 {"trace-write", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptTraceWrite },
1836 {"log-level", true, OPTION_FUNC, "", false, "", fgOptLogLevel },
1837 {"log-class", true, OPTION_FUNC, "", false, "", fgOptLogClasses },
1838 {"log-dir", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptLogDir },
1839 {"view-offset", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptViewOffset },
1840 {"visibility", true, OPTION_FUNC, "", false, "", fgOptVisibilityMeters },
1841 {"visibility-miles", true, OPTION_FUNC, "", false, "", fgOptVisibilityMiles },
1842 {"random-wind", false, OPTION_FUNC, "", false, "", fgOptRandomWind },
1843 {"wind", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptWind },
1844 {"turbulence", true, OPTION_FUNC, "", false, "", fgOptTurbulence },
1845 {"ceiling", true, OPTION_FUNC, "", false, "", fgOptCeiling },
1846 {"wp", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptWp },
1847 {"flight-plan", true, OPTION_STRING, "/autopilot/route-manager/file-path", false, "", NULL },
1848 {"config", true, OPTION_IGNORE | OPTION_MULTI, "", false, "", 0 },
1849 {"addon", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptAddon },
1850 {"data", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptAdditionalDataDir },
1851 {"aircraft", true, OPTION_STRING, "/sim/aircraft", false, "", 0 },
1852 {"vehicle", true, OPTION_STRING, "/sim/aircraft", false, "", 0 },
1853 {"failure", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptFailure },
1854 #ifdef ENABLE_IAX
1855 {"enable-fgcom", false, OPTION_BOOL, "/sim/fgcom/enabled", true, "", 0 },
1856 {"disable-fgcom", false, OPTION_BOOL, "/sim/fgcom/enabled", false, "", 0 },
1857 #endif
1858 {"com1", true, OPTION_DOUBLE, "/instrumentation/comm[0]/frequencies/selected-mhz", false, "", 0 },
1859 {"com2", true, OPTION_DOUBLE, "/instrumentation/comm[1]/frequencies/selected-mhz", false, "", 0 },
1860 {"nav1", true, OPTION_FUNC, "", false, "", fgOptNAV1 },
1861 {"nav2", true, OPTION_FUNC, "", false, "", fgOptNAV2 },
1862 {"adf", /*legacy*/ true, OPTION_FUNC, "", false, "", fgOptADF },
1863 {"adf1", true, OPTION_FUNC, "", false, "", fgOptADF1 },
1864 {"adf2", true, OPTION_FUNC, "", false, "", fgOptADF2 },
1865 {"dme", true, OPTION_FUNC, "", false, "", fgOptDME },
1866 {"min-status", true, OPTION_STRING, "/sim/aircraft-min-status", false, "all", 0 },
1867 {"livery", true, OPTION_FUNC, "", false, "", fgOptLivery },
1868 {"ai-scenario", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptScenario },
1869 {"parking-id", true, OPTION_FUNC, "", false, "", fgOptParkpos },
1870 {"parkpos", true, OPTION_FUNC, "", false, "", fgOptParkpos },
1871 {"version", false, OPTION_IGNORE, "", false, "", 0 },
1872 {"json-report", false, OPTION_IGNORE, "", false, "", 0 },
1873 {"enable-fpe", false, OPTION_IGNORE, "", false, "", 0},
1874 {"fgviewer", false, OPTION_IGNORE, "", false, "", 0},
1875 {"no-default-config", false, OPTION_IGNORE, "", false, "", 0},
1876 {"prop", true, OPTION_FUNC | OPTION_MULTI, "", false, "", fgOptSetProperty},
1877 {"load-tape", true, OPTION_FUNC, "", false, "", fgOptLoadTape },
1878 {"developer", true, OPTION_IGNORE | OPTION_BOOL, "", false, "", nullptr },
1879 {"jsbsim-output-directive-file", true, OPTION_STRING, "/sim/jsbsim/output-directive-file", false, "", nullptr },
1880 {"disable-gui", false, OPTION_FUNC, "", false, "", fgOptDisableGUI },
1881 {"restart-launcher", false, OPTION_BOOL, "/sim/restart-launcher-on-exit", true, "", nullptr},
1882 {nullptr, false, 0, nullptr, false, nullptr, nullptr}
1883 };
1884
1885 namespace flightgear
1886 {
1887
1888 /**
1889 * internal storage of a value->option binding
1890 */
1891 class OptionValue
1892 {
1893 public:
OptionValue(OptionDesc * d,const string & v)1894 OptionValue(OptionDesc* d, const string& v) :
1895 desc(d), value(v)
1896 {;}
1897
1898 OptionDesc* desc;
1899 string value;
1900 };
1901
1902 typedef std::vector<OptionValue> OptionValueVec;
1903 typedef std::map<string, OptionDesc*> OptionDescDict;
1904
1905 class Options::OptionsPrivate
1906 {
1907 public:
1908
findValue(const string & key) const1909 OptionValueVec::const_iterator findValue(const string& key) const
1910 {
1911 OptionValueVec::const_iterator it = values.begin();
1912 for (; it != values.end(); ++it) {
1913 if (!it->desc) {
1914 continue; // ignore markers
1915 }
1916
1917 if (it->desc->option == key) {
1918 return it;
1919 }
1920 } // of set values iteration
1921
1922 return it; // not found
1923 }
1924
findValue(const string & key)1925 OptionValueVec::iterator findValue(const string& key)
1926 {
1927 OptionValueVec::iterator it = values.begin();
1928 for (; it != values.end(); ++it) {
1929 if (!it->desc) {
1930 continue; // ignore markers
1931 }
1932
1933 if (it->desc->option == key) {
1934 return it;
1935 }
1936 } // of set values iteration
1937
1938 return it; // not found
1939 }
1940
findOption(const string & key) const1941 OptionDesc* findOption(const string& key) const
1942 {
1943 OptionDescDict::const_iterator it = options.find(key);
1944 if (it == options.end()) {
1945 return NULL;
1946 }
1947
1948 return it->second;
1949 }
1950
processOption(OptionDesc * desc,const string & arg_value)1951 int processOption(OptionDesc* desc, const string& arg_value)
1952 {
1953 if (!desc) {
1954 return FG_OPTIONS_OK; // tolerate marker options
1955 }
1956
1957 switch ( desc->type & 0xffff ) {
1958 case OPTION_BOOL:
1959 fgSetBool( desc->property, desc->b_param );
1960 break;
1961 case OPTION_STRING:
1962 if ( desc->has_param && !arg_value.empty() ) {
1963 fgSetString( desc->property, arg_value.c_str() );
1964 } else if ( !desc->has_param && arg_value.empty() ) {
1965 fgSetString( desc->property, desc->s_param );
1966 } else if ( desc->has_param ) {
1967 SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" );
1968 return FG_OPTIONS_ERROR;
1969 } else {
1970 SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' does not have a parameter" );
1971 return FG_OPTIONS_ERROR;
1972 }
1973 break;
1974 case OPTION_DOUBLE:
1975 if ( !arg_value.empty() ) {
1976 fgSetDouble( desc->property, atof( arg_value ) );
1977 } else {
1978 SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" );
1979 return FG_OPTIONS_ERROR;
1980 }
1981 break;
1982 case OPTION_INT:
1983 if ( !arg_value.empty() ) {
1984 fgSetInt( desc->property, atoi( arg_value ) );
1985 } else {
1986 SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" );
1987 return FG_OPTIONS_ERROR;
1988 }
1989 break;
1990 case OPTION_CHANNEL:
1991 // XXX return value of add_channel should be checked?
1992 if ( desc->has_param && !arg_value.empty() ) {
1993 add_channel( desc->option, arg_value );
1994 } else if ( !desc->has_param && arg_value.empty() ) {
1995 add_channel( desc->option, desc->s_param );
1996 } else if ( desc->has_param ) {
1997 SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" );
1998 return FG_OPTIONS_ERROR;
1999 } else {
2000 SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' does not have a parameter" );
2001 return FG_OPTIONS_ERROR;
2002 }
2003 break;
2004 case OPTION_FUNC:
2005 if ( desc->has_param && !arg_value.empty() ) {
2006 return desc->func( arg_value.c_str() );
2007 } else if ( !desc->has_param && arg_value.empty() ) {
2008 return desc->func( desc->s_param );
2009 } else if ( desc->has_param ) {
2010 SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' needs a parameter" );
2011 return FG_OPTIONS_ERROR;
2012 } else {
2013 SG_LOG( SG_GENERAL, SG_ALERT, "Option '" << desc->option << "' does not have a parameter" );
2014 return FG_OPTIONS_ERROR;
2015 }
2016 break;
2017
2018 case OPTION_IGNORE:
2019 break;
2020 }
2021
2022 return FG_OPTIONS_OK;
2023 }
2024
2025 /**
2026 * insert a marker value into the values vector. This is necessary
2027 * when processing options, to ensure the correct ordering, where we scan
2028 * for marker values in reverse, and then forwards within each group.
2029 */
insertGroupMarker()2030 void insertGroupMarker()
2031 {
2032 values.push_back(OptionValue(NULL, "-"));
2033 }
2034
2035 /**
2036 * given a current iterator into the values, find the preceding group marker,
2037 * or return the beginning of the value vector.
2038 */
rfindGroup(OptionValueVec::const_iterator pos) const2039 OptionValueVec::const_iterator rfindGroup(OptionValueVec::const_iterator pos) const
2040 {
2041 while (--pos != values.begin()) {
2042 if (pos->desc == NULL) {
2043 return pos; // found a marker, we're done
2044 }
2045 }
2046
2047 return pos;
2048 }
2049
2050 // Return a pointer to a new JSON array node
2051 // (["/foo/bar", "/other/path", ...]) created from the given PathList.
createJSONArrayFromPathList(const PathList & pl) const2052 cJSON *createJSONArrayFromPathList(const PathList& pl) const
2053 {
2054 cJSON *resultNode = cJSON_CreateArray();
2055 cJSON *prevNode = nullptr;
2056 bool isFirst = true;
2057
2058 for (const SGPath& path : pl) {
2059 cJSON *pathNode = cJSON_CreateString(path.utf8Str().c_str());
2060
2061 if (isFirst) {
2062 isFirst = false;
2063 resultNode->child = pathNode;
2064 } else {
2065 prevNode->next = pathNode;
2066 pathNode->prev = prevNode;
2067 }
2068
2069 prevNode = pathNode;
2070 }
2071
2072 return resultNode;
2073 }
2074
2075 bool showHelp,
2076 verbose,
2077 showAircraft,
2078 shouldLoadDefaultConfig;
2079
2080 OptionDescDict options;
2081 OptionValueVec values;
2082 simgear::PathList configFiles;
2083 simgear::PathList propertyFiles;
2084 };
2085
sharedInstance()2086 Options* Options::sharedInstance()
2087 {
2088 if (shared_instance == NULL) {
2089 shared_instance = new Options;
2090 }
2091
2092 return shared_instance;
2093 }
2094
reset()2095 void Options::reset()
2096 {
2097 if (shared_instance != nullptr) {
2098 delete shared_instance;
2099 shared_instance = nullptr;
2100 }
2101 }
2102
Options()2103 Options::Options() :
2104 p(new OptionsPrivate())
2105 {
2106 p->showHelp = false;
2107 p->verbose = false;
2108 p->showAircraft = false;
2109 p->shouldLoadDefaultConfig = true;
2110
2111 // build option map
2112 OptionDesc *desc = &fgOptionArray[ 0 ];
2113 while ( desc->option != 0 ) {
2114 p->options[ desc->option ] = desc;
2115 ++desc;
2116 }
2117 }
2118
~Options()2119 Options::~Options()
2120 {
2121 }
2122
init(int argc,char ** argv,const SGPath & appDataPath)2123 OptionResult Options::init(int argc, char** argv, const SGPath& appDataPath)
2124 {
2125 // first, process the command line
2126 bool inOptions = true;
2127 for (int i=1; i<argc; ++i) {
2128 // important : on first run after the Gatekeeper quarantine flag is
2129 // cleared, launchd passes us a null argument here. Avoid dying in
2130 // that case.
2131 if (!argv[i]) {
2132 continue;
2133 }
2134
2135 if (inOptions && (argv[i][0] == '-')) {
2136 if (strcmp(argv[i], "--") == 0) { // end of options delimiter
2137 inOptions = false;
2138 continue;
2139 }
2140
2141 int result = parseOption(argv[i], /* fromConfigFile */ false);
2142 processArgResult(result);
2143 } else {
2144 // XML properties file
2145 SGPath f = SGPath::fromUtf8(argv[i]);
2146 if (!f.exists()) {
2147 SG_LOG(SG_GENERAL, SG_ALERT, "config file not found:" << f);
2148 } else {
2149 p->propertyFiles.push_back(f);
2150 }
2151 }
2152 } // of arguments iteration
2153 p->insertGroupMarker(); // command line is one group
2154
2155 // establish log-level before anything else - otherwise it is not possible
2156 // to show extra (debug/info/warning) messages for the start-up phase.
2157 // Leave the simgear logstream default value of SG_ALERT if the argument is
2158 // not supplied.
2159 if (isOptionSet("log-level"))
2160 fgOptLogLevel(valueForOption("log-level").c_str());
2161
2162 simgear::PathList::const_iterator i;
2163 for (i = p->configFiles.begin(); i != p->configFiles.end(); ++i) {
2164 readConfig(*i);
2165 }
2166
2167 if (!p->shouldLoadDefaultConfig) {
2168 return setupRoot(argc, argv);
2169 }
2170
2171 // then config files
2172 SGPath config;
2173
2174 if( !hostname.empty() ) {
2175 // Check for ~/.fgfsrc.hostname
2176 config = SGPath::home();
2177 config.append(".fgfsrc");
2178 config.concat( "." );
2179 config.concat( hostname );
2180 readConfig(config);
2181 }
2182
2183 // Check for ~/.fgfsrc
2184 config = SGPath::home();
2185 config.append(".fgfsrc");
2186 readConfig(config);
2187
2188 // check for a config file in app data
2189 SGPath appDataConfig(appDataPath);
2190 appDataConfig.append("fgfsrc");
2191 if (appDataConfig.exists()) {
2192 readConfig(appDataConfig);
2193 }
2194
2195 // setup FG_ROOT
2196 auto res = setupRoot(argc, argv);
2197 if (res != FG_OPTIONS_OK) {
2198 return res;
2199 }
2200
2201 // system.fgfsrc is disabled, as we no longer allow anything in fgdata to set
2202 // fg-root/fg-home/fg-aircraft and hence control what files Nasal can access
2203 std::string nameForError = config.utf8Str();
2204 if( ! hostname.empty() ) {
2205 config = globals->get_fg_root();
2206 config.append( "system.fgfsrc" );
2207 config.concat( "." );
2208 config.concat( hostname );
2209 if (config.exists()) {
2210 flightgear::fatalMessageBoxThenExit(
2211 "Unsupported configuration",
2212 "You have a '" + config.utf8Str() + "' file, which is no longer "
2213 "processed for security reasons.",
2214 "If you created this file intentionally, please move it to '" +
2215 nameForError + "'.");
2216 }
2217 }
2218
2219 config = globals->get_fg_root();
2220 config.append( "system.fgfsrc" );
2221 if (config.exists()) {
2222 flightgear::fatalMessageBoxThenExit(
2223 "Unsupported configuration",
2224 "You have a '" + config.utf8Str() + "' file, which is no longer "
2225 "processed for security reasons.",
2226 "If you created this file intentionally, please move it to '" +
2227 nameForError + "'.");
2228 }
2229
2230 return FG_OPTIONS_OK;
2231 }
2232
initPaths()2233 void Options::initPaths()
2234 {
2235 for (const string& pathOpt : valuesForOption("fg-aircraft")) {
2236 PathList paths = SGPath::pathsFromUtf8(pathOpt);
2237 globals->append_aircraft_paths(paths);
2238 }
2239
2240 const char* envp = ::getenv("FG_AIRCRAFT");
2241 if (envp) {
2242 globals->append_aircraft_paths(SGPath::pathsFromEnv("FG_AIRCRAFT"));
2243 }
2244
2245 }
2246
initAircraft()2247 OptionResult Options::initAircraft()
2248 {
2249 string aircraft;
2250 if (isOptionSet("aircraft")) {
2251 aircraft = valueForOption("aircraft");
2252 } else if (isOptionSet("vehicle")) {
2253 aircraft = valueForOption("vehicle");
2254 }
2255
2256 if (!aircraft.empty()) {
2257 fgSetString("/sim/aircraft-id", aircraft);
2258 const auto lastDotPos = aircraft.rfind('.');
2259 if (lastDotPos != string::npos) {
2260 // ensure /sim/aircraft is only the local ID, not the fully-qualified ID
2261 // otherwise some existing logic gets confused.
2262 fgSetString("/sim/aircraft", aircraft.substr(lastDotPos + 1));
2263 } else {
2264 fgSetString("/sim/aircraft", aircraft);
2265 }
2266
2267 SG_LOG(SG_INPUT, SG_INFO, "aircraft = " << aircraft );
2268 } else {
2269 SG_LOG(SG_INPUT, SG_INFO, "No user specified aircraft, using default" );
2270 // ensure aircraft-id is valid
2271 fgSetString("/sim/aircraft-id", fgGetString("/sim/aircraft"));
2272 }
2273
2274 if (p->showAircraft) {
2275 PathList path_list;
2276
2277 fgOptLogLevel( "alert" );
2278
2279 // First place to check is the 'Aircraft' sub-directory in $FG_ROOT
2280
2281 SGPath rootAircraft = globals->get_fg_root();
2282 rootAircraft.append("Aircraft");
2283 path_list.push_back(rootAircraft);
2284
2285 // Additionally, aircraft may also be found in user-defined places
2286 // (via $FG_AIRCRAFT or with the '--fg-aircraft' option)
2287 PathList aircraft_paths = globals->get_aircraft_paths();
2288
2289 path_list.insert(path_list.end(), aircraft_paths.begin(),
2290 aircraft_paths.end());
2291
2292 fgShowAircraft(path_list);
2293 // this is to indicate that we did show it
2294 return FG_OPTIONS_SHOW_AIRCRAFT;
2295 }
2296
2297 if (isOptionSet("aircraft-dir")) {
2298 SGPath aircraftDirPath = SGPath::fromUtf8(valueForOption("aircraft-dir"));
2299 SGPath realAircraftPath = aircraftDirPath.realpath();
2300 globals->append_read_allowed_paths(realAircraftPath);
2301
2302 // Set this now, so it's available in FindAndCacheAircraft. Use realpath()
2303 // as in FGGlobals::append_aircraft_path(), otherwise fgValidatePath()
2304 // will deny access to resources under this path if one of its components
2305 // is a symlink (which is not a problem, since it was given as is by the
2306 // user---this is very different from a symlink *under* the aircraft dir
2307 // or a scenery dir).
2308 fgSetString("/sim/aircraft-dir", realAircraftPath.utf8Str());
2309 }
2310
2311 if (isOptionSet("state")) {
2312 std::string stateName = valueForOption("state");
2313 // can't validate this until the -set.xml is parsed
2314 fgSetString("/sim/aircraft-state", stateName);
2315 }
2316
2317 return FG_OPTIONS_OK;
2318 }
2319
processArgResult(int result)2320 void Options::processArgResult(int result)
2321 {
2322 if ((result == FG_OPTIONS_HELP) || (result == FG_OPTIONS_ERROR))
2323 p->showHelp = true;
2324 else if (result == FG_OPTIONS_VERBOSE_HELP)
2325 p->verbose = true;
2326 else if (result == FG_OPTIONS_SHOW_AIRCRAFT) {
2327 p->showAircraft = true;
2328 } else if (result == FG_OPTIONS_NO_DEFAULT_CONFIG) {
2329 p->shouldLoadDefaultConfig = false;
2330 } else if (result == FG_OPTIONS_SHOW_SOUND_DEVICES) {
2331 SGSoundMgr smgr;
2332
2333 smgr.init();
2334 string vendor = smgr.get_vendor();
2335 string renderer = smgr.get_renderer();
2336 cout << renderer << " provided by " << vendor << endl;
2337 cout << endl << "No. Device" << endl;
2338
2339 vector <std::string>devices = smgr.get_available_devices();
2340 for (vector <std::string>::size_type i=0; i<devices.size(); i++) {
2341 cout << i << ". \"" << devices[i] << "\"" << endl;
2342 }
2343 devices.clear();
2344 smgr.stop();
2345 exit(0);
2346 } else if (result == FG_OPTIONS_EXIT) {
2347 exit(0);
2348 }
2349 }
2350
readConfig(const SGPath & path)2351 void Options::readConfig(const SGPath& path)
2352 {
2353 sg_gzifstream in( path );
2354 if ( !in.is_open() ) {
2355 return;
2356 }
2357
2358 SG_LOG( SG_GENERAL, SG_INFO, "Processing config file: " << path );
2359
2360 in >> skipcomment;
2361 while ( ! in.eof() ) {
2362 string line;
2363 getline( in, line, '\n' );
2364
2365 // catch extraneous (DOS) line ending character
2366 int i;
2367 for (i = line.length(); i > 0; i--)
2368 if (line[i - 1] > 32)
2369 break;
2370 line = line.substr( 0, i );
2371
2372 if ( parseOption(line, /* fromConfigFile */ true) == FG_OPTIONS_ERROR ) {
2373 cerr << endl << "Config file parse error: " << path << " '"
2374 << line << "'" << endl;
2375 p->showHelp = true;
2376 }
2377 in >> skipcomment;
2378 }
2379
2380 p->insertGroupMarker(); // each config file is a group
2381 }
2382
parseOption(const string & s,bool fromConfigFile)2383 int Options::parseOption(const string& s, bool fromConfigFile)
2384 {
2385 if ((s == "--help") || (s=="-h")) {
2386 return FG_OPTIONS_HELP;
2387 } else if ( (s == "--verbose") || (s == "-v") ) {
2388 // verbose help/usage request
2389 return FG_OPTIONS_VERBOSE_HELP;
2390 } else if ((s == "--console") || (s == "-c")) {
2391 fgOptConsole(nullptr);
2392 return FG_OPTIONS_OK;
2393 } else if (s.find("-psn") == 0) {
2394 // on Mac, when launched from the GUI, we are passed the ProcessSerialNumber
2395 // as an argument (and no others). Silently ignore the argument here.
2396 return FG_OPTIONS_OK;
2397 } else if ( s.find( "--show-aircraft") == 0) {
2398 return(FG_OPTIONS_SHOW_AIRCRAFT);
2399 } else if ( s.find( "--show-sound-devices") == 0) {
2400 return(FG_OPTIONS_SHOW_SOUND_DEVICES);
2401 } else if ( s.find( "--no-default-config") == 0) {
2402 return FG_OPTIONS_NO_DEFAULT_CONFIG;
2403 } else if ( s.find( "--prop:") == 0) {
2404 // property setting has a slightly different syntax, so fudge things
2405 OptionDesc* desc = p->findOption("prop");
2406 if (s.find("=", 7) == string::npos) { // no equals token
2407 SG_LOG(SG_GENERAL, SG_ALERT, "malformed property option:" << s);
2408 return FG_OPTIONS_ERROR;
2409 }
2410
2411 p->values.push_back(OptionValue(desc, s.substr(7)));
2412 return FG_OPTIONS_OK;
2413 } else if ( s.find("--config=") == 0) {
2414 SGPath path = s.substr(9);
2415 if (path.extension() == "xml") {
2416 p->propertyFiles.push_back(path);
2417 } else if (fromConfigFile) {
2418 flightgear::fatalMessageBoxThenExit(
2419 "FlightGear",
2420 "Invalid use of the --config option.",
2421 "Sorry, it is currently not supported to load a configuration file "
2422 "using --config from another configuration file.\n\n"
2423 "Note: this does not apply to loading of XML PropertyList files "
2424 "with --config.");
2425 } else { // the --config option comes from the command line
2426 p->configFiles.push_back(path);
2427 }
2428
2429 return FG_OPTIONS_OK;
2430 } else if ( s.find( "--" ) == 0 ) {
2431 size_t eqPos = s.find( '=' );
2432 string key, value;
2433 if (eqPos == string::npos) {
2434 key = s.substr(2);
2435 } else {
2436 key = s.substr( 2, eqPos - 2 );
2437 value = s.substr( eqPos + 1);
2438 }
2439
2440 return addOption(key, value);
2441 } else {
2442 flightgear::modalMessageBox("Unknown option", "Unknown command-line option: " + s);
2443 return FG_OPTIONS_ERROR;
2444 }
2445 }
2446
addOption(const string & key,const string & value)2447 int Options::addOption(const string &key, const string &value)
2448 {
2449 if (key == "config") {
2450 // occurs when the launcher adds --config options
2451 SGPath path(value);
2452 if (!path.exists()) {
2453 return FG_OPTIONS_ERROR;
2454 }
2455
2456 if (path.extension() == "xml") {
2457 p->propertyFiles.push_back(path);
2458 } else {
2459 p->insertGroupMarker(); // begin a group for the config file
2460 readConfig(path);
2461 }
2462
2463 return FG_OPTIONS_OK;
2464 }
2465
2466 OptionDesc* desc = p->findOption(key);
2467 if (!desc) {
2468 flightgear::modalMessageBox("Unknown option", "Unknown command-line option: " + key);
2469 return FG_OPTIONS_ERROR;
2470 }
2471
2472 if (!(desc->type & OPTION_MULTI)) {
2473 OptionValueVec::const_iterator it = p->findValue(key);
2474 if (it != p->values.end()) {
2475 SG_LOG(SG_GENERAL, SG_WARN, "multiple values forbidden for option:" << key << ", ignoring:" << value);
2476 return FG_OPTIONS_OK;
2477 }
2478 }
2479
2480 p->values.push_back(OptionValue(desc, value));
2481 return FG_OPTIONS_OK;
2482 }
2483
setOption(const string & key,const string & value)2484 int Options::setOption(const string &key, const string &value)
2485 {
2486 OptionDesc* desc = p->findOption(key);
2487 if (!desc) {
2488 flightgear::modalMessageBox("Unknown option", "Unknown command-line option: " + key);
2489 return FG_OPTIONS_ERROR;
2490 }
2491
2492 if (!(desc->type & OPTION_MULTI)) {
2493 OptionValueVec::iterator it = p->findValue(key);
2494 if (it != p->values.end()) {
2495 // remove existing valye
2496 p->values.erase(it);
2497 }
2498 }
2499
2500 p->values.push_back(OptionValue(desc, value));
2501 return FG_OPTIONS_OK;
2502 }
2503
clearOption(const std::string & key)2504 void Options::clearOption(const std::string& key)
2505 {
2506 OptionValueVec::iterator it = p->findValue(key);
2507 for (; it != p->values.end(); it = p->findValue(key)) {
2508 p->values.erase(it);
2509 }
2510 }
2511
isOptionSet(const string & key) const2512 bool Options::isOptionSet(const string &key) const
2513 {
2514 OptionValueVec::const_iterator it = p->findValue(key);
2515 return (it != p->values.end());
2516 }
2517
valueForOption(const string & key,const string & defValue) const2518 string Options::valueForOption(const string& key, const string& defValue) const
2519 {
2520 OptionValueVec::const_iterator it = p->findValue(key);
2521 if (it == p->values.end()) {
2522 return defValue;
2523 }
2524
2525 return it->value;
2526 }
2527
valuesForOption(const std::string & key) const2528 string_list Options::valuesForOption(const std::string& key) const
2529 {
2530 string_list result;
2531 OptionValueVec::const_iterator it = p->values.begin();
2532 for (; it != p->values.end(); ++it) {
2533 if (!it->desc) {
2534 continue; // ignore marker values
2535 }
2536
2537 if (it->desc->option == key) {
2538 result.push_back(it->value);
2539 }
2540 }
2541
2542 return result;
2543 }
2544
defaultDownloadDir()2545 SGPath defaultDownloadDir()
2546 {
2547 #if defined(SG_WINDOWS)
2548 const SGPath p = SGPath::home() / "FlightGear" / "Downloads";
2549 return p;
2550 #endif
2551 return globals->get_fg_home();
2552 }
2553
actualDownloadDir()2554 SGPath Options::actualDownloadDir()
2555 {
2556 SGPath downloadDir = SGPath::fromUtf8(valueForOption("download-dir"));
2557 if (!downloadDir.isNull()) {
2558 return downloadDir;
2559 }
2560
2561 return defaultDownloadDir();
2562 }
2563
defaultTextureCacheDir()2564 SGPath defaultTextureCacheDir()
2565 {
2566 return Options::sharedInstance()->actualDownloadDir() / "TextureCache";
2567 }
2568
processOptions()2569 OptionResult Options::processOptions()
2570 {
2571 // establish locale before showing help (this selects the default locale,
2572 // when no explicit option was set)
2573 globals->get_locale()->selectLanguage(valueForOption("language").c_str());
2574
2575 // now FG_ROOT is setup, process various command line options that bail us
2576 // out quickly, but rely on aircraft / root settings
2577 if (p->showHelp) {
2578 showUsage();
2579 return FG_OPTIONS_EXIT;
2580 }
2581
2582 // processing order is complicated. We must process groups LIFO, but the
2583 // values *within* each group in FIFO order, to retain consistency with
2584 // older versions of FG, and existing user configs.
2585 // in practice this means system.fgfsrc must be *processed* before
2586 // .fgfsrc, which must be processed before the command line args, and so on.
2587 OptionValueVec::const_iterator groupEnd = p->values.end();
2588
2589 while (groupEnd != p->values.begin()) {
2590 OptionValueVec::const_iterator groupBegin = p->rfindGroup(groupEnd);
2591 // run over the group in FIFO order
2592 OptionValueVec::const_iterator it;
2593 for (it = groupBegin; it != groupEnd; ++it) {
2594 int result = p->processOption(it->desc, it->value);
2595 switch(result)
2596 {
2597 case FG_OPTIONS_ERROR:
2598 showUsage();
2599 return FG_OPTIONS_ERROR;
2600
2601 case FG_OPTIONS_EXIT:
2602 return FG_OPTIONS_EXIT;
2603
2604 default:
2605 break;
2606 }
2607 if (it->desc) {
2608 SG_LOG(SG_GENERAL, SG_INFO, "\toption:" << it->desc->option << " = " << it->value);
2609 }
2610 }
2611
2612 groupEnd = groupBegin;
2613 }
2614
2615 for (const SGPath& file : p->propertyFiles) {
2616 SG_LOG(SG_GENERAL, SG_INFO,
2617 "Reading command-line property file " << file);
2618 readProperties(file, globals->get_props());
2619 }
2620
2621 // now options are process, do supplemental fixup
2622 const char *envp = ::getenv( "FG_SCENERY" );
2623 if (envp) {
2624 globals->append_fg_scenery(SGPath::pathsFromEnv("FG_SCENERY"));
2625 }
2626
2627 // Download dir fix-up
2628 SGPath downloadDir = SGPath::fromUtf8(valueForOption("download-dir"));
2629 if (downloadDir.isNull()) {
2630 downloadDir = defaultDownloadDir();
2631 SG_LOG(SG_GENERAL, SG_INFO,
2632 "Using default download dir: " << downloadDir);
2633 } else {
2634 SG_LOG(SG_GENERAL, SG_INFO,
2635 "Using explicit download dir: " << downloadDir);
2636 }
2637
2638 simgear::Dir d(downloadDir);
2639 if (!d.exists()) {
2640 SG_LOG(SG_GENERAL, SG_INFO,
2641 "Creating download dir: " << downloadDir);
2642 d.create(0755);
2643 }
2644
2645 // This is safe because the value of 'downloadDir' is trustworthy. In
2646 // particular, it can't be influenced by Nasal code, not even indirectly
2647 // via a Nasal-writable place such as the property tree.
2648 globals->set_download_dir(downloadDir);
2649
2650 // Texture Cache directory handling
2651 SGPath textureCacheDir = SGPath::fromUtf8(valueForOption("texture-cache-dir"));
2652 if (textureCacheDir.isNull()) {
2653 textureCacheDir = defaultTextureCacheDir();
2654 SG_LOG(SG_GENERAL, SG_INFO,
2655 "Using default texture cache directory: " << textureCacheDir);
2656 }
2657 else {
2658 SG_LOG(SG_GENERAL, SG_INFO,
2659 "Using explicit texture cache directory: " << textureCacheDir);
2660 }
2661
2662 simgear::Dir tcd(textureCacheDir);
2663 if (!tcd.exists()) {
2664 SG_LOG(SG_GENERAL, SG_INFO,
2665 "Creating texture cache directory: " << textureCacheDir);
2666 tcd.create(0755);
2667 }
2668
2669 globals->set_texture_cache_dir(textureCacheDir);
2670
2671
2672 // TerraSync directory fixup
2673 SGPath terrasyncDir = SGPath::fromUtf8(valueForOption("terrasync-dir"));
2674 if (terrasyncDir.isNull()) {
2675 terrasyncDir = downloadDir / "TerraSync";
2676 // No “default” qualifier here, because 'downloadDir' may be non-default
2677 SG_LOG(SG_GENERAL, SG_INFO,
2678 "Using TerraSync dir: " << terrasyncDir);
2679 } else {
2680 SG_LOG(SG_GENERAL, SG_INFO,
2681 "Using explicit TerraSync dir: " << terrasyncDir);
2682 }
2683
2684 d = simgear::Dir(terrasyncDir);
2685 if (!d.exists()) {
2686 SG_LOG(SG_GENERAL, SG_INFO,
2687 "Creating TerraSync dir: " << terrasyncDir);
2688 d.create(0755);
2689 }
2690
2691 // This is safe because the value of 'terrasyncDir' is trustworthy. In
2692 // particular, it can't be influenced by Nasal code, not even indirectly
2693 // via a Nasal-writable place such as the property tree.
2694 globals->set_terrasync_dir(terrasyncDir);
2695
2696 // check if we setup a scenery path so far
2697 bool addFGDataScenery = globals->get_fg_scenery().empty();
2698
2699 // always add the terrasync location, regardless of whether terrasync
2700 // is enabled or not. This allows us to toggle terrasync on/off at
2701 // runtime and have things work as expected
2702 const PathList& scenery_paths(globals->get_fg_scenery());
2703 if (std::find(scenery_paths.begin(), scenery_paths.end(), terrasyncDir) == scenery_paths.end()) {
2704 // terrasync dir is not in the scenery paths, add it
2705 globals->append_fg_scenery(terrasyncDir);
2706 }
2707
2708 if (addFGDataScenery) {
2709 // no scenery paths set at all, use the data in FG_ROOT
2710 // ensure this path is added last
2711 SGPath root(globals->get_fg_root());
2712 root.append("Scenery");
2713 globals->append_fg_scenery(root);
2714 }
2715
2716 if (isOptionSet("json-report")) {
2717 printJSONReport();
2718 return FG_OPTIONS_EXIT;
2719 } else if (isOptionSet("version")) {
2720 showVersion();
2721 return FG_OPTIONS_EXIT;
2722 }
2723
2724 return FG_OPTIONS_OK;
2725 }
2726
showUsage() const2727 void Options::showUsage() const
2728 {
2729 fgOptLogLevel( "alert" );
2730
2731 FGLocale *locale = globals->get_locale();
2732 SGPropertyNode options_root;
2733
2734 simgear::requestConsole(); // ensure console is shown on Windows
2735 cout << endl;
2736
2737 try {
2738 fgLoadProps("options.xml", &options_root);
2739 } catch (const sg_exception &) {
2740 cout << "Unable to read the help file." << endl;
2741 cout << "Make sure the file options.xml is located in the FlightGear base directory," << endl;
2742 cout << "and the location of the base directory is specified by setting $FG_ROOT or" << endl;
2743 cout << "by adding --fg-root=path as a program argument." << endl;
2744
2745 exit(-1);
2746 }
2747
2748 SGPropertyNode *options = options_root.getNode("options");
2749 if (!options) {
2750 SG_LOG( SG_GENERAL, SG_ALERT,
2751 "Error reading options.xml: <options> element not found." );
2752 exit(-1);
2753 }
2754
2755 if (!locale->loadResource("options"))
2756 {
2757 cout << "Unable to read the language resource." << endl;
2758 exit(-1);
2759 }
2760
2761 std::string usage = locale->getLocalizedString(options->getStringValue("usage"), "options");
2762 if (!usage.empty()) {
2763 cout << usage << endl;
2764 }
2765
2766 vector<SGPropertyNode_ptr>section = options->getChildren("section");
2767 for (unsigned int j = 0; j < section.size(); j++) {
2768 string msg = "";
2769
2770 vector<SGPropertyNode_ptr>option = section[j]->getChildren("option");
2771 for (unsigned int k = 0; k < option.size(); k++) {
2772
2773 SGPropertyNode *name = option[k]->getNode("name");
2774 SGPropertyNode *short_name = option[k]->getNode("short");
2775 SGPropertyNode *key = option[k]->getNode("key");
2776 SGPropertyNode *arg = option[k]->getNode("arg");
2777 bool brief = option[k]->getNode("brief") != 0;
2778
2779 if ((brief || p->verbose) && name) {
2780 string tmp = name->getStringValue();
2781
2782 if (key){
2783 tmp.append(":");
2784 tmp.append(key->getStringValue());
2785 }
2786 if (arg) {
2787 tmp.append("=");
2788 tmp.append(arg->getStringValue());
2789 }
2790 if (short_name) {
2791 tmp.append(", -");
2792 tmp.append(short_name->getStringValue());
2793 }
2794
2795 if (tmp.size() <= 25) {
2796 msg+= " --";
2797 msg += tmp;
2798 msg.append( 27-tmp.size(), ' ');
2799 } else {
2800 msg += "\n --";
2801 msg += tmp + '\n';
2802 msg.append(32, ' ');
2803 }
2804 // There may be more than one <description> tag associated
2805 // with one option
2806
2807 vector<SGPropertyNode_ptr> desc;
2808 desc = option[k]->getChildren("description");
2809 if (! desc.empty()) {
2810 for ( unsigned int l = 0; l < desc.size(); l++) {
2811 string t = desc[l]->getStringValue();
2812
2813 // There may be more than one translation line.
2814 vector<SGPropertyNode_ptr>trans_desc = locale->getLocalizedStrings(t.c_str(),"options");
2815 for ( unsigned int m = 0; m < trans_desc.size(); m++ ) {
2816 string t_str = trans_desc[m]->getStringValue();
2817
2818 if ((m > 0) || ((l > 0) && m == 0)) {
2819 msg.append( 32, ' ');
2820 }
2821
2822 // If the string is too large to fit on the screen,
2823 // then split it up in several pieces.
2824
2825 while ( t_str.size() > 47 ) {
2826
2827 string::size_type m = t_str.rfind(' ', 47);
2828 msg += t_str.substr(0, m) + '\n';
2829 msg.append( 32, ' ');
2830
2831 t_str.erase(t_str.begin(), t_str.begin() + m + 1);
2832 }
2833 msg += t_str + '\n';
2834 }
2835 }
2836 }
2837 }
2838 }
2839
2840 std::string name = locale->getLocalizedString(section[j]->getStringValue("name"),"options");
2841 if (!msg.empty() && !name.empty()) {
2842 cout << endl << name << ":" << endl;
2843 cout << msg;
2844 msg.erase();
2845 }
2846 }
2847
2848 if ( !p->verbose ) {
2849 std::string verbose_help = locale->getLocalizedString(options->getStringValue("verbose-help"),"options");
2850 if (!verbose_help.empty())
2851 cout << endl << verbose_help << endl;
2852 }
2853 #ifdef _MSC_VER
2854 std::cout << "Hit a key to continue..." << std::endl;
2855 std::cin.get();
2856 #endif
2857 }
2858
showVersion() const2859 void Options::showVersion() const
2860 {
2861 cout << "FlightGear version: " << FLIGHTGEAR_VERSION << endl;
2862 cout << "Revision: " << REVISION << endl;
2863 cout << "Build-Id: " << JENKINS_BUILD_ID << endl;
2864 cout << "Build-Type: " << FG_BUILD_TYPE << endl;
2865 cout << "FG_ROOT=" << globals->get_fg_root().utf8Str() << endl;
2866 cout << "FG_HOME=" << globals->get_fg_home().utf8Str() << endl;
2867 cout << "FG_SCENERY=";
2868
2869 PathList scn = globals->get_fg_scenery();
2870 cout << SGPath::join(scn, SGPath::pathListSep) << endl;
2871 cout << "SimGear version: " << SG_STRINGIZE(SIMGEAR_VERSION) << endl;
2872 cout << "OSG version: " << osgGetVersion() << endl;
2873 cout << "PLIB version: " << PLIB_VERSION << endl;
2874 }
2875
2876 // Print a report using JSON syntax on the standard output, encoded in UTF-8.
2877 //
2878 // The report format is versioned, don't forget to update it when making
2879 // changes (see below).
printJSONReport() const2880 void Options::printJSONReport() const
2881 {
2882 cJSON *rootNode = cJSON_CreateObject();
2883
2884 cJSON *metaNode = cJSON_CreateObject();
2885 cJSON_AddItemToObject(rootNode, "meta", metaNode);
2886 cJSON_AddStringToObject(metaNode, "type", "FlightGear JSON report");
2887 // When making compatible changes to the format (e.g., adding members to
2888 // JSON objects), only the minor version number should be increased.
2889 // Increase the major version number when a change is backward-incompatible
2890 // (such as the removal, renaming or semantic change of a member). Of
2891 // course, incompatible changes should only be considered as a last
2892 // recourse.
2893 cJSON_AddNumberToObject(metaNode, "format major version", 1);
2894 cJSON_AddNumberToObject(metaNode, "format minor version", 0);
2895
2896 cJSON *generalNode = cJSON_CreateObject();
2897 cJSON_AddItemToObject(rootNode, "general", generalNode);
2898 cJSON_AddStringToObject(generalNode, "name", "FlightGear");
2899 cJSON_AddStringToObject(generalNode, "version", FLIGHTGEAR_VERSION);
2900 cJSON_AddStringToObject(generalNode, "build ID", JENKINS_BUILD_ID);
2901 cJSON_AddStringToObject(generalNode, "build type", FG_BUILD_TYPE);
2902
2903 cJSON *configNode = cJSON_CreateObject();
2904 cJSON_AddItemToObject(rootNode, "config", configNode);
2905 cJSON_AddStringToObject(configNode, "FG_ROOT",
2906 globals->get_fg_root().utf8Str().c_str());
2907 cJSON_AddStringToObject(configNode, "FG_HOME",
2908 globals->get_fg_home().utf8Str().c_str());
2909
2910 cJSON *sceneryPathsNode = p->createJSONArrayFromPathList(globals->get_fg_scenery());
2911 cJSON_AddItemToObject(configNode, "scenery paths", sceneryPathsNode);
2912
2913 cJSON *aircraftPathsNode = p->createJSONArrayFromPathList(
2914 globals->get_aircraft_paths());
2915 cJSON_AddItemToObject(configNode, "aircraft paths", aircraftPathsNode);
2916
2917 cJSON_AddStringToObject(configNode, "TerraSync directory",
2918 globals->get_terrasync_dir().utf8Str().c_str());
2919
2920 cJSON_AddStringToObject(configNode, "download directory",
2921 globals->get_download_dir().utf8Str().c_str());
2922
2923 cJSON_AddStringToObject(configNode, "autosave file",
2924 globals->autosaveFilePath().utf8Str().c_str());
2925
2926 // Get the ordered lists of apt.dat, fix.dat and nav.dat files used by the
2927 // NavCache
2928 NavDataCache* cache = NavDataCache::instance();
2929 if (!cache) {
2930 cache = NavDataCache::createInstance();
2931 }
2932
2933 cJSON *navDataNode = cJSON_CreateObject();
2934 cJSON_AddItemToObject(rootNode, "navigation data", navDataNode);
2935
2936 // Write each list to the JSON tree
2937 for (const auto& datType: {NavDataCache::DATFILETYPE_APT,
2938 NavDataCache::DATFILETYPE_FIX,
2939 NavDataCache::DATFILETYPE_NAV}) {
2940 // For this method, it doesn't matter if the cache is out-of-date
2941 const NavDataCache::DatFilesGroupInfo& datFilesInfo =
2942 cache->getDatFilesInfo(datType);
2943 cJSON *datPathsNode = p->createJSONArrayFromPathList(datFilesInfo.paths);
2944 string key = NavDataCache::datTypeStr[datType] + ".dat files";
2945 cJSON_AddItemToObject(navDataNode, key.c_str(), datPathsNode);
2946 }
2947
2948 // Print the JSON tree to the standard output
2949 char *report = cJSON_Print(rootNode);
2950 cout << report << endl;
2951 cJSON_Delete(rootNode);
2952 }
2953
2954 #if defined(__CYGWIN__)
platformDefaultRoot() const2955 SGPath Options::platformDefaultRoot() const
2956 {
2957 return SGPath::fromUtf8("../data");
2958 }
2959
2960 #elif defined(SG_WINDOWS)
platformDefaultRoot() const2961 SGPath Options::platformDefaultRoot() const
2962 {
2963 return SGPath::fromUtf8("..\\data");
2964 }
2965 #elif defined(SG_MAC)
2966 // platformDefaultRoot defined in CocoaHelpers.mm
2967 #else
platformDefaultRoot() const2968 SGPath Options::platformDefaultRoot() const
2969 {
2970 return SGPath::fromUtf8(PKGLIBDIR);
2971 }
2972
2973 #endif
2974
extractOptions() const2975 string_list Options::extractOptions() const
2976 {
2977 string_list result;
2978 for (auto opt : p->values) {
2979 if (opt.desc == nullptr) {
2980 continue;
2981 }
2982
2983 if (!strcmp(opt.desc->option,"prop")) {
2984 result.push_back("prop:" + opt.value);
2985 } else if (opt.value.empty()) {
2986 result.push_back(opt.desc->option);
2987 } else {
2988 result.push_back(std::string(opt.desc->option) + "=" + opt.value);
2989 }
2990 }
2991
2992 return result;
2993 }
2994
setupRoot(int argc,char ** argv)2995 OptionResult Options::setupRoot(int argc, char** argv)
2996 {
2997 SGPath root(globals->get_fg_root());
2998 bool usingDefaultRoot = false;
2999
3000 // root has already been set, so skip the fg_root setting and validation.
3001 if (!root.isNull()) {
3002 return FG_OPTIONS_OK;
3003 }
3004
3005 if (isOptionSet("fg-root")) {
3006 root = SGPath::fromUtf8(valueForOption("fg-root")); // easy!
3007 SG_LOG(SG_GENERAL, SG_INFO, "set from command-line argument: fg_root = " << root );
3008 } else {
3009 // Next check if fg-root is set as an env variable
3010 char *envp = ::getenv( "FG_ROOT" );
3011 if ( envp != nullptr ) {
3012 root = SGPath::fromEnv("FG_ROOT");
3013 SG_LOG(SG_GENERAL, SG_INFO, "set from FG_ROOT env var: fg_root = " << root );
3014 } else {
3015 #if defined(HAVE_QT)
3016 auto restoreResult = SetupRootDialog::restoreUserSelectedRoot(root);
3017 if (restoreResult == SetupRootDialog::UserExit) {
3018 return FG_OPTIONS_EXIT;
3019 } else if (restoreResult == SetupRootDialog::UseDefault) {
3020 root = SGPath{}; // clear any value, so we fall through in root.isNull() below
3021 }
3022 #endif
3023
3024 if (root.isNull()) {
3025 usingDefaultRoot = true;
3026 root = platformDefaultRoot();
3027 SG_LOG(SG_GENERAL, SG_INFO, "platform default fg_root = " << root );
3028 } else {
3029 SG_LOG(SG_GENERAL, SG_INFO, "Qt launcher set fg_root = " << root );
3030 }
3031 }
3032 }
3033
3034 globals->set_fg_root(root);
3035 string base_version = fgBasePackageVersion(root);
3036
3037
3038 #if defined(HAVE_QT)
3039 // only compare major and minor version, not the patch level.
3040 const int versionComp = simgear::strutils::compare_versions(FLIGHTGEAR_VERSION, base_version, 2);
3041
3042 // note we never end up here if restoring a user selected root via
3043 // the Qt GUI, since that code pre-validates the path. But if we're using
3044 // a command-line, env-var or default root this check can fail and
3045 // we still want to use the GUI in that case
3046 if (versionComp != 0) {
3047 flightgear::initApp(argc, argv);
3048 bool ok = SetupRootDialog::runDialog(usingDefaultRoot);
3049 if (!ok) {
3050 return FG_OPTIONS_EXIT;
3051 }
3052 }
3053 #else
3054 SG_UNUSED(usingDefaultRoot);
3055
3056 // validate it
3057 if (base_version.empty()) {
3058 flightgear::fatalMessageBoxThenExit(
3059 "Base package not found",
3060 "Required data files not found, please check your installation.",
3061 "Looking for base-package files at: '" + root.str() + "'");
3062 }
3063
3064 // only compare major and minor version, not the patch level.
3065 const int versionComp = simgear::strutils::compare_versions(FLIGHTGEAR_VERSION, base_version, 2);
3066 if (versionComp != 0) {
3067 flightgear::fatalMessageBoxThenExit(
3068 "Base package version mismatch",
3069 "Version check failed, please check your installation.",
3070 "Found data files for version '" + base_version + "' at '" +
3071 globals->get_fg_root().str() + "', version '" +
3072 std::string(FLIGHTGEAR_VERSION) + "' is required.");
3073 }
3074 #endif
3075 return FG_OPTIONS_OK;
3076 }
3077
shouldLoadDefaultConfig() const3078 bool Options::shouldLoadDefaultConfig() const
3079 {
3080 return p->shouldLoadDefaultConfig;
3081 }
3082
setShouldLoadDefaultConfig(bool load)3083 void Options::setShouldLoadDefaultConfig(bool load)
3084 {
3085 p->shouldLoadDefaultConfig = load;
3086 }
3087
checkForArg(int argc,char * argv[],const char * checkArg)3088 bool Options::checkForArg(int argc, char* argv[], const char* checkArg)
3089 {
3090 for (int i = 0; i < argc; ++i) {
3091 char* arg = argv[i];
3092 if (arg == nullptr) {
3093 continue;
3094 }
3095
3096 if (*arg != '-') { // we only care about args with a leading hypen
3097 continue;
3098 }
3099
3100 arg++;
3101 if (*arg == '-') { // skip double hypens
3102 arg++;
3103 }
3104
3105 if (strcmp(arg, checkArg) == 0) {
3106 return true;
3107 }
3108 }
3109
3110 return false;
3111 }
3112
getArgValue(int argc,char * argv[],const char * checkArg)3113 std::string Options::getArgValue(int argc, char* argv[], const char* checkArg)
3114 {
3115 const auto len = strlen(checkArg);
3116 for (int i = 0; i < argc; ++i) {
3117 char* arg = argv[i];
3118 if (arg == nullptr) {
3119 continue;
3120 }
3121
3122 if (strncmp(arg, checkArg, len) == 0) {
3123 const auto alen = strlen(arg);
3124 if ((alen - len) < 2)
3125 return {}; // no value after the =, or missing = entirely
3126 return std::string(arg + len + 1);
3127 }
3128 } // of args iteration
3129
3130 return {};
3131 }
3132
3133 } // of namespace flightgear
3134