/** * MojoSetup; a portable, flexible installation application. * * Please see the file LICENSE.txt in the source's root directory. * * This file written by Ryan C. Gordon. * Copyright (c) 2006-2010 Ryan C. Gordon and others. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Ryan C. Gordon * */ #if !SUPPORT_GUI_WWW #error Something is wrong in the build system. #endif #define BUILDING_EXTERNAL_PLUGIN 1 #include "gui.h" MOJOGUI_PLUGIN(www) #if !GUI_STATIC_LINK_WWW CREATE_MOJOGUI_ENTRY_POINT(www) #endif #include #define FREE_AND_NULL(x) { free(x); x = NULL; } // tapdance between things WinSock and BSD Sockets define differently... #if PLATFORM_WINDOWS #include typedef int socklen_t; #define setprotoent(x) assert(x == 0) #define sockErrno() WSAGetLastError() #define wouldBlockError(err) (err == WSAEWOULDBLOCK) #define intrError(err) (err == WSAEINTR) static inline void setBlocking(SOCKET s, boolean blocking) { u_long val = (blocking) ? 0 : 1; ioctlsocket(s, FIONBIO, &val); } // setBlocking static const char *sockStrErrVal(int val) { STUBBED("Windows strerror"); return "sockStrErrVal() is unimplemented."; } // sockStrErrVal static boolean initSocketSupport(void) { WSADATA data; int rc = WSAStartup(MAKEWORD(1, 1), &data); if (rc != 0) { logError("www: WSAStartup() failed: %0", sockStrErrVal(rc)); return false; } // if logInfo("www: WinSock initialized (want %0.%1, got %2.%3).", numstr((int) (LOBYTE(data.wVersion))), numstr((int) (HIBYTE(data.wVersion))), numstr((int) (LOBYTE(data.wHighVersion))), numstr((int) (HIBYTE(data.wHighVersion)))); logInfo("www: WinSock description: %0", data.szDescription); logInfo("www: WinSock system status: %0", data.szSystemStatus); logInfo("www: WinSock max sockets: %0", numstr((int) data.iMaxSockets)); return true; } // initSocketSupport #define deinitSocketSupport() WSACleanup() #else #include #include #include #include #include #include #include #include #include typedef int SOCKET; #define SOCKET_ERROR (-1) #define INVALID_SOCKET (-1) #define closesocket(x) close(x) #define sockErrno() (errno) #define sockStrErrVal(val) strerror(val) #define intrError(err) (err == EINTR) #define initSocketSupport() (true) #define deinitSocketSupport() static inline boolean wouldBlockError(int err) { return ((err == EWOULDBLOCK) || (err == EAGAIN)); } // wouldBlockError static void setBlocking(SOCKET s, boolean blocking) { int flags = fcntl(s, F_GETFL, 0); if (blocking) flags &= ~O_NONBLOCK; else flags |= O_NONBLOCK; fcntl(s, F_SETFL, flags); } // setBlocking #endif #define sockStrError() (sockStrErrVal(sockErrno())) typedef struct _S_WebRequest { char *key; char *value; struct _S_WebRequest *next; } WebRequest; static char *output = NULL; static char *lastProgressType = NULL; static char *lastComponent = NULL; static char *baseUrl = NULL; static WebRequest *webRequest = NULL; static uint32 percentTicks = 0; static SOCKET listenSocket = INVALID_SOCKET; static SOCKET clientSocket = INVALID_SOCKET; static uint8 MojoGui_www_priority(boolean istty) { return MOJOGUI_PRIORITY_TRY_LAST; } // MojoGui_www_priority static void freeWebRequest(void) { while (webRequest) { WebRequest *next = webRequest->next; free(webRequest->key); free(webRequest->value); free(webRequest); webRequest = next; } // while } // freeWebRequest static void addWebRequest(const char *key, const char *val) { if ((key != NULL) && (*key != '\0')) { WebRequest *req = (WebRequest *) xmalloc(sizeof (WebRequest)); req->key = xstrdup(key); req->value = xstrdup(val); req->next = webRequest; webRequest = req; logDebug("www: request element '%0' = '%1'", key, val); } // if } // addWebRequest static int hexVal(char ch) { if ((ch >= 'a') && (ch <= 'f')) return (ch - 'a') + 10; else if ((ch >= 'A') && (ch <= 'F')) return (ch - 'A') + 10; else if ((ch >= '0') && (ch <= '9')) return (ch - '0'); return -1; } // hexVal static void unescapeUri(char *uri) { char *ptr = uri; while ((ptr = strchr(ptr, '%')) != NULL) { int a, b; if ((a = hexVal(ptr[1])) != -1) { if ((b = hexVal(ptr[2])) != -1) { *(ptr++) = (char) ((a * 16) + b); memmove(ptr, ptr+2, strlen(ptr+1)); } // if else { *(ptr++) = '?'; memmove(ptr, ptr+1, strlen(ptr)); } // else } // if else { *(ptr++) = '?'; } // else } // while } // unescapeUri static int strAdd(char **ptr, size_t *len, size_t *alloc, const char *fmt, ...) { size_t bw = 0; size_t avail = *alloc - *len; va_list ap; va_start(ap, fmt); bw = vsnprintf(*ptr + *len, avail, fmt, ap); va_end(ap); if (bw >= avail) { const size_t add = (*alloc + (bw + 1)); // double plus the new len. *alloc += add; avail += add; *ptr = xrealloc(*ptr, *alloc); va_start(ap, fmt); bw = vsnprintf(*ptr + *len, avail, fmt, ap); va_end(ap); } // if *len += bw; return bw; } // strAdd static char *htmlescape(const char *str) { size_t len = 0, alloc = 0; char *retval = NULL; char ch; while ((ch = *(str++)) != '\0') { switch (ch) { case '&': strAdd(&retval, &len, &alloc, "&"); break; case '<': strAdd(&retval, &len, &alloc, "<"); break; case '>': strAdd(&retval, &len, &alloc, ">"); break; case '"': strAdd(&retval, &len, &alloc, """); break; case '\'': strAdd(&retval, &len, &alloc, "'"); break; default: strAdd(&retval, &len, &alloc, "%c", ch); break; } // switch } // while return retval; } // htmlescape static const char *standardResponseHeaders = "Content-Type: text/html; charset=utf-8\n" "Accept-Ranges: none\n" "Cache-Control: no-cache\n" "Connection: close\n\n"; static void setHtmlString(char **str, int responseCode, const char *responseString, const char *title, const char *html) { size_t len = 0, alloc = 0; FREE_AND_NULL(*str); strAdd(str, &len, &alloc, "HTTP/1.1 %d %s\n" // responseCode, responseString "%s" // standardResponseHeaders "" "" "%s" // title "" "%s" // html "\n", responseCode, responseString, standardResponseHeaders, title, html); } // setHtmlString static void setHtml(const char *title, const char *html) { setHtmlString(&output, 200, "OK", title, html); } // setHtml static void sendStringAndDrop(SOCKET *_s, const char *str) { SOCKET s = *_s; int outlen = 0; if (str == NULL) str = ""; else outlen = strlen(str); setBlocking(s, true); while (outlen > 0) { int rc = send(s, str, outlen, 0); if (rc != SOCKET_ERROR) { str += rc; outlen -= rc; } // if else { const int err = sockErrno(); if (!intrError(err)) { logError("www: send() failed: %0", sockStrErrVal(err)); break; } // if } // else } // while closesocket(s); *_s = INVALID_SOCKET; } // sendStringAndDrop static void respond404(SOCKET *s) { char *text = htmlescape(_("Not Found")); char *str = NULL; size_t len = 0, alloc = 0; char *html = NULL; strAdd(&html, &len, &alloc, "

