diff --git a/core/input/input.cpp b/core/input/input.cpp index b909157dec..512f402158 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -159,6 +159,8 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("start_joy_vibration", "device", "weak_magnitude", "strong_magnitude", "duration"), &Input::start_joy_vibration, DEFVAL(0)); ClassDB::bind_method(D_METHOD("stop_joy_vibration", "device"), &Input::stop_joy_vibration); ClassDB::bind_method(D_METHOD("vibrate_handheld", "duration_ms", "amplitude"), &Input::vibrate_handheld, DEFVAL(500), DEFVAL(-1.0)); + ClassDB::bind_method(D_METHOD("set_ignore_joypad_on_unfocused_application", "enable"), &Input::set_ignore_joypad_on_unfocused_application); + ClassDB::bind_method(D_METHOD("is_ignoring_joypad_on_unfocused_application"), &Input::is_ignoring_joypad_on_unfocused_application); ClassDB::bind_method(D_METHOD("get_gravity"), &Input::get_gravity); ClassDB::bind_method(D_METHOD("get_accelerometer"), &Input::get_accelerometer); ClassDB::bind_method(D_METHOD("get_magnetometer"), &Input::get_magnetometer); @@ -207,6 +209,7 @@ void Input::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_accumulated_input"), "set_use_accumulated_input", "is_using_accumulated_input"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emulate_mouse_from_touch"), "set_emulate_mouse_from_touch", "is_emulating_mouse_from_touch"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emulate_touch_from_mouse"), "set_emulate_touch_from_mouse", "is_emulating_touch_from_mouse"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_joypad_on_unfocused_application"), "set_ignore_joypad_on_unfocused_application", "is_ignoring_joypad_on_unfocused_application"); BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE); BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN); @@ -376,6 +379,10 @@ bool Input::is_mouse_button_pressed(MouseButton p_button) const { return mouse_button_mask.has_flag(mouse_button_to_mask(p_button)); } +bool Input::_should_ignore_joypad_events() const { + return ignore_joypad_on_unfocused_application && !application_focused && !embedder_focused; +} + static JoyAxis _combine_device(JoyAxis p_value, int p_device) { return JoyAxis((int)p_value | (p_device << 20)); } @@ -1011,6 +1018,7 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em device_state.pressed[event_index] = is_pressed; device_state.strength[event_index] = p_event->get_action_strength(E.key); device_state.raw_strength[event_index] = p_event->get_action_raw_strength(E.key); + device_state.event_type[event_index] = p_event->get_type(); // Update the action's global state and cache. if (!is_pressed) { @@ -1057,6 +1065,12 @@ void Input::set_joy_features(int p_device, JoypadFeatures *p_features) { } void Input::set_joy_light(int p_device, const Color &p_color) { + _THREAD_SAFE_METHOD_ + + if (_should_ignore_joypad_events()) { + return; + } + Joypad *joypad = joy_names.getptr(p_device); if (!joypad || !joypad->has_light || joypad->features == nullptr) { return; @@ -1072,6 +1086,11 @@ bool Input::has_joy_light(int p_device) const { Vector3 Input::get_joy_accelerometer(int p_device) const { _THREAD_SAFE_METHOD_ + + if (_should_ignore_joypad_events()) { + return Vector3(); + } + const MotionInfo *motion = joy_motion.getptr(p_device); if (motion == nullptr) { return Vector3(); @@ -1090,6 +1109,11 @@ Vector3 Input::get_joy_accelerometer(int p_device) const { Vector3 Input::get_joy_gravity(int p_device) const { _THREAD_SAFE_METHOD_ + + if (_should_ignore_joypad_events()) { + return Vector3(); + } + const MotionInfo *motion = joy_motion.getptr(p_device); if (motion == nullptr) { return Vector3(); @@ -1104,6 +1128,11 @@ Vector3 Input::get_joy_gravity(int p_device) const { Vector3 Input::get_joy_gyroscope(int p_device) const { _THREAD_SAFE_METHOD_ + + if (_should_ignore_joypad_events()) { + return Vector3(); + } + const MotionInfo *motion = joy_motion.getptr(p_device); if (motion == nullptr) { return Vector3(); @@ -1262,6 +1291,11 @@ void Input::set_joy_motion_sensors_rate(int p_device, float p_rate) { void Input::start_joy_vibration(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration) { _THREAD_SAFE_METHOD_ + + if (_should_ignore_joypad_events()) { + return; + } + if (p_weak_magnitude < 0.f || p_weak_magnitude > 1.f || p_strong_magnitude < 0.f || p_strong_magnitude > 1.f) { return; } @@ -1287,6 +1321,17 @@ void Input::vibrate_handheld(int p_duration_ms, float p_amplitude) { OS::get_singleton()->vibrate_handheld(p_duration_ms, p_amplitude); } +void Input::set_ignore_joypad_on_unfocused_application(bool p_ignore) { + ignore_joypad_on_unfocused_application = p_ignore; + if (_should_ignore_joypad_events()) { + release_pressed_events(); + } +} + +bool Input::is_ignoring_joypad_on_unfocused_application() const { + return ignore_joypad_on_unfocused_application; +} + void Input::set_gravity(const Vector3 &p_gravity) { _THREAD_SAFE_METHOD_ @@ -1553,17 +1598,49 @@ bool Input::is_using_accumulated_input() { } void Input::release_pressed_events() { + // Don't release the events if the application (or the window it's embedded in) is still focused. + if (application_focused || embedder_focused) { + return; + } + flush_buffered_events(); // this is needed to release actions strengths keys_pressed.clear(); physical_keys_pressed.clear(); key_label_pressed.clear(); - joy_buttons_pressed.clear(); - _joy_axis.clear(); + if (ignore_joypad_on_unfocused_application) { + joy_buttons_pressed.clear(); + _joy_axis.clear(); - for (KeyValue &E : action_states) { - if (E.value.cache.pressed) { - action_release(E.key); + for (KeyValue &E : action_states) { + if (E.value.cache.pressed) { + action_release(E.key); + } + } + + for (int device : get_connected_joypads()) { + stop_joy_vibration(device); + } + } else { + for (KeyValue &E : action_states) { + if (E.value.cache.pressed) { + bool is_only_joypad_pressed = true; + int max_event = InputMap::get_singleton()->action_get_events(E.key)->size() + 1; // +1 comes from InputEventAction. + + for (const KeyValue &kv : E.value.device_states) { + const ActionState::DeviceState &device_state = kv.value; + for (int i = 0; i < max_event; i++) { + if (device_state.event_type[i] != InputEventType::JOY_MOTION && device_state.event_type[i] != InputEventType::JOY_BUTTON && device_state.strength[i] > 0.0f) { + is_only_joypad_pressed = false; + action_release(E.key); + break; + } + } + if (!is_only_joypad_pressed) { + break; + } + } + } } } } @@ -1574,6 +1651,11 @@ void Input::set_event_dispatch_function(EventDispatchFunc p_function) { void Input::joy_button(int p_device, JoyButton p_button, bool p_pressed) { _THREAD_SAFE_METHOD_; + + if (_should_ignore_joypad_events()) { + return; + } + Joypad &joy = joy_names[p_device]; ERR_FAIL_INDEX((int)p_button, (int)JoyButton::MAX); @@ -1602,6 +1684,10 @@ void Input::joy_button(int p_device, JoyButton p_button, bool p_pressed) { void Input::joy_axis(int p_device, JoyAxis p_axis, float p_value) { _THREAD_SAFE_METHOD_; + if (_should_ignore_joypad_events()) { + return; + } + ERR_FAIL_INDEX((int)p_axis, (int)JoyAxis::MAX); Joypad &joy = joy_names[p_device]; @@ -1671,6 +1757,11 @@ void Input::joy_axis(int p_device, JoyAxis p_axis, float p_value) { void Input::joy_hat(int p_device, BitField p_val) { _THREAD_SAFE_METHOD_; + + if (_should_ignore_joypad_events()) { + return; + } + const Joypad &joy = joy_names[p_device]; JoyEvent map[(size_t)HatDir::MAX]; @@ -1713,6 +1804,11 @@ void Input::joy_hat(int p_device, BitField p_val) { void Input::joy_motion_sensors(int p_device, const Vector3 &p_accelerometer, const Vector3 &p_gyroscope) { _THREAD_SAFE_METHOD_ + + if (_should_ignore_joypad_events()) { + return; + } + // TODO: events MotionInfo *motion = joy_motion.getptr(p_device); if (motion == nullptr) { @@ -2275,6 +2371,7 @@ Input::Input() { gravity_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_gravity", false); gyroscope_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_gyroscope", false); magnetometer_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_magnetometer", false); + ignore_joypad_on_unfocused_application = GLOBAL_DEF_RST_BASIC("input_devices/joypads/ignore_joypad_on_unfocused_application", false); } Input::~Input() { diff --git a/core/input/input.h b/core/input/input.h index 65928216ab..6d732877ef 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -126,6 +126,9 @@ private: int64_t mouse_window = 0; bool legacy_just_pressed_behavior = false; bool disable_input = false; + bool ignore_joypad_on_unfocused_application = false; + bool application_focused = true; + bool embedder_focused = false; struct ActionState { uint64_t pressed_physics_frame = UINT64_MAX; @@ -140,6 +143,7 @@ private: bool pressed[MAX_EVENT] = { false }; float strength[MAX_EVENT] = { 0.0 }; float raw_strength[MAX_EVENT] = { 0.0 }; + InputEventType event_type[MAX_EVENT] = { InputEventType::INVALID }; }; bool api_pressed = false; float api_strength = 0.0; @@ -307,6 +311,8 @@ private: #endif friend class DisplayServer; + friend class SceneTree; + friend class SceneDebugger; static void (*set_mouse_mode_func)(MouseMode); static MouseMode (*get_mouse_mode_func)(); @@ -321,6 +327,8 @@ private: EventDispatchFunc event_dispatch_function = nullptr; + bool _should_ignore_joypad_events() const; + #ifndef DISABLE_DEPRECATED void _vibrate_handheld_bind_compat_91143(int p_duration_ms = 500); static void _bind_compatibility_methods(); @@ -424,6 +432,9 @@ public: void stop_joy_vibration(int p_device); void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0); + void set_ignore_joypad_on_unfocused_application(bool p_ignore); + bool is_ignoring_joypad_on_unfocused_application() const; + void set_mouse_position(const Point2 &p_posf); void action_press(const StringName &p_action, float p_strength = 1.f); diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index f99d275556..ee7d3aa640 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -751,6 +751,9 @@ If [code]true[/code], sends touch input events when clicking or dragging the mouse. See also [member ProjectSettings.input_devices/pointing/emulate_touch_from_mouse]. + + If [code]true[/code], joypad input (including motion sensors) and LED light changes will be ignored and joypad vibration will be stopped when the application is not focused. + Controls the mouse mode. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 93b7b14d9b..b5ce49092b 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1662,6 +1662,9 @@ 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], joypad input (including motion sensors) and LED light changes will be ignored and joypad vibration will be stopped when the application is not focused. + 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/editor/run/game_view_plugin.cpp b/editor/run/game_view_plugin.cpp index 4f1f773d98..f15d5bf703 100644 --- a/editor/run/game_view_plugin.cpp +++ b/editor/run/game_view_plugin.cpp @@ -248,6 +248,17 @@ void GameViewDebugger::set_camera_manipulate_mode(EditorDebuggerNode::CameraOver } } +void GameViewDebugger::report_window_focused(bool p_focused) { + Array message; + message.append(p_focused); + + for (Ref &I : sessions) { + if (I->is_active()) { + I->send_message("scene:report_window_focused", message); + } + } +} + void GameViewDebugger::reset_camera_2d_position() { for (Ref &I : sessions) { if (I->is_active()) { @@ -987,6 +998,14 @@ void GameView::_notification(int p_what) { _update_ui(); } break; + + case NOTIFICATION_WM_WINDOW_FOCUS_IN: + case NOTIFICATION_WM_WINDOW_FOCUS_OUT: { + if (embed_on_play) { + debugger->report_window_focused(p_what == NOTIFICATION_WM_WINDOW_FOCUS_IN); + } + } break; + case NOTIFICATION_WM_POSITION_CHANGED: { if (window_wrapper->get_window_enabled()) { _update_floating_window_settings(); diff --git a/editor/run/game_view_plugin.h b/editor/run/game_view_plugin.h index 8857cda337..a42a4bf91a 100644 --- a/editor/run/game_view_plugin.h +++ b/editor/run/game_view_plugin.h @@ -101,6 +101,8 @@ public: void set_camera_override(bool p_enabled); void set_camera_manipulate_mode(EditorDebuggerNode::CameraOverride p_mode); + void report_window_focused(bool p_focused); + void reset_camera_2d_position(); void reset_camera_3d_position(); diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index 32f9cbae67..33d7c0f32c 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -33,6 +33,7 @@ #include "core/config/engine.h" #include "core/debugger/debugger_marshalls.h" #include "core/debugger/engine_debugger.h" +#include "core/input/input.h" #include "core/input/shortcut.h" #include "core/io/dir_access.h" #include "core/io/resource_loader.h" @@ -506,6 +507,17 @@ Error SceneDebugger::_msg_rq_screenshot(const Array &p_args) { return OK; } +Error SceneDebugger::_msg_report_window_focused(const Array &p_args) { + ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA); + + bool focused = p_args[0]; + Input::get_singleton()->embedder_focused = focused; + if (Input::get_singleton()->_should_ignore_joypad_events()) { + Input::get_singleton()->release_pressed_events(); + } + return OK; +} + // endregion HashMap SceneDebugger::message_handlers; @@ -582,6 +594,7 @@ void SceneDebugger::_init_message_handlers() { message_handlers["runtime_node_select_reset_camera_3d"] = _msg_runtime_node_select_reset_camera_3d; #endif message_handlers["rq_screenshot"] = _msg_rq_screenshot; + message_handlers["report_window_focused"] = _msg_report_window_focused; } void SceneDebugger::_save_node(ObjectID id, const String &p_path) { diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h index 1960fc73ab..9acc093ab2 100644 --- a/scene/debugger/scene_debugger.h +++ b/scene/debugger/scene_debugger.h @@ -106,6 +106,7 @@ private: static Error _msg_runtime_node_select_set_avoid_locked(const Array &p_args); static Error _msg_runtime_node_select_set_prefer_group(const Array &p_args); static Error _msg_rq_screenshot(const Array &p_args); + static Error _msg_report_window_focused(const Array &p_args); static Error _msg_runtime_node_select_reset_camera_2d(const Array &p_args); static Error _msg_transform_camera_2d(const Array &p_args); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index ec25b46ad9..44ec0f7770 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -921,9 +921,21 @@ void SceneTree::_notification(int p_notification) { case NOTIFICATION_WM_ABOUT: case NOTIFICATION_CRASH: case NOTIFICATION_APPLICATION_RESUMED: - case NOTIFICATION_APPLICATION_PAUSED: + case NOTIFICATION_APPLICATION_PAUSED: { + // Pass these to nodes, since they are mirrored. + get_root()->propagate_notification(p_notification); + } break; + case NOTIFICATION_APPLICATION_FOCUS_IN: case NOTIFICATION_APPLICATION_FOCUS_OUT: { + if (Input::get_singleton()) { + Input::get_singleton()->application_focused = p_notification == NOTIFICATION_APPLICATION_FOCUS_IN; + + if (Input::get_singleton()->_should_ignore_joypad_events()) { + Input::get_singleton()->release_pressed_events(); + } + } + // Pass these to nodes, since they are mirrored. get_root()->propagate_notification(p_notification); } break;