1 // fgjs.cxx -- assign joystick axes to flightgear properties
3 // Updated to allow xml output & added a few bits & pieces
4 // Laurie Bradshaw, Jun 2005
6 // Written by Tony Peden, started May 2001
8 // Copyright (C) 2001 Tony Peden (apeden@earthlink.net)
9 // Copyright (C) 2006 Stefan Seifert
11 // This program is free software; you can redistribute it and/or
12 // modify it under the terms of the GNU General Public License as
13 // published by the Free Software Foundation; either version 2 of the
14 // License, or (at your option) any later version.
16 // This program is distributed in the hope that it will be useful, but
17 // WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 // General Public License for more details.
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
29 #include <simgear/compiler.h>
31 #if defined( _MSC_VER ) || defined( __MINGW32__ )
32 # include <Winsock2.h>
48 #include <simgear/constants.h>
49 #include <simgear/debug/logstream.hxx>
50 #include <simgear/misc/sg_path.hxx>
51 #include <simgear/misc/sgstream.hxx>
52 #include <simgear/structure/exception.hxx>
53 #include <simgear/props/props_io.hxx>
55 #include <Main/fg_io.hxx>
56 #include <Main/fg_props.hxx>
57 #include <Main/globals.hxx>
62 bool confirmAnswer() {
65 cout << "Is this correct? (y/n) $ ";
67 cin.ignore(256, '\n');
75 string getFGRoot( int argc, char *argv[] );
77 int main( int argc, char *argv[] ) {
79 for (int i = 1; i < argc; i++) {
80 if (strcmp("--help", argv[i]) == 0) {
81 cout << "Usage:" << endl;
82 cout << " --help\t\t\tShow this help" << endl;
84 } else if (strncmp("--fg-root=", argv[i], 10) == 0) {
87 cout << "Unknown option \"" << argv[i] << "\"" << endl;
94 jsSuper *jss = new jsSuper();
95 jsInput *jsi = new jsInput(jss);
96 jsi->displayValues(false);
98 cout << "Found " << jss->getNumJoysticks() << " joystick(s)" << endl;
100 if(jss->getNumJoysticks() <= 0) {
101 cout << "Can't find any joysticks ..." << endl;
104 cout << endl << "Now measuring the dead band of your joystick. The dead band is the area " << endl
105 << "where the joystick is centered and should not generate any input. Move all " << endl
106 << "axes around in this dead zone during the ten seconds this test will take." << endl;
107 cout << "Press enter to continue." << endl;
108 cin.ignore(1024, '\n');
110 cout << endl << "Dead band calibration finished. Press enter to start control assignment." << endl;
111 cin.ignore(1024, '\n');
113 jss->firstJoystick();
114 fstream *xfs = new fstream[ jss->getNumJoysticks() ];
115 SGPropertyNode_ptr *jstree = new SGPropertyNode_ptr[ jss->getNumJoysticks() ];
117 cout << "Joystick #" << jss->getCurrentJoystickId()
118 << " \"" << jss->getJoystick()->getName() << "\" has "
119 << jss->getJoystick()->getNumAxes() << " axes" << endl;
122 snprintf(filename, 16, "js%i.xml", jss->getCurrentJoystickId());
123 xfs[ jss->getCurrentJoystickId() ].open(filename, ios::out);
124 jstree[ jss->getCurrentJoystickId() ] = new SGPropertyNode();
125 } while ( jss->nextJoystick() );
127 SGPath templatefile( getFGRoot(argc, argv) );
128 templatefile.append("Input");
129 templatefile.append("Joysticks");
130 templatefile.append("template.xml");
132 SGPropertyNode *templatetree = new SGPropertyNode();
134 readProperties(templatefile.str().c_str(), templatetree);
135 } catch (sg_io_exception e) {
136 cout << e.getFormattedMessage ();
139 vector<SGPropertyNode_ptr> axes = templatetree->getChildren("axis");
140 for(vector<SGPropertyNode_ptr>::iterator iter = axes.begin(); iter != axes.end(); iter++) {
141 cout << "Move the control you wish to use for " << (*iter)->getStringValue("desc")
142 << " " << (*iter)->getStringValue("direction") << endl;
143 cout << "Pressing a button skips this axis" << endl;
146 if (jsi->getInputAxis() != -1) {
147 cout << endl << "Assigned axis " << jsi->getInputAxis()
148 << " on joystick " << jsi->getInputJoystick()
149 << " to control " << (*iter)->getStringValue("desc") << endl;
150 if ( confirmAnswer() ) {
151 SGPropertyNode *axis = jstree[ jsi->getInputJoystick() ]->getChild("axis", jsi->getInputAxis(), true);
152 copyProperties(*iter, axis);
153 axis->setDoubleValue("dead-band", jss->getJoystick(jsi->getInputJoystick())
154 ->getDeadBand(jsi->getInputAxis()));
155 axis->setDoubleValue("binding/factor", jsi->getInputAxisPositive() ? 1.0 : -1.0);
160 cout << "Skipping control" << endl;
161 if ( ! confirmAnswer() )
167 vector<SGPropertyNode_ptr> buttons = templatetree->getChildren("button");
168 for(vector<SGPropertyNode_ptr>::iterator iter = buttons.begin(); iter != buttons.end(); iter++) {
169 cout << "Press the button you wish to use for " << (*iter)->getStringValue("desc") << endl;
170 cout << "Moving a joystick axis skips this button" << endl;
173 if (jsi->getInputButton() != -1) {
174 cout << endl << "Assigned button " << jsi->getInputButton()
175 << " on joystick " << jsi->getInputJoystick()
176 << " to control " << (*iter)->getStringValue("desc") << endl;
177 if ( confirmAnswer() ) {
178 SGPropertyNode *button = jstree[ jsi->getInputJoystick() ]->getChild("button", jsi->getInputButton(), true);
179 copyProperties(*iter, button);
184 cout << "Skipping control" << endl;
185 if (! confirmAnswer())
191 cout << "Your joystick settings are in ";
192 for (int i = 0; i < jss->getNumJoysticks(); i++) {
194 cout << "js" << i << ".xml";
195 if (i + 2 < jss->getNumJoysticks())
197 else if (i + 1 < jss->getNumJoysticks())
200 jstree[i]->setStringValue("name", jss->getJoystick(i)->getName());
201 writeProperties(xfs[i], jstree[i], true);
202 } catch (sg_io_exception e) {
203 cout << e.getFormattedMessage ();
207 cout << "." << endl << "Check and edit as desired. Once you are happy," << endl
208 << "move relevant js<n>.xml files to $FG_ROOT/Input/Joysticks/ (if you didn't use" << endl
209 << "an attached controller, you don't need to move the corresponding file)" << endl;
219 char *homedir = ::getenv( "HOME" );
220 char *hostname = ::getenv( "HOSTNAME" );
221 bool free_hostname = false;
223 // Scan the command line options for the specified option and return
225 static string fgScanForOption( const string& option, int argc, char **argv ) {
228 if (hostname == NULL)
231 gethostname(_hostname, 256);
232 hostname = strdup(_hostname);
233 free_hostname = true;
236 SG_LOG(SG_GENERAL, SG_INFO, "Scanning command line for: " << option );
238 int len = option.length();
241 SG_LOG( SG_GENERAL, SG_DEBUG, "argv[" << i << "] = " << argv[i] );
243 string arg = argv[i];
244 if ( arg.find( option ) == 0 ) {
245 return arg.substr( len );
254 // Scan the user config files for the specified option and return
256 static string fgScanForOption( const string& option, const string& path ) {
257 sg_gzifstream in( path );
258 if ( !in.is_open() ) {
262 SG_LOG( SG_GENERAL, SG_INFO, "Scanning " << path << " for: " << option );
264 int len = option.length();
267 while ( ! in.eof() ) {
269 getline( in, line, '\n' );
271 // catch extraneous (DOS) line ending character
272 if ( line[line.length() - 1] < 32 ) {
273 line = line.substr( 0, line.length()-1 );
276 if ( line.find( option ) == 0 ) {
277 return line.substr( len );
286 // Scan the user config files for the specified option and return
288 static string fgScanForOption( const string& option ) {
291 #if defined( unix ) || defined( __CYGWIN__ )
292 // Next check home directory for .fgfsrc.hostname file
294 if ( homedir != NULL ) {
295 SGPath config( homedir );
296 config.append( ".fgfsrc" );
297 config.concat( "." );
298 config.concat( hostname );
299 arg = fgScanForOption( option, config.str() );
304 // Next check home directory for .fgfsrc file
306 if ( homedir != NULL ) {
307 SGPath config( homedir );
308 config.append( ".fgfsrc" );
309 arg = fgScanForOption( option, config.str() );
316 // Read in configuration (files and command line options) but only set
318 string getFGRoot ( int argc, char **argv ) {
321 // First parse command line options looking for --fg-root=, this
322 // will override anything specified in a config file
323 root = fgScanForOption( "--fg-root=", argc, argv);
325 // Check in one of the user configuration files.
327 root = fgScanForOption( "--fg-root=" );
329 // Next check if fg-root is set as an env variable
330 if ( root.empty() ) {
331 char *envp = ::getenv( "FG_ROOT" );
332 if ( envp != NULL ) {
337 // Otherwise, default to a random compiled-in location if we can't
338 // find fg-root any other way.
339 if ( root.empty() ) {
340 #if defined( __CYGWIN__ )
341 root = "/FlightGear";
342 #elif defined( WIN32 )
343 root = "\\FlightGear";
344 #elif defined(OSX_BUNDLE)
345 /* the following code looks for the base package directly inside
346 the application bundle. This can be changed fairly easily by
347 fiddling with the code below. And yes, I know it's ugly and verbose.
349 CFBundleRef appBundle = CFBundleGetMainBundle();
350 CFURLRef appUrl = CFBundleCopyBundleURL(appBundle);
351 CFRelease(appBundle);
353 // look for a 'data' subdir directly inside the bundle : is there
354 // a better place? maybe in Resources? I don't know ...
355 CFURLRef dataDir = CFURLCreateCopyAppendingPathComponent(NULL, appUrl, CFSTR("data"), true);
357 // now convert down to a path, and the a c-string
358 CFStringRef path = CFURLCopyFileSystemPath(dataDir, kCFURLPOSIXPathStyle);
359 root = CFStringGetCStringPtr(path, CFStringGetSystemEncoding());
362 CFRelease(appBundle);
370 SG_LOG(SG_INPUT, SG_INFO, "fg_root = " << root );