%s

", text); setHtmlString(&str, 404, text, text, html); free(html); free(text); sendStringAndDrop(s, str); free(str); } // respond404 static boolean parseGet(char *get) { char *uri = NULL; char *ver = NULL; uri = strchr(get, ' '); if (uri == NULL) return false; *(uri++) = '\0'; ver = strchr(uri, ' '); if (ver == NULL) return false; *(ver++) = '\0'; if (strcmp(get, "GET") != 0) return false; if (uri[0] != '/') return false; uri++; // skip dirsep. // !!! FIXME: we may want to feed stock files ( tags, etc) // !!! FIXME: at some point in the future. if ((uri[0] != '?') && (uri[0] != '\0')) return false; if (strncmp(ver, "HTTP/", 5) != 0) return false; if (*uri == '?') uri++; // skip initial argsep. do { char *next = strchr(uri, '&'); char *val = NULL; if (next != NULL) *(next++) = '\0'; val = strchr(uri, '='); if (val == NULL) val = ""; else *(val++) = '\0'; unescapeUri(uri); unescapeUri(val); addWebRequest(uri, val); uri = next; } while (uri != NULL); return true; } // parseGet static boolean parseRequest(char *reqstr) { do { char *next = strchr(reqstr, '\n'); char *val = NULL; if (next != NULL) *(next++) = '\0'; val = strchr(reqstr, ':'); if (val == NULL) val = ""; else { *(val++) = '\0'; while (*val == ' ') val++; } // else if (*reqstr != '\0') { size_t len = 0, alloc = 0; char *buf = NULL; strAdd(&buf, &len, &alloc, "HTTP-%s", reqstr); addWebRequest(buf, val); free(buf); } // if reqstr = next; } while (reqstr != NULL); return true; } // parseRequest static WebRequest *servePage(boolean blocking) { int newline = 0; char ch = 0; struct sockaddr_in addr; socklen_t addrlen = 0; int s = 0; char *reqstr = NULL; size_t len = 0, alloc = 0; int err = 0; freeWebRequest(); if (listenSocket == INVALID_SOCKET) return NULL; if (clientSocket != INVALID_SOCKET) // response to feed to client. sendStringAndDrop(&clientSocket, output); if (blocking) setBlocking(listenSocket, true); do { s = accept(listenSocket, (struct sockaddr *) &addr, &addrlen); err = sockErrno(); } while ( (s == INVALID_SOCKET) && (intrError(err)) ); if (blocking) setBlocking(listenSocket, false); // reset what we toggled up there. if (s == INVALID_SOCKET) { if (wouldBlockError(err)) assert(!blocking); else { logError("www: accept() failed: %0", sockStrErrVal(err)); closesocket(listenSocket); // make all future i/o fail too. listenSocket = INVALID_SOCKET; } // else return NULL; } // if setBlocking(s, true); // Doing this one char at a time isn't efficient, but it's easy. while (1) { if (recv(s, &ch, 1, 0) == SOCKET_ERROR) { const int err = sockErrno(); if (!intrError(err)) // just try again on interrupt. { logError("www: recv() failed: %0", sockStrErrVal(err)); FREE_AND_NULL(reqstr); closesocket(s); s = INVALID_SOCKET; break; } // if } // if else if (ch == '\n') // newline { if (++newline == 2) break; // end of request. strAdd(&reqstr, &len, &alloc, "\n"); } // if else if (ch != '\r') { newline = 0; strAdd(&reqstr, &len, &alloc, "%c", ch); } // else if } // while if (reqstr != NULL) { char *get = NULL; char *ptr = strchr(reqstr, '\n'); if (ptr != NULL) { *ptr = '\0'; ptr++; } // if // reqstr is the GET (or whatever) request, ptr is the rest. get = xstrdup(reqstr); if (ptr == NULL) { *ptr = '\0'; len = 0; } // if else { len = strlen(ptr); memmove(reqstr, ptr, len+1); } // else logDebug("www: request '%0'", get); // okay, now (get) and (reqptr) are separate strings. // These parse*() functions update (webRequest). if ( (parseGet(get)) && (parseRequest(reqstr)) ) logDebug("www: accepted request"); else { logError("www: rejected bogus request"); freeWebRequest(); respond404(&s); } // else free(reqstr); free(get); } // if clientSocket = s; return webRequest; } // servePage static SOCKET create_listen_socket(short portnum) { SOCKET s = INVALID_SOCKET; int protocol = 0; // pray this is right. struct protoent *prot; setprotoent(0); prot = getprotobyname("tcp"); if (prot != NULL) protocol = prot->p_proto; s = socket(PF_INET, SOCK_STREAM, protocol); if (s == INVALID_SOCKET) logInfo("www: socket() failed ('%0')", sockStrError()); else { boolean success = false; struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(portnum); addr.sin_addr.s_addr = INADDR_ANY; // !!! FIXME: bind to localhost. // So we can bind this socket over and over in debug runs... #if ((!defined _NDEBUG) && (!defined NDEBUG)) { int on = 1; setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &on, sizeof (on)); } #endif if (bind(s, (struct sockaddr *) &addr, sizeof (addr)) == SOCKET_ERROR) logError("www: bind() failed ('%0')", sockStrError()); else if (listen(s, 5) == SOCKET_ERROR) logError("www: listen() failed ('%0')", sockStrError()); else { logInfo("www: socket created on port %0", numstr(portnum)); success = true; } // else if (!success) { closesocket(s); s = INVALID_SOCKET; } // if } // if return s; } // create_listen_socket static boolean MojoGui_www_init(void) { size_t len = 0, alloc = 0; short portnum = 7341; // !!! FIXME: try some random ports. percentTicks = 0; if (!initSocketSupport()) { logInfo("www: socket subsystem init failed, use another UI."); return false; } // if listenSocket = create_listen_socket(portnum); if (listenSocket < 0) { logInfo("www: no listen socket, use another UI."); return false; } // if setBlocking(listenSocket, false); strAdd(&baseUrl, &len, &alloc, "http://localhost:%d/", (int) portnum); return true; } // MojoGui_www_init static void MojoGui_www_deinit(void) { // Catch any waiting browser connections...and tell them to buzz off! :) char *donetitle = htmlescape(_("Shutting down...")); char *donetext = htmlescape(_("You can close this browser now.")); size_t len = 0, alloc = 0; char *html = NULL; strAdd(&html, &len, &alloc, "
%s

