diff --git a/MUNGA_L4/L4PADRIO.cpp b/MUNGA_L4/L4PADRIO.cpp index d853b74..53568b3 100644 --- a/MUNGA_L4/L4PADRIO.cpp +++ b/MUNGA_L4/L4PADRIO.cpp @@ -42,6 +42,48 @@ namespace return (GetAsyncKeyState(virtual_key) & 0x8000) != 0; } + // + // RP412INPUTFOCUS - do the controls answer only while the game is the + // window in front? On unless the file says 0. + // + // The pod was the only thing running on its cabinet, so the virtual + // RIO reads the key state directly rather than waiting on the message + // pump. That is the right call for latency and it is why the pedals + // feel like pedals - but a direct read is a read of the WHOLE + // keyboard, whatever has focus, so a player alt-tabbed into a text + // editor was flying the pod with every note they typed. + // + Logical InputNeedsFocus() + { + static int setting = -1; + if (setting < 0) + { + const char *value = getenv("RP412INPUTFOCUS"); + setting = (value != NULL && *value == '0') ? 0 : 1; + } + return setting ? True : False; + } + + // + // Whether the foreground window is one of OURS - the process, not one + // particular handle. The cockpit is a shell full of child panes, the + // exploded view is six windows of its own and the plasma glass + // another, so any of them being in front is the game being in front. + // Matching a single HWND would drop the controls the moment somebody + // clicked an MFD. + // + Logical ProcessHasFocus() + { + HWND foreground = GetForegroundWindow(); + if (foreground == NULL) + { + return False; + } + DWORD foreground_process = 0; + GetWindowThreadProcessId(foreground, &foreground_process); + return (foreground_process == GetCurrentProcessId()) ? True : False; + } + // // A generic stick axis is already normalized -1..1, so the deadzone // is a plain cut about centre with the remainder rescaled - press @@ -231,6 +273,11 @@ PadRIO::PadRIO() activeInstance = this; DEBUG_STREAM << "PadRIO: virtual RIO active (XInput pad + keyboard)\n" << std::flush; + DEBUG_STREAM << "PadRIO: controls " + << (InputNeedsFocus() + ? "answer only while the game window has focus" + : "answer whether the game has focus or not (RP412INPUTFOCUS=0)") + << "\n" << std::flush; // // Only open DirectInput when the profile actually asks for it. A @@ -390,6 +437,25 @@ void } lastPollTick = now; + //--------------------------------------------------------------- + // Is the game the window in front? Every source below is gated on + // this - keyboard, pad and stick alike. + // + // Gated rather than skipped, and that is the whole trick: each + // source reads as RELEASED instead of the poll returning early, so + // the diffs further down turn whatever was held at the moment you + // switched away into proper release events. Bail out instead and a + // key held on alt-tab stays down until you come back, which is the + // stuck throttle this is meant to prevent rather than cause. + // + // The throttle accumulator is the deliberate exception. It is the + // pod's one sticky axis and it integrates what the controls ask + // for, so controls asking for nothing simply stop moving it - you + // come back to the speed you left, not to a dead stop. + //--------------------------------------------------------------- + Logical input_live = + (!InputNeedsFocus() || ProcessHasFocus()) ? True : False; + //--------------------------------------------------------------- // Find / keep the XInput pad. Probing empty slots is slow, so an // absent pad is only re-probed every 3 seconds. @@ -398,7 +464,7 @@ void memset(&pad, 0, sizeof(pad)); Logical pad_live = False; - if (padIndex >= 0) + if (input_live && padIndex >= 0) { pad_live = (XInputGetState((DWORD) padIndex, &pad) == ERROR_SUCCESS); if (!pad_live) @@ -407,7 +473,7 @@ void padIndex = -1; } } - if (padIndex < 0 && (now - lastPadCheckTick) >= 3000) + if (input_live && padIndex < 0 && (now - lastPadCheckTick) >= 3000) { lastPadCheckTick = now; for (DWORD i = 0; i < 4; ++i) @@ -442,7 +508,7 @@ void for (int i = 0; i < profile.keyButtonCount; ++i) { PadKeyButtonBinding *binding = &profile.keyButtons[i]; - Logical down = KeyDown(binding->virtualKey); + Logical down = input_live && KeyDown(binding->virtualKey); if (binding->toggle && down && !binding->wasDown) { binding->latched = !binding->latched; @@ -495,8 +561,15 @@ void { joyDevice[slot] = -1; } - if (profile.joyAxisCount > 0 || profile.joyButtonCount > 0 || - profile.joyHatCount > 0) + // + // Unfocused this whole block is skipped, which leaves every joyDevice + // slot at -1 - so the button, hat and axis loops below find no device + // and read released and centred on their own. The stick is opened + // DISCL_BACKGROUND (it has to be, or it stops answering the moment a + // pane takes focus), so not polling it is what makes it go quiet. + // + if (input_live && (profile.joyAxisCount > 0 || profile.joyButtonCount > 0 || + profile.joyHatCount > 0)) { RPJoyPoll(); for (int slot = 0; slot < BindJoyDeviceSlots; ++slot) @@ -614,7 +687,7 @@ void for (int i = 0; i < profile.keyAxisCount; ++i) { const PadKeyAxisBinding *binding = &profile.keyAxes[i]; - if (KeyDown(binding->virtualKey)) + if (input_live && KeyDown(binding->virtualKey)) { if (binding->mode == BindKeyRate) { diff --git a/RP_L4/RPL4ENVIRON.cpp b/RP_L4/RPL4ENVIRON.cpp index aedb7ef..54fa930 100644 --- a/RP_L4/RPL4ENVIRON.cpp +++ b/RP_L4/RPL4ENVIRON.cpp @@ -50,6 +50,20 @@ namespace "# Unset falls back to KEYBOARD alone.\n" "L4CONTROLS=PAD;KEYBOARD\n" "\n" +"# Read the keyboard, pad and stick only while the game is the window in\n" +"# front. The pod was the only thing running on its cabinet, so the\n" +"# virtual RIO reads the key state directly rather than waiting on the\n" +"# message pump - which means it reads it whatever is in front, and\n" +"# switching to another window to type flies the pod around while you\n" +"# type in it.\n" +"# 1 controls go neutral when you switch away (default)\n" +"# 0 read them regardless, as earlier builds did\n" +"# Any window of the game counts as the game, so clicking an MFD pane or\n" +"# the plasma glass does not drop your controls. Real RIO cockpit\n" +"# hardware is unaffected either way - this is the keyboard, pad and\n" +"# joystick path only.\n" +"RP412INPUTFOCUS=1\n" +"\n" "# Renderer bring-up argument. Only its presence is checked (the DPL\n" "# resolution parsing it once fed is gone) and the game refuses to start\n" "# without it - any non-empty value works. Leave as shipped.\n"