api: check for window focus when accepting input (#317)

## Link to GitHub Issue, if one exists
Fixes #139 

## Description of change
Add an option for detecting window focus when accepting input.

The new default will be to have the following:
 * naive bindings will check for window focus
* rawinput binds are not affected, will continue to be accepted
regardless of focus

Other options (always / never choices) can be picked by the user to
tweak the behavior if needed.

Clean up usage of `0xff` constant to represent invalid naive vkey.

## Testing
Still in progress, especially in regards to performance.

On a i7-9700 system, 10000 calls to `superexit::has_focus()` takes
250-350 microseconds, so this should not be a big deal at all.
This commit is contained in:
bicarus-dev
2025-05-07 13:22:29 -07:00
committed by GitHub
parent 6e7bf99af8
commit 26ea8a1b92
8 changed files with 75 additions and 9 deletions
+34 -4
View File
@@ -1,5 +1,8 @@
#include "api.h"
#include <optional>
#include "launcher/superexit.h"
#include "rawinput/rawinput.h"
#include "rawinput/piuio.h"
#include "util/time.h"
@@ -64,18 +67,29 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
auto current_button = &_button;
auto alternatives = check_alts ? &current_button->getAlternatives() : nullptr;
unsigned int button_count = 0;
std::optional<bool> window_has_focus;
while (true) {
// naive behavior
if (current_button->isNaive()) {
GameAPI::Buttons::State state;
auto vkey = current_button->getVKey();
// check for focus
if (vkey != INVALID_VKEY && rawinput::NAIVE_REQUIRE_FOCUS) {
if (!window_has_focus.has_value()) {
window_has_focus = superexit::has_focus();
}
if (!window_has_focus.value()) {
vkey = INVALID_VKEY;
}
}
// read
auto vkey = current_button->getVKey();
GameAPI::Buttons::State state;
if (vkey == 0xFF) {
if (vkey == INVALID_VKEY) {
state = BUTTON_NOT_PRESSED;
} else {
state = (GetAsyncKeyState(current_button->getVKey()) & 0x8000) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
state = (GetAsyncKeyState(vkey) & 0x8000) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
}
// invert
@@ -106,6 +120,16 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
auto &devid = current_button->getDeviceIdentifier();
auto device = manager->devices_get(devid, false); // TODO: fix to update only
// check for focus
if (device && rawinput::RAWINPUT_REQUIRE_FOCUS) {
if (!window_has_focus.has_value()) {
window_has_focus = superexit::has_focus();
}
if (!window_has_focus.value()) {
device = nullptr;
}
}
// get state if device was marked as updated
GameAPI::Buttons::State state = current_button->getLastState();
double *last_up = nullptr;
@@ -571,6 +595,12 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
switch (device->type) {
case rawinput::MOUSE: {
// check for focus
if (rawinput::NAIVE_REQUIRE_FOCUS && !superexit::has_focus()) {
value = analog.getLastState();
break;
}
// get mouse position
auto mouse = device->mouseInfo;
long pos;
+1 -1
View File
@@ -284,7 +284,7 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
std::string vKeyString = fmt::format("{:#x}", vKey);
// device must be existing
if (this->device_identifier.empty() && vKey == 0xFF) {
if (this->device_identifier.empty() && vKey == INVALID_VKEY) {
return "";
}
+6 -4
View File
@@ -33,12 +33,14 @@ enum ButtonAnalogType {
extern const char *ButtonAnalogTypeStr[];
constexpr unsigned short INVALID_VKEY = UINT16_C(0xFF);
class Button {
private:
std::vector<Button> alternatives;
std::string name;
std::string device_identifier = "";
unsigned short vKey = 0xFF;
unsigned short vKey = INVALID_VKEY;
ButtonAnalogType analog_type = BAT_NONE;
double debounce_up = 0.0;
double debounce_down = 0.0;
@@ -68,12 +70,12 @@ public:
if (this->override_enabled) {
return true;
}
if (this->vKey != 0xFF) {
if (this->vKey != INVALID_VKEY) {
return true;
}
for (auto &alternative : this->alternatives) {
if (alternative.vKey != 0xFF) {
if (alternative.vKey != INVALID_VKEY) {
return true;
}
}
@@ -82,7 +84,7 @@ public:
}
inline void clearBindings() {
vKey = 0xFF;
vKey = INVALID_VKEY;
alternatives.clear();
device_identifier = "";
analog_type = BAT_NONE;
+11
View File
@@ -1077,6 +1077,17 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::spice2x_EnableSMXDedicab].value_bool()) {
rawinput::ENABLE_SMX_DEDICAB = true;
}
if (options[launcher::Options::InputRequiresFocus].is_active()) {
const auto &text = options[launcher::Options::InputRequiresFocus].value_text();
if (text == "always") {
rawinput::NAIVE_REQUIRE_FOCUS = true;
rawinput::RAWINPUT_REQUIRE_FOCUS = true;
} else if (text == "never") {
rawinput::NAIVE_REQUIRE_FOCUS = false;
rawinput::RAWINPUT_REQUIRE_FOCUS = false;
}
}
if (options[launcher::Options::MidiAlgoVer].is_active()) {
if (options[launcher::Options::MidiAlgoVer].value_text() == "legacy") {
midi_algo = rawinput::MidiNoteAlgorithm::LEGACY;
+17
View File
@@ -2170,6 +2170,23 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.setting_name = "20",
.category = "I/O Options",
},
{
// InputRequiresFocus
.title = "Input Requires Focus",
.name = "inputfocus",
.desc =
"Determine input behavior when game window is not in focus.\n\n"
"never: allow input regardless of focus\n"
"naive (default): only naive bindings check for window focus, rawinput binds are always allowed\n"
"always: only process input when window is in focus",
.type = OptionType::Enum,
.category = "I/O Options",
.elements = {
{"never", ""},
{"naive", ""},
{"always", ""},
},
},
{
// NostalgiaPoke
.title = "Nost Screen Poke",
+1
View File
@@ -233,6 +233,7 @@ namespace launcher {
IIDXRecQuality,
MidiAlgoVer,
MidiNoteSustain,
InputRequiresFocus,
NostalgiaPoke,
ForceBackBufferCount,
SDVXWindowedSubscreenSize,
+2
View File
@@ -28,6 +28,8 @@ namespace rawinput {
int TOUCHSCREEN_RANGE_X = 0;
int TOUCHSCREEN_RANGE_Y = 0;
bool DUMP_HID_DEVICES_TO_LOG = false;
bool NAIVE_REQUIRE_FOCUS = true;
bool RAWINPUT_REQUIRE_FOCUS = false;
// set this to something slightly longer than 16.67ms (60Hz) so that I/O can pick it up
// this may need to be adjusted for each game in the future if there is a game that polls less
+3
View File
@@ -28,6 +28,9 @@ namespace rawinput {
extern uint32_t MIDI_NOTE_SUSTAIN;
extern bool NAIVE_REQUIRE_FOCUS;
extern bool RAWINPUT_REQUIRE_FOCUS;
struct DeviceCallback {
void *data;
std::function<void(void*, Device*)> f;