summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/android/SCsub1
-rw-r--r--platform/android/audio_driver_opensl.cpp4
-rw-r--r--platform/android/dir_access_jandroid.cpp8
-rw-r--r--platform/android/dir_access_jandroid.h4
-rw-r--r--platform/android/display_server_android.cpp60
-rw-r--r--platform/android/display_server_android.h13
-rw-r--r--platform/android/export/export_plugin.cpp73
-rw-r--r--platform/android/export/gradle_export_util.cpp30
-rw-r--r--platform/android/file_access_android.cpp8
-rw-r--r--platform/android/file_access_android.h5
-rw-r--r--platform/android/java/app/AndroidManifest.xml7
-rw-r--r--platform/android/java/editor/src/main/AndroidManifest.xml9
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.java14
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.java3
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java60
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java29
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotLib.java5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java9
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java134
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java102
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java298
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/tts/GodotUtterance.java55
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java4
-rw-r--r--platform/android/java_godot_io_wrapper.cpp58
-rw-r--r--platform/android/java_godot_io_wrapper.h10
-rw-r--r--platform/android/java_godot_lib_jni.cpp8
-rw-r--r--platform/android/java_godot_lib_jni.h1
-rw-r--r--platform/android/os_android.cpp12
-rw-r--r--platform/android/os_android.h10
-rw-r--r--platform/android/tts_android.cpp189
-rw-r--r--platform/android/tts_android.h67
-rw-r--r--platform/iphone/SCsub1
-rw-r--r--platform/iphone/display_server_iphone.h13
-rw-r--r--platform/iphone/display_server_iphone.mm73
-rw-r--r--platform/iphone/export/export_plugin.cpp98
-rw-r--r--platform/iphone/export/export_plugin.h10
-rw-r--r--platform/iphone/godot_view_gesture_recognizer.mm1
-rw-r--r--platform/iphone/ios.h11
-rw-r--r--platform/iphone/ios.mm99
-rw-r--r--platform/iphone/os_iphone.h8
-rw-r--r--platform/iphone/os_iphone.mm19
-rw-r--r--platform/iphone/tts_ios.h63
-rw-r--r--platform/iphone/tts_ios.mm164
-rw-r--r--platform/javascript/api/javascript_tools_editor_plugin.cpp29
-rw-r--r--platform/javascript/detect.py2
-rw-r--r--platform/javascript/display_server_javascript.cpp88
-rw-r--r--platform/javascript/display_server_javascript.h18
-rw-r--r--platform/javascript/export/export_plugin.cpp42
-rw-r--r--platform/javascript/export/export_server.h7
-rw-r--r--platform/javascript/godot_js.h10
-rw-r--r--platform/javascript/godot_webgl2.h37
-rw-r--r--platform/javascript/http_client_javascript.cpp2
-rw-r--r--platform/javascript/javascript_singleton.cpp2
-rw-r--r--platform/javascript/js/libs/library_godot_display.js91
-rw-r--r--platform/javascript/js/libs/library_godot_os.js4
-rw-r--r--platform/javascript/os_javascript.cpp11
-rw-r--r--platform/javascript/os_javascript.h3
-rw-r--r--platform/javascript/platform_config.h2
-rw-r--r--platform/linuxbsd/SCsub3
-rw-r--r--platform/linuxbsd/crash_handler_linuxbsd.cpp22
-rw-r--r--platform/linuxbsd/detect.py23
-rw-r--r--platform/linuxbsd/detect_prime_x11.cpp2
-rw-r--r--platform/linuxbsd/display_server_x11.cpp156
-rw-r--r--platform/linuxbsd/display_server_x11.h65
-rw-r--r--platform/linuxbsd/export/export_plugin.cpp12
-rw-r--r--platform/linuxbsd/gl_manager_x11.h15
-rw-r--r--platform/linuxbsd/joypad_linux.cpp338
-rw-r--r--platform/linuxbsd/joypad_linux.h41
-rw-r--r--platform/linuxbsd/os_linuxbsd.cpp109
-rw-r--r--platform/linuxbsd/os_linuxbsd.h4
-rw-r--r--platform/linuxbsd/speechd-so_wrap.c881
-rw-r--r--platform/linuxbsd/speechd-so_wrap.h330
-rw-r--r--platform/linuxbsd/tts_linux.cpp261
-rw-r--r--platform/linuxbsd/tts_linux.h78
-rw-r--r--platform/osx/SCsub1
-rw-r--r--platform/osx/crash_handler_osx.mm22
-rw-r--r--platform/osx/dir_access_osx.mm4
-rw-r--r--platform/osx/display_server_osx.h22
-rw-r--r--platform/osx/display_server_osx.mm124
-rw-r--r--platform/osx/export/codesign.cpp69
-rw-r--r--platform/osx/export/codesign.h14
-rw-r--r--platform/osx/export/export_plugin.cpp92
-rw-r--r--platform/osx/export/export_plugin.h4
-rw-r--r--platform/osx/export/lipo.cpp31
-rw-r--r--platform/osx/export/lipo.h2
-rw-r--r--platform/osx/export/macho.cpp48
-rw-r--r--platform/osx/export/macho.h8
-rw-r--r--platform/osx/export/plist.cpp5
-rw-r--r--platform/osx/gl_manager_osx_legacy.h6
-rw-r--r--platform/osx/joypad_osx.cpp6
-rw-r--r--platform/osx/joypad_osx.h12
-rw-r--r--platform/osx/key_mapping_osx.mm4
-rw-r--r--platform/osx/os_osx.h2
-rw-r--r--platform/osx/os_osx.mm42
-rw-r--r--platform/osx/tts_osx.h71
-rw-r--r--platform/osx/tts_osx.mm266
-rw-r--r--platform/osx/vulkan_context_osx.h2
-rw-r--r--platform/uwp/export/app_packager.cpp50
-rw-r--r--platform/uwp/export/app_packager.h8
-rw-r--r--platform/uwp/export/export_plugin.cpp8
-rw-r--r--platform/uwp/export/export_plugin.h26
-rw-r--r--platform/uwp/joypad_uwp.h2
-rw-r--r--platform/uwp/os_uwp.cpp13
-rw-r--r--platform/uwp/os_uwp.h15
-rw-r--r--platform/windows/SCsub1
-rw-r--r--platform/windows/crash_handler_windows.cpp44
-rw-r--r--platform/windows/detect.py2
-rw-r--r--platform/windows/display_server_windows.cpp121
-rw-r--r--platform/windows/display_server_windows.h17
-rw-r--r--platform/windows/export/export_plugin.cpp82
-rw-r--r--platform/windows/export/export_plugin.h3
-rw-r--r--platform/windows/gl_manager_windows.h13
-rw-r--r--platform/windows/godot_windows.cpp18
-rw-r--r--platform/windows/joypad_windows.cpp10
-rw-r--r--platform/windows/joypad_windows.h4
-rw-r--r--platform/windows/os_windows.cpp139
-rw-r--r--platform/windows/os_windows.h9
-rw-r--r--platform/windows/tts_windows.cpp269
-rw-r--r--platform/windows/tts_windows.h80
120 files changed, 5109 insertions, 1224 deletions
diff --git a/platform/android/SCsub b/platform/android/SCsub
index 1a3c158d2e..ad226255bc 100644
--- a/platform/android/SCsub
+++ b/platform/android/SCsub
@@ -8,6 +8,7 @@ android_files = [
"file_access_android.cpp",
"audio_driver_opensl.cpp",
"dir_access_jandroid.cpp",
+ "tts_android.cpp",
"thread_jandroid.cpp",
"net_socket_android.cpp",
"java_godot_lib_jni.cpp",
diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp
index 8495d2cc18..dcaa586e3b 100644
--- a/platform/android/audio_driver_opensl.cpp
+++ b/platform/android/audio_driver_opensl.cpp
@@ -75,7 +75,7 @@ void AudioDriverOpenSL::_buffer_callback(
void AudioDriverOpenSL::_buffer_callbacks(
SLAndroidSimpleBufferQueueItf queueItf,
void *pContext) {
- AudioDriverOpenSL *ad = (AudioDriverOpenSL *)pContext;
+ AudioDriverOpenSL *ad = static_cast<AudioDriverOpenSL *>(pContext);
ad->_buffer_callback(queueItf);
}
@@ -208,7 +208,7 @@ void AudioDriverOpenSL::_record_buffer_callback(SLAndroidSimpleBufferQueueItf qu
}
void AudioDriverOpenSL::_record_buffer_callbacks(SLAndroidSimpleBufferQueueItf queueItf, void *pContext) {
- AudioDriverOpenSL *ad = (AudioDriverOpenSL *)pContext;
+ AudioDriverOpenSL *ad = static_cast<AudioDriverOpenSL *>(pContext);
ad->_record_buffer_callback(queueItf);
}
diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp
index 7fb4f54fca..e2b1c757d6 100644
--- a/platform/android/dir_access_jandroid.cpp
+++ b/platform/android/dir_access_jandroid.cpp
@@ -42,7 +42,7 @@ jmethodID DirAccessJAndroid::_dir_next = nullptr;
jmethodID DirAccessJAndroid::_dir_close = nullptr;
jmethodID DirAccessJAndroid::_dir_is_dir = nullptr;
-DirAccess *DirAccessJAndroid::create_fs() {
+Ref<DirAccess> DirAccessJAndroid::create_fs() {
return memnew(DirAccessJAndroid);
}
@@ -143,7 +143,7 @@ Error DirAccessJAndroid::change_dir(String p_dir) {
return OK;
}
-String DirAccessJAndroid::get_current_dir(bool p_include_drive) {
+String DirAccessJAndroid::get_current_dir(bool p_include_drive) const {
return "res://" + current_dir;
}
@@ -155,9 +155,9 @@ bool DirAccessJAndroid::file_exists(String p_file) {
sd = current_dir.plus_file(p_file);
}
- FileAccessAndroid *f = memnew(FileAccessAndroid);
+ Ref<FileAccessAndroid> f;
+ f.instantiate();
bool exists = f->file_exists(sd);
- memdelete(f);
return exists;
}
diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h
index 4f4a984b12..3d3c9f9223 100644
--- a/platform/android/dir_access_jandroid.h
+++ b/platform/android/dir_access_jandroid.h
@@ -51,7 +51,7 @@ class DirAccessJAndroid : public DirAccess {
String current_dir;
String current;
- static DirAccess *create_fs();
+ static Ref<DirAccess> create_fs();
public:
virtual Error list_dir_begin(); ///< This starts dir listing
@@ -64,7 +64,7 @@ public:
virtual String get_drive(int p_drive);
virtual Error change_dir(String p_dir); ///< can be relative or absolute, return false on success
- virtual String get_current_dir(bool p_include_drive = true); ///< return current dir location
+ virtual String get_current_dir(bool p_include_drive = true) const; ///< return current dir location
virtual bool file_exists(String p_file);
virtual bool dir_exists(String p_dir);
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index a7a8801bdc..d414ea5824 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -34,6 +34,7 @@
#include "java_godot_io_wrapper.h"
#include "java_godot_wrapper.h"
#include "os_android.h"
+#include "tts_android.h"
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_device_vulkan.h"
@@ -42,7 +43,7 @@
#endif
DisplayServerAndroid *DisplayServerAndroid::get_singleton() {
- return (DisplayServerAndroid *)DisplayServer::get_singleton();
+ return static_cast<DisplayServerAndroid *>(DisplayServer::get_singleton());
}
bool DisplayServerAndroid::has_feature(Feature p_feature) const {
@@ -63,6 +64,7 @@ bool DisplayServerAndroid::has_feature(Feature p_feature) const {
case FEATURE_ORIENTATION:
case FEATURE_TOUCHSCREEN:
case FEATURE_VIRTUAL_KEYBOARD:
+ case FEATURE_TEXT_TO_SPEECH:
return true;
default:
return false;
@@ -73,6 +75,34 @@ String DisplayServerAndroid::get_name() const {
return "Android";
}
+bool DisplayServerAndroid::tts_is_speaking() const {
+ return TTS_Android::is_speaking();
+}
+
+bool DisplayServerAndroid::tts_is_paused() const {
+ return TTS_Android::is_paused();
+}
+
+Array DisplayServerAndroid::tts_get_voices() const {
+ return TTS_Android::get_voices();
+}
+
+void DisplayServerAndroid::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
+ TTS_Android::speak(p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_interrupt);
+}
+
+void DisplayServerAndroid::tts_pause() {
+ TTS_Android::pause();
+}
+
+void DisplayServerAndroid::tts_resume() {
+ TTS_Android::resume();
+}
+
+void DisplayServerAndroid::tts_stop() {
+ TTS_Android::stop();
+}
+
void DisplayServerAndroid::clipboard_set(const String &p_text) {
GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
ERR_FAIL_COND(!godot_java);
@@ -106,6 +136,18 @@ bool DisplayServerAndroid::clipboard_has() const {
}
}
+Array DisplayServerAndroid::get_display_cutouts() const {
+ GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
+ ERR_FAIL_NULL_V(godot_io_java, Array());
+ return godot_io_java->get_display_cutouts();
+}
+
+Rect2i DisplayServerAndroid::get_display_safe_area() const {
+ GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
+ ERR_FAIL_NULL_V(godot_io_java, Rect2i());
+ return godot_io_java->get_display_safe_area();
+}
+
void DisplayServerAndroid::screen_set_keep_on(bool p_enable) {
GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
ERR_FAIL_COND(!godot_java);
@@ -147,11 +189,8 @@ Size2i DisplayServerAndroid::screen_get_size(int p_screen) const {
}
Rect2i DisplayServerAndroid::screen_get_usable_rect(int p_screen) const {
- GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
- ERR_FAIL_COND_V(!godot_io_java, Rect2i());
- int xywh[4];
- godot_io_java->screen_get_usable_rect(xywh);
- return Rect2i(xywh[0], xywh[1], xywh[2], xywh[3]);
+ Size2i display_size = OS_Android::get_singleton()->get_display_size();
+ return Rect2i(0, 0, display_size.width, display_size.height);
}
int DisplayServerAndroid::screen_get_dpi(int p_screen) const {
@@ -161,6 +200,13 @@ int DisplayServerAndroid::screen_get_dpi(int p_screen) const {
return godot_io_java->get_screen_dpi();
}
+float DisplayServerAndroid::screen_get_scale(int p_screen) const {
+ GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
+ ERR_FAIL_COND_V(!godot_io_java, 1.0f);
+
+ return godot_io_java->get_scaled_density();
+}
+
float DisplayServerAndroid::screen_get_refresh_rate(int p_screen) const {
GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
if (!godot_io_java) {
@@ -270,7 +316,7 @@ int64_t DisplayServerAndroid::window_get_native_handle(HandleType p_handle_type,
return 0; // Not supported.
}
case WINDOW_HANDLE: {
- return (int64_t)((OS_Android *)OS::get_singleton())->get_godot_java()->get_activity();
+ return reinterpret_cast<int64_t>(static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity());
}
case WINDOW_VIEW: {
return 0; // Not supported.
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index 23077a6529..65bf2ec1a8 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -91,10 +91,22 @@ public:
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
+ virtual bool tts_is_speaking() const override;
+ virtual bool tts_is_paused() const override;
+ virtual Array tts_get_voices() const override;
+
+ virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
+ virtual void tts_pause() override;
+ virtual void tts_resume() override;
+ virtual void tts_stop() override;
+
virtual void clipboard_set(const String &p_text) override;
virtual String clipboard_get() const override;
virtual bool clipboard_has() const override;
+ virtual Array get_display_cutouts() const override;
+ virtual Rect2i get_display_safe_area() const override;
+
virtual void screen_set_keep_on(bool p_enable) override;
virtual bool screen_is_kept_on() const override;
@@ -106,6 +118,7 @@ public:
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index df3693ba61..d357cd586e 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -249,7 +249,7 @@ static const int DEFAULT_TARGET_SDK_VERSION = 30; // Should match the value in '
const String SDK_VERSION_RANGE = vformat("%s,%s,1", DEFAULT_MIN_SDK_VERSION, DEFAULT_TARGET_SDK_VERSION);
void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
- EditorExportPlatformAndroid *ea = (EditorExportPlatformAndroid *)ud;
+ EditorExportPlatformAndroid *ea = static_cast<EditorExportPlatformAndroid *>(ud);
while (!ea->quit_request.is_set()) {
// Check for plugins updates
@@ -596,8 +596,8 @@ Vector<String> EditorExportPlatformAndroid::get_abis() {
/// List the gdap files in the directory specified by the p_path parameter.
Vector<String> EditorExportPlatformAndroid::list_gdap_files(const String &p_path) {
Vector<String> dir_files;
- DirAccessRef da = DirAccess::open(p_path);
- if (da) {
+ Ref<DirAccess> da = DirAccess::open(p_path);
+ if (da.is_valid()) {
da->list_dir_begin();
while (true) {
String file = da->get_next();
@@ -685,7 +685,7 @@ Error EditorExportPlatformAndroid::save_apk_so(void *p_userdata, const SharedObj
ERR_PRINT(err);
return FAILED;
}
- APKExportData *ed = (APKExportData *)p_userdata;
+ APKExportData *ed = static_cast<APKExportData *>(p_userdata);
Vector<String> abis = get_abis();
bool exported = false;
for (int i = 0; i < p_so.tags.size(); ++i) {
@@ -710,7 +710,7 @@ Error EditorExportPlatformAndroid::save_apk_so(void *p_userdata, const SharedObj
}
Error EditorExportPlatformAndroid::save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
- APKExportData *ed = (APKExportData *)p_userdata;
+ APKExportData *ed = static_cast<APKExportData *>(p_userdata);
String dst_path = p_path.replace_first("res://", "assets/");
store_in_apk(ed, dst_path, p_data, _should_compress_asset(p_path, p_data) ? Z_DEFLATED : 0);
@@ -725,7 +725,7 @@ Error EditorExportPlatformAndroid::copy_gradle_so(void *p_userdata, const Shared
ERR_FAIL_COND_V_MSG(!p_so.path.get_file().begins_with("lib"), FAILED,
"Android .so file names must start with \"lib\", but got: " + p_so.path);
Vector<String> abis = get_abis();
- CustomExportData *export_data = (CustomExportData *)p_userdata;
+ CustomExportData *export_data = static_cast<CustomExportData *>(p_userdata);
bool exported = false;
for (int i = 0; i < p_so.tags.size(); ++i) {
int abi_index = abis.find(p_so.tags[i]);
@@ -863,6 +863,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
bool classify_as_game = p_preset->get("package/classify_as_game");
bool retain_data_on_uninstall = p_preset->get("package/retain_data_on_uninstall");
bool exclude_from_recents = p_preset->get("package/exclude_from_recents");
+ bool is_resizeable = bool(GLOBAL_GET("display/window/size/resizable"));
Vector<String> perms;
// Write permissions into the perms variable.
@@ -980,6 +981,10 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
encode_uint32(exclude_from_recents, &p_manifest.write[iofs + 16]);
}
+ if (tname == "activity" && attrname == "resizeableActivity") {
+ encode_uint32(is_resizeable, &p_manifest.write[iofs + 16]);
+ }
+
if (tname == "supports-screens") {
if (attrname == "smallScreens") {
encode_uint32(screen_support_small ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
@@ -995,16 +1000,23 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
}
}
- if (tname == "meta-data" && attrname == "name" && value == "xr_hand_tracking_metadata_name") {
- if (xr_mode_index == XR_MODE_OPENXR && hand_tracking_index > XR_HAND_TRACKING_NONE) {
+ // Hand tracking related configurations
+ if (xr_mode_index == XR_MODE_OPENXR && hand_tracking_index > XR_HAND_TRACKING_NONE) {
+ if (tname == "meta-data" && attrname == "name" && value == "xr_hand_tracking_metadata_name") {
string_table.write[attr_value] = "com.oculus.handtracking.frequency";
}
- }
- if (tname == "meta-data" && attrname == "value" && value == "xr_hand_tracking_metadata_value") {
- if (xr_mode_index == XR_MODE_OPENXR && hand_tracking_index > XR_HAND_TRACKING_NONE) {
+ if (tname == "meta-data" && attrname == "value" && value == "xr_hand_tracking_metadata_value") {
string_table.write[attr_value] = (hand_tracking_frequency_index == XR_HAND_TRACKING_FREQUENCY_LOW ? "LOW" : "HIGH");
}
+
+ if (tname == "meta-data" && attrname == "name" && value == "xr_hand_tracking_version_name") {
+ string_table.write[attr_value] = "com.oculus.handtracking.version";
+ }
+
+ if (tname == "meta-data" && attrname == "name" && value == "xr_hand_tracking_version_value") {
+ string_table.write[attr_value] = "V2.0";
+ }
}
iofs += 20;
@@ -2003,7 +2015,7 @@ String EditorExportPlatformAndroid::get_apksigner_path() {
Error errn;
String build_tools_dir = sdk_path.plus_file("build-tools");
- DirAccessRef da = DirAccess::open(build_tools_dir, &errn);
+ Ref<DirAccess> da = DirAccess::open(build_tools_dir, &errn);
if (errn != OK) {
print_error("Unable to open Android 'build-tools' directory.");
return apksigner_path;
@@ -2120,7 +2132,7 @@ bool EditorExportPlatformAndroid::can_export(const Ref<EditorExportPreset> &p_pr
} else {
Error errn;
// Check for the platform-tools directory.
- DirAccessRef da = DirAccess::open(sdk_path.plus_file("platform-tools"), &errn);
+ Ref<DirAccess> da = DirAccess::open(sdk_path.plus_file("platform-tools"), &errn);
if (errn != OK) {
err += TTR("Invalid Android SDK path in Editor Settings.");
err += TTR("Missing 'platform-tools' directory!");
@@ -2138,7 +2150,7 @@ bool EditorExportPlatformAndroid::can_export(const Ref<EditorExportPreset> &p_pr
}
// Check for the build-tools directory.
- DirAccessRef build_tools_da = DirAccess::open(sdk_path.plus_file("build-tools"), &errn);
+ Ref<DirAccess> build_tools_da = DirAccess::open(sdk_path.plus_file("build-tools"), &errn);
if (errn != OK) {
err += TTR("Invalid Android SDK path in Editor Settings.");
err += TTR("Missing 'build-tools' directory!");
@@ -2417,12 +2429,12 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre
}
void EditorExportPlatformAndroid::_clear_assets_directory() {
- DirAccessRef da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ Ref<DirAccess> da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES);
// Clear the APK assets directory
if (da_res->dir_exists(APK_ASSETS_DIRECTORY)) {
print_verbose("Clearing APK assets directory..");
- DirAccessRef da_assets = DirAccess::open(APK_ASSETS_DIRECTORY);
+ Ref<DirAccess> da_assets = DirAccess::open(APK_ASSETS_DIRECTORY);
da_assets->erase_contents_recursive();
da_res->remove(APK_ASSETS_DIRECTORY);
}
@@ -2430,7 +2442,7 @@ void EditorExportPlatformAndroid::_clear_assets_directory() {
// Clear the AAB assets directory
if (da_res->dir_exists(AAB_ASSETS_DIRECTORY)) {
print_verbose("Clearing AAB assets directory..");
- DirAccessRef da_assets = DirAccess::open(AAB_ASSETS_DIRECTORY);
+ Ref<DirAccess> da_assets = DirAccess::open(AAB_ASSETS_DIRECTORY);
da_assets->erase_contents_recursive();
da_res->remove(AAB_ASSETS_DIRECTORY);
}
@@ -2450,7 +2462,7 @@ void EditorExportPlatformAndroid::_remove_copied_libs() {
ERR_FAIL_COND_MSG(error, "Error parsing \"" + libs_json + "\" on line " + itos(json.get_error_line()) + ": " + json.get_error_message());
Vector<String> libs = json.get_data();
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
for (int i = 0; i < libs.size(); i++) {
print_verbose("Removing previously installed library " + libs[i]);
da->remove(libs[i]);
@@ -2539,14 +2551,13 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
//test that installed build version is alright
{
print_verbose("Checking build version..");
- FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ);
- if (!f) {
+ Ref<FileAccess> f = FileAccess::open("res://android/.build_version", FileAccess::READ);
+ if (f.is_null()) {
EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu."));
return ERR_UNCONFIGURED;
}
String version = f->get_line().strip_edges();
print_verbose("- build version: " + version);
- f->close();
if (version != VERSION_FULL_CONFIG) {
EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n Template installed: %s\n Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG));
return ERR_UNCONFIGURED;
@@ -2582,10 +2593,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
return err;
}
if (user_data.libs.size() > 0) {
- FileAccessRef fa = FileAccess::open(GDNATIVE_LIBS_PATH, FileAccess::WRITE);
+ Ref<FileAccess> fa = FileAccess::open(GDNATIVE_LIBS_PATH, FileAccess::WRITE);
JSON json;
fa->store_string(json.stringify(user_data.libs, "\t"));
- fa->close();
}
} else {
print_verbose("Saving apk expansion file..");
@@ -2765,8 +2775,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
return ERR_FILE_BAD_PATH;
}
- FileAccess *src_f = nullptr;
- zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
+ zlib_filefunc_def io = zipio_create_io();
if (ep.step(TTR("Creating APK..."), 0)) {
return ERR_SKIP;
@@ -2780,9 +2789,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
int ret = unzGoToFirstFile(pkg);
- zlib_filefunc_def io2 = io;
- FileAccess *dst_f = nullptr;
- io2.opaque = &dst_f;
+ zlib_filefunc_def io2 = zipio_create_io();
String tmp_unaligned_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpexport-unaligned." + uitos(OS::get_singleton()->get_unix_time()) + ".apk");
@@ -2807,6 +2814,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
unz_file_info info;
char fname[16384];
ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);
+ if (ret != UNZ_OK) {
+ break;
+ }
bool skip = false;
@@ -2975,9 +2985,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
ret = unzGoToFirstFile(tmp_unaligned);
- io2 = io;
- dst_f = nullptr;
- io2.opaque = &dst_f;
+ io2 = zipio_create_io();
zipFile final_apk = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io2);
// Take files from the unaligned APK and write them out to the aligned one
@@ -2991,6 +2999,9 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
char fname[16384];
char extra[16384];
ret = unzGetCurrentFileInfo(tmp_unaligned, &info, fname, 16384, extra, 16384 - ZIP_ALIGNMENT, nullptr, 0);
+ if (ret != UNZ_OK) {
+ break;
+ }
String file = String::utf8(fname);
diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp
index ab915a5f85..d9574a1a52 100644
--- a/platform/android/export/gradle_export_util.cpp
+++ b/platform/android/export/gradle_export_util.cpp
@@ -75,8 +75,8 @@ String _get_android_orientation_label(DisplayServer::ScreenOrientation screen_or
// Utility method used to create a directory.
Error create_directory(const String &p_dir) {
if (!DirAccess::exists(p_dir)) {
- DirAccessRef filesystem_da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
- ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'.");
+ Ref<DirAccess> filesystem_da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ ERR_FAIL_COND_V_MSG(filesystem_da.is_null(), ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'.");
Error err = filesystem_da->make_dir_recursive(p_dir);
ERR_FAIL_COND_V_MSG(err, ERR_CANT_CREATE, "Cannot create directory '" + p_dir + "'.");
}
@@ -91,10 +91,9 @@ Error store_file_at_path(const String &p_path, const Vector<uint8_t> &p_data) {
if (err != OK) {
return err;
}
- FileAccess *fa = FileAccess::open(p_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(!fa, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");
+ Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");
fa->store_buffer(p_data.ptr(), p_data.size());
- memdelete(fa);
return OK;
}
@@ -109,10 +108,9 @@ Error store_string_at_path(const String &p_path, const String &p_data) {
}
return err;
}
- FileAccess *fa = FileAccess::open(p_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(!fa, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");
+ Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V_MSG(fa.is_null(), ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");
fa->store_string(p_data);
- memdelete(fa);
return OK;
}
@@ -122,7 +120,7 @@ Error store_string_at_path(const String &p_path, const String &p_data) {
// It's functionality mirrors that of the method save_apk_file.
// This method will be called ONLY when custom build is enabled.
Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
- CustomExportData *export_data = (CustomExportData *)p_userdata;
+ CustomExportData *export_data = static_cast<CustomExportData *>(p_userdata);
String dst_path = p_path.replace_first("res://", export_data->assets_directory + "/");
print_verbose("Saving project files from " + p_path + " into " + dst_path);
Error err = store_file_at_path(dst_path, p_data);
@@ -152,8 +150,8 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset
store_string_at_path("res://android/build/res/values/godot_project_name_string.xml", processed_default_xml_string);
// Searches the Gradle project res/ directory to find all supported locales
- DirAccessRef da = DirAccess::open("res://android/build/res");
- if (!da) {
+ Ref<DirAccess> da = DirAccess::open("res://android/build/res");
+ if (da.is_null()) {
if (OS::get_singleton()->is_stdout_verbose()) {
print_error("Unable to open Android resources directory.");
}
@@ -253,11 +251,13 @@ String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
String orientation = _get_android_orientation_label(DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation"))));
String manifest_activity_text = vformat(
" <activity android:name=\"com.godot.game.GodotApp\" "
- "tools:replace=\"android:screenOrientation,android:excludeFromRecents\" "
+ "tools:replace=\"android:screenOrientation,android:excludeFromRecents,android:resizeableActivity\" "
"android:excludeFromRecents=\"%s\" "
- "android:screenOrientation=\"%s\">\n",
+ "android:screenOrientation=\"%s\" "
+ "android:resizeableActivity=\"%s\">\n",
bool_to_string(p_preset->get("package/exclude_from_recents")),
- orientation);
+ orientation,
+ bool_to_string(bool(GLOBAL_GET("display/window/size/resizable"))));
if (uses_xr) {
manifest_activity_text += " <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"true\" />\n";
} else {
@@ -279,6 +279,7 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_
" android:requestLegacyExternalStorage=\"%s\"\n"
" tools:replace=\"android:allowBackup,android:isGame,android:hasFragileUserData,android:requestLegacyExternalStorage\"\n"
" tools:ignore=\"GoogleAppIndexingWarning\">\n\n"
+ " <meta-data tools:node=\"remove\" android:name=\"xr_hand_tracking_version_name\" />\n"
" <meta-data tools:node=\"remove\" android:name=\"xr_hand_tracking_metadata_name\" />\n",
bool_to_string(p_preset->get("user_data_backup/allow")),
bool_to_string(p_preset->get("package/classify_as_game")),
@@ -293,6 +294,7 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_
manifest_application_text += vformat(
" <meta-data tools:node=\"replace\" android:name=\"com.oculus.handtracking.frequency\" android:value=\"%s\" />\n",
hand_tracking_frequency);
+ manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.oculus.handtracking.version\" android:value=\"V2.0\" />\n";
}
} else {
manifest_application_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.supportedDevices\" />\n";
diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp
index c84a919b6b..d7ba31e3c9 100644
--- a/platform/android/file_access_android.cpp
+++ b/platform/android/file_access_android.cpp
@@ -34,11 +34,13 @@
AAssetManager *FileAccessAndroid::asset_manager = nullptr;
-FileAccess *FileAccessAndroid::create_android() {
+Ref<FileAccess> FileAccessAndroid::create_android() {
return memnew(FileAccessAndroid);
}
Error FileAccessAndroid::_open(const String &p_path, int p_mode_flags) {
+ _close();
+
String path = fix_path(p_path).simplify_path();
if (path.begins_with("/")) {
path = path.substr(1, path.length());
@@ -58,7 +60,7 @@ Error FileAccessAndroid::_open(const String &p_path, int p_mode_flags) {
return OK;
}
-void FileAccessAndroid::close() {
+void FileAccessAndroid::_close() {
if (!a) {
return;
}
@@ -162,5 +164,5 @@ bool FileAccessAndroid::file_exists(const String &p_path) {
}
FileAccessAndroid::~FileAccessAndroid() {
- close();
+ _close();
}
diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h
index fb612cd008..33b692da20 100644
--- a/platform/android/file_access_android.h
+++ b/platform/android/file_access_android.h
@@ -38,17 +38,18 @@
//#include <android_native_app_glue.h>
class FileAccessAndroid : public FileAccess {
- static FileAccess *create_android();
+ static Ref<FileAccess> create_android();
mutable AAsset *a = nullptr;
mutable uint64_t len = 0;
mutable uint64_t pos = 0;
mutable bool eof = false;
+ void _close();
+
public:
static AAssetManager *asset_manager;
virtual Error _open(const String &p_path, int p_mode_flags); ///< open a file
- virtual void close(); ///< close a file
virtual bool is_open() const; ///< true when file is open
virtual void seek(uint64_t p_position); ///< seek to a given position
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index 4c4501729d..c98e8f1d55 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -40,6 +40,13 @@
android:name="xr_hand_tracking_metadata_name"
android:value="xr_hand_tracking_metadata_value"/>
+ <!-- XR hand tracking version -->
+ <!-- This is modified by the exporter based on the selected xr mode. DO NOT CHANGE the values here. -->
+ <!-- Removed at export time if the xr mode is not VR or hand tracking is disabled. -->
+ <meta-data
+ android:name="xr_hand_tracking_version_name"
+ android:value="xr_hand_tracking_version_value"/>
+
<!-- Supported Meta devices -->
<!-- This is removed by the exporter if the xr mode is not VR. -->
<meta-data
diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml
index 0708ffa32f..bae075d929 100644
--- a/platform/android/java/editor/src/main/AndroidManifest.xml
+++ b/platform/android/java/editor/src/main/AndroidManifest.xml
@@ -29,8 +29,7 @@
android:name=".GodotProjectManager"
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
android:launchMode="singleTask"
- android:resizeableActivity="false"
- android:screenOrientation="landscape"
+ android:screenOrientation="userLandscape"
android:exported="true"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
android:process=":GodotProjectManager">
@@ -46,8 +45,7 @@
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"
android:process=":GodotEditor"
android:launchMode="singleTask"
- android:resizeableActivity="false"
- android:screenOrientation="landscape"
+ android:screenOrientation="userLandscape"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
</activity>
@@ -57,8 +55,7 @@
android:label="@string/godot_project_name_string"
android:process=":GodotGame"
android:launchMode="singleTask"
- android:resizeableActivity="false"
- android:screenOrientation="landscape"
+ android:screenOrientation="userLandscape"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
</activity>
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.java b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.java
index b3a340cc64..8a6bf88267 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.java
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.java
@@ -107,4 +107,18 @@ public class GodotEditor extends FullScreenGodotApp {
Intent newInstance = new Intent(this, targetClass).putExtra(COMMAND_LINE_PARAMS, args);
startActivity(newInstance);
}
+
+ @Override
+ public void setRequestedOrientation(int requestedOrientation) {
+ if (!overrideOrientationRequest()) {
+ super.setRequestedOrientation(requestedOrientation);
+ }
+ }
+
+ /**
+ * The Godot Android Editor sets its own orientation via its AndroidManifest
+ */
+ protected boolean overrideOrientationRequest() {
+ return true;
+ }
}
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.java b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.java
index 5a0be391cf..12766775a8 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.java
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.java
@@ -34,4 +34,7 @@ package org.godotengine.editor;
* Drives the 'run project' window of the Godot Editor.
*/
public class GodotGame extends GodotEditor {
+ protected boolean overrideOrientationRequest() {
+ return false;
+ }
}
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 e8ffbb9481..fb1604f6af 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java
@@ -65,10 +65,6 @@ public abstract class FullScreenGodotApp extends FragmentActivity implements God
} else {
Log.v(TAG, "Creating new Godot fragment instance.");
godotFragment = initGodotInstance();
- if (godotFragment == null) {
- throw new IllegalStateException("Godot instance must be non-null.");
- }
-
getSupportFragmentManager().beginTransaction().replace(R.id.godot_fragment_container, godotFragment).setPrimaryNavigationFragment(godotFragment).commitNowAllowingStateLoss();
}
}
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 8a86136daf..377881be85 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -36,6 +36,7 @@ import static android.content.Context.WINDOW_SERVICE;
import org.godotengine.godot.input.GodotEditText;
import org.godotengine.godot.plugin.GodotPlugin;
import org.godotengine.godot.plugin.GodotPluginRegistry;
+import org.godotengine.godot.tts.GodotTTS;
import org.godotengine.godot.utils.GodotNetUtils;
import org.godotengine.godot.utils.PermissionsUtil;
import org.godotengine.godot.xr.XRMode;
@@ -165,6 +166,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
public static GodotIO io;
public static GodotNetUtils netUtils;
+ public static GodotTTS tts;
public interface ResultCallback {
void callback(int requestCode, int resultCode, Intent data);
@@ -458,6 +460,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
io = new GodotIO(activity);
GodotLib.io = io;
netUtils = new GodotNetUtils(activity);
+ tts = new GodotTTS(activity);
mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME);
@@ -509,17 +512,14 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
use_debug_opengl = true;
} else if (command_line[i].equals("--use_immersive")) {
use_immersive = true;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+
- window.getDecorView().setSystemUiVisibility(
- View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar
- View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-
- UiChangeListener();
- }
+ window.getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar
+ View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ UiChangeListener();
} else if (command_line[i].equals("--use_apk_expansion")) {
use_apk_expansion = true;
} else if (has_extra && command_line[i].equals("--apk_expansion_md5")) {
@@ -666,14 +666,13 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
}
public String getClipboard() {
- String copiedText = "";
-
- if (mClipboard.hasPrimaryClip()) {
- ClipData.Item item = mClipboard.getPrimaryClip().getItemAt(0);
- copiedText = item.getText().toString();
- }
-
- return copiedText;
+ ClipData clipData = mClipboard.getPrimaryClip();
+ if (clipData == null)
+ return "";
+ CharSequence text = clipData.getItemAt(0).getText();
+ if (text == null)
+ return "";
+ return text.toString();
}
public void setClipboard(String p_text) {
@@ -699,7 +698,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_GAME);
mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME);
- if (use_immersive && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+
+ if (use_immersive) {
Window window = getActivity().getWindow();
window.getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
@@ -719,15 +718,13 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
final View decorView = getActivity().getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener(visibility -> {
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- decorView.setSystemUiVisibility(
- View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
- View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_FULLSCREEN |
- View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
- }
+ decorView.setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
});
}
@@ -888,9 +885,8 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
// Create Hex String
StringBuilder hexString = new StringBuilder();
- for (int i = 0; i < messageDigest.length; i++) {
- String s = Integer.toHexString(0xFF & messageDigest[i]);
-
+ for (byte b : messageDigest) {
+ String s = Integer.toHexString(0xFF & b);
if (s.length() == 1) {
s = "0" + s;
}
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 b151e7eec1..b69d25dd8b 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -38,6 +38,7 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.AssetManager;
import android.graphics.Point;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
@@ -51,6 +52,7 @@ import android.view.DisplayCutout;
import android.view.WindowInsets;
import java.io.IOException;
+import java.util.List;
import java.util.Locale;
// Wrapper for native library
@@ -222,10 +224,14 @@ public class GodotIO {
}
public int getScreenDPI() {
- DisplayMetrics metrics = activity.getApplicationContext().getResources().getDisplayMetrics();
+ DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
return (int)(metrics.density * 160f);
}
+ public float getScaledDensity() {
+ return activity.getResources().getDisplayMetrics().scaledDensity;
+ }
+
public double getScreenRefreshRate(double fallback) {
Display display = activity.getWindowManager().getDefaultDisplay();
if (display != null) {
@@ -234,7 +240,7 @@ public class GodotIO {
return fallback;
}
- public int[] screenGetUsableRect() {
+ public int[] getDisplaySafeArea() {
DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
Display display = activity.getWindowManager().getDefaultDisplay();
Point size = new Point();
@@ -256,6 +262,25 @@ public class GodotIO {
return result;
}
+ public int[] getDisplayCutouts() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
+ return new int[0];
+ DisplayCutout cutout = activity.getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
+ if (cutout == null)
+ return new int[0];
+ List<Rect> rects = cutout.getBoundingRects();
+ int cutouts = rects.size();
+ int[] result = new int[cutouts * 4];
+ int index = 0;
+ for (Rect rect : rects) {
+ result[index++] = rect.left;
+ result[index++] = rect.top;
+ result[index++] = rect.width();
+ result[index++] = rect.height();
+ }
+ return result;
+ }
+
public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (edit != null)
edit.showKeyboard(p_existing_text, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
index 253a51b83c..1f8f8c82a6 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java
@@ -92,6 +92,11 @@ public class GodotLib {
public static native boolean step();
/**
+ * TTS callback.
+ */
+ public static native void ttsCallback(int event, int id, int pos);
+
+ /**
* Forward touch events from the main thread to the GL thread.
*/
public static native void touch(int inputDevice, int event, int pointer, int pointerCount, float[] positions);
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 c06d89b843..8694bb91e1 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
@@ -34,8 +34,9 @@ import static org.godotengine.godot.utils.GLUtils.DEBUG;
import org.godotengine.godot.GodotLib;
import org.godotengine.godot.GodotRenderView;
-import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener;
+import android.content.Context;
+import android.hardware.input.InputManager;
import android.os.Build;
import android.util.Log;
import android.util.SparseArray;
@@ -53,9 +54,9 @@ import java.util.Set;
/**
* Handles input related events for the {@link GodotRenderView} view.
*/
-public class GodotInputHandler implements InputDeviceListener {
+public class GodotInputHandler implements InputManager.InputDeviceListener {
private final GodotRenderView mRenderView;
- private final InputManagerCompat mInputManager;
+ private final InputManager mInputManager;
private final String tag = this.getClass().getSimpleName();
@@ -64,7 +65,7 @@ public class GodotInputHandler implements InputDeviceListener {
public GodotInputHandler(GodotRenderView godotView) {
mRenderView = godotView;
- mInputManager = InputManagerCompat.Factory.getInputManager(mRenderView.getView().getContext());
+ mInputManager = (InputManager)mRenderView.getView().getContext().getSystemService(Context.INPUT_SERVICE);
mInputManager.registerInputDeviceListener(this, null);
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java
deleted file mode 100644
index 21fdc658bb..0000000000
--- a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerCompat.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.godotengine.godot.input;
-
-import android.content.Context;
-import android.os.Handler;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-
-public interface InputManagerCompat {
- /**
- * Gets information about the input device with the specified id.
- *
- * @param id The device id
- * @return The input device or null if not found
- */
- InputDevice getInputDevice(int id);
-
- /**
- * Gets the ids of all input devices in the system.
- *
- * @return The input device ids.
- */
- int[] getInputDeviceIds();
-
- /**
- * Registers an input device listener to receive notifications about when
- * input devices are added, removed or changed.
- *
- * @param listener The listener to register.
- * @param handler The handler on which the listener should be invoked, or
- * null if the listener should be invoked on the calling thread's
- * looper.
- */
- void registerInputDeviceListener(InputManagerCompat.InputDeviceListener listener,
- Handler handler);
-
- /**
- * Unregisters an input device listener.
- *
- * @param listener The listener to unregister.
- */
- void unregisterInputDeviceListener(InputManagerCompat.InputDeviceListener listener);
-
- /*
- * The following three calls are to simulate V16 behavior on pre-Jellybean
- * devices. If you don't call them, your callback will never be called
- * pre-API 16.
- */
-
- /**
- * Pass the motion events to the InputManagerCompat. This is used to
- * optimize for polling for controllers. If you do not pass these events in,
- * polling will cause regular object creation.
- *
- * @param event the motion event from the app
- */
- void onGenericMotionEvent(MotionEvent event);
-
- /**
- * Tell the V9 input manager that it should stop polling for disconnected
- * devices. You can call this during onPause in your activity, although you
- * might want to call it whenever your game is not active (or whenever you
- * don't care about being notified of new input devices)
- */
- void onPause();
-
- /**
- * Tell the V9 input manager that it should start polling for disconnected
- * devices. You can call this during onResume in your activity, although you
- * might want to call it less often (only when the gameplay is actually
- * active)
- */
- void onResume();
-
- interface InputDeviceListener {
- /**
- * Called whenever the input manager detects that a device has been
- * added. This will only be called in the V9 version when a motion event
- * is detected.
- *
- * @param deviceId The id of the input device that was added.
- */
- void onInputDeviceAdded(int deviceId);
-
- /**
- * Called whenever the properties of an input device have changed since
- * they were last queried. This will not be called for the V9 version of
- * the API.
- *
- * @param deviceId The id of the input device that changed.
- */
- void onInputDeviceChanged(int deviceId);
-
- /**
- * Called whenever the input manager detects that a device has been
- * removed. For the V9 version, this can take some time depending on the
- * poll rate.
- *
- * @param deviceId The id of the input device that was removed.
- */
- void onInputDeviceRemoved(int deviceId);
- }
-
- /**
- * Use this to construct a compatible InputManager.
- */
- class Factory {
- /**
- * Constructs and returns a compatible InputManger
- *
- * @param context the Context that will be used to get the system
- * service from
- * @return a compatible implementation of InputManager
- */
- public static InputManagerCompat getInputManager(Context context) {
- return new InputManagerV16(context);
- }
- }
-}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java b/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java
deleted file mode 100644
index 0dbc13c77b..0000000000
--- a/platform/android/java/lib/src/org/godotengine/godot/input/InputManagerV16.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.godotengine.godot.input;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.hardware.input.InputManager;
-import android.os.Build;
-import android.os.Handler;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-
-import java.util.HashMap;
-import java.util.Map;
-
-@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
-public class InputManagerV16 implements InputManagerCompat {
- private final InputManager mInputManager;
- private final Map<InputManagerCompat.InputDeviceListener, V16InputDeviceListener> mListeners;
-
- public InputManagerV16(Context context) {
- mInputManager = (InputManager)context.getSystemService(Context.INPUT_SERVICE);
- mListeners = new HashMap<>();
- }
-
- @Override
- public InputDevice getInputDevice(int id) {
- return mInputManager.getInputDevice(id);
- }
-
- @Override
- public int[] getInputDeviceIds() {
- return mInputManager.getInputDeviceIds();
- }
-
- static class V16InputDeviceListener implements InputManager.InputDeviceListener {
- final InputManagerCompat.InputDeviceListener mIDL;
-
- public V16InputDeviceListener(InputDeviceListener idl) {
- mIDL = idl;
- }
-
- @Override
- public void onInputDeviceAdded(int deviceId) {
- mIDL.onInputDeviceAdded(deviceId);
- }
-
- @Override
- public void onInputDeviceChanged(int deviceId) {
- mIDL.onInputDeviceChanged(deviceId);
- }
-
- @Override
- public void onInputDeviceRemoved(int deviceId) {
- mIDL.onInputDeviceRemoved(deviceId);
- }
- }
-
- @Override
- public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
- V16InputDeviceListener v16Listener = new V16InputDeviceListener(listener);
- mInputManager.registerInputDeviceListener(v16Listener, handler);
- mListeners.put(listener, v16Listener);
- }
-
- @Override
- public void unregisterInputDeviceListener(InputDeviceListener listener) {
- V16InputDeviceListener curListener = mListeners.remove(listener);
- if (null != curListener) {
- mInputManager.unregisterInputDeviceListener(curListener);
- }
- }
-
- @Override
- public void onGenericMotionEvent(MotionEvent event) {
- // unused in V16
- }
-
- @Override
- public void onPause() {
- // unused in V16
- }
-
- @Override
- public void onResume() {
- // unused in V16
- }
-}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java b/platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java
new file mode 100644
index 0000000000..2239ddac8e
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/tts/GodotTTS.java
@@ -0,0 +1,298 @@
+/*************************************************************************/
+/* GodotTTS.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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. */
+/*************************************************************************/
+
+package org.godotengine.godot.tts;
+
+import org.godotengine.godot.GodotLib;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.UtteranceProgressListener;
+import android.speech.tts.Voice;
+
+import androidx.annotation.Keep;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Set;
+
+/**
+ * Wrapper for Android Text to Speech API and custom utterance query implementation.
+ * <p>
+ * A [GodotTTS] provides the following features:
+ * <p>
+ * <ul>
+ * <li>Access to the Android Text to Speech API.
+ * <li>Utterance pause / resume functions, unsupported by Android TTS API.
+ * </ul>
+ */
+@Keep
+public class GodotTTS extends UtteranceProgressListener {
+ // Note: These constants must be in sync with DisplayServer::TTSUtteranceEvent enum from "servers/display_server.h".
+ final private static int EVENT_START = 0;
+ final private static int EVENT_END = 1;
+ final private static int EVENT_CANCEL = 2;
+ final private static int EVENT_BOUNDARY = 3;
+
+ final private TextToSpeech synth;
+ final private LinkedList<GodotUtterance> queue;
+ final private Object lock = new Object();
+ private GodotUtterance lastUtterance;
+
+ private boolean speaking;
+ private boolean paused;
+
+ public GodotTTS(Activity p_activity) {
+ synth = new TextToSpeech(p_activity, null);
+ queue = new LinkedList<GodotUtterance>();
+
+ synth.setOnUtteranceProgressListener(this);
+ }
+
+ private void updateTTS() {
+ if (!speaking && queue.size() > 0) {
+ int mode = TextToSpeech.QUEUE_FLUSH;
+ GodotUtterance message = queue.pollFirst();
+
+ Set<Voice> voices = synth.getVoices();
+ for (Voice v : voices) {
+ if (v.getName().equals(message.voice)) {
+ synth.setVoice(v);
+ break;
+ }
+ }
+ synth.setPitch(message.pitch);
+ synth.setSpeechRate(message.rate);
+
+ Bundle params = new Bundle();
+ params.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, message.volume / 100.f);
+
+ lastUtterance = message;
+ lastUtterance.start = 0;
+ lastUtterance.offset = 0;
+ paused = false;
+
+ synth.speak(message.text, mode, params, String.valueOf(message.id));
+ speaking = true;
+ }
+ }
+
+ /**
+ * Called by TTS engine when the TTS service is about to speak the specified range.
+ */
+ @Override
+ public void onRangeStart(String utteranceId, int start, int end, int frame) {
+ synchronized (lock) {
+ if (lastUtterance != null && Integer.parseInt(utteranceId) == lastUtterance.id) {
+ lastUtterance.offset = start;
+ GodotLib.ttsCallback(EVENT_BOUNDARY, lastUtterance.id, start + lastUtterance.start);
+ }
+ }
+ }
+
+ /**
+ * Called by TTS engine when an utterance was canceled in progress.
+ */
+ @Override
+ public void onStop(String utteranceId, boolean interrupted) {
+ synchronized (lock) {
+ if (lastUtterance != null && !paused && Integer.parseInt(utteranceId) == lastUtterance.id) {
+ GodotLib.ttsCallback(EVENT_CANCEL, lastUtterance.id, 0);
+ speaking = false;
+ updateTTS();
+ }
+ }
+ }
+
+ /**
+ * Called by TTS engine when an utterance has begun to be spoken..
+ */
+ @Override
+ public void onStart(String utteranceId) {
+ synchronized (lock) {
+ if (lastUtterance != null && lastUtterance.start == 0 && Integer.parseInt(utteranceId) == lastUtterance.id) {
+ GodotLib.ttsCallback(EVENT_START, lastUtterance.id, 0);
+ }
+ }
+ }
+
+ /**
+ * Called by TTS engine when an utterance was successfully finished.
+ */
+ @Override
+ public void onDone(String utteranceId) {
+ synchronized (lock) {
+ if (lastUtterance != null && !paused && Integer.parseInt(utteranceId) == lastUtterance.id) {
+ GodotLib.ttsCallback(EVENT_END, lastUtterance.id, 0);
+ speaking = false;
+ updateTTS();
+ }
+ }
+ }
+
+ /**
+ * Called by TTS engine when an error has occurred during processing.
+ */
+ @Override
+ public void onError(String utteranceId, int errorCode) {
+ synchronized (lock) {
+ if (lastUtterance != null && !paused && Integer.parseInt(utteranceId) == lastUtterance.id) {
+ GodotLib.ttsCallback(EVENT_CANCEL, lastUtterance.id, 0);
+ speaking = false;
+ updateTTS();
+ }
+ }
+ }
+
+ /**
+ * Called by TTS engine when an error has occurred during processing (pre API level 21 version).
+ */
+ @Override
+ public void onError(String utteranceId) {
+ synchronized (lock) {
+ if (lastUtterance != null && !paused && Integer.parseInt(utteranceId) == lastUtterance.id) {
+ GodotLib.ttsCallback(EVENT_CANCEL, lastUtterance.id, 0);
+ speaking = false;
+ updateTTS();
+ }
+ }
+ }
+
+ /**
+ * Adds an utterance to the queue.
+ */
+ public void speak(String text, String voice, int volume, float pitch, float rate, int utterance_id, boolean interrupt) {
+ synchronized (lock) {
+ GodotUtterance message = new GodotUtterance(text, voice, volume, pitch, rate, utterance_id);
+ queue.addLast(message);
+
+ if (isPaused()) {
+ resumeSpeaking();
+ } else {
+ updateTTS();
+ }
+ }
+ }
+
+ /**
+ * Puts the synthesizer into a paused state.
+ */
+ public void pauseSpeaking() {
+ synchronized (lock) {
+ if (!paused) {
+ paused = true;
+ synth.stop();
+ }
+ }
+ }
+
+ /**
+ * Resumes the synthesizer if it was paused.
+ */
+ public void resumeSpeaking() {
+ synchronized (lock) {
+ if (lastUtterance != null && paused) {
+ int mode = TextToSpeech.QUEUE_FLUSH;
+
+ Set<Voice> voices = synth.getVoices();
+ for (Voice v : voices) {
+ if (v.getName().equals(lastUtterance.voice)) {
+ synth.setVoice(v);
+ break;
+ }
+ }
+ synth.setPitch(lastUtterance.pitch);
+ synth.setSpeechRate(lastUtterance.rate);
+
+ Bundle params = new Bundle();
+ params.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, lastUtterance.volume / 100.f);
+
+ lastUtterance.start = lastUtterance.offset;
+ lastUtterance.offset = 0;
+ paused = false;
+
+ synth.speak(lastUtterance.text.substring(lastUtterance.start), mode, params, String.valueOf(lastUtterance.id));
+ speaking = true;
+ } else {
+ paused = false;
+ }
+ }
+ }
+
+ /**
+ * Stops synthesis in progress and removes all utterances from the queue.
+ */
+ public void stopSpeaking() {
+ synchronized (lock) {
+ for (GodotUtterance u : queue) {
+ GodotLib.ttsCallback(EVENT_CANCEL, u.id, 0);
+ }
+ queue.clear();
+
+ if (lastUtterance != null) {
+ GodotLib.ttsCallback(EVENT_CANCEL, lastUtterance.id, 0);
+ }
+ lastUtterance = null;
+
+ paused = false;
+ speaking = false;
+
+ synth.stop();
+ }
+ }
+
+ /**
+ * Returns voice information.
+ */
+ public String[] getVoices() {
+ Set<Voice> voices = synth.getVoices();
+ String[] list = new String[voices.size()];
+ int i = 0;
+ for (Voice v : voices) {
+ list[i++] = v.getLocale().toString() + ";" + v.getName();
+ }
+ return list;
+ }
+
+ /**
+ * Returns true if the synthesizer is generating speech, or have utterance waiting in the queue.
+ */
+ public boolean isSpeaking() {
+ return speaking;
+ }
+
+ /**
+ * Returns true if the synthesizer is in a paused state.
+ */
+ public boolean isPaused() {
+ return paused;
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/tts/GodotUtterance.java b/platform/android/java/lib/src/org/godotengine/godot/tts/GodotUtterance.java
new file mode 100644
index 0000000000..bde37e7315
--- /dev/null
+++ b/platform/android/java/lib/src/org/godotengine/godot/tts/GodotUtterance.java
@@ -0,0 +1,55 @@
+/*************************************************************************/
+/* GodotUtterance.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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. */
+/*************************************************************************/
+
+package org.godotengine.godot.tts;
+
+/**
+ * A speech request for GodotTTS.
+ */
+class GodotUtterance {
+ final String text;
+ final String voice;
+ final int volume;
+ final float pitch;
+ final float rate;
+ final int id;
+
+ int offset = -1;
+ int start = 0;
+
+ GodotUtterance(String text, String voice, int volume, float pitch, float rate, int id) {
+ this.text = text;
+ this.voice = voice;
+ this.volume = volume;
+ this.pitch = pitch;
+ this.rate = rate;
+ this.id = id;
+ }
+}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java b/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java
index 39a57f587a..47df23fe1a 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/Crypt.java
@@ -43,8 +43,8 @@ public class Crypt {
// Create Hex String
StringBuilder hexString = new StringBuilder();
- for (int i = 0; i < messageDigest.length; i++)
- hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
+ for (byte b : messageDigest)
+ hexString.append(Integer.toHexString(0xFF & b));
return hexString.toString();
} catch (Exception e) {
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index d6e3ad90b1..7ae3a65105 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -31,6 +31,8 @@
#include "java_godot_io_wrapper.h"
#include "core/error/error_list.h"
+#include "core/math/rect2.h"
+#include "core/variant/variant.h"
// JNIEnv is only valid within the thread it belongs to, in a multi threading environment
// we can't cache it.
@@ -51,11 +53,13 @@ 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_display_cutouts = p_env->GetMethodID(cls, "getDisplayCutouts", "()[I"),
+ _get_display_safe_area = p_env->GetMethodID(cls, "getDisplaySafeArea", "()[I"),
_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");
+ _get_scaled_density = p_env->GetMethodID(cls, "getScaledDensity", "()F");
_get_screen_refresh_rate = p_env->GetMethodID(cls, "getScreenRefreshRate", "(D)D");
- _screen_get_usable_rect = p_env->GetMethodID(cls, "screenGetUsableRect", "()[I"),
_get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;");
_show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;ZIII)V");
_hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V");
@@ -138,6 +142,16 @@ int GodotIOJavaWrapper::get_screen_dpi() {
}
}
+float GodotIOJavaWrapper::get_scaled_density() {
+ if (_get_scaled_density) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, 1.0f);
+ return env->CallFloatMethod(godot_io_instance, _get_scaled_density);
+ } else {
+ return 1.0f;
+ }
+}
+
float GodotIOJavaWrapper::get_screen_refresh_rate(float fallback) {
if (_get_screen_refresh_rate) {
JNIEnv *env = get_jni_env();
@@ -151,18 +165,38 @@ float GodotIOJavaWrapper::get_screen_refresh_rate(float fallback) {
return fallback;
}
-void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) {
- if (_screen_get_usable_rect) {
- JNIEnv *env = get_jni_env();
- ERR_FAIL_COND(env == nullptr);
- jintArray returnArray = (jintArray)env->CallObjectMethod(godot_io_instance, _screen_get_usable_rect);
- ERR_FAIL_COND(env->GetArrayLength(returnArray) != 4);
- jint *arrayBody = env->GetIntArrayElements(returnArray, JNI_FALSE);
- for (int i = 0; i < 4; i++) {
- p_rect_xywh[i] = arrayBody[i];
- }
- env->ReleaseIntArrayElements(returnArray, arrayBody, 0);
+Array GodotIOJavaWrapper::get_display_cutouts() {
+ Array result;
+ ERR_FAIL_NULL_V(_get_display_cutouts, result);
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, result);
+ jintArray returnArray = (jintArray)env->CallObjectMethod(godot_io_instance, _get_display_cutouts);
+ jint arrayLength = env->GetArrayLength(returnArray);
+ jint *arrayBody = env->GetIntArrayElements(returnArray, JNI_FALSE);
+ int cutouts = arrayLength / 4;
+ for (int i = 0; i < cutouts; i++) {
+ int x = arrayBody[i * 4];
+ int y = arrayBody[i * 4 + 1];
+ int width = arrayBody[i * 4 + 2];
+ int height = arrayBody[i * 4 + 3];
+ Rect2 cutout(x, y, width, height);
+ result.append(cutout);
}
+ env->ReleaseIntArrayElements(returnArray, arrayBody, 0);
+ return result;
+}
+
+Rect2i GodotIOJavaWrapper::get_display_safe_area() {
+ Rect2i result;
+ ERR_FAIL_NULL_V(_get_display_safe_area, result);
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, result);
+ jintArray returnArray = (jintArray)env->CallObjectMethod(godot_io_instance, _get_display_safe_area);
+ ERR_FAIL_COND_V(env->GetArrayLength(returnArray) != 4, result);
+ jint *arrayBody = env->GetIntArrayElements(returnArray, JNI_FALSE);
+ result = Rect2i(arrayBody[0], arrayBody[1], arrayBody[2], arrayBody[3]);
+ env->ReleaseIntArrayElements(returnArray, arrayBody, 0);
+ return result;
}
String GodotIOJavaWrapper::get_unique_id() {
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index 38a2b710a9..02c57130ab 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -37,6 +37,8 @@
#include <android/log.h>
#include <jni.h>
+#include "core/math/rect2i.h"
+#include "core/variant/array.h"
#include "string_android.h"
// Class that makes functions in java/src/org/godotengine/godot/GodotIO.java callable from C++
@@ -48,11 +50,13 @@ private:
jmethodID _open_URI = 0;
jmethodID _get_cache_dir = 0;
jmethodID _get_data_dir = 0;
+ jmethodID _get_display_cutouts = 0;
+ jmethodID _get_display_safe_area = 0;
jmethodID _get_locale = 0;
jmethodID _get_model = 0;
jmethodID _get_screen_DPI = 0;
+ jmethodID _get_scaled_density = 0;
jmethodID _get_screen_refresh_rate = 0;
- jmethodID _screen_get_usable_rect = 0;
jmethodID _get_unique_id = 0;
jmethodID _show_keyboard = 0;
jmethodID _hide_keyboard = 0;
@@ -72,8 +76,10 @@ public:
String get_locale();
String get_model();
int get_screen_dpi();
+ float get_scaled_density();
float get_screen_refresh_rate(float fallback);
- void screen_get_usable_rect(int (&p_rect_xywh)[4]);
+ Array get_display_cutouts();
+ Rect2i get_display_safe_area();
String get_unique_id();
bool has_vk();
void show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end);
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index ea72bc0e15..8ad72b499e 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -49,6 +49,7 @@
#include "os_android.h"
#include "string_android.h"
#include "thread_jandroid.h"
+#include "tts_android.h"
#include <android/input.h>
#include <unistd.h>
@@ -96,6 +97,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *en
DirAccessJAndroid::setup(godot_io_java->get_instance());
NetSocketAndroid::setup(godot_java->get_member_object("netUtils", "Lorg/godotengine/godot/utils/GodotNetUtils;", env));
+ TTS_Android::setup(godot_java->get_member_object("tts", "Lorg/godotengine/godot/tts/GodotTTS;", env));
os_android = new OS_Android(godot_java, godot_io_java, p_use_apk_expansion);
@@ -213,6 +215,10 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jcl
}
}
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ttsCallback(JNIEnv *env, jclass clazz, jint event, jint id, jint pos) {
+ TTS_Android::_java_utterance_callback(event, id, pos);
+}
+
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz) {
if (step.get() == -1) {
return true;
@@ -503,6 +509,8 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNI
return;
}
+ // We force redraw to ensure we render at least once when resuming the app.
+ Main::force_redraw();
if (os_android->get_main_loop()) {
os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_RESUMED);
}
diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h
index e686ee5c09..4f2195942c 100644
--- a/platform/android/java_godot_lib_jni.h
+++ b/platform/android/java_godot_lib_jni.h
@@ -43,6 +43,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jc
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface);
JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jclass clazz);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ttsCallback(JNIEnv *env, jclass clazz, jint event, jint id, jint pos);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jclass clazz);
void touch_preprocessing(JNIEnv *env, jclass clazz, jint input_device, jint ev, jint pointer, jint pointer_count, jfloatArray positions, jint buttons_mask = 0, jfloat vertical_factor = 0, jfloat horizontal_factor = 0);
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 positions);
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index ef53415f16..25daf1ca90 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -139,7 +139,7 @@ void OS_Android::finalize() {
}
OS_Android *OS_Android::get_singleton() {
- return (OS_Android *)OS::get_singleton();
+ return static_cast<OS_Android *>(OS::get_singleton());
}
GodotJavaWrapper *OS_Android::get_godot_java() {
@@ -162,9 +162,14 @@ Vector<String> OS_Android::get_granted_permissions() const {
return godot_java->get_granted_permissions();
}
-Error OS_Android::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
+Error OS_Android::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
p_library_handle = dlopen(p_path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + ".");
+
+ if (r_resolved_path != nullptr) {
+ *r_resolved_path = p_path;
+ }
+
return OK;
}
@@ -187,10 +192,11 @@ bool OS_Android::main_loop_iterate(bool *r_should_swap_buffers) {
return false;
}
DisplayServerAndroid::get_singleton()->process_events();
+ uint64_t current_frames_drawn = Engine::get_singleton()->get_frames_drawn();
bool exit = Main::iteration();
if (r_should_swap_buffers) {
- *r_should_swap_buffers = !is_in_low_processor_usage_mode() || RenderingServer::get_singleton()->has_changed();
+ *r_should_swap_buffers = !is_in_low_processor_usage_mode() || RenderingServer::get_singleton()->has_changed() || current_frames_drawn != Engine::get_singleton()->get_frames_drawn();
}
return exit;
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index 48239b3f84..f86c5b5212 100644
--- a/platform/android/os_android.h
+++ b/platform/android/os_android.h
@@ -52,7 +52,7 @@ private:
#endif
#if defined(VULKAN_ENABLED)
- ANativeWindow *native_window;
+ ANativeWindow *native_window = nullptr;
#endif
mutable String data_dir_cache;
@@ -60,10 +60,10 @@ private:
AudioDriverOpenSL audio_driver_android;
- MainLoop *main_loop;
+ MainLoop *main_loop = nullptr;
- GodotJavaWrapper *godot_java;
- GodotIOJavaWrapper *godot_io_java;
+ GodotJavaWrapper *godot_java = nullptr;
+ GodotIOJavaWrapper *godot_io_java = nullptr;
public:
static const char *ANDROID_EXEC_PATH;
@@ -90,7 +90,7 @@ public:
virtual void alert(const String &p_alert, const String &p_title) override;
- virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override;
+ virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override;
virtual String get_name() const override;
virtual MainLoop *get_main_loop() const override;
diff --git a/platform/android/tts_android.cpp b/platform/android/tts_android.cpp
new file mode 100644
index 0000000000..528878f14e
--- /dev/null
+++ b/platform/android/tts_android.cpp
@@ -0,0 +1,189 @@
+/*************************************************************************/
+/* tts_android.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "tts_android.h"
+
+#include "java_godot_wrapper.h"
+#include "os_android.h"
+#include "string_android.h"
+#include "thread_jandroid.h"
+
+jobject TTS_Android::tts = 0;
+jclass TTS_Android::cls = 0;
+
+jmethodID TTS_Android::_is_speaking = 0;
+jmethodID TTS_Android::_is_paused = 0;
+jmethodID TTS_Android::_get_voices = 0;
+jmethodID TTS_Android::_speak = 0;
+jmethodID TTS_Android::_pause_speaking = 0;
+jmethodID TTS_Android::_resume_speaking = 0;
+jmethodID TTS_Android::_stop_speaking = 0;
+
+Map<int, Char16String> TTS_Android::ids;
+
+void TTS_Android::setup(jobject p_tts) {
+ JNIEnv *env = get_jni_env();
+
+ tts = env->NewGlobalRef(p_tts);
+
+ jclass c = env->GetObjectClass(tts);
+ cls = (jclass)env->NewGlobalRef(c);
+
+ _is_speaking = env->GetMethodID(cls, "isSpeaking", "()Z");
+ _is_paused = env->GetMethodID(cls, "isPaused", "()Z");
+ _get_voices = env->GetMethodID(cls, "getVoices", "()[Ljava/lang/String;");
+ _speak = env->GetMethodID(cls, "speak", "(Ljava/lang/String;Ljava/lang/String;IFFIZ)V");
+ _pause_speaking = env->GetMethodID(cls, "pauseSpeaking", "()V");
+ _resume_speaking = env->GetMethodID(cls, "resumeSpeaking", "()V");
+ _stop_speaking = env->GetMethodID(cls, "stopSpeaking", "()V");
+}
+
+void TTS_Android::_java_utterance_callback(int p_event, int p_id, int p_pos) {
+ if (ids.has(p_id)) {
+ int pos = 0;
+ if ((DisplayServer::TTSUtteranceEvent)p_event == DisplayServer::TTS_UTTERANCE_BOUNDARY) {
+ // Convert position from UTF-16 to UTF-32.
+ const Char16String &string = ids[p_id];
+ for (int i = 0; i < MIN(p_pos, string.length()); i++) {
+ char16_t c = string[i];
+ if ((c & 0xfffffc00) == 0xd800) {
+ i++;
+ }
+ pos++;
+ }
+ } else if ((DisplayServer::TTSUtteranceEvent)p_event != DisplayServer::TTS_UTTERANCE_STARTED) {
+ ids.erase(p_id);
+ }
+ DisplayServer::get_singleton()->tts_post_utterance_event((DisplayServer::TTSUtteranceEvent)p_event, p_id, pos);
+ }
+}
+
+bool TTS_Android::is_speaking() {
+ if (_is_speaking) {
+ JNIEnv *env = get_jni_env();
+
+ ERR_FAIL_COND_V(env == nullptr, false);
+ return env->CallBooleanMethod(tts, _is_speaking);
+ } else {
+ return false;
+ }
+}
+
+bool TTS_Android::is_paused() {
+ if (_is_paused) {
+ JNIEnv *env = get_jni_env();
+
+ ERR_FAIL_COND_V(env == nullptr, false);
+ return env->CallBooleanMethod(tts, _is_paused);
+ } else {
+ return false;
+ }
+}
+
+Array TTS_Android::get_voices() {
+ Array list;
+ if (_get_voices) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND_V(env == nullptr, list);
+
+ jobject voices_object = env->CallObjectMethod(tts, _get_voices);
+ jobjectArray *arr = reinterpret_cast<jobjectArray *>(&voices_object);
+
+ jsize len = env->GetArrayLength(*arr);
+ for (int i = 0; i < len; i++) {
+ jstring jStr = (jstring)env->GetObjectArrayElement(*arr, i);
+ String str = jstring_to_string(jStr, env);
+ Vector<String> tokens = str.split(";", true, 2);
+ if (tokens.size() == 2) {
+ Dictionary voice_d;
+ voice_d["name"] = tokens[1];
+ voice_d["id"] = tokens[1];
+ voice_d["language"] = tokens[0];
+ list.push_back(voice_d);
+ }
+ env->DeleteLocalRef(jStr);
+ }
+ }
+ return list;
+}
+
+void TTS_Android::speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
+ if (p_interrupt) {
+ stop();
+ }
+
+ if (p_text.is_empty()) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, p_utterance_id);
+ return;
+ }
+
+ ids[p_utterance_id] = p_text.utf16();
+
+ if (_speak) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+
+ jstring jStrT = env->NewStringUTF(p_text.utf8().get_data());
+ jstring jStrV = env->NewStringUTF(p_voice.utf8().get_data());
+ env->CallVoidMethod(tts, _speak, jStrT, jStrV, CLAMP(p_volume, 0, 100), CLAMP(p_pitch, 0.f, 2.f), CLAMP(p_rate, 0.1f, 10.f), p_utterance_id, p_interrupt);
+ }
+}
+
+void TTS_Android::pause() {
+ if (_pause_speaking) {
+ JNIEnv *env = get_jni_env();
+
+ ERR_FAIL_COND(env == nullptr);
+ env->CallVoidMethod(tts, _pause_speaking);
+ }
+}
+
+void TTS_Android::resume() {
+ if (_resume_speaking) {
+ JNIEnv *env = get_jni_env();
+
+ ERR_FAIL_COND(env == nullptr);
+ env->CallVoidMethod(tts, _resume_speaking);
+ }
+}
+
+void TTS_Android::stop() {
+ for (Map<int, Char16String>::Element *E = ids.front(); E; E = E->next()) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, E->key());
+ }
+ ids.clear();
+
+ if (_stop_speaking) {
+ JNIEnv *env = get_jni_env();
+
+ ERR_FAIL_COND(env == nullptr);
+ env->CallVoidMethod(tts, _stop_speaking);
+ }
+}
diff --git a/platform/android/tts_android.h b/platform/android/tts_android.h
new file mode 100644
index 0000000000..efeed94856
--- /dev/null
+++ b/platform/android/tts_android.h
@@ -0,0 +1,67 @@
+/*************************************************************************/
+/* tts_android.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 TTS_ANDROID_H
+#define TTS_ANDROID_H
+
+#include "core/string/ustring.h"
+#include "core/variant/array.h"
+#include "servers/display_server.h"
+
+#include <jni.h>
+
+class TTS_Android {
+ static jobject tts;
+ static jclass cls;
+
+ static jmethodID _is_speaking;
+ static jmethodID _is_paused;
+ static jmethodID _get_voices;
+ static jmethodID _speak;
+ static jmethodID _pause_speaking;
+ static jmethodID _resume_speaking;
+ static jmethodID _stop_speaking;
+
+ static Map<int, Char16String> ids;
+
+public:
+ static void setup(jobject p_tts);
+ static void _java_utterance_callback(int p_event, int p_id, int p_pos);
+
+ static bool is_speaking();
+ static bool is_paused();
+ static Array get_voices();
+ static void speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt);
+ static void pause();
+ static void resume();
+ static void stop();
+};
+
+#endif // TTS_ANDROID_H
diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub
index 58b574a72f..5e10bf5646 100644
--- a/platform/iphone/SCsub
+++ b/platform/iphone/SCsub
@@ -13,6 +13,7 @@ iphone_lib = [
"display_server_iphone.mm",
"joypad_iphone.mm",
"godot_view.mm",
+ "tts_ios.mm",
"display_layer.mm",
"godot_app_delegate.m",
"godot_view_renderer.mm",
diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h
index 7441550f67..7af222e3f8 100644
--- a/platform/iphone/display_server_iphone.h
+++ b/platform/iphone/display_server_iphone.h
@@ -58,6 +58,8 @@ class DisplayServerIPhone : public DisplayServer {
RenderingDeviceVulkan *rendering_device_vulkan = nullptr;
#endif
+ id tts = nullptr;
+
DisplayServer::ScreenOrientation screen_orientation;
ObjectID window_attached_instance_id;
@@ -123,6 +125,17 @@ public:
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
+ virtual bool tts_is_speaking() const override;
+ virtual bool tts_is_paused() const override;
+ virtual Array tts_get_voices() const override;
+
+ virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
+ virtual void tts_pause() override;
+ virtual void tts_resume() override;
+ virtual void tts_stop() override;
+
+ virtual Rect2i get_display_safe_area() const override;
+
virtual int get_screen_count() const override;
virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm
index a0f8daf5a0..573ee9b7a8 100644
--- a/platform/iphone/display_server_iphone.mm
+++ b/platform/iphone/display_server_iphone.mm
@@ -38,6 +38,7 @@
#include "ios.h"
#import "keyboard_input_view.h"
#include "os_iphone.h"
+#include "tts_ios.h"
#import "view_controller.h"
#import <Foundation/Foundation.h>
@@ -52,6 +53,9 @@ DisplayServerIPhone *DisplayServerIPhone::get_singleton() {
DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
rendering_driver = p_rendering_driver;
+ // Init TTS
+ tts = [[TTS_IOS alloc] init];
+
#if defined(GLES3_ENABLED)
// FIXME: Add support for both OpenGL and Vulkan when OpenGL is implemented
// again,
@@ -310,6 +314,7 @@ bool DisplayServerIPhone::has_feature(Feature p_feature) const {
case FEATURE_ORIENTATION:
case FEATURE_TOUCHSCREEN:
case FEATURE_VIRTUAL_KEYBOARD:
+ case FEATURE_TEXT_TO_SPEECH:
return true;
default:
return false;
@@ -320,6 +325,57 @@ String DisplayServerIPhone::get_name() const {
return "iPhone";
}
+bool DisplayServerIPhone::tts_is_speaking() const {
+ ERR_FAIL_COND_V(!tts, false);
+ return [tts isSpeaking];
+}
+
+bool DisplayServerIPhone::tts_is_paused() const {
+ ERR_FAIL_COND_V(!tts, false);
+ return [tts isPaused];
+}
+
+Array DisplayServerIPhone::tts_get_voices() const {
+ ERR_FAIL_COND_V(!tts, Array());
+ return [tts getVoices];
+}
+
+void DisplayServerIPhone::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
+ ERR_FAIL_COND(!tts);
+ [tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt];
+}
+
+void DisplayServerIPhone::tts_pause() {
+ ERR_FAIL_COND(!tts);
+ [tts pauseSpeaking];
+}
+
+void DisplayServerIPhone::tts_resume() {
+ ERR_FAIL_COND(!tts);
+ [tts resumeSpeaking];
+}
+
+void DisplayServerIPhone::tts_stop() {
+ ERR_FAIL_COND(!tts);
+ [tts stopSpeaking];
+}
+
+Rect2i DisplayServerIPhone::get_display_safe_area() const {
+ if (@available(iOS 11, *)) {
+ UIEdgeInsets insets = UIEdgeInsetsZero;
+ UIView *view = AppDelegate.viewController.godotView;
+ if ([view respondsToSelector:@selector(safeAreaInsets)]) {
+ insets = [view safeAreaInsets];
+ }
+ float scale = screen_get_scale();
+ Size2i insets_position = Size2i(insets.left, insets.top) * scale;
+ Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale;
+ return Rect2i(screen_get_position() + insets_position, screen_get_size() - insets_size);
+ } else {
+ return Rect2i(screen_get_position(), screen_get_size());
+ }
+}
+
int DisplayServerIPhone::get_screen_count() const {
return 1;
}
@@ -339,22 +395,7 @@ Size2i DisplayServerIPhone::screen_get_size(int p_screen) const {
}
Rect2i DisplayServerIPhone::screen_get_usable_rect(int p_screen) const {
- if (@available(iOS 11, *)) {
- UIEdgeInsets insets = UIEdgeInsetsZero;
- UIView *view = AppDelegate.viewController.godotView;
-
- if ([view respondsToSelector:@selector(safeAreaInsets)]) {
- insets = [view safeAreaInsets];
- }
-
- float scale = screen_get_scale(p_screen);
- Size2i insets_position = Size2i(insets.left, insets.top) * scale;
- Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale;
-
- return Rect2i(screen_get_position(p_screen) + insets_position, screen_get_size(p_screen) - insets_size);
- } else {
- return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen));
- }
+ return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen));
}
int DisplayServerIPhone::screen_get_dpi(int p_screen) const {
diff --git a/platform/iphone/export/export_plugin.cpp b/platform/iphone/export/export_plugin.cpp
index ac5886e620..57bee59523 100644
--- a/platform/iphone/export/export_plugin.cpp
+++ b/platform/iphone/export/export_plugin.cpp
@@ -530,8 +530,8 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr
String json_description = "{\"images\":[";
String sizes;
- DirAccessRef da = DirAccess::open(p_iconset_dir);
- ERR_FAIL_COND_V_MSG(!da, ERR_CANT_OPEN, "Cannot open directory '" + p_iconset_dir + "'.");
+ Ref<DirAccess> da = DirAccess::open(p_iconset_dir);
+ ERR_FAIL_COND_V_MSG(da.is_null(), ERR_CANT_OPEN, "Cannot open directory '" + p_iconset_dir + "'.");
for (uint64_t i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) {
IconInfo info = icon_infos[i];
@@ -588,17 +588,15 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr
}
json_description += "]}";
- FileAccess *json_file = FileAccess::open(p_iconset_dir + "Contents.json", FileAccess::WRITE);
- ERR_FAIL_COND_V(!json_file, ERR_CANT_CREATE);
+ Ref<FileAccess> json_file = FileAccess::open(p_iconset_dir + "Contents.json", FileAccess::WRITE);
+ ERR_FAIL_COND_V(json_file.is_null(), ERR_CANT_CREATE);
CharString json_utf8 = json_description.utf8();
json_file->store_buffer((const uint8_t *)json_utf8.get_data(), json_utf8.length());
- memdelete(json_file);
- FileAccess *sizes_file = FileAccess::open(p_iconset_dir + "sizes", FileAccess::WRITE);
- ERR_FAIL_COND_V(!sizes_file, ERR_CANT_CREATE);
+ Ref<FileAccess> sizes_file = FileAccess::open(p_iconset_dir + "sizes", FileAccess::WRITE);
+ ERR_FAIL_COND_V(sizes_file.is_null(), ERR_CANT_CREATE);
CharString sizes_utf8 = sizes.utf8();
sizes_file->store_buffer((const uint8_t *)sizes_utf8.get_data(), sizes_utf8.length());
- memdelete(sizes_file);
return OK;
}
@@ -672,8 +670,8 @@ Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref<EditorExpor
}
Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) {
- DirAccessRef da = DirAccess::open(p_dest_dir);
- ERR_FAIL_COND_V_MSG(!da, ERR_CANT_OPEN, "Cannot open directory '" + p_dest_dir + "'.");
+ Ref<DirAccess> da = DirAccess::open(p_dest_dir);
+ ERR_FAIL_COND_V_MSG(da.is_null(), ERR_CANT_OPEN, "Cannot open directory '" + p_dest_dir + "'.");
for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) {
LoadingScreenInfo info = loading_screen_infos[i];
@@ -761,7 +759,7 @@ Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExp
return OK;
}
-Error EditorExportPlatformIOS::_walk_dir_recursive(DirAccess *p_da, FileHandler p_handler, void *p_userdata) {
+Error EditorExportPlatformIOS::_walk_dir_recursive(Ref<DirAccess> &p_da, FileHandler p_handler, void *p_userdata) {
Vector<String> dirs;
String current_dir = p_da->get_current_dir();
p_da->list_dir_begin();
@@ -807,7 +805,7 @@ struct CodesignData {
Error EditorExportPlatformIOS::_codesign(String p_file, void *p_userdata) {
if (p_file.ends_with(".dylib")) {
- CodesignData *data = (CodesignData *)p_userdata;
+ CodesignData *data = static_cast<CodesignData *>(p_userdata);
print_line(String("Signing ") + p_file);
String sign_id;
@@ -964,8 +962,8 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese
Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) {
String binary_name = p_out_dir.get_file().get_basename();
- DirAccessRef da = DirAccess::create_for_path(p_asset);
- if (!da) {
+ Ref<DirAccess> da = DirAccess::create_for_path(p_asset);
+ if (da.is_null()) {
ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + p_asset + ".");
}
bool file_exists = da->file_exists(p_asset);
@@ -1030,8 +1028,8 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String
destination = p_out_dir.plus_file(asset_path);
}
- DirAccessRef filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'.");
+ Ref<DirAccess> filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ ERR_FAIL_COND_V_MSG(filesystem_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'.");
if (!filesystem_da->dir_exists(destination_dir)) {
Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir);
@@ -1097,11 +1095,9 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String
String info_plist = info_plist_format.replace("$name", file_name);
- FileAccess *f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE);
- if (f) {
+ Ref<FileAccess> f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE);
+ if (f.is_valid()) {
f->store_string(info_plist);
- f->close();
- memdelete(f);
}
}
}
@@ -1411,8 +1407,8 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
}
{
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- if (da) {
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ if (da.is_valid()) {
String current_dir = da->get_current_dir();
// remove leftovers from last export so they don't interfere
@@ -1488,12 +1484,11 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
Vector<IOSExportAsset> assets;
- DirAccessRef tmp_app_path = DirAccess::create_for_path(dest_dir);
- ERR_FAIL_COND_V(!tmp_app_path, ERR_CANT_CREATE);
+ Ref<DirAccess> tmp_app_path = DirAccess::create_for_path(dest_dir);
+ ERR_FAIL_COND_V(tmp_app_path.is_null(), ERR_CANT_CREATE);
print_line("Unzipping...");
- FileAccess *src_f = nullptr;
- zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
+ zlib_filefunc_def io = zipio_create_io();
unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io);
if (!src_pkg_zip) {
EditorNode::add_io_error("Could not open export template (not a zip file?):\n" + src_pkg_name);
@@ -1515,6 +1510,9 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
unz_file_info info;
char fname[16384];
ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0);
+ if (ret != UNZ_OK) {
+ break;
+ }
String file = String::utf8(fname);
@@ -1572,15 +1570,15 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
}
/* write the file */
- FileAccess *f = FileAccess::open(file, FileAccess::WRITE);
- if (!f) {
- ERR_PRINT("Can't write '" + file + "'.");
- unzClose(src_pkg_zip);
- return ERR_CANT_CREATE;
- };
- f->store_buffer(data.ptr(), data.size());
- f->close();
- memdelete(f);
+ {
+ Ref<FileAccess> f = FileAccess::open(file, FileAccess::WRITE);
+ if (f.is_null()) {
+ ERR_PRINT("Can't write '" + file + "'.");
+ unzClose(src_pkg_zip);
+ return ERR_CANT_CREATE;
+ };
+ f->store_buffer(data.ptr(), data.size());
+ }
#if defined(OSX_ENABLED) || defined(X11_ENABLED)
if (is_execute) {
@@ -1611,7 +1609,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
{
String fname = dest_dir + binary_name + "/en.lproj";
tmp_app_path->make_dir_recursive(fname);
- FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
+ Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
f->store_line("/* Localized versions of Info.plist keys */");
f->store_line("");
f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get("application/config/name").operator String() + "\";");
@@ -1626,7 +1624,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
String lang = tr->get_locale();
String fname = dest_dir + binary_name + "/" + lang + ".lproj";
tmp_app_path->make_dir_recursive(fname);
- FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
+ Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
f->store_line("/* Localized versions of Info.plist keys */");
f->store_line("");
if (appnames.has(lang)) {
@@ -1680,9 +1678,8 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
String launch_image_path = dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/";
String splash_image_path = dest_dir + binary_name + "/Images.xcassets/SplashImage.imageset/";
- DirAccessRef launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
-
- if (!launch_screen_da) {
+ Ref<DirAccess> launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ if (launch_screen_da.is_null()) {
return ERR_CANT_CREATE;
}
@@ -1719,25 +1716,24 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
_export_additional_assets(dest_dir + binary_name, libraries, assets);
_add_assets_to_project(p_preset, project_file_data, assets);
String project_file_name = dest_dir + binary_name + ".xcodeproj/project.pbxproj";
- FileAccess *f = FileAccess::open(project_file_name, FileAccess::WRITE);
- if (!f) {
- ERR_PRINT("Can't write '" + project_file_name + "'.");
- return ERR_CANT_CREATE;
- };
- f->store_buffer(project_file_data.ptr(), project_file_data.size());
- f->close();
- memdelete(f);
+ {
+ Ref<FileAccess> f = FileAccess::open(project_file_name, FileAccess::WRITE);
+ if (f.is_null()) {
+ ERR_PRINT("Can't write '" + project_file_name + "'.");
+ return ERR_CANT_CREATE;
+ };
+ f->store_buffer(project_file_data.ptr(), project_file_data.size());
+ }
#ifdef OSX_ENABLED
{
if (ep.step("Code-signing dylibs", 2)) {
return ERR_SKIP;
}
- DirAccess *dylibs_dir = DirAccess::open(dest_dir + binary_name + "/dylibs");
- ERR_FAIL_COND_V(!dylibs_dir, ERR_CANT_OPEN);
+ Ref<DirAccess> dylibs_dir = DirAccess::open(dest_dir + binary_name + "/dylibs");
+ ERR_FAIL_COND_V(dylibs_dir.is_null(), ERR_CANT_OPEN);
CodesignData codesign_data(p_preset, p_debug);
err = _walk_dir_recursive(dylibs_dir, _codesign, &codesign_data);
- memdelete(dylibs_dir);
ERR_FAIL_COND_V(err, err);
}
diff --git a/platform/iphone/export/export_plugin.h b/platform/iphone/export/export_plugin.h
index c01983e39f..2c6faed691 100644
--- a/platform/iphone/export/export_plugin.h
+++ b/platform/iphone/export/export_plugin.h
@@ -53,8 +53,6 @@
class EditorExportPlatformIOS : public EditorExportPlatform {
GDCLASS(EditorExportPlatformIOS, EditorExportPlatform);
- int version_code;
-
Ref<ImageTexture> logo;
// Plugins
@@ -65,7 +63,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
Vector<PluginConfigIOS> plugins;
typedef Error (*FileHandler)(String p_file, void *p_userdata);
- static Error _walk_dir_recursive(DirAccess *p_da, FileHandler p_handler, void *p_userdata);
+ static Error _walk_dir_recursive(Ref<DirAccess> &p_da, FileHandler p_handler, void *p_userdata);
static Error _codesign(String p_file, void *p_userdata);
void _blend_and_rotate(Ref<Image> &p_dst, Ref<Image> &p_src, bool p_rot);
@@ -141,7 +139,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
}
static void _check_for_changes_poll_thread(void *ud) {
- EditorExportPlatformIOS *ea = (EditorExportPlatformIOS *)ud;
+ EditorExportPlatformIOS *ea = static_cast<EditorExportPlatformIOS *>(ud);
while (!ea->quit_request.is_set()) {
// Nothing to do if we already know the plugins have changed.
@@ -215,8 +213,8 @@ public:
/// List the gdip files in the directory specified by the p_path parameter.
static Vector<String> list_plugin_config_files(const String &p_path, bool p_check_directories) {
Vector<String> dir_files;
- DirAccessRef da = DirAccess::open(p_path);
- if (da) {
+ Ref<DirAccess> da = DirAccess::open(p_path);
+ if (da.is_valid()) {
da->list_dir_begin();
while (true) {
String file = da->get_next();
diff --git a/platform/iphone/godot_view_gesture_recognizer.mm b/platform/iphone/godot_view_gesture_recognizer.mm
index c8137f35ff..49a92add5e 100644
--- a/platform/iphone/godot_view_gesture_recognizer.mm
+++ b/platform/iphone/godot_view_gesture_recognizer.mm
@@ -70,6 +70,7 @@ const CGFloat kGLGestureMovementDistance = 0.5;
self.cancelsTouchesInView = YES;
self.delaysTouchesBegan = YES;
self.delaysTouchesEnded = YES;
+ self.requiresExclusiveTouchType = NO;
self.delayTimeInterval = GLOBAL_GET("input_devices/pointing/ios/touch_delay");
diff --git a/platform/iphone/ios.h b/platform/iphone/ios.h
index 03e663b05c..0607d7b395 100644
--- a/platform/iphone/ios.h
+++ b/platform/iphone/ios.h
@@ -32,15 +32,26 @@
#define IOS_H
#include "core/object/class_db.h"
+#import <CoreHaptics/CoreHaptics.h>
class iOS : public Object {
GDCLASS(iOS, Object);
static void _bind_methods();
+private:
+ CHHapticEngine *haptic_engine API_AVAILABLE(ios(13)) = nullptr;
+
+ CHHapticEngine *get_haptic_engine_instance() API_AVAILABLE(ios(13));
+ void start_haptic_engine();
+ void stop_haptic_engine();
+
public:
static void alert(const char *p_alert, const char *p_title);
+ bool supports_haptic_engine();
+ void vibrate_haptic_engine(float p_duration_seconds);
+
String get_model() const;
String get_rate_url(int p_app_id) const;
diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm
index ad1ea70c10..cca28cc055 100644
--- a/platform/iphone/ios.mm
+++ b/platform/iphone/ios.mm
@@ -32,11 +32,110 @@
#import "app_delegate.h"
#import "view_controller.h"
+
+#import <CoreHaptics/CoreHaptics.h>
#import <UIKit/UIKit.h>
#include <sys/sysctl.h>
void iOS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url);
+ ClassDB::bind_method(D_METHOD("supports_haptic_engine"), &iOS::supports_haptic_engine);
+ ClassDB::bind_method(D_METHOD("start_haptic_engine"), &iOS::start_haptic_engine);
+ ClassDB::bind_method(D_METHOD("stop_haptic_engine"), &iOS::stop_haptic_engine);
+};
+
+bool iOS::supports_haptic_engine() {
+ if (@available(iOS 13, *)) {
+ id<CHHapticDeviceCapability> capabilities = [CHHapticEngine capabilitiesForHardware];
+ return capabilities.supportsHaptics;
+ }
+
+ return false;
+}
+
+CHHapticEngine *iOS::get_haptic_engine_instance() API_AVAILABLE(ios(13)) {
+ if (haptic_engine == nullptr) {
+ NSError *error = nullptr;
+ haptic_engine = [[CHHapticEngine alloc] initAndReturnError:&error];
+
+ if (!error) {
+ [haptic_engine setAutoShutdownEnabled:true];
+ } else {
+ haptic_engine = nullptr;
+ NSLog(@"Could not initialize haptic engine: %@", error);
+ }
+ }
+
+ return haptic_engine;
+}
+
+void iOS::vibrate_haptic_engine(float p_duration_seconds) API_AVAILABLE(ios(13)) {
+ if (@available(iOS 13, *)) { // We need the @available check every time to make the compiler happy...
+ if (supports_haptic_engine()) {
+ CHHapticEngine *haptic_engine = get_haptic_engine_instance();
+ if (haptic_engine) {
+ NSDictionary *hapticDict = @{
+ CHHapticPatternKeyPattern : @[
+ @{CHHapticPatternKeyEvent : @{
+ CHHapticPatternKeyEventType : CHHapticEventTypeHapticTransient,
+ CHHapticPatternKeyTime : @(CHHapticTimeImmediate),
+ CHHapticPatternKeyEventDuration : @(p_duration_seconds)
+ },
+ },
+ ],
+ };
+
+ NSError *error;
+ CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:hapticDict error:&error];
+
+ [[haptic_engine createPlayerWithPattern:pattern error:&error] startAtTime:0 error:&error];
+
+ NSLog(@"Could not vibrate using haptic engine: %@", error);
+ }
+
+ return;
+ }
+ }
+
+ NSLog(@"Haptic engine is not supported in this version of iOS");
+}
+
+void iOS::start_haptic_engine() {
+ if (@available(iOS 13, *)) {
+ if (supports_haptic_engine()) {
+ CHHapticEngine *haptic_engine = get_haptic_engine_instance();
+ if (haptic_engine) {
+ [haptic_engine startWithCompletionHandler:^(NSError *returnedError) {
+ if (returnedError) {
+ NSLog(@"Could not start haptic engine: %@", returnedError);
+ }
+ }];
+ }
+
+ return;
+ }
+ }
+
+ NSLog(@"Haptic engine is not supported in this version of iOS");
+}
+
+void iOS::stop_haptic_engine() {
+ if (@available(iOS 13, *)) {
+ if (supports_haptic_engine()) {
+ CHHapticEngine *haptic_engine = get_haptic_engine_instance();
+ if (haptic_engine) {
+ [haptic_engine stopWithCompletionHandler:^(NSError *returnedError) {
+ if (returnedError) {
+ NSLog(@"Could not stop haptic engine: %@", returnedError);
+ }
+ }];
+ }
+
+ return;
+ }
+ }
+
+ NSLog(@"Haptic engine is not supported in this version of iOS");
}
void iOS::alert(const char *p_alert, const char *p_title) {
diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h
index 3281ff0cdb..d03403bbb4 100644
--- a/platform/iphone/os_iphone.h
+++ b/platform/iphone/os_iphone.h
@@ -52,11 +52,11 @@ private:
AudioDriverCoreAudio audio_driver;
- iOS *ios;
+ iOS *ios = nullptr;
- JoypadIPhone *joypad_iphone;
+ JoypadIPhone *joypad_iphone = nullptr;
- MainLoop *main_loop;
+ MainLoop *main_loop = nullptr;
virtual void initialize_core() override;
virtual void initialize() override;
@@ -92,7 +92,7 @@ public:
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
- virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override;
+ virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override;
virtual Error close_dynamic_library(void *p_library_handle) override;
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override;
diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm
index 1d990b5625..95b06b728e 100644
--- a/platform/iphone/os_iphone.mm
+++ b/platform/iphone/os_iphone.mm
@@ -204,12 +204,17 @@ void OSIPhone::finalize() {
// MARK: Dynamic Libraries
-Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
+Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
if (p_path.length() == 0) {
p_library_handle = RTLD_SELF;
+
+ if (r_resolved_path != nullptr) {
+ *r_resolved_path = p_path;
+ }
+
return OK;
}
- return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path);
+ return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path, r_resolved_path);
}
Error OSIPhone::close_dynamic_library(void *p_library_handle) {
@@ -259,7 +264,7 @@ Error OSIPhone::shell_open(String p_uri) {
}
void OSIPhone::set_user_data_dir(String p_dir) {
- DirAccessRef da = DirAccess::open(p_dir);
+ Ref<DirAccess> da = DirAccess::open(p_dir);
user_data_dir = da->get_current_dir();
printf("setting data dir to %s from %s\n", user_data_dir.utf8().get_data(), p_dir.utf8().get_data());
}
@@ -298,8 +303,12 @@ String OSIPhone::get_processor_name() const {
}
void OSIPhone::vibrate_handheld(int p_duration_ms) {
- // iOS does not support duration for vibration
- AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
+ if (ios->supports_haptic_engine()) {
+ ios->vibrate_haptic_engine((float)p_duration_ms / 1000.f);
+ } else {
+ // iOS <13 does not support duration for vibration
+ AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
+ }
}
bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
diff --git a/platform/iphone/tts_ios.h b/platform/iphone/tts_ios.h
new file mode 100644
index 0000000000..3fac762b62
--- /dev/null
+++ b/platform/iphone/tts_ios.h
@@ -0,0 +1,63 @@
+/*************************************************************************/
+/* tts_ios.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 TTS_IOS_H
+#define TTS_IOS_H
+
+#if __has_include(<AVFAudio/AVSpeechSynthesis.h>)
+#import <AVFAudio/AVSpeechSynthesis.h>
+#else
+#import <AVFoundation/AVFoundation.h>
+#endif
+
+#include "core/string/ustring.h"
+#include "core/templates/list.h"
+#include "core/templates/map.h"
+#include "core/variant/array.h"
+#include "servers/display_server.h"
+
+@interface TTS_IOS : NSObject <AVSpeechSynthesizerDelegate> {
+ bool speaking;
+ Map<id, int> ids;
+
+ AVSpeechSynthesizer *av_synth;
+ List<DisplayServer::TTSUtterance> queue;
+}
+
+- (void)pauseSpeaking;
+- (void)resumeSpeaking;
+- (void)stopSpeaking;
+- (bool)isSpeaking;
+- (bool)isPaused;
+- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt;
+- (Array)getVoices;
+@end
+
+#endif // TTS_IOS_H
diff --git a/platform/iphone/tts_ios.mm b/platform/iphone/tts_ios.mm
new file mode 100644
index 0000000000..a079d02add
--- /dev/null
+++ b/platform/iphone/tts_ios.mm
@@ -0,0 +1,164 @@
+/*************************************************************************/
+/* tts_ios.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "tts_ios.h"
+
+@implementation TTS_IOS
+
+- (id)init {
+ self = [super init];
+ self->speaking = false;
+ self->av_synth = [[AVSpeechSynthesizer alloc] init];
+ [self->av_synth setDelegate:self];
+ print_verbose("Text-to-Speech: AVSpeechSynthesizer initialized.");
+ return self;
+}
+
+- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth willSpeakRangeOfSpeechString:(NSRange)characterRange utterance:(AVSpeechUtterance *)utterance {
+ NSString *string = [utterance speechString];
+
+ // Convert from UTF-16 to UTF-32 position.
+ int pos = 0;
+ for (NSUInteger i = 0; i < MIN(characterRange.location, string.length); i++) {
+ unichar c = [string characterAtIndex:i];
+ if ((c & 0xfffffc00) == 0xd800) {
+ i++;
+ }
+ pos++;
+ }
+
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, ids[utterance], pos);
+}
+
+- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didCancelSpeechUtterance:(AVSpeechUtterance *)utterance {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, ids[utterance]);
+ ids.erase(utterance);
+ speaking = false;
+ [self update];
+}
+
+- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didFinishSpeechUtterance:(AVSpeechUtterance *)utterance {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, ids[utterance]);
+ ids.erase(utterance);
+ speaking = false;
+ [self update];
+}
+
+- (void)update {
+ if (!speaking && queue.size() > 0) {
+ DisplayServer::TTSUtterance &message = queue.front()->get();
+
+ AVSpeechUtterance *new_utterance = [[AVSpeechUtterance alloc] initWithString:[NSString stringWithUTF8String:message.text.utf8().get_data()]];
+ [new_utterance setVoice:[AVSpeechSynthesisVoice voiceWithIdentifier:[NSString stringWithUTF8String:message.voice.utf8().get_data()]]];
+ if (message.rate > 1.f) {
+ [new_utterance setRate:Math::range_lerp(message.rate, 1.f, 10.f, AVSpeechUtteranceDefaultSpeechRate, AVSpeechUtteranceMaximumSpeechRate)];
+ } else if (message.rate < 1.f) {
+ [new_utterance setRate:Math::range_lerp(message.rate, 0.1f, 1.f, AVSpeechUtteranceMinimumSpeechRate, AVSpeechUtteranceDefaultSpeechRate)];
+ }
+ [new_utterance setPitchMultiplier:message.pitch];
+ [new_utterance setVolume:(Math::range_lerp(message.volume, 0.f, 100.f, 0.f, 1.f))];
+
+ ids[new_utterance] = message.id;
+ [av_synth speakUtterance:new_utterance];
+
+ queue.pop_front();
+
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, message.id);
+ speaking = true;
+ }
+}
+
+- (void)pauseSpeaking {
+ [av_synth pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate];
+}
+
+- (void)resumeSpeaking {
+ [av_synth continueSpeaking];
+}
+
+- (void)stopSpeaking {
+ for (DisplayServer::TTSUtterance &message : queue) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, message.id);
+ }
+ queue.clear();
+ [av_synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
+ speaking = false;
+}
+
+- (bool)isSpeaking {
+ return speaking || (queue.size() > 0);
+}
+
+- (bool)isPaused {
+ return [av_synth isPaused];
+}
+
+- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt {
+ if (interrupt) {
+ [self stopSpeaking];
+ }
+
+ if (text.is_empty()) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, utterance_id);
+ return;
+ }
+
+ DisplayServer::TTSUtterance message;
+ message.text = text;
+ message.voice = voice;
+ message.volume = CLAMP(volume, 0, 100);
+ message.pitch = CLAMP(pitch, 0.f, 2.f);
+ message.rate = CLAMP(rate, 0.1f, 10.f);
+ message.id = utterance_id;
+ queue.push_back(message);
+
+ if ([self isPaused]) {
+ [self resumeSpeaking];
+ } else {
+ [self update];
+ }
+}
+
+- (Array)getVoices {
+ Array list;
+ for (AVSpeechSynthesisVoice *voice in [AVSpeechSynthesisVoice speechVoices]) {
+ NSString *voiceIdentifierString = [voice identifier];
+ NSString *voiceLocaleIdentifier = [voice language];
+ NSString *voiceName = [voice name];
+ Dictionary voice_d;
+ voice_d["name"] = String::utf8([voiceName UTF8String]);
+ voice_d["id"] = String::utf8([voiceIdentifierString UTF8String]);
+ voice_d["language"] = String::utf8([voiceLocaleIdentifier UTF8String]);
+ list.push_back(voice_d);
+ }
+ return list;
+}
+
+@end
diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp
index 0442a1eaeb..198af61eff 100644
--- a/platform/javascript/api/javascript_tools_editor_plugin.cpp
+++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp
@@ -64,8 +64,7 @@ void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) {
}
String resource_path = ProjectSettings::get_singleton()->get_resource_path();
- FileAccess *src_f;
- zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
+ zlib_filefunc_def io = zipio_create_io();
// Name the downloaded 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.
@@ -82,22 +81,22 @@ void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) {
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(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(), output_name.utf8().get_data(), "application/zip");
+ {
+ Ref<FileAccess> f = FileAccess::open(output_path, FileAccess::READ);
+ ERR_FAIL_COND_MSG(f.is_null(), "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(), 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) {
- FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
- if (!f) {
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
+ if (f.is_null()) {
WARN_PRINT("Unable to open file for zipping: " + p_path);
return;
}
@@ -105,8 +104,6 @@ void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, z
uint64_t len = f->get_length();
data.resize(len);
f->get_buffer(data.ptrw(), len);
- f->close();
- memdelete(f);
String path = p_path.replace_first(p_base_path, "");
zipOpenNewFileInZip(p_zip,
@@ -124,8 +121,8 @@ 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) {
- DirAccessRef dir = DirAccess::open(p_path);
- if (!dir) {
+ Ref<DirAccess> dir = DirAccess::open(p_path);
+ if (dir.is_null()) {
WARN_PRINT("Unable to open directory for zipping: " + p_path);
return;
}
diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py
index b6be44fbb2..709104c5ee 100644
--- a/platform/javascript/detect.py
+++ b/platform/javascript/detect.py
@@ -212,6 +212,8 @@ def configure(env):
sys.exit(255)
env.Append(CCFLAGS=["-s", "RELOCATABLE=1"])
env.Append(LINKFLAGS=["-s", "RELOCATABLE=1"])
+ # Weak symbols are broken upstream: https://github.com/emscripten-core/emscripten/issues/12819
+ env.Append(CPPDEFINES=["ZSTD_HAVE_WEAK_SYMBOLS=0"])
env.extra_suffix = ".gdnative" + env.extra_suffix
# Reduce code size by generating less support code (e.g. skip NodeJS support).
diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp
index a38040922d..312707c3b5 100644
--- a/platform/javascript/display_server_javascript.cpp
+++ b/platform/javascript/display_server_javascript.cpp
@@ -274,6 +274,90 @@ const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape
}
}
+bool DisplayServerJavaScript::tts_is_speaking() const {
+ return godot_js_tts_is_speaking();
+}
+
+bool DisplayServerJavaScript::tts_is_paused() const {
+ return godot_js_tts_is_paused();
+}
+
+void DisplayServerJavaScript::update_voices_callback(int p_size, const char **p_voice) {
+ get_singleton()->voices.clear();
+ for (int i = 0; i < p_size; i++) {
+ Vector<String> tokens = String::utf8(p_voice[i]).split(";", true, 2);
+ if (tokens.size() == 2) {
+ Dictionary voice_d;
+ voice_d["name"] = tokens[1];
+ voice_d["id"] = tokens[1];
+ voice_d["language"] = tokens[0];
+ get_singleton()->voices.push_back(voice_d);
+ }
+ }
+}
+
+Array DisplayServerJavaScript::tts_get_voices() const {
+ godot_js_tts_get_voices(update_voices_callback);
+ return voices;
+}
+
+void DisplayServerJavaScript::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
+ if (p_interrupt) {
+ tts_stop();
+ }
+
+ if (p_text.is_empty()) {
+ tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, p_utterance_id);
+ return;
+ }
+
+ CharString string = p_text.utf8();
+ utterance_ids[p_utterance_id] = string;
+
+ godot_js_tts_speak(string.get_data(), p_voice.utf8().get_data(), CLAMP(p_volume, 0, 100), CLAMP(p_pitch, 0.f, 2.f), CLAMP(p_rate, 0.1f, 10.f), p_utterance_id, DisplayServerJavaScript::_js_utterance_callback);
+}
+
+void DisplayServerJavaScript::tts_pause() {
+ godot_js_tts_pause();
+}
+
+void DisplayServerJavaScript::tts_resume() {
+ godot_js_tts_resume();
+}
+
+void DisplayServerJavaScript::tts_stop() {
+ for (Map<int, CharString>::Element *E = utterance_ids.front(); E; E = E->next()) {
+ tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, E->key());
+ }
+ utterance_ids.clear();
+ godot_js_tts_stop();
+}
+
+void DisplayServerJavaScript::_js_utterance_callback(int p_event, int p_id, int p_pos) {
+ DisplayServerJavaScript *ds = (DisplayServerJavaScript *)DisplayServer::get_singleton();
+ if (ds->utterance_ids.has(p_id)) {
+ int pos = 0;
+ if ((TTSUtteranceEvent)p_event == DisplayServer::TTS_UTTERANCE_BOUNDARY) {
+ // Convert position from UTF-8 to UTF-32.
+ const CharString &string = ds->utterance_ids[p_id];
+ for (int i = 0; i < MIN(p_pos, string.length()); i++) {
+ uint8_t c = string[i];
+ if ((c & 0xe0) == 0xc0) {
+ i += 1;
+ } else if ((c & 0xf0) == 0xe0) {
+ i += 2;
+ } else if ((c & 0xf8) == 0xf0) {
+ i += 3;
+ }
+ pos++;
+ }
+ } else if ((TTSUtteranceEvent)p_event != DisplayServer::TTS_UTTERANCE_STARTED) {
+ ds->utterance_ids.erase(p_id);
+ }
+ ds->tts_post_utterance_event((TTSUtteranceEvent)p_event, p_id, pos);
+ }
+}
+
void DisplayServerJavaScript::cursor_set_shape(CursorShape p_shape) {
ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
if (cursor_shape == p_shape) {
@@ -287,7 +371,7 @@ DisplayServer::CursorShape DisplayServerJavaScript::cursor_get_shape() const {
return cursor_shape;
}
-void DisplayServerJavaScript::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+void DisplayServerJavaScript::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
if (p_cursor.is_valid()) {
Ref<Texture2D> texture = p_cursor;
Ref<AtlasTexture> atlas_texture = p_cursor;
@@ -755,6 +839,8 @@ bool DisplayServerJavaScript::has_feature(Feature p_feature) const {
//case FEATURE_ORIENTATION:
case FEATURE_VIRTUAL_KEYBOARD:
return godot_js_display_vk_available() != 0;
+ case FEATURE_TEXT_TO_SPEECH:
+ return godot_js_display_tts_available() != 0;
default:
return false;
}
diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h
index b50956d91c..623546bbd0 100644
--- a/platform/javascript/display_server_javascript.h
+++ b/platform/javascript/display_server_javascript.h
@@ -55,6 +55,8 @@ private:
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE webgl_ctx = 0;
#endif
+ Map<int, CharString> utterance_ids;
+
WindowMode window_mode = WINDOW_MODE_WINDOWED;
ObjectID window_attached_instance_id = {};
@@ -66,6 +68,8 @@ private:
String clipboard;
Point2 touches[32];
+ Array voices;
+
char canvas_id[256] = { 0 };
bool cursor_inside_canvas = true;
CursorShape cursor_shape = CURSOR_ARROW;
@@ -89,6 +93,7 @@ private:
static void vk_input_text_callback(const char *p_text, int p_cursor);
static void gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid);
void process_joypads();
+ static void _js_utterance_callback(int p_event, int p_id, int p_pos);
static Vector<String> get_rendering_drivers_func();
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
@@ -97,6 +102,7 @@ private:
static void request_quit_callback();
static void window_blur_callback();
+ static void update_voices_callback(int p_size, const char **p_voice);
static void update_clipboard_callback(const char *p_text);
static void send_window_event_callback(int p_notification);
static void drop_files_js_callback(char **p_filev, int p_filec);
@@ -115,10 +121,20 @@ public:
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
+ // tts
+ virtual bool tts_is_speaking() const override;
+ virtual bool tts_is_paused() const override;
+ virtual Array tts_get_voices() const override;
+
+ virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
+ virtual void tts_pause() override;
+ virtual void tts_resume() override;
+ virtual void tts_stop() override;
+
// cursor
virtual void cursor_set_shape(CursorShape p_shape) override;
virtual CursorShape cursor_get_shape() const override;
- virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;
+ virtual void cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;
// mouse
virtual void mouse_set_mode(MouseMode p_mode) override;
diff --git a/platform/javascript/export/export_plugin.cpp b/platform/javascript/export/export_plugin.cpp
index 84694461cf..66d93d7c49 100644
--- a/platform/javascript/export/export_plugin.cpp
+++ b/platform/javascript/export/export_plugin.cpp
@@ -33,8 +33,7 @@
#include "core/config/project_settings.h"
Error EditorExportPlatformJavaScript::_extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa) {
- FileAccess *src_f = nullptr;
- zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
+ zlib_filefunc_def io = zipio_create_io();
unzFile pkg = unzOpen2(p_template.utf8().get_data(), &io);
if (!pkg) {
@@ -70,14 +69,13 @@ Error EditorExportPlatformJavaScript::_extract_template(const String &p_template
//write
String dst = p_dir.plus_file(file.replace("godot", p_name));
- FileAccess *f = FileAccess::open(dst, FileAccess::WRITE);
- if (!f) {
+ Ref<FileAccess> f = FileAccess::open(dst, FileAccess::WRITE);
+ if (f.is_null()) {
EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + dst);
unzClose(pkg);
return ERR_FILE_CANT_WRITE;
}
f->store_buffer(data.ptr(), data.size());
- memdelete(f);
} while (unzGoToNextFile(pkg) == UNZ_OK);
unzClose(pkg);
@@ -85,13 +83,12 @@ Error EditorExportPlatformJavaScript::_extract_template(const String &p_template
}
Error EditorExportPlatformJavaScript::_write_or_error(const uint8_t *p_content, int p_size, String p_path) {
- FileAccess *f = FileAccess::open(p_path, FileAccess::WRITE);
- if (!f) {
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
+ if (f.is_null()) {
EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + p_path);
return ERR_FILE_CANT_WRITE;
}
f->store_buffer(p_content, p_size);
- memdelete(f);
return OK;
}
@@ -233,15 +230,13 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> &
const String sw_path = dir.plus_file(name + ".service.worker.js");
Vector<uint8_t> sw;
{
- FileAccess *f = FileAccess::open(sw_path, FileAccess::READ);
- if (!f) {
+ Ref<FileAccess> f = FileAccess::open(sw_path, FileAccess::READ);
+ if (f.is_null()) {
EditorNode::get_singleton()->show_warning(TTR("Could not read file:") + "\n" + sw_path);
return ERR_FILE_CANT_READ;
}
sw.resize(f->get_length());
f->get_buffer(sw.ptrw(), sw.size());
- memdelete(f);
- f = nullptr;
}
_replace_strings(replaces, sw);
Error err = _write_or_error(sw.ptr(), sw.size(), dir.plus_file(name + ".service.worker.js"));
@@ -252,7 +247,7 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> &
// Custom offline page
const String offline_page = p_preset->get("progressive_web_app/offline_page");
if (!offline_page.is_empty()) {
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
const String offline_dest = dir.plus_file(name + ".offline.html");
err = da->copy(ProjectSettings::get_singleton()->globalize_path(offline_page), offline_dest);
if (err != OK) {
@@ -456,7 +451,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
}
{
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
for (int i = 0; i < shared_objects.size(); i++) {
String dst = base_dir.plus_file(shared_objects[i].path.get_file());
error = da->copy(shared_objects[i].path, dst);
@@ -475,32 +470,25 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
// Parse generated file sizes (pck and wasm, to help show a meaningful loading bar).
Dictionary file_sizes;
- FileAccess *f = nullptr;
- f = FileAccess::open(pck_path, FileAccess::READ);
- if (f) {
+ Ref<FileAccess> f = FileAccess::open(pck_path, FileAccess::READ);
+ if (f.is_valid()) {
file_sizes[pck_path.get_file()] = (uint64_t)f->get_length();
- memdelete(f);
- f = nullptr;
}
f = FileAccess::open(base_path + ".wasm", FileAccess::READ);
- if (f) {
+ if (f.is_valid()) {
file_sizes[base_name + ".wasm"] = (uint64_t)f->get_length();
- memdelete(f);
- f = nullptr;
}
// Read the HTML shell file (custom or from template).
const String html_path = custom_html.is_empty() ? base_path + ".html" : custom_html;
Vector<uint8_t> html;
f = FileAccess::open(html_path, FileAccess::READ);
- if (!f) {
+ if (f.is_null()) {
EditorNode::get_singleton()->show_warning(TTR("Could not read HTML shell:") + "\n" + html_path);
return ERR_FILE_CANT_READ;
}
html.resize(f->get_length());
f->get_buffer(html.ptrw(), html.size());
- memdelete(f);
- f = nullptr;
// Generate HTML file with replaced strings.
_fix_html(html, p_preset, base_name, p_debug, p_flags, shared_objects, file_sizes);
@@ -586,7 +574,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
}
const String dest = EditorPaths::get_singleton()->get_cache_dir().plus_file("web");
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
if (!da->dir_exists(dest)) {
Error err = da->make_dir_recursive(dest);
if (err != OK) {
@@ -651,7 +639,7 @@ Ref<Texture2D> EditorExportPlatformJavaScript::get_run_icon() const {
}
void EditorExportPlatformJavaScript::_server_thread_poll(void *data) {
- EditorExportPlatformJavaScript *ej = (EditorExportPlatformJavaScript *)data;
+ EditorExportPlatformJavaScript *ej = static_cast<EditorExportPlatformJavaScript *>(data);
while (!ej->server_quit) {
OS::get_singleton()->delay_usec(6900);
{
diff --git a/platform/javascript/export/export_server.h b/platform/javascript/export/export_server.h
index 1b908ad9b0..f77ac3d1ad 100644
--- a/platform/javascript/export/export_server.h
+++ b/platform/javascript/export/export_server.h
@@ -153,8 +153,8 @@ public:
}
const String ctype = mimes[req_ext];
- FileAccess *f = FileAccess::open(filepath, FileAccess::READ);
- ERR_FAIL_COND(!f);
+ Ref<FileAccess> f = FileAccess::open(filepath, FileAccess::READ);
+ ERR_FAIL_COND(f.is_null());
String s = "HTTP/1.1 200 OK\r\n";
s += "Connection: Close\r\n";
s += "Content-Type: " + ctype + "\r\n";
@@ -166,7 +166,6 @@ public:
CharString cs = s.utf8();
Error err = peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1);
if (err != OK) {
- memdelete(f);
ERR_FAIL();
}
@@ -178,11 +177,9 @@ public:
}
err = peer->put_data(bytes, read);
if (err != OK) {
- memdelete(f);
ERR_FAIL();
}
}
- memdelete(f);
}
void poll() {
diff --git a/platform/javascript/godot_js.h b/platform/javascript/godot_js.h
index 2cb5c3025c..1a383c9799 100644
--- a/platform/javascript/godot_js.h
+++ b/platform/javascript/godot_js.h
@@ -67,6 +67,15 @@ extern int godot_js_input_gamepad_sample_get(int p_idx, float r_btns[16], int32_
extern void godot_js_input_paste_cb(void (*p_callback)(const char *p_text));
extern void godot_js_input_drop_files_cb(void (*p_callback)(char **p_filev, int p_filec));
+// TTS
+extern int godot_js_tts_is_speaking();
+extern int godot_js_tts_is_paused();
+extern int godot_js_tts_get_voices(void (*p_callback)(int p_size, const char **p_voices));
+extern void godot_js_tts_speak(const char *p_text, const char *p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, void (*p_callback)(int p_event, int p_id, int p_pos));
+extern void godot_js_tts_pause();
+extern void godot_js_tts_resume();
+extern void godot_js_tts_stop();
+
// Display
extern int godot_js_display_screen_dpi_get();
extern double godot_js_display_pixel_ratio_get();
@@ -109,6 +118,7 @@ extern void godot_js_display_notification_cb(void (*p_callback)(int p_notificati
// Display Virtual Keyboard
extern int godot_js_display_vk_available();
+extern int godot_js_display_tts_available();
extern void godot_js_display_vk_cb(void (*p_input)(const char *p_text, int p_cursor));
extern void godot_js_display_vk_show(const char *p_text, int p_multiline, int p_start, int p_end);
extern void godot_js_display_vk_hide();
diff --git a/platform/javascript/godot_webgl2.h b/platform/javascript/godot_webgl2.h
new file mode 100644
index 0000000000..7c357ff66d
--- /dev/null
+++ b/platform/javascript/godot_webgl2.h
@@ -0,0 +1,37 @@
+/*************************************************************************/
+/* godot_webgl2.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 GODOT_WEBGL2_H
+#define GODOT_WEBGL2_H
+
+#include "GLES3/gl3.h"
+#include "webgl/webgl2.h"
+
+#endif
diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp
index c946302862..32bdfed4c7 100644
--- a/platform/javascript/http_client_javascript.cpp
+++ b/platform/javascript/http_client_javascript.cpp
@@ -76,7 +76,7 @@ void HTTPClientJavaScript::set_connection(const Ref<StreamPeer> &p_connection) {
}
Ref<StreamPeer> HTTPClientJavaScript::get_connection() const {
- ERR_FAIL_V_MSG(REF(), "Accessing an HTTPClientJavaScript's StreamPeer is not supported for the HTML5 platform.");
+ ERR_FAIL_V_MSG(Ref<RefCounted>(), "Accessing an HTTPClientJavaScript's StreamPeer is not supported for the HTML5 platform.");
}
Error HTTPClientJavaScript::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len) {
diff --git a/platform/javascript/javascript_singleton.cpp b/platform/javascript/javascript_singleton.cpp
index c9c4d6e1b9..8dc7aba5f8 100644
--- a/platform/javascript/javascript_singleton.cpp
+++ b/platform/javascript/javascript_singleton.cpp
@@ -207,7 +207,7 @@ int JavaScriptObjectImpl::_variant2js(const void **p_args, int p_pos, godot_js_w
break;
case Variant::INT: {
const int64_t tmp = v->operator int64_t();
- if (tmp >= 1 << 31) {
+ if (tmp >= 1LL << 31) {
r_val->r = (double)tmp;
return Variant::FLOAT;
}
diff --git a/platform/javascript/js/libs/library_godot_display.js b/platform/javascript/js/libs/library_godot_display.js
index 2bdf4e56ad..5997631bf8 100644
--- a/platform/javascript/js/libs/library_godot_display.js
+++ b/platform/javascript/js/libs/library_godot_display.js
@@ -330,6 +330,91 @@ const GodotDisplay = {
return 0;
},
+ godot_js_tts_is_speaking__sig: 'i',
+ godot_js_tts_is_speaking: function () {
+ return window.speechSynthesis.speaking;
+ },
+
+ godot_js_tts_is_paused__sig: 'i',
+ godot_js_tts_is_paused: function () {
+ return window.speechSynthesis.paused;
+ },
+
+ godot_js_tts_get_voices__sig: 'vi',
+ godot_js_tts_get_voices: function (p_callback) {
+ const func = GodotRuntime.get_func(p_callback);
+ try {
+ const arr = [];
+ const voices = window.speechSynthesis.getVoices();
+ for (let i = 0; i < voices.length; i++) {
+ arr.push(`${voices[i].lang};${voices[i].name}`);
+ }
+ const c_ptr = GodotRuntime.allocStringArray(arr);
+ func(arr.length, c_ptr);
+ GodotRuntime.freeStringArray(c_ptr, arr.length);
+ } catch (e) {
+ // Fail graciously.
+ }
+ },
+
+ godot_js_tts_speak__sig: 'viiiffii',
+ godot_js_tts_speak: function (p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_callback) {
+ const func = GodotRuntime.get_func(p_callback);
+
+ function listener_end(evt) {
+ evt.currentTarget.cb(1 /*TTS_UTTERANCE_ENDED*/, evt.currentTarget.id, 0);
+ }
+
+ function listener_start(evt) {
+ evt.currentTarget.cb(0 /*TTS_UTTERANCE_STARTED*/, evt.currentTarget.id, 0);
+ }
+
+ function listener_error(evt) {
+ evt.currentTarget.cb(2 /*TTS_UTTERANCE_CANCELED*/, evt.currentTarget.id, 0);
+ }
+
+ function listener_bound(evt) {
+ evt.currentTarget.cb(3 /*TTS_UTTERANCE_BOUNDARY*/, evt.currentTarget.id, evt.charIndex);
+ }
+
+ const utterance = new SpeechSynthesisUtterance(GodotRuntime.parseString(p_text));
+ utterance.rate = p_rate;
+ utterance.pitch = p_pitch;
+ utterance.volume = p_volume / 100.0;
+ utterance.addEventListener('end', listener_end);
+ utterance.addEventListener('start', listener_start);
+ utterance.addEventListener('error', listener_error);
+ utterance.addEventListener('boundary', listener_bound);
+ utterance.id = p_utterance_id;
+ utterance.cb = func;
+ const voice = GodotRuntime.parseString(p_voice);
+ const voices = window.speechSynthesis.getVoices();
+ for (let i = 0; i < voices.length; i++) {
+ if (voices[i].name === voice) {
+ utterance.voice = voices[i];
+ break;
+ }
+ }
+ window.speechSynthesis.resume();
+ window.speechSynthesis.speak(utterance);
+ },
+
+ godot_js_tts_pause__sig: 'v',
+ godot_js_tts_pause: function () {
+ window.speechSynthesis.pause();
+ },
+
+ godot_js_tts_resume__sig: 'v',
+ godot_js_tts_resume: function () {
+ window.speechSynthesis.resume();
+ },
+
+ godot_js_tts_stop__sig: 'v',
+ godot_js_tts_stop: function () {
+ window.speechSynthesis.cancel();
+ window.speechSynthesis.resume();
+ },
+
godot_js_display_alert__sig: 'vi',
godot_js_display_alert: function (p_text) {
window.alert(GodotRuntime.parseString(p_text)); // eslint-disable-line no-alert
@@ -377,6 +462,7 @@ const GodotDisplay = {
GodotRuntime.setHeapValue(height, window.screen.height * scale, 'i32');
},
+ godot_js_display_window_size_get__sig: 'vii',
godot_js_display_window_size_get: function (p_width, p_height) {
GodotRuntime.setHeapValue(p_width, GodotConfig.canvas.width, 'i32');
GodotRuntime.setHeapValue(p_height, GodotConfig.canvas.height, 'i32');
@@ -625,6 +711,11 @@ const GodotDisplay = {
return GodotDisplayVK.available();
},
+ godot_js_display_tts_available__sig: 'i',
+ godot_js_display_tts_available: function () {
+ return 'speechSynthesis' in window;
+ },
+
godot_js_display_vk_cb__sig: 'vi',
godot_js_display_vk_cb: function (p_input_cb) {
const input_cb = GodotRuntime.get_func(p_input_cb);
diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js
index 12d06a8d51..377eec3234 100644
--- a/platform/javascript/js/libs/library_godot_os.js
+++ b/platform/javascript/js/libs/library_godot_os.js
@@ -305,7 +305,9 @@ const GodotOS = {
godot_js_os_hw_concurrency_get__sig: 'i',
godot_js_os_hw_concurrency_get: function () {
- return navigator.hardwareConcurrency || 1;
+ // TODO Godot core needs fixing to avoid spawning too many threads (> 24).
+ const concurrency = navigator.hardwareConcurrency || 1;
+ return concurrency < 2 ? concurrency : 2;
},
godot_js_os_download_buffer__sig: 'viiii',
diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp
index da88ea18b0..1686353229 100644
--- a/platform/javascript/os_javascript.cpp
+++ b/platform/javascript/os_javascript.cpp
@@ -131,6 +131,10 @@ int OS_JavaScript::get_process_id() const {
ERR_FAIL_V_MSG(0, "OS::get_process_id() is not available on the HTML5 platform.");
}
+bool OS_JavaScript::is_process_running(const ProcessID &p_pid) const {
+ return false;
+}
+
int OS_JavaScript::get_processor_count() const {
return godot_js_os_hw_concurrency_get();
}
@@ -221,10 +225,15 @@ bool OS_JavaScript::is_userfs_persistent() const {
return idb_available;
}
-Error OS_JavaScript::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
+Error OS_JavaScript::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
String path = p_path.get_file();
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ". Error: " + dlerror());
+
+ if (r_resolved_path != nullptr) {
+ *r_resolved_path = path;
+ }
+
return OK;
}
diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h
index 9e272f9aa1..0c672111cc 100644
--- a/platform/javascript/os_javascript.h
+++ b/platform/javascript/os_javascript.h
@@ -79,6 +79,7 @@ public:
Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
Error kill(const ProcessID &p_pid) override;
int get_process_id() const override;
+ bool is_process_running(const ProcessID &p_pid) const override;
int get_processor_count() const override;
int get_default_thread_pool_size() const override { return 1; }
@@ -99,7 +100,7 @@ public:
void alert(const String &p_alert, const String &p_title = "ALERT!") override;
- Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) override;
+ Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override;
void resume_audio();
diff --git a/platform/javascript/platform_config.h b/platform/javascript/platform_config.h
index ba1b0d459e..1970fe0fa0 100644
--- a/platform/javascript/platform_config.h
+++ b/platform/javascript/platform_config.h
@@ -29,3 +29,5 @@
/*************************************************************************/
#include <alloca.h>
+
+#define OPENGL_INCLUDE_H "platform/javascript/godot_webgl2.h"
diff --git a/platform/linuxbsd/SCsub b/platform/linuxbsd/SCsub
index cec8706fbc..09a432eae2 100644
--- a/platform/linuxbsd/SCsub
+++ b/platform/linuxbsd/SCsub
@@ -20,6 +20,9 @@ if "x11" in env and env["x11"]:
"key_mapping_x11.cpp",
]
+if "speechd" in env and env["speechd"]:
+ common_linuxbsd.append(["speechd-so_wrap.c", "tts_linux.cpp"])
+
if "vulkan" in env and env["vulkan"]:
common_linuxbsd.append("vulkan_context_x11.cpp")
diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp
index b4ec7924f6..33da094860 100644
--- a/platform/linuxbsd/crash_handler_linuxbsd.cpp
+++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/os/os.h"
+#include "core/string/print_string.h"
#include "core/version.h"
#include "main/main.h"
@@ -61,21 +62,22 @@ static void handle_crash(int sig) {
msg = proj_settings->get("debug/settings/crash_handler/message");
}
- // 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);
-
+ // Tell MainLoop about the crash. This can be handled by users too in Node.
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);
}
+ // Dump the backtrace to stderr with a message to the user
+ print_error("\n================================================================");
+ print_error(vformat("%s: Program crashed with signal %d", __FUNCTION__, sig));
+
// Print the engine version just before, so that people are reminded to include the version in backtrace reports.
if (String(VERSION_HASH).is_empty()) {
- fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME);
+ print_error(vformat("Engine version: %s", VERSION_FULL_NAME));
} else {
- fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH);
+ print_error(vformat("Engine version: %s (%s)", VERSION_FULL_NAME, VERSION_HASH));
}
- fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());
+ print_error(vformat("Dumping the backtrace. %s", msg));
char **strings = backtrace_symbols(bt_buffer, size);
if (strings) {
for (size_t i = 1; i < size; i++) {
@@ -117,13 +119,13 @@ static void handle_crash(int sig) {
output = output.substr(0, output.length() - 1);
}
- fprintf(stderr, "[%ld] %s (%s)\n", (long int)i, fname, output.utf8().get_data());
+ print_error(vformat("[%d] %s (%s)", (int64_t)i, fname, output));
}
free(strings);
}
- fprintf(stderr, "-- END OF BACKTRACE --\n");
- fprintf(stderr, "================================================================\n");
+ print_error("-- END OF BACKTRACE --");
+ print_error("================================================================");
// Abort to pass the error to the OS
abort();
diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py
index f3a6004356..19cf341c85 100644
--- a/platform/linuxbsd/detect.py
+++ b/platform/linuxbsd/detect.py
@@ -75,6 +75,7 @@ def get_opts():
BoolVariable("use_msan", "Use LLVM compiler memory sanitizer (MSAN)", False),
BoolVariable("pulseaudio", "Detect and use PulseAudio", True),
BoolVariable("dbus", "Detect and use D-Bus to handle screensaver", True),
+ BoolVariable("speechd", "Detect and use Speech Dispatcher for Text-to-Speech support", True),
BoolVariable("udev", "Use udev for gamepad connection callbacks", True),
BoolVariable("x11", "Enable X11 display", True),
BoolVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", True),
@@ -115,7 +116,7 @@ def configure(env):
## Architecture
- is64 = sys.maxsize > 2 ** 32
+ is64 = sys.maxsize > 2**32
if env["bits"] == "default":
env["bits"] = "64" if is64 else "32"
@@ -319,29 +320,41 @@ def configure(env):
if os.system("pkg-config --exists alsa") == 0: # 0 means found
env["alsa"] = True
env.Append(CPPDEFINES=["ALSA_ENABLED", "ALSAMIDI_ENABLED"])
+ env.ParseConfig("pkg-config alsa --cflags") # Only cflags, we dlopen the library.
else:
print("Warning: ALSA libraries not found. Disabling the ALSA audio driver.")
if env["pulseaudio"]:
if os.system("pkg-config --exists libpulse") == 0: # 0 means found
env.Append(CPPDEFINES=["PULSEAUDIO_ENABLED"])
- env.ParseConfig("pkg-config --cflags libpulse")
+ env.ParseConfig("pkg-config libpulse --cflags") # Only cflags, we dlopen the library.
else:
+ env["pulseaudio"] = False
print("Warning: PulseAudio development libraries not found. Disabling the PulseAudio audio driver.")
if env["dbus"]:
if os.system("pkg-config --exists dbus-1") == 0: # 0 means found
env.Append(CPPDEFINES=["DBUS_ENABLED"])
- env.ParseConfig("pkg-config --cflags --libs dbus-1")
+ env.ParseConfig("pkg-config dbus-1 --cflags --libs")
else:
print("Warning: D-Bus development libraries not found. Disabling screensaver prevention.")
+ if env["speechd"]:
+ if os.system("pkg-config --exists speech-dispatcher") == 0: # 0 means found
+ env.Append(CPPDEFINES=["SPEECHD_ENABLED"])
+ env.ParseConfig("pkg-config speech-dispatcher --cflags") # Only cflags, we dlopen the library.
+ else:
+ env["speechd"] = False
+ print("Warning: Speech Dispatcher development libraries not found. Disabling Text-to-Speech support.")
+
if platform.system() == "Linux":
env.Append(CPPDEFINES=["JOYDEV_ENABLED"])
if env["udev"]:
if os.system("pkg-config --exists libudev") == 0: # 0 means found
env.Append(CPPDEFINES=["UDEV_ENABLED"])
+ env.ParseConfig("pkg-config libudev --cflags") # Only cflags, we dlopen the library.
else:
+ env["udev"] = False
print("Warning: libudev development libraries not found. Disabling controller hotplugging support.")
else:
env["udev"] = False # Linux specific
@@ -366,11 +379,11 @@ def configure(env):
if not env["use_volk"]:
env.ParseConfig("pkg-config vulkan --cflags --libs")
if not env["builtin_glslang"]:
- # No pkgconfig file for glslang so far
+ # No pkgconfig file so far, hardcode expected lib name.
env.Append(LIBS=["glslang", "SPIRV"])
env.Append(CPPDEFINES=["GLES3_ENABLED"])
- env.Append(LIBS=["GL"])
+ env.ParseConfig("pkg-config gl --cflags --libs")
env.Append(LIBS=["pthread"])
diff --git a/platform/linuxbsd/detect_prime_x11.cpp b/platform/linuxbsd/detect_prime_x11.cpp
index 63531d33fa..42b7f68a5e 100644
--- a/platform/linuxbsd/detect_prime_x11.cpp
+++ b/platform/linuxbsd/detect_prime_x11.cpp
@@ -55,7 +55,7 @@
typedef GLXContext (*GLXCREATECONTEXTATTRIBSARBPROC)(Display *, GLXFBConfig, GLXContext, Bool, const int *);
struct vendor {
- const char *glxvendor;
+ const char *glxvendor = nullptr;
int priority = 0;
};
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp
index 8fa8c64efe..b35f0daec6 100644
--- a/platform/linuxbsd/display_server_x11.cpp
+++ b/platform/linuxbsd/display_server_x11.cpp
@@ -139,6 +139,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const {
case FEATURE_KEEP_SCREEN_ON:
#endif
case FEATURE_CLIPBOARD_PRIMARY:
+ case FEATURE_TEXT_TO_SPEECH:
return true;
default: {
}
@@ -307,6 +308,45 @@ void DisplayServerX11::_flush_mouse_motion() {
xi.relative_motion.y = 0;
}
+#ifdef SPEECHD_ENABLED
+
+bool DisplayServerX11::tts_is_speaking() const {
+ ERR_FAIL_COND_V(!tts, false);
+ return tts->is_speaking();
+}
+
+bool DisplayServerX11::tts_is_paused() const {
+ ERR_FAIL_COND_V(!tts, false);
+ return tts->is_paused();
+}
+
+Array DisplayServerX11::tts_get_voices() const {
+ ERR_FAIL_COND_V(!tts, Array());
+ return tts->get_voices();
+}
+
+void DisplayServerX11::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
+ ERR_FAIL_COND(!tts);
+ tts->speak(p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_interrupt);
+}
+
+void DisplayServerX11::tts_pause() {
+ ERR_FAIL_COND(!tts);
+ tts->pause();
+}
+
+void DisplayServerX11::tts_resume() {
+ ERR_FAIL_COND(!tts);
+ tts->resume();
+}
+
+void DisplayServerX11::tts_stop() {
+ ERR_FAIL_COND(!tts);
+ tts->stop();
+}
+
+#endif
+
void DisplayServerX11::mouse_set_mode(MouseMode p_mode) {
_THREAD_SAFE_METHOD_
@@ -670,17 +710,17 @@ void DisplayServerX11::_clipboard_transfer_ownership(Atom p_source, Window x11_w
int DisplayServerX11::get_screen_count() const {
_THREAD_SAFE_METHOD_
+ int count = 0;
// Using Xinerama Extension
int event_base, error_base;
- const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
- if (!ext_okay) {
- return 0;
+ if (XineramaQueryExtension(x11_display, &event_base, &error_base)) {
+ XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
+ XFree(xsi);
+ } else {
+ count = XScreenCount(x11_display);
}
- int count;
- XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count);
- XFree(xsi);
return count;
}
@@ -712,6 +752,19 @@ Rect2i DisplayServerX11::_screen_get_rect(int p_screen) const {
if (xsi) {
XFree(xsi);
}
+ } else {
+ int count = XScreenCount(x11_display);
+ if (p_screen < count) {
+ Window root = XRootWindow(x11_display, p_screen);
+ XWindowAttributes xwa;
+ XGetWindowAttributes(x11_display, root, &xwa);
+ rect.position.x = xwa.x;
+ rect.position.y = xwa.y;
+ rect.size.width = xwa.width;
+ rect.size.height = xwa.height;
+ } else {
+ ERR_PRINT("Invalid screen index: " + itos(p_screen) + "(count: " + itos(count) + ").");
+ }
}
return rect;
@@ -2130,7 +2183,7 @@ bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) co
unsigned char *data = nullptr;
if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, sizeof(Hints), False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
if (data && (format == 32) && (len >= 5)) {
- borderless = !((Hints *)data)->decorations;
+ borderless = !(reinterpret_cast<Hints *>(data)->decorations);
}
if (data) {
XFree(data);
@@ -2162,7 +2215,7 @@ void DisplayServerX11::window_request_attention(WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
- WindowData &wd = windows[p_window];
+ const WindowData &wd = windows[p_window];
// Using EWMH -- Extended Window Manager Hints
//
// Sets the _NET_WM_STATE_DEMANDS_ATTENTION atom for WM_STATE
@@ -2188,7 +2241,7 @@ void DisplayServerX11::window_move_to_foreground(WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
- WindowData &wd = windows[p_window];
+ const WindowData &wd = windows[p_window];
XEvent xev;
Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False);
@@ -2303,7 +2356,7 @@ DisplayServerX11::CursorShape DisplayServerX11::cursor_get_shape() const {
return current_cursor;
}
-void DisplayServerX11::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
_THREAD_SAFE_METHOD_
if (p_cursor.is_valid()) {
@@ -2534,10 +2587,9 @@ DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display,
unsigned long bytes_after = 0;
unsigned char *ret = nullptr;
- int read_bytes = 1024;
-
// Keep trying to read the property until there are no bytes unread.
if (p_property != None) {
+ int read_bytes = 1024;
do {
if (ret != nullptr) {
XFree(ret);
@@ -2557,7 +2609,7 @@ DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display,
return p;
}
-static Atom pick_target_from_list(Display *p_display, Atom *p_list, int p_count) {
+static Atom pick_target_from_list(Display *p_display, const Atom *p_list, int p_count) {
static const char *target_type = "text/uri-list";
for (int i = 0; i < p_count; i++) {
@@ -3052,7 +3104,7 @@ void DisplayServerX11::_window_changed(XEvent *event) {
}
void DisplayServerX11::_dispatch_input_events(const Ref<InputEvent> &p_event) {
- ((DisplayServerX11 *)(get_singleton()))->_dispatch_input_event(p_event);
+ static_cast<DisplayServerX11 *>(get_singleton())->_dispatch_input_event(p_event);
}
void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) {
@@ -3062,7 +3114,7 @@ void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) {
Callable::CallError ce;
{
- List<WindowID>::Element *E = popup_list.front();
+ List<WindowID>::Element *E = popup_list.back();
if (E && Object::cast_to<InputEventKey>(*p_event)) {
// Redirect keyboard input to active popup.
if (windows.has(E->get())) {
@@ -3106,7 +3158,7 @@ void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_ev
}
void DisplayServerX11::_poll_events_thread(void *ud) {
- DisplayServerX11 *display_server = (DisplayServerX11 *)ud;
+ DisplayServerX11 *display_server = static_cast<DisplayServerX11 *>(ud);
display_server->_poll_events();
}
@@ -3208,20 +3260,24 @@ Rect2i DisplayServerX11::window_get_popup_safe_rect(WindowID p_window) const {
}
void DisplayServerX11::popup_open(WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
WindowData &wd = windows[p_window];
if (wd.is_popup) {
- // Close all popups, up to current popup parent, or every popup if new window is not transient.
+ // Find current popup parent, or root popup if new window is not transient.
+ List<WindowID>::Element *C = nullptr;
List<WindowID>::Element *E = popup_list.back();
while (E) {
if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) {
- _send_window_event(windows[E->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
- List<WindowID>::Element *F = E->prev();
- popup_list.erase(E);
- E = F;
+ C = E;
+ E = E->prev();
} else {
break;
}
}
+ if (C) {
+ _send_window_event(windows[C->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
+ }
time_since_popup = OS::get_singleton()->get_ticks_msec();
popup_list.push_back(p_window);
@@ -3229,16 +3285,22 @@ void DisplayServerX11::popup_open(WindowID p_window) {
}
void DisplayServerX11::popup_close(WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
List<WindowID>::Element *E = popup_list.find(p_window);
while (E) {
- _send_window_event(windows[E->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
List<WindowID>::Element *F = E->next();
+ WindowID win_id = E->get();
popup_list.erase(E);
+
+ _send_window_event(windows[win_id], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
E = F;
}
}
void DisplayServerX11::mouse_process_popups() {
+ _THREAD_SAFE_METHOD_
+
if (popup_list.is_empty()) {
return;
}
@@ -3259,7 +3321,9 @@ void DisplayServerX11::mouse_process_popups() {
Vector2i pos = Vector2i(root_attrs.x + root_x, root_attrs.y + root_y);
if ((pos != last_mouse_monitor_pos) || (mask != last_mouse_monitor_mask)) {
if (((mask & Button1Mask) || (mask & Button2Mask) || (mask & Button3Mask) || (mask & Button4Mask) || (mask & Button5Mask))) {
+ List<WindowID>::Element *C = nullptr;
List<WindowID>::Element *E = popup_list.back();
+ // Find top popup to close.
while (E) {
// Popup window area.
Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get()));
@@ -3270,12 +3334,13 @@ void DisplayServerX11::mouse_process_popups() {
} else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) {
break;
} else {
- _send_window_event(windows[E->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
- List<WindowID>::Element *F = E->prev();
- popup_list.erase(E);
- E = F;
+ C = E;
+ E = E->prev();
}
}
+ if (C) {
+ _send_window_event(windows[C->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
+ }
}
}
last_mouse_monitor_mask = mask;
@@ -4466,24 +4531,11 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
r_error = OK;
- current_cursor = CURSOR_ARROW;
- mouse_mode = MOUSE_MODE_VISIBLE;
-
for (int i = 0; i < CURSOR_MAX; i++) {
cursors[i] = None;
img[i] = nullptr;
}
- xmbstring = nullptr;
-
- last_click_ms = 0;
- last_click_button_index = MouseButton::NONE;
- last_click_pos = Point2i(-100, -100);
-
- last_timestamp = 0;
- last_mouse_pos_valid = false;
- last_keyrelease_time = 0;
-
XInitThreads(); //always use threads
/** XLIB INITIALIZATION **/
@@ -4518,8 +4570,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
}
const char *err;
- xrr_get_monitors = nullptr;
- xrr_free_monitors = nullptr;
int xrandr_major = 0;
int xrandr_minor = 0;
int event_base, error_base;
@@ -4595,11 +4645,10 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
XFree(imvalret);
}
- /* Atorm internment */
+ /* Atom internment */
wm_delete = XInternAtom(x11_display, "WM_DELETE_WINDOW", true);
- //Set Xdnd (drag & drop) support
+ // Set Xdnd (drag & drop) support.
xdnd_aware = XInternAtom(x11_display, "XdndAware", False);
- xdnd_version = 5;
xdnd_enter = XInternAtom(x11_display, "XdndEnter", False);
xdnd_position = XInternAtom(x11_display, "XdndPosition", False);
xdnd_status = XInternAtom(x11_display, "XdndStatus", False);
@@ -4608,6 +4657,11 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
xdnd_finished = XInternAtom(x11_display, "XdndFinished", False);
xdnd_selection = XInternAtom(x11_display, "XdndSelection", False);
+#ifdef SPEECHD_ENABLED
+ // Init TTS
+ tts = memnew(TTS_Linux);
+#endif
+
//!!!!!!!!!!!!!!!!!!!!!!!!!!
//TODO - do Vulkan and OpenGL support checks, driver selection and fallback
rendering_driver = p_rendering_driver;
@@ -4681,11 +4735,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
}
driver_found = true;
- // gl_manager->set_use_vsync(current_videomode.use_vsync);
-
if (true) {
- // if (RasterizerGLES3::is_viable() == OK) {
- // RasterizerGLES3::register_config();
RasterizerGLES3::make_current();
} else {
memdelete(gl_manager);
@@ -4860,12 +4910,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
}
cursor_set_shape(CURSOR_BUSY);
- requested = None;
-
- /*if (p_desired.layered) {
- set_window_per_pixel_transparency_enabled(true);
- }*/
-
XEvent xevent;
while (XPending(x11_display) > 0) {
XNextEvent(x11_display, &xevent);
@@ -4960,6 +5004,10 @@ DisplayServerX11::~DisplayServerX11() {
memfree(xmbstring);
}
+#ifdef SPEECHD_ENABLED
+ memdelete(tts);
+#endif
+
#ifdef DBUS_ENABLED
memdelete(screensaver);
#endif
diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h
index 0f44aa4b11..ee47d1a12c 100644
--- a/platform/linuxbsd/display_server_x11.h
+++ b/platform/linuxbsd/display_server_x11.h
@@ -46,6 +46,10 @@
#include "servers/rendering/renderer_compositor.h"
#include "servers/rendering_server.h"
+#if defined(SPEECHD_ENABLED)
+#include "tts_linux.h"
+#endif
+
#if defined(GLES3_ENABLED)
#include "gl_manager_x11.h"
#endif
@@ -96,8 +100,8 @@ class DisplayServerX11 : public DisplayServer {
Atom xdnd_finished;
Atom xdnd_selection;
Atom xdnd_aware;
- Atom requested;
- int xdnd_version;
+ Atom requested = None;
+ int xdnd_version = 5;
#if defined(GLES3_ENABLED)
GLManager_X11 *gl_manager = nullptr;
@@ -108,10 +112,14 @@ class DisplayServerX11 : public DisplayServer {
#endif
#if defined(DBUS_ENABLED)
- FreeDesktopScreenSaver *screensaver;
+ FreeDesktopScreenSaver *screensaver = nullptr;
bool keep_screen_on = false;
#endif
+#ifdef SPEECHD_ENABLED
+ TTS_Linux *tts = nullptr;
+#endif
+
struct WindowData {
Window x11_window;
::XIC xic;
@@ -166,21 +174,21 @@ class DisplayServerX11 : public DisplayServer {
String internal_clipboard;
String internal_clipboard_primary;
- Window xdnd_source_window;
+ Window xdnd_source_window = 0;
::Display *x11_display;
- char *xmbstring;
- int xmblen;
- unsigned long last_timestamp;
- ::Time last_keyrelease_time;
+ char *xmbstring = nullptr;
+ int xmblen = 0;
+ unsigned long last_timestamp = 0;
+ ::Time last_keyrelease_time = 0;
::XIM xim;
::XIMStyle xim_style;
static void _xim_destroy_callback(::XIM im, ::XPointer client_data,
::XPointer call_data);
Point2i last_mouse_pos;
- bool last_mouse_pos_valid;
- Point2i last_click_pos;
- uint64_t last_click_ms;
+ bool last_mouse_pos_valid = false;
+ Point2i last_click_pos = Point2i(-100, -100);
+ uint64_t last_click_ms = 0;
MouseButton last_click_button_index = MouseButton::NONE;
MouseButton last_button_state = MouseButton::NONE;
bool app_focused = false;
@@ -213,7 +221,7 @@ class DisplayServerX11 : public DisplayServer {
void _get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state);
void _flush_mouse_motion();
- MouseMode mouse_mode;
+ MouseMode mouse_mode = MOUSE_MODE_VISIBLE;
Point2i center;
void _handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo = false);
@@ -225,31 +233,27 @@ class DisplayServerX11 : public DisplayServer {
String _clipboard_get(Atom p_source, Window x11_window) const;
void _clipboard_transfer_ownership(Atom p_source, Window x11_window) const;
- //bool minimized;
- //bool window_has_focus;
- bool do_mouse_warp;
+ bool do_mouse_warp = false;
- const char *cursor_theme;
- int cursor_size;
+ const char *cursor_theme = nullptr;
+ int cursor_size = 0;
XcursorImage *img[CURSOR_MAX];
Cursor cursors[CURSOR_MAX];
Cursor null_cursor;
- CursorShape current_cursor;
+ CursorShape current_cursor = CURSOR_ARROW;
Map<CursorShape, Vector<Variant>> cursors_cache;
- bool layered_window;
+ bool layered_window = false;
String rendering_driver;
- //bool window_focused;
- //void set_wm_border(bool p_enabled);
void set_wm_fullscreen(bool p_enabled);
void set_wm_above(bool p_enabled);
typedef xrr_monitor_info *(*xrr_get_monitors_t)(Display *dpy, Window window, Bool get_active, int *nmonitors);
typedef void (*xrr_free_monitors_t)(xrr_monitor_info *monitors);
- xrr_get_monitors_t xrr_get_monitors;
- xrr_free_monitors_t xrr_free_monitors;
- void *xrandr_handle;
+ xrr_get_monitors_t xrr_get_monitors = nullptr;
+ xrr_free_monitors_t xrr_free_monitors = nullptr;
+ void *xrandr_handle = nullptr;
Bool xrandr_ext_ok;
struct Property {
@@ -298,6 +302,17 @@ public:
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
+#ifdef SPEECHD_ENABLED
+ virtual bool tts_is_speaking() const override;
+ virtual bool tts_is_paused() const override;
+ virtual Array tts_get_voices() const override;
+
+ virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
+ virtual void tts_pause() override;
+ virtual void tts_resume() override;
+ virtual void tts_stop() override;
+#endif
+
virtual void mouse_set_mode(MouseMode p_mode) override;
virtual MouseMode mouse_get_mode() const override;
@@ -392,7 +407,7 @@ public:
virtual void cursor_set_shape(CursorShape p_shape) override;
virtual CursorShape cursor_get_shape() const override;
- virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) override;
+ virtual void cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) override;
virtual int keyboard_get_layout_count() const override;
virtual int keyboard_get_current_layout() const override;
diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp
index 24906fa3fb..9f7fab6ee8 100644
--- a/platform/linuxbsd/export/export_plugin.cpp
+++ b/platform/linuxbsd/export/export_plugin.cpp
@@ -34,8 +34,8 @@
#include "editor/editor_node.h"
Error EditorExportPlatformLinuxBSD::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) {
- FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE);
- ERR_FAIL_COND_V(!f, ERR_CANT_CREATE);
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE);
f->store_line("#!/bin/sh");
f->store_line("echo -ne '\\033c\\033]0;" + p_app_name + "\\a'");
@@ -101,8 +101,8 @@ List<String> EditorExportPlatformLinuxBSD::get_binary_extensions(const Ref<Edito
Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) const {
// Patch the header of the "pck" section in the ELF file so that it corresponds to the embedded data
- FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE);
- if (!f) {
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ_WRITE);
+ if (f.is_null()) {
return ERR_CANT_OPEN;
}
@@ -110,7 +110,6 @@ Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int
{
uint32_t magic = f->get_32();
if (magic != 0x464c457f) { // 0x7F + "ELF"
- f->close();
return ERR_FILE_CORRUPT;
}
}
@@ -120,7 +119,6 @@ Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int
int bits = f->get_8() * 32;
if (bits == 32 && p_embedded_size >= 0x100000000) {
- f->close();
ERR_FAIL_V_MSG(ERR_INVALID_DATA, "32-bit executables cannot have embedded data >= 4 GiB.");
}
@@ -165,7 +163,6 @@ Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int
f->seek(string_data_pos);
strings = (uint8_t *)memalloc(string_data_size);
if (!strings) {
- f->close();
return ERR_OUT_OF_MEMORY;
}
f->get_buffer(strings, string_data_size);
@@ -198,7 +195,6 @@ Error EditorExportPlatformLinuxBSD::fixup_embedded_pck(const String &p_path, int
}
memfree(strings);
- f->close();
return found ? OK : ERR_FILE_CORRUPT;
}
diff --git a/platform/linuxbsd/gl_manager_x11.h b/platform/linuxbsd/gl_manager_x11.h
index 0bb0a446ab..fb2c74a2b6 100644
--- a/platform/linuxbsd/gl_manager_x11.h
+++ b/platform/linuxbsd/gl_manager_x11.h
@@ -52,21 +52,20 @@ public:
private:
// any data specific to the window
struct GLWindow {
- GLWindow() { in_use = false; }
- bool in_use;
+ bool in_use = false;
// the external ID .. should match the GL window number .. unused I think
- DisplayServer::WindowID window_id;
- int width;
- int height;
+ DisplayServer::WindowID window_id = DisplayServer::INVALID_WINDOW_ID;
+ int width = 0;
+ int height = 0;
::Window x11_window;
- int gldisplay_id;
+ int gldisplay_id = 0;
};
struct GLDisplay {
GLDisplay() { context = nullptr; }
~GLDisplay();
- GLManager_X11_Private *context;
+ GLManager_X11_Private *context = nullptr;
::Display *x11_display;
XVisualInfo x_vi;
XSetWindowAttributes x_swa;
@@ -82,7 +81,7 @@ private:
LocalVector<GLWindow> _windows;
LocalVector<GLDisplay> _displays;
- GLWindow *_current_window;
+ GLWindow *_current_window = nullptr;
void _internal_set_current_window(GLWindow *p_win);
diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp
index 65d53b266f..bc018e366b 100644
--- a/platform/linuxbsd/joypad_linux.cpp
+++ b/platform/linuxbsd/joypad_linux.cpp
@@ -65,6 +65,7 @@ void JoypadLinux::Joypad::reset() {
abs_map[i] = -1;
curr_axis[i] = 0;
}
+ events.clear();
}
JoypadLinux::JoypadLinux(Input *in) {
@@ -84,23 +85,26 @@ JoypadLinux::JoypadLinux(Input *in) {
print_verbose("JoypadLinux: udev disabled, parsing /dev/input to detect joypads.");
#endif
input = in;
- joy_thread.start(joy_thread_func, this);
+ monitor_joypads_thread.start(monitor_joypads_thread_func, this);
+ joypad_events_thread.start(joypad_events_thread_func, this);
}
JoypadLinux::~JoypadLinux() {
- exit_monitor.set();
- joy_thread.wait_to_finish();
- close_joypad();
+ monitor_joypads_exit.set();
+ joypad_events_exit.set();
+ monitor_joypads_thread.wait_to_finish();
+ joypad_events_thread.wait_to_finish();
+ close_joypads();
}
-void JoypadLinux::joy_thread_func(void *p_user) {
+void JoypadLinux::monitor_joypads_thread_func(void *p_user) {
if (p_user) {
- JoypadLinux *joy = (JoypadLinux *)p_user;
- joy->run_joypad_thread();
+ JoypadLinux *joy = static_cast<JoypadLinux *>(p_user);
+ joy->monitor_joypads_thread_run();
}
}
-void JoypadLinux::run_joypad_thread() {
+void JoypadLinux::monitor_joypads_thread_run() {
#ifdef UDEV_ENABLED
if (use_udev) {
udev *_udev = udev_new();
@@ -140,7 +144,6 @@ void JoypadLinux::enumerate_joypads(udev *p_udev) {
if (devnode) {
String devnode_str = devnode;
if (devnode_str.find(ignore_str) == -1) {
- MutexLock lock(joy_mutex);
open_joypad(devnode);
}
}
@@ -156,7 +159,7 @@ void JoypadLinux::monitor_joypads(udev *p_udev) {
udev_monitor_enable_receiving(mon);
int fd = udev_monitor_get_fd(mon);
- while (!exit_monitor.is_set()) {
+ while (!monitor_joypads_exit.is_set()) {
fd_set fds;
struct timeval tv;
int ret;
@@ -175,7 +178,6 @@ void JoypadLinux::monitor_joypads(udev *p_udev) {
dev = udev_monitor_receive_device(mon);
if (dev && udev_device_get_devnode(dev) != nullptr) {
- MutexLock lock(joy_mutex);
String action = udev_device_get_action(dev);
const char *devnode = udev_device_get_devnode(dev);
if (devnode) {
@@ -184,11 +186,10 @@ void JoypadLinux::monitor_joypads(udev *p_udev) {
if (action == "add") {
open_joypad(devnode);
} else if (String(action) == "remove") {
- close_joypad(get_joy_from_path(devnode));
+ close_joypad(devnode);
}
}
}
-
udev_device_unref(dev);
}
}
@@ -199,59 +200,54 @@ void JoypadLinux::monitor_joypads(udev *p_udev) {
#endif
void JoypadLinux::monitor_joypads() {
- while (!exit_monitor.is_set()) {
- {
- MutexLock lock(joy_mutex);
-
- DIR *input_directory;
- input_directory = opendir("/dev/input");
- if (input_directory) {
- struct dirent *current;
- char fname[64];
-
- while ((current = readdir(input_directory)) != nullptr) {
- if (strncmp(current->d_name, "event", 5) != 0) {
- continue;
- }
- sprintf(fname, "/dev/input/%.*s", 16, current->d_name);
- if (attached_devices.find(fname) == -1) {
- open_joypad(fname);
- }
+ while (!monitor_joypads_exit.is_set()) {
+ DIR *input_directory;
+ input_directory = opendir("/dev/input");
+ if (input_directory) {
+ struct dirent *current;
+ char fname[64];
+
+ while ((current = readdir(input_directory)) != nullptr) {
+ if (strncmp(current->d_name, "event", 5) != 0) {
+ continue;
+ }
+ sprintf(fname, "/dev/input/%.*s", 16, current->d_name);
+ if (attached_devices.find(fname) == -1) {
+ open_joypad(fname);
}
}
- closedir(input_directory);
}
- usleep(1000000); // 1s
+ closedir(input_directory);
}
+ usleep(1000000); // 1s
}
-int JoypadLinux::get_joy_from_path(String p_path) const {
+void JoypadLinux::close_joypads() {
for (int i = 0; i < JOYPADS_MAX; i++) {
- if (joypads[i].devpath == p_path) {
- return i;
- }
+ MutexLock lock(joypads_mutex[i]);
+ Joypad &joypad = joypads[i];
+ close_joypad(joypad, i);
}
- return -2;
}
-void JoypadLinux::close_joypad(int p_id) {
- if (p_id == -1) {
- for (int i = 0; i < JOYPADS_MAX; i++) {
- close_joypad(i);
+void JoypadLinux::close_joypad(const char *p_devpath) {
+ for (int i = 0; i < JOYPADS_MAX; i++) {
+ MutexLock lock(joypads_mutex[i]);
+ Joypad &joypad = joypads[i];
+ if (joypads[i].devpath == p_devpath) {
+ close_joypad(joypad, i);
}
- return;
- } else if (p_id < 0) {
- return;
}
+}
- Joypad &joy = joypads[p_id];
-
- if (joy.fd != -1) {
- close(joy.fd);
- joy.fd = -1;
- attached_devices.remove_at(attached_devices.find(joy.devpath));
+void JoypadLinux::close_joypad(Joypad &p_joypad, int p_id) {
+ if (p_joypad.fd != -1) {
+ close(p_joypad.fd);
+ p_joypad.fd = -1;
+ attached_devices.erase(p_joypad.devpath);
input->joy_connection_changed(p_id, false, "");
}
+ p_joypad.events.clear();
}
static String _hex_str(uint8_t p_byte) {
@@ -265,27 +261,25 @@ static String _hex_str(uint8_t p_byte) {
return ret;
}
-void JoypadLinux::setup_joypad_properties(int p_id) {
- Joypad *joy = &joypads[p_id];
-
+void JoypadLinux::setup_joypad_properties(Joypad &p_joypad) {
unsigned long keybit[NBITS(KEY_MAX)] = { 0 };
unsigned long absbit[NBITS(ABS_MAX)] = { 0 };
int num_buttons = 0;
int num_axes = 0;
- if ((ioctl(joy->fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) < 0) ||
- (ioctl(joy->fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) < 0)) {
+ if ((ioctl(p_joypad.fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) < 0) ||
+ (ioctl(p_joypad.fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit) < 0)) {
return;
}
for (int i = BTN_JOYSTICK; i < KEY_MAX; ++i) {
if (test_bit(i, keybit)) {
- joy->key_map[i] = num_buttons++;
+ p_joypad.key_map[i] = num_buttons++;
}
}
for (int i = BTN_MISC; i < BTN_JOYSTICK; ++i) {
if (test_bit(i, keybit)) {
- joy->key_map[i] = num_buttons++;
+ p_joypad.key_map[i] = num_buttons++;
}
}
for (int i = 0; i < ABS_MISC; ++i) {
@@ -295,21 +289,21 @@ void JoypadLinux::setup_joypad_properties(int p_id) {
continue;
}
if (test_bit(i, absbit)) {
- joy->abs_map[i] = num_axes++;
- joy->abs_info[i] = memnew(input_absinfo);
- if (ioctl(joy->fd, EVIOCGABS(i), joy->abs_info[i]) < 0) {
- memdelete(joy->abs_info[i]);
- joy->abs_info[i] = nullptr;
+ p_joypad.abs_map[i] = num_axes++;
+ p_joypad.abs_info[i] = memnew(input_absinfo);
+ if (ioctl(p_joypad.fd, EVIOCGABS(i), p_joypad.abs_info[i]) < 0) {
+ memdelete(p_joypad.abs_info[i]);
+ p_joypad.abs_info[i] = nullptr;
}
}
}
- joy->force_feedback = false;
- joy->ff_effect_timestamp = 0;
+ p_joypad.force_feedback = false;
+ p_joypad.ff_effect_timestamp = 0;
unsigned long ffbit[NBITS(FF_CNT)];
- if (ioctl(joy->fd, EVIOCGBIT(EV_FF, sizeof(ffbit)), ffbit) != -1) {
+ if (ioctl(p_joypad.fd, EVIOCGBIT(EV_FF, sizeof(ffbit)), ffbit) != -1) {
if (test_bit(FF_RUMBLE, ffbit)) {
- joy->force_feedback = true;
+ p_joypad.force_feedback = true;
}
}
}
@@ -353,12 +347,12 @@ void JoypadLinux::open_joypad(const char *p_path) {
return;
}
- joypads[joy_num].reset();
-
- Joypad &joy = joypads[joy_num];
- joy.fd = fd;
- joy.devpath = String(p_path);
- setup_joypad_properties(joy_num);
+ MutexLock lock(joypads_mutex[joy_num]);
+ Joypad &joypad = joypads[joy_num];
+ joypad.reset();
+ joypad.fd = fd;
+ joypad.devpath = String(p_path);
+ setup_joypad_properties(joypad);
sprintf(uid, "%04x%04x", BSWAP16(inpid.bustype), 0);
if (inpid.vendor && inpid.product && inpid.version) {
uint16_t vendor = BSWAP16(inpid.vendor);
@@ -379,13 +373,12 @@ void JoypadLinux::open_joypad(const char *p_path) {
}
}
-void JoypadLinux::joypad_vibration_start(int p_id, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) {
- Joypad &joy = joypads[p_id];
- if (!joy.force_feedback || joy.fd == -1 || p_weak_magnitude < 0.f || p_weak_magnitude > 1.f || p_strong_magnitude < 0.f || p_strong_magnitude > 1.f) {
+void JoypadLinux::joypad_vibration_start(Joypad &p_joypad, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) {
+ if (!p_joypad.force_feedback || p_joypad.fd == -1 || p_weak_magnitude < 0.f || p_weak_magnitude > 1.f || p_strong_magnitude < 0.f || p_strong_magnitude > 1.f) {
return;
}
- if (joy.ff_effect_id != -1) {
- joypad_vibration_stop(p_id, p_timestamp);
+ if (p_joypad.ff_effect_id != -1) {
+ joypad_vibration_stop(p_joypad, p_timestamp);
}
struct ff_effect effect;
@@ -396,7 +389,7 @@ void JoypadLinux::joypad_vibration_start(int p_id, float p_weak_magnitude, float
effect.replay.length = floor(p_duration * 1000);
effect.replay.delay = 0;
- if (ioctl(joy.fd, EVIOCSFF, &effect) < 0) {
+ if (ioctl(p_joypad.fd, EVIOCSFF, &effect) < 0) {
return;
}
@@ -404,26 +397,25 @@ void JoypadLinux::joypad_vibration_start(int p_id, float p_weak_magnitude, float
play.type = EV_FF;
play.code = effect.id;
play.value = 1;
- if (write(joy.fd, (const void *)&play, sizeof(play)) == -1) {
+ if (write(p_joypad.fd, (const void *)&play, sizeof(play)) == -1) {
print_verbose("Couldn't write to Joypad device.");
}
- joy.ff_effect_id = effect.id;
- joy.ff_effect_timestamp = p_timestamp;
+ p_joypad.ff_effect_id = effect.id;
+ p_joypad.ff_effect_timestamp = p_timestamp;
}
-void JoypadLinux::joypad_vibration_stop(int p_id, uint64_t p_timestamp) {
- Joypad &joy = joypads[p_id];
- if (!joy.force_feedback || joy.fd == -1 || joy.ff_effect_id == -1) {
+void JoypadLinux::joypad_vibration_stop(Joypad &p_joypad, uint64_t p_timestamp) {
+ if (!p_joypad.force_feedback || p_joypad.fd == -1 || p_joypad.ff_effect_id == -1) {
return;
}
- if (ioctl(joy.fd, EVIOCRMFF, joy.ff_effect_id) < 0) {
+ if (ioctl(p_joypad.fd, EVIOCRMFF, p_joypad.ff_effect_id) < 0) {
return;
}
- joy.ff_effect_id = -1;
- joy.ff_effect_timestamp = p_timestamp;
+ p_joypad.ff_effect_id = -1;
+ p_joypad.ff_effect_timestamp = p_timestamp;
}
float JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value) const {
@@ -433,104 +425,124 @@ float JoypadLinux::axis_correct(const input_absinfo *p_abs, int p_value) const {
return 2.0f * (p_value - min) / (max - min) - 1.0f;
}
-void JoypadLinux::process_joypads() {
- if (joy_mutex.try_lock() != OK) {
- return;
+void JoypadLinux::joypad_events_thread_func(void *p_user) {
+ if (p_user) {
+ JoypadLinux *joy = (JoypadLinux *)p_user;
+ joy->joypad_events_thread_run();
}
+}
+
+void JoypadLinux::joypad_events_thread_run() {
+ while (!joypad_events_exit.is_set()) {
+ bool no_events = true;
+ for (int i = 0; i < JOYPADS_MAX; i++) {
+ MutexLock lock(joypads_mutex[i]);
+ Joypad &joypad = joypads[i];
+ if (joypad.fd == -1) {
+ continue;
+ }
+ input_event event;
+ while (read(joypad.fd, &event, sizeof(event)) > 0) {
+ no_events = false;
+ JoypadEvent joypad_event;
+ joypad_event.type = event.type;
+ joypad_event.code = event.code;
+ joypad_event.value = event.value;
+ joypad.events.push_back(joypad_event);
+ }
+ if (errno != EAGAIN) {
+ close_joypad(joypad, i);
+ }
+ }
+ if (no_events) {
+ usleep(10000); // 10ms
+ }
+ }
+}
+
+void JoypadLinux::process_joypads() {
for (int i = 0; i < JOYPADS_MAX; i++) {
- if (joypads[i].fd == -1) {
+ MutexLock lock(joypads_mutex[i]);
+ Joypad &joypad = joypads[i];
+ if (joypad.fd == -1) {
continue;
}
+ for (uint32_t j = 0; j < joypad.events.size(); j++) {
+ const JoypadEvent &joypad_event = joypad.events[j];
+ // joypad_event may be tainted and out of MAX_KEY range, which will cause
+ // joypad.key_map[joypad_event.code] to crash
+ if (joypad_event.code >= MAX_KEY) {
+ return;
+ }
- input_event events[32];
- Joypad *joy = &joypads[i];
-
- int len;
-
- while ((len = read(joy->fd, events, (sizeof events))) > 0) {
- len /= sizeof(events[0]);
- for (int j = 0; j < len; j++) {
- input_event &ev = events[j];
-
- // ev may be tainted and out of MAX_KEY range, which will cause
- // joy->key_map[ev.code] to crash
- if (ev.code >= MAX_KEY) {
- return;
- }
-
- switch (ev.type) {
- case EV_KEY:
- input->joy_button(i, (JoyButton)joy->key_map[ev.code], ev.value);
- break;
-
- case EV_ABS:
-
- switch (ev.code) {
- case ABS_HAT0X:
- if (ev.value != 0) {
- if (ev.value < 0) {
- joy->dpad = (HatMask)((joy->dpad | HatMask::LEFT) & ~HatMask::RIGHT);
- } else {
- joy->dpad = (HatMask)((joy->dpad | HatMask::RIGHT) & ~HatMask::LEFT);
- }
+ switch (joypad_event.type) {
+ case EV_KEY:
+ input->joy_button(i, (JoyButton)joypad.key_map[joypad_event.code], joypad_event.value);
+ break;
+
+ case EV_ABS:
+ switch (joypad_event.code) {
+ case ABS_HAT0X:
+ if (joypad_event.value != 0) {
+ if (joypad_event.value < 0) {
+ joypad.dpad = (HatMask)((joypad.dpad | HatMask::LEFT) & ~HatMask::RIGHT);
} else {
- joy->dpad &= ~(HatMask::LEFT | HatMask::RIGHT);
+ joypad.dpad = (HatMask)((joypad.dpad | HatMask::RIGHT) & ~HatMask::LEFT);
}
-
- input->joy_hat(i, (HatMask)joy->dpad);
- break;
-
- case ABS_HAT0Y:
- if (ev.value != 0) {
- if (ev.value < 0) {
- joy->dpad = (HatMask)((joy->dpad | HatMask::UP) & ~HatMask::DOWN);
- } else {
- joy->dpad = (HatMask)((joy->dpad | HatMask::DOWN) & ~HatMask::UP);
- }
+ } else {
+ joypad.dpad &= ~(HatMask::LEFT | HatMask::RIGHT);
+ }
+ input->joy_hat(i, (HatMask)joypad.dpad);
+ break;
+
+ case ABS_HAT0Y:
+ if (joypad_event.value != 0) {
+ if (joypad_event.value < 0) {
+ joypad.dpad = (HatMask)((joypad.dpad | HatMask::UP) & ~HatMask::DOWN);
} else {
- joy->dpad &= ~(HatMask::UP | HatMask::DOWN);
- }
-
- input->joy_hat(i, (HatMask)joy->dpad);
- break;
-
- default:
- if (ev.code >= MAX_ABS) {
- return;
- }
- if (joy->abs_map[ev.code] != -1 && joy->abs_info[ev.code]) {
- float value = axis_correct(joy->abs_info[ev.code], ev.value);
- joy->curr_axis[joy->abs_map[ev.code]] = value;
+ joypad.dpad = (HatMask)((joypad.dpad | HatMask::DOWN) & ~HatMask::UP);
}
- break;
- }
- break;
- }
+ } else {
+ joypad.dpad &= ~(HatMask::UP | HatMask::DOWN);
+ }
+ input->joy_hat(i, (HatMask)joypad.dpad);
+ break;
+
+ default:
+ if (joypad_event.code >= MAX_ABS) {
+ return;
+ }
+ if (joypad.abs_map[joypad_event.code] != -1 && joypad.abs_info[joypad_event.code]) {
+ float value = axis_correct(joypad.abs_info[joypad_event.code], joypad_event.value);
+ joypad.curr_axis[joypad.abs_map[joypad_event.code]] = value;
+ }
+ break;
+ }
+ break;
}
}
+ joypad.events.clear();
+
for (int j = 0; j < MAX_ABS; j++) {
- int index = joy->abs_map[j];
+ int index = joypad.abs_map[j];
if (index != -1) {
- input->joy_axis(i, (JoyAxis)index, joy->curr_axis[index]);
+ input->joy_axis(i, (JoyAxis)index, joypad.curr_axis[index]);
}
}
- if (len == 0 || (len < 0 && errno != EAGAIN)) {
- close_joypad(i);
- }
- if (joy->force_feedback) {
+ if (joypad.force_feedback) {
uint64_t timestamp = input->get_joy_vibration_timestamp(i);
- if (timestamp > joy->ff_effect_timestamp) {
+ if (timestamp > joypad.ff_effect_timestamp) {
Vector2 strength = input->get_joy_vibration_strength(i);
float duration = input->get_joy_vibration_duration(i);
if (strength.x == 0 && strength.y == 0) {
- joypad_vibration_stop(i, timestamp);
+ joypad_vibration_stop(joypad, timestamp);
} else {
- joypad_vibration_start(i, strength.x, strength.y, duration, timestamp);
+ joypad_vibration_start(joypad, strength.x, strength.y, duration, timestamp);
}
}
}
}
- joy_mutex.unlock();
}
-#endif
+
+#endif // JOYDEV_ENABLED
diff --git a/platform/linuxbsd/joypad_linux.h b/platform/linuxbsd/joypad_linux.h
index 9177465547..4afc261ce7 100644
--- a/platform/linuxbsd/joypad_linux.h
+++ b/platform/linuxbsd/joypad_linux.h
@@ -35,6 +35,7 @@
#include "core/input/input.h"
#include "core/os/mutex.h"
#include "core/os/thread.h"
+#include "core/templates/local_vector.h"
struct input_absinfo;
@@ -51,6 +52,12 @@ private:
MAX_KEY = 767, // Hack because <linux/input.h> can't be included here
};
+ struct JoypadEvent {
+ uint16_t type;
+ uint16_t code;
+ int32_t value;
+ };
+
struct Joypad {
float curr_axis[MAX_ABS];
int key_map[MAX_KEY];
@@ -65,6 +72,8 @@ private:
int ff_effect_id = 0;
uint64_t ff_effect_timestamp = 0;
+ LocalVector<JoypadEvent> events;
+
~Joypad();
void reset();
};
@@ -72,29 +81,39 @@ private:
#ifdef UDEV_ENABLED
bool use_udev = false;
#endif
- SafeFlag exit_monitor;
- Mutex joy_mutex;
- Thread joy_thread;
Input *input = nullptr;
+
+ SafeFlag monitor_joypads_exit;
+ SafeFlag joypad_events_exit;
+ Thread monitor_joypads_thread;
+ Thread joypad_events_thread;
+
Joypad joypads[JOYPADS_MAX];
+ Mutex joypads_mutex[JOYPADS_MAX];
+
Vector<String> attached_devices;
- static void joy_thread_func(void *p_user);
+ static void monitor_joypads_thread_func(void *p_user);
+ void monitor_joypads_thread_run();
+
+ void open_joypad(const char *p_path);
+ void setup_joypad_properties(Joypad &p_joypad);
- int get_joy_from_path(String p_path) const;
+ void close_joypads();
+ void close_joypad(const char *p_devpath);
+ void close_joypad(Joypad &p_joypad, int p_id);
- void setup_joypad_properties(int p_id);
- void close_joypad(int p_id = -1);
#ifdef UDEV_ENABLED
void enumerate_joypads(struct udev *p_udev);
void monitor_joypads(struct udev *p_udev);
#endif
void monitor_joypads();
- void run_joypad_thread();
- void open_joypad(const char *p_path);
- void joypad_vibration_start(int p_id, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp);
- void joypad_vibration_stop(int p_id, uint64_t p_timestamp);
+ void joypad_vibration_start(Joypad &p_joypad, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp);
+ void joypad_vibration_stop(Joypad &p_joypad, uint64_t p_timestamp);
+
+ static void joypad_events_thread_func(void *p_user);
+ void joypad_events_thread_run();
float axis_correct(const input_absinfo *p_abs, int p_value) const;
};
diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp
index 86874687ad..b73d4dc626 100644
--- a/platform/linuxbsd/os_linuxbsd.cpp
+++ b/platform/linuxbsd/os_linuxbsd.cpp
@@ -131,20 +131,19 @@ void OS_LinuxBSD::initialize_joypads() {
String OS_LinuxBSD::get_unique_id() const {
static String machine_id;
if (machine_id.is_empty()) {
- if (FileAccess *f = FileAccess::open("/etc/machine-id", FileAccess::READ)) {
+ Ref<FileAccess> f = FileAccess::open("/etc/machine-id", FileAccess::READ);
+ if (f.is_valid()) {
while (machine_id.is_empty() && !f->eof_reached()) {
machine_id = f->get_line().strip_edges();
}
- f->close();
- memdelete(f);
}
}
return machine_id;
}
String OS_LinuxBSD::get_processor_name() const {
- FileAccessRef f = FileAccess::open("/proc/cpuinfo", FileAccess::READ);
- ERR_FAIL_COND_V_MSG(!f, "", String("Couldn't open `/proc/cpuinfo` to get the CPU model name. Returning an empty string."));
+ Ref<FileAccess> f = FileAccess::open("/proc/cpuinfo", FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(f.is_null(), "", String("Couldn't open `/proc/cpuinfo` to get the CPU model name. Returning an empty string."));
while (!f->eof_reached()) {
const String line = f->get_line();
@@ -243,6 +242,91 @@ bool OS_LinuxBSD::_check_internal_feature_support(const String &p_feature) {
return p_feature == "pc";
}
+uint64_t OS_LinuxBSD::get_embedded_pck_offset() const {
+ Ref<FileAccess> f = FileAccess::open(get_executable_path(), FileAccess::READ);
+ if (f.is_null()) {
+ return 0;
+ }
+
+ // Read and check ELF magic number.
+ {
+ uint32_t magic = f->get_32();
+ if (magic != 0x464c457f) { // 0x7F + "ELF"
+ return 0;
+ }
+ }
+
+ // Read program architecture bits from class field.
+ int bits = f->get_8() * 32;
+
+ // Get info about the section header table.
+ int64_t section_table_pos;
+ int64_t section_header_size;
+ if (bits == 32) {
+ section_header_size = 40;
+ f->seek(0x20);
+ section_table_pos = f->get_32();
+ f->seek(0x30);
+ } else { // 64
+ section_header_size = 64;
+ f->seek(0x28);
+ section_table_pos = f->get_64();
+ f->seek(0x3c);
+ }
+ int num_sections = f->get_16();
+ int string_section_idx = f->get_16();
+
+ // Load the strings table.
+ uint8_t *strings;
+ {
+ // Jump to the strings section header.
+ f->seek(section_table_pos + string_section_idx * section_header_size);
+
+ // Read strings data size and offset.
+ int64_t string_data_pos;
+ int64_t string_data_size;
+ if (bits == 32) {
+ f->seek(f->get_position() + 0x10);
+ string_data_pos = f->get_32();
+ string_data_size = f->get_32();
+ } else { // 64
+ f->seek(f->get_position() + 0x18);
+ string_data_pos = f->get_64();
+ string_data_size = f->get_64();
+ }
+
+ // Read strings data.
+ f->seek(string_data_pos);
+ strings = (uint8_t *)memalloc(string_data_size);
+ if (!strings) {
+ return 0;
+ }
+ f->get_buffer(strings, string_data_size);
+ }
+
+ // Search for the "pck" section.
+ int64_t off = 0;
+ for (int i = 0; i < num_sections; ++i) {
+ int64_t section_header_pos = section_table_pos + i * section_header_size;
+ f->seek(section_header_pos);
+
+ uint32_t name_offset = f->get_32();
+ if (strcmp((char *)strings + name_offset, "pck") == 0) {
+ if (bits == 32) {
+ f->seek(section_header_pos + 0x10);
+ off = f->get_32();
+ } else { // 64
+ f->seek(section_header_pos + 0x18);
+ off = f->get_64();
+ }
+ break;
+ }
+ }
+ memfree(strings);
+
+ return off;
+}
+
String OS_LinuxBSD::get_config_path() const {
if (has_environment("XDG_CONFIG_HOME")) {
if (get_environment("XDG_CONFIG_HOME").is_absolute_path()) {
@@ -465,7 +549,7 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) {
// Create needed directories for decided trash can location.
{
- DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ Ref<DirAccess> dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
Error err = dir_access->make_dir_recursive(trash_path);
// Issue an error if trash can is not created properly.
@@ -512,13 +596,14 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) {
String trash_info = "[Trash Info]\nPath=" + path.uri_encode() + "\nDeletionDate=" + timestamp + "\n";
{
Error err;
- FileAccessRef file = FileAccess::open(trash_path + "/info/" + file_name + ".trashinfo", FileAccess::WRITE, &err);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Can't create trashinfo file: \"" + trash_path + "/info/" + file_name + ".trashinfo\"");
- file->store_string(trash_info);
- file->close();
+ {
+ Ref<FileAccess> file = FileAccess::open(trash_path + "/info/" + file_name + ".trashinfo", FileAccess::WRITE, &err);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Can't create trashinfo file: \"" + trash_path + "/info/" + file_name + ".trashinfo\"");
+ file->store_string(trash_info);
+ }
// Rename our resource before moving it to the trash can.
- DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ Ref<DirAccess> dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
err = dir_access->rename(path, renamed_path);
ERR_FAIL_COND_V_MSG(err != OK, err, "Can't rename file \"" + path + "\" to \"" + renamed_path + "\"");
}
@@ -535,7 +620,7 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) {
// Issue an error if "mv" failed to move the given resource to the trash can.
if (err != OK || retval != 0) {
ERR_PRINT("move_to_trash: Could not move the resource \"" + path + "\" to the trash can \"" + trash_path + "/files\"");
- DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ Ref<DirAccess> dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
err = dir_access->rename(renamed_path, path);
ERR_FAIL_COND_V_MSG(err != OK, err, "Could not rename \"" + renamed_path + "\" back to its original name: \"" + path + "\"");
return FAILED;
diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h
index d3857e85f8..3f97b86eae 100644
--- a/platform/linuxbsd/os_linuxbsd.h
+++ b/platform/linuxbsd/os_linuxbsd.h
@@ -63,7 +63,7 @@ class OS_LinuxBSD : public OS_Unix {
CrashHandler crash_handler;
- MainLoop *main_loop;
+ MainLoop *main_loop = nullptr;
protected:
virtual void initialize() override;
@@ -78,6 +78,8 @@ public:
virtual MainLoop *get_main_loop() const override;
+ virtual uint64_t get_embedded_pck_offset() const override;
+
virtual String get_config_path() const override;
virtual String get_data_path() const override;
virtual String get_cache_path() const override;
diff --git a/platform/linuxbsd/speechd-so_wrap.c b/platform/linuxbsd/speechd-so_wrap.c
new file mode 100644
index 0000000000..749474e181
--- /dev/null
+++ b/platform/linuxbsd/speechd-so_wrap.c
@@ -0,0 +1,881 @@
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ./dynload-wrapper/generate-wrapper.py 0.3 on 2022-04-28 14:34:21
+// flags: ./dynload-wrapper/generate-wrapper.py --sys-include <libspeechd.h> --include /usr/include/speech-dispatcher/libspeechd.h --soname libspeechd.so.2 --init-name speechd --omit-prefix spd_get_client_list --output-header speechd-so_wrap.h --output-implementation speechd-so_wrap.c
+//
+#include <stdint.h>
+
+#define SPDConnectionAddress__free SPDConnectionAddress__free_dylibloader_orig_speechd
+#define spd_get_default_address spd_get_default_address_dylibloader_orig_speechd
+#define spd_open spd_open_dylibloader_orig_speechd
+#define spd_open2 spd_open2_dylibloader_orig_speechd
+#define spd_close spd_close_dylibloader_orig_speechd
+#define spd_say spd_say_dylibloader_orig_speechd
+#define spd_sayf spd_sayf_dylibloader_orig_speechd
+#define spd_stop spd_stop_dylibloader_orig_speechd
+#define spd_stop_all spd_stop_all_dylibloader_orig_speechd
+#define spd_stop_uid spd_stop_uid_dylibloader_orig_speechd
+#define spd_cancel spd_cancel_dylibloader_orig_speechd
+#define spd_cancel_all spd_cancel_all_dylibloader_orig_speechd
+#define spd_cancel_uid spd_cancel_uid_dylibloader_orig_speechd
+#define spd_pause spd_pause_dylibloader_orig_speechd
+#define spd_pause_all spd_pause_all_dylibloader_orig_speechd
+#define spd_pause_uid spd_pause_uid_dylibloader_orig_speechd
+#define spd_resume spd_resume_dylibloader_orig_speechd
+#define spd_resume_all spd_resume_all_dylibloader_orig_speechd
+#define spd_resume_uid spd_resume_uid_dylibloader_orig_speechd
+#define spd_key spd_key_dylibloader_orig_speechd
+#define spd_char spd_char_dylibloader_orig_speechd
+#define spd_wchar spd_wchar_dylibloader_orig_speechd
+#define spd_sound_icon spd_sound_icon_dylibloader_orig_speechd
+#define spd_set_voice_type spd_set_voice_type_dylibloader_orig_speechd
+#define spd_set_voice_type_all spd_set_voice_type_all_dylibloader_orig_speechd
+#define spd_set_voice_type_uid spd_set_voice_type_uid_dylibloader_orig_speechd
+#define spd_get_voice_type spd_get_voice_type_dylibloader_orig_speechd
+#define spd_set_synthesis_voice spd_set_synthesis_voice_dylibloader_orig_speechd
+#define spd_set_synthesis_voice_all spd_set_synthesis_voice_all_dylibloader_orig_speechd
+#define spd_set_synthesis_voice_uid spd_set_synthesis_voice_uid_dylibloader_orig_speechd
+#define spd_set_data_mode spd_set_data_mode_dylibloader_orig_speechd
+#define spd_set_notification_on spd_set_notification_on_dylibloader_orig_speechd
+#define spd_set_notification_off spd_set_notification_off_dylibloader_orig_speechd
+#define spd_set_notification spd_set_notification_dylibloader_orig_speechd
+#define spd_set_voice_rate spd_set_voice_rate_dylibloader_orig_speechd
+#define spd_set_voice_rate_all spd_set_voice_rate_all_dylibloader_orig_speechd
+#define spd_set_voice_rate_uid spd_set_voice_rate_uid_dylibloader_orig_speechd
+#define spd_get_voice_rate spd_get_voice_rate_dylibloader_orig_speechd
+#define spd_set_voice_pitch spd_set_voice_pitch_dylibloader_orig_speechd
+#define spd_set_voice_pitch_all spd_set_voice_pitch_all_dylibloader_orig_speechd
+#define spd_set_voice_pitch_uid spd_set_voice_pitch_uid_dylibloader_orig_speechd
+#define spd_get_voice_pitch spd_get_voice_pitch_dylibloader_orig_speechd
+#define spd_set_voice_pitch_range spd_set_voice_pitch_range_dylibloader_orig_speechd
+#define spd_set_voice_pitch_range_all spd_set_voice_pitch_range_all_dylibloader_orig_speechd
+#define spd_set_voice_pitch_range_uid spd_set_voice_pitch_range_uid_dylibloader_orig_speechd
+#define spd_set_volume spd_set_volume_dylibloader_orig_speechd
+#define spd_set_volume_all spd_set_volume_all_dylibloader_orig_speechd
+#define spd_set_volume_uid spd_set_volume_uid_dylibloader_orig_speechd
+#define spd_get_volume spd_get_volume_dylibloader_orig_speechd
+#define spd_set_punctuation spd_set_punctuation_dylibloader_orig_speechd
+#define spd_set_punctuation_all spd_set_punctuation_all_dylibloader_orig_speechd
+#define spd_set_punctuation_uid spd_set_punctuation_uid_dylibloader_orig_speechd
+#define spd_set_capital_letters spd_set_capital_letters_dylibloader_orig_speechd
+#define spd_set_capital_letters_all spd_set_capital_letters_all_dylibloader_orig_speechd
+#define spd_set_capital_letters_uid spd_set_capital_letters_uid_dylibloader_orig_speechd
+#define spd_set_spelling spd_set_spelling_dylibloader_orig_speechd
+#define spd_set_spelling_all spd_set_spelling_all_dylibloader_orig_speechd
+#define spd_set_spelling_uid spd_set_spelling_uid_dylibloader_orig_speechd
+#define spd_set_language spd_set_language_dylibloader_orig_speechd
+#define spd_set_language_all spd_set_language_all_dylibloader_orig_speechd
+#define spd_set_language_uid spd_set_language_uid_dylibloader_orig_speechd
+#define spd_get_language spd_get_language_dylibloader_orig_speechd
+#define spd_set_output_module spd_set_output_module_dylibloader_orig_speechd
+#define spd_set_output_module_all spd_set_output_module_all_dylibloader_orig_speechd
+#define spd_set_output_module_uid spd_set_output_module_uid_dylibloader_orig_speechd
+#define spd_get_message_list_fd spd_get_message_list_fd_dylibloader_orig_speechd
+#define spd_list_modules spd_list_modules_dylibloader_orig_speechd
+#define free_spd_modules free_spd_modules_dylibloader_orig_speechd
+#define spd_get_output_module spd_get_output_module_dylibloader_orig_speechd
+#define spd_list_voices spd_list_voices_dylibloader_orig_speechd
+#define spd_list_synthesis_voices spd_list_synthesis_voices_dylibloader_orig_speechd
+#define free_spd_voices free_spd_voices_dylibloader_orig_speechd
+#define spd_execute_command_with_list_reply spd_execute_command_with_list_reply_dylibloader_orig_speechd
+#define spd_execute_command spd_execute_command_dylibloader_orig_speechd
+#define spd_execute_command_with_reply spd_execute_command_with_reply_dylibloader_orig_speechd
+#define spd_execute_command_wo_mutex spd_execute_command_wo_mutex_dylibloader_orig_speechd
+#define spd_send_data spd_send_data_dylibloader_orig_speechd
+#define spd_send_data_wo_mutex spd_send_data_wo_mutex_dylibloader_orig_speechd
+#include <libspeechd.h>
+#undef SPDConnectionAddress__free
+#undef spd_get_default_address
+#undef spd_open
+#undef spd_open2
+#undef spd_close
+#undef spd_say
+#undef spd_sayf
+#undef spd_stop
+#undef spd_stop_all
+#undef spd_stop_uid
+#undef spd_cancel
+#undef spd_cancel_all
+#undef spd_cancel_uid
+#undef spd_pause
+#undef spd_pause_all
+#undef spd_pause_uid
+#undef spd_resume
+#undef spd_resume_all
+#undef spd_resume_uid
+#undef spd_key
+#undef spd_char
+#undef spd_wchar
+#undef spd_sound_icon
+#undef spd_set_voice_type
+#undef spd_set_voice_type_all
+#undef spd_set_voice_type_uid
+#undef spd_get_voice_type
+#undef spd_set_synthesis_voice
+#undef spd_set_synthesis_voice_all
+#undef spd_set_synthesis_voice_uid
+#undef spd_set_data_mode
+#undef spd_set_notification_on
+#undef spd_set_notification_off
+#undef spd_set_notification
+#undef spd_set_voice_rate
+#undef spd_set_voice_rate_all
+#undef spd_set_voice_rate_uid
+#undef spd_get_voice_rate
+#undef spd_set_voice_pitch
+#undef spd_set_voice_pitch_all
+#undef spd_set_voice_pitch_uid
+#undef spd_get_voice_pitch
+#undef spd_set_voice_pitch_range
+#undef spd_set_voice_pitch_range_all
+#undef spd_set_voice_pitch_range_uid
+#undef spd_set_volume
+#undef spd_set_volume_all
+#undef spd_set_volume_uid
+#undef spd_get_volume
+#undef spd_set_punctuation
+#undef spd_set_punctuation_all
+#undef spd_set_punctuation_uid
+#undef spd_set_capital_letters
+#undef spd_set_capital_letters_all
+#undef spd_set_capital_letters_uid
+#undef spd_set_spelling
+#undef spd_set_spelling_all
+#undef spd_set_spelling_uid
+#undef spd_set_language
+#undef spd_set_language_all
+#undef spd_set_language_uid
+#undef spd_get_language
+#undef spd_set_output_module
+#undef spd_set_output_module_all
+#undef spd_set_output_module_uid
+#undef spd_get_message_list_fd
+#undef spd_list_modules
+#undef free_spd_modules
+#undef spd_get_output_module
+#undef spd_list_voices
+#undef spd_list_synthesis_voices
+#undef free_spd_voices
+#undef spd_execute_command_with_list_reply
+#undef spd_execute_command
+#undef spd_execute_command_with_reply
+#undef spd_execute_command_wo_mutex
+#undef spd_send_data
+#undef spd_send_data_wo_mutex
+#include <dlfcn.h>
+#include <stdio.h>
+void (*SPDConnectionAddress__free_dylibloader_wrapper_speechd)( SPDConnectionAddress*);
+SPDConnectionAddress* (*spd_get_default_address_dylibloader_wrapper_speechd)( char**);
+SPDConnection* (*spd_open_dylibloader_wrapper_speechd)(const char*,const char*,const char*, SPDConnectionMode);
+SPDConnection* (*spd_open2_dylibloader_wrapper_speechd)(const char*,const char*,const char*, SPDConnectionMode, SPDConnectionAddress*, int, char**);
+void (*spd_close_dylibloader_wrapper_speechd)( SPDConnection*);
+int (*spd_say_dylibloader_wrapper_speechd)( SPDConnection*, SPDPriority,const char*);
+int (*spd_sayf_dylibloader_wrapper_speechd)( SPDConnection*, SPDPriority,const char*,...);
+int (*spd_stop_dylibloader_wrapper_speechd)( SPDConnection*);
+int (*spd_stop_all_dylibloader_wrapper_speechd)( SPDConnection*);
+int (*spd_stop_uid_dylibloader_wrapper_speechd)( SPDConnection*, int);
+int (*spd_cancel_dylibloader_wrapper_speechd)( SPDConnection*);
+int (*spd_cancel_all_dylibloader_wrapper_speechd)( SPDConnection*);
+int (*spd_cancel_uid_dylibloader_wrapper_speechd)( SPDConnection*, int);
+int (*spd_pause_dylibloader_wrapper_speechd)( SPDConnection*);
+int (*spd_pause_all_dylibloader_wrapper_speechd)( SPDConnection*);
+int (*spd_pause_uid_dylibloader_wrapper_speechd)( SPDConnection*, int);
+int (*spd_resume_dylibloader_wrapper_speechd)( SPDConnection*);
+int (*spd_resume_all_dylibloader_wrapper_speechd)( SPDConnection*);
+int (*spd_resume_uid_dylibloader_wrapper_speechd)( SPDConnection*, int);
+int (*spd_key_dylibloader_wrapper_speechd)( SPDConnection*, SPDPriority,const char*);
+int (*spd_char_dylibloader_wrapper_speechd)( SPDConnection*, SPDPriority,const char*);
+int (*spd_wchar_dylibloader_wrapper_speechd)( SPDConnection*, SPDPriority, wchar_t);
+int (*spd_sound_icon_dylibloader_wrapper_speechd)( SPDConnection*, SPDPriority,const char*);
+int (*spd_set_voice_type_dylibloader_wrapper_speechd)( SPDConnection*, SPDVoiceType);
+int (*spd_set_voice_type_all_dylibloader_wrapper_speechd)( SPDConnection*, SPDVoiceType);
+int (*spd_set_voice_type_uid_dylibloader_wrapper_speechd)( SPDConnection*, SPDVoiceType, unsigned int);
+SPDVoiceType (*spd_get_voice_type_dylibloader_wrapper_speechd)( SPDConnection*);
+int (*spd_set_synthesis_voice_dylibloader_wrapper_speechd)( SPDConnection*,const char*);
+int (*spd_set_synthesis_voice_all_dylibloader_wrapper_speechd)( SPDConnection*,const char*);
+int (*spd_set_synthesis_voice_uid_dylibloader_wrapper_speechd)( SPDConnection*,const char*, unsigned int);
+int (*spd_set_data_mode_dylibloader_wrapper_speechd)( SPDConnection*, SPDDataMode);
+int (*spd_set_notification_on_dylibloader_wrapper_speechd)( SPDConnection*, SPDNotification);
+int (*spd_set_notification_off_dylibloader_wrapper_speechd)( SPDConnection*, SPDNotification);
+int (*spd_set_notification_dylibloader_wrapper_speechd)( SPDConnection*, SPDNotification,const char*);
+int (*spd_set_voice_rate_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+int (*spd_set_voice_rate_all_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+int (*spd_set_voice_rate_uid_dylibloader_wrapper_speechd)( SPDConnection*, signed int, unsigned int);
+int (*spd_get_voice_rate_dylibloader_wrapper_speechd)( SPDConnection*);
+int (*spd_set_voice_pitch_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+int (*spd_set_voice_pitch_all_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+int (*spd_set_voice_pitch_uid_dylibloader_wrapper_speechd)( SPDConnection*, signed int, unsigned int);
+int (*spd_get_voice_pitch_dylibloader_wrapper_speechd)( SPDConnection*);
+int (*spd_set_voice_pitch_range_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+int (*spd_set_voice_pitch_range_all_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+int (*spd_set_voice_pitch_range_uid_dylibloader_wrapper_speechd)( SPDConnection*, signed int, unsigned int);
+int (*spd_set_volume_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+int (*spd_set_volume_all_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+int (*spd_set_volume_uid_dylibloader_wrapper_speechd)( SPDConnection*, signed int, unsigned int);
+int (*spd_get_volume_dylibloader_wrapper_speechd)( SPDConnection*);
+int (*spd_set_punctuation_dylibloader_wrapper_speechd)( SPDConnection*, SPDPunctuation);
+int (*spd_set_punctuation_all_dylibloader_wrapper_speechd)( SPDConnection*, SPDPunctuation);
+int (*spd_set_punctuation_uid_dylibloader_wrapper_speechd)( SPDConnection*, SPDPunctuation, unsigned int);
+int (*spd_set_capital_letters_dylibloader_wrapper_speechd)( SPDConnection*, SPDCapitalLetters);
+int (*spd_set_capital_letters_all_dylibloader_wrapper_speechd)( SPDConnection*, SPDCapitalLetters);
+int (*spd_set_capital_letters_uid_dylibloader_wrapper_speechd)( SPDConnection*, SPDCapitalLetters, unsigned int);
+int (*spd_set_spelling_dylibloader_wrapper_speechd)( SPDConnection*, SPDSpelling);
+int (*spd_set_spelling_all_dylibloader_wrapper_speechd)( SPDConnection*, SPDSpelling);
+int (*spd_set_spelling_uid_dylibloader_wrapper_speechd)( SPDConnection*, SPDSpelling, unsigned int);
+int (*spd_set_language_dylibloader_wrapper_speechd)( SPDConnection*,const char*);
+int (*spd_set_language_all_dylibloader_wrapper_speechd)( SPDConnection*,const char*);
+int (*spd_set_language_uid_dylibloader_wrapper_speechd)( SPDConnection*,const char*, unsigned int);
+char* (*spd_get_language_dylibloader_wrapper_speechd)( SPDConnection*);
+int (*spd_set_output_module_dylibloader_wrapper_speechd)( SPDConnection*,const char*);
+int (*spd_set_output_module_all_dylibloader_wrapper_speechd)( SPDConnection*,const char*);
+int (*spd_set_output_module_uid_dylibloader_wrapper_speechd)( SPDConnection*,const char*, unsigned int);
+int (*spd_get_message_list_fd_dylibloader_wrapper_speechd)( SPDConnection*, int, int*, char**);
+char** (*spd_list_modules_dylibloader_wrapper_speechd)( SPDConnection*);
+void (*free_spd_modules_dylibloader_wrapper_speechd)( char**);
+char* (*spd_get_output_module_dylibloader_wrapper_speechd)( SPDConnection*);
+char** (*spd_list_voices_dylibloader_wrapper_speechd)( SPDConnection*);
+SPDVoice** (*spd_list_synthesis_voices_dylibloader_wrapper_speechd)( SPDConnection*);
+void (*free_spd_voices_dylibloader_wrapper_speechd)( SPDVoice**);
+char** (*spd_execute_command_with_list_reply_dylibloader_wrapper_speechd)( SPDConnection*, char*);
+int (*spd_execute_command_dylibloader_wrapper_speechd)( SPDConnection*, char*);
+int (*spd_execute_command_with_reply_dylibloader_wrapper_speechd)( SPDConnection*, char*, char**);
+int (*spd_execute_command_wo_mutex_dylibloader_wrapper_speechd)( SPDConnection*, char*);
+char* (*spd_send_data_dylibloader_wrapper_speechd)( SPDConnection*,const char*, int);
+char* (*spd_send_data_wo_mutex_dylibloader_wrapper_speechd)( SPDConnection*,const char*, int);
+int initialize_speechd(int verbose) {
+ void *handle;
+ char *error;
+ handle = dlopen("libspeechd.so.2", RTLD_LAZY);
+ if (!handle) {
+ if (verbose) {
+ fprintf(stderr, "%s\n", dlerror());
+ }
+ return(1);
+ }
+ dlerror();
+// SPDConnectionAddress__free
+ *(void **) (&SPDConnectionAddress__free_dylibloader_wrapper_speechd) = dlsym(handle, "SPDConnectionAddress__free");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_get_default_address
+ *(void **) (&spd_get_default_address_dylibloader_wrapper_speechd) = dlsym(handle, "spd_get_default_address");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_open
+ *(void **) (&spd_open_dylibloader_wrapper_speechd) = dlsym(handle, "spd_open");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_open2
+ *(void **) (&spd_open2_dylibloader_wrapper_speechd) = dlsym(handle, "spd_open2");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_close
+ *(void **) (&spd_close_dylibloader_wrapper_speechd) = dlsym(handle, "spd_close");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_say
+ *(void **) (&spd_say_dylibloader_wrapper_speechd) = dlsym(handle, "spd_say");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_sayf
+ *(void **) (&spd_sayf_dylibloader_wrapper_speechd) = dlsym(handle, "spd_sayf");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_stop
+ *(void **) (&spd_stop_dylibloader_wrapper_speechd) = dlsym(handle, "spd_stop");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_stop_all
+ *(void **) (&spd_stop_all_dylibloader_wrapper_speechd) = dlsym(handle, "spd_stop_all");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_stop_uid
+ *(void **) (&spd_stop_uid_dylibloader_wrapper_speechd) = dlsym(handle, "spd_stop_uid");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_cancel
+ *(void **) (&spd_cancel_dylibloader_wrapper_speechd) = dlsym(handle, "spd_cancel");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_cancel_all
+ *(void **) (&spd_cancel_all_dylibloader_wrapper_speechd) = dlsym(handle, "spd_cancel_all");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_cancel_uid
+ *(void **) (&spd_cancel_uid_dylibloader_wrapper_speechd) = dlsym(handle, "spd_cancel_uid");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_pause
+ *(void **) (&spd_pause_dylibloader_wrapper_speechd) = dlsym(handle, "spd_pause");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_pause_all
+ *(void **) (&spd_pause_all_dylibloader_wrapper_speechd) = dlsym(handle, "spd_pause_all");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_pause_uid
+ *(void **) (&spd_pause_uid_dylibloader_wrapper_speechd) = dlsym(handle, "spd_pause_uid");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_resume
+ *(void **) (&spd_resume_dylibloader_wrapper_speechd) = dlsym(handle, "spd_resume");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_resume_all
+ *(void **) (&spd_resume_all_dylibloader_wrapper_speechd) = dlsym(handle, "spd_resume_all");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_resume_uid
+ *(void **) (&spd_resume_uid_dylibloader_wrapper_speechd) = dlsym(handle, "spd_resume_uid");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_key
+ *(void **) (&spd_key_dylibloader_wrapper_speechd) = dlsym(handle, "spd_key");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_char
+ *(void **) (&spd_char_dylibloader_wrapper_speechd) = dlsym(handle, "spd_char");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_wchar
+ *(void **) (&spd_wchar_dylibloader_wrapper_speechd) = dlsym(handle, "spd_wchar");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_sound_icon
+ *(void **) (&spd_sound_icon_dylibloader_wrapper_speechd) = dlsym(handle, "spd_sound_icon");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_voice_type
+ *(void **) (&spd_set_voice_type_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_voice_type");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_voice_type_all
+ *(void **) (&spd_set_voice_type_all_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_voice_type_all");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_voice_type_uid
+ *(void **) (&spd_set_voice_type_uid_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_voice_type_uid");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_get_voice_type
+ *(void **) (&spd_get_voice_type_dylibloader_wrapper_speechd) = dlsym(handle, "spd_get_voice_type");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_synthesis_voice
+ *(void **) (&spd_set_synthesis_voice_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_synthesis_voice");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_synthesis_voice_all
+ *(void **) (&spd_set_synthesis_voice_all_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_synthesis_voice_all");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_synthesis_voice_uid
+ *(void **) (&spd_set_synthesis_voice_uid_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_synthesis_voice_uid");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_data_mode
+ *(void **) (&spd_set_data_mode_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_data_mode");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_notification_on
+ *(void **) (&spd_set_notification_on_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_notification_on");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_notification_off
+ *(void **) (&spd_set_notification_off_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_notification_off");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_notification
+ *(void **) (&spd_set_notification_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_notification");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_voice_rate
+ *(void **) (&spd_set_voice_rate_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_voice_rate");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_voice_rate_all
+ *(void **) (&spd_set_voice_rate_all_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_voice_rate_all");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_voice_rate_uid
+ *(void **) (&spd_set_voice_rate_uid_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_voice_rate_uid");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_get_voice_rate
+ *(void **) (&spd_get_voice_rate_dylibloader_wrapper_speechd) = dlsym(handle, "spd_get_voice_rate");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_voice_pitch
+ *(void **) (&spd_set_voice_pitch_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_voice_pitch");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_voice_pitch_all
+ *(void **) (&spd_set_voice_pitch_all_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_voice_pitch_all");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_voice_pitch_uid
+ *(void **) (&spd_set_voice_pitch_uid_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_voice_pitch_uid");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_get_voice_pitch
+ *(void **) (&spd_get_voice_pitch_dylibloader_wrapper_speechd) = dlsym(handle, "spd_get_voice_pitch");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_voice_pitch_range
+ *(void **) (&spd_set_voice_pitch_range_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_voice_pitch_range");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_voice_pitch_range_all
+ *(void **) (&spd_set_voice_pitch_range_all_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_voice_pitch_range_all");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_voice_pitch_range_uid
+ *(void **) (&spd_set_voice_pitch_range_uid_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_voice_pitch_range_uid");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_volume
+ *(void **) (&spd_set_volume_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_volume");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_volume_all
+ *(void **) (&spd_set_volume_all_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_volume_all");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_volume_uid
+ *(void **) (&spd_set_volume_uid_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_volume_uid");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_get_volume
+ *(void **) (&spd_get_volume_dylibloader_wrapper_speechd) = dlsym(handle, "spd_get_volume");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_punctuation
+ *(void **) (&spd_set_punctuation_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_punctuation");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_punctuation_all
+ *(void **) (&spd_set_punctuation_all_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_punctuation_all");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_punctuation_uid
+ *(void **) (&spd_set_punctuation_uid_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_punctuation_uid");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_capital_letters
+ *(void **) (&spd_set_capital_letters_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_capital_letters");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_capital_letters_all
+ *(void **) (&spd_set_capital_letters_all_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_capital_letters_all");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_capital_letters_uid
+ *(void **) (&spd_set_capital_letters_uid_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_capital_letters_uid");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_spelling
+ *(void **) (&spd_set_spelling_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_spelling");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_spelling_all
+ *(void **) (&spd_set_spelling_all_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_spelling_all");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_spelling_uid
+ *(void **) (&spd_set_spelling_uid_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_spelling_uid");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_language
+ *(void **) (&spd_set_language_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_language");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_language_all
+ *(void **) (&spd_set_language_all_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_language_all");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_language_uid
+ *(void **) (&spd_set_language_uid_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_language_uid");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_get_language
+ *(void **) (&spd_get_language_dylibloader_wrapper_speechd) = dlsym(handle, "spd_get_language");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_output_module
+ *(void **) (&spd_set_output_module_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_output_module");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_output_module_all
+ *(void **) (&spd_set_output_module_all_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_output_module_all");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_set_output_module_uid
+ *(void **) (&spd_set_output_module_uid_dylibloader_wrapper_speechd) = dlsym(handle, "spd_set_output_module_uid");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_get_message_list_fd
+ *(void **) (&spd_get_message_list_fd_dylibloader_wrapper_speechd) = dlsym(handle, "spd_get_message_list_fd");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_list_modules
+ *(void **) (&spd_list_modules_dylibloader_wrapper_speechd) = dlsym(handle, "spd_list_modules");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// free_spd_modules
+ *(void **) (&free_spd_modules_dylibloader_wrapper_speechd) = dlsym(handle, "free_spd_modules");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_get_output_module
+ *(void **) (&spd_get_output_module_dylibloader_wrapper_speechd) = dlsym(handle, "spd_get_output_module");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_list_voices
+ *(void **) (&spd_list_voices_dylibloader_wrapper_speechd) = dlsym(handle, "spd_list_voices");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_list_synthesis_voices
+ *(void **) (&spd_list_synthesis_voices_dylibloader_wrapper_speechd) = dlsym(handle, "spd_list_synthesis_voices");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// free_spd_voices
+ *(void **) (&free_spd_voices_dylibloader_wrapper_speechd) = dlsym(handle, "free_spd_voices");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_execute_command_with_list_reply
+ *(void **) (&spd_execute_command_with_list_reply_dylibloader_wrapper_speechd) = dlsym(handle, "spd_execute_command_with_list_reply");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_execute_command
+ *(void **) (&spd_execute_command_dylibloader_wrapper_speechd) = dlsym(handle, "spd_execute_command");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_execute_command_with_reply
+ *(void **) (&spd_execute_command_with_reply_dylibloader_wrapper_speechd) = dlsym(handle, "spd_execute_command_with_reply");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_execute_command_wo_mutex
+ *(void **) (&spd_execute_command_wo_mutex_dylibloader_wrapper_speechd) = dlsym(handle, "spd_execute_command_wo_mutex");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_send_data
+ *(void **) (&spd_send_data_dylibloader_wrapper_speechd) = dlsym(handle, "spd_send_data");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// spd_send_data_wo_mutex
+ *(void **) (&spd_send_data_wo_mutex_dylibloader_wrapper_speechd) = dlsym(handle, "spd_send_data_wo_mutex");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+return 0;
+}
diff --git a/platform/linuxbsd/speechd-so_wrap.h b/platform/linuxbsd/speechd-so_wrap.h
new file mode 100644
index 0000000000..8e1c053348
--- /dev/null
+++ b/platform/linuxbsd/speechd-so_wrap.h
@@ -0,0 +1,330 @@
+#ifndef DYLIBLOAD_WRAPPER_SPEECHD
+#define DYLIBLOAD_WRAPPER_SPEECHD
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ./dynload-wrapper/generate-wrapper.py 0.3 on 2022-04-28 14:34:21
+// flags: ./dynload-wrapper/generate-wrapper.py --sys-include <libspeechd.h> --include /usr/include/speech-dispatcher/libspeechd.h --soname libspeechd.so.2 --init-name speechd --omit-prefix spd_get_client_list --output-header speechd-so_wrap.h --output-implementation speechd-so_wrap.c
+//
+#include <stdint.h>
+
+#define SPDConnectionAddress__free SPDConnectionAddress__free_dylibloader_orig_speechd
+#define spd_get_default_address spd_get_default_address_dylibloader_orig_speechd
+#define spd_open spd_open_dylibloader_orig_speechd
+#define spd_open2 spd_open2_dylibloader_orig_speechd
+#define spd_close spd_close_dylibloader_orig_speechd
+#define spd_say spd_say_dylibloader_orig_speechd
+#define spd_sayf spd_sayf_dylibloader_orig_speechd
+#define spd_stop spd_stop_dylibloader_orig_speechd
+#define spd_stop_all spd_stop_all_dylibloader_orig_speechd
+#define spd_stop_uid spd_stop_uid_dylibloader_orig_speechd
+#define spd_cancel spd_cancel_dylibloader_orig_speechd
+#define spd_cancel_all spd_cancel_all_dylibloader_orig_speechd
+#define spd_cancel_uid spd_cancel_uid_dylibloader_orig_speechd
+#define spd_pause spd_pause_dylibloader_orig_speechd
+#define spd_pause_all spd_pause_all_dylibloader_orig_speechd
+#define spd_pause_uid spd_pause_uid_dylibloader_orig_speechd
+#define spd_resume spd_resume_dylibloader_orig_speechd
+#define spd_resume_all spd_resume_all_dylibloader_orig_speechd
+#define spd_resume_uid spd_resume_uid_dylibloader_orig_speechd
+#define spd_key spd_key_dylibloader_orig_speechd
+#define spd_char spd_char_dylibloader_orig_speechd
+#define spd_wchar spd_wchar_dylibloader_orig_speechd
+#define spd_sound_icon spd_sound_icon_dylibloader_orig_speechd
+#define spd_set_voice_type spd_set_voice_type_dylibloader_orig_speechd
+#define spd_set_voice_type_all spd_set_voice_type_all_dylibloader_orig_speechd
+#define spd_set_voice_type_uid spd_set_voice_type_uid_dylibloader_orig_speechd
+#define spd_get_voice_type spd_get_voice_type_dylibloader_orig_speechd
+#define spd_set_synthesis_voice spd_set_synthesis_voice_dylibloader_orig_speechd
+#define spd_set_synthesis_voice_all spd_set_synthesis_voice_all_dylibloader_orig_speechd
+#define spd_set_synthesis_voice_uid spd_set_synthesis_voice_uid_dylibloader_orig_speechd
+#define spd_set_data_mode spd_set_data_mode_dylibloader_orig_speechd
+#define spd_set_notification_on spd_set_notification_on_dylibloader_orig_speechd
+#define spd_set_notification_off spd_set_notification_off_dylibloader_orig_speechd
+#define spd_set_notification spd_set_notification_dylibloader_orig_speechd
+#define spd_set_voice_rate spd_set_voice_rate_dylibloader_orig_speechd
+#define spd_set_voice_rate_all spd_set_voice_rate_all_dylibloader_orig_speechd
+#define spd_set_voice_rate_uid spd_set_voice_rate_uid_dylibloader_orig_speechd
+#define spd_get_voice_rate spd_get_voice_rate_dylibloader_orig_speechd
+#define spd_set_voice_pitch spd_set_voice_pitch_dylibloader_orig_speechd
+#define spd_set_voice_pitch_all spd_set_voice_pitch_all_dylibloader_orig_speechd
+#define spd_set_voice_pitch_uid spd_set_voice_pitch_uid_dylibloader_orig_speechd
+#define spd_get_voice_pitch spd_get_voice_pitch_dylibloader_orig_speechd
+#define spd_set_voice_pitch_range spd_set_voice_pitch_range_dylibloader_orig_speechd
+#define spd_set_voice_pitch_range_all spd_set_voice_pitch_range_all_dylibloader_orig_speechd
+#define spd_set_voice_pitch_range_uid spd_set_voice_pitch_range_uid_dylibloader_orig_speechd
+#define spd_set_volume spd_set_volume_dylibloader_orig_speechd
+#define spd_set_volume_all spd_set_volume_all_dylibloader_orig_speechd
+#define spd_set_volume_uid spd_set_volume_uid_dylibloader_orig_speechd
+#define spd_get_volume spd_get_volume_dylibloader_orig_speechd
+#define spd_set_punctuation spd_set_punctuation_dylibloader_orig_speechd
+#define spd_set_punctuation_all spd_set_punctuation_all_dylibloader_orig_speechd
+#define spd_set_punctuation_uid spd_set_punctuation_uid_dylibloader_orig_speechd
+#define spd_set_capital_letters spd_set_capital_letters_dylibloader_orig_speechd
+#define spd_set_capital_letters_all spd_set_capital_letters_all_dylibloader_orig_speechd
+#define spd_set_capital_letters_uid spd_set_capital_letters_uid_dylibloader_orig_speechd
+#define spd_set_spelling spd_set_spelling_dylibloader_orig_speechd
+#define spd_set_spelling_all spd_set_spelling_all_dylibloader_orig_speechd
+#define spd_set_spelling_uid spd_set_spelling_uid_dylibloader_orig_speechd
+#define spd_set_language spd_set_language_dylibloader_orig_speechd
+#define spd_set_language_all spd_set_language_all_dylibloader_orig_speechd
+#define spd_set_language_uid spd_set_language_uid_dylibloader_orig_speechd
+#define spd_get_language spd_get_language_dylibloader_orig_speechd
+#define spd_set_output_module spd_set_output_module_dylibloader_orig_speechd
+#define spd_set_output_module_all spd_set_output_module_all_dylibloader_orig_speechd
+#define spd_set_output_module_uid spd_set_output_module_uid_dylibloader_orig_speechd
+#define spd_get_message_list_fd spd_get_message_list_fd_dylibloader_orig_speechd
+#define spd_list_modules spd_list_modules_dylibloader_orig_speechd
+#define free_spd_modules free_spd_modules_dylibloader_orig_speechd
+#define spd_get_output_module spd_get_output_module_dylibloader_orig_speechd
+#define spd_list_voices spd_list_voices_dylibloader_orig_speechd
+#define spd_list_synthesis_voices spd_list_synthesis_voices_dylibloader_orig_speechd
+#define free_spd_voices free_spd_voices_dylibloader_orig_speechd
+#define spd_execute_command_with_list_reply spd_execute_command_with_list_reply_dylibloader_orig_speechd
+#define spd_execute_command spd_execute_command_dylibloader_orig_speechd
+#define spd_execute_command_with_reply spd_execute_command_with_reply_dylibloader_orig_speechd
+#define spd_execute_command_wo_mutex spd_execute_command_wo_mutex_dylibloader_orig_speechd
+#define spd_send_data spd_send_data_dylibloader_orig_speechd
+#define spd_send_data_wo_mutex spd_send_data_wo_mutex_dylibloader_orig_speechd
+#include <libspeechd.h>
+#undef SPDConnectionAddress__free
+#undef spd_get_default_address
+#undef spd_open
+#undef spd_open2
+#undef spd_close
+#undef spd_say
+#undef spd_sayf
+#undef spd_stop
+#undef spd_stop_all
+#undef spd_stop_uid
+#undef spd_cancel
+#undef spd_cancel_all
+#undef spd_cancel_uid
+#undef spd_pause
+#undef spd_pause_all
+#undef spd_pause_uid
+#undef spd_resume
+#undef spd_resume_all
+#undef spd_resume_uid
+#undef spd_key
+#undef spd_char
+#undef spd_wchar
+#undef spd_sound_icon
+#undef spd_set_voice_type
+#undef spd_set_voice_type_all
+#undef spd_set_voice_type_uid
+#undef spd_get_voice_type
+#undef spd_set_synthesis_voice
+#undef spd_set_synthesis_voice_all
+#undef spd_set_synthesis_voice_uid
+#undef spd_set_data_mode
+#undef spd_set_notification_on
+#undef spd_set_notification_off
+#undef spd_set_notification
+#undef spd_set_voice_rate
+#undef spd_set_voice_rate_all
+#undef spd_set_voice_rate_uid
+#undef spd_get_voice_rate
+#undef spd_set_voice_pitch
+#undef spd_set_voice_pitch_all
+#undef spd_set_voice_pitch_uid
+#undef spd_get_voice_pitch
+#undef spd_set_voice_pitch_range
+#undef spd_set_voice_pitch_range_all
+#undef spd_set_voice_pitch_range_uid
+#undef spd_set_volume
+#undef spd_set_volume_all
+#undef spd_set_volume_uid
+#undef spd_get_volume
+#undef spd_set_punctuation
+#undef spd_set_punctuation_all
+#undef spd_set_punctuation_uid
+#undef spd_set_capital_letters
+#undef spd_set_capital_letters_all
+#undef spd_set_capital_letters_uid
+#undef spd_set_spelling
+#undef spd_set_spelling_all
+#undef spd_set_spelling_uid
+#undef spd_set_language
+#undef spd_set_language_all
+#undef spd_set_language_uid
+#undef spd_get_language
+#undef spd_set_output_module
+#undef spd_set_output_module_all
+#undef spd_set_output_module_uid
+#undef spd_get_message_list_fd
+#undef spd_list_modules
+#undef free_spd_modules
+#undef spd_get_output_module
+#undef spd_list_voices
+#undef spd_list_synthesis_voices
+#undef free_spd_voices
+#undef spd_execute_command_with_list_reply
+#undef spd_execute_command
+#undef spd_execute_command_with_reply
+#undef spd_execute_command_wo_mutex
+#undef spd_send_data
+#undef spd_send_data_wo_mutex
+#ifdef __cplusplus
+extern "C" {
+#endif
+#define SPDConnectionAddress__free SPDConnectionAddress__free_dylibloader_wrapper_speechd
+#define spd_get_default_address spd_get_default_address_dylibloader_wrapper_speechd
+#define spd_open spd_open_dylibloader_wrapper_speechd
+#define spd_open2 spd_open2_dylibloader_wrapper_speechd
+#define spd_close spd_close_dylibloader_wrapper_speechd
+#define spd_say spd_say_dylibloader_wrapper_speechd
+#define spd_sayf spd_sayf_dylibloader_wrapper_speechd
+#define spd_stop spd_stop_dylibloader_wrapper_speechd
+#define spd_stop_all spd_stop_all_dylibloader_wrapper_speechd
+#define spd_stop_uid spd_stop_uid_dylibloader_wrapper_speechd
+#define spd_cancel spd_cancel_dylibloader_wrapper_speechd
+#define spd_cancel_all spd_cancel_all_dylibloader_wrapper_speechd
+#define spd_cancel_uid spd_cancel_uid_dylibloader_wrapper_speechd
+#define spd_pause spd_pause_dylibloader_wrapper_speechd
+#define spd_pause_all spd_pause_all_dylibloader_wrapper_speechd
+#define spd_pause_uid spd_pause_uid_dylibloader_wrapper_speechd
+#define spd_resume spd_resume_dylibloader_wrapper_speechd
+#define spd_resume_all spd_resume_all_dylibloader_wrapper_speechd
+#define spd_resume_uid spd_resume_uid_dylibloader_wrapper_speechd
+#define spd_key spd_key_dylibloader_wrapper_speechd
+#define spd_char spd_char_dylibloader_wrapper_speechd
+#define spd_wchar spd_wchar_dylibloader_wrapper_speechd
+#define spd_sound_icon spd_sound_icon_dylibloader_wrapper_speechd
+#define spd_set_voice_type spd_set_voice_type_dylibloader_wrapper_speechd
+#define spd_set_voice_type_all spd_set_voice_type_all_dylibloader_wrapper_speechd
+#define spd_set_voice_type_uid spd_set_voice_type_uid_dylibloader_wrapper_speechd
+#define spd_get_voice_type spd_get_voice_type_dylibloader_wrapper_speechd
+#define spd_set_synthesis_voice spd_set_synthesis_voice_dylibloader_wrapper_speechd
+#define spd_set_synthesis_voice_all spd_set_synthesis_voice_all_dylibloader_wrapper_speechd
+#define spd_set_synthesis_voice_uid spd_set_synthesis_voice_uid_dylibloader_wrapper_speechd
+#define spd_set_data_mode spd_set_data_mode_dylibloader_wrapper_speechd
+#define spd_set_notification_on spd_set_notification_on_dylibloader_wrapper_speechd
+#define spd_set_notification_off spd_set_notification_off_dylibloader_wrapper_speechd
+#define spd_set_notification spd_set_notification_dylibloader_wrapper_speechd
+#define spd_set_voice_rate spd_set_voice_rate_dylibloader_wrapper_speechd
+#define spd_set_voice_rate_all spd_set_voice_rate_all_dylibloader_wrapper_speechd
+#define spd_set_voice_rate_uid spd_set_voice_rate_uid_dylibloader_wrapper_speechd
+#define spd_get_voice_rate spd_get_voice_rate_dylibloader_wrapper_speechd
+#define spd_set_voice_pitch spd_set_voice_pitch_dylibloader_wrapper_speechd
+#define spd_set_voice_pitch_all spd_set_voice_pitch_all_dylibloader_wrapper_speechd
+#define spd_set_voice_pitch_uid spd_set_voice_pitch_uid_dylibloader_wrapper_speechd
+#define spd_get_voice_pitch spd_get_voice_pitch_dylibloader_wrapper_speechd
+#define spd_set_voice_pitch_range spd_set_voice_pitch_range_dylibloader_wrapper_speechd
+#define spd_set_voice_pitch_range_all spd_set_voice_pitch_range_all_dylibloader_wrapper_speechd
+#define spd_set_voice_pitch_range_uid spd_set_voice_pitch_range_uid_dylibloader_wrapper_speechd
+#define spd_set_volume spd_set_volume_dylibloader_wrapper_speechd
+#define spd_set_volume_all spd_set_volume_all_dylibloader_wrapper_speechd
+#define spd_set_volume_uid spd_set_volume_uid_dylibloader_wrapper_speechd
+#define spd_get_volume spd_get_volume_dylibloader_wrapper_speechd
+#define spd_set_punctuation spd_set_punctuation_dylibloader_wrapper_speechd
+#define spd_set_punctuation_all spd_set_punctuation_all_dylibloader_wrapper_speechd
+#define spd_set_punctuation_uid spd_set_punctuation_uid_dylibloader_wrapper_speechd
+#define spd_set_capital_letters spd_set_capital_letters_dylibloader_wrapper_speechd
+#define spd_set_capital_letters_all spd_set_capital_letters_all_dylibloader_wrapper_speechd
+#define spd_set_capital_letters_uid spd_set_capital_letters_uid_dylibloader_wrapper_speechd
+#define spd_set_spelling spd_set_spelling_dylibloader_wrapper_speechd
+#define spd_set_spelling_all spd_set_spelling_all_dylibloader_wrapper_speechd
+#define spd_set_spelling_uid spd_set_spelling_uid_dylibloader_wrapper_speechd
+#define spd_set_language spd_set_language_dylibloader_wrapper_speechd
+#define spd_set_language_all spd_set_language_all_dylibloader_wrapper_speechd
+#define spd_set_language_uid spd_set_language_uid_dylibloader_wrapper_speechd
+#define spd_get_language spd_get_language_dylibloader_wrapper_speechd
+#define spd_set_output_module spd_set_output_module_dylibloader_wrapper_speechd
+#define spd_set_output_module_all spd_set_output_module_all_dylibloader_wrapper_speechd
+#define spd_set_output_module_uid spd_set_output_module_uid_dylibloader_wrapper_speechd
+#define spd_get_message_list_fd spd_get_message_list_fd_dylibloader_wrapper_speechd
+#define spd_list_modules spd_list_modules_dylibloader_wrapper_speechd
+#define free_spd_modules free_spd_modules_dylibloader_wrapper_speechd
+#define spd_get_output_module spd_get_output_module_dylibloader_wrapper_speechd
+#define spd_list_voices spd_list_voices_dylibloader_wrapper_speechd
+#define spd_list_synthesis_voices spd_list_synthesis_voices_dylibloader_wrapper_speechd
+#define free_spd_voices free_spd_voices_dylibloader_wrapper_speechd
+#define spd_execute_command_with_list_reply spd_execute_command_with_list_reply_dylibloader_wrapper_speechd
+#define spd_execute_command spd_execute_command_dylibloader_wrapper_speechd
+#define spd_execute_command_with_reply spd_execute_command_with_reply_dylibloader_wrapper_speechd
+#define spd_execute_command_wo_mutex spd_execute_command_wo_mutex_dylibloader_wrapper_speechd
+#define spd_send_data spd_send_data_dylibloader_wrapper_speechd
+#define spd_send_data_wo_mutex spd_send_data_wo_mutex_dylibloader_wrapper_speechd
+extern void (*SPDConnectionAddress__free_dylibloader_wrapper_speechd)( SPDConnectionAddress*);
+extern SPDConnectionAddress* (*spd_get_default_address_dylibloader_wrapper_speechd)( char**);
+extern SPDConnection* (*spd_open_dylibloader_wrapper_speechd)(const char*,const char*,const char*, SPDConnectionMode);
+extern SPDConnection* (*spd_open2_dylibloader_wrapper_speechd)(const char*,const char*,const char*, SPDConnectionMode, SPDConnectionAddress*, int, char**);
+extern void (*spd_close_dylibloader_wrapper_speechd)( SPDConnection*);
+extern int (*spd_say_dylibloader_wrapper_speechd)( SPDConnection*, SPDPriority,const char*);
+extern int (*spd_sayf_dylibloader_wrapper_speechd)( SPDConnection*, SPDPriority,const char*,...);
+extern int (*spd_stop_dylibloader_wrapper_speechd)( SPDConnection*);
+extern int (*spd_stop_all_dylibloader_wrapper_speechd)( SPDConnection*);
+extern int (*spd_stop_uid_dylibloader_wrapper_speechd)( SPDConnection*, int);
+extern int (*spd_cancel_dylibloader_wrapper_speechd)( SPDConnection*);
+extern int (*spd_cancel_all_dylibloader_wrapper_speechd)( SPDConnection*);
+extern int (*spd_cancel_uid_dylibloader_wrapper_speechd)( SPDConnection*, int);
+extern int (*spd_pause_dylibloader_wrapper_speechd)( SPDConnection*);
+extern int (*spd_pause_all_dylibloader_wrapper_speechd)( SPDConnection*);
+extern int (*spd_pause_uid_dylibloader_wrapper_speechd)( SPDConnection*, int);
+extern int (*spd_resume_dylibloader_wrapper_speechd)( SPDConnection*);
+extern int (*spd_resume_all_dylibloader_wrapper_speechd)( SPDConnection*);
+extern int (*spd_resume_uid_dylibloader_wrapper_speechd)( SPDConnection*, int);
+extern int (*spd_key_dylibloader_wrapper_speechd)( SPDConnection*, SPDPriority,const char*);
+extern int (*spd_char_dylibloader_wrapper_speechd)( SPDConnection*, SPDPriority,const char*);
+extern int (*spd_wchar_dylibloader_wrapper_speechd)( SPDConnection*, SPDPriority, wchar_t);
+extern int (*spd_sound_icon_dylibloader_wrapper_speechd)( SPDConnection*, SPDPriority,const char*);
+extern int (*spd_set_voice_type_dylibloader_wrapper_speechd)( SPDConnection*, SPDVoiceType);
+extern int (*spd_set_voice_type_all_dylibloader_wrapper_speechd)( SPDConnection*, SPDVoiceType);
+extern int (*spd_set_voice_type_uid_dylibloader_wrapper_speechd)( SPDConnection*, SPDVoiceType, unsigned int);
+extern SPDVoiceType (*spd_get_voice_type_dylibloader_wrapper_speechd)( SPDConnection*);
+extern int (*spd_set_synthesis_voice_dylibloader_wrapper_speechd)( SPDConnection*,const char*);
+extern int (*spd_set_synthesis_voice_all_dylibloader_wrapper_speechd)( SPDConnection*,const char*);
+extern int (*spd_set_synthesis_voice_uid_dylibloader_wrapper_speechd)( SPDConnection*,const char*, unsigned int);
+extern int (*spd_set_data_mode_dylibloader_wrapper_speechd)( SPDConnection*, SPDDataMode);
+extern int (*spd_set_notification_on_dylibloader_wrapper_speechd)( SPDConnection*, SPDNotification);
+extern int (*spd_set_notification_off_dylibloader_wrapper_speechd)( SPDConnection*, SPDNotification);
+extern int (*spd_set_notification_dylibloader_wrapper_speechd)( SPDConnection*, SPDNotification,const char*);
+extern int (*spd_set_voice_rate_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+extern int (*spd_set_voice_rate_all_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+extern int (*spd_set_voice_rate_uid_dylibloader_wrapper_speechd)( SPDConnection*, signed int, unsigned int);
+extern int (*spd_get_voice_rate_dylibloader_wrapper_speechd)( SPDConnection*);
+extern int (*spd_set_voice_pitch_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+extern int (*spd_set_voice_pitch_all_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+extern int (*spd_set_voice_pitch_uid_dylibloader_wrapper_speechd)( SPDConnection*, signed int, unsigned int);
+extern int (*spd_get_voice_pitch_dylibloader_wrapper_speechd)( SPDConnection*);
+extern int (*spd_set_voice_pitch_range_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+extern int (*spd_set_voice_pitch_range_all_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+extern int (*spd_set_voice_pitch_range_uid_dylibloader_wrapper_speechd)( SPDConnection*, signed int, unsigned int);
+extern int (*spd_set_volume_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+extern int (*spd_set_volume_all_dylibloader_wrapper_speechd)( SPDConnection*, signed int);
+extern int (*spd_set_volume_uid_dylibloader_wrapper_speechd)( SPDConnection*, signed int, unsigned int);
+extern int (*spd_get_volume_dylibloader_wrapper_speechd)( SPDConnection*);
+extern int (*spd_set_punctuation_dylibloader_wrapper_speechd)( SPDConnection*, SPDPunctuation);
+extern int (*spd_set_punctuation_all_dylibloader_wrapper_speechd)( SPDConnection*, SPDPunctuation);
+extern int (*spd_set_punctuation_uid_dylibloader_wrapper_speechd)( SPDConnection*, SPDPunctuation, unsigned int);
+extern int (*spd_set_capital_letters_dylibloader_wrapper_speechd)( SPDConnection*, SPDCapitalLetters);
+extern int (*spd_set_capital_letters_all_dylibloader_wrapper_speechd)( SPDConnection*, SPDCapitalLetters);
+extern int (*spd_set_capital_letters_uid_dylibloader_wrapper_speechd)( SPDConnection*, SPDCapitalLetters, unsigned int);
+extern int (*spd_set_spelling_dylibloader_wrapper_speechd)( SPDConnection*, SPDSpelling);
+extern int (*spd_set_spelling_all_dylibloader_wrapper_speechd)( SPDConnection*, SPDSpelling);
+extern int (*spd_set_spelling_uid_dylibloader_wrapper_speechd)( SPDConnection*, SPDSpelling, unsigned int);
+extern int (*spd_set_language_dylibloader_wrapper_speechd)( SPDConnection*,const char*);
+extern int (*spd_set_language_all_dylibloader_wrapper_speechd)( SPDConnection*,const char*);
+extern int (*spd_set_language_uid_dylibloader_wrapper_speechd)( SPDConnection*,const char*, unsigned int);
+extern char* (*spd_get_language_dylibloader_wrapper_speechd)( SPDConnection*);
+extern int (*spd_set_output_module_dylibloader_wrapper_speechd)( SPDConnection*,const char*);
+extern int (*spd_set_output_module_all_dylibloader_wrapper_speechd)( SPDConnection*,const char*);
+extern int (*spd_set_output_module_uid_dylibloader_wrapper_speechd)( SPDConnection*,const char*, unsigned int);
+extern int (*spd_get_message_list_fd_dylibloader_wrapper_speechd)( SPDConnection*, int, int*, char**);
+extern char** (*spd_list_modules_dylibloader_wrapper_speechd)( SPDConnection*);
+extern void (*free_spd_modules_dylibloader_wrapper_speechd)( char**);
+extern char* (*spd_get_output_module_dylibloader_wrapper_speechd)( SPDConnection*);
+extern char** (*spd_list_voices_dylibloader_wrapper_speechd)( SPDConnection*);
+extern SPDVoice** (*spd_list_synthesis_voices_dylibloader_wrapper_speechd)( SPDConnection*);
+extern void (*free_spd_voices_dylibloader_wrapper_speechd)( SPDVoice**);
+extern char** (*spd_execute_command_with_list_reply_dylibloader_wrapper_speechd)( SPDConnection*, char*);
+extern int (*spd_execute_command_dylibloader_wrapper_speechd)( SPDConnection*, char*);
+extern int (*spd_execute_command_with_reply_dylibloader_wrapper_speechd)( SPDConnection*, char*, char**);
+extern int (*spd_execute_command_wo_mutex_dylibloader_wrapper_speechd)( SPDConnection*, char*);
+extern char* (*spd_send_data_dylibloader_wrapper_speechd)( SPDConnection*,const char*, int);
+extern char* (*spd_send_data_wo_mutex_dylibloader_wrapper_speechd)( SPDConnection*,const char*, int);
+int initialize_speechd(int verbose);
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/platform/linuxbsd/tts_linux.cpp b/platform/linuxbsd/tts_linux.cpp
new file mode 100644
index 0000000000..aea1183d3d
--- /dev/null
+++ b/platform/linuxbsd/tts_linux.cpp
@@ -0,0 +1,261 @@
+/*************************************************************************/
+/* tts_linux.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "tts_linux.h"
+
+#include "core/config/project_settings.h"
+#include "servers/text_server.h"
+
+TTS_Linux *TTS_Linux::singleton = nullptr;
+
+void TTS_Linux::speech_init_thread_func(void *p_userdata) {
+ TTS_Linux *tts = (TTS_Linux *)p_userdata;
+ if (tts) {
+ MutexLock thread_safe_method(tts->_thread_safe_);
+#ifdef DEBUG_ENABLED
+ int dylibloader_verbose = 1;
+#else
+ int dylibloader_verbose = 0;
+#endif
+ if (initialize_speechd(dylibloader_verbose) == 0) {
+ CharString class_str;
+ String config_name = GLOBAL_GET("application/config/name");
+ if (config_name.length() == 0) {
+ class_str = "Godot_Engine";
+ } else {
+ class_str = config_name.utf8();
+ }
+ tts->synth = spd_open(class_str, "Godot_Engine_Speech_API", "Godot_Engine", SPD_MODE_THREADED);
+ if (tts->synth) {
+ tts->synth->callback_end = &speech_event_callback;
+ tts->synth->callback_cancel = &speech_event_callback;
+ tts->synth->callback_im = &speech_event_index_mark;
+ spd_set_notification_on(tts->synth, SPD_END);
+ spd_set_notification_on(tts->synth, SPD_CANCEL);
+
+ print_verbose("Text-to-Speech: Speech Dispatcher initialized.");
+ } else {
+ print_verbose("Text-to-Speech: Cannot initialize Speech Dispatcher synthesizer!");
+ }
+ } else {
+ print_verbose("Text-to-Speech: Cannot load Speech Dispatcher library!");
+ }
+ }
+}
+
+void TTS_Linux::speech_event_index_mark(size_t p_msg_id, size_t p_client_id, SPDNotificationType p_type, char *p_index_mark) {
+ TTS_Linux *tts = TTS_Linux::get_singleton();
+ if (tts && tts->ids.has(p_msg_id)) {
+ MutexLock thread_safe_method(tts->_thread_safe_);
+ // Get word offset from the index mark injected to the text stream.
+ String mark = String::utf8(p_index_mark);
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, tts->ids[p_msg_id], mark.to_int());
+ }
+}
+
+void TTS_Linux::speech_event_callback(size_t p_msg_id, size_t p_client_id, SPDNotificationType p_type) {
+ TTS_Linux *tts = TTS_Linux::get_singleton();
+ if (tts) {
+ MutexLock thread_safe_method(tts->_thread_safe_);
+ List<DisplayServer::TTSUtterance> &queue = tts->queue;
+ if (!tts->paused && tts->ids.has(p_msg_id)) {
+ if (p_type == SPD_EVENT_END) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, tts->ids[p_msg_id]);
+ tts->ids.erase(p_msg_id);
+ tts->last_msg_id = -1;
+ tts->speaking = false;
+ } else if (p_type == SPD_EVENT_CANCEL) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, tts->ids[p_msg_id]);
+ tts->ids.erase(p_msg_id);
+ tts->last_msg_id = -1;
+ tts->speaking = false;
+ }
+ }
+ if (!tts->speaking && queue.size() > 0) {
+ DisplayServer::TTSUtterance &message = queue.front()->get();
+
+ // Inject index mark after each word.
+ String text;
+ String language;
+ SPDVoice **voices = spd_list_synthesis_voices(tts->synth);
+ if (voices != nullptr) {
+ SPDVoice **voices_ptr = voices;
+ while (*voices_ptr != nullptr) {
+ if (String::utf8((*voices_ptr)->name) == message.voice) {
+ language = String::utf8((*voices_ptr)->language);
+ break;
+ }
+ voices_ptr++;
+ }
+ free_spd_voices(voices);
+ }
+ PackedInt32Array breaks = TS->string_get_word_breaks(message.text, language);
+ int prev = 0;
+ for (int i = 0; i < breaks.size(); i++) {
+ text += message.text.substr(prev, breaks[i] - prev);
+ text += "<mark name=\"" + String::num_int64(breaks[i], 10) + "\"/>";
+ prev = breaks[i];
+ }
+ text += message.text.substr(prev, -1);
+
+ spd_set_synthesis_voice(tts->synth, message.voice.utf8().get_data());
+ spd_set_volume(tts->synth, message.volume * 2 - 100);
+ spd_set_voice_pitch(tts->synth, (message.pitch - 1) * 100);
+ float rate = 0;
+ if (message.rate > 1.f) {
+ rate = log10(MIN(message.rate, 2.5f)) / log10(2.5f) * 100;
+ } else if (message.rate < 1.f) {
+ rate = log10(MAX(message.rate, 0.5f)) / log10(0.5f) * -100;
+ }
+ spd_set_voice_rate(tts->synth, rate);
+ spd_set_data_mode(tts->synth, SPD_DATA_SSML);
+ tts->last_msg_id = spd_say(tts->synth, SPD_TEXT, text.utf8().get_data());
+ tts->ids[tts->last_msg_id] = message.id;
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, message.id);
+
+ queue.pop_front();
+ tts->speaking = true;
+ }
+ }
+}
+
+bool TTS_Linux::is_speaking() const {
+ return speaking;
+}
+
+bool TTS_Linux::is_paused() const {
+ return paused;
+}
+
+Array TTS_Linux::get_voices() const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!synth, Array());
+ Array list;
+ SPDVoice **voices = spd_list_synthesis_voices(synth);
+ if (voices != nullptr) {
+ SPDVoice **voices_ptr = voices;
+ while (*voices_ptr != nullptr) {
+ Dictionary voice_d;
+ voice_d["name"] = String::utf8((*voices_ptr)->name);
+ voice_d["id"] = String::utf8((*voices_ptr)->name);
+ voice_d["language"] = String::utf8((*voices_ptr)->language) + "_" + String::utf8((*voices_ptr)->variant);
+ list.push_back(voice_d);
+
+ voices_ptr++;
+ }
+ free_spd_voices(voices);
+ }
+ return list;
+}
+
+void TTS_Linux::speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!synth);
+ if (p_interrupt) {
+ stop();
+ }
+
+ if (p_text.is_empty()) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, p_utterance_id);
+ return;
+ }
+
+ DisplayServer::TTSUtterance message;
+ message.text = p_text;
+ message.voice = p_voice;
+ message.volume = CLAMP(p_volume, 0, 100);
+ message.pitch = CLAMP(p_pitch, 0.f, 2.f);
+ message.rate = CLAMP(p_rate, 0.1f, 10.f);
+ message.id = p_utterance_id;
+ queue.push_back(message);
+
+ if (is_paused()) {
+ resume();
+ } else {
+ speech_event_callback(0, 0, SPD_EVENT_BEGIN);
+ }
+}
+
+void TTS_Linux::pause() {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!synth);
+ if (spd_pause(synth) == 0) {
+ paused = true;
+ }
+}
+
+void TTS_Linux::resume() {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!synth);
+ spd_resume(synth);
+ paused = false;
+}
+
+void TTS_Linux::stop() {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!synth);
+ for (DisplayServer::TTSUtterance &message : queue) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, message.id);
+ }
+ if ((last_msg_id != -1) && ids.has(last_msg_id)) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, ids[last_msg_id]);
+ }
+ queue.clear();
+ ids.clear();
+ last_msg_id = -1;
+ spd_cancel(synth);
+ spd_resume(synth);
+ speaking = false;
+ paused = false;
+}
+
+TTS_Linux *TTS_Linux::get_singleton() {
+ return singleton;
+}
+
+TTS_Linux::TTS_Linux() {
+ singleton = this;
+ // Speech Dispatcher init can be slow, it might wait for helper process to start on background, so run it in the thread.
+ init_thread.start(speech_init_thread_func, this);
+}
+
+TTS_Linux::~TTS_Linux() {
+ init_thread.wait_to_finish();
+ if (synth) {
+ spd_close(synth);
+ }
+
+ singleton = nullptr;
+}
diff --git a/platform/linuxbsd/tts_linux.h b/platform/linuxbsd/tts_linux.h
new file mode 100644
index 0000000000..4d39af8970
--- /dev/null
+++ b/platform/linuxbsd/tts_linux.h
@@ -0,0 +1,78 @@
+/*************************************************************************/
+/* tts_linux.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 TTS_LINUX_H
+#define TTS_LINUX_H
+
+#include "core/os/thread.h"
+#include "core/os/thread_safe.h"
+#include "core/string/ustring.h"
+#include "core/templates/list.h"
+#include "core/templates/map.h"
+#include "core/variant/array.h"
+#include "servers/display_server.h"
+
+#include "speechd-so_wrap.h"
+
+class TTS_Linux {
+ _THREAD_SAFE_CLASS_
+
+ List<DisplayServer::TTSUtterance> queue;
+ SPDConnection *synth = nullptr;
+ bool speaking = false;
+ bool paused = false;
+ int last_msg_id = -1;
+ Map<int, int> ids;
+
+ Thread init_thread;
+
+ static void speech_init_thread_func(void *p_userdata);
+ static void speech_event_callback(size_t p_msg_id, size_t p_client_id, SPDNotificationType p_type);
+ static void speech_event_index_mark(size_t p_msg_id, size_t p_client_id, SPDNotificationType p_type, char *p_index_mark);
+
+ static TTS_Linux *singleton;
+
+public:
+ static TTS_Linux *get_singleton();
+
+ bool is_speaking() const;
+ bool is_paused() const;
+ Array get_voices() const;
+
+ void speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false);
+ void pause();
+ void resume();
+ void stop();
+
+ TTS_Linux();
+ ~TTS_Linux();
+};
+
+#endif // TTS_LINUX_H
diff --git a/platform/osx/SCsub b/platform/osx/SCsub
index d72a75af04..3a4c95613d 100644
--- a/platform/osx/SCsub
+++ b/platform/osx/SCsub
@@ -18,6 +18,7 @@ files = [
"key_mapping_osx.mm",
"godot_main_osx.mm",
"dir_access_osx.mm",
+ "tts_osx.mm",
"joypad_osx.cpp",
"vulkan_context_osx.mm",
"gl_manager_osx_legacy.mm",
diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm
index 06ed91907c..a798ba3b46 100644
--- a/platform/osx/crash_handler_osx.mm
+++ b/platform/osx/crash_handler_osx.mm
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/os/os.h"
+#include "core/string/print_string.h"
#include "core/version.h"
#include "main/main.h"
@@ -85,21 +86,22 @@ static void handle_crash(int sig) {
msg = proj_settings->get("debug/settings/crash_handler/message");
}
- // 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);
-
+ // Tell MainLoop about the crash. This can be handled by users too in Node.
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);
}
+ // Dump the backtrace to stderr with a message to the user
+ print_error("\n================================================================");
+ print_error(vformat("%s: Program crashed with signal %d", __FUNCTION__, sig));
+
// Print the engine version just before, so that people are reminded to include the version in backtrace reports.
if (String(VERSION_HASH).is_empty()) {
- fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME);
+ print_error(vformat("Engine version: %s", VERSION_FULL_NAME));
} else {
- fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH);
+ print_error(vformat("Engine version: %s (%s)", VERSION_FULL_NAME, VERSION_HASH));
}
- fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());
+ print_error(vformat("Dumping the backtrace. %s", msg));
char **strings = backtrace_symbols(bt_buffer, size);
if (strings) {
void *load_addr = (void *)load_address();
@@ -157,13 +159,13 @@ static void handle_crash(int sig) {
}
}
- fprintf(stderr, "[%zu] %s\n", i, output.utf8().get_data());
+ print_error(vformat("[%d] %s", (int64_t)i, output));
}
free(strings);
}
- fprintf(stderr, "-- END OF BACKTRACE --\n");
- fprintf(stderr, "================================================================\n");
+ print_error("-- END OF BACKTRACE --");
+ print_error("================================================================");
// Abort to pass the error to the OS
abort();
diff --git a/platform/osx/dir_access_osx.mm b/platform/osx/dir_access_osx.mm
index d26f35e847..6bafb9470d 100644
--- a/platform/osx/dir_access_osx.mm
+++ b/platform/osx/dir_access_osx.mm
@@ -34,8 +34,8 @@
#include <errno.h>
-#include <AppKit/NSWorkspace.h>
-#include <Foundation/Foundation.h>
+#import <AppKit/NSWorkspace.h>
+#import <Foundation/Foundation.h>
String DirAccessOSX::fix_unicode_name(const char *p_name) const {
String fname;
diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h
index fa3091ff81..538a9bc04c 100644
--- a/platform/osx/display_server_osx.h
+++ b/platform/osx/display_server_osx.h
@@ -45,10 +45,11 @@
#include "platform/osx/vulkan_context_osx.h"
#endif // VULKAN_ENABLED
-#include <AppKit/AppKit.h>
-#include <AppKit/NSCursor.h>
-#include <ApplicationServices/ApplicationServices.h>
-#include <CoreVideo/CoreVideo.h>
+#import <AppKit/AppKit.h>
+#import <AppKit/NSCursor.h>
+#import <ApplicationServices/ApplicationServices.h>
+#import <CoreVideo/CoreVideo.h>
+#import <Foundation/Foundation.h>
#undef BitMap
#undef CursorShape
@@ -137,6 +138,8 @@ private:
Vector<KeyEvent> key_event_buffer;
int key_event_pos = 0;
+ id tts = nullptr;
+
Point2i im_selection;
String im_text;
@@ -264,6 +267,15 @@ public:
virtual void global_menu_remove_item(const String &p_menu_root, int p_idx) override;
virtual void global_menu_clear(const String &p_menu_root) override;
+ virtual bool tts_is_speaking() const override;
+ virtual bool tts_is_paused() const override;
+ virtual Array tts_get_voices() const override;
+
+ virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
+ virtual void tts_pause() override;
+ virtual void tts_resume() override;
+ virtual void tts_stop() override;
+
virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) override;
virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override;
@@ -361,7 +373,7 @@ public:
void cursor_update_shape();
virtual void cursor_set_shape(CursorShape p_shape) override;
virtual CursorShape cursor_get_shape() const override;
- virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;
+ virtual void cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;
virtual bool get_swap_cancel_ok() override;
diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm
index 6cdadcae39..a16bd2e8de 100644
--- a/platform/osx/display_server_osx.mm
+++ b/platform/osx/display_server_osx.mm
@@ -37,18 +37,20 @@
#include "key_mapping_osx.h"
#include "os_osx.h"
+#include "tts_osx.h"
+
#include "core/io/marshalls.h"
#include "core/math/geometry_2d.h"
#include "core/os/keyboard.h"
#include "main/main.h"
#include "scene/resources/texture.h"
-#include <Carbon/Carbon.h>
-#include <Cocoa/Cocoa.h>
-#include <IOKit/IOCFPlugIn.h>
-#include <IOKit/IOKitLib.h>
-#include <IOKit/hid/IOHIDKeys.h>
-#include <IOKit/hid/IOHIDLib.h>
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+#import <IOKit/IOCFPlugIn.h>
+#import <IOKit/IOKitLib.h>
+#import <IOKit/hid/IOHIDKeys.h>
+#import <IOKit/hid/IOHIDLib.h>
#if defined(GLES3_ENABLED)
#include "drivers/gles3/rasterizer_gles3.h"
@@ -144,7 +146,7 @@ DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, V
[wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed];
}
- CALayer *layer = [wd.window_view layer];
+ CALayer *layer = [(NSView *)wd.window_view layer];
if (layer) {
layer.contentsScale = scale;
}
@@ -172,7 +174,7 @@ DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, V
wd.size.width = contentRect.size.width * scale;
wd.size.height = contentRect.size.height * scale;
- CALayer *layer = [wd.window_view layer];
+ CALayer *layer = [(NSView *)wd.window_view layer];
if (layer) {
layer.contentsScale = scale;
}
@@ -207,16 +209,16 @@ void DisplayServerOSX::_update_window_style(WindowData p_wd) {
if (borderless_full) {
// If the window covers up the screen set the level to above the main menu and hide on deactivate.
- [p_wd.window_object setLevel:NSMainMenuWindowLevel + 1];
- [p_wd.window_object setHidesOnDeactivate:YES];
+ [(NSWindow *)p_wd.window_object setLevel:NSMainMenuWindowLevel + 1];
+ [(NSWindow *)p_wd.window_object setHidesOnDeactivate:YES];
} else {
// Reset these when our window is not a borderless window that covers up the screen.
if (p_wd.on_top && !p_wd.fullscreen) {
- [p_wd.window_object setLevel:NSFloatingWindowLevel];
+ [(NSWindow *)p_wd.window_object setLevel:NSFloatingWindowLevel];
} else {
- [p_wd.window_object setLevel:NSNormalWindowLevel];
+ [(NSWindow *)p_wd.window_object setLevel:NSNormalWindowLevel];
}
- [p_wd.window_object setHidesOnDeactivate:NO];
+ [(NSWindow *)p_wd.window_object setHidesOnDeactivate:NO];
}
}
@@ -232,7 +234,7 @@ void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled
[wd.window_object setBackgroundColor:[NSColor clearColor]];
[wd.window_object setOpaque:NO];
[wd.window_object setHasShadow:NO];
- CALayer *layer = [wd.window_view layer];
+ CALayer *layer = [(NSView *)wd.window_view layer];
if (layer) {
[layer setBackgroundColor:[NSColor clearColor].CGColor];
[layer setOpaque:NO];
@@ -247,7 +249,7 @@ void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled
[wd.window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]];
[wd.window_object setOpaque:YES];
[wd.window_object setHasShadow:YES];
- CALayer *layer = [wd.window_view layer];
+ CALayer *layer = [(NSView *)wd.window_view layer];
if (layer) {
[layer setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1].CGColor];
[layer setOpaque:YES];
@@ -326,7 +328,7 @@ void DisplayServerOSX::_dispatch_input_event(const Ref<InputEvent> &p_event) {
Callable::CallError ce;
{
- List<WindowID>::Element *E = popup_list.front();
+ List<WindowID>::Element *E = popup_list.back();
if (E && Object::cast_to<InputEventKey>(*p_event)) {
// Redirect keyboard input to active popup.
if (windows.has(E->get())) {
@@ -702,6 +704,7 @@ bool DisplayServerOSX::has_feature(Feature p_feature) const {
case FEATURE_NATIVE_ICON:
//case FEATURE_KEEP_SCREEN_ON:
case FEATURE_SWAP_BUFFERS:
+ case FEATURE_TEXT_TO_SPEECH:
return true;
default: {
}
@@ -1458,6 +1461,41 @@ void DisplayServerOSX::global_menu_clear(const String &p_menu_root) {
}
}
+bool DisplayServerOSX::tts_is_speaking() const {
+ ERR_FAIL_COND_V(!tts, false);
+ return [tts isSpeaking];
+}
+
+bool DisplayServerOSX::tts_is_paused() const {
+ ERR_FAIL_COND_V(!tts, false);
+ return [tts isPaused];
+}
+
+Array DisplayServerOSX::tts_get_voices() const {
+ ERR_FAIL_COND_V(!tts, Array());
+ return [tts getVoices];
+}
+
+void DisplayServerOSX::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
+ ERR_FAIL_COND(!tts);
+ [tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt];
+}
+
+void DisplayServerOSX::tts_pause() {
+ ERR_FAIL_COND(!tts);
+ [tts pauseSpeaking];
+}
+
+void DisplayServerOSX::tts_resume() {
+ ERR_FAIL_COND(!tts);
+ [tts resumeSpeaking];
+}
+
+void DisplayServerOSX::tts_stop() {
+ ERR_FAIL_COND(!tts);
+ [tts stopSpeaking];
+}
+
Error DisplayServerOSX::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) {
_THREAD_SAFE_METHOD_
@@ -2218,7 +2256,7 @@ void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) {
} break;
case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
case WINDOW_MODE_FULLSCREEN: {
- [wd.window_object setLevel:NSNormalWindowLevel];
+ [(NSWindow *)wd.window_object setLevel:NSNormalWindowLevel];
_set_window_per_pixel_transparency_enabled(true, p_window);
if (wd.resize_disabled) { // Restore resize disabled.
[wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable];
@@ -2342,9 +2380,9 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
return;
}
if (p_enabled) {
- [wd.window_object setLevel:NSFloatingWindowLevel];
+ [(NSWindow *)wd.window_object setLevel:NSFloatingWindowLevel];
} else {
- [wd.window_object setLevel:NSNormalWindowLevel];
+ [(NSWindow *)wd.window_object setLevel:NSNormalWindowLevel];
}
} break;
case WINDOW_FLAG_TRANSPARENT: {
@@ -2385,7 +2423,7 @@ bool DisplayServerOSX::window_get_flag(WindowFlags p_flag, WindowID p_window) co
if (wd.fullscreen) {
return wd.on_top;
} else {
- return [wd.window_object level] == NSFloatingWindowLevel;
+ return [(NSWindow *)wd.window_object level] == NSFloatingWindowLevel;
}
} break;
case WINDOW_FLAG_TRANSPARENT: {
@@ -2636,7 +2674,7 @@ DisplayServerOSX::CursorShape DisplayServerOSX::cursor_get_shape() const {
return cursor_shape;
}
-void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+void DisplayServerOSX::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
_THREAD_SAFE_METHOD_
if (p_cursor.is_valid()) {
@@ -2892,14 +2930,13 @@ void DisplayServerOSX::swap_buffers() {
void DisplayServerOSX::set_native_icon(const String &p_filename) {
_THREAD_SAFE_METHOD_
- FileAccess *f = FileAccess::open(p_filename, FileAccess::READ);
- ERR_FAIL_COND(!f);
+ Ref<FileAccess> f = FileAccess::open(p_filename, FileAccess::READ);
+ ERR_FAIL_COND(f.is_null());
Vector<uint8_t> data;
uint64_t len = f->get_length();
data.resize(len);
f->get_buffer((uint8_t *)&data.write[0], len);
- memdelete(f);
NSData *icon_data = [[NSData alloc] initWithBytes:&data.write[0] length:len];
ERR_FAIL_COND_MSG(!icon_data, "Error reading icon data.");
@@ -3000,21 +3037,25 @@ Rect2i DisplayServerOSX::window_get_popup_safe_rect(WindowID p_window) const {
}
void DisplayServerOSX::popup_open(WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
WindowData &wd = windows[p_window];
if (wd.is_popup) {
bool was_empty = popup_list.is_empty();
- // Close all popups, up to current popup parent, or every popup if new window is not transient.
+ // Find current popup parent, or root popup if new window is not transient.
+ List<WindowID>::Element *C = nullptr;
List<WindowID>::Element *E = popup_list.back();
while (E) {
if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) {
- send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
- List<WindowID>::Element *F = E->prev();
- popup_list.erase(E);
- E = F;
+ C = E;
+ E = E->prev();
} else {
break;
}
}
+ if (C) {
+ send_window_event(windows[C->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+ }
if (was_empty && popup_list.is_empty()) {
// Inform OS that popup was opened, to close other native popups.
@@ -3026,12 +3067,16 @@ void DisplayServerOSX::popup_open(WindowID p_window) {
}
void DisplayServerOSX::popup_close(WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
bool was_empty = popup_list.is_empty();
List<WindowID>::Element *E = popup_list.find(p_window);
while (E) {
- send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
List<WindowID>::Element *F = E->next();
+ WindowID win_id = E->get();
popup_list.erase(E);
+
+ send_window_event(windows[win_id], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
E = F;
}
if (!was_empty && popup_list.is_empty()) {
@@ -3047,11 +3092,8 @@ void DisplayServerOSX::mouse_process_popups(bool p_close) {
if (p_close) {
// Close all popups.
List<WindowID>::Element *E = popup_list.front();
- while (E) {
+ if (E) {
send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
- List<WindowID>::Element *F = E->next();
- popup_list.erase(E);
- E = F;
}
if (!was_empty) {
// Inform OS that all popups are closed.
@@ -3064,7 +3106,9 @@ void DisplayServerOSX::mouse_process_popups(bool p_close) {
}
Point2i pos = mouse_get_position();
+ List<WindowID>::Element *C = nullptr;
List<WindowID>::Element *E = popup_list.back();
+ // Find top popup to close.
while (E) {
// Popup window area.
Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get()));
@@ -3075,12 +3119,13 @@ void DisplayServerOSX::mouse_process_popups(bool p_close) {
} else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) {
break;
} else {
- send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
- List<WindowID>::Element *F = E->prev();
- popup_list.erase(E);
- E = F;
+ C = E;
+ E = E->prev();
}
}
+ if (C) {
+ send_window_event(windows[C->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+ }
if (!was_empty && popup_list.is_empty()) {
// Inform OS that all popups are closed.
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"];
@@ -3114,6 +3159,9 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode
// Register to be notified on displays arrangement changes.
CGDisplayRegisterReconfigurationCallback(_displays_arrangement_changed, nullptr);
+ // Init TTS
+ tts = [[TTS_OSX alloc] init];
+
NSMenuItem *menu_item;
NSString *title;
diff --git a/platform/osx/export/codesign.cpp b/platform/osx/export/codesign.cpp
index b609a21c44..fd044c00cc 100644
--- a/platform/osx/export/codesign.cpp
+++ b/platform/osx/export/codesign.cpp
@@ -49,8 +49,8 @@
/*************************************************************************/
String CodeSignCodeResources::hash_sha1_base64(const String &p_path) {
- FileAccessRef fa = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(!fa, String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path));
+ Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(fa.is_null(), String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path));
CryptoCore::SHA1Context ctx;
ctx.start();
@@ -68,14 +68,13 @@ String CodeSignCodeResources::hash_sha1_base64(const String &p_path) {
unsigned char hash[0x14];
ctx.finish(hash);
- fa->close();
return CryptoCore::b64_encode_str(hash, 0x14);
}
String CodeSignCodeResources::hash_sha256_base64(const String &p_path) {
- FileAccessRef fa = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(!fa, String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path));
+ Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(fa.is_null(), String(), vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path));
CryptoCore::SHA256Context ctx;
ctx.start();
@@ -93,7 +92,6 @@ String CodeSignCodeResources::hash_sha256_base64(const String &p_path) {
unsigned char hash[0x20];
ctx.finish(hash);
- fa->close();
return CryptoCore::b64_encode_str(hash, 0x20);
}
@@ -211,16 +209,14 @@ bool CodeSignCodeResources::add_nested_file(const String &p_root, const String &
} \
}
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- ERR_FAIL_COND_V(!da, false);
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ ERR_FAIL_COND_V(da.is_null(), false);
Vector<String> files_to_add;
if (LipO::is_lipo(p_exepath)) {
String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file("_lipo");
Error err = da->make_dir_recursive(tmp_path_name);
- if (err != OK) {
- ERR_FAIL_V_MSG(false, vformat("CodeSign/CodeResources: Failed to create \"%s\" subfolder.", tmp_path_name));
- }
+ ERR_FAIL_COND_V_MSG(err != OK, false, vformat("CodeSign/CodeResources: Failed to create \"%s\" subfolder.", tmp_path_name));
LipO lip;
if (lip.open_file(p_exepath)) {
for (int i = 0; i < lip.get_arch_count(); i++) {
@@ -287,8 +283,8 @@ bool CodeSignCodeResources::add_nested_file(const String &p_root, const String &
}
bool CodeSignCodeResources::add_folder_recursive(const String &p_root, const String &p_path, const String &p_main_exe_path) {
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- ERR_FAIL_COND_V(!da, false);
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ ERR_FAIL_COND_V(da.is_null(), false);
Error err = da->change_dir(p_root.plus_file(p_path));
ERR_FAIL_COND_V(err != OK, false);
@@ -431,12 +427,11 @@ bool CodeSignCodeResources::save_to_file(const String &p_path) {
String text = pl.save_text();
ERR_FAIL_COND_V_MSG(text.is_empty(), false, "CodeSign/CodeResources: Generating resources PList failed.");
- FileAccessRef fa = FileAccess::open(p_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(!fa, false, vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path));
+ Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("CodeSign/CodeResources: Can't open file: \"%s\".", p_path));
CharString cs = text.utf8();
fa->store_buffer((const uint8_t *)cs.ptr(), cs.length());
- fa->close();
return true;
}
@@ -809,8 +804,8 @@ int CodeSignRequirements::get_size() const {
return blob.size();
}
-void CodeSignRequirements::write_to_file(FileAccess *p_file) const {
- ERR_FAIL_COND_MSG(!p_file, "CodeSign/Requirements: Invalid file handle.");
+void CodeSignRequirements::write_to_file(Ref<FileAccess> p_file) const {
+ ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/Requirements: Invalid file handle.");
p_file->store_buffer(blob.ptr(), blob.size());
}
@@ -863,8 +858,8 @@ int CodeSignEntitlementsText::get_size() const {
return blob.size();
}
-void CodeSignEntitlementsText::write_to_file(FileAccess *p_file) const {
- ERR_FAIL_COND_MSG(!p_file, "CodeSign/EntitlementsText: Invalid file handle.");
+void CodeSignEntitlementsText::write_to_file(Ref<FileAccess> p_file) const {
+ ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/EntitlementsText: Invalid file handle.");
p_file->store_buffer(blob.ptr(), blob.size());
}
@@ -918,8 +913,8 @@ int CodeSignEntitlementsBinary::get_size() const {
return blob.size();
}
-void CodeSignEntitlementsBinary::write_to_file(FileAccess *p_file) const {
- ERR_FAIL_COND_MSG(!p_file, "CodeSign/EntitlementsBinary: Invalid file handle.");
+void CodeSignEntitlementsBinary::write_to_file(Ref<FileAccess> p_file) const {
+ ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/EntitlementsBinary: Invalid file handle.");
p_file->store_buffer(blob.ptr(), blob.size());
}
@@ -947,7 +942,7 @@ CodeSignCodeDirectory::CodeSignCodeDirectory(uint8_t p_hash_size, uint8_t p_hash
}
blob.resize(cd_size);
memset(blob.ptrw() + 8, 0x00, cd_size - 8);
- CodeDirectoryHeader *cd = (CodeDirectoryHeader *)(blob.ptrw() + 8);
+ CodeDirectoryHeader *cd = reinterpret_cast<CodeDirectoryHeader *>(blob.ptrw() + 8);
bool is_64_cl = (p_code_limit >= std::numeric_limits<uint32_t>::max());
@@ -1040,8 +1035,8 @@ int CodeSignCodeDirectory::get_size() const {
return blob.size();
}
-void CodeSignCodeDirectory::write_to_file(FileAccess *p_file) const {
- ERR_FAIL_COND_MSG(!p_file, "CodeSign/CodeDirectory: Invalid file handle.");
+void CodeSignCodeDirectory::write_to_file(Ref<FileAccess> p_file) const {
+ ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/CodeDirectory: Invalid file handle.");
p_file->store_buffer(blob.ptr(), blob.size());
}
@@ -1086,8 +1081,8 @@ int CodeSignSignature::get_size() const {
return blob.size();
}
-void CodeSignSignature::write_to_file(FileAccess *p_file) const {
- ERR_FAIL_COND_MSG(!p_file, "CodeSign/Signature: Invalid file handle.");
+void CodeSignSignature::write_to_file(Ref<FileAccess> p_file) const {
+ ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/Signature: Invalid file handle.");
p_file->store_buffer(blob.ptr(), blob.size());
}
@@ -1115,8 +1110,8 @@ int CodeSignSuperBlob::get_size() const {
return size;
}
-void CodeSignSuperBlob::write_to_file(FileAccess *p_file) const {
- ERR_FAIL_COND_MSG(!p_file, "CodeSign/SuperBlob: Invalid file handle.");
+void CodeSignSuperBlob::write_to_file(Ref<FileAccess> p_file) const {
+ ERR_FAIL_COND_MSG(p_file.is_null(), "CodeSign/SuperBlob: Invalid file handle.");
uint32_t size = get_size();
uint32_t data_offset = 12 + blobs.size() * 8;
@@ -1147,8 +1142,8 @@ void CodeSignSuperBlob::write_to_file(FileAccess *p_file) const {
PackedByteArray CodeSign::file_hash_sha1(const String &p_path) {
PackedByteArray file_hash;
- FileAccessRef f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(!f, PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path));
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(f.is_null(), PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path));
CryptoCore::SHA1Context ctx;
ctx.start();
@@ -1171,8 +1166,8 @@ PackedByteArray CodeSign::file_hash_sha1(const String &p_path) {
PackedByteArray CodeSign::file_hash_sha256(const String &p_path) {
PackedByteArray file_hash;
- FileAccessRef f = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(!f, PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path));
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(f.is_null(), PackedByteArray(), vformat("CodeSign: Can't open file: \"%s\".", p_path));
CryptoCore::SHA256Context ctx;
ctx.start();
@@ -1208,8 +1203,8 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const
String id;
String main_exe = p_exe_path;
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- if (!da) {
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ if (da.is_null()) {
r_error_msg = TTR("Can't get filesystem access.");
ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access.");
}
@@ -1522,8 +1517,8 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const
}
Error CodeSign::codesign(bool p_use_hardened_runtime, bool p_force, const String &p_path, const String &p_ent_path, String &r_error_msg) {
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- if (!da) {
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ if (da.is_null()) {
r_error_msg = TTR("Can't get filesystem access.");
ERR_FAIL_V_MSG(ERR_CANT_CREATE, "CodeSign: Can't get filesystem access.");
}
diff --git a/platform/osx/export/codesign.h b/platform/osx/export/codesign.h
index e5e9be5c28..3a08c0ea86 100644
--- a/platform/osx/export/codesign.h
+++ b/platform/osx/export/codesign.h
@@ -132,7 +132,7 @@ public:
virtual int get_size() const = 0;
virtual uint32_t get_index_type() const = 0;
- virtual void write_to_file(FileAccess *p_file) const = 0;
+ virtual void write_to_file(Ref<FileAccess> p_file) const = 0;
};
/*************************************************************************/
@@ -168,7 +168,7 @@ public:
virtual int get_size() const override;
virtual uint32_t get_index_type() const override { return 0x00000002; };
- virtual void write_to_file(FileAccess *p_file) const override;
+ virtual void write_to_file(Ref<FileAccess> p_file) const override;
};
/*************************************************************************/
@@ -190,7 +190,7 @@ public:
virtual int get_size() const override;
virtual uint32_t get_index_type() const override { return 0x00000005; };
- virtual void write_to_file(FileAccess *p_file) const override;
+ virtual void write_to_file(Ref<FileAccess> p_file) const override;
};
/*************************************************************************/
@@ -212,7 +212,7 @@ public:
virtual int get_size() const override;
virtual uint32_t get_index_type() const override { return 0x00000007; };
- virtual void write_to_file(FileAccess *p_file) const override;
+ virtual void write_to_file(Ref<FileAccess> p_file) const override;
};
/*************************************************************************/
@@ -314,7 +314,7 @@ public:
virtual int get_size() const override;
virtual uint32_t get_index_type() const override { return 0x00000000; };
- virtual void write_to_file(FileAccess *p_file) const override;
+ virtual void write_to_file(Ref<FileAccess> p_file) const override;
};
/*************************************************************************/
@@ -333,7 +333,7 @@ public:
virtual int get_size() const override;
virtual uint32_t get_index_type() const override { return 0x00010000; };
- virtual void write_to_file(FileAccess *p_file) const override;
+ virtual void write_to_file(Ref<FileAccess> p_file) const override;
};
/*************************************************************************/
@@ -347,7 +347,7 @@ public:
bool add_blob(const Ref<CodeSignBlob> &p_blob);
int get_size() const;
- void write_to_file(FileAccess *p_file) const;
+ void write_to_file(Ref<FileAccess> p_file) const;
};
/*************************************************************************/
diff --git a/platform/osx/export/export_plugin.cpp b/platform/osx/export/export_plugin.cpp
index 4f06342fac..94ef875072 100644
--- a/platform/osx/export/export_plugin.cpp
+++ b/platform/osx/export/export_plugin.cpp
@@ -256,22 +256,23 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_
String path = EditorPaths::get_singleton()->get_cache_dir().plus_file("icon.png");
ResourceSaver::save(path, it);
- FileAccess *f = FileAccess::open(path, FileAccess::READ);
- if (!f) {
- // Clean up generated file.
- DirAccess::remove_file_or_error(path);
- ERR_FAIL();
- }
+ {
+ Ref<FileAccess> f = FileAccess::open(path, FileAccess::READ);
+ if (f.is_null()) {
+ // Clean up generated file.
+ DirAccess::remove_file_or_error(path);
+ ERR_FAIL();
+ }
- int ofs = data.size();
- uint64_t len = f->get_length();
- data.resize(data.size() + len + 8);
- f->get_buffer(&data.write[ofs + 8], len);
- memdelete(f);
- len += 8;
- len = BSWAP32(len);
- memcpy(&data.write[ofs], icon_infos[i].name, 4);
- encode_uint32(len, &data.write[ofs + 4]);
+ int ofs = data.size();
+ uint64_t len = f->get_length();
+ data.resize(data.size() + len + 8);
+ f->get_buffer(&data.write[ofs + 8], len);
+ len += 8;
+ len = BSWAP32(len);
+ memcpy(&data.write[ofs], icon_infos[i].name, 4);
+ encode_uint32(len, &data.write[ofs + 4]);
+ }
// Clean up generated file.
DirAccess::remove_file_or_error(path);
@@ -565,7 +566,7 @@ Error EditorExportPlatformOSX::_code_sign_directory(const Ref<EditorExportPreset
}
Error dir_access_error;
- DirAccessRef dir_access{ DirAccess::open(p_path, &dir_access_error) };
+ Ref<DirAccess> dir_access{ DirAccess::open(p_path, &dir_access_error) };
if (dir_access_error != OK) {
return dir_access_error;
@@ -603,7 +604,7 @@ Error EditorExportPlatformOSX::_code_sign_directory(const Ref<EditorExportPreset
return OK;
}
-Error EditorExportPlatformOSX::_copy_and_sign_files(DirAccessRef &dir_access, const String &p_src_path,
+Error EditorExportPlatformOSX::_copy_and_sign_files(Ref<DirAccess> &dir_access, const String &p_src_path,
const String &p_in_app_path, bool p_sign_enabled,
const Ref<EditorExportPreset> &p_preset, const String &p_ent_path,
bool p_should_error_on_non_code_sign) {
@@ -633,7 +634,7 @@ Error EditorExportPlatformOSX::_copy_and_sign_files(DirAccessRef &dir_access, co
}
Error EditorExportPlatformOSX::_export_osx_plugins_for(Ref<EditorExportPlugin> p_editor_export_plugin,
- const String &p_app_path_name, DirAccessRef &dir_access,
+ const String &p_app_path_name, Ref<DirAccess> &dir_access,
bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset,
const String &p_ent_path) {
Error error{ OK };
@@ -683,8 +684,8 @@ Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const Strin
}
Error EditorExportPlatformOSX::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) {
- FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE);
- ERR_FAIL_COND_V(!f, ERR_CANT_CREATE);
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE);
f->store_line("#!/bin/sh");
f->store_line("echo -ne '\\033c\\033]0;" + p_app_name + "\\a'");
@@ -721,8 +722,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
return ERR_FILE_BAD_PATH;
}
- FileAccess *src_f = nullptr;
- zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
+ zlib_filefunc_def io = zipio_create_io();
if (ep.step(TTR("Creating app bundle"), 0)) {
return ERR_SKIP;
@@ -778,8 +778,8 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
Error err = OK;
- DirAccessRef tmp_app_dir = DirAccess::create_for_path(tmp_base_path_name);
- if (!tmp_app_dir) {
+ Ref<DirAccess> tmp_app_dir = DirAccess::create_for_path(tmp_base_path_name);
+ if (tmp_app_dir.is_null()) {
err = ERR_CANT_CREATE;
}
@@ -832,7 +832,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
{
String fname = tmp_app_path_name + "/Contents/Resources/en.lproj";
tmp_app_dir->make_dir_recursive(fname);
- FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
+ Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
f->store_line("/* Localized versions of Info.plist keys */");
f->store_line("");
f->store_line("CFBundleDisplayName = \"" + ProjectSettings::get_singleton()->get("application/config/name").operator String() + "\";");
@@ -878,7 +878,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
String lang = tr->get_locale();
String fname = tmp_app_path_name + "/Contents/Resources/" + lang + ".lproj";
tmp_app_dir->make_dir_recursive(fname);
- FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
+ Ref<FileAccess> f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
f->store_line("/* Localized versions of Info.plist keys */");
f->store_line("");
if (appnames.has(lang)) {
@@ -935,6 +935,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
unz_file_info info;
char fname[16384];
ret = unzGetCurrentFileInfo(src_pkg_zip, &info, fname, 16384, nullptr, 0, nullptr, 0);
+ if (ret != UNZ_OK) {
+ break;
+ }
String file = String::utf8(fname);
@@ -993,12 +996,10 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
if (!iconpath.is_empty()) {
if (iconpath.get_extension() == "icns") {
- FileAccess *icon = FileAccess::open(iconpath, FileAccess::READ);
- if (icon) {
+ Ref<FileAccess> icon = FileAccess::open(iconpath, FileAccess::READ);
+ if (icon.is_valid()) {
data.resize(icon->get_length());
icon->get_buffer(&data.write[0], icon->get_length());
- icon->close();
- memdelete(icon);
}
} else {
Ref<Image> icon;
@@ -1039,15 +1040,13 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
err = tmp_app_dir->make_dir_recursive(file.get_base_dir());
}
if (err == OK) {
- FileAccess *f = FileAccess::open(file, FileAccess::WRITE);
- if (f) {
+ Ref<FileAccess> f = FileAccess::open(file, FileAccess::WRITE);
+ if (f.is_valid()) {
f->store_buffer(data.ptr(), data.size());
- f->close();
if (is_execute) {
// chmod with 0755 if the file is executable.
FileAccess::set_unix_permissions(file, 0755);
}
- memdelete(f);
} else {
err = ERR_CANT_CREATE;
}
@@ -1091,8 +1090,8 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
if (sign_enabled && (ent_path.is_empty())) {
ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + ".entitlements");
- FileAccess *ent_f = FileAccess::open(ent_path, FileAccess::WRITE);
- if (ent_f) {
+ Ref<FileAccess> ent_f = FileAccess::open(ent_path, FileAccess::WRITE);
+ if (ent_f.is_valid()) {
ent_f->store_line("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
ent_f->store_line("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
ent_f->store_line("<plist version=\"1.0\">");
@@ -1213,16 +1212,13 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
ent_f->store_line("</dict>");
ent_f->store_line("</plist>");
-
- ent_f->close();
- memdelete(ent_f);
} else {
err = ERR_CANT_CREATE;
}
if ((err == OK) && helpers.size() > 0) {
ent_f = FileAccess::open(hlp_ent_path, FileAccess::WRITE);
- if (ent_f) {
+ if (ent_f.is_valid()) {
ent_f->store_line("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
ent_f->store_line("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
ent_f->store_line("<plist version=\"1.0\">");
@@ -1233,9 +1229,6 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
ent_f->store_line("<true/>");
ent_f->store_line("</dict>");
ent_f->store_line("</plist>");
-
- ent_f->close();
- memdelete(ent_f);
} else {
err = ERR_CANT_CREATE;
}
@@ -1243,7 +1236,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
}
if ((err == OK) && helpers.size() > 0) {
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
for (int i = 0; i < helpers.size(); i++) {
String hlp_path = helpers[i];
err = da->copy(hlp_path, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file());
@@ -1270,7 +1263,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
}
if (err == OK) {
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
for (int i = 0; i < shared_objects.size(); i++) {
String src_path = ProjectSettings::get_singleton()->globalize_path(shared_objects[i].path);
if (shared_objects[i].target.is_empty()) {
@@ -1334,8 +1327,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
OS::get_singleton()->move_to_trash(p_path);
}
- FileAccess *dst_f = nullptr;
- zlib_filefunc_def io_dst = zipio_create_io_from_file(&dst_f);
+ zlib_filefunc_def io_dst = zipio_create_io();
zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst);
_zip_folder_recursive(zip, tmp_base_path_name, "", pkg_name);
@@ -1380,7 +1372,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) {
String dir = p_folder.is_empty() ? p_root_path : p_root_path.plus_file(p_folder);
- DirAccessRef da = DirAccess::open(dir);
+ Ref<DirAccess> da = DirAccess::open(dir);
da->list_dir_begin();
String f = da->get_next();
while (!f.is_empty()) {
@@ -1471,8 +1463,8 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String
0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions
0);
- FileAccessRef fa = FileAccess::open(dir.plus_file(f), FileAccess::READ);
- if (!fa) {
+ Ref<FileAccess> fa = FileAccess::open(dir.plus_file(f), FileAccess::READ);
+ if (fa.is_null()) {
ERR_FAIL_MSG(vformat("Can't open file to read from path \"%s\".", dir.plus_file(f)));
}
const int bufsize = 16384;
diff --git a/platform/osx/export/export_plugin.h b/platform/osx/export/export_plugin.h
index b3edfb7f90..013e5eaa71 100644
--- a/platform/osx/export/export_plugin.h
+++ b/platform/osx/export/export_plugin.h
@@ -58,11 +58,11 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
Error _notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path);
Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_warn = true);
Error _code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path, const String &p_ent_path, bool p_should_error_on_non_code = true);
- Error _copy_and_sign_files(DirAccessRef &dir_access, const String &p_src_path, const String &p_in_app_path,
+ Error _copy_and_sign_files(Ref<DirAccess> &dir_access, const String &p_src_path, const String &p_in_app_path,
bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset, const String &p_ent_path,
bool p_should_error_on_non_code_sign);
Error _export_osx_plugins_for(Ref<EditorExportPlugin> p_editor_export_plugin, const String &p_app_path_name,
- DirAccessRef &dir_access, bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset,
+ Ref<DirAccess> &dir_access, bool p_sign_enabled, const Ref<EditorExportPreset> &p_preset,
const String &p_ent_path);
Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name);
void _zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name);
diff --git a/platform/osx/export/lipo.cpp b/platform/osx/export/lipo.cpp
index 66d2ecdbcf..82baf18c52 100644
--- a/platform/osx/export/lipo.cpp
+++ b/platform/osx/export/lipo.cpp
@@ -35,8 +35,8 @@
#ifdef MODULE_REGEX_ENABLED
bool LipO::is_lipo(const String &p_path) {
- FileAccessRef fb = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(!fb, false, vformat("LipO: Can't open file: \"%s\".", p_path));
+ Ref<FileAccess> fb = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_path));
uint32_t magic = fb->get_32();
return (magic == 0xbebafeca || magic == 0xcafebabe || magic == 0xbfbafeca || magic == 0xcafebabf);
}
@@ -45,7 +45,7 @@ bool LipO::create_file(const String &p_output_path, const PackedStringArray &p_f
close();
fa = FileAccess::open(p_output_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(!fa, false, vformat("LipO: Can't open file: \"%s\".", p_output_path));
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_output_path));
uint64_t max_size = 0;
for (int i = 0; i < p_files.size(); i++) {
@@ -64,8 +64,8 @@ bool LipO::create_file(const String &p_output_path, const PackedStringArray &p_f
archs.push_back(arch);
- FileAccessRef fb = FileAccess::open(p_files[i], FileAccess::READ);
- if (!fb) {
+ Ref<FileAccess> fb = FileAccess::open(p_files[i], FileAccess::READ);
+ if (fb.is_null()) {
close();
ERR_FAIL_V_MSG(false, vformat("LipO: Can't open file: \"%s.\"", p_files[i]));
}
@@ -101,8 +101,8 @@ bool LipO::create_file(const String &p_output_path, const PackedStringArray &p_f
// Write files and padding.
for (int i = 0; i < archs.size(); i++) {
- FileAccessRef fb = FileAccess::open(p_files[i], FileAccess::READ);
- if (!fb) {
+ Ref<FileAccess> fb = FileAccess::open(p_files[i], FileAccess::READ);
+ if (fb.is_null()) {
close();
ERR_FAIL_V_MSG(false, vformat("LipO: Can't open file: \"%s.\"", p_files[i]));
}
@@ -123,7 +123,6 @@ bool LipO::create_file(const String &p_output_path, const PackedStringArray &p_f
if (br > 0) {
fa->store_buffer(step, br);
}
- fb->close();
}
return true;
}
@@ -132,7 +131,7 @@ bool LipO::open_file(const String &p_path) {
close();
fa = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(!fa, false, vformat("LipO: Can't open file: \"%s\".", p_path));
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_path));
uint32_t magic = fa->get_32();
if (magic == 0xbebafeca) {
@@ -197,16 +196,16 @@ bool LipO::open_file(const String &p_path) {
}
int LipO::get_arch_count() const {
- ERR_FAIL_COND_V_MSG(!fa, 0, "LipO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "LipO: File not opened.");
return archs.size();
}
bool LipO::extract_arch(int p_index, const String &p_path) {
- ERR_FAIL_COND_V_MSG(!fa, false, "LipO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, "LipO: File not opened.");
ERR_FAIL_INDEX_V(p_index, archs.size(), false);
- FileAccessRef fb = FileAccess::open(p_path, FileAccess::WRITE);
- ERR_FAIL_COND_V_MSG(!fb, false, vformat("LipO: Can't open file: \"%s\".", p_path));
+ Ref<FileAccess> fb = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("LipO: Can't open file: \"%s\".", p_path));
fa->seek(archs[p_index].offset);
@@ -223,16 +222,10 @@ bool LipO::extract_arch(int p_index, const String &p_path) {
if (br > 0) {
fb->store_buffer(step, br);
}
- fb->close();
return true;
}
void LipO::close() {
- if (fa) {
- fa->close();
- memdelete(fa);
- fa = nullptr;
- }
archs.clear();
}
diff --git a/platform/osx/export/lipo.h b/platform/osx/export/lipo.h
index 68bbe42dd6..0e419be17e 100644
--- a/platform/osx/export/lipo.h
+++ b/platform/osx/export/lipo.h
@@ -50,7 +50,7 @@ class LipO : public RefCounted {
uint32_t align;
};
- FileAccess *fa = nullptr;
+ Ref<FileAccess> fa;
Vector<FatArch> archs;
static inline size_t PAD(size_t s, size_t a) {
diff --git a/platform/osx/export/macho.cpp b/platform/osx/export/macho.cpp
index 08f2a855b0..e6e67eff06 100644
--- a/platform/osx/export/macho.cpp
+++ b/platform/osx/export/macho.cpp
@@ -49,7 +49,7 @@ uint32_t MachO::seg_align(uint64_t p_vmaddr, uint32_t p_min, uint32_t p_max) {
}
bool MachO::alloc_signature(uint64_t p_size) {
- ERR_FAIL_COND_V_MSG(!fa, false, "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, "MachO: File not opened.");
if (signature_offset != 0) {
// Nothing to do, already have signature load command.
return true;
@@ -103,15 +103,15 @@ bool MachO::alloc_signature(uint64_t p_size) {
}
bool MachO::is_macho(const String &p_path) {
- FileAccessRef fb = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND_V_MSG(!fb, false, vformat("MachO: Can't open file: \"%s\".", p_path));
+ Ref<FileAccess> fb = FileAccess::open(p_path, FileAccess::READ);
+ ERR_FAIL_COND_V_MSG(fb.is_null(), false, vformat("MachO: Can't open file: \"%s\".", p_path));
uint32_t magic = fb->get_32();
return (magic == 0xcefaedfe || magic == 0xfeedface || magic == 0xcffaedfe || magic == 0xfeedfacf);
}
bool MachO::open_file(const String &p_path) {
fa = FileAccess::open(p_path, FileAccess::READ_WRITE);
- ERR_FAIL_COND_V_MSG(!fa, false, vformat("MachO: Can't open file: \"%s\".", p_path));
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, vformat("MachO: Can't open file: \"%s\".", p_path));
uint32_t magic = fa->get_32();
MachHeader mach_header;
@@ -232,37 +232,37 @@ bool MachO::open_file(const String &p_path) {
}
uint64_t MachO::get_exe_base() {
- ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened.");
return exe_base;
}
uint64_t MachO::get_exe_limit() {
- ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened.");
return exe_limit;
}
int32_t MachO::get_align() {
- ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened.");
return align;
}
uint32_t MachO::get_cputype() {
- ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened.");
return cputype;
}
uint32_t MachO::get_cpusubtype() {
- ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened.");
return cpusubtype;
}
uint64_t MachO::get_size() {
- ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened.");
return fa->get_length();
}
uint64_t MachO::get_signature_offset() {
- ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened.");
ERR_FAIL_COND_V_MSG(signature_offset == 0, 0, "MachO: No signature load command.");
fa->seek(signature_offset + 8);
@@ -274,7 +274,7 @@ uint64_t MachO::get_signature_offset() {
}
uint64_t MachO::get_code_limit() {
- ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened.");
if (signature_offset == 0) {
return fa->get_length() + PAD(fa->get_length(), 16);
@@ -284,7 +284,7 @@ uint64_t MachO::get_code_limit() {
}
uint64_t MachO::get_signature_size() {
- ERR_FAIL_COND_V_MSG(!fa, 0, "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), 0, "MachO: File not opened.");
ERR_FAIL_COND_V_MSG(signature_offset == 0, 0, "MachO: No signature load command.");
fa->seek(signature_offset + 12);
@@ -296,7 +296,7 @@ uint64_t MachO::get_signature_size() {
}
bool MachO::is_signed() {
- ERR_FAIL_COND_V_MSG(!fa, false, "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, "MachO: File not opened.");
if (signature_offset == 0) {
return false;
}
@@ -325,7 +325,7 @@ bool MachO::is_signed() {
}
PackedByteArray MachO::get_cdhash_sha1() {
- ERR_FAIL_COND_V_MSG(!fa, PackedByteArray(), "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "MachO: File not opened.");
if (signature_offset == 0) {
return PackedByteArray();
}
@@ -372,7 +372,7 @@ PackedByteArray MachO::get_cdhash_sha1() {
}
PackedByteArray MachO::get_cdhash_sha256() {
- ERR_FAIL_COND_V_MSG(!fa, PackedByteArray(), "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "MachO: File not opened.");
if (signature_offset == 0) {
return PackedByteArray();
}
@@ -419,7 +419,7 @@ PackedByteArray MachO::get_cdhash_sha256() {
}
PackedByteArray MachO::get_requirements() {
- ERR_FAIL_COND_V_MSG(!fa, PackedByteArray(), "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), PackedByteArray(), "MachO: File not opened.");
if (signature_offset == 0) {
return PackedByteArray();
}
@@ -451,16 +451,16 @@ PackedByteArray MachO::get_requirements() {
return PackedByteArray();
}
-const FileAccess *MachO::get_file() const {
+const Ref<FileAccess> MachO::get_file() const {
return fa;
}
-FileAccess *MachO::get_file() {
+Ref<FileAccess> MachO::get_file() {
return fa;
}
bool MachO::set_signature_size(uint64_t p_size) {
- ERR_FAIL_COND_V_MSG(!fa, false, "MachO: File not opened.");
+ ERR_FAIL_COND_V_MSG(fa.is_null(), false, "MachO: File not opened.");
// Ensure signature load command exists.
ERR_FAIL_COND_V_MSG(link_edit_offset == 0, false, "MachO: No __LINKEDIT segment found.");
@@ -545,12 +545,4 @@ bool MachO::set_signature_size(uint64_t p_size) {
return true;
}
-MachO::~MachO() {
- if (fa) {
- fa->close();
- memdelete(fa);
- fa = nullptr;
- }
-}
-
#endif // MODULE_REGEX_ENABLED
diff --git a/platform/osx/export/macho.h b/platform/osx/export/macho.h
index e09906898b..6cfc3c44f5 100644
--- a/platform/osx/export/macho.h
+++ b/platform/osx/export/macho.h
@@ -161,7 +161,7 @@ class MachO : public RefCounted {
uint32_t reserved3;
};
- FileAccess *fa = nullptr;
+ Ref<FileAccess> fa;
bool swap = false;
uint64_t lc_limit = 0;
@@ -203,13 +203,11 @@ public:
PackedByteArray get_requirements();
- const FileAccess *get_file() const;
- FileAccess *get_file();
+ const Ref<FileAccess> get_file() const;
+ Ref<FileAccess> get_file();
uint64_t get_signature_size();
bool set_signature_size(uint64_t p_size);
-
- ~MachO();
};
#endif // MODULE_REGEX_ENABLED
diff --git a/platform/osx/export/plist.cpp b/platform/osx/export/plist.cpp
index 553b864180..d089233b80 100644
--- a/platform/osx/export/plist.cpp
+++ b/platform/osx/export/plist.cpp
@@ -343,8 +343,8 @@ PList::PList(const String &p_string) {
bool PList::load_file(const String &p_filename) {
root = Ref<PListNode>();
- FileAccessRef fb = FileAccess::open(p_filename, FileAccess::READ);
- if (!fb) {
+ Ref<FileAccess> fb = FileAccess::open(p_filename, FileAccess::READ);
+ if (fb.is_null()) {
return false;
}
@@ -398,7 +398,6 @@ bool PList::load_string(const String &p_string) {
}
if (token == "/plist") {
- in_plist = false;
done_plist = true;
break;
}
diff --git a/platform/osx/gl_manager_osx_legacy.h b/platform/osx/gl_manager_osx_legacy.h
index b5a1b9dd98..76d58de229 100644
--- a/platform/osx/gl_manager_osx_legacy.h
+++ b/platform/osx/gl_manager_osx_legacy.h
@@ -38,9 +38,9 @@
#include "core/templates/local_vector.h"
#include "servers/display_server.h"
-#include <AppKit/AppKit.h>
-#include <ApplicationServices/ApplicationServices.h>
-#include <CoreVideo/CoreVideo.h>
+#import <AppKit/AppKit.h>
+#import <ApplicationServices/ApplicationServices.h>
+#import <CoreVideo/CoreVideo.h>
class GLManager_OSX {
public:
diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp
index 7d31ede61d..be9567e17c 100644
--- a/platform/osx/joypad_osx.cpp
+++ b/platform/osx/joypad_osx.cpp
@@ -195,7 +195,7 @@ void joypad::add_hid_element(IOHIDElementRef p_element) {
}
static void hid_element_added(const void *p_value, void *p_parameter) {
- joypad *joy = (joypad *)p_parameter;
+ joypad *joy = static_cast<joypad *>(p_parameter);
joy->add_hid_element((IOHIDElementRef)p_value);
}
@@ -540,10 +540,10 @@ static CFDictionaryRef create_match_dictionary(const UInt32 page, const UInt32 u
CFDictionaryRef retval = nullptr;
CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
- const void *keys[2] = { (void *)CFSTR(kIOHIDDeviceUsagePageKey), (void *)CFSTR(kIOHIDDeviceUsageKey) };
- const void *vals[2] = { (void *)pageNumRef, (void *)usageNumRef };
if (pageNumRef && usageNumRef) {
+ const void *keys[2] = { (void *)CFSTR(kIOHIDDeviceUsagePageKey), (void *)CFSTR(kIOHIDDeviceUsageKey) };
+ const void *vals[2] = { (void *)pageNumRef, (void *)usageNumRef };
retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
diff --git a/platform/osx/joypad_osx.h b/platform/osx/joypad_osx.h
index 4ca7fb1698..3f89048ce6 100644
--- a/platform/osx/joypad_osx.h
+++ b/platform/osx/joypad_osx.h
@@ -32,13 +32,13 @@
#define JOYPADOSX_H
#ifdef MACOS_10_0_4
-#include <IOKit/hidsystem/IOHIDUsageTables.h>
+#import <IOKit/hidsystem/IOHIDUsageTables.h>
#else
-#include <Kernel/IOKit/hidsystem/IOHIDUsageTables.h>
+#import <Kernel/IOKit/hidsystem/IOHIDUsageTables.h>
#endif
-#include <ForceFeedback/ForceFeedback.h>
-#include <ForceFeedback/ForceFeedbackConstants.h>
-#include <IOKit/hid/IOHIDLib.h>
+#import <ForceFeedback/ForceFeedback.h>
+#import <ForceFeedback/ForceFeedbackConstants.h>
+#import <IOKit/hid/IOHIDLib.h>
#include "core/input/input.h"
@@ -94,7 +94,7 @@ class JoypadOSX {
};
private:
- Input *input;
+ Input *input = nullptr;
IOHIDManagerRef hid_manager;
Vector<joypad> device_list;
diff --git a/platform/osx/key_mapping_osx.mm b/platform/osx/key_mapping_osx.mm
index fde9206824..bfec45de58 100644
--- a/platform/osx/key_mapping_osx.mm
+++ b/platform/osx/key_mapping_osx.mm
@@ -30,8 +30,8 @@
#include "key_mapping_osx.h"
-#include <Carbon/Carbon.h>
-#include <Cocoa/Cocoa.h>
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
bool KeyMappingOSX::is_numpad_key(unsigned int key) {
static const unsigned int table[] = {
diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h
index 53c5c8bd90..e4ec411c96 100644
--- a/platform/osx/os_osx.h
+++ b/platform/osx/os_osx.h
@@ -80,7 +80,7 @@ public:
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
- virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override;
+ virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override;
virtual MainLoop *get_main_loop() const override;
diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm
index 7e0cf9f9cc..a8fa56e34b 100644
--- a/platform/osx/os_osx.mm
+++ b/platform/osx/os_osx.mm
@@ -47,7 +47,7 @@
_FORCE_INLINE_ String OS_OSX::get_framework_executable(const String &p_path) {
// Append framework executable name, or return as is if p_path is not a framework.
- DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
if (da->dir_exists(p_path) && da->file_exists(p_path.plus_file(p_path.get_file().get_basename()))) {
return p_path.plus_file(p_path.get_file().get_basename());
} else {
@@ -150,7 +150,7 @@ void OS_OSX::alert(const String &p_alert, const String &p_title) {
}
}
-Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
+Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
String path = get_framework_executable(p_path);
if (!FileAccess::exists(path)) {
@@ -165,6 +165,11 @@ Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle,
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + ".");
+
+ if (r_resolved_path != nullptr) {
+ *r_resolved_path = path;
+ }
+
return OK;
}
@@ -313,21 +318,22 @@ String OS_OSX::get_executable_path() const {
}
Error OS_OSX::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
- if (@available(macOS 10.15, *)) {
- // Use NSWorkspace if path is an .app bundle.
- NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
- NSBundle *bundle = [NSBundle bundleWithURL:url];
- if (bundle) {
- NSMutableArray *arguments = [[NSMutableArray alloc] init];
- for (const List<String>::Element *E = p_arguments.front(); E; E = E->next()) {
- [arguments addObject:[NSString stringWithUTF8String:E->get().utf8().get_data()]];
- }
+ // Use NSWorkspace if path is an .app bundle.
+ NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
+ NSBundle *bundle = [NSBundle bundleWithURL:url];
+ if (bundle) {
+ NSMutableArray *arguments = [[NSMutableArray alloc] init];
+ for (const String &arg : p_arguments) {
+ [arguments addObject:[NSString stringWithUTF8String:arg.utf8().get_data()]];
+ }
+ if (@available(macOS 10.15, *)) {
NSWorkspaceOpenConfiguration *configuration = [[NSWorkspaceOpenConfiguration alloc] init];
[configuration setArguments:arguments];
[configuration setCreatesNewApplicationInstance:YES];
__block dispatch_semaphore_t lock = dispatch_semaphore_create(0);
__block Error err = ERR_TIMEOUT;
__block pid_t pid = 0;
+
[[NSWorkspace sharedWorkspace] openApplicationAtURL:url
configuration:configuration
completionHandler:^(NSRunningApplication *app, NSError *error) {
@@ -350,7 +356,19 @@ Error OS_OSX::create_process(const String &p_path, const List<String> &p_argumen
return err;
} else {
- return OS_Unix::create_process(p_path, p_arguments, r_child_id, p_open_console);
+ Error err = ERR_TIMEOUT;
+ NSError *error = nullptr;
+ NSRunningApplication *app = [[NSWorkspace sharedWorkspace] launchApplicationAtURL:url options:NSWorkspaceLaunchNewInstance configuration:[NSDictionary dictionaryWithObject:arguments forKey:NSWorkspaceLaunchConfigurationArguments] error:&error];
+ if (error) {
+ err = ERR_CANT_FORK;
+ NSLog(@"Failed to execute: %@", error.localizedDescription);
+ } else {
+ if (r_child_id) {
+ *r_child_id = (ProcessID)[app processIdentifier];
+ }
+ err = OK;
+ }
+ return err;
}
} else {
return OS_Unix::create_process(p_path, p_arguments, r_child_id, p_open_console);
diff --git a/platform/osx/tts_osx.h b/platform/osx/tts_osx.h
new file mode 100644
index 0000000000..54d419e573
--- /dev/null
+++ b/platform/osx/tts_osx.h
@@ -0,0 +1,71 @@
+/*************************************************************************/
+/* tts_osx.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 TTS_OSX_H
+#define TTS_OSX_H
+
+#include "core/string/ustring.h"
+#include "core/templates/list.h"
+#include "core/templates/map.h"
+#include "core/variant/array.h"
+#include "servers/display_server.h"
+
+#import <AppKit/AppKit.h>
+
+#if __has_include(<AVFAudio/AVSpeechSynthesis.h>)
+#import <AVFAudio/AVSpeechSynthesis.h>
+#else
+#import <AVFoundation/AVFoundation.h>
+#endif
+
+@interface TTS_OSX : NSObject <AVSpeechSynthesizerDelegate> {
+ // AVSpeechSynthesizer
+ bool speaking;
+ Map<id, int> ids;
+
+ // NSSpeechSynthesizer
+ bool paused;
+ bool have_utterance;
+ int last_utterance;
+
+ id synth; // NSSpeechSynthesizer or AVSpeechSynthesizer
+ List<DisplayServer::TTSUtterance> queue;
+}
+
+- (void)pauseSpeaking;
+- (void)resumeSpeaking;
+- (void)stopSpeaking;
+- (bool)isSpeaking;
+- (bool)isPaused;
+- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt;
+- (Array)getVoices;
+@end
+
+#endif // TTS_OSX_H
diff --git a/platform/osx/tts_osx.mm b/platform/osx/tts_osx.mm
new file mode 100644
index 0000000000..e6a5236cd9
--- /dev/null
+++ b/platform/osx/tts_osx.mm
@@ -0,0 +1,266 @@
+/*************************************************************************/
+/* tts_osx.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "tts_osx.h"
+
+@implementation TTS_OSX
+
+- (id)init {
+ self = [super init];
+ self->speaking = false;
+ self->have_utterance = false;
+ self->last_utterance = -1;
+ self->paused = false;
+ if (@available(macOS 10.14, *)) {
+ self->synth = [[AVSpeechSynthesizer alloc] init];
+ [self->synth setDelegate:self];
+ print_verbose("Text-to-Speech: AVSpeechSynthesizer initialized.");
+ } else {
+ self->synth = [[NSSpeechSynthesizer alloc] init];
+ [self->synth setDelegate:self];
+ print_verbose("Text-to-Speech: NSSpeechSynthesizer initialized.");
+ }
+ return self;
+}
+
+// AVSpeechSynthesizer callback (macOS 10.14+)
+
+- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth willSpeakRangeOfSpeechString:(NSRange)characterRange utterance:(AVSpeechUtterance *)utterance API_AVAILABLE(macosx(10.14)) {
+ NSString *string = [utterance speechString];
+
+ // Convert from UTF-16 to UTF-32 position.
+ int pos = 0;
+ for (NSUInteger i = 0; i < MIN(characterRange.location, string.length); i++) {
+ unichar c = [string characterAtIndex:i];
+ if ((c & 0xfffffc00) == 0xd800) {
+ i++;
+ }
+ pos++;
+ }
+
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, ids[utterance], pos);
+}
+
+// AVSpeechSynthesizer callback (macOS 10.14+)
+
+- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didCancelSpeechUtterance:(AVSpeechUtterance *)utterance API_AVAILABLE(macosx(10.14)) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, ids[utterance]);
+ ids.erase(utterance);
+ speaking = false;
+ [self update];
+}
+
+// AVSpeechSynthesizer callback (macOS 10.14+)
+
+- (void)speechSynthesizer:(AVSpeechSynthesizer *)av_synth didFinishSpeechUtterance:(AVSpeechUtterance *)utterance API_AVAILABLE(macosx(10.14)) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, ids[utterance]);
+ ids.erase(utterance);
+ speaking = false;
+ [self update];
+}
+
+// NSSpeechSynthesizer callback (macOS 10.4+)
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer *)ns_synth willSpeakWord:(NSRange)characterRange ofString:(NSString *)string {
+ if (!paused && have_utterance) {
+ // Convert from UTF-16 to UTF-32 position.
+ int pos = 0;
+ for (NSUInteger i = 0; i < MIN(characterRange.location, string.length); i++) {
+ unichar c = [string characterAtIndex:i];
+ if ((c & 0xfffffc00) == 0xd800) {
+ i++;
+ }
+ pos++;
+ }
+
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, last_utterance, pos);
+ }
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer *)ns_synth didFinishSpeaking:(BOOL)success {
+ if (!paused && have_utterance) {
+ if (success) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, last_utterance);
+ } else {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, last_utterance);
+ }
+ have_utterance = false;
+ }
+ speaking = false;
+ [self update];
+}
+
+- (void)update {
+ if (!speaking && queue.size() > 0) {
+ DisplayServer::TTSUtterance &message = queue.front()->get();
+
+ if (@available(macOS 10.14, *)) {
+ AVSpeechSynthesizer *av_synth = synth;
+ AVSpeechUtterance *new_utterance = [[AVSpeechUtterance alloc] initWithString:[NSString stringWithUTF8String:message.text.utf8().get_data()]];
+ [new_utterance setVoice:[AVSpeechSynthesisVoice voiceWithIdentifier:[NSString stringWithUTF8String:message.voice.utf8().get_data()]]];
+ if (message.rate > 1.f) {
+ [new_utterance setRate:Math::range_lerp(message.rate, 1.f, 10.f, AVSpeechUtteranceDefaultSpeechRate, AVSpeechUtteranceMaximumSpeechRate)];
+ } else if (message.rate < 1.f) {
+ [new_utterance setRate:Math::range_lerp(message.rate, 0.1f, 1.f, AVSpeechUtteranceMinimumSpeechRate, AVSpeechUtteranceDefaultSpeechRate)];
+ }
+ [new_utterance setPitchMultiplier:message.pitch];
+ [new_utterance setVolume:(Math::range_lerp(message.volume, 0.f, 100.f, 0.f, 1.f))];
+
+ ids[new_utterance] = message.id;
+ [av_synth speakUtterance:new_utterance];
+ } else {
+ NSSpeechSynthesizer *ns_synth = synth;
+ [ns_synth setObject:nil forProperty:NSSpeechResetProperty error:nil];
+ [ns_synth setVoice:[NSString stringWithUTF8String:message.voice.utf8().get_data()]];
+ int base_pitch = [[ns_synth objectForProperty:NSSpeechPitchBaseProperty error:nil] intValue];
+ [ns_synth setObject:[NSNumber numberWithInt:(base_pitch * (message.pitch / 2.f + 0.5f))] forProperty:NSSpeechPitchBaseProperty error:nullptr];
+ [ns_synth setVolume:(Math::range_lerp(message.volume, 0.f, 100.f, 0.f, 1.f))];
+ [ns_synth setRate:(message.rate * 200)];
+
+ last_utterance = message.id;
+ have_utterance = true;
+ [ns_synth startSpeakingString:[NSString stringWithUTF8String:message.text.utf8().get_data()]];
+ }
+ queue.pop_front();
+
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, message.id);
+ speaking = true;
+ }
+}
+
+- (void)pauseSpeaking {
+ if (@available(macOS 10.14, *)) {
+ AVSpeechSynthesizer *av_synth = synth;
+ [av_synth pauseSpeakingAtBoundary:AVSpeechBoundaryImmediate];
+ } else {
+ NSSpeechSynthesizer *ns_synth = synth;
+ [ns_synth pauseSpeakingAtBoundary:NSSpeechImmediateBoundary];
+ }
+ paused = true;
+}
+
+- (void)resumeSpeaking {
+ if (@available(macOS 10.14, *)) {
+ AVSpeechSynthesizer *av_synth = synth;
+ [av_synth continueSpeaking];
+ } else {
+ NSSpeechSynthesizer *ns_synth = synth;
+ [ns_synth continueSpeaking];
+ }
+ paused = false;
+}
+
+- (void)stopSpeaking {
+ for (DisplayServer::TTSUtterance &message : queue) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, message.id);
+ }
+ queue.clear();
+ if (@available(macOS 10.14, *)) {
+ AVSpeechSynthesizer *av_synth = synth;
+ [av_synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];
+ } else {
+ NSSpeechSynthesizer *ns_synth = synth;
+ if (have_utterance) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, last_utterance);
+ }
+ [ns_synth stopSpeaking];
+ }
+ have_utterance = false;
+ speaking = false;
+ paused = false;
+}
+
+- (bool)isSpeaking {
+ return speaking || (queue.size() > 0);
+}
+
+- (bool)isPaused {
+ if (@available(macOS 10.14, *)) {
+ AVSpeechSynthesizer *av_synth = synth;
+ return [av_synth isPaused];
+ } else {
+ return paused;
+ }
+}
+
+- (void)speak:(const String &)text voice:(const String &)voice volume:(int)volume pitch:(float)pitch rate:(float)rate utterance_id:(int)utterance_id interrupt:(bool)interrupt {
+ if (interrupt) {
+ [self stopSpeaking];
+ }
+
+ if (text.is_empty()) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, utterance_id);
+ return;
+ }
+
+ DisplayServer::TTSUtterance message;
+ message.text = text;
+ message.voice = voice;
+ message.volume = CLAMP(volume, 0, 100);
+ message.pitch = CLAMP(pitch, 0.f, 2.f);
+ message.rate = CLAMP(rate, 0.1f, 10.f);
+ message.id = utterance_id;
+ queue.push_back(message);
+
+ if ([self isPaused]) {
+ [self resumeSpeaking];
+ } else {
+ [self update];
+ }
+}
+
+- (Array)getVoices {
+ Array list;
+ if (@available(macOS 10.14, *)) {
+ for (AVSpeechSynthesisVoice *voice in [AVSpeechSynthesisVoice speechVoices]) {
+ NSString *voiceIdentifierString = [voice identifier];
+ NSString *voiceLocaleIdentifier = [voice language];
+ NSString *voiceName = [voice name];
+ Dictionary voice_d;
+ voice_d["name"] = String::utf8([voiceName UTF8String]);
+ voice_d["id"] = String::utf8([voiceIdentifierString UTF8String]);
+ voice_d["language"] = String::utf8([voiceLocaleIdentifier UTF8String]);
+ list.push_back(voice_d);
+ }
+ } else {
+ for (NSString *voiceIdentifierString in [NSSpeechSynthesizer availableVoices]) {
+ NSString *voiceLocaleIdentifier = [[NSSpeechSynthesizer attributesForVoice:voiceIdentifierString] objectForKey:NSVoiceLocaleIdentifier];
+ NSString *voiceName = [[NSSpeechSynthesizer attributesForVoice:voiceIdentifierString] objectForKey:NSVoiceName];
+ Dictionary voice_d;
+ voice_d["name"] = String([voiceName UTF8String]);
+ voice_d["id"] = String([voiceIdentifierString UTF8String]);
+ voice_d["language"] = String([voiceLocaleIdentifier UTF8String]);
+ list.push_back(voice_d);
+ }
+ }
+ return list;
+}
+
+@end
diff --git a/platform/osx/vulkan_context_osx.h b/platform/osx/vulkan_context_osx.h
index b78b4eb141..ade0f4a4c9 100644
--- a/platform/osx/vulkan_context_osx.h
+++ b/platform/osx/vulkan_context_osx.h
@@ -32,7 +32,7 @@
#define VULKAN_DEVICE_OSX_H
#include "drivers/vulkan/vulkan_context.h"
-#include <AppKit/AppKit.h>
+#import <AppKit/AppKit.h>
class VulkanContextOSX : public VulkanContext {
virtual const char *_get_platform_surface_extension() const;
diff --git a/platform/uwp/export/app_packager.cpp b/platform/uwp/export/app_packager.cpp
index e7978ff74d..2f70c3e74c 100644
--- a/platform/uwp/export/app_packager.cpp
+++ b/platform/uwp/export/app_packager.cpp
@@ -46,7 +46,7 @@ String AppxPackager::hash_block(const uint8_t *p_block_data, size_t p_block_len)
}
void AppxPackager::make_block_map(const String &p_path) {
- FileAccess *tmp_file = FileAccess::open(p_path, FileAccess::WRITE);
+ Ref<FileAccess> tmp_file = FileAccess::open(p_path, FileAccess::WRITE);
tmp_file->store_string("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>");
tmp_file->store_string("<BlockMap xmlns=\"http://schemas.microsoft.com/appx/2010/blockmap\" HashMethod=\"http://www.w3.org/2001/04/xmlenc#sha256\">");
@@ -69,9 +69,6 @@ void AppxPackager::make_block_map(const String &p_path) {
}
tmp_file->store_string("</BlockMap>");
-
- tmp_file->close();
- memdelete(tmp_file);
}
String AppxPackager::content_type(String p_extension) {
@@ -89,7 +86,7 @@ String AppxPackager::content_type(String p_extension) {
}
void AppxPackager::make_content_types(const String &p_path) {
- FileAccess *tmp_file = FileAccess::open(p_path, FileAccess::WRITE);
+ Ref<FileAccess> tmp_file = FileAccess::open(p_path, FileAccess::WRITE);
tmp_file->store_string("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
tmp_file->store_string("<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">");
@@ -118,9 +115,6 @@ void AppxPackager::make_content_types(const String &p_path) {
tmp_file->store_string("<Override PartName=\"/AppxMetadata/CodeIntegrity.cat\" ContentType=\"application/vnd.ms-pkiseccat\" />");
tmp_file->store_string("</Types>");
-
- tmp_file->close();
- memdelete(tmp_file);
}
Vector<uint8_t> AppxPackager::make_file_header(FileMeta p_file_meta) {
@@ -285,7 +279,7 @@ Vector<uint8_t> AppxPackager::make_end_of_central_record() {
return buf;
}
-void AppxPackager::init(FileAccess *p_fa) {
+void AppxPackager::init(Ref<FileAccess> p_fa) {
package = p_fa;
central_dir_offset = 0;
end_of_central_dir_offset = 0;
@@ -301,7 +295,6 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t
FileMeta meta;
meta.name = p_file_name;
meta.uncompressed_size = p_len;
- meta.compressed_size = p_len;
meta.compressed = p_compress;
meta.zip_offset = package->get_position();
@@ -309,7 +302,6 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t
// Data for compression
z_stream strm;
- FileAccess *strm_f = nullptr;
Vector<uint8_t> strm_in;
strm_in.resize(BLOCK_SIZE);
Vector<uint8_t> strm_out;
@@ -317,7 +309,7 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t
if (p_compress) {
strm.zalloc = zipio_alloc;
strm.zfree = zipio_free;
- strm.opaque = &strm_f;
+ strm.opaque = Z_NULL;
strm_out.resize(BLOCK_SIZE + 8);
@@ -419,16 +411,15 @@ void AppxPackager::finish() {
const String &tmp_blockmap_file_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpblockmap.xml");
make_block_map(tmp_blockmap_file_path);
- FileAccess *blockmap_file = FileAccess::open(tmp_blockmap_file_path, FileAccess::READ);
- Vector<uint8_t> blockmap_buffer;
- blockmap_buffer.resize(blockmap_file->get_length());
-
- blockmap_file->get_buffer(blockmap_buffer.ptrw(), blockmap_buffer.size());
+ {
+ Ref<FileAccess> blockmap_file = FileAccess::open(tmp_blockmap_file_path, FileAccess::READ);
+ Vector<uint8_t> blockmap_buffer;
+ blockmap_buffer.resize(blockmap_file->get_length());
- add_file("AppxBlockMap.xml", blockmap_buffer.ptr(), blockmap_buffer.size(), -1, -1, true);
+ blockmap_file->get_buffer(blockmap_buffer.ptrw(), blockmap_buffer.size());
- blockmap_file->close();
- memdelete(blockmap_file);
+ add_file("AppxBlockMap.xml", blockmap_buffer.ptr(), blockmap_buffer.size(), -1, -1, true);
+ }
// Add content types
@@ -437,16 +428,15 @@ void AppxPackager::finish() {
const String &tmp_content_types_file_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpcontenttypes.xml");
make_content_types(tmp_content_types_file_path);
- FileAccess *types_file = FileAccess::open(tmp_content_types_file_path, FileAccess::READ);
- Vector<uint8_t> types_buffer;
- types_buffer.resize(types_file->get_length());
-
- types_file->get_buffer(types_buffer.ptrw(), types_buffer.size());
+ {
+ Ref<FileAccess> types_file = FileAccess::open(tmp_content_types_file_path, FileAccess::READ);
+ Vector<uint8_t> types_buffer;
+ types_buffer.resize(types_file->get_length());
- add_file("[Content_Types].xml", types_buffer.ptr(), types_buffer.size(), -1, -1, true);
+ types_file->get_buffer(types_buffer.ptrw(), types_buffer.size());
- types_file->close();
- memdelete(types_file);
+ add_file("[Content_Types].xml", types_buffer.ptr(), types_buffer.size(), -1, -1, true);
+ }
// Cleanup generated files.
DirAccess::remove_file_or_error(tmp_blockmap_file_path);
@@ -467,9 +457,7 @@ void AppxPackager::finish() {
Vector<uint8_t> end_record = make_end_of_central_record();
package->store_buffer(end_record.ptr(), end_record.size());
- package->close();
- memdelete(package);
- package = nullptr;
+ package.unref();
}
AppxPackager::AppxPackager() {}
diff --git a/platform/uwp/export/app_packager.h b/platform/uwp/export/app_packager.h
index da118449c7..ea42e9bdfe 100644
--- a/platform/uwp/export/app_packager.h
+++ b/platform/uwp/export/app_packager.h
@@ -87,14 +87,14 @@ class AppxPackager {
};
String progress_task;
- FileAccess *package = nullptr;
+ Ref<FileAccess> package;
Set<String> mime_types;
Vector<FileMeta> file_metadata;
- ZPOS64_T central_dir_offset;
- ZPOS64_T end_of_central_dir_offset;
+ ZPOS64_T central_dir_offset = 0;
+ ZPOS64_T end_of_central_dir_offset = 0;
Vector<uint8_t> central_dir_data;
String hash_block(const uint8_t *p_block_data, size_t p_block_len);
@@ -138,7 +138,7 @@ class AppxPackager {
public:
void set_progress_task(String p_task) { progress_task = p_task; }
- void init(FileAccess *p_fa);
+ void init(Ref<FileAccess> p_fa);
Error add_file(String p_file_name, const uint8_t *p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress = false);
void finish();
diff --git a/platform/uwp/export/export_plugin.cpp b/platform/uwp/export/export_plugin.cpp
index 88343d6f85..7e06bf01e3 100644
--- a/platform/uwp/export/export_plugin.cpp
+++ b/platform/uwp/export/export_plugin.cpp
@@ -295,14 +295,13 @@ Error EditorExportPlatformUWP::export_project(const Ref<EditorExportPreset> &p_p
Error err = OK;
- FileAccess *fa_pack = FileAccess::open(p_path, FileAccess::WRITE, &err);
+ Ref<FileAccess> fa_pack = FileAccess::open(p_path, FileAccess::WRITE, &err);
ERR_FAIL_COND_V_MSG(err != OK, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'.");
AppxPackager packager;
packager.init(fa_pack);
- FileAccess *src_f = nullptr;
- zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
+ zlib_filefunc_def io = zipio_create_io();
if (ep.step("Creating package...", 0)) {
return ERR_SKIP;
@@ -332,6 +331,9 @@ Error EditorExportPlatformUWP::export_project(const Ref<EditorExportPreset> &p_p
unz_file_info info;
char fname[16834];
ret = unzGetCurrentFileInfo(pkg, &info, fname, 16834, nullptr, 0, nullptr, 0);
+ if (ret != UNZ_OK) {
+ break;
+ }
String path = String::utf8(fname);
diff --git a/platform/uwp/export/export_plugin.h b/platform/uwp/export/export_plugin.h
index bf89b10ffa..ceb6d613b3 100644
--- a/platform/uwp/export/export_plugin.h
+++ b/platform/uwp/export/export_plugin.h
@@ -346,21 +346,21 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
ERR_FAIL_V_MSG(data, err_string);
}
- FileAccess *f = FileAccess::open(tmp_path, FileAccess::READ, &err);
+ {
+ Ref<FileAccess> f = FileAccess::open(tmp_path, FileAccess::READ, &err);
+
+ if (err != OK) {
+ String err_string = "Couldn't open temp logo file.";
+ // Cleanup generated file.
+ DirAccess::remove_file_or_error(tmp_path);
+ EditorNode::add_io_error(err_string);
+ ERR_FAIL_V_MSG(data, err_string);
+ }
- if (err != OK) {
- String err_string = "Couldn't open temp logo file.";
- // Cleanup generated file.
- DirAccess::remove_file_or_error(tmp_path);
- EditorNode::add_io_error(err_string);
- ERR_FAIL_V_MSG(data, err_string);
+ data.resize(f->get_length());
+ f->get_buffer(data.ptrw(), data.size());
}
- data.resize(f->get_length());
- f->get_buffer(data.ptrw(), data.size());
-
- f->close();
- memdelete(f);
DirAccess::remove_file_or_error(tmp_path);
return data;
@@ -417,7 +417,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
}
static Error save_appx_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
- AppxPackager *packager = (AppxPackager *)p_userdata;
+ AppxPackager *packager = static_cast<AppxPackager *>(p_userdata);
String dst_path = p_path.replace_first("res://", "game/");
return packager->add_file(dst_path, p_data.ptr(), p_data.size(), p_file, p_total, _should_compress_asset(p_path, p_data));
diff --git a/platform/uwp/joypad_uwp.h b/platform/uwp/joypad_uwp.h
index 29f5109056..0869f1961d 100644
--- a/platform/uwp/joypad_uwp.h
+++ b/platform/uwp/joypad_uwp.h
@@ -68,7 +68,7 @@ private:
ControllerDevice controllers[MAX_CONTROLLERS];
- InputDefault *input;
+ InputDefault *input = nullptr;
void OnGamepadAdded(Platform::Object ^ sender, Windows::Gaming::Input::Gamepad ^ value);
void OnGamepadRemoved(Platform::Object ^ sender, Windows::Gaming::Input::Gamepad ^ value);
diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp
index 22a54911f9..1614bfdcc3 100644
--- a/platform/uwp/os_uwp.cpp
+++ b/platform/uwp/os_uwp.cpp
@@ -631,7 +631,7 @@ OS::CursorShape OS_UWP::get_cursor_shape() const {
return cursor_shape;
}
-void OS_UWP::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+void OS_UWP::set_custom_mouse_cursor(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
// TODO
}
@@ -647,6 +647,10 @@ Error OS_UWP::kill(const ProcessID &p_pid) {
return FAILED;
}
+bool OS_UWP::is_process_running(const ProcessID &p_pid) const {
+ return false;
+}
+
Error OS_UWP::set_cwd(const String &p_cwd) {
return FAILED;
}
@@ -733,10 +737,15 @@ static String format_error_message(DWORD id) {
return msg;
}
-Error OS_UWP::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
+Error OS_UWP::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
String full_path = "game/" + p_path;
p_library_handle = (void *)LoadPackagedLibrary((LPCWSTR)(full_path.utf16().get_data()), 0);
ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + full_path + ", error: " + format_error_message(GetLastError()) + ".");
+
+ if (r_resolved_path != nullptr) {
+ *r_resolved_path = full_path;
+ }
+
return OK;
}
diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h
index 573d86af7c..bddf63ff18 100644
--- a/platform/uwp/os_uwp.h
+++ b/platform/uwp/os_uwp.h
@@ -74,7 +74,7 @@ private:
KEY_EVENT_BUFFER_SIZE = 512
};
- FILE *stdo;
+ FILE *stdo = nullptr;
KeyEvent key_event_buffer[KEY_EVENT_BUFFER_SIZE];
int key_event_pos;
@@ -87,16 +87,16 @@ private:
bool outside;
int old_x, old_y;
Point2i center;
- RenderingServer *rendering_server;
+ RenderingServer *rendering_server = nullptr;
int pressrc;
- ContextEGL_UWP *gl_context;
+ ContextEGL_UWP *gl_context = nullptr;
Windows::UI::Core::CoreWindow ^ window;
VideoMode video_mode;
int video_driver_index;
- MainLoop *main_loop;
+ MainLoop *main_loop = nullptr;
AudioDriverXAudio2 audio_driver;
@@ -111,7 +111,7 @@ private:
CursorShape cursor_shape;
- InputDefault *input;
+ InputDefault *input = nullptr;
JoypadUWP ^ joypad;
@@ -198,6 +198,7 @@ public:
virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false);
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false);
virtual Error kill(const ProcessID &p_pid);
+ virtual bool is_process_running(const ProcessID &p_pid) const;
virtual bool has_environment(const String &p_var) const;
virtual String get_environment(const String &p_var) const;
@@ -208,7 +209,7 @@ public:
void set_cursor_shape(CursorShape p_shape);
CursorShape get_cursor_shape() const;
- virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
+ virtual void set_custom_mouse_cursor(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
void set_icon(const Ref<Image> &p_icon);
virtual String get_executable_path() const;
@@ -233,7 +234,7 @@ public:
virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
virtual void hide_virtual_keyboard();
- virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
+ virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr);
virtual Error close_dynamic_library(void *p_library_handle);
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false);
diff --git a/platform/windows/SCsub b/platform/windows/SCsub
index 76234c3065..7e412b140f 100644
--- a/platform/windows/SCsub
+++ b/platform/windows/SCsub
@@ -13,6 +13,7 @@ common_win = [
"display_server_windows.cpp",
"key_mapping_windows.cpp",
"joypad_windows.cpp",
+ "tts_windows.cpp",
"windows_terminal_logger.cpp",
"vulkan_context_win.cpp",
"gl_manager_windows.cpp",
diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp
index 3b2c6fe9f6..6ce10e6f0f 100644
--- a/platform/windows/crash_handler_windows.cpp
+++ b/platform/windows/crash_handler_windows.cpp
@@ -32,6 +32,7 @@
#include "core/config/project_settings.h"
#include "core/os/os.h"
+#include "core/string/print_string.h"
#include "core/version.h"
#include "main/main.h"
@@ -129,13 +130,28 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
return EXCEPTION_CONTINUE_SEARCH;
}
- fprintf(stderr, "\n================================================================\n");
- fprintf(stderr, "%s: Program crashed\n", __FUNCTION__);
+ String msg;
+ const ProjectSettings *proj_settings = ProjectSettings::get_singleton();
+ if (proj_settings) {
+ msg = proj_settings->get("debug/settings/crash_handler/message");
+ }
+ // Tell MainLoop about the crash. This can be handled by users too in Node.
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);
}
+ print_error("\n================================================================");
+ print_error(vformat("%s: Program crashed", __FUNCTION__));
+
+ // Print the engine version just before, so that people are reminded to include the version in backtrace reports.
+ if (String(VERSION_HASH).is_empty()) {
+ print_error(vformat("Engine version: %s", VERSION_FULL_NAME));
+ } else {
+ print_error(vformat("Engine version: %s (%s)", VERSION_FULL_NAME, VERSION_HASH));
+ }
+ print_error(vformat("Dumping the backtrace. %s", msg));
+
// Load the symbols:
if (!SymInitialize(process, nullptr, false)) {
return EXCEPTION_CONTINUE_SEARCH;
@@ -174,20 +190,6 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
IMAGE_NT_HEADERS *h = ImageNtHeader(base);
DWORD image_type = h->FileHeader.Machine;
- String msg;
- const ProjectSettings *proj_settings = ProjectSettings::get_singleton();
- if (proj_settings) {
- 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).is_empty()) {
- fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME);
- } else {
- fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH);
- }
- fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());
-
int n = 0;
do {
if (skip_first) {
@@ -197,12 +199,12 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
std::string fnName = symbol(process, frame.AddrPC.Offset).undecorated_name();
if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &offset_from_symbol, &line)) {
- fprintf(stderr, "[%d] %s (%s:%d)\n", n, fnName.c_str(), line.FileName, line.LineNumber);
+ print_error(vformat("[%d] %s (%s:%d)", n, fnName.c_str(), (char *)line.FileName, (int)line.LineNumber));
} else {
- fprintf(stderr, "[%d] %s\n", n, fnName.c_str());
+ print_error(vformat("[%d] %s", n, fnName.c_str()));
}
} else {
- fprintf(stderr, "[%d] ???\n", n);
+ print_error(vformat("[%d] ???", n));
}
n++;
@@ -213,8 +215,8 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
}
} while (frame.AddrReturn.Offset != 0 && n < 256);
- fprintf(stderr, "-- END OF BACKTRACE --\n");
- fprintf(stderr, "================================================================\n");
+ print_error("-- END OF BACKTRACE --");
+ print_error("================================================================");
SymCleanup(process);
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 249a0d2e79..0b18fb74fb 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -252,6 +252,7 @@ def configure_msvc(env, manual_msvc_config):
"kernel32",
"ole32",
"oleaut32",
+ "sapi",
"user32",
"gdi32",
"IPHLPAPI",
@@ -426,6 +427,7 @@ def configure_mingw(env):
"ws2_32",
"kernel32",
"oleaut32",
+ "sapi",
"dinput8",
"dxguid",
"ksuser",
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 877e82e707..93cab85441 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -84,6 +84,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const {
case FEATURE_NATIVE_ICON:
case FEATURE_SWAP_BUFFERS:
case FEATURE_KEEP_SCREEN_ON:
+ case FEATURE_TEXT_TO_SPEECH:
return true;
default:
return false;
@@ -133,6 +134,41 @@ void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) {
}
}
+bool DisplayServerWindows::tts_is_speaking() const {
+ ERR_FAIL_COND_V(!tts, false);
+ return tts->is_speaking();
+}
+
+bool DisplayServerWindows::tts_is_paused() const {
+ ERR_FAIL_COND_V(!tts, false);
+ return tts->is_paused();
+}
+
+Array DisplayServerWindows::tts_get_voices() const {
+ ERR_FAIL_COND_V(!tts, Array());
+ return tts->get_voices();
+}
+
+void DisplayServerWindows::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
+ ERR_FAIL_COND(!tts);
+ tts->speak(p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_interrupt);
+}
+
+void DisplayServerWindows::tts_pause() {
+ ERR_FAIL_COND(!tts);
+ tts->pause();
+}
+
+void DisplayServerWindows::tts_resume() {
+ ERR_FAIL_COND(!tts);
+ tts->resume();
+}
+
+void DisplayServerWindows::tts_stop() {
+ ERR_FAIL_COND(!tts);
+ tts->stop();
+}
+
void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) {
_THREAD_SAFE_METHOD_
@@ -430,9 +466,8 @@ static int QueryDpiForMonitor(HMONITOR hmon, _MonitorDpiType dpiType = MDT_Defau
}
UINT x = 0, y = 0;
- HRESULT hr = E_FAIL;
if (hmon && (Shcore != (HMODULE)INVALID_HANDLE_VALUE)) {
- hr = getDPIForMonitor(hmon, dpiType /*MDT_Effective_DPI*/, &x, &y);
+ HRESULT hr = getDPIForMonitor(hmon, dpiType /*MDT_Effective_DPI*/, &x, &y);
if (SUCCEEDED(hr) && (x > 0) && (y > 0)) {
dpiX = (int)x;
dpiY = (int)y;
@@ -844,8 +879,8 @@ void DisplayServerWindows::window_set_exclusive(WindowID p_window, bool p_exclus
if (wd.exclusive != p_exclusive) {
wd.exclusive = p_exclusive;
if (wd.transient_parent != INVALID_WINDOW_ID) {
- WindowData &wd_parent = windows[wd.transient_parent];
if (wd.exclusive) {
+ WindowData &wd_parent = windows[wd.transient_parent];
SetWindowLongPtr(wd.hWnd, GWLP_HWNDPARENT, (LONG_PTR)wd_parent.hWnd);
} else {
SetWindowLongPtr(wd.hWnd, GWLP_HWNDPARENT, (LONG_PTR) nullptr);
@@ -1071,6 +1106,10 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain
SetWindowLongPtr(wd.hWnd, GWL_STYLE, style);
SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex);
+ if (icon.is_valid()) {
+ set_icon(icon);
+ }
+
SetWindowPos(wd.hWnd, wd.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | ((wd.no_focus || wd.is_popup) ? SWP_NOACTIVATE : 0));
if (p_repaint) {
@@ -1281,7 +1320,7 @@ void DisplayServerWindows::window_request_attention(WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
- WindowData &wd = windows[p_window];
+ const WindowData &wd = windows[p_window];
FLASHWINFO info;
info.cbSize = sizeof(FLASHWINFO);
@@ -1455,7 +1494,7 @@ void DisplayServerWindows::GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTra
DeleteDC(hMainDC);
}
-void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+void DisplayServerWindows::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
_THREAD_SAFE_METHOD_
if (p_cursor.is_valid()) {
@@ -1562,13 +1601,8 @@ void DisplayServerWindows::cursor_set_custom_image(const RES &p_cursor, CursorSh
}
}
- if (hAndMask != nullptr) {
- DeleteObject(hAndMask);
- }
-
- if (hXorMask != nullptr) {
- DeleteObject(hXorMask);
- }
+ DeleteObject(hAndMask);
+ DeleteObject(hXorMask);
memfree(buffer);
DeleteObject(bitmap);
@@ -1771,8 +1805,8 @@ void DisplayServerWindows::swap_buffers() {
void DisplayServerWindows::set_native_icon(const String &p_filename) {
_THREAD_SAFE_METHOD_
- FileAccess *f = FileAccess::open(p_filename, FileAccess::READ);
- ERR_FAIL_COND_MSG(!f, "Cannot open file with icon '" + p_filename + "'.");
+ Ref<FileAccess> f = FileAccess::open(p_filename, FileAccess::READ);
+ ERR_FAIL_COND_MSG(f.is_null(), "Cannot open file with icon '" + p_filename + "'.");
ICONDIR *icon_dir = (ICONDIR *)memalloc(sizeof(ICONDIR));
int pos = 0;
@@ -1858,7 +1892,6 @@ void DisplayServerWindows::set_native_icon(const String &p_filename) {
err = GetLastError();
ERR_FAIL_COND_MSG(err, "Error setting ICON_BIG: " + format_error_message(err) + ".");
- memdelete(f);
memdelete(icon_dir);
}
@@ -1866,9 +1899,11 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!p_icon.is_valid());
- Ref<Image> icon = p_icon->duplicate();
- if (icon->get_format() != Image::FORMAT_RGBA8) {
- icon->convert(Image::FORMAT_RGBA8);
+ if (icon != p_icon) {
+ icon = p_icon->duplicate();
+ if (icon->get_format() != Image::FORMAT_RGBA8) {
+ icon->convert(Image::FORMAT_RGBA8);
+ }
}
int w = icon->get_width();
int h = icon->get_height();
@@ -1994,7 +2029,7 @@ void DisplayServerWindows::_send_window_event(const WindowData &wd, WindowEvent
}
void DisplayServerWindows::_dispatch_input_events(const Ref<InputEvent> &p_event) {
- ((DisplayServerWindows *)(get_singleton()))->_dispatch_input_event(p_event);
+ static_cast<DisplayServerWindows *>(get_singleton())->_dispatch_input_event(p_event);
}
void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) {
@@ -2010,7 +2045,7 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event)
Callable::CallError ce;
{
- List<WindowID>::Element *E = popup_list.front();
+ List<WindowID>::Element *E = popup_list.back();
if (E && Object::cast_to<InputEventKey>(*p_event)) {
// Redirect keyboard input to active popup.
if (windows.has(E->get())) {
@@ -2081,20 +2116,24 @@ Rect2i DisplayServerWindows::window_get_popup_safe_rect(WindowID p_window) const
}
void DisplayServerWindows::popup_open(WindowID p_window) {
- WindowData &wd = windows[p_window];
+ _THREAD_SAFE_METHOD_
+
+ const WindowData &wd = windows[p_window];
if (wd.is_popup) {
- // Close all popups, up to current popup parent, or every popup if new window is not transient.
+ // Find current popup parent, or root popup if new window is not transient.
+ List<WindowID>::Element *C = nullptr;
List<WindowID>::Element *E = popup_list.back();
while (E) {
if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) {
- _send_window_event(windows[E->get()], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST);
- List<WindowID>::Element *F = E->prev();
- popup_list.erase(E);
- E = F;
+ C = E;
+ E = E->prev();
} else {
break;
}
}
+ if (C) {
+ _send_window_event(windows[C->get()], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST);
+ }
time_since_popup = OS::get_singleton()->get_ticks_msec();
popup_list.push_back(p_window);
@@ -2102,17 +2141,22 @@ void DisplayServerWindows::popup_open(WindowID p_window) {
}
void DisplayServerWindows::popup_close(WindowID p_window) {
+ _THREAD_SAFE_METHOD_
+
List<WindowID>::Element *E = popup_list.find(p_window);
while (E) {
- _send_window_event(windows[E->get()], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST);
List<WindowID>::Element *F = E->next();
+ WindowID win_id = E->get();
popup_list.erase(E);
+
+ _send_window_event(windows[win_id], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST);
E = F;
}
}
LRESULT DisplayServerWindows::MouseProc(int code, WPARAM wParam, LPARAM lParam) {
_THREAD_SAFE_METHOD_
+
uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup;
if (delta > 250) {
switch (wParam) {
@@ -2123,7 +2167,9 @@ LRESULT DisplayServerWindows::MouseProc(int code, WPARAM wParam, LPARAM lParam)
case WM_MBUTTONDOWN: {
MOUSEHOOKSTRUCT *ms = (MOUSEHOOKSTRUCT *)lParam;
Point2i pos = Point2i(ms->pt.x, ms->pt.y);
+ List<WindowID>::Element *C = nullptr;
List<WindowID>::Element *E = popup_list.back();
+ // Find top popup to close.
while (E) {
// Popup window area.
Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get()));
@@ -2134,13 +2180,13 @@ LRESULT DisplayServerWindows::MouseProc(int code, WPARAM wParam, LPARAM lParam)
} else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) {
break;
} else {
- _send_window_event(windows[E->get()], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST);
- List<WindowID>::Element *F = E->prev();
- popup_list.erase(E);
- E = F;
+ C = E;
+ E = E->prev();
}
}
-
+ if (C) {
+ _send_window_event(windows[C->get()], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST);
+ }
} break;
}
}
@@ -3483,7 +3529,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
shift_mem = false;
control_mem = false;
meta_mem = false;
- hInstance = ((OS_Windows *)OS::get_singleton())->get_hinstance();
+ hInstance = static_cast<OS_Windows *>(OS::get_singleton())->get_hinstance();
pressrc = 0;
old_invalid = true;
@@ -3493,6 +3539,9 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
rendering_driver = p_rendering_driver;
+ // Init TTS
+ tts = memnew(TTS_Windows);
+
// Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink.
HMODULE wintab_lib = LoadLibraryW(L"wintab32.dll");
if (wintab_lib) {
@@ -3651,7 +3700,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
r_error = OK;
- ((OS_Windows *)OS::get_singleton())->set_main_window(windows[MAIN_WINDOW_ID].hWnd);
+ static_cast<OS_Windows *>(OS::get_singleton())->set_main_window(windows[MAIN_WINDOW_ID].hWnd);
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
}
@@ -3735,4 +3784,8 @@ DisplayServerWindows::~DisplayServerWindows() {
gl_manager = nullptr;
}
#endif
+ if (tts) {
+ memdelete(tts);
+ }
+ CoUninitialize();
}
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 71fedf2bca..febc8a2043 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -46,6 +46,7 @@
#include "servers/rendering/renderer_compositor.h"
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
#include "servers/rendering_server.h"
+#include "tts_windows.h"
#ifdef XAUDIO2_ENABLED
#include "drivers/xaudio2/audio_driver_xaudio2.h"
@@ -320,6 +321,8 @@ class DisplayServerWindows : public DisplayServer {
String rendering_driver;
bool app_focused = false;
+ TTS_Windows *tts = nullptr;
+
struct WindowData {
HWND hWnd;
//layered window
@@ -392,10 +395,11 @@ class DisplayServerWindows : public DisplayServer {
Rect2i parent_safe_rect;
};
- JoypadWindows *joypad;
+ JoypadWindows *joypad = nullptr;
HHOOK mouse_monitor = nullptr;
List<WindowID> popup_list;
uint64_t time_since_popup = 0;
+ Ref<Image> icon;
WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect);
WindowID window_id_counter = MAIN_WINDOW_ID;
@@ -454,6 +458,15 @@ public:
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
+ virtual bool tts_is_speaking() const override;
+ virtual bool tts_is_paused() const override;
+ virtual Array tts_get_voices() const override;
+
+ virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
+ virtual void tts_pause() override;
+ virtual void tts_resume() override;
+ virtual void tts_stop() override;
+
virtual void mouse_set_mode(MouseMode p_mode) override;
virtual MouseMode mouse_get_mode() const override;
@@ -549,7 +562,7 @@ public:
virtual void cursor_set_shape(CursorShape p_shape) override;
virtual CursorShape cursor_get_shape() const override;
- virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;
+ virtual void cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;
virtual bool get_swap_cancel_ok() override;
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index e627253739..7627a3cba3 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -42,8 +42,8 @@ Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPres
}
Error EditorExportPlatformWindows::_export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path) {
- FileAccessRef f = FileAccess::open(p_path, FileAccess::WRITE);
- ERR_FAIL_COND_V(!f, ERR_CANT_CREATE);
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V(f.is_null(), ERR_CANT_CREATE);
f->store_line("@echo off");
f->store_line("title \"" + p_app_name + "\"");
@@ -53,17 +53,27 @@ Error EditorExportPlatformWindows::_export_debug_script(const Ref<EditorExportPr
return OK;
}
-Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
- Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, p_path, p_flags);
-
- if (err != OK) {
- return err;
+Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+ if (p_preset->get("application/modify_resources")) {
+ return _rcedit_add_data(p_preset, p_path);
+ } else {
+ return OK;
}
+}
- _rcedit_add_data(p_preset, p_path);
-
+Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) {
+ String pck_path = p_path;
+ if (p_preset->get("binary_format/embed_pck")) {
+ pck_path = p_path.get_basename() + ".tmp";
+ }
+ Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, pck_path, p_flags);
if (p_preset->get("codesign/enable") && err == OK) {
- err = _code_sign(p_preset, p_path);
+ err = _code_sign(p_preset, pck_path);
+ }
+
+ if (p_preset->get("binary_format/embed_pck") && err == OK) {
+ Ref<DirAccess> tmp_dir = DirAccess::create_for_path(p_path.get_base_dir());
+ err = tmp_dir->rename(pck_path, p_path);
}
String app_name;
@@ -117,6 +127,7 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/description"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray()));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/modify_resources"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), ""));
@@ -127,17 +138,16 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/trademarks"), ""));
}
-void EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
+Error EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit");
- if (rcedit_path.is_empty()) {
- WARN_PRINT("The rcedit tool is not configured in the Editor Settings (Export > Windows > Rcedit). No custom icon or app information data will be embedded in the exported executable.");
- return;
+ if (rcedit_path != String() && !FileAccess::exists(rcedit_path)) {
+ ERR_PRINT("Could not find rcedit executable at " + rcedit_path + ", aborting.");
+ return ERR_FILE_NOT_FOUND;
}
- if (!FileAccess::exists(rcedit_path)) {
- ERR_PRINT("Could not find rcedit executable at " + rcedit_path + ", no icon or app information data will be included.");
- return;
+ if (rcedit_path == String()) {
+ rcedit_path = "rcedit"; // try to run rcedit from PATH
}
#ifndef WINDOWS_ENABLED
@@ -145,8 +155,8 @@ void EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset>
String wine_path = EditorSettings::get_singleton()->get("export/windows/wine");
if (!wine_path.is_empty() && !FileAccess::exists(wine_path)) {
- ERR_PRINT("Could not find wine executable at " + wine_path + ", no icon or app information data will be included.");
- return;
+ ERR_PRINT("Could not find wine executable at " + wine_path + ", aborting.");
+ return ERR_FILE_NOT_FOUND;
}
if (wine_path.is_empty()) {
@@ -204,13 +214,22 @@ void EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset>
args.push_back(trademarks);
}
-#ifdef WINDOWS_ENABLED
- OS::get_singleton()->execute(rcedit_path, args);
-#else
+#ifndef WINDOWS_ENABLED
// On non-Windows we need WINE to run rcedit
args.push_front(rcedit_path);
- OS::get_singleton()->execute(wine_path, args);
+ rcedit_path = wine_path;
#endif
+
+ String str;
+ Error err = OS::get_singleton()->execute(rcedit_path, args, &str, nullptr, true);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Could not start rcedit executable, configure rcedit path in the Editor Settings (Export > Windows > Rcedit).");
+ print_line("rcedit (" + p_path + "): " + str);
+
+ if (str.find("Fatal error") != -1) {
+ return FAILED;
+ }
+
+ return OK;
}
Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
@@ -347,7 +366,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
String str;
Error err = OS::get_singleton()->execute(signtool_path, args, &str, nullptr, true);
- ERR_FAIL_COND_V(err != OK, err);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Could not start signtool executable, configure signtool path in the Editor Settings (Export > Windows > Signtool).");
print_line("codesign (" + p_path + "): " + str);
#ifndef WINDOWS_ENABLED
@@ -359,7 +378,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
}
#ifndef WINDOWS_ENABLED
- DirAccessRef tmp_dir = DirAccess::create_for_path(p_path.get_base_dir());
+ Ref<DirAccess> tmp_dir = DirAccess::create_for_path(p_path.get_base_dir());
err = tmp_dir->remove(p_path);
ERR_FAIL_COND_V(err != OK, err);
@@ -376,7 +395,7 @@ bool EditorExportPlatformWindows::can_export(const Ref<EditorExportPreset> &p_pr
bool valid = EditorExportPlatformPC::can_export(p_preset, err, r_missing_templates);
String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit");
- if (rcedit_path.is_empty()) {
+ if (p_preset->get("application/modify_resources") && rcedit_path.is_empty()) {
err += TTR("The rcedit tool must be configured in the Editor Settings (Export > Windows > Rcedit) to change the icon or app information data.") + "\n";
}
@@ -417,8 +436,12 @@ bool EditorExportPlatformWindows::can_export(const Ref<EditorExportPreset> &p_pr
Error EditorExportPlatformWindows::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) const {
// Patch the header of the "pck" section in the PE file so that it corresponds to the embedded data
- FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE);
- if (!f) {
+ if (p_embedded_size + p_embedded_start >= 0x100000000) { // Check for total executable size
+ ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Windows executables cannot be >= 4 GiB.");
+ }
+
+ Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ_WRITE);
+ if (f.is_null()) {
return ERR_CANT_OPEN;
}
@@ -430,7 +453,6 @@ Error EditorExportPlatformWindows::fixup_embedded_pck(const String &p_path, int6
f->seek(pe_pos);
uint32_t magic = f->get_32();
if (magic != 0x00004550) {
- f->close();
return ERR_FILE_CORRUPT;
}
}
@@ -480,7 +502,5 @@ Error EditorExportPlatformWindows::fixup_embedded_pck(const String &p_path, int6
}
}
- f->close();
-
return found ? OK : ERR_FILE_CORRUPT;
}
diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h
index 39d1cf4c77..b48ee7c985 100644
--- a/platform/windows/export/export_plugin.h
+++ b/platform/windows/export/export_plugin.h
@@ -38,12 +38,13 @@
#include "platform/windows/logo.gen.h"
class EditorExportPlatformWindows : public EditorExportPlatformPC {
- void _rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path);
+ Error _rcedit_add_data(const Ref<EditorExportPreset> &p_preset, const String &p_path);
Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path);
Error _export_debug_script(const Ref<EditorExportPreset> &p_preset, const String &p_app_name, const String &p_pkg_name, const String &p_path);
public:
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
+ virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) override;
virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
virtual void get_export_options(List<ExportOption> *r_options) override;
diff --git a/platform/windows/gl_manager_windows.h b/platform/windows/gl_manager_windows.h
index 6423c54855..dc411983e8 100644
--- a/platform/windows/gl_manager_windows.h
+++ b/platform/windows/gl_manager_windows.h
@@ -52,19 +52,18 @@ public:
private:
// any data specific to the window
struct GLWindow {
- GLWindow() { in_use = false; }
- bool in_use;
+ bool in_use = false;
// the external ID .. should match the GL window number .. unused I think
- DisplayServer::WindowID window_id;
- int width;
- int height;
+ DisplayServer::WindowID window_id = DisplayServer::INVALID_WINDOW_ID;
+ int width = 0;
+ int height = 0;
// windows specific
HDC hDC;
HWND hwnd;
- int gldisplay_id;
+ int gldisplay_id = 0;
};
struct GLDisplay {
@@ -75,7 +74,7 @@ private:
LocalVector<GLWindow> _windows;
LocalVector<GLDisplay> _displays;
- GLWindow *_current_window;
+ GLWindow *_current_window = nullptr;
PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT;
PFNWGLGETSWAPINTERVALEXTPROC wglGetSwapIntervalEXT;
diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp
index ad4e3ae77c..8de3ef294a 100644
--- a/platform/windows/godot_windows.cpp
+++ b/platform/windows/godot_windows.cpp
@@ -39,7 +39,18 @@
#ifndef TOOLS_ENABLED
#if defined _MSC_VER
#pragma section("pck", read)
-__declspec(allocate("pck")) static const char dummy[8] = { 0 };
+__declspec(allocate("pck")) static char dummy[8] = { 0 };
+
+// Dummy function to prevent LTO from discarding "pck" section.
+extern "C" char *__cdecl pck_section_dummy_call() {
+ return &dummy[0];
+};
+#if defined _AMD64_
+#pragma comment(linker, "/include:pck_section_dummy_call")
+#elif defined _X86_
+#pragma comment(linker, "/include:_pck_section_dummy_call")
+#endif
+
#elif defined __GNUC__
static const char dummy[8] __attribute__((section("pck"), used)) = { 0 };
#endif
@@ -140,11 +151,6 @@ int widechar_main(int argc, wchar_t **argv) {
setlocale(LC_CTYPE, "");
-#ifndef TOOLS_ENABLED
- // Workaround to prevent LTCG (MSVC LTO) from removing "pck" section
- const char *dummy_guard = dummy;
-#endif
-
char **argv_utf8 = new char *[argc];
for (int i = 0; i < argc; ++i) {
diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp
index 494e0b9105..d039fd13a7 100644
--- a/platform/windows/joypad_windows.cpp
+++ b/platform/windows/joypad_windows.cpp
@@ -250,7 +250,7 @@ void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_
}
BOOL CALLBACK JoypadWindows::enumCallback(const DIDEVICEINSTANCE *p_instance, void *p_context) {
- JoypadWindows *self = (JoypadWindows *)p_context;
+ JoypadWindows *self = static_cast<JoypadWindows *>(p_context);
if (self->is_xinput_device(&p_instance->guidProduct)) {
return DIENUM_CONTINUE;
}
@@ -258,9 +258,9 @@ BOOL CALLBACK JoypadWindows::enumCallback(const DIDEVICEINSTANCE *p_instance, vo
return DIENUM_CONTINUE;
}
-BOOL CALLBACK JoypadWindows::objectsCallback(const DIDEVICEOBJECTINSTANCE *instance, void *context) {
- JoypadWindows *self = (JoypadWindows *)context;
- self->setup_joypad_object(instance, self->id_to_change);
+BOOL CALLBACK JoypadWindows::objectsCallback(const DIDEVICEOBJECTINSTANCE *p_instance, void *p_context) {
+ JoypadWindows *self = static_cast<JoypadWindows *>(p_context);
+ self->setup_joypad_object(p_instance, self->id_to_change);
return DIENUM_CONTINUE;
}
@@ -404,7 +404,7 @@ void JoypadWindows::process_joypads() {
// on mingw, these constants are not constants
int count = 8;
- LONG axes[] = { DIJOFS_X, DIJOFS_Y, DIJOFS_Z, DIJOFS_RX, DIJOFS_RY, DIJOFS_RZ, (LONG)DIJOFS_SLIDER(0), (LONG)DIJOFS_SLIDER(1) };
+ const LONG axes[] = { DIJOFS_X, DIJOFS_Y, DIJOFS_Z, DIJOFS_RX, DIJOFS_RY, DIJOFS_RZ, (LONG)DIJOFS_SLIDER(0), (LONG)DIJOFS_SLIDER(1) };
int values[] = { js.lX, js.lY, js.lZ, js.lRx, js.lRy, js.lRz, js.rglSlider[0], js.rglSlider[1] };
for (int j = 0; j < joy->joy_axis.size(); j++) {
diff --git a/platform/windows/joypad_windows.h b/platform/windows/joypad_windows.h
index 4f15bcf080..d239471a5c 100644
--- a/platform/windows/joypad_windows.h
+++ b/platform/windows/joypad_windows.h
@@ -105,10 +105,10 @@ private:
typedef DWORD(WINAPI *XInputGetState_t)(DWORD dwUserIndex, XINPUT_STATE *pState);
typedef DWORD(WINAPI *XInputSetState_t)(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration);
- HWND *hWnd;
+ HWND *hWnd = nullptr;
HANDLE xinput_dll;
LPDIRECTINPUT8 dinput;
- Input *input;
+ Input *input = nullptr;
int id_to_change;
int slider_count;
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index b4669e452a..68e188bbed 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -202,7 +202,7 @@ Error OS_Windows::get_entropy(uint8_t *r_buffer, int p_bytes) {
return OK;
}
-Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
+Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
String path = p_path.replace("/", "\\");
if (!FileAccess::exists(path)) {
@@ -230,6 +230,10 @@ Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_han
remove_dll_directory(cookie);
}
+ if (r_resolved_path != nullptr) {
+ *r_resolved_path = path;
+ }
+
return OK;
}
@@ -383,6 +387,31 @@ String OS_Windows::_quote_command_line_argument(const String &p_text) const {
return p_text;
}
+static void _append_to_pipe(char *p_bytes, int p_size, String *r_pipe, Mutex *p_pipe_mutex) {
+ // Try to convert from default ANSI code page to Unicode.
+ LocalVector<wchar_t> wchars;
+ int total_wchars = MultiByteToWideChar(CP_ACP, 0, p_bytes, p_size, nullptr, 0);
+ if (total_wchars > 0) {
+ wchars.resize(total_wchars);
+ if (MultiByteToWideChar(CP_ACP, 0, p_bytes, p_size, wchars.ptr(), total_wchars) == 0) {
+ wchars.clear();
+ }
+ }
+
+ if (p_pipe_mutex) {
+ p_pipe_mutex->lock();
+ }
+ if (wchars.is_empty()) {
+ // Let's hope it's compatible with UTF-8.
+ (*r_pipe) += String::utf8(p_bytes, p_size);
+ } else {
+ (*r_pipe) += String(wchars.ptr(), total_wchars);
+ }
+ if (p_pipe_mutex) {
+ p_pipe_mutex->unlock();
+ }
+}
+
Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) {
String path = p_path.replace("/", "\\");
String command = _quote_command_line_argument(path);
@@ -431,21 +460,44 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
if (r_pipe) {
CloseHandle(pipe[1]); // Close pipe write handle (only child process is writing).
- char buf[4096];
+
+ LocalVector<char> bytes;
+ int bytes_in_buffer = 0;
+
+ const int CHUNK_SIZE = 4096;
DWORD read = 0;
for (;;) { // Read StdOut and StdErr from pipe.
- bool success = ReadFile(pipe[0], buf, 4096, &read, NULL);
+ bytes.resize(bytes_in_buffer + CHUNK_SIZE);
+ const bool success = ReadFile(pipe[0], bytes.ptr() + bytes_in_buffer, CHUNK_SIZE, &read, NULL);
if (!success || read == 0) {
break;
}
- if (p_pipe_mutex) {
- p_pipe_mutex->lock();
+
+ // Assume that all possible encodings are ASCII-compatible.
+ // Break at newline to allow receiving long output in portions.
+ int newline_index = -1;
+ for (int i = read - 1; i >= 0; i--) {
+ if (bytes[bytes_in_buffer + i] == '\n') {
+ newline_index = i;
+ break;
+ }
}
- (*r_pipe) += String::utf8(buf, read);
- if (p_pipe_mutex) {
- p_pipe_mutex->unlock();
+ if (newline_index == -1) {
+ bytes_in_buffer += read;
+ continue;
}
+
+ const int bytes_to_convert = bytes_in_buffer + (newline_index + 1);
+ _append_to_pipe(bytes.ptr(), bytes_to_convert, r_pipe, p_pipe_mutex);
+
+ bytes_in_buffer = read - (newline_index + 1);
+ memmove(bytes.ptr(), bytes.ptr() + bytes_to_convert, bytes_in_buffer);
+ }
+
+ if (bytes_in_buffer > 0) {
+ _append_to_pipe(bytes.ptr(), bytes_in_buffer, r_pipe, p_pipe_mutex);
}
+
CloseHandle(pipe[0]); // Close pipe read handle.
} else {
WaitForSingleObject(pi.pi.hProcess, INFINITE);
@@ -513,6 +565,25 @@ int OS_Windows::get_process_id() const {
return _getpid();
}
+bool OS_Windows::is_process_running(const ProcessID &p_pid) const {
+ if (!process_map->has(p_pid)) {
+ return false;
+ }
+
+ const PROCESS_INFORMATION &pi = (*process_map)[p_pid].pi;
+
+ DWORD dw_exit_code = 0;
+ if (!GetExitCodeProcess(pi.hProcess, &dw_exit_code)) {
+ return false;
+ }
+
+ if (dw_exit_code != STILL_ACTIVE) {
+ return false;
+ }
+
+ return true;
+}
+
Error OS_Windows::set_cwd(const String &p_cwd) {
if (_wchdir((LPCWSTR)(p_cwd.utf16().get_data())) != 0) {
return ERR_CANT_OPEN;
@@ -686,6 +757,58 @@ MainLoop *OS_Windows::get_main_loop() const {
return main_loop;
}
+uint64_t OS_Windows::get_embedded_pck_offset() const {
+ Ref<FileAccess> f = FileAccess::open(get_executable_path(), FileAccess::READ);
+ if (f.is_null()) {
+ return 0;
+ }
+
+ // Process header.
+ {
+ f->seek(0x3c);
+ uint32_t pe_pos = f->get_32();
+
+ f->seek(pe_pos);
+ uint32_t magic = f->get_32();
+ if (magic != 0x00004550) {
+ return 0;
+ }
+ }
+
+ int num_sections;
+ {
+ int64_t header_pos = f->get_position();
+
+ f->seek(header_pos + 2);
+ num_sections = f->get_16();
+ f->seek(header_pos + 16);
+ uint16_t opt_header_size = f->get_16();
+
+ // Skip rest of header + optional header to go to the section headers.
+ f->seek(f->get_position() + 2 + opt_header_size);
+ }
+ int64_t section_table_pos = f->get_position();
+
+ // Search for the "pck" section.
+ int64_t off = 0;
+ for (int i = 0; i < num_sections; ++i) {
+ int64_t section_header_pos = section_table_pos + i * 40;
+ f->seek(section_header_pos);
+
+ uint8_t section_name[9];
+ f->get_buffer(section_name, 8);
+ section_name[8] = '\0';
+
+ if (strcmp((char *)section_name, "pck") == 0) {
+ f->seek(section_header_pos + 20);
+ off = f->get_32();
+ break;
+ }
+ }
+
+ return off;
+}
+
String OS_Windows::get_config_path() const {
// The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on Windows as well.
if (has_environment("XDG_CONFIG_HOME")) {
diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h
index 5bfd24327e..378438a075 100644
--- a/platform/windows/os_windows.h
+++ b/platform/windows/os_windows.h
@@ -60,14 +60,14 @@
class JoypadWindows;
class OS_Windows : public OS {
#ifdef STDOUT_FILE
- FILE *stdo;
+ FILE *stdo = nullptr;
#endif
uint64_t ticks_start;
uint64_t ticks_per_second;
HINSTANCE hInstance;
- MainLoop *main_loop;
+ MainLoop *main_loop = nullptr;
#ifdef WASAPI_ENABLED
AudioDriverWASAPI driver_wasapi;
@@ -108,7 +108,7 @@ public:
virtual Error get_entropy(uint8_t *r_buffer, int p_bytes) override;
- virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override;
+ virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false, String *r_resolved_path = nullptr) override;
virtual Error close_dynamic_library(void *p_library_handle) override;
virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override;
@@ -132,6 +132,7 @@ public:
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
virtual Error kill(const ProcessID &p_pid) override;
virtual int get_process_id() const override;
+ virtual bool is_process_running(const ProcessID &p_pid) const override;
virtual bool has_environment(const String &p_var) const override;
virtual String get_environment(const String &p_var) const override;
@@ -144,6 +145,8 @@ public:
virtual int get_processor_count() const override;
virtual String get_processor_name() const override;
+ virtual uint64_t get_embedded_pck_offset() const override;
+
virtual String get_config_path() const override;
virtual String get_data_path() const override;
virtual String get_cache_path() const override;
diff --git a/platform/windows/tts_windows.cpp b/platform/windows/tts_windows.cpp
new file mode 100644
index 0000000000..05249934ba
--- /dev/null
+++ b/platform/windows/tts_windows.cpp
@@ -0,0 +1,269 @@
+/*************************************************************************/
+/* tts_windows.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "tts_windows.h"
+
+TTS_Windows *TTS_Windows::singleton = nullptr;
+
+void __stdcall TTS_Windows::speech_event_callback(WPARAM wParam, LPARAM lParam) {
+ TTS_Windows *tts = TTS_Windows::get_singleton();
+ SPEVENT event;
+ while (tts->synth->GetEvents(1, &event, NULL) == S_OK) {
+ if (tts->ids.has(event.ulStreamNum)) {
+ if (event.eEventId == SPEI_START_INPUT_STREAM) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_STARTED, tts->ids[event.ulStreamNum].id);
+ } else if (event.eEventId == SPEI_END_INPUT_STREAM) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_ENDED, tts->ids[event.ulStreamNum].id);
+ tts->ids.erase(event.ulStreamNum);
+ tts->_update_tts();
+ } else if (event.eEventId == SPEI_WORD_BOUNDARY) {
+ const Char16String &string = tts->ids[event.ulStreamNum].string;
+ int pos = 0;
+ for (int i = 0; i < MIN(event.lParam, string.length()); i++) {
+ char16_t c = string[i];
+ if ((c & 0xfffffc00) == 0xd800) {
+ i++;
+ }
+ pos++;
+ }
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_BOUNDARY, tts->ids[event.ulStreamNum].id, pos - tts->ids[event.ulStreamNum].offset);
+ }
+ }
+ }
+}
+
+void TTS_Windows::_update_tts() {
+ if (!is_speaking() && !paused && queue.size() > 0) {
+ DisplayServer::TTSUtterance &message = queue.front()->get();
+
+ String text;
+ DWORD flags = SPF_ASYNC | SPF_PURGEBEFORESPEAK | SPF_IS_XML;
+ String pitch_tag = String("<pitch absmiddle=\"") + String::num_int64(message.pitch * 10 - 10, 10) + String("\">");
+ text = pitch_tag + message.text + String("</pitch>");
+
+ IEnumSpObjectTokens *cpEnum;
+ ISpObjectToken *cpVoiceToken;
+ ULONG ulCount = 0;
+ ULONG stream_number = 0;
+ ISpObjectTokenCategory *cpCategory;
+ HRESULT hr = CoCreateInstance(CLSID_SpObjectTokenCategory, nullptr, CLSCTX_INPROC_SERVER, IID_ISpObjectTokenCategory, (void **)&cpCategory);
+ if (SUCCEEDED(hr)) {
+ hr = cpCategory->SetId(SPCAT_VOICES, false);
+ if (SUCCEEDED(hr)) {
+ hr = cpCategory->EnumTokens(nullptr, nullptr, &cpEnum);
+ if (SUCCEEDED(hr)) {
+ hr = cpEnum->GetCount(&ulCount);
+ while (SUCCEEDED(hr) && ulCount--) {
+ wchar_t *w_id = 0L;
+ hr = cpEnum->Next(1, &cpVoiceToken, nullptr);
+ cpVoiceToken->GetId(&w_id);
+ if (String::utf16((const char16_t *)w_id) == message.voice) {
+ synth->SetVoice(cpVoiceToken);
+ cpVoiceToken->Release();
+ break;
+ }
+ cpVoiceToken->Release();
+ }
+ cpEnum->Release();
+ }
+ }
+ cpCategory->Release();
+ }
+
+ UTData ut;
+ ut.string = text.utf16();
+ ut.offset = pitch_tag.length(); // Substract injected <pitch> tag offset.
+ ut.id = message.id;
+
+ synth->SetVolume(message.volume);
+ synth->SetRate(10.f * log10(message.rate) / log10(3.f));
+ synth->Speak((LPCWSTR)ut.string.get_data(), flags, &stream_number);
+
+ ids[stream_number] = ut;
+
+ queue.pop_front();
+ }
+}
+
+bool TTS_Windows::is_speaking() const {
+ ERR_FAIL_COND_V(!synth, false);
+
+ SPVOICESTATUS status;
+ synth->GetStatus(&status, nullptr);
+ return (status.dwRunningState == SPRS_IS_SPEAKING);
+}
+
+bool TTS_Windows::is_paused() const {
+ ERR_FAIL_COND_V(!synth, false);
+ return paused;
+}
+
+Array TTS_Windows::get_voices() const {
+ Array list;
+ IEnumSpObjectTokens *cpEnum;
+ ISpObjectToken *cpVoiceToken;
+ ISpDataKey *cpDataKeyAttribs;
+ ULONG ulCount = 0;
+ ISpObjectTokenCategory *cpCategory;
+ HRESULT hr = CoCreateInstance(CLSID_SpObjectTokenCategory, nullptr, CLSCTX_INPROC_SERVER, IID_ISpObjectTokenCategory, (void **)&cpCategory);
+ if (SUCCEEDED(hr)) {
+ hr = cpCategory->SetId(SPCAT_VOICES, false);
+ if (SUCCEEDED(hr)) {
+ hr = cpCategory->EnumTokens(nullptr, nullptr, &cpEnum);
+ if (SUCCEEDED(hr)) {
+ hr = cpEnum->GetCount(&ulCount);
+ while (SUCCEEDED(hr) && ulCount--) {
+ hr = cpEnum->Next(1, &cpVoiceToken, nullptr);
+ HRESULT hr_attr = cpVoiceToken->OpenKey(SPTOKENKEY_ATTRIBUTES, &cpDataKeyAttribs);
+ if (SUCCEEDED(hr_attr)) {
+ wchar_t *w_id = nullptr;
+ wchar_t *w_lang = nullptr;
+ wchar_t *w_name = nullptr;
+ cpVoiceToken->GetId(&w_id);
+ cpDataKeyAttribs->GetStringValue(L"Language", &w_lang);
+ cpDataKeyAttribs->GetStringValue(nullptr, &w_name);
+ LCID locale = wcstol(w_lang, nullptr, 16);
+
+ int locale_chars = GetLocaleInfoW(locale, LOCALE_SISO639LANGNAME, nullptr, 0);
+ int region_chars = GetLocaleInfoW(locale, LOCALE_SISO3166CTRYNAME, nullptr, 0);
+ wchar_t *w_lang_code = new wchar_t[locale_chars];
+ wchar_t *w_reg_code = new wchar_t[region_chars];
+ GetLocaleInfoW(locale, LOCALE_SISO639LANGNAME, w_lang_code, locale_chars);
+ GetLocaleInfoW(locale, LOCALE_SISO3166CTRYNAME, w_reg_code, region_chars);
+
+ Dictionary voice_d;
+ voice_d["id"] = String::utf16((const char16_t *)w_id);
+ if (w_name) {
+ voice_d["name"] = String::utf16((const char16_t *)w_name);
+ } else {
+ voice_d["name"] = voice_d["id"].operator String().replace("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices\\Tokens\\", "");
+ }
+ voice_d["language"] = String::utf16((const char16_t *)w_lang_code) + "_" + String::utf16((const char16_t *)w_reg_code);
+ list.push_back(voice_d);
+
+ delete[] w_lang_code;
+ delete[] w_reg_code;
+
+ cpDataKeyAttribs->Release();
+ }
+ cpVoiceToken->Release();
+ }
+ cpEnum->Release();
+ }
+ }
+ cpCategory->Release();
+ }
+ return list;
+}
+
+void TTS_Windows::speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
+ ERR_FAIL_COND(!synth);
+ if (p_interrupt) {
+ stop();
+ }
+
+ if (p_text.is_empty()) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, p_utterance_id);
+ return;
+ }
+
+ DisplayServer::TTSUtterance message;
+ message.text = p_text;
+ message.voice = p_voice;
+ message.volume = CLAMP(p_volume, 0, 100);
+ message.pitch = CLAMP(p_pitch, 0.f, 2.f);
+ message.rate = CLAMP(p_rate, 0.1f, 10.f);
+ message.id = p_utterance_id;
+ queue.push_back(message);
+
+ if (is_paused()) {
+ resume();
+ } else {
+ _update_tts();
+ }
+}
+
+void TTS_Windows::pause() {
+ ERR_FAIL_COND(!synth);
+ if (!paused) {
+ if (synth->Pause() == S_OK) {
+ paused = true;
+ }
+ }
+}
+
+void TTS_Windows::resume() {
+ ERR_FAIL_COND(!synth);
+ synth->Resume();
+ paused = false;
+}
+
+void TTS_Windows::stop() {
+ ERR_FAIL_COND(!synth);
+
+ SPVOICESTATUS status;
+ synth->GetStatus(&status, nullptr);
+ if (ids.has(status.ulCurrentStream)) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, ids[status.ulCurrentStream].id);
+ ids.erase(status.ulCurrentStream);
+ }
+ for (DisplayServer::TTSUtterance &message : queue) {
+ DisplayServer::get_singleton()->tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, message.id);
+ }
+ queue.clear();
+ synth->Speak(nullptr, SPF_PURGEBEFORESPEAK, nullptr);
+ synth->Resume();
+ paused = false;
+}
+
+TTS_Windows *TTS_Windows::get_singleton() {
+ return singleton;
+}
+
+TTS_Windows::TTS_Windows() {
+ singleton = this;
+ CoInitialize(nullptr);
+
+ if (SUCCEEDED(CoCreateInstance(CLSID_SpVoice, nullptr, CLSCTX_ALL, IID_ISpVoice, (void **)&synth))) {
+ ULONGLONG event_mask = SPFEI(SPEI_END_INPUT_STREAM) | SPFEI(SPEI_START_INPUT_STREAM) | SPFEI(SPEI_WORD_BOUNDARY);
+ synth->SetInterest(event_mask, event_mask);
+ synth->SetNotifyCallbackFunction(&speech_event_callback, (WPARAM)(this), 0);
+ print_verbose("Text-to-Speech: SAPI initialized.");
+ } else {
+ print_verbose("Text-to-Speech: Cannot initialize ISpVoice!");
+ }
+}
+
+TTS_Windows::~TTS_Windows() {
+ if (synth) {
+ synth->Release();
+ }
+ singleton = nullptr;
+}
diff --git a/platform/windows/tts_windows.h b/platform/windows/tts_windows.h
new file mode 100644
index 0000000000..5da404baf9
--- /dev/null
+++ b/platform/windows/tts_windows.h
@@ -0,0 +1,80 @@
+/*************************************************************************/
+/* tts_windows.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 TTS_WINDOWS_H
+#define TTS_WINDOWS_H
+
+#include "core/string/ustring.h"
+#include "core/templates/list.h"
+#include "core/templates/map.h"
+#include "core/variant/array.h"
+#include "servers/display_server.h"
+
+#include <objbase.h>
+#include <sapi.h>
+#include <wchar.h>
+#include <winnls.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+class TTS_Windows {
+ List<DisplayServer::TTSUtterance> queue;
+ ISpVoice *synth = nullptr;
+ bool paused = false;
+ struct UTData {
+ Char16String string;
+ int offset;
+ int id;
+ };
+ Map<ULONG, UTData> ids;
+
+ static void __stdcall speech_event_callback(WPARAM wParam, LPARAM lParam);
+ void _update_tts();
+
+ static TTS_Windows *singleton;
+
+public:
+ static TTS_Windows *get_singleton();
+
+ bool is_speaking() const;
+ bool is_paused() const;
+ Array get_voices() const;
+
+ void speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false);
+ void pause();
+ void resume();
+ void stop();
+
+ TTS_Windows();
+ ~TTS_Windows();
+};
+
+#endif // TTS_WINDOWS_H