/* TE4 - T-Engine 4 Copyright (C) 2009 - 2018 Nicolas Casalini This program 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 Foundation, either version 3 of the License, or (at your option) any later version. This program 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 this program. If not, see <http://www.gnu.org/licenses/>. Nicolas Casalini "DarkGod" darkgod@te4.org */ #include "display.h" #include "lua.h" #include "lauxlib.h" #include "lualib.h" #include "auxiliar.h" #include "physfs.h" #include "core_lua.h" #include "types.h" #include "main.h" #include "getself.h" #include "te4web.h" #include "web-external.h" #include "lua_externs.h" /* * Grab web browser methods -- availabe only here */ static bool webcore = FALSE; static void (*te4_web_setup)( int, char**, char*, void*(*)(), void(*)(void*), void(*)(void*), void(*)(void*), void* (*)(int, int), void (*)(void*), void (*)(void*, int, int, const void*), void (*)(bool*, bool*, bool*, bool*), void (*)(int handlers, const char *fct, int nb_args, WebJsValue *args, WebJsValue *ret) ); static void (*te4_web_initialize)(const char *locales, const char *pak); static void (*te4_web_shutdown)(); static void (*te4_web_do_update)(void (*cb)(WebEvent*)); static void (*te4_web_new)(web_view_type *view, int w, int h); static bool (*te4_web_close)(web_view_type *view); static void* (*te4_web_toscreen)(web_view_type *view, int *w, int *h); static bool (*te4_web_loading)(web_view_type *view); static void (*te4_web_focus)(web_view_type *view, bool focus); static void (*te4_web_inject_mouse_move)(web_view_type *view, int x, int y); static void (*te4_web_inject_mouse_wheel)(web_view_type *view, int x, int y); static void (*te4_web_inject_mouse_button)(web_view_type *view, int kind, bool up); static void (*te4_web_inject_key)(web_view_type *view, int scancode, int asymb, const char *uni, int unilen, bool up); static void (*te4_web_download_action)(web_view_type *view, long id, const char *path); static void (*te4_web_reply_local)(int id, const char *mime, const char *result, size_t len); static void (*te4_web_load_url)(web_view_type *view, const char *url); static void (*te4_web_set_js_call)(web_view_type *view, const char *name); static int lua_web_new(lua_State *L) { int w = luaL_checknumber(L, 1); int h = luaL_checknumber(L, 2); web_view_type *view = (web_view_type*)lua_newuserdata(L, sizeof(web_view_type)); auxiliar_setclass(L, "web{view}", -1); lua_pushvalue(L, 3); view->handlers = luaL_ref(L, LUA_REGISTRYINDEX); te4_web_new(view, w, h); return 1; } static int lua_web_close(lua_State *L) { web_view_type *view = (web_view_type*)auxiliar_checkclass(L, "web{view}", 1); if (!te4_web_close(view)) { luaL_unref(L, LUA_REGISTRYINDEX, view->handlers); } return 0; } static int lua_web_load_url(lua_State *L) { web_view_type *view = (web_view_type*)auxiliar_checkclass(L, "web{view}", 1); const char* url = luaL_checkstring(L, 2); te4_web_load_url(view, url); return 0; } static int lua_web_usable(lua_State *L) { web_view_type *view = (web_view_type*)auxiliar_checkclass(L, "web{view}", 1); int w = -1, h = -1; GLuint *tex = (GLuint*)te4_web_toscreen(view, &w, &h); lua_pushboolean(L, tex ? TRUE : FALSE); return 1; } static int lua_web_toscreen(lua_State *L) { web_view_type *view = (web_view_type*)auxiliar_checkclass(L, "web{view}", 1); int x = luaL_checknumber(L, 2); int y = luaL_checknumber(L, 3); int w = -1; int h = -1; if (lua_isnumber(L, 4)) w = lua_tonumber(L, 4); if (lua_isnumber(L, 5)) h = lua_tonumber(L, 5); GLuint *tex = (GLuint*)te4_web_toscreen(view, &w, &h); if (tex) { float r = 1, g = 1, b = 1, a = 1; tglBindTexture(GL_TEXTURE_2D, *tex); GLfloat texcoords[2*4] = { 0, 0, 0, 1, 1, 1, 1, 0, }; GLfloat colors[4*4] = { r, g, b, a, r, g, b, a, r, g, b, a, r, g, b, a, }; glColorPointer(4, GL_FLOAT, 0, colors); glTexCoordPointer(2, GL_FLOAT, 0, texcoords); GLfloat vertices[2*4] = { x, y, x, y + h, x + w, y + h, x + w, y, }; glVertexPointer(2, GL_FLOAT, 0, vertices); glDrawArrays(GL_QUADS, 0, 4); } return 0; } static int lua_web_loading(lua_State *L) { web_view_type *view = (web_view_type*)auxiliar_checkclass(L, "web{view}", 1); lua_pushboolean(L, te4_web_loading(view)); return 1; } static int lua_web_focus(lua_State *L) { web_view_type *view = (web_view_type*)auxiliar_checkclass(L, "web{view}", 1); te4_web_focus(view, lua_toboolean(L, 2)); return 0; } static int lua_web_inject_mouse_move(lua_State *L) { web_view_type *view = (web_view_type*)auxiliar_checkclass(L, "web{view}", 1); int x = luaL_checknumber(L, 2); int y = luaL_checknumber(L, 3); te4_web_inject_mouse_move(view, x, y); return 0; } static int lua_web_inject_mouse_wheel(lua_State *L) { web_view_type *view = (web_view_type*)auxiliar_checkclass(L, "web{view}", 1); int x = luaL_checknumber(L, 2); int y = luaL_checknumber(L, 3); te4_web_inject_mouse_wheel(view, x, y); return 0; } static int lua_web_inject_mouse_button(lua_State *L) { web_view_type *view = (web_view_type*)auxiliar_checkclass(L, "web{view}", 1); bool up = lua_toboolean(L, 2); int kind = luaL_checknumber(L, 3); te4_web_inject_mouse_button(view, kind, up); return 0; } static int lua_web_inject_key(lua_State *L) { web_view_type *view = (web_view_type*)auxiliar_checkclass(L, "web{view}", 1); bool up = lua_toboolean(L, 2); int scancode = lua_tonumber(L, 3); int asymb = lua_tonumber(L, 4); const char *uni = NULL; size_t unilen = 0; if (lua_isstring(L, 5)) uni = lua_tolstring(L, 5, &unilen); te4_web_inject_key(view, scancode, asymb, uni, unilen, up); return 0; } static int lua_web_download_action(lua_State *L) { web_view_type *view = (web_view_type*)auxiliar_checkclass(L, "web{view}", 1); long id = lua_tonumber(L, 2); if (lua_isstring(L, 3)) { const char *path = lua_tostring(L, 3); if (!physfs_check_allow_path_write(L, path)) return 0; te4_web_download_action(view, id, path); } else { te4_web_download_action(view, id, NULL); } return 0; } static int lua_web_set_method(lua_State *L) { web_view_type *view = (web_view_type*)auxiliar_checkclass(L, "web{view}", 1); const char *name = luaL_checkstring(L, 2); te4_web_set_js_call(view, name); return 0; } static int lua_web_local_reply_file(lua_State *L) { int id = lua_tonumber(L, 1); const char *mime = luaL_checkstring(L, 2); const char *file = luaL_checkstring(L, 3); PHYSFS_file *f = PHYSFS_openRead(file); if (!f) { te4_web_reply_local(id, mime, NULL, 0); return 0; } size_t len = PHYSFS_fileLength(f); char *data = malloc(len * sizeof(char)); size_t read = 0; while (read < len) { size_t rl = PHYSFS_read(f, data + read, sizeof(char), len - read); if (rl <= 0) break; read += rl; } PHYSFS_close(f); te4_web_reply_local(id, mime, data, read); return 0; } static int lua_web_local_reply_data(lua_State *L) { int id = lua_tonumber(L, 1); const char *mime = luaL_checkstring(L, 2); size_t len; const char *data = luaL_checklstring(L, 3, &len); te4_web_reply_local(id, mime, data, len); return 0; } static const struct luaL_Reg view_reg[] = { {"__gc", lua_web_close}, {"usable", lua_web_usable}, {"downloadAction", lua_web_download_action}, {"loadURL", lua_web_load_url}, {"toScreen", lua_web_toscreen}, {"focus", lua_web_focus}, {"loading", lua_web_loading}, {"injectMouseMove", lua_web_inject_mouse_move}, {"injectMouseWheel", lua_web_inject_mouse_wheel}, {"injectMouseButton", lua_web_inject_mouse_button}, {"injectKey", lua_web_inject_key}, {"setMethod", lua_web_set_method}, {NULL, NULL}, }; static const struct luaL_Reg weblib[] = { {"new", lua_web_new}, {"localReplyData", lua_web_local_reply_data}, {"localReplyFile", lua_web_local_reply_file}, {NULL, NULL}, }; int browsers_count = 0; static lua_State *he_L; static void handle_event(WebEvent *event) { switch (event->kind) { case TE4_WEB_EVENT_TITLE_CHANGE: lua_rawgeti(he_L, LUA_REGISTRYINDEX, event->handlers); lua_pushstring(he_L, "on_title"); lua_gettable(he_L, -2); lua_remove(he_L, -2); if (!lua_isnil(he_L, -1)) { lua_pushstring(he_L, event->data.title); docall(he_L, 1, 0); } else lua_pop(he_L, 1); break; case TE4_WEB_EVENT_REQUEST_POPUP_URL: lua_rawgeti(he_L, LUA_REGISTRYINDEX, event->handlers); lua_pushstring(he_L, "on_popup"); lua_gettable(he_L, -2); lua_remove(he_L, -2); if (!lua_isnil(he_L, -1)) { lua_pushstring(he_L, event->data.popup.url); lua_pushnumber(he_L, event->data.popup.w); lua_pushnumber(he_L, event->data.popup.h); docall(he_L, 3, 0); } else lua_pop(he_L, 1); break; case TE4_WEB_EVENT_DOWNLOAD_REQUEST: printf("[WEBCORE] download request %ld = %s :: %s :: %s\n", event->data.download_request.id, event->data.download_request.url, event->data.download_request.name, event->data.download_request.mime); lua_rawgeti(he_L, LUA_REGISTRYINDEX, event->handlers); lua_pushstring(he_L, "on_download_request"); lua_gettable(he_L, -2); lua_remove(he_L, -2); if (!lua_isnil(he_L, -1)) { lua_pushnumber(he_L, event->data.download_request.id); lua_pushstring(he_L, event->data.download_request.url); lua_pushstring(he_L, event->data.download_request.name); lua_pushstring(he_L, event->data.download_request.mime); docall(he_L, 4, 0); } else lua_pop(he_L, 1); break; case TE4_WEB_EVENT_DOWNLOAD_UPDATE: printf("[WEBCORE] download update %ld = %ld :: %ld :: %d :: %ld\n", event->data.download_update.id, event->data.download_update.got, event->data.download_update.total, event->data.download_update.percent, event->data.download_update.speed); lua_rawgeti(he_L, LUA_REGISTRYINDEX, event->handlers); lua_pushstring(he_L, "on_download_update"); lua_gettable(he_L, -2); lua_remove(he_L, -2); if (!lua_isnil(he_L, -1)) { lua_pushnumber(he_L, event->data.download_update.id); lua_pushnumber(he_L, event->data.download_update.got); lua_pushnumber(he_L, event->data.download_update.total); lua_pushnumber(he_L, event->data.download_update.percent); lua_pushnumber(he_L, event->data.download_update.speed); docall(he_L, 5, 0); } else lua_pop(he_L, 1); break; case TE4_WEB_EVENT_DOWNLOAD_FINISH: printf("[WEBCORE] download finish %ld\n", event->data.download_finish.id); lua_rawgeti(he_L, LUA_REGISTRYINDEX, event->handlers); lua_pushstring(he_L, "on_download_finish"); lua_gettable(he_L, -2); lua_remove(he_L, -2); if (!lua_isnil(he_L, -1)) { lua_pushnumber(he_L, event->data.download_finish.id); docall(he_L, 1, 0); } else lua_pop(he_L, 1); break; case TE4_WEB_EVENT_LOADING: lua_rawgeti(he_L, LUA_REGISTRYINDEX, event->handlers); lua_pushstring(he_L, "on_loading"); lua_gettable(he_L, -2); lua_remove(he_L, -2); if (!lua_isnil(he_L, -1)) { lua_pushstring(he_L, event->data.loading.url); lua_pushnumber(he_L, event->data.loading.status); docall(he_L, 2, 0); } else lua_pop(he_L, 1); break; case TE4_WEB_EVENT_END_BROWSER: lua_rawgeti(he_L, LUA_REGISTRYINDEX, event->handlers); lua_pushstring(he_L, "on_crash"); lua_gettable(he_L, -2); lua_remove(he_L, -2); if (!lua_isnil(he_L, -1)) { docall(he_L, 0, 0); } else lua_pop(he_L, 1); break; case TE4_WEB_EVENT_LOCAL_REQUEST: lua_getglobal(he_L, "core"); lua_getfield(he_L, -1, "webview"); lua_getfield(he_L, -1, "responder"); lua_remove(he_L, -2); lua_remove(he_L, -2); if (!lua_isnil(he_L, -1)) { lua_pushnumber(he_L, event->data.local_request.id); lua_pushstring(he_L, event->data.local_request.path); docall(he_L, 2, 0); } else lua_pop(he_L, 1); break; case TE4_WEB_EVENT_RUN_LUA: if (!luaL_loadstring(he_L, event->data.run_lua.code)) { docall(he_L, 0, 0); } else { printf("[WEBCORE] Failed to run lua code:\n%s\n ==>> Error: %s\n", event->data.run_lua.code, lua_tostring(he_L, -1)); lua_pop(he_L, 1); } break; case TE4_WEB_EVENT_DELETE_TEXTURE: break; case TE4_WEB_EVENT_BROWSER_COUNT: browsers_count = event->data.count; printf("[WEBCORE] Browser count %d\n", browsers_count); break; } } void te4_web_update(lua_State *L) { if (webcore) { he_L = L; te4_web_do_update(handle_event); } } void te4_web_init(lua_State *L) { if (!webcore) return; char *locales = PHYSFS_getDependentPath("/cef3/locales/"); char *pak = PHYSFS_getDependentPath("/cef3/"); te4_web_initialize(locales, pak); free(locales); free(pak); auxiliar_newclass(L, "web{view}", view_reg); luaL_openlib(L, "core.webview", weblib, 0); lua_pushstring(L, "kind"); lua_pushstring(L, "cef3"); lua_settable(L, -3); lua_settop(L, 0); } static void *web_mutex_create() { return (void*)SDL_CreateMutex(); } static void web_mutex_destroy(void *mutex) { SDL_DestroyMutex((SDL_mutex*)mutex); } static void web_mutex_lock(void *mutex) { SDL_mutexP((SDL_mutex*)mutex); } static void web_mutex_unlock(void *mutex) { SDL_mutexV((SDL_mutex*)mutex); } static void *web_make_texture(int w, int h) { GLuint *tex = malloc(sizeof(GLuint)); glGenTextures(1, tex); glBindTexture(GL_TEXTURE_2D, *tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); GLfloat largest_supported_anisotropy; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &largest_supported_anisotropy); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, largest_supported_anisotropy); unsigned char *buffer = calloc(w * h * 4, sizeof(unsigned char)); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, buffer); free(buffer); GLenum err = glGetError(); if (err != GL_NO_ERROR) { printf("[WEBCORE] failing making a %dx%d texture, status %d\n", w, h, err); glDeleteTextures(1, tex); free(tex); return NULL; } return tex; } static void web_del_texture(void *tex) { if (!tex) return; GLuint t = *((GLuint*)tex); glDeleteTextures(1, &t); free(tex); } static void web_texture_update(void *tex, int w, int h, const void* buffer) { if (!tex) return; GLuint t = *((GLuint*)tex); tglBindTexture(GL_TEXTURE_2D, t); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_BGRA, GL_UNSIGNED_BYTE, buffer); } static void web_key_mods(bool *shift, bool *ctrl, bool *alt, bool *meta) { SDL_Keymod smod = SDL_GetModState(); *shift = *ctrl = *alt = *meta = FALSE; if (smod & KMOD_SHIFT) *shift = TRUE; if (smod & KMOD_CTRL) *ctrl = TRUE; if (smod & KMOD_ALT) *alt = TRUE; if (smod & KMOD_GUI) *meta = TRUE; } static void web_instant_js(int handlers, const char *fct, int nb_args, WebJsValue *args, WebJsValue *ret) { lua_rawgeti(he_L, LUA_REGISTRYINDEX, handlers); lua_pushstring(he_L, fct); lua_gettable(he_L, -2); lua_remove(he_L, -2); if (!lua_isnil(he_L, -1)) { int i; for (i = 0; i < nb_args; i++) { if (args[i].kind == TE4_WEB_JS_NULL) lua_pushnil(he_L); else if (args[i].kind == TE4_WEB_JS_BOOLEAN) lua_pushboolean(he_L, args[i].data.b); else if (args[i].kind == TE4_WEB_JS_NUMBER) lua_pushnumber(he_L, args[i].data.n); else if (args[i].kind == TE4_WEB_JS_STRING) lua_pushstring(he_L, args[i].data.s); } if (!docall(he_L, nb_args, 1)) { if (lua_isnumber(he_L, -1)) { ret->kind = TE4_WEB_JS_NUMBER; ret->data.n = lua_tonumber(he_L, -1); } else if (lua_isstring(he_L, -1)) { ret->kind = TE4_WEB_JS_STRING; ret->data.s = lua_tostring(he_L, -1); } else if (lua_isboolean(he_L, -1)) { ret->kind = TE4_WEB_JS_BOOLEAN; ret->data.b = lua_toboolean(he_L, -1); } else { ret->kind = TE4_WEB_JS_NULL; } lua_pop(he_L, 1); } else { ret->kind = TE4_WEB_JS_NULL; } } else { ret->kind = TE4_WEB_JS_NULL; lua_pop(he_L, 1); } } void te4_web_load() { char *spawnname = NULL; char *libname = NULL; const char *self = get_self_executable(g_argc, g_argv); #if defined(SELFEXE_LINUX) || defined(SELFEXE_BSD) #if defined(TE4_RELPATH64) const char *spawnbname = "cef3spawn64"; spawnname = malloc(strlen(self) + strlen(spawnbname) + 1); strcpy(spawnname, self); strcpy(strrchr(spawnname, '/') + 1, spawnbname); const char *name = "lib64/libte4-web.so"; char *lib = malloc(strlen(self) + strlen(name) + 1); strcpy(lib, self); strcpy(strrchr(lib, '/') + 1, name); libname = lib; void *web = SDL_LoadObject(lib); #elif defined(TE4_RELPATH32) const char *spawnbname = "cef3spawn32"; spawnname = malloc(strlen(self) + strlen(spawnbname) + 1); strcpy(spawnname, self); strcpy(strrchr(spawnname, '/') + 1, spawnbname); const char *name = "lib/libte4-web.so"; char *lib = malloc(strlen(self) + strlen(name) + 1); strcpy(lib, self); strcpy(strrchr(lib, '/') + 1, name); libname = lib; void *web = SDL_LoadObject(lib); #else const char *spawnbname = "cef3spawn"; spawnname = malloc(strlen(self) + strlen(spawnbname) + 1); strcpy(spawnname, self); strcpy(strrchr(spawnname, '/') + 1, spawnbname); const char *name = "libte4-web.so"; char *lib = malloc(strlen(self) + strlen(name) + 1); strcpy(lib, self); strcpy(strrchr(lib, '/') + 1, name); libname = lib; void *web = SDL_LoadObject(lib); #endif #elif defined(SELFEXE_WINDOWS) const char *spawnbname = "cef3spawn.exe"; spawnname = malloc(strlen(self) + strlen(spawnbname) + 1); strcpy(spawnname, self); strcpy(strrchr(spawnname, '\\') + 1, spawnbname); const char *name = "te4-web.dll"; char *lib = malloc(strlen(self) + strlen(name) + 1); strcpy(lib, self); strcpy(strrchr(lib, '\\') + 1, name); libname = lib; void *web = SDL_LoadObject(lib); #elif defined(SELFEXE_MACOSX) spawnname = NULL; const char *name = "libte4-web.dylib"; char *lib = malloc(strlen(self) + strlen(name) + 1); strcpy(lib, self); strcpy(lib+strlen(self), name); libname = lib; void *web = SDL_LoadObject(lib); #else void *web = NULL; #endif printf("WebCore config: library(%s) spawn(%s)\n", libname ? libname : "--", spawnname ? spawnname : "--"); printf("Loading WebCore: %s\n", web ? "loaded!" : SDL_GetError()); #if defined(SELFEXE_LINUX) || defined(SELFEXE_BSD) // Hack to fix a strange bug when it fails to load the library on some linux version it core dumps. So we restart a new process and tell it to not even try if (!web) { char **newargs = calloc(g_argc + 2, sizeof(char*)); int i; for (i = 0; i < g_argc; i++) newargs[i] = g_argv[i]; newargs[g_argc] = strdup("--no-web"); newargs[g_argc+1] = NULL; execv(get_self_executable(g_argc, g_argv), newargs); } #endif if (web) { webcore = TRUE; te4_web_setup = (void (*)( int, char**, char*, void*(*)(), void(*)(void*), void(*)(void*), void(*)(void*), void* (*)(int, int), void (*)(void*), void (*)(void*, int, int, const void*), void (*)(bool*, bool*, bool*, bool*), void (*)(int handlers, const char *fct, int nb_args, WebJsValue *args, WebJsValue *ret) )) SDL_LoadFunction(web, "te4_web_setup"); te4_web_initialize = (void (*)(const char *locales, const char *pak)) SDL_LoadFunction(web, "te4_web_initialize"); te4_web_shutdown = (void (*)()) SDL_LoadFunction(web, "te4_web_shutdown"); te4_web_do_update = (void (*)(void (*cb)(WebEvent*))) SDL_LoadFunction(web, "te4_web_do_update"); te4_web_new = (void (*)(web_view_type *view, int w, int h)) SDL_LoadFunction(web, "te4_web_new"); te4_web_close = (bool (*)(web_view_type *view)) SDL_LoadFunction(web, "te4_web_close"); te4_web_toscreen = (void* (*)(web_view_type *view, int *w, int *h)) SDL_LoadFunction(web, "te4_web_toscreen"); te4_web_loading = (bool (*)(web_view_type *view)) SDL_LoadFunction(web, "te4_web_loading"); te4_web_focus = (void (*)(web_view_type *view, bool focus)) SDL_LoadFunction(web, "te4_web_focus"); te4_web_inject_mouse_move = (void (*)(web_view_type *view, int x, int y)) SDL_LoadFunction(web, "te4_web_inject_mouse_move"); te4_web_inject_mouse_wheel = (void (*)(web_view_type *view, int x, int y)) SDL_LoadFunction(web, "te4_web_inject_mouse_wheel"); te4_web_inject_mouse_button = (void (*)(web_view_type *view, int kind, bool up)) SDL_LoadFunction(web, "te4_web_inject_mouse_button"); te4_web_inject_key = (void (*)(web_view_type *view, int scancode, int asymb, const char *uni, int unilen, bool up)) SDL_LoadFunction(web, "te4_web_inject_key"); te4_web_download_action = (void (*)(web_view_type *view, long id, const char *path)) SDL_LoadFunction(web, "te4_web_download_action"); te4_web_reply_local = (void (*)(int id, const char *mime, const char *result, size_t len)) SDL_LoadFunction(web, "te4_web_reply_local"); te4_web_load_url = (void (*)(web_view_type *view, const char *url)) SDL_LoadFunction(web, "te4_web_load_url"); te4_web_set_js_call = (void (*)(web_view_type *view, const char *name)) SDL_LoadFunction(web, "te4_web_set_js_call"); te4_web_setup( g_argc, g_argv, spawnname, web_mutex_create, web_mutex_destroy, web_mutex_lock, web_mutex_unlock, web_make_texture, web_del_texture, web_texture_update, web_key_mods, web_instant_js ); } } void te4_web_terminate() { if (!webcore) return; te4_web_shutdown(); }