Enable pthreads on Emscripten (#17586)
* workerized RA * Workerized (non-async) web player, using OPFS This patch eliminates the need for asyncify and uses modern filesystem APIs instead of the deprecated, unmaintained BrowserFS. This is a WIP patch because it won't fully work until these two Emscripten PRs land and are released: https://github.com/emscripten-core/emscripten/pull/23518 https://github.com/emscripten-core/emscripten/pull/23021 The former fixes an offscreen canvas context recreation bug, and the latter adds an equivalent to BrowserFS's XHR filesystem (but without the hazardous running-XHR-on-the-main-thread problem). The biggest issue is that local storage of users who were using the old version of the webplayer will be gone when they switch to the new webplayer. I don't have a good story for converting the old BrowserFS IDBFS contents into the new OPFS filesystem (the move is worth doing because OPFS supports seeking and reading only bits of a file, and because BrowserFS is dead). I've kept around the old libretro webplayer under pkg/emscripten/libretro-classic, and with these make flags you can build a non-workerized RA that uses asyncify to sleep as before: make -f Makefile.emscripten libretro=$CORE HAVE_WORKER=0 HAVE_WASMFS=0 PTHREAD=0 HAVE_AL=1 I also moved the default directory for core content on emscripten to not be a subdirectory of the local filesystem mount, because it's confusing to have a subdirectory that's lazily fetched and not mirrored to the local storage. I think it won't impact existing users of the classic web player because they already have a retroarch.cfg in place. * Get fetchfs working without manifest support * makefile fixes * fix scaling, remove zip dependency * Support asset/cheats/etc downloaders for emscripten - Add http transfer support for emscripten - At the task_http level, not the net_http level --- so no netplay or webdav. - Change default paths to be more like other platforms - Gives us smaller bundles and a faster boot time - Had to work around a task queue bug on Emscripten - I made the smallest possible change to do it, but it may be better to fix in rthread.c * Load an emscripten file_packager package on first run If no ozone assets are present, load a libretro_minimal package created using Emscripten's built-in file packager. * updated readme, removed indexer from wasmfs libretro-web * Put back zip dependency, load asset bundle into opfs on first run * fix upload path * Remove unused function * easy testing setup for two multithreaded conditions 1. make PROXY_TO_PTHREAD=1 (slower) 2. make PROXY_TO_PTHREAD=0 (bad audio, because doesn't sleep in openal.c) * Remove condition on sleep in openal also make input_driver check existence of drv->axis, drv->button before calling them. * Fix resizing under EGL * Don't force config file path on emscripten * Add time.h include to netplay, default HAVE_NETPLAYDISCOVERY to 0 * Remove nearly all proxied joypad calls under emscripten * Fix file uploads under firefox * Fix safari API uses, but Safari still hangs in OPFS filesystem mount I think this can be fixed by moving the backend creation off the main thread. * Move filesystem init into emscripten C entry point * Setup filesystems off of main thread * re-set default player to async Also improve Safari compatibility under proxy-to-pthread condition * Safari upload file fixes * Remove some excess prints * Fix typo
This commit is contained in:
parent
6ec4ffb2fa
commit
56014a27d6
|
@ -1536,7 +1536,10 @@ ifeq ($(HAVE_GL_CONTEXT), 1)
|
|||
endif
|
||||
|
||||
ifeq ($(HAVE_EMSCRIPTEN), 1)
|
||||
OBJ += gfx/drivers_context/emscriptenegl_ctx.o
|
||||
ifeq ($(HAVE_EGL), 1)
|
||||
OBJ += gfx/drivers_context/emscriptenegl_ctx.o
|
||||
endif
|
||||
OBJ += gfx/drivers_context/emscriptenwebgl_ctx.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_MALI_FBDEV), 1)
|
||||
|
@ -2203,11 +2206,16 @@ ifeq ($(HAVE_NETWORKING), 1)
|
|||
$(LIBRETRO_COMM_DIR)/net/net_socket.o \
|
||||
core_updater_list.o \
|
||||
network/natt.o \
|
||||
tasks/task_http.o \
|
||||
tasks/task_netplay_lan_scan.o \
|
||||
tasks/task_netplay_nat_traversal.o \
|
||||
tasks/task_netplay_find_content.o
|
||||
|
||||
ifeq ($(HAVE_EMSCRIPTEN), 1)
|
||||
OBJ += tasks/task_http_emscripten.o
|
||||
else
|
||||
OBJ += tasks/task_http.o
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_MENU), 1)
|
||||
OBJ += tasks/task_pl_thumbnail_download.o
|
||||
endif
|
||||
|
|
|
@ -19,12 +19,18 @@ HAVE_PATCH = 1
|
|||
HAVE_DSP_FILTER = 1
|
||||
HAVE_VIDEO_FILTER = 1
|
||||
HAVE_OVERLAY = 1
|
||||
HAVE_NETWORKING = 1
|
||||
HAVE_LIBRETRODB = 1
|
||||
HAVE_COMPRESSION = 1
|
||||
HAVE_UPDATE_ASSETS = 1
|
||||
HAVE_ONLINE_UPDATER = 1
|
||||
HAVE_GLSL = 1
|
||||
HAVE_SCREENSHOTS = 1
|
||||
HAVE_REWIND = 1
|
||||
HAVE_AUDIOMIXER = 1
|
||||
HAVE_CC_RESAMPLER = 1
|
||||
HAVE_EGL = 1
|
||||
HAVE_EGL ?= 1
|
||||
HAVE_OPENGLES = 1
|
||||
HAVE_RJPEG = 0
|
||||
HAVE_RPNG = 1
|
||||
HAVE_EMSCRIPTEN = 1
|
||||
|
@ -48,6 +54,12 @@ HAVE_7ZIP = 1
|
|||
HAVE_BSV_MOVIE = 1
|
||||
HAVE_AL = 1
|
||||
HAVE_CHD ?= 0
|
||||
HAVE_WASMFS ?= 0
|
||||
PROXY_TO_PTHREAD ?= 0
|
||||
HAVE_NETPLAYDISCOVERY ?= 0
|
||||
|
||||
DEFINES += -DHAVE_NETWORKING -DHAVE_ONLINE_UPDATER -DHAVE_UPDATE_ASSETS -DHAVE_COMPRESSION
|
||||
DEFINES += -DHAVE_UPDATE_CORE_INFO
|
||||
|
||||
# WARNING -- READ BEFORE ENABLING
|
||||
# The rwebaudio driver is known to have several audio bugs, such as
|
||||
|
@ -61,12 +73,12 @@ HAVE_RWEBAUDIO = 0
|
|||
GL_DEBUG ?= 0
|
||||
|
||||
# enable javascript filesystem tracking
|
||||
FS_DEBUG = 1
|
||||
FS_DEBUG = 0
|
||||
|
||||
HAVE_OPENGLES ?= 1
|
||||
HAVE_OPENGLES3 ?= 0
|
||||
|
||||
ASYNC ?= 0
|
||||
ASYNC ?= 1
|
||||
LTO ?= 0
|
||||
PTHREAD ?= 0
|
||||
|
||||
|
@ -92,59 +104,84 @@ _cmd_toggle_menu,_cmd_reload_config,_cmd_toggle_grab_mouse,_cmd_toggle_game_focu
|
|||
_cmd_set_volume,_cmd_set_shader,_cmd_cheat_set_code,_cmd_cheat_get_code,_cmd_cheat_toggle_index,_cmd_cheat_get_code_state,_cmd_cheat_realloc,\
|
||||
_cmd_cheat_get_size,_cmd_cheat_apply_cheats
|
||||
|
||||
LIBS := -s USE_ZLIB=1
|
||||
LDFLAGS := -L. --no-heap-copy -s $(LIBS) -s STACK_SIZE=$(STACK_SIZE) -s INITIAL_MEMORY=$(INITIAL_HEAP) \
|
||||
-s EXPORTED_RUNTIME_METHODS=callMain,FS,PATH,ERRNO_CODES,stringToNewUTF8,UTF8ToString \
|
||||
-s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS="$(EXPORTED_FUNCTIONS)" \
|
||||
-s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME="libretro_$(subst -,_,$(LIBRETRO))" \
|
||||
--extern-pre-js emscripten/pre.js \
|
||||
--js-library emscripten/library_rwebcam.js \
|
||||
--js-library emscripten/library_platform_emscripten.js
|
||||
EXPORTS := callMain,FS,PATH,ERRNO_CODES,ENV,stringToNewUTF8,UTF8ToString,Browser,GL
|
||||
|
||||
ifeq ($(HAVE_RWEBAUDIO), 1)
|
||||
LDFLAGS += --js-library emscripten/library_rwebaudio.js
|
||||
DEFINES += -DHAVE_RWEBAUDIO
|
||||
endif
|
||||
ifeq ($(HAVE_AL), 1)
|
||||
LDFLAGS += -lopenal
|
||||
DEFINES += -DHAVE_AL
|
||||
override ASYNC = 1
|
||||
endif
|
||||
LIBS := -s USE_ZLIB=1 -lbrowser.js
|
||||
|
||||
ifneq ($(PTHREAD), 0)
|
||||
LDFLAGS += -s MAXIMUM_MEMORY=1073741824 -pthread -s PTHREAD_POOL_SIZE=$(PTHREAD)
|
||||
CFLAGS += -pthread
|
||||
HAVE_THREADS=1
|
||||
else
|
||||
HAVE_THREADS=0
|
||||
endif
|
||||
|
||||
ifeq ($(ASYNC), 1)
|
||||
LDFLAGS += -s ASYNCIFY=$(ASYNC) -s ASYNCIFY_STACK_SIZE=8192
|
||||
ifeq ($(DEBUG), 1)
|
||||
LDFLAGS += -s ASYNCIFY_DEBUG=1 # -s ASYNCIFY_ADVISE
|
||||
ifeq ($(HAVE_WASMFS), 1)
|
||||
DEFINES += -DHAVE_WASMFS=1
|
||||
LIBS += -sWASMFS -sFORCE_FILESYSTEM=1 -lfetchfs.js -lopfs.js
|
||||
EXPORTS := $(EXPORTS),FETCHFS,OPFS
|
||||
ifeq ($(PTHREAD),0)
|
||||
$(error ERROR: WASMFS requires threading support)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(PROXY_TO_PTHREAD),1)
|
||||
LIBS += -sUSE_ES6_IMPORT_META=0 -sENVIRONMENT=worker,web
|
||||
LIBS += -sPROXY_TO_PTHREAD -sOFFSCREENCANVAS_SUPPORT
|
||||
DEFINES += -DUSE_OFFSCREENCANVAS=1 -DPROXY_TO_PTHREAD=1
|
||||
else
|
||||
override ASYNC = 1
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL2), 1)
|
||||
LIBS += -s USE_SDL=2
|
||||
DEFINES += -DHAVE_SDL2
|
||||
endif
|
||||
|
||||
|
||||
LDFLAGS := -L. --no-heap-copy -s STACK_SIZE=$(STACK_SIZE) -s INITIAL_MEMORY=$(INITIAL_HEAP) \
|
||||
-s EXPORTED_RUNTIME_METHODS=$(EXPORTS) \
|
||||
-s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS="$(EXPORTED_FUNCTIONS)" \
|
||||
-s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME="libretro_$(subst -,_,$(LIBRETRO))" \
|
||||
-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=0 \
|
||||
--extern-pre-js emscripten/pre.js \
|
||||
--js-library emscripten/library_rwebcam.js \
|
||||
-gsource-map -g2 \
|
||||
--js-library emscripten/library_platform_emscripten.js
|
||||
|
||||
ifeq ($(HAVE_OPENGLES), 1)
|
||||
ifeq ($(HAVE_OPENGLES3), 1)
|
||||
LDFLAGS += -s FULL_ES3=1 -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2
|
||||
LDFLAGS += -s FULL_ES3=1 -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2 -lGL
|
||||
else
|
||||
LDFLAGS += -s FULL_ES2=1
|
||||
LDFLAGS += -s FULL_ES2=1 -s MIN_WEBGL_VERSION=1 -s MAX_WEBGL_VERSION=2 -lGL
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(GL_DEBUG), 1)
|
||||
LDFLAGS += -s GL_ASSERTIONS=1 -s GL_DEBUG=1
|
||||
LDFLAGS += -s GL_ASSERTIONS=1 -s GL_DEBUG=1 -DHAVE_GL_DEBUG_ES=1
|
||||
endif
|
||||
|
||||
ifeq ($(FS_DEBUG), 1)
|
||||
LDFLAGS += -s FS_DEBUG=1
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_SDL2), 1)
|
||||
LIBS += -s USE_SDL=2
|
||||
DEFINES += -DHAVE_SDL2
|
||||
ifeq ($(HAVE_RWEBAUDIO), 1)
|
||||
LDFLAGS += --js-library emscripten/library_rwebaudio.js
|
||||
DEFINES += -DHAVE_RWEBAUDIO
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_AL), 1)
|
||||
LDFLAGS += -lopenal
|
||||
DEFINES += -DHAVE_AL
|
||||
endif
|
||||
|
||||
ifneq ($(PTHREAD), 0)
|
||||
LDFLAGS += -s WASM_MEM_MAX=1073741824 -pthread -s PTHREAD_POOL_SIZE=$(PTHREAD)
|
||||
CFLAGS += -pthread -s SHARED_MEMORY
|
||||
HAVE_THREADS=1
|
||||
else
|
||||
HAVE_THREADS=0
|
||||
endif
|
||||
|
||||
|
||||
ifeq ($(ASYNC), 1)
|
||||
DEFINES += -DEMSCRIPTEN_ASYNCIFY
|
||||
LDFLAGS += -s ASYNCIFY=$(ASYNC) -s ASYNCIFY_STACK_SIZE=8192
|
||||
ifeq ($(DEBUG), 1)
|
||||
LDFLAGS += -s ASYNCIFY_DEBUG=1 # -s ASYNCIFY_ADVISE
|
||||
endif
|
||||
endif
|
||||
|
||||
include Makefile.common
|
||||
|
@ -183,8 +220,10 @@ RARCH_OBJ := $(addprefix $(OBJDIR)/,$(OBJ))
|
|||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(RARCH_OBJ) $(libretro)
|
||||
@$(if $(libretro), mv -f $(libretro) $(libretro_new),)
|
||||
$(libretro_new) : $(libretro)
|
||||
mv -f $(libretro) $(libretro_new)
|
||||
|
||||
$(TARGET): $(RARCH_OBJ) $(libretro_new)
|
||||
@$(if $(Q), $(shell echo echo "LD $@ \<obj\> $(libretro_new) $(LIBS) $(LDFLAGS)"),)
|
||||
$(Q)$(LD) -o $@ $(RARCH_OBJ) $(libretro_new) $(LIBS) $(LDFLAGS)
|
||||
|
||||
|
|
|
@ -1833,7 +1833,11 @@
|
|||
#define DEFAULT_BUILDBOT_SERVER_URL ""
|
||||
#endif
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
#define DEFAULT_BUILDBOT_ASSETS_SERVER_URL "https://buildbot.libretro.com/assets/"
|
||||
#else
|
||||
#define DEFAULT_BUILDBOT_ASSETS_SERVER_URL "http://buildbot.libretro.com/assets/"
|
||||
#endif
|
||||
|
||||
#define DEFAULT_DISCORD_APP_ID "475456035851599874"
|
||||
|
||||
|
|
|
@ -15,28 +15,6 @@ var LibraryPlatformEmscripten = {
|
|||
}
|
||||
},
|
||||
|
||||
PlatformEmscriptenWatchCanvasSize: function() {
|
||||
RPE.observer = new ResizeObserver(function(e) {
|
||||
var width, height;
|
||||
var entry = e.find(i => i.target == Module.canvas);
|
||||
if (!entry) return;
|
||||
if (entry.devicePixelContentBoxSize) {
|
||||
width = entry.devicePixelContentBoxSize[0].inlineSize;
|
||||
height = entry.devicePixelContentBoxSize[0].blockSize;
|
||||
} else {
|
||||
width = Math.round(entry.contentRect.width * window.devicePixelRatio);
|
||||
height = Math.round(entry.contentRect.height * window.devicePixelRatio);
|
||||
}
|
||||
Module.setCanvasSize(width, height);
|
||||
Module.print("Setting real canvas size: " + width + " x " + height);
|
||||
});
|
||||
RPE.observer.observe(Module.canvas);
|
||||
window.addEventListener("resize", function(e) {
|
||||
RPE.observer.unobserve(Module.canvas);
|
||||
RPE.observer.observe(Module.canvas);
|
||||
}, false);
|
||||
},
|
||||
|
||||
PlatformEmscriptenPowerStateInit: function() {
|
||||
if (!navigator.getBattery) return;
|
||||
navigator.getBattery().then(function(battery) {
|
||||
|
|
|
@ -17,7 +17,13 @@
|
|||
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <emscripten/html5.h>
|
||||
#if HAVE_WASMFS
|
||||
#include <emscripten/wasmfs.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <file/config_file.h>
|
||||
#include <queues/task_queue.h>
|
||||
|
@ -50,7 +56,33 @@
|
|||
#include "../../audio/audio_driver.h"
|
||||
|
||||
void emscripten_mainloop(void);
|
||||
void PlatformEmscriptenWatchCanvasSize(void);
|
||||
void PlatformEmscriptenWatchCanvasSize(void) {
|
||||
MAIN_THREAD_ASYNC_EM_ASM(
|
||||
RPE.observer = new ResizeObserver(function(_e) {
|
||||
var container = Module.canvas.parentElement;
|
||||
var width = container.offsetWidth;
|
||||
var height = container.offsetHeight;
|
||||
var w = Module.canvas.width;
|
||||
var h = Module.canvas.height;
|
||||
if (w == 0 || h == 0 || width == 0 || height == 0) { return; }
|
||||
/* Module.print("Setting real canvas size: " + width + " x " + height); */
|
||||
var new_w = `${width}px`;
|
||||
var new_h = `${height}px`;
|
||||
if (Module.canvas.style.width != new_w || Module.canvas.style.height != new_h) {
|
||||
Module.canvas.style.width = new_w;
|
||||
Module.canvas.style.height = new_h;
|
||||
}
|
||||
if (!Module.canvas.controlTransferredOffscreen) {
|
||||
Module.Browser.setCanvasSize(width, height);
|
||||
}
|
||||
});
|
||||
RPE.observer.observe(Module.canvas.parentElement);
|
||||
window.addEventListener("resize", function(e) {
|
||||
RPE.observer.unobserve(Module.canvas.parentElement);
|
||||
RPE.observer.observe(Module.canvas.parentElement);
|
||||
}, false);
|
||||
);
|
||||
}
|
||||
void PlatformEmscriptenPowerStateInit(void);
|
||||
bool PlatformEmscriptenPowerStateGetSupported(void);
|
||||
int PlatformEmscriptenPowerStateGetDischargeTime(void);
|
||||
|
@ -262,17 +294,101 @@ static void frontend_emscripten_get_env(int *argc, char *argv[],
|
|||
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CACHE], "/tmp/",
|
||||
"retroarch", sizeof(g_defaults.dirs[DEFAULT_DIR_CACHE]));
|
||||
|
||||
/* history and main config */
|
||||
/* history */
|
||||
strlcpy(g_defaults.dirs[DEFAULT_DIR_CONTENT_HISTORY],
|
||||
user_path, sizeof(g_defaults.dirs[DEFAULT_DIR_CONTENT_HISTORY]));
|
||||
fill_pathname_join(g_defaults.path_config, user_path,
|
||||
FILE_PATH_MAIN_CONFIG, sizeof(g_defaults.path_config));
|
||||
|
||||
#ifndef IS_SALAMANDER
|
||||
dir_check_defaults("custom.ini");
|
||||
#endif
|
||||
}
|
||||
|
||||
typedef struct args {
|
||||
int argc;
|
||||
char **argv;
|
||||
} args_t;
|
||||
static bool retro_started = false;
|
||||
static bool filesystem_ready = false;
|
||||
|
||||
#if HAVE_WASMFS
|
||||
void PlatformEmscriptenMountFilesystems(void *info) {
|
||||
char *opfs_mount = getenv("OPFS");
|
||||
char *fetch_manifest = getenv("FETCH_MANIFEST");
|
||||
if(opfs_mount) {
|
||||
int res;
|
||||
printf("[OPFS] Mount OPFS at %s\n", opfs_mount);
|
||||
backend_t opfs = wasmfs_create_opfs_backend();
|
||||
{
|
||||
char *parent = strdup(opfs_mount);
|
||||
path_parent_dir(parent, strlen(parent));
|
||||
if(!path_mkdir(parent)) {
|
||||
printf("mkdir error %d\n",errno);
|
||||
abort();
|
||||
}
|
||||
free(parent);
|
||||
}
|
||||
res = wasmfs_create_directory(opfs_mount, 0777, opfs);
|
||||
if(res) {
|
||||
printf("[OPFS] error result %d\n",res);
|
||||
if(errno) {
|
||||
printf("[OPFS] errno %d\n",errno);
|
||||
abort();
|
||||
}
|
||||
abort();
|
||||
}
|
||||
}
|
||||
if(fetch_manifest) {
|
||||
/* fetch_manifest should be a path to a manifest file.
|
||||
manifest files have this format:
|
||||
|
||||
URL PATH
|
||||
URL PATH
|
||||
URL PATH
|
||||
...
|
||||
|
||||
Where URL may not contain spaces, but PATH may.
|
||||
*/
|
||||
int max_line_len = 1024;
|
||||
printf("[FetchFS] read fetch manifest from %s\n",fetch_manifest);
|
||||
FILE *file = fopen(fetch_manifest, O_RDONLY);
|
||||
char *line = calloc(sizeof(char), max_line_len);
|
||||
size_t len = 0;
|
||||
while (getline(&line, &len, file) != -1) {
|
||||
char *path = strstr(line, " ");
|
||||
backend_t fetch;
|
||||
int fd;
|
||||
if (!path) {
|
||||
printf("Manifest file has invalid line %s\n",line);
|
||||
return;
|
||||
}
|
||||
*path = '\0';
|
||||
path += 1;
|
||||
printf("Fetch %s from %s\n", path, line);
|
||||
{
|
||||
char *parent = strdup(path);
|
||||
path_parent_dir(parent, strlen(parent));
|
||||
if(!path_mkdir(parent)) {
|
||||
printf("mkdir error %d\n",errno);
|
||||
abort();
|
||||
}
|
||||
free(parent);
|
||||
}
|
||||
fetch = wasmfs_create_fetch_backend(line, 8*1024*1024);
|
||||
fd = wasmfs_create_file(path, 0777, fetch);
|
||||
close(fd);
|
||||
}
|
||||
fclose(file);
|
||||
free(line);
|
||||
}
|
||||
filesystem_ready = true;
|
||||
#if !PROXY_TO_PTHREAD
|
||||
while (!retro_started) {
|
||||
retro_sleep(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif /* HAVE_WASMFS */
|
||||
|
||||
static enum frontend_powerstate frontend_emscripten_get_powerstate(int *seconds, int *percent)
|
||||
{
|
||||
enum frontend_powerstate ret = FRONTEND_POWERSTATE_NONE;
|
||||
|
@ -303,17 +419,42 @@ static uint64_t frontend_emscripten_get_free_mem(void)
|
|||
return PlatformEmscriptenGetFreeMem();
|
||||
}
|
||||
|
||||
void emscripten_bootup_mainloop(void *argptr) {
|
||||
if(filesystem_ready) {
|
||||
args_t *args = (args_t*)argptr;
|
||||
emscripten_set_main_loop(emscripten_mainloop, 0, 0);
|
||||
emscripten_set_main_loop_timing(EM_TIMING_RAF, 1);
|
||||
rarch_main(args->argc, args->argv, NULL);
|
||||
retro_started = true;
|
||||
free(args);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
args_t *args = calloc(sizeof(args_t), 1);
|
||||
|
||||
PlatformEmscriptenWatchCanvasSize();
|
||||
PlatformEmscriptenPowerStateInit();
|
||||
|
||||
EM_ASM({
|
||||
specialHTMLTargets["!canvas"] = Module.canvas;
|
||||
});
|
||||
|
||||
emscripten_set_main_loop(emscripten_mainloop, 0, 0);
|
||||
rarch_main(argc, argv, NULL);
|
||||
emscripten_set_canvas_element_size("#canvas", 800, 600);
|
||||
emscripten_set_element_css_size("#canvas", 800.0, 600.0);
|
||||
#if HAVE_WASMFS
|
||||
#if PROXY_TO_PTHREAD
|
||||
{
|
||||
PlatformEmscriptenMountFilesystems(NULL);
|
||||
}
|
||||
#else /* !PROXY_TO_PTHREAD */
|
||||
{
|
||||
sthread_t *thread = sthread_create(PlatformEmscriptenMountFilesystems, NULL);
|
||||
sthread_detach(thread);
|
||||
}
|
||||
#endif /* PROXY_TO_PTHREAD */
|
||||
#else /* !HAVE_WASMFS */
|
||||
filesystem_ready = true;
|
||||
#endif /* HAVE_WASMFS */
|
||||
emscripten_set_main_loop_arg(emscripten_bootup_mainloop, (void *)args, 0, 0);
|
||||
emscripten_set_main_loop_timing(EM_TIMING_RAF, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ static void gfx_ctx_emscripten_swap_interval(void *data, int interval)
|
|||
|
||||
static void gfx_ctx_emscripten_get_canvas_size(int *width, int *height)
|
||||
{
|
||||
EMSCRIPTEN_RESULT r = emscripten_get_canvas_element_size("!canvas", width, height);
|
||||
EMSCRIPTEN_RESULT r = emscripten_get_canvas_element_size("#canvas", width, height);
|
||||
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
|
@ -70,11 +70,10 @@ static void gfx_ctx_emscripten_check_window(void *data, bool *quit,
|
|||
emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data;
|
||||
|
||||
gfx_ctx_emscripten_get_canvas_size(&input_width, &input_height);
|
||||
|
||||
*resize = (emscripten->fb_width != input_width || emscripten->fb_height != input_height);
|
||||
*width = emscripten->fb_width = (unsigned)input_width;
|
||||
*height = emscripten->fb_height = (unsigned)input_height;
|
||||
*quit = false;
|
||||
*resize = false;
|
||||
}
|
||||
|
||||
static void gfx_ctx_emscripten_swap_buffers(void *data)
|
||||
|
@ -94,9 +93,10 @@ static void gfx_ctx_emscripten_get_video_size(void *data,
|
|||
|
||||
if (!emscripten)
|
||||
return;
|
||||
|
||||
*width = emscripten->fb_width;
|
||||
*height = emscripten->fb_height;
|
||||
int w, h;
|
||||
gfx_ctx_emscripten_get_canvas_size(&w, &h);
|
||||
*width = w;
|
||||
*height = h;
|
||||
}
|
||||
|
||||
static bool gfx_ctx_emscripten_get_metrics(void *data,
|
||||
|
@ -124,11 +124,9 @@ static void gfx_ctx_emscripten_destroy(void *data)
|
|||
|
||||
if (!emscripten)
|
||||
return;
|
||||
|
||||
#ifdef HAVE_EGL
|
||||
egl_destroy(&emscripten->egl);
|
||||
#endif
|
||||
|
||||
free(data);
|
||||
}
|
||||
|
||||
|
@ -191,7 +189,6 @@ static void *gfx_ctx_emscripten_init(void *video_driver)
|
|||
#endif
|
||||
|
||||
return emscripten;
|
||||
|
||||
error:
|
||||
gfx_ctx_emscripten_destroy(video_driver);
|
||||
return NULL;
|
||||
|
|
|
@ -0,0 +1,298 @@
|
|||
/* RetroArch - A frontend for libretro.
|
||||
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
|
||||
* Copyright (C) 2011-2017 - Daniel De Matteis
|
||||
* Copyright (C) 2012-2015 - Michael Lelli
|
||||
*
|
||||
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with RetroArch.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <emscripten/html5.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "../../config.h"
|
||||
#endif
|
||||
|
||||
#include "../../retroarch.h"
|
||||
#include "../../verbosity.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx;
|
||||
unsigned fb_width;
|
||||
unsigned fb_height;
|
||||
} emscripten_ctx_data_t;
|
||||
|
||||
static void gfx_ctx_emscripten_webgl_swap_interval(void *data, int interval)
|
||||
{
|
||||
if (interval == 0)
|
||||
emscripten_set_main_loop_timing(EM_TIMING_SETIMMEDIATE, 0);
|
||||
else
|
||||
emscripten_set_main_loop_timing(EM_TIMING_RAF, interval);
|
||||
}
|
||||
|
||||
static void gfx_ctx_emscripten_webgl_get_canvas_size(int *width, int *height)
|
||||
{
|
||||
EmscriptenFullscreenChangeEvent fullscreen_status;
|
||||
bool is_fullscreen = false;
|
||||
EMSCRIPTEN_RESULT r = emscripten_get_fullscreen_status(&fullscreen_status);
|
||||
|
||||
if (r == EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
if (fullscreen_status.isFullscreen)
|
||||
{
|
||||
is_fullscreen = true;
|
||||
*width = fullscreen_status.screenWidth;
|
||||
*height = fullscreen_status.screenHeight;
|
||||
}
|
||||
}
|
||||
if (!is_fullscreen)
|
||||
{
|
||||
double w, h;
|
||||
r = emscripten_get_element_css_size("#canvas", &w, &h);
|
||||
*width = (int)w;
|
||||
*height = (int)h;
|
||||
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
*width = 800;
|
||||
*height = 600;
|
||||
RARCH_ERR("[EMSCRIPTEN/WebGL]: Could not get screen dimensions: %d\n",r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_ctx_emscripten_webgl_check_window(void *data, bool *quit,
|
||||
bool *resize, unsigned *width, unsigned *height)
|
||||
{
|
||||
int input_width=0;
|
||||
int input_height=0;
|
||||
emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data;
|
||||
|
||||
gfx_ctx_emscripten_webgl_get_canvas_size(&input_width, &input_height);
|
||||
*width = (unsigned)input_width;
|
||||
*height = (unsigned)input_height;
|
||||
*resize = (*width != emscripten->fb_width || *height != emscripten->fb_height);
|
||||
emscripten->fb_width = *width;
|
||||
emscripten->fb_height = *height;
|
||||
*quit = false;
|
||||
}
|
||||
|
||||
static void gfx_ctx_emscripten_webgl_swap_buffers(void *data)
|
||||
{
|
||||
#ifdef USE_OFFSCREENCANVAS
|
||||
emscripten_webgl_commit_frame();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void gfx_ctx_emscripten_webgl_get_video_size(void *data,
|
||||
unsigned *width, unsigned *height)
|
||||
{
|
||||
emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data;
|
||||
int s_width, s_height;
|
||||
if (!emscripten)
|
||||
return;
|
||||
gfx_ctx_emscripten_webgl_get_canvas_size(&s_width, &s_height);
|
||||
*width = (unsigned)s_width;
|
||||
*height = (unsigned)s_height;
|
||||
}
|
||||
|
||||
static void gfx_ctx_emscripten_webgl_destroy(void *data)
|
||||
{
|
||||
emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data;
|
||||
|
||||
if (!emscripten)
|
||||
return;
|
||||
|
||||
emscripten_webgl_destroy_context(emscripten->ctx);
|
||||
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void *gfx_ctx_emscripten_webgl_init(void *video_driver)
|
||||
{
|
||||
int width, height;
|
||||
emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)
|
||||
calloc(1, sizeof(*emscripten));
|
||||
|
||||
EmscriptenWebGLContextAttributes attrs={0};
|
||||
emscripten_webgl_init_context_attributes(&attrs);
|
||||
attrs.alpha = false;
|
||||
attrs.depth = true;
|
||||
attrs.stencil = true;
|
||||
attrs.antialias = false;
|
||||
attrs.powerPreference = EM_WEBGL_POWER_PREFERENCE_HIGH_PERFORMANCE;
|
||||
attrs.majorVersion = 2;
|
||||
attrs.minorVersion = 0;
|
||||
attrs.enableExtensionsByDefault = true;
|
||||
#ifdef USE_OFFSCREENCANVAS
|
||||
attrs.explicitSwapControl = true;
|
||||
#else
|
||||
attrs.explicitSwapControl = false;
|
||||
#endif
|
||||
attrs.renderViaOffscreenBackBuffer = false;
|
||||
attrs.proxyContextToMainThread = EMSCRIPTEN_WEBGL_CONTEXT_PROXY_DISALLOW;
|
||||
|
||||
if (!emscripten)
|
||||
return NULL;
|
||||
|
||||
emscripten->ctx = emscripten_webgl_create_context("#canvas", &attrs);
|
||||
if(!emscripten->ctx) {
|
||||
RARCH_ERR("[EMSCRIPTEN/WEBGL]: Failed to initialize webgl\n");
|
||||
goto error;
|
||||
}
|
||||
emscripten_webgl_get_drawing_buffer_size(emscripten->ctx, &width, &height);
|
||||
emscripten_webgl_make_context_current(emscripten->ctx);
|
||||
emscripten->fb_width = (unsigned)width;
|
||||
emscripten->fb_height = (unsigned)height;
|
||||
|
||||
return emscripten;
|
||||
|
||||
error:
|
||||
gfx_ctx_emscripten_webgl_destroy(video_driver);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool gfx_ctx_emscripten_webgl_set_video_mode(void *data,
|
||||
unsigned width, unsigned height,
|
||||
bool fullscreen)
|
||||
{
|
||||
emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data;
|
||||
EMSCRIPTEN_RESULT r;
|
||||
if(!emscripten || !emscripten->ctx) return false;
|
||||
|
||||
if (width != 0 && height != 0) {
|
||||
r = emscripten_set_canvas_element_size("#canvas",
|
||||
(int)width, (int)height);
|
||||
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS) {
|
||||
RARCH_ERR("[EMSCRIPTEN/WebGL]: error resizing canvas: %d\n", r);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
emscripten->fb_width = width;
|
||||
emscripten->fb_height = height;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gfx_ctx_emscripten_webgl_set_resize(void *data, unsigned width, unsigned height) {
|
||||
emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data;
|
||||
EMSCRIPTEN_RESULT r;
|
||||
if(!emscripten || !emscripten->ctx) return false;
|
||||
r = emscripten_set_canvas_element_size("#canvas",
|
||||
(int)width, (int)height);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS) {
|
||||
RARCH_ERR("[EMSCRIPTEN/WebGL]: error resizing canvas: %d\n", r);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static enum gfx_ctx_api gfx_ctx_emscripten_webgl_get_api(void *data) { return GFX_CTX_OPENGL_ES_API; }
|
||||
|
||||
static bool gfx_ctx_emscripten_webgl_bind_api(void *data,
|
||||
enum gfx_ctx_api api, unsigned major, unsigned minor)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void gfx_ctx_emscripten_webgl_input_driver(void *data,
|
||||
const char *name,
|
||||
input_driver_t **input, void **input_data)
|
||||
{
|
||||
void *rwebinput = input_driver_init_wrap(&input_rwebinput, name);
|
||||
*input = rwebinput ? &input_rwebinput : NULL;
|
||||
*input_data = rwebinput;
|
||||
}
|
||||
|
||||
static bool gfx_ctx_emscripten_webgl_get_metrics(void *data,
|
||||
enum display_metric_types type, float *value)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
// there is no way to get the actual DPI in emscripten, so return a standard value instead.
|
||||
// this is needed for menu touch/pointer swipe scrolling to work.
|
||||
case DISPLAY_METRIC_DPI:
|
||||
*value = 150.0f;
|
||||
break;
|
||||
|
||||
default:
|
||||
*value = 0.0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool gfx_ctx_emscripten_webgl_has_focus(void *data) {
|
||||
emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data;
|
||||
return emscripten && emscripten->ctx;
|
||||
}
|
||||
|
||||
static bool gfx_ctx_emscripten_webgl_suppress_screensaver(void *data, bool enable) { return false; }
|
||||
|
||||
static float gfx_ctx_emscripten_webgl_translate_aspect(void *data,
|
||||
unsigned width, unsigned height) { return (float)width / height; }
|
||||
|
||||
static void gfx_ctx_emscripten_webgl_bind_hw_render(void *data, bool enable)
|
||||
{
|
||||
emscripten_ctx_data_t *emscripten = (emscripten_ctx_data_t*)data;
|
||||
emscripten_webgl_make_context_current(emscripten->ctx);
|
||||
}
|
||||
|
||||
static uint32_t gfx_ctx_emscripten_webgl_get_flags(void *data)
|
||||
{
|
||||
uint32_t flags = 0;
|
||||
BIT32_SET(flags, GFX_CTX_FLAGS_SHADERS_GLSL);
|
||||
return flags;
|
||||
}
|
||||
|
||||
static void gfx_ctx_emscripten_webgl_set_flags(void *data, uint32_t flags) { }
|
||||
|
||||
const gfx_ctx_driver_t gfx_ctx_emscripten_webgl = {
|
||||
gfx_ctx_emscripten_webgl_init,
|
||||
gfx_ctx_emscripten_webgl_destroy,
|
||||
gfx_ctx_emscripten_webgl_get_api,
|
||||
gfx_ctx_emscripten_webgl_bind_api,
|
||||
gfx_ctx_emscripten_webgl_swap_interval,
|
||||
gfx_ctx_emscripten_webgl_set_video_mode,
|
||||
gfx_ctx_emscripten_webgl_get_video_size,
|
||||
NULL, /* get_refresh_rate */
|
||||
NULL, /* get_video_output_size */
|
||||
NULL, /* get_video_output_prev */
|
||||
NULL, /* get_video_output_next */
|
||||
gfx_ctx_emscripten_webgl_get_metrics,
|
||||
gfx_ctx_emscripten_webgl_translate_aspect,
|
||||
NULL, /* update_title */
|
||||
gfx_ctx_emscripten_webgl_check_window,
|
||||
gfx_ctx_emscripten_webgl_set_resize, /* set_resize */
|
||||
gfx_ctx_emscripten_webgl_has_focus,
|
||||
gfx_ctx_emscripten_webgl_suppress_screensaver,
|
||||
false,
|
||||
gfx_ctx_emscripten_webgl_swap_buffers,
|
||||
gfx_ctx_emscripten_webgl_input_driver,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
"webgl_emscripten",
|
||||
gfx_ctx_emscripten_webgl_get_flags,
|
||||
gfx_ctx_emscripten_webgl_set_flags,
|
||||
gfx_ctx_emscripten_webgl_bind_hw_render,
|
||||
NULL, /* get_context_data */
|
||||
NULL /* make_current */
|
||||
};
|
|
@ -162,8 +162,11 @@ static const gfx_ctx_driver_t *gfx_ctx_gl_drivers[] = {
|
|||
#ifdef HAVE_OSMESA
|
||||
&gfx_ctx_osmesa,
|
||||
#endif
|
||||
#ifdef EMSCRIPTEN
|
||||
#if (defined(EMSCRIPTEN) && defined(HAVE_EGL))
|
||||
&gfx_ctx_emscripten,
|
||||
#endif
|
||||
#ifdef EMSCRIPTEN
|
||||
&gfx_ctx_emscripten_webgl,
|
||||
#endif
|
||||
&gfx_ctx_null,
|
||||
NULL
|
||||
|
|
|
@ -1384,6 +1384,7 @@ extern const gfx_ctx_driver_t gfx_ctx_cgl;
|
|||
extern const gfx_ctx_driver_t gfx_ctx_cocoagl;
|
||||
extern const gfx_ctx_driver_t gfx_ctx_cocoavk;
|
||||
extern const gfx_ctx_driver_t gfx_ctx_emscripten;
|
||||
extern const gfx_ctx_driver_t gfx_ctx_emscripten_webgl;
|
||||
extern const gfx_ctx_driver_t gfx_ctx_opendingux_fbdev;
|
||||
extern const gfx_ctx_driver_t gfx_ctx_khr_display;
|
||||
extern const gfx_ctx_driver_t gfx_ctx_gdi;
|
||||
|
|
|
@ -417,7 +417,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
|
|||
rwebinput_generate_lut();
|
||||
|
||||
r = emscripten_set_keydown_callback(
|
||||
"!canvas", rwebinput, false,
|
||||
"#canvas", rwebinput, false,
|
||||
rwebinput_keyboard_cb);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
|
@ -426,7 +426,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
|
|||
}
|
||||
|
||||
r = emscripten_set_keyup_callback(
|
||||
"!canvas", rwebinput, false,
|
||||
"#canvas", rwebinput, false,
|
||||
rwebinput_keyboard_cb);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
|
@ -435,7 +435,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
|
|||
}
|
||||
|
||||
r = emscripten_set_keypress_callback(
|
||||
"!canvas", rwebinput, false,
|
||||
"#canvas", rwebinput, false,
|
||||
rwebinput_keyboard_cb);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
|
@ -443,7 +443,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
|
|||
"[EMSCRIPTEN/INPUT] failed to create keypress callback: %d\n", r);
|
||||
}
|
||||
|
||||
r = emscripten_set_mousedown_callback("!canvas", rwebinput, false,
|
||||
r = emscripten_set_mousedown_callback("#canvas", rwebinput, false,
|
||||
rwebinput_mouse_cb);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
|
@ -451,7 +451,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
|
|||
"[EMSCRIPTEN/INPUT] failed to create mousedown callback: %d\n", r);
|
||||
}
|
||||
|
||||
r = emscripten_set_mouseup_callback("!canvas", rwebinput, false,
|
||||
r = emscripten_set_mouseup_callback("#canvas", rwebinput, false,
|
||||
rwebinput_mouse_cb);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
|
@ -459,7 +459,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
|
|||
"[EMSCRIPTEN/INPUT] failed to create mouseup callback: %d\n", r);
|
||||
}
|
||||
|
||||
r = emscripten_set_mousemove_callback("!canvas", rwebinput, false,
|
||||
r = emscripten_set_mousemove_callback("#canvas", rwebinput, false,
|
||||
rwebinput_mouse_cb);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
|
@ -468,7 +468,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
|
|||
}
|
||||
|
||||
r = emscripten_set_wheel_callback(
|
||||
"!canvas", rwebinput, false,
|
||||
"#canvas", rwebinput, false,
|
||||
rwebinput_wheel_cb);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
|
@ -476,7 +476,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
|
|||
"[EMSCRIPTEN/INPUT] failed to create wheel callback: %d\n", r);
|
||||
}
|
||||
|
||||
r = emscripten_set_touchstart_callback("!canvas", rwebinput, false,
|
||||
r = emscripten_set_touchstart_callback("#canvas", rwebinput, false,
|
||||
rwebinput_touch_cb);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
|
@ -484,7 +484,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
|
|||
"[EMSCRIPTEN/INPUT] failed to create touchstart callback: %d\n", r);
|
||||
}
|
||||
|
||||
r = emscripten_set_touchend_callback("!canvas", rwebinput, false,
|
||||
r = emscripten_set_touchend_callback("#canvas", rwebinput, false,
|
||||
rwebinput_touch_cb);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
|
@ -492,7 +492,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
|
|||
"[EMSCRIPTEN/INPUT] failed to create touchend callback: %d\n", r);
|
||||
}
|
||||
|
||||
r = emscripten_set_touchmove_callback("!canvas", rwebinput, false,
|
||||
r = emscripten_set_touchmove_callback("#canvas", rwebinput, false,
|
||||
rwebinput_touch_cb);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
|
@ -500,7 +500,7 @@ static void *rwebinput_input_init(const char *joypad_driver)
|
|||
"[EMSCRIPTEN/INPUT] failed to create touchmove callback: %d\n", r);
|
||||
}
|
||||
|
||||
r = emscripten_set_touchcancel_callback("!canvas", rwebinput, false,
|
||||
r = emscripten_set_touchcancel_callback("#canvas", rwebinput, false,
|
||||
rwebinput_touch_cb);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
|
@ -819,7 +819,7 @@ static void rwebinput_input_poll(void *data)
|
|||
static void rwebinput_grab_mouse(void *data, bool state)
|
||||
{
|
||||
if (state)
|
||||
emscripten_request_pointerlock("!canvas", EM_TRUE);
|
||||
emscripten_request_pointerlock("#canvas", EM_TRUE);
|
||||
else
|
||||
emscripten_exit_pointerlock();
|
||||
}
|
||||
|
|
|
@ -26,6 +26,12 @@
|
|||
#include "../../verbosity.h"
|
||||
|
||||
#define CLAMPDOUBLE(x) MIN(1.0, MAX(-1.0, (x)))
|
||||
#define NUM_BUTTONS 64
|
||||
#define NUM_AXES 64
|
||||
|
||||
/* TODO/FIXME - static globals */
|
||||
static struct EmscriptenGamepadEvent _pads[DEFAULT_MAX_PADS];
|
||||
static bool _live_pads[DEFAULT_MAX_PADS] = {0};
|
||||
|
||||
static EM_BOOL rwebpad_gamepad_cb(int event_type,
|
||||
const EmscriptenGamepadEvent *gamepad_event, void *user_data)
|
||||
|
@ -36,6 +42,8 @@ static EM_BOOL rwebpad_gamepad_cb(int event_type,
|
|||
switch (event_type)
|
||||
{
|
||||
case EMSCRIPTEN_EVENT_GAMEPADCONNECTED:
|
||||
_pads[gamepad_event->index] = *gamepad_event;
|
||||
_live_pads[gamepad_event->index] = true;
|
||||
input_autoconfigure_connect(
|
||||
gamepad_event->id, /* name */
|
||||
NULL, /* display name */
|
||||
|
@ -45,6 +53,7 @@ static EM_BOOL rwebpad_gamepad_cb(int event_type,
|
|||
pid); /* pid */
|
||||
break;
|
||||
case EMSCRIPTEN_EVENT_GAMEPADDISCONNECTED:
|
||||
_live_pads[gamepad_event->index] = false;
|
||||
input_autoconfigure_disconnect(gamepad_event->index,
|
||||
rwebpad_joypad.ident);
|
||||
break;
|
||||
|
@ -68,28 +77,23 @@ static void *rwebpad_joypad_init(void *data)
|
|||
r = emscripten_set_gamepaddisconnected_callback(NULL, false,
|
||||
rwebpad_gamepad_cb);
|
||||
|
||||
return (void*)-1;
|
||||
return (void*)(-1);
|
||||
}
|
||||
|
||||
static const char *rwebpad_joypad_name(unsigned pad)
|
||||
{
|
||||
static EmscriptenGamepadEvent gamepad_state;
|
||||
EMSCRIPTEN_RESULT r = emscripten_get_gamepad_status(pad, &gamepad_state);
|
||||
if (r == EMSCRIPTEN_RESULT_SUCCESS)
|
||||
return gamepad_state.id;
|
||||
return "";
|
||||
if (pad >= DEFAULT_MAX_PADS || !_live_pads[pad]) {
|
||||
return "";
|
||||
}
|
||||
return _pads[pad].id;
|
||||
}
|
||||
|
||||
static int32_t rwebpad_joypad_button(unsigned port, uint16_t joykey)
|
||||
{
|
||||
EmscriptenGamepadEvent gamepad_state;
|
||||
EMSCRIPTEN_RESULT r = emscripten_get_gamepad_status(
|
||||
port, &gamepad_state);
|
||||
|
||||
if (port >= DEFAULT_MAX_PADS)
|
||||
return 0;
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
if (port >= DEFAULT_MAX_PADS || !_live_pads[port])
|
||||
return 0;
|
||||
gamepad_state = _pads[port];
|
||||
if (joykey < gamepad_state.numButtons)
|
||||
return gamepad_state.digitalButton[joykey];
|
||||
return 0;
|
||||
|
@ -98,21 +102,17 @@ static int32_t rwebpad_joypad_button(unsigned port, uint16_t joykey)
|
|||
static void rwebpad_joypad_get_buttons(unsigned port_num, input_bits_t *state)
|
||||
{
|
||||
EmscriptenGamepadEvent gamepad_state;
|
||||
EMSCRIPTEN_RESULT r = emscripten_get_gamepad_status(
|
||||
port_num, &gamepad_state);
|
||||
|
||||
if (r == EMSCRIPTEN_RESULT_SUCCESS)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < gamepad_state.numButtons; i++)
|
||||
{
|
||||
if (gamepad_state.digitalButton[i])
|
||||
BIT256_SET_PTR(state, i);
|
||||
}
|
||||
}
|
||||
else
|
||||
unsigned i;
|
||||
if (port_num >= DEFAULT_MAX_PADS || !_live_pads[port_num]) {
|
||||
BIT256_CLEAR_ALL_PTR(state);
|
||||
return;
|
||||
}
|
||||
gamepad_state = _pads[port_num];
|
||||
for (i = 0; i < gamepad_state.numButtons; i++)
|
||||
{
|
||||
if (gamepad_state.digitalButton[i])
|
||||
BIT256_SET_PTR(state, i);
|
||||
}
|
||||
}
|
||||
|
||||
static int16_t rwebpad_joypad_axis_state(
|
||||
|
@ -138,11 +138,10 @@ static int16_t rwebpad_joypad_axis_state(
|
|||
|
||||
static int16_t rwebpad_joypad_axis(unsigned port, uint32_t joyaxis)
|
||||
{
|
||||
EmscriptenGamepadEvent gamepad_state;
|
||||
EMSCRIPTEN_RESULT r = emscripten_get_gamepad_status(port, &gamepad_state);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
if (port >= DEFAULT_MAX_PADS || !_live_pads[port]) {
|
||||
return 0;
|
||||
return rwebpad_joypad_axis_state(&gamepad_state, port, joyaxis);
|
||||
}
|
||||
return rwebpad_joypad_axis_state(&_pads[port], port, joyaxis);
|
||||
}
|
||||
|
||||
static int16_t rwebpad_joypad_state(
|
||||
|
@ -154,13 +153,9 @@ static int16_t rwebpad_joypad_state(
|
|||
EmscriptenGamepadEvent gamepad_state;
|
||||
int16_t ret = 0;
|
||||
uint16_t port_idx = joypad_info->joy_idx;
|
||||
EMSCRIPTEN_RESULT r = emscripten_get_gamepad_status(
|
||||
port_idx, &gamepad_state);
|
||||
if (r != EMSCRIPTEN_RESULT_SUCCESS)
|
||||
if (port_idx >= DEFAULT_MAX_PADS || !_live_pads[port])
|
||||
return 0;
|
||||
if (port_idx >= DEFAULT_MAX_PADS)
|
||||
return 0;
|
||||
|
||||
gamepad_state = _pads[port];
|
||||
for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++)
|
||||
{
|
||||
/* Auto-binds are per joypad, not per user. */
|
||||
|
@ -187,16 +182,16 @@ static int16_t rwebpad_joypad_state(
|
|||
static void rwebpad_joypad_poll(void)
|
||||
{
|
||||
emscripten_sample_gamepad_data();
|
||||
for (int i = 0; i < DEFAULT_MAX_PADS; i++) {
|
||||
if (_live_pads[i]) {
|
||||
emscripten_get_gamepad_status(i, &_pads[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool rwebpad_joypad_query_pad(unsigned pad)
|
||||
{
|
||||
EmscriptenGamepadEvent gamepad_state;
|
||||
EMSCRIPTEN_RESULT r = emscripten_get_gamepad_status(pad, &gamepad_state);
|
||||
|
||||
if (r == EMSCRIPTEN_RESULT_SUCCESS)
|
||||
return gamepad_state.connected == EM_TRUE;
|
||||
return false;
|
||||
return _live_pads[pad];
|
||||
}
|
||||
|
||||
static void rwebpad_joypad_destroy(void) { }
|
||||
|
|
|
@ -1076,16 +1076,16 @@ static int16_t input_joypad_analog_axis(
|
|||
? joypad_info->auto_binds[ident_y_plus].joyaxis
|
||||
: bind_y_plus->joyaxis;
|
||||
/* normalized magnitude for radial scaled analog deadzone */
|
||||
if (x_axis_plus != AXIS_NONE)
|
||||
if (x_axis_plus != AXIS_NONE && drv->axis)
|
||||
x = drv->axis(
|
||||
joypad_info->joy_idx, x_axis_plus);
|
||||
if (x_axis_minus != AXIS_NONE)
|
||||
if (x_axis_minus != AXIS_NONE && drv->axis)
|
||||
x += drv->axis(joypad_info->joy_idx,
|
||||
x_axis_minus);
|
||||
if (y_axis_plus != AXIS_NONE)
|
||||
if (y_axis_plus != AXIS_NONE && drv->axis)
|
||||
y = drv->axis(
|
||||
joypad_info->joy_idx, y_axis_plus);
|
||||
if (y_axis_minus != AXIS_NONE)
|
||||
if (y_axis_minus != AXIS_NONE && drv->axis)
|
||||
y += drv->axis(
|
||||
joypad_info->joy_idx, y_axis_minus);
|
||||
normal_mag = (1.0f / 0x7fff) * sqrt(x * x + y * y);
|
||||
|
@ -1113,9 +1113,9 @@ static int16_t input_joypad_analog_axis(
|
|||
uint16_t key_plus = (bind_plus->joykey == NO_BTN)
|
||||
? joypad_info->auto_binds[ident_plus].joykey
|
||||
: bind_plus->joykey;
|
||||
if (drv->button(joypad_info->joy_idx, key_plus))
|
||||
if (drv->button && drv->button(joypad_info->joy_idx, key_plus))
|
||||
res = 0x7fff;
|
||||
if (drv->button(joypad_info->joy_idx, key_minus))
|
||||
if (drv->button && drv->button(joypad_info->joy_idx, key_minus))
|
||||
res += -0x7fff;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
#include <psp2/kernel/threadmgr.h>
|
||||
#elif defined(_3DS)
|
||||
#include <3ds.h>
|
||||
#elif defined(EMSCRIPTEN)
|
||||
#elif (defined(EMSCRIPTEN) && defined(EMSCRIPTEN_ASYNCIFY))
|
||||
#include <emscripten/emscripten.h>
|
||||
#else
|
||||
#include <time.h>
|
||||
|
@ -100,7 +100,7 @@ static int nanosleepDOS(const struct timespec *rqtp, struct timespec *rmtp)
|
|||
#define retro_sleep(msec) (usleep(1000 * (msec)))
|
||||
#elif defined(WIIU)
|
||||
#define retro_sleep(msec) (OSSleepTicks(ms_to_ticks((msec))))
|
||||
#elif defined(EMSCRIPTEN)
|
||||
#elif defined(EMSCRIPTEN) && defined(EMSCRIPTEN_ASYNCIFY)
|
||||
#define retro_sleep(msec) (emscripten_sleep(msec))
|
||||
#else
|
||||
static INLINE void retro_sleep(unsigned msec)
|
||||
|
|
|
@ -35,7 +35,9 @@
|
|||
#ifdef HAVE_THREADS
|
||||
#include <rthreads/rthreads.h>
|
||||
#endif
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
#include <retro_timers.h>
|
||||
#endif
|
||||
#ifdef HAVE_GCD
|
||||
#include <dispatch/dispatch.h>
|
||||
#endif
|
||||
|
@ -522,8 +524,14 @@ static void threaded_worker(void *userdata)
|
|||
}
|
||||
|
||||
slock_unlock(running_lock);
|
||||
|
||||
task->handler(task);
|
||||
#ifdef EMSCRIPTEN
|
||||
/* Workaround emscripten pthread bug where not parking the
|
||||
thread will prevent other important stuff from
|
||||
happening. Maybe due to lack of signals implementation in
|
||||
emscripten's pthreads? */
|
||||
retro_sleep(1);
|
||||
#endif
|
||||
|
||||
slock_lock(property_lock);
|
||||
finished = ((task->flags & RETRO_TASK_FLG_FINISHED) > 0) ? true : false;
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#endif
|
||||
|
||||
#include <retro_timers.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <math/float_minmax.h>
|
||||
#include <string/stdstring.h>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
The RetroArch Web Player is RetroArch compiled through [Emscripten](https://emscripten.org/). The following outlines how to compile RetroArch using Emscripten, and running it in your browser.
|
||||
|
||||
## Compiling
|
||||
## Compiling the Single-Threaded Player
|
||||
|
||||
To compile RetroArch with Emscripten, you'll first have to [download and install the Emscripten SDK](https://emscripten.org/docs/getting_started/downloads.html) at 3.1.46:
|
||||
|
||||
|
@ -35,7 +35,7 @@ cp fceumm_libretro.{js,wasm} pkg/emscripten/libretro
|
|||
|
||||
The emscripten build in the retroarch tree does not contain the necessary web assets for a complete RetroArch installation. You'll need the asset package from the latest emscripten nightly build ( https://buildbot.libretro.com/nightly/emscripten/ ); take its `assets/` folder and put it into `pkg/emscripten/libretro`. This `assets/` folder should contain a `frontend/` directory and a `cores/` directory.
|
||||
|
||||
If you're building your own frontend asset bundle (i.e. modifying `frontend/bundle/`), you'll need to turn the bundle into zipped partfiles. Open a terminal in `assets/frontend` and `zip -r9 bundle.zip bundle && split -b 30M bundle.zip bundle.zip.` (this should work on Mac and Linux, please file a PR with instructions for Windows).
|
||||
If you're building your own frontend asset bundle (i.e. modifying `frontend/bundle/`), you'll need to turn the bundle into zipped partfiles. Open a terminal in `assets/frontend/` and `zip -r9 bundle.zip bundle && cd .. && split -b 30M bundle.zip bundle.zip.` (this should work on Mac and Linux, please file a PR with instructions for Windows).
|
||||
|
||||
If you want to add more built-in core content files to `assets/cores`, you need to re-run the indexer script:
|
||||
|
||||
|
@ -83,11 +83,11 @@ emmake make -f Makefile platform=emscripten
|
|||
cp melonds_libretro_emscripten.bc ~/retroarch/RetroArch/libretro_emscripten.bc
|
||||
```
|
||||
|
||||
Now build the frontend with the pthreads env variable: (2 is the number of workers this can be any integer)
|
||||
Now build the frontend with the pthreads env variable: (2 is the number of workers; this can be any integer, but many browsers limit the number of workers)
|
||||
|
||||
```
|
||||
cd ~/retroarch/RetroArch
|
||||
pthread=2 emmake make -f Makefile.emscripten LIBRETRO=melonds && cp melonds_libretro.* pkg/emscripten/libretro
|
||||
emmake make -f Makefile.emscripten LIBRETRO=melonds PTHREAD=2 && cp melonds_libretro.* pkg/emscripten/libretro
|
||||
```
|
||||
|
||||
Your resulting output will be located in:
|
||||
|
@ -95,12 +95,11 @@ Your resulting output will be located in:
|
|||
```
|
||||
~/retroarch/RetroArch/pkg/emscripten/libretro/melonds_libretro.js
|
||||
~/retroarch/RetroArch/pkg/emscripten/libretro/melonds_libretro.wasm
|
||||
~/retroarch/RetroArch/pkg/emscripten/libretro/melonds_libretro.worker.js
|
||||
```
|
||||
|
||||
## Setting up your webserver (Threaded)
|
||||
|
||||
Unless loading from `localhost` you will need to server the content from an HTTPS endpoint with a valid SSL certificate. This is a security limitation imposed by the browser. Along with that you will need to set content control policies with special headers in your server:
|
||||
To support multithreaded builds, you will need to serve the content from an HTTPS endpoint with a valid SSL certificate. This is a security limitation imposed by the browser. Along with that you will need to set content control policies with special headers in your server:
|
||||
|
||||
In Nodejs with express:
|
||||
|
||||
|
@ -108,6 +107,7 @@ In Nodejs with express:
|
|||
app.use(function(req, res, next) {
|
||||
res.header("Cross-Origin-Embedder-Policy", "require-corp");
|
||||
res.header("Cross-Origin-Opener-Policy", "same-origin");
|
||||
res.header("Cross-Origin-Resource-Policy", "same-origin");
|
||||
next();
|
||||
});
|
||||
```
|
||||
|
@ -117,4 +117,50 @@ In NGINX: (site config under `server {`)
|
|||
```
|
||||
add_header Cross-Origin-Opener-Policy same-origin;
|
||||
add_header Cross-Origin-Embedder-Policy require-corp;
|
||||
add_header Cross-Origin-Resource-Policy same-origin;
|
||||
```
|
||||
|
||||
Node http-server:
|
||||
|
||||
```
|
||||
http-server . -S \
|
||||
--header "Cross-Origin-Opener-Policy: same-origin" \
|
||||
--header "Cross-Origin-Embedder-Policy: require-corp" \
|
||||
--header "Cross-Origin-Resource-Policy: same-origin"
|
||||
```
|
||||
|
||||
# Compiling the Multi-Threaded Frontend
|
||||
|
||||
To compile the multi-threaded RetroArch frontend with Emscripten and make use of wasmfs and other features, you'll first have to [download and install the Emscripten SDK](https://emscripten.org/docs/getting_started/downloads.html). Currently, we need the "top of tree" or latest version of Emscripten:
|
||||
|
||||
```
|
||||
git clone https://github.com/emscripten-core/emsdk.git
|
||||
cd emsdk
|
||||
./emsdk install tot
|
||||
./emsdk activate tot
|
||||
source emsdk_env.sh
|
||||
```
|
||||
|
||||
After emsdk is installed you will need to build an emulator core, move that output into Retroarch, and use helper scripts to produce web ready assets, in this example we will be building [https://github.com/libretro/libretro-fceumm](https://github.com/libretro/libretro-fceumm):
|
||||
|
||||
```
|
||||
mkdir ~/retroarch
|
||||
cd ~/retroarch
|
||||
git clone https://github.com/libretro/libretro-fceumm.git
|
||||
cd libretro-fceumm
|
||||
emmake make -f Makefile.libretro platform=emscripten
|
||||
git clone https://github.com/libretro/RetroArch.git ~/retroarch/RetroArch
|
||||
cp ~/retroarch/libretro-fceumm/fceumm_libretro_emscripten.bc ~/retroarch/RetroArch/libretro_emscripten.bc
|
||||
|
||||
cd ~/retroarch
|
||||
emmake make -f Makefile.emscripten LIBRETRO=fceumm PROXY_TO_PTHREAD=1 PTHREAD=4 HAVE_WASMFS=1 ASYNC=0 HAVE_EGL=0 -j all
|
||||
cp fceumm_libretro.{js,wasm} pkg/emscripten/libretro-thread
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
The emscripten build in the retroarch tree does not contain the necessary web assets for a complete RetroArch installation. While it supports the regular desktop asset and content downloaders, we also provide a small bundle of UI assets for first launch. You can obtain these files from the nightly Emscripten build on the buildbot, or make them yourself by `zip -r bundle-minimal.zip bundle-minimal` (essentially, just the `assets/ozone`, `assets/pkg`, and `assets/sounds` folders from the regular asset package).
|
||||
|
||||
## Usage
|
||||
|
||||
Hosting the threaded web build is the same as for the multi-threaded emulators above; SSL and proper CORS headers are required.
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>RetroArch Web Player</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.3/css/bootstrap.min.css" rel="stylesheet" type="text/css">
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.0/css/font-awesome.min.css">
|
||||
<!-- Material Design Bootstrap -->
|
||||
<link href="//cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.1.1/css/mdb.min.css" rel="stylesheet">
|
||||
<link href="libretro.css" rel="stylesheet" type="text/css">
|
||||
<link rel="shortcut icon" href="media/retroarch.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<!--Navbar-->
|
||||
<nav class="navbar navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<!--navbar content-->
|
||||
<div class="navbar-toggleable-xs">
|
||||
<!--Links-->
|
||||
<ul class="nav navbar-nav">
|
||||
<div class="dropdown">
|
||||
<li class="nav-item dropdown">
|
||||
<button class="btn btn-primary dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Core Selection</button>
|
||||
<div class="dropdown-menu dropdown-primary" aria-labelledby="dropdownMenu1" data-dropdown-in="fadeIn" data-dropdown-out="fadeOut" id="core-selector">
|
||||
<a class="dropdown-item" href="." data-core="2048">2048</a>
|
||||
<a class="dropdown-item" href="." data-core="anarch">Anarch</a>
|
||||
<a class="dropdown-item" href="." data-core="ardens">Arduboy (Ardens)</a>
|
||||
<a class="dropdown-item" href="." data-core="arduous">Arduboy (Arduous)</a>
|
||||
<a class="dropdown-item" href="." data-core="bk">Elektronika - BK-0010/BK-0011 (BK)</a>
|
||||
<a class="dropdown-item" href="." data-core="chailove">ChaiLove</a>
|
||||
<a class="dropdown-item" href="." data-core="craft">Minecraft (Craft)</a>
|
||||
<a class="dropdown-item" href="." data-core="DoubleCherryGB">Nintendo - Game Boy / Color (DoubleCherryGB)</a>
|
||||
<a class="dropdown-item" href="." data-core="ecwolf">Wolfenstein 3D (ECWolf)</a>
|
||||
<a class="dropdown-item" href="." data-core="fbalpha2012">Arcade (FB Alpha 2012)</a>
|
||||
<a class="dropdown-item" href="." data-core="fbalpha2012_cps1">Arcade (FB Alpha 2012 CPS1)</a>
|
||||
<a class="dropdown-item" href="." data-core="fbalpha2012_cps2">Arcade (FB Alpha 2012 CPS2)</a>
|
||||
<a class="dropdown-item" href="." data-core="fbalpha2012_neogeo">Arcade (FB Alpha 2012 NeoGeo)</a>
|
||||
<a class="dropdown-item" href="." data-core="fceumm">Nintendo - NES / Famicom (FCEUmm)</a>
|
||||
<a class="dropdown-item" href="." data-core="freechaf">Fairchild ChannelF (FreeChaF)</a>
|
||||
<a class="dropdown-item" href="." data-core="galaksija">Galaksija</a>
|
||||
<a class="dropdown-item" href="." data-core="gambatte">Nintendo - Game Boy / Color (Gambatte)</a>
|
||||
<a class="dropdown-item" href="." data-core="gme">Game Music Emu</a>
|
||||
<a class="dropdown-item" href="." data-core="gearboy">Nintendo - Game Boy / Color (GearBoy)</a>
|
||||
<a class="dropdown-item" href="." data-core="gearcoleco">Coleco - ColecoVision (GearColeco)</a>
|
||||
<a class="dropdown-item" href="." data-core="gearsystem">Sega - MS/GG/SG-1000 (GearSystem)</a>
|
||||
<a class="dropdown-item" href="." data-core="genesis_plus_gx">Sega - MS/GG/MD/CD (Genesis Plus GX)</a>
|
||||
<a class="dropdown-item" href="." data-core="genesis_plus_gx_wide">Sega - MS/GG/MD/CD (Genesis Plus GX Wide)</a>
|
||||
<a class="dropdown-item" href="." data-core="gong">Gong</a>
|
||||
<a class="dropdown-item" href="." data-core="gw">Handheld Electronic (GW)</a>
|
||||
<a class="dropdown-item" href="." data-core="handy">Atari - Lynx (Handy)</a>
|
||||
<a class="dropdown-item" href="." data-core="jaxe">CHIP-8/S-CHIP/XO-CHIP (JAXE)</a>
|
||||
<a class="dropdown-item" href="." data-core="jumpnbump">Jump 'n Bump</a>
|
||||
<a class="dropdown-item" href="." data-core="lowresnx">LowResNX</a>
|
||||
<a class="dropdown-item" href="." data-core="lutro">Lua Engine (Lutro)</a>
|
||||
<a class="dropdown-item" href="." data-core="m2000">Philips - P2000T (M2000)</a>
|
||||
<a class="dropdown-item" href="." data-core="mame2000">Arcade - MAME 2000</a>
|
||||
<a class="dropdown-item" href="." data-core="mame2003">Arcade - MAME 2003</a>
|
||||
<a class="dropdown-item" href="." data-core="mame2003_plus">Arcade - MAME 2003-Plus</a>
|
||||
<a class="dropdown-item" href="." data-core="mednafen_lynx">Atari - Lynx (Beetle Lynx)</a>
|
||||
<a class="dropdown-item" href="." data-core="mednafen_ngp">SNK - Neo Geo Pocket / Color (Beetle Neo Geo Pop)</a>
|
||||
<a class="dropdown-item" href="." data-core="mednafen_pce_fast">NEC - PC Engine / CD (Beetle PC Engine Fast)</a>
|
||||
<a class="dropdown-item" href="." data-core="mednafen_vb">Nintendo - Virtual Boy (Beetle VB)</a>
|
||||
<a class="dropdown-item" href="." data-core="mednafen_wswan">Bandai - WonderSwan/Color (Beetle WonderSwan)</a>
|
||||
<a class="dropdown-item" href="." data-core="mgba">Nintendo - Game Boy Advance (mGBA)</a>
|
||||
<a class="dropdown-item" href="." data-core="minivmac">Mac II (MiniVmac)</a>
|
||||
<a class="dropdown-item" href="." data-core="mu">Palm OS(Mu)</a>
|
||||
<a class="dropdown-item" href="." data-core="mrboom">Bomberman (Mr.Boom)</a>
|
||||
<a class="dropdown-item" href="." data-core="neocd">SNK - Neo Geo CD (NeoCD)</a>
|
||||
<a class="dropdown-item" href="." data-core="nestopia">Nintendo - NES / Famicom (Nestopia)</a>
|
||||
<a class="dropdown-item" href="." data-core="numero">Texas Instruments TI-83 (Numero)</a>
|
||||
<a class="dropdown-item" href="." data-core="nxengine">Cave Story (NX Engine)</a>
|
||||
<a class="dropdown-item" href="." data-core="o2em">Magnavox - Odyssey2 / Philips Videopac+ (O2EM)</a>
|
||||
<a class="dropdown-item" href="." data-core="opera">The 3DO Company - 3DO (Opera)</a>
|
||||
<a class="dropdown-item" href="." data-core="pcsx_rearmed">Sony - PlayStation (PCSX ReARMed)</a>
|
||||
<a class="dropdown-item" href="." data-core="picodrive">Sega - MS/GG/MD/CD/32X (PicoDrive)</a>
|
||||
<a class="dropdown-item" href="." data-core="pocketcdg">PocketCDG</a>
|
||||
<a class="dropdown-item" href="." data-core="prboom">Doom (PrBoom)</a>
|
||||
<a class="dropdown-item" href="." data-core="quasi88">NEC - PC-8000 / PC-8800 series (QUASI88)</a>
|
||||
<a class="dropdown-item" href="." data-core="quicknes">Nintendo - NES / Famicom (QuickNES)</a>
|
||||
<a class="dropdown-item" href="." data-core="retro8">PICO-8 (Retro8)</a>
|
||||
<a class="dropdown-item" href="." data-core="scummvm">ScummVM</a>
|
||||
<a class="dropdown-item" href="." data-core="snes9x2002">Nintendo - SNES / SFC (Snes9x 2002)</a>
|
||||
<a class="dropdown-item" href="." data-core="snes9x2005">Nintendo - SNES / SFC (Snes9x 2005)</a>
|
||||
<a class="dropdown-item" href="." data-core="snes9x2010">Nintendo - SNES / SFC (Snes9x 2010)</a>
|
||||
<a class="dropdown-item" href="." data-core="snes9x">Nintendo - SNES / SFC (Snes9x)</a>
|
||||
<a class="dropdown-item" href="." data-core="squirreljme">Java ME (SquirrelJME)</a>
|
||||
<a class="dropdown-item" href="." data-core="tamalibretro">Bandai - Tamagothci P1 (TamaLIBretro)</a>
|
||||
<a class="dropdown-item" href="." data-core="tgbdual">Nintendo - Game Boy / Color (TGB Dual)</a>
|
||||
<a class="dropdown-item" href="." data-core="theodore">Theodore (Thomson TO8/TO9)</a>
|
||||
<a class="dropdown-item" href="." data-core="tic80">TIC-80</a>
|
||||
<a class="dropdown-item" href="." data-core="tyrquake">Quake (TyrQuake)</a>
|
||||
<a class="dropdown-item" href="." data-core="uw8">MicroW8 (UW8)</a>
|
||||
<a class="dropdown-item" href="." data-core="uzem">Uzebox (Uzem)</a>
|
||||
<a class="dropdown-item" href="." data-core="vaporspec">Vaporspec</a>
|
||||
<a class="dropdown-item" href="." data-core="vba_next">Nintendo - Game Boy Advance (VBA Next)</a>
|
||||
<a class="dropdown-item" href="." data-core="vecx">GCE - Vectrex (Vecx)</a>
|
||||
<a class="dropdown-item" href="." data-core="vice_x64">Commodore - C64 (VICE x64, fast)</a>
|
||||
<a class="dropdown-item" href="." data-core="vice_x64sc">Commodore - C64 (VICE x64sc, accurate)</a>
|
||||
<a class="dropdown-item" href="." data-core="vice_x128">Commodore - C128 (VICE x128)</a>
|
||||
<a class="dropdown-item" href="." data-core="vice_xcbm2">Commodore - CBM-II 6x0/7x0 (VICE xcbm2)</a>
|
||||
<a class="dropdown-item" href="." data-core="vice_xcbm5x0">Commodore - CBM-II 5x0 (xcbm5x0)</a>
|
||||
<a class="dropdown-item" href="." data-core="vice_xpet">Commodore - PET (VICE xpet)</a>
|
||||
<a class="dropdown-item" href="." data-core="vice_xplus4">Commodore - PLUS/4 (VICE xplus4)</a>
|
||||
<a class="dropdown-item" href="." data-core="vice_xscpu64">Commodore - C64 SuperCPU (VICE xscpu4)</a>
|
||||
<a class="dropdown-item" href="." data-core="vice_xvic">Commodore - VIC-20 (VICE xvic)</a>
|
||||
<a class="dropdown-item" href="." data-core="virtualxt">VirtualXT</a>
|
||||
<a class="dropdown-item" href="." data-core="vitaquake2">Quake II (vitaQuake 2)</a>
|
||||
<a class="dropdown-item" href="." data-core="vitaquake2-rogue">Quake II - Ground Zero (vitaQuake2 (rogue))</a>
|
||||
<a class="dropdown-item" href="." data-core="vitaquake2-xatrix">Quake II - The Reckoning (vitaQuake2 (xatrix))</a>
|
||||
<a class="dropdown-item" href="." data-core="vitaquake2-zaero">Quake II - Zaero (vitaQuake2 (zaero))</a>
|
||||
<a class="dropdown-item" href="." data-core="wasm4">WASM4</a>
|
||||
<a class="dropdown-item" href="." data-core="x1">Sharp X1 (X Millenium)</a>
|
||||
<a class="dropdown-item" href="." data-core="xrick">Rick Dangerous (XRick)</a>
|
||||
</div>
|
||||
<button class="btn btn-primary disabled" id="btnRun" disabled>
|
||||
<span class="fa fa-spinner fa-spin" id="icnRun"></span> Run
|
||||
</button>
|
||||
<button class="btn btn-primary disabled" id="btnAdd" disabled>
|
||||
<span class="fa fa-plus" id="icnAdd"></span> Add Content
|
||||
</button>
|
||||
<input style="display: none" type="file" id="btnRom" name="upload" multiple />
|
||||
<button class="btn btn-primary tooltip-enable" id="btnClean" title="Cleanup storage">
|
||||
<span class="fa fa-trash-o" id="icnClean"></span> <span class="sr-only">Cleanup</span>
|
||||
</button>
|
||||
<button class="btn btn-primary disabled tooltip-enable" id="btnMenu" title="Menu toggle" disabled>
|
||||
<span class="fa fa-bars" id="icnMenu"></span> <span class="sr-only">Menu</span>
|
||||
</button>
|
||||
<button class="btn btn-primary disabled tooltip-enable" id="btnFullscreen" title="Fullscreen" disabled>
|
||||
<span class="fa fa-desktop" id="icnFullscreen"></span> <span class="sr-only">Fullscreen</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary tooltip-enable" data-toggle="modal" data-target="#helpModal">Help</button>
|
||||
</li>
|
||||
</div>
|
||||
</ul>
|
||||
<div class="toggleMenu">
|
||||
<button class="btn btn-primary" id="btnHideMenu" title="Toggle Menu">
|
||||
<span class="fa fa-chevron-up" id="icnHideMenu"></span> <span class="sr-only">Hide Top Navigation</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Basics steps modal for Web Libretro -->
|
||||
<div class="modal fade" id="helpModal" role="dialog" style="color:black;">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h1 class="modal-title">Basics</h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h3><b>Load Core</b></h3>
|
||||
<p>Load your core by clicking on the first tab. Scroll down until you reach the desired Core. We will use Nestopia for now. Don't forget - Content must be compatible with the matched Core.</p>
|
||||
<ul>
|
||||
<li>Nes: <i>NESTOPIA</i></li>
|
||||
<li>Game Boy / Color: <i>Gambatte</i></li>
|
||||
</ul>
|
||||
<p>etc.</p>
|
||||
<p></p>
|
||||
<h3><b>Load Content</b></h3>
|
||||
<p>After selecting Core, click Run. After RetroArch opens, click Add Content and select your compatible ROM.</p>
|
||||
<ul>
|
||||
<li>Nestopia > <i>YourGame.nes</i></li>
|
||||
<li>Gambatte > <i>YourGame.gbc</i></li>
|
||||
</ul>
|
||||
<p>etc.</p>
|
||||
<p></p>
|
||||
<h3><b><span class="fa fa-trash-o"></span> Cleanup Storage</b></h3>
|
||||
<p>The trashcan erases your existing configuration and presets. If the Web Player doesn't start, you should click the trashcan and refresh the cache in your browser (usually F5 or Shift+F5).</p>
|
||||
<p></p>
|
||||
<h3><b><span class="fa fa-bars"></span> Quick Menu</b></h3>
|
||||
<p>If you click on the three line icons, the Quick Menu will open here as in RetroArch.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--/.navbar content-->
|
||||
</nav>
|
||||
<div class="bg-inverse webplayer-container">
|
||||
<div class="webplayer_border text-xs-center" id="canvas_div">
|
||||
<div class="showMenu">
|
||||
<button type="button" class="btn btn-link">
|
||||
<span class="fa fa-chevron-down" id="icnShowMenu"></span> <span class="sr-only">Show Top Navigation</span>
|
||||
</button>
|
||||
</div>
|
||||
<canvas class="webplayer" id="canvas" tabindex="1" oncontextmenu="event.preventDefault()" style="display: none"></canvas>
|
||||
<img class="webplayer-preview img-fluid" src="media/canvas.png" width="960" height="720" alt="RetroArch Logo">
|
||||
</div>
|
||||
</div>
|
||||
<script crossorigin="anonymous" src="//code.jquery.com/jquery-3.1.0.min.js"></script>
|
||||
<script crossorigin="anonymous" src="//rawgit.com/jeresig/jquery.hotkeys/master/jquery.hotkeys.js"></script>
|
||||
<script crossorigin="anonymous" src="//cdnjs.cloudflare.com/ajax/libs/tether/1.3.4/js/tether.min.js"></script>
|
||||
<script crossorigin="anonymous" src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.3/js/bootstrap.min.js"></script>
|
||||
<script src="analytics.js"></script>
|
||||
<script src="zip-no-worker.min.js"></script>
|
||||
<script src="libretro.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* RetroArch Web Player
|
||||
*
|
||||
* This provides the basic styling for the RetroArch web player.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Make sure the background of the player is black.
|
||||
* Also make sure line height is 0 so there's no extra space on the bottom.
|
||||
*/
|
||||
.webplayer-container {
|
||||
background-color: black;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Webplayer Preview when not loaded.
|
||||
*/
|
||||
.webplayer-preview {
|
||||
margin: 0 auto;
|
||||
cursor: wait;
|
||||
opacity: 0.2;
|
||||
transition: all 0.8s;
|
||||
-webkit-animation: loading 0.8s ease-in-out infinite alternate;
|
||||
-moz-animation: loading 0.8s ease-in-out infinite alternate;
|
||||
animation: loading 0.8s ease-in-out infinite alternate;
|
||||
}
|
||||
.webplayer-preview.loaded {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
-webkit-animation: loaded 0.8s ease-in-out;
|
||||
-moz-animation: loaded 0.8s ease-in-out;
|
||||
animation: loaded 0.8s ease-in-out;
|
||||
}
|
||||
@keyframes loaded {
|
||||
from {
|
||||
opacity: 0.2;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@-moz-keyframes loaded {
|
||||
from {
|
||||
opacity: 0.2;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes loaded {
|
||||
from {
|
||||
opacity: 0.2;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes loading{
|
||||
from {
|
||||
opacity: 0.2;
|
||||
}
|
||||
to {
|
||||
opacity: 0.35;
|
||||
}
|
||||
}
|
||||
@-moz-keyframes loading{
|
||||
from {
|
||||
opacity: 0.2;
|
||||
}
|
||||
to {
|
||||
opacity: 0.35;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes loading {
|
||||
from {
|
||||
opacity: 0.2;
|
||||
}
|
||||
to {
|
||||
opacity: 0.35;
|
||||
}
|
||||
}
|
||||
|
||||
canvas.webplayer {
|
||||
border: none;
|
||||
outline: none;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
#canvas_div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hack to make emscripten stop messing with the canvas size while in fullscreen.
|
||||
* Foiled again!
|
||||
*/
|
||||
:fullscreen canvas.webplayer {
|
||||
min-width: 100vw;
|
||||
max-width: 100vw;
|
||||
min-height: 100vh;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-family: monospace;
|
||||
font-size: 0.7em;
|
||||
height: 95%;
|
||||
width: 95%;
|
||||
border-style: none;
|
||||
border-color: transparent;
|
||||
overflow: auto;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle Top Navigation
|
||||
*/
|
||||
.toggleMenu {
|
||||
float: right;
|
||||
}
|
||||
.showMenu {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
#icnShowMenu {
|
||||
color: #565656 !important;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
box-shadow: none;
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
/**
|
||||
* RetroArch Web Player
|
||||
*
|
||||
* This provides the basic JavaScript for the RetroArch web player.
|
||||
*/
|
||||
|
||||
var filesystem_ready = false;
|
||||
var retroarch_ready = false;
|
||||
var setImmediate;
|
||||
|
||||
var Module = {
|
||||
noInitialRun: true,
|
||||
arguments: ["-v", "--menu"],
|
||||
noImageDecoding: true,
|
||||
noAudioDecoding: true,
|
||||
|
||||
encoder: new TextEncoder(),
|
||||
message_queue: [],
|
||||
message_out: [],
|
||||
message_accum: "",
|
||||
|
||||
retroArchSend: function(msg) {
|
||||
let bytes = this.encoder.encode(msg + "\n");
|
||||
this.message_queue.push([bytes, 0]);
|
||||
},
|
||||
retroArchRecv: function() {
|
||||
let out = this.message_out.shift();
|
||||
if (out == null && this.message_accum != "") {
|
||||
out = this.message_accum;
|
||||
this.message_accum = "";
|
||||
}
|
||||
return out;
|
||||
},
|
||||
preRun: [
|
||||
function(module) {
|
||||
Module.ENV['OPFS'] = "/home/web_user/retroarch";
|
||||
},
|
||||
function(module) {
|
||||
function stdin() {
|
||||
// Return ASCII code of character, or null if no input
|
||||
while (module.message_queue.length > 0) {
|
||||
var msg = module.message_queue[0][0];
|
||||
var index = module.message_queue[0][1];
|
||||
if (index >= msg.length) {
|
||||
module.message_queue.shift();
|
||||
} else {
|
||||
module.message_queue[0][1] = index + 1;
|
||||
// assumption: msg is a uint8array
|
||||
return msg[index];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function stdout(c) {
|
||||
if (c == null) {
|
||||
// flush
|
||||
if (module.message_accum != "") {
|
||||
module.message_out.push(module.message_accum);
|
||||
module.message_accum = "";
|
||||
}
|
||||
} else {
|
||||
let s = String.fromCharCode(c);
|
||||
if (s == "\n") {
|
||||
if (module.message_accum != "") {
|
||||
module.message_out.push(module.message_accum);
|
||||
module.message_accum = "";
|
||||
}
|
||||
} else {
|
||||
module.message_accum = module.message_accum + s;
|
||||
}
|
||||
}
|
||||
}
|
||||
module.FS.init(stdin, stdout);
|
||||
}
|
||||
],
|
||||
postRun: [],
|
||||
onRuntimeInitialized: function() {
|
||||
retroarch_ready = true;
|
||||
appInitialized();
|
||||
},
|
||||
print: function(text) {
|
||||
console.log("stdout:", text);
|
||||
},
|
||||
printErr: function(text) {
|
||||
console.log("stderr:", text);
|
||||
},
|
||||
canvas: document.getElementById("canvas"),
|
||||
totalDependencies: 0,
|
||||
monitorRunDependencies: function(left) {
|
||||
this.totalDependencies = Math.max(this.totalDependencies, left);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
async function cleanupStorage()
|
||||
{
|
||||
localStorage.clear();
|
||||
const root = await navigator.storage.getDirectory();
|
||||
for await (const handle of root.values()) {
|
||||
await root.removeEntry(handle.name, {recursive: true});
|
||||
}
|
||||
document.getElementById("btnClean").disabled = true;
|
||||
}
|
||||
|
||||
function appInitialized()
|
||||
{
|
||||
/* Need to wait for the wasm runtime to load before enabling the Run button. */
|
||||
if (retroarch_ready && filesystem_ready)
|
||||
{
|
||||
preLoadingComplete();
|
||||
}
|
||||
}
|
||||
|
||||
function preLoadingComplete() {
|
||||
$('#icnRun').removeClass('fa-spinner').removeClass('fa-spin');
|
||||
$('#icnRun').addClass('fa-play');
|
||||
// Make the Preview image clickable to start RetroArch.
|
||||
$('.webplayer-preview').addClass('loaded').click(function() {
|
||||
startRetroArch();
|
||||
return false;
|
||||
});
|
||||
$('#btnRun').removeClass('disabled').removeAttr("disabled").click(function() {
|
||||
startRetroArch();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Retrieve the value of the given GET parameter.
|
||||
function getParam(name) {
|
||||
var results = new RegExp('[?&]' + name + '=([^&#]*)').exec(window.location.href);
|
||||
if (results) {
|
||||
return results[1] || null;
|
||||
}
|
||||
}
|
||||
|
||||
function startRetroArch() {
|
||||
$('.webplayer').show();
|
||||
$('.webplayer-preview').hide();
|
||||
document.getElementById("btnRun").disabled = true;
|
||||
|
||||
$('#btnAdd').removeClass("disabled").removeAttr("disabled").click(function() {
|
||||
$('#btnRom').click();
|
||||
});
|
||||
$('#btnRom').removeAttr("disabled").change(function(e) {
|
||||
selectFiles(e.target.files);
|
||||
});
|
||||
$('#btnMenu').removeClass("disabled").removeAttr("disabled").click(function() {
|
||||
Module._cmd_toggle_menu();
|
||||
Module.canvas.focus();
|
||||
});
|
||||
$('#btnFullscreen').removeClass("disabled").removeAttr("disabled").click(function() {
|
||||
Module.requestFullscreen(false);
|
||||
Module.canvas.focus();
|
||||
});
|
||||
Module.canvas.focus();
|
||||
Module.canvas.addEventListener("pointerdown", function() {
|
||||
Module.canvas.focus();
|
||||
}, false);
|
||||
Module.callMain(Module.arguments);
|
||||
}
|
||||
|
||||
function selectFiles(files) {
|
||||
$('#btnAdd').addClass('disabled');
|
||||
$('#icnAdd').removeClass('fa-plus');
|
||||
$('#icnAdd').addClass('fa-spinner spinning');
|
||||
var count = files.length;
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
filereader = new FileReader();
|
||||
filereader.file_name = files[i].name;
|
||||
filereader.readAsArrayBuffer(files[i]);
|
||||
filereader.onload = function() {
|
||||
uploadData(this.result, this.file_name)
|
||||
};
|
||||
filereader.onloadend = function(evt) {
|
||||
console.log("WEBPLAYER: file: " + this.file_name + " upload complete");
|
||||
if (evt.target.readyState == FileReader.DONE) {
|
||||
$('#btnAdd').removeClass('disabled');
|
||||
$('#icnAdd').removeClass('fa-spinner spinning');
|
||||
$('#icnAdd').addClass('fa-plus');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function uploadData(data, name) {
|
||||
setupWorker.postMessage({command:"upload_file", name:name, data:data}, {transfer:[data]});
|
||||
}
|
||||
|
||||
function switchCore(corename) {
|
||||
localStorage.setItem("core", corename);
|
||||
}
|
||||
|
||||
function switchStorage(backend) {
|
||||
if (backend != localStorage.getItem("backend")) {
|
||||
localStorage.setItem("backend", backend);
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
// When the browser has loaded everything.
|
||||
$(function() {
|
||||
// Enable data clear
|
||||
$('#btnClean').click(function() {
|
||||
cleanupStorage();
|
||||
});
|
||||
|
||||
// Enable all available ToolTips.
|
||||
$('.tooltip-enable').tooltip({
|
||||
placement: 'right'
|
||||
});
|
||||
|
||||
// Allow hiding the top menu.
|
||||
$('.showMenu').hide();
|
||||
$('#btnHideMenu, .showMenu').click(function() {
|
||||
$('nav').slideToggle('slow');
|
||||
$('.showMenu').toggle('slow');
|
||||
});
|
||||
|
||||
// Attempt to disable some default browser keys.
|
||||
var keys = {
|
||||
9: "tab",
|
||||
13: "enter",
|
||||
16: "shift",
|
||||
18: "alt",
|
||||
27: "esc",
|
||||
33: "rePag",
|
||||
34: "avPag",
|
||||
35: "end",
|
||||
36: "home",
|
||||
37: "left",
|
||||
38: "up",
|
||||
39: "right",
|
||||
40: "down",
|
||||
112: "F1",
|
||||
113: "F2",
|
||||
114: "F3",
|
||||
115: "F4",
|
||||
116: "F5",
|
||||
117: "F6",
|
||||
118: "F7",
|
||||
119: "F8",
|
||||
120: "F9",
|
||||
121: "F10",
|
||||
122: "F11",
|
||||
123: "F12"
|
||||
};
|
||||
window.addEventListener('keydown', function(e) {
|
||||
if (keys[e.which]) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// Switch the core when selecting one.
|
||||
$('#core-selector a').click(function() {
|
||||
var coreChoice = $(this).data('core');
|
||||
switchCore(coreChoice);
|
||||
});
|
||||
// Find which core to load.
|
||||
var core = localStorage.getItem("core", core);
|
||||
if (!core) {
|
||||
core = 'gambatte';
|
||||
}
|
||||
loadCore(core);
|
||||
});
|
||||
|
||||
async function downloadScript(src) {
|
||||
let resp = await fetch(src);
|
||||
let blob = await resp.blob();
|
||||
return blob;
|
||||
}
|
||||
|
||||
function loadCore(core) {
|
||||
// Make the core the selected core in the UI.
|
||||
var coreTitle = $('#core-selector a[data-core="' + core + '"]').addClass('active').text();
|
||||
$('#dropdownMenu1').text(coreTitle);
|
||||
downloadScript("./"+core+"_libretro.js").then(scriptBlob => {
|
||||
Module.mainScriptUrlOrBlob = scriptBlob;
|
||||
import(URL.createObjectURL(scriptBlob)).then(script => {
|
||||
script.default(Module).then(mod => {
|
||||
Module = mod;
|
||||
}).catch(err => { console.error("Couldn't instantiate module",err); throw err; });
|
||||
}).catch(err => { console.error("Couldn't load script",err); throw err; });
|
||||
});
|
||||
}
|
||||
|
||||
const setupWorker = new Worker("libretro.worker.js");
|
||||
setupWorker.onmessage = (msg) => {
|
||||
if(msg.data.command == "loaded_bundle") {
|
||||
filesystem_ready = true;
|
||||
localStorage.setItem("asset_time", msg.data.time);
|
||||
appInitialized();
|
||||
} else if(msg.data.command == "uploaded_file") {
|
||||
// console.log("finished upload of",msg.data.name);
|
||||
}
|
||||
}
|
||||
setupWorker.postMessage({command:"load_bundle",time:localStorage.getItem("asset_time") ?? ""});
|
|
@ -0,0 +1,66 @@
|
|||
importScripts("zip-no-worker.min.js");
|
||||
|
||||
async function writeFile(path, data) {
|
||||
const root = await navigator.storage.getDirectory();
|
||||
const dir_end = path.lastIndexOf("/");
|
||||
const parent = path.substr(0, dir_end);
|
||||
const child = path.substr(dir_end+1);
|
||||
const parent_dir = await mkdirTree(parent);
|
||||
const file = await parent_dir.getFileHandle(child,{create:true});
|
||||
const stream = await file.createSyncAccessHandle();
|
||||
const written = stream.write(data);
|
||||
stream.close();
|
||||
}
|
||||
|
||||
async function mkdirTree(path) {
|
||||
const root = await navigator.storage.getDirectory();
|
||||
const parts = path.split("/");
|
||||
let here = root;
|
||||
for (const part of parts) {
|
||||
if (part == "") { continue; }
|
||||
here = await here.getDirectoryHandle(part, {create:true});
|
||||
}
|
||||
return here;
|
||||
}
|
||||
|
||||
async function setupZipFS(zipBuf) {
|
||||
const root = await navigator.storage.getDirectory();
|
||||
const zipReader = new zip.ZipReader(new zip.Uint8ArrayReader(zipBuf), {useWebWorkers:false});
|
||||
const entries = await zipReader.getEntries();
|
||||
for(const file of entries) {
|
||||
if (file.getData && !file.directory) {
|
||||
const writer = new zip.Uint8ArrayWriter();
|
||||
const data = await file.getData(writer);
|
||||
await writeFile(file.filename, data);
|
||||
} else if (file.directory) {
|
||||
await mkdirTree(file.filename);
|
||||
}
|
||||
}
|
||||
await zipReader.close();
|
||||
}
|
||||
|
||||
onmessage = async (msg) => {
|
||||
if(msg.data.command == "load_bundle") {
|
||||
let old_timestamp = msg.data;
|
||||
try {
|
||||
const root = await navigator.storage.getDirectory();
|
||||
const _bundle = await root.getDirectoryHandle("bundle");
|
||||
} catch (_e) {
|
||||
old_timestamp = "";
|
||||
}
|
||||
let resp = await fetch("assets/frontend/bundle-minimal.zip", {
|
||||
headers: {
|
||||
"If-Modified-Since": old_timestamp
|
||||
}
|
||||
});
|
||||
if (resp.status == 200) {
|
||||
await setupZipFS(new Uint8Array(await resp.arrayBuffer()));
|
||||
} else {
|
||||
await resp.text();
|
||||
}
|
||||
postMessage({command:"loaded_bundle", time:resp.headers.get("last-modified")});
|
||||
} else if(msg.data.command == "upload_file") {
|
||||
await writeFile("/home/web_user/retroarch/userdata/content/"+msg.data.name, new Uint8Array(msg.data.data));
|
||||
postMessage({command:"uploaded_file",name:msg.data.name});
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -192,10 +192,10 @@
|
|||
<img class="webplayer-preview img-fluid" src="media/canvas.png" width="960" height="720" alt="RetroArch Logo">
|
||||
</div>
|
||||
</div>
|
||||
<script src="//code.jquery.com/jquery-3.1.0.min.js"></script>
|
||||
<script src="//rawgit.com/jeresig/jquery.hotkeys/master/jquery.hotkeys.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/tether/1.3.4/js/tether.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.3/js/bootstrap.min.js"></script>
|
||||
<script crossorigin="anonymous" src="//code.jquery.com/jquery-3.1.0.min.js"></script>
|
||||
<script crossorigin="anonymous" src="//rawgit.com/jeresig/jquery.hotkeys/master/jquery.hotkeys.js"></script>
|
||||
<script crossorigin="anonymous" src="//cdnjs.cloudflare.com/ajax/libs/tether/1.3.4/js/tether.min.js"></script>
|
||||
<script crossorigin="anonymous" src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.3/js/bootstrap.min.js"></script>
|
||||
<script src="analytics.js"></script>
|
||||
<!--script src="//wzrd.in/standalone/browserfs@0.6.1"></script-->
|
||||
<script src="browserfs.min.js"></script>
|
||||
|
|
|
@ -81,22 +81,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the border around the player.
|
||||
*/
|
||||
canvas.webplayer {
|
||||
border: none;
|
||||
outline: none;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hack to make emscripten stop messing with the canvas size while in fullscreen.
|
||||
* Foiled again!
|
||||
*/
|
||||
:fullscreen canvas.webplayer {
|
||||
min-width: 100vw;
|
||||
max-width: 100vw;
|
||||
min-height: 100vh;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
textarea {
|
||||
|
|
|
@ -9,7 +9,7 @@ var initializationCount = 0;
|
|||
|
||||
var Module = {
|
||||
noInitialRun: true,
|
||||
arguments: ["-v", "--menu"],
|
||||
arguments: ["-v", "--menu", "-c", "/home/web_user/retroarch/userdata/retroarch.cfg"],
|
||||
|
||||
encoder: new TextEncoder(),
|
||||
message_queue: [],
|
||||
|
@ -203,9 +203,8 @@ function setupFileSystem(backend) {
|
|||
var xfs2 = new BrowserFS.FileSystem.XmlHttpRequest(".index-xhr", "assets/cores/");
|
||||
|
||||
console.log("WEBPLAYER: initializing filesystem: " + backend);
|
||||
mfs.mount('/home/web_user/retroarch/bundle', xfs1);
|
||||
mfs.mount('/home/web_user/retroarch/userdata', afs);
|
||||
|
||||
mfs.mount('/home/web_user/retroarch/', xfs1);
|
||||
mfs.mount('/home/web_user/retroarch/userdata/content/downloads', xfs2);
|
||||
BrowserFS.initialize(mfs);
|
||||
var BFS = new BrowserFS.EmscriptenFS(Module.FS, Module.PATH, Module.ERRNO_CODES);
|
||||
|
@ -386,4 +385,4 @@ function loadCore(core) {
|
|||
console.error("Couldn't load script", err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,360 @@
|
|||
/* RetroArch - A frontend for libretro.
|
||||
* Copyright (C) 2011-2017 - Daniel De Matteis
|
||||
*
|
||||
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with RetroArch.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSCRIPTEN
|
||||
#error "task_http_emscripten only makes sense in emscripten builds"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "verbosity.h"
|
||||
#include <emscripten/emscripten.h>
|
||||
|
||||
#include <string/stdstring.h>
|
||||
#include <compat/strl.h>
|
||||
#include <file/file_path.h>
|
||||
#include <retro_timers.h>
|
||||
#include <retro_miscellaneous.h>
|
||||
|
||||
#ifdef RARCH_INTERNAL
|
||||
#include "../gfx/video_display_server.h"
|
||||
#endif
|
||||
#include "task_file_transfer.h"
|
||||
#include "tasks_internal.h"
|
||||
|
||||
struct http_handle
|
||||
{
|
||||
int handle;
|
||||
char connection_url[NAME_MAX_LENGTH];
|
||||
http_transfer_data_t *response;
|
||||
};
|
||||
|
||||
typedef struct http_handle http_handle_t;
|
||||
|
||||
static void task_http_transfer_handler(retro_task_t *task)
|
||||
{
|
||||
http_handle_t *http = (http_handle_t*)task->state;
|
||||
uint8_t flg = task_get_flags(task);
|
||||
|
||||
if ((flg & RETRO_TASK_FLG_CANCELLED) > 0)
|
||||
goto task_finished;
|
||||
|
||||
if (http->response || (http->handle == -1))
|
||||
goto task_finished;
|
||||
|
||||
return;
|
||||
task_finished:
|
||||
task_set_flags(task, RETRO_TASK_FLG_FINISHED, true);
|
||||
if (http->response)
|
||||
{
|
||||
if ((flg & RETRO_TASK_FLG_CANCELLED) > 0)
|
||||
{
|
||||
string_list_free(http->response->headers);
|
||||
free(http->response->data);
|
||||
free(http->response);
|
||||
http->response = NULL;
|
||||
task_set_error(task,
|
||||
strldup("Task cancelled.", sizeof("Task cancelled.")));
|
||||
}
|
||||
else
|
||||
{
|
||||
bool mute;
|
||||
task_set_data(task, http->response);
|
||||
mute = ((task->flags & RETRO_TASK_FLG_MUTE) > 0);
|
||||
if (!mute && http->response->status >= 400)
|
||||
task_set_error(task, strldup("Download failed.",
|
||||
sizeof("Download failed.")));
|
||||
}
|
||||
}
|
||||
free(http);
|
||||
}
|
||||
|
||||
static void http_transfer_progress_cb(retro_task_t *task)
|
||||
{
|
||||
if (task)
|
||||
video_display_server_set_window_progress(task->progress,
|
||||
((task->flags & RETRO_TASK_FLG_FINISHED) > 0));
|
||||
}
|
||||
|
||||
static bool task_http_finder(retro_task_t *task, void *user_data)
|
||||
{
|
||||
http_handle_t *http = NULL;
|
||||
if (task && (task->handler == task_http_transfer_handler) && user_data)
|
||||
if ((http = (http_handle_t*)task->state))
|
||||
return string_is_equal(http->connection_url, (const char*)user_data);
|
||||
return false;
|
||||
}
|
||||
static void task_http_transfer_cleanup(retro_task_t *task)
|
||||
{
|
||||
http_transfer_data_t* data = (http_transfer_data_t*)task_get_data(task);
|
||||
if (data)
|
||||
{
|
||||
string_list_free(data->headers);
|
||||
if (data->data)
|
||||
free(data->data);
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
void wget_onload_cb(unsigned handle, void *t_ptr, void *data, unsigned len) {
|
||||
retro_task_t *task = (retro_task_t *)t_ptr;
|
||||
http_handle_t *http = (http_handle_t*)task->state;
|
||||
http_transfer_data_t *resp;
|
||||
if (!(resp = (http_transfer_data_t*)malloc(sizeof(*resp)))) {
|
||||
http->handle = -1;
|
||||
return;
|
||||
} else {
|
||||
resp->data = data;
|
||||
resp->len = len;
|
||||
resp->status = 200;
|
||||
resp->headers = NULL; // sorry webdav
|
||||
http->response = resp;
|
||||
}
|
||||
}
|
||||
|
||||
void wget_onerror_cb(unsigned handle, void *t_ptr, int status, const char *err) {
|
||||
retro_task_t *task = (retro_task_t *)t_ptr;
|
||||
http_handle_t *http = (http_handle_t*)task->state;
|
||||
bool mute = ((task->flags & RETRO_TASK_FLG_MUTE) > 0);
|
||||
if (!mute)
|
||||
task_set_error(task, strldup("Download failed.",
|
||||
sizeof("Download failed.")));
|
||||
http->handle = -1;
|
||||
}
|
||||
|
||||
void wget_onprogress_cb(unsigned handle, void *t_ptr, int pos, int tot) {
|
||||
retro_task_t *task = (retro_task_t *)t_ptr;
|
||||
if (tot == 0)
|
||||
task_set_progress(task, -1);
|
||||
else if (pos < (((size_t)-1) / 100))
|
||||
/* prefer multiply then divide for more accurate results */
|
||||
task_set_progress(task, (signed)(pos * 100 / tot));
|
||||
else
|
||||
/* but invert the logic if it would cause an overflow */
|
||||
task_set_progress(task, MIN((signed)pos / (tot / 100), 100));
|
||||
}
|
||||
|
||||
static void *task_push_http_transfer_generic(
|
||||
const char *url, const char *method,
|
||||
const char *data, const char *user_agent,
|
||||
const char *headers,
|
||||
bool mute,
|
||||
retro_task_callback_t cb, void *user_data)
|
||||
{
|
||||
retro_task_t *t = NULL;
|
||||
http_handle_t *http = NULL;
|
||||
int wget_handle = -1;
|
||||
if (!url)
|
||||
return NULL;
|
||||
|
||||
if (!string_is_equal(method, "GET"))
|
||||
{
|
||||
/* POST requests usually mutate the server, so assume multiple calls are
|
||||
* intended, even if they're duplicated. Additionally, they may differ
|
||||
* only by the POST data, and task_http_finder doesn't look at that, so
|
||||
* unique requests could be misclassified as duplicates.
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
task_finder_data_t find_data;
|
||||
find_data.func = task_http_finder;
|
||||
find_data.userdata = (void*)url;
|
||||
|
||||
/* Concurrent download of the same file is not allowed */
|
||||
if (task_queue_find(&find_data))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!(http = (http_handle_t*)malloc(sizeof(*http))))
|
||||
goto error;
|
||||
|
||||
http->handle = -1;
|
||||
http->response = NULL;
|
||||
http->connection_url[0] = '\0';
|
||||
|
||||
strlcpy(http->connection_url, url, sizeof(http->connection_url));
|
||||
|
||||
if (!(t = task_init()))
|
||||
goto error;
|
||||
|
||||
|
||||
t->handler = task_http_transfer_handler;
|
||||
t->state = http;
|
||||
t->callback = cb;
|
||||
t->progress_cb = http_transfer_progress_cb;
|
||||
t->cleanup = task_http_transfer_cleanup;
|
||||
t->user_data = user_data;
|
||||
t->progress = -1;
|
||||
if (mute)
|
||||
t->flags |= RETRO_TASK_FLG_MUTE;
|
||||
else
|
||||
t->flags &= ~RETRO_TASK_FLG_MUTE;
|
||||
|
||||
wget_handle = emscripten_async_wget2_data(url, method, data, t, false, wget_onload_cb, wget_onerror_cb, wget_onprogress_cb);
|
||||
|
||||
http->handle = wget_handle;
|
||||
|
||||
task_queue_push(t);
|
||||
|
||||
return t;
|
||||
|
||||
error:
|
||||
if (http)
|
||||
free(http);
|
||||
if (t)
|
||||
free(t);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void* task_push_http_transfer(const char *url, bool mute,
|
||||
const char *type,
|
||||
retro_task_callback_t cb, void *user_data)
|
||||
{
|
||||
return task_push_http_transfer_generic(url, type ? type : "GET", NULL, NULL, NULL, mute, cb, user_data);
|
||||
}
|
||||
|
||||
void *task_push_webdav_stat(const char *url, bool mute, const char *headers,
|
||||
retro_task_callback_t cb, void *user_data)
|
||||
{
|
||||
RARCH_ERR("[http] response headers not supported, webdav won't work\n");
|
||||
return task_push_http_transfer_generic(url, "OPTIONS", NULL, NULL, headers, mute, cb, user_data);
|
||||
}
|
||||
|
||||
void* task_push_webdav_mkdir(const char *url, bool mute,
|
||||
const char *headers,
|
||||
retro_task_callback_t cb, void *user_data)
|
||||
{
|
||||
RARCH_ERR("[http] response headers not supported, webdav won't work\n");
|
||||
return task_push_http_transfer_generic(url, "MKCOL", NULL, NULL, headers, mute, cb, user_data);
|
||||
}
|
||||
|
||||
void* task_push_webdav_put(const char *url,
|
||||
const void *put_data, size_t len, bool mute,
|
||||
const char *headers, retro_task_callback_t cb, void *user_data)
|
||||
{
|
||||
char expect[1024]; /* TODO/FIXME - check size */
|
||||
size_t _len;
|
||||
RARCH_ERR("[http] response headers not supported, webdav won't work\n");
|
||||
|
||||
_len = strlcpy(expect, "Expect: 100-continue\r\n", sizeof(expect));
|
||||
if (headers)
|
||||
{
|
||||
strlcpy(expect + _len, headers, sizeof(expect) - _len);
|
||||
}
|
||||
|
||||
return task_push_http_transfer_generic(url, "PUT", put_data, NULL, expect, mute, cb, user_data);
|
||||
}
|
||||
|
||||
void* task_push_webdav_delete(const char *url, bool mute,
|
||||
const char *headers,
|
||||
retro_task_callback_t cb, void *user_data)
|
||||
{
|
||||
RARCH_ERR("[http] response headers not supported, webdav won't work\n");
|
||||
return task_push_http_transfer_generic(url, "DELETE", NULL, NULL, headers, mute, cb, user_data);
|
||||
}
|
||||
|
||||
void *task_push_webdav_move(const char *url,
|
||||
const char *dest, bool mute, const char *headers,
|
||||
retro_task_callback_t cb, void *user_data)
|
||||
{
|
||||
size_t _len;
|
||||
char dest_header[PATH_MAX_LENGTH + 512];
|
||||
RARCH_ERR("[http] response headers not supported, webdav won't work\n");
|
||||
|
||||
_len = strlcpy(dest_header, "Destination: ", sizeof(dest_header));
|
||||
_len += strlcpy(dest_header + _len, dest, sizeof(dest_header) - _len);
|
||||
_len += strlcpy(dest_header + _len, "\r\n", sizeof(dest_header) - _len);
|
||||
|
||||
if (headers)
|
||||
strlcpy(dest_header + _len, headers, sizeof(dest_header) - _len);
|
||||
|
||||
return task_push_http_transfer_generic(url, "MOVE", NULL, NULL, dest_header, mute, cb, user_data);
|
||||
}
|
||||
|
||||
void* task_push_http_transfer_file(const char* url, bool mute,
|
||||
const char* type,
|
||||
retro_task_callback_t cb, file_transfer_t* transfer_data)
|
||||
{
|
||||
size_t _len;
|
||||
const char *s = NULL;
|
||||
char tmp[NAME_MAX_LENGTH] = "";
|
||||
retro_task_t *t = NULL;
|
||||
|
||||
if (string_is_empty(url))
|
||||
return NULL;
|
||||
|
||||
if (!(t = (retro_task_t*)task_push_http_transfer_generic(
|
||||
/* should be using type but some callers now rely on type being ignored */
|
||||
url, "GET",
|
||||
NULL, NULL, NULL,
|
||||
mute, cb, transfer_data)))
|
||||
return NULL;
|
||||
|
||||
if (transfer_data)
|
||||
s = transfer_data->path;
|
||||
else
|
||||
s = url;
|
||||
|
||||
_len = strlcpy(tmp, msg_hash_to_str(MSG_DOWNLOADING), sizeof(tmp));
|
||||
tmp[ _len] = ' ';
|
||||
tmp[++_len] = '\0';
|
||||
|
||||
if (string_ends_with_size(s, ".index",
|
||||
strlen(s), STRLEN_CONST(".index")))
|
||||
s = msg_hash_to_str(MSG_INDEX_FILE);
|
||||
|
||||
strlcpy(tmp + _len, s, sizeof(tmp) - _len);
|
||||
|
||||
t->title = strdup(tmp);
|
||||
return t;
|
||||
}
|
||||
|
||||
void* task_push_http_transfer_with_user_agent(const char *url, bool mute,
|
||||
const char *type, const char *user_agent,
|
||||
retro_task_callback_t cb, void *user_data)
|
||||
{
|
||||
return task_push_http_transfer_generic(url, type ? type : "GET", NULL, user_agent, NULL, mute, cb, user_data);
|
||||
}
|
||||
|
||||
void* task_push_http_transfer_with_headers(const char *url, bool mute,
|
||||
const char *type, const char *headers,
|
||||
retro_task_callback_t cb, void *user_data)
|
||||
{
|
||||
return task_push_http_transfer_generic(url, type ? type : "GET", NULL, NULL, headers, mute, cb, user_data);
|
||||
}
|
||||
|
||||
void* task_push_http_post_transfer(const char *url,
|
||||
const char *post_data, bool mute,
|
||||
const char *type, retro_task_callback_t cb, void *user_data)
|
||||
{
|
||||
return task_push_http_transfer_generic(url, type ? type : "POST", post_data, NULL, NULL, mute, cb, user_data);
|
||||
}
|
||||
|
||||
void* task_push_http_post_transfer_with_user_agent(const char *url,
|
||||
const char *post_data, bool mute,
|
||||
const char *type, const char *user_agent,
|
||||
retro_task_callback_t cb, void *user_data)
|
||||
{
|
||||
return task_push_http_transfer_generic(url, type ? type : "POST", post_data, user_agent, NULL, mute, cb, user_data);
|
||||
}
|
||||
|
||||
void* task_push_http_post_transfer_with_headers(const char *url,
|
||||
const char *post_data, bool mute,
|
||||
const char *type, const char *headers,
|
||||
retro_task_callback_t cb, void *user_data)
|
||||
{
|
||||
return task_push_http_transfer_generic(url, type ? type : "POST", post_data, NULL, headers, mute, cb, user_data);
|
||||
}
|
Loading…
Reference in New Issue