diff --git a/core/input/input.cpp b/core/input/input.cpp index 5314e9f02d1c..ad1d0c206c23 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -149,6 +149,7 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("get_vector", "negative_x", "positive_x", "negative_y", "positive_y", "deadzone"), &Input::get_vector, DEFVAL(-1.0f)); ClassDB::bind_method(D_METHOD("add_joy_mapping", "mapping", "update_existing"), &Input::add_joy_mapping, DEFVAL(false)); ClassDB::bind_method(D_METHOD("remove_joy_mapping", "guid"), &Input::remove_joy_mapping); + ClassDB::bind_method(D_METHOD("is_joy_auto_mapped", "device"), &Input::is_joy_auto_mapped); ClassDB::bind_method(D_METHOD("is_joy_known", "device"), &Input::is_joy_known); ClassDB::bind_method(D_METHOD("get_joy_axis", "device", "axis"), &Input::get_joy_axis); ClassDB::bind_method(D_METHOD("get_joy_name", "device"), &Input::get_joy_name); @@ -1074,6 +1075,55 @@ bool Input::is_emulating_touch_from_mouse() const { return emulate_touch_from_mouse; } +void Input::set_unknown_gamepad_auto_mapped(bool p_auto) { + unknown_gamepad_auto_mapped = p_auto; +} + +bool Input::is_unknown_gamepad_auto_mapped() { + return unknown_gamepad_auto_mapped; +} + +void Input::unknown_gamepad_auto_map(const StringName &p_guid, const String &p_name, const int *p_key_map, const int *p_axis_map, bool p_trigger_is_key) { + JoyDeviceMapping mapping; + mapping.auto_generated = true; + mapping.uid = p_guid; + mapping.name = p_name; + + for (int i = 0; i < int(JoyButton::SDL_MAX); i++) { + JoyBinding binding; + + binding.outputType = TYPE_BUTTON; + binding.output.button = JoyButton(i); + + binding.inputType = TYPE_BUTTON; + binding.input.button = JoyButton(p_key_map[i]); + + mapping.bindings.push_back(binding); + } + + for (int i = 0; i < int(JoyAxis::SDL_MAX); i++) { + JoyBinding binding; + + binding.outputType = TYPE_AXIS; + binding.output.axis.axis = JoyAxis(i); + binding.output.axis.range = JoyAxisRange::FULL_AXIS; + + if (p_trigger_is_key && i >= int(JoyAxis::SDL_MAX) - 2) { + binding.inputType = TYPE_BUTTON; + binding.input.button = JoyButton(p_axis_map[i]); + } else { + binding.inputType = TYPE_AXIS; + binding.input.axis.axis = JoyAxis(p_axis_map[i]); + binding.input.axis.range = JoyAxisRange::FULL_AXIS; + binding.input.axis.invert = false; + } + + mapping.bindings.push_back(binding); + } + + map_db.push_back(mapping); +} + // Calling this whenever the game window is focused helps unsticking the "touch mouse" // if the OS or its abstraction class hasn't properly reported that touch pointers raised void Input::ensure_touch_mouse_raised() { @@ -1735,11 +1785,33 @@ void Input::set_fallback_mapping(const String &p_guid) { } } +bool Input::is_mapping_known(const StringName &p_guid) { + for (const JoyDeviceMapping &map : map_db) { + if (map.uid == p_guid) { + return true; + } + } + return false; +} + +bool Input::is_joy_auto_mapped(int p_device) { + if (!joy_names.has(p_device)) { + return false; + } + + int mapping = joy_names[p_device].mapping; + if (mapping == -1) { + return false; + } + + return map_db[mapping].auto_generated; +} + //platforms that use the remapping system can override and call to these ones bool Input::is_joy_known(int p_device) { if (joy_names.has(p_device)) { int mapping = joy_names[p_device].mapping; - if (mapping != -1 && mapping != fallback_mapping) { + if (mapping != -1 && mapping != fallback_mapping && !map_db[mapping].auto_generated) { return true; } } diff --git a/core/input/input.h b/core/input/input.h index 005ddcca4fdb..3d49a7768e25 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -135,6 +135,7 @@ class Input : public Object { HashMap action_states; + bool unknown_gamepad_auto_mapped = true; bool emulate_touch_from_mouse = false; bool emulate_mouse_from_touch = false; bool agile_input_event_flushing = false; @@ -238,6 +239,7 @@ class Input : public Object { }; struct JoyDeviceMapping { + bool auto_generated = false; String uid; String name; Vector bindings; @@ -349,6 +351,9 @@ class Input : public Object { void set_emulate_touch_from_mouse(bool p_emulate); bool is_emulating_touch_from_mouse() const; + void set_unknown_gamepad_auto_mapped(bool p_auto); + bool is_unknown_gamepad_auto_mapped(); + void unknown_gamepad_auto_map(const StringName &p_guid, const String &p_name, const int *p_key_map, const int *p_axis_map, bool p_trigger_is_key); void ensure_touch_mouse_raised(); void set_emulate_mouse_from_touch(bool p_emulate); @@ -369,6 +374,8 @@ class Input : public Object { int get_unused_joy_id(); + bool is_mapping_known(const StringName &p_guid); + bool is_joy_auto_mapped(int p_device); bool is_joy_known(int p_device); String get_joy_guid(int p_device) const; bool should_ignore_device(int p_vendor_id, int p_product_id) const; diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index 6fe5b7a80229..adb5c80fc720 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -235,6 +235,13 @@ Returns [code]true[/code] if any action, key, joypad button, or mouse button is being pressed. This will also return [code]true[/code] if any action is simulated via code by calling [method action_press]. + + + + + Returns [code]true[/code] If the specified device is currently auto mapped. See also [member ProjectSettings.input_devices/gamepad/unknown_gamepad_auto_mapped]. + + @@ -248,6 +255,7 @@ Returns [code]true[/code] if the system knows the specified device. This means that it sets all button and axis indices. Unknown joypads are not expected to match these constants, but you can still retrieve events from them. + [b]Note:[/b] This method returns [code]false[/code] even if the specified device is auto mapped (see [method is_joy_auto_mapped]). diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 388cd8175382..fbef18333755 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1422,6 +1422,11 @@ If [code]false[/code], no input will be lost. [b]Note:[/b] You should in nearly all cases prefer the [code]false[/code] setting. The legacy behavior is to enable supporting old projects that rely on the old logic, without changes to script. + + If [code]true[/code], allows unknown gamepads to be auto mapped, according to keycode semantics and convention. When the gamepad's mapping cannot be found in the built-in mapping database, a mapping will be auto generated as a fallback. + [b]Note:[/b] The gamepad is not guaranteed to work properly with the generated mapping. + [b]Note:[/b] This setting is only effective on Linux. + Specifies the tablet driver to use. If left empty, the default driver will be used. [b]Note:[/b] The driver in use can be overridden at runtime via the [code]--tablet-driver[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url]. diff --git a/main/main.cpp b/main/main.cpp index b3b6d960e7a1..c2685d6aed24 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -3226,6 +3226,9 @@ Error Main::setup2(bool p_show_boot_logo) { bool agile_input_event_flushing = GLOBAL_DEF("input_devices/buffering/agile_event_flushing", false); id->set_agile_input_event_flushing(agile_input_event_flushing); + bool unknown_gamepad_auto_mapped = GLOBAL_DEF("input_devices/gamepad/unknown_gamepad_auto_mapped", true); + id->set_unknown_gamepad_auto_mapped(unknown_gamepad_auto_mapped); + if (bool(GLOBAL_DEF_BASIC("input_devices/pointing/emulate_touch_from_mouse", false)) && !(editor || project_manager)) { if (!DisplayServer::get_singleton()->is_touchscreen_available()) { diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index fce6a5b2be11..505a0534244a 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -75,6 +75,9 @@ JoypadLinux::Joypad::~Joypad() { void JoypadLinux::Joypad::reset() { dpad = 0; fd = -1; + for (int i = 0; i < MAX_KEY; i++) { + key_map[i] = -1; + } for (int i = 0; i < MAX_ABS; i++) { abs_map[i] = -1; curr_axis[i] = 0; @@ -314,6 +317,7 @@ void JoypadLinux::setup_joypad_properties(Joypad &p_joypad) { p_joypad.key_map[i] = num_buttons++; } } + for (int i = 0; i < ABS_MISC; ++i) { /* Skip hats */ if (i == ABS_HAT0X) { @@ -340,6 +344,117 @@ void JoypadLinux::setup_joypad_properties(Joypad &p_joypad) { } } +void JoypadLinux::_auto_remap(Joypad &p_joypad, const StringName &p_guid, const String &p_name, bool p_hat0x_exist, bool p_hat0y_exist) { + if (p_joypad.key_map[BTN_GAMEPAD] == -1) { + return; + } + + // Generate key mapping for JoyButton. + + int joy_button_mappings[int(JoyButton::SDL_MAX)]; + for (int i = 0; i < int(JoyButton::SDL_MAX); i++) { + joy_button_mappings[i] = -1; + } + +#define BUTTON_MAP_KEY(button, keycode) (joy_button_mappings[int(button)] = p_joypad.key_map[keycode]) +#define KEY_EXIST(keycode) (p_joypad.key_map[keycode] != -1) +#define UNUSED_KEY_HIDE(keycode) (p_joypad.key_map[keycode] = -p_joypad.key_map[keycode] - 2) // Used for two events occur at one key press. + + BUTTON_MAP_KEY(JoyButton::A, BTN_A); + BUTTON_MAP_KEY(JoyButton::B, BTN_B); + BUTTON_MAP_KEY(JoyButton::X, BTN_X); + BUTTON_MAP_KEY(JoyButton::Y, BTN_Y); + + if (KEY_EXIST(KEY_BACK)) { + BUTTON_MAP_KEY(JoyButton::BACK, KEY_BACK); + } else { + BUTTON_MAP_KEY(JoyButton::BACK, BTN_SELECT); + } + + if (KEY_EXIST(KEY_HOMEPAGE)) { + BUTTON_MAP_KEY(JoyButton::GUIDE, KEY_HOMEPAGE); + } else { + BUTTON_MAP_KEY(JoyButton::GUIDE, BTN_MODE); + } + + BUTTON_MAP_KEY(JoyButton::START, BTN_START); + BUTTON_MAP_KEY(JoyButton::LEFT_STICK, BTN_THUMBL); + BUTTON_MAP_KEY(JoyButton::RIGHT_STICK, BTN_THUMBR); + BUTTON_MAP_KEY(JoyButton::LEFT_SHOULDER, BTN_TL); + BUTTON_MAP_KEY(JoyButton::RIGHT_SHOULDER, BTN_TR); + + if (!p_hat0y_exist) { + BUTTON_MAP_KEY(JoyButton::DPAD_UP, BTN_DPAD_UP); + BUTTON_MAP_KEY(JoyButton::DPAD_DOWN, BTN_DPAD_DOWN); + } else { + UNUSED_KEY_HIDE(BTN_DPAD_UP); + UNUSED_KEY_HIDE(BTN_DPAD_DOWN); + } + if (!p_hat0x_exist) { + BUTTON_MAP_KEY(JoyButton::DPAD_LEFT, BTN_DPAD_LEFT); + BUTTON_MAP_KEY(JoyButton::DPAD_RIGHT, BTN_DPAD_RIGHT); + } else { + UNUSED_KEY_HIDE(BTN_DPAD_LEFT); + UNUSED_KEY_HIDE(BTN_DPAD_RIGHT); + } + + if (KEY_EXIST(KEY_RECORD)) { + BUTTON_MAP_KEY(JoyButton::MISC1, KEY_RECORD); + } else { + BUTTON_MAP_KEY(JoyButton::MISC1, BTN_Z); + } + + // Generate key mapping for JoyAxis. + + int joy_axis_mappings[int(JoyAxis::SDL_MAX)]; + for (int i = 0; i < int(JoyAxis::SDL_MAX); i++) { + joy_axis_mappings[i] = -1; + } + +#define AXIS_MAP_ABS(axis, abscode) (joy_axis_mappings[int(axis)] = p_joypad.abs_map[abscode]) +#define AXIS_MAP_KEY(axis, keycode) (joy_axis_mappings[int(axis)] = p_joypad.key_map[keycode]) +#define ABS_EXIST(abscode) (p_joypad.abs_map[abscode] != -1) + + AXIS_MAP_ABS(JoyAxis::LEFT_X, ABS_X); + AXIS_MAP_ABS(JoyAxis::LEFT_Y, ABS_Y); + + bool trigger_is_key = true; + + if (ABS_EXIST(ABS_RX)) { + AXIS_MAP_ABS(JoyAxis::RIGHT_X, ABS_RX); + AXIS_MAP_ABS(JoyAxis::RIGHT_Y, ABS_RY); + + if (ABS_EXIST(ABS_BRAKE)) { + AXIS_MAP_ABS(JoyAxis::TRIGGER_LEFT, ABS_BRAKE); + AXIS_MAP_ABS(JoyAxis::TRIGGER_RIGHT, ABS_GAS); + trigger_is_key = false; + } else if (ABS_EXIST(ABS_Z)) { + AXIS_MAP_ABS(JoyAxis::TRIGGER_LEFT, ABS_Z); + AXIS_MAP_ABS(JoyAxis::TRIGGER_RIGHT, ABS_RZ); + trigger_is_key = false; + } + } else { // ABS_RX does not exist. Try another solution. + AXIS_MAP_ABS(JoyAxis::RIGHT_X, ABS_Z); + AXIS_MAP_ABS(JoyAxis::RIGHT_Y, ABS_RZ); + + if (ABS_EXIST(ABS_BRAKE)) { + AXIS_MAP_ABS(JoyAxis::TRIGGER_LEFT, ABS_BRAKE); + AXIS_MAP_ABS(JoyAxis::TRIGGER_RIGHT, ABS_GAS); + trigger_is_key = false; + } + } + + if (trigger_is_key) { + AXIS_MAP_KEY(JoyAxis::TRIGGER_LEFT, BTN_TL2); + AXIS_MAP_KEY(JoyAxis::TRIGGER_RIGHT, BTN_TR2); + } else { + UNUSED_KEY_HIDE(BTN_TL2); + UNUSED_KEY_HIDE(BTN_TR2); + } + + input->unknown_gamepad_auto_map(p_guid, p_name, joy_button_mappings, joy_axis_mappings, trigger_is_key); +} + void JoypadLinux::open_joypad(const char *p_path) { int joy_num = input->get_unused_joy_id(); int fd = open(p_path, O_RDWR | O_NONBLOCK); @@ -419,6 +534,12 @@ void JoypadLinux::open_joypad(const char *p_path) { } } + if (input->is_unknown_gamepad_auto_mapped() && !input->is_mapping_known(uid)) { + bool hat0x_exist = test_bit(ABS_HAT0X, absbit); + bool hat0y_exist = test_bit(ABS_HAT0Y, absbit); + _auto_remap(joypad, uid, name, hat0x_exist, hat0y_exist); + } + input->joy_connection_changed(joy_num, true, name, uid, joypad_info); } else { String uidname = uid; @@ -427,6 +548,13 @@ void JoypadLinux::open_joypad(const char *p_path) { uidname = uidname + _hex_str(name[i]); } uidname += "00"; + + if (input->is_unknown_gamepad_auto_mapped() && !input->is_mapping_known(uid)) { + bool hat0x_exist = test_bit(ABS_HAT0X, absbit); + bool hat0y_exist = test_bit(ABS_HAT0Y, absbit); + _auto_remap(joypad, uid, name, hat0x_exist, hat0y_exist); + } + input->joy_connection_changed(joy_num, true, name, uidname); } } @@ -535,9 +663,13 @@ void JoypadLinux::process_joypads() { } switch (joypad_event.type) { - case EV_KEY: - input->joy_button(i, (JoyButton)joypad.key_map[joypad_event.code], joypad_event.value); - break; + case EV_KEY: { + int button_idx = joypad.key_map[joypad_event.code]; + if (input->is_unknown_gamepad_auto_mapped() && button_idx < -1) { + break; // Some buttons may need to be hidden. + } + input->joy_button(i, (JoyButton)button_idx, joypad_event.value); + } break; case EV_ABS: switch (joypad_event.code) { diff --git a/platform/linuxbsd/joypad_linux.h b/platform/linuxbsd/joypad_linux.h index bf24d8e5a52e..1a8688e197d2 100644 --- a/platform/linuxbsd/joypad_linux.h +++ b/platform/linuxbsd/joypad_linux.h @@ -112,6 +112,8 @@ class JoypadLinux { static void monitor_joypads_thread_func(void *p_user); void monitor_joypads_thread_run(); + void _auto_remap(Joypad &p_joypad, const StringName &p_guid, const String &p_name, bool p_hat0x_exist, bool p_hat0y_exist); + void open_joypad(const char *p_path); void setup_joypad_properties(Joypad &p_joypad);