1 # Joystick configuration library.
2 var DIALOGROOT = "/sim/gui/dialogs/joystick-config";
7 # Hash of the custom axis/buttons
8 var custom_bindings = {};
10 # Class for an individual joystick axis binding
12 new: func(name, prop, invertable) {
13 var m = { parents: [Axis] };
16 m.invertable = invertable;
22 var m = { parents: [Axis] };
25 m.invertable = me.invertable;
26 m.inverted = me.inverted;
34 parse: func(prop) { },
36 readProps: func(props) {},
38 getName: func() { return me.name; },
39 getBinding: func(axis) { return props.Node.new(); },
40 isInvertable: func() { return me.invertable; },
41 isInverted: func() { return me.inverted; },
43 setInverted: func(b) {
44 if (me.invertable) me.inverted = b;
50 var m = { parents: [CustomAxis, Axis.new("Custom", "", 0) ] };
51 me.custom_binding = nil;
56 var m = { parents: [CustomAxis, Axis.new("Custom", "", 0) ] };
57 m.custom_binding = me.custom_binding;
62 var p = props.Node.new();
64 if (prop.getNode("binding") != nil) {
65 props.copy(prop.getNode("binding"), p);
66 me.custom_binding = p;
73 getBinding: func(axis) {
74 var p = props.Node.new().getNode("axis[" ~ axis ~ "]", 1);
75 p.getNode("desc", 1).setValue(me.name);
76 props.copy(me.custom_binding, p.getNode("binding", 1));
83 var m = { parents: [UnboundAxis, Axis.new("None", "", 0) ] };
88 var m = { parents: [UnboundAxis, Axis.new("None", "", 0) ] };
96 getBinding: func(axis) {
102 var PropertyScaleAxis = {
103 new: func(name, prop, deadband=0, factor=1, offset=0) {
104 var m = { parents: [PropertyScaleAxis, Axis.new(name, prop, 1) ] };
106 m.deadband = deadband;
113 var m = { parents: [PropertyScaleAxis, Axis.new(me.name, me.prop, 1) ] };
115 m.inverted = me.inverted;
118 m.deadband = me.deadband;
119 m.factor = me.factor;
120 m.offset = me.offset;
126 var cmd = prop.getNode("binding", 1).getNode("command", 1).getValue();
127 var p = prop.getNode("binding", 1).getNode("property", 1).getValue();
128 return ((cmd == "property-scale") and (p == me.prop));
132 me.deadband = p.getNode("binding", 1).getNode("dead-band", 1).getValue();
133 if (p.getNode("binding", 1).getNode("factor", 1).getValue() != nil) {
134 me.inverted = (p.getNode("binding", 1).getNode("factor", 1).getValue() < 0);
136 me.offset = p.getNode("binding", 1).getNode("offset", 1).getValue();
139 getBinding: func(axis) {
140 var p = props.Node.new();
141 p = p.getNode("axis[" ~ axis ~ "]", 1);
142 p.getNode("desc", 1).setValue(me.name);
143 p.getNode("binding", 1).getNode("command", 1).setValue("property-scale");
144 p.getNode("binding", 1).getNode("property", 1).setValue(me.prop);
145 p.getNode("binding", 1).getNode("dead-band", 1).setValue(me.deadband);
147 p.getNode("binding", 1).getNode("factor", 1).setValue(0 - me.factor);
149 p.getNode("binding", 1).getNode("factor", 1).setValue(me.factor);
151 p.getNode("binding", 1).getNode("offset", 1).setValue(me.offset);
157 var NasalScaleAxis = {
158 new: func(name, script, prop) {
159 var m = { parents: [NasalScaleAxis, Axis.new(name, prop, 0) ] };
166 var m = { parents: [NasalScaleAxis, Axis.new(me.name, me.prop, 0) ] };
168 m.script = me.script;
174 var cmd = prop.getNode("binding", 1).getNode("command", 1).getValue();
175 var p = prop.getNode("binding", 1).getNode("script", 1).getValue();
176 if ((p != nil) and (cmd == "nasal")) {
178 p = string.replace(p, ";", "");
180 return (p == me.script);
186 getBinding: func(axis) {
187 var p = props.Node.new().getNode("axis[" ~ axis ~ "]", 1);
188 p.getNode("desc", 1).setValue(me.name);
189 p.getNode("binding", 1).getNode("command", 1).setValue("nasal");
190 p.getNode("binding", 1).getNode("script", 1).setValue(me.script);
195 var NasalLowHighAxis = {
196 new: func(name, lowscript, highscript, prop) {
197 var m = { parents: [NasalLowHighAxis, Axis.new(name, prop, 1) ] };
198 m.lowscript = lowscript;
199 m.highscript = highscript;
204 var m = { parents: [NasalLowHighAxis, Axis.new(me.name, me.prop, 1) ] };
206 m.inverted = me.inverted;
208 m.lowscript = me.lowscript;
209 m.highscript = me.highscript;
214 var cmd = prop.getNode("low", 1).getNode("binding", 1).getNode("command", 1).getValue();
215 var p = prop.getNode("low", 1).getNode("binding", 1).getNode("script", 1).getValue();
217 if ((p == nil) or (cmd != "nasal")) return 0;
219 p = string.replace(p, ";", "");
222 if (p == me.lowscript) {
226 if (p == me.highscript) {
234 getBinding: func(axis) {
235 var p = props.Node.new().getNode("axis[" ~ axis ~ "]", 1);
236 p.getNode("desc", 1).setValue(me.name);
238 p.getNode("low", 1).getNode("binding", 1).getNode("command", 1).setValue("nasal");
239 p.getNode("high", 1).getNode("binding", 1).getNode("command", 1).setValue("nasal");
242 p.getNode("low", 1).getNode("binding", 1).getNode("script", 1).setValue(me.highscript);
243 p.getNode("high", 1).getNode("binding", 1).getNode("script", 1).setValue(me.lowscript);
245 p.getNode("low", 1).getNode("binding", 1).getNode("script", 1).setValue(me.lowscript);
246 p.getNode("high", 1).getNode("binding", 1).getNode("script", 1).setValue(me.highscript);
255 PropertyScaleAxis.new("Aileron", "/controls/flight/aileron"),
256 PropertyScaleAxis.new("Elevator", "/controls/flight/elevator"),
257 PropertyScaleAxis.new("Rudder", "/controls/flight/rudder"),
258 NasalScaleAxis.new("Throttle", "controls.throttleAxis();", "/controls/engines/engine[0]/throttle") ,
259 NasalScaleAxis.new("Mixture", "controls.mixtureAxis();", "/controls/engines/engine[0]/mixture") ,
260 NasalScaleAxis.new("Propeller", "controls.propellerAxis();", "/controls/engines/engine[0]/propeller-pitch") ,
261 NasalLowHighAxis.new("View (horizontal)",
262 "setprop(\"/sim/current-view/goal-heading-offset-deg\", getprop(\"/sim/current-view/goal-heading-offset-deg\") + 30);",
263 "setprop(\"/sim/current-view/goal-heading-offset-deg\", getprop(\"/sim/current-view/goal-heading-offset-deg\") - 30);",
264 "/sim/current-view/goal-heading-offset-deg"),
265 NasalLowHighAxis.new("View (vertical)",
266 "setprop(\"/sim/current-view/goal-pitch-offset-deg\", getprop(\"/sim/current-view/goal-pitch-offset-deg\") - 20);",
267 "setprop(\"/sim/current-view/goal-pitch-offset-deg\", getprop(\"/sim/current-view/goal-pitch-offset-deg\") + 20);",
268 "/sim/current-view/goal-heading-offset-deg"),
269 # PropertyScaleAxis.new("Aileron Trim", "/controls/flight/aileron-trim"),
270 # PropertyScaleAxis.new("Elevator Trim", "/controls/flight/elevator-trim"),
271 # PropertyScaleAxis.new("Rudder Trim", "/controls/flight/rudder-trim"),
272 PropertyScaleAxis.new("Brake Left", "/controls/gear/brake-left", 0, 0.5, 1.0),
273 PropertyScaleAxis.new("Brake Right", "/controls/gear/brake-right", 0, 0.5, 1.0),
274 NasalLowHighAxis.new("Aileron Trim", "controls.aileronTrim(-1);", "controls.aileronTrim(1);", "/controls/flight/aileron-trim"),
275 NasalLowHighAxis.new("Elevator Trim", "controls.elevatorTrim(-1);", "controls.elevatorTrim(1);", "/controls/flight/elevator-trim"),
276 NasalLowHighAxis.new("Rudder Trim", "controls.rudderTrim(-1);", "controls.rudderTrim(1);", "/controls/flight/rudder-trim"),
282 var ButtonBinding = {
283 new: func(name, binding, repeatable) {
284 var m = { parents: [ButtonBinding] };
287 m.repeatable = repeatable;
292 var m = { parents: [ButtonBinding] };
294 m.binding= me.binding;
295 m.repeatable = me.repeatable;
303 getName: func() { return me.name; },
304 getBinding: func(button) { return nil; },
305 isRepeatable: func() { return me.repeatable; }
310 var m = { parents: [CustomButton, ButtonBinding.new("Custom", "", 0) ] };
311 m.custom_binding = nil;
316 var m = { parents: [CustomButton, ButtonBinding.new("Custom", "", 0) ] };
317 m.custom_binding = me.custom_binding;
322 if (prop.getNode("binding") != nil) {
323 var p = props.Node.new();
324 props.copy(prop.getNode("binding"), p);
325 me.custom_binding = p;
332 getBinding: func(button) {
333 var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
334 p.getNode("desc", 1).setValue(me.name);
335 props.copy(me.custom_binding, p.getNode("binding", 1));
340 var UnboundButton = {
342 var m = { parents: [UnboundButton, ButtonBinding.new("None", "", 0) ] };
347 var m = { parents: [UnboundButton, ButtonBinding.new("None", "", 0) ] };
352 return (prop.getNode("binding") != nil);
355 getBinding: func(button) {
356 var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
357 p.getNode("desc", 1).setValue(me.name);
363 var PropertyToggleButton = {
364 new: func(name, prop) {
365 var m = { parents: [PropertyToggleButton, ButtonBinding.new(name, prop, 0) ] };
370 var m = { parents: [PropertyToggleButton, ButtonBinding.new(me.name, me.binding, 0) ] };
376 var c = prop.getNode("binding", 1).getNode("command", 1).getValue();
377 var p = prop.getNode("binding", 1).getNode("property", 1).getValue();
378 return ((c == "property-toggle") and (p == me.prop));
381 getBinding: func(button) {
382 var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
383 p.getNode("desc", 1).setValue(me.name);
384 p.getNode("binding", 1).getNode("command", 1).setValue("property-toggle");
385 p.getNode("binding", 1).getNode("property", 1).setValue(me.binding);
390 var PropertyAdjustButton = {
391 new: func(name, prop, step) {
392 var m = { parents: [PropertyAdjustButton, ButtonBinding.new(name, prop, 0) ] };
398 var m = { parents: [PropertyAdjustButton, ButtonBinding.new(me.name, me.binding, 0) ] };
404 var c = prop.getNode("binding", 1).getNode("command", 1).getValue();
405 var p = prop.getNode("binding", 1).getNode("property", 1).getValue();
406 var s = prop.getNode("binding", 1).getNode("step", 1).getValue();
407 return ((c == "property-adjust") and (p == me.binding) and (s == me.step));
410 getBinding: func(button) {
411 var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
412 p.getNode("desc", 1).setValue(me.name);
413 p.getNode("binding", 1).getNode("command", 1).setValue("property-adjust");
414 p.getNode("binding", 1).getNode("property", 1).setValue(me.binding);
415 p.getNode("binding", 1).getNode("step", 1).setValue(me.step);
421 new: func(name, script, repeatable) {
422 var m = { parents: [NasalButton, ButtonBinding.new(name, script, repeatable) ] };
427 var m = { parents: [NasalButton, ButtonBinding.new(me.name, me.binding, me.repeatable) ] };
432 var c = prop.getNode("binding", 1).getNode("command", 1).getValue();
433 var p = prop.getNode("binding", 1).getNode("script", 1).getValue();
435 if (p == nil) { return 0; }
438 p = string.replace(p, ";", "");
441 return ((c == "nasal") and (p == me.binding));
444 getBinding: func(button) {
445 var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
446 p.getNode("desc", 1).setValue(me.name);
447 p.getNode("binding", 1).getNode("command", 1).setValue("nasal");
448 p.getNode("binding", 1).getNode("script", 1).setValue(me.binding);
449 p.getNode("repeatable", 1).setValue(me.repeatable);
455 var NasalHoldButton = {
456 new: func(name, script, scriptUp) {
457 var m = { parents: [NasalHoldButton, ButtonBinding.new(name, script, 0) ] };
458 m.scriptUp = scriptUp;
463 var m = { parents: [NasalHoldButton, ButtonBinding.new(me.name, me.binding, 0) ] };
464 m.scriptUp = me.scriptUp;
470 var c = prop.getNode("mod-up", 1).getNode("binding", 1).getNode("command", 1).getValue();
471 var p1 = prop.getNode("binding", 1).getNode("script", 1).getValue();
472 var p2 = prop.getNode("mod-up", 1).getNode("binding", 1).getNode("script", 1).getValue();
474 if (p2 == nil) { return 0; }
476 p1 = string.trim(p1);
477 p1 = string.replace(p1, ";", "");
480 return ((c == "nasal") and (p1 == me.binding));
483 getBinding: func(button) {
484 var p = props.Node.new().getNode("button[" ~ button ~ "]", 1);
485 p.getNode("desc", 1).setValue(me.name);
486 p.getNode("repeatable", 1).setValue("false");
487 p.getNode("binding", 1).getNode("command", 1).setValue("nasal");
488 p.getNode("binding", 1).getNode("script", 1).setValue(me.binding);
489 p.getNode("mod-up", 1).getNode("binding", 1).getNode("command", 1).setValue("nasal");
490 p.getNode("mod-up", 1).getNode("binding", 1).getNode("script", 1).setValue(me.scriptUp);
495 var buttonBindings = [
496 NasalButton.new("Elevator Trim Up", "controls.elevatorTrim(-1);", 1),
497 NasalButton.new("Elevator Trim Down", "controls.elevatorTrim(1);", 1),
498 NasalButton.new("Rudder Trim Left", "controls.rudderTrim(-1);", 1),
499 NasalButton.new("Rudder Trim Right", "controls.rudderTrim(1);", 1),
500 NasalButton.new("Aileron Trim Left", "controls.aileronTrim(-1);", 1),
501 NasalButton.new("Aileron Trim Right", "controls.aileronTrim(1);", 1),
502 NasalHoldButton.new("FGCom PTT", "controls.ptt(1);", "controls.ptt(0);"),
503 NasalHoldButton.new("Trigger", "controls.trigger(1);", "controls.trigger(0);"),
504 NasalHoldButton.new("Flaps Up", "controls.flapsDown(-1);", "controls.flapsDown(0);"),
505 NasalHoldButton.new("Flaps Down", "controls.flapsDown(1);", "controls.flapsDown(0);"),
506 NasalHoldButton.new("Gear Up", "controls.gearDown(-1);", "controls.gearDown(0);"),
507 NasalHoldButton.new("Gear Down", "controls.gearDown(1);", "controls.gearDown(0);"),
508 NasalHoldButton.new("Spoilers Retract", "controls.stepSpoilers(-1);", "controls.stepSpoilers(0);"),
509 NasalHoldButton.new("Spoilers Deploy", "controls.stepSpoilers(1);", "controls.stepSpoilers(0);"),
510 NasalHoldButton.new("Brakes", "controls.applyBrakes(1);", "controls.applyBrakes(0);"),
512 NasalButton.new("View Decrease", "view.decrease(0.75);", 1),
513 NasalButton.new("View Increase", "view.increase(0.75);", 1),
514 NasalButton.new("View Cycle Forwards", "view.stepView(1);", 0),
515 NasalButton.new("View Cycle Backwards", "view.stepView(-1);", 0),
516 PropertyAdjustButton.new("View Left", "/sim/current-view/goal-heading-offset-deg", "30.0"),
517 PropertyAdjustButton.new("View Right", "/sim/current-view/goal-heading-offset-deg", "-30.0"),
518 PropertyAdjustButton.new("View Up", "/sim/current-view/goal-pitch-offset-deg", "20.0"),
519 PropertyAdjustButton.new("View Down", "/sim/current-view/goal-pitch-offset-deg", "-20.0"),
523 # Parse config from the /input tree and write it to the
525 var readConfig = func(dialog_root="/sim/gui/dialogs/joystick-config") {
527 var js_name = getprop(dialog_root ~ "/selected-joystick");
528 var joysticks = props.globals.getNode("/input/joysticks").getChildren("js");
530 if (size(joysticks) == 0) { return 0; }
532 if (js_name == nil) {
533 js_name = joysticks[0].getNode("id").getValue();
538 forindex (var i; joysticks) {
539 if ((joysticks[i].getNode("id") != nil) and
540 (joysticks[i].getNode("id").getValue() == js_name))
543 setprop(dialog_root ~ "/selected-joystick", js_name);
544 setprop(dialog_root ~ "/selected-joystick-index", i);
545 setprop(dialog_root ~ "/selected-joystick-config", joysticks[i].getNode("source").getValue());
550 # We didn't find the joystick we expected - default to the first
551 setprop(dialog_root ~ "/selected-joystick", joysticks[0].getNode("id").getValue());
552 setprop(dialog_root ~ "/selected-joystick-index", 0);
553 setprop(dialog_root ~ "/selected-joystick-config", joysticks[0].getNode("source").getValue());
556 # Set up the axes assignments
557 var axes = js.getChildren("axis");
559 for (var axis = 0; axis < MAX_AXES; axis = axis +1) {
561 var p = props.globals.getNode(dialog_root ~ "/axis[" ~ axis ~ "]", 1);
563 p = props.globals.getNode(dialog_root ~ "/axis[" ~ axis ~ "]", 1);
565 # Note that we can't simply use an index into the axes array
566 # as that doesn't work for a sparsley populated set of axes.
567 # E.g. one with n="3"
568 var a = js.getNode("axis[" ~ axis ~ "]");
571 # Read properties from bindings
572 props.copy(a, p.getNode("original_binding", 1));
575 foreach (var b; joystick.axisBindings) {
576 if ((binding == nil) and (a != nil) and b.match(a)) {
579 p.getNode("binding", 1).setValue(binding.getName());
580 p.getNode("invertable", 1).setValue(binding.isInvertable());
581 p.getNode("inverted", 1).setValue(binding.isInverted());
585 if (binding == nil) {
586 # No binding for this axis
587 p.getNode("binding", 1).setValue("None");
588 p.getNode("invertable", 1).setValue(0);
589 p.getNode("inverted", 1).setValue(0);
590 p.removeChild("original_binding");
593 p.getNode("binding", 1).setValue("None");
594 p.getNode("invertable", 1).setValue(0);
595 p.getNode("inverted", 1).setValue(0);
596 p.removeChild("original_binding");
600 # Set up button assignment.
601 var buttons = js.getChildren("button");
603 for (var button = 0; button < MAX_BUTTONS; button = button + 1) {
604 var btn = props.globals.getNode(dialog_root ~ "/button[" ~ button ~ "]", 1);
606 btn = props.globals.getNode(dialog_root ~ "/button[" ~ button ~ "]", 1);
608 # Note that we can't simply use an index into the buttons array
609 # as that doesn't work for a sparsley populated set of buttons.
610 # E.g. one with n="3"
611 var a = js.getNode("button[" ~ button ~ "]");
614 # Read properties from bindings
615 props.copy(a, btn.getNode("original_binding", 1));
617 foreach (var b; joystick.buttonBindings) {
618 if ((binding == nil) and (a != nil) and b.match(a)) {
620 btn.getNode("binding", 1).setValue(binding.getName());
621 props.copy(b.getBinding(button), btn.getNode("original_binding", 1));
626 btn.getNode("binding", 1).setValue("None");
627 btn.removeChild("original_binding");
630 btn.getNode("binding", 1).setValue("None");
631 btn.removeChild("original_binding");
636 var nasals = js.getChildren("nasal");
638 for (var nasal = 0; nasal < MAX_NASALS; nasal = nasal + 1) {
639 var nas = props.globals.getNode(dialog_root ~ "/nasal[" ~ nasal ~ "]", 1);
641 nas = props.globals.getNode(dialog_root ~ "/nasal[" ~ nasal ~ "]", 1);
643 # Note that we can't simply use an index into the buttons array
644 # as that doesn't work for a sparsley populated set of buttons.
645 # E.g. one with n="3"
646 var a = js.getNode("nasal[" ~ nasal ~ "]");
648 props.copy(a, nas.getNode("original_script", 1));
653 var writeConfig = func(dialog_root="/sim/gui/dialogs/joystick-config", reset=0) {
655 # Write out the joystick file.
656 var config = props.Node.new();
657 var id = getprop(dialog_root ~ "/selected-joystick");
660 # We've been asked to reset the joystick config to the default. As we can't
661 # delete the configuration file, we achieve this by setting an invalid name
662 # tag that won't match.
663 config.getNode("name", 1).setValue("UNUSED INVALID CONFIG");
665 config.getNode("name", 1).setValue(id);
668 var nasals = props.globals.getNode(dialog_root).getChildren("nasal");
669 forindex (var nas; nasals) {
670 var nasalscript = config.getNode("nasal[" ~ nas ~ "]", 1);
671 props.copy(props.globals.getNode(dialog_root ~ "/nasal[" ~ nas ~ "]/original_script", 1), nasalscript);
674 var axes = props.globals.getNode(dialog_root).getChildren("axis");
675 forindex (var axis; axes) {
676 var name = getprop(dialog_root ~ "/axis[" ~ axis ~ "]/binding");
678 if (name != "None") {
679 foreach (var binding; axisBindings) {
680 if (binding.getName() == name) {
681 var b = binding.clone();
682 b.setInverted(getprop(dialog_root ~ "/axis[" ~ axis ~ "]/inverted"));
684 # Generate the axis and binding
685 var axisnode = config.getNode("axis[" ~ axis ~ "]", 1);
686 if (name == "Custom") {
687 props.copy(props.globals.getNode(dialog_root ~ "/axis[" ~ axis ~ "]/original_binding", 1), axisnode);
689 props.copy(b.getBinding(axis), axisnode);
696 var buttons = props.globals.getNode(dialog_root).getChildren("button");
697 forindex (var btn; buttons) {
698 var name = getprop(dialog_root ~ "/button[" ~ btn ~ "]/binding");
700 if (name != "None") {
701 foreach (var binding; buttonBindings) {
702 if (binding.getName() == name) {
703 var b = binding.clone();
704 # Generate the axis and binding
705 var buttonprop = config.getNode("button[" ~ btn ~ "]", 1);
706 if (name == "Custom") {
707 props.copy(props.globals.getNode(dialog_root ~ "/button[" ~ btn ~ "]/original_binding", 1), buttonprop);
709 props.copy(b.getBinding(btn), buttonprop);
717 filename = string.replace(filename, " ", "-");
718 filename = string.replace(filename, ".", "");
719 filename = string.replace(filename, "/", "");
722 io.write_properties(getprop("/sim/fg-home") ~ "/Input/Joysticks/" ~ filename ~ ".xml", config);
725 var resetConfig = func(dialog_root="/sim/gui/dialogs/joystick-config") {
726 writeConfig(dialog_root, 1);