summaryrefslogtreecommitdiff
path: root/platform/javascript
diff options
context:
space:
mode:
Diffstat (limited to 'platform/javascript')
-rw-r--r--platform/javascript/api/javascript_tools_editor_plugin.cpp30
-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.cpp52
-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
15 files changed, 304 insertions, 55 deletions
diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp
index 0442a1eaeb..1507f32375 100644
--- a/platform/javascript/api/javascript_tools_editor_plugin.cpp
+++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp
@@ -64,8 +64,8 @@ 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);
+ Ref<FileAccess> io_fa;
+ zlib_filefunc_def io = zipio_create_io(&io_fa);
// 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 +82,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 +105,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 +122,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 4448acccc2..9576256d03 100644
--- a/platform/javascript/export/export_plugin.cpp
+++ b/platform/javascript/export/export_plugin.cpp
@@ -33,8 +33,8 @@
#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);
+ Ref<FileAccess> io_fa;
+ zlib_filefunc_def io = zipio_create_io(&io_fa);
unzFile pkg = unzOpen2(p_template.utf8().get_data(), &io);
if (!pkg) {
@@ -70,14 +70,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 +84,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 +231,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 +248,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) {
@@ -360,6 +356,15 @@ Ref<Texture2D> EditorExportPlatformJavaScript::get_logo() const {
}
bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+#ifndef DEV_ENABLED
+ // We don't provide export templates for the HTML5 platform currently as there
+ // is no suitable renderer to use with them. So we forbid exporting and tell
+ // users why. This is skipped in DEV_ENABLED so that contributors can still test
+ // the pipeline once we start having WebGL or WebGPU support.
+ r_error = "The HTML5 platform is currently not supported in Godot 4.0, as there is no suitable renderer for it.\n";
+ return false;
+#endif
+
String err;
bool valid = false;
ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type");
@@ -447,7 +452,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);
@@ -466,32 +471,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);
@@ -577,7 +575,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) {
@@ -642,7 +640,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"