Fix bug where last axis of the joystick was written out with the original bindings...
[fg:toms-fgdata.git] / Nasal / joystick.nas
1 # Joystick configuration library.
2 var DIALOGROOT  = "/sim/gui/dialogs/joystick-config";
3 var MAX_AXES = 8;
4 var MAX_BUTTONS = 24;
5
6 # Hash of the custom axis/buttons
7 var custom_bindings = {};
8
9 # Class for an individual joystick axis binding
10 var Axis = {
11   new: func(name, prop, invertable) {
12     var m = { parents: [Axis] };
13     m.name = name;
14     m.prop = prop;
15     m.invertable = invertable;
16     m.inverted = 0;
17     return m;
18   },
19   
20   clone: func() {
21     var m = { parents: [Axis] };
22     m.name = me.name;
23     m.prop = me.prop;
24     m.invertable = me.invertable;
25     m.inverted = me.inverted;
26     return m;
27   },
28   
29   match: func(prop) {
30     return 0;
31   },
32   
33   parse: func(prop) { },
34
35   readProps: func(props) {},
36     
37   getName: func() { return me.name; },
38   getBinding: func(axis) { return props.Node.new(); },
39   isInvertable: func() { return me.invertable; },
40   isInverted: func() { return me.inverted; },
41   
42   setInverted: func(b) { 
43     if (me.invertable) me.inverted = b;
44   },  
45 };
46
47 var CustomAxis = {
48   new: func() {
49     var m = { parents: [CustomAxis, Axis.new("Custom", "", 0) ] };
50     me.custom_binding = nil;
51     return m;
52   },
53
54   clone: func() {
55     var m = { parents: [CustomAxis, Axis.new("Custom", "", 0) ] };
56     m.custom_binding = me.custom_binding;
57     return m;
58   },
59   
60   match: func(prop) {
61     var p = props.Node.new();
62     
63     if (prop.getNode("binding") != nil) {
64       props.copy(prop.getNode("binding"), p);
65       me.custom_binding = p;
66       return 1;
67     } else  {
68       return 0;
69     }
70   },
71   
72   getBinding: func(axis) {
73     var p = props.Node.new().getNode("axis[" ~ axis ~ "]", 1);
74     p.getNode("desc", 1).setValue(me.name);
75     props.copy(me.custom_binding, p.getNode("binding", 1));    
76     return p;
77   },  
78 };
79
80 var UnboundAxis = {
81   new: func() {
82     var m = { parents: [UnboundAxis, Axis.new("None", "", 0) ] };
83     return m;
84   },
85
86   clone: func() {
87     var m = { parents: [UnboundAxis, Axis.new("None", "", 0) ] };
88     return m;
89   },
90   
91   match: func(prop) {
92     return 1;
93   },
94   
95   getBinding: func(axis) {
96     return nil;
97   },
98 };
99
100
101 var PropertyScaleAxis = {
102   new: func(name, prop, deadband=0, factor=1, offset=0) {
103     var m = { parents: [PropertyScaleAxis, Axis.new(name, prop, 1) ] };
104     m.prop=prop;
105     m.deadband = deadband;
106     m.factor = factor;
107     m.offset = offset;
108     return m;
109   },
110
111   clone: func() {
112     var m = { parents: [PropertyScaleAxis, Axis.new(me.name, me.prop, 1) ] };
113
114     m.inverted = me.inverted;
115     
116     m.prop= me.prop;
117     m.deadband = me.deadband;
118     m.factor = me.factor;
119     m.offset = me.offset;
120     return m;
121   },
122
123   
124   match: func(prop) {
125     var cmd = prop.getNode("binding", 1).getNode("command", 1).getValue();
126     var p = prop.getNode("binding", 1).getNode("property", 1).getValue();
127     return ((cmd == "property-scale") and (p == me.prop));
128   },
129   
130   parse: func(p) { 
131     me.deadband = p.getNode("binding", 1).getNode("dead-band", 1).getValue();        
132     if (p.getNode("binding", 1).getNode("factor", 1).getValue() != nil) {
133       me.inverted = (p.getNode("binding", 1).getNode("factor", 1).getValue() < 0);
134     }
135     me.offset = p.getNode("binding", 1).getNode("offset", 1).getValue();
136   },
137   
138   getBinding: func(axis) {
139     var p = props.Node.new();
140     p = p.getNode("axis[" ~ axis ~ "]", 1);
141     p.getNode("desc", 1).setValue(me.name);
142     p.getNode("binding", 1).getNode("command", 1).setValue("property-scale");
143     p.getNode("binding", 1).getNode("property", 1).setValue(me.prop);
144     p.getNode("binding", 1).getNode("dead-band", 1).setValue(me.deadband);
145     if (me.inverted) {    
146       p.getNode("binding", 1).getNode("factor", 1).setValue(0 - me.factor);
147     } else {
148       p.getNode("binding", 1).getNode("factor", 1).setValue(me.factor);
149     }
150     p.getNode("binding", 1).getNode("offset", 1).setValue(me.offset);
151     return p;
152   },
153   
154 };
155
156 var NasalScaleAxis = {
157   new: func(name, script, prop) {
158     var m = { parents: [NasalScaleAxis, Axis.new(name, prop, 0) ] };
159     m.script = script;
160     m.prop = prop;
161     return m;
162   },
163   
164   clone: func() {
165     var m = { parents: [NasalScaleAxis, Axis.new(me.name, me.prop, 0) ] };
166     
167     m.script = me.script;
168     m.prop = me.prop;
169     return m;
170   },
171   
172   match: func(prop) {
173     var cmd = prop.getNode("binding", 1).getNode("command", 1).getValue();
174     var p = prop.getNode("binding", 1).getNode("script", 1).getValue();
175     if ((p != nil) and (cmd == "nasal")) {
176       p = string.trim(p);
177       p = string.replace(p, ";", "");
178       p = p ~ ";";
179       return (p == me.script);
180     } else {
181       return 0;
182     }
183   },
184   
185   getBinding: func(axis) {
186     var p = props.Node.new().getNode("axis[" ~ axis ~ "]", 1);
187     p.getNode("desc", 1).setValue(me.name);
188     p.getNode("binding", 1).getNode("command", 1).setValue("nasal");
189     p.getNode("binding", 1).getNode("script", 1).setValue(me.script);
190     return p;
191   },
192 };
193
194 var NasalLowHighAxis = {
195   new: func(name, lowscript, highscript, prop) {
196     var m = { parents: [NasalLowHighAxis, Axis.new(name, prop, 1) ] };
197     m.lowscript = lowscript;
198     m.highscript = highscript;
199     return m;
200   },
201   
202   clone: func() {
203     var m = { parents: [NasalLowHighAxis, Axis.new(me.name, me.prop, 1) ] };
204
205     m.inverted = me.inverted;
206
207     m.lowscript = me.lowscript;
208     m.highscript = me.highscript;
209     return m;
210   },  
211   
212   match: func(prop) {
213     var cmd = prop.getNode("low", 1).getNode("binding", 1).getNode("command", 1).getValue();
214     var p = prop.getNode("low", 1).getNode("binding", 1).getNode("script", 1).getValue();
215
216     if ((p == nil) or (cmd != "nasal")) return 0;
217     p = string.trim(p);
218     p = string.replace(p, ";", "");
219     p = p ~ ";";
220     
221     if (p == me.lowscript) { 
222       return 1;
223     }
224     
225     if (p == me.highscript) {
226       me.inverted = 1;
227       return 1;    
228     }
229     
230     return 0;
231   },
232   
233   getBinding: func(axis) {
234     var p = props.Node.new().getNode("axis[" ~ axis ~ "]", 1);
235     p.getNode("desc", 1).setValue(me.name);
236
237     p.getNode("low", 1).getNode("binding", 1).getNode("command", 1).setValue("nasal");
238     p.getNode("high", 1).getNode("binding", 1).getNode("command", 1).setValue("nasal");
239     
240     if (me.inverted) {
241       p.getNode("low", 1).getNode("binding", 1).getNode("script", 1).setValue(me.highscript);
242       p.getNode("high", 1).getNode("binding", 1).getNode("script", 1).setValue(me.lowscript);
243     } else {
244       p.getNode("low", 1).getNode("binding", 1).getNode("script", 1).setValue(me.lowscript);
245       p.getNode("high", 1).getNode("binding", 1).getNode("script", 1).setValue(me.highscript);
246     }
247     
248     return p;
249   },
250 };
251
252
253 var axisBindings = [
254   PropertyScaleAxis.new("Aileron", "/controls/flight/aileron"),
255   PropertyScaleAxis.new("Elevator", "/controls/flight/elevator"),
256   PropertyScaleAxis.new("Rudder",   "/controls/flight/rudder"),
257   NasalScaleAxis.new("Throttle",  "controls.throttleAxis();", "/controls/engines/engine[0]/throttle") ,
258   NasalScaleAxis.new("Mixture",   "controls.mixtureAxis();", "/controls/engines/engine[0]/mixture") ,
259   NasalScaleAxis.new("Propeller", "controls.propellerAxis();", "/controls/engines/engine[0]/propeller-pitch") , 
260   NasalLowHighAxis.new("View (horizontal)",   
261                       "setprop(\"/sim/current-view/goal-heading-offset-deg\", getprop(\"/sim/current-view/goal-heading-offset-deg\") + 30);", 
262                       "setprop(\"/sim/current-view/goal-heading-offset-deg\", getprop(\"/sim/current-view/goal-heading-offset-deg\") - 30);", 
263                       "/sim/current-view/goal-heading-offset-deg"), 
264   NasalLowHighAxis.new("View (vertical)",
265                        "setprop(\"/sim/current-view/goal-pitch-offset-deg\", getprop(\"/sim/current-view/goal-pitch-offset-deg\") - 20);", 
266                        "setprop(\"/sim/current-view/goal-pitch-offset-deg\", getprop(\"/sim/current-view/goal-pitch-offset-deg\") + 20);", 
267                        "/sim/current-view/goal-heading-offset-deg"), 
268 #  PropertyScaleAxis.new("Aileron Trim",  "/controls/flight/aileron-trim"),
269 #  PropertyScaleAxis.new("Elevator Trim", "/controls/flight/elevator-trim"),
270 #  PropertyScaleAxis.new("Rudder Trim",  "/controls/flight/rudder-trim"),
271   PropertyScaleAxis.new("Brake Left", "/controls/gear/brake-left", 0, 0.5, 1.0),
272   PropertyScaleAxis.new("Brake Right", "/controls/gear/brake-right", 0, 0.5, 1.0),
273   NasalLowHighAxis.new("Aileron Trim",  "controls.aileronTrim(-1);", "controls.aileronTrim(1);", "/controls/flight/aileron-trim"),
274   NasalLowHighAxis.new("Elevator Trim", "controls.elevatorTrim(-1);", "controls.elevatorTrim(1);", "/controls/flight/elevator-trim"),
275   NasalLowHighAxis.new("Rudder Trim",   "controls.rudderTrim(-1);", "controls.rudderTrim(1);", "/controls/flight/rudder-trim"), 
276   CustomAxis.new(),
277   UnboundAxis.new(),
278 ];
279
280 # Button bindings
281 var ButtonBinding = {
282   new: func(name, binding, repeatable) {
283     var m = { parents: [ButtonBinding] };
284     m.name = name;
285     m.binding = binding;
286     m.repeatable = repeatable;
287     return m;
288   },
289   
290   clone: func() {
291     var m = { parents: [ButtonBinding] };
292     m.name = me.name;
293     m.binding= me.binding;
294     m.repeatable = me.repeatable;
295     return m;
296   },
297   
298   match: func(prop) {
299     return 0;
300   },
301
302   getName: func() { return me.name; },
303   getBinding: func(button) { return nil; },
304   isRepeatable: func() { return me.repeatable; }
305 };
306
307 var CustomButton = {
308   new: func() {
309     var m = { parents: [CustomButton, ButtonBinding.new("Custom", "", 0) ] };
310     m.custom_binding = nil;
311     return m;
312   },
313
314   clone: func() {
315     var m = { parents: [CustomButton, ButtonBinding.new("Custom", "", 0) ] };
316     m.custom_binding = me.custom_binding;
317     return m;
318   },
319     
320   match: func(prop) {
321     if (prop.getNode("binding") != nil) {
322       var p = props.Node.new();
323       props.copy(prop.getNode("binding"), p);
324       me.custom_binding = p;
325       return 1;
326     } else  {
327       return 0;
328     }
329   },
330   
331   getBinding: func(button) {
332     var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
333     p.getNode("desc", 1).setValue(me.name);
334     props.copy(me.custom_binding, p.getNode("binding", 1));    
335     return p;
336   },  
337 };
338
339 var UnboundButton = {
340   new: func() {
341     var m = { parents: [UnboundButton, ButtonBinding.new("None", "", 0) ] };
342     return m;
343   },
344
345   clone: func() {
346     var m = { parents: [UnboundButton, ButtonBinding.new("None", "", 0) ] };
347     return m;
348   },
349   
350   match: func(prop) {
351     return (prop.getNode("binding") != nil);
352   },
353   
354   getBinding: func(button) {
355     var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
356     p.getNode("desc", 1).setValue(me.name);
357     return p;
358   },
359 };
360
361
362 var PropertyToggleButton = {
363   new: func(name, prop) {
364     var m = { parents: [PropertyToggleButton, ButtonBinding.new(name, prop, 0) ] };
365     return m;
366   },
367
368   clone: func() {
369     var m = { parents: [PropertyToggleButton, ButtonBinding.new(me.name, me.binding, 0) ] };
370     return m;
371   },
372
373   match: func(prop) {
374   
375     var c = prop.getNode("binding", 1).getNode("command", 1).getValue();
376     var p = prop.getNode("binding", 1).getNode("property", 1).getValue();
377     return ((c == "property-toggle") and (p == me.prop));
378   },
379   
380   getBinding: func(button) {
381     var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
382     p.getNode("desc", 1).setValue(me.name);
383     p.getNode("binding", 1).getNode("command", 1).setValue("property-toggle");
384     p.getNode("binding", 1).getNode("property", 1).setValue(me.binding);
385     return p;
386   },
387 };
388
389 var PropertyAdjustButton = {
390   new: func(name, prop, step) {
391     var m = { parents: [PropertyAdjustButton, ButtonBinding.new(name, prop, 0) ] };
392     m.step = step;
393     return m;
394   },
395
396   clone: func() {
397     var m = { parents: [PropertyAdjustButton, ButtonBinding.new(me.name, me.binding, 0) ] };
398     m.step = me.step;
399     return m;
400   },
401
402   match: func(prop) {  
403     var c = prop.getNode("binding", 1).getNode("command", 1).getValue();
404     var p = prop.getNode("binding", 1).getNode("property", 1).getValue();
405     var s = prop.getNode("binding", 1).getNode("step", 1).getValue();
406     return ((c == "property-adjust") and (p == me.binding) and (s == me.step));
407   },
408   
409   getBinding: func(button) {
410     var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
411     p.getNode("desc", 1).setValue(me.name);
412     p.getNode("binding", 1).getNode("command", 1).setValue("property-adjust");
413     p.getNode("binding", 1).getNode("property", 1).setValue(me.binding);
414     p.getNode("binding", 1).getNode("step", 1).setValue(me.step);
415     return p;
416   },
417 };
418
419 var NasalButton = {
420   new: func(name, script, repeatable) {
421     var m = { parents: [NasalButton, ButtonBinding.new(name, script, repeatable) ] };
422     return m;
423   },
424
425   clone: func() {
426     var m = { parents: [NasalButton, ButtonBinding.new(me.name, me.binding, me.repeatable) ] };
427     return m;
428   },
429
430   match: func(prop) {  
431     var c = prop.getNode("binding", 1).getNode("command", 1).getValue();
432     var p = prop.getNode("binding", 1).getNode("script", 1).getValue();
433     
434     if (p == nil) { return 0; }
435     
436     p = string.trim(p);
437     p = string.replace(p, ";", "");
438     p = p ~ ";";
439     
440     return ((c == "nasal") and (p == me.binding));
441   },
442   
443   getBinding: func(button) {
444     var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
445     p.getNode("desc", 1).setValue(me.name);
446     p.getNode("binding", 1).getNode("command", 1).setValue("nasal");
447     p.getNode("binding", 1).getNode("script", 1).setValue(me.binding);
448     p.getNode("repeatable", 1).setValue(me.repeatable);
449     return p;
450   },
451 };
452
453
454 var NasalHoldButton = {
455   new: func(name, script, scriptUp) {
456     var m = { parents: [NasalHoldButton, ButtonBinding.new(name, script, 0) ] };
457     m.scriptUp = scriptUp;
458     return m;
459   },
460
461   clone: func() {
462     var m = { parents: [NasalHoldButton, ButtonBinding.new(me.name, me.binding, 0) ] };
463     m.scriptUp = me.scriptUp;
464     return m;
465   },
466
467   match: func(prop) {  
468   
469     var c = prop.getNode("mod-up", 1).getNode("binding", 1).getNode("command", 1).getValue();
470     var p1 = prop.getNode("binding", 1).getNode("script", 1).getValue();
471     var p2 = prop.getNode("mod-up", 1).getNode("binding", 1).getNode("script", 1).getValue();
472     
473     if (p2 == nil) { return 0; }
474     
475     p1 = string.trim(p1);
476     p1 = string.replace(p1, ";", "");
477     p1 = p1 ~ ";";
478     
479     return ((c == "nasal") and (p1 == me.binding));
480   },
481   
482   getBinding: func(button) {
483     var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
484     p.getNode("desc", 1).setValue(me.name);
485     p.getNode("repeatable", 1).setValue("false");
486     p.getNode("binding", 1).getNode("command", 1).setValue("nasal");
487     p.getNode("binding", 1).getNode("script", 1).setValue(me.binding);
488     p.getNode("mod-up", 1).getNode("binding", 1).getNode("command", 1).setValue("nasal");
489     p.getNode("mod-up", 1).getNode("binding", 1).getNode("script", 1).setValue(me.scriptUp);
490     return p;
491   },
492 };
493
494 var buttonBindings = [
495   NasalButton.new("Elevator Trim Up", "controls.elevatorTrim(-1);", 1),
496   NasalButton.new("Elevator Trim Down", "controls.elevatorTrim(1);", 1),
497   NasalButton.new("Rudder Trim Left", "controls.rudderTrim(-1);", 1),
498   NasalButton.new("Rudder Trim Right", "controls.rudderTrim(1);", 1),
499   NasalButton.new("Aileron Trim Left", "controls.aileronTrim(-1);", 1),
500   NasalButton.new("Aileron Trim Right", "controls.aileronTrim(1);", 1),
501   NasalHoldButton.new("FGCom PTT", "controls.ptt(1);", "controls.ptt(-1);"),
502   NasalHoldButton.new("Trigger", "controls.trigger(1);", "controls.trigger(0);"),
503   NasalHoldButton.new("Flaps Up", "controls.flapsDown(-1);", "controls.flapsDown(0);"),
504   NasalHoldButton.new("Flaps Down", "controls.flapsDown(1);", "controls.flapsDown(0);"),
505   NasalHoldButton.new("Gear Up", "controls.gearDown(-1);", "controls.gearDown(0);"),
506   NasalHoldButton.new("Gear Down", "controls.gearDown(1);", "controls.gearDown(0);"),
507   NasalHoldButton.new("Spoilers Retract", "controls.stepSpoilers(-1);", "controls.stepSpoilers(0);"),
508   NasalHoldButton.new("Spoilers Deploy", "controls.stepSpoilers(1);", "controls.stepSpoilers(0);"),
509   NasalHoldButton.new("Brakes", "controls.applyBrakes(1);", "controls.applyBrakes(0);"),
510
511   NasalButton.new("View Decrease", "view.decrease(0.75);", 1),
512   NasalButton.new("View Increase", "view.increase(0.75);", 1),
513   NasalButton.new("View Cycle Forwards", "view.stepView(1);", 0),
514   NasalButton.new("View Cycle Backwards", "view.stepView(-1);", 0),
515   PropertyAdjustButton.new("View Left", "/sim/current-view/goal-heading-offset-deg", "30.0"),
516   PropertyAdjustButton.new("View Right", "/sim/current-view/goal-heading-offset-deg", "-30.0"),
517   PropertyAdjustButton.new("View Up", "/sim/current-view/goal-pitch-offset-deg", "20.0"),
518   PropertyAdjustButton.new("View Down", "/sim/current-view/goal-pitch-offset-deg", "-20.0"),
519   CustomButton.new(),
520 ];
521
522 # Parse config from the /input tree and write it to the
523 # dialog_root.
524 var readConfig = func(dialog_root="/sim/gui/dialogs/joystick-config") {
525
526   var js_name = getprop(dialog_root ~ "/selected-joystick");
527   var joysticks = props.globals.getNode("/input/joysticks").getChildren("js");
528   
529   if (size(joysticks) == 0) { return 0; }
530   
531   if (js_name == nil) {
532     js_name = joysticks[0].getNode("id").getValue();  
533   }
534   
535   var js = nil;
536   
537   forindex (var i; joysticks) {
538     if ((joysticks[i].getNode("id") != nil) and 
539         (joysticks[i].getNode("id").getValue() == js_name)) 
540     {
541       js = joysticks[i];
542       setprop(dialog_root ~ "/selected-joystick", js_name);
543       setprop(dialog_root ~ "/selected-joystick-index", i);
544       setprop(dialog_root ~ "/selected-joystick-config", joysticks[i].getNode("source").getValue());
545     }
546   }
547   
548   if (js == nil) {
549     # We didn't find the joystick we expected - default to the first 
550     setprop(dialog_root ~ "/selected-joystick", joysticks[0].getNode("id").getValue());
551     setprop(dialog_root ~ "/selected-joystick-index", 0);
552     setprop(dialog_root ~ "/selected-joystick-config", joysticks[0].getNode("source").getValue());
553   }
554   
555   # Set up the axes assignments
556   var axes = js.getChildren("axis");
557
558   for (var axis = 0; axis < MAX_AXES; axis = axis +1) {
559
560     var p = props.globals.getNode(dialog_root ~ "/axis[" ~ axis ~ "]", 1);
561     p.remove();
562     p = props.globals.getNode(dialog_root ~ "/axis[" ~ axis ~ "]", 1);
563     
564     # Note that we can't simply use an index into the axes array
565     # as that doesn't work for a sparsley populated set of axes.
566     # E.g. one with n="3"
567     var a = js.getNode("axis[" ~ axis ~ "]");
568   
569     if (a != nil) {         
570       # Read properties from bindings
571       props.copy(a, p.getNode("original_binding", 1));
572
573       var binding = nil;
574       foreach (var b; joystick.axisBindings) {
575         if ((binding == nil) and (a != nil) and b.match(a)) {
576           binding = b.clone();
577           binding.parse(a);
578           p.getNode("binding", 1).setValue(binding.getName());
579           p.getNode("invertable", 1).setValue(binding.isInvertable());
580           p.getNode("inverted", 1).setValue(binding.isInverted());
581         }
582       }
583       
584       if (binding == nil) {
585         # No binding for this axis
586         p.getNode("binding", 1).setValue("None");
587         p.getNode("invertable", 1).setValue(0);
588         p.getNode("inverted", 1).setValue(0);
589         p.removeChild("original_binding");
590       }         
591     } else {
592       p.getNode("binding", 1).setValue("None");
593       p.getNode("invertable", 1).setValue(0);
594       p.getNode("inverted", 1).setValue(0);
595       p.removeChild("original_binding");
596     }
597   }
598
599   # Set up button assignment.
600   var buttons = js.getChildren("button");
601
602   for (var button = 0; button < MAX_BUTTONS; button = button + 1) {
603     var btn = props.globals.getNode(dialog_root ~ "/button[" ~ button ~ "]", 1);
604     btn.remove();
605     btn = props.globals.getNode(dialog_root ~ "/button[" ~ button ~ "]", 1);
606     
607     # Note that we can't simply use an index into the buttons array
608     # as that doesn't work for a sparsley populated set of buttons.
609     # E.g. one with n="3"
610     var a = js.getNode("button[" ~ button ~ "]");
611
612     if (a != nil) {         
613       # Read properties from bindings
614       props.copy(a, btn.getNode("original_binding", 1));
615       var binding = nil;
616       foreach (var b; joystick.buttonBindings) {
617         if ((binding == nil) and (a != nil) and b.match(a)) {
618           binding = b.clone();
619           btn.getNode("binding", 1).setValue(binding.getName());
620           props.copy(b.getBinding(button), btn.getNode("original_binding", 1));
621         }
622       }
623       
624       if (b == nil) {
625         btn.getNode("binding", 1).setValue("None");
626         btn.removeChild("original_binding");
627       }
628     } else {
629       btn.getNode("binding", 1).setValue("None");
630       btn.removeChild("original_binding");
631     }
632   }
633 }
634
635 var writeConfig = func(dialog_root="/sim/gui/dialogs/joystick-config") {  
636
637   # Write out the joystick file.
638   var config = props.Node.new();
639   var id = getprop(dialog_root ~ "/selected-joystick");
640   config.getNode("name", 1).setValue(id);
641   
642   var axes = props.globals.getNode(dialog_root).getChildren("axis");
643   forindex (var axis; axes) {
644     var name = getprop(dialog_root ~ "/axis[" ~ axis ~ "]/binding");
645     
646     if (name != "None") {
647       foreach (var binding; axisBindings) {
648         if (binding.getName() == name) {
649           var b = binding.clone();
650           b.setInverted(getprop(dialog_root ~ "/axis[" ~ axis ~ "]/inverted"));
651           
652           # Generate the axis and binding
653           var axisnode = config.getNode("axis[" ~ axis ~ "]", 1);
654           if (name == "Custom") {          
655             props.copy(props.globals.getNode(dialog_root ~ "/axis[" ~ axis ~ "]/original_binding", 1), axisnode);
656           } else {
657             props.copy(b.getBinding(axis), axisnode);
658           }
659         }
660       }    
661     }
662   }
663
664   var buttons = props.globals.getNode(dialog_root).getChildren("button");
665   forindex (var btn; buttons) {
666     var name = getprop(dialog_root ~ "/button[" ~ btn ~ "]/binding");
667
668     if (name != "None") {
669       foreach (var binding; buttonBindings) {
670         if (binding.getName() == name) {
671           var b = binding.clone();
672           # Generate the axis and binding
673           var buttonprop = config.getNode("button[" ~ btn ~ "]", 1);
674           if (name == "Custom") {          
675             props.copy(props.globals.getNode(dialog_root ~ "/button[" ~ btn ~ "]/original_binding", 1), buttonprop);
676           } else {
677             props.copy(b.getBinding(btn), buttonprop);
678           }
679         }
680       }    
681     }
682   }
683   
684   var filename = id;
685   filename = string.replace(filename, " ", "-");
686   filename = string.replace(filename, ".", "");
687   filename = string.replace(filename, "/", "");
688   
689   # Write out the file
690   io.write_properties(getprop("/sim/fg-home") ~ "/Input/Joysticks/" ~ filename ~ ".xml", config);
691 }
692
693