diff options
Diffstat (limited to 'frontends/riscos/gui.c')
-rw-r--r-- | frontends/riscos/gui.c | 2522 |
1 files changed, 2522 insertions, 0 deletions
diff --git a/frontends/riscos/gui.c b/frontends/riscos/gui.c new file mode 100644 index 000000000..309f27bdb --- /dev/null +++ b/frontends/riscos/gui.c @@ -0,0 +1,2522 @@ +/* + * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> + * Copyright 2004-2008 James Bursa <bursa@users.sourceforge.net> + * Copyright 2003 John M Bell <jmb202@ecs.soton.ac.uk> + * Copyright 2005 Richard Wilson <info@tinct.net> + * Copyright 2004 Andrew Timmins <atimmins@blueyonder.co.uk> + * Copyright 2004-2009 John Tytgat <joty@netsurf-browser.org> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdbool.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <signal.h> +#include <unixlib/local.h> +#include <fpu_control.h> +#include <oslib/help.h> +#include <oslib/uri.h> +#include <oslib/inetsuite.h> +#include <oslib/pdriver.h> +#include <oslib/osfile.h> +#include <oslib/hourglass.h> +#include <oslib/osgbpb.h> +#include <oslib/osbyte.h> +#include <oslib/osmodule.h> +#include <oslib/osfscontrol.h> + +#include "utils/utils.h" +#include "utils/nsoption.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "utils/file.h" +#include "utils/filename.h" +#include "utils/url.h" +#include "utils/corestrings.h" +#include "desktop/gui_fetch.h" +#include "desktop/gui_misc.h" +#include "desktop/save_complete.h" +#include "desktop/treeview.h" +#include "desktop/netsurf.h" +#include "desktop/browser.h" +#include "content/urldb.h" +#include "content/hlcache.h" +#include "content/backing_store.h" + +#include "riscos/gui.h" +#include "riscos/bitmap.h" +#include "riscos/wimputils.h" +#include "riscos/hotlist.h" +#include "riscos/buffer.h" +#include "riscos/textselection.h" +#include "riscos/print.h" +#include "riscos/save.h" +#include "riscos/dialog.h" +#include "riscos/wimp.h" +#include "riscos/message.h" +#include "riscos/help.h" +#include "riscos/query.h" +#include "riscos/window.h" +#include "riscos/iconbar.h" +#include "riscos/sslcert.h" +#include "riscos/global_history.h" +#include "riscos/cookies.h" +#include "riscos/wimp_event.h" +#include "riscos/uri.h" +#include "riscos/url_protocol.h" +#include "riscos/mouse.h" +#include "riscos/ucstables.h" +#include "riscos/filetype.h" +#include "riscos/font.h" +#include "riscos/toolbar.h" +#include "riscos/content-handlers/artworks.h" +#include "riscos/content-handlers/draw.h" +#include "riscos/content-handlers/sprite.h" + +bool riscos_done = false; + +extern bool ro_plot_patterned_lines; + +int os_version = 0; + +const char * const __dynamic_da_name = "NetSurf"; /**< For UnixLib. */ +int __dynamic_da_max_size = 128 * 1024 * 1024; /**< For UnixLib. */ +int __feature_imagefs_is_file = 1; /**< For UnixLib. */ +/* default filename handling */ +int __riscosify_control = __RISCOSIFY_NO_SUFFIX | + __RISCOSIFY_NO_REVERSE_SUFFIX; +#ifndef __ELF__ +extern int __dynamic_num; +#endif + +const char * NETSURF_DIR; + +static const char *task_name = "NetSurf"; +#define CHOICES_PREFIX "<Choices$Write>.WWW.NetSurf." + +ro_gui_drag_type gui_current_drag_type; +wimp_t task_handle; /**< RISC OS wimp task handle. */ +static clock_t gui_last_poll; /**< Time of last wimp_poll. */ +osspriteop_area *gui_sprites; /**< Sprite area containing pointer and hotlist sprites */ + +#define DIR_SEP ('.') + +/** + * Accepted wimp user messages. + */ +static ns_wimp_message_list task_messages = { + message_HELP_REQUEST, + { + message_DATA_SAVE, + message_DATA_SAVE_ACK, + message_DATA_LOAD, + message_DATA_LOAD_ACK, + message_DATA_OPEN, + message_PRE_QUIT, + message_SAVE_DESKTOP, + message_MENU_WARNING, + message_MENUS_DELETED, + message_WINDOW_INFO, + message_CLAIM_ENTITY, + message_DATA_REQUEST, + message_DRAGGING, + message_DRAG_CLAIM, + message_MODE_CHANGE, + message_PALETTE_CHANGE, + message_FONT_CHANGED, + message_URI_PROCESS, + message_URI_RETURN_RESULT, + message_INET_SUITE_OPEN_URL, +#ifdef WITH_PLUGIN + message_PLUG_IN_OPENING, + message_PLUG_IN_CLOSED, + message_PLUG_IN_RESHAPE_REQUEST, + message_PLUG_IN_FOCUS, + message_PLUG_IN_URL_ACCESS, + message_PLUG_IN_STATUS, + message_PLUG_IN_BUSY, + message_PLUG_IN_STREAM_NEW, + message_PLUG_IN_STREAM_WRITE, + message_PLUG_IN_STREAM_WRITTEN, + message_PLUG_IN_STREAM_DESTROY, + message_PLUG_IN_OPEN, + message_PLUG_IN_CLOSE, + message_PLUG_IN_RESHAPE, + message_PLUG_IN_STREAM_AS_FILE, + message_PLUG_IN_NOTIFY, + message_PLUG_IN_ABORT, + message_PLUG_IN_ACTION, + /* message_PLUG_IN_INFORMED, (not provided by oslib) */ +#endif + message_PRINT_SAVE, + message_PRINT_ERROR, + message_PRINT_TYPE_ODD, + message_HOTLIST_ADD_URL, + message_HOTLIST_CHANGED, + 0 + } +}; + + +static struct +{ + int width; /* in OS units */ + int height; +} screen_info; + + +/** + * Callback to translate resource to full url for RISC OS. + * + * Transforms a resource: path into a full URL. The returned URL is + * used as the target for a redirect. The caller takes ownership of + * the returned nsurl including unrefing it when finished with it. + * + * \param path The path of the resource to locate. + * \return A string containing the full URL of the target object or + * NULL if no suitable resource can be found. + */ +static nsurl *gui_get_resource_url(const char *path) +{ + static const char base_url[] = "file:///NetSurf:/Resources/"; + size_t path_len, length; + char *raw; + nsurl *url = NULL; + + /* Map paths first */ + if (strcmp(path, "adblock.css") == 0) { + path = "AdBlock"; + + } else if (strcmp(path, "default.css") == 0) { + path = "CSS"; + + } else if (strcmp(path, "quirks.css") == 0) { + path = "Quirks"; + + } else if (strcmp(path, "favicon.ico") == 0) { + path = "Icons/content.png"; + + } else if (strcmp(path, "user.css") == 0) { + /* Special case; this file comes from Choices: */ + nsurl_create("file:///Choices:WWW/NetSurf/User", &url); + return url; + } + + path_len = strlen(path); + + /* Find max URL length */ + length = SLEN(base_url) + SLEN("xx/") + path_len + 1; + + raw = malloc(length); + if (raw != NULL) { + /* Insert base URL */ + char *ptr = memcpy(raw, base_url, SLEN(base_url)); + ptr += SLEN(base_url); + + /* Add language directory to URL, for translated files */ + /* TODO: handle non-en langauages + * handle non-html translated files */ + if (path_len > SLEN(".html") && + strncmp(path + path_len - SLEN(".html"), + ".html", SLEN(".html")) == 0) { + memcpy(ptr, "en/", SLEN("en/")); + ptr += SLEN("en/"); + } + + /* Add filename to URL */ + memcpy(ptr, path, path_len); + ptr += path_len; + + /* Terminate string */ + *ptr = '\0'; + + nsurl_create(raw, &url); + free(raw); + } + + return url; +} + + +/** + * Set colour option from wimp. + * + * \param opts The netsurf options. + * \param wimp wimp colour value + * \param option the netsurf option enum. + * \param def_colour The default colour value to use. + * \return NSERROR_OK on success or error code. + */ +static nserror +set_colour_from_wimp(struct nsoption_s *opts, + wimp_colour wimp, + enum nsoption_e option, + colour def_colour) +{ + os_error *error; + os_PALETTE(20) palette; + + error = xwimp_read_true_palette((os_palette *) &palette); + if (error != NULL) { + LOG("xwimp_read_palette: 0x%x: %s", + error->errnum, error->errmess); + } else { + /* entries are in B0G0R0LL */ + def_colour = palette.entries[wimp] >> 8; + } + + opts[option].value.c = def_colour; + + return NSERROR_OK; +} + + +/** + * Set option defaults for riscos frontend + * + * @param defaults The option table to update. + * @return error status. + * + * @todo The wimp_COLOUR_... values here map the colour definitions to + * parts of the RISC OS desktop palette. In places this is fairly + * arbitrary, and could probably do with re-checking. + */ +static nserror set_defaults(struct nsoption_s *defaults) +{ + /* Set defaults for absent option strings */ + nsoption_setnull_charp(ca_bundle, strdup("NetSurf:Resources.ca-bundle")); + nsoption_setnull_charp(cookie_file, strdup("NetSurf:Cookies")); + nsoption_setnull_charp(cookie_jar, strdup(CHOICES_PREFIX "Cookies")); + + if (nsoption_charp(ca_bundle) == NULL || + nsoption_charp(cookie_file) == NULL || + nsoption_charp(cookie_jar) == NULL) { + LOG("Failed initialising default options"); + return NSERROR_BAD_PARAMETER; + } + + /* RISC OS platform does not generally benefit from disc cache + * so the default should be off. + */ + nsoption_set_uint(disc_cache_size, 0); + + /* set default system colours for riscos ui */ + set_colour_from_wimp(defaults, wimp_COLOUR_BLACK, NSOPTION_sys_colour_ActiveBorder, 0x00000000); + set_colour_from_wimp(defaults, wimp_COLOUR_CREAM, NSOPTION_sys_colour_ActiveCaption, 0x00dddddd); + set_colour_from_wimp(defaults, wimp_COLOUR_VERY_LIGHT_GREY, NSOPTION_sys_colour_AppWorkspace, 0x00eeeeee); + set_colour_from_wimp(defaults, wimp_COLOUR_VERY_LIGHT_GREY, NSOPTION_sys_colour_Background, 0x00aa0000);/* \TODO -- Check */ + set_colour_from_wimp(defaults, wimp_COLOUR_VERY_LIGHT_GREY, NSOPTION_sys_colour_ButtonFace, 0x00aaaaaa); + set_colour_from_wimp(defaults, wimp_COLOUR_DARK_GREY, NSOPTION_sys_colour_ButtonHighlight, 0x00cccccc);/* \TODO -- Check */ + set_colour_from_wimp(defaults, wimp_COLOUR_MID_DARK_GREY, NSOPTION_sys_colour_ButtonShadow, 0x00bbbbbb); + set_colour_from_wimp(defaults, wimp_COLOUR_BLACK, NSOPTION_sys_colour_ButtonText, 0x00000000); + set_colour_from_wimp(defaults, wimp_COLOUR_BLACK, NSOPTION_sys_colour_CaptionText, 0x00000000); + set_colour_from_wimp(defaults, wimp_COLOUR_MID_LIGHT_GREY, NSOPTION_sys_colour_GrayText, 0x00777777);/* \TODO -- Check */ + set_colour_from_wimp(defaults, wimp_COLOUR_BLACK, NSOPTION_sys_colour_Highlight, 0x00ee0000); + set_colour_from_wimp(defaults, wimp_COLOUR_WHITE, NSOPTION_sys_colour_HighlightText, 0x00000000); + set_colour_from_wimp(defaults, wimp_COLOUR_BLACK, NSOPTION_sys_colour_InactiveBorder, 0x00000000); + set_colour_from_wimp(defaults, wimp_COLOUR_LIGHT_GREY, NSOPTION_sys_colour_InactiveCaption, 0x00ffffff); + set_colour_from_wimp(defaults, wimp_COLOUR_BLACK, NSOPTION_sys_colour_InactiveCaptionText, 0x00cccccc); + set_colour_from_wimp(defaults, wimp_COLOUR_CREAM, NSOPTION_sys_colour_InfoBackground, 0x00aaaaaa); + set_colour_from_wimp(defaults, wimp_COLOUR_BLACK, NSOPTION_sys_colour_InfoText, 0x00000000); + set_colour_from_wimp(defaults, wimp_COLOUR_WHITE, NSOPTION_sys_colour_Menu, 0x00aaaaaa); + set_colour_from_wimp(defaults, wimp_COLOUR_BLACK, NSOPTION_sys_colour_MenuText, 0x00000000); + set_colour_from_wimp(defaults, wimp_COLOUR_LIGHT_GREY, NSOPTION_sys_colour_Scrollbar, 0x00aaaaaa);/* \TODO -- Check */ + set_colour_from_wimp(defaults, wimp_COLOUR_MID_DARK_GREY, NSOPTION_sys_colour_ThreeDDarkShadow, 0x00555555); + set_colour_from_wimp(defaults, wimp_COLOUR_VERY_LIGHT_GREY, NSOPTION_sys_colour_ThreeDFace, 0x00dddddd); + set_colour_from_wimp(defaults, wimp_COLOUR_WHITE, NSOPTION_sys_colour_ThreeDHighlight, 0x00aaaaaa); + set_colour_from_wimp(defaults, wimp_COLOUR_WHITE, NSOPTION_sys_colour_ThreeDLightShadow, 0x00999999); + set_colour_from_wimp(defaults, wimp_COLOUR_MID_DARK_GREY, NSOPTION_sys_colour_ThreeDShadow, 0x00777777); + set_colour_from_wimp(defaults, wimp_COLOUR_VERY_LIGHT_GREY, NSOPTION_sys_colour_Window, 0x00aaaaaa); + set_colour_from_wimp(defaults, wimp_COLOUR_BLACK, NSOPTION_sys_colour_WindowFrame, 0x00000000); + set_colour_from_wimp(defaults, wimp_COLOUR_BLACK, NSOPTION_sys_colour_WindowText, 0x00000000); + + return NSERROR_OK; +} + + + + +/** + * Create intermediate directories for Choices and User Data files + */ +static void ro_gui_create_dirs(void) +{ + char buf[256]; + char *path; + + /* Choices */ + path = getenv("NetSurf$ChoicesSave"); + if (!path) + die("Failed to find NetSurf Choices save path"); + + snprintf(buf, sizeof(buf), "%s", path); + netsurf_mkdir_all(buf); + + /* URL */ + snprintf(buf, sizeof(buf), "%s", nsoption_charp(url_save)); + netsurf_mkdir_all(buf); + + /* Hotlist */ + snprintf(buf, sizeof(buf), "%s", nsoption_charp(hotlist_save)); + netsurf_mkdir_all(buf); + + /* Recent */ + snprintf(buf, sizeof(buf), "%s", nsoption_charp(recent_save)); + netsurf_mkdir_all(buf); + + /* Theme */ + snprintf(buf, sizeof(buf), "%s", nsoption_charp(theme_save)); + netsurf_mkdir_all(buf); + /* and the final directory part (as theme_save is a directory) */ + xosfile_create_dir(buf, 0); +} + + +/** + * Ensures the gui exits cleanly. + */ +static void ro_gui_cleanup(void) +{ + ro_gui_buffer_close(); + xhourglass_off(); + /* Uninstall NetSurf-specific fonts */ + xos_cli("FontRemove NetSurf:Resources.Fonts."); +} + + +/** + * Handles a signal + */ +static void ro_gui_signal(int sig) +{ + static const os_error error = { 1, "NetSurf has detected a serious " + "error and must exit. Please submit a bug report, " + "attaching the browser log file." }; + os_colour old_sand, old_glass; + + ro_gui_cleanup(); + + xhourglass_on(); + xhourglass_colours(0x0000ffff, 0x000000ff, &old_sand, &old_glass); + nsoption_dump(stderr, NULL); + /*rufl_dump_state();*/ + +#ifndef __ELF__ + /* save WimpSlot and DA to files if NetSurf$CoreDump exists */ + int used; + xos_read_var_val_size("NetSurf$CoreDump", 0, 0, &used, 0, 0); + if (used) { + int curr_slot; + xwimp_slot_size(-1, -1, &curr_slot, 0, 0); + LOG("saving WimpSlot, size 0x%x", curr_slot); + xosfile_save("$.NetSurf_Slot", 0x8000, 0, + (byte *) 0x8000, + (byte *) 0x8000 + curr_slot); + + if (__dynamic_num != -1) { + int size; + byte *base_address; + xosdynamicarea_read(__dynamic_num, &size, + &base_address, 0, 0, 0, 0, 0); + LOG("saving DA %i, base %p, size 0x%x", __dynamic_num, base_address, size); + xosfile_save("$.NetSurf_DA", + (bits) base_address, 0, + base_address, + base_address + size); + } + } +#else + /* Save WimpSlot and UnixLib managed DAs when UnixEnv$coredump + * defines a coredump directory. */ + const _kernel_oserror *err = __unixlib_write_coredump (NULL); + if (err != NULL) + LOG("Coredump failed: %s", err->errmess); +#endif + + xhourglass_colours(old_sand, old_glass, 0, 0); + xhourglass_off(); + + __write_backtrace(sig); + + xwimp_report_error_by_category(&error, + wimp_ERROR_BOX_GIVEN_CATEGORY | + wimp_ERROR_BOX_CATEGORY_ERROR << + wimp_ERROR_BOX_CATEGORY_SHIFT, + "NetSurf", "!netsurf", + (osspriteop_area *) 1, "Quit", 0); + xos_cli("Filer_Run <Wimp$ScrapDir>.WWW.NetSurf.Log"); + + _Exit(sig); +} + + +/** + * Read a "line" from an Acorn URI file. + * + * \param fp file pointer to read from + * \param b buffer for line, size 400 bytes + * \return true on success, false on EOF + */ +static bool ro_gui_uri_file_parse_line(FILE *fp, char *b) +{ + int c; + unsigned int i = 0; + + c = getc(fp); + if (c == EOF) + return false; + + /* skip comment lines */ + while (c == '#') { + do { c = getc(fp); } while (c != EOF && 32 <= c); + if (c == EOF) + return false; + do { c = getc(fp); } while (c != EOF && c < 32); + if (c == EOF) + return false; + } + + /* read "line" */ + do { + if (i == 399) + return false; + b[i++] = c; + c = getc(fp); + } while (c != EOF && 32 <= c); + + /* skip line ending control characters */ + while (c != EOF && c < 32) + c = getc(fp); + + if (c != EOF) + ungetc(c, fp); + + b[i] = 0; + return true; +} + + +/** + * Parse an Acorn URI file. + * + * \param file_name file to read + * \param uri_title pointer to receive title data, or NULL for no data + * \return URL from file, or 0 on error and error reported + */ +static char *ro_gui_uri_file_parse(const char *file_name, char **uri_title) +{ + /* See the "Acorn URI Handler Functional Specification" for the + * definition of the URI file format. */ + char line[400]; + char *url = NULL; + FILE *fp; + + *uri_title = NULL; + fp = fopen(file_name, "rb"); + if (!fp) { + LOG("fopen(\"%s\", \"rb\"): %i: %s", file_name, errno, strerror(errno)); + ro_warn_user("LoadError", strerror(errno)); + return 0; + } + + /* "URI" */ + if (!ro_gui_uri_file_parse_line(fp, line) || strcmp(line, "URI") != 0) + goto uri_syntax_error; + + /* version */ + if (!ro_gui_uri_file_parse_line(fp, line) || + strspn(line, "0123456789") != strlen(line)) + goto uri_syntax_error; + + /* URI */ + if (!ro_gui_uri_file_parse_line(fp, line)) + goto uri_syntax_error; + + url = strdup(line); + if (!url) { + ro_warn_user("NoMemory", 0); + fclose(fp); + return 0; + } + + /* title */ + if (!ro_gui_uri_file_parse_line(fp, line)) + goto uri_free; + if (uri_title && line[0] && ((line[0] != '*') || line[1])) { + *uri_title = strdup(line); + if (!*uri_title) /* non-fatal */ + ro_warn_user("NoMemory", 0); + } + fclose(fp); + + return url; + +uri_free: + free(url); + +uri_syntax_error: + fclose(fp); + ro_warn_user("URIError", 0); + return 0; +} + + +/** + * Parse an ANT URL file. + * + * \param file_name file to read + * \return URL from file, or 0 on error and error reported + */ +static char *ro_gui_url_file_parse(const char *file_name) +{ + char line[400]; + char *url; + FILE *fp; + + fp = fopen(file_name, "r"); + if (!fp) { + LOG("fopen(\"%s\", \"r\"): %i: %s", file_name, errno, strerror(errno)); + ro_warn_user("LoadError", strerror(errno)); + return 0; + } + + if (!fgets(line, sizeof line, fp)) { + if (ferror(fp)) { + LOG("fgets: %i: %s", errno, strerror(errno)); + ro_warn_user("LoadError", strerror(errno)); + } else + ro_warn_user("LoadError", messages_get("EmptyError")); + fclose(fp); + return 0; + } + + fclose(fp); + + if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + + url = strdup(line); + if (!url) { + ro_warn_user("NoMemory", 0); + return 0; + } + + return url; +} + + +/** + * Parse an IEURL file. + * + * \param file_name file to read + * \return URL from file, or 0 on error and error reported + */ +static char *ro_gui_ieurl_file_parse(const char *file_name) +{ + char line[400]; + char *url = 0; + FILE *fp; + + fp = fopen(file_name, "r"); + if (!fp) { + LOG("fopen(\"%s\", \"r\"): %i: %s", file_name, errno, strerror(errno)); + ro_warn_user("LoadError", strerror(errno)); + return 0; + } + + while (fgets(line, sizeof line, fp)) { + if (strncmp(line, "URL=", 4) == 0) { + if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + url = strdup(line + 4); + if (!url) { + fclose(fp); + ro_warn_user("NoMemory", 0); + return 0; + } + break; + } + } + if (ferror(fp)) { + LOG("fgets: %i: %s", errno, strerror(errno)); + ro_warn_user("LoadError", strerror(errno)); + fclose(fp); + return 0; + } + + fclose(fp); + + if (!url) + ro_warn_user("URIError", 0); + + return url; +} + + +/** + * Handle Message_DataOpen (double-click on file in the Filer). + * + * \param message The wimp message to open. + */ +static void ro_msg_dataopen(wimp_message *message) +{ + int file_type = message->data.data_xfer.file_type; + char *url = 0; + os_error *oserror; + nsurl *urlns; + nserror error; + size_t len; + + switch (file_type) { + case 0xb28: /* ANT URL file */ + url = ro_gui_url_file_parse(message->data.data_xfer.file_name); + error = nsurl_create(url, &urlns); + free(url); + break; + + case 0xfaf: /* HTML file */ + error = netsurf_path_to_nsurl(message->data.data_xfer.file_name, + &urlns); + break; + + case 0x1ba: /* IEURL file */ + url = ro_gui_ieurl_file_parse(message-> + data.data_xfer.file_name); + error = nsurl_create(url, &urlns); + free(url); + break; + + case 0x2000: /* application */ + len = strlen(message->data.data_xfer.file_name); + if (len < 9 || strcmp(".!NetSurf", + message->data.data_xfer.file_name + len - 9)) + return; + + if (nsoption_charp(homepage_url) && + nsoption_charp(homepage_url)[0]) { + error = nsurl_create(nsoption_charp(homepage_url), + &urlns); + } else { + error = nsurl_create(NETSURF_HOMEPAGE, &urlns); + } + break; + + default: + return; + } + + /* send DataLoadAck */ + message->action = message_DATA_LOAD_ACK; + message->your_ref = message->my_ref; + oserror = xwimp_send_message(wimp_USER_MESSAGE, message, message->sender); + if (oserror) { + LOG("xwimp_send_message: 0x%x: %s", oserror->errnum, oserror->errmess); + ro_warn_user("WimpError", oserror->errmess); + return; + } + + if (error != NSERROR_OK) { + ro_warn_user(messages_get_errorcode(error), 0); + return; + } + + /* create a new window with the file */ + error = browser_window_create(BW_CREATE_HISTORY, + urlns, + NULL, + NULL, + NULL); + nsurl_unref(urlns); + if (error != NSERROR_OK) { + ro_warn_user(messages_get_errorcode(error), 0); + } +} + + +/** + * Handle Message_DataLoad (file dragged in). + */ +static void ro_msg_dataload(wimp_message *message) +{ + int file_type = message->data.data_xfer.file_type; + char *urltxt = NULL; + char *title = NULL; + struct gui_window *g; + os_error *oserror; + nsurl *url; + nserror error; + + g = ro_gui_window_lookup(message->data.data_xfer.w); + if (g) { + if (ro_gui_window_dataload(g, message)) + return; + } + else { + g = ro_gui_toolbar_lookup(message->data.data_xfer.w); + if (g && ro_gui_toolbar_dataload(g, message)) + return; + } + + switch (file_type) { + case FILETYPE_ACORN_URI: + urltxt = ro_gui_uri_file_parse(message->data.data_xfer.file_name, + &title); + error = nsurl_create(urltxt, &url); + free(urltxt); + break; + + case FILETYPE_ANT_URL: + urltxt = ro_gui_url_file_parse(message->data.data_xfer.file_name); + error = nsurl_create(urltxt, &url); + free(urltxt); + break; + + case FILETYPE_IEURL: + urltxt = ro_gui_ieurl_file_parse(message->data.data_xfer.file_name); + error = nsurl_create(urltxt, &url); + free(urltxt); + break; + + case FILETYPE_HTML: + case FILETYPE_JNG: + case FILETYPE_CSS: + case FILETYPE_MNG: + case FILETYPE_GIF: + case FILETYPE_BMP: + case FILETYPE_ICO: + case osfile_TYPE_DRAW: + case FILETYPE_PNG: + case FILETYPE_JPEG: + case osfile_TYPE_SPRITE: + case osfile_TYPE_TEXT: + case FILETYPE_ARTWORKS: + case FILETYPE_SVG: + /* display the actual file */ + error = netsurf_path_to_nsurl(message->data.data_xfer.file_name, &url); + break; + + default: + return; + } + + /* report error to user */ + if (error != NSERROR_OK) { + ro_warn_user(messages_get_errorcode(error), 0); + return; + } + + + if (g) { + error = browser_window_navigate(g->bw, + url, + NULL, + BW_NAVIGATE_HISTORY, + NULL, + NULL, + NULL); + } else { + error = browser_window_create(BW_CREATE_HISTORY, + url, + NULL, + NULL, + NULL); + } + nsurl_unref(url); + if (error != NSERROR_OK) { + ro_warn_user(messages_get_errorcode(error), 0); + } + + + /* send DataLoadAck */ + message->action = message_DATA_LOAD_ACK; + message->your_ref = message->my_ref; + oserror = xwimp_send_message(wimp_USER_MESSAGE, message, + message->sender); + if (oserror) { + LOG("xwimp_send_message: 0x%x: %s", oserror->errnum, oserror->errmess); + ro_warn_user("WimpError", oserror->errmess); + return; + } + +} + + +/** + * Ensure that the filename in a data transfer message is NULL terminated + * (some applications, especially BASIC programs use CR) + * + * \param message message to be corrected + */ +static void ro_msg_terminate_filename(wimp_full_message_data_xfer *message) +{ + const char *ep = (char*)message + message->size; + char *p = message->file_name; + + if ((size_t)message->size >= sizeof(*message)) + ep = (char*)message + sizeof(*message) - 1; + + while (p < ep && *p >= ' ') p++; + *p = '\0'; +} + + +/** + * Handle Message_DataSave + */ +static void ro_msg_datasave(wimp_message *message) +{ + wimp_full_message_data_xfer *dataxfer = (wimp_full_message_data_xfer*)message; + + /* remove ghost caret if drag-and-drop protocol was used */ +// ro_gui_selection_drag_reset(); + + ro_msg_terminate_filename(dataxfer); + + if (ro_gui_selection_prepare_paste_datasave(dataxfer)) + return; + + switch (dataxfer->file_type) { + case FILETYPE_ACORN_URI: + case FILETYPE_ANT_URL: + case FILETYPE_IEURL: + case FILETYPE_HTML: + case FILETYPE_JNG: + case FILETYPE_CSS: + case FILETYPE_MNG: + case FILETYPE_GIF: + case FILETYPE_BMP: + case FILETYPE_ICO: + case osfile_TYPE_DRAW: + case FILETYPE_PNG: + case FILETYPE_JPEG: + case osfile_TYPE_SPRITE: + case osfile_TYPE_TEXT: + case FILETYPE_ARTWORKS: + case FILETYPE_SVG: { + os_error *error; + + dataxfer->your_ref = dataxfer->my_ref; + dataxfer->size = offsetof(wimp_full_message_data_xfer, file_name) + 16; + dataxfer->action = message_DATA_SAVE_ACK; + dataxfer->est_size = -1; + memcpy(dataxfer->file_name, "<Wimp$Scrap>", 13); + + error = xwimp_send_message(wimp_USER_MESSAGE, (wimp_message*)dataxfer, message->sender); + if (error) { + LOG("xwimp_send_message: 0x%x: %s", error->errnum, error->errmess); + ro_warn_user("WimpError", error->errmess); + } + } + break; + } +} + + +/** + * Handle Message_DataSaveAck. + */ +static void ro_msg_datasave_ack(wimp_message *message) +{ + ro_msg_terminate_filename((wimp_full_message_data_xfer*)message); + + if (ro_print_ack(message)) + return; + + switch (gui_current_drag_type) { + case GUI_DRAG_DOWNLOAD_SAVE: + ro_gui_download_datasave_ack(message); + break; + + case GUI_DRAG_SAVE: + ro_gui_save_datasave_ack(message); + gui_current_drag_type = GUI_DRAG_NONE; + break; + + default: + break; + } + + gui_current_drag_type = GUI_DRAG_NONE; +} + + +/** + * Handle PreQuit message + * + * \param message PreQuit message from Wimp + */ +static void ro_msg_prequit(wimp_message *message) +{ + if (!ro_gui_prequit()) { + os_error *error; + + /* we're objecting to the close down */ + message->your_ref = message->my_ref; + error = xwimp_send_message(wimp_USER_MESSAGE_ACKNOWLEDGE, + message, message->sender); + if (error) { + LOG("xwimp_send_message: 0x%x:%s", error->errnum, error->errmess); + ro_warn_user("WimpError", error->errmess); + } + } +} + + +/** + * Handle SaveDesktop message. + * + * \param message SaveDesktop message from Wimp. + */ +static void ro_msg_save_desktop(wimp_message *message) +{ + os_error *error; + + error = xosgbpb_writew(message->data.save_desktopw.file, + (const byte*)"Run ", 4, NULL); + if (!error) { + error = xosgbpb_writew(message->data.save_desktopw.file, + (const byte*)NETSURF_DIR, strlen(NETSURF_DIR), NULL); + if (!error) + error = xos_bputw('\n', message->data.save_desktopw.file); + } + + if (error) { + LOG("xosgbpb_writew/xos_bputw: 0x%x:%s", error->errnum, error->errmess); + ro_warn_user("SaveError", error->errmess); + + /* we must cancel the save by acknowledging the message */ + message->your_ref = message->my_ref; + error = xwimp_send_message(wimp_USER_MESSAGE_ACKNOWLEDGE, + message, message->sender); + if (error) { + LOG("xwimp_send_message: 0x%x:%s", error->errnum, error->errmess); + ro_warn_user("WimpError", error->errmess); + } + } +} + + +/** + * Handle WindowInfo message (part of the iconising protocol) + * + * \param message WindowInfo message from the Iconiser + */ +static void ro_msg_window_info(wimp_message *message) +{ + wimp_full_message_window_info *wi; + struct gui_window *g; + + /* allow the user to turn off thumbnail icons */ + if (!nsoption_bool(thumbnail_iconise)) + return; + + wi = (wimp_full_message_window_info*)message; + g = ro_gui_window_lookup(wi->w); + + /* ic_<task name> will suffice for our other windows */ + if (g) { + ro_gui_window_iconise(g, wi); + ro_gui_dialog_close_persistent(wi->w); + } +} + + +/** + * Get screen properties following a mode change. + */ +static void ro_gui_get_screen_properties(void) +{ + static const ns_os_vdu_var_list vars = { + os_MODEVAR_XWIND_LIMIT, + { + os_MODEVAR_YWIND_LIMIT, + os_MODEVAR_XEIG_FACTOR, + os_MODEVAR_YEIG_FACTOR, + os_VDUVAR_END_LIST + } + }; + os_error *error; + int vals[4]; + + error = xos_read_vdu_variables(PTR_OS_VDU_VAR_LIST(&vars), vals); + if (error) { + LOG("xos_read_vdu_variables: 0x%x: %s", error->errnum, error->errmess); + ro_warn_user("MiscError", error->errmess); + return; + } + screen_info.width = (vals[0] + 1) << vals[2]; + screen_info.height = (vals[1] + 1) << vals[3]; +} + + +/** + * Warn the user if Inet$Resolvers is not set. + */ +static void ro_gui_check_resolvers(void) +{ + char *resolvers; + resolvers = getenv("Inet$Resolvers"); + if (resolvers && resolvers[0]) { + LOG("Inet$Resolvers '%s'", resolvers); + } else { + LOG("Inet$Resolvers not set or empty"); + ro_warn_user("Resolvers", 0); + } +} + + +/** + * Initialise the RISC OS specific GUI. + * + * \param argc The number of command line arguments. + * \param argv The string vector of command line arguments. + */ +static nserror gui_init(int argc, char** argv) +{ + struct { + void (*sigabrt)(int); + void (*sigfpe)(int); + void (*sigill)(int); + void (*sigint)(int); + void (*sigsegv)(int); + void (*sigterm)(int); + void (*sigoserror)(int); + } prev_sigs; + char path[40]; + os_error *error; + int length; + char *nsdir_temp; + byte *base; + nsurl *url; + nserror ret; + bool open_window; + + /* re-enable all FPU exceptions/traps except inexact operations, + * which we're not interested in, and underflow which is incorrectly + * raised when converting an exact value of 0 from double-precision + * to single-precision on FPEmulator v4.09-4.11 (MVFD F0,#0:MVFS F0,F0) + * - UnixLib disables all FP exceptions by default */ + + _FPU_SETCW(_FPU_IEEE & ~(_FPU_MASK_PM | _FPU_MASK_UM)); + + xhourglass_start(1); + + /* read OS version for code that adapts to conform to the OS + * (remember that it's preferable to check for specific features + * being present) */ + xos_byte(osbyte_IN_KEY, 0, 0xff, &os_version, NULL); + + /* the first release version of the A9home OS is incapable of + plotting patterned lines (presumably a fault in the hw acceleration) */ + if (!xosmodule_lookup("VideoHWSMI", NULL, NULL, &base, NULL, NULL)) { +#if 0 // this fault still hasn't been fixed, so disable patterned lines for all versions until it has + const char *help = (char*)base + ((int*)base)[5]; + while (*help > 9) help++; + while (*help == 9) help++; + if (!memcmp(help, "0.55", 4)) +#endif + ro_plot_patterned_lines = false; + } + + /* Create our choices directories */ + ro_gui_create_dirs(); + + /* Register exit and signal handlers */ + atexit(ro_gui_cleanup); + prev_sigs.sigabrt = signal(SIGABRT, ro_gui_signal); + prev_sigs.sigfpe = signal(SIGFPE, ro_gui_signal); + prev_sigs.sigill = signal(SIGILL, ro_gui_signal); + prev_sigs.sigint = signal(SIGINT, ro_gui_signal); + prev_sigs.sigsegv = signal(SIGSEGV, ro_gui_signal); + prev_sigs.sigterm = signal(SIGTERM, ro_gui_signal); + prev_sigs.sigoserror = signal(SIGOSERROR, ro_gui_signal); + + if (prev_sigs.sigabrt == SIG_ERR || prev_sigs.sigfpe == SIG_ERR || + prev_sigs.sigill == SIG_ERR || + prev_sigs.sigint == SIG_ERR || + prev_sigs.sigsegv == SIG_ERR || + prev_sigs.sigterm == SIG_ERR || + prev_sigs.sigoserror == SIG_ERR) + die("Failed registering signal handlers"); + + /* Load in UI sprites */ + gui_sprites = ro_gui_load_sprite_file("NetSurf:Resources.Sprites"); + if (!gui_sprites) + die("Unable to load Sprites."); + + /* Find NetSurf directory */ + nsdir_temp = getenv("NetSurf$Dir"); + if (!nsdir_temp) + die("Failed to locate NetSurf directory"); + NETSURF_DIR = strdup(nsdir_temp); + if (!NETSURF_DIR) + die("Failed duplicating NetSurf directory string"); + + /* Initialise filename allocator */ + filename_initialise(); + + /* Initialise save complete functionality */ + save_complete_init(); + + /* Load in visited URLs and Cookies */ + urldb_load(nsoption_charp(url_path)); + urldb_load_cookies(nsoption_charp(cookie_file)); + + /* Initialise with the wimp */ + error = xwimp_initialise(wimp_VERSION_RO38, task_name, + PTR_WIMP_MESSAGE_LIST(&task_messages), 0, + &task_handle); + if (error) { + LOG("xwimp_initialise: 0x%x: %s", error->errnum, error->errmess); + die(error->errmess); + } + /* Register message handlers */ + ro_message_register_route(message_HELP_REQUEST, + ro_gui_interactive_help_request); + ro_message_register_route(message_DATA_OPEN, + ro_msg_dataopen); + ro_message_register_route(message_DATA_SAVE, + ro_msg_datasave); + ro_message_register_route(message_DATA_SAVE_ACK, + ro_msg_datasave_ack); + ro_message_register_route(message_PRE_QUIT, + ro_msg_prequit); + ro_message_register_route(message_SAVE_DESKTOP, + ro_msg_save_desktop); + ro_message_register_route(message_DRAGGING, + ro_gui_selection_dragging); + ro_message_register_route(message_DRAG_CLAIM, + ro_gui_selection_drag_claim); + ro_message_register_route(message_WINDOW_INFO, + ro_msg_window_info); + + /* Initialise the font subsystem */ + nsfont_init(); + + /* Initialise global information */ + ro_gui_get_screen_properties(); + ro_gui_wimp_get_desktop_font(); + + /* Issue a *Desktop to poke AcornURI into life */ + if (getenv("NetSurf$Start_URI_Handler")) + xwimp_start_task("Desktop", 0); + + /* Open the templates */ + if ((length = snprintf(path, sizeof(path), + "NetSurf:Resources.%s.Templates", + nsoption_charp(language))) < 0 || length >= (int)sizeof(path)) + die("Failed to locate Templates resource."); + error = xwimp_open_template(path); + if (error) { + LOG("xwimp_open_template failed: 0x%x: %s", error->errnum, error->errmess); + die(error->errmess); + } + + ret = treeview_init(12); + if (ret != NSERROR_OK) { + die("Failed to initialise treeview"); + } + + /* Initialise themes before dialogs */ + ro_gui_theme_initialise(); + + /* Initialise dialog windows (must be after UI sprites are loaded) */ + ro_gui_dialog_init(); + + /* Initialise download window */ + ro_gui_download_init(); + + /* Initialise menus */ + ro_gui_menu_init(); + + /* Initialise query windows */ + ro_gui_query_init(); + + /* Initialise the history subsystem */ + ro_gui_history_init(); + + /* Initialise toolbars */ + ro_toolbar_init(); + + /* Initialise url bar module */ + ro_gui_url_bar_init(); + + /* Initialise browser windows */ + ro_gui_window_initialise(); + + /* Done with the templates file */ + wimp_close_template(); + + /* Create Iconbar icon and menus */ + ro_gui_iconbar_initialise(); + + /* Finally, check Inet$Resolvers for sanity */ + ro_gui_check_resolvers(); + + /* certificate verification window */ + ro_gui_cert_postinitialise(); + + /* hotlist window */ + ro_gui_hotlist_postinitialise(); + + /* global history window */ + ro_gui_global_history_postinitialise(); + + /* cookies window */ + ro_gui_cookies_postinitialise(); + + open_window = nsoption_bool(open_browser_at_startup); + + /* parse command-line arguments */ + if (argc == 2) { + LOG("parameters: '%s'", argv[1]); + /* this is needed for launching URI files */ + if (strcasecmp(argv[1], "-nowin") == 0) { + return NSERROR_OK; + } + ret = nsurl_create(NETSURF_HOMEPAGE, &url); + } + else if (argc == 3) { + LOG("parameters: '%s' '%s'", argv[1], argv[2]); + open_window = true; + + /* HTML files */ + if (strcasecmp(argv[1], "-html") == 0) { + ret = netsurf_path_to_nsurl(argv[2], &url); + } + /* URL files */ + else if (strcasecmp(argv[1], "-urlf") == 0) { + char *urlf = ro_gui_url_file_parse(argv[2]); + if (!urlf) { + LOG("allocation failed"); + die("Insufficient memory for URL"); + } + ret = nsurl_create(urlf, &url); + free(urlf); + } + /* ANT URL Load */ + else if (strcasecmp(argv[1], "-url") == 0) { + ret = nsurl_create(argv[2], &url); + } + /* Unknown => exit here. */ + else { + LOG("Unknown parameters: '%s' '%s'", argv[1], argv[2]); + return NSERROR_BAD_PARAMETER; + } + } + /* get user's homepage (if configured) */ + else if (nsoption_charp(homepage_url) && + nsoption_charp(homepage_url)[0]) { + ret = nsurl_create(nsoption_charp(homepage_url), &url); + } + /* default homepage */ + else { + ret = nsurl_create(NETSURF_HOMEPAGE, &url); + } + + /* check for url creation error */ + if (ret != NSERROR_OK) { + return ret; + } + + if (open_window) { + ret = browser_window_create(BW_CREATE_HISTORY, + url, + NULL, + NULL, + NULL); + } + nsurl_unref(url); + + return ret; +} + + +/** + * Determine the default language to use. + * + * RISC OS has no standard way of determining which language the user prefers. + * We have to guess from the 'Country' setting. + */ +const char *ro_gui_default_language(void) +{ + char path[40]; + const char *lang; + int country; + os_error *error; + + /* choose a language from the configured country number */ + error = xosbyte_read(osbyte_VAR_COUNTRY_NUMBER, &country); + if (error) { + LOG("xosbyte_read failed: 0x%x: %s", error->errnum, error->errmess); + country = 1; + } + switch (country) { + case 7: /* Germany */ + case 30: /* Austria */ + case 35: /* Switzerland (70% German-speaking) */ + lang = "de"; + break; + case 6: /* France */ + case 18: /* Canada2 (French Canada?) */ + lang = "fr"; + break; + case 34: /* Netherlands */ + lang = "nl"; + break; + default: + lang = "en"; + break; + } + sprintf(path, "NetSurf:Resources.%s", lang); + if (is_dir(path)) + return lang; + return "en"; +} + + +/** + * Create a nsurl from a RISC OS pathname. + * + * Perform the necessary operations on a path to generate a nsurl. + * + * @param[in] path The RISC OS pathname to convert. + * @param[out] url_out pointer to recive the nsurl, The returned url must be + * unreferenced by the caller. + * @return NSERROR_OK and the url is placed in \a url or error code on faliure. + */ +static nserror ro_path_to_nsurl(const char *path, struct nsurl **url_out) +{ + int spare; + char *canonical_path; /* canonicalised RISC OS path */ + char *unix_path; /* unix path */ + char *escurl; + os_error *error; + nserror ret; + int urllen; + char *url; /* resulting url */ + + /* calculate the canonical risc os path */ + error = xosfscontrol_canonicalise_path(path, 0, 0, 0, 0, &spare); + if (error) { + LOG("xosfscontrol_canonicalise_path failed: 0x%x: %s", error->errnum, error->errmess); + ro_warn_user("PathToURL", error->errmess); + return NSERROR_NOT_FOUND; + } + + canonical_path = malloc(1 - spare); + if (canonical_path == NULL) { + free(canonical_path); + return NSERROR_NOMEM; + } + + error = xosfscontrol_canonicalise_path(path, canonical_path, 0, 0, 1 - spare, 0); + if (error) { + LOG("xosfscontrol_canonicalise_path failed: 0x%x: %s", error->errnum, error->errmess); + ro_warn_user("PathToURL", error->errmess); + free(canonical_path); + return NSERROR_NOT_FOUND; + } + + /* create a unix path from the cananocal risc os one */ + unix_path = __unixify(canonical_path, __RISCOSIFY_NO_REVERSE_SUFFIX, NULL, 0, 0); + + if (unix_path == NULL) { + LOG("__unixify failed: %s", canonical_path); + free(canonical_path); + return NSERROR_BAD_PARAMETER; + } + free(canonical_path); + + /* convert the unix path into a url */ + urllen = strlen(unix_path) + FILE_SCHEME_PREFIX_LEN + 1; + url = malloc(urllen); + if (url == NULL) { + LOG("Unable to allocate url"); + free(unix_path); + return NSERROR_NOMEM; + } + + if (*unix_path == '/') { + snprintf(url, urllen, "%s%s", FILE_SCHEME_PREFIX, unix_path + 1); + } else { + snprintf(url, urllen, "%s%s", FILE_SCHEME_PREFIX, unix_path); + } + free(unix_path); + + /* We don't want '/' to be escaped. */ + ret = url_escape(url, FILE_SCHEME_PREFIX_LEN, false, "/", &escurl); + free(url); + if (ret != NSERROR_OK) { + return ret; + } + + ret = nsurl_create(escurl, url_out); + free(escurl); + + return ret; +} + + +/** + * Create a path from a nsurl using posix file handling. + * + * @param[in] url The url to encode. + * @param[out] path_out A string containing the result path which should + * be freed by the caller. + * @return NSERROR_OK and the path is written to \a path or error code + * on faliure. + */ +static nserror ro_nsurl_to_path(struct nsurl *url, char **path_out) +{ + lwc_string *urlpath; + char *unpath; + char *path; + bool match; + lwc_string *scheme; + nserror res; + char *r; + + if ((url == NULL) || (path_out == NULL)) { + return NSERROR_BAD_PARAMETER; + } + + scheme = nsurl_get_component(url, NSURL_SCHEME); + + if (lwc_string_caseless_isequal(scheme, corestring_lwc_file, + &match) != lwc_error_ok) + { + return NSERROR_BAD_PARAMETER; + } + lwc_string_unref(scheme); + if (match == false) { + return NSERROR_BAD_PARAMETER; + } + + urlpath = nsurl_get_component(url, NSURL_PATH); + if (urlpath == NULL) { + return NSERROR_BAD_PARAMETER; + } + + res = url_unescape(lwc_string_data(urlpath), &unpath); + lwc_string_unref(urlpath); + if (res != NSERROR_OK) { + return res; + } + + /* RISC OS path should not be more than 100 characters longer */ + path = malloc(strlen(unpath) + 100); + if (path == NULL) { + free(unpath); + return NSERROR_NOMEM; + } + + r = __riscosify(unpath, 0, __RISCOSIFY_NO_SUFFIX, + path, strlen(unpath) + 100, 0); + free(unpath); + if (r == NULL) { + free(path); + return NSERROR_NOMEM; + } + + *path_out = path; + + return NSERROR_OK; +} + + +/** + * Ensures output logging stream is correctly configured. + */ +static bool nslog_stream_configure(FILE *fptr) +{ + /* set log stream to be non-buffering */ + setbuf(fptr, NULL); + + return true; +} + + +/** + * Close down the gui (RISC OS). + */ +static void gui_quit(void) +{ + urldb_save_cookies(nsoption_charp(cookie_jar)); + urldb_save(nsoption_charp(url_save)); + ro_gui_window_quit(); + ro_gui_global_history_destroy(); + ro_gui_hotlist_destroy(); + ro_gui_cookies_destroy(); + ro_gui_saveas_quit(); + ro_gui_url_bar_fini(); + rufl_quit(); + free(gui_sprites); + xwimp_close_down(task_handle); + xhourglass_off(); +} + + +/** + * Handle Close_Window_Request events. + */ +static void ro_gui_close_window_request(wimp_close *close) +{ + if (ro_gui_alt_pressed()) + ro_gui_window_close_all(); + else { + if (ro_gui_wimp_event_close_window(close->w)) + return; + ro_gui_dialog_close(close->w); + } +} + + +/** + * Handle key press paste callback. + */ +static void ro_gui_keypress_cb(void *pw) +{ + wimp_key *key = (wimp_key *) pw; + + if (ro_gui_wimp_event_keypress(key) == false) { + os_error *error = xwimp_process_key(key->c); + if (error) { + LOG("xwimp_process_key: 0x%x: %s", error->errnum, error->errmess); + ro_warn_user("WimpError", error->errmess); + } + } + + free(key); +} + + +/** + * Handle gui keypress. + */ +static void ro_gui_keypress(wimp_key *key) +{ + if (key->c == wimp_KEY_ESCAPE && + (gui_current_drag_type == GUI_DRAG_SAVE || + gui_current_drag_type == GUI_DRAG_DOWNLOAD_SAVE)) { + + /* Allow Escape key to be used for cancelling a drag + * save (easier than finding somewhere safe to abort + * the drag) + */ + ro_gui_drag_box_cancel(); + gui_current_drag_type = GUI_DRAG_NONE; + } else if (key->c == 22 /* Ctrl-V */) { + wimp_key *copy; + + /* Must copy the keypress as it's on the stack */ + copy = malloc(sizeof(wimp_key)); + if (copy == NULL) + return; + memcpy(copy, key, sizeof(wimp_key)); + + ro_gui_selection_prepare_paste(key->w, ro_gui_keypress_cb, copy); + } else if (ro_gui_wimp_event_keypress(key) == false) { + os_error *error = xwimp_process_key(key->c); + if (error) { + LOG("xwimp_process_key: 0x%x: %s", error->errnum, error->errmess); + ro_warn_user("WimpError", error->errmess); + } + } +} + + +/** + * Handle the three User_Message events. + */ +static void ro_gui_user_message(wimp_event_no event, wimp_message *message) +{ + /* attempt automatic routing */ + if (ro_message_handle_message(event, message)) + return; + + switch (message->action) { + case message_DATA_LOAD: + ro_msg_terminate_filename((wimp_full_message_data_xfer*)message); + + if (event == wimp_USER_MESSAGE_ACKNOWLEDGE) { + if (ro_print_current_window) + ro_print_dataload_bounce(message); + } else if (ro_gui_selection_prepare_paste_dataload( + (wimp_full_message_data_xfer *) message) == false) { + ro_msg_dataload(message); + } + break; + + case message_DATA_LOAD_ACK: + if (ro_print_current_window) + ro_print_cleanup(); + break; + + case message_MENU_WARNING: + ro_gui_menu_warning((wimp_message_menu_warning *) + &message->data); + break; + + case message_MENUS_DELETED: + ro_gui_menu_message_deleted((wimp_message_menus_deleted *) + &message->data); + break; + + case message_CLAIM_ENTITY: + ro_gui_selection_claim_entity((wimp_full_message_claim_entity*)message); + break; + + case message_DATA_REQUEST: + ro_gui_selection_data_request((wimp_full_message_data_request*)message); + break; + + case message_MODE_CHANGE: + ro_gui_get_screen_properties(); + rufl_invalidate_cache(); + break; + + case message_PALETTE_CHANGE: + break; + + case message_FONT_CHANGED: + ro_gui_wimp_get_desktop_font(); + break; + + case message_URI_PROCESS: + if (event != wimp_USER_MESSAGE_ACKNOWLEDGE) + ro_uri_message_received(message); + break; + case message_URI_RETURN_RESULT: + ro_uri_bounce(message); + break; + case message_INET_SUITE_OPEN_URL: + if (event == wimp_USER_MESSAGE_ACKNOWLEDGE) { + ro_url_bounce(message); + } + else { + ro_url_message_received(message); + } + break; +#ifdef WITH_PLUGIN + case message_PLUG_IN_OPENING: + plugin_opening(message); + break; + case message_PLUG_IN_CLOSED: + plugin_closed(message); + break; + case message_PLUG_IN_RESHAPE_REQUEST: + plugin_reshape_request(message); + break; + case message_PLUG_IN_FOCUS: + break; + case message_PLUG_IN_URL_ACCESS: + plugin_url_access(message); + break; + case message_PLUG_IN_STATUS: + plugin_status(message); + break; + case message_PLUG_IN_BUSY: + break; + case message_PLUG_IN_STREAM_NEW: + plugin_stream_new(message); + break; + case message_PLUG_IN_STREAM_WRITE: + break; + case message_PLUG_IN_STREAM_WRITTEN: + plugin_stream_written(message); + break; + case message_PLUG_IN_STREAM_DESTROY: + break; + case message_PLUG_IN_OPEN: + if (event == wimp_USER_MESSAGE_ACKNOWLEDGE) + plugin_open_msg(message); + break; + case message_PLUG_IN_CLOSE: + if (event == wimp_USER_MESSAGE_ACKNOWLEDGE) + plugin_close_msg(message); + break; + case message_PLUG_IN_RESHAPE: + case message_PLUG_IN_STREAM_AS_FILE: + case message_PLUG_IN_NOTIFY: + case message_PLUG_IN_ABORT: + case message_PLUG_IN_ACTION: + break; +#endif + case message_PRINT_SAVE: + if (event == wimp_USER_MESSAGE_ACKNOWLEDGE) + ro_print_save_bounce(message); + break; + case message_PRINT_ERROR: + ro_print_error(message); + break; + case message_PRINT_TYPE_ODD: + ro_print_type_odd(message); + break; + case message_HOTLIST_CHANGED: + ro_gui_hotlist_add_cleanup(); + break; + case message_QUIT: + riscos_done = true; + break; + } +} + + +/** + * Process a Wimp_Poll event. + * + * \param event wimp event number + * \param block parameter block + */ +static void ro_gui_handle_event(wimp_event_no event, wimp_block *block) +{ + switch (event) { + case wimp_NULL_REASON_CODE: + ro_gui_throb(); + ro_mouse_poll(); + break; + + case wimp_REDRAW_WINDOW_REQUEST: + ro_gui_wimp_event_redraw_window(&block->redraw); + break; + + case wimp_OPEN_WINDOW_REQUEST: + ro_gui_open_window_request(&block->open); + break; + + case wimp_CLOSE_WINDOW_REQUEST: + ro_gui_close_window_request(&block->close); + break; + + case wimp_POINTER_LEAVING_WINDOW: + ro_mouse_pointer_leaving_window(&block->leaving); + break; + + case wimp_POINTER_ENTERING_WINDOW: + ro_gui_wimp_event_pointer_entering_window(&block->entering); + break; + + case wimp_MOUSE_CLICK: + ro_gui_wimp_event_mouse_click(&block->pointer); + break; + + case wimp_USER_DRAG_BOX: + ro_mouse_drag_end(&block->dragged); + break; + + case wimp_KEY_PRESSED: + ro_gui_keypress(&(block->key)); + break; + + case wimp_MENU_SELECTION: + ro_gui_menu_selection(&(block->selection)); + break; + + /* Scroll requests fall back to a generic handler because we + * might get these events for any window from a scroll-wheel. + */ + + case wimp_SCROLL_REQUEST: + if (!ro_gui_wimp_event_scroll_window(&(block->scroll))) + ro_gui_scroll(&(block->scroll)); + break; + + case wimp_USER_MESSAGE: + case wimp_USER_MESSAGE_RECORDED: + case wimp_USER_MESSAGE_ACKNOWLEDGE: + ro_gui_user_message(event, &(block->message)); + break; + } +} + + +/** + * Poll the RISC OS wimp for events. + */ +static void riscos_poll(void) +{ + wimp_event_no event; + wimp_block block; + const wimp_poll_flags mask = wimp_MASK_LOSE | wimp_MASK_GAIN | wimp_SAVE_FP; + os_t track_poll_offset; + + /* Poll wimp. */ + xhourglass_off(); + track_poll_offset = ro_mouse_poll_interval(); + if (sched_active || (track_poll_offset > 0)) { + os_t t = os_read_monotonic_time(); + + if (track_poll_offset > 0) { + t += track_poll_offset; + } else { + t += 10; + } + + if (sched_active && (sched_time - t) < 0) { + t = sched_time; + } + + event = wimp_poll_idle(mask, &block, t, 0); + } else { + event = wimp_poll(wimp_MASK_NULL | mask, &block, 0); + } + + xhourglass_on(); + gui_last_poll = clock(); + ro_gui_handle_event(event, &block); + + /* Only run scheduled callbacks on a null poll + * We cannot do this in the null event handler, as that may be called + * from gui_multitask(). Scheduled callbacks must only be run from the + * top-level. + */ + if (event == wimp_NULL_REASON_CODE) { + schedule_run(); + } + + ro_gui_window_update_boxes(); +} + + +/** + * Handle Open_Window_Request events. + */ +void ro_gui_open_window_request(wimp_open *open) +{ + os_error *error; + + if (ro_gui_wimp_event_open_window(open)) + return; + + error = xwimp_open_window(open); + if (error) { + LOG("xwimp_open_window: 0x%x: %s", error->errnum, error->errmess); + ro_warn_user("WimpError", error->errmess); + return; + } +} + + +/** + * source bounce callback. + */ +static void ro_gui_view_source_bounce(wimp_message *message) +{ + char *filename; + os_error *error; + char command[256]; + + /* run the file as text */ + filename = ((wimp_full_message_data_xfer *)message)->file_name; + sprintf(command, "@RunType_FFF %s", filename); + error = xwimp_start_task(command, 0); + if (error) { + LOG("xwimp_start_task failed: 0x%x: %s", error->errnum, error->errmess); + ro_warn_user("WimpError", error->errmess); + } +} + + +/** + * Send the source of a content to a text editor. + */ +void ro_gui_view_source(hlcache_handle *c) +{ + os_error *error; + char *temp_name; + wimp_full_message_data_xfer message; + int objtype; + bool done = false; + + const char *source_data; + unsigned long source_size; + + if (!c) { + ro_warn_user("MiscError", "No document source"); + return; + } + + source_data = content_get_source_data(c, &source_size); + + if (!source_data) { + ro_warn_user("MiscError", "No document source"); + return; + } + + /* try to load local files directly. */ + if (netsurf_nsurl_to_path(hlcache_handle_get_url(c), &temp_name) == NSERROR_OK) { + error = xosfile_read_no_path(temp_name, &objtype, 0, 0, 0, 0); + if ((!error) && (objtype == osfile_IS_FILE)) { + snprintf(message.file_name, 212, "%s", temp_name); + message.file_name[211] = '\0'; + done = true; + } + free(temp_name); + } + if (!done) { + /* We cannot release the requested filename until after it + * has finished being used. As we can't easily find out when + * this is, we simply don't bother releasing it and simply + * allow it to be re-used next time NetSurf is started. The + * memory overhead from doing this is under 1 byte per + * filename. */ + char *r; + char full_name[256]; + const char *filename = filename_request(); + if (!filename) { + ro_warn_user("NoMemory", 0); + return; + } + + snprintf(full_name, 256, "%s/%s", TEMP_FILENAME_PREFIX, + filename); + full_name[255] = '\0'; + r = __riscosify(full_name, 0, __RISCOSIFY_NO_SUFFIX, + message.file_name, 212, 0); + if (r == 0) { + LOG("__riscosify failed"); + return; + } + message.file_name[211] = '\0'; + + error = xosfile_save_stamped(message.file_name, + ro_content_filetype(c), + (byte *) source_data, + (byte *) source_data + source_size); + if (error) { + LOG("xosfile_save_stamped failed: 0x%x: %s", error->errnum, error->errmess); + ro_warn_user("MiscError", error->errmess); + return; + } + } + + /* begin the DataOpen protocol */ + message.your_ref = 0; + message.size = 44 + ((strlen(message.file_name) + 4) & (~3u)); + message.action = message_DATA_OPEN; + message.w = 0; + message.i = 0; + message.pos.x = 0; + message.pos.y = 0; + message.est_size = 0; + message.file_type = 0xfff; + ro_message_send_message(wimp_USER_MESSAGE_RECORDED, + (wimp_message*)&message, 0, + ro_gui_view_source_bounce); +} + + +/** + * Broadcast an URL that we can't handle. + */ +static nserror gui_launch_url(struct nsurl *url) +{ + /* Try ant broadcast */ + ro_url_broadcast(nsurl_access(url)); + return NSERROR_OK; +} + + +/** + * Choose the language to use. + */ +static void ro_gui_choose_language(void) +{ + /* if option_language exists and is valid, use that */ + if (nsoption_charp(language)) { + char path[40]; + if (2 < strlen(nsoption_charp(language))) + nsoption_charp(language)[2] = 0; + sprintf(path, "NetSurf:Resources.%s", nsoption_charp(language)); + + if (is_dir(path)) { + nsoption_setnull_charp(accept_language, + strdup(nsoption_charp(language))); + return; + } + nsoption_set_charp(language, NULL); + } + + nsoption_set_charp(language, strdup(ro_gui_default_language())); + if (nsoption_charp(language) == NULL) + die("Out of memory"); + nsoption_set_charp(accept_language, strdup(nsoption_charp(language))); + if (nsoption_charp(accept_language) == NULL) + die("Out of memory"); +} + + +/** + * Display a warning for a serious problem (eg memory exhaustion). + * + * \param warning message key for warning message + * \param detail additional message, or 0 + */ +nserror ro_warn_user(const char *warning, const char *detail) +{ + LOG("%s %s", warning, detail); + + if (dialog_warning) { + char warn_buffer[300]; + snprintf(warn_buffer, sizeof warn_buffer, "%s %s", + messages_get(warning), + detail ? detail : ""); + warn_buffer[sizeof warn_buffer - 1] = 0; + ro_gui_set_icon_string(dialog_warning, ICON_WARNING_MESSAGE, + warn_buffer, true); + xwimp_set_icon_state(dialog_warning, ICON_WARNING_HELP, + wimp_ICON_DELETED, wimp_ICON_DELETED); + ro_gui_dialog_open(dialog_warning); + xos_bell(); + } else { + /* probably haven't initialised (properly), use a + non-multitasking error box */ + os_error error; + snprintf(error.errmess, sizeof error.errmess, "%s %s", + messages_get(warning), + detail ? detail : ""); + error.errmess[sizeof error.errmess - 1] = 0; + xwimp_report_error_by_category(&error, + wimp_ERROR_BOX_OK_ICON | + wimp_ERROR_BOX_GIVEN_CATEGORY | + wimp_ERROR_BOX_CATEGORY_ERROR << + wimp_ERROR_BOX_CATEGORY_SHIFT, + "NetSurf", "!netsurf", + (osspriteop_area *) 1, 0, 0); + } + + return NSERROR_OK; +} + + +/** + * Display an error and exit. + * + * Should only be used during initialisation. + */ +void die(const char * const error) +{ + os_error warn_error; + + LOG("%s", error); + + warn_error.errnum = 1; /* \todo: reasonable ? */ + strncpy(warn_error.errmess, messages_get(error), + sizeof(warn_error.errmess)-1); + warn_error.errmess[sizeof(warn_error.errmess)-1] = '\0'; + xwimp_report_error_by_category(&warn_error, + wimp_ERROR_BOX_OK_ICON | + wimp_ERROR_BOX_GIVEN_CATEGORY | + wimp_ERROR_BOX_CATEGORY_ERROR << + wimp_ERROR_BOX_CATEGORY_SHIFT, + "NetSurf", "!netsurf", + (osspriteop_area *) 1, 0, 0); + exit(EXIT_FAILURE); +} + + +/** + * Test whether it's okay to shutdown, prompting the user if not. + * + * \return true iff it's okay to shutdown immediately + */ +bool ro_gui_prequit(void) +{ + return ro_gui_download_prequit(); +} + + +/** + * Generate a riscos path from one or more component elemnts. + * + * Constructs a complete path element from passed components. The + * second (and subsequent) components have a slash substituted for all + * riscos directory separators. + * + * If a string is allocated it must be freed by the caller. + * + * @param[in,out] str pointer to string pointer if this is NULL enough + * storage will be allocated for the complete path. + * @param[in,out] size The size of the space available if \a str not + * NULL on input and if not NULL set to the total + * output length on output. + * @param[in] nelm The number of elements. + * @param[in] ap The elements of the path as string pointers. + * @return NSERROR_OK and the complete path is written to str + * or error code on faliure. + */ +static nserror riscos_mkpath(char **str, size_t *size, size_t nelm, va_list ap) +{ + const char *elm[16]; + size_t elm_len[16]; + size_t elm_idx; + char *fname; + size_t fname_len = 0; + char *curp; + size_t idx; + + /* check the parameters are all sensible */ + if ((nelm == 0) || (nelm > 16)) { + return NSERROR_BAD_PARAMETER; + } + if ((*str != NULL) && (size == NULL)) { + /* if the caller is providing the buffer they must say + * how much space is available. + */ + return NSERROR_BAD_PARAMETER; + } + + /* calculate how much storage we need for the complete path + * with all the elements. + */ + for (elm_idx = 0; elm_idx < nelm; elm_idx++) { + elm[elm_idx] = va_arg(ap, const char *); + /* check the argument is not NULL */ + if (elm[elm_idx] == NULL) { + return NSERROR_BAD_PARAMETER; + } + elm_len[elm_idx] = strlen(elm[elm_idx]); + fname_len += elm_len[elm_idx]; + } + fname_len += nelm; /* allow for separators and terminator */ + + /* ensure there is enough space */ + fname = *str; + if (fname != NULL) { + if (fname_len > *size) { + return NSERROR_NOSPACE; + } + } else { + fname = malloc(fname_len); + if (fname == NULL) { + return NSERROR_NOMEM; + } + } + + /* copy the elements in with directory separator */ + curp = fname; + + /* first element is not altered */ + memmove(curp, elm[0], elm_len[0]); + curp += elm_len[0]; + /* ensure there is a delimiter */ + if (curp[-1] != DIR_SEP) { + *curp = DIR_SEP; + curp++; + } + + /* subsequent elemnts have slashes substituted with directory + * separators. + */ + for (elm_idx = 1; elm_idx < nelm; elm_idx++) { + for (idx = 0; idx < elm_len[elm_idx]; idx++) { + if (elm[elm_idx][idx] == DIR_SEP) { + *curp = '/'; + } else { + *curp = elm[elm_idx][idx]; + } + curp++; + } + *curp = DIR_SEP; + curp++; + } + curp[-1] = 0; /* NULL terminate */ + + assert((curp - fname) <= (int)fname_len); + + *str = fname; + if (size != NULL) { + *size = fname_len; + } + + return NSERROR_OK; + +} + + +/** + * Get the basename of a file using posix path handling. + * + * This gets the last element of a path and returns it. The returned + * element has all forward slashes translated into riscos directory + * separators. + * + * @param[in] path The path to extract the name from. + * @param[in,out] str Pointer to string pointer if this is NULL enough + * storage will be allocated for the path element. + * @param[in,out] size The size of the space available if \a + * str not NULL on input and set to the total + * output length on output. + * @return NSERROR_OK and the complete path is written to str + * or error code on faliure. + */ +static nserror riscos_basename(const char *path, char **str, size_t *size) +{ + const char *leafname; + char *fname; + char *temp; + + if (path == NULL) { + return NSERROR_BAD_PARAMETER; + } + + leafname = strrchr(path, DIR_SEP); + if (!leafname) { + leafname = path; + } else { + leafname += 1; + } + + fname = strdup(leafname); + if (fname == NULL) { + return NSERROR_NOMEM; + } + + /** @todo check this leafname translation is actually required */ + /* and s/\//\./g */ + for (temp = fname; *temp != 0; temp++) { + if (*temp == '/') { + *temp = DIR_SEP; + } + } + + *str = fname; + if (size != NULL) { + *size = strlen(fname); + } + return NSERROR_OK; +} + + +/** + * Ensure that all directory elements needed to store a filename exist. + * + * Given a path of x.y.z directories x and x.y will be created. + * + * @param fname The filename to ensure the path to exists. + * @return NSERROR_OK on success or error code on failure. + */ +static nserror riscos_mkdir_all(const char *fname) +{ + char *dname; + char *cur; + + dname = strdup(fname); + + cur = dname; + while ((cur = strchr(cur, '.'))) { + *cur = '\0'; + xosfile_create_dir(dname, 0); + *cur++ = '.'; + } + + free(dname); + + return NSERROR_OK; +} + +/** + * Find screen size in OS units. + */ +void ro_gui_screen_size(int *width, int *height) +{ + *width = screen_info.width; + *height = screen_info.height; +} + + +/** + * Send the debug dump of a content to a text editor. + */ +void ro_gui_dump_browser_window(struct browser_window *bw) +{ + os_error *error; + + /* open file for dump */ + FILE *stream = fopen("<Wimp$ScrapDir>.WWW.NetSurf.dump", "w"); + if (!stream) { + LOG("fopen: errno %i", errno); + ro_warn_user("SaveError", strerror(errno)); + return; + } + + browser_window_debug_dump(bw, stream, CONTENT_DEBUG_RENDER); + + fclose(stream); + + /* launch file in editor */ + error = xwimp_start_task("Filer_Run <Wimp$ScrapDir>.WWW.NetSurf.dump", + 0); + if (error) { + LOG("xwimp_start_task failed: 0x%x: %s", error->errnum, error->errmess); + ro_warn_user("WimpError", error->errmess); + } +} + + +static struct gui_file_table riscos_file_table = { + .mkpath = riscos_mkpath, + .basename = riscos_basename, + .nsurl_to_path = ro_nsurl_to_path, + .path_to_nsurl = ro_path_to_nsurl, + .mkdir_all = riscos_mkdir_all, +}; + +static struct gui_fetch_table riscos_fetch_table = { + .filetype = fetch_filetype, + + .get_resource_url = gui_get_resource_url, + .mimetype = fetch_mimetype, +}; + +static struct gui_misc_table riscos_misc_table = { + .schedule = riscos_schedule, + .warning = ro_warn_user, + + .quit = gui_quit, + .launch_url = gui_launch_url, + .cert_verify = gui_cert_verify, + .login = gui_401login_open, +}; + + +static char *get_cachepath(void) +{ + char *cachedir; + char *cachepath = NULL; + nserror ret; + + cachedir = getenv("Cache$Dir"); + if ((cachedir == NULL) || (cachedir[0] == 0)) { + LOG("cachedir was null"); + return NULL; + } + ret = netsurf_mkpath(&cachepath, NULL, 2, cachedir, "NetSurf"); + if (ret != NSERROR_OK) { + return NULL; + } + return cachepath; +} + +/** + * Normal entry point from RISC OS. + */ +int main(int argc, char** argv) +{ + char *cachepath; + char path[40]; + int length; + os_var_type type; + int used = -1; /* slightly better with older OSLib versions */ + os_error *error; + nserror ret; + struct netsurf_table riscos_table = { + .misc = &riscos_misc_table, + .window = riscos_window_table, + .clipboard = riscos_clipboard_table, + .download = riscos_download_table, + .fetch = &riscos_fetch_table, + .file = &riscos_file_table, + .utf8 = riscos_utf8_table, + .search = riscos_search_table, + .llcache = filesystem_llcache_table, + .bitmap = riscos_bitmap_table, + .layout = riscos_layout_table, + }; + + ret = netsurf_register(&riscos_table); + if (ret != NSERROR_OK) { + die("NetSurf operation table failed registration"); + } + + /* Consult NetSurf$Logging environment variable to decide if logging + * is required. */ + error = xos_read_var_val_size("NetSurf$Logging", 0, os_VARTYPE_STRING, + &used, NULL, &type); + if (error != NULL || type != os_VARTYPE_STRING || used != -2) { + verbose_log = true; + } else { + char logging_env[2]; + error = xos_read_var_val("NetSurf$Logging", logging_env, + sizeof(logging_env), 0, os_VARTYPE_STRING, + &used, NULL, &type); + if (error != NULL || logging_env[0] != '0') { + verbose_log = true; + } else { + verbose_log = false; + } + } + + /* initialise logging. Not fatal if it fails but not much we + * can do about it either. + */ + nslog_init(nslog_stream_configure, &argc, argv); + + /* user options setup */ + ret = nsoption_init(set_defaults, &nsoptions, &nsoptions_default); + if (ret != NSERROR_OK) { + die("Options failed to initialise"); + } + nsoption_read("NetSurf:Choices", NULL); + nsoption_commandline(&argc, argv, NULL); + + /* Choose the interface language to use */ + ro_gui_choose_language(); + + /* select language-specific Messages */ + if (((length = snprintf(path, + sizeof(path), + "NetSurf:Resources.%s.Messages", + nsoption_charp(language))) < 0) || + (length >= (int)sizeof(path))) { + die("Failed to locate Messages resource."); + } + + /* initialise messages */ + messages_add_from_file(path); + + /* obtain cache path */ + cachepath = get_cachepath(); + + /* common initialisation */ + ret = netsurf_init(cachepath); + free(cachepath); + if (ret != NSERROR_OK) { + die("NetSurf failed to initialise core"); + } + + artworks_init(); + draw_init(); + sprite_init(); + + /* Load some extra RISC OS specific Messages */ + messages_add_from_file("NetSurf:Resources.LangNames"); + + ret = gui_init(argc, argv); + if (ret != NSERROR_OK) { + ro_warn_user(messages_get_errorcode(ret), 0); + } + + while (!riscos_done) { + riscos_poll(); + } + + netsurf_exit(); + + return 0; +} |