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