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