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