", donetext); setHtml(donetitle, html); free(html); free(donetitle); free(donetext); while (servePage(false) != NULL) { /* no-op. */ } freeWebRequest(); FREE_AND_NULL(output); FREE_AND_NULL(lastProgressType); FREE_AND_NULL(lastComponent); FREE_AND_NULL(baseUrl); if (clientSocket != INVALID_SOCKET) { closesocket(clientSocket); clientSocket = INVALID_SOCKET; } // if if (listenSocket != INVALID_SOCKET) { closesocket(listenSocket); listenSocket = INVALID_SOCKET; } // if deinitSocketSupport(); } // MojoGui_www_deinit static int doPromptPage(const char *title, const char *text, boolean centertxt, const char *pagename, const char **buttons, const char **locButtons, int bcount) { char *htmltitle = htmlescape(title); boolean sawPage = false; int answer = -1; int i = 0; char *html = NULL; size_t len = 0, alloc = 0; const char *align = ((centertxt) ? " align='center'" : ""); strAdd(&html, &len, &alloc, "
" "
" // pagename "" // pagename "" "%s" // align, text "" "" "" "
", pagename, pagename, align, text); for (i = 0; i < bcount; i++) { const char *button = buttons[i]; const char *loc = locButtons[i]; strAdd(&html, &len, &alloc, "", button, loc); } // for strAdd(&html, &len, &alloc, "
" "
" "
"); setHtml(htmltitle, html); free(htmltitle); free(html); while ((!sawPage) || (answer == -1)) { WebRequest *req = servePage(true); sawPage = false; answer = -1; while (req != NULL) { const char *k = req->key; const char *v = req->value; if ( (strcmp(k, "page") == 0) && (strcmp(v, pagename) == 0) ) sawPage = true; else { for (i = 0; i < bcount; i++) { if (strcmp(k, buttons[i]) == 0) { answer = i; break; } // if } // for } // else req = req->next; } // while } // while return answer; } // doPromptPage static void MojoGui_www_msgbox(const char *title, const char *text) { const char *buttons[] = { "ok" }; const char *locButtons[] = { htmlescape(_("OK")) }; char *htmltext = htmlescape(text); doPromptPage(title, htmltext, true, "msgbox", buttons, locButtons, 1); free(htmltext); free((void *) locButtons[0]); } // MojoGui_www_msgbox static boolean MojoGui_www_promptyn(const char *title, const char *text, boolean defval) { // !!! FIXME: // We currently ignore defval int i, rc; char *htmltext = htmlescape(text); const char *buttons[] = { "no", "yes" }; const char *locButtons[] = { htmlescape(_("No")), htmlescape(_("Yes")) }; assert(STATICARRAYLEN(buttons) == STATICARRAYLEN(locButtons)); rc = doPromptPage(title, htmltext, true, "promptyn", buttons, locButtons, STATICARRAYLEN(buttons)); free(htmltext); for (i = 0; i < STATICARRAYLEN(locButtons); i++) free((void *) locButtons[i]); return (rc == 1); } // MojoGui_www_promptyn static MojoGuiYNAN MojoGui_www_promptynan(const char *title, const char *text, boolean defval) { // !!! FIXME: // We currently ignore defval int i, rc; char *htmltext = htmlescape(text); const char *buttons[] = { "no", "yes", "always", "never" }; const char *locButtons[] = { htmlescape(_("No")), htmlescape(_("Yes")), htmlescape(_("Always")), htmlescape(_("Never")), }; assert(STATICARRAYLEN(buttons) == STATICARRAYLEN(locButtons)); rc = doPromptPage(title, htmltext, true, "promptynan", buttons, locButtons, STATICARRAYLEN(buttons)); free(htmltext); for (i = 0; i < STATICARRAYLEN(locButtons); i++) free((void *) locButtons[i]); return (MojoGuiYNAN) rc; } // MojoGui_www_promptynan static boolean MojoGui_www_start(const char *title, const MojoGuiSplash *splash) { return true; } // MojoGui_www_start static void MojoGui_www_stop(void) { // no-op. } // MojoGui_www_stop static int MojoGui_www_readme(const char *name, const uint8 *data, size_t datalen, boolean can_back, boolean can_fwd) { char *text = NULL; size_t len = 0, alloc = 0; char *htmldata = htmlescape((const char *) data); int i, rc; int cancelbutton = -1; int backbutton = -1; int fwdbutton = -1; int bcount = 0; const char *buttons[4] = { NULL, NULL, NULL, NULL }; const char *locButtons[4] = { NULL, NULL, NULL, NULL }; assert(STATICARRAYLEN(buttons) == STATICARRAYLEN(locButtons)); cancelbutton = bcount++; buttons[cancelbutton] = "cancel"; locButtons[cancelbutton] = xstrdup(_("Cancel")); if (can_back) { backbutton = bcount++; buttons[backbutton] = "back"; locButtons[backbutton] = xstrdup(_("Back")); } // if if (can_fwd) { fwdbutton = bcount++; buttons[fwdbutton] = "next"; locButtons[fwdbutton] = xstrdup(_("Next")); } // if strAdd(&text, &len, &alloc, "
\n%s\n
", htmldata); free(htmldata); rc = doPromptPage(name, text, false, "readme", buttons, locButtons, bcount); free(text); for (i = 0; i < STATICARRAYLEN(locButtons); i++) free((void *) locButtons[i]); if (rc == backbutton) return -1; else if (rc == cancelbutton) return 0; return 1; } // MojoGui_www_readme static int MojoGui_www_options(MojoGuiSetupOptions *opts, boolean can_back, boolean can_fwd) { // !!! FIXME: write me. STUBBED("www options"); return 1; } // MojoGui_www_options static char *MojoGui_www_destination(const char **recommends, int recnum, int *command, boolean can_back, boolean can_fwd) { char *retval = NULL; char *title = xstrdup(_("Destination")); char *html = NULL; size_t len = 0, alloc = 0; boolean checked = true; int cancelbutton = -1; int backbutton = -1; int fwdbutton = -1; int bcount = 0; int rc = 0; int i = 0; const char *buttons[4] = { NULL, NULL, NULL, NULL }; const char *locButtons[4] = { NULL, NULL, NULL, NULL }; assert(STATICARRAYLEN(buttons) == STATICARRAYLEN(locButtons)); cancelbutton = bcount++; buttons[cancelbutton] = "cancel"; locButtons[cancelbutton] = xstrdup(_("Cancel")); if (can_back) { backbutton = bcount++; buttons[backbutton] = "back"; locButtons[backbutton] = xstrdup(_("Back")); } // if if (can_fwd) { fwdbutton = bcount++; buttons[fwdbutton] = "next"; locButtons[fwdbutton] = xstrdup(_("Next")); } // if strAdd(&html, &len, &alloc, "
" ""); for (i = 0; i < recnum; i++) { strAdd(&html, &len, &alloc, "" "" "", ((checked) ? "checked='true'" : ""), recommends[i], recommends[i]); checked = false; } // for strAdd(&html, &len, &alloc, "" "" "" "
" "%s" "
" "" "" "
" "
", ((checked) ? "checked='true'" : "")); rc = doPromptPage(title, html, true, "destination", buttons, locButtons, bcount); free(title); free(html); for (i = 0; i < STATICARRAYLEN(locButtons); i++) free((void *) locButtons[i]); if (rc == backbutton) *command = -1; else if (rc == cancelbutton) *command = 0; else { const char *dest = NULL; const char *customdest = NULL; WebRequest *req = webRequest; while (req != NULL) { const char *k = req->key; const char *v = req->value; if (strcmp(k, "dest") == 0) dest = v; else if (strcmp(k, "customdest") == 0) customdest = v; req = req->next; } // while if (dest != NULL) { if (strcmp(dest, "*") == 0) dest = customdest; } // if if (dest == NULL) *command = 0; // !!! FIXME: maybe loop with doPromptPage again. else { retval = xstrdup(dest); *command = 1; } // else } // else return retval; } // MojoGui_www_destination static int MojoGui_www_productkey(const char *desc, const char *fmt, char *buf, const int buflen, boolean can_back, boolean can_fwd) { char *prompt = xstrdup(_("Please enter your product key")); int retval = -1; char *html = NULL; size_t len = 0, alloc = 0; int cancelbutton = -1; int backbutton = -1; int fwdbutton = -1; int bcount = 0; int rc = 0; int i = 0; const char *buttons[4] = { NULL, NULL, NULL, NULL }; const char *locButtons[4] = { NULL, NULL, NULL, NULL }; assert(STATICARRAYLEN(buttons) == STATICARRAYLEN(locButtons)); cancelbutton = bcount++; buttons[cancelbutton] = "cancel"; locButtons[cancelbutton] = xstrdup(_("Cancel")); if (can_back) { backbutton = bcount++; buttons[backbutton] = "back"; locButtons[backbutton] = xstrdup(_("Back")); } // if if (can_fwd) { fwdbutton = bcount++; buttons[fwdbutton] = "next"; locButtons[fwdbutton] = xstrdup(_("Next")); } // if strAdd(&html, &len, &alloc, "
" "%s
" "" "
", prompt, ((*buf) ? buf : "")); free(prompt); rc = doPromptPage(desc, html, true, "productkey", buttons, locButtons, bcount); free(html); for (i = 0; i < STATICARRAYLEN(locButtons); i++) free((void *) locButtons[i]); if (rc == backbutton) retval = -1; else if (rc == cancelbutton) retval = 0; else { WebRequest *req = webRequest; const char *keyval = NULL; while (req != NULL) { const char *k = req->key; const char *v = req->value; if (strcmp(k, "productkey") == 0) keyval = v; req = req->next; } // while if (keyval == NULL) retval = 0; // !!! FIXME: maybe loop with doPromptPage again. else { snprintf(buf, buflen, "%s", keyval); if (isValidProductKey(fmt, buf)) retval = 1; // !!! FIXME: must try again if invalid key. } // else } // else return retval; } // MojoGui_www_productkey static boolean MojoGui_www_insertmedia(const char *medianame) { char *htmltext = NULL; char *text = NULL; size_t len = 0, alloc = 0; int i, rc; const char *buttons[] = { "cancel", "ok" }; const char *locButtons[] = { htmlescape(_("Cancel")), htmlescape(_("OK")) }; char *title = xstrdup(_("Media change")); char *fmt = xstrdup(_("Please insert '%0'")); char *msg = format(fmt, medianame); strAdd(&text, &len, &alloc, msg); free(msg); free(fmt); htmltext = htmlescape(text); free(text); assert(STATICARRAYLEN(buttons) == STATICARRAYLEN(locButtons)); rc = doPromptPage(title, htmltext, true, "insertmedia", buttons, locButtons, STATICARRAYLEN(buttons)); free(title); free(htmltext); for (i = 0; i < STATICARRAYLEN(locButtons); i++) free((void *) locButtons[i]); return (rc == 1); } // MojoGui_www_insertmedia static void MojoGui_www_progressitem(void) { // no-op in this UI target. } // MojoGui_www_progressitem static boolean MojoGui_www_progress(const char *type, const char *component, int percent, const char *item, boolean can_cancel) { return true; } // MojoGui_www_progress static void MojoGui_www_final(const char *msg) { MojoGui_www_msgbox(_("Finish"), msg); } // MojoGui_www_final // end of gui_www.c ...