summaryrefslogtreecommitdiffstats
path: root/platform/web
diff options
context:
space:
mode:
Diffstat (limited to 'platform/web')
-rw-r--r--platform/web/api/web_tools_editor_plugin.cpp2
-rw-r--r--platform/web/audio_driver_web.cpp2
-rw-r--r--platform/web/detect.py10
-rw-r--r--platform/web/display_server_web.cpp36
-rw-r--r--platform/web/display_server_web.h5
-rw-r--r--platform/web/doc_classes/EditorExportPlatformWeb.xml54
-rw-r--r--platform/web/dom_keys.inc2
-rw-r--r--platform/web/export/export.cpp5
-rw-r--r--platform/web/export/export.h1
-rw-r--r--platform/web/export/export_plugin.cpp41
-rw-r--r--platform/web/export/export_plugin.h2
-rw-r--r--platform/web/godot_js.h1
-rw-r--r--platform/web/js/libs/library_godot_fetch.js5
-rw-r--r--platform/web/js/libs/library_godot_os.js22
-rw-r--r--platform/web/os_web.cpp3
15 files changed, 159 insertions, 32 deletions
diff --git a/platform/web/api/web_tools_editor_plugin.cpp b/platform/web/api/web_tools_editor_plugin.cpp
index 146a48db81..213204ff33 100644
--- a/platform/web/api/web_tools_editor_plugin.cpp
+++ b/platform/web/api/web_tools_editor_plugin.cpp
@@ -75,7 +75,7 @@ void WebToolsEditorPlugin::_download_zip() {
const String project_name_safe = project_name.to_lower().replace(" ", "_");
const String datetime_safe =
Time::get_singleton()->get_datetime_string_from_system(false, true).replace(" ", "_");
- const String output_name = OS::get_singleton()->get_safe_dir_name(vformat("%s_%s.zip"));
+ const String output_name = OS::get_singleton()->get_safe_dir_name(vformat("%s_%s.zip", project_name_safe, datetime_safe));
const String output_path = String("/tmp").path_join(output_name);
zipFile zip = zipOpen2(output_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io);
diff --git a/platform/web/audio_driver_web.cpp b/platform/web/audio_driver_web.cpp
index 1d7b96d707..c6c67db3de 100644
--- a/platform/web/audio_driver_web.cpp
+++ b/platform/web/audio_driver_web.cpp
@@ -103,7 +103,7 @@ void AudioDriverWeb::_audio_driver_capture(int p_from, int p_samples) {
Error AudioDriverWeb::init() {
int latency = GLOBAL_GET("audio/driver/output_latency");
if (!audio_context.inited) {
- audio_context.mix_rate = GLOBAL_GET("audio/driver/mix_rate");
+ audio_context.mix_rate = _get_configured_mix_rate();
audio_context.channel_count = godot_audio_init(&audio_context.mix_rate, latency, &_state_change_callback, &_latency_update_callback);
audio_context.inited = true;
}
diff --git a/platform/web/detect.py b/platform/web/detect.py
index 08c1ff7b4a..419d8918f2 100644
--- a/platform/web/detect.py
+++ b/platform/web/detect.py
@@ -48,6 +48,16 @@ def get_opts():
]
+def get_doc_classes():
+ return [
+ "EditorExportPlatformWeb",
+ ]
+
+
+def get_doc_path():
+ return "doc_classes"
+
+
def get_flags():
return [
("arch", "wasm32"),
diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp
index 565d439a92..e870f0da29 100644
--- a/platform/web/display_server_web.cpp
+++ b/platform/web/display_server_web.cpp
@@ -30,6 +30,7 @@
#include "display_server_web.h"
+#include "core/config/project_settings.h"
#ifdef GLES3_ENABLED
#include "drivers/gles3/rasterizer_gles3.h"
#endif
@@ -298,10 +299,12 @@ const char *DisplayServerWeb::godot2dom_cursor(DisplayServer::CursorShape p_shap
}
bool DisplayServerWeb::tts_is_speaking() const {
+ ERR_FAIL_COND_V_MSG(!tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
return godot_js_tts_is_speaking();
}
bool DisplayServerWeb::tts_is_paused() const {
+ ERR_FAIL_COND_V_MSG(!tts, false, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
return godot_js_tts_is_paused();
}
@@ -320,11 +323,13 @@ void DisplayServerWeb::update_voices_callback(int p_size, const char **p_voice)
}
TypedArray<Dictionary> DisplayServerWeb::tts_get_voices() const {
+ ERR_FAIL_COND_V_MSG(!tts, TypedArray<Dictionary>(), "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
godot_js_tts_get_voices(update_voices_callback);
return voices;
}
void DisplayServerWeb::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_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
if (p_interrupt) {
tts_stop();
}
@@ -341,14 +346,17 @@ void DisplayServerWeb::tts_speak(const String &p_text, const String &p_voice, in
}
void DisplayServerWeb::tts_pause() {
+ ERR_FAIL_COND_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
godot_js_tts_pause();
}
void DisplayServerWeb::tts_resume() {
+ ERR_FAIL_COND_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
godot_js_tts_resume();
}
void DisplayServerWeb::tts_stop() {
+ ERR_FAIL_COND_MSG(!tts, "Enable the \"audio/general/text_to_speech\" project setting to use text-to-speech.");
for (const KeyValue<int, CharString> &E : utterance_ids) {
tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, E.key);
}
@@ -398,16 +406,12 @@ void DisplayServerWeb::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu
ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
if (p_cursor.is_valid()) {
Ref<Texture2D> texture = p_cursor;
+ ERR_FAIL_COND(!texture.is_valid());
Ref<AtlasTexture> atlas_texture = p_cursor;
- Ref<Image> image;
Size2 texture_size;
Rect2 atlas_rect;
- if (texture.is_valid()) {
- image = texture->get_image();
- }
-
- if (!image.is_valid() && atlas_texture.is_valid()) {
+ if (atlas_texture.is_valid()) {
texture = atlas_texture->get_atlas();
atlas_rect.size.width = texture->get_width();
@@ -417,21 +421,25 @@ void DisplayServerWeb::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu
texture_size.width = atlas_texture->get_region().size.x;
texture_size.height = atlas_texture->get_region().size.y;
- } else if (image.is_valid()) {
+ } else {
texture_size.width = texture->get_width();
texture_size.height = texture->get_height();
}
- ERR_FAIL_COND(!texture.is_valid());
ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
- image = texture->get_image();
+ Ref<Image> image = texture->get_image();
ERR_FAIL_COND(!image.is_valid());
- image = image->duplicate();
+ image = image->duplicate(true);
+
+ if (image->is_compressed()) {
+ Error err = image->decompress();
+ ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock.");
+ }
if (atlas_texture.is_valid()) {
image->crop_from_point(
@@ -771,6 +779,8 @@ DisplayServer *DisplayServerWeb::create_func(const String &p_rendering_driver, W
DisplayServerWeb::DisplayServerWeb(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Error &r_error) {
r_error = OK; // Always succeeds for now.
+ tts = GLOBAL_GET("audio/general/text_to_speech");
+
// Ensure the canvas ID.
godot_js_config_canvas_id_get(canvas_id, 256);
@@ -866,7 +876,7 @@ bool DisplayServerWeb::has_feature(Feature p_feature) const {
case FEATURE_VIRTUAL_KEYBOARD:
return godot_js_display_vk_available() != 0;
case FEATURE_TEXT_TO_SPEECH:
- return godot_js_display_tts_available() != 0;
+ return tts && (godot_js_display_tts_available() != 0);
default:
return false;
}
@@ -1070,6 +1080,10 @@ bool DisplayServerWeb::can_any_window_draw() const {
return true;
}
+DisplayServer::VSyncMode DisplayServerWeb::window_get_vsync_mode(WindowID p_vsync_mode) const {
+ return DisplayServer::VSYNC_ENABLED;
+}
+
void DisplayServerWeb::process_events() {
Input::get_singleton()->flush_buffered_events();
if (godot_js_input_gamepad_sample() == OK) {
diff --git a/platform/web/display_server_web.h b/platform/web/display_server_web.h
index 2e50a6bbc8..3b03b102cd 100644
--- a/platform/web/display_server_web.h
+++ b/platform/web/display_server_web.h
@@ -37,6 +37,8 @@
#include <emscripten/html5.h>
class DisplayServerWeb : public DisplayServer {
+ // No need to register with GDCLASS, it's platform-specific and nothing is added.
+
private:
struct JSTouchEvent {
uint32_t identifier[32] = { 0 };
@@ -79,6 +81,7 @@ private:
MouseButton last_click_button_index = MouseButton::NONE;
bool swap_cancel_ok = false;
+ bool tts = false;
// utilities
static void dom2godot_mod(Ref<InputEventWithModifiers> ev, int p_mod, Key p_keycode);
@@ -213,6 +216,8 @@ public:
virtual bool can_any_window_draw() const override;
+ virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override;
+
// events
virtual void process_events() override;
diff --git a/platform/web/doc_classes/EditorExportPlatformWeb.xml b/platform/web/doc_classes/EditorExportPlatformWeb.xml
new file mode 100644
index 0000000000..6e5a2ac078
--- /dev/null
+++ b/platform/web/doc_classes/EditorExportPlatformWeb.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EditorExportPlatformWeb" inherits="EditorExportPlatform" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Exporter for the Web.
+ </brief_description>
+ <description>
+ </description>
+ <tutorials>
+ <link title="Exporting for the Web">$DOCS_URL/tutorials/export/exporting_for_web.html</link>
+ </tutorials>
+ <members>
+ <member name="custom_template/debug" type="String" setter="" getter="">
+ Path to the custom export template. If left empty, default template is used.
+ </member>
+ <member name="custom_template/release" type="String" setter="" getter="">
+ Path to the custom export template. If left empty, default template is used.
+ </member>
+ <member name="html/canvas_resize_policy" type="int" setter="" getter="">
+ The canvas resize policy determines how the canvas should be resized by Godot.
+ </member>
+ <member name="html/custom_html_shell" type="String" setter="" getter="">
+ </member>
+ <member name="html/experimental_virtual_keyboard" type="bool" setter="" getter="">
+ </member>
+ <member name="html/export_icon" type="bool" setter="" getter="">
+ </member>
+ <member name="html/focus_canvas_on_start" type="bool" setter="" getter="">
+ </member>
+ <member name="html/head_include" type="String" setter="" getter="">
+ </member>
+ <member name="progressive_web_app/background_color" type="Color" setter="" getter="">
+ </member>
+ <member name="progressive_web_app/display" type="int" setter="" getter="">
+ </member>
+ <member name="progressive_web_app/enabled" type="bool" setter="" getter="">
+ </member>
+ <member name="progressive_web_app/icon_144x144" type="String" setter="" getter="">
+ </member>
+ <member name="progressive_web_app/icon_180x180" type="String" setter="" getter="">
+ </member>
+ <member name="progressive_web_app/icon_512x512" type="String" setter="" getter="">
+ </member>
+ <member name="progressive_web_app/offline_page" type="String" setter="" getter="">
+ </member>
+ <member name="progressive_web_app/orientation" type="int" setter="" getter="">
+ </member>
+ <member name="variant/extensions_support" type="bool" setter="" getter="">
+ </member>
+ <member name="vram_texture_compression/for_desktop" type="bool" setter="" getter="">
+ </member>
+ <member name="vram_texture_compression/for_mobile" type="bool" setter="" getter="">
+ </member>
+ </members>
+</class>
diff --git a/platform/web/dom_keys.inc b/platform/web/dom_keys.inc
index ae3b2fc1a5..cd94b779c0 100644
--- a/platform/web/dom_keys.inc
+++ b/platform/web/dom_keys.inc
@@ -33,7 +33,7 @@
// See https://w3c.github.io/uievents-code/#code-value-tables
Key dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], bool p_physical) {
#define DOM2GODOT(p_str, p_godot_code) \
- if (memcmp((const void *)p_str, (void *)(p_physical ? p_key : p_code), strlen(p_str) + 1) == 0) { \
+ if (memcmp((const void *)p_str, (void *)(p_physical ? p_code : p_key), strlen(p_str) + 1) == 0) { \
return Key::p_godot_code; \
}
diff --git a/platform/web/export/export.cpp b/platform/web/export/export.cpp
index 11e728ea16..80c29024a8 100644
--- a/platform/web/export/export.cpp
+++ b/platform/web/export/export.cpp
@@ -31,8 +31,13 @@
#include "export.h"
#include "editor/editor_settings.h"
+#include "editor/export/editor_export.h"
#include "export_plugin.h"
+void register_web_exporter_types() {
+ GDREGISTER_VIRTUAL_CLASS(EditorExportPlatformWeb);
+}
+
void register_web_exporter() {
#ifndef ANDROID_ENABLED
EDITOR_DEF("export/web/http_host", "localhost");
diff --git a/platform/web/export/export.h b/platform/web/export/export.h
index 8d2bbfff26..da02bd8d93 100644
--- a/platform/web/export/export.h
+++ b/platform/web/export/export.h
@@ -31,6 +31,7 @@
#ifndef WEB_EXPORT_H
#define WEB_EXPORT_H
+void register_web_exporter_types();
void register_web_exporter();
#endif // WEB_EXPORT_H
diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp
index d8e04904c7..2fff628c85 100644
--- a/platform/web/export/export_plugin.cpp
+++ b/platform/web/export/export_plugin.cpp
@@ -33,6 +33,7 @@
#include "core/config/project_settings.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
+#include "editor/export/editor_export.h"
#include "platform/web/logo_svg.gen.h"
#include "platform/web/run_icon_svg.gen.h"
@@ -320,7 +321,7 @@ void EditorExportPlatformWeb::get_preset_features(const Ref<EditorExportPreset>
r_features->push_back("wasm32");
}
-void EditorExportPlatformWeb::get_export_options(List<ExportOption> *r_options) {
+void EditorExportPlatformWeb::get_export_options(List<ExportOption> *r_options) const {
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
@@ -656,31 +657,37 @@ void EditorExportPlatformWeb::_server_thread_poll(void *data) {
}
EditorExportPlatformWeb::EditorExportPlatformWeb() {
- server.instantiate();
- server_thread.start(_server_thread_poll, this);
+ if (EditorNode::get_singleton()) {
+ server.instantiate();
+ server_thread.start(_server_thread_poll, this);
#ifdef MODULE_SVG_ENABLED
- Ref<Image> img = memnew(Image);
- const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE);
+ Ref<Image> img = memnew(Image);
+ const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE);
- ImageLoaderSVG img_loader;
- img_loader.create_image_from_string(img, _web_logo_svg, EDSCALE, upsample, false);
- logo = ImageTexture::create_from_image(img);
+ ImageLoaderSVG img_loader;
+ img_loader.create_image_from_string(img, _web_logo_svg, EDSCALE, upsample, false);
+ logo = ImageTexture::create_from_image(img);
- img_loader.create_image_from_string(img, _web_run_icon_svg, EDSCALE, upsample, false);
- run_icon = ImageTexture::create_from_image(img);
+ img_loader.create_image_from_string(img, _web_run_icon_svg, EDSCALE, upsample, false);
+ run_icon = ImageTexture::create_from_image(img);
#endif
- Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();
- if (theme.is_valid()) {
- stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons"));
- } else {
- stop_icon.instantiate();
+ Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();
+ if (theme.is_valid()) {
+ stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons"));
+ } else {
+ stop_icon.instantiate();
+ }
}
}
EditorExportPlatformWeb::~EditorExportPlatformWeb() {
- server->stop();
+ if (server.is_valid()) {
+ server->stop();
+ }
server_quit = true;
- server_thread.wait_to_finish();
+ if (server_thread.is_started()) {
+ server_thread.wait_to_finish();
+ }
}
diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h
index e74c945837..334f12d64d 100644
--- a/platform/web/export/export_plugin.h
+++ b/platform/web/export/export_plugin.h
@@ -99,7 +99,7 @@ class EditorExportPlatformWeb : public EditorExportPlatform {
public:
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override;
- virtual void get_export_options(List<ExportOption> *r_options) override;
+ virtual void get_export_options(List<ExportOption> *r_options) const override;
virtual String get_name() const override;
virtual String get_os_name() const override;
diff --git a/platform/web/godot_js.h b/platform/web/godot_js.h
index 3a41f63fa3..660822e291 100644
--- a/platform/web/godot_js.h
+++ b/platform/web/godot_js.h
@@ -49,6 +49,7 @@ extern void godot_js_os_fs_sync(void (*p_callback)());
extern int godot_js_os_execute(const char *p_json);
extern void godot_js_os_shell_open(const char *p_uri);
extern int godot_js_os_hw_concurrency_get();
+extern int godot_js_os_has_feature(const char *p_ftr);
extern int godot_js_pwa_cb(void (*p_callback)());
extern int godot_js_pwa_update();
diff --git a/platform/web/js/libs/library_godot_fetch.js b/platform/web/js/libs/library_godot_fetch.js
index b50012c1e2..1bb48bfd6a 100644
--- a/platform/web/js/libs/library_godot_fetch.js
+++ b/platform/web/js/libs/library_godot_fetch.js
@@ -50,17 +50,22 @@ const GodotFetch = {
return;
}
let chunked = false;
+ let bodySize = -1;
response.headers.forEach(function (value, header) {
const v = value.toLowerCase().trim();
const h = header.toLowerCase().trim();
if (h === 'transfer-encoding' && v === 'chunked') {
chunked = true;
}
+ if (h === 'content-length') {
+ bodySize = parseInt(v, 10);
+ }
});
obj.status = response.status;
obj.response = response;
obj.reader = response.body.getReader();
obj.chunked = chunked;
+ obj.bodySize = bodySize;
},
onerror: function (id, err) {
diff --git a/platform/web/js/libs/library_godot_os.js b/platform/web/js/libs/library_godot_os.js
index c4c45a3036..00ae399583 100644
--- a/platform/web/js/libs/library_godot_os.js
+++ b/platform/web/js/libs/library_godot_os.js
@@ -291,6 +291,28 @@ const GodotOS = {
});
},
+ godot_js_os_has_feature__sig: 'ii',
+ godot_js_os_has_feature: function (p_ftr) {
+ const ftr = GodotRuntime.parseString(p_ftr);
+ const ua = navigator.userAgent;
+ if (ftr === 'web_macos') {
+ return (ua.indexOf('Mac') !== -1) ? 1 : 0;
+ }
+ if (ftr === 'web_windows') {
+ return (ua.indexOf('Windows') !== -1) ? 1 : 0;
+ }
+ if (ftr === 'web_android') {
+ return (ua.indexOf('Android') !== -1) ? 1 : 0;
+ }
+ if (ftr === 'web_ios') {
+ return ((ua.indexOf('iPhone') !== -1) || (ua.indexOf('iPad') !== -1) || (ua.indexOf('iPod') !== -1)) ? 1 : 0;
+ }
+ if (ftr === 'web_linuxbsd') {
+ return ((ua.indexOf('CrOS') !== -1) || (ua.indexOf('BSD') !== -1) || (ua.indexOf('Linux') !== -1) || (ua.indexOf('X11') !== -1)) ? 1 : 0;
+ }
+ return 0;
+ },
+
godot_js_os_execute__sig: 'ii',
godot_js_os_execute: function (p_json) {
const json_args = GodotRuntime.parseString(p_json);
diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp
index 964bce01da..0f84e7e841 100644
--- a/platform/web/os_web.cpp
+++ b/platform/web/os_web.cpp
@@ -136,6 +136,9 @@ bool OS_Web::_check_internal_feature_support(const String &p_feature) {
if (p_feature == "web") {
return true;
}
+ if (godot_js_os_has_feature(p_feature.utf8().get_data())) {
+ return true;
+ }
return false;
}