Skip to content
Snippets Groups Projects
Forked from tome / Tales of MajEyal
6648 commits behind the upstream repository.
web.c 20.52 KiB
/*
    TE4 - T-Engine 4
    Copyright (C) 2009 - 2014 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;

		glBindTexture(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)) te4_web_download_action(view, id, lua_tostring(L, 3));
	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 (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();
}