2014-02-10 02:10:30 +01:00
/*************************************************************************/
/* os_javascript.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
2017-08-27 14:16:55 +02:00
/* https://godotengine.org */
2014-02-10 02:10:30 +01:00
/*************************************************************************/
2020-01-01 11:16:22 +01:00
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
2014-02-10 02:10:30 +01:00
/* */
/* 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. */
/*************************************************************************/
2018-01-05 00:50:27 +01:00
2014-02-10 02:10:30 +01:00
# include "os_javascript.h"
2017-01-16 19:19:45 +01:00
2018-09-11 18:13:45 +02:00
# include "core/io/file_access_buffered_fa.h"
2018-10-01 21:35:55 +02:00
# include "drivers/gles2/rasterizer_gles2.h"
# include "drivers/gles3/rasterizer_gles3.h"
# include "drivers/unix/dir_access_unix.h"
# include "drivers/unix/file_access_unix.h"
2014-02-10 02:10:30 +01:00
# include "main/main.h"
2017-03-05 16:44:50 +01:00
# include "servers/visual/visual_server_raster.h"
2014-02-10 02:10:30 +01:00
2017-01-16 19:19:45 +01:00
# include <emscripten.h>
2018-09-16 19:37:36 +02:00
# include <png.h>
2017-03-05 16:44:50 +01:00
# include <stdlib.h>
2017-01-16 19:19:45 +01:00
2018-07-08 02:23:19 +02:00
# include "dom_keys.inc"
2017-04-24 21:41:39 +02:00
# define DOM_BUTTON_LEFT 0
# define DOM_BUTTON_MIDDLE 1
# define DOM_BUTTON_RIGHT 2
2018-07-05 16:50:30 +02:00
# define DOM_BUTTON_XBUTTON1 3
# define DOM_BUTTON_XBUTTON2 4
2017-04-21 05:01:31 +02:00
2018-07-08 02:23:19 +02:00
// Window (canvas)
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
static void focus_canvas ( ) {
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
/* clang-format off */
EM_ASM (
Module . canvas . focus ( ) ;
) ;
/* clang-format on */
2014-02-10 02:10:30 +01:00
}
2018-07-08 02:23:19 +02:00
static bool is_canvas_focused ( ) {
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
/* clang-format off */
return EM_ASM_INT_V (
return document . activeElement = = Module . canvas ;
) ;
/* clang-format on */
2014-02-10 02:10:30 +01:00
}
2019-06-14 22:37:21 +02:00
static Point2 correct_canvas_position ( int x , int y ) {
int canvas_width ;
int canvas_height ;
emscripten_get_canvas_element_size ( NULL , & canvas_width , & canvas_height ) ;
double element_width ;
double element_height ;
emscripten_get_element_css_size ( NULL , & element_width , & element_height ) ;
x = ( int ) ( canvas_width / element_width * x ) ;
y = ( int ) ( canvas_height / element_height * y ) ;
return Point2 ( x , y ) ;
}
2018-07-08 02:23:19 +02:00
static bool cursor_inside_canvas = true ;
2016-11-26 13:13:16 +01:00
2018-07-08 02:23:19 +02:00
EM_BOOL OS_JavaScript : : fullscreen_change_callback ( int p_event_type , const EmscriptenFullscreenChangeEvent * p_event , void * p_user_data ) {
2016-11-26 13:13:16 +01:00
2018-07-08 02:23:19 +02:00
OS_JavaScript * os = get_singleton ( ) ;
// Empty ID is canvas.
String target_id = String : : utf8 ( p_event - > id ) ;
if ( target_id . empty ( ) | | target_id = = " canvas " ) {
// This event property is the only reliable data on
// browser fullscreen state.
os - > video_mode . fullscreen = p_event - > isFullscreen ;
2018-08-12 15:39:32 +02:00
if ( os - > video_mode . fullscreen ) {
os - > entering_fullscreen = false ;
} else {
// Restoring maximized window now will cause issues,
// so delay until main_loop_iterate.
os - > just_exited_fullscreen = true ;
}
2016-11-26 13:13:16 +01:00
}
return false ;
}
2018-07-08 02:23:19 +02:00
void OS_JavaScript : : set_video_mode ( const VideoMode & p_video_mode , int p_screen ) {
2017-07-22 15:43:05 +02:00
2018-07-08 02:23:19 +02:00
video_mode = p_video_mode ;
2017-07-22 15:43:05 +02:00
}
2018-07-08 02:23:19 +02:00
OS : : VideoMode OS_JavaScript : : get_video_mode ( int p_screen ) const {
2017-07-22 15:43:05 +02:00
2018-07-08 02:23:19 +02:00
return video_mode ;
2017-07-22 15:43:05 +02:00
}
2018-07-08 02:23:19 +02:00
Size2 OS_JavaScript : : get_screen_size ( int p_screen ) const {
2017-07-22 15:43:05 +02:00
2018-07-08 02:23:19 +02:00
EmscriptenFullscreenChangeEvent ev ;
EMSCRIPTEN_RESULT result = emscripten_get_fullscreen_status ( & ev ) ;
ERR_FAIL_COND_V ( result ! = EMSCRIPTEN_RESULT_SUCCESS , Size2 ( ) ) ;
return Size2 ( ev . screenWidth , ev . screenHeight ) ;
2017-07-22 15:43:05 +02:00
}
2018-07-08 02:23:19 +02:00
void OS_JavaScript : : set_window_size ( const Size2 p_size ) {
2017-04-21 05:01:31 +02:00
2018-07-08 02:23:19 +02:00
windowed_size = p_size ;
2018-08-12 15:39:32 +02:00
if ( video_mode . fullscreen ) {
2018-07-08 02:23:19 +02:00
window_maximized = false ;
set_window_fullscreen ( false ) ;
2017-07-22 15:43:05 +02:00
} else {
2018-08-12 15:39:32 +02:00
if ( window_maximized ) {
emscripten_exit_soft_fullscreen ( ) ;
window_maximized = false ;
}
2018-10-02 02:30:27 +02:00
emscripten_set_canvas_element_size ( NULL , p_size . x , p_size . y ) ;
2017-07-22 15:43:05 +02:00
}
2017-04-21 05:01:31 +02:00
}
2018-07-08 02:23:19 +02:00
Size2 OS_JavaScript : : get_window_size ( ) const {
2017-04-21 05:01:31 +02:00
2018-10-02 02:30:27 +02:00
int canvas [ 2 ] ;
emscripten_get_canvas_element_size ( NULL , canvas , canvas + 1 ) ;
2018-07-08 02:23:19 +02:00
return Size2 ( canvas [ 0 ] , canvas [ 1 ] ) ;
2017-04-21 05:01:31 +02:00
}
2018-07-08 02:23:19 +02:00
void OS_JavaScript : : set_window_maximized ( bool p_enabled ) {
2017-04-21 05:01:31 +02:00
2018-08-12 15:39:32 +02:00
if ( video_mode . fullscreen ) {
window_maximized = p_enabled ;
2018-07-08 02:23:19 +02:00
set_window_fullscreen ( false ) ;
2018-08-12 15:39:32 +02:00
} else if ( ! p_enabled ) {
emscripten_exit_soft_fullscreen ( ) ;
window_maximized = false ;
} else if ( ! window_maximized ) {
// Prevent calling emscripten_enter_soft_fullscreen mutltiple times,
// this would hide page elements permanently.
2018-07-08 02:23:19 +02:00
EmscriptenFullscreenStrategy strategy ;
strategy . scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH ;
strategy . canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF ;
strategy . filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT ;
strategy . canvasResizedCallback = NULL ;
emscripten_enter_soft_fullscreen ( NULL , & strategy ) ;
2018-08-12 15:39:32 +02:00
window_maximized = p_enabled ;
2018-07-08 02:23:19 +02:00
}
}
2017-04-21 05:01:31 +02:00
2018-07-08 02:23:19 +02:00
bool OS_JavaScript : : is_window_maximized ( ) const {
2017-04-21 05:01:31 +02:00
2018-07-08 02:23:19 +02:00
return window_maximized ;
2017-04-21 05:01:31 +02:00
}
2018-07-08 02:23:19 +02:00
void OS_JavaScript : : set_window_fullscreen ( bool p_enabled ) {
2017-04-24 21:41:39 +02:00
2018-08-12 15:39:32 +02:00
if ( p_enabled = = video_mode . fullscreen ) {
2018-07-08 02:23:19 +02:00
return ;
}
2017-04-24 21:41:39 +02:00
2018-08-12 15:39:32 +02:00
// Just request changes here, if successful, logic continues in
// fullscreen_change_callback.
2018-07-08 02:23:19 +02:00
if ( p_enabled ) {
if ( window_maximized ) {
2018-08-12 15:39:32 +02:00
// Soft fullsreen during real fullscreen can cause issues, so exit.
// This must be called before requesting full screen.
emscripten_exit_soft_fullscreen ( ) ;
2018-07-08 02:23:19 +02:00
}
EmscriptenFullscreenStrategy strategy ;
strategy . scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH ;
strategy . canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF ;
strategy . filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT ;
strategy . canvasResizedCallback = NULL ;
2018-08-12 15:39:32 +02:00
EMSCRIPTEN_RESULT result = emscripten_request_fullscreen_strategy ( NULL , false , & strategy ) ;
2019-08-09 06:49:33 +02:00
ERR_FAIL_COND_MSG ( result = = EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED , " Enabling fullscreen is only possible from an input callback for the HTML5 platform. " ) ;
ERR_FAIL_COND_MSG ( result ! = EMSCRIPTEN_RESULT_SUCCESS , " Enabling fullscreen is only possible from an input callback for the HTML5 platform. " ) ;
2018-08-12 15:39:32 +02:00
// Not fullscreen yet, so prevent "windowed" canvas dimensions from
// being overwritten.
entering_fullscreen = true ;
2018-07-08 02:23:19 +02:00
} else {
2018-08-12 15:39:32 +02:00
// No logic allowed here, since exiting w/ ESC key won't use this function.
ERR_FAIL_COND ( emscripten_exit_fullscreen ( ) ! = EMSCRIPTEN_RESULT_SUCCESS ) ;
2018-07-08 02:23:19 +02:00
}
}
2017-04-24 21:41:39 +02:00
2018-07-08 02:23:19 +02:00
bool OS_JavaScript : : is_window_fullscreen ( ) const {
2017-04-24 21:41:39 +02:00
2018-07-08 02:23:19 +02:00
return video_mode . fullscreen ;
2017-04-24 21:41:39 +02:00
}
2018-07-08 02:23:19 +02:00
void OS_JavaScript : : get_fullscreen_mode_list ( List < VideoMode > * p_list , int p_screen ) const {
2017-04-24 21:41:39 +02:00
2018-07-08 02:23:19 +02:00
Size2 screen = get_screen_size ( ) ;
p_list - > push_back ( OS : : VideoMode ( screen . width , screen . height , true ) ) ;
}
2017-04-24 21:41:39 +02:00
2018-07-08 02:23:19 +02:00
// Keys
2017-04-24 21:41:39 +02:00
2018-07-08 02:23:19 +02:00
template < typename T >
static void dom2godot_mod ( T * emscripten_event_ptr , Ref < InputEventWithModifiers > godot_event ) {
2017-04-24 21:41:39 +02:00
2018-07-08 02:23:19 +02:00
godot_event - > set_shift ( emscripten_event_ptr - > shiftKey ) ;
godot_event - > set_alt ( emscripten_event_ptr - > altKey ) ;
godot_event - > set_control ( emscripten_event_ptr - > ctrlKey ) ;
godot_event - > set_metakey ( emscripten_event_ptr - > metaKey ) ;
2017-04-24 21:41:39 +02:00
}
2018-07-08 02:23:19 +02:00
static Ref < InputEventKey > setup_key_event ( const EmscriptenKeyboardEvent * emscripten_event ) {
2016-04-16 00:19:02 +02:00
2017-06-19 15:43:46 +02:00
Ref < InputEventKey > ev ;
ev . instance ( ) ;
ev - > set_echo ( emscripten_event - > repeat ) ;
dom2godot_mod ( emscripten_event , ev ) ;
ev - > set_scancode ( dom2godot_scancode ( emscripten_event - > keyCode ) ) ;
2016-04-16 00:19:02 +02:00
String unicode = String : : utf8 ( emscripten_event - > key ) ;
2018-07-08 02:23:19 +02:00
// Check if empty or multi-character (e.g. `CapsLock`).
2017-03-05 16:44:50 +01:00
if ( unicode . length ( ) ! = 1 ) {
2018-07-08 02:23:19 +02:00
// Might be empty as well, but better than nonsense.
2016-04-16 00:19:02 +02:00
unicode = String : : utf8 ( emscripten_event - > charValue ) ;
}
2017-03-05 16:44:50 +01:00
if ( unicode . length ( ) = = 1 ) {
2017-06-19 15:43:46 +02:00
ev - > set_unicode ( unicode [ 0 ] ) ;
2016-04-16 00:19:02 +02:00
}
return ev ;
}
2018-07-08 02:23:19 +02:00
EM_BOOL OS_JavaScript : : keydown_callback ( int p_event_type , const EmscriptenKeyboardEvent * p_event , void * p_user_data ) {
2016-04-16 00:19:02 +02:00
2018-07-08 02:23:19 +02:00
OS_JavaScript * os = get_singleton ( ) ;
Ref < InputEventKey > ev = setup_key_event ( p_event ) ;
2017-06-19 15:43:46 +02:00
ev - > set_pressed ( true ) ;
if ( ev - > get_unicode ( ) = = 0 & & keycode_has_unicode ( ev - > get_scancode ( ) ) ) {
2018-07-08 02:23:19 +02:00
// Defer to keypress event for legacy unicode retrieval.
os - > deferred_key_event = ev ;
// Do not suppress keypress event.
return false ;
2016-04-19 14:15:20 +02:00
}
2018-07-08 02:23:19 +02:00
os - > input - > parse_input_event ( ev ) ;
2019-01-20 14:25:15 +01:00
// Resume audio context after input in case autoplay was denied.
os - > audio_driver_javascript . resume ( ) ;
2016-04-19 14:15:20 +02:00
return true ;
}
2018-07-08 02:23:19 +02:00
EM_BOOL OS_JavaScript : : keypress_callback ( int p_event_type , const EmscriptenKeyboardEvent * p_event , void * p_user_data ) {
2016-04-19 14:15:20 +02:00
2018-07-08 02:23:19 +02:00
OS_JavaScript * os = get_singleton ( ) ;
os - > deferred_key_event - > set_unicode ( p_event - > charCode ) ;
os - > input - > parse_input_event ( os - > deferred_key_event ) ;
2016-04-19 14:15:20 +02:00
return true ;
2016-04-16 00:19:02 +02:00
}
2018-07-08 02:23:19 +02:00
EM_BOOL OS_JavaScript : : keyup_callback ( int p_event_type , const EmscriptenKeyboardEvent * p_event , void * p_user_data ) {
2016-04-16 00:19:02 +02:00
2018-07-08 02:23:19 +02:00
Ref < InputEventKey > ev = setup_key_event ( p_event ) ;
2017-06-19 15:43:46 +02:00
ev - > set_pressed ( false ) ;
2018-07-08 02:23:19 +02:00
get_singleton ( ) - > input - > parse_input_event ( ev ) ;
2017-05-20 17:38:03 +02:00
return ev - > get_scancode ( ) ! = KEY_UNKNOWN & & ev - > get_scancode ( ) ! = 0 ;
2016-04-16 00:19:02 +02:00
}
2018-07-08 02:23:19 +02:00
// Mouse
Point2 OS_JavaScript : : get_mouse_position ( ) const {
return input - > get_mouse_position ( ) ;
2016-01-21 02:23:15 +01:00
}
2018-07-08 02:23:19 +02:00
int OS_JavaScript : : get_mouse_button_state ( ) const {
2018-03-21 15:51:44 +01:00
2018-07-08 02:23:19 +02:00
return input - > get_mouse_button_mask ( ) ;
2017-07-22 15:43:05 +02:00
}
2018-07-08 02:23:19 +02:00
EM_BOOL OS_JavaScript : : mouse_button_callback ( int p_event_type , const EmscriptenMouseEvent * p_event , void * p_user_data ) {
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
OS_JavaScript * os = get_singleton ( ) ;
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
Ref < InputEventMouseButton > ev ;
ev . instance ( ) ;
ev - > set_pressed ( p_event_type = = EMSCRIPTEN_EVENT_MOUSEDOWN ) ;
2019-06-14 22:37:21 +02:00
ev - > set_position ( correct_canvas_position ( p_event - > canvasX , p_event - > canvasY ) ) ;
2018-07-08 02:23:19 +02:00
ev - > set_global_position ( ev - > get_position ( ) ) ;
dom2godot_mod ( p_event , ev ) ;
2020-01-17 14:10:50 +01:00
2018-07-08 02:23:19 +02:00
switch ( p_event - > button ) {
case DOM_BUTTON_LEFT : ev - > set_button_index ( BUTTON_LEFT ) ; break ;
case DOM_BUTTON_MIDDLE : ev - > set_button_index ( BUTTON_MIDDLE ) ; break ;
case DOM_BUTTON_RIGHT : ev - > set_button_index ( BUTTON_RIGHT ) ; break ;
case DOM_BUTTON_XBUTTON1 : ev - > set_button_index ( BUTTON_XBUTTON1 ) ; break ;
case DOM_BUTTON_XBUTTON2 : ev - > set_button_index ( BUTTON_XBUTTON2 ) ; break ;
default : return false ;
2018-03-07 19:53:13 +01:00
}
2014-02-10 02:10:30 +01:00
2018-07-20 18:16:09 +02:00
if ( ev - > is_pressed ( ) ) {
2020-01-17 14:10:50 +01:00
double diff = emscripten_get_now ( ) - os - > last_click_ms ;
2018-07-20 18:16:09 +02:00
if ( ev - > get_button_index ( ) = = os - > last_click_button_index ) {
if ( diff < 400 & & Point2 ( os - > last_click_pos ) . distance_to ( ev - > get_position ( ) ) < 5 ) {
os - > last_click_ms = 0 ;
os - > last_click_pos = Point2 ( - 100 , - 100 ) ;
os - > last_click_button_index = - 1 ;
ev - > set_doubleclick ( true ) ;
}
} else {
os - > last_click_button_index = ev - > get_button_index ( ) ;
}
if ( ! ev - > is_doubleclick ( ) ) {
os - > last_click_ms + = diff ;
os - > last_click_pos = ev - > get_position ( ) ;
}
}
2018-07-08 02:23:19 +02:00
int mask = os - > input - > get_mouse_button_mask ( ) ;
int button_flag = 1 < < ( ev - > get_button_index ( ) - 1 ) ;
if ( ev - > is_pressed ( ) ) {
// Since the event is consumed, focus manually. The containing iframe,
// if exists, may not have focus yet, so focus even if already focused.
focus_canvas ( ) ;
mask | = button_flag ;
} else if ( mask & button_flag ) {
mask & = ~ button_flag ;
2017-11-19 15:30:09 +01:00
} else {
2018-07-08 02:23:19 +02:00
// Received release event, but press was outside the canvas, so ignore.
return false ;
2017-11-19 15:30:09 +01:00
}
2018-07-08 02:23:19 +02:00
ev - > set_button_mask ( mask ) ;
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
os - > input - > parse_input_event ( ev ) ;
2019-01-20 14:25:15 +01:00
// Resume audio context after input in case autoplay was denied.
os - > audio_driver_javascript . resume ( ) ;
2018-07-08 02:23:19 +02:00
// Prevent multi-click text selection and wheel-click scrolling anchor.
// Context menu is prevented through contextmenu event.
return true ;
2014-02-10 02:10:30 +01:00
}
2018-07-08 02:23:19 +02:00
EM_BOOL OS_JavaScript : : mousemove_callback ( int p_event_type , const EmscriptenMouseEvent * p_event , void * p_user_data ) {
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
OS_JavaScript * os = get_singleton ( ) ;
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
int input_mask = os - > input - > get_mouse_button_mask ( ) ;
2019-06-14 22:37:21 +02:00
Point2 pos = correct_canvas_position ( p_event - > canvasX , p_event - > canvasY ) ;
2018-07-08 02:23:19 +02:00
// For motion outside the canvas, only read mouse movement if dragging
// started inside the canvas; imitating desktop app behaviour.
if ( ! cursor_inside_canvas & & ! input_mask )
return false ;
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
Ref < InputEventMouseMotion > ev ;
ev . instance ( ) ;
dom2godot_mod ( p_event , ev ) ;
ev - > set_button_mask ( input_mask ) ;
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
ev - > set_position ( pos ) ;
ev - > set_global_position ( ev - > get_position ( ) ) ;
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
ev - > set_relative ( Vector2 ( p_event - > movementX , p_event - > movementY ) ) ;
os - > input - > set_mouse_position ( ev - > get_position ( ) ) ;
ev - > set_speed ( os - > input - > get_last_mouse_speed ( ) ) ;
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
os - > input - > parse_input_event ( ev ) ;
// Don't suppress mouseover/-leave events.
return false ;
2014-02-10 02:10:30 +01:00
}
2017-04-29 21:35:31 +02:00
static const char * godot2dom_cursor ( OS : : CursorShape p_shape ) {
switch ( p_shape ) {
case OS : : CURSOR_ARROW :
default :
return " auto " ;
case OS : : CURSOR_IBEAM : return " text " ;
case OS : : CURSOR_POINTING_HAND : return " pointer " ;
case OS : : CURSOR_CROSS : return " crosshair " ;
case OS : : CURSOR_WAIT : return " progress " ;
case OS : : CURSOR_BUSY : return " wait " ;
case OS : : CURSOR_DRAG : return " grab " ;
case OS : : CURSOR_CAN_DROP : return " grabbing " ;
case OS : : CURSOR_FORBIDDEN : return " no-drop " ;
case OS : : CURSOR_VSIZE : return " ns-resize " ;
case OS : : CURSOR_HSIZE : return " ew-resize " ;
case OS : : CURSOR_BDIAGSIZE : return " nesw-resize " ;
case OS : : CURSOR_FDIAGSIZE : return " nwse-resize " ;
case OS : : CURSOR_MOVE : return " move " ;
case OS : : CURSOR_VSPLIT : return " row-resize " ;
case OS : : CURSOR_HSPLIT : return " col-resize " ;
case OS : : CURSOR_HELP : return " help " ;
}
}
2018-07-08 02:23:19 +02:00
static void set_css_cursor ( const char * p_cursor ) {
2014-02-10 02:10:30 +01:00
2017-04-29 04:54:48 +02:00
/* clang-format off */
EM_ASM_ ( {
2018-01-06 15:53:04 +01:00
Module . canvas . style . cursor = UTF8ToString ( $ 0 ) ;
2017-04-29 04:54:48 +02:00
} , p_cursor ) ;
/* clang-format on */
2014-02-10 02:10:30 +01:00
}
2018-10-02 02:30:27 +02:00
static bool is_css_cursor_hidden ( ) {
2014-02-10 02:10:30 +01:00
2017-04-29 04:54:48 +02:00
/* clang-format off */
2018-10-02 02:30:27 +02:00
return EM_ASM_INT ( {
return Module . canvas . style . cursor = = = ' none ' ;
} ) ;
2017-04-29 04:54:48 +02:00
/* clang-format on */
2014-02-10 02:10:30 +01:00
}
2018-07-08 02:23:19 +02:00
void OS_JavaScript : : set_cursor_shape ( CursorShape p_shape ) {
ERR_FAIL_INDEX ( p_shape , CURSOR_MAX ) ;
2018-11-28 17:04:37 +01:00
if ( get_mouse_mode ( ) = = MOUSE_MODE_VISIBLE ) {
if ( cursors [ p_shape ] ! = " " ) {
Vector < String > url = cursors [ p_shape ] . split ( " ? " ) ;
set_css_cursor ( ( " url( \" " + url [ 0 ] + " \" ) " + url [ 1 ] + " , auto " ) . utf8 ( ) ) ;
} else {
set_css_cursor ( godot2dom_cursor ( p_shape ) ) ;
}
}
2018-07-08 02:23:19 +02:00
cursor_shape = p_shape ;
}
void OS_JavaScript : : set_custom_mouse_cursor ( const RES & p_cursor , CursorShape p_shape , const Vector2 & p_hotspot ) {
2018-11-28 17:04:37 +01:00
if ( p_cursor . is_valid ( ) ) {
2019-07-08 22:37:18 +02:00
Map < CursorShape , Vector < Variant > > : : Element * cursor_c = cursors_cache . find ( p_shape ) ;
if ( cursor_c ) {
if ( cursor_c - > get ( ) [ 0 ] = = p_cursor & & cursor_c - > get ( ) [ 1 ] = = p_hotspot ) {
set_cursor_shape ( p_shape ) ;
return ;
}
cursors_cache . erase ( p_shape ) ;
}
2018-11-28 17:04:37 +01:00
Ref < Texture > texture = p_cursor ;
Ref < AtlasTexture > atlas_texture = p_cursor ;
Ref < Image > image ;
Size2 texture_size ;
Rect2 atlas_rect ;
if ( texture . is_valid ( ) ) {
image = texture - > get_data ( ) ;
2019-01-27 17:39:16 +01:00
if ( image . is_valid ( ) ) {
image - > duplicate ( ) ;
}
2018-11-28 17:04:37 +01:00
}
if ( ! image . is_valid ( ) & & atlas_texture . is_valid ( ) ) {
texture = atlas_texture - > get_atlas ( ) ;
atlas_rect . size . width = texture - > get_width ( ) ;
atlas_rect . size . height = texture - > get_height ( ) ;
atlas_rect . position . x = atlas_texture - > get_region ( ) . position . x ;
atlas_rect . position . y = atlas_texture - > get_region ( ) . position . y ;
texture_size . width = atlas_texture - > get_region ( ) . size . x ;
texture_size . height = atlas_texture - > get_region ( ) . size . y ;
} else if ( image . is_valid ( ) ) {
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_data ( ) ;
ERR_FAIL_COND ( ! image . is_valid ( ) ) ;
2019-01-27 17:39:16 +01:00
image = image - > duplicate ( ) ;
2018-11-28 17:04:37 +01:00
if ( atlas_texture . is_valid ( ) )
image - > crop_from_point (
atlas_rect . position . x ,
atlas_rect . position . y ,
texture_size . width ,
texture_size . height ) ;
if ( image - > get_format ( ) ! = Image : : FORMAT_RGBA8 ) {
image - > convert ( Image : : FORMAT_RGBA8 ) ;
}
png_image png_meta ;
memset ( & png_meta , 0 , sizeof png_meta ) ;
png_meta . version = PNG_IMAGE_VERSION ;
png_meta . width = texture_size . width ;
png_meta . height = texture_size . height ;
png_meta . format = PNG_FORMAT_RGBA ;
PoolByteArray png ;
size_t len ;
PoolByteArray : : Read r = image - > get_data ( ) . read ( ) ;
ERR_FAIL_COND ( ! png_image_write_get_memory_size ( png_meta , len , 0 , r . ptr ( ) , 0 , NULL ) ) ;
png . resize ( len ) ;
PoolByteArray : : Write w = png . write ( ) ;
ERR_FAIL_COND ( ! png_image_write_to_memory ( & png_meta , w . ptr ( ) , & len , 0 , r . ptr ( ) , 0 , NULL ) ) ;
w = PoolByteArray : : Write ( ) ;
r = png . read ( ) ;
char * object_url ;
/* clang-format off */
EM_ASM ( {
var PNG_PTR = $ 0 ;
var PNG_LEN = $ 1 ;
var PTR = $ 2 ;
var png = new Blob ( [ HEAPU8 . slice ( PNG_PTR , PNG_PTR + PNG_LEN ) ] , { type : ' image / png ' } ) ;
var url = URL . createObjectURL ( png ) ;
var length_bytes = lengthBytesUTF8 ( url ) + 1 ;
var string_on_wasm_heap = _malloc ( length_bytes ) ;
setValue ( PTR , string_on_wasm_heap , ' * ' ) ;
stringToUTF8 ( url , string_on_wasm_heap , length_bytes ) ;
} , r . ptr ( ) , len , & object_url ) ;
/* clang-format on */
r = PoolByteArray : : Read ( ) ;
String url = String : : utf8 ( object_url ) + " ? " + itos ( p_hotspot . x ) + " " + itos ( p_hotspot . y ) ;
/* clang-format off */
EM_ASM ( { _free ( $ 0 ) ; } , object_url ) ;
/* clang-format on */
if ( cursors [ p_shape ] ! = " " ) {
/* clang-format off */
EM_ASM ( {
URL . revokeObjectURL ( UTF8ToString ( $ 0 ) . split ( ' ? ' ) [ 0 ] ) ;
} , cursors [ p_shape ] . utf8 ( ) . get_data ( ) ) ;
/* clang-format on */
cursors [ p_shape ] = " " ;
}
cursors [ p_shape ] = url ;
2019-07-08 22:37:18 +02:00
Vector < Variant > params ;
params . push_back ( p_cursor ) ;
params . push_back ( p_hotspot ) ;
cursors_cache . insert ( p_shape , params ) ;
2018-11-28 17:04:37 +01:00
} else if ( cursors [ p_shape ] ! = " " ) {
/* clang-format off */
EM_ASM ( {
URL . revokeObjectURL ( UTF8ToString ( $ 0 ) . split ( ' ? ' ) [ 0 ] ) ;
} , cursors [ p_shape ] . utf8 ( ) . get_data ( ) ) ;
/* clang-format on */
cursors [ p_shape ] = " " ;
2019-10-03 13:02:11 +02:00
cursors_cache . erase ( p_shape ) ;
2018-11-28 17:04:37 +01:00
}
set_cursor_shape ( cursor_shape ) ;
2018-07-08 02:23:19 +02:00
}
2017-04-29 04:54:48 +02:00
void OS_JavaScript : : set_mouse_mode ( OS : : MouseMode p_mode ) {
2014-02-10 02:10:30 +01:00
2019-08-09 06:49:33 +02:00
ERR_FAIL_COND_MSG ( p_mode = = MOUSE_MODE_CONFINED , " MOUSE_MODE_CONFINED is not supported for the HTML5 platform. " ) ;
2017-04-29 04:54:48 +02:00
if ( p_mode = = get_mouse_mode ( ) )
return ;
if ( p_mode = = MOUSE_MODE_VISIBLE ) {
2018-11-28 17:04:37 +01:00
// set_css_cursor must be called before set_cursor_shape to make the cursor visible
2017-04-29 21:35:31 +02:00
set_css_cursor ( godot2dom_cursor ( cursor_shape ) ) ;
2018-11-28 17:04:37 +01:00
set_cursor_shape ( cursor_shape ) ;
2017-04-29 04:54:48 +02:00
emscripten_exit_pointerlock ( ) ;
} else if ( p_mode = = MOUSE_MODE_HIDDEN ) {
set_css_cursor ( " none " ) ;
emscripten_exit_pointerlock ( ) ;
} else if ( p_mode = = MOUSE_MODE_CAPTURED ) {
EMSCRIPTEN_RESULT result = emscripten_request_pointerlock ( " canvas " , false ) ;
2019-08-09 06:49:33 +02:00
ERR_FAIL_COND_MSG ( result = = EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED , " MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback. " ) ;
ERR_FAIL_COND_MSG ( result ! = EMSCRIPTEN_RESULT_SUCCESS , " MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback. " ) ;
2018-11-28 17:04:37 +01:00
// set_css_cursor must be called before set_cursor_shape to make the cursor visible
2017-04-29 21:35:31 +02:00
set_css_cursor ( godot2dom_cursor ( cursor_shape ) ) ;
2018-11-28 17:04:37 +01:00
set_cursor_shape ( cursor_shape ) ;
2017-04-29 04:54:48 +02:00
}
}
OS : : MouseMode OS_JavaScript : : get_mouse_mode ( ) const {
2018-10-02 02:30:27 +02:00
if ( is_css_cursor_hidden ( ) )
2017-04-29 04:54:48 +02:00
return MOUSE_MODE_HIDDEN ;
EmscriptenPointerlockChangeEvent ev ;
emscripten_get_pointerlock_status ( & ev ) ;
2018-07-08 02:23:19 +02:00
return ( ev . isActive & & String : : utf8 ( ev . id ) = = " canvas " ) ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE ;
2014-02-10 02:10:30 +01:00
}
2016-11-23 23:53:38 +01:00
2018-07-08 02:23:19 +02:00
// Wheel
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
EM_BOOL OS_JavaScript : : wheel_callback ( int p_event_type , const EmscriptenWheelEvent * p_event , void * p_user_data ) {
2016-11-23 23:53:38 +01:00
2018-07-08 02:23:19 +02:00
ERR_FAIL_COND_V ( p_event_type ! = EMSCRIPTEN_EVENT_WHEEL , false ) ;
if ( ! is_canvas_focused ( ) ) {
if ( cursor_inside_canvas ) {
focus_canvas ( ) ;
} else {
return false ;
}
}
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
InputDefault * input = get_singleton ( ) - > input ;
Ref < InputEventMouseButton > ev ;
ev . instance ( ) ;
ev - > set_position ( input - > get_mouse_position ( ) ) ;
ev - > set_global_position ( ev - > get_position ( ) ) ;
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
ev - > set_shift ( input - > is_key_pressed ( KEY_SHIFT ) ) ;
ev - > set_alt ( input - > is_key_pressed ( KEY_ALT ) ) ;
ev - > set_control ( input - > is_key_pressed ( KEY_CONTROL ) ) ;
ev - > set_metakey ( input - > is_key_pressed ( KEY_META ) ) ;
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
if ( p_event - > deltaY < 0 )
ev - > set_button_index ( BUTTON_WHEEL_UP ) ;
else if ( p_event - > deltaY > 0 )
ev - > set_button_index ( BUTTON_WHEEL_DOWN ) ;
else if ( p_event - > deltaX > 0 )
ev - > set_button_index ( BUTTON_WHEEL_LEFT ) ;
else if ( p_event - > deltaX < 0 )
ev - > set_button_index ( BUTTON_WHEEL_RIGHT ) ;
else
return false ;
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
// Different browsers give wildly different delta values, and we can't
// interpret deltaMode, so use default value for wheel events' factor.
2014-02-10 02:10:30 +01:00
2018-07-08 15:12:13 +02:00
int button_flag = 1 < < ( ev - > get_button_index ( ) - 1 ) ;
2018-07-08 02:23:19 +02:00
ev - > set_pressed ( true ) ;
2018-07-08 15:12:13 +02:00
ev - > set_button_mask ( input - > get_mouse_button_mask ( ) | button_flag ) ;
2018-07-08 02:23:19 +02:00
input - > parse_input_event ( ev ) ;
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
ev - > set_pressed ( false ) ;
2018-07-08 15:12:13 +02:00
ev - > set_button_mask ( input - > get_mouse_button_mask ( ) & ~ button_flag ) ;
2018-07-08 02:23:19 +02:00
input - > parse_input_event ( ev ) ;
return true ;
2014-02-10 02:10:30 +01:00
}
2018-07-08 02:23:19 +02:00
// Touch
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
bool OS_JavaScript : : has_touchscreen_ui_hint ( ) const {
/* clang-format off */
return EM_ASM_INT_V (
return ' ontouchstart ' in window ;
) ;
/* clang-format on */
2016-11-26 13:13:16 +01:00
}
2018-07-08 02:23:19 +02:00
EM_BOOL OS_JavaScript : : touch_press_callback ( int p_event_type , const EmscriptenTouchEvent * p_event , void * p_user_data ) {
2016-11-26 13:13:16 +01:00
2018-07-08 02:23:19 +02:00
OS_JavaScript * os = get_singleton ( ) ;
Ref < InputEventScreenTouch > ev ;
ev . instance ( ) ;
int lowest_id_index = - 1 ;
for ( int i = 0 ; i < p_event - > numTouches ; + + i ) {
2016-11-26 13:13:16 +01:00
2018-07-08 02:23:19 +02:00
const EmscriptenTouchPoint & touch = p_event - > touches [ i ] ;
if ( lowest_id_index = = - 1 | | touch . identifier < p_event - > touches [ lowest_id_index ] . identifier )
lowest_id_index = i ;
if ( ! touch . isChanged )
continue ;
ev - > set_index ( touch . identifier ) ;
2019-06-14 22:37:21 +02:00
ev - > set_position ( correct_canvas_position ( touch . canvasX , touch . canvasY ) ) ;
2018-07-08 02:23:19 +02:00
os - > touches [ i ] = ev - > get_position ( ) ;
ev - > set_pressed ( p_event_type = = EMSCRIPTEN_EVENT_TOUCHSTART ) ;
2016-11-26 13:13:16 +01:00
2018-07-08 02:23:19 +02:00
os - > input - > parse_input_event ( ev ) ;
2016-11-26 13:13:16 +01:00
}
2019-01-20 14:25:15 +01:00
// Resume audio context after input in case autoplay was denied.
os - > audio_driver_javascript . resume ( ) ;
2018-07-08 02:23:19 +02:00
return true ;
2014-02-10 02:10:30 +01:00
}
2015-04-02 19:54:36 +02:00
2018-07-08 02:23:19 +02:00
EM_BOOL OS_JavaScript : : touchmove_callback ( int p_event_type , const EmscriptenTouchEvent * p_event , void * p_user_data ) {
2015-04-02 19:54:36 +02:00
2018-07-08 02:23:19 +02:00
OS_JavaScript * os = get_singleton ( ) ;
Ref < InputEventScreenDrag > ev ;
ev . instance ( ) ;
int lowest_id_index = - 1 ;
for ( int i = 0 ; i < p_event - > numTouches ; + + i ) {
2016-11-26 13:13:16 +01:00
2018-07-08 02:23:19 +02:00
const EmscriptenTouchPoint & touch = p_event - > touches [ i ] ;
if ( lowest_id_index = = - 1 | | touch . identifier < p_event - > touches [ lowest_id_index ] . identifier )
lowest_id_index = i ;
if ( ! touch . isChanged )
continue ;
ev - > set_index ( touch . identifier ) ;
2019-06-14 22:37:21 +02:00
ev - > set_position ( correct_canvas_position ( touch . canvasX , touch . canvasY ) ) ;
2018-07-08 02:23:19 +02:00
Point2 & prev = os - > touches [ i ] ;
ev - > set_relative ( ev - > get_position ( ) - prev ) ;
prev = ev - > get_position ( ) ;
2016-11-26 13:13:16 +01:00
2018-07-08 02:23:19 +02:00
os - > input - > parse_input_event ( ev ) ;
2017-07-25 20:55:44 +02:00
}
2018-07-08 02:23:19 +02:00
return true ;
}
2017-07-25 20:55:44 +02:00
2018-07-08 02:23:19 +02:00
// Gamepad
2017-07-25 20:55:44 +02:00
2018-07-08 02:23:19 +02:00
EM_BOOL OS_JavaScript : : gamepad_change_callback ( int p_event_type , const EmscriptenGamepadEvent * p_event , void * p_user_data ) {
InputDefault * input = get_singleton ( ) - > input ;
if ( p_event_type = = EMSCRIPTEN_EVENT_GAMEPADCONNECTED ) {
String guid = " " ;
if ( String : : utf8 ( p_event - > mapping ) = = " standard " )
guid = " Default HTML5 Gamepad " ;
input - > joy_connection_changed ( p_event - > index , true , String : : utf8 ( p_event - > id ) , guid ) ;
} else {
input - > joy_connection_changed ( p_event - > index , false , " " ) ;
2016-11-26 13:13:16 +01:00
}
2018-07-08 02:23:19 +02:00
return true ;
2016-11-26 13:13:16 +01:00
}
2018-07-08 02:23:19 +02:00
void OS_JavaScript : : process_joypads ( ) {
2016-11-26 13:13:16 +01:00
2018-07-08 02:23:19 +02:00
int joypad_count = emscripten_get_num_gamepads ( ) ;
for ( int joypad = 0 ; joypad < joypad_count ; joypad + + ) {
EmscriptenGamepadEvent state ;
2018-08-30 02:23:06 +02:00
EMSCRIPTEN_RESULT query_result = emscripten_get_gamepad_status ( joypad , & state ) ;
// Chromium reserves gamepads slots, so NO_DATA is an expected result.
ERR_CONTINUE ( query_result ! = EMSCRIPTEN_RESULT_SUCCESS & &
query_result ! = EMSCRIPTEN_RESULT_NO_DATA ) ;
if ( query_result = = EMSCRIPTEN_RESULT_SUCCESS & & state . connected ) {
2016-11-26 13:13:16 +01:00
2018-07-08 02:23:19 +02:00
int button_count = MIN ( state . numButtons , 18 ) ;
int axis_count = MIN ( state . numAxes , 8 ) ;
for ( int button = 0 ; button < button_count ; button + + ) {
float value = state . analogButton [ button ] ;
if ( String : : utf8 ( state . mapping ) = = " standard " & & ( button = = JOY_ANALOG_L2 | | button = = JOY_ANALOG_R2 ) ) {
InputDefault : : JoyAxis joy_axis ;
joy_axis . min = 0 ;
joy_axis . value = value ;
input - > joy_axis ( joypad , button , joy_axis ) ;
} else {
input - > joy_button ( joypad , button , value ) ;
}
}
for ( int axis = 0 ; axis < axis_count ; axis + + ) {
InputDefault : : JoyAxis joy_axis ;
joy_axis . min = - 1 ;
joy_axis . value = state . axis [ axis ] ;
input - > joy_axis ( joypad , axis , joy_axis ) ;
}
2016-11-26 13:13:16 +01:00
}
}
}
2018-07-08 02:23:19 +02:00
bool OS_JavaScript : : is_joy_known ( int p_device ) {
2016-11-26 13:13:16 +01:00
2018-07-08 02:23:19 +02:00
return input - > is_joy_mapped ( p_device ) ;
2015-04-02 19:54:36 +02:00
}
2018-07-08 02:23:19 +02:00
String OS_JavaScript : : get_joy_guid ( int p_device ) const {
2017-07-25 20:55:44 +02:00
2018-07-08 02:23:19 +02:00
return input - > get_joy_guid_remapped ( p_device ) ;
2017-07-25 20:55:44 +02:00
}
2018-07-08 02:23:19 +02:00
// Video
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
int OS_JavaScript : : get_video_driver_count ( ) const {
return VIDEO_DRIVER_MAX ;
2014-02-10 02:10:30 +01:00
}
2018-07-08 02:23:19 +02:00
const char * OS_JavaScript : : get_video_driver_name ( int p_driver ) const {
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
switch ( p_driver ) {
case VIDEO_DRIVER_GLES3 :
return " GLES3 " ;
case VIDEO_DRIVER_GLES2 :
return " GLES2 " ;
}
2019-08-09 06:49:33 +02:00
ERR_FAIL_V_MSG ( NULL , " Invalid video driver index: " + itos ( p_driver ) + " . " ) ;
2014-02-10 02:10:30 +01:00
}
2018-07-08 02:23:19 +02:00
// Audio
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
int OS_JavaScript : : get_audio_driver_count ( ) const {
return 1 ;
2014-02-10 02:10:30 +01:00
}
2018-07-08 02:23:19 +02:00
const char * OS_JavaScript : : get_audio_driver_name ( int p_driver ) const {
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
return " JavaScript " ;
2014-02-10 02:10:30 +01:00
}
2019-05-28 12:59:29 +02:00
// Clipboard
2019-05-28 22:27:03 +02:00
extern " C " EMSCRIPTEN_KEEPALIVE void update_clipboard ( const char * p_text ) {
// Only call set_clipboard from OS (sets local clipboard)
OS : : get_singleton ( ) - > OS : : set_clipboard ( p_text ) ;
}
2019-05-28 12:59:29 +02:00
void OS_JavaScript : : set_clipboard ( const String & p_text ) {
OS : : set_clipboard ( p_text ) ;
/* clang-format off */
int err = EM_ASM_INT ( {
var text = UTF8ToString ( $ 0 ) ;
if ( ! navigator . clipboard | | ! navigator . clipboard . writeText )
return 1 ;
2019-10-24 16:46:31 +02:00
navigator . clipboard . writeText ( text ) . catch ( function ( e ) {
2019-05-28 12:59:29 +02:00
// Setting OS clipboard is only possible from an input callback.
console . error ( " Setting OS clipboard is only possible from an input callback for the HTML5 plafrom. Exception: " , e ) ;
} ) ;
return 0 ;
} , p_text . utf8 ( ) . get_data ( ) ) ;
/* clang-format on */
2019-08-09 06:49:33 +02:00
ERR_FAIL_COND_MSG ( err , " Clipboard API is not supported. " ) ;
2019-05-28 12:59:29 +02:00
}
2019-05-29 21:30:29 +02:00
String OS_JavaScript : : get_clipboard ( ) const {
/* clang-format off */
EM_ASM ( {
try {
navigator . clipboard . readText ( ) . then ( function ( result ) {
ccall ( ' update_clipboard ' , ' void ' , [ ' string ' ] , [ result ] ) ;
} ) . catch ( function ( e ) {
// Fail graciously.
} ) ;
} catch ( e ) {
// Fail graciously.
}
} ) ;
/* clang-format on */
return this - > OS : : get_clipboard ( ) ;
}
2018-07-08 02:23:19 +02:00
// Lifecycle
2018-07-19 23:58:15 +02:00
int OS_JavaScript : : get_current_video_driver ( ) const {
return video_driver_index ;
}
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
void OS_JavaScript : : initialize_core ( ) {
2017-04-29 21:35:31 +02:00
2018-07-08 02:23:19 +02:00
OS_Unix : : initialize_core ( ) ;
FileAccess : : make_default < FileAccessBufferedFA < FileAccessUnix > > ( FileAccess : : ACCESS_RESOURCES ) ;
2014-02-10 02:10:30 +01:00
}
2018-07-08 02:23:19 +02:00
Error OS_JavaScript : : initialize ( const VideoMode & p_desired , int p_video_driver , int p_audio_driver ) {
EmscriptenWebGLContextAttributes attributes ;
emscripten_webgl_init_context_attributes ( & attributes ) ;
attributes . alpha = false ;
attributes . antialias = false ;
ERR_FAIL_INDEX_V ( p_video_driver , VIDEO_DRIVER_MAX , ERR_INVALID_PARAMETER ) ;
2018-08-25 00:04:25 +02:00
bool gles3 = true ;
if ( p_video_driver = = VIDEO_DRIVER_GLES2 ) {
gles3 = false ;
}
bool gl_initialization_error = false ;
while ( true ) {
if ( gles3 ) {
if ( RasterizerGLES3 : : is_viable ( ) = = OK ) {
attributes . majorVersion = 2 ;
RasterizerGLES3 : : register_config ( ) ;
RasterizerGLES3 : : make_current ( ) ;
break ;
} else {
2019-03-05 13:46:51 +01:00
if ( GLOBAL_GET ( " rendering/quality/driver/fallback_to_gles2 " ) ) {
2018-08-25 00:04:25 +02:00
p_video_driver = VIDEO_DRIVER_GLES2 ;
gles3 = false ;
continue ;
} else {
gl_initialization_error = true ;
break ;
}
}
} else {
if ( RasterizerGLES2 : : is_viable ( ) = = OK ) {
attributes . majorVersion = 1 ;
RasterizerGLES2 : : register_config ( ) ;
RasterizerGLES2 : : make_current ( ) ;
break ;
} else {
gl_initialization_error = true ;
break ;
}
}
2018-07-08 02:23:19 +02:00
}
2018-07-19 23:58:15 +02:00
2018-07-08 02:23:19 +02:00
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context ( NULL , & attributes ) ;
2018-08-25 00:04:25 +02:00
if ( emscripten_webgl_make_context_current ( ctx ) ! = EMSCRIPTEN_RESULT_SUCCESS ) {
gl_initialization_error = true ;
}
if ( gl_initialization_error ) {
OS : : get_singleton ( ) - > alert ( " Your browser does not support any of the supported WebGL versions. \n "
" Please update your browser version. " ,
" Unable to initialize Video driver " ) ;
return ERR_UNAVAILABLE ;
}
video_driver_index = p_video_driver ;
2018-07-08 02:23:19 +02:00
video_mode = p_desired ;
2019-01-23 03:39:45 +01:00
// fullscreen_change_callback will correct this if the request is successful.
2018-07-08 02:23:19 +02:00
video_mode . fullscreen = false ;
2019-01-23 03:39:45 +01:00
// Emscripten only attempts fullscreen requests if the user input callback
// was registered through one its own functions, so request manually for
// start-up fullscreen.
if ( p_desired . fullscreen ) {
/* clang-format off */
EM_ASM ( {
( canvas . requestFullscreen | | canvas . msRequestFullscreen | |
canvas . mozRequestFullScreen | | canvas . mozRequestFullscreen | |
canvas . webkitRequestFullscreen
) . call ( canvas ) ;
} ) ;
/* clang-format on */
}
2018-07-08 02:23:19 +02:00
/* clang-format off */
if ( EM_ASM_INT_V ( { return Module . resizeCanvasOnStart } ) ) {
/* clang-format on */
set_window_size ( Size2 ( video_mode . width , video_mode . height ) ) ;
} else {
set_window_size ( get_window_size ( ) ) ;
}
char locale_ptr [ 16 ] ;
/* clang-format off */
EM_ASM_ARGS ( {
stringToUTF8 ( Module . locale , $ 0 , 16 ) ;
} , locale_ptr ) ;
/* clang-format on */
setenv ( " LANG " , locale_ptr , true ) ;
2018-01-05 16:37:31 +01:00
2018-07-08 02:23:19 +02:00
AudioDriverManager : : initialize ( p_audio_driver ) ;
VisualServer * visual_server = memnew ( VisualServerRaster ( ) ) ;
input = memnew ( InputDefault ) ;
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
EMSCRIPTEN_RESULT result ;
# define EM_CHECK(ev) \
if ( result ! = EMSCRIPTEN_RESULT_SUCCESS ) \
ERR_PRINTS ( " Error while setting " # ev " callback: Code " + itos ( result ) )
# define SET_EM_CALLBACK(target, ev, cb) \
result = emscripten_set_ # # ev # # _callback ( target , NULL , true , & cb ) ; \
EM_CHECK ( ev )
# define SET_EM_CALLBACK_NOTARGET(ev, cb) \
result = emscripten_set_ # # ev # # _callback ( NULL , true , & cb ) ; \
EM_CHECK ( ev )
// These callbacks from Emscripten's html5.h suffice to access most
// JavaScript APIs. For APIs that are not (sufficiently) exposed, EM_ASM
// is used below.
SET_EM_CALLBACK ( " #window " , mousemove , mousemove_callback )
SET_EM_CALLBACK ( " #canvas " , mousedown , mouse_button_callback )
SET_EM_CALLBACK ( " #window " , mouseup , mouse_button_callback )
SET_EM_CALLBACK ( " #window " , wheel , wheel_callback )
SET_EM_CALLBACK ( " #window " , touchstart , touch_press_callback )
SET_EM_CALLBACK ( " #window " , touchmove , touchmove_callback )
SET_EM_CALLBACK ( " #window " , touchend , touch_press_callback )
SET_EM_CALLBACK ( " #window " , touchcancel , touch_press_callback )
SET_EM_CALLBACK ( " #canvas " , keydown , keydown_callback )
SET_EM_CALLBACK ( " #canvas " , keypress , keypress_callback )
SET_EM_CALLBACK ( " #canvas " , keyup , keyup_callback )
SET_EM_CALLBACK ( NULL , fullscreenchange , fullscreen_change_callback )
SET_EM_CALLBACK_NOTARGET ( gamepadconnected , gamepad_change_callback )
SET_EM_CALLBACK_NOTARGET ( gamepaddisconnected , gamepad_change_callback )
# undef SET_EM_CALLBACK_NODATA
# undef SET_EM_CALLBACK
# undef EM_CHECK
2017-09-03 01:44:00 +02:00
/* clang-format off */
EM_ASM_ARGS ( {
2018-01-06 15:53:04 +01:00
const send_notification = cwrap ( ' send_notification ' , null , [ ' number ' ] ) ;
2018-07-08 02:23:19 +02:00
const notifications = arguments ;
( [ ' mouseover ' , ' mouseleave ' , ' focus ' , ' blur ' ] ) . forEach ( function ( event , index ) {
Module . canvas . addEventListener ( event , send_notification . bind ( null , notifications [ index ] ) ) ;
2017-09-03 01:44:00 +02:00
} ) ;
2019-05-28 22:27:03 +02:00
// Clipboard
const update_clipboard = cwrap ( ' update_clipboard ' , null , [ ' string ' ] ) ;
window . addEventListener ( ' paste ' , function ( evt ) {
update_clipboard ( evt . clipboardData . getData ( ' text ' ) ) ;
} , true ) ;
2017-09-03 01:44:00 +02:00
} ,
MainLoop : : NOTIFICATION_WM_MOUSE_ENTER ,
MainLoop : : NOTIFICATION_WM_MOUSE_EXIT ,
MainLoop : : NOTIFICATION_WM_FOCUS_IN ,
MainLoop : : NOTIFICATION_WM_FOCUS_OUT
) ;
/* clang-format on */
2018-07-08 02:23:19 +02:00
visual_server - > init ( ) ;
return OK ;
2014-02-10 02:10:30 +01:00
}
2017-09-03 01:44:00 +02:00
2018-07-08 02:23:19 +02:00
void OS_JavaScript : : set_main_loop ( MainLoop * p_main_loop ) {
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
main_loop = p_main_loop ;
input - > set_main_loop ( p_main_loop ) ;
}
MainLoop * OS_JavaScript : : get_main_loop ( ) const {
return main_loop ;
}
void OS_JavaScript : : run_async ( ) {
main_loop - > init ( ) ;
emscripten_set_main_loop ( main_loop_callback , - 1 , false ) ;
}
void OS_JavaScript : : main_loop_callback ( ) {
get_singleton ( ) - > main_loop_iterate ( ) ;
}
2015-09-12 15:54:47 +02:00
2018-07-08 02:23:19 +02:00
bool OS_JavaScript : : main_loop_iterate ( ) {
if ( is_userfs_persistent ( ) & & sync_wait_time > = 0 ) {
int64_t current_time = get_ticks_msec ( ) ;
int64_t elapsed_time = current_time - last_sync_check_time ;
last_sync_check_time = current_time ;
2015-09-12 15:54:47 +02:00
2018-07-08 02:23:19 +02:00
sync_wait_time - = elapsed_time ;
2015-09-12 15:54:47 +02:00
2018-07-08 02:23:19 +02:00
if ( sync_wait_time < 0 ) {
2017-01-15 20:00:04 +01:00
/* clang-format off */
2015-09-12 15:54:47 +02:00
EM_ASM (
2019-02-24 04:40:06 +01:00
FS . syncfs ( function ( error ) {
if ( error ) { err ( ' Failed to save IDB file system : ' + error . message ) ; }
2017-01-12 14:14:40 +01:00
} ) ;
2015-09-12 15:54:47 +02:00
) ;
2017-01-15 20:00:04 +01:00
/* clang-format on */
2015-09-12 15:54:47 +02:00
}
}
2018-08-12 15:39:32 +02:00
2019-01-19 19:39:17 +01:00
if ( emscripten_sample_gamepad_data ( ) = = EMSCRIPTEN_RESULT_SUCCESS )
process_joypads ( ) ;
2017-07-25 20:55:44 +02:00
2018-08-12 15:39:32 +02:00
if ( just_exited_fullscreen ) {
if ( window_maximized ) {
EmscriptenFullscreenStrategy strategy ;
strategy . scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH ;
strategy . canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF ;
strategy . filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT ;
strategy . canvasResizedCallback = NULL ;
emscripten_enter_soft_fullscreen ( NULL , & strategy ) ;
} else {
2018-10-02 02:30:27 +02:00
emscripten_set_canvas_element_size ( NULL , windowed_size . width , windowed_size . height ) ;
2017-07-25 20:55:44 +02:00
}
2018-08-12 15:39:32 +02:00
just_exited_fullscreen = false ;
2017-07-25 20:55:44 +02:00
}
2018-08-12 15:39:32 +02:00
2018-10-02 02:30:27 +02:00
int canvas [ 2 ] ;
emscripten_get_canvas_element_size ( NULL , canvas , canvas + 1 ) ;
2018-08-12 15:39:32 +02:00
video_mode . width = canvas [ 0 ] ;
video_mode . height = canvas [ 1 ] ;
if ( ! window_maximized & & ! video_mode . fullscreen & & ! just_exited_fullscreen & & ! entering_fullscreen ) {
windowed_size . width = canvas [ 0 ] ;
windowed_size . height = canvas [ 1 ] ;
}
2014-02-10 02:10:30 +01:00
return Main : : iteration ( ) ;
}
2018-07-08 02:23:19 +02:00
void OS_JavaScript : : delete_main_loop ( ) {
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
memdelete ( main_loop ) ;
2014-02-10 02:10:30 +01:00
}
2018-07-08 02:23:19 +02:00
void OS_JavaScript : : finalize ( ) {
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
memdelete ( input ) ;
2014-02-10 02:10:30 +01:00
}
2018-07-08 02:23:19 +02:00
// Miscellaneous
2014-02-10 02:10:30 +01:00
2019-05-14 10:28:27 +02:00
Error OS_JavaScript : : execute ( const String & p_path , const List < String > & p_arguments , bool p_blocking , ProcessID * r_child_id , String * r_pipe , int * r_exitcode , bool read_stderr , Mutex * p_pipe_mutex ) {
2018-10-29 21:15:15 +01:00
2019-08-09 06:49:33 +02:00
ERR_FAIL_V_MSG ( ERR_UNAVAILABLE , " OS::execute() is not available on the HTML5 platform. " ) ;
2018-10-29 21:15:15 +01:00
}
Error OS_JavaScript : : kill ( const ProcessID & p_pid ) {
2019-08-09 06:49:33 +02:00
ERR_FAIL_V_MSG ( ERR_UNAVAILABLE , " OS::kill() is not available on the HTML5 platform. " ) ;
2018-10-29 21:15:15 +01:00
}
int OS_JavaScript : : get_process_id ( ) const {
2019-08-09 06:49:33 +02:00
ERR_FAIL_V_MSG ( 0 , " OS::get_process_id() is not available on the HTML5 platform. " ) ;
2018-10-29 21:15:15 +01:00
}
2018-07-08 02:23:19 +02:00
extern " C " EMSCRIPTEN_KEEPALIVE void send_notification ( int p_notification ) {
if ( p_notification = = MainLoop : : NOTIFICATION_WM_MOUSE_ENTER | | p_notification = = MainLoop : : NOTIFICATION_WM_MOUSE_EXIT ) {
cursor_inside_canvas = p_notification = = MainLoop : : NOTIFICATION_WM_MOUSE_ENTER ;
}
OS_JavaScript : : get_singleton ( ) - > get_main_loop ( ) - > notification ( p_notification ) ;
2014-02-10 02:10:30 +01:00
}
2018-07-08 02:23:19 +02:00
bool OS_JavaScript : : _check_internal_feature_support ( const String & p_feature ) {
if ( p_feature = = " HTML5 " | | p_feature = = " web " )
return true ;
# ifdef JAVASCRIPT_EVAL_ENABLED
if ( p_feature = = " JavaScript " )
return true ;
# endif
return false ;
2014-02-10 02:10:30 +01:00
}
2018-07-08 02:23:19 +02:00
void OS_JavaScript : : alert ( const String & p_alert , const String & p_title ) {
2017-01-15 20:00:04 +01:00
/* clang-format off */
2016-11-18 18:52:44 +01:00
EM_ASM_ ( {
2018-07-08 02:23:19 +02:00
window . alert ( UTF8ToString ( $ 0 ) ) ;
} , p_alert . utf8 ( ) . get_data ( ) ) ;
2017-01-15 20:00:04 +01:00
/* clang-format on */
2016-11-18 18:52:44 +01:00
}
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
void OS_JavaScript : : set_window_title ( const String & p_title ) {
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
/* clang-format off */
EM_ASM_ ( {
document . title = UTF8ToString ( $ 0 ) ;
} , p_title . utf8 ( ) . get_data ( ) ) ;
/* clang-format on */
2014-02-10 02:10:30 +01:00
}
2018-09-16 19:37:36 +02:00
void OS_JavaScript : : set_icon ( const Ref < Image > & p_icon ) {
ERR_FAIL_COND ( p_icon . is_null ( ) ) ;
Ref < Image > icon = p_icon ;
if ( icon - > is_compressed ( ) ) {
icon = icon - > duplicate ( ) ;
2019-06-11 14:49:34 +02:00
ERR_FAIL_COND ( icon - > decompress ( ) ! = OK ) ;
2018-09-16 19:37:36 +02:00
}
if ( icon - > get_format ( ) ! = Image : : FORMAT_RGBA8 ) {
if ( icon = = p_icon )
icon = icon - > duplicate ( ) ;
icon - > convert ( Image : : FORMAT_RGBA8 ) ;
}
png_image png_meta ;
memset ( & png_meta , 0 , sizeof png_meta ) ;
png_meta . version = PNG_IMAGE_VERSION ;
png_meta . width = icon - > get_width ( ) ;
png_meta . height = icon - > get_height ( ) ;
png_meta . format = PNG_FORMAT_RGBA ;
PoolByteArray png ;
size_t len ;
PoolByteArray : : Read r = icon - > get_data ( ) . read ( ) ;
ERR_FAIL_COND ( ! png_image_write_get_memory_size ( png_meta , len , 0 , r . ptr ( ) , 0 , NULL ) ) ;
png . resize ( len ) ;
PoolByteArray : : Write w = png . write ( ) ;
ERR_FAIL_COND ( ! png_image_write_to_memory ( & png_meta , w . ptr ( ) , & len , 0 , r . ptr ( ) , 0 , NULL ) ) ;
w = PoolByteArray : : Write ( ) ;
r = png . read ( ) ;
/* clang-format off */
EM_ASM_ARGS ( {
var PNG_PTR = $ 0 ;
var PNG_LEN = $ 1 ;
var png = new Blob ( [ HEAPU8 . slice ( PNG_PTR , PNG_PTR + PNG_LEN ) ] , { type : " image/png " } ) ;
var url = URL . createObjectURL ( png ) ;
var link = document . getElementById ( ' - gd - engine - icon ' ) ;
if ( link = = = null ) {
link = document . createElement ( ' link ' ) ;
link . rel = ' icon ' ;
link . id = ' - gd - engine - icon ' ;
document . head . appendChild ( link ) ;
}
link . href = url ;
} , r . ptr ( ) , len ) ;
/* clang-format on */
}
2016-11-18 18:52:44 +01:00
String OS_JavaScript : : get_executable_path ( ) const {
2017-03-28 03:21:21 +02:00
return OS : : get_executable_path ( ) ;
2016-11-18 18:52:44 +01:00
}
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
Error OS_JavaScript : : shell_open ( String p_uri ) {
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
// Open URI in a new tab, browser will deal with it by protocol.
/* clang-format off */
EM_ASM_ ( {
window . open ( UTF8ToString ( $ 0 ) , ' _blank ' ) ;
} , p_uri . utf8 ( ) . get_data ( ) ) ;
/* clang-format on */
return OK ;
2015-09-12 15:54:47 +02:00
}
2014-02-10 02:10:30 +01:00
2019-05-20 19:36:24 +02:00
String OS_JavaScript : : get_name ( ) const {
2016-01-21 02:23:15 +01:00
2018-07-08 02:23:19 +02:00
return " HTML5 " ;
}
2016-01-21 02:23:15 +01:00
2018-07-08 02:23:19 +02:00
bool OS_JavaScript : : can_draw ( ) const {
2016-01-21 02:23:15 +01:00
2018-07-08 02:23:19 +02:00
return true ; // Always?
2016-01-21 02:23:15 +01:00
}
2018-07-08 02:23:19 +02:00
String OS_JavaScript : : get_user_data_dir ( ) const {
2016-01-21 02:23:15 +01:00
2018-07-08 02:23:19 +02:00
return " /userfs " ;
} ;
2016-01-21 02:23:15 +01:00
2018-07-08 02:23:19 +02:00
String OS_JavaScript : : get_resource_dir ( ) const {
2016-01-21 02:23:15 +01:00
2018-07-08 02:23:19 +02:00
return " / " ;
2016-01-21 02:23:15 +01:00
}
2017-09-12 21:09:06 +02:00
OS : : PowerState OS_JavaScript : : get_power_state ( ) {
2018-03-21 15:51:44 +01:00
WARN_PRINT ( " Power management is not supported for the HTML5 platform, defaulting to POWERSTATE_UNKNOWN " ) ;
return OS : : POWERSTATE_UNKNOWN ;
2016-07-23 13:15:55 +02:00
}
int OS_JavaScript : : get_power_seconds_left ( ) {
2018-03-21 15:51:44 +01:00
WARN_PRINT ( " Power management is not supported for the HTML5 platform, defaulting to -1 " ) ;
return - 1 ;
2016-07-23 13:15:55 +02:00
}
2014-02-10 02:10:30 +01:00
2016-07-23 13:15:55 +02:00
int OS_JavaScript : : get_power_percent_left ( ) {
2018-03-21 15:51:44 +01:00
WARN_PRINT ( " Power management is not supported for the HTML5 platform, defaulting to -1 " ) ;
return - 1 ;
2016-07-23 13:15:55 +02:00
}
2018-07-08 02:23:19 +02:00
void OS_JavaScript : : file_access_close_callback ( const String & p_file , int p_flags ) {
2018-01-12 00:15:21 +01:00
2018-07-08 02:23:19 +02:00
OS_JavaScript * os = get_singleton ( ) ;
if ( os - > is_userfs_persistent ( ) & & p_file . begins_with ( " /userfs " ) & & p_flags & FileAccess : : WRITE ) {
os - > last_sync_check_time = OS : : get_singleton ( ) - > get_ticks_msec ( ) ;
// Wait five seconds in case more files are about to be closed.
os - > sync_wait_time = 5000 ;
}
}
2018-01-12 00:15:21 +01:00
2018-07-08 02:23:19 +02:00
void OS_JavaScript : : set_idb_available ( bool p_idb_available ) {
2018-01-12 00:15:21 +01:00
2018-07-08 02:23:19 +02:00
idb_available = p_idb_available ;
2017-07-19 22:00:46 +02:00
}
2018-07-08 02:23:19 +02:00
bool OS_JavaScript : : is_userfs_persistent ( ) const {
2017-10-02 16:09:24 +02:00
2018-07-08 02:23:19 +02:00
return idb_available ;
2017-10-02 16:09:24 +02:00
}
2018-07-08 02:23:19 +02:00
OS_JavaScript * OS_JavaScript : : get_singleton ( ) {
2017-10-02 16:09:24 +02:00
2018-07-08 02:23:19 +02:00
return static_cast < OS_JavaScript * > ( OS : : get_singleton ( ) ) ;
2017-10-02 16:09:24 +02:00
}
2018-07-08 02:23:19 +02:00
OS_JavaScript : : OS_JavaScript ( int p_argc , char * p_argv [ ] ) {
List < String > arguments ;
for ( int i = 1 ; i < p_argc ; i + + ) {
arguments . push_back ( String : : utf8 ( p_argv [ i ] ) ) ;
}
set_cmdline ( p_argv [ 0 ] , arguments ) ;
2018-01-07 00:04:09 +01:00
2018-07-20 18:16:09 +02:00
last_click_button_index = - 1 ;
last_click_ms = 0 ;
last_click_pos = Point2 ( - 100 , - 100 ) ;
2017-03-05 16:44:50 +01:00
window_maximized = false ;
2018-08-12 15:39:32 +02:00
entering_fullscreen = false ;
just_exited_fullscreen = false ;
2014-02-10 02:10:30 +01:00
2018-07-08 02:23:19 +02:00
main_loop = NULL ;
idb_available = false ;
sync_wait_time = - 1 ;
2015-09-12 15:54:47 +02:00
2018-07-08 02:23:19 +02:00
AudioDriverManager : : add_driver ( & audio_driver_javascript ) ;
2018-01-07 00:04:09 +01:00
Vector < Logger * > loggers ;
loggers . push_back ( memnew ( StdLogger ) ) ;
_set_logger ( memnew ( CompositeLogger ( loggers ) ) ) ;
2018-03-04 18:18:05 +01:00
2018-07-08 02:23:19 +02:00
FileAccessUnix : : close_notification_func = file_access_close_callback ;
2014-02-10 02:10:30 +01:00
}