Path: blob/master/platform/web/display_server_web.cpp
20871 views
/**************************************************************************/1/* display_server_web.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "display_server_web.h"3132#include "dom_keys.inc"33#include "godot_js.h"34#include "os_web.h"3536#include "core/config/project_settings.h"37#include "core/input/input.h"38#include "core/object/callable_method_pointer.h"39#include "core/os/main_loop.h"40#include "servers/rendering/dummy/rasterizer_dummy.h"4142#ifdef GLES3_ENABLED43#include "drivers/gles3/rasterizer_gles3.h"44#endif4546#include <emscripten.h>47#include <png.h>4849#define DOM_BUTTON_LEFT 050#define DOM_BUTTON_MIDDLE 151#define DOM_BUTTON_RIGHT 252#define DOM_BUTTON_XBUTTON1 353#define DOM_BUTTON_XBUTTON2 45455DisplayServerWeb *DisplayServerWeb::get_singleton() {56return static_cast<DisplayServerWeb *>(DisplayServer::get_singleton());57}5859// Window (canvas)60bool DisplayServerWeb::check_size_force_redraw() {61bool size_changed = godot_js_display_size_update() != 0;62if (size_changed && rect_changed_callback.is_valid()) {63Size2i window_size = window_get_size();64Variant size = Rect2i(Point2i(), window_size); // TODO use window_get_position if implemented.65rect_changed_callback.call(size);66emscripten_set_canvas_element_size(canvas_id, window_size.x, window_size.y);67}68return size_changed;69}7071void DisplayServerWeb::fullscreen_change_callback(int p_fullscreen) {72#ifdef PROXY_TO_PTHREAD_ENABLED73if (!Thread::is_main_thread()) {74callable_mp_static(DisplayServerWeb::_fullscreen_change_callback).call_deferred(p_fullscreen);75return;76}77#endif7879_fullscreen_change_callback(p_fullscreen);80}8182void DisplayServerWeb::_fullscreen_change_callback(int p_fullscreen) {83DisplayServerWeb *display = get_singleton();84if (p_fullscreen) {85display->window_mode = WINDOW_MODE_FULLSCREEN;86} else {87display->window_mode = WINDOW_MODE_WINDOWED;88}89}9091// Drag and drop callback.92void DisplayServerWeb::drop_files_js_callback(const char **p_filev, int p_filec) {93Vector<String> files;94for (int i = 0; i < p_filec; i++) {95files.push_back(String::utf8(p_filev[i]));96}9798#ifdef PROXY_TO_PTHREAD_ENABLED99if (!Thread::is_main_thread()) {100callable_mp_static(DisplayServerWeb::_drop_files_js_callback).call_deferred(files);101return;102}103#endif104105_drop_files_js_callback(files);106}107108void DisplayServerWeb::_drop_files_js_callback(const Vector<String> &p_files) {109DisplayServerWeb *ds = get_singleton();110if (!ds) {111ERR_FAIL_MSG("Unable to drop files because the DisplayServer is not active");112}113if (!ds->drop_files_callback.is_valid()) {114return;115}116Variant v_files = p_files;117const Variant *v_args[1] = { &v_files };118Variant ret;119Callable::CallError ce;120ds->drop_files_callback.callp((const Variant **)&v_args, 1, ret, ce);121if (ce.error != Callable::CallError::CALL_OK) {122ERR_PRINT(vformat("Failed to execute drop files callback: %s.", Variant::get_callable_error_text(ds->drop_files_callback, v_args, 1, ce)));123}124}125126// Web quit request callback.127void DisplayServerWeb::request_quit_callback() {128#ifdef PROXY_TO_PTHREAD_ENABLED129if (!Thread::is_main_thread()) {130callable_mp_static(DisplayServerWeb::_request_quit_callback).call_deferred();131return;132}133#endif134135_request_quit_callback();136}137138void DisplayServerWeb::_request_quit_callback() {139DisplayServerWeb *ds = get_singleton();140if (ds && ds->window_event_callback.is_valid()) {141Variant event = int(DisplayServer::WINDOW_EVENT_CLOSE_REQUEST);142ds->window_event_callback.call(event);143}144}145146// Keys147148void DisplayServerWeb::dom2godot_mod(Ref<InputEventWithModifiers> ev, int p_mod, Key p_keycode) {149if (p_keycode != Key::SHIFT) {150ev->set_shift_pressed(p_mod & 1);151}152if (p_keycode != Key::ALT) {153ev->set_alt_pressed(p_mod & 2);154}155if (p_keycode != Key::CTRL) {156ev->set_ctrl_pressed(p_mod & 4);157}158if (p_keycode != Key::META) {159ev->set_meta_pressed(p_mod & 8);160}161}162163void DisplayServerWeb::key_callback(int p_pressed, int p_repeat, int p_modifiers) {164DisplayServerWeb *ds = get_singleton();165JSKeyEvent &key_event = ds->key_event;166167const String code = String::utf8(key_event.code);168const String key = String::utf8(key_event.key);169170#ifdef PROXY_TO_PTHREAD_ENABLED171if (!Thread::is_main_thread()) {172callable_mp_static(DisplayServerWeb::_key_callback).call_deferred(code, key, p_pressed, p_repeat, p_modifiers);173return;174}175#endif176177_key_callback(code, key, p_pressed, p_repeat, p_modifiers);178}179180void DisplayServerWeb::_key_callback(const String &p_key_event_code, const String &p_key_event_key, int p_pressed, int p_repeat, int p_modifiers) {181// Resume audio context after input in case autoplay was denied.182OS_Web::get_singleton()->resume_audio();183184DisplayServerWeb *ds = get_singleton();185if (ds->ime_started) {186return;187}188189char32_t c = 0x00;190String unicode = p_key_event_key;191if (unicode.length() == 1) {192c = unicode[0];193}194195Key keycode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), false);196Key scancode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), true);197KeyLocation location = dom_code2godot_key_location(p_key_event_code.utf8().get_data());198199DisplayServerWeb::KeyEvent ke;200201ke.pressed = p_pressed;202ke.echo = p_repeat;203ke.raw = true;204ke.keycode = fix_keycode(c, keycode);205ke.physical_keycode = scancode;206ke.key_label = fix_key_label(c, keycode);207ke.unicode = fix_unicode(c);208ke.location = location;209ke.mod = p_modifiers;210211if (ds->key_event_pos >= ds->key_event_buffer.size()) {212ds->key_event_buffer.resize(1 + ds->key_event_pos);213}214ds->key_event_buffer.write[ds->key_event_pos++] = ke;215216// Make sure to flush all events so we can call restricted APIs inside the event.217Input::get_singleton()->flush_buffered_events();218}219220// Mouse221222int DisplayServerWeb::mouse_button_callback(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers) {223#ifdef PROXY_TO_PTHREAD_ENABLED224if (!Thread::is_main_thread()) {225callable_mp_static(DisplayServerWeb::_mouse_button_callback).call_deferred(p_pressed, p_button, p_x, p_y, p_modifiers);226return true;227}228#endif229230return _mouse_button_callback(p_pressed, p_button, p_x, p_y, p_modifiers);231}232233int DisplayServerWeb::_mouse_button_callback(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers) {234DisplayServerWeb *ds = get_singleton();235236Point2 pos(p_x, p_y);237Ref<InputEventMouseButton> ev;238ev.instantiate();239ev->set_position(pos);240ev->set_global_position(pos);241ev->set_pressed(p_pressed);242dom2godot_mod(ev, p_modifiers, Key::NONE);243244switch (p_button) {245case DOM_BUTTON_LEFT:246ev->set_button_index(MouseButton::LEFT);247break;248case DOM_BUTTON_MIDDLE:249ev->set_button_index(MouseButton::MIDDLE);250break;251case DOM_BUTTON_RIGHT:252ev->set_button_index(MouseButton::RIGHT);253break;254case DOM_BUTTON_XBUTTON1:255ev->set_button_index(MouseButton::MB_XBUTTON1);256break;257case DOM_BUTTON_XBUTTON2:258ev->set_button_index(MouseButton::MB_XBUTTON2);259break;260default:261return false;262}263264if (p_pressed) {265uint64_t diff = (OS::get_singleton()->get_ticks_usec() / 1000) - ds->last_click_ms;266267if (ev->get_button_index() == ds->last_click_button_index) {268if (diff < 400 && Point2(ds->last_click_pos).distance_to(ev->get_position()) < 5) {269ds->last_click_ms = 0;270ds->last_click_pos = Point2(-100, -100);271ds->last_click_button_index = MouseButton::NONE;272ev->set_double_click(true);273}274275} else {276ds->last_click_button_index = ev->get_button_index();277}278279if (!ev->is_double_click()) {280ds->last_click_ms += diff;281ds->last_click_pos = ev->get_position();282}283}284285BitField<MouseButtonMask> mask = Input::get_singleton()->get_mouse_button_mask();286MouseButtonMask button_flag = mouse_button_to_mask(ev->get_button_index());287if (ev->is_pressed()) {288mask.set_flag(button_flag);289} else if (mask.has_flag(button_flag)) {290mask.clear_flag(button_flag);291} else {292// Received release event, but press was outside the canvas, so ignore.293return false;294}295ev->set_button_mask(mask);296297Input::get_singleton()->parse_input_event(ev);298// Resume audio context after input in case autoplay was denied.299OS_Web::get_singleton()->resume_audio();300301// Make sure to flush all events so we can call restricted APIs inside the event.302Input::get_singleton()->flush_buffered_events();303304// Prevent multi-click text selection and wheel-click scrolling anchor.305// Context menu is prevented through contextmenu event.306return true;307}308309void DisplayServerWeb::mouse_move_callback(double p_x, double p_y, double p_rel_x, double p_rel_y, int p_modifiers, double p_pressure) {310#ifdef PROXY_TO_PTHREAD_ENABLED311if (!Thread::is_main_thread()) {312callable_mp_static(DisplayServerWeb::_mouse_move_callback).call_deferred(p_x, p_y, p_rel_x, p_rel_y, p_modifiers, p_pressure);313return;314}315#endif316317_mouse_move_callback(p_x, p_y, p_rel_x, p_rel_y, p_modifiers, p_pressure);318}319320void DisplayServerWeb::_mouse_move_callback(double p_x, double p_y, double p_rel_x, double p_rel_y, int p_modifiers, double p_pressure) {321BitField<MouseButtonMask> input_mask = Input::get_singleton()->get_mouse_button_mask();322// For motion outside the canvas, only read mouse movement if dragging323// started inside the canvas; imitating desktop app behavior.324if (!get_singleton()->cursor_inside_canvas && input_mask.is_empty()) {325return;326}327328Point2 pos(p_x, p_y);329Ref<InputEventMouseMotion> ev;330ev.instantiate();331dom2godot_mod(ev, p_modifiers, Key::NONE);332ev->set_button_mask(input_mask);333334ev->set_position(pos);335ev->set_global_position(pos);336ev->set_pressure((float)p_pressure);337338ev->set_relative(Vector2(p_rel_x, p_rel_y));339ev->set_relative_screen_position(ev->get_relative());340ev->set_velocity(Input::get_singleton()->get_last_mouse_velocity());341ev->set_screen_velocity(ev->get_velocity());342343Input::get_singleton()->parse_input_event(ev);344}345346// Cursor347const char *DisplayServerWeb::godot2dom_cursor(DisplayServer::CursorShape p_shape) {348switch (p_shape) {349case DisplayServer::CURSOR_ARROW:350return "default";351case DisplayServer::CURSOR_IBEAM:352return "text";353case DisplayServer::CURSOR_POINTING_HAND:354return "pointer";355case DisplayServer::CURSOR_CROSS:356return "crosshair";357case DisplayServer::CURSOR_WAIT:358return "wait";359case DisplayServer::CURSOR_BUSY:360return "progress";361case DisplayServer::CURSOR_DRAG:362return "grab";363case DisplayServer::CURSOR_CAN_DROP:364return "grabbing";365case DisplayServer::CURSOR_FORBIDDEN:366return "no-drop";367case DisplayServer::CURSOR_VSIZE:368return "ns-resize";369case DisplayServer::CURSOR_HSIZE:370return "ew-resize";371case DisplayServer::CURSOR_BDIAGSIZE:372return "nesw-resize";373case DisplayServer::CURSOR_FDIAGSIZE:374return "nwse-resize";375case DisplayServer::CURSOR_MOVE:376return "move";377case DisplayServer::CURSOR_VSPLIT:378return "row-resize";379case DisplayServer::CURSOR_HSPLIT:380return "col-resize";381case DisplayServer::CURSOR_HELP:382return "help";383default:384return "default";385}386}387388bool DisplayServerWeb::tts_is_speaking() const {389return godot_js_tts_is_speaking();390}391392bool DisplayServerWeb::tts_is_paused() const {393return godot_js_tts_is_paused();394}395396void DisplayServerWeb::update_voices_callback(int p_size, const char **p_voice) {397Vector<String> voices;398for (int i = 0; i < p_size; i++) {399voices.append(String::utf8(p_voice[i]));400}401402#ifdef PROXY_TO_PTHREAD_ENABLED403if (!Thread::is_main_thread()) {404callable_mp_static(DisplayServerWeb::_update_voices_callback).call_deferred(voices);405return;406}407#endif408409_update_voices_callback(voices);410}411412void DisplayServerWeb::_update_voices_callback(const Vector<String> &p_voices) {413get_singleton()->voices.clear();414for (int i = 0; i < p_voices.size(); i++) {415Vector<String> tokens = p_voices[i].split(";", true, 2);416if (tokens.size() == 2) {417Dictionary voice_d;418voice_d["name"] = tokens[1];419voice_d["id"] = tokens[1];420voice_d["language"] = tokens[0];421get_singleton()->voices.push_back(voice_d);422}423}424}425426TypedArray<Dictionary> DisplayServerWeb::tts_get_voices() const {427godot_js_tts_get_voices(update_voices_callback);428return voices;429}430431void DisplayServerWeb::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int64_t p_utterance_id, bool p_interrupt) {432if (p_interrupt) {433tts_stop();434}435436if (p_text.is_empty()) {437tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, p_utterance_id);438return;439}440441CharString string = p_text.utf8();442utterance_ids[p_utterance_id] = string;443444godot_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, DisplayServerWeb::js_utterance_callback);445}446447void DisplayServerWeb::tts_pause() {448godot_js_tts_pause();449}450451void DisplayServerWeb::tts_resume() {452godot_js_tts_resume();453}454455void DisplayServerWeb::tts_stop() {456for (const KeyValue<int64_t, CharString> &E : utterance_ids) {457tts_post_utterance_event(DisplayServer::TTS_UTTERANCE_CANCELED, E.key);458}459utterance_ids.clear();460godot_js_tts_stop();461}462463void DisplayServerWeb::js_utterance_callback(int p_event, int64_t p_id, int p_pos) {464#ifdef PROXY_TO_PTHREAD_ENABLED465if (!Thread::is_main_thread()) {466callable_mp_static(DisplayServerWeb::_js_utterance_callback).call_deferred(p_event, p_id, p_pos);467return;468}469#endif470471_js_utterance_callback(p_event, p_id, p_pos);472}473474void DisplayServerWeb::_js_utterance_callback(int p_event, int64_t p_id, int p_pos) {475DisplayServerWeb *ds = (DisplayServerWeb *)DisplayServer::get_singleton();476if (ds->utterance_ids.has(p_id)) {477int pos = 0;478if ((TTSUtteranceEvent)p_event == DisplayServer::TTS_UTTERANCE_BOUNDARY) {479// Convert position from UTF-8 to UTF-32.480const CharString &string = ds->utterance_ids[p_id];481for (int i = 0; i < MIN(p_pos, string.length()); i++) {482uint8_t c = string[i];483if ((c & 0xe0) == 0xc0) {484i += 1;485} else if ((c & 0xf0) == 0xe0) {486i += 2;487} else if ((c & 0xf8) == 0xf0) {488i += 3;489}490pos++;491}492} else if ((TTSUtteranceEvent)p_event != DisplayServer::TTS_UTTERANCE_STARTED) {493ds->utterance_ids.erase(p_id);494}495ds->tts_post_utterance_event((TTSUtteranceEvent)p_event, p_id, pos);496}497}498499void DisplayServerWeb::cursor_set_shape(CursorShape p_shape) {500ERR_FAIL_INDEX(p_shape, CURSOR_MAX);501if (cursor_shape == p_shape) {502return;503}504cursor_shape = p_shape;505godot_js_display_cursor_set_shape(godot2dom_cursor(cursor_shape));506}507508DisplayServer::CursorShape DisplayServerWeb::cursor_get_shape() const {509return cursor_shape;510}511512void DisplayServerWeb::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {513ERR_FAIL_INDEX(p_shape, CURSOR_MAX);514if (p_cursor.is_valid()) {515Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot);516ERR_FAIL_COND(image.is_null());517Vector2i texture_size = image->get_size();518519if (image->get_format() != Image::FORMAT_RGBA8) {520image->convert(Image::FORMAT_RGBA8);521}522523png_image png_meta;524memset(&png_meta, 0, sizeof png_meta);525png_meta.version = PNG_IMAGE_VERSION;526png_meta.width = texture_size.width;527png_meta.height = texture_size.height;528png_meta.format = PNG_FORMAT_RGBA;529530PackedByteArray png;531size_t len;532PackedByteArray data = image->get_data();533ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, data.ptr(), 0, nullptr));534535png.resize(len);536ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr));537538godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), png.ptr(), len, p_hotspot.x, p_hotspot.y);539540} else {541godot_js_display_cursor_set_custom_shape(godot2dom_cursor(p_shape), nullptr, 0, 0, 0);542}543544cursor_set_shape(cursor_shape);545}546547// Mouse mode548void DisplayServerWeb::_mouse_update_mode() {549MouseMode wanted_mouse_mode = mouse_mode_override_enabled550? mouse_mode_override551: mouse_mode_base;552553ERR_FAIL_COND_MSG(wanted_mouse_mode == MOUSE_MODE_CONFINED || wanted_mouse_mode == MOUSE_MODE_CONFINED_HIDDEN, "MOUSE_MODE_CONFINED is not supported for the Web platform.");554if (wanted_mouse_mode == mouse_get_mode()) {555return;556}557558if (wanted_mouse_mode == MOUSE_MODE_VISIBLE) {559godot_js_display_cursor_set_visible(1);560godot_js_display_cursor_lock_set(0);561562} else if (wanted_mouse_mode == MOUSE_MODE_HIDDEN) {563godot_js_display_cursor_set_visible(0);564godot_js_display_cursor_lock_set(0);565566} else if (wanted_mouse_mode == MOUSE_MODE_CAPTURED) {567godot_js_display_cursor_set_visible(1);568godot_js_display_cursor_lock_set(1);569}570}571572void DisplayServerWeb::mouse_set_mode(MouseMode p_mode) {573ERR_FAIL_INDEX(p_mode, MouseMode::MOUSE_MODE_MAX);574575if (mouse_mode_override_enabled) {576mouse_mode_base = p_mode;577// No need to update, as override is enabled.578return;579}580if (p_mode == mouse_mode_base && p_mode == mouse_get_mode()) {581// No need to update, as it is currently set as the correct mode.582return;583}584585mouse_mode_base = p_mode;586_mouse_update_mode();587}588589DisplayServer::MouseMode DisplayServerWeb::mouse_get_mode() const {590if (godot_js_display_cursor_is_hidden()) {591return MOUSE_MODE_HIDDEN;592}593594if (godot_js_display_cursor_is_locked()) {595return MOUSE_MODE_CAPTURED;596}597return MOUSE_MODE_VISIBLE;598}599600void DisplayServerWeb::mouse_set_mode_override(MouseMode p_mode) {601ERR_FAIL_INDEX(p_mode, MouseMode::MOUSE_MODE_MAX);602603if (!mouse_mode_override_enabled) {604mouse_mode_override = p_mode;605// No need to update, as override is not enabled.606return;607}608if (p_mode == mouse_mode_override && p_mode == mouse_get_mode()) {609// No need to update, as it is currently set as the correct mode.610return;611}612613mouse_mode_override = p_mode;614_mouse_update_mode();615}616617DisplayServer::MouseMode DisplayServerWeb::mouse_get_mode_override() const {618return mouse_mode_override;619}620621void DisplayServerWeb::mouse_set_mode_override_enabled(bool p_override_enabled) {622if (p_override_enabled == mouse_mode_override_enabled) {623return;624}625mouse_mode_override_enabled = p_override_enabled;626_mouse_update_mode();627}628629bool DisplayServerWeb::mouse_is_mode_override_enabled() const {630return mouse_mode_override_enabled;631}632633Point2i DisplayServerWeb::mouse_get_position() const {634return Input::get_singleton()->get_mouse_position();635}636637// Wheel638int DisplayServerWeb::mouse_wheel_callback(int p_delta_mode, double p_delta_x, double p_delta_y) {639#ifdef PROXY_TO_PTHREAD_ENABLED640if (!Thread::is_main_thread()) {641callable_mp_static(DisplayServerWeb::_mouse_wheel_callback).call_deferred(p_delta_mode, p_delta_x, p_delta_y);642return true;643}644#endif645646return _mouse_wheel_callback(p_delta_mode, p_delta_x, p_delta_y);647}648649int DisplayServerWeb::_mouse_wheel_callback(int p_delta_mode, double p_delta_x, double p_delta_y) {650if (!godot_js_display_canvas_is_focused() && !godot_js_is_ime_focused()) {651if (get_singleton()->cursor_inside_canvas) {652godot_js_display_canvas_focus();653} else {654return false;655}656}657658Input *input = Input::get_singleton();659Ref<InputEventMouseButton> ev;660ev.instantiate();661ev->set_position(input->get_mouse_position());662ev->set_global_position(ev->get_position());663664ev->set_shift_pressed(input->is_key_pressed(Key::SHIFT));665ev->set_alt_pressed(input->is_key_pressed(Key::ALT));666ev->set_ctrl_pressed(input->is_key_pressed(Key::CTRL));667ev->set_meta_pressed(input->is_key_pressed(Key::META));668669enum DeltaMode {670DELTA_MODE_PIXEL = 0,671DELTA_MODE_LINE = 1,672DELTA_MODE_PAGE = 2,673};674const float MOUSE_WHEEL_PIXEL_FACTOR = 0.03f;675const float MOUSE_WHEEL_LINE_FACTOR = 0.3f;676const float MOUSE_WHEEL_PAGE_FACTOR = 1.0f;677float mouse_wheel_factor;678679switch (p_delta_mode) {680case DELTA_MODE_PIXEL: {681mouse_wheel_factor = MOUSE_WHEEL_PIXEL_FACTOR;682} break;683case DELTA_MODE_LINE: {684mouse_wheel_factor = MOUSE_WHEEL_LINE_FACTOR;685} break;686case DELTA_MODE_PAGE: {687mouse_wheel_factor = MOUSE_WHEEL_PAGE_FACTOR;688} break;689}690691if (p_delta_y < 0) {692ev->set_button_index(MouseButton::WHEEL_UP);693ev->set_factor(-p_delta_y * mouse_wheel_factor);694} else if (p_delta_y > 0) {695ev->set_button_index(MouseButton::WHEEL_DOWN);696ev->set_factor(p_delta_y * mouse_wheel_factor);697} else if (p_delta_x > 0) {698ev->set_button_index(MouseButton::WHEEL_LEFT);699ev->set_factor(p_delta_x * mouse_wheel_factor);700} else if (p_delta_x < 0) {701ev->set_button_index(MouseButton::WHEEL_RIGHT);702ev->set_factor(-p_delta_x * mouse_wheel_factor);703} else {704return false;705}706707MouseButtonMask button_flag = mouse_button_to_mask(ev->get_button_index());708BitField<MouseButtonMask> button_mask = input->get_mouse_button_mask();709button_mask.set_flag(button_flag);710711ev->set_pressed(true);712ev->set_button_mask(button_mask);713input->parse_input_event(ev);714715Ref<InputEventMouseButton> release = ev->duplicate();716release->set_pressed(false);717release->set_button_mask(input->get_mouse_button_mask());718input->parse_input_event(release);719720return true;721}722723// Touch724void DisplayServerWeb::touch_callback(int p_type, int p_count) {725#ifdef PROXY_TO_PTHREAD_ENABLED726if (!Thread::is_main_thread()) {727callable_mp_static(DisplayServerWeb::_touch_callback).call_deferred(p_type, p_count);728return;729}730#endif731732_touch_callback(p_type, p_count);733}734735void DisplayServerWeb::_touch_callback(int p_type, int p_count) {736DisplayServerWeb *ds = get_singleton();737738const JSTouchEvent &touch_event = ds->touch_event;739for (int i = 0; i < p_count; i++) {740Point2 point(touch_event.coords[i * 2], touch_event.coords[i * 2 + 1]);741if (p_type == 2) {742// touchmove743Ref<InputEventScreenDrag> ev;744ev.instantiate();745ev->set_index(touch_event.identifier[i]);746ev->set_position(point);747748Point2 &prev = ds->touches[i];749ev->set_relative(ev->get_position() - prev);750ev->set_relative_screen_position(ev->get_relative());751prev = ev->get_position();752753Input::get_singleton()->parse_input_event(ev);754} else {755// touchstart/touchend756Ref<InputEventScreenTouch> ev;757758// Resume audio context after input in case autoplay was denied.759OS_Web::get_singleton()->resume_audio();760761ev.instantiate();762ev->set_index(touch_event.identifier[i]);763ev->set_position(point);764ev->set_pressed(p_type == 0);765ds->touches[i] = point;766767Input::get_singleton()->parse_input_event(ev);768769// Make sure to flush all events so we can call restricted APIs inside the event.770Input::get_singleton()->flush_buffered_events();771}772}773}774775bool DisplayServerWeb::is_touchscreen_available() const {776return godot_js_display_touchscreen_is_available() || (Input::get_singleton() && Input::get_singleton()->is_emulating_touch_from_mouse());777}778779// Virtual Keyboard780void DisplayServerWeb::vk_input_text_callback(const char *p_text, int p_cursor) {781String text = String::utf8(p_text);782783#ifdef PROXY_TO_PTHREAD_ENABLED784if (!Thread::is_main_thread()) {785callable_mp_static(DisplayServerWeb::_vk_input_text_callback).call_deferred(text, p_cursor);786return;787}788#endif789790_vk_input_text_callback(text, p_cursor);791}792793void DisplayServerWeb::_vk_input_text_callback(const String &p_text, int p_cursor) {794DisplayServerWeb *ds = DisplayServerWeb::get_singleton();795if (!ds || !ds->input_text_callback.is_valid()) {796return;797}798// Call input_text799ds->input_text_callback.call(p_text, true);800// Insert key right to reach position.801Input *input = Input::get_singleton();802Ref<InputEventKey> k;803for (int i = 0; i < p_cursor; i++) {804k.instantiate();805k->set_pressed(true);806k->set_echo(false);807k->set_keycode(Key::RIGHT);808input->parse_input_event(k);809k.instantiate();810k->set_pressed(false);811k->set_echo(false);812k->set_keycode(Key::RIGHT);813input->parse_input_event(k);814}815}816817void DisplayServerWeb::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, VirtualKeyboardType p_type, int p_max_input_length, int p_cursor_start, int p_cursor_end) {818godot_js_display_vk_show(p_existing_text.utf8().get_data(), p_type, p_cursor_start, p_cursor_end);819}820821void DisplayServerWeb::virtual_keyboard_hide() {822godot_js_display_vk_hide();823}824825void DisplayServerWeb::window_blur_callback() {826#ifdef PROXY_TO_PTHREAD_ENABLED827if (!Thread::is_main_thread()) {828callable_mp_static(DisplayServerWeb::_window_blur_callback).call_deferred();829return;830}831#endif832833_window_blur_callback();834}835836void DisplayServerWeb::_window_blur_callback() {837Input::get_singleton()->release_pressed_events();838}839840// Gamepad841void DisplayServerWeb::gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid) {842String id = p_id;843String guid = p_guid;844845#ifdef PROXY_TO_PTHREAD_ENABLED846if (!Thread::is_main_thread()) {847callable_mp_static(DisplayServerWeb::_gamepad_callback).call_deferred(p_index, p_connected, id, guid);848return;849}850#endif851852_gamepad_callback(p_index, p_connected, id, guid);853}854855void DisplayServerWeb::_gamepad_callback(int p_index, int p_connected, const String &p_id, const String &p_guid) {856if (p_connected) {857DisplayServerWeb::get_singleton()->gamepad_count += 1;858} else {859DisplayServerWeb::get_singleton()->gamepad_count -= 1;860}861862Input *input = Input::get_singleton();863if (p_connected) {864input->joy_connection_changed(p_index, true, p_id, p_guid);865} else {866input->joy_connection_changed(p_index, false, "");867}868}869870// IME.871void DisplayServerWeb::ime_callback(int p_type, const char *p_text) {872String text = String::utf8(p_text);873874#ifdef PROXY_TO_PTHREAD_ENABLED875if (!Thread::is_main_thread()) {876callable_mp_static(DisplayServerWeb::_ime_callback).call_deferred(p_type, text);877return;878}879#endif880881_ime_callback(p_type, text);882}883884void DisplayServerWeb::_ime_callback(int p_type, const String &p_text) {885DisplayServerWeb *ds = get_singleton();886// Resume audio context after input in case autoplay was denied.887OS_Web::get_singleton()->resume_audio();888889switch (p_type) {890case 0: {891// IME start.892ds->ime_text = String();893ds->ime_selection = Vector2i();894for (int i = ds->key_event_pos - 1; i >= 0; i--) {895// Delete last raw keydown event from query.896if (ds->key_event_buffer[i].pressed && ds->key_event_buffer[i].raw) {897ds->key_event_buffer.remove_at(i);898ds->key_event_pos--;899break;900}901}902OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);903ds->ime_started = true;904} break;905case 1: {906// IME update.907if (ds->ime_active && ds->ime_started) {908ds->ime_text = p_text;909ds->ime_selection = Vector2i(ds->ime_text.length(), ds->ime_text.length());910OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);911}912} break;913case 2: {914// IME commit.915if (ds->ime_active && ds->ime_started) {916ds->ime_started = false;917918ds->ime_text = String();919ds->ime_selection = Vector2i();920OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);921922String text = p_text;923for (int i = 0; i < text.length(); i++) {924DisplayServerWeb::KeyEvent ke;925926ke.pressed = true;927ke.echo = false;928ke.raw = false;929ke.keycode = Key::NONE;930ke.physical_keycode = Key::NONE;931ke.key_label = Key::NONE;932ke.unicode = text[i];933ke.mod = 0;934935if (ds->key_event_pos >= ds->key_event_buffer.size()) {936ds->key_event_buffer.resize(1 + ds->key_event_pos);937}938ds->key_event_buffer.write[ds->key_event_pos++] = ke;939}940}941} break;942default:943break;944}945946ds->process_keys();947Input::get_singleton()->flush_buffered_events();948}949950void DisplayServerWeb::window_set_ime_active(const bool p_active, WindowID p_window) {951ime_active = p_active;952godot_js_set_ime_active(p_active);953}954955void DisplayServerWeb::window_set_ime_position(const Point2i &p_pos, WindowID p_window) {956godot_js_set_ime_position(p_pos.x, p_pos.y);957}958959Point2i DisplayServerWeb::ime_get_selection() const {960return ime_selection;961}962963String DisplayServerWeb::ime_get_text() const {964return ime_text;965}966967void DisplayServerWeb::process_joypads() {968Input *input = Input::get_singleton();969int32_t pads = godot_js_input_gamepad_sample_count();970int32_t s_btns_num = 0;971int32_t s_axes_num = 0;972int32_t s_standard = 0;973float s_btns[16];974float s_axes[10];975for (int idx = 0; idx < pads; idx++) {976int err = godot_js_input_gamepad_sample_get(idx, s_btns, &s_btns_num, s_axes, &s_axes_num, &s_standard);977if (err) {978continue;979}980for (int b = 0; b < s_btns_num; b++) {981// Buttons 6 and 7 in the standard mapping need to be982// axis to be handled as JoyAxis::TRIGGER by Godot.983if (s_standard && (b == 6)) {984input->joy_axis(idx, JoyAxis::TRIGGER_LEFT, s_btns[b]);985} else if (s_standard && (b == 7)) {986input->joy_axis(idx, JoyAxis::TRIGGER_RIGHT, s_btns[b]);987} else {988input->joy_button(idx, (JoyButton)b, s_btns[b]);989}990}991for (int a = 0; a < s_axes_num; a++) {992input->joy_axis(idx, (JoyAxis)a, s_axes[a]);993}994}995}996997Vector<String> DisplayServerWeb::get_rendering_drivers_func() {998Vector<String> drivers;999#ifdef GLES3_ENABLED1000drivers.push_back("opengl3");1001#endif1002return drivers;1003}10041005// Clipboard1006void DisplayServerWeb::update_clipboard_callback(const char *p_text) {1007String text = String::utf8(p_text);10081009#ifdef PROXY_TO_PTHREAD_ENABLED1010if (!Thread::is_main_thread()) {1011callable_mp_static(DisplayServerWeb::_update_clipboard_callback).call_deferred(text);1012return;1013}1014#endif10151016_update_clipboard_callback(text);1017}10181019void DisplayServerWeb::_update_clipboard_callback(const String &p_text) {1020get_singleton()->clipboard = p_text;1021}10221023void DisplayServerWeb::clipboard_set(const String &p_text) {1024clipboard = p_text;1025int err = godot_js_display_clipboard_set(p_text.utf8().get_data());1026ERR_FAIL_COND_MSG(err, "Clipboard API is not supported.");1027}10281029String DisplayServerWeb::clipboard_get() const {1030godot_js_display_clipboard_get(update_clipboard_callback);1031return clipboard;1032}10331034void DisplayServerWeb::send_window_event_callback(int p_notification) {1035#ifdef PROXY_TO_PTHREAD_ENABLED1036if (!Thread::is_main_thread()) {1037callable_mp_static(DisplayServerWeb::_send_window_event_callback).call_deferred(p_notification);1038return;1039}1040#endif10411042_send_window_event_callback(p_notification);1043}10441045void DisplayServerWeb::_send_window_event_callback(int p_notification) {1046DisplayServerWeb *ds = get_singleton();1047if (!ds) {1048return;1049}1050if (p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER || p_notification == DisplayServer::WINDOW_EVENT_MOUSE_EXIT) {1051ds->cursor_inside_canvas = p_notification == DisplayServer::WINDOW_EVENT_MOUSE_ENTER;1052}1053if (godot_js_is_ime_focused() && (p_notification == DisplayServer::WINDOW_EVENT_FOCUS_IN || p_notification == DisplayServer::WINDOW_EVENT_FOCUS_OUT)) {1054return;1055}1056if (ds->window_event_callback.is_valid()) {1057Variant event = int(p_notification);1058ds->window_event_callback.call(event);1059}1060}10611062void DisplayServerWeb::set_icon(const Ref<Image> &p_icon) {1063if (p_icon.is_valid()) {1064ERR_FAIL_COND(p_icon->get_width() <= 0 || p_icon->get_height() <= 0);10651066Ref<Image> icon = p_icon;1067if (icon->is_compressed()) {1068icon = icon->duplicate();1069ERR_FAIL_COND(icon->decompress() != OK);1070}1071if (icon->get_format() != Image::FORMAT_RGBA8) {1072if (icon == p_icon) {1073icon = icon->duplicate();1074}1075icon->convert(Image::FORMAT_RGBA8);1076}10771078png_image png_meta;1079memset(&png_meta, 0, sizeof png_meta);1080png_meta.version = PNG_IMAGE_VERSION;1081png_meta.width = icon->get_width();1082png_meta.height = icon->get_height();1083png_meta.format = PNG_FORMAT_RGBA;10841085PackedByteArray png;1086size_t len;1087PackedByteArray data = icon->get_data();1088ERR_FAIL_COND(!png_image_write_get_memory_size(png_meta, len, 0, data.ptr(), 0, nullptr));10891090png.resize(len);1091ERR_FAIL_COND(!png_image_write_to_memory(&png_meta, png.ptrw(), &len, 0, data.ptr(), 0, nullptr));10921093godot_js_display_window_icon_set(png.ptr(), len);1094} else {1095godot_js_display_window_icon_set(nullptr, 0);1096}1097}10981099void DisplayServerWeb::_dispatch_input_event(const Ref<InputEvent> &p_event) {1100Callable cb = get_singleton()->input_event_callback;1101if (cb.is_valid()) {1102cb.call(p_event);1103}1104}11051106DisplayServer *DisplayServerWeb::create_func(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, Context p_context, int64_t p_parent_window, Error &r_error) {1107return memnew(DisplayServerWeb(p_rendering_driver, p_window_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error));1108}11091110DisplayServerWeb::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, Context p_context, int64_t p_parent_window, Error &r_error) {1111r_error = OK; // Always succeeds for now.11121113native_menu = memnew(NativeMenu); // Dummy native menu.11141115// Ensure the canvas ID.1116godot_js_config_canvas_id_get(canvas_id, 256);11171118// Handle contextmenu, webglcontextlost1119godot_js_display_setup_canvas(p_resolution.x, p_resolution.y, (p_window_mode == WINDOW_MODE_FULLSCREEN || p_window_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), OS::get_singleton()->is_hidpi_allowed() ? 1 : 0);11201121// Check if it's windows.1122swap_cancel_ok = godot_js_display_is_swap_ok_cancel() == 1;11231124// Expose method for requesting quit.1125godot_js_os_request_quit_cb(request_quit_callback);11261127#ifdef GLES3_ENABLED1128bool webgl2_inited = false;1129if (godot_js_display_has_webgl(2)) {1130EmscriptenWebGLContextAttributes attributes;1131emscripten_webgl_init_context_attributes(&attributes);1132attributes.alpha = OS::get_singleton()->is_layered_allowed();1133attributes.antialias = false;1134attributes.majorVersion = 2;1135attributes.explicitSwapControl = true;11361137webgl_ctx = emscripten_webgl_create_context(canvas_id, &attributes);1138webgl2_inited = webgl_ctx && emscripten_webgl_make_context_current(webgl_ctx) == EMSCRIPTEN_RESULT_SUCCESS;1139}1140if (webgl2_inited) {1141if (!emscripten_webgl_enable_extension(webgl_ctx, "OVR_multiview2")) {1142print_verbose("Failed to enable WebXR extension.");1143}1144RasterizerGLES3::make_current(false);11451146} else {1147OS::get_singleton()->alert(1148"Your browser seems not to support WebGL 2.\n\n"1149"If possible, consider updating your browser version and video card drivers.",1150"Unable to initialize WebGL 2 video driver");1151RasterizerDummy::make_current();1152}1153#else1154RasterizerDummy::make_current();1155#endif11561157// JS Input interface (js/libs/library_godot_input.js)1158godot_js_input_mouse_button_cb(&DisplayServerWeb::mouse_button_callback);1159godot_js_input_mouse_move_cb(&DisplayServerWeb::mouse_move_callback);1160godot_js_input_mouse_wheel_cb(&DisplayServerWeb::mouse_wheel_callback);1161godot_js_input_touch_cb(&DisplayServerWeb::touch_callback, touch_event.identifier, touch_event.coords);1162godot_js_input_key_cb(&DisplayServerWeb::key_callback, key_event.code, key_event.key);1163godot_js_input_paste_cb(&DisplayServerWeb::update_clipboard_callback);1164godot_js_input_drop_files_cb(&DisplayServerWeb::drop_files_js_callback);1165godot_js_input_gamepad_cb(&DisplayServerWeb::gamepad_callback);1166godot_js_set_ime_cb(&DisplayServerWeb::ime_callback, &DisplayServerWeb::key_callback, key_event.code, key_event.key);11671168// JS Display interface (js/libs/library_godot_display.js)1169godot_js_display_fullscreen_cb(&DisplayServerWeb::fullscreen_change_callback);1170godot_js_display_window_blur_cb(&DisplayServerWeb::window_blur_callback);1171godot_js_display_notification_cb(&DisplayServerWeb::send_window_event_callback,1172WINDOW_EVENT_MOUSE_ENTER,1173WINDOW_EVENT_MOUSE_EXIT,1174WINDOW_EVENT_FOCUS_IN,1175WINDOW_EVENT_FOCUS_OUT);1176godot_js_display_vk_cb(&DisplayServerWeb::vk_input_text_callback);11771178Input::get_singleton()->set_event_dispatch_function(_dispatch_input_event);1179}11801181DisplayServerWeb::~DisplayServerWeb() {1182if (native_menu) {1183memdelete(native_menu);1184native_menu = nullptr;1185}1186#ifdef GLES3_ENABLED1187if (webgl_ctx) {1188emscripten_webgl_commit_frame();1189emscripten_webgl_destroy_context(webgl_ctx);1190}1191#endif1192}11931194bool DisplayServerWeb::has_feature(Feature p_feature) const {1195switch (p_feature) {1196#ifndef DISABLE_DEPRECATED1197case FEATURE_GLOBAL_MENU: {1198return (native_menu && native_menu->has_feature(NativeMenu::FEATURE_GLOBAL_MENU));1199} break;1200#endif1201//case FEATURE_HIDPI:1202case FEATURE_ICON:1203case FEATURE_CLIPBOARD:1204case FEATURE_CURSOR_SHAPE:1205case FEATURE_CUSTOM_CURSOR_SHAPE:1206case FEATURE_MOUSE:1207case FEATURE_TOUCHSCREEN:1208return true;1209//case FEATURE_MOUSE_WARP:1210//case FEATURE_NATIVE_DIALOG:1211//case FEATURE_NATIVE_DIALOG_INPUT:1212//case FEATURE_NATIVE_DIALOG_FILE:1213//case FEATURE_NATIVE_DIALOG_FILE_EXTRA:1214//case FEATURE_NATIVE_DIALOG_FILE_MIME:1215//case FEATURE_NATIVE_ICON:1216//case FEATURE_WINDOW_TRANSPARENCY:1217//case FEATURE_KEEP_SCREEN_ON:1218//case FEATURE_ORIENTATION:1219case FEATURE_IME:1220// IME does not work with experimental VK support.1221return godot_js_display_vk_available() == 0;1222case FEATURE_VIRTUAL_KEYBOARD:1223return godot_js_display_vk_available() != 0;1224case FEATURE_TEXT_TO_SPEECH:1225return godot_js_display_tts_available() != 0;1226default:1227return false;1228}1229}12301231void DisplayServerWeb::register_web_driver() {1232register_create_function("web", create_func, get_rendering_drivers_func);1233}12341235String DisplayServerWeb::get_name() const {1236return "web";1237}12381239int DisplayServerWeb::get_screen_count() const {1240return 1;1241}12421243int DisplayServerWeb::get_primary_screen() const {1244return 0;1245}12461247Point2i DisplayServerWeb::screen_get_position(int p_screen) const {1248p_screen = _get_screen_index(p_screen);1249int screen_count = get_screen_count();1250ERR_FAIL_INDEX_V(p_screen, screen_count, Point2i());12511252return Point2i(0, 0); // TODO offsetX/Y?1253}12541255Size2i DisplayServerWeb::screen_get_size(int p_screen) const {1256p_screen = _get_screen_index(p_screen);1257int screen_count = get_screen_count();1258ERR_FAIL_INDEX_V(p_screen, screen_count, Size2i());12591260int size[2];1261godot_js_display_screen_size_get(size, size + 1);1262return Size2(size[0], size[1]);1263}12641265Rect2i DisplayServerWeb::screen_get_usable_rect(int p_screen) const {1266p_screen = _get_screen_index(p_screen);1267int screen_count = get_screen_count();1268ERR_FAIL_INDEX_V(p_screen, screen_count, Rect2i());12691270int size[2];1271godot_js_display_window_size_get(size, size + 1);1272return Rect2i(0, 0, size[0], size[1]);1273}12741275int DisplayServerWeb::screen_get_dpi(int p_screen) const {1276p_screen = _get_screen_index(p_screen);1277int screen_count = get_screen_count();1278ERR_FAIL_INDEX_V(p_screen, screen_count, 72);12791280return godot_js_display_screen_dpi_get();1281}12821283float DisplayServerWeb::screen_get_scale(int p_screen) const {1284p_screen = _get_screen_index(p_screen);1285int screen_count = get_screen_count();1286ERR_FAIL_INDEX_V(p_screen, screen_count, 1.0f);12871288return godot_js_display_pixel_ratio_get();1289}12901291float DisplayServerWeb::screen_get_refresh_rate(int p_screen) const {1292p_screen = _get_screen_index(p_screen);1293int screen_count = get_screen_count();1294ERR_FAIL_INDEX_V(p_screen, screen_count, SCREEN_REFRESH_RATE_FALLBACK);12951296return SCREEN_REFRESH_RATE_FALLBACK; // Web doesn't have much of a need for the screen refresh rate, and there's no native way to do so.1297}12981299Vector<DisplayServer::WindowID> DisplayServerWeb::get_window_list() const {1300Vector<WindowID> ret;1301ret.push_back(MAIN_WINDOW_ID);1302return ret;1303}13041305DisplayServerWeb::WindowID DisplayServerWeb::get_window_at_screen_position(const Point2i &p_position) const {1306return MAIN_WINDOW_ID;1307}13081309void DisplayServerWeb::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {1310window_attached_instance_id = p_instance;1311}13121313ObjectID DisplayServerWeb::window_get_attached_instance_id(WindowID p_window) const {1314return window_attached_instance_id;1315}13161317void DisplayServerWeb::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {1318rect_changed_callback = p_callable;1319}13201321void DisplayServerWeb::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {1322window_event_callback = p_callable;1323}13241325void DisplayServerWeb::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {1326input_event_callback = p_callable;1327}13281329void DisplayServerWeb::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {1330input_text_callback = p_callable;1331}13321333void DisplayServerWeb::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {1334drop_files_callback = p_callable;1335}13361337void DisplayServerWeb::window_set_title(const String &p_title, WindowID p_window) {1338godot_js_display_window_title_set(p_title.utf8().get_data());1339}13401341int DisplayServerWeb::window_get_current_screen(WindowID p_window) const {1342ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, INVALID_SCREEN);1343return 0;1344}13451346void DisplayServerWeb::window_set_current_screen(int p_screen, WindowID p_window) {1347// Not implemented.1348}13491350Point2i DisplayServerWeb::window_get_position(WindowID p_window) const {1351return Point2i();1352}13531354Point2i DisplayServerWeb::window_get_position_with_decorations(WindowID p_window) const {1355return Point2i();1356}13571358void DisplayServerWeb::window_set_position(const Point2i &p_position, WindowID p_window) {1359// Not supported.1360}13611362void DisplayServerWeb::window_set_transient(WindowID p_window, WindowID p_parent) {1363// Not supported.1364}13651366void DisplayServerWeb::window_set_max_size(const Size2i p_size, WindowID p_window) {1367// Not supported.1368}13691370Size2i DisplayServerWeb::window_get_max_size(WindowID p_window) const {1371return Size2i();1372}13731374void DisplayServerWeb::window_set_min_size(const Size2i p_size, WindowID p_window) {1375// Not supported.1376}13771378Size2i DisplayServerWeb::window_get_min_size(WindowID p_window) const {1379return Size2i();1380}13811382void DisplayServerWeb::window_set_size(const Size2i p_size, WindowID p_window) {1383godot_js_display_desired_size_set(p_size.x, p_size.y);1384}13851386Size2i DisplayServerWeb::window_get_size(WindowID p_window) const {1387int size[2];1388godot_js_display_window_size_get(size, size + 1);1389return Size2i(size[0], size[1]);1390}13911392Size2i DisplayServerWeb::window_get_size_with_decorations(WindowID p_window) const {1393return window_get_size(p_window);1394}13951396void DisplayServerWeb::window_set_mode(WindowMode p_mode, WindowID p_window) {1397if (window_mode == p_mode) {1398return;1399}14001401switch (p_mode) {1402case WINDOW_MODE_WINDOWED: {1403if (window_mode == WINDOW_MODE_FULLSCREEN) {1404godot_js_display_fullscreen_exit();1405}1406window_mode = WINDOW_MODE_WINDOWED;1407} break;1408case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:1409case WINDOW_MODE_FULLSCREEN: {1410int result = godot_js_display_fullscreen_request();1411ERR_FAIL_COND_MSG(result, "The request was denied. Remember that enabling fullscreen is only possible from an input callback for the Web platform.");1412} break;1413case WINDOW_MODE_MAXIMIZED:1414case WINDOW_MODE_MINIMIZED:1415// WindowMode MAXIMIZED and MINIMIZED are not supported in Web platform.1416break;1417default:1418break;1419}1420}14211422DisplayServerWeb::WindowMode DisplayServerWeb::window_get_mode(WindowID p_window) const {1423return window_mode;1424}14251426bool DisplayServerWeb::window_is_maximize_allowed(WindowID p_window) const {1427return false;1428}14291430void DisplayServerWeb::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {1431// Not supported.1432}14331434bool DisplayServerWeb::window_get_flag(WindowFlags p_flag, WindowID p_window) const {1435return false;1436}14371438void DisplayServerWeb::window_request_attention(WindowID p_window) {1439// Not supported.1440}14411442void DisplayServerWeb::window_move_to_foreground(WindowID p_window) {1443// Not supported.1444}14451446bool DisplayServerWeb::window_is_focused(WindowID p_window) const {1447return true;1448}14491450bool DisplayServerWeb::window_can_draw(WindowID p_window) const {1451return true;1452}14531454bool DisplayServerWeb::can_any_window_draw() const {1455return true;1456}14571458DisplayServer::VSyncMode DisplayServerWeb::window_get_vsync_mode(WindowID p_vsync_mode) const {1459return DisplayServer::VSYNC_ENABLED;1460}14611462void DisplayServerWeb::process_events() {1463process_keys();1464Input::get_singleton()->flush_buffered_events();14651466if (gamepad_count > 0) {1467if (godot_js_input_gamepad_sample() == OK) {1468process_joypads();1469}1470}1471}14721473void DisplayServerWeb::process_keys() {1474for (int i = 0; i < key_event_pos; i++) {1475const DisplayServerWeb::KeyEvent &ke = key_event_buffer[i];14761477Ref<InputEventKey> ev;1478ev.instantiate();1479ev->set_pressed(ke.pressed);1480ev->set_echo(ke.echo);1481ev->set_keycode(ke.keycode);1482ev->set_physical_keycode(ke.physical_keycode);1483ev->set_key_label(ke.key_label);1484ev->set_unicode(ke.unicode);1485ev->set_location(ke.location);1486if (ke.raw) {1487dom2godot_mod(ev, ke.mod, ke.keycode);1488}14891490Input::get_singleton()->parse_input_event(ev);1491}1492key_event_pos = 0;1493}14941495int DisplayServerWeb::get_current_video_driver() const {1496return 1;1497}14981499bool DisplayServerWeb::get_swap_cancel_ok() {1500return swap_cancel_ok;1501}15021503void DisplayServerWeb::swap_buffers() {1504#ifdef GLES3_ENABLED1505if (webgl_ctx) {1506emscripten_webgl_commit_frame();1507}1508#endif1509}151015111512