]> git.mxchange.org Git - flightgear.git/blob - src/Input/fgjs.cxx
add <mod-meta> and <mod-super> XML elements for key bindings
[flightgear.git] / src / Input / fgjs.cxx
1 // fgjs.cxx -- assign joystick axes to flightgear properties
2 //
3 // Updated to allow xml output & added a few bits & pieces
4 // Laurie Bradshaw, Jun 2005
5 //
6 // Written by Tony Peden, started May 2001
7 //
8 // Copyright (C) 2001  Tony Peden (apeden@earthlink.net)
9 // Copyright (C) 2006  Stefan Seifert
10 //
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.
15 //
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.
20 //
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.
24
25 #ifdef HAVE_CONFIG_H
26 #  include <config.h>
27 #endif
28
29 #include <simgear/compiler.h>
30
31 #include <math.h>
32
33 #include STL_IOSTREAM
34 #include STL_FSTREAM
35 #include STL_STRING
36
37 SG_USING_STD(fstream);
38 SG_USING_STD(cout);
39 SG_USING_STD(cin);
40 SG_USING_STD(endl);
41 SG_USING_STD(ios);
42 SG_USING_STD(string);
43
44 #include <simgear/constants.h>
45 #include <simgear/debug/logstream.hxx>
46 #include <simgear/misc/sg_path.hxx>
47 #include <simgear/misc/sgstream.hxx>
48 #include <simgear/structure/exception.hxx>
49
50 #include <Main/fg_io.hxx>
51 #include <Main/fg_props.hxx>
52 #include <Main/globals.hxx>
53
54 #include "jsinput.h"
55
56
57 bool confirmAnswer() {
58     char answer;
59     do {
60         cout << "Is this correct? (y/n) $ ";
61         cin >> answer;
62         cin.ignore(256, '\n');
63         if (answer == 'y')
64             return true;
65         if (answer == 'n')
66             return false;
67     } while (true);
68 }
69
70 string getFGRoot( int argc, char *argv[] );
71
72 int main( int argc, char *argv[] ) {
73
74     for (int i = 1; i < argc; i++) {
75         if (strcmp("--help", argv[i]) == 0) {
76             cout << "Usage:" << endl;
77             cout << "  --help\t\t\tShow this help" << endl;
78             exit(0);
79         } else {
80             cout << "Unknown option \"" << argv[i] << "\"" << endl;
81             exit(0);
82         }
83     }
84
85     jsInit();
86
87     jsSuper *jss = new jsSuper();
88     jsInput *jsi = new jsInput(jss);
89     jsi->displayValues(false);
90
91     cout << "Found " << jss->getNumJoysticks() << " joystick(s)" << endl;
92
93     if(jss->getNumJoysticks() <= 0) {
94         cout << "Can't find any joysticks ..." << endl;
95         exit(1);
96     }
97     cout << endl << "Now measuring the dead band of your joystick. The dead band is the area " << endl
98                  << "where the joystick is centered and should not generate any input. Move all " << endl
99                  << "axes around in this dead zone during the ten seconds this test will take." << endl;
100     cout << "Press enter to continue." << endl;
101     cin.ignore(1024, '\n');
102     jsi->findDeadBand();
103     cout << endl << "Dead band calibration finished. Press enter to start control assignment." << endl;
104     cin.ignore(1024, '\n');
105
106     jss->firstJoystick();
107     fstream *xfs = new fstream[ jss->getNumJoysticks() ];
108     SGPropertyNode_ptr *jstree = new SGPropertyNode_ptr[ jss->getNumJoysticks() ];
109     do {
110         cout << "Joystick #" << jss->getCurrentJoystickId()
111              << " \"" << jss->getJoystick()->getName() << "\" has "
112              << jss->getJoystick()->getNumAxes() << " axes" << endl;
113
114         char filename[16];
115         snprintf(filename, 16, "js%i.xml", jss->getCurrentJoystickId());
116         xfs[ jss->getCurrentJoystickId() ].open(filename, ios::out);
117         jstree[ jss->getCurrentJoystickId() ] = new SGPropertyNode();
118     } while ( jss->nextJoystick() );
119
120     SGPath templatefile( getFGRoot(argc, argv) );
121     templatefile.append("Input");
122     templatefile.append("Joysticks");
123     templatefile.append("template.xml");
124
125     SGPropertyNode *templatetree = new SGPropertyNode();
126     try {
127         readProperties(templatefile.str().c_str(), templatetree);
128     } catch (sg_io_exception e) {
129         cout << e.getFormattedMessage ();
130     }
131
132     vector<SGPropertyNode_ptr> axes = templatetree->getChildren("axis");
133     for(vector<SGPropertyNode_ptr>::iterator iter = axes.begin(); iter != axes.end(); iter++) {
134         cout << "Move the control you wish to use for " << (*iter)->getStringValue("desc")
135              << " " << (*iter)->getStringValue("direction") << endl;
136         cout << "Pressing a button skips this axis" << endl;
137         fflush( stdout );
138         jsi->getInput();
139         if (jsi->getInputAxis() != -1) {
140             cout << endl << "Assigned axis " << jsi->getInputAxis()
141                  << " on joystick " << jsi->getInputJoystick()
142                  << " to control " << (*iter)->getStringValue("desc") << endl;
143             if ( confirmAnswer() ) {
144                 SGPropertyNode *axis = jstree[ jsi->getInputJoystick() ]->getChild("axis", jsi->getInputAxis(), true);
145                 copyProperties(*iter, axis);
146                 axis->setDoubleValue("dead-band", jss->getJoystick(jsi->getInputJoystick())
147                         ->getDeadBand(jsi->getInputAxis()));
148                 axis->setDoubleValue("binding/factor", jsi->getInputAxisPositive() ? 1.0 : -1.0);
149             } else {
150                 iter--;
151             }
152         } else {
153             cout << "Skipping control" << endl;
154             if ( ! confirmAnswer() )
155                 iter--;
156         }
157         cout << endl;
158     }
159
160     vector<SGPropertyNode_ptr> buttons = templatetree->getChildren("button");
161     for(vector<SGPropertyNode_ptr>::iterator iter = buttons.begin(); iter != buttons.end(); iter++) {
162         cout << "Press the button you wish to use for " << (*iter)->getStringValue("desc") << endl;
163         cout << "Moving a joystick axis skips this button" << endl;
164         fflush( stdout );
165         jsi->getInput();
166         if (jsi->getInputButton() != -1) {
167             cout << endl << "Assigned button " << jsi->getInputButton()
168                  << " on joystick " << jsi->getInputJoystick()
169                  << " to control " << (*iter)->getStringValue("desc") << endl;
170             if ( confirmAnswer() ) {
171                 SGPropertyNode *button = jstree[ jsi->getInputJoystick() ]->getChild("button", jsi->getInputButton(), true);
172                 copyProperties(*iter, button);
173             } else {
174                 iter--;
175             }
176         } else {
177             cout << "Skipping control" << endl;
178             if (! confirmAnswer())
179                 iter--;
180         }
181         cout << endl;
182     }
183
184     cout << "Your joystick settings are in ";
185     for (int i = 0; i < jss->getNumJoysticks(); i++) {
186         try {
187             cout << "js" << i << ".xml";
188             if (i + 2 < jss->getNumJoysticks())
189                 cout << ", ";
190             else if (i + 1 < jss->getNumJoysticks())
191                 cout << " and ";
192
193             jstree[i]->setStringValue("name", jss->getJoystick(i)->getName());
194             writeProperties(xfs[i], jstree[i], true);
195         } catch (sg_io_exception e) {
196             cout << e.getFormattedMessage ();
197         }
198         xfs[i].close();
199     }
200     cout << "." << endl << "Check and edit as desired. Once you are happy," << endl
201          << "move relevant js<n>.xml files to $FG_ROOT/Input/Joysticks/ (if you didn't use" << endl
202          << "an attached controller, you don't need to move the corresponding file)" << endl;
203
204     delete jsi;
205     delete[] xfs;
206     delete jss;
207
208     return 1;
209 }
210
211 char *homedir = ::getenv( "HOME" );
212 char *hostname = ::getenv( "HOSTNAME" );
213 bool free_hostname = false;
214
215 // Scan the command line options for the specified option and return
216 // the value.
217 static string fgScanForOption( const string& option, int argc, char **argv ) {
218     int i = 1;
219
220     if (hostname == NULL)
221     {
222         char _hostname[256];
223         gethostname(_hostname, 256);
224         hostname = strdup(_hostname);
225         free_hostname = true;
226     }
227
228     SG_LOG(SG_GENERAL, SG_INFO, "Scanning command line for: " << option );
229
230     int len = option.length();
231
232     while ( i < argc ) {
233         SG_LOG( SG_GENERAL, SG_DEBUG, "argv[" << i << "] = " << argv[i] );
234
235         string arg = argv[i];
236         if ( arg.find( option ) == 0 ) {
237             return arg.substr( len );
238         }
239
240         i++;
241     }
242
243     return "";
244 }
245
246 // Scan the user config files for the specified option and return
247 // the value.
248 static string fgScanForOption( const string& option, const string& path ) {
249     sg_gzifstream in( path );
250     if ( !in.is_open() ) {
251         return "";
252     }
253
254     SG_LOG( SG_GENERAL, SG_INFO, "Scanning " << path << " for: " << option );
255
256     int len = option.length();
257
258     in >> skipcomment;
259 #ifndef __MWERKS__
260     while ( ! in.eof() ) {
261 #else
262     char c = '\0';
263     while ( in.get(c) && c != '\0' ) {
264         in.putback(c);
265 #endif
266         string line;
267
268 #if defined( macintosh )
269         getline( in, line, '\r' );
270 #else
271         getline( in, line, '\n' );
272 #endif
273
274         // catch extraneous (DOS) line ending character
275         if ( line[line.length() - 1] < 32 ) {
276             line = line.substr( 0, line.length()-1 );
277         }
278
279         if ( line.find( option ) == 0 ) {
280             return line.substr( len );
281         }
282
283         in >> skipcomment;
284     }
285
286     return "";
287 }
288
289 // Scan the user config files for the specified option and return
290 // the value.
291 static string fgScanForOption( const string& option ) {
292     string arg("");
293
294 #if defined( unix ) || defined( __CYGWIN__ )
295     // Next check home directory for .fgfsrc.hostname file
296     if ( arg.empty() ) {
297         if ( homedir != NULL ) {
298             SGPath config( homedir );
299             config.append( ".fgfsrc" );
300             config.concat( "." );
301             config.concat( hostname );
302             arg = fgScanForOption( option, config.str() );
303         }
304     }
305 #endif
306
307     // Next check home directory for .fgfsrc file
308     if ( arg.empty() ) {
309         if ( homedir != NULL ) {
310             SGPath config( homedir );
311             config.append( ".fgfsrc" );
312             arg = fgScanForOption( option, config.str() );
313         }
314     }
315
316     return arg;
317 }
318
319 // Read in configuration (files and command line options) but only set
320 // fg_root
321 string getFGRoot ( int argc, char **argv ) {
322     string root;
323
324     // First parse command line options looking for --fg-root=, this
325     // will override anything specified in a config file
326     root = fgScanForOption( "--fg-root=", argc, argv);
327
328     // Check in one of the user configuration files.
329     if (root.empty() )
330         root = fgScanForOption( "--fg-root=" );
331
332     // Next check if fg-root is set as an env variable
333     if ( root.empty() ) {
334         char *envp = ::getenv( "FG_ROOT" );
335         if ( envp != NULL ) {
336             root = envp;
337         }
338     }
339
340     // Otherwise, default to a random compiled-in location if we can't
341     // find fg-root any other way.
342     if ( root.empty() ) {
343 #if defined( __CYGWIN__ )
344         root = "/FlightGear";
345 #elif defined( WIN32 )
346         root = "\\FlightGear";
347 #elif defined(OSX_BUNDLE)
348         /* the following code looks for the base package directly inside
349             the application bundle. This can be changed fairly easily by
350             fiddling with the code below. And yes, I know it's ugly and verbose.
351         */
352         CFBundleRef appBundle = CFBundleGetMainBundle();
353         CFURLRef appUrl = CFBundleCopyBundleURL(appBundle);
354         CFRelease(appBundle);
355
356         // look for a 'data' subdir directly inside the bundle : is there
357         // a better place? maybe in Resources? I don't know ...
358         CFURLRef dataDir = CFURLCreateCopyAppendingPathComponent(NULL, appUrl, CFSTR("data"), true);
359
360         // now convert down to a path, and the a c-string
361         CFStringRef path = CFURLCopyFileSystemPath(dataDir, kCFURLPOSIXPathStyle);
362         root = CFStringGetCStringPtr(path, CFStringGetSystemEncoding());
363
364         // tidy up.
365         CFRelease(appBundle);
366         CFRelease(dataDir);
367         CFRelease(path);
368 #else
369         root = PKGLIBDIR;
370 #endif
371     }
372
373     SG_LOG(SG_INPUT, SG_INFO, "fg_root = " << root );
374
375     return root;
376 }