engine/plugins/cef/cef.c

2316 lines
74 KiB
C

//fte defs
#include "../plugin.h"
#include "../engine.h"
static plugfsfuncs_t *fsfuncs;
static plugsubconsolefuncs_t *confuncs;
static plugclientfuncs_t *clientfuncs;
#pragma GCC diagnostic ignored "-Wstrict-prototypes" //not my bug.
//libcef defs
#include "include/cef_version.h"
#include "include/capi/cef_app_capi.h"
#include "include/capi/cef_client_capi.h"
#include "include/capi/cef_parser_capi.h"
#include "include/capi/cef_request_context_handler_capi.h"
//#include "include/capi/cef_url_capi.h"
#include "assert.h"
#if defined(_DEBUG) && defined(_MSC_VER)
#include <crtdbg.h>
#endif
#ifndef _WIN32
#include <unistd.h>
#include <sys/stat.h>
#endif
#define EXPECTED_COMMIT_NUMBER 2179 //last version of libcef we tried building against...
#if EXPECTED_COMMIT_NUMBER != EXPECTED_COMMIT_NUMBER
#warning "libcef version different from expected. expect problems with libcef's unstable API."
#endif
#define cef_addref(ptr) (ptr)->base.add_ref(&(ptr)->base)
#define cef_release(ptr) (((ptr)->base.release)(&(ptr)->base))
#ifndef LIBCEF_STATIC
#define LIBCEF_DYNAMIC
#endif
#ifdef LIBCEF_DYNAMIC
//avoid conflicts with cef headers
#define cef_version_info pcef_version_info
#define cef_initialize pcef_initialize
#define cef_do_message_loop_work pcef_do_message_loop_work
#define cef_shutdown pcef_shutdown
#define cef_execute_process pcef_execute_process
#define cef_browser_host_create_browser_sync pcef_browser_host_create_browser_sync
#define cef_string_utf8_to_utf16 pcef_string_utf8_to_utf16
#define cef_string_utf16_to_utf8 pcef_string_utf16_to_utf8
#define cef_string_utf16_clear pcef_string_utf16_clear
#define cef_string_utf16_set pcef_string_utf16_set
#define cef_string_utf8_clear pcef_string_utf8_clear
#define cef_string_utf8_set pcef_string_utf8_set
#define cef_string_userfree_utf16_free pcef_string_userfree_utf16_free
#define cef_register_scheme_handler_factory pcef_register_scheme_handler_factory
#define cef_get_mime_type pcef_get_mime_type
#define cef_v8value_create_function pcef_v8value_create_function
#define cef_v8value_create_string pcef_v8value_create_string
#define cef_process_message_create pcef_process_message_create
#define cef_v8context_get_current_context pcef_v8context_get_current_context
#define cef_post_task pcef_post_task
#define cef_request_context_create_context pcef_request_context_create_context
#define cef_string_multimap_alloc pcef_string_multimap_alloc
#define cef_string_multimap_append pcef_string_multimap_append
#define cef_string_multimap_size pcef_string_multimap_size
#define cef_string_multimap_key pcef_string_multimap_key
#define cef_string_multimap_value pcef_string_multimap_value
#define cef_string_multimap_free pcef_string_multimap_free
#define cef_string_list_size pcef_string_list_size
#define cef_string_list_value pcef_string_list_value
static int (*cef_version_info)(int entry);
static int (*cef_initialize)(const struct _cef_main_args_t* args, const cef_settings_t* settings, cef_app_t* application, void* windows_sandbox_info);
static void (*cef_do_message_loop_work)(void);
static void (*cef_shutdown)(void);
static int (*cef_execute_process)(const cef_main_args_t* args, cef_app_t* application, void* windows_sandbox_info);
static cef_browser_t* (*cef_browser_host_create_browser_sync)(const cef_window_info_t* windowInfo, cef_client_t* client, const cef_string_t* url, const cef_browser_settings_t* settings, cef_dictionary_value_t* extra_info, cef_request_context_t* request_context);
static int (*cef_string_utf8_to_utf16)(const char* src, size_t src_len, cef_string_utf16_t* output);
static int (*cef_string_utf16_to_utf8)(const char16* src, size_t src_len, cef_string_utf8_t* output);
static void (*cef_string_utf16_clear)(cef_string_utf16_t* str);
static int (*cef_string_utf16_set)(const char16* src, size_t src_len, cef_string_utf16_t* output, int copy);
static void (*cef_string_utf8_clear)(cef_string_utf8_t* str);
static int (*cef_string_utf8_set)(const char* src, size_t src_len, cef_string_utf8_t* output, int copy);
static void (*cef_string_userfree_utf16_free)(cef_string_userfree_utf16_t str);
static int (*cef_register_scheme_handler_factory)(const cef_string_t* scheme_name, const cef_string_t* domain_name, cef_scheme_handler_factory_t* factory);
static cef_string_userfree_t (*cef_get_mime_type)(const cef_string_t* extension);
static cef_v8value_t* (*cef_v8value_create_function)(const cef_string_t* name, cef_v8handler_t* handler);
static cef_v8value_t* (*cef_v8value_create_string)(const cef_string_t* value);
static cef_process_message_t* (*cef_process_message_create)(const cef_string_t* name);
static cef_v8context_t* (*cef_v8context_get_current_context)(void); //typical C++ programmers omitted the void.
static int (*cef_post_task)(cef_thread_id_t threadId, cef_task_t* task);
static cef_request_context_t* (*cef_request_context_create_context)(const cef_request_context_settings_t* settings, cef_request_context_handler_t* handler);
static cef_string_multimap_t (*cef_string_multimap_alloc)(void);
static int (*cef_string_multimap_append)(cef_string_multimap_t map, const cef_string_t* key, const cef_string_t* value);
static size_t (*cef_string_multimap_size)(cef_string_multimap_t map);
static int (*cef_string_multimap_key)(cef_string_multimap_t map, size_t index, cef_string_t* key);
static int (*cef_string_multimap_value)(cef_string_multimap_t map, size_t index, cef_string_t* value);
static void (*cef_string_multimap_free)(cef_string_multimap_t map);
static size_t (*cef_string_list_size)(cef_string_list_t list);
static int (*cef_string_list_value)(cef_string_list_t list, size_t index, cef_string_t* value);
#ifdef _WIN32
//we can't use pSys_LoadLibrary, because plugin builtins do not work unless the engine's plugin system is fully initialised, which doesn't happen in the 'light weight' sub processes, so we'll just roll our own (consistent) version.
dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs)
{
int i;
HMODULE lib;
lib = LoadLibrary(name);
if (!lib)
{
{ //.dll implies that it is a system dll, or something that is otherwise windows-specific already.
char libname[MAX_OSPATH];
#ifdef _WIN64
Q_snprintf(libname, sizeof(libname), "%s_64", name);
#elif defined(_WIN32)
Q_snprintf(libname, sizeof(libname), "%s_32", name);
#else
#error wut? not win32?
#endif
lib = LoadLibrary(libname);
}
if (!lib)
return NULL;
}
if (funcs)
{
for (i = 0; funcs[i].name; i++)
{
*funcs[i].funcptr = GetProcAddress(lib, funcs[i].name);
if (!*funcs[i].funcptr)
break;
}
if (funcs[i].name)
{
Con_DPrintf("Missing export \"%s\" in \"%s\"\n", funcs[i].name, name);
FreeModule((dllhandle_t*)lib);
lib = NULL;
}
}
return (dllhandle_t*)lib;
}
#else
#include <dlfcn.h>
dllhandle_t *Sys_LoadLibrary(const char *name, dllfunction_t *funcs)
{
int i;
dllhandle_t *lib;
lib = NULL;
if (!lib)
lib = dlopen (va("%s.so", name), RTLD_LAZY);
if (!lib && !strstr(name, ".so"))
lib = dlopen (va("./%s.so", name), RTLD_LAZY);
if (!lib)
{
Con_DPrintf("%s\n", dlerror());
return NULL;
}
if (funcs)
{
for (i = 0; funcs[i].name; i++)
{
*funcs[i].funcptr = dlsym(lib, funcs[i].name);
if (!*funcs[i].funcptr)
break;
}
if (funcs[i].name)
{
Con_DPrintf("Unable to find symbol \"%s\" in \"%s\"\n", funcs[i].name, name);
// Sys_CloseLibrary((dllhandle_t*)lib);
lib = NULL;
}
}
return (dllhandle_t*)lib;
}
#endif
#endif
static cvar_t *cef_incognito;
static cvar_t *cef_allowplugins;
static cvar_t *cef_allowcvars;
static cvar_t *cef_devtools;
static char plugname[MAX_OSPATH];
static char *newconsole;
/*static void setcefstring(char *str, cef_string_t *r)
{
cef_string_from_utf8(str, strlen(str), r);
}*/
static cef_string_t makecefstring(char *str)
{
cef_string_t r = {NULL};
cef_string_from_utf8(str, strlen(str), &r);
return r;
}
static cef_string_t *makecefstringptr(char *str, cef_string_t *ptr)
{
cef_string_from_utf8(str, strlen(str), ptr);
return ptr;
}
static char *Info_JSONify (char *s, char *o, size_t outlen)
{
outlen--; //so we don't have to consider nulls
if (*s == '\\')
s++;
while (*s)
{
//min overhead
if (outlen < 6)
break;
outlen -= 6;
*o++ = ',';
*o++ = '\"';
for (; *s && *s != '\\'; s++)
{
if (*s != '\"' && outlen)
{
outlen--;
*o++ = *s;
}
}
*o++ = '\"';
*o++ = ':';
*o++ = '\"';
if (!*s++)
{
//should never happen.
*o++ = '\"';
*o = 0;
return o;
}
for (; *s && *s != '\\'; s++)
{
if (*s != '\"' && outlen)
{
outlen--;
*o++ = *s;
}
}
*o++ = '\"';
if (*s)
s++;
}
*o = 0;
return o;
}
#ifdef _MSC_VER
#define atomic_fetch_add(p,v) (InterlockedIncrement(p)-1)
#define atomic_fetch_sub(p,v) (InterlockedDecrement(p)+1)
#define atomic_uint32_t LONG
#else
#define atomic_fetch_add __sync_fetch_and_add
#define atomic_fetch_sub __sync_fetch_and_sub
#define atomic_uint32_t unsigned int
#endif
typedef struct
{
atomic_uint32_t refcount; //this needs to be atomic to avoid multiple threads adding at the same time
//cef interface objects
cef_client_t client;
cef_render_handler_t render_handler;
cef_display_handler_t display_handler;
cef_request_handler_t request_handler;
cef_life_span_handler_t life_span_handler;
cef_context_menu_handler_t context_menu_handler;
cef_browser_t *thebrowser;
void *videodata;
int videowidth;
int videoheight;
qboolean updated;
int desiredwidth;
int desiredheight;
char *consolename; //for internal plugin use.
qboolean fullscreen;
cef_string_utf8_t currenturl;
cef_string_utf8_t currenticon;
cef_string_utf8_t currenttitle;
cef_string_utf8_t currentstatus;
cef_mouse_event_t mousepos;
unsigned char keystate[K_MAX];
} browser_t;
unsigned int numbrowsers;
static void browser_addref(browser_t *br)
{
atomic_fetch_add(&br->refcount, 1);
}
static int browser_release(browser_t *br)
{
if (atomic_fetch_sub(&br->refcount, 1) == 1)
{
if (br->consolename)
free(br->consolename);
if (br->videodata)
free(br->videodata);
cef_string_utf8_clear(&br->currenturl);
cef_string_utf8_clear(&br->currenticon);
cef_string_utf8_clear(&br->currenttitle);
cef_string_utf8_clear(&br->currentstatus);
numbrowsers--;
free(br);
return true;
}
return false;
}
#define browser_subs(sub) \
static void CEF_CALLBACK browser_##sub##_addref(cef_base_ref_counted_t* self) {browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, sub.base)); browser_addref(br);}; \
static int CEF_CALLBACK browser_##sub##_release(cef_base_ref_counted_t* self) {browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, sub.base)); return browser_release(br);}; \
static int CEF_CALLBACK browser_##sub##_hasoneref(cef_base_ref_counted_t* self) {browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, sub.base)); return br->refcount == 1;};
browser_subs(client);
browser_subs(render_handler);
browser_subs(display_handler);
browser_subs(request_handler);
browser_subs(life_span_handler);
browser_subs(context_menu_handler);
#undef browser_subs
//client methods
static cef_render_handler_t *CEF_CALLBACK browser_get_render_handler(cef_client_t *self)
{
browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, client));
cef_addref(&br->render_handler);
return &br->render_handler;
}
static cef_life_span_handler_t *CEF_CALLBACK browser_get_life_span_handler(cef_client_t *self)
{
browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, client));
cef_addref(&br->life_span_handler);
return &br->life_span_handler;
}
static cef_context_menu_handler_t *CEF_CALLBACK browser_get_context_menu_handler(cef_client_t *self)
{
browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, client));
cef_addref(&br->context_menu_handler);
return &br->context_menu_handler;
}
static cef_display_handler_t *CEF_CALLBACK browser_get_display_handler(cef_client_t *self)
{
browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, client));
cef_addref(&br->display_handler);
return &br->display_handler;
}
static cef_request_handler_t *CEF_CALLBACK browser_get_request_handler(cef_client_t *self)
{
browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, client));
cef_addref(&br->request_handler);
return &br->request_handler;
}
static qboolean browser_handle_query(const char *req, char *buffer, size_t buffersize)
{
if (!req)
return false;
else if (!strncmp(req, "getcvar_", 8))
{
if (cef_allowcvars->value && cvarfuncs->GetString(req+8, buffer, buffersize))
return true;
}
else if (!strncmp(req, "setcvar_", 8))
{
const char *eq = strchr(req+8, '=');
if (eq)
*(char*)eq++ = 0;
else
eq = req+strlen(req);
if (cef_allowcvars->value)
{
cvarfuncs->SetString(req+8, eq);
*buffer = 0;
return true;
}
}
else if (!strcmp(req, "getstats"))
{ //1 [, one sign, 10 chars, one ], one comma
//FIXME: should be more than just a one-off.
unsigned int stats[256], i, m;
char *e = buffer;
m = clientfuncs->GetStats(0, stats, countof(stats));
if (!m)
{
m = 0;
stats[m++] = 0;
}
*e++ = '[';
for (i = 0; i < m; i++)
{
if (i)
*e++ = ',';
sprintf(e, "%i", (int)stats[i]);
e += strlen(e);
}
*e++ = ']';
*e = 0;
assert(e <= buffer + buffersize);
return true;
}
else if (!strcmp(req, "getseats"))
{
int i;
char *e = buffer;
int players[MAX_SPLITS];
int tracks[MAX_SPLITS];
int seats = clientfuncs->GetLocalPlayerNumbers(0, MAX_SPLITS, players, tracks);
*e++ = '[';
for (i = 0; i < seats; i++)
{
if (i)
*e++ = ',';
sprintf(e, "{\"player\":%i,\"track\":%i}", players[i], tracks[i]); e += strlen(e);
}
*e++ = ']';
*e = 0;
assert(e <= buffer + buffersize);
return true;
}
else if (!strcmp(req, "getserverinfo"))
{
char serverinfo[4096];
char *e = buffer;
clientfuncs->GetServerInfo(serverinfo, sizeof(serverinfo));
e = Info_JSONify(serverinfo, e, buffer + buffersize - e-1);
if (e == buffer) e++;
*buffer = '{';
*e++ = '}';
*e = 0;
assert(e <= buffer + buffersize);
return true;
}
else if (!strcmp(req, "getplayers"))
{
unsigned int i;
char *e = buffer;
plugclientinfo_t info;
*e++ = '[';
for (i = 0; ; i++)
{
clientfuncs->GetPlayerInfo(i, &info);
if (buffer + buffersize - e-1 < 100)
break;
if (i)
*e++ = ',';
*e++ = '{';
//splurge the specific info
sprintf(e, "\"frags\":%i,\"ping\":%i,\"pl\":%i,\"active\":%i,\"userid\":%i", info.frags, info.ping, info.pl, info.activetime, info.userid);
e += strlen(e);
//splurge the generic info (colours, name, team)
e = Info_JSONify(info.userinfo, e, buffer + buffersize - e-1);
*e++ = '}';
}
*e++ = ']';
*e = 0;
assert(e <= buffer + buffersize);
return true;
}
return false;
}
static int CEF_CALLBACK browser_on_process_message_received(cef_client_t* self, cef_browser_t* browser, cef_frame_t* frame, cef_process_id_t source_process, cef_process_message_t* message)
{
int handled = false;
// browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, request_handler));
cef_string_userfree_t msgnameunusable = message->get_name(message);
cef_string_utf8_t name = {NULL};
cef_string_to_utf8(msgnameunusable->str, msgnameunusable->length, &name);
if (!strcmp(name.str, "fte_query"))
{
char buffer[8192];
int id1, id2;
cef_process_message_t *reply;
cef_string_utf8_t queryname = {NULL};
cef_string_t str = {NULL};
cef_list_value_t *args = message->get_argument_list(message);
cef_string_userfree_t cmdunusable = args->get_string(args, 0);
cef_string_to_utf8(cmdunusable?cmdunusable->str:NULL, cmdunusable?cmdunusable->length:0, &queryname);
id1 = args->get_int(args, 2);
id2 = args->get_int(args, 3);
cef_release(args);
reply = cef_process_message_create(msgnameunusable);
args = reply->get_argument_list(reply);
args->set_string(args, 0, cmdunusable);
args->set_int(args, 2, id1);
args->set_int(args, 3, id2);
if (browser_handle_query(queryname.str, buffer, sizeof(buffer)))
{
str = makecefstring(buffer);
args->set_string(args, 1, &str);
cef_string_clear(&str);
}
else
args->set_null(args, 1);
cef_release(args);
cef_string_utf8_clear(&queryname);
if (cmdunusable)
cef_string_userfree_free(cmdunusable);
frame->send_process_message(frame, source_process, reply);
handled = true;
}
cef_release(message);
cef_release(browser);
cef_string_utf8_clear(&name);
cef_string_userfree_free(msgnameunusable);
// if (messagerouter->OnProcessMessageReceived(browser, source_process, message))
// return true;
return handled;
}
//render_handler methods
static void CEF_CALLBACK browser_get_view_rect(cef_render_handler_t *self, cef_browser_t *browser, cef_rect_t *rect)
{
browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, render_handler));
rect->x = 0;
rect->y = 0;
rect->width = br->desiredwidth;
rect->height = br->desiredheight;
cef_release(browser);
}
static void CEF_CALLBACK browser_on_paint(cef_render_handler_t *self, cef_browser_t *browser, cef_paint_element_type_t type, size_t dirtyRectsCount, cef_rect_t const* dirtyRects, const void* buffer, int width, int height)
{
browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, render_handler));
cef_release(browser);
//popups are gonna be so awkward...
if (type != PET_VIEW)
return;
/*
if (pbocontext)
{
if (!PBO_Lock(pbocontext, width, height, &lost, rgba))
return;
if (lost)
PBO_Update(pbocontext, buffer, width, height, stride);
else while (dirtyRectsCount --> 0)
PBO_Update(pbocontext, (char*)buffer+(width*dirtyRects->y + dirtyRects->x)*4), dirtyRects->width, dirtyRects->height, width*4);
PBO_Unlock(pbocontext);
}
else
*/
if (br->videowidth != width || br->videoheight != height)
{ //copy the entire thing.
if (br->videodata)
free(br->videodata);
br->videowidth = width;
br->videoheight = height;
br->videodata = malloc(width * height * 4);
memcpy(br->videodata, buffer, width * height * 4);
}
else
{ //try to save cpu time by copying only the dirty parts
while (dirtyRectsCount --> 0)
{
if (width == dirtyRects->width && height == dirtyRects->height)
memcpy(br->videodata, buffer, width * height * 4);
else
{
int y;
const unsigned int *src;
unsigned int *dst;
src = buffer;
src += width * dirtyRects->y + dirtyRects->x;
dst = br->videodata;
dst += width * dirtyRects->y + dirtyRects->x;
for (y = 0; y < dirtyRects->height; y++)
{
memcpy(dst, src, dirtyRects->width*4);
src += width;
dst += width;
}
}
dirtyRects++;
}
}
br->updated = true;
}
static void CEF_CALLBACK browser_on_before_close(cef_life_span_handler_t* self, cef_browser_t* browser)
{
browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, life_span_handler));
if (br->thebrowser)
{ //we may have already released our reference to this, if something else was blocking for some reason.
cef_release(br->thebrowser);
br->thebrowser = NULL;
}
cef_release(browser);
}
//context_menu_handler methods
static void CEF_CALLBACK browser_on_before_context_menu(struct _cef_context_menu_handler_t* self, struct _cef_browser_t* browser, struct _cef_frame_t* frame, struct _cef_context_menu_params_t* params, struct _cef_menu_model_t* model)
{
// browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, context_menu_handler));
//wipe whatever elements libcef thinks it should add
model->clear(model);
//don't bother adding any new ones.
cef_release(browser);
cef_release(frame);
cef_release(params);
cef_release(model);
}
//display_handler methods
//redirect console.log messages to quake's console, but only display them if we've got developer set.
static int CEF_CALLBACK browser_on_console_message(cef_display_handler_t* self, cef_browser_t* browser, cef_log_severity_t level, const cef_string_t* message, const cef_string_t* source, int line)
{
cef_string_utf8_t u8_source = {NULL};
cef_string_utf8_t u8_message = {NULL};
if (source)
cef_string_to_utf8(source->str, source->length, &u8_source);
if (message)
cef_string_to_utf8(message->str, message->length, &u8_message);
Con_DPrintf("%s:%i: %s\n", u8_source.str, line, u8_message.str);
cef_string_utf8_clear(&u8_source);
cef_string_utf8_clear(&u8_message);
cef_release(browser);
return true;
}
static void CEF_CALLBACK browser_on_title_change(cef_display_handler_t* self, cef_browser_t* browser, const cef_string_t* title)
{
browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, display_handler));
if (title)
cef_string_to_utf8(title->str, title->length, &br->currenttitle);
else
cef_string_utf8_copy(br->currenturl.str, br->currenturl.length, &br->currenttitle);
if (br->consolename)
confuncs->SetConsoleString(br->consolename, "title", br->currenttitle.str?br->currenttitle.str:"");
cef_release(browser);
}
static void CEF_CALLBACK browser_on_favicon_urlchange(cef_display_handler_t* self, cef_browser_t* browser, cef_string_list_t favicon)
{
browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, display_handler));
cef_string_t str = {NULL};
if (favicon)
{
//size_t opts = cef_string_list_size(favicon);
cef_string_list_value(favicon, 0, &str);
}
cef_string_to_utf8(str.str, str.length, &br->currenticon);
cef_string_clear(&str);
if (br->consolename)
confuncs->SetConsoleString(br->consolename, "icon", br->currenticon.str?br->currenticon.str:"");
cef_release(browser);
}
static void CEF_CALLBACK browser_on_fullscreenmode_change(cef_display_handler_t* self, cef_browser_t* browser, int fullscreen)
{
browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, display_handler));
br->fullscreen = fullscreen;
if (br->consolename)
confuncs->SetConsoleFloat(br->consolename, "fullscreen", br->fullscreen);
cef_release(browser);
}
static void CEF_CALLBACK browser_on_address_change(cef_display_handler_t* self, cef_browser_t* browser, cef_frame_t* frame, const cef_string_t* url)
{
browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, display_handler));
cef_string_to_utf8(url->str, url->length, &br->currenturl);
if (br->currenticon.length)
{
cef_string_utf8_clear(&br->currenticon);
if (br->consolename)
confuncs->SetConsoleString(br->consolename, "icon", br->currenticon.str?br->currenticon.str:"");
}
//FIXME: should probably make sure its the root frame
// Con_Printf("new url: %s\n", url.ToString().c_str());
cef_release(browser);
cef_release(frame);
}
static int CEF_CALLBACK browser_on_tooltip(cef_display_handler_t* self, cef_browser_t* browser, cef_string_t* text)
{
browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, display_handler));
if (br->consolename)
{
cef_string_utf8_t u8_text = {NULL};
cef_string_to_utf8(text->str, text->length, &u8_text);
confuncs->SetConsoleString(br->consolename, "tooltip", u8_text.str?u8_text.str:"");
cef_string_utf8_clear(&u8_text);
}
cef_release(browser);
return true; //cef won't draw tooltips when running like this
}
static void CEF_CALLBACK browser_on_status_message(cef_display_handler_t* self, cef_browser_t* browser, const cef_string_t* value)
{
browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, display_handler));
if (br->consolename)
{
cef_string_utf8_t u8_value = {NULL};
if (value)
cef_string_to_utf8(value->str, value->length, &u8_value);
confuncs->SetConsoleString(br->consolename, "footer", u8_value.str?u8_value.str:"");
cef_string_utf8_clear(&u8_value);
}
cef_release(browser);
}
//request_handler methods
static int CEF_CALLBACK browser_on_before_browse(cef_request_handler_t* self, cef_browser_t* browser, cef_frame_t* frame, cef_request_t* request, int user_gesture, int is_redirect)
{
// browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, request_handler));
cef_release(browser);
cef_release(frame);
cef_release(request);
return false; //false = allow navigation, true = block
}
static void CEF_CALLBACK browser_on_render_process_terminated(cef_request_handler_t* self, cef_browser_t* browser, cef_termination_status_t status)
{
browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, request_handler));
if (br->videodata)
free(br->videodata);
br->videowidth = 1;
br->videoheight = 1;
br->videodata = malloc(br->videowidth * br->videoheight * 4);
memset(br->videodata, 0, br->videowidth * br->videoheight * 4);
br->updated = true;
cef_release(browser);
}
static browser_t *browser_create(void)
{
browser_t *nb = malloc(sizeof(*nb));
memset(nb, 0, sizeof(*nb));
nb->refcount = 1;
#define browser_subs(sub) \
nb->sub.base.add_ref = browser_##sub##_addref; \
nb->sub.base.release = browser_##sub##_release; \
nb->sub.base.has_one_ref = browser_##sub##_hasoneref; \
nb->sub.base.size = sizeof(nb->sub);
browser_subs(client);
browser_subs(render_handler);
browser_subs(display_handler);
browser_subs(request_handler);
browser_subs(life_span_handler);
browser_subs(context_menu_handler);
#undef browser_subs
nb->client.get_context_menu_handler = browser_get_context_menu_handler;
nb->client.get_dialog_handler = NULL;
nb->client.get_display_handler = browser_get_display_handler;
nb->client.get_download_handler = NULL;
nb->client.get_drag_handler = NULL;
nb->client.get_find_handler = NULL;
nb->client.get_focus_handler = NULL;
nb->client.get_jsdialog_handler = NULL;
nb->client.get_keyboard_handler = NULL;
nb->client.get_life_span_handler = browser_get_life_span_handler;
nb->client.get_load_handler = NULL;
nb->client.get_render_handler = browser_get_render_handler;
nb->client.get_request_handler = browser_get_request_handler;
nb->client.on_process_message_received = browser_on_process_message_received;
// nb->render_handler.get_accessibility_handler = NULL;
nb->render_handler.get_root_screen_rect = NULL;
nb->render_handler.get_view_rect = browser_get_view_rect;
nb->render_handler.get_screen_point = NULL;
nb->render_handler.get_screen_info = NULL;
nb->render_handler.on_popup_show = NULL;
nb->render_handler.on_popup_size = NULL;
nb->render_handler.on_paint = browser_on_paint;
nb->render_handler.on_cursor_change = NULL;
nb->render_handler.start_dragging = NULL;
nb->render_handler.update_drag_cursor = NULL;
nb->render_handler.on_scroll_offset_changed = NULL;
// nb->render_handler.on_ime_composition_range_changed = NULL;
nb->display_handler.on_address_change = browser_on_address_change;
nb->display_handler.on_title_change = browser_on_title_change;
nb->display_handler.on_favicon_urlchange = browser_on_favicon_urlchange;
nb->display_handler.on_fullscreen_mode_change = browser_on_fullscreenmode_change;
nb->display_handler.on_tooltip = browser_on_tooltip;
nb->display_handler.on_status_message = browser_on_status_message;
nb->display_handler.on_console_message = browser_on_console_message;
nb->request_handler.on_before_browse = browser_on_before_browse;
nb->request_handler.on_open_urlfrom_tab = NULL;
// nb->request_handler.on_before_resource_load = NULL;
// nb->request_handler.get_resource_handler = NULL;
// nb->request_handler.on_resource_redirect = NULL;
// nb->request_handler.on_resource_response = NULL;
// nb->request_handler.get_resource_response_filter = NULL;
// nb->request_handler.on_resource_load_complete = NULL;
nb->request_handler.get_auth_credentials = NULL;
nb->request_handler.on_quota_request = NULL;
// nb->request_handler.on_protocol_execution = NULL; //FIXME: should implement.
nb->request_handler.on_certificate_error = NULL;
// nb->request_handler.on_select_client_certificate = NULL; //we have no such certs
nb->request_handler.on_plugin_crashed = NULL;
nb->request_handler.on_render_view_ready = NULL;
nb->request_handler.on_render_process_terminated = browser_on_render_process_terminated;
nb->life_span_handler.on_before_popup = NULL;
nb->life_span_handler.on_after_created = NULL;
nb->life_span_handler.do_close = NULL;
nb->life_span_handler.on_before_close = browser_on_before_close;
nb->context_menu_handler.on_before_context_menu = browser_on_before_context_menu;
nb->context_menu_handler.run_context_menu = NULL; //fixme: implement a working context menu somehow
nb->context_menu_handler.on_context_menu_command = NULL; //for custom context things, like opening in a new window...
nb->context_menu_handler.on_context_menu_dismissed = NULL; //
nb->desiredwidth = 640;
nb->desiredheight = 480;
if (newconsole)
nb->consolename = strdup(newconsole);
else
nb->consolename = NULL;
//make it white until there's actually something to draw
nb->videowidth = 1;
nb->videoheight = 1;
nb->videodata = malloc(nb->videowidth * nb->videoheight * 4);
*(int*)nb->videodata = 0x00ffffff;
memset(nb->videodata, 0xff, nb->videowidth * nb->videoheight * 4);
nb->updated = true;
numbrowsers++;
return nb;
}
//request contexts are per-session things. eg, incognito tabs would have their own private instance
static cef_request_context_t *request_context;
static cef_request_context_handler_t request_context_handler;
//request_context_handler methods
static int CEF_CALLBACK request_context_handler_on_before_plugin_load(cef_request_context_handler_t* self, const cef_string_t* mime_type, const cef_string_t* plugin_url, int is_main_frame, const cef_string_t* top_origin_url, cef_web_plugin_info_t* plugin_info, cef_plugin_policy_t* plugin_policy)
{
// Con_DPrintf("%s (%s), \"%s\" \"%s\" \"%s\" \"%s\"\n", policy_url.ToString().c_str(), url.ToString().c_str(),
// info->GetName().ToString().c_str(), info->GetPath().ToString().c_str(), info->GetVersion().ToString().c_str(), info->GetDescription().ToString().c_str());
*plugin_policy = PLUGIN_POLICY_BLOCK; //block by default (user can manually override supposedly). most plugins are unlikely to cope well with our offscreen rendering stuff, and flash sucks.
cef_release(plugin_info);
if (!cef_allowplugins->value)
{
*plugin_policy = PLUGIN_POLICY_DISABLE;
// Con_Printf("Blocking plugin: %s (%s)\n", info->GetName().ToString().c_str(), url.ToString().c_str());
}
else
return false; //false to use the 'recommended' policy
return true;
}
//there's only one of these, so I'm not going to bother making separate objects for all of the interfaces, nor ref counting
static cef_app_t app;
static cef_browser_process_handler_t browser_process_handler;
static cef_render_process_handler_t render_process_handler;
static cef_v8handler_t v8handler_query; //window.fte_query
static cef_scheme_handler_factory_t scheme_handler_factory;
static cef_browser_process_handler_t* CEF_CALLBACK app_get_browser_process_handler(cef_app_t* self)
{
//cef_addref(&browser_process_handler);
return &browser_process_handler;
}
static cef_render_process_handler_t* CEF_CALLBACK app_get_render_process_handler(cef_app_t* self)
{
//cef_addref(&render_process_handler);
return &render_process_handler;
}
static void CEF_CALLBACK app_on_register_custom_schemes(struct _cef_app_t* self, cef_scheme_registrar_t* registrar)
{
cef_string_t fte = makecefstring("fte");
registrar->add_custom_scheme(registrar, &fte, CEF_SCHEME_OPTION_NONE
|CEF_SCHEME_OPTION_STANDARD
/*|CEF_SCHEME_OPTION_LOCAL*/
|CEF_SCHEME_OPTION_DISPLAY_ISOLATED
|CEF_SCHEME_OPTION_SECURE
|CEF_SCHEME_OPTION_CORS_ENABLED
/*|CEF_SCHEME_OPTION_CSP_BYPASSING*/
/*|CEF_SCHEME_OPTION_FETCH_ENABLED*/
);
cef_string_clear(&fte);
}
static void CEF_CALLBACK browser_process_handler_on_context_initialized(cef_browser_process_handler_t* self)
{
cef_string_t fte = makecefstring("fte");
cef_register_scheme_handler_factory(&fte, NULL, &scheme_handler_factory);
cef_string_clear(&fte);
}
static void CEF_CALLBACK browser_process_handler_on_before_child_process_launch(cef_browser_process_handler_t* self, cef_command_line_t* command_line)
{
char arg[2048];
cef_string_t cefisannoying = {NULL};
Q_snprintf(arg, sizeof(arg), "--plugwrapper %s CefSubprocessInit", plugname);
cef_string_from_utf8(arg, strlen(arg), &cefisannoying);
command_line->append_argument(command_line, &cefisannoying);
cef_string_clear(&cefisannoying);
// MessageBoxW(NULL, command_line->GetCommandLineString().c_str(), L"CEF", 0);
cef_release(command_line);
}
static void CEF_CALLBACK render_process_handler_on_context_created(cef_render_process_handler_t* self, cef_browser_t* browser, cef_frame_t* frame, cef_v8context_t* context)
{
cef_string_t key = makecefstring("fte_query");
cef_v8value_t *jswindow = context->get_global(context);
jswindow->set_value_bykey(jswindow, &key, cef_v8value_create_function(&key, &v8handler_query), V8_PROPERTY_ATTRIBUTE_READONLY | V8_PROPERTY_ATTRIBUTE_DONTENUM | V8_PROPERTY_ATTRIBUTE_DONTDELETE);
cef_string_clear(&key);
cef_release(browser);
cef_release(frame);
cef_release(context);
}
//only use these in the 'renderer' thread / javascript thread.
typedef struct activequery_s
{
cef_v8value_t *callbackfunc; //this is the js function to call when the result is available.
cef_v8context_t *context;
cef_frame_t *frame;
int64 queryid;
struct activequery_s *next;
} activequery_t;
static activequery_t *queries;
static int64 next_queryid;
typedef struct
{
cef_task_t task;
cef_string_userfree_t request;
cef_string_userfree_t result;
int64 queryid;
atomic_uint32_t refcount;
} queryresponse_t;
static void CEF_CALLBACK queryresponse_addref(cef_base_ref_counted_t* self)
{
queryresponse_t *qr = (queryresponse_t*)((char*)self - offsetof(queryresponse_t, task.base));
atomic_fetch_add(&qr->refcount, 1);
}
static int CEF_CALLBACK queryresponse_release(cef_base_ref_counted_t* self)
{
queryresponse_t *qr = (queryresponse_t*)((char*)self - offsetof(queryresponse_t, task.base));
if (atomic_fetch_sub(&qr->refcount, 1) == 1)
{
if (qr->request)
cef_string_userfree_free(qr->request);
if (qr->result)
cef_string_userfree_free(qr->result);
free(qr);
return true;
}
return false;
}
static void CEF_CALLBACK queryresponse_execute(struct _cef_task_t* self)
{ //lethal injection.
queryresponse_t *qr = (queryresponse_t*)((char*)self - offsetof(queryresponse_t, task.base));
activequery_t **link, *q;
for (link = &queries; (q=*link); link = &(*link)->next)
{
if (q->queryid == qr->queryid)
{
if (q->callbackfunc)
{
cef_v8value_t *args[2] = {cef_v8value_create_string(qr->request), cef_v8value_create_string(qr->result)};
cef_v8value_t *r;
cef_addref(q->context);
r = q->callbackfunc->execute_function_with_context(q->callbackfunc, q->context, NULL, 2, args);
cef_release(r);
}
//and clear up the request context too.
*link = q->next;
if (q->callbackfunc)
cef_release(q->callbackfunc);
cef_release(q->frame);
cef_release(q->context);
free(q);
return;
}
}
}
static void CEF_CALLBACK render_process_handler_on_context_released(cef_render_process_handler_t* self, cef_browser_t* browser, cef_frame_t* frame, cef_v8context_t* context)
{
activequery_t **link, *q;
for (link = &queries; (q=*link); )
{
if (q->context == context)// && q->frame == frame)
{
*link = q->next;
if (q->callbackfunc)
cef_release(q->callbackfunc);
cef_release(q->frame);
cef_release(q->context);
free(q);
continue;
}
link = &(*link)->next;
}
cef_release(browser);
cef_release(frame);
cef_release(context);
}
/*javascript methods for the guest code to call*/
static cef_v8value_t *makev8string(char *str)
{
cef_v8value_t *r;
cef_string_t cs = makecefstring(str);
r = cef_v8value_create_string(&cs);
cef_string_clear(&cs);
return r;
}
static int CEF_CALLBACK fsfunc_execute(cef_v8handler_t* self, const cef_string_t* name, cef_v8value_t* object, size_t argumentsCount, cef_v8value_t* const* arguments, cef_v8value_t** retval, cef_string_t* exception)
{
cef_process_message_t *msg;
cef_list_value_t *args;
cef_v8context_t *v8ctx = cef_v8context_get_current_context();
cef_browser_t *browser = v8ctx->get_browser(v8ctx);
cef_frame_t *frame = v8ctx->get_frame(v8ctx);
// int64 frame_id = frame->get_identifier(frame);
// cef_string_t key = {L"omgwtfitkindaworks"};
// key.length = wcslen(key.str);
// *exception = makecefstring("SOME KIND OF EXCEPTION!");
*retval = makev8string("");
if (argumentsCount)
{
cef_string_userfree_t setting = arguments[0]->get_string_value(arguments[0]);
activequery_t *q = malloc(sizeof(*q));
memset(q, 0, sizeof(*q));
q->context = v8ctx; //hold on to these
q->frame = frame;
q->queryid = ++next_queryid;
q->next = queries;
q->callbackfunc = arguments[1];
queries = q;
cef_addref(q->callbackfunc);
msg = cef_process_message_create(name);
args = msg->get_argument_list(msg);
args->set_string(args, 0, setting);
args->set_null(args, 1);
args->set_int(args, 2, q->queryid & 0xffffffff);
args->set_int(args, 3, (q->queryid>>32) & 0xffffffff);
cef_release(args);
frame->send_process_message(frame, PID_BROWSER, msg);
if (setting)
cef_string_userfree_free(setting);
}
else
{
cef_release(frame);
cef_release(v8ctx);
}
cef_release(browser);
return 1;
}
static int CEF_CALLBACK render_process_handler_on_process_message_received(cef_render_process_handler_t* self,cef_browser_t* browser, cef_frame_t* frame, cef_process_id_t source_process, cef_process_message_t* message)
{
int handled = false;
// browser_t *br = (browser_t*)((char*)self - offsetof(browser_t, request_handler));
cef_string_userfree_t msgnameunusable = message->get_name(message);
cef_string_utf8_t name = {NULL};
cef_string_to_utf8(msgnameunusable->str, msgnameunusable->length, &name);
if (!strcmp(name.str, "fte_query"))
{
cef_list_value_t *args = message->get_argument_list(message);
queryresponse_t *task = malloc(sizeof(*task));
memset(task, 0, sizeof(*task));
task->refcount = 1;
task->task.base.size = sizeof(task->task);
task->task.base.add_ref = queryresponse_addref;
task->task.base.release = queryresponse_release;
task->task.execute = queryresponse_execute;
task->request = args->get_string(args, 0);
task->result = args->get_string(args, 1);
task->queryid = args->get_int(args, 2) | ((int64)args->get_int(args, 3)<<32u);
cef_release(args);
cef_post_task(TID_RENDERER, &task->task);
handled = true;
}
cef_string_utf8_clear(&name);
cef_string_userfree_free(msgnameunusable);
cef_release(browser);
cef_release(message);
return handled;
}
/* fte://file/path scheme handler */
typedef struct
{
cef_resource_handler_t rh;
atomic_uint32_t refcount;
vfsfile_t *fh;
char *data;
size_t offset;
size_t datasize;
unsigned int resultcode;
char *responseheaders;
} fteresource_t;
static void CEF_CALLBACK resource_handler_addref(cef_base_ref_counted_t* self)
{
fteresource_t *rh = (fteresource_t*)((char*)self - offsetof(fteresource_t, rh.base));
atomic_fetch_add(&rh->refcount, 1);
}
static int CEF_CALLBACK resource_handler_release(cef_base_ref_counted_t* self)
{
fteresource_t *rh = (fteresource_t*)((char*)self - offsetof(fteresource_t, rh.base));
if (atomic_fetch_sub(&rh->refcount, 1) == 1)
{
if (rh->fh)
VFS_CLOSE(rh->fh);
if (rh->data)
free(rh->data);
free(rh->responseheaders);
free(rh);
return true;
}
return false;
}
static void res_catfield_l(char **const orig, const char *key, int kl, const char *val, int vl)
{
size_t ol = *orig?strlen(*orig):0;
char *n;
if (ol)
{
n = malloc(ol+1+kl+1+vl+1);
memcpy(n, *orig, ol);
n[ol++] = '\n';
}
else
n = malloc(kl+1+vl+1);
memcpy(n+ol, key, kl);
ol+=kl;
n[ol++] = '\n';
memcpy(n+ol, val, vl);
ol+=vl;
n[ol++] = 0;
free(*orig);
*orig = n;
}
static void res_catfield(char **const orig, const char *key, const char *val)
{
res_catfield_l(orig, key, strlen(key), val, strlen(val));
}
static void res_catfield_csuf(char **const orig, const char *key, cef_string_userfree_t cs)
{
cef_string_utf8_t u8 = {NULL};
cef_string_to_utf8(cs->str, cs->length, &u8);
res_catfield_l(orig, key, strlen(key), u8.str, u8.length);
cef_string_utf8_clear(&u8);
}
static void res_catfield_cs(char **const orig, cef_string_t *key, cef_string_t *val)
{
cef_string_utf8_t keyu8 = {NULL};
cef_string_utf8_t valu8 = {NULL};
cef_string_to_utf8(key->str, key->length, &keyu8);
cef_string_to_utf8(val->str, val->length, &valu8);
res_catfield_l(orig, keyu8.str, keyu8.length, valu8.str, valu8.length);
cef_string_utf8_clear(&keyu8);
cef_string_utf8_clear(&valu8);
}
static int CEF_CALLBACK resource_handler_process_request(cef_resource_handler_t* self, cef_request_t* request, cef_callback_t* callback)
{
fteresource_t *rh = (fteresource_t*)((char*)self - offsetof(fteresource_t, rh));
cef_string_userfree_t url = request->get_url(request), method;
cef_post_data_t *postdata;
size_t numelements;
cef_post_data_element_t *elements[1];
size_t postsize;
char *postbytes;
char *q;
char *e;
cef_string_utf8_t u8_url = {NULL}, u8={NULL};
cef_string_t ext = {NULL};
cef_string_to_utf8(url->str, url->length, &u8_url);
rh->resultcode = 404;
//hack at the url to hide the
q = strchr(u8_url.str, '?');
if (q)
*q = 0;
for(e = q?q:u8_url.str+strlen(u8_url.str); e > u8_url.str; )
{
e--;
if (*e == '/')
break; //no extension
if (*e == '.')
{
e++;
cef_string_from_utf8(e, strlen(e), &ext);
break;
}
}
res_catfield(&rh->responseheaders, "Access-Control-Allow-Origin", "fte://data");
res_catfield(&rh->responseheaders, "Access-Control-Allow-Origin", "fte://csqc");
res_catfield(&rh->responseheaders, "Access-Control-Allow-Origin", "fte://ssqc");
res_catfield(&rh->responseheaders, "Access-Control-Allow-Origin", "fte://menu");
//sandboxed to the same dir that qc can fopen/fwrite.
//(also blocks any fancy http:// parsing that an engine might do)
if (!strncmp(u8_url.str, "fte://data/", 11))
{
rh->fh = fsfuncs->OpenVFS(u8_url.str+6, "rb", FS_GAME);
if (rh->fh)
{
cef_string_userfree_t mt = cef_get_mime_type(&ext);
if (mt)
{
res_catfield_csuf(&rh->responseheaders, "Content-Type", mt);
cef_string_userfree_free(mt);
}
rh->resultcode = 200;
}
else
{
rh->resultcode = 404;
res_catfield(&rh->responseheaders, "Content-Type", "text/html");
rh->data = strdup("<html><style type=\"text/css\">body {background-color: lightblue;}</style><title>not found</title>File not found within game filesystem.</html>");
rh->datasize = strlen(rh->data);
}
}
else if (!strncmp(u8_url.str, "fte://ssqc/", 11) || !strncmp(u8_url.str, "fte://csqc/", 11) || !strncmp(u8_url.str, "fte://menu/", 11))
{
struct pubprogfuncs_s *progs;
const char *page;
const char *respheaders = NULL;
const char *reqheaders = NULL;
if (ext.str)
{
cef_string_userfree_t mt = cef_get_mime_type(&ext);
if (mt)
{
res_catfield_csuf((char**)&respheaders, "Content-Type", mt);
cef_string_userfree_free(mt);
}
}
if(q)
*q = '?'; //put it back so the qc can get the full url.
rh->resultcode = 404;
if (!strncmp(u8_url.str, "fte://ssqc/", 11))
progs = plugfuncs->GetEngineInterface("SSQCVM", sizeof(*progs)); //WARNING: goes direct rather than via the server, so basically single-player only.
else if (!strncmp(u8_url.str, "fte://csqc/", 11))
progs = plugfuncs->GetEngineInterface("CSQCVM", sizeof(*progs));
else if (!strncmp(u8_url.str, "fte://menu/", 11))
progs = plugfuncs->GetEngineInterface("MenuQCVM", sizeof(*progs));
else
progs = NULL;
if (progs)
{
func_t func = progs->FindFunction(progs, "Cef_GeneratePage", PR_ANY);
if (func)
{
void *pr_globals = PR_globals(progs, PR_CURRENT);
((string_t *)pr_globals)[OFS_PARM0] = progs->TempString(progs, u8_url.str+11);
method = request->get_method(request);
cef_string_to_utf8(method->str, method->length, &u8);
((string_t *)pr_globals)[OFS_PARM1] = progs->TempString(progs, u8.str);
cef_string_userfree_free(method);
cef_string_utf8_clear(&u8);
postdata = request->get_post_data(request);
if (postdata)
{
numelements = countof(elements);
memset(elements, 0, sizeof(elements));
postdata->get_elements(postdata, &numelements, elements);
postsize = elements[0]->get_bytes_count(elements[0]);
postbytes = malloc(postsize+1);
elements[0]->get_bytes(elements[0], postsize, postbytes);
postbytes[postsize] = 0;
((string_t *)pr_globals)[OFS_PARM2] = progs->TempString(progs, postbytes);
free(postbytes);
cef_release(elements[0]);
}
else
((string_t *)pr_globals)[OFS_PARM2] = 0;
{
size_t i, elems;
cef_string_t key = {NULL}, val = {NULL};
cef_string_multimap_t hmap = cef_string_multimap_alloc();
request->get_header_map(request, hmap);
elems = cef_string_multimap_size(hmap);
for (i = 0; i < elems; i++)
{
cef_string_multimap_key(hmap, i, &key);
cef_string_multimap_key(hmap, i, &val);
res_catfield_cs(&rh->responseheaders, &key, &val);
cef_string_clear(&key);
cef_string_clear(&val);
}
cef_string_multimap_free(hmap);
}
((string_t *)pr_globals)[OFS_PARM3] = reqheaders?progs->TempString(progs, reqheaders):0; //request heders
((string_t *)pr_globals)[OFS_PARM4] = rh->responseheaders?progs->TempString(progs, rh->responseheaders):0; //response headers
((string_t *)pr_globals)[OFS_PARM5] = 0;
((string_t *)pr_globals)[OFS_PARM6] = 0;
((string_t *)pr_globals)[OFS_PARM7] = 0;
progs->ExecuteProgram(progs, func);
if (((string_t *)pr_globals)[OFS_RETURN])
{
page = progs->StringToNative(progs, ((string_t *)pr_globals)[OFS_RETURN]);
respheaders = progs->StringToNative(progs, ((string_t *)pr_globals)[OFS_PARM4]);
rh->resultcode = 200;
}
else
page = "<html><style type=\"text/css\">body {background-color: lightblue;}</style><title>not found</title>Cef_GeneratePage returned null</html>";
}
else
page = "<html><style type=\"text/css\">body {background-color: lightblue;}</style><title>not found</title>Cef_GeneratePage not implemented by mod</html>";
}
else
page = "<html><style type=\"text/css\">body {background-color: lightblue;}</style><title>not found</title>That QCVM is not running</html>";
if (*respheaders == '\n')
respheaders++;
rh->responseheaders = strdup(respheaders);
//FIXME: only return any data if we were successful OR the mime is text/html
rh->data = strdup(page);
rh->datasize = strlen(rh->data);
}
else
{
rh->resultcode = 403;
res_catfield(&rh->responseheaders, "Content-Type", "text/html");
rh->data = strdup("<html><style type=\"text/css\">body {background-color: lightblue;}</style><title>forbidden</title><a href=\"fte://data/index.html\">Try here</a> <a href=\"fte://csqc/index.html\">Or try here</a></html>");
rh->datasize = strlen(rh->data);
}
cef_string_userfree_free(url);
cef_string_utf8_clear(&u8_url);
callback->cont(callback); //headers are now known... should be delayed.
cef_release(callback);
cef_release(request);
return 1; //failure is reported as an http error code rather than an exception
}
static char *strseps(char *str, char *chars)
{ //find the next separator
char *best = str+strlen(str);
char *c;
if (*str)
while(*chars)
{
c = strchr(str, *chars++);
if (c && c < best)
best = c;
}
return best;
}
static void CEF_CALLBACK resource_handler_get_response_headers(cef_resource_handler_t* self, cef_response_t* response, int64* response_length, cef_string_t* redirectUrl)
{
fteresource_t *rh = (fteresource_t*)((char*)self - offsetof(fteresource_t, rh));
cef_string_multimap_t *hmap;
cef_string_t key = {NULL}, val={NULL};
if (rh->fh)
*response_length = VFS_GETLEN(rh->fh);
else if (rh->data)
*response_length = rh->datasize;
else
*response_length = -1;
hmap = cef_string_multimap_alloc();
if (rh->responseheaders)
{
char *start;
char *sep;
char *nl;
for (start = rh->responseheaders; *start; )
{
sep = strseps(start, ":\n");
nl = strseps(sep+1, "\n");
cef_string_from_utf8(start, sep-start, &key);
if (*sep)
sep++;
cef_string_from_utf8(sep, nl-sep, &val);
cef_string_multimap_append(hmap, &key, &val);
if (*nl)
start = nl+1;
else
break;
}
}
cef_string_multimap_append(hmap, makecefstringptr("Access-Control-Allow-Origin", &key), makecefstringptr("fte://data", &val));
response->set_header_map(response, hmap);
// response->set_mime_type(response, &rh->mimetype);
response->set_status(response, rh->resultcode);
cef_string_clear(&key);
cef_string_clear(&val);
cef_release(response);
}
static int CEF_CALLBACK resource_handler_read_response(cef_resource_handler_t* self, void* data_out, int bytes_to_read, int* bytes_read, cef_callback_t* callback)
{
fteresource_t *rh = (fteresource_t*)((char*)self - offsetof(fteresource_t, rh));
if (rh->fh)
*bytes_read = VFS_READ(rh->fh, data_out, bytes_to_read);
else if (rh->data)
{
if (bytes_to_read > rh->datasize - rh->offset)
bytes_to_read = rh->datasize - rh->offset;
*bytes_read = bytes_to_read;
memcpy(data_out, rh->data + rh->offset, bytes_to_read);
rh->offset += bytes_to_read;
}
else
*bytes_read = 0;
//callback->cont(callback); //headers are now known... should be delayed.
cef_release(callback);
if (*bytes_read <= 0)
{
*bytes_read = 0;
return 0;
}
return true; //more to come
}
static void CEF_CALLBACK resource_handler_cancel(cef_resource_handler_t* self)
{
fteresource_t *rh = (fteresource_t*)((char*)self - offsetof(fteresource_t, rh));
if (rh->fh)
VFS_CLOSE(rh->fh);
rh->fh = NULL;
if (rh->data)
free(rh->data);
rh->data = NULL;
rh->offset = 0;
rh->datasize = 0;
}
static cef_resource_handler_t* CEF_CALLBACK scheme_handler_factory_create(cef_scheme_handler_factory_t* self, cef_browser_t* browser, cef_frame_t* frame, const cef_string_t* scheme_name, cef_request_t* request)
{
fteresource_t *rh = malloc(sizeof(*rh));
memset(rh, 0, sizeof(*rh));
rh->rh.base.size = sizeof(*rh);
rh->rh.base.add_ref = resource_handler_addref;
rh->rh.base.release = resource_handler_release;
rh->rh.process_request = resource_handler_process_request;
rh->rh.get_response_headers = resource_handler_get_response_headers;
rh->rh.read_response = resource_handler_read_response;
rh->rh.cancel = resource_handler_cancel;
cef_addref(&rh->rh);
cef_release(browser);
cef_release(frame);
cef_release(request);
return &rh->rh;
}
static void app_initialize(void)
{
app.base.size = sizeof(app);
app.get_browser_process_handler = app_get_browser_process_handler;
app.get_render_process_handler = app_get_render_process_handler;
app.on_register_custom_schemes = app_on_register_custom_schemes;
browser_process_handler.base.size = sizeof(browser_process_handler);
browser_process_handler.on_before_child_process_launch = browser_process_handler_on_before_child_process_launch;
browser_process_handler.on_context_initialized = browser_process_handler_on_context_initialized;
render_process_handler.base.size = sizeof(render_process_handler);
render_process_handler.on_context_created = render_process_handler_on_context_created;
render_process_handler.on_context_released = render_process_handler_on_context_released;
render_process_handler.on_process_message_received = render_process_handler_on_process_message_received;
v8handler_query.base.size = sizeof(v8handler_query);
v8handler_query.execute = fsfunc_execute;
scheme_handler_factory.base.size = sizeof(scheme_handler_factory);
scheme_handler_factory.create = scheme_handler_factory_create;
request_context_handler.base.size = sizeof(request_context_handler);
request_context_handler.on_before_plugin_load = request_context_handler_on_before_plugin_load;
}
static int cefwasinitialised;
cef_request_context_t *Cef_GetRequestContext(void)
{
char utf8[MAX_OSPATH];
cef_request_context_t *ret = NULL;
qboolean incog;
incog = cef_incognito->value;
if (!incog)
ret = request_context;
if (!ret)
{
cef_request_context_settings_t csettings = {sizeof(csettings)};
csettings.persist_user_preferences = !incog;
if (!incog && fsfuncs->NativePath("cefcache", FS_ROOT, utf8, sizeof(utf8)))
cef_string_from_utf8(utf8, strlen(utf8), &csettings.cache_path); //should be empty for incognito.
ret = cef_request_context_create_context(&csettings, &request_context_handler);
cef_string_clear(&csettings.cache_path);
}
else
cef_addref(ret);
if (!incog && !request_context)
{
request_context = ret;
cef_addref(request_context);
}
return ret;
}
static qboolean Cef_Init(qboolean engineprocess);
struct mediacallbacks_s; //todo...
static void *Cef_Create(const char *name, struct mediacallbacks_s *callbacks)
{
cef_window_info_t window_info = {0};
cef_browser_settings_t browserSettings = {sizeof(browserSettings)};
browser_t *newbrowser;
cef_string_t url = {NULL};
if (!strcmp(name, "cef"))
name += 3;
else if (!strncmp(name, "cef:", 4))
name += 4;
else if (!strcmp(name, "http"))
name += 4;
else if (!strncmp(name, "http:", 5))
;
else if (!strncmp(name, "https:", 6))
;
else if (!strncmp(name, "ftp:", 4))
;
else
return NULL;
if (!cefwasinitialised)
{
char utf8[MAX_OSPATH];
cef_main_args_t mainargs = {0};
cef_settings_t settings = {sizeof(settings)};
if (!Cef_Init(true))
return NULL;
//const char *ua = "FTEBrowser";
//cef_string_from_utf8(ua, strlen(ua), &settings.user_agent);
if (fsfuncs->NativePath("cefcache", FS_ROOT, utf8, sizeof(utf8)))
cef_string_from_utf8(utf8, strlen(utf8), &settings.cache_path);
if (fsfuncs->NativePath("cef_debug.log", FS_ROOT, utf8, sizeof(utf8)))
cef_string_from_utf8(utf8, strlen(utf8), &settings.log_file);
if (fsfuncs->NativePath("", FS_BINARYPATH, utf8, sizeof(utf8)))
cef_string_from_utf8(utf8, strlen(utf8), &settings.resources_dir_path);
if (fsfuncs->NativePath("locales", FS_BINARYPATH, utf8, sizeof(utf8)))
{
struct stat statbuf;
if (stat(utf8, &statbuf) < 0)
{
Con_Printf("%s not found\n", utf8);
return NULL;
}
cef_string_from_utf8(utf8, strlen(utf8), &settings.locales_dir_path);
}
#ifdef _WIN32
{
wchar_t omgwtfamonkey[MAX_OSPATH];
if (GetModuleFileNameW(NULL, omgwtfamonkey, countof(omgwtfamonkey)))
cef_string_from_utf16(omgwtfamonkey, wcslen(omgwtfamonkey), &settings.browser_subprocess_path);
mainargs.instance = GetModuleHandle(NULL);
}
#endif
#ifdef _DEBUG
settings.log_severity = LOGSEVERITY_VERBOSE;
#else
settings.log_severity = LOGSEVERITY_DISABLE;
#endif
settings.background_color = 0x00ffffff;
// settings.single_process = true;
#ifdef _WIN32
// settings.multi_threaded_message_loop = true; //fixme: use this.
#endif
settings.windowless_rendering_enabled = true;
// settings.command_line_args_disabled = true;
// settings.persist_session_cookies = false;
{
char *s;
strcpy(utf8, FULLENGINENAME "/" STRINGIFY(FTE_VER_MAJOR) "." STRINGIFY(FTE_VER_MINOR));
while((s = strchr(utf8, ' ')))
*s = '_';
cef_string_from_utf8(utf8, strlen(utf8), &settings.product_version);
}
cefwasinitialised = !!cef_initialize(&mainargs, &settings, &app, NULL);
cef_string_clear(&settings.browser_subprocess_path);
cef_string_clear(&settings.product_version);
cef_string_clear(&settings.cache_path);
cef_string_clear(&settings.log_file);
}
if (!cefwasinitialised)
return NULL;
//tbh, web browser's are so horribly insecure that it seems pointless to even try disabling stuff that might be useful
browserSettings.windowless_frame_rate = 60;
browserSettings.javascript_close_windows = STATE_DISABLED;
browserSettings.javascript_access_clipboard = STATE_DISABLED;
// browserSettings.universal_access_from_file_urls = STATE_DISABLED;
// browserSettings.file_access_from_file_urls = STATE_DISABLED;
browserSettings.remote_fonts = STATE_DISABLED;
browserSettings.plugins = STATE_DISABLED;
browserSettings.background_color = 0x00ffffff;
window_info.windowless_rendering_enabled = true;
memset(&window_info.parent_window, 0, sizeof(window_info.parent_window));
newbrowser = browser_create();
if (!newbrowser)
return NULL;
if (!*name || !strcmp(name, "http:") || !strcmp(name, "https:"))
name = "about:blank";
cef_string_from_utf8(name, strlen(name), &url);
cef_addref(&newbrowser->client);
newbrowser->thebrowser = cef_browser_host_create_browser_sync(&window_info, &newbrowser->client, &url, &browserSettings, NULL, Cef_GetRequestContext());
cef_string_to_utf8(url.str, url.length, &newbrowser->currenturl);
cef_string_clear(&url);
if (!newbrowser->thebrowser)
{
browser_release(newbrowser);
return NULL; //cef fucked up.
}
if (cef_devtools->value)
{
cef_browser_host_t *host = newbrowser->thebrowser->get_host(newbrowser->thebrowser);
browser_t *devtools = browser_create();
#ifdef _WIN32
window_info.style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE;
window_info.parent_window = NULL;
window_info.x = CW_USEDEFAULT;
window_info.y = CW_USEDEFAULT;
window_info.width = CW_USEDEFAULT;
window_info.height = CW_USEDEFAULT;
window_info.window_name = makecefstring("CEF Dev Tools");
#else
memset(&window_info.parent_window, 0, sizeof(window_info.parent_window));
window_info.x = 0;
window_info.y = 0;
window_info.width = 320;
window_info.height = 240;
#endif
window_info.windowless_rendering_enabled = false;
cef_addref(&devtools->client);
host->show_dev_tools(host, &window_info, &devtools->client, &browserSettings, NULL);
cef_release(host);
browser_release(devtools); //cef should continue to hold a reference to it while its visible, but its otherwise out of engine now.
#ifdef _WIN32
cef_string_clear(&window_info.window_name);
#endif
}
return (void*)newbrowser;
}
static void *Cef_CreateOld(const char *name)
{
return Cef_Create(name, NULL);
}
static qboolean VARGS Cef_DisplayFrame(void *ctx, qboolean nosound, qboolean forcevideo, double mediatime, void (QDECL *uploadtexture)(void *ectx, uploadfmt_t fmt, int width, int height, void *data, void *palette), void *ectx)
{
browser_t *browser = (browser_t*)ctx;
if (browser->updated || forcevideo)
{
uploadtexture(ectx, TF_BGRA32, browser->videowidth, browser->videoheight, browser->videodata, NULL);
browser->updated = false;
}
return true;
}
static void Cef_Destroy(void *ctx)
{ //engine isn't allowed to talk about the browser any more. kill it.
browser_t *br = (browser_t*)ctx;
cef_browser_host_t *host = br->thebrowser->get_host(br->thebrowser);
host->close_browser(host, true);
cef_release(host);
if (br->thebrowser)
{
cef_release(br->thebrowser);
br->thebrowser = NULL;
}
//now release our reference to it.
browser_release(br); //hopefully this should be the last reference, but we might be waiting for something on the cef side. hopefully nothing blocking on an unload event...
}
static void VARGS Cef_CursorMove (void *ctx, float posx, float posy)
{
browser_t *br = (browser_t*)ctx;
cef_browser_host_t *host = br->thebrowser->get_host(br->thebrowser);
br->mousepos.x = (int)(posx * br->desiredwidth);
br->mousepos.y = (int)(posy * br->desiredheight);
br->mousepos.modifiers = 0;
host->send_mouse_move_event(host, &br->mousepos, false);
cef_release(host);
}
static void VARGS Cef_Key (void *ctx, int code, int unicode, int event)
{
browser_t *browser = (browser_t*)ctx;
cef_browser_host_t *host = browser->thebrowser->get_host(browser->thebrowser);
//handle mouse buttons
if (code >= K_MOUSE1 && code <= K_MOUSE3)
{
int buttons[] = {MBT_LEFT, MBT_RIGHT, MBT_MIDDLE};
if (!event || browser->keystate[code])
host->send_mouse_click_event(host, &browser->mousepos, buttons[code-K_MOUSE1], event?true:false, 1);
if (event)
browser->keystate[code] = 0;
else
browser->keystate[code] = 1;
cef_release(host);
return;
}
//handle mouse wheels
if (code == K_MWHEELUP || code == K_MWHEELDOWN)
{
if (!event)
host->send_mouse_wheel_event(host, &browser->mousepos, 0, (code == K_MWHEELDOWN)?-32:32);
cef_release(host);
return;
}
//handle keypress/release events
if (code)
{
cef_key_event_t kev = {0};
if (event && !browser->keystate[code])
{
cef_release(host);
return; //releasing a key that is already released is weird.
}
kev.type = event?KEYEVENT_KEYUP:KEYEVENT_RAWKEYDOWN;
kev.modifiers = 0;
switch(code)
{
case 0: kev.windows_key_code = 0; break;
case K_UPARROW: kev.windows_key_code = 0x26/*VK_UP*/; break;
case K_DOWNARROW: kev.windows_key_code = 0x28/*VK_DOWN*/; break;
case K_LEFTARROW: kev.windows_key_code = 0x25/*VK_LEFT*/; break;
case K_RIGHTARROW: kev.windows_key_code = 0x27/*VK_RIGHT*/; break;
case K_ESCAPE: kev.windows_key_code = 0x1b/*VK_ESCAPE*/; break;
case K_SPACE: kev.windows_key_code = 0x20/*VK_SPACE*/; break;
case K_RSHIFT: kev.windows_key_code = 0x10/*VK_SHIFT*/; break;
case K_LSHIFT: kev.windows_key_code = 0x10/*VK_SHIFT*/; break;
case K_RCTRL: kev.windows_key_code = 0x11/*VK_CONTROL*/; break;
case K_LCTRL: kev.windows_key_code = 0x11/*VK_CONTROL*/; break;
case K_RALT: kev.windows_key_code = 0x12/*VK_MENU*/; break;
case K_LALT: kev.windows_key_code = 0x12/*VK_MENU*/; break;
case K_TAB: kev.windows_key_code = 0x09/*VK_TAB*/; break;
case K_RWIN: kev.windows_key_code = 0x5c/*VK_RWIN*/; break;
case K_LWIN: kev.windows_key_code = 0x5b/*VK_LWIN*/; break;
case K_APP: kev.windows_key_code = 0x5d/*VK_APPS*/; break;
case K_F1: kev.windows_key_code = 0x70/*VK_F1*/; break;
case K_F2: kev.windows_key_code = 0x71/*VK_F2*/; break;
case K_F3: kev.windows_key_code = 0x72/*VK_F3*/; break;
case K_F4: kev.windows_key_code = 0x73/*VK_F4*/; break;
case K_F5: kev.windows_key_code = 0x74/*VK_F5*/; break;
case K_F6: kev.windows_key_code = 0x75/*VK_F6*/; break;
case K_F7: kev.windows_key_code = 0x76/*VK_F7*/; break;
case K_F8: kev.windows_key_code = 0x77/*VK_F8*/; break;
case K_F9: kev.windows_key_code = 0x78/*VK_F9*/; break;
case K_F10: kev.windows_key_code = 0x79/*VK_F10*/; break;
case K_F11: kev.windows_key_code = 0x81/*VK_F11*/; break;
case K_F12: kev.windows_key_code = 0x82/*VK_F12*/; break;
case K_BACKSPACE: kev.windows_key_code = 0x08/*VK_BACK*/; break;
case K_DEL: kev.windows_key_code = 0x2e/*VK_DELETE*/; break;
case K_HOME: kev.windows_key_code = 0x24/*VK_HOME*/; break;
case K_END: kev.windows_key_code = 0x23/*VK_END*/; break;
case K_INS: kev.windows_key_code = 0x2d/*VK_INSERT*/; break;
case K_PGUP: kev.windows_key_code = 0x21/*VK_PRIOR*/; break;
case K_PGDN: kev.windows_key_code = 0x22/*VK_NEXT*/; break;
default:
if ((code >= 0x30 && code <= 0x39) || (code >= 0x41 && code <= 0x5a))
kev.windows_key_code = code;
else if (code >= 'a' && code <= 'z')
kev.windows_key_code = (code-'a') + 'A';
else
kev.windows_key_code = 0;
break;
}
kev.native_key_code = unicode<<16;
if (browser->keystate[code])
kev.native_key_code |= 1<<30;
if (event)
kev.native_key_code |= 1u<<31;
if (event)
browser->keystate[code] = 0;
else
browser->keystate[code] = 1;
kev.is_system_key = 0;
kev.character = unicode;
kev.unmodified_character = unicode;
kev.focus_on_editable_field = true;
host->send_key_event(host, &kev);
}
//handle text input events (down events only)
if (unicode && !event)
{
cef_key_event_t kev;
kev.type = KEYEVENT_CHAR;
kev.modifiers = 0;
kev.windows_key_code = unicode;
kev.native_key_code = unicode<<16;
if (browser->keystate[code])
kev.native_key_code |= 1<<30;
if (event)
kev.native_key_code |= 1u<<31;
kev.is_system_key = 0;
kev.character = unicode;
kev.unmodified_character = unicode;
kev.focus_on_editable_field = true;
host->send_key_event(host, &kev);
}
cef_release(host);
}
static qboolean VARGS Cef_SetSize (void *ctx, int width, int height)
{
browser_t *browser = (browser_t*)ctx;
cef_browser_host_t *host = browser->thebrowser->get_host(browser->thebrowser);
if (browser->desiredwidth != width || browser->desiredheight != height)
{
browser->desiredwidth = width;
browser->desiredheight = height;
host->was_resized(host);
}
cef_release(host);
return qtrue;
}
static void VARGS Cef_GetSize (void *ctx, int *width, int *height)
{
//this specifies the logical size/aspect of the browser object
browser_t *browser = (browser_t*)ctx;
*width = browser->desiredwidth;
*height = browser->desiredheight;
}
static void VARGS Cef_ChangeStream (void *ctx, const char *streamname)
{
browser_t *browser = (browser_t*)ctx;
cef_browser_host_t *host = browser->thebrowser->get_host(browser->thebrowser);
cef_frame_t *frame = NULL;
if (!strncmp(streamname, "cmd:", 4))
{
const char *cmd = streamname+4;
if (!strcmp(cmd, "focus"))
host->send_focus_event(host, true);
else if (!strcmp(cmd, "unfocus"))
host->send_focus_event(host, false);
else if (!strcmp(cmd, "refresh"))
browser->thebrowser->reload(browser->thebrowser);
else if (!strcmp(cmd, "transparent"))
;
else if (!strcmp(cmd, "opaque"))
;
else if (!strcmp(cmd, "stop"))
browser->thebrowser->stop_load(browser->thebrowser);
else if (!strcmp(cmd, "back"))
browser->thebrowser->go_back(browser->thebrowser);
else if (!strcmp(cmd, "forward"))
browser->thebrowser->go_forward(browser->thebrowser);
else
{
frame = browser->thebrowser->get_focused_frame(browser->thebrowser);
if (!strcmp(cmd, "undo"))
frame->undo(frame);
else if (!strcmp(cmd, "redo"))
frame->redo(frame);
else if (!strcmp(cmd, "cut"))
frame->cut(frame);
else if (!strcmp(cmd, "copy"))
frame->copy(frame);
else if (!strcmp(cmd, "paste"))
;//frame->paste(frame); //possible security hole, as this uses the system clipboard
else if (!strcmp(cmd, "del"))
frame->del(frame);
else if (!strcmp(cmd, "selectall"))
frame->select_all(frame);
else
Con_Printf("unrecognised cmd: %s\n", cmd);
}
}
else if (!strncmp(streamname, "javascript:", 11))
{
cef_string_t thescript = {NULL};
cef_string_t url = {NULL};
cef_string_from_utf8(streamname+11, strlen(streamname+11), &thescript);
cef_string_from_utf8("http://localhost/", strlen("http://localhost/"), &url);
frame = browser->thebrowser->get_main_frame(browser->thebrowser);
frame->execute_java_script(frame, &thescript, &url, 1);
cef_string_clear(&thescript);
cef_string_clear(&url);
}
/*else if (!strncmp(streamname, "raw:", 4))
{
cef_string_t thehtml = {NULL};
cef_string_t url = {NULL};
cef_string_from_utf8(streamname+4, strlen(streamname+4), &thehtml);
cef_string_from_utf8("http://localhost/", strlen("http://localhost/"), &url);
frame = browser->thebrowser->get_main_frame(browser->thebrowser);
frame->load_string(frame, &thehtml, &url);
cef_string_clear(&thehtml);
cef_string_clear(&url);
}*/
else if (*streamname && strcmp(streamname, "http:") && strcmp(streamname, "https:"))
{
cef_string_t url = {NULL};
cef_string_from_utf8(streamname, strlen(streamname), &url);
frame = browser->thebrowser->get_main_frame(browser->thebrowser);
frame->load_url(frame, &url);
cef_string_clear(&url);
}
if (frame)
cef_release(frame);
cef_release(host);
}
qboolean VARGS Cef_GetProperty (void *ctx, const char *field, char *out, size_t *outsize)
{
browser_t *browser = (browser_t*)ctx;
const char *ret = NULL;
if (!strcmp(field, "url"))
ret = browser->currenturl.str;
else if (!strcmp(field, "title"))
ret = browser->currenttitle.str;
else if (!strcmp(field, "status"))
ret = browser->currentstatus.str;
else if (!strcmp(field, "icon"))
ret = browser->currenticon.str;
if (ret)
{
size_t retsize = strlen(ret);
if (out)
{
if (*outsize < retsize)
return false; //caller fucked up
memcpy(out, ret, retsize);
}
*outsize = retsize;
return true;
}
return false;
}
static media_decoder_funcs_t decoderfuncs;
static qintptr_t Cef_Tick(qintptr_t *args)
{
if (cefwasinitialised)
{
cef_do_message_loop_work();
/* libcef can't cope with this.
if (!numbrowsers)
{
if (request_context)
{
cef_release(request_context);
request_context = NULL;
}
cef_shutdown();
cefwasinitialised = false;
}
*/
}
return 0;
}
static qintptr_t Cef_Shutdown(qintptr_t *args)
{
if (cefwasinitialised)
{
int tries = 1000*10;//60*5; //keep trying for a duration (in ms)... give up after then as it just isn't working.
while(numbrowsers && tries > 0)
{
cef_do_message_loop_work();
tries -= 10;
#ifdef _WIN32
Sleep(10);
#else
usleep(10*1000);
#endif
}
#ifdef _WIN32
if (numbrowsers)
{ //this really should NOT be happening.
MessageBox(NULL, "Browsers are still open", "CEF Fuckup", 0);
}
#endif
if (request_context)
{
cef_release(request_context);
request_context = NULL;
}
cef_shutdown();
cefwasinitialised = false;
numbrowsers = 0;
}
#if defined(_DEBUG) && defined(_MSC_VER)
// _CrtDumpMemoryLeaks();
#endif
return 0;
}
#ifndef _WIN32
static int argc=0;
static char *argv[64];
static char commandline[8192];
static void SetupArgv(cef_main_args_t *a)
{
FILE *f;
if (!argc)
{
f = fopen("/proc/self/cmdline", "r");
if (f)
{
char *s = commandline;
char *e = commandline+fread(commandline, 1, sizeof(commandline), f);
fclose(f);
while(s < e)
{
argv[argc++] = s;
while(*s)
s++;
s++;
}
}
}
a->argc = argc;
a->argv = argv;
}
#endif
//if we're a subprocess and somehow failed to add the --dllwrapper arg to the engine, then make sure we're not starting endless processes.
static qboolean Cef_Init(qboolean engineprocess)
{
#ifdef _WIN32
cef_main_args_t args = {GetModuleHandle(NULL)};
#else
cef_main_args_t args;
SetupArgv(&args);
#endif
{
int result;
#ifdef LIBCEF_DYNAMIC
dllfunction_t ceffuncs[] =
{
{(void **)&cef_version_info, "cef_version_info"},
{(void **)&cef_initialize, "cef_initialize"},
{(void **)&cef_do_message_loop_work, "cef_do_message_loop_work"},
{(void **)&cef_shutdown, "cef_shutdown"},
{(void **)&cef_execute_process, "cef_execute_process"},
{(void **)&cef_browser_host_create_browser_sync,"cef_browser_host_create_browser_sync"},
{(void **)&cef_string_utf8_to_utf16, "cef_string_utf8_to_utf16"},
{(void **)&cef_string_utf16_to_utf8, "cef_string_utf16_to_utf8"},
{(void **)&cef_string_utf16_clear, "cef_string_utf16_clear"},
{(void **)&cef_string_utf16_set, "cef_string_utf16_set"},
{(void **)&cef_string_utf8_clear, "cef_string_utf8_clear"},
{(void **)&cef_string_utf8_set, "cef_string_utf8_set"},
{(void **)&cef_string_userfree_utf16_free, "cef_string_userfree_utf16_free"},
{(void **)&cef_register_scheme_handler_factory, "cef_register_scheme_handler_factory"},
{(void **)&cef_v8value_create_function, "cef_v8value_create_function"},
{(void **)&cef_v8value_create_string, "cef_v8value_create_string"},
{(void **)&cef_process_message_create, "cef_process_message_create"},
{(void **)&cef_v8context_get_current_context, "cef_v8context_get_current_context"},
{(void **)&cef_post_task, "cef_post_task"},
{(void **)&cef_request_context_create_context, "cef_request_context_create_context"},
{(void **)&cef_string_multimap_alloc, "cef_string_multimap_alloc"},
{(void **)&cef_string_multimap_append, "cef_string_multimap_append"},
{(void **)&cef_string_multimap_size, "cef_string_multimap_size"},
{(void **)&cef_string_multimap_key, "cef_string_multimap_key"},
{(void **)&cef_string_multimap_value, "cef_string_multimap_value"},
{(void **)&cef_string_multimap_free, "cef_string_multimap_free"},
{(void **)&cef_string_list_size, "cef_string_list_size"},
{(void **)&cef_string_list_value, "cef_string_list_value"},
{NULL}
};
if (!Sys_LoadLibrary("libcef", ceffuncs))
{
if (engineprocess)
Con_Printf("Unable to load libcef (version "CEF_VERSION")\n");
return false;
}
#endif
if (engineprocess)
{
Con_Printf("libcef %i.%i: chrome %i.%i (%i)\n", cef_version_info(0), cef_version_info(1), cef_version_info(2), cef_version_info(3), cef_version_info(4));
if (cef_version_info(0) != CEF_VERSION_MAJOR||
cef_version_info(1) != CEF_VERSION_MINOR||
cef_version_info(2) != CEF_VERSION_PATCH||
cef_version_info(3) != CEF_COMMIT_NUMBER||
cef_version_info(4) != CHROME_VERSION_MAJOR||
cef_version_info(5) != CHROME_VERSION_MINOR||
cef_version_info(6) != CHROME_VERSION_BUILD||
cef_version_info(7) != CHROME_VERSION_PATCH)
{ //the libcef api hash can be used to see if there's an api change that might break stuff.
//refuse to load it if the api changed.
Con_Printf("libcef outdated. Please install libcef version "CEF_VERSION"\n");
return false;
}
}
app_initialize();
result = cef_execute_process(&args, &app, 0);
if (result >= 0 || !engineprocess)
{ //result is meant to be the exit code that the child process is meant to exit with
//either way, we really don't want to return to the engine because that would run a second instance of it.
exit(result);
return qfalse;
}
}
return qtrue;
}
//works with the --dllwrapper engine argument
int NATIVEEXPORT CefSubprocessInit(void)
{
return Cef_Init(false);
}
static qintptr_t Cef_ExecuteCommand(qintptr_t *args)
{
char cmd[256];
cmdfuncs->Argv(0, cmd, sizeof(cmd));
if (!strcmp(cmd, "cef"))
{
if (confuncs)
{
static int sequence;
char f[128];
char videomap[8192];
Q_snprintf(f, sizeof(f), "libcef:%i", ++sequence);
newconsole = f;
strcpy(videomap, "cef:");
cmdfuncs->Argv(1, videomap+4, sizeof(videomap)-4);
if (!videomap[4])
strcpy(videomap, "cef:http://fte.triptohell.info");
confuncs->SetConsoleString(f, "title", videomap+4);
confuncs->SetConsoleFloat(f, "iswindow", true);
confuncs->SetConsoleFloat(f, "forceutf8", true);
confuncs->SetConsoleFloat(f, "wnd_w", 640+16);
confuncs->SetConsoleFloat(f, "wnd_h", 480+16+8);
confuncs->SetConsoleString(f, "backvideomap", videomap);
confuncs->SetConsoleFloat(f, "linebuffered", 2);
confuncs->SetActive(f);
newconsole = NULL;
}
return true;
}
return false;
}
qboolean Plug_Init(void)
{
fsfuncs = (plugfsfuncs_t*)plugfuncs->GetEngineInterface(plugfsfuncs_name, sizeof(*fsfuncs)); //for fte://data/ scheme
confuncs = (plugsubconsolefuncs_t*)plugfuncs->GetEngineInterface(plugsubconsolefuncs_name, sizeof(*confuncs)); //for cef command etc.
clientfuncs = (plugclientfuncs_t*)plugfuncs->GetEngineInterface(plugclientfuncs_name, sizeof(*clientfuncs)); //for weird people trying to use xml requests to query game status (for hud stuff)
if (!fsfuncs || !confuncs || !clientfuncs
|| !plugfuncs->ExportFunction("Tick", Cef_Tick)
|| !plugfuncs->ExportFunction("Shutdown", Cef_Shutdown))
{
Con_Printf("CEF plugin failed: Required engine feature missing.\n");
return false;
}
decoderfuncs.structsize = sizeof(media_decoder_funcs_t);
decoderfuncs.drivername = "cef";
decoderfuncs.createdecoder = Cef_CreateOld;
// decoderfuncs.createdecoderCB = Cef_Create;
decoderfuncs.decodeframe = Cef_DisplayFrame;
decoderfuncs.shutdown = Cef_Destroy;
decoderfuncs.cursormove = Cef_CursorMove;
decoderfuncs.key = Cef_Key;
decoderfuncs.setsize = Cef_SetSize;
decoderfuncs.getsize = Cef_GetSize;
decoderfuncs.changestream = Cef_ChangeStream;
decoderfuncs.getproperty = Cef_GetProperty;
if (!plugfuncs->ExportInterface("Media_VideoDecoder", &decoderfuncs, sizeof(decoderfuncs)))
{
Con_Printf("CEF plugin failed: Engine doesn't support media decoder plugins\n");
return false;
}
if (plugfuncs->ExportFunction("ExecuteCommand", Cef_ExecuteCommand))
cmdfuncs->AddCommand("cef");
cef_incognito = cvarfuncs->GetNVFDG("cef_incognito", "0", 0, NULL, "browser settings");
cef_allowplugins = cvarfuncs->GetNVFDG("cef_allowplugins", "0", 0, NULL, "browser settings");
cef_allowcvars = cvarfuncs->GetNVFDG("cef_allowcvars", "0", 0, NULL, "browser settings");
cef_devtools = cvarfuncs->GetNVFDG("cef_devtools", "0", 0, NULL, "browser settings");
return true;
}