#include #include #include #include #ifdef _WIN32 #include #include #else #include #include #endif #include #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(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) { fprintf(stderr, "RKT_WEBVIEW_PRG environment variable not set, cannot start webview program\n");fflush(stderr); return false; } if (!fileExists(rkt_webview_prg_path)) { fprintf(stderr, "%s does not exist or is not executable\n", rkt_webview_prg_path);fflush(stderr); 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(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 //STARTUPINFO si; //PROCESS_INFORMATION pi; //ZeroMemory( &si, sizeof(si) ); //si.cb = sizeof(si); //ZeroMemory( &pi, sizeof(pi) ); 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); const char *td = getenv("TEMP"); if (td == nullptr) { td = "C:\\"; } std::string tmpdir = td; std::string logfile = tmpdir + "\\" + "rktwebview.log"; //handler->name + ".log"; std::string errfile = tmpdir + "\\" + "rktwebview.err"; std::string redirect = " ^>" + logfile + " 2^>" + errfile; std::string cmdline = "start /b \"\" cmd /c \"" + exe + " " + cmdargs + " " + /*redirect*/ + "\"" + redirect; char *cmdline_str = const_cast(cmdline.c_str()); auto cwd = std::filesystem::current_path(); std::filesystem::current_path(path); int r = system(cmdline_str); std::filesystem::current_path(cwd); //CreateProcessA(NULL, cmdline_str, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, path.c_str(), &si, &pi); handler->rkt_webview_prg_pid = 0; if (r != 0) { fprintf(stderr, "Cannot create process '%s' (error = %ld)\n", cmdline_str, GetLastError());fflush(stderr); } return (r == 0); #else char *argv[] = { rkt_webview_prg_path, const_cast(handler->name.c_str()), shm_size_str, command_slot, command_result_slot, event_slot, nullptr }; int r = posix_spawn(&handler->rkt_webview_prg_pid, rkt_webview_prg_path, nullptr, nullptr, argv, environ); 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) { fprintf(stderr, "Sending quit message\n");fflush(stderr); handler->command_queue->enqueue(CMD_QUIT); fprintf(stderr, "Message sent\n");fflush(stderr); bool stopped = false; while(!stopped) { int cmd; std::string s; fprintf(stderr, "Getting result of quit message\n");fflush(stderr); handler->command_result_queue->dequeue(cmd, s, true); fprintf(stderr, "got %d\n", cmd);fflush(stderr); 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); \ 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); 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) } window_state_t rkt_webview_window_state(rktwebview_t w) { auto f = [w]() { CMDRES0(CMD_WINDOW_STATE, w) }; int r = f(); return static_cast(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 { fprintf(stderr, "UNEXPECTED: data kind %d cannot be freed\n", d->kind);fflush(stderr); } } rkt_data_t *rkt_webview_version() { rkt_data_t *d = static_cast(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(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(malloc(sizeof(rkt_data_t))); d->kind = rkt_data_kind_t::event; size_t ds = data.size(); d->data.event.event = reinterpret_cast(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(env_cmds[i]); #endif } }