583 lines
16 KiB
C++
583 lines
16 KiB
C++
#include <malloc.h>
|
|
#include <chrono>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#include <filesystem>
|
|
#else
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <spawn.h>
|
|
#include <filesystem>
|
|
#include <sys/types.h>
|
|
#include <pwd.h>
|
|
#endif
|
|
|
|
#include <fcntl.h>
|
|
#include "rktwebview.h"
|
|
#include "rkt_protocol.h"
|
|
#include "shmqueue.h"
|
|
#include "json.h"
|
|
#include "utils.h"
|
|
|
|
#define SHM_SIZE (10 * 1024 * 1024) // 10MB
|
|
#define COMMAND_SLOT 1
|
|
#define COMMAND_RESULT_SLOT 2
|
|
#define EVENT_SLOT 3
|
|
|
|
//#define DEBUG
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// Utility functions
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
typedef struct {
|
|
std::string name;
|
|
size_t shm_size;
|
|
Shm *shm;
|
|
ShmQueue *command_queue;
|
|
ShmQueue *command_result_queue;
|
|
ShmQueue *event_queue;
|
|
#ifdef _WIN32
|
|
HANDLE rkt_webview_prg_pid;
|
|
#else
|
|
pid_t rkt_webview_prg_pid;
|
|
#endif
|
|
|
|
bool rkt_webview_prg_started;
|
|
bool valid;
|
|
} Handle_t;
|
|
|
|
Handle_t *handler = nullptr;
|
|
|
|
static bool fileExists(const char *filename) {
|
|
#ifdef _WIN32
|
|
DWORD dwAttrib = GetFileAttributes(filename);
|
|
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
|
|
!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
|
|
#else
|
|
return access(filename, X_OK) != -1;
|
|
#endif
|
|
}
|
|
|
|
uint64_t current_ms() {
|
|
using namespace std::chrono;
|
|
return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
bool runRktWebview(Handle_t *handler)
|
|
{
|
|
char *rkt_webview_prg_path = getenv("RKT_WEBVIEW_PRG");
|
|
if (rkt_webview_prg_path == nullptr) {
|
|
ERROR0("RKT_WEBVIEW_PRG environment variable not set, cannot start webview program\n");
|
|
return false;
|
|
}
|
|
if (!fileExists(rkt_webview_prg_path)) {
|
|
ERROR1("%s does not exist or is not executable\n", rkt_webview_prg_path);
|
|
return false;
|
|
}
|
|
|
|
std::string path = basedir(rkt_webview_prg_path);
|
|
|
|
char shm_size_str[30];
|
|
char command_slot[10];
|
|
char command_result_slot[10];
|
|
char event_slot[10];
|
|
|
|
sprintf(shm_size_str, "%d", static_cast<int>(handler->shm_size));
|
|
sprintf(command_slot, "%d", COMMAND_SLOT);
|
|
sprintf(command_result_slot, "%d", COMMAND_RESULT_SLOT);
|
|
sprintf(event_slot, "%d", EVENT_SLOT);
|
|
|
|
// run rktwebview_prg using the environment variable RKT_WEBVIEW_PRG
|
|
#ifdef _WIN32
|
|
|
|
const char *td = getenv("TEMP");
|
|
if (td == nullptr) { td = "C:\\"; }
|
|
std::string tmpdir = td;
|
|
std::string logfile = tmpdir + "\\" + "rktwebview.log"; //handler->name + ".log";
|
|
|
|
SECURITY_ATTRIBUTES sa;
|
|
sa.nLength = sizeof(sa);
|
|
sa.lpSecurityDescriptor = NULL;
|
|
sa.bInheritHandle = TRUE;
|
|
|
|
HANDLE h = CreateFile(logfile.c_str(),
|
|
0, //FILE_APPEND_DATA,
|
|
FILE_SHARE_WRITE | FILE_SHARE_READ,
|
|
&sa,
|
|
OPEN_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
|
|
STARTUPINFO si;
|
|
PROCESS_INFORMATION pi;
|
|
ZeroMemory( &si, sizeof(si) );
|
|
ZeroMemory( &pi, sizeof(pi) );
|
|
|
|
si.cb = sizeof(si);
|
|
si.dwFlags |= STARTF_USESTDHANDLES;
|
|
si.hStdInput= NULL;
|
|
si.hStdOutput = h;
|
|
si.hStdError = h;
|
|
|
|
DWORD flags = CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS;
|
|
|
|
std::string cmdargs = std::string("") + handler->name + " " + shm_size_str + " " + command_slot + " " + command_result_slot + " " + event_slot;
|
|
std::string exe = std::string(rkt_webview_prg_path);
|
|
std::string dir = basedir(exe);
|
|
|
|
std::string cmdline = exe + " " + cmdargs;
|
|
|
|
char *cmdline_str = const_cast<char *>(cmdline.c_str());
|
|
|
|
bool r = CreateProcessA(NULL, cmdline_str, NULL, NULL, TRUE, flags, NULL, path.c_str(), &si, &pi);
|
|
if (!r) {
|
|
ERROR2("Cannot create process '%s' (error = %ld)\n", cmdline_str, GetLastError());
|
|
}
|
|
return r;
|
|
#else
|
|
char *argv[] = { rkt_webview_prg_path, const_cast<char *>(handler->name.c_str()), shm_size_str, command_slot, command_result_slot, event_slot, nullptr };
|
|
|
|
struct passwd *pw = getpwuid(getuid());
|
|
const char *homedir = pw->pw_dir;
|
|
|
|
std::string log_file = std::string(homedir) + "/.racket-webview.log";
|
|
|
|
posix_spawn_file_actions_t action;
|
|
posix_spawn_file_actions_init(&action);
|
|
posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, log_file.c_str(), O_CREAT|O_WRONLY, 0600);
|
|
posix_spawn_file_actions_addopen(&action, STDERR_FILENO, log_file.c_str(), O_WRONLY|O_APPEND, 0600);
|
|
|
|
int r = posix_spawn(&handler->rkt_webview_prg_pid, rkt_webview_prg_path, &action, nullptr, argv, environ);
|
|
|
|
posix_spawn_file_actions_destroy(&action);
|
|
|
|
return (r == 0);
|
|
#endif
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// Main C Interface
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
void rkt_webview_cleanup()
|
|
{
|
|
if (handler != nullptr) {
|
|
// Does nothing.
|
|
// See QTBUG-145033
|
|
// QtWebEngine cannot be started as part of QApplication more than once in an application run.
|
|
// So we would need to cleanup at exit of racket/drracket.
|
|
// Cleaning up when exiting the current custodian is not enough.
|
|
|
|
/*
|
|
QApplication *app = handler->app();
|
|
if (app != nullptr) {
|
|
// testing with #:custodian (current-custodian)
|
|
QTimer t;
|
|
QObject::connect(&t, &QTimer::timeout, app, [app]() {
|
|
fprintf(stderr, "Quitting\n");
|
|
app->quit();
|
|
});
|
|
t.setSingleShot(true);
|
|
t.start(250);
|
|
fprintf(stderr, "Executing app\n");
|
|
app->exec();
|
|
fprintf(stderr, "App exited\n");
|
|
delete handler;
|
|
handler = nullptr;
|
|
}
|
|
*/
|
|
|
|
if (handler->valid) {
|
|
INFO0("Sending quit message\n");
|
|
handler->command_queue->enqueue(CMD_QUIT);
|
|
INFO0("Message sent\n");
|
|
bool stopped = false;
|
|
while(!stopped) {
|
|
int cmd;
|
|
std::string s;
|
|
INFO0("Getting result of quit message\n");
|
|
handler->command_result_queue->dequeue(cmd, s, true);
|
|
INFO1("got %d\n", cmd);
|
|
if (cmd == RESULT_QUIT) {
|
|
stopped = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
delete handler->event_queue;
|
|
delete handler->command_result_queue;
|
|
delete handler->command_queue;
|
|
delete handler->shm;
|
|
delete handler;
|
|
handler = nullptr;
|
|
}
|
|
}
|
|
|
|
void rkt_webview_init()
|
|
{
|
|
if (handler == nullptr) {
|
|
// Create shared memory and communication queues
|
|
|
|
char buf[1024];
|
|
#ifdef DEBUG
|
|
sprintf(buf, "rktwebview-dbg");
|
|
#else
|
|
#ifdef _WIN32
|
|
DWORD p = GetCurrentProcessId();
|
|
sprintf(buf, "rktwebview-%ld", p);
|
|
#else
|
|
pid_t p = getpid();
|
|
sprintf(buf, "rktwebview-%x", p);
|
|
#endif
|
|
#endif
|
|
|
|
handler = new Handle_t;
|
|
handler->valid = true;
|
|
handler->name = buf;
|
|
|
|
handler->shm_size = SHM_SIZE;
|
|
handler->shm = new Shm(handler->name.data(), handler->shm_size, true);
|
|
if (!handler->shm->isValid()) { handler->valid = false; }
|
|
handler->command_queue = new ShmQueue(handler->shm, COMMAND_SLOT, true);
|
|
handler->command_result_queue = new ShmQueue(handler->shm, COMMAND_RESULT_SLOT, true);
|
|
handler->event_queue = new ShmQueue(handler->shm, EVENT_SLOT, true);
|
|
|
|
// Start rktwebview_prg application with the right information
|
|
#ifndef DEBUG
|
|
handler->rkt_webview_prg_started = runRktWebview(handler);
|
|
if (!handler->rkt_webview_prg_started) { handler->valid = false; }
|
|
#endif
|
|
}
|
|
}
|
|
|
|
bool rkt_webview_valid(rktwebview_t wv)
|
|
{
|
|
rkt_webview_init();
|
|
if (handler != nullptr && handler->valid) {
|
|
handler->command_queue->enqueue(CMD_HANDLE_IS_VALID);
|
|
int result;
|
|
std::string json_result;
|
|
handler->command_result_queue->dequeue(result, json_result, true);
|
|
return result == 0;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static inline bool validHandle()
|
|
{
|
|
return (handler != nullptr) && handler->valid;
|
|
}
|
|
|
|
#define FAIL_HANDLE if (!validHandle()) { return result_t::invalid_handle; }
|
|
#define FAIL_CONTEXT if (!validHandle()) { return 0; }
|
|
#define FAIL_WV if (!validHandle()) { return 0; }
|
|
#define NOOP_HANDLE if (!validHandle()) { return; }
|
|
#define FAIL_CALL_JS if (!validHandle()) { return nullptr; }
|
|
|
|
rkt_wv_context_t rkt_webview_new_context(const char *boilerplate_js, const char *optional_server_cert_pem)
|
|
{
|
|
rkt_webview_init();
|
|
FAIL_CONTEXT
|
|
|
|
JSON j;
|
|
|
|
std::string bpj(boilerplate_js);
|
|
bool has_pem_cert = optional_server_cert_pem != nullptr;
|
|
std::string osc(has_pem_cert ? optional_server_cert_pem : "");
|
|
|
|
j["boilerplate_js"] = bpj;
|
|
j["has_pem_cert"] = has_pem_cert;
|
|
j["pem_cert"] = osc;
|
|
|
|
handler->command_queue->enqueue(CMD_CONTEXT_NEW, j.dump());
|
|
|
|
int result;
|
|
std::string json_result;
|
|
handler->command_result_queue->dequeue(result, json_result, true);
|
|
return result;
|
|
}
|
|
|
|
int rkt_webview_create(rkt_wv_context_t context, rktwebview_t parent)
|
|
{
|
|
rkt_webview_init();
|
|
FAIL_WV
|
|
|
|
JSON j;
|
|
j["context"] = context;
|
|
j["parent"] = parent;
|
|
|
|
handler->command_queue->enqueue(CMD_CREATE_WV, j.dump());
|
|
|
|
int result;
|
|
std::string json_result;
|
|
handler->command_result_queue->dequeue(result, json_result, true);
|
|
|
|
return result;
|
|
}
|
|
|
|
void rkt_webview_close(rktwebview_t wv)
|
|
{
|
|
rkt_webview_init();
|
|
NOOP_HANDLE
|
|
|
|
JSON j;
|
|
j["wv"] = wv;
|
|
handler->command_queue->enqueue(CMD_CLOSE_WV, j.dump());
|
|
}
|
|
|
|
#define CMDRES4(cmd, wv, key, val, key2, val2, key3, val3, key4, val4) \
|
|
rkt_webview_init(); \
|
|
FAIL_HANDLE \
|
|
JSON j; \
|
|
j["wv"] = wv; \
|
|
j[key] = val; \
|
|
j[key2] = val2; \
|
|
j[key3] = val3; \
|
|
j[key4] = val4; \
|
|
handler->command_queue->enqueue(cmd, j.dump()); \
|
|
int result; \
|
|
std::string json_result; \
|
|
handler->command_result_queue->dequeue(result, json_result, true); \
|
|
result_t r = static_cast<result_t>(result); \
|
|
return r;
|
|
|
|
#define CMDRES3(cmd, wv, key, val, key2, val2, key3, val3) \
|
|
CMDRES4(cmd, wv, key, val, key2, val2, key3, val3, "nil3", "none")
|
|
|
|
#define CMDRES2(cmd, wv, key, val, key2, val2) \
|
|
CMDRES3(cmd, wv, key, val, key2, val2, "nil2", "none")
|
|
|
|
#define CMDRES(cmd, wv, key, val) \
|
|
CMDRES2(cmd, wv, key, val, "nil1", "none")
|
|
|
|
#define CMDRES0(cmd, wv) \
|
|
CMDRES(cmd, wv, "nil0", "none")
|
|
|
|
result_t rkt_webview_set_url(rktwebview_t wv, const char *url)
|
|
{
|
|
CMDRES(CMD_SET_URL, wv, "url", url)
|
|
}
|
|
|
|
result_t rkt_webview_set_html(rktwebview_t wv, const char *html)
|
|
{
|
|
CMDRES(CMD_SET_HTML, wv, "html", html)
|
|
}
|
|
|
|
|
|
result_t rkt_webview_run_js(rktwebview_t wv, const char *js)
|
|
{
|
|
CMDRES(CMD_RUN_JS, wv, "js", js)
|
|
}
|
|
|
|
rkt_data_t *rkt_webview_call_js(rktwebview_t wv, const char *js)
|
|
{
|
|
rkt_webview_init();
|
|
FAIL_CALL_JS
|
|
|
|
JSON j;
|
|
j["wv"] = wv;
|
|
j["js"] = std::string(js);
|
|
handler->command_queue->enqueue(CMD_CALL_JS, j.dump());
|
|
|
|
int result;
|
|
std::string json_result;
|
|
handler->command_result_queue->dequeue(result, json_result, true);
|
|
|
|
rkt_data_t *r = new rkt_data_t();
|
|
r->kind = rkt_data_kind_t::js_result;
|
|
r->data.js_result.result = static_cast<result_t>(result);
|
|
r->data.js_result.value = strdup(json_result.c_str());
|
|
|
|
return r;
|
|
}
|
|
|
|
result_t rkt_webview_open_devtools(rktwebview_t wv)
|
|
{
|
|
CMDRES(CMD_OPEN_DEVTOOLS, wv, "nil", "none")
|
|
}
|
|
|
|
/*
|
|
void rkt_webview_process_events(int for_ms)
|
|
{
|
|
rkt_webview_init();
|
|
|
|
uint64_t start_ms = current_ms();
|
|
uint64_t end_ms = start_ms + for_ms;
|
|
|
|
while (current_ms() < end_ms) {
|
|
QThread::usleep(500); // sleep 0.5 ms
|
|
handler->doEvents();
|
|
}
|
|
}
|
|
*/
|
|
|
|
result_t rkt_webview_move(rktwebview_t wv, int x, int y)
|
|
{
|
|
CMDRES2(CMD_MOVE, wv, "x", x, "y", y)
|
|
}
|
|
|
|
|
|
result_t rkt_webview_resize(rktwebview_t wv, int width, int height)
|
|
{
|
|
CMDRES2(CMD_RESIZE, wv, "w", width, "h", height)
|
|
}
|
|
|
|
|
|
result_t rkt_webview_hide(rktwebview_t w)
|
|
{
|
|
CMDRES0(CMD_HIDE, w)
|
|
}
|
|
|
|
result_t rkt_webview_show(rktwebview_t w)
|
|
{
|
|
CMDRES0(CMD_SHOW, w)
|
|
}
|
|
|
|
result_t rkt_webview_present(rktwebview_t w)
|
|
{
|
|
CMDRES0(CMD_PRESENT, w)
|
|
}
|
|
|
|
result_t rkt_webview_maximize(rktwebview_t w)
|
|
{
|
|
CMDRES0(CMD_MAXIMIZE, w)
|
|
}
|
|
|
|
result_t rkt_webview_minimize(rktwebview_t w)
|
|
{
|
|
CMDRES0(CMD_MINIMIZE, w)
|
|
}
|
|
|
|
result_t rkt_webview_show_normal(rktwebview_t w)
|
|
{
|
|
CMDRES0(CMD_SHOW_NORMAL, w)
|
|
}
|
|
|
|
void rkt_webview_set_loglevel(rkt_webview_loglevel_t l)
|
|
{
|
|
auto f = [l]() {
|
|
CMDRES0(CMD_SET_LOGLEVEL, static_cast<int>(l));
|
|
};
|
|
f();
|
|
}
|
|
|
|
|
|
window_state_t rkt_webview_window_state(rktwebview_t w)
|
|
{
|
|
auto f = [w]() {
|
|
CMDRES0(CMD_WINDOW_STATE, w)
|
|
};
|
|
int r = f();
|
|
return static_cast<window_state_t>(r);
|
|
}
|
|
|
|
result_t rkt_webview_set_title(rktwebview_t wv, const char *title)
|
|
{
|
|
CMDRES(CMD_SET_TITLE, wv, "title", title)
|
|
}
|
|
|
|
result_t rkt_webview_choose_dir(rktwebview_t w, const char *title, const char *base_dir)
|
|
{
|
|
CMDRES2(CMD_CHOOSE_DIR, w, "title", title, "base_dir", base_dir)
|
|
}
|
|
|
|
result_t rkt_webview_file_open(rktwebview_t w, const char *title, const char *base_dir, const char *permitted_exts)
|
|
{
|
|
CMDRES3(CMD_FILE_OPEN, w, "title", title, "base_dir", base_dir, "permitted_exts", permitted_exts)
|
|
}
|
|
|
|
result_t rkt_webview_file_save(rktwebview_t w, const char *title, const char *base_dir, const char *permitted_exts)
|
|
{
|
|
CMDRES3(CMD_FILE_SAVE, w, "title", title, "base_dir", base_dir, "permitted_exts", permitted_exts)
|
|
}
|
|
|
|
void rkt_webview_set_ou_token(rktwebview_t wv, const char *token)
|
|
{
|
|
rkt_webview_init();
|
|
NOOP_HANDLE
|
|
|
|
JSON j;
|
|
j["wv"] = wv;
|
|
j["token"] = std::string(token);
|
|
|
|
handler->command_queue->enqueue(CMD_SET_OU_TOKEN, j.dump());
|
|
}
|
|
|
|
void rkt_webview_free_data(rkt_data_t *d)
|
|
{
|
|
if (d == nullptr) { return; }
|
|
|
|
if (d->kind == version) {
|
|
free(d);
|
|
} else if (d->kind == event) {
|
|
free(d->data.event.event);
|
|
free(d);
|
|
} else if (d->kind == js_result) {
|
|
free(d->data.js_result.value);
|
|
free(d);
|
|
} else {
|
|
ERROR1("UNEXPECTED: data kind %d cannot be freed\n", d->kind);
|
|
}
|
|
}
|
|
|
|
rkt_data_t *rkt_webview_version()
|
|
{
|
|
rkt_data_t *d = static_cast<rkt_data_t *>(malloc(sizeof(rkt_data_t)));
|
|
d->kind = version;
|
|
d->data.version.api_major = RKT_WEBVIEW_API_MAJOR;
|
|
d->data.version.api_minor = RKT_WEBVIEW_API_MINOR;
|
|
d->data.version.api_patch = RKT_WEBVIEW_API_PATCH;
|
|
return d;
|
|
}
|
|
|
|
result_t rkt_webview_message_box(rktwebview_t w, const char *title, const char *message, const char *submessage, rkt_messagetype_t type)
|
|
{
|
|
CMDRES4(CMD_MSG_BOX, w, "title", title, "message", message, "submessage", submessage, "type", static_cast<int>(type))
|
|
}
|
|
|
|
int rkt_webview_events_waiting()
|
|
{
|
|
return handler->event_queue->depth();
|
|
}
|
|
|
|
rkt_data_t *rkt_webview_get_event()
|
|
{
|
|
//fprintf(stderr, "rkt_webview_get_event\n");
|
|
int wv;
|
|
std::string data;
|
|
if (handler->event_queue->dequeue(wv, data, false)) {
|
|
//fprintf(stderr, "got event %d %s\n", wv, data.c_str());
|
|
rkt_data_t *d = reinterpret_cast<rkt_data_t *>(malloc(sizeof(rkt_data_t)));
|
|
d->kind = rkt_data_kind_t::event;
|
|
|
|
size_t ds = data.size();
|
|
d->data.event.event = reinterpret_cast<char *>(malloc(ds + 1));
|
|
memcpy(d->data.event.event, data.c_str(), ds);
|
|
d->data.event.event[ds] = '\0';
|
|
d->data.event.wv = wv;
|
|
//fprintf(stderr, "event: %d - '%s'\n", d->data.event.wv, d->data.event.event);fflush(stderr);
|
|
return d;
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
void rkt_webview_env(const char *env_cmds[])
|
|
{
|
|
int i;
|
|
for(i = 0; env_cmds[i] != nullptr && strcmp(env_cmds[i], "") != 0; i++) {
|
|
#ifdef WIN32
|
|
_putenv(env_cmds[i]);
|
|
#else
|
|
putenv(const_cast<char *>(env_cmds[i]));
|
|
#endif
|
|
}
|
|
}
|
|
|