summaryrefslogtreecommitdiffstats
path: root/platform/javascript
diff options
context:
space:
mode:
Diffstat (limited to 'platform/javascript')
-rw-r--r--platform/javascript/SCsub41
-rw-r--r--platform/javascript/detect.py62
-rw-r--r--platform/javascript/export/export.cpp12
-rw-r--r--platform/javascript/godot_shell.html6
-rw-r--r--platform/javascript/os_javascript.cpp128
-rw-r--r--platform/javascript/os_javascript.h2
6 files changed, 174 insertions, 77 deletions
diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub
index 02ff2090f9..b804863ee1 100644
--- a/platform/javascript/SCsub
+++ b/platform/javascript/SCsub
@@ -19,29 +19,34 @@ javascript_objects = []
for x in javascript_files:
javascript_objects.append(env_javascript.Object(x))
-env.Append(LINKFLAGS=["-s", "EXPORTED_FUNCTIONS=\"['_main','_audio_server_mix_function','_main_after_fs_sync']\""])
+env.Append(LINKFLAGS=["-s", "EXPORTED_FUNCTIONS=\"['_main','_audio_server_mix_function','_main_after_fs_sync','_send_notification']\""])
env.Append(LINKFLAGS=["--shell-file", '"platform/javascript/godot_shell.html"'])
-html_file = env.Program('#bin/godot', javascript_objects, PROGSUFFIX=env["PROGSUFFIX"] + ".html")[0]
+# output file name without file extension
+basename = "godot" + env["PROGSUFFIX"]
+target_dir = env.Dir("#bin")
+js_file = target_dir.File(basename + ".js")
+implicit_targets = [js_file]
+
+zip_dir = target_dir.Dir('.javascript_zip')
+zip_files = env.InstallAs([zip_dir.File("godot.js"), zip_dir.File("godotfs.js")], [js_file, "#misc/dist/html_fs/godotfs.js"])
+
+if env['wasm'] == 'yes':
+ wasm_file = target_dir.File(basename+'.wasm')
+ implicit_targets.append(wasm_file)
+ zip_files.append(InstallAs(zip_dir.File('godot.wasm'), wasm_file))
+else:
+ asmjs_files = [target_dir.File(basename+'.asm.js'), target_dir.File(basename+'.html.mem')]
+ zip_files.append(InstallAs([zip_dir.File('godot.asm.js'), zip_dir.File('godot.mem')], asmjs_files))
+ implicit_targets.extend(asmjs_files)
+
+# HTML file must be the first target in the list
+html_file = env.Program(["#bin/godot"] + implicit_targets, javascript_objects, PROGSUFFIX=env["PROGSUFFIX"]+".html")[0]
Depends(html_file, "godot_shell.html")
-basename = "godot" + env["PROGSUFFIX"] # output file name without file extension
# Emscripten hardcodes file names, so replace common base name with
# placeholder while leaving extension; also change `.html.mem` to just `.mem`
fixup_html = env.Substfile(html_file, SUBST_DICT=[(basename, '$$GODOT_BASE'), ('.html.mem', '.mem')], SUBSTFILESUFFIX='.fixup.html')
-zip_dir = env.Dir('#bin/.javascript_zip')
-zip_files = []
-js_file = env.SideEffect(html_file.File(basename+'.js'), html_file)
-zip_files.append(env.InstallAs(
- [zip_dir.File('godot.html'), zip_dir.File('godot.js'), zip_dir.File('godotfs.js')],
- [fixup_html, js_file, '#misc/dist/html_fs/godotfs.js']))
-
-if env['wasm'] == 'yes':
- wasm_file = env.SideEffect(html_file.File(basename+'.wasm'), html_file)
- zip_files.append(env.InstallAs(zip_dir.File('godot.wasm'), wasm_file))
-else:
- asmjs_files = env.SideEffect([html_file.File(basename+'.asm.js'), html_file.File(basename+'.html.mem')], html_file)
- zip_files.append(env.InstallAs([zip_dir.File('godot.asm.js'), zip_dir.File('godot.mem')], asmjs_files))
-
-Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX']+env['ZIPSUFFIX'], ZIPROOT=zip_dir)
+zip_files.append(InstallAs(zip_dir.File('godot.html'), fixup_html))
+Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX']+env['ZIPSUFFIX'], ZIPROOT=zip_dir, ZIPCOMSTR="Archving $SOURCES as $TARGET")
diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py
index 41fe3fb027..68c8d1eea5 100644
--- a/platform/javascript/detect.py
+++ b/platform/javascript/detect.py
@@ -1,6 +1,6 @@
import os
-import sys
import string
+import sys
def is_active():
@@ -12,7 +12,8 @@ def get_name():
def can_build():
- return os.environ.has_key("EMSCRIPTEN_ROOT")
+
+ return (os.environ.has_key("EMSCRIPTEN_ROOT"))
def get_opts():
@@ -27,12 +28,12 @@ def get_flags():
return [
('tools', 'no'),
- ('module_etc1_enabled', 'no'),
('module_theora_enabled', 'no'),
]
def create(env):
+
# remove Windows' .exe suffix
return env.Clone(tools=['textfile', 'zip'], PROGSUFFIX='')
@@ -45,10 +46,26 @@ def escape_target_backslashes(target, source, env, for_signature):
def configure(env):
- env['ENV'] = os.environ
- env.Append(CPPPATH=['#platform/javascript'])
+ ## Build type
+
+ if (env["target"] == "release"):
+ env.Append(CCFLAGS=['-O3'])
+ env.Append(LINKFLAGS=['-O3'])
+ elif (env["target"] == "release_debug"):
+ env.Append(CCFLAGS=['-O2', '-DDEBUG_ENABLED'])
+ env.Append(LINKFLAGS=['-O2', '-s', 'ASSERTIONS=1'])
+ # retain function names at the cost of file size, for backtraces and profiling
+ env.Append(LINKFLAGS=['--profiling-funcs'])
+
+ elif (env["target"] == "debug"):
+ env.Append(CCFLAGS=['-O1', '-D_DEBUG', '-g', '-DDEBUG_ENABLED'])
+ env.Append(LINKFLAGS=['-O1', '-g'])
+
+ ## Compiler configuration
+
+ env['ENV'] = os.environ
env.PrependENVPath('PATH', os.environ['EMSCRIPTEN_ROOT'])
env['CC'] = 'emcc'
env['CXX'] = 'em++'
@@ -57,6 +74,7 @@ def configure(env):
# Emscripten's ar has issues with duplicate file names, so use cc
env['AR'] = 'emcc'
env['ARFLAGS'] = '-o'
+
if (os.name == 'nt'):
# use TempFileMunge on Windows since some commands get too long for
# cmd.exe even with spawn_fix
@@ -68,26 +86,20 @@ def configure(env):
env['OBJSUFFIX'] = '.bc'
env['LIBSUFFIX'] = '.bc'
- if (env["target"] == "release"):
- env.Append(CCFLAGS=['-O3'])
- env.Append(LINKFLAGS=['-O3'])
- elif (env["target"] == "release_debug"):
- env.Append(CCFLAGS=['-O2', '-DDEBUG_ENABLED'])
- env.Append(LINKFLAGS=['-O2', '-s', 'ASSERTIONS=1'])
- # retain function names at the cost of file size, for backtraces and profiling
- env.Append(LINKFLAGS=['--profiling-funcs'])
- elif (env["target"] == "debug"):
- env.Append(CCFLAGS=['-O1', '-D_DEBUG', '-g', '-DDEBUG_ENABLED'])
- env.Append(LINKFLAGS=['-O1', '-g'])
+ ## Compile flags
- # TODO: Move that to opus module's config
- if("module_opus_enabled" in env and env["module_opus_enabled"] != "no"):
- env.opus_fixed_point = "yes"
+ env.Append(CPPPATH=['#platform/javascript'])
+ env.Append(CPPFLAGS=['-DJAVASCRIPT_ENABLED', '-DUNIX_ENABLED', '-DPTHREAD_NO_RENAME', '-DTYPED_METHOD_BIND', '-DNO_THREADS'])
+ env.Append(CPPFLAGS=['-DGLES3_ENABLED'])
# These flags help keep the file size down
env.Append(CPPFLAGS=["-fno-exceptions", '-DNO_SAFE_CAST', '-fno-rtti'])
- env.Append(CPPFLAGS=['-DJAVASCRIPT_ENABLED', '-DUNIX_ENABLED', '-DPTHREAD_NO_RENAME', '-DTYPED_METHOD_BIND', '-DNO_THREADS'])
- env.Append(CPPFLAGS=['-DGLES3_ENABLED'])
+
+ if env['javascript_eval'] == 'yes':
+ env.Append(CPPFLAGS=['-DJAVASCRIPT_EVAL_ENABLED'])
+
+ ## Link flags
+
env.Append(LINKFLAGS=['-s', 'USE_WEBGL2=1'])
if (env['wasm'] == 'yes'):
@@ -101,8 +113,6 @@ def configure(env):
env.Append(LINKFLAGS=['-s', 'ASM_JS=1'])
env.Append(LINKFLAGS=['--separate-asm'])
- if env['javascript_eval'] == 'yes':
- env.Append(CPPFLAGS=['-DJAVASCRIPT_EVAL_ENABLED'])
-
-
- import methods
+ # TODO: Move that to opus module's config
+ if("module_opus_enabled" in env and env["module_opus_enabled"] != "no"):
+ env.opus_fixed_point = "yes"
diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp
index 4bdfdae39e..b436d52363 100644
--- a/platform/javascript/export/export.cpp
+++ b/platform/javascript/export/export.cpp
@@ -61,6 +61,7 @@ public:
virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const;
virtual String get_name() const;
+ virtual String get_os_name() const;
virtual Ref<Texture> get_logo() const;
virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const;
@@ -74,6 +75,12 @@ public:
virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags);
virtual Ref<Texture> get_run_icon() const;
+ virtual void get_platform_features(List<String> *r_features) {
+
+ r_features->push_back("web");
+ r_features->push_back("JavaScript");
+ }
+
EditorExportPlatformJavaScript();
};
@@ -167,6 +174,11 @@ String EditorExportPlatformJavaScript::get_name() const {
return "HTML5";
}
+String EditorExportPlatformJavaScript::get_os_name() const {
+
+ return "JavaScript";
+}
+
Ref<Texture> EditorExportPlatformJavaScript::get_logo() const {
return logo;
diff --git a/platform/javascript/godot_shell.html b/platform/javascript/godot_shell.html
index 6c7069a8f0..ee7399a129 100644
--- a/platform/javascript/godot_shell.html
+++ b/platform/javascript/godot_shell.html
@@ -83,6 +83,10 @@
color: white;
}
+ #canvas:focus {
+ outline: none;
+ }
+
/* Status display
* ============== */
@@ -147,7 +151,7 @@ $GODOT_HEAD_INCLUDE
</head>
<body>
<div id="container">
- <canvas id="canvas" width="640" height="480" onclick="canvas.ownerDocument.defaultView.focus();" oncontextmenu="event.preventDefault();">
+ <canvas id="canvas" width="640" height="480" tabindex="0" oncontextmenu="event.preventDefault();">
HTML5 canvas appears to be unsupported in the current browser.<br />
Please try updating or use a different browser.
</canvas>
diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp
index 9df26f1471..0708d46196 100644
--- a/platform/javascript/os_javascript.cpp
+++ b/platform/javascript/os_javascript.cpp
@@ -29,8 +29,8 @@
/*************************************************************************/
#include "os_javascript.h"
-#include "core/global_config.h"
#include "core/io/file_access_buffered_fa.h"
+#include "core/project_settings.h"
#include "dom_keys.h"
#include "drivers/gles3/rasterizer_gles3.h"
#include "drivers/unix/dir_access_unix.h"
@@ -145,6 +145,31 @@ static EM_BOOL _fullscreen_change_callback(int event_type, const EmscriptenFulls
static InputDefault *_input;
+static bool is_canvas_focused() {
+
+ /* clang-format off */
+ return EM_ASM_INT_V(
+ return document.activeElement == Module.canvas;
+ );
+ /* clang-format on */
+}
+
+static void focus_canvas() {
+
+ /* clang-format off */
+ EM_ASM(
+ Module.canvas.focus();
+ );
+ /* clang-format on */
+}
+
+static bool _cursor_inside_canvas = true;
+
+static bool is_cursor_inside_canvas() {
+
+ return _cursor_inside_canvas;
+}
+
static EM_BOOL _mousebutton_callback(int event_type, const EmscriptenMouseEvent *mouse_event, void *user_data) {
ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_MOUSEDOWN && event_type != EMSCRIPTEN_EVENT_MOUSEUP, false);
@@ -164,26 +189,42 @@ static EM_BOOL _mousebutton_callback(int event_type, const EmscriptenMouseEvent
}
int mask = _input->get_mouse_button_mask();
- if (ev->is_pressed())
+ if (ev->is_pressed()) {
+ // since the event is consumed, focus manually
+ if (!is_canvas_focused()) {
+ focus_canvas();
+ }
mask |= 1 << ev->get_button_index();
- else
+ } else if (mask & (1 << ev->get_button_index())) {
mask &= ~(1 << ev->get_button_index());
+ } else {
+ // release event, but press was outside the canvas, so ignore
+ return false;
+ }
ev->set_button_mask(mask >> 1);
_input->parse_input_event(ev);
+ // prevent selection dragging
return true;
}
static EM_BOOL _mousemove_callback(int event_type, const EmscriptenMouseEvent *mouse_event, void *user_data) {
ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_MOUSEMOVE, false);
+ OS_JavaScript *os = static_cast<OS_JavaScript *>(user_data);
+ int input_mask = _input->get_mouse_button_mask();
+ Point2 pos = Point2(mouse_event->canvasX, mouse_event->canvasY);
+ // outside the canvas, only read mouse movement if dragging started inside
+ // the canvas; imitating desktop app behaviour
+ if (!is_cursor_inside_canvas() && !input_mask)
+ return false;
Ref<InputEventMouseMotion> ev;
ev.instance();
dom2godot_mod(mouse_event, ev);
- ev->set_button_mask(_input->get_mouse_button_mask() >> 1);
+ ev->set_button_mask(input_mask >> 1);
- ev->set_position(Point2(mouse_event->canvasX, mouse_event->canvasY));
+ ev->set_position(pos);
ev->set_global_position(ev->get_position());
ev->set_relative(_input->get_mouse_position() - ev->get_position());
@@ -191,12 +232,20 @@ static EM_BOOL _mousemove_callback(int event_type, const EmscriptenMouseEvent *m
ev->set_speed(_input->get_last_mouse_speed());
_input->parse_input_event(ev);
- return true;
+ // don't suppress mouseover/leave events
+ return false;
}
static EM_BOOL _wheel_callback(int event_type, const EmscriptenWheelEvent *wheel_event, void *user_data) {
ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_WHEEL, false);
+ if (!is_canvas_focused()) {
+ if (is_cursor_inside_canvas()) {
+ focus_canvas();
+ } else {
+ return false;
+ }
+ }
Ref<InputEventMouseButton> ev;
ev.instance();
@@ -387,6 +436,15 @@ static EM_BOOL joy_callback_func(int p_type, const EmscriptenGamepadEvent *p_eve
return false;
}
+extern "C" {
+void send_notification(int notif) {
+ if (notif == MainLoop::NOTIFICATION_WM_MOUSE_ENTER || notif == MainLoop::NOTIFICATION_WM_MOUSE_EXIT) {
+ _cursor_inside_canvas = notif == MainLoop::NOTIFICATION_WM_MOUSE_ENTER;
+ }
+ OS_JavaScript::get_singleton()->get_main_loop()->notification(notif);
+}
+}
+
void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
print_line("Init OS");
@@ -465,17 +523,17 @@ void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, i
EM_CHECK(ev)
EMSCRIPTEN_RESULT result;
- SET_EM_CALLBACK("#canvas", mousemove, _mousemove_callback)
+ SET_EM_CALLBACK("#window", mousemove, _mousemove_callback)
SET_EM_CALLBACK("#canvas", mousedown, _mousebutton_callback)
- SET_EM_CALLBACK("#canvas", mouseup, _mousebutton_callback)
- SET_EM_CALLBACK("#canvas", wheel, _wheel_callback)
- SET_EM_CALLBACK("#canvas", touchstart, _touchpress_callback)
- SET_EM_CALLBACK("#canvas", touchmove, _touchmove_callback)
- SET_EM_CALLBACK("#canvas", touchend, _touchpress_callback)
- SET_EM_CALLBACK("#canvas", touchcancel, _touchpress_callback)
- SET_EM_CALLBACK(NULL, keydown, _keydown_callback)
- SET_EM_CALLBACK(NULL, keypress, _keypress_callback)
- SET_EM_CALLBACK(NULL, keyup, _keyup_callback)
+ SET_EM_CALLBACK("#window", mouseup, _mousebutton_callback)
+ SET_EM_CALLBACK("#window", wheel, _wheel_callback)
+ SET_EM_CALLBACK("#window", touchstart, _touchpress_callback)
+ SET_EM_CALLBACK("#window", touchmove, _touchmove_callback)
+ SET_EM_CALLBACK("#window", touchend, _touchpress_callback)
+ SET_EM_CALLBACK("#window", touchcancel, _touchpress_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, resize, _browser_resize_callback)
SET_EM_CALLBACK(NULL, fullscreenchange, _fullscreen_change_callback)
SET_EM_CALLBACK_NODATA(gamepadconnected, joy_callback_func)
@@ -485,9 +543,24 @@ void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, i
#undef SET_EM_CALLBACK
#undef EM_CHECK
+ /* clang-format off */
+ EM_ASM_ARGS({
+ const send_notification = Module.cwrap('send_notification', null, ['number']);
+ const notifs = arguments;
+ (['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, i) {
+ Module.canvas.addEventListener(event, send_notification.bind(this, notifs[i]));
+ });
+ },
+ MainLoop::NOTIFICATION_WM_MOUSE_ENTER,
+ MainLoop::NOTIFICATION_WM_MOUSE_EXIT,
+ MainLoop::NOTIFICATION_WM_FOCUS_IN,
+ MainLoop::NOTIFICATION_WM_FOCUS_OUT
+ );
+/* clang-format on */
+
#ifdef JAVASCRIPT_EVAL_ENABLED
javascript_eval = memnew(JavaScript);
- GlobalConfig::get_singleton()->add_singleton(GlobalConfig::Singleton("JavaScript", javascript_eval));
+ ProjectSettings::get_singleton()->add_singleton(ProjectSettings::Singleton("JavaScript", javascript_eval));
#endif
visual_server->init();
@@ -777,20 +850,6 @@ void OS_JavaScript::main_loop_end() {
main_loop->finish();
}
-void OS_JavaScript::main_loop_focusout() {
-
- if (main_loop)
- main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT);
- //audio_driver_javascript.set_pause(true);
-}
-
-void OS_JavaScript::main_loop_focusin() {
-
- if (main_loop)
- main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN);
- //audio_driver_javascript.set_pause(false);
-}
-
void OS_JavaScript::process_accelerometer(const Vector3 &p_accelerometer) {
input->set_accelerometer(p_accelerometer);
@@ -828,7 +887,7 @@ String OS_JavaScript::get_data_dir() const {
return get_data_dir_func();
*/
return "/userfs";
- //return GlobalConfig::get_singleton()->get_singleton_object("GodotOS")->call("get_data_dir");
+ //return ProjectSettings::get_singleton()->get_singleton_object("GodotOS")->call("get_data_dir");
};
String OS_JavaScript::get_executable_path() const {
@@ -911,6 +970,11 @@ int OS_JavaScript::get_power_percent_left() {
return power_manager->get_power_percent_left();
}
+bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) {
+
+ return p_feature == "web" || p_feature == "s3tc"; // TODO check for these features really being available
+}
+
OS_JavaScript::OS_JavaScript(const char *p_execpath, GetDataDirFunc p_get_data_dir_func) {
set_cmdline(p_execpath, get_cmdline_args());
main_loop = NULL;
diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h
index 65269148ec..24e96e20dd 100644
--- a/platform/javascript/os_javascript.h
+++ b/platform/javascript/os_javascript.h
@@ -164,6 +164,8 @@ public:
virtual int get_power_seconds_left();
virtual int get_power_percent_left();
+ virtual bool _check_internal_feature_support(const String &p_feature);
+
OS_JavaScript(const char *p_execpath, GetDataDirFunc p_get_data_dir_func);
~OS_JavaScript();
};