L4RIO.h splits the abstract cockpit-control surface (RIOBase: enums, the five analog Scalars, GetNextEvent/SetLamp, no-op serial ops, NEW IsOperational) out of the serial RIO (RIO : PCSerialPacket, RIOBase -- byte-for-byte behavior kept, ctor assigns as before); LBE4ControlsManager holds a RIOBase* and gains the gated L4CONTROLS=PAD factory arm (BT_GLASS; OFF build logs+ignores the token). NEW gated TUs: L4PADRIO (XInput+keyboard synthesize the surface; 3s hot-plug re-probe; focus-guarded keys; per-poll AnalogEvent heartbeat; lampState[] + static SetScreenButton/GetLampState for the panel) and L4PADBINDINGS (content\bindings.txt profile, self-documenting default written on first run; deflect/slew/set axis model; addresses validated against ButtonCount). Verified live (glass build, L4CONTROLS=PAD): bindings written+parsed 44/10/5, XInput pad detected, 121 streamed mappings install via stock PrimaryRIO path, 2157 frames clean. Pod build (gates OFF) compiles the split with zero behavioral delta. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
554 lines
15 KiB
C++
554 lines
15 KiB
C++
#include "mungal4.h"
|
|
#pragma hdrstop
|
|
|
|
//###########################################################################
|
|
// L4PADBINDINGS -- the PadRIO input-binding profile (BT_GLASS only; this TU
|
|
// is only in the build when the gate is on -- see CMakeLists.txt).
|
|
// Format + semantics: L4PADBINDINGS.h.
|
|
//###########################################################################
|
|
|
|
#include "l4padbindings.h"
|
|
#include "l4ctrl.h"
|
|
|
|
#include <windows.h>
|
|
#include <xinput.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
static const char *bindingsFileName = "bindings.txt";
|
|
|
|
//###########################################################################
|
|
// Name tables
|
|
//###########################################################################
|
|
|
|
struct NamedValue
|
|
{
|
|
const char *name;
|
|
int value;
|
|
};
|
|
|
|
//
|
|
// Win32 virtual-key names accepted in bindings.txt. Single letters and
|
|
// digits are accepted directly ("W", "5"); everything else by name.
|
|
//
|
|
static const NamedValue keyNames[] =
|
|
{
|
|
{ "SPACE", VK_SPACE }, { "ENTER", VK_RETURN },
|
|
{ "TAB", VK_TAB }, { "BACKSPACE", VK_BACK },
|
|
{ "LSHIFT", VK_LSHIFT }, { "RSHIFT", VK_RSHIFT },
|
|
{ "LCTRL", VK_LCONTROL }, { "RCTRL", VK_RCONTROL },
|
|
{ "LALT", VK_LMENU }, { "RALT", VK_RMENU },
|
|
{ "UP", VK_UP }, { "DOWN", VK_DOWN },
|
|
{ "LEFT", VK_LEFT }, { "RIGHT", VK_RIGHT },
|
|
{ "INSERT", VK_INSERT }, { "DELETE", VK_DELETE },
|
|
{ "HOME", VK_HOME }, { "END", VK_END },
|
|
{ "PAGEUP", VK_PRIOR }, { "PAGEDOWN", VK_NEXT },
|
|
{ "NUMPAD0", VK_NUMPAD0 }, { "NUMPAD1", VK_NUMPAD1 },
|
|
{ "NUMPAD2", VK_NUMPAD2 }, { "NUMPAD3", VK_NUMPAD3 },
|
|
{ "NUMPAD4", VK_NUMPAD4 }, { "NUMPAD5", VK_NUMPAD5 },
|
|
{ "NUMPAD6", VK_NUMPAD6 }, { "NUMPAD7", VK_NUMPAD7 },
|
|
{ "NUMPAD8", VK_NUMPAD8 }, { "NUMPAD9", VK_NUMPAD9 },
|
|
{ "NUMPADPLUS", VK_ADD }, { "NUMPADMINUS", VK_SUBTRACT },
|
|
{ "NUMPADSTAR", VK_MULTIPLY }, { "NUMPADSLASH", VK_DIVIDE },
|
|
{ "NUMPADDOT", VK_DECIMAL },
|
|
{ "F1", VK_F1 }, { "F2", VK_F2 }, { "F3", VK_F3 }, { "F4", VK_F4 },
|
|
{ "F5", VK_F5 }, { "F6", VK_F6 }, { "F7", VK_F7 }, { "F8", VK_F8 },
|
|
{ "F9", VK_F9 }, { "F10", VK_F10 }, { "F11", VK_F11 }, { "F12", VK_F12 },
|
|
{ "MINUS", VK_OEM_MINUS }, { "EQUALS", VK_OEM_PLUS },
|
|
{ "COMMA", VK_OEM_COMMA }, { "PERIOD", VK_OEM_PERIOD },
|
|
{ "SEMICOLON", VK_OEM_1 }, { "SLASH", VK_OEM_2 },
|
|
{ "BACKTICK", VK_OEM_3 }, { "LBRACKET", VK_OEM_4 },
|
|
{ "BACKSLASH", VK_OEM_5 }, { "RBRACKET", VK_OEM_6 },
|
|
{ "QUOTE", VK_OEM_7 },
|
|
};
|
|
|
|
static const NamedValue padButtonNames[] =
|
|
{
|
|
{ "A", XINPUT_GAMEPAD_A },
|
|
{ "B", XINPUT_GAMEPAD_B },
|
|
{ "X", XINPUT_GAMEPAD_X },
|
|
{ "Y", XINPUT_GAMEPAD_Y },
|
|
{ "LB", XINPUT_GAMEPAD_LEFT_SHOULDER },
|
|
{ "RB", XINPUT_GAMEPAD_RIGHT_SHOULDER },
|
|
{ "BACK", XINPUT_GAMEPAD_BACK },
|
|
{ "START", XINPUT_GAMEPAD_START },
|
|
{ "LS", XINPUT_GAMEPAD_LEFT_THUMB },
|
|
{ "RS", XINPUT_GAMEPAD_RIGHT_THUMB },
|
|
{ "DPAD_UP", XINPUT_GAMEPAD_DPAD_UP },
|
|
{ "DPAD_DOWN", XINPUT_GAMEPAD_DPAD_DOWN },
|
|
{ "DPAD_LEFT", XINPUT_GAMEPAD_DPAD_LEFT },
|
|
{ "DPAD_RIGHT", XINPUT_GAMEPAD_DPAD_RIGHT },
|
|
};
|
|
|
|
static const NamedValue padAxisNames[] =
|
|
{
|
|
{ "LX", PadBindingProfile::PadAxisLX },
|
|
{ "LY", PadBindingProfile::PadAxisLY },
|
|
{ "RX", PadBindingProfile::PadAxisRX },
|
|
{ "RY", PadBindingProfile::PadAxisRY },
|
|
{ "LT", PadBindingProfile::PadAxisLT },
|
|
{ "RT", PadBindingProfile::PadAxisRT },
|
|
};
|
|
|
|
static const NamedValue channelNames[] =
|
|
{
|
|
{ "Throttle", PadBindingProfile::ChannelThrottle },
|
|
{ "JoystickX", PadBindingProfile::ChannelJoystickX },
|
|
{ "JoystickY", PadBindingProfile::ChannelJoystickY },
|
|
{ "LeftPedal", PadBindingProfile::ChannelLeftPedal },
|
|
{ "RightPedal", PadBindingProfile::ChannelRightPedal },
|
|
};
|
|
|
|
static int
|
|
LookupName(const NamedValue *table, int count, const char *name)
|
|
{
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
if (_stricmp(table[i].name, name) == 0)
|
|
{
|
|
return table[i].value;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
LookupKeyName(const char *name)
|
|
{
|
|
//
|
|
// Single letter / digit binds itself ('A'..'Z','0'..'9' are the VK
|
|
// codes for the un-shifted keys).
|
|
//
|
|
if (strlen(name) == 1)
|
|
{
|
|
char c = (char)toupper((unsigned char)name[0]);
|
|
if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
|
|
{
|
|
return (int)c;
|
|
}
|
|
}
|
|
return LookupName(keyNames, sizeof(keyNames)/sizeof(keyNames[0]), name);
|
|
}
|
|
|
|
//###########################################################################
|
|
// The default profile.
|
|
//
|
|
// RIO addresses (L4CTRL.h button enum): 0x40 joystick trigger, 0x3F the
|
|
// throttle-handle reverse button, 0x41-0x44 the hat (look behind/forward/
|
|
// right/left), 0x45-0x47 pinky/thumb-low/thumb-high, 0x10-0x15 + 0x18-0x1D
|
|
// the 12 secondary-panel buttons, 0x00-0x0F the lower aux banks, 0x20-0x37
|
|
// the upper aux banks. Keypad unit 0 = the pilot's MFD keypad.
|
|
//###########################################################################
|
|
|
|
static const char *defaultProfileText =
|
|
"# bindings.txt -- PadRIO input bindings (glass cockpit). Auto-written with\n"
|
|
"# defaults on first run; edit freely, delete to regenerate. Grammar:\n"
|
|
"# key <KEYNAME> button <addr> RIO button (hex 0x.. ok)\n"
|
|
"# key <KEYNAME> keypad <unit> <key> MFD keypad unit 0-2, key 0-15\n"
|
|
"# key <KEYNAME> axis <channel> deflect <+|-> <rate> spring stick\n"
|
|
"# key <KEYNAME> axis <channel> slew <+|-> <rate> throttle lever\n"
|
|
"# key <KEYNAME> axis <channel> set <value> jump/detent\n"
|
|
"# pad <PADBTN> button <addr>\n"
|
|
"# padaxis <PADAXIS> axis <channel> [invert] [slew <rate>]\n"
|
|
"# Channels: Throttle JoystickX JoystickY LeftPedal RightPedal\n"
|
|
"# Pad buttons: A B X Y LB RB BACK START LS RS DPAD_* Axes: LX LY RX RY LT RT\n"
|
|
"\n"
|
|
"# --- flight: numpad spring stick, pedals, throttle lever ---\n"
|
|
"key NUMPAD8 axis JoystickY deflect + 2.5\n"
|
|
"key NUMPAD2 axis JoystickY deflect - 2.5\n"
|
|
"key NUMPAD4 axis JoystickX deflect - 2.5\n"
|
|
"key NUMPAD6 axis JoystickX deflect + 2.5\n"
|
|
"key NUMPAD7 axis LeftPedal deflect + 2.5\n"
|
|
"key NUMPAD9 axis RightPedal deflect + 2.5\n"
|
|
"key LSHIFT axis Throttle slew + 0.7\n"
|
|
"key LCTRL axis Throttle slew - 0.7\n"
|
|
"key NUMPAD5 axis Throttle set 0\n"
|
|
"\n"
|
|
"# --- weapons / hat / handle buttons ---\n"
|
|
"key SPACE button 0x40\n"
|
|
"key LALT button 0x3F\n"
|
|
"key V button 0x47\n"
|
|
"key C button 0x46\n"
|
|
"key B button 0x45\n"
|
|
"key UP button 0x42\n"
|
|
"key DOWN button 0x41\n"
|
|
"key LEFT button 0x44\n"
|
|
"key RIGHT button 0x43\n"
|
|
"\n"
|
|
"# --- secondary panel (12) on the number row ---\n"
|
|
"key 1 button 0x10\n"
|
|
"key 2 button 0x11\n"
|
|
"key 3 button 0x12\n"
|
|
"key 4 button 0x13\n"
|
|
"key 5 button 0x14\n"
|
|
"key 6 button 0x15\n"
|
|
"key 7 button 0x18\n"
|
|
"key 8 button 0x19\n"
|
|
"key 9 button 0x1A\n"
|
|
"key 0 button 0x1B\n"
|
|
"key MINUS button 0x1C\n"
|
|
"key EQUALS button 0x1D\n"
|
|
"\n"
|
|
"# --- aux banks on F5-F8 (upper center 1-4) ---\n"
|
|
"key F5 button 0x27\n"
|
|
"key F6 button 0x26\n"
|
|
"key F7 button 0x25\n"
|
|
"key F8 button 0x24\n"
|
|
"\n"
|
|
"# --- pilot MFD keypad on QWERTY row (unit 0, keys 0-9) ---\n"
|
|
"key Q keypad 0 1\n"
|
|
"key W keypad 0 2\n"
|
|
"key E keypad 0 3\n"
|
|
"key R keypad 0 4\n"
|
|
"key T keypad 0 5\n"
|
|
"key Y keypad 0 6\n"
|
|
"key U keypad 0 7\n"
|
|
"key I keypad 0 8\n"
|
|
"key O keypad 0 9\n"
|
|
"key P keypad 0 0\n"
|
|
"\n"
|
|
"# --- XInput pad ---\n"
|
|
"padaxis LX axis JoystickX\n"
|
|
"padaxis LY axis JoystickY invert\n"
|
|
"padaxis LT axis LeftPedal\n"
|
|
"padaxis RT axis RightPedal\n"
|
|
"padaxis RY axis Throttle slew 0.7\n"
|
|
"pad A button 0x40\n"
|
|
"pad B button 0x3F\n"
|
|
"pad X button 0x47\n"
|
|
"pad Y button 0x42\n"
|
|
"pad LB button 0x44\n"
|
|
"pad RB button 0x43\n"
|
|
"pad DPAD_UP button 0x42\n"
|
|
"pad DPAD_DOWN button 0x41\n"
|
|
"pad DPAD_LEFT button 0x44\n"
|
|
"pad DPAD_RIGHT button 0x43\n";
|
|
|
|
//###########################################################################
|
|
// PadBindingProfile
|
|
//###########################################################################
|
|
|
|
PadBindingProfile::PadBindingProfile():
|
|
keyBindingCount(0),
|
|
padButtonBindingCount(0),
|
|
padAxisBindingCount(0)
|
|
{
|
|
}
|
|
|
|
PadBindingProfile::~PadBindingProfile()
|
|
{
|
|
}
|
|
|
|
void
|
|
PadBindingProfile::WriteDefaultFile(const char *path)
|
|
{
|
|
FILE *f = fopen(path, "wt");
|
|
if (f == NULL)
|
|
{
|
|
DEBUG_STREAM << "[padrio] cannot write " << path
|
|
<< " -- using built-in defaults\n" << std::flush;
|
|
return;
|
|
}
|
|
fputs(defaultProfileText, f);
|
|
fclose(f);
|
|
DEBUG_STREAM << "[padrio] wrote default " << path << "\n" << std::flush;
|
|
}
|
|
|
|
//
|
|
// Parse one binding line into the tables. Returns 0 on success, -1 on a
|
|
// malformed line (caller logs). The line is tokenized in place.
|
|
//
|
|
int
|
|
PadBindingProfile::ParseLine(char *line, int line_number)
|
|
{
|
|
char *tokens[8];
|
|
int tokenCount = 0;
|
|
char *cursor = line;
|
|
while (tokenCount < 8)
|
|
{
|
|
while (*cursor == ' ' || *cursor == '\t') ++cursor;
|
|
if (*cursor == '\0' || *cursor == '#' ||
|
|
*cursor == '\r' || *cursor == '\n')
|
|
{
|
|
break;
|
|
}
|
|
tokens[tokenCount++] = cursor;
|
|
while (*cursor && *cursor != ' ' && *cursor != '\t' &&
|
|
*cursor != '\r' && *cursor != '\n')
|
|
{
|
|
++cursor;
|
|
}
|
|
if (*cursor) *cursor++ = '\0';
|
|
}
|
|
if (tokenCount == 0)
|
|
{
|
|
return 0; // blank / comment
|
|
}
|
|
if (tokenCount < 3)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
//
|
|
// Build the action from tokens[2]... (shared by key/pad rows).
|
|
//
|
|
Action action;
|
|
memset(&action, 0, sizeof(action));
|
|
int actionAt = 2;
|
|
|
|
if (_stricmp(tokens[0], "padaxis") == 0)
|
|
{
|
|
if (tokenCount < 4 || _stricmp(tokens[2], "axis") != 0)
|
|
{
|
|
return -1;
|
|
}
|
|
int axis = LookupName(padAxisNames,
|
|
sizeof(padAxisNames)/sizeof(padAxisNames[0]), tokens[1]);
|
|
int channel = LookupName(channelNames,
|
|
sizeof(channelNames)/sizeof(channelNames[0]), tokens[3]);
|
|
if (axis < 0 || channel < 0 ||
|
|
padAxisBindingCount >= (int)(sizeof(padAxisBindings)/sizeof(padAxisBindings[0])))
|
|
{
|
|
return -1;
|
|
}
|
|
PadAxisBinding &b = padAxisBindings[padAxisBindingCount];
|
|
b.axis = axis;
|
|
b.channel = channel;
|
|
b.invert = 0;
|
|
b.slew = 0;
|
|
b.slewRate = 0.0f;
|
|
for (int t = 4; t < tokenCount; ++t)
|
|
{
|
|
if (_stricmp(tokens[t], "invert") == 0)
|
|
{
|
|
b.invert = 1;
|
|
}
|
|
else if (_stricmp(tokens[t], "slew") == 0 && t+1 < tokenCount)
|
|
{
|
|
b.slew = 1;
|
|
b.slewRate = (float)atof(tokens[++t]);
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
++padAxisBindingCount;
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// key/pad rows: parse the action clause.
|
|
//
|
|
if (_stricmp(tokens[actionAt], "button") == 0)
|
|
{
|
|
if (tokenCount < actionAt+2)
|
|
{
|
|
return -1;
|
|
}
|
|
action.kind = ActionButton;
|
|
action.address = (int)strtol(tokens[actionAt+1], NULL, 0);
|
|
if (action.address < 0 ||
|
|
action.address >= LBE4ControlsManager::ButtonCount)
|
|
{
|
|
return -1; // out of the buttonGroup range -- would overrun
|
|
}
|
|
}
|
|
else if (_stricmp(tokens[actionAt], "keypad") == 0)
|
|
{
|
|
if (tokenCount < actionAt+3)
|
|
{
|
|
return -1;
|
|
}
|
|
action.kind = ActionKeypad;
|
|
action.address = (int)strtol(tokens[actionAt+1], NULL, 0);
|
|
action.key = (int)strtol(tokens[actionAt+2], NULL, 0);
|
|
if (action.address < 0 ||
|
|
action.address >= LBE4ControlsManager::KeyboardCount ||
|
|
action.key < 0 || action.key > 15)
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
else if (_stricmp(tokens[actionAt], "axis") == 0)
|
|
{
|
|
if (tokenCount < actionAt+3)
|
|
{
|
|
return -1;
|
|
}
|
|
action.channel = LookupName(channelNames,
|
|
sizeof(channelNames)/sizeof(channelNames[0]), tokens[actionAt+1]);
|
|
if (action.channel < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
const char *mode = tokens[actionAt+2];
|
|
if (_stricmp(mode, "set") == 0)
|
|
{
|
|
if (tokenCount < actionAt+4)
|
|
{
|
|
return -1;
|
|
}
|
|
action.kind = ActionAxisSet;
|
|
action.rate = (float)atof(tokens[actionAt+3]);
|
|
}
|
|
else
|
|
{
|
|
if (tokenCount < actionAt+5)
|
|
{
|
|
return -1;
|
|
}
|
|
if (_stricmp(mode, "deflect") == 0)
|
|
{
|
|
action.kind = ActionAxisDeflect;
|
|
}
|
|
else if (_stricmp(mode, "slew") == 0)
|
|
{
|
|
action.kind = ActionAxisSlew;
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
float sign;
|
|
if (strcmp(tokens[actionAt+3], "+") == 0) sign = 1.0f;
|
|
else if (strcmp(tokens[actionAt+3], "-") == 0) sign = -1.0f;
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
action.rate = sign * (float)atof(tokens[actionAt+4]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (_stricmp(tokens[0], "key") == 0)
|
|
{
|
|
int vk = LookupKeyName(tokens[1]);
|
|
if (vk < 0 ||
|
|
keyBindingCount >= (int)(sizeof(keyBindings)/sizeof(keyBindings[0])))
|
|
{
|
|
return -1;
|
|
}
|
|
keyBindings[keyBindingCount].virtualKey = vk;
|
|
keyBindings[keyBindingCount].action = action;
|
|
++keyBindingCount;
|
|
return 0;
|
|
}
|
|
else if (_stricmp(tokens[0], "pad") == 0)
|
|
{
|
|
if (action.kind != ActionButton && action.kind != ActionKeypad)
|
|
{
|
|
return -1; // pad buttons cannot drive axes; use padaxis
|
|
}
|
|
int mask = LookupName(padButtonNames,
|
|
sizeof(padButtonNames)/sizeof(padButtonNames[0]), tokens[1]);
|
|
if (mask < 0 ||
|
|
padButtonBindingCount >= (int)(sizeof(padButtonBindings)/sizeof(padButtonBindings[0])))
|
|
{
|
|
return -1;
|
|
}
|
|
padButtonBindings[padButtonBindingCount].buttonMask = (unsigned)mask;
|
|
padButtonBindings[padButtonBindingCount].action = action;
|
|
++padButtonBindingCount;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
PadBindingProfile::LoadDefaults()
|
|
{
|
|
keyBindingCount = 0;
|
|
padButtonBindingCount = 0;
|
|
padAxisBindingCount = 0;
|
|
|
|
//
|
|
// Parse the built-in default text line by line (one code path for
|
|
// defaults and files -- the defaults ARE a valid file).
|
|
//
|
|
const char *cursor = defaultProfileText;
|
|
char line[256];
|
|
int line_number = 0;
|
|
while (*cursor)
|
|
{
|
|
int n = 0;
|
|
while (cursor[n] && cursor[n] != '\n' && n < (int)sizeof(line)-1)
|
|
{
|
|
++n;
|
|
}
|
|
memcpy(line, cursor, n);
|
|
line[n] = '\0';
|
|
cursor += n + (cursor[n] == '\n' ? 1 : 0);
|
|
++line_number;
|
|
if (ParseLine(line, line_number) != 0)
|
|
{
|
|
DEBUG_STREAM << "[padrio] BUG: default profile line "
|
|
<< line_number << " malformed\n" << std::flush;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
PadBindingProfile::Load()
|
|
{
|
|
FILE *f = fopen(bindingsFileName, "rt");
|
|
if (f == NULL)
|
|
{
|
|
WriteDefaultFile(bindingsFileName);
|
|
f = fopen(bindingsFileName, "rt");
|
|
}
|
|
if (f == NULL)
|
|
{
|
|
LoadDefaults();
|
|
return;
|
|
}
|
|
|
|
keyBindingCount = 0;
|
|
padButtonBindingCount = 0;
|
|
padAxisBindingCount = 0;
|
|
|
|
char line[256];
|
|
int line_number = 0;
|
|
int accepted = 0, rejected = 0;
|
|
while (fgets(line, sizeof(line), f))
|
|
{
|
|
++line_number;
|
|
char parse_copy[256];
|
|
strcpy(parse_copy, line);
|
|
if (ParseLine(parse_copy, line_number) != 0)
|
|
{
|
|
DEBUG_STREAM << "[padrio] " << bindingsFileName << " line "
|
|
<< line_number << " rejected: " << line << std::flush;
|
|
++rejected;
|
|
}
|
|
else
|
|
{
|
|
++accepted;
|
|
}
|
|
}
|
|
fclose(f);
|
|
|
|
if (keyBindingCount == 0 && padAxisBindingCount == 0)
|
|
{
|
|
//
|
|
// A file that binds NOTHING is treated as damaged -- fall back so
|
|
// the developer is never left with a dead cockpit.
|
|
//
|
|
DEBUG_STREAM << "[padrio] " << bindingsFileName
|
|
<< " contains no usable bindings -- using built-in defaults\n"
|
|
<< std::flush;
|
|
LoadDefaults();
|
|
return;
|
|
}
|
|
DEBUG_STREAM << "[padrio] bindings loaded: " << keyBindingCount
|
|
<< " keys, " << padButtonBindingCount << " pad buttons, "
|
|
<< padAxisBindingCount << " pad axes"
|
|
<< (rejected ? " (rejected lines logged above)" : "")
|
|
<< "\n" << std::flush;
|
|
}
|