diff options
Diffstat (limited to 'platform')
48 files changed, 900 insertions, 852 deletions
diff --git a/platform/android/SCsub b/platform/android/SCsub index 56fbd2f7e4..ecc72019e5 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -4,6 +4,7 @@ Import("env") android_files = [ "os_android.cpp", + "android_input_handler.cpp", "file_access_android.cpp", "audio_driver_opensl.cpp", "dir_access_jandroid.cpp", diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp new file mode 100644 index 0000000000..e03375e8d9 --- /dev/null +++ b/platform/android/android_input_handler.cpp @@ -0,0 +1,395 @@ +/*************************************************************************/ +/* android_input_handler.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "android_input_handler.h" + +#include "android_keys_utils.h" +#include "display_server_android.h" + +void AndroidInputHandler::process_joy_event(AndroidInputHandler::JoypadEvent p_event) { + switch (p_event.type) { + case JOY_EVENT_BUTTON: + Input::get_singleton()->joy_button(p_event.device, (JoyButton)p_event.index, p_event.pressed); + break; + case JOY_EVENT_AXIS: + Input::JoyAxisValue value; + value.min = -1; + value.value = p_event.value; + Input::get_singleton()->joy_axis(p_event.device, (JoyAxis)p_event.index, value); + break; + case JOY_EVENT_HAT: + Input::get_singleton()->joy_hat(p_event.device, (HatMask)p_event.hat); + break; + default: + return; + } +} + +void AndroidInputHandler::_set_key_modifier_state(Ref<InputEventWithModifiers> ev) { + ev->set_shift_pressed(shift_mem); + ev->set_alt_pressed(alt_mem); + ev->set_meta_pressed(meta_mem); + ev->set_ctrl_pressed(control_mem); +} + +void AndroidInputHandler::process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed) { + static char32_t prev_wc = 0; + char32_t unicode = p_unicode_char; + if ((p_unicode_char & 0xfffffc00) == 0xd800) { + if (prev_wc != 0) { + ERR_PRINT("invalid utf16 surrogate input"); + } + prev_wc = unicode; + return; // Skip surrogate. + } else if ((unicode & 0xfffffc00) == 0xdc00) { + if (prev_wc == 0) { + ERR_PRINT("invalid utf16 surrogate input"); + return; // Skip invalid surrogate. + } + unicode = (prev_wc << 10UL) + unicode - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev_wc = 0; + } else { + prev_wc = 0; + } + + Ref<InputEventKey> ev; + ev.instantiate(); + int val = unicode; + int keycode = android_get_keysym(p_keycode); + int phy_keycode = android_get_keysym(p_scancode); + + if (keycode == KEY_SHIFT) { + shift_mem = p_pressed; + } + if (keycode == KEY_ALT) { + alt_mem = p_pressed; + } + if (keycode == KEY_CTRL) { + control_mem = p_pressed; + } + if (keycode == KEY_META) { + meta_mem = p_pressed; + } + + ev->set_keycode((Key)keycode); + ev->set_physical_keycode((Key)phy_keycode); + ev->set_unicode(val); + ev->set_pressed(p_pressed); + + _set_key_modifier_state(ev); + + if (val == '\n') { + ev->set_keycode(KEY_ENTER); + } else if (val == 61448) { + ev->set_keycode(KEY_BACKSPACE); + ev->set_unicode(KEY_BACKSPACE); + } else if (val == 61453) { + ev->set_keycode(KEY_ENTER); + ev->set_unicode(KEY_ENTER); + } else if (p_keycode == 4) { + if (DisplayServerAndroid *dsa = Object::cast_to<DisplayServerAndroid>(DisplayServer::get_singleton())) { + dsa->send_window_event(DisplayServer::WINDOW_EVENT_GO_BACK_REQUEST, true); + } + } + + Input::get_singleton()->parse_input_event(ev); +} + +void AndroidInputHandler::process_touch(int p_event, int p_pointer, const Vector<AndroidInputHandler::TouchPos> &p_points) { + switch (p_event) { + case AMOTION_EVENT_ACTION_DOWN: { //gesture begin + if (touch.size()) { + //end all if exist + for (int i = 0; i < touch.size(); i++) { + Ref<InputEventScreenTouch> ev; + ev.instantiate(); + ev->set_index(touch[i].id); + ev->set_pressed(false); + ev->set_position(touch[i].pos); + Input::get_singleton()->parse_input_event(ev); + } + } + + touch.resize(p_points.size()); + for (int i = 0; i < p_points.size(); i++) { + touch.write[i].id = p_points[i].id; + touch.write[i].pos = p_points[i].pos; + } + + //send touch + for (int i = 0; i < touch.size(); i++) { + Ref<InputEventScreenTouch> ev; + ev.instantiate(); + ev->set_index(touch[i].id); + ev->set_pressed(true); + ev->set_position(touch[i].pos); + Input::get_singleton()->parse_input_event(ev); + } + + } break; + case AMOTION_EVENT_ACTION_MOVE: { //motion + ERR_FAIL_COND(touch.size() != p_points.size()); + + for (int i = 0; i < touch.size(); i++) { + int idx = -1; + for (int j = 0; j < p_points.size(); j++) { + if (touch[i].id == p_points[j].id) { + idx = j; + break; + } + } + + ERR_CONTINUE(idx == -1); + + if (touch[i].pos == p_points[idx].pos) + continue; //no move unncesearily + + Ref<InputEventScreenDrag> ev; + ev.instantiate(); + ev->set_index(touch[i].id); + ev->set_position(p_points[idx].pos); + ev->set_relative(p_points[idx].pos - touch[i].pos); + Input::get_singleton()->parse_input_event(ev); + touch.write[i].pos = p_points[idx].pos; + } + + } break; + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_UP: { //release + if (touch.size()) { + //end all if exist + for (int i = 0; i < touch.size(); i++) { + Ref<InputEventScreenTouch> ev; + ev.instantiate(); + ev->set_index(touch[i].id); + ev->set_pressed(false); + ev->set_position(touch[i].pos); + Input::get_singleton()->parse_input_event(ev); + } + touch.clear(); + } + } break; + case AMOTION_EVENT_ACTION_POINTER_DOWN: { // add touch + for (int i = 0; i < p_points.size(); i++) { + if (p_points[i].id == p_pointer) { + TouchPos tp = p_points[i]; + touch.push_back(tp); + + Ref<InputEventScreenTouch> ev; + ev.instantiate(); + + ev->set_index(tp.id); + ev->set_pressed(true); + ev->set_position(tp.pos); + Input::get_singleton()->parse_input_event(ev); + + break; + } + } + } break; + case AMOTION_EVENT_ACTION_POINTER_UP: { // remove touch + for (int i = 0; i < touch.size(); i++) { + if (touch[i].id == p_pointer) { + Ref<InputEventScreenTouch> ev; + ev.instantiate(); + ev->set_index(touch[i].id); + ev->set_pressed(false); + ev->set_position(touch[i].pos); + Input::get_singleton()->parse_input_event(ev); + touch.remove(i); + + break; + } + } + } break; + } +} + +void AndroidInputHandler::process_hover(int p_type, Point2 p_pos) { + // https://developer.android.com/reference/android/view/MotionEvent.html#ACTION_HOVER_ENTER + switch (p_type) { + case AMOTION_EVENT_ACTION_HOVER_MOVE: // hover move + case AMOTION_EVENT_ACTION_HOVER_ENTER: // hover enter + case AMOTION_EVENT_ACTION_HOVER_EXIT: { // hover exit + Ref<InputEventMouseMotion> ev; + ev.instantiate(); + _set_key_modifier_state(ev); + ev->set_position(p_pos); + ev->set_global_position(p_pos); + ev->set_relative(p_pos - hover_prev_pos); + Input::get_singleton()->parse_input_event(ev); + hover_prev_pos = p_pos; + } break; + } +} + +void AndroidInputHandler::process_mouse_event(int input_device, int event_action, int event_android_buttons_mask, Point2 event_pos, float event_vertical_factor, float event_horizontal_factor) { + MouseButton event_buttons_mask = _android_button_mask_to_godot_button_mask(event_android_buttons_mask); + switch (event_action) { + case AMOTION_EVENT_ACTION_BUTTON_PRESS: + case AMOTION_EVENT_ACTION_BUTTON_RELEASE: { + Ref<InputEventMouseButton> ev; + ev.instantiate(); + _set_key_modifier_state(ev); + if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) { + ev->set_position(event_pos); + ev->set_global_position(event_pos); + } else { + ev->set_position(hover_prev_pos); + ev->set_global_position(hover_prev_pos); + } + ev->set_pressed(event_action == AMOTION_EVENT_ACTION_BUTTON_PRESS); + MouseButton changed_button_mask = MouseButton(buttons_state ^ event_buttons_mask); + + buttons_state = event_buttons_mask; + + ev->set_button_index(_button_index_from_mask(changed_button_mask)); + ev->set_button_mask(event_buttons_mask); + Input::get_singleton()->parse_input_event(ev); + } break; + + case AMOTION_EVENT_ACTION_MOVE: { + Ref<InputEventMouseMotion> ev; + ev.instantiate(); + _set_key_modifier_state(ev); + if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) { + ev->set_position(event_pos); + ev->set_global_position(event_pos); + ev->set_relative(event_pos - hover_prev_pos); + hover_prev_pos = event_pos; + } else { + ev->set_position(hover_prev_pos); + ev->set_global_position(hover_prev_pos); + ev->set_relative(event_pos); + } + ev->set_button_mask(event_buttons_mask); + Input::get_singleton()->parse_input_event(ev); + } break; + case AMOTION_EVENT_ACTION_SCROLL: { + Ref<InputEventMouseButton> ev; + ev.instantiate(); + if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) { + ev->set_position(event_pos); + ev->set_global_position(event_pos); + } else { + ev->set_position(hover_prev_pos); + ev->set_global_position(hover_prev_pos); + } + ev->set_pressed(true); + buttons_state = event_buttons_mask; + if (event_vertical_factor > 0) { + _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_UP, event_vertical_factor); + } else if (event_vertical_factor < 0) { + _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_DOWN, -event_vertical_factor); + } + + if (event_horizontal_factor > 0) { + _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_RIGHT, event_horizontal_factor); + } else if (event_horizontal_factor < 0) { + _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_LEFT, -event_horizontal_factor); + } + } break; + } +} + +void AndroidInputHandler::_wheel_button_click(MouseButton event_buttons_mask, const Ref<InputEventMouseButton> &ev, MouseButton wheel_button, float factor) { + Ref<InputEventMouseButton> evd = ev->duplicate(); + _set_key_modifier_state(evd); + evd->set_button_index(wheel_button); + evd->set_button_mask(MouseButton(event_buttons_mask ^ (1 << (wheel_button - 1)))); + evd->set_factor(factor); + Input::get_singleton()->parse_input_event(evd); + Ref<InputEventMouseButton> evdd = evd->duplicate(); + evdd->set_pressed(false); + evdd->set_button_mask(event_buttons_mask); + Input::get_singleton()->parse_input_event(evdd); +} + +void AndroidInputHandler::process_double_tap(int event_android_button_mask, Point2 p_pos) { + MouseButton event_button_mask = _android_button_mask_to_godot_button_mask(event_android_button_mask); + Ref<InputEventMouseButton> ev; + ev.instantiate(); + _set_key_modifier_state(ev); + ev->set_position(p_pos); + ev->set_global_position(p_pos); + ev->set_pressed(event_button_mask != 0); + ev->set_button_index(_button_index_from_mask(event_button_mask)); + ev->set_button_mask(event_button_mask); + ev->set_double_click(true); + Input::get_singleton()->parse_input_event(ev); +} + +MouseButton AndroidInputHandler::_button_index_from_mask(MouseButton button_mask) { + switch (button_mask) { + case MOUSE_BUTTON_MASK_LEFT: + return MOUSE_BUTTON_LEFT; + case MOUSE_BUTTON_MASK_RIGHT: + return MOUSE_BUTTON_RIGHT; + case MOUSE_BUTTON_MASK_MIDDLE: + return MOUSE_BUTTON_MIDDLE; + case MOUSE_BUTTON_MASK_XBUTTON1: + return MOUSE_BUTTON_XBUTTON1; + case MOUSE_BUTTON_MASK_XBUTTON2: + return MOUSE_BUTTON_XBUTTON2; + default: + return MOUSE_BUTTON_NONE; + } +} + +MouseButton AndroidInputHandler::_android_button_mask_to_godot_button_mask(int android_button_mask) { + MouseButton godot_button_mask = MOUSE_BUTTON_NONE; + if (android_button_mask & AMOTION_EVENT_BUTTON_PRIMARY) { + godot_button_mask |= MOUSE_BUTTON_MASK_LEFT; + } + if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) { + godot_button_mask |= MOUSE_BUTTON_MASK_RIGHT; + } + if (android_button_mask & AMOTION_EVENT_BUTTON_TERTIARY) { + godot_button_mask |= MOUSE_BUTTON_MASK_MIDDLE; + } + if (android_button_mask & AMOTION_EVENT_BUTTON_BACK) { + godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON1; + } + if (android_button_mask & AMOTION_EVENT_BUTTON_FORWARD) { + godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON2; + } + + return godot_button_mask; +} + +void AndroidInputHandler::process_scroll(Point2 p_pos) { + Ref<InputEventPanGesture> ev; + ev.instantiate(); + _set_key_modifier_state(ev); + ev->set_position(p_pos); + ev->set_delta(p_pos - scroll_prev_pos); + Input::get_singleton()->parse_input_event(ev); + scroll_prev_pos = p_pos; +} diff --git a/platform/android/android_input_handler.h b/platform/android/android_input_handler.h new file mode 100644 index 0000000000..2918ca300b --- /dev/null +++ b/platform/android/android_input_handler.h @@ -0,0 +1,91 @@ +/*************************************************************************/ +/* android_input_handler.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef ANDROID_INPUT_HANDLER_H +#define ANDROID_INPUT_HANDLER_H + +#include "core/input/input.h" + +// This class encapsulates all the handling of input events that come from the Android UI thread. +// Remarks: +// - It's not thread-safe by itself, so its functions must only be called on a single thread, which is the Android UI thread. +// - Its functions must only call thread-safe methods. +class AndroidInputHandler { +public: + struct TouchPos { + int id = 0; + Point2 pos; + }; + + enum { + JOY_EVENT_BUTTON = 0, + JOY_EVENT_AXIS = 1, + JOY_EVENT_HAT = 2 + }; + + struct JoypadEvent { + int device = 0; + int type = 0; + int index = 0; + bool pressed = false; + float value = 0; + int hat = 0; + }; + +private: + bool alt_mem = false; + bool shift_mem = false; + bool control_mem = false; + bool meta_mem = false; + + MouseButton buttons_state = MOUSE_BUTTON_NONE; + + Vector<TouchPos> touch; + Point2 hover_prev_pos; // needed to calculate the relative position on hover events + Point2 scroll_prev_pos; // needed to calculate the relative position on scroll events + + void _set_key_modifier_state(Ref<InputEventWithModifiers> ev); + + static MouseButton _button_index_from_mask(MouseButton button_mask); + static MouseButton _android_button_mask_to_godot_button_mask(int android_button_mask); + + void _wheel_button_click(MouseButton event_buttons_mask, const Ref<InputEventMouseButton> &ev, MouseButton wheel_button, float factor); + +public: + void process_touch(int p_event, int p_pointer, const Vector<TouchPos> &p_points); + void process_hover(int p_type, Point2 p_pos); + void process_mouse_event(int input_device, int event_action, int event_android_buttons_mask, Point2 event_pos, float event_vertical_factor = 0, float event_horizontal_factor = 0); + void process_double_tap(int event_android_button_mask, Point2 p_pos); + void process_scroll(Point2 p_pos); + void process_joy_event(JoypadEvent p_event); + void process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed); +}; + +#endif diff --git a/platform/android/detect.py b/platform/android/detect.py index 7a993e9ca6..61ccad9ac3 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -93,7 +93,7 @@ def configure(env): install_ndk_if_needed(env) # Workaround for MinGW. See: - # http://www.scons.org/wiki/LongCmdLinesOnWin32 + # https://www.scons.org/wiki/LongCmdLinesOnWin32 if os.name == "nt": import subprocess diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index 0bae090702..0eeee8215d 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -161,7 +161,7 @@ bool DirAccessJAndroid::dir_exists(String p_dir) { if (current_dir == "") sd = p_dir; else { - if (p_dir.is_rel_path()) + if (p_dir.is_relative_path()) sd = current_dir.plus_file(p_dir); else sd = fix_path(p_dir); diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index d200d024c5..720752d28f 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -30,7 +30,6 @@ #include "display_server_android.h" -#include "android_keys_utils.h" #include "core/config/project_settings.h" #include "java_godot_io_wrapper.h" #include "java_godot_wrapper.h" @@ -203,17 +202,21 @@ void DisplayServerAndroid::window_set_drop_files_callback(const Callable &p_call // Not supported on Android. } -void DisplayServerAndroid::_window_callback(const Callable &p_callable, const Variant &p_arg) const { +void DisplayServerAndroid::_window_callback(const Callable &p_callable, const Variant &p_arg, bool p_deferred) const { if (!p_callable.is_null()) { const Variant *argp = &p_arg; Variant ret; Callable::CallError ce; - p_callable.call((const Variant **)&argp, 1, ret, ce); + if (p_deferred) { + p_callable.call((const Variant **)&argp, 1, ret, ce); + } else { + p_callable.call_deferred((const Variant **)&argp, 1); + } } } -void DisplayServerAndroid::send_window_event(DisplayServer::WindowEvent p_event) const { - _window_callback(window_event_callback, int(p_event)); +void DisplayServerAndroid::send_window_event(DisplayServer::WindowEvent p_event, bool p_deferred) const { + _window_callback(window_event_callback, int(p_event), p_deferred); } void DisplayServerAndroid::send_input_event(const Ref<InputEvent> &p_event) const { @@ -335,7 +338,7 @@ bool DisplayServerAndroid::can_any_window_draw() const { } void DisplayServerAndroid::process_events() { - Input::get_singleton()->flush_accumulated_events(); + Input::get_singleton()->flush_buffered_events(); } Vector<String> DisplayServerAndroid::get_rendering_drivers_func() { @@ -454,6 +457,7 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis #endif Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); + Input::get_singleton()->set_use_input_buffering(true); // Needed because events will come directly from the UI thread r_error = OK; } @@ -473,344 +477,6 @@ DisplayServerAndroid::~DisplayServerAndroid() { #endif } -void DisplayServerAndroid::process_joy_event(DisplayServerAndroid::JoypadEvent p_event) { - switch (p_event.type) { - case JOY_EVENT_BUTTON: - Input::get_singleton()->joy_button(p_event.device, (JoyButton)p_event.index, p_event.pressed); - break; - case JOY_EVENT_AXIS: - Input::JoyAxisValue value; - value.min = -1; - value.value = p_event.value; - Input::get_singleton()->joy_axis(p_event.device, (JoyAxis)p_event.index, value); - break; - case JOY_EVENT_HAT: - Input::get_singleton()->joy_hat(p_event.device, (HatMask)p_event.hat); - break; - default: - return; - } -} - -void DisplayServerAndroid::_set_key_modifier_state(Ref<InputEventWithModifiers> ev) { - ev->set_shift_pressed(shift_mem); - ev->set_alt_pressed(alt_mem); - ev->set_meta_pressed(meta_mem); - ev->set_ctrl_pressed(control_mem); -} - -void DisplayServerAndroid::process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed) { - static char32_t prev_wc = 0; - char32_t unicode = p_unicode_char; - if ((p_unicode_char & 0xfffffc00) == 0xd800) { - if (prev_wc != 0) { - ERR_PRINT("invalid utf16 surrogate input"); - } - prev_wc = unicode; - return; // Skip surrogate. - } else if ((unicode & 0xfffffc00) == 0xdc00) { - if (prev_wc == 0) { - ERR_PRINT("invalid utf16 surrogate input"); - return; // Skip invalid surrogate. - } - unicode = (prev_wc << 10UL) + unicode - ((0xd800 << 10UL) + 0xdc00 - 0x10000); - prev_wc = 0; - } else { - prev_wc = 0; - } - - Ref<InputEventKey> ev; - ev.instantiate(); - int val = unicode; - int keycode = android_get_keysym(p_keycode); - int phy_keycode = android_get_keysym(p_scancode); - - if (keycode == KEY_SHIFT) { - shift_mem = p_pressed; - } - if (keycode == KEY_ALT) { - alt_mem = p_pressed; - } - if (keycode == KEY_CTRL) { - control_mem = p_pressed; - } - if (keycode == KEY_META) { - meta_mem = p_pressed; - } - - ev->set_keycode((Key)keycode); - ev->set_physical_keycode((Key)phy_keycode); - ev->set_unicode(val); - ev->set_pressed(p_pressed); - - _set_key_modifier_state(ev); - - if (val == '\n') { - ev->set_keycode(KEY_ENTER); - } else if (val == 61448) { - ev->set_keycode(KEY_BACKSPACE); - ev->set_unicode(KEY_BACKSPACE); - } else if (val == 61453) { - ev->set_keycode(KEY_ENTER); - ev->set_unicode(KEY_ENTER); - } else if (p_keycode == 4) { - OS_Android::get_singleton()->main_loop_request_go_back(); - } - - Input::get_singleton()->accumulate_input_event(ev); -} - -void DisplayServerAndroid::process_touch(int p_event, int p_pointer, const Vector<DisplayServerAndroid::TouchPos> &p_points) { - switch (p_event) { - case AMOTION_EVENT_ACTION_DOWN: { //gesture begin - if (touch.size()) { - //end all if exist - for (int i = 0; i < touch.size(); i++) { - Ref<InputEventScreenTouch> ev; - ev.instantiate(); - ev->set_index(touch[i].id); - ev->set_pressed(false); - ev->set_position(touch[i].pos); - Input::get_singleton()->accumulate_input_event(ev); - } - } - - touch.resize(p_points.size()); - for (int i = 0; i < p_points.size(); i++) { - touch.write[i].id = p_points[i].id; - touch.write[i].pos = p_points[i].pos; - } - - //send touch - for (int i = 0; i < touch.size(); i++) { - Ref<InputEventScreenTouch> ev; - ev.instantiate(); - ev->set_index(touch[i].id); - ev->set_pressed(true); - ev->set_position(touch[i].pos); - Input::get_singleton()->accumulate_input_event(ev); - } - - } break; - case AMOTION_EVENT_ACTION_MOVE: { //motion - ERR_FAIL_COND(touch.size() != p_points.size()); - - for (int i = 0; i < touch.size(); i++) { - int idx = -1; - for (int j = 0; j < p_points.size(); j++) { - if (touch[i].id == p_points[j].id) { - idx = j; - break; - } - } - - ERR_CONTINUE(idx == -1); - - if (touch[i].pos == p_points[idx].pos) - continue; //no move unncesearily - - Ref<InputEventScreenDrag> ev; - ev.instantiate(); - ev->set_index(touch[i].id); - ev->set_position(p_points[idx].pos); - ev->set_relative(p_points[idx].pos - touch[i].pos); - Input::get_singleton()->accumulate_input_event(ev); - touch.write[i].pos = p_points[idx].pos; - } - - } break; - case AMOTION_EVENT_ACTION_CANCEL: - case AMOTION_EVENT_ACTION_UP: { //release - if (touch.size()) { - //end all if exist - for (int i = 0; i < touch.size(); i++) { - Ref<InputEventScreenTouch> ev; - ev.instantiate(); - ev->set_index(touch[i].id); - ev->set_pressed(false); - ev->set_position(touch[i].pos); - Input::get_singleton()->accumulate_input_event(ev); - } - touch.clear(); - } - } break; - case AMOTION_EVENT_ACTION_POINTER_DOWN: { // add touch - for (int i = 0; i < p_points.size(); i++) { - if (p_points[i].id == p_pointer) { - TouchPos tp = p_points[i]; - touch.push_back(tp); - - Ref<InputEventScreenTouch> ev; - ev.instantiate(); - - ev->set_index(tp.id); - ev->set_pressed(true); - ev->set_position(tp.pos); - Input::get_singleton()->accumulate_input_event(ev); - - break; - } - } - } break; - case AMOTION_EVENT_ACTION_POINTER_UP: { // remove touch - for (int i = 0; i < touch.size(); i++) { - if (touch[i].id == p_pointer) { - Ref<InputEventScreenTouch> ev; - ev.instantiate(); - ev->set_index(touch[i].id); - ev->set_pressed(false); - ev->set_position(touch[i].pos); - Input::get_singleton()->accumulate_input_event(ev); - touch.remove(i); - - break; - } - } - } break; - } -} - -void DisplayServerAndroid::process_hover(int p_type, Point2 p_pos) { - // https://developer.android.com/reference/android/view/MotionEvent.html#ACTION_HOVER_ENTER - switch (p_type) { - case AMOTION_EVENT_ACTION_HOVER_MOVE: // hover move - case AMOTION_EVENT_ACTION_HOVER_ENTER: // hover enter - case AMOTION_EVENT_ACTION_HOVER_EXIT: { // hover exit - Ref<InputEventMouseMotion> ev; - ev.instantiate(); - _set_key_modifier_state(ev); - ev->set_position(p_pos); - ev->set_global_position(p_pos); - ev->set_relative(p_pos - hover_prev_pos); - Input::get_singleton()->accumulate_input_event(ev); - hover_prev_pos = p_pos; - } break; - } -} - -void DisplayServerAndroid::process_mouse_event(int input_device, int event_action, int event_android_buttons_mask, Point2 event_pos, float event_vertical_factor, float event_horizontal_factor) { - MouseButton event_buttons_mask = _android_button_mask_to_godot_button_mask(event_android_buttons_mask); - switch (event_action) { - case AMOTION_EVENT_ACTION_BUTTON_PRESS: - case AMOTION_EVENT_ACTION_BUTTON_RELEASE: { - Ref<InputEventMouseButton> ev; - ev.instantiate(); - _set_key_modifier_state(ev); - if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) { - ev->set_position(event_pos); - ev->set_global_position(event_pos); - } else { - ev->set_position(hover_prev_pos); - ev->set_global_position(hover_prev_pos); - } - ev->set_pressed(event_action == AMOTION_EVENT_ACTION_BUTTON_PRESS); - MouseButton changed_button_mask = MouseButton(buttons_state ^ event_buttons_mask); - - buttons_state = event_buttons_mask; - - ev->set_button_index(_button_index_from_mask(changed_button_mask)); - ev->set_button_mask(event_buttons_mask); - Input::get_singleton()->accumulate_input_event(ev); - } break; - - case AMOTION_EVENT_ACTION_MOVE: { - Ref<InputEventMouseMotion> ev; - ev.instantiate(); - _set_key_modifier_state(ev); - if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) { - ev->set_position(event_pos); - ev->set_global_position(event_pos); - ev->set_relative(event_pos - hover_prev_pos); - hover_prev_pos = event_pos; - } else { - ev->set_position(hover_prev_pos); - ev->set_global_position(hover_prev_pos); - ev->set_relative(event_pos); - } - ev->set_button_mask(event_buttons_mask); - Input::get_singleton()->accumulate_input_event(ev); - } break; - case AMOTION_EVENT_ACTION_SCROLL: { - Ref<InputEventMouseButton> ev; - ev.instantiate(); - if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE) { - ev->set_position(event_pos); - ev->set_global_position(event_pos); - } else { - ev->set_position(hover_prev_pos); - ev->set_global_position(hover_prev_pos); - } - ev->set_pressed(true); - buttons_state = event_buttons_mask; - if (event_vertical_factor > 0) { - _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_UP, event_vertical_factor); - } else if (event_vertical_factor < 0) { - _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_DOWN, -event_vertical_factor); - } - - if (event_horizontal_factor > 0) { - _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_RIGHT, event_horizontal_factor); - } else if (event_horizontal_factor < 0) { - _wheel_button_click(event_buttons_mask, ev, MOUSE_BUTTON_WHEEL_LEFT, -event_horizontal_factor); - } - } break; - } -} - -void DisplayServerAndroid::_wheel_button_click(MouseButton event_buttons_mask, const Ref<InputEventMouseButton> &ev, MouseButton wheel_button, float factor) { - Ref<InputEventMouseButton> evd = ev->duplicate(); - _set_key_modifier_state(evd); - evd->set_button_index(wheel_button); - evd->set_button_mask(MouseButton(event_buttons_mask ^ (1 << (wheel_button - 1)))); - evd->set_factor(factor); - Input::get_singleton()->accumulate_input_event(evd); - Ref<InputEventMouseButton> evdd = evd->duplicate(); - evdd->set_pressed(false); - evdd->set_button_mask(event_buttons_mask); - Input::get_singleton()->accumulate_input_event(evdd); -} - -void DisplayServerAndroid::process_double_tap(int event_android_button_mask, Point2 p_pos) { - MouseButton event_button_mask = _android_button_mask_to_godot_button_mask(event_android_button_mask); - Ref<InputEventMouseButton> ev; - ev.instantiate(); - _set_key_modifier_state(ev); - ev->set_position(p_pos); - ev->set_global_position(p_pos); - ev->set_pressed(event_button_mask != 0); - ev->set_button_index(_button_index_from_mask(event_button_mask)); - ev->set_button_mask(event_button_mask); - ev->set_double_click(true); - Input::get_singleton()->accumulate_input_event(ev); -} - -MouseButton DisplayServerAndroid::_button_index_from_mask(MouseButton button_mask) { - switch (button_mask) { - case MOUSE_BUTTON_MASK_LEFT: - return MOUSE_BUTTON_LEFT; - case MOUSE_BUTTON_MASK_RIGHT: - return MOUSE_BUTTON_RIGHT; - case MOUSE_BUTTON_MASK_MIDDLE: - return MOUSE_BUTTON_MIDDLE; - case MOUSE_BUTTON_MASK_XBUTTON1: - return MOUSE_BUTTON_XBUTTON1; - case MOUSE_BUTTON_MASK_XBUTTON2: - return MOUSE_BUTTON_XBUTTON2; - default: - return MOUSE_BUTTON_NONE; - } -} - -void DisplayServerAndroid::process_scroll(Point2 p_pos) { - Ref<InputEventPanGesture> ev; - ev.instantiate(); - _set_key_modifier_state(ev); - ev->set_position(p_pos); - ev->set_delta(p_pos - scroll_prev_pos); - Input::get_singleton()->accumulate_input_event(ev); - scroll_prev_pos = p_pos; -} - void DisplayServerAndroid::process_accelerometer(const Vector3 &p_accelerometer) { Input::get_singleton()->set_accelerometer(p_accelerometer); } @@ -852,32 +518,11 @@ DisplayServer::MouseMode DisplayServerAndroid::mouse_get_mode() const { } Point2i DisplayServerAndroid::mouse_get_position() const { - return hover_prev_pos; + return Input::get_singleton()->get_mouse_position(); } MouseButton DisplayServerAndroid::mouse_get_button_state() const { - return buttons_state; -} - -MouseButton DisplayServerAndroid::_android_button_mask_to_godot_button_mask(int android_button_mask) { - MouseButton godot_button_mask = MOUSE_BUTTON_NONE; - if (android_button_mask & AMOTION_EVENT_BUTTON_PRIMARY) { - godot_button_mask |= MOUSE_BUTTON_MASK_LEFT; - } - if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) { - godot_button_mask |= MOUSE_BUTTON_MASK_RIGHT; - } - if (android_button_mask & AMOTION_EVENT_BUTTON_TERTIARY) { - godot_button_mask |= MOUSE_BUTTON_MASK_MIDDLE; - } - if (android_button_mask & AMOTION_EVENT_BUTTON_BACK) { - godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON1; - } - if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) { - godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON2; - } - - return godot_button_mask; + return (MouseButton)Input::get_singleton()->get_mouse_button_mask(); } void DisplayServerAndroid::cursor_set_shape(DisplayServer::CursorShape p_shape) { diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index 9b9f5e99f6..669a1c80e4 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -39,37 +39,8 @@ class RenderingDeviceVulkan; #endif class DisplayServerAndroid : public DisplayServer { -public: - struct TouchPos { - int id = 0; - Point2 pos; - }; - - enum { - JOY_EVENT_BUTTON = 0, - JOY_EVENT_AXIS = 1, - JOY_EVENT_HAT = 2 - }; - - struct JoypadEvent { - int device = 0; - int type = 0; - int index = 0; - bool pressed = false; - float value = 0; - int hat = 0; - }; - -private: String rendering_driver; - bool alt_mem = false; - bool shift_mem = false; - bool control_mem = false; - bool meta_mem = false; - - MouseButton buttons_state = MOUSE_BUTTON_NONE; - // https://developer.android.com/reference/android/view/PointerIcon // mapping between Godot's cursor shape to Android's' int android_cursors[CURSOR_MAX] = { @@ -96,10 +67,6 @@ private: bool keep_screen_on; - Vector<TouchPos> touch; - Point2 hover_prev_pos; // needed to calculate the relative position on hover events - Point2 scroll_prev_pos; // needed to calculate the relative position on scroll events - CursorShape cursor_shape = CursorShape::CURSOR_ARROW; #if defined(VULKAN_ENABLED) @@ -114,18 +81,10 @@ private: Callable input_text_callback; Callable rect_changed_callback; - void _window_callback(const Callable &p_callable, const Variant &p_arg) const; + void _window_callback(const Callable &p_callable, const Variant &p_arg, bool p_deferred = false) const; static void _dispatch_input_events(const Ref<InputEvent> &p_event); - void _set_key_modifier_state(Ref<InputEventWithModifiers> ev); - - static MouseButton _button_index_from_mask(MouseButton button_mask); - - static MouseButton _android_button_mask_to_godot_button_mask(int android_button_mask); - - void _wheel_button_click(MouseButton event_buttons_mask, const Ref<InputEventMouseButton> &ev, MouseButton wheel_button, float factor); - public: static DisplayServerAndroid *get_singleton(); @@ -158,7 +117,7 @@ public: virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; - void send_window_event(WindowEvent p_event) const; + void send_window_event(WindowEvent p_event, bool p_deferred = false) const; void send_input_event(const Ref<InputEvent> &p_event) const; void send_input_text(const String &p_text) const; @@ -210,13 +169,6 @@ public: void process_gravity(const Vector3 &p_gravity); void process_magnetometer(const Vector3 &p_magnetometer); void process_gyroscope(const Vector3 &p_gyroscope); - void process_touch(int p_event, int p_pointer, const Vector<TouchPos> &p_points); - void process_hover(int p_type, Point2 p_pos); - void process_mouse_event(int input_device, int event_action, int event_android_buttons_mask, Point2 event_pos, float event_vertical_factor = 0, float event_horizontal_factor = 0); - void process_double_tap(int event_android_button_mask, Point2 p_pos); - void process_scroll(Point2 p_pos); - void process_joy_event(JoypadEvent p_event); - void process_key_event(int p_keycode, int p_scancode, int p_unicode_char, bool p_pressed); virtual void cursor_set_shape(CursorShape p_shape) override; virtual CursorShape cursor_get_shape() const override; diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index fc86abb6f1..8df61831c2 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -48,6 +48,8 @@ void register_android_exporter() { EDITOR_DEF("export/android/shutdown_adb_on_exit", true); + EDITOR_DEF("export/android/one_click_deploy_clear_previous_install", false); + Ref<EditorExportPlatformAndroid> exporter = Ref<EditorExportPlatformAndroid>(memnew(EditorExportPlatformAndroid)); EditorExport::get_singleton()->add_export_platform(exporter); } diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 17ee173855..5c1c3281a6 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -717,6 +717,10 @@ Error EditorExportPlatformAndroid::copy_gradle_so(void *p_userdata, const Shared return OK; } +bool EditorExportPlatformAndroid::_has_storage_permission(const Vector<String> &p_permissions) { + return p_permissions.find("android.permission.READ_EXTERNAL_STORAGE") != -1 || p_permissions.find("android.permission.WRITE_EXTERNAL_STORAGE") != -1; +} + void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) { const char **aperms = android_perms; while (*aperms) { @@ -763,12 +767,17 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres Vector<String> perms; _get_permissions(p_preset, p_give_internet, perms); for (int i = 0; i < perms.size(); i++) { - manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", perms.get(i)); + String permission = perms.get(i); + if (permission == "android.permission.WRITE_EXTERNAL_STORAGE" || permission == "android.permission.READ_EXTERNAL_STORAGE") { + manifest_text += vformat(" <uses-permission android:name=\"%s\" android:maxSdkVersion=\"29\" />\n", permission); + } else { + manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", permission); + } } manifest_text += _get_xr_features_tag(p_preset); manifest_text += _get_instrumentation_tag(p_preset); - manifest_text += _get_application_tag(p_preset); + manifest_text += _get_application_tag(p_preset, _has_storage_permission(perms)); manifest_text += "</manifest>\n"; String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release")); @@ -824,6 +833,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p Vector<String> perms; // Write permissions into the perms variable. _get_permissions(p_preset, p_give_internet, perms); + bool has_storage_permission = _has_storage_permission(perms); while (ofs < (uint32_t)p_manifest.size()) { uint32_t chunk = decode_uint32(&p_manifest[ofs]); @@ -913,6 +923,10 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p } } + if (tname == "application" && attrname == "requestLegacyExternalStorage") { + encode_uint32(has_storage_permission ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); + } + if (tname == "application" && attrname == "allowBackup") { encode_uint32(backup_allowed, &p_manifest.write[iofs + 16]); } @@ -1632,8 +1646,6 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), false)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "org.godotengine.$genname")); @@ -1780,7 +1792,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int rv; String output; - bool remove_prev = p_preset->get("one_click_deploy/clear_previous_install"); + bool remove_prev = EDITOR_GET("export/android/one_click_deploy_clear_previous_install"); String version_name = p_preset->get("version/name"); String package_name = p_preset->get("package/unique_name"); @@ -2597,7 +2609,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP String export_filename = p_path.get_file(); String export_path = p_path.get_base_dir(); - if (export_path.is_rel_path()) { + if (export_path.is_relative_path()) { export_path = OS::get_singleton()->get_resource_dir().plus_file(export_path); } export_path = ProjectSettings::get_singleton()->globalize_path(export_path).simplify_path(); diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index 909428c2fe..b061ee4e04 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -134,6 +134,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { static Error copy_gradle_so(void *p_userdata, const SharedObject &p_so); + bool _has_storage_permission(const Vector<String> &p_permissions); + void _get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions); void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug); diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index 76512226bf..b9e28a7937 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -235,18 +235,20 @@ String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) { return manifest_activity_text; } -String _get_application_tag(const Ref<EditorExportPreset> &p_preset) { +String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission) { String manifest_application_text = vformat( " <application android:label=\"@string/godot_project_name_string\"\n" " android:allowBackup=\"%s\"\n" " android:icon=\"@mipmap/icon\"\n" " android:isGame=\"%s\"\n" " android:hasFragileUserData=\"%s\"\n" - " tools:replace=\"android:allowBackup,android:isGame,android:hasFragileUserData\"\n" + " android:requestLegacyExternalStorage=\"%s\"\n" + " tools:replace=\"android:allowBackup,android:isGame,android:hasFragileUserData,android:requestLegacyExternalStorage\"\n" " tools:ignore=\"GoogleAppIndexingWarning\">\n\n", bool_to_string(p_preset->get("user_data_backup/allow")), bool_to_string(p_preset->get("package/classify_as_game")), - bool_to_string(p_preset->get("package/retain_data_on_uninstall"))); + bool_to_string(p_preset->get("package/retain_data_on_uninstall")), + bool_to_string(p_has_storage_permission)); manifest_application_text += _get_activity_tag(p_preset); manifest_application_text += " </application>\n"; diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index 44e9a1727d..8a93c25d79 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -81,6 +81,6 @@ String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset); String _get_activity_tag(const Ref<EditorExportPreset> &p_preset); -String _get_application_tag(const Ref<EditorExportPreset> &p_preset); +String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission); #endif //GODOT_GRADLE_EXPORT_UTIL_H diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 467a0dc3c0..00e01884cf 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -22,6 +22,7 @@ android:icon="@mipmap/icon" android:isGame="true" android:hasFragileUserData="false" + android:requestLegacyExternalStorage="false" tools:ignore="GoogleAppIndexingWarning" > <!-- Records the version of the Godot editor used for building --> diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index 81fc87b7ef..fad64c675f 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -1,8 +1,8 @@ ext.versions = [ - androidGradlePlugin: '4.2.1', - compileSdk : 29, - minSdk : 18, - targetSdk : 29, + androidGradlePlugin: '4.2.2', + compileSdk : 30, + minSdk : 19, + targetSdk : 30, buildTools : '30.0.3', supportCoreUtils : '1.0.0', kotlinVersion : '1.5.10', diff --git a/platform/android/java/app/gradle.properties b/platform/android/java/app/gradle.properties index 19587bd81f..0ad8e611ca 100644 --- a/platform/android/java/app/gradle.properties +++ b/platform/android/java/app/gradle.properties @@ -4,7 +4,7 @@ # where otherwise specified. # For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html +# https://www.gradle.org/docs/current/userguide/build_environment.html android.enableJetifier=true android.useAndroidX=true @@ -15,7 +15,7 @@ org.gradle.jvmargs=-Xmx4536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# https://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true org.gradle.warning.mode=all diff --git a/platform/android/java/gradle.properties b/platform/android/java/gradle.properties index b51a19a005..5cd94e85d9 100644 --- a/platform/android/java/gradle.properties +++ b/platform/android/java/gradle.properties @@ -7,7 +7,7 @@ # any settings specified in this file. # For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html +# https://www.gradle.org/docs/current/userguide/build_environment.html android.enableJetifier=true android.useAndroidX=true @@ -18,7 +18,7 @@ org.gradle.jvmargs=-Xmx4536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# https://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true org.gradle.warning.mode=all diff --git a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java index 2a72c9818d..9aa65fd786 100644 --- a/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java +++ b/platform/android/java/lib/src/com/google/android/vending/expansion/downloader/Helpers.java @@ -54,7 +54,7 @@ public class Helpers { /* * Parse the Content-Disposition HTTP Header. The format of the header is defined here: - * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for + * https://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for * content that is going to be downloaded to the file system. We only support the attachment * type. */ diff --git a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java index ad7048cbf3..3600706c7c 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java +++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java @@ -30,6 +30,7 @@ package org.godotengine.godot; +import android.content.ComponentName; import android.content.Intent; import android.os.Bundle; import android.util.Log; @@ -86,6 +87,26 @@ public abstract class FullScreenGodotApp extends FragmentActivity implements God } @Override + public final void onGodotRestartRequested(Godot instance) { + if (instance == godotFragment) { + // HACK: + // + // Currently it's very hard to properly deinitialize Godot on Android to restart the game + // from scratch. Therefore, we need to kill the whole app process and relaunch it. + // + // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including + // releasing and reloading native libs or resetting their state somehow and clearing statics). + // + // Using instrumentation is a way of making the whole app process restart, because Android + // will kill any process of the same package which was already running. + // + Bundle args = new Bundle(); + args.putParcelable("intent", getIntent()); + startInstrumentation(new ComponentName(this, GodotInstrumentation.class), null, args); + } + } + + @Override public void onNewIntent(Intent intent) { super.onNewIntent(intent); if (godotFragment != null) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index 76751a886c..896b169953 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -49,7 +49,6 @@ import android.content.ClipData; import android.content.ClipboardManager; import android.content.ComponentName; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; @@ -68,15 +67,12 @@ import android.os.Environment; import android.os.Messenger; import android.os.VibrationEffect; import android.os.Vibrator; -import android.provider.Settings.Secure; import android.view.Display; -import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; -import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; import android.widget.Button; @@ -321,7 +317,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC @SuppressLint("MissingPermission") @Keep private void vibrate(int durationMs) { - if (requestPermission("VIBRATE")) { + if (durationMs > 0 && requestPermission("VIBRATE")) { Vibrator v = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE); if (v != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -335,22 +331,8 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC } public void restart() { - // HACK: - // - // Currently it's very hard to properly deinitialize Godot on Android to restart the game - // from scratch. Therefore, we need to kill the whole app process and relaunch it. - // - // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including - // releasing and reloading native libs or resetting their state somehow and clearing statics). - // - // Using instrumentation is a way of making the whole app process restart, because Android - // will kill any process of the same package which was already running. - // - final Activity activity = getActivity(); - if (activity != null) { - Bundle args = new Bundle(); - args.putParcelable("intent", mCurrentIntent); - activity.startInstrumentation(new ComponentName(activity, GodotInstrumentation.class), null, args); + if (godotHost != null) { + godotHost.onGodotRestartRequested(this); } } @@ -471,7 +453,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC final Activity activity = getActivity(); io = new GodotIO(activity); - io.unique_id = Secure.getString(activity.getContentResolver(), Secure.ANDROID_ID); GodotLib.io = io; netUtils = new GodotNetUtils(activity); mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java index 58e982c569..7b22895994 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java @@ -58,4 +58,10 @@ public interface GodotHost { * Invoked on the UI thread as the last step of the Godot instance clean up phase. */ default void onGodotForceQuit(Godot instance) {} + + /** + * Invoked on the GL thread when the Godot instance wants to be restarted. It's up to the host + * to perform the appropriate action(s). + */ + default void onGodotRestartRequested(Godot instance) {} } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java index 66882e8e72..d85d88ec6c 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -30,15 +30,19 @@ package org.godotengine.godot; -import org.godotengine.godot.input.*; +import org.godotengine.godot.input.GodotEditText; import android.app.Activity; -import android.content.*; +import android.content.ActivityNotFoundException; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.AssetManager; import android.graphics.Point; import android.net.Uri; -import android.os.*; +import android.os.Build; +import android.os.Environment; +import android.provider.Settings; +import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; @@ -47,14 +51,16 @@ import android.view.DisplayCutout; import android.view.WindowInsets; import java.io.IOException; -import java.io.InputStream; import java.util.Locale; // Wrapper for native library public class GodotIO { - AssetManager am; - final Activity activity; + private static final String TAG = GodotIO.class.getSimpleName(); + + private final AssetManager am; + private final Activity activity; + private final String uniqueId; GodotEditText edit; final int SCREEN_LANDSCAPE = 0; @@ -66,167 +72,6 @@ public class GodotIO { final int SCREEN_SENSOR = 6; ///////////////////////// - /// FILES - ///////////////////////// - - public int last_file_id = 1; - - static class AssetData { - public boolean eof = false; - public String path; - public InputStream is; - public int len; - public int pos; - } - - SparseArray<AssetData> streams; - - public int file_open(String path, boolean write) { - //System.out.printf("file_open: Attempt to Open %s\n",path); - - //Log.v("MyApp", "TRYING TO OPEN FILE: " + path); - if (write) - return -1; - - AssetData ad = new AssetData(); - - try { - ad.is = am.open(path); - - } catch (Exception e) { - //System.out.printf("Exception on file_open: %s\n",path); - return -1; - } - - try { - ad.len = ad.is.available(); - } catch (Exception e) { - System.out.printf("Exception availabling on file_open: %s\n", path); - return -1; - } - - ad.path = path; - ad.pos = 0; - ++last_file_id; - streams.put(last_file_id, ad); - - return last_file_id; - } - public int file_get_size(int id) { - if (streams.get(id) == null) { - System.out.printf("file_get_size: Invalid file id: %d\n", id); - return -1; - } - - return streams.get(id).len; - } - public void file_seek(int id, int bytes) { - if (streams.get(id) == null) { - System.out.printf("file_get_size: Invalid file id: %d\n", id); - return; - } - //seek sucks - AssetData ad = streams.get(id); - if (bytes > ad.len) - bytes = ad.len; - if (bytes < 0) - bytes = 0; - - try { - if (bytes > (int)ad.pos) { - int todo = bytes - (int)ad.pos; - while (todo > 0) { - todo -= ad.is.skip(todo); - } - ad.pos = bytes; - } else if (bytes < (int)ad.pos) { - ad.is = am.open(ad.path); - - ad.pos = bytes; - int todo = bytes; - while (todo > 0) { - todo -= ad.is.skip(todo); - } - } - - ad.eof = false; - } catch (IOException e) { - System.out.printf("Exception on file_seek: %s\n", e); - return; - } - } - - public int file_tell(int id) { - if (streams.get(id) == null) { - System.out.printf("file_read: Can't tell eof for invalid file id: %d\n", id); - return 0; - } - - AssetData ad = streams.get(id); - return ad.pos; - } - public boolean file_eof(int id) { - if (streams.get(id) == null) { - System.out.printf("file_read: Can't check eof for invalid file id: %d\n", id); - return false; - } - - AssetData ad = streams.get(id); - return ad.eof; - } - - public byte[] file_read(int id, int bytes) { - if (streams.get(id) == null) { - System.out.printf("file_read: Can't read invalid file id: %d\n", id); - return new byte[0]; - } - - AssetData ad = streams.get(id); - - if (ad.pos + bytes > ad.len) { - bytes = ad.len - ad.pos; - ad.eof = true; - } - - if (bytes == 0) { - return new byte[0]; - } - - byte[] buf1 = new byte[bytes]; - int r = 0; - try { - r = ad.is.read(buf1); - } catch (IOException e) { - System.out.printf("Exception on file_read: %s\n", e); - return new byte[bytes]; - } - - if (r == 0) { - return new byte[0]; - } - - ad.pos += r; - - if (r < bytes) { - byte[] buf2 = new byte[r]; - for (int i = 0; i < r; i++) - buf2[i] = buf1[i]; - return buf2; - } else { - return buf1; - } - } - - public void file_close(int id) { - if (streams.get(id) == null) { - System.out.printf("file_close: Can't close invalid file id: %d\n", id); - return; - } - - streams.remove(id); - } - - ///////////////////////// /// DIRECTORIES ///////////////////////// @@ -236,9 +81,9 @@ public class GodotIO { public String path; } - public int last_dir_id = 1; + private int last_dir_id = 1; - SparseArray<AssetDir> dirs; + private final SparseArray<AssetDir> dirs; public int dir_open(String path) { AssetDir ad = new AssetDir(); @@ -257,7 +102,6 @@ public class GodotIO { return -1; } - //System.out.printf("Opened dir: %s\n",path); ++last_dir_id; dirs.put(last_dir_id, ad); @@ -320,9 +164,14 @@ public class GodotIO { GodotIO(Activity p_activity) { am = p_activity.getAssets(); activity = p_activity; - //streams = new HashMap<Integer, AssetData>(); - streams = new SparseArray<>(); dirs = new SparseArray<>(); + String androidId = Settings.Secure.getString(activity.getContentResolver(), + Settings.Secure.ANDROID_ID); + if (androidId == null) { + androidId = ""; + } + + uniqueId = androidId; } ///////////////////////// @@ -331,7 +180,6 @@ public class GodotIO { public int openURI(String p_uri) { try { - Log.v("MyApp", "TRYING TO OPEN URI: " + p_uri); String path = p_uri; String type = ""; if (path.startsWith("/")) { @@ -357,12 +205,12 @@ public class GodotIO { } } - public String getDataDir() { - return activity.getFilesDir().getAbsolutePath(); + public String getCacheDir() { + return activity.getCacheDir().getAbsolutePath(); } - public String getExternalDataDir() { - return activity.getExternalFilesDir(null).getAbsolutePath(); + public String getDataDir() { + return activity.getFilesDir().getAbsolutePath(); } public String getLocale() { @@ -456,51 +304,58 @@ public class GodotIO { public static final int SYSTEM_DIR_PICTURES = 6; public static final int SYSTEM_DIR_RINGTONES = 7; - public String getSystemDir(int idx) { - String what = ""; + public String getSystemDir(int idx, boolean shared_storage) { + String what; switch (idx) { - case SYSTEM_DIR_DESKTOP: { - //what=Environment.DIRECTORY_DOCUMENTS; - what = Environment.DIRECTORY_DOWNLOADS; + case SYSTEM_DIR_DESKTOP: + default: { + what = null; // This leads to the app specific external root directory. } break; + case SYSTEM_DIR_DCIM: { what = Environment.DIRECTORY_DCIM; - } break; + case SYSTEM_DIR_DOCUMENTS: { - what = Environment.DIRECTORY_DOWNLOADS; - //what=Environment.DIRECTORY_DOCUMENTS; + what = Environment.DIRECTORY_DOCUMENTS; } break; + case SYSTEM_DIR_DOWNLOADS: { what = Environment.DIRECTORY_DOWNLOADS; - } break; + case SYSTEM_DIR_MOVIES: { what = Environment.DIRECTORY_MOVIES; - } break; + case SYSTEM_DIR_MUSIC: { what = Environment.DIRECTORY_MUSIC; } break; + case SYSTEM_DIR_PICTURES: { what = Environment.DIRECTORY_PICTURES; } break; + case SYSTEM_DIR_RINGTONES: { what = Environment.DIRECTORY_RINGTONES; - } break; } - if (what.equals("")) - return ""; - return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath(); + if (shared_storage) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + Log.w(TAG, "Shared storage access is limited on Android 10 and higher."); + } + if (TextUtils.isEmpty(what)) { + return Environment.getExternalStorageDirectory().getAbsolutePath(); + } else { + return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath(); + } + } else { + return activity.getExternalFilesDir(what).getAbsolutePath(); + } } - protected static final String PREFS_FILE = "device_id.xml"; - protected static final String PREFS_DEVICE_ID = "device_id"; - - public static String unique_id = ""; public String getUniqueID() { - return unique_id; + return uniqueId; } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java index 1d60c21c60..6b248fd034 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.java @@ -75,7 +75,7 @@ public class GodotGestureHandler extends GestureDetector.SimpleOnGestureListener final int x = Math.round(event.getX()); final int y = Math.round(event.getY()); final int buttonMask = event.getButtonState(); - queueEvent(() -> GodotLib.doubleTap(buttonMask, x, y)); + GodotLib.doubleTap(buttonMask, x, y); return true; } @@ -84,7 +84,7 @@ public class GodotGestureHandler extends GestureDetector.SimpleOnGestureListener //Log.i("GodotGesture", "onScroll"); final int x = Math.round(distanceX); final int y = Math.round(distanceY); - queueEvent(() -> GodotLib.scroll(x, y)); + GodotLib.scroll(x, y); return true; } diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java index 4dc9157545..fc0b84b392 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java @@ -68,10 +68,6 @@ public class GodotInputHandler implements InputDeviceListener { mInputManager.registerInputDeviceListener(this, null); } - private void queueEvent(Runnable task) { - mRenderView.queueOnRenderThread(task); - } - private boolean isKeyEvent_GameDevice(int source) { // Note that keyboards are often (SOURCE_KEYBOARD | SOURCE_DPAD) if (source == (InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_DPAD)) @@ -96,13 +92,12 @@ public class GodotInputHandler implements InputDeviceListener { if (mJoystickIds.indexOfKey(deviceId) >= 0) { final int button = getGodotButton(keyCode); final int godotJoyId = mJoystickIds.get(deviceId); - - queueEvent(() -> GodotLib.joybutton(godotJoyId, button, false)); + GodotLib.joybutton(godotJoyId, button, false); } } else { final int scanCode = event.getScanCode(); final int chr = event.getUnicodeChar(0); - queueEvent(() -> GodotLib.key(keyCode, scanCode, chr, false)); + GodotLib.key(keyCode, scanCode, chr, false); } return true; @@ -132,13 +127,12 @@ public class GodotInputHandler implements InputDeviceListener { if (mJoystickIds.indexOfKey(deviceId) >= 0) { final int button = getGodotButton(keyCode); final int godotJoyId = mJoystickIds.get(deviceId); - - queueEvent(() -> GodotLib.joybutton(godotJoyId, button, true)); + GodotLib.joybutton(godotJoyId, button, true); } } else { final int scanCode = event.getScanCode(); final int chr = event.getUnicodeChar(0); - queueEvent(() -> GodotLib.key(keyCode, scanCode, chr, true)); + GodotLib.key(keyCode, scanCode, chr, true); } return true; @@ -170,18 +164,16 @@ public class GodotInputHandler implements InputDeviceListener { final int action = event.getActionMasked(); final int pointer_idx = event.getPointerId(event.getActionIndex()); - mRenderView.queueOnRenderThread(() -> { - switch (action) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_MOVE: - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_POINTER_DOWN: { - GodotLib.touch(event.getSource(), action, pointer_idx, evcount, arr); - } break; - } - }); + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_MOVE: + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_POINTER_DOWN: { + GodotLib.touch(event.getSource(), action, pointer_idx, evcount, arr); + } break; + } } return true; } @@ -205,7 +197,7 @@ public class GodotInputHandler implements InputDeviceListener { // save value to prevent repeats joystick.axesValues.put(axis, value); final int godotAxisIdx = i; - queueEvent(() -> GodotLib.joyaxis(godotJoyId, godotAxisIdx, value)); + GodotLib.joyaxis(godotJoyId, godotAxisIdx, value); } } @@ -215,7 +207,7 @@ public class GodotInputHandler implements InputDeviceListener { if (joystick.hatX != hatX || joystick.hatY != hatY) { joystick.hatX = hatX; joystick.hatY = hatY; - queueEvent(() -> GodotLib.joyhat(godotJoyId, hatX, hatY)); + GodotLib.joyhat(godotJoyId, hatX, hatY); } } return true; @@ -224,7 +216,7 @@ public class GodotInputHandler implements InputDeviceListener { final float x = event.getX(); final float y = event.getY(); final int type = event.getAction(); - queueEvent(() -> GodotLib.hover(type, x, y)); + GodotLib.hover(type, x, y); return true; } else if (event.isFromSource(InputDevice.SOURCE_MOUSE) || event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) { @@ -316,7 +308,7 @@ public class GodotInputHandler implements InputDeviceListener { } mJoysticksDevices.put(deviceId, joystick); - queueEvent(() -> GodotLib.joyconnectionchanged(id, true, joystick.name)); + GodotLib.joyconnectionchanged(id, true, joystick.name); } @Override @@ -328,8 +320,7 @@ public class GodotInputHandler implements InputDeviceListener { final int godotJoyId = mJoystickIds.get(deviceId); mJoystickIds.delete(deviceId); mJoysticksDevices.delete(deviceId); - - queueEvent(() -> GodotLib.joyconnectionchanged(godotJoyId, false, "")); + GodotLib.joyconnectionchanged(godotJoyId, false, ""); } @Override @@ -418,7 +409,7 @@ public class GodotInputHandler implements InputDeviceListener { final float x = event.getX(); final float y = event.getY(); final int type = event.getAction(); - queueEvent(() -> GodotLib.hover(type, x, y)); + GodotLib.hover(type, x, y); return true; } case MotionEvent.ACTION_BUTTON_PRESS: @@ -428,7 +419,7 @@ public class GodotInputHandler implements InputDeviceListener { final float y = event.getY(); final int buttonsMask = event.getButtonState(); final int action = event.getAction(); - queueEvent(() -> GodotLib.touch(event.getSource(), action, 0, 1, new float[] { 0, x, y }, buttonsMask)); + GodotLib.touch(event.getSource(), action, 0, 1, new float[] { 0, x, y }, buttonsMask); return true; } case MotionEvent.ACTION_SCROLL: { @@ -438,7 +429,7 @@ public class GodotInputHandler implements InputDeviceListener { final int action = event.getAction(); final float verticalFactor = event.getAxisValue(MotionEvent.AXIS_VSCROLL); final float horizontalFactor = event.getAxisValue(MotionEvent.AXIS_HSCROLL); - queueEvent(() -> GodotLib.touch(event.getSource(), action, 0, 1, new float[] { 0, x, y }, buttonsMask, verticalFactor, horizontalFactor)); + GodotLib.touch(event.getSource(), action, 0, 1, new float[] { 0, x, y }, buttonsMask, verticalFactor, horizontalFactor); } case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_UP: { diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java index 020870a110..002a75277d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java @@ -94,17 +94,15 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene public void beforeTextChanged(final CharSequence pCharSequence, final int start, final int count, final int after) { //Log.d(TAG, "beforeTextChanged(" + pCharSequence + ")start: " + start + ",count: " + count + ",after: " + after); - mRenderView.queueOnRenderThread(() -> { - for (int i = 0; i < count; ++i) { - GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, true); - GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, false); - - if (mHasSelection) { - mHasSelection = false; - break; - } + for (int i = 0; i < count; ++i) { + GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, true); + GodotLib.key(KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL, 0, false); + + if (mHasSelection) { + mHasSelection = false; + break; } - }); + } } @Override @@ -115,17 +113,15 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene for (int i = start; i < start + count; ++i) { newChars[i - start] = pCharSequence.charAt(i); } - mRenderView.queueOnRenderThread(() -> { - for (int i = 0; i < count; ++i) { - int key = newChars[i]; - if ((key == '\n') && !mEdit.isMultiline()) { - // Return keys are handled through action events - continue; - } - GodotLib.key(0, 0, key, true); - GodotLib.key(0, 0, key, false); + for (int i = 0; i < count; ++i) { + int key = newChars[i]; + if ((key == '\n') && !mEdit.isMultiline()) { + // Return keys are handled through action events + continue; } - }); + GodotLib.key(0, 0, key, true); + GodotLib.key(0, 0, key, false); + } } @Override @@ -133,13 +129,11 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene if (mEdit == pTextView && isFullScreenEdit()) { final String characters = pKeyEvent.getCharacters(); - mRenderView.queueOnRenderThread(() -> { - for (int i = 0; i < characters.length(); i++) { - final int ch = characters.codePointAt(i); - GodotLib.key(0, 0, ch, true); - GodotLib.key(0, 0, ch, false); - } - }); + for (int i = 0; i < characters.length(); i++) { + final int ch = characters.codePointAt(i); + GodotLib.key(0, 0, ch, true); + GodotLib.key(0, 0, ch, false); + } } if (pActionID == EditorInfo.IME_ACTION_DONE) { diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 5e99135498..5cd2c382d2 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -48,8 +48,8 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc } _open_URI = p_env->GetMethodID(cls, "openURI", "(Ljava/lang/String;)I"); + _get_cache_dir = p_env->GetMethodID(cls, "getCacheDir", "()Ljava/lang/String;"); _get_data_dir = p_env->GetMethodID(cls, "getDataDir", "()Ljava/lang/String;"); - _get_external_data_dir = p_env->GetMethodID(cls, "getExternalDataDir", "()Ljava/lang/String;"); _get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;"); _get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;"); _get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I"); @@ -59,7 +59,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc _hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V"); _set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V"); _get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I"); - _get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(I)Ljava/lang/String;"); + _get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(IZ)Ljava/lang/String;"); } } @@ -82,22 +82,22 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) { } } -String GodotIOJavaWrapper::get_user_data_dir() { - if (_get_data_dir) { +String GodotIOJavaWrapper::get_cache_dir() { + if (_get_cache_dir) { JNIEnv *env = get_jni_env(); ERR_FAIL_COND_V(env == nullptr, String()); - jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir); + jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_cache_dir); return jstring_to_string(s, env); } else { return String(); } } -String GodotIOJavaWrapper::get_external_data_dir() { - if (_get_external_data_dir) { +String GodotIOJavaWrapper::get_user_data_dir() { + if (_get_data_dir) { JNIEnv *env = get_jni_env(); ERR_FAIL_COND_V(env == nullptr, String()); - jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_external_data_dir); + jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir); return jstring_to_string(s, env); } else { return String(); @@ -200,11 +200,11 @@ int GodotIOJavaWrapper::get_screen_orientation() { } } -String GodotIOJavaWrapper::get_system_dir(int p_dir) { +String GodotIOJavaWrapper::get_system_dir(int p_dir, bool p_shared_storage) { if (_get_system_dir) { JNIEnv *env = get_jni_env(); ERR_FAIL_COND_V(env == nullptr, String(".")); - jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir); + jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir, p_shared_storage); return jstring_to_string(s, env); } else { return String("."); diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h index e4c0a4b2c7..8f6d7f813f 100644 --- a/platform/android/java_godot_io_wrapper.h +++ b/platform/android/java_godot_io_wrapper.h @@ -46,8 +46,8 @@ private: jclass cls; jmethodID _open_URI = 0; + jmethodID _get_cache_dir = 0; jmethodID _get_data_dir = 0; - jmethodID _get_external_data_dir = 0; jmethodID _get_locale = 0; jmethodID _get_model = 0; jmethodID _get_screen_DPI = 0; @@ -66,8 +66,8 @@ public: jobject get_instance(); Error open_uri(const String &p_uri); + String get_cache_dir(); String get_user_data_dir(); - String get_external_data_dir(); String get_locale(); String get_model(); int get_screen_dpi(); @@ -80,7 +80,7 @@ public: void set_vk_height(int p_height); void set_screen_orientation(int p_orient); int get_screen_orientation(); - String get_system_dir(int p_dir); + String get_system_dir(int p_dir, bool p_shared_storage); }; #endif /* !JAVA_GODOT_IO_WRAPPER_H */ diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index ce7a49e53c..d971727269 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -34,6 +34,7 @@ #include "java_godot_wrapper.h" #include "android/asset_manager_jni.h" +#include "android_input_handler.h" #include "api/java_class_wrapper.h" #include "api/jni_singleton.h" #include "core/config/engine.h" @@ -56,11 +57,12 @@ static JavaClassWrapper *java_class_wrapper = nullptr; static OS_Android *os_android = nullptr; +static AndroidInputHandler *input_handler = nullptr; static GodotJavaWrapper *godot_java = nullptr; static GodotIOJavaWrapper *godot_io_java = nullptr; static bool initialized = false; -static int step = 0; +static SafeNumeric<int> step; // Shared between UI and render threads static Size2 new_size; static Vector3 accelerometer; @@ -111,6 +113,9 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env if (godot_java) { delete godot_java; } + if (input_handler) { + delete input_handler; + } if (os_android) { delete os_android; } @@ -165,7 +170,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, j os_android->set_display_size(Size2i(p_width, p_height)); // No need to reset the surface during startup - if (step > 0) { + if (step.get() > 0) { if (p_surface) { ANativeWindow *native_window = ANativeWindow_fromSurface(env, p_surface); os_android->set_native_window(native_window); @@ -179,7 +184,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, j JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface, jboolean p_32_bits) { if (os_android) { - if (step == 0) { + if (step.get() == 0) { // During startup os_android->set_context_is_16_bits(!p_32_bits); if (p_surface) { @@ -188,33 +193,36 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *en } } else { // Rendering context recreated because it was lost; restart app to let it reload everything + step.set(-1); // Ensure no further steps are attempted and no further events are sent os_android->main_loop_end(); godot_java->restart(env); - step = -1; // Ensure no further steps are attempted } } } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz) { - if (step == 0) + if (step.get() == 0) return; - os_android->main_loop_request_go_back(); + if (DisplayServerAndroid *dsa = Object::cast_to<DisplayServerAndroid>(DisplayServer::get_singleton())) { + dsa->send_window_event(DisplayServer::WINDOW_EVENT_GO_BACK_REQUEST); + } } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz) { - if (step == -1) + if (step.get() == -1) return; - if (step == 0) { + if (step.get() == 0) { // Since Godot is initialized on the UI thread, main_thread_id was set to that thread's id, // but for Godot purposes, the main thread is the one running the game loop Main::setup2(Thread::get_caller_id()); - ++step; + input_handler = new AndroidInputHandler(); + step.increment(); return; } - if (step == 1) { + if (step.get() == 1) { if (!Main::start()) { return; // should exit instead and print the error } @@ -222,7 +230,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jcl godot_java->on_godot_setup_completed(env); os_android->main_loop_begin(); godot_java->on_godot_main_loop_started(env); - ++step; + step.increment(); } DisplayServerAndroid::get_singleton()->process_accelerometer(accelerometer); @@ -236,91 +244,100 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jcl } void touch_preprocessing(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask, jfloat vertical_factor, jfloat horizontal_factor) { - if (step == 0) + if (step.get() <= 0) return; - Vector<DisplayServerAndroid::TouchPos> points; + Vector<AndroidInputHandler::TouchPos> points; for (int i = 0; i < pointer_count; i++) { jfloat p[3]; env->GetFloatArrayRegion(positions, i * 3, 3, p); - DisplayServerAndroid::TouchPos tp; + AndroidInputHandler::TouchPos tp; tp.pos = Point2(p[1], p[2]); tp.id = (int)p[0]; points.push_back(tp); } if ((input_device & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE || (input_device & AINPUT_SOURCE_MOUSE_RELATIVE) == AINPUT_SOURCE_MOUSE_RELATIVE) { - DisplayServerAndroid::get_singleton()->process_mouse_event(input_device, ev, buttons_mask, points[0].pos, vertical_factor, horizontal_factor); + input_handler->process_mouse_event(input_device, ev, buttons_mask, points[0].pos, vertical_factor, horizontal_factor); } else { - DisplayServerAndroid::get_singleton()->process_touch(ev, pointer, points); + input_handler->process_touch(ev, pointer, points); } } +// Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3F(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray position) { touch_preprocessing(env, clazz, input_device, ev, pointer, pointer_count, position); } +// Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FI(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray position, jint buttons_mask) { touch_preprocessing(env, clazz, input_device, ev, pointer, pointer_count, position, buttons_mask); } +// Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch__IIII_3FIFF(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray position, jint buttons_mask, jfloat vertical_factor, jfloat horizontal_factor) { touch_preprocessing(env, clazz, input_device, ev, pointer, pointer_count, position, buttons_mask, vertical_factor, horizontal_factor); } +// Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jclass clazz, jint p_type, jfloat p_x, jfloat p_y) { - if (step == 0) + if (step.get() <= 0) return; - DisplayServerAndroid::get_singleton()->process_hover(p_type, Point2(p_x, p_y)); + input_handler->process_hover(p_type, Point2(p_x, p_y)); } +// Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_doubleTap(JNIEnv *env, jclass clazz, jint p_button_mask, jint p_x, jint p_y) { - if (step == 0) + if (step.get() <= 0) return; - DisplayServerAndroid::get_singleton()->process_double_tap(p_button_mask, Point2(p_x, p_y)); + input_handler->process_double_tap(p_button_mask, Point2(p_x, p_y)); } +// Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_scroll(JNIEnv *env, jclass clazz, jint p_x, jint p_y) { - if (step == 0) + if (step.get() <= 0) return; - DisplayServerAndroid::get_singleton()->process_scroll(Point2(p_x, p_y)); + input_handler->process_scroll(Point2(p_x, p_y)); } +// Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jclass clazz, jint p_device, jint p_button, jboolean p_pressed) { - if (step == 0) + if (step.get() <= 0) return; - DisplayServerAndroid::JoypadEvent jevent; + AndroidInputHandler::JoypadEvent jevent; jevent.device = p_device; - jevent.type = DisplayServerAndroid::JOY_EVENT_BUTTON; + jevent.type = AndroidInputHandler::JOY_EVENT_BUTTON; jevent.index = p_button; jevent.pressed = p_pressed; - DisplayServerAndroid::get_singleton()->process_joy_event(jevent); + input_handler->process_joy_event(jevent); } +// Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jclass clazz, jint p_device, jint p_axis, jfloat p_value) { - if (step == 0) + if (step.get() <= 0) return; - DisplayServerAndroid::JoypadEvent jevent; + AndroidInputHandler::JoypadEvent jevent; jevent.device = p_device; - jevent.type = DisplayServerAndroid::JOY_EVENT_AXIS; + jevent.type = AndroidInputHandler::JOY_EVENT_AXIS; jevent.index = p_axis; jevent.value = p_value; - DisplayServerAndroid::get_singleton()->process_joy_event(jevent); + input_handler->process_joy_event(jevent); } +// Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, jclass clazz, jint p_device, jint p_hat_x, jint p_hat_y) { - if (step == 0) + if (step.get() <= 0) return; - DisplayServerAndroid::JoypadEvent jevent; + AndroidInputHandler::JoypadEvent jevent; jevent.device = p_device; - jevent.type = DisplayServerAndroid::JOY_EVENT_HAT; + jevent.type = AndroidInputHandler::JOY_EVENT_HAT; int hat = 0; if (p_hat_x != 0) { if (p_hat_x < 0) @@ -336,9 +353,10 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv *env, j } jevent.hat = hat; - DisplayServerAndroid::get_singleton()->process_joy_event(jevent); + input_handler->process_joy_event(jevent); } +// Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv *env, jclass clazz, jint p_device, jboolean p_connected, jstring p_name) { if (os_android) { String name = jstring_to_string(p_name, env); @@ -346,11 +364,12 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged( } } +// Called on the UI thread JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jclass clazz, jint p_keycode, jint p_scancode, jint p_unicode_char, jboolean p_pressed) { - if (step == 0) + if (step.get() <= 0) return; - DisplayServerAndroid::get_singleton()->process_key_event(p_keycode, p_scancode, p_unicode_char, p_pressed); + input_handler->process_key_event(p_keycode, p_scancode, p_unicode_char, p_pressed); } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv *env, jclass clazz, jfloat x, jfloat y, jfloat z) { @@ -370,14 +389,14 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_gyroscope(JNIEnv *env } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv *env, jclass clazz) { - if (step == 0) + if (step.get() <= 0) return; os_android->main_loop_focusin(); } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusout(JNIEnv *env, jclass clazz) { - if (step == 0) + if (step.get() <= 0) return; os_android->main_loop_focusout(); @@ -456,7 +475,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResu } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz) { - if (step == 0) + if (step.get() <= 0) return; if (os_android->get_main_loop()) { @@ -465,7 +484,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNI } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz) { - if (step == 0) + if (step.get() <= 0) return; if (os_android->get_main_loop()) { diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 792a390e36..21fb31d991 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -188,10 +188,6 @@ void OS_Android::main_loop_focusin() { audio_driver_android.set_pause(false); } -void OS_Android::main_loop_request_go_back() { - DisplayServerAndroid::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_GO_BACK_REQUEST); -} - Error OS_Android::shell_open(String p_uri) { return godot_io_java->open_uri(p_uri); } @@ -217,6 +213,10 @@ String OS_Android::get_model_name() const { return OS_Unix::get_model_name(); } +String OS_Android::get_data_path() const { + return get_user_data_dir(); +} + String OS_Android::get_user_data_dir() const { if (data_dir_cache != String()) return data_dir_cache; @@ -229,11 +229,11 @@ String OS_Android::get_user_data_dir() const { return "."; } -String OS_Android::get_external_data_dir() const { - String data_dir = godot_io_java->get_external_data_dir(); - if (data_dir != "") { - data_dir = _remove_symlink(data_dir); - return data_dir; +String OS_Android::get_cache_path() const { + String cache_dir = godot_io_java->get_cache_dir(); + if (cache_dir != "") { + cache_dir = _remove_symlink(cache_dir); + return cache_dir; } return "."; } @@ -246,8 +246,8 @@ String OS_Android::get_unique_id() const { return OS::get_unique_id(); } -String OS_Android::get_system_dir(SystemDir p_dir) const { - return godot_io_java->get_system_dir(p_dir); +String OS_Android::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { + return godot_io_java->get_system_dir(p_dir, p_shared_storage); } void OS_Android::set_display_size(const Size2i &p_size) { diff --git a/platform/android/os_android.h b/platform/android/os_android.h index 38f0f3edc7..c938297821 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -95,7 +95,6 @@ public: void main_loop_begin(); bool main_loop_iterate(); - void main_loop_request_go_back(); void main_loop_end(); void main_loop_focusout(); void main_loop_focusin(); @@ -111,14 +110,15 @@ public: virtual Error shell_open(String p_uri) override; virtual String get_user_data_dir() const override; - virtual String get_external_data_dir() const override; + virtual String get_data_path() const override; + virtual String get_cache_path() const override; virtual String get_resource_dir() const override; virtual String get_locale() const override; virtual String get_model_name() const override; virtual String get_unique_id() const override; - virtual String get_system_dir(SystemDir p_dir) const override; + virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override; void vibrate_handheld(int p_duration_ms) override; diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp index 54f541f607..c50195639c 100644 --- a/platform/javascript/api/javascript_tools_editor_plugin.cpp +++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp @@ -35,6 +35,7 @@ #include "core/config/project_settings.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" +#include "core/os/time.h" #include "editor/editor_node.h" #include <emscripten/emscripten.h> @@ -58,23 +59,40 @@ JavaScriptToolsEditorPlugin::JavaScriptToolsEditorPlugin(EditorNode *p_editor) { void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) { if (!Engine::get_singleton() || !Engine::get_singleton()->is_editor_hint()) { - WARN_PRINT("Project download is only available in Editor mode"); + ERR_PRINT("Downloading the project as a ZIP archive is only available in Editor mode."); return; } String resource_path = ProjectSettings::get_singleton()->get_resource_path(); FileAccess *src_f; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); - zipFile zip = zipOpen2("/tmp/project.zip", APPEND_STATUS_CREATE, nullptr, &io); - String base_path = resource_path.substr(0, resource_path.rfind("/")) + "/"; + + // Name the downloded ZIP file to contain the project name and download date for easier organization. + // Replace characters not allowed (or risky) in Windows file names with safe characters. + // In the project name, all invalid characters become an empty string so that a name + // like "Platformer 2: Godette's Revenge" becomes "platformer_2-_godette-s_revenge". + const String project_name_safe = + GLOBAL_GET("application/config/name").to_lower().replace(" ", "_"); + const String datetime_safe = + Time::get_singleton()->get_datetime_string_from_system(false, true).replace(" ", "_"); + const String output_name = OS::get_singleton()->get_safe_dir_name(vformat("%s_%s.zip")); + const String output_path = String("/tmp").plus_file(output_name); + + zipFile zip = zipOpen2(output_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io); + const String base_path = resource_path.substr(0, resource_path.rfind("/")) + "/"; _zip_recursive(resource_path, base_path, zip); zipClose(zip, nullptr); - FileAccess *f = FileAccess::open("/tmp/project.zip", FileAccess::READ); - ERR_FAIL_COND_MSG(!f, "Unable to create zip file"); + FileAccess *f = FileAccess::open(output_path, FileAccess::READ); + ERR_FAIL_COND_MSG(!f, "Unable to create ZIP file."); Vector<uint8_t> buf; buf.resize(f->get_length()); f->get_buffer(buf.ptrw(), buf.size()); - godot_js_os_download_buffer(buf.ptr(), buf.size(), "project.zip", "application/zip"); + godot_js_os_download_buffer(buf.ptr(), buf.size(), output_name.utf8().get_data(), "application/zip"); + + f->close(); + memdelete(f); + // Remove the temporary file since it was sent to the user's native filesystem as a download. + DirAccess::remove_file_or_error(output_path); } void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, zipFile p_zip) { @@ -108,7 +126,7 @@ void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, z void JavaScriptToolsEditorPlugin::_zip_recursive(String p_path, String p_base_path, zipFile p_zip) { DirAccess *dir = DirAccess::open(p_path); if (!dir) { - WARN_PRINT("Unable to open dir for zipping: " + p_path); + WARN_PRINT("Unable to open directory for zipping: " + p_path); return; } dir->list_dir_begin(); diff --git a/platform/javascript/emscripten_helpers.py b/platform/javascript/emscripten_helpers.py index ab98838e20..4dad2d5204 100644 --- a/platform/javascript/emscripten_helpers.py +++ b/platform/javascript/emscripten_helpers.py @@ -24,7 +24,10 @@ def get_build_version(): v = "%d.%d" % (version.major, version.minor) if version.patch > 0: v += ".%d" % version.patch - v += ".%s.%s" % (version.status, name) + status = version.status + if os.getenv("GODOT_VERSION_STATUS") != None: + status = str(os.getenv("GODOT_VERSION_STATUS")) + v += ".%s.%s" % (status, name) return v diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index 5aa750757c..99e7ee8b5f 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -106,7 +106,7 @@ autoAddDeps(GodotConfig, '$GodotConfig'); mergeInto(LibraryManager.library, GodotConfig); const GodotFS = { - $GodotFS__deps: ['$FS', '$IDBFS', '$GodotRuntime'], + $GodotFS__deps: ['$ERRNO_CODES', '$FS', '$IDBFS', '$GodotRuntime'], $GodotFS__postset: [ 'Module["initFS"] = GodotFS.init;', 'Module["copyToFS"] = GodotFS.copy_to_fs;', diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp index ea0222cb19..0e98af71fa 100644 --- a/platform/linuxbsd/crash_handler_linuxbsd.cpp +++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp @@ -32,6 +32,8 @@ #include "core/config/project_settings.h" #include "core/os/os.h" +#include "core/version.h" +#include "core/version_hash.gen.h" #include "main/main.h" #ifdef DEBUG_ENABLED @@ -61,12 +63,19 @@ static void handle_crash(int sig) { } // Dump the backtrace to stderr with a message to the user + fprintf(stderr, "\n================================================================\n"); fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig); if (OS::get_singleton()->get_main_loop()) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); } + // Print the engine version just before, so that people are reminded to include the version in backtrace reports. + if (String(VERSION_HASH).length() != 0) { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); + } else { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); + } fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); char **strings = backtrace_symbols(bt_buffer, size); if (strings) { @@ -115,6 +124,7 @@ static void handle_crash(int sig) { free(strings); } fprintf(stderr, "-- END OF BACKTRACE --\n"); + fprintf(stderr, "================================================================\n"); // Abort to pass the error to the OS abort(); diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 5b26b23806..a39941339a 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -2247,7 +2247,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, k->set_shift_pressed(true); } - Input::get_singleton()->accumulate_input_event(k); + Input::get_singleton()->parse_input_event(k); } memfree(utf8string); return; @@ -2396,7 +2396,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, } } - Input::get_singleton()->accumulate_input_event(k); + Input::get_singleton()->parse_input_event(k); } Atom DisplayServerX11::_process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property) const { @@ -2883,13 +2883,13 @@ void DisplayServerX11::process_events() { // in a spurious mouse motion event being sent to Godot; remember it to be able to filter it out xi.mouse_pos_to_filter = pos; } - Input::get_singleton()->accumulate_input_event(st); + Input::get_singleton()->parse_input_event(st); } else { if (!xi.state.has(index)) { // Defensive break; } xi.state.erase(index); - Input::get_singleton()->accumulate_input_event(st); + Input::get_singleton()->parse_input_event(st); } } break; @@ -2906,7 +2906,7 @@ void DisplayServerX11::process_events() { sd->set_index(index); sd->set_position(pos); sd->set_relative(pos - curr_pos_elem->value()); - Input::get_singleton()->accumulate_input_event(sd); + Input::get_singleton()->parse_input_event(sd); curr_pos_elem->value() = pos; } @@ -3058,7 +3058,7 @@ void DisplayServerX11::process_events() { st->set_index(E->key()); st->set_window_id(window_id); st->set_position(E->get()); - Input::get_singleton()->accumulate_input_event(st); + Input::get_singleton()->parse_input_event(st); } xi.state.clear(); #endif @@ -3156,7 +3156,7 @@ void DisplayServerX11::process_events() { mb->set_window_id(window_id_other); mb->set_position(Vector2(x, y)); mb->set_global_position(mb->get_position()); - Input::get_singleton()->accumulate_input_event(mb); + Input::get_singleton()->parse_input_event(mb); } break; } @@ -3164,7 +3164,7 @@ void DisplayServerX11::process_events() { } } - Input::get_singleton()->accumulate_input_event(mb); + Input::get_singleton()->parse_input_event(mb); } break; case MotionNotify: { @@ -3280,7 +3280,7 @@ void DisplayServerX11::process_events() { // this is so that the relative motion doesn't get messed up // after we regain focus. if (focused) { - Input::get_singleton()->accumulate_input_event(mm); + Input::get_singleton()->parse_input_event(mm); } else { // Propagate the event to the focused window, // because it's received only on the topmost window. @@ -3300,7 +3300,7 @@ void DisplayServerX11::process_events() { mm->set_position(pos_focused); mm->set_global_position(pos_focused); mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); - Input::get_singleton()->accumulate_input_event(mm); + Input::get_singleton()->parse_input_event(mm); break; } @@ -3433,7 +3433,7 @@ void DisplayServerX11::process_events() { */ } - Input::get_singleton()->flush_accumulated_events(); + Input::get_singleton()->flush_buffered_events(); } void DisplayServerX11::release_rendering_thread() { diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 08630be8b0..2c9801f512 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -273,7 +273,7 @@ String OS_LinuxBSD::get_cache_path() const { } } -String OS_LinuxBSD::get_system_dir(SystemDir p_dir) const { +String OS_LinuxBSD::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { String xdgparam; switch (p_dir) { diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index 1e06587322..35c80e3f9b 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -84,7 +84,7 @@ public: virtual String get_data_path() const override; virtual String get_cache_path() const override; - virtual String get_system_dir(SystemDir p_dir) const override; + virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override; virtual Error shell_open(String p_uri) override; diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm index 0f128d504f..31228b10b4 100644 --- a/platform/osx/crash_handler_osx.mm +++ b/platform/osx/crash_handler_osx.mm @@ -32,6 +32,8 @@ #include "core/config/project_settings.h" #include "core/os/os.h" +#include "core/version.h" +#include "core/version_hash.gen.h" #include "main/main.h" #include <string.h> @@ -85,11 +87,18 @@ static void handle_crash(int sig) { } // Dump the backtrace to stderr with a message to the user + fprintf(stderr, "\n================================================================\n"); fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig); if (OS::get_singleton()->get_main_loop()) OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); + // Print the engine version just before, so that people are reminded to include the version in backtrace reports. + if (String(VERSION_HASH).length() != 0) { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); + } else { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); + } fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); char **strings = backtrace_symbols(bt_buffer, size); if (strings) { @@ -148,6 +157,7 @@ static void handle_crash(int sig) { free(strings); } fprintf(stderr, "-- END OF BACKTRACE --\n"); + fprintf(stderr, "================================================================\n"); // Abort to pass the error to the OS abort(); diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index cc8400980a..43b7d7c1e0 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -690,7 +690,7 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, M mb->set_double_click([event clickCount] == 2); } - Input::get_singleton()->accumulate_input_event(mb); + Input::get_singleton()->parse_input_event(mb); } - (void)mouseDown:(NSEvent *)event { @@ -799,7 +799,7 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, M _get_key_modifier_state([event modifierFlags], mm); Input::get_singleton()->set_mouse_position(wd.mouse_pos); - Input::get_singleton()->accumulate_input_event(mm); + Input::get_singleton()->parse_input_event(mm); } - (void)rightMouseDown:(NSEvent *)event { @@ -875,7 +875,7 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, M ev->set_position(_get_mouse_pos(wd, [event locationInWindow])); ev->set_factor([event magnification] + 1.0); - Input::get_singleton()->accumulate_input_event(ev); + Input::get_singleton()->parse_input_event(ev); } - (void)viewDidChangeBackingProperties { @@ -1357,7 +1357,7 @@ inline void sendScrollEvent(DisplayServer::WindowID window_id, MouseButton butto DS_OSX->last_button_state |= (MouseButton)mask; sc->set_button_mask(DS_OSX->last_button_state); - Input::get_singleton()->accumulate_input_event(sc); + Input::get_singleton()->parse_input_event(sc); sc.instantiate(); sc->set_window_id(window_id); @@ -1369,7 +1369,7 @@ inline void sendScrollEvent(DisplayServer::WindowID window_id, MouseButton butto DS_OSX->last_button_state &= (MouseButton)~mask; sc->set_button_mask(DS_OSX->last_button_state); - Input::get_singleton()->accumulate_input_event(sc); + Input::get_singleton()->parse_input_event(sc); } inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy, int modifierFlags) { @@ -1384,7 +1384,7 @@ inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy pg->set_position(wd.mouse_pos); pg->set_delta(Vector2(-dx, -dy)); - Input::get_singleton()->accumulate_input_event(pg); + Input::get_singleton()->parse_input_event(pg); } - (void)scrollWheel:(NSEvent *)event { @@ -3198,7 +3198,7 @@ String DisplayServerOSX::keyboard_get_layout_name(int p_index) const { void DisplayServerOSX::_push_input(const Ref<InputEvent> &p_event) { Ref<InputEvent> ev = p_event; - Input::get_singleton()->accumulate_input_event(ev); + Input::get_singleton()->parse_input_event(ev); } void DisplayServerOSX::_release_pressed_events() { @@ -3253,7 +3253,7 @@ void DisplayServerOSX::_send_event(NSEvent *p_event) { k->set_physical_keycode(KEY_PERIOD); k->set_echo([p_event isARepeat]); - Input::get_singleton()->accumulate_input_event(k); + Input::get_singleton()->parse_input_event(k); } } } @@ -3331,7 +3331,7 @@ void DisplayServerOSX::process_events() { if (!drop_events) { _process_key_events(); - Input::get_singleton()->flush_accumulated_events(); + Input::get_singleton()->flush_buffered_events(); } for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { diff --git a/platform/osx/export/export_plugin.cpp b/platform/osx/export/export_plugin.cpp index 53ae1ea6fe..54a3104482 100644 --- a/platform/osx/export/export_plugin.cpp +++ b/platform/osx/export/export_plugin.cpp @@ -193,16 +193,16 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_ }; static const MacOSIconInfo icon_infos[] = { - { "ic10", "", true, 1024 }, //1024x1024 32-bit PNG and 512x512@2x 32-bit "retina" PNG + { "ic10", "", true, 1024 }, //1024×1024 32-bit PNG and 512×512@2x 32-bit "retina" PNG { "ic09", "", true, 512 }, //512×512 32-bit PNG - { "ic14", "", true, 512 }, //256x256@2x 32-bit "retina" PNG + { "ic14", "", true, 512 }, //256×256@2x 32-bit "retina" PNG { "ic08", "", true, 256 }, //256×256 32-bit PNG - { "ic13", "", true, 256 }, //128x128@2x 32-bit "retina" PNG - { "ic07", "", true, 128 }, //128x128 32-bit PNG - { "ic12", "", true, 64 }, //32x32@2x 32-bit "retina" PNG - { "ic11", "", true, 32 }, //16x16@2x 32-bit "retina" PNG - { "il32", "l8mk", false, 32 }, //32x32 24-bit RLE + 8-bit uncompressed mask - { "is32", "s8mk", false, 16 } //16x16 24-bit RLE + 8-bit uncompressed mask + { "ic13", "", true, 256 }, //128×128@2x 32-bit "retina" PNG + { "ic07", "", true, 128 }, //128×128 32-bit PNG + { "ic12", "", true, 64 }, //32×32@2× 32-bit "retina" PNG + { "ic11", "", true, 32 }, //16×16@2× 32-bit "retina" PNG + { "il32", "l8mk", false, 32 }, //32×32 24-bit RLE + 8-bit uncompressed mask + { "is32", "s8mk", false, 16 } //16×16 24-bit RLE + 8-bit uncompressed mask }; for (uint64_t i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) { @@ -931,7 +931,7 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String zipfi.tmz_date.tm_hour = time.hour; zipfi.tmz_date.tm_mday = date.day; zipfi.tmz_date.tm_min = time.minute; - zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, http://www.cplusplus.com/reference/ctime/tm/ + zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, https://www.cplusplus.com/reference/ctime/tm/ zipfi.tmz_date.tm_sec = time.second; zipfi.tmz_date.tm_year = date.year; zipfi.dosDate = 0; @@ -976,7 +976,7 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String zipfi.tmz_date.tm_hour = time.hour; zipfi.tmz_date.tm_mday = date.day; zipfi.tmz_date.tm_min = time.minute; - zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, http://www.cplusplus.com/reference/ctime/tm/ + zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, https://www.cplusplus.com/reference/ctime/tm/ zipfi.tmz_date.tm_sec = time.second; zipfi.tmz_date.tm_year = date.year; zipfi.dosDate = 0; diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp index d778271350..e67d2b0e91 100644 --- a/platform/osx/joypad_osx.cpp +++ b/platform/osx/joypad_osx.cpp @@ -143,6 +143,8 @@ void joypad::add_hid_element(IOHIDElementRef p_element) { switch (usage) { case kHIDUsage_Sim_Rudder: case kHIDUsage_Sim_Throttle: + case kHIDUsage_Sim_Accelerator: + case kHIDUsage_Sim_Brake: if (!has_element(cookie, &axis_elements)) { list = &axis_elements; } @@ -332,6 +334,13 @@ bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) { p_joy->add_hid_elements(array); CFRelease(array); } + // Xbox controller hat values start at 1 rather than 0. + p_joy->offset_hat = vendor == 0x45e && + (product_id == 0x0b05 || + product_id == 0x02e0 || + product_id == 0x02fd || + product_id == 0x0b13); + return true; } @@ -388,13 +397,16 @@ bool joypad::check_ff_features() { return false; } -static int process_hat_value(int p_min, int p_max, int p_value) { +static int process_hat_value(int p_min, int p_max, int p_value, bool p_offset_hat) { int range = (p_max - p_min + 1); int value = p_value - p_min; int hat_value = HatMask::HAT_MASK_CENTER; if (range == 4) { value *= 2; } + if (p_offset_hat) { + value -= 1; + } switch (value) { case 0: @@ -468,7 +480,7 @@ void JoypadOSX::process_joypads() { for (int j = 0; j < joy.hat_elements.size(); j++) { rec_element &elem = joy.hat_elements.write[j]; int value = joy.get_hid_element_state(&elem); - int hat_value = process_hat_value(elem.min, elem.max, value); + int hat_value = process_hat_value(elem.min, elem.max, value, joy.offset_hat); input->joy_hat(joy.id, (HatMask)hat_value); } diff --git a/platform/osx/joypad_osx.h b/platform/osx/joypad_osx.h index bf7e8949df..c060c3d523 100644 --- a/platform/osx/joypad_osx.h +++ b/platform/osx/joypad_osx.h @@ -64,6 +64,7 @@ struct joypad { Vector<rec_element> hat_elements; int id = 0; + bool offset_hat = false; io_service_t ffservice = 0; /* Interface for force feedback, 0 = no ff */ FFCONSTANTFORCE ff_constant_force; diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index 37d30add78..df41ccd892 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -84,7 +84,7 @@ public: virtual String get_bundle_resource_dir() const override; virtual String get_godot_dir_name() const override; - virtual String get_system_dir(SystemDir p_dir) const override; + virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override; Error shell_open(String p_uri) override; diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index c458a0264a..c6e35fee83 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -395,7 +395,7 @@ String OS_OSX::get_godot_dir_name() const { return String(VERSION_SHORT_NAME).capitalize(); } -String OS_OSX::get_system_dir(SystemDir p_dir) const { +String OS_OSX::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { NSSearchPathDirectory id; bool found = true; diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp index e2d507eddd..1b4dae207f 100644 --- a/platform/windows/crash_handler_windows.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -32,6 +32,8 @@ #include "core/config/project_settings.h" #include "core/os/os.h" +#include "core/version.h" +#include "core/version_hash.gen.h" #include "main/main.h" #ifdef CRASH_HANDLER_EXCEPTION @@ -127,6 +129,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { return EXCEPTION_CONTINUE_SEARCH; } + fprintf(stderr, "\n================================================================\n"); fprintf(stderr, "%s: Program crashed\n", __FUNCTION__); if (OS::get_singleton()->get_main_loop()) @@ -175,6 +178,12 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { msg = proj_settings->get("debug/settings/crash_handler/message"); } + // Print the engine version just before, so that people are reminded to include the version in backtrace reports. + if (String(VERSION_HASH).length() != 0) { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); + } else { + fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); + } fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); int n = 0; @@ -200,6 +209,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { } while (frame.AddrReturn.Offset != 0 && n < 256); fprintf(stderr, "-- END OF BACKTRACE --\n"); + fprintf(stderr, "================================================================\n"); SymCleanup(process); diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 9154f7749e..3961480d23 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -321,7 +321,7 @@ def configure_msvc(env, manual_msvc_config): def configure_mingw(env): # Workaround for MinGW. See: - # http://www.scons.org/wiki/LongCmdLinesOnWin32 + # https://www.scons.org/wiki/LongCmdLinesOnWin32 env.use_windows_spawn_fix() ## Build type diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 42a517879b..cf51caa6d5 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -854,12 +854,13 @@ Size2i DisplayServerWindows::window_get_size(WindowID p_window) const { ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); const WindowData &wd = windows[p_window]; + // GetClientRect() returns a zero rect for a minimized window, so we need to get the size in another way. if (wd.minimized) { return Size2(wd.width, wd.height); } RECT r; - if (GetClientRect(wd.hWnd, &r)) { // Only area inside of window border + if (GetClientRect(wd.hWnd, &r)) { // Retrieves area inside of window border. return Size2(r.right - r.left, r.bottom - r.top); } return Size2(); @@ -1527,7 +1528,7 @@ void DisplayServerWindows::process_events() { if (!drop_events) { _process_key_events(); - Input::get_singleton()->flush_accumulated_events(); + Input::get_singleton()->flush_buffered_events(); } } @@ -1738,7 +1739,7 @@ void DisplayServerWindows::_touch_event(WindowID p_window, bool p_pressed, float event->set_pressed(p_pressed); event->set_position(Vector2(p_x, p_y)); - Input::get_singleton()->accumulate_input_event(event); + Input::get_singleton()->parse_input_event(event); } void DisplayServerWindows::_drag_event(WindowID p_window, float p_x, float p_y, int idx) { @@ -1757,7 +1758,7 @@ void DisplayServerWindows::_drag_event(WindowID p_window, float p_x, float p_y, event->set_position(Vector2(p_x, p_y)); event->set_relative(Vector2(p_x, p_y) - curr->get()); - Input::get_singleton()->accumulate_input_event(event); + Input::get_singleton()->parse_input_event(event); curr->get() = Vector2(p_x, p_y); } @@ -1900,7 +1901,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } case WM_GETMINMAXINFO: { if (windows[window_id].resizable && !windows[window_id].fullscreen) { - Size2 decor = window_get_size(window_id) - window_get_real_size(window_id); // Size of window decorations + // Size of window decorations. + Size2 decor = window_get_real_size(window_id) - window_get_size(window_id); + MINMAXINFO *min_max_info = (MINMAXINFO *)lParam; if (windows[window_id].min_size != Size2()) { min_max_info->ptMinTrackSize.x = windows[window_id].min_size.x + decor.x; @@ -2022,7 +2025,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } if (windows[window_id].window_has_focus && mm->get_relative() != Vector2()) - Input::get_singleton()->accumulate_input_event(mm); + Input::get_singleton()->parse_input_event(mm); } delete[] lpb; } break; @@ -2111,7 +2114,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA old_x = mm->get_position().x; old_y = mm->get_position().y; if (windows[window_id].window_has_focus) - Input::get_singleton()->accumulate_input_event(mm); + Input::get_singleton()->parse_input_event(mm); } return 0; } @@ -2258,7 +2261,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA old_x = mm->get_position().x; old_y = mm->get_position().y; if (windows[window_id].window_has_focus) { - Input::get_singleton()->accumulate_input_event(mm); + Input::get_singleton()->parse_input_event(mm); } return 0; //Pointer event handled return 0 to avoid duplicate WM_MOUSEMOVE event @@ -2364,7 +2367,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA old_x = mm->get_position().x; old_y = mm->get_position().y; if (windows[window_id].window_has_focus) - Input::get_singleton()->accumulate_input_event(mm); + Input::get_singleton()->parse_input_event(mm); } break; case WM_LBUTTONDOWN: @@ -2533,7 +2536,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mb->set_global_position(mb->get_position()); - Input::get_singleton()->accumulate_input_event(mb); + Input::get_singleton()->parse_input_event(mb); if (mb->is_pressed() && mb->get_button_index() > 3 && mb->get_button_index() < 8) { //send release for mouse wheel Ref<InputEventMouseButton> mbd = mb->duplicate(); @@ -2541,7 +2544,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA last_button_state &= (MouseButton) ~(1 << (mbd->get_button_index() - 1)); mbd->set_button_mask(last_button_state); mbd->set_pressed(false); - Input::get_singleton()->accumulate_input_event(mbd); + Input::get_singleton()->parse_input_event(mbd); } } break; @@ -2563,10 +2566,13 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; case WM_SIZE: { - // Ignore size when a SIZE_MINIMIZED event is triggered + // Ignore window size change when a SIZE_MINIMIZED event is triggered. if (wParam != SIZE_MINIMIZED) { + // The new width and height of the client area. int window_w = LOWORD(lParam); int window_h = HIWORD(lParam); + + // Set new value to the size if it isn't preserved. if (window_w > 0 && window_h > 0 && !windows[window_id].preserve_window_size) { windows[window_id].width = window_w; windows[window_id].height = window_h; @@ -2577,29 +2583,38 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } #endif - } else { + } else { // If the size is preserved. windows[window_id].preserve_window_size = false; + + // Restore the old size. window_set_size(Size2(windows[window_id].width, windows[window_id].height), window_id); } - } else { + } else { // When the window has been minimized, preserve its size. windows[window_id].preserve_window_size = true; } + // Call windows rect change callback. if (!windows[window_id].rect_changed_callback.is_null()) { Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height); - Variant *sizep = &size; + Variant *size_ptr = &size; Variant ret; Callable::CallError ce; - windows[window_id].rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); + windows[window_id].rect_changed_callback.call((const Variant **)&size_ptr, 1, ret, ce); } + // The window has been maximized. if (wParam == SIZE_MAXIMIZED) { windows[window_id].maximized = true; windows[window_id].minimized = false; - } else if (wParam == SIZE_MINIMIZED) { + } + // The window has been minimized. + else if (wParam == SIZE_MINIMIZED) { windows[window_id].maximized = false; windows[window_id].minimized = true; - } else if (wParam == SIZE_RESTORED) { + windows[window_id].preserve_window_size = false; + } + // The window has been resized, but neither the SIZE_MINIMIZED nor SIZE_MAXIMIZED value applies. + else if (wParam == SIZE_RESTORED) { windows[window_id].maximized = false; windows[window_id].minimized = false; } @@ -2626,7 +2641,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA ZeroMemory(dib_data, dib_size.x * dib_size.y * 4); } #endif - //return 0; // Jump Back } break; case WM_ENTERSIZEMOVE: { @@ -2866,7 +2880,7 @@ void DisplayServerWindows::_process_key_events() { if (k->get_unicode() < 32) k->set_unicode(0); - Input::get_singleton()->accumulate_input_event(k); + Input::get_singleton()->parse_input_event(k); } //do nothing @@ -2924,7 +2938,7 @@ void DisplayServerWindows::_process_key_events() { k->set_echo((ke.uMsg == WM_KEYDOWN && (ke.lParam & (1 << 30)))); - Input::get_singleton()->accumulate_input_event(k); + Input::get_singleton()->parse_input_event(k); } break; } diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 2c8afaf7de..2a0a509d43 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -681,7 +681,7 @@ String OS_Windows::get_godot_dir_name() const { return String(VERSION_SHORT_NAME).capitalize(); } -String OS_Windows::get_system_dir(SystemDir p_dir) const { +String OS_Windows::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { KNOWNFOLDERID id; switch (p_dir) { diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index ea0c263b78..c4a2eda8f4 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -150,7 +150,7 @@ public: virtual String get_cache_path() const override; virtual String get_godot_dir_name() const override; - virtual String get_system_dir(SystemDir p_dir) const override; + virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override; virtual String get_user_data_dir() const override; virtual String get_unique_id() const override; |