summaryrefslogtreecommitdiff
path: root/platform/javascript
diff options
context:
space:
mode:
Diffstat (limited to 'platform/javascript')
-rw-r--r--platform/javascript/SCsub49
-rw-r--r--platform/javascript/audio_driver_javascript.cpp149
-rw-r--r--platform/javascript/audio_driver_javascript.h11
-rw-r--r--platform/javascript/detect.py126
-rw-r--r--platform/javascript/dom_keys.inc (renamed from platform/javascript/dom_keys.h)17
-rw-r--r--platform/javascript/engine.js87
-rw-r--r--platform/javascript/export/export.cpp44
-rw-r--r--platform/javascript/http_client_javascript.cpp2
-rw-r--r--platform/javascript/http_request.js6
-rw-r--r--platform/javascript/javascript_eval.cpp4
-rw-r--r--platform/javascript/javascript_main.cpp31
-rw-r--r--platform/javascript/logo.pngbin2316 -> 1236 bytes
-rw-r--r--platform/javascript/os_javascript.cpp1231
-rw-r--r--platform/javascript/os_javascript.h137
-rw-r--r--platform/javascript/power_javascript.cpp73
-rw-r--r--platform/javascript/power_javascript.h53
-rw-r--r--platform/javascript/pre.js2
-rw-r--r--platform/javascript/run_icon.pngbin471 -> 290 bytes
18 files changed, 966 insertions, 1056 deletions
diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub
index 66a8a8d93c..a93c98a89f 100644
--- a/platform/javascript/SCsub
+++ b/platform/javascript/SCsub
@@ -3,38 +3,35 @@
Import('env')
javascript_files = [
- "os_javascript.cpp",
- "audio_driver_javascript.cpp",
- "javascript_main.cpp",
- "power_javascript.cpp",
- "http_client_javascript.cpp",
- "javascript_eval.cpp",
+ 'audio_driver_javascript.cpp',
+ 'http_client_javascript.cpp',
+ 'javascript_eval.cpp',
+ 'javascript_main.cpp',
+ 'os_javascript.cpp',
]
-env_javascript = env.Clone()
-if env['target'] == "profile":
- env_javascript.Append(CPPFLAGS=['-DPROFILER_ENABLED'])
-
-javascript_objects = []
-for x in javascript_files:
- javascript_objects.append(env_javascript.Object(x))
-
-env.Append(LINKFLAGS=["-s", "EXPORTED_FUNCTIONS=\"['_main','_main_after_fs_sync','_send_notification']\""])
-
-target_dir = env.Dir("#bin")
-build = env.add_program(['#bin/godot', target_dir.File('godot' + env['PROGSUFFIX'] + '.wasm')], javascript_objects, PROGSUFFIX=env['PROGSUFFIX'] + '.js');
+build = env.add_program(['#bin/godot${PROGSUFFIX}.js', '#bin/godot${PROGSUFFIX}.wasm'], javascript_files);
js, wasm = build
-js_libraries = []
-js_libraries.append(env.File('http_request.js'))
+js_libraries = [
+ 'http_request.js',
+]
for lib in js_libraries:
- env.Append(LINKFLAGS=['--js-library', lib.path])
+ env.Append(LINKFLAGS=['--js-library', env.File(lib).path])
env.Depends(build, js_libraries)
wrapper_start = env.File('pre.js')
wrapper_end = env.File('engine.js')
-js_final = env.Textfile('#bin/godot', [wrapper_start, js, wrapper_end], TEXTFILESUFFIX=env['PROGSUFFIX'] + '.wrapped.js')
-
-zip_dir = target_dir.Dir('.javascript_zip')
-zip_files = env.InstallAs([zip_dir.File('godot.js'), zip_dir.File('godot.wasm'), zip_dir.File('godot.html')], [js_final, wasm, '#misc/dist/html/default.html'])
-Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX'] + env['ZIPSUFFIX'], ZIPROOT=zip_dir, ZIPCOMSTR="Archving $SOURCES as $TARGET")
+js_wrapped = env.Textfile('#bin/godot', [wrapper_start, js, wrapper_end], TEXTFILESUFFIX='${PROGSUFFIX}.wrapped.js')
+
+zip_dir = env.Dir('#bin/.javascript_zip')
+zip_files = env.InstallAs([
+ zip_dir.File('godot.js'),
+ zip_dir.File('godot.wasm'),
+ zip_dir.File('godot.html')
+], [
+ js_wrapped,
+ wasm,
+ '#misc/dist/html/full-size.html'
+])
+env.Zip('#bin/godot', zip_files, ZIPROOT=zip_dir, ZIPSUFFIX='${PROGSUFFIX}${ZIPSUFFIX}', ZIPCOMSTR='Archving $SOURCES as $TARGET')
diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp
index 5bf345e6cd..7a6613bb32 100644
--- a/platform/javascript/audio_driver_javascript.cpp
+++ b/platform/javascript/audio_driver_javascript.cpp
@@ -32,113 +32,134 @@
#include <emscripten.h>
-AudioDriverJavaScript *AudioDriverJavaScript::singleton_js = NULL;
+AudioDriverJavaScript *AudioDriverJavaScript::singleton = NULL;
const char *AudioDriverJavaScript::get_name() const {
return "JavaScript";
}
-extern "C" EMSCRIPTEN_KEEPALIVE void js_audio_driver_mix_function(int p_frames) {
+extern "C" EMSCRIPTEN_KEEPALIVE void audio_driver_js_mix() {
- //print_line("MIXI! "+itos(p_frames));
- AudioDriverJavaScript::singleton_js->mix_to_js(p_frames);
+ AudioDriverJavaScript::singleton->mix_to_js();
}
-void AudioDriverJavaScript::mix_to_js(int p_frames) {
+void AudioDriverJavaScript::mix_to_js() {
- int todo = p_frames;
- int offset = 0;
+ int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode());
+ int sample_count = memarr_len(internal_buffer) / channel_count;
+ int32_t *stream_buffer = reinterpret_cast<int32_t *>(internal_buffer);
+ audio_server_process(sample_count, stream_buffer);
+ for (int i = 0; i < sample_count * channel_count; i++) {
+ internal_buffer[i] = float(stream_buffer[i] >> 16) / 32768.0;
+ }
+}
- while (todo) {
+Error AudioDriverJavaScript::init() {
- int tomix = MIN(todo, INTERNAL_BUFFER_SIZE);
+ /* clang-format off */
+ EM_ASM({
+ _audioDriver_audioContext = new (window.AudioContext || window.webkitAudioContext);
+ _audioDriver_scriptNode = null;
+ });
+ /* clang-format on */
- audio_server_process(p_frames, stream_buffer);
- for (int i = 0; i < tomix * internal_buffer_channels; i++) {
- internal_buffer[i] = float(stream_buffer[i] >> 16) / 32768.0;
+ int channel_count = get_total_channels_by_speaker_mode(get_speaker_mode());
+ /* clang-format off */
+ int buffer_length = EM_ASM_INT({
+ var CHANNEL_COUNT = $0;
+
+ var channelCount = _audioDriver_audioContext.destination.channelCount;
+ try {
+ // Try letting the browser recommend a buffer length.
+ _audioDriver_scriptNode = _audioDriver_audioContext.createScriptProcessor(0, 0, channelCount);
+ } catch (e) {
+ // ...otherwise, default to 4096.
+ _audioDriver_scriptNode = _audioDriver_audioContext.createScriptProcessor(4096, 0, channelCount);
}
+ _audioDriver_scriptNode.connect(_audioDriver_audioContext.destination);
- /* clang-format off */
- EM_ASM_ARGS({
- var data = HEAPF32.subarray($0 / 4, $0 / 4 + $2 * 2);
-
- for (var channel = 0; channel < _as_output_buffer.numberOfChannels; channel++) {
- var outputData = _as_output_buffer.getChannelData(channel);
- // Loop through samples
- for (var sample = 0; sample < $2; sample++) {
- // make output equal to the same as the input
- outputData[sample + $1] = data[sample * 2 + channel];
- }
- }
- }, internal_buffer, offset, tomix);
- /* clang-format on */
-
- todo -= tomix;
- offset += tomix;
+ return _audioDriver_scriptNode.bufferSize;
+ }, channel_count);
+ /* clang-format on */
+ if (!buffer_length) {
+ return FAILED;
}
-}
-
-Error AudioDriverJavaScript::init() {
- return OK;
+ if (!internal_buffer || memarr_len(internal_buffer) != buffer_length * channel_count) {
+ if (internal_buffer)
+ memdelete_arr(internal_buffer);
+ internal_buffer = memnew_arr(float, buffer_length *channel_count);
+ }
+ return internal_buffer ? OK : ERR_OUT_OF_MEMORY;
}
void AudioDriverJavaScript::start() {
- internal_buffer = memnew_arr(float, INTERNAL_BUFFER_SIZE *internal_buffer_channels);
- stream_buffer = memnew_arr(int32_t, INTERNAL_BUFFER_SIZE * 4); //max 4 channels
-
/* clang-format off */
- mix_rate = EM_ASM_INT({
- _as_audioctx = new (window.AudioContext || window.webkitAudioContext);
- _as_script_node = _as_audioctx.createScriptProcessor($0, 0, $1);
- _as_script_node.connect(_as_audioctx.destination);
- console.log(_as_script_node.bufferSize);
- var jsAudioDriverMixFunction = cwrap('js_audio_driver_mix_function', null, ['number']);
-
- _as_script_node.onaudioprocess = function(audioProcessingEvent) {
- // The output buffer contains the samples that will be modified and played
- _as_output_buffer = audioProcessingEvent.outputBuffer;
- jsAudioDriverMixFunction([_as_output_buffer.getChannelData(0).length]);
+ EM_ASM({
+ var INTERNAL_BUFFER_PTR = $0;
+
+ var audioDriverMixFunction = cwrap('audio_driver_js_mix');
+ _audioDriver_scriptNode.onaudioprocess = function(audioProcessingEvent) {
+ audioDriverMixFunction();
+ // The output buffer contains the samples that will be modified and played.
+ var output = audioProcessingEvent.outputBuffer;
+ var input = HEAPF32.subarray(
+ INTERNAL_BUFFER_PTR / HEAPF32.BYTES_PER_ELEMENT,
+ INTERNAL_BUFFER_PTR / HEAPF32.BYTES_PER_ELEMENT + output.length * output.numberOfChannels);
+
+ for (var channel = 0; channel < output.numberOfChannels; channel++) {
+ var outputData = output.getChannelData(channel);
+ // Loop through samples.
+ for (var sample = 0; sample < outputData.length; sample++) {
+ // Set output equal to input.
+ outputData[sample] = input[sample * output.numberOfChannels + channel];
+ }
+ }
};
- return _as_audioctx.sampleRate;
- }, INTERNAL_BUFFER_SIZE, internal_buffer_channels);
+ }, internal_buffer);
/* clang-format on */
}
int AudioDriverJavaScript::get_mix_rate() const {
- return mix_rate;
+ /* clang-format off */
+ return EM_ASM_INT_V({
+ return _audioDriver_audioContext.sampleRate;
+ });
+ /* clang-format on */
}
AudioDriver::SpeakerMode AudioDriverJavaScript::get_speaker_mode() const {
- return SPEAKER_MODE_STEREO;
+ /* clang-format off */
+ return get_speaker_mode_by_total_channels(EM_ASM_INT_V({
+ return _audioDriver_audioContext.destination.channelCount;
+ }));
+ /* clang-format on */
}
+// No locking, as threads are not supported.
void AudioDriverJavaScript::lock() {
-
- /*no locking, as threads are not supported
- if (active && mutex)
- mutex->lock();
- */
}
void AudioDriverJavaScript::unlock() {
-
- /*no locking, as threads are not supported
- if (active && mutex)
- mutex->unlock();
- */
}
void AudioDriverJavaScript::finish() {
+
+ /* clang-format off */
+ EM_ASM({
+ _audioDriver_audioContext = null;
+ _audioDriver_scriptNode = null;
+ });
+ /* clang-format on */
+ memdelete_arr(internal_buffer);
+ internal_buffer = NULL;
}
AudioDriverJavaScript::AudioDriverJavaScript() {
- internal_buffer_channels = 2;
- mix_rate = DEFAULT_MIX_RATE;
- singleton_js = this;
+ singleton = this;
}
diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h
index d78ab8eea4..a65a8ec29f 100644
--- a/platform/javascript/audio_driver_javascript.h
+++ b/platform/javascript/audio_driver_javascript.h
@@ -35,18 +35,11 @@
class AudioDriverJavaScript : public AudioDriver {
- enum {
- INTERNAL_BUFFER_SIZE = 4096,
- };
-
- int mix_rate;
float *internal_buffer;
- int internal_buffer_channels;
- int32_t *stream_buffer;
public:
- void mix_to_js(int p_frames);
- static AudioDriverJavaScript *singleton_js;
+ void mix_to_js();
+ static AudioDriverJavaScript *singleton;
virtual const char *get_name() const;
diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py
index 7e6a1518ed..fc909f6619 100644
--- a/platform/javascript/detect.py
+++ b/platform/javascript/detect.py
@@ -1,5 +1,4 @@
import os
-import string
import sys
@@ -8,23 +7,22 @@ def is_active():
def get_name():
- return "JavaScript"
+ return 'JavaScript'
def can_build():
-
- return ("EMSCRIPTEN_ROOT" in os.environ or "EMSCRIPTEN" in os.environ)
+ return 'EM_CONFIG' in os.environ or os.path.exists(os.path.expanduser('~/.emscripten'))
def get_opts():
from SCons.Variables import BoolVariable
return [
+ # eval() can be a security concern, so it can be disabled.
BoolVariable('javascript_eval', 'Enable JavaScript eval interface', True),
]
def get_flags():
-
return [
('tools', False),
('module_theora_enabled', False),
@@ -36,24 +34,11 @@ def get_flags():
]
-def create(env):
-
- # remove Windows' .exe suffix
- return env.Clone(tools=['textfile', 'zip'], PROGSUFFIX='')
-
-
-def escape_sources_backslashes(target, source, env, for_signature):
- return [path.replace('\\','\\\\') for path in env.GetBuildPath(source)]
-
-def escape_target_backslashes(target, source, env, for_signature):
- return env.GetBuildPath(target[0]).replace('\\','\\\\')
-
-
def configure(env):
## Build type
- if (env["target"] == "release"):
+ if env['target'] != 'debug':
# Use -Os to prioritize optimizing for reduced file size. This is
# particularly valuable for the web platform because it directly
# decreases download time.
@@ -62,65 +47,96 @@ def configure(env):
# run-time performance.
env.Append(CCFLAGS=['-Os'])
env.Append(LINKFLAGS=['-Os'])
-
- 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'])
+ if env['target'] == 'release_debug':
+ env.Append(CPPDEFINES=['DEBUG_ENABLED'])
+ # Retain function names for backtraces at the cost of file size.
+ env.Append(LINKFLAGS=['--profiling-funcs'])
+ else:
+ env.Append(CPPDEFINES=['DEBUG_ENABLED'])
+ env.Append(CCFLAGS=['-O1', '-g'])
env.Append(LINKFLAGS=['-O1', '-g'])
+ env.Append(LINKFLAGS=['-s', 'ASSERTIONS=1'])
## Compiler configuration
env['ENV'] = os.environ
- if ("EMSCRIPTEN_ROOT" in os.environ):
- env.PrependENVPath('PATH', os.environ['EMSCRIPTEN_ROOT'])
- elif ("EMSCRIPTEN" in os.environ):
- env.PrependENVPath('PATH', os.environ['EMSCRIPTEN'])
- env['CC'] = 'emcc'
- env['CXX'] = 'em++'
- env['LINK'] = 'emcc'
- env['RANLIB'] = 'emranlib'
- # 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
- # need to escape backslashes for this
- env['ESCAPED_SOURCES'] = escape_sources_backslashes
- env['ESCAPED_TARGET'] = escape_target_backslashes
- env['ARCOM'] = '${TEMPFILE("%s")}' % env['ARCOM'].replace('$SOURCES', '$ESCAPED_SOURCES').replace('$TARGET', '$ESCAPED_TARGET')
+ em_config_file = os.getenv('EM_CONFIG') or os.path.expanduser('~/.emscripten')
+ if not os.path.exists(em_config_file):
+ raise RuntimeError("Emscripten configuration file '%s' does not exist" % em_config_file)
+ with open(em_config_file) as f:
+ em_config = {}
+ try:
+ # Emscripten configuration file is a Python file with simple assignments.
+ exec(f.read(), em_config)
+ except StandardError as e:
+ raise RuntimeError("Emscripten configuration file '%s' is invalid:\n%s" % (em_config_file, e))
+ if 'EMSCRIPTEN_ROOT' not in em_config:
+ raise RuntimeError("'EMSCRIPTEN_ROOT' missing in Emscripten configuration file '%s'" % em_config_file)
+ env.PrependENVPath('PATH', em_config['EMSCRIPTEN_ROOT'])
+
+ env['CC'] = 'emcc'
+ env['CXX'] = 'em++'
+ env['LINK'] = 'emcc'
+
+ # Emscripten's ar has issues with duplicate file names, so use cc.
+ env['AR'] = 'emcc'
+ env['ARFLAGS'] = '-o'
+ # emranlib is a noop, so it's safe to use with AR=emcc.
+ env['RANLIB'] = 'emranlib'
+
+ # Use TempFileMunge since some AR invocations are too long for cmd.exe.
+ # Use POSIX-style paths, required with TempFileMunge.
+ env['ARCOM_POSIX'] = env['ARCOM'].replace(
+ '$TARGET', '$TARGET.posix').replace(
+ '$SOURCES', '$SOURCES.posix')
+ env['ARCOM'] = '${TEMPFILE(ARCOM_POSIX)}'
+
+ # All intermediate files are just LLVM bitcode.
+ env['OBJPREFIX'] = ''
env['OBJSUFFIX'] = '.bc'
+ env['PROGPREFIX'] = ''
+ # Program() output consists of multiple files, so specify suffixes manually at builder.
+ env['PROGSUFFIX'] = ''
+ env['LIBPREFIX'] = 'lib'
env['LIBSUFFIX'] = '.bc'
+ env['LIBPREFIXES'] = ['$LIBPREFIX']
+ env['LIBSUFFIXES'] = ['$LIBSUFFIX']
## Compile flags
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'])
+ env.Append(CPPDEFINES=['JAVASCRIPT_ENABLED', 'UNIX_ENABLED'])
- # These flags help keep the file size down
- env.Append(CPPFLAGS=["-fno-exceptions", '-DNO_SAFE_CAST', '-fno-rtti'])
+ # No multi-threading (SharedArrayBuffer) available yet,
+ # once feasible also consider memory buffer size issues.
+ env.Append(CPPDEFINES=['NO_THREADS'])
+
+ # These flags help keep the file size down.
+ env.Append(CCFLAGS=['-fno-exceptions', '-fno-rtti'])
+ # Don't use dynamic_cast, necessary with no-rtti.
+ env.Append(CPPDEFINES=['NO_SAFE_CAST'])
if env['javascript_eval']:
- env.Append(CPPFLAGS=['-DJAVASCRIPT_EVAL_ENABLED'])
+ env.Append(CPPDEFINES=['JAVASCRIPT_EVAL_ENABLED'])
## Link flags
env.Append(LINKFLAGS=['-s', 'BINARYEN=1'])
+
+ # Allow increasing memory buffer size during runtime. This is efficient
+ # when using WebAssembly (in comparison to asm.js) and works well for
+ # us since we don't know requirements at compile-time.
env.Append(LINKFLAGS=['-s', 'ALLOW_MEMORY_GROWTH=1'])
+
+ # This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 1.
env.Append(LINKFLAGS=['-s', 'USE_WEBGL2=1'])
- env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS="[\'FS\']"'])
env.Append(LINKFLAGS=['-s', 'INVOKE_RUN=0'])
+
+ # TODO: Reevaluate usage of this setting now that engine.js manages engine runtime.
env.Append(LINKFLAGS=['-s', 'NO_EXIT_RUNTIME=1'])
- # TODO: Move that to opus module's config
+ # TODO: Move that to opus module's config.
if 'module_opus_enabled' in env and env['module_opus_enabled']:
- env.opus_fixed_point = "yes"
+ env.opus_fixed_point = 'yes'
diff --git a/platform/javascript/dom_keys.h b/platform/javascript/dom_keys.inc
index 4edca63c6d..dc8d67d52b 100644
--- a/platform/javascript/dom_keys.h
+++ b/platform/javascript/dom_keys.inc
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* dom_keys.h */
+/* dom_keys.inc */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,9 +28,6 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef DOM_KEYS_H
-#define DOM_KEYS_H
-
#include "os/keyboard.h"
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#Constants_for_keyCode_value
@@ -295,8 +292,8 @@ int dom2godot_scancode(int dom_keycode) {
//case DOM_VK_SELECT: return KEY_UNKNOWN;
- case DOM_VK_PRINTSCREEN: // this is the usual printScreen key
- case DOM_VK_PRINT: // maybe for alt+printScreen or physical printers?
+ case DOM_VK_PRINTSCREEN:
+ case DOM_VK_PRINT:
return KEY_PRINT;
//case DOM_VK_EXECUTE: return KEY_UNKNOWN;
@@ -311,11 +308,11 @@ int dom2godot_scancode(int dom_keycode) {
case DOM_VK_SLEEP:
return KEY_STANDBY;
- // these are numpad keys according to MDN
+ // Numpad keys
case DOM_VK_MULTIPLY: return KEY_KP_MULTIPLY;
case DOM_VK_ADD: return KEY_KP_ADD;
case DOM_VK_SEPARATOR:
- return KEY_KP_PERIOD; // good enough?
+ return KEY_KP_PERIOD; // Good enough?
case DOM_VK_SUBTRACT: return KEY_KP_SUBTRACT;
case DOM_VK_DECIMAL: return KEY_KP_PERIOD;
case DOM_VK_DIVIDE:
@@ -376,10 +373,8 @@ int dom2godot_scancode(int dom_keycode) {
case DOM_VK_QUOTE:
return KEY_APOSTROPHE;
- // rest is OEM/unusual
+ // The rest is OEM/unusual.
default: return KEY_UNKNOWN;
};
}
-
-#endif
diff --git a/platform/javascript/engine.js b/platform/javascript/engine.js
index bca1851f40..c3ef5bbbb5 100644
--- a/platform/javascript/engine.js
+++ b/platform/javascript/engine.js
@@ -1,3 +1,5 @@
+ exposedLibs['PATH'] = PATH;
+ exposedLibs['FS'] = FS;
return Module;
},
};
@@ -8,10 +10,18 @@
var DOWNLOAD_ATTEMPTS_MAX = 4;
var basePath = null;
+ var wasmFilenameExtensionOverride = null;
var engineLoadPromise = null;
var loadingFiles = {};
+ function getPathLeaf(path) {
+
+ while (path.endsWith('/'))
+ path = path.slice(0, -1);
+ return path.slice(path.lastIndexOf('/') + 1);
+ }
+
function getBasePath(path) {
if (path.endsWith('/'))
@@ -23,14 +33,15 @@
function getBaseName(path) {
- path = getBasePath(path);
- return path.slice(path.lastIndexOf('/') + 1);
+ return getPathLeaf(getBasePath(path));
}
Engine = function Engine() {
this.rtenv = null;
+ var LIBS = {};
+
var initPromise = null;
var unloadAfterInit = true;
@@ -80,11 +91,11 @@
return new Promise(function(resolve, reject) {
rtenvProps.onRuntimeInitialized = resolve;
rtenvProps.onAbort = reject;
- rtenvProps.engine.rtenv = Engine.RuntimeEnvironment(rtenvProps);
+ rtenvProps.engine.rtenv = Engine.RuntimeEnvironment(rtenvProps, LIBS);
});
}
- this.preloadFile = function(pathOrBuffer, bufferFilename) {
+ this.preloadFile = function(pathOrBuffer, destPath) {
if (pathOrBuffer instanceof ArrayBuffer) {
pathOrBuffer = new Uint8Array(pathOrBuffer);
@@ -93,14 +104,14 @@
}
if (pathOrBuffer instanceof Uint8Array) {
preloadedFiles.push({
- name: bufferFilename,
+ path: destPath,
buffer: pathOrBuffer
});
return Promise.resolve();
} else if (typeof pathOrBuffer === 'string') {
return loadPromise(pathOrBuffer, preloadProgressTracker).then(function(xhr) {
preloadedFiles.push({
- name: pathOrBuffer,
+ path: destPath || pathOrBuffer,
buffer: xhr.response
});
});
@@ -119,8 +130,17 @@
this.startGame = function(mainPack) {
executableName = getBaseName(mainPack);
- return Promise.all([this.init(getBasePath(mainPack)), this.preloadFile(mainPack)]).then(
- Function.prototype.apply.bind(synchronousStart, this, [])
+ var mainArgs = [];
+ if (!getPathLeaf(mainPack).endsWith('.pck')) {
+ mainArgs = ['--main-pack', getPathLeaf(mainPack)];
+ }
+ return Promise.all([
+ // Load from directory,
+ this.init(getBasePath(mainPack)),
+ // ...but write to root where the engine expects it.
+ this.preloadFile(mainPack, getPathLeaf(mainPack))
+ ]).then(
+ Function.prototype.apply.bind(synchronousStart, this, mainArgs)
);
};
@@ -138,18 +158,6 @@
}
var actualCanvas = this.rtenv.canvas;
- var testContext = false;
- var testCanvas;
- try {
- testCanvas = document.createElement('canvas');
- testContext = testCanvas.getContext('webgl2') || testCanvas.getContext('experimental-webgl2');
- } catch (e) {}
- if (!testContext) {
- throw new Error("WebGL 2 not available");
- }
- testCanvas = null;
- testContext = null;
-
// canvas can grab focus on click
if (actualCanvas.tabIndex < 0) {
actualCanvas.tabIndex = 0;
@@ -158,6 +166,10 @@
actualCanvas.style.padding = 0;
actualCanvas.style.borderWidth = 0;
actualCanvas.style.borderStyle = 'none';
+ // disable right-click context menu
+ actualCanvas.addEventListener('contextmenu', function(ev) {
+ ev.preventDefault();
+ }, false);
// until context restoration is implemented
actualCanvas.addEventListener('webglcontextlost', function(ev) {
alert("WebGL context lost, please reload the page");
@@ -175,7 +187,16 @@
this.rtenv.thisProgram = executableName || getBaseName(basePath);
preloadedFiles.forEach(function(file) {
- this.rtenv.FS.createDataFile('/', file.name, new Uint8Array(file.buffer), true, true, true);
+ var dir = LIBS.PATH.dirname(file.path);
+ try {
+ LIBS.FS.stat(dir);
+ } catch (e) {
+ if (e.code !== 'ENOENT') {
+ throw e;
+ }
+ LIBS.FS.mkdirTree(dir);
+ }
+ LIBS.FS.createDataFile('/', file.path, new Uint8Array(file.buffer), true, true, true);
}, this);
preloadedFiles = null;
@@ -273,6 +294,28 @@
Engine.RuntimeEnvironment = engine.RuntimeEnvironment;
+ Engine.isWebGLAvailable = function(majorVersion = 1) {
+
+ var testContext = false;
+ try {
+ var testCanvas = document.createElement('canvas');
+ if (majorVersion === 1) {
+ testContext = testCanvas.getContext('webgl') || testCanvas.getContext('experimental-webgl');
+ } else if (majorVersion === 2) {
+ testContext = testCanvas.getContext('webgl2') || testCanvas.getContext('experimental-webgl2');
+ }
+ } catch (e) {}
+ return !!testContext;
+ };
+
+ Engine.setWebAssemblyFilenameExtension = function(override) {
+
+ if (String(override).length === 0) {
+ throw new Error('Invalid WebAssembly filename extension override');
+ }
+ wasmFilenameExtensionOverride = String(override);
+ }
+
Engine.load = function(newBasePath) {
if (newBasePath !== undefined) basePath = getBasePath(newBasePath);
@@ -280,7 +323,7 @@
if (typeof WebAssembly !== 'object')
return Promise.reject(new Error("Browser doesn't support WebAssembly"));
// TODO cache/retrieve module to/from idb
- engineLoadPromise = loadPromise(basePath + '.wasm').then(function(xhr) {
+ engineLoadPromise = loadPromise(basePath + '.' + (wasmFilenameExtensionOverride || 'wasm')).then(function(xhr) {
return xhr.response;
});
engineLoadPromise = engineLoadPromise.catch(function(err) {
diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp
index d81aa25c32..a7f0084562 100644
--- a/platform/javascript/export/export.cpp
+++ b/platform/javascript/export/export.cpp
@@ -74,6 +74,9 @@ public:
r_features->push_back(get_os_name());
}
+ virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, Set<String> &p_features) {
+ }
+
EditorExportPlatformJavaScript();
};
@@ -95,7 +98,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re
CharString cs = str_export.utf8();
p_html.resize(cs.length());
for (int i = 0; i < cs.length(); i++) {
- p_html[i] = cs[i];
+ p_html.write[i] = cs[i];
}
}
@@ -117,10 +120,10 @@ void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_op
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), false));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc2"), true));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_GLOBAL_FILE, "html"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_FILE, "*.html"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/head_include", PROPERTY_HINT_MULTILINE_TEXT), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "zip"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "zip"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
}
String EditorExportPlatformJavaScript::get_name() const {
@@ -140,14 +143,35 @@ Ref<Texture> EditorExportPlatformJavaScript::get_logo() const {
bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
- r_missing_templates = false;
+ bool valid = false;
+ String err;
+
+ if (find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE) != "")
+ valid = true;
+ else if (find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG) != "")
+ valid = true;
+
+ if (p_preset->get("custom_template/debug") != "") {
+ if (FileAccess::exists(p_preset->get("custom_template/debug"))) {
+ valid = true;
+ } else {
+ err += "Custom debug template not found.\n";
+ }
+ }
+
+ if (p_preset->get("custom_template/release") != "") {
+ if (FileAccess::exists(p_preset->get("custom_template/release"))) {
+ valid = true;
+ } else {
+ err += "Custom release template not found.\n";
+ }
+ }
- if (find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE) == String())
- r_missing_templates = true;
- else if (find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG) == String())
- r_missing_templates = true;
+ if (!err.empty())
+ r_error = err;
- return !r_missing_templates;
+ r_missing_templates = !valid;
+ return valid;
}
String EditorExportPlatformJavaScript::get_binary_extension(const Ref<EditorExportPreset> &p_preset) const {
diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp
index 118a77835e..8d90e01ae1 100644
--- a/platform/javascript/http_client_javascript.cpp
+++ b/platform/javascript/http_client_javascript.cpp
@@ -88,7 +88,7 @@ Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Ve
ERR_FAIL_COND_V(port < 0, ERR_UNCONFIGURED);
ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER);
- String url = (use_tls ? "https://" : "http://") + host + ":" + itos(port) + "/" + p_url;
+ String url = (use_tls ? "https://" : "http://") + host + ":" + itos(port) + p_url;
godot_xhr_reset(xhr_id);
godot_xhr_open(xhr_id, _methods[p_method], url.utf8().get_data(),
username.empty() ? NULL : username.utf8().get_data(),
diff --git a/platform/javascript/http_request.js b/platform/javascript/http_request.js
index c420052e54..ee1c06c623 100644
--- a/platform/javascript/http_request.js
+++ b/platform/javascript/http_request.js
@@ -82,7 +82,7 @@ var GodotHTTPRequest = {
godot_xhr_send_string: function(xhrId, strPtr) {
if (!strPtr) {
- Module.printErr("Failed to send string per XHR: null pointer");
+ console.warn("Failed to send string per XHR: null pointer");
return;
}
GodotHTTPRequest.requests[xhrId].send(UTF8ToString(strPtr));
@@ -90,11 +90,11 @@ var GodotHTTPRequest = {
godot_xhr_send_data: function(xhrId, ptr, len) {
if (!ptr) {
- Module.printErr("Failed to send data per XHR: null pointer");
+ console.warn("Failed to send data per XHR: null pointer");
return;
}
if (len < 0) {
- Module.printErr("Failed to send data per XHR: buffer length less than 0");
+ console.warn("Failed to send data per XHR: buffer length less than 0");
return;
}
GodotHTTPRequest.requests[xhrId].send(HEAPU8.subarray(ptr, ptr + len));
diff --git a/platform/javascript/javascript_eval.cpp b/platform/javascript/javascript_eval.cpp
index 2ef88345f6..07b4c192e6 100644
--- a/platform/javascript/javascript_eval.cpp
+++ b/platform/javascript/javascript_eval.cpp
@@ -69,7 +69,7 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
eval_ret = eval(UTF8ToString(CODE));
}
} catch (e) {
- Module.printErr(e);
+ console.warn(e);
eval_ret = null;
}
@@ -97,7 +97,7 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) {
if (array_ptr!==0) {
_free(array_ptr)
}
- Module.printErr(e);
+ console.warn(e);
// fall through
}
break;
diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp
index e85fe0800f..3829e8d406 100644
--- a/platform/javascript/javascript_main.cpp
+++ b/platform/javascript/javascript_main.cpp
@@ -28,38 +28,30 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "emscripten.h"
#include "io/resource_loader.h"
#include "main/main.h"
#include "os_javascript.h"
-OS_JavaScript *os = NULL;
+#include <emscripten/emscripten.h>
-static void main_loop() {
-
- os->main_loop_iterate();
-}
-
-extern "C" void main_after_fs_sync(char *p_idbfs_err) {
+extern "C" EMSCRIPTEN_KEEPALIVE void main_after_fs_sync(char *p_idbfs_err) {
String idbfs_err = String::utf8(p_idbfs_err);
if (!idbfs_err.empty()) {
print_line("IndexedDB not available: " + idbfs_err);
}
- os->set_idbfs_available(idbfs_err.empty());
- // Ease up compatibility
+ OS_JavaScript *os = OS_JavaScript::get_singleton();
+ os->set_idb_available(idbfs_err.empty());
+ // Ease up compatibility.
ResourceLoader::set_abort_on_missing_resources(false);
Main::start();
- os->main_loop_begin();
- emscripten_set_main_loop(main_loop, 0, false);
+ os->run_async();
}
int main(int argc, char *argv[]) {
- printf("let it go dude!\n");
-
- // sync from persistent state into memory and then
- // run the 'main_after_fs_sync' function
+ // Sync from persistent state into memory and then
+ // run the 'main_after_fs_sync' function.
/* clang-format off */
EM_ASM(
FS.mkdir('/userfs');
@@ -70,9 +62,10 @@ int main(int argc, char *argv[]) {
);
/* clang-format on */
- os = new OS_JavaScript(argv[0], NULL);
- Error err = Main::setup(argv[0], argc - 1, &argv[1]);
+ new OS_JavaScript(argc, argv);
+ // TODO: Check error return value.
+ Main::setup(argv[0], argc - 1, &argv[1]);
return 0;
- // continued async in main_after_fs_sync() from syncfs() callback
+ // Continued async in main_after_fs_sync() from the syncfs() callback.
}
diff --git a/platform/javascript/logo.png b/platform/javascript/logo.png
index ce911180ac..36832d93ba 100644
--- a/platform/javascript/logo.png
+++ b/platform/javascript/logo.png
Binary files differ
diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp
index e226ab6332..5a8a05d4df 100644
--- a/platform/javascript/os_javascript.cpp
+++ b/platform/javascript/os_javascript.cpp
@@ -30,97 +30,26 @@
#include "os_javascript.h"
-#include "core/engine.h"
-#include "core/io/file_access_buffered_fa.h"
-#include "dom_keys.h"
-#include "drivers/gles3/rasterizer_gles3.h"
-#include "drivers/unix/dir_access_unix.h"
-#include "drivers/unix/file_access_unix.h"
+#include "gles2/rasterizer_gles2.h"
+#include "gles3/rasterizer_gles3.h"
+#include "io/file_access_buffered_fa.h"
#include "main/main.h"
#include "servers/visual/visual_server_raster.h"
+#include "unix/dir_access_unix.h"
+#include "unix/file_access_unix.h"
#include <emscripten.h>
#include <stdlib.h>
+#include "dom_keys.inc"
+
#define DOM_BUTTON_LEFT 0
#define DOM_BUTTON_MIDDLE 1
#define DOM_BUTTON_RIGHT 2
+#define DOM_BUTTON_XBUTTON1 3
+#define DOM_BUTTON_XBUTTON2 4
-template <typename T>
-static void dom2godot_mod(T emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) {
-
- 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);
-}
-
-int OS_JavaScript::get_video_driver_count() const {
-
- return 1;
-}
-
-const char *OS_JavaScript::get_video_driver_name(int p_driver) const {
-
- return "GLES3";
-}
-
-int OS_JavaScript::get_audio_driver_count() const {
-
- return 1;
-}
-
-const char *OS_JavaScript::get_audio_driver_name(int p_driver) const {
-
- return "JavaScript";
-}
-
-void OS_JavaScript::initialize_core() {
-
- OS_Unix::initialize_core();
- FileAccess::make_default<FileAccessBufferedFA<FileAccessUnix> >(FileAccess::ACCESS_RESOURCES);
-}
-
-static EM_BOOL _browser_resize_callback(int event_type, const EmscriptenUiEvent *ui_event, void *user_data) {
-
- ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_RESIZE, false);
-
- OS_JavaScript *os = static_cast<OS_JavaScript *>(user_data);
- // The order of the fullscreen change event and the window size change
- // event varies, even within just one browser, so defer handling
- os->request_canvas_size_adjustment();
- return false;
-}
-
-static EM_BOOL _fullscreen_change_callback(int event_type, const EmscriptenFullscreenChangeEvent *event, void *user_data) {
-
- ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_FULLSCREENCHANGE, false);
-
- OS_JavaScript *os = static_cast<OS_JavaScript *>(user_data);
- String id = String::utf8(event->id);
- // empty id is canvas
- if (id.empty() || id == "canvas") {
-
- OS::VideoMode vm = os->get_video_mode();
- // this event property is the only reliable information on
- // browser fullscreen state
- vm.fullscreen = event->isFullscreen;
- os->set_video_mode(vm);
- os->request_canvas_size_adjustment();
- }
- return false;
-}
-
-static InputDefault *_input;
-
-static bool is_canvas_focused() {
-
- /* clang-format off */
- return EM_ASM_INT_V(
- return document.activeElement == Module.canvas;
- );
- /* clang-format on */
-}
+// Window (canvas)
static void focus_canvas() {
@@ -131,216 +60,158 @@ static void focus_canvas() {
/* clang-format on */
}
-static bool _cursor_inside_canvas = true;
-
-static bool is_cursor_inside_canvas() {
+static bool is_canvas_focused() {
- return _cursor_inside_canvas;
+ /* clang-format off */
+ return EM_ASM_INT_V(
+ return document.activeElement == Module.canvas;
+ );
+ /* clang-format on */
}
-static EM_BOOL _mousebutton_callback(int event_type, const EmscriptenMouseEvent *mouse_event, void *user_data) {
+static bool cursor_inside_canvas = true;
- ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_MOUSEDOWN && event_type != EMSCRIPTEN_EVENT_MOUSEUP, false);
+EM_BOOL OS_JavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) {
- Ref<InputEventMouseButton> ev;
- ev.instance();
- ev->set_pressed(event_type == EMSCRIPTEN_EVENT_MOUSEDOWN);
- ev->set_position(Point2(mouse_event->canvasX, mouse_event->canvasY));
- ev->set_global_position(ev->get_position());
- dom2godot_mod(mouse_event, ev);
-
- switch (mouse_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;
- default: return false;
- }
-
- int mask = _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
- if (!is_canvas_focused()) {
- focus_canvas();
+ 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;
+ 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;
}
- mask |= button_flag;
- } else if (mask & button_flag) {
- mask &= ~button_flag;
- } else {
- // release event, but press was outside the canvas, so ignore
- return false;
}
- ev->set_button_mask(mask);
-
- _input->parse_input_event(ev);
- // prevent selection dragging
- return true;
+ return false;
}
-static EM_BOOL _mousemove_callback(int event_type, const EmscriptenMouseEvent *mouse_event, void *user_data) {
+void OS_JavaScript::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
- 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;
+ video_mode = p_video_mode;
+}
- Ref<InputEventMouseMotion> ev;
- ev.instance();
- dom2godot_mod(mouse_event, ev);
- ev->set_button_mask(input_mask);
+OS::VideoMode OS_JavaScript::get_video_mode(int p_screen) const {
- ev->set_position(pos);
- ev->set_global_position(ev->get_position());
+ return video_mode;
+}
- ev->set_relative(ev->get_position() - _input->get_mouse_position());
- _input->set_mouse_position(ev->get_position());
- ev->set_speed(_input->get_last_mouse_speed());
+Size2 OS_JavaScript::get_screen_size(int p_screen) const {
- _input->parse_input_event(ev);
- // don't suppress mouseover/leave events
- return false;
+ 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);
}
-static EM_BOOL _wheel_callback(int event_type, const EmscriptenWheelEvent *wheel_event, void *user_data) {
+void OS_JavaScript::set_window_size(const Size2 p_size) {
- ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_WHEEL, false);
- if (!is_canvas_focused()) {
- if (is_cursor_inside_canvas()) {
- focus_canvas();
- } else {
- return false;
+ windowed_size = p_size;
+ if (video_mode.fullscreen) {
+ window_maximized = false;
+ set_window_fullscreen(false);
+ } else {
+ if (window_maximized) {
+ emscripten_exit_soft_fullscreen();
+ window_maximized = false;
}
+ emscripten_set_canvas_size(p_size.x, p_size.y);
}
-
- Ref<InputEventMouseButton> ev;
- ev.instance();
- ev->set_button_mask(_input->get_mouse_button_mask());
- ev->set_position(_input->get_mouse_position());
- ev->set_global_position(ev->get_position());
-
- 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));
-
- if (wheel_event->deltaY < 0)
- ev->set_button_index(BUTTON_WHEEL_UP);
- else if (wheel_event->deltaY > 0)
- ev->set_button_index(BUTTON_WHEEL_DOWN);
- else if (wheel_event->deltaX > 0)
- ev->set_button_index(BUTTON_WHEEL_LEFT);
- else if (wheel_event->deltaX < 0)
- ev->set_button_index(BUTTON_WHEEL_RIGHT);
- else
- return false;
-
- // Different browsers give wildly different delta values, and we can't
- // interpret deltaMode, so use default value for wheel events' factor
-
- ev->set_pressed(true);
- _input->parse_input_event(ev);
-
- ev->set_pressed(false);
- _input->parse_input_event(ev);
-
- return true;
}
-static Point2 _prev_touches[32];
-
-static EM_BOOL _touchpress_callback(int event_type, const EmscriptenTouchEvent *touch_event, void *user_data) {
-
- ERR_FAIL_COND_V(
- event_type != EMSCRIPTEN_EVENT_TOUCHSTART &&
- event_type != EMSCRIPTEN_EVENT_TOUCHEND &&
- event_type != EMSCRIPTEN_EVENT_TOUCHCANCEL,
- false);
+Size2 OS_JavaScript::get_window_size() const {
- Ref<InputEventScreenTouch> ev;
- ev.instance();
- int lowest_id_index = -1;
- for (int i = 0; i < touch_event->numTouches; ++i) {
+ int canvas[3];
+ emscripten_get_canvas_size(canvas, canvas + 1, canvas + 2);
+ return Size2(canvas[0], canvas[1]);
+}
- const EmscriptenTouchPoint &touch = touch_event->touches[i];
- if (lowest_id_index == -1 || touch.identifier < touch_event->touches[lowest_id_index].identifier)
- lowest_id_index = i;
- if (!touch.isChanged)
- continue;
- ev->set_index(touch.identifier);
- ev->set_position(Point2(touch.canvasX, touch.canvasY));
- _prev_touches[i] = ev->get_position();
- ev->set_pressed(event_type == EMSCRIPTEN_EVENT_TOUCHSTART);
+void OS_JavaScript::set_window_maximized(bool p_enabled) {
- _input->parse_input_event(ev);
+ if (video_mode.fullscreen) {
+ window_maximized = p_enabled;
+ set_window_fullscreen(false);
+ } 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.
+ 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);
+ window_maximized = p_enabled;
}
+}
- if (touch_event->touches[lowest_id_index].isChanged) {
+bool OS_JavaScript::is_window_maximized() const {
- Ref<InputEventMouseButton> ev_mouse;
- ev_mouse.instance();
- ev_mouse->set_button_mask(_input->get_mouse_button_mask());
- dom2godot_mod(touch_event, ev_mouse);
+ return window_maximized;
+}
- const EmscriptenTouchPoint &first_touch = touch_event->touches[lowest_id_index];
- ev_mouse->set_position(Point2(first_touch.canvasX, first_touch.canvasY));
- ev_mouse->set_global_position(ev_mouse->get_position());
+void OS_JavaScript::set_window_fullscreen(bool p_enabled) {
- ev_mouse->set_button_index(BUTTON_LEFT);
- ev_mouse->set_pressed(event_type == EMSCRIPTEN_EVENT_TOUCHSTART);
+ if (p_enabled == video_mode.fullscreen) {
+ return;
+ }
- _input->parse_input_event(ev_mouse);
+ // Just request changes here, if successful, logic continues in
+ // fullscreen_change_callback.
+ if (p_enabled) {
+ if (window_maximized) {
+ // Soft fullsreen during real fullscreen can cause issues, so exit.
+ // This must be called before requesting full screen.
+ emscripten_exit_soft_fullscreen();
+ }
+ 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_RESULT result = emscripten_request_fullscreen_strategy(NULL, false, &strategy);
+ ERR_EXPLAIN("Enabling fullscreen is only possible from an input callback for the HTML5 platform");
+ ERR_FAIL_COND(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED);
+ ERR_FAIL_COND(result != EMSCRIPTEN_RESULT_SUCCESS);
+ // Not fullscreen yet, so prevent "windowed" canvas dimensions from
+ // being overwritten.
+ entering_fullscreen = true;
+ } else {
+ // No logic allowed here, since exiting w/ ESC key won't use this function.
+ ERR_FAIL_COND(emscripten_exit_fullscreen() != EMSCRIPTEN_RESULT_SUCCESS);
}
- return true;
}
-static EM_BOOL _touchmove_callback(int event_type, const EmscriptenTouchEvent *touch_event, void *user_data) {
-
- ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_TOUCHMOVE, false);
-
- Ref<InputEventScreenDrag> ev;
- ev.instance();
- int lowest_id_index = -1;
- for (int i = 0; i < touch_event->numTouches; ++i) {
-
- const EmscriptenTouchPoint &touch = touch_event->touches[i];
- if (lowest_id_index == -1 || touch.identifier < touch_event->touches[lowest_id_index].identifier)
- lowest_id_index = i;
- if (!touch.isChanged)
- continue;
- ev->set_index(touch.identifier);
- ev->set_position(Point2(touch.canvasX, touch.canvasY));
- Point2 &prev = _prev_touches[i];
- ev->set_relative(ev->get_position() - prev);
- prev = ev->get_position();
+bool OS_JavaScript::is_window_fullscreen() const {
- _input->parse_input_event(ev);
- }
+ return video_mode.fullscreen;
+}
- if (touch_event->touches[lowest_id_index].isChanged) {
+void OS_JavaScript::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
- Ref<InputEventMouseMotion> ev_mouse;
- ev_mouse.instance();
- dom2godot_mod(touch_event, ev_mouse);
- ev_mouse->set_button_mask(_input->get_mouse_button_mask());
+ Size2 screen = get_screen_size();
+ p_list->push_back(OS::VideoMode(screen.width, screen.height, true));
+}
- const EmscriptenTouchPoint &first_touch = touch_event->touches[lowest_id_index];
- ev_mouse->set_position(Point2(first_touch.canvasX, first_touch.canvasY));
- ev_mouse->set_global_position(ev_mouse->get_position());
+// Keys
- ev_mouse->set_relative(ev_mouse->get_position() - _input->get_mouse_position());
- _input->set_mouse_position(ev_mouse->get_position());
- ev_mouse->set_speed(_input->get_last_mouse_speed());
+template <typename T>
+static void dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) {
- _input->parse_input_event(ev_mouse);
- }
- return true;
+ 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);
}
-static Ref<InputEventKey> _setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) {
+static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) {
Ref<InputEventKey> ev;
ev.instance();
@@ -349,9 +220,9 @@ static Ref<InputEventKey> _setup_key_event(const EmscriptenKeyboardEvent *emscri
ev->set_scancode(dom2godot_scancode(emscripten_event->keyCode));
String unicode = String::utf8(emscripten_event->key);
- // check if empty or multi-character (e.g. `CapsLock`)
+ // Check if empty or multi-character (e.g. `CapsLock`).
if (unicode.length() != 1) {
- // might be empty as well, but better than nonsense
+ // Might be empty as well, but better than nonsense.
unicode = String::utf8(emscripten_event->charValue);
}
if (unicode.length() == 1) {
@@ -361,169 +232,115 @@ static Ref<InputEventKey> _setup_key_event(const EmscriptenKeyboardEvent *emscri
return ev;
}
-static Ref<InputEventKey> deferred_key_event;
-
-static EM_BOOL _keydown_callback(int event_type, const EmscriptenKeyboardEvent *key_event, void *user_data) {
-
- ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_KEYDOWN, false);
+EM_BOOL OS_JavaScript::keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) {
- Ref<InputEventKey> ev = _setup_key_event(key_event);
+ OS_JavaScript *os = get_singleton();
+ Ref<InputEventKey> ev = setup_key_event(p_event);
ev->set_pressed(true);
if (ev->get_unicode() == 0 && keycode_has_unicode(ev->get_scancode())) {
- // defer to keypress event for legacy unicode retrieval
- deferred_key_event = ev;
- return false; // do not suppress keypress event
+ // Defer to keypress event for legacy unicode retrieval.
+ os->deferred_key_event = ev;
+ // Do not suppress keypress event.
+ return false;
}
- _input->parse_input_event(ev);
+ os->input->parse_input_event(ev);
return true;
}
-static EM_BOOL _keypress_callback(int event_type, const EmscriptenKeyboardEvent *key_event, void *user_data) {
-
- ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_KEYPRESS, false);
+EM_BOOL OS_JavaScript::keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) {
- deferred_key_event->set_unicode(key_event->charCode);
- _input->parse_input_event(deferred_key_event);
+ OS_JavaScript *os = get_singleton();
+ os->deferred_key_event->set_unicode(p_event->charCode);
+ os->input->parse_input_event(os->deferred_key_event);
return true;
}
-static EM_BOOL _keyup_callback(int event_type, const EmscriptenKeyboardEvent *key_event, void *user_data) {
+EM_BOOL OS_JavaScript::keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) {
- ERR_FAIL_COND_V(event_type != EMSCRIPTEN_EVENT_KEYUP, false);
-
- Ref<InputEventKey> ev = _setup_key_event(key_event);
+ Ref<InputEventKey> ev = setup_key_event(p_event);
ev->set_pressed(false);
- _input->parse_input_event(ev);
+ get_singleton()->input->parse_input_event(ev);
return ev->get_scancode() != KEY_UNKNOWN && ev->get_scancode() != 0;
}
-static EM_BOOL joy_callback_func(int p_type, const EmscriptenGamepadEvent *p_event, void *p_user) {
- OS_JavaScript *os = (OS_JavaScript *)OS::get_singleton();
- if (os) {
- return os->joy_connection_changed(p_type, p_event);
- }
- return false;
-}
+// Mouse
-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);
+Point2 OS_JavaScript::get_mouse_position() const {
+
+ return input->get_mouse_position();
}
+
+int OS_JavaScript::get_mouse_button_state() const {
+
+ return input->get_mouse_button_mask();
}
-Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
+EM_BOOL OS_JavaScript::mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) {
- print_line("Init OS");
+ OS_JavaScript *os = get_singleton();
- EmscriptenWebGLContextAttributes attributes;
- emscripten_webgl_init_context_attributes(&attributes);
- attributes.alpha = false;
- attributes.antialias = false;
- attributes.majorVersion = 2;
- EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(NULL, &attributes);
- ERR_FAIL_COND_V(emscripten_webgl_make_context_current(ctx) != EMSCRIPTEN_RESULT_SUCCESS, ERR_UNAVAILABLE);
+ Ref<InputEventMouseButton> ev;
+ ev.instance();
+ ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_MOUSEDOWN);
+ ev->set_position(Point2(p_event->canvasX, p_event->canvasY));
+ ev->set_global_position(ev->get_position());
+ dom2godot_mod(p_event, ev);
+ 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;
+ }
- video_mode = p_desired;
- // can't fulfil fullscreen request due to browser security
- video_mode.fullscreen = false;
- /* clang-format off */
- if (EM_ASM_INT_V({ return Module.resizeCanvasOnStart })) {
- /* clang-format on */
- set_window_size(Size2(video_mode.width, video_mode.height));
+ 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;
} else {
- set_window_size(get_window_size());
+ // Received release event, but press was outside the canvas, so ignore.
+ return false;
}
+ ev->set_button_mask(mask);
- 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);
-
- print_line("Init Audio");
-
- AudioDriverManager::add_driver(&audio_driver_javascript);
- AudioDriverManager::initialize(p_audio_driver);
-
- RasterizerGLES3::register_config();
- RasterizerGLES3::make_current();
-
- print_line("Init VS");
-
- visual_server = memnew(VisualServerRaster());
- // visual_server->cursor_set_visible(false, 0);
-
- print_line("Init Physicsserver");
-
- input = memnew(InputDefault);
- _input = input;
-
- power_manager = memnew(PowerJavascript);
-
-#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, this, true, &cb); \
- EM_CHECK(ev)
-#define SET_EM_CALLBACK_NODATA(ev, cb) \
- result = emscripten_set_##ev##_callback(NULL, true, &cb); \
- EM_CHECK(ev)
-
- EMSCRIPTEN_RESULT result;
- SET_EM_CALLBACK("#window", mousemove, _mousemove_callback)
- SET_EM_CALLBACK("#canvas", mousedown, _mousebutton_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)
- SET_EM_CALLBACK_NODATA(gamepaddisconnected, joy_callback_func)
-
-#undef SET_EM_CALLBACK_NODATA
-#undef SET_EM_CALLBACK
-#undef EM_CHECK
-
- visual_server->init();
-
- return OK;
+ os->input->parse_input_event(ev);
+ // Prevent multi-click text selection and wheel-click scrolling anchor.
+ // Context menu is prevented through contextmenu event.
+ return true;
}
-void OS_JavaScript::set_main_loop(MainLoop *p_main_loop) {
-
- main_loop = p_main_loop;
- input->set_main_loop(p_main_loop);
-}
+EM_BOOL OS_JavaScript::mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) {
-void OS_JavaScript::delete_main_loop() {
+ OS_JavaScript *os = get_singleton();
- memdelete(main_loop);
-}
+ int input_mask = os->input->get_mouse_button_mask();
+ Point2 pos = Point2(p_event->canvasX, p_event->canvasY);
+ // 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;
-void OS_JavaScript::finalize() {
+ Ref<InputEventMouseMotion> ev;
+ ev.instance();
+ dom2godot_mod(p_event, ev);
+ ev->set_button_mask(input_mask);
- memdelete(input);
-}
+ ev->set_position(pos);
+ ev->set_global_position(ev->get_position());
-void OS_JavaScript::alert(const String &p_alert, const String &p_title) {
+ 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());
- /* clang-format off */
- EM_ASM_({
- window.alert(UTF8ToString($0));
- }, p_alert.utf8().get_data());
- /* clang-format on */
+ os->input->parse_input_event(ev);
+ // Don't suppress mouseover/-leave events.
+ return false;
}
static const char *godot2dom_cursor(OS::CursorShape p_shape) {
@@ -551,7 +368,7 @@ static const char *godot2dom_cursor(OS::CursorShape p_shape) {
}
}
-void OS_JavaScript::set_css_cursor(const char *p_cursor) {
+static void set_css_cursor(const char *p_cursor) {
/* clang-format off */
EM_ASM_({
@@ -560,7 +377,7 @@ void OS_JavaScript::set_css_cursor(const char *p_cursor) {
/* clang-format on */
}
-const char *OS_JavaScript::get_css_cursor() const {
+static const char *get_css_cursor() {
char cursor[16];
/* clang-format off */
@@ -571,9 +388,20 @@ const char *OS_JavaScript::get_css_cursor() const {
return cursor;
}
+void OS_JavaScript::set_cursor_shape(CursorShape p_shape) {
+
+ ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
+
+ cursor_shape = p_shape;
+ if (get_mouse_mode() != MOUSE_MODE_HIDDEN)
+ set_css_cursor(godot2dom_cursor(cursor_shape));
+}
+
+void OS_JavaScript::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+}
+
void OS_JavaScript::set_mouse_mode(OS::MouseMode p_mode) {
- ERR_FAIL_INDEX(p_mode, MOUSE_MODE_CONFINED + 1);
ERR_EXPLAIN("MOUSE_MODE_CONFINED is not supported for the HTML5 platform");
ERR_FAIL_COND(p_mode == MOUSE_MODE_CONFINED);
if (p_mode == get_mouse_mode())
@@ -601,190 +429,307 @@ void OS_JavaScript::set_mouse_mode(OS::MouseMode p_mode) {
OS::MouseMode OS_JavaScript::get_mouse_mode() const {
- if (!strcmp(get_css_cursor(), "none"))
+ if (String::utf8(get_css_cursor()) == "none")
return MOUSE_MODE_HIDDEN;
EmscriptenPointerlockChangeEvent ev;
emscripten_get_pointerlock_status(&ev);
- return ev.isActive && (strcmp(ev.id, "canvas") == 0) ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE;
+ return (ev.isActive && String::utf8(ev.id) == "canvas") ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE;
}
-Point2 OS_JavaScript::get_mouse_position() const {
+// Wheel
- return input->get_mouse_position();
-}
+EM_BOOL OS_JavaScript::wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data) {
-int OS_JavaScript::get_mouse_button_state() const {
+ ERR_FAIL_COND_V(p_event_type != EMSCRIPTEN_EVENT_WHEEL, false);
+ if (!is_canvas_focused()) {
+ if (cursor_inside_canvas) {
+ focus_canvas();
+ } else {
+ return false;
+ }
+ }
- return input->get_mouse_button_mask();
-}
+ InputDefault *input = get_singleton()->input;
+ Ref<InputEventMouseButton> ev;
+ ev.instance();
+ ev->set_button_mask(input->get_mouse_button_mask());
+ ev->set_position(input->get_mouse_position());
+ ev->set_global_position(ev->get_position());
-void OS_JavaScript::set_window_title(const String &p_title) {
+ 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));
- /* clang-format off */
- EM_ASM_({
- document.title = UTF8ToString($0);
- }, p_title.utf8().get_data());
- /* clang-format on */
-}
+ 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;
-//interesting byt not yet
-//void set_clipboard(const String& p_text);
-//String get_clipboard() const;
+ // Different browsers give wildly different delta values, and we can't
+ // interpret deltaMode, so use default value for wheel events' factor.
-void OS_JavaScript::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
+ ev->set_pressed(true);
+ input->parse_input_event(ev);
- video_mode = p_video_mode;
+ ev->set_pressed(false);
+ input->parse_input_event(ev);
+
+ return true;
}
-OS::VideoMode OS_JavaScript::get_video_mode(int p_screen) const {
+// Touch
- return video_mode;
+bool OS_JavaScript::has_touchscreen_ui_hint() const {
+
+ /* clang-format off */
+ return EM_ASM_INT_V(
+ return 'ontouchstart' in window;
+ );
+ /* clang-format on */
}
-Size2 OS_JavaScript::get_screen_size(int p_screen) const {
+EM_BOOL OS_JavaScript::touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) {
- 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);
-}
+ OS_JavaScript *os = get_singleton();
+ Ref<InputEventScreenTouch> ev;
+ ev.instance();
+ int lowest_id_index = -1;
+ for (int i = 0; i < p_event->numTouches; ++i) {
-void OS_JavaScript::set_window_size(const Size2 p_size) {
+ 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);
+ ev->set_position(Point2(touch.canvasX, touch.canvasY));
+ os->touches[i] = ev->get_position();
+ ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_TOUCHSTART);
- windowed_size = p_size;
- if (is_window_fullscreen()) {
- window_maximized = false;
- set_window_fullscreen(false);
- } else if (is_window_maximized()) {
- set_window_maximized(false);
- } else {
- video_mode.width = p_size.x;
- video_mode.height = p_size.y;
- emscripten_set_canvas_size(p_size.x, p_size.y);
+ os->input->parse_input_event(ev);
}
+ return true;
}
-Size2 OS_JavaScript::get_window_size() const {
+EM_BOOL OS_JavaScript::touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) {
- int canvas[3];
- emscripten_get_canvas_size(canvas, canvas + 1, canvas + 2);
- return Size2(canvas[0], canvas[1]);
-}
+ OS_JavaScript *os = get_singleton();
+ Ref<InputEventScreenDrag> ev;
+ ev.instance();
+ int lowest_id_index = -1;
+ for (int i = 0; i < p_event->numTouches; ++i) {
-void OS_JavaScript::set_window_maximized(bool p_enabled) {
+ 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);
+ ev->set_position(Point2(touch.canvasX, touch.canvasY));
+ Point2 &prev = os->touches[i];
+ ev->set_relative(ev->get_position() - prev);
+ prev = ev->get_position();
- window_maximized = p_enabled;
- if (is_window_fullscreen()) {
- set_window_fullscreen(false);
- return;
+ os->input->parse_input_event(ev);
}
- // Calling emscripten_enter_soft_fullscreen mutltiple times hides all
- // page elements except the canvas permanently, so track state
- if (p_enabled && !soft_fs_enabled) {
+ return true;
+}
- 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);
- soft_fs_enabled = true;
- video_mode.width = get_window_size().width;
- video_mode.height = get_window_size().height;
- } else if (!p_enabled) {
+// Gamepad
- emscripten_exit_soft_fullscreen();
- soft_fs_enabled = false;
- video_mode.width = windowed_size.width;
- video_mode.height = windowed_size.height;
- emscripten_set_canvas_size(video_mode.width, video_mode.height);
+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, "");
}
+ return true;
}
-void OS_JavaScript::set_window_fullscreen(bool p_enable) {
+void OS_JavaScript::process_joypads() {
- if (p_enable == is_window_fullscreen()) {
- return;
- }
+ int joypad_count = emscripten_get_num_gamepads();
+ for (int joypad = 0; joypad < joypad_count; joypad++) {
+ EmscriptenGamepadEvent state;
+ emscripten_get_gamepad_status(joypad, &state);
+ if (state.connected) {
- // only requesting changes here, if successful, canvas is resized in
- // _browser_resize_callback or _fullscreen_change_callback
- EMSCRIPTEN_RESULT result;
- if (p_enable) {
- if (window_maximized) {
- // soft fs during real fs can cause issues
- set_window_maximized(false);
- window_maximized = true;
- }
- 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_request_fullscreen_strategy(NULL, false, &strategy);
- } else {
- result = emscripten_exit_fullscreen();
- if (result != EMSCRIPTEN_RESULT_SUCCESS) {
- ERR_PRINTS("Failed to exit fullscreen: Code " + itos(result));
+ 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);
+ }
}
}
}
-bool OS_JavaScript::is_window_fullscreen() const {
+bool OS_JavaScript::is_joy_known(int p_device) {
- return video_mode.fullscreen;
+ return input->is_joy_mapped(p_device);
}
-void OS_JavaScript::request_canvas_size_adjustment() {
+String OS_JavaScript::get_joy_guid(int p_device) const {
- canvas_size_adjustment_requested = true;
+ return input->get_joy_guid_remapped(p_device);
}
-void OS_JavaScript::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
+// Video
- Size2 screen = get_screen_size();
- p_list->push_back(OS::VideoMode(screen.width, screen.height, true));
+int OS_JavaScript::get_video_driver_count() const {
+
+ return VIDEO_DRIVER_MAX;
}
-String OS_JavaScript::get_name() {
+const char *OS_JavaScript::get_video_driver_name(int p_driver) const {
- return "HTML5";
+ switch (p_driver) {
+ case VIDEO_DRIVER_GLES3:
+ return "GLES3";
+ case VIDEO_DRIVER_GLES2:
+ return "GLES2";
+ }
+ ERR_EXPLAIN("Invalid video driver index " + itos(p_driver));
+ ERR_FAIL_V(NULL);
}
-MainLoop *OS_JavaScript::get_main_loop() const {
+// Audio
- return main_loop;
+int OS_JavaScript::get_audio_driver_count() const {
+
+ return 1;
}
-bool OS_JavaScript::can_draw() const {
+const char *OS_JavaScript::get_audio_driver_name(int p_driver) const {
- return true; //always?
+ return "JavaScript";
}
-void OS_JavaScript::set_cursor_shape(CursorShape p_shape) {
+// Lifecycle
+int OS_JavaScript::get_current_video_driver() const {
+ return video_driver_index;
+}
- ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
+void OS_JavaScript::initialize_core() {
- cursor_shape = p_shape;
- if (get_mouse_mode() != MOUSE_MODE_HIDDEN)
- set_css_cursor(godot2dom_cursor(cursor_shape));
+ OS_Unix::initialize_core();
+ FileAccess::make_default<FileAccessBufferedFA<FileAccessUnix> >(FileAccess::ACCESS_RESOURCES);
}
-void OS_JavaScript::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
-}
+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);
+ switch (p_video_driver) {
+ case VIDEO_DRIVER_GLES3:
+ attributes.majorVersion = 2;
+ RasterizerGLES3::register_config();
+ RasterizerGLES3::make_current();
+ break;
+ case VIDEO_DRIVER_GLES2:
+ attributes.majorVersion = 1;
+ RasterizerGLES2::register_config();
+ RasterizerGLES2::make_current();
+ break;
+ }
-void OS_JavaScript::main_loop_begin() {
+ video_driver_index = p_video_driver;
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(NULL, &attributes);
+ ERR_EXPLAIN("WebGL " + itos(attributes.majorVersion) + ".0 not available");
+ ERR_FAIL_COND_V(emscripten_webgl_make_context_current(ctx) != EMSCRIPTEN_RESULT_SUCCESS, ERR_UNAVAILABLE);
- if (main_loop)
- main_loop->init();
+ video_mode = p_desired;
+ // Can't fulfil fullscreen request during start-up due to browser security.
+ video_mode.fullscreen = false;
+ /* 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);
+
+ AudioDriverManager::initialize(p_audio_driver);
+ VisualServer *visual_server = memnew(VisualServerRaster());
+ input = memnew(InputDefault);
+
+ 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
/* clang-format off */
EM_ASM_ARGS({
const send_notification = cwrap('send_notification', null, ['number']);
- const notifs = arguments;
- (['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, i) {
- Module.canvas.addEventListener(event, send_notification.bind(null, notifs[i]));
+ const notifications = arguments;
+ (['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, index) {
+ Module.canvas.addEventListener(event, send_notification.bind(null, notifications[index]));
});
},
MainLoop::NOTIFICATION_WM_MOUSE_ENTER,
@@ -793,226 +738,244 @@ void OS_JavaScript::main_loop_begin() {
MainLoop::NOTIFICATION_WM_FOCUS_OUT
);
/* clang-format on */
+
+ visual_server->init();
+
+ return OK;
}
-bool OS_JavaScript::main_loop_iterate() {
+void OS_JavaScript::set_main_loop(MainLoop *p_main_loop) {
- if (!main_loop)
- return false;
+ 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();
+}
+
+bool OS_JavaScript::main_loop_iterate() {
- if (idbfs_available && time_to_save_sync >= 0) {
- int64_t newtime = get_ticks_msec();
- int64_t elapsed = newtime - last_sync_time;
- last_sync_time = newtime;
+ 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;
- time_to_save_sync -= elapsed;
+ sync_wait_time -= elapsed_time;
- if (time_to_save_sync < 0) {
- //time to sync, for real
+ if (sync_wait_time < 0) {
/* clang-format off */
EM_ASM(
FS.syncfs(function(err) {
- if (err) { Module.printErr('Failed to save IDB file system: ' + err.message); }
+ if (err) { console.warn('Failed to save IDB file system: ' + err.message); }
});
);
/* clang-format on */
}
}
+
process_joypads();
- if (canvas_size_adjustment_requested) {
- if (video_mode.fullscreen || window_maximized) {
- video_mode.width = get_window_size().width;
- video_mode.height = get_window_size().height;
- }
- if (!video_mode.fullscreen) {
- set_window_maximized(window_maximized);
+ 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 {
+ emscripten_set_canvas_size(windowed_size.width, windowed_size.height);
}
- canvas_size_adjustment_requested = false;
+ just_exited_fullscreen = false;
+ }
+
+ int canvas[3];
+ emscripten_get_canvas_size(canvas, canvas + 1, canvas + 2);
+ 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];
}
+
return Main::iteration();
}
-void OS_JavaScript::main_loop_end() {
+void OS_JavaScript::delete_main_loop() {
- if (main_loop)
- main_loop->finish();
+ memdelete(main_loop);
}
-void OS_JavaScript::process_accelerometer(const Vector3 &p_accelerometer) {
+void OS_JavaScript::finalize() {
- input->set_accelerometer(p_accelerometer);
+ memdelete(input);
}
-bool OS_JavaScript::has_touchscreen_ui_hint() const {
+// Miscellaneous
- /* clang-format off */
- return EM_ASM_INT_V(
- return 'ontouchstart' in window;
- );
- /* clang-format on */
+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);
}
-void OS_JavaScript::main_loop_request_quit() {
+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
+
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_get_current_context();
+ // All extensions are already automatically enabled, this function allows
+ // checking WebGL extension support without inline JavaScript
+ if (p_feature == "s3tc")
+ return emscripten_webgl_enable_extension(ctx, "WEBGL_compressed_texture_s3tc_srgb");
+ if (p_feature == "etc")
+ return emscripten_webgl_enable_extension(ctx, "WEBGL_compressed_texture_etc1");
+ if (p_feature == "etc2")
+ return emscripten_webgl_enable_extension(ctx, "WEBGL_compressed_texture_etc");
- if (main_loop)
- main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST);
+ return false;
}
-Error OS_JavaScript::shell_open(String p_uri) {
+void OS_JavaScript::alert(const String &p_alert, const String &p_title) {
+
/* clang-format off */
EM_ASM_({
- window.open(UTF8ToString($0), '_blank');
- }, p_uri.utf8().get_data());
+ window.alert(UTF8ToString($0));
+ }, p_alert.utf8().get_data());
/* clang-format on */
- return OK;
}
-String OS_JavaScript::get_resource_dir() const {
+void OS_JavaScript::set_window_title(const String &p_title) {
- return "/"; //javascript has it's own filesystem for resources inside the APK
+ /* clang-format off */
+ EM_ASM_({
+ document.title = UTF8ToString($0);
+ }, p_title.utf8().get_data());
+ /* clang-format on */
}
-String OS_JavaScript::get_user_data_dir() const {
-
- /*
- if (get_user_data_dir_func)
- return get_user_data_dir_func();
- */
- return "/userfs";
-};
-
String OS_JavaScript::get_executable_path() const {
return OS::get_executable_path();
}
-void OS_JavaScript::_close_notification_funcs(const String &p_file, int p_flags) {
+Error OS_JavaScript::shell_open(String p_uri) {
- OS_JavaScript *os = static_cast<OS_JavaScript *>(get_singleton());
- if (os->idbfs_available && p_file.begins_with("/userfs") && p_flags & FileAccess::WRITE) {
- os->last_sync_time = OS::get_singleton()->get_ticks_msec();
- os->time_to_save_sync = 5000; //five seconds since last save
- }
+ // 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;
}
-void OS_JavaScript::process_joypads() {
-
- int joy_count = emscripten_get_num_gamepads();
- for (int i = 0; i < joy_count; i++) {
- EmscriptenGamepadEvent state;
- emscripten_get_gamepad_status(i, &state);
- if (state.connected) {
+String OS_JavaScript::get_name() {
- int num_buttons = MIN(state.numButtons, 18);
- int num_axes = MIN(state.numAxes, 8);
- for (int j = 0; j < num_buttons; j++) {
+ return "HTML5";
+}
- float value = state.analogButton[j];
- if (String(state.mapping) == "standard" && (j == 6 || j == 7)) {
- InputDefault::JoyAxis jx;
- jx.min = 0;
- jx.value = value;
- input->joy_axis(i, j, jx);
- } else {
- input->joy_button(i, j, value);
- }
- }
- for (int j = 0; j < num_axes; j++) {
+bool OS_JavaScript::can_draw() const {
- InputDefault::JoyAxis jx;
- jx.min = -1;
- jx.value = state.axis[j];
- input->joy_axis(i, j, jx);
- }
- }
- }
+ return true; // Always?
}
-bool OS_JavaScript::joy_connection_changed(int p_type, const EmscriptenGamepadEvent *p_event) {
- if (p_type == EMSCRIPTEN_EVENT_GAMEPADCONNECTED) {
+String OS_JavaScript::get_user_data_dir() const {
- String guid = "";
- if (String(p_event->mapping) == "standard")
- guid = "Default HTML5 Gamepad";
- input->joy_connection_changed(p_event->index, true, String(p_event->id), guid);
- } else {
- input->joy_connection_changed(p_event->index, false, "");
- }
- return true;
-}
+ return "/userfs";
+};
-bool OS_JavaScript::is_joy_known(int p_device) {
- return input->is_joy_mapped(p_device);
-}
+String OS_JavaScript::get_resource_dir() const {
-String OS_JavaScript::get_joy_guid(int p_device) const {
- return input->get_joy_guid_remapped(p_device);
+ return "/";
}
OS::PowerState OS_JavaScript::get_power_state() {
- return power_manager->get_power_state();
+
+ WARN_PRINT("Power management is not supported for the HTML5 platform, defaulting to POWERSTATE_UNKNOWN");
+ return OS::POWERSTATE_UNKNOWN;
}
int OS_JavaScript::get_power_seconds_left() {
- return power_manager->get_power_seconds_left();
+
+ WARN_PRINT("Power management is not supported for the HTML5 platform, defaulting to -1");
+ return -1;
}
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) {
+ WARN_PRINT("Power management is not supported for the HTML5 platform, defaulting to -1");
+ return -1;
+}
- if (p_feature == "HTML5" || p_feature == "web")
- return true;
+void OS_JavaScript::file_access_close_callback(const String &p_file, int p_flags) {
-#ifdef JAVASCRIPT_EVAL_ENABLED
- if (p_feature == "JavaScript")
- return true;
-#endif
+ 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;
+ }
+}
- EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_get_current_context();
- // all extensions are already automatically enabled, this function allows
- // checking WebGL extension support without inline JavaScript
- if (p_feature == "s3tc" && emscripten_webgl_enable_extension(ctx, "WEBGL_compressed_texture_s3tc_srgb"))
- return true;
- if (p_feature == "etc" && emscripten_webgl_enable_extension(ctx, "WEBGL_compressed_texture_etc1"))
- return true;
- if (p_feature == "etc2" && emscripten_webgl_enable_extension(ctx, "WEBGL_compressed_texture_etc"))
- return true;
+void OS_JavaScript::set_idb_available(bool p_idb_available) {
- return false;
+ idb_available = p_idb_available;
}
-void OS_JavaScript::set_idbfs_available(bool p_idbfs_available) {
+bool OS_JavaScript::is_userfs_persistent() const {
- idbfs_available = p_idbfs_available;
+ return idb_available;
}
-bool OS_JavaScript::is_userfs_persistent() const {
+OS_JavaScript *OS_JavaScript::get_singleton() {
- return idbfs_available;
+ return static_cast<OS_JavaScript *>(OS::get_singleton());
}
-OS_JavaScript::OS_JavaScript(const char *p_execpath, GetUserDataDirFunc p_get_user_data_dir_func) {
+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);
- set_cmdline(p_execpath, get_cmdline_args());
- main_loop = NULL;
window_maximized = false;
- soft_fs_enabled = false;
- canvas_size_adjustment_requested = false;
+ entering_fullscreen = false;
+ just_exited_fullscreen = false;
- get_user_data_dir_func = p_get_user_data_dir_func;
- FileAccessUnix::close_notification_func = _close_notification_funcs;
+ main_loop = NULL;
- idbfs_available = false;
- time_to_save_sync = -1;
+ idb_available = false;
+ sync_wait_time = -1;
+
+ AudioDriverManager::add_driver(&audio_driver_javascript);
Vector<Logger *> loggers;
loggers.push_back(memnew(StdLogger));
_set_logger(memnew(CompositeLogger(loggers)));
-}
-OS_JavaScript::~OS_JavaScript() {
+ FileAccessUnix::close_notification_func = file_access_close_callback;
}
diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h
index f0ba9422e8..f40fb8fc7e 100644
--- a/platform/javascript/os_javascript.h
+++ b/platform/javascript/os_javascript.h
@@ -32,54 +32,58 @@
#define OS_JAVASCRIPT_H
#include "audio_driver_javascript.h"
-#include "drivers/unix/os_unix.h"
#include "main/input_default.h"
-#include "os/input.h"
-#include "os/main_loop.h"
-#include "power_javascript.h"
#include "servers/audio_server.h"
#include "servers/visual/rasterizer.h"
+#include "unix/os_unix.h"
#include <emscripten/html5.h>
-typedef String (*GetUserDataDirFunc)();
-
class OS_JavaScript : public OS_Unix {
- bool idbfs_available;
- int64_t time_to_save_sync;
- int64_t last_sync_time;
-
- VisualServer *visual_server;
- AudioDriverJavaScript audio_driver_javascript;
-
- InputDefault *input;
+ VideoMode video_mode;
Vector2 windowed_size;
bool window_maximized;
- bool soft_fs_enabled;
- bool canvas_size_adjustment_requested;
- VideoMode video_mode;
+ bool entering_fullscreen;
+ bool just_exited_fullscreen;
+
+ InputDefault *input;
+ Ref<InputEventKey> deferred_key_event;
CursorShape cursor_shape;
+ Point2 touches[32];
+
MainLoop *main_loop;
+ AudioDriverJavaScript audio_driver_javascript;
+
+ bool idb_available;
+ int64_t sync_wait_time;
+ int64_t last_sync_check_time;
+
+ static EM_BOOL fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data);
+
+ static EM_BOOL keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
+ static EM_BOOL keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
+ static EM_BOOL keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data);
- GetUserDataDirFunc get_user_data_dir_func;
+ static EM_BOOL mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data);
+ static EM_BOOL mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data);
- PowerJavascript *power_manager;
+ static EM_BOOL wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data);
- static void _close_notification_funcs(const String &p_file, int p_flags);
+ static EM_BOOL touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data);
+ static EM_BOOL touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data);
+ static EM_BOOL gamepad_change_callback(int p_event_type, const EmscriptenGamepadEvent *p_event, void *p_user_data);
void process_joypads();
- void set_css_cursor(const char *);
- const char *get_css_cursor() const;
+ static void main_loop_callback();
-public:
- // functions used by main to initialize/deintialize the OS
- virtual int get_video_driver_count() const;
- virtual const char *get_video_driver_name(int p_driver) const;
+ static void file_access_close_callback(const String &p_file, int p_flags);
- virtual int get_audio_driver_count() const;
- virtual const char *get_audio_driver_name(int p_driver) const;
+ int video_driver_index;
+
+protected:
+ virtual int get_current_video_driver() const;
virtual void initialize_core();
virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
@@ -89,77 +93,64 @@ public:
virtual void finalize();
- typedef int64_t ProcessID;
-
- //static OS* get_singleton();
-
- virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
-
- virtual void set_mouse_mode(MouseMode p_mode);
- virtual MouseMode get_mouse_mode() const;
- virtual Point2 get_mouse_position() const;
- virtual int get_mouse_button_state() const;
- virtual void set_window_title(const String &p_title);
+ virtual bool _check_internal_feature_support(const String &p_feature);
- //virtual void set_clipboard(const String& p_text);
- //virtual String get_clipboard() const;
+public:
+ // Override return type to make writing static callbacks less tedious.
+ static OS_JavaScript *get_singleton();
virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
virtual VideoMode get_video_mode(int p_screen = 0) const;
virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
- virtual Size2 get_screen_size(int p_screen = -1) const;
-
virtual void set_window_size(const Size2);
virtual Size2 get_window_size() const;
virtual void set_window_maximized(bool p_enabled);
- virtual bool is_window_maximized() const { return window_maximized; }
- virtual void set_window_fullscreen(bool p_enable);
+ virtual bool is_window_maximized() const;
+ virtual void set_window_fullscreen(bool p_enabled);
virtual bool is_window_fullscreen() const;
+ virtual Size2 get_screen_size(int p_screen = -1) const;
- void request_canvas_size_adjustment();
+ virtual Point2 get_mouse_position() const;
+ virtual int get_mouse_button_state() const;
+ virtual void set_cursor_shape(CursorShape p_shape);
+ virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
+ virtual void set_mouse_mode(MouseMode p_mode);
+ virtual MouseMode get_mouse_mode() const;
- virtual String get_name();
- virtual MainLoop *get_main_loop() const;
+ virtual bool has_touchscreen_ui_hint() const;
- virtual bool can_draw() const;
+ virtual bool is_joy_known(int p_device);
+ virtual String get_joy_guid(int p_device) const;
- virtual bool is_userfs_persistent() const;
+ virtual int get_video_driver_count() const;
+ virtual const char *get_video_driver_name(int p_driver) const;
- virtual void set_cursor_shape(CursorShape p_shape);
- virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
+ virtual int get_audio_driver_count() const;
+ virtual const char *get_audio_driver_name(int p_driver) const;
- void main_loop_begin();
+ virtual MainLoop *get_main_loop() const;
+ void run_async();
bool main_loop_iterate();
- void main_loop_request_quit();
- void main_loop_end();
- void main_loop_focusout();
- void main_loop_focusin();
- virtual bool has_touchscreen_ui_hint() const;
-
- virtual Error shell_open(String p_uri);
- virtual String get_user_data_dir() const;
+ virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
+ virtual void set_window_title(const String &p_title);
String get_executable_path() const;
- virtual String get_resource_dir() const;
-
- void process_accelerometer(const Vector3 &p_accelerometer);
- void push_input(const Ref<InputEvent> &p_ev);
+ virtual Error shell_open(String p_uri);
+ virtual String get_name();
+ virtual bool can_draw() const;
- virtual bool is_joy_known(int p_device);
- virtual String get_joy_guid(int p_device) const;
- bool joy_connection_changed(int p_type, const EmscriptenGamepadEvent *p_event);
+ virtual String get_resource_dir() const;
+ virtual String get_user_data_dir() const;
virtual OS::PowerState get_power_state();
virtual int get_power_seconds_left();
virtual int get_power_percent_left();
- virtual bool _check_internal_feature_support(const String &p_feature);
-
- void set_idbfs_available(bool p_idbfs_available);
+ void set_idb_available(bool p_idb_available);
+ virtual bool is_userfs_persistent() const;
- OS_JavaScript(const char *p_execpath, GetUserDataDirFunc p_get_user_data_dir_func);
- ~OS_JavaScript();
+ OS_JavaScript(int p_argc, char *p_argv[]);
};
#endif
diff --git a/platform/javascript/power_javascript.cpp b/platform/javascript/power_javascript.cpp
deleted file mode 100644
index 5241644dbc..0000000000
--- a/platform/javascript/power_javascript.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/*************************************************************************/
-/* power_javascript.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "power_javascript.h"
-#include "error_macros.h"
-
-bool PowerJavascript::UpdatePowerInfo() {
- // TODO Javascript implementation
- return false;
-}
-
-OS::PowerState PowerJavascript::get_power_state() {
- if (UpdatePowerInfo()) {
- return power_state;
- } else {
- WARN_PRINT("Power management is not implemented on this platform, defaulting to POWERSTATE_UNKNOWN");
- return OS::POWERSTATE_UNKNOWN;
- }
-}
-
-int PowerJavascript::get_power_seconds_left() {
- if (UpdatePowerInfo()) {
- return nsecs_left;
- } else {
- WARN_PRINT("Power management is not implemented on this platform, defaulting to -1");
- return -1;
- }
-}
-
-int PowerJavascript::get_power_percent_left() {
- if (UpdatePowerInfo()) {
- return percent_left;
- } else {
- WARN_PRINT("Power management is not implemented on this platform, defaulting to -1");
- return -1;
- }
-}
-
-PowerJavascript::PowerJavascript() :
- nsecs_left(-1),
- percent_left(-1),
- power_state(OS::POWERSTATE_UNKNOWN) {
-}
-
-PowerJavascript::~PowerJavascript() {
-}
diff --git a/platform/javascript/power_javascript.h b/platform/javascript/power_javascript.h
deleted file mode 100644
index c0c564aa60..0000000000
--- a/platform/javascript/power_javascript.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*************************************************************************/
-/* power_javascript.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef PLATFORM_JAVASCRIPT_POWER_JAVASCRIPT_H_
-#define PLATFORM_JAVASCRIPT_POWER_JAVASCRIPT_H_
-
-#include "os/os.h"
-
-class PowerJavascript {
-private:
- int nsecs_left;
- int percent_left;
- OS::PowerState power_state;
-
- bool UpdatePowerInfo();
-
-public:
- PowerJavascript();
- virtual ~PowerJavascript();
-
- OS::PowerState get_power_state();
- int get_power_seconds_left();
- int get_power_percent_left();
-};
-
-#endif /* PLATFORM_JAVASCRIPT_POWER_JAVASCRIPT_H_ */
diff --git a/platform/javascript/pre.js b/platform/javascript/pre.js
index 311aa44fda..02194bc75e 100644
--- a/platform/javascript/pre.js
+++ b/platform/javascript/pre.js
@@ -1,2 +1,2 @@
var Engine = {
- RuntimeEnvironment: function(Module) {
+ RuntimeEnvironment: function(Module, exposedLibs) {
diff --git a/platform/javascript/run_icon.png b/platform/javascript/run_icon.png
index dedee6f479..574abb0150 100644
--- a/platform/javascript/run_icon.png
+++ b/platform/javascript/run_icon.png
Binary files differ