summaryrefslogtreecommitdiff
path: root/frontends/riscos/gui.c
diff options
context:
space:
mode:
Diffstat (limited to 'frontends/riscos/gui.c')
-rw-r--r--frontends/riscos/gui.c2522
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;
+}