From 35e3d1a0bb3e1a9ef49740c12df42be9a17a8228 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Wed, 30 Sep 2026 08:47:44 +0200 Subject: [PATCH] fix: update NetSurf port & montauk frontend to use new paths for fonts and icons --- README.md | 2 +- www-client/netsurf/Makefile | 84 +- www-client/netsurf/compat/stb_image_impl.c | 18 + www-client/netsurf/files/manifest.toml | 11 + www-client/netsurf/files/montauk.c | 477 +++++++++++ www-client/netsurf/files/montauk.h | 8 + www-client/netsurf/files/montauk_bridge.cpp | 794 ++++++++++++++++++ www-client/netsurf/files/montauk_bridge.h | 98 +++ www-client/netsurf/files/montauk_image.c | 131 +++ www-client/netsurf/files/montauk_ui.c | 538 ++++++++++++ www-client/netsurf/files/montauk_ui.h | 13 + .../netsurf/files/stb_truetype_impl.cpp | 23 + .../0002-respath-separator-montaukos.patch | 26 +- .../patches/0006-native-http-fetcher.patch | 43 + .../patches/0007-montauk-window-plotter.patch | 236 ++++++ .../patches/0008-montauk-auto-redraw.patch | 28 + .../patches/0009-native-font-metrics.patch | 135 +++ .../0010-font-point-to-pixel-scaling.patch | 67 ++ .../patches/0011-native-browser-shell.patch | 544 ++++++++++++ .../patches/0012-native-downloads.patch | 113 +++ .../0013-bitmap-rowstride-and-bounds.patch | 21 + 21 files changed, 3376 insertions(+), 34 deletions(-) create mode 100644 www-client/netsurf/compat/stb_image_impl.c create mode 100644 www-client/netsurf/files/manifest.toml create mode 100644 www-client/netsurf/files/montauk.c create mode 100644 www-client/netsurf/files/montauk.h create mode 100644 www-client/netsurf/files/montauk_bridge.cpp create mode 100644 www-client/netsurf/files/montauk_bridge.h create mode 100644 www-client/netsurf/files/montauk_image.c create mode 100644 www-client/netsurf/files/montauk_ui.c create mode 100644 www-client/netsurf/files/montauk_ui.h create mode 100644 www-client/netsurf/files/stb_truetype_impl.cpp create mode 100644 www-client/netsurf/patches/0006-native-http-fetcher.patch create mode 100644 www-client/netsurf/patches/0007-montauk-window-plotter.patch create mode 100644 www-client/netsurf/patches/0008-montauk-auto-redraw.patch create mode 100644 www-client/netsurf/patches/0009-native-font-metrics.patch create mode 100644 www-client/netsurf/patches/0010-font-point-to-pixel-scaling.patch create mode 100644 www-client/netsurf/patches/0011-native-browser-shell.patch create mode 100644 www-client/netsurf/patches/0012-native-downloads.patch create mode 100644 www-client/netsurf/patches/0013-bitmap-rowstride-and-bounds.patch diff --git a/README.md b/README.md index 758483b..a7b9148 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,6 @@ make ramdisk | libnspsl | [netsurf-browser/libnspsl](https://github.com/netsurf-browser/libnspsl) | Public suffix list, for cookie scoping. | | libnsgif | [netsurf-browser/libnsgif](https://github.com/netsurf-browser/libnsgif) | GIF decoder. | | libnsbmp | [netsurf-browser/libnsbmp](https://github.com/netsurf-browser/libnsbmp) | BMP/ICO decoder. | -| netsurf | [netsurf-browser/netsurf](https://github.com/netsurf-browser/netsurf) | NetSurf web browser - monkey frontend only. **Not** an app bundle. | +| netsurf | [netsurf-browser/netsurf](https://github.com/netsurf-browser/netsurf) | Native browser app built on NetSurf, with BearSSL HTTP/HTTPS, tabs, downloads, bookmarks, find, common image formats, and system TrueType fonts. | | doom | [ozkl/doomgeneric](https://github.com/ozkl/doomgeneric) | `doom1.wad` is proprietary and deliberately not bundled. | | git | [git/git](https://github.com/git/git) | v2.39.5. WIP - installs to `0:/os/git.elf`. Local repositories only. See [dev-vcs/git/README.md](dev-vcs/git/README.md). | diff --git a/www-client/netsurf/Makefile b/www-client/netsurf/Makefile index 79e39f3..9ea0aa8 100644 --- a/www-client/netsurf/Makefile +++ b/www-client/netsurf/Makefile @@ -1,22 +1,19 @@ -# NetSurf for MontaukOS -- monkey frontend +# NetSurf for MontaukOS -- native window browser frontend # # Upstream: https://github.com/netsurf-browser/netsurf (official mirror of # git.netsurf-browser.org; submodule at upstream/, pinned). # -# monkey is NetSurf's headless, script-driven frontend: it drives the full -# browser core (HTML parse -> CSS cascade -> layout) and reports through a -# line protocol on stdout instead of drawing. That makes it the right first -# target -- no graphics, no fonts, and no fetcher required. +# The port reuses NetSurf's compact monkey frontend glue, but MontaukOS builds +# enter a native Window Server event loop with browser chrome and render into a +# shared surface. The line-oriented monkey protocol remains available off-OS. # # CONFIGURATION -# NETSURF_USE_CURL=NO no libcurl port; the built-in file/about/data -# fetchers remain, which is enough for file:// URLs. -# fetch_montauk will register alongside them later. -# NETSURF_USE_OPENSSL=NO reached only through curl. BearSSL is the eventual -# answer, via the fetcher rather than through here. +# NETSURF_USE_CURL=NO no libcurl port; fetch_montauk uses MontaukOS DNS, +# TCP, and the native BearSSL TLS helper. +# NETSURF_USE_OPENSSL=NO HTTPS is implemented by fetch_montauk via BearSSL. # NETSURF_USE_DUKTAPE=NO no JavaScript; also avoids the nsgenbind host tool. -# NETSURF_USE_PNG/JPEG/ no ports for these codecs yet. GIF and BMP are on, -# JPEGXL/WEBP=NO since libnsgif and libnsbmp are in the sysroot. +# NETSURF_USE_JPEG=YES enables the port-local stb_image JPEG/PNG handler. +# JPEGXL/WEBP/SVG=NO those optional decoder libraries are not ported. # NETSURF_FS_BACKING_STORE=YES NOT optional for monkey: frontends/monkey/ # main.c references filesystem_llcache_table # unconditionally, and that lives in @@ -48,17 +45,20 @@ include ../../mk/port.mk # dropped at the top of the checkout, not inside build/. NS_BUILD_OUT := $(UPSTREAM)/nsmonkey -# Where the binary and its resources land on MontaukOS. NetSurf cannot start -# without its resources (Messages, default.css, quirks.css ...), so the -# resource path is compiled in and the tree is installed alongside. -NS_RESPATH := 0:/os/netsurf/ -NS_INSTALL := $(MONTAUKOS)/programs/bin/os +# NetSurf cannot start without its resources (Messages, default.css, +# quirks.css ...). Keep the compile-time fallback inside the app bundle; the +# MontaukOS frontend normally derives this directory from argv[0] at runtime. +NS_RESPATH := 0:/apps/netsurf/res/ +APP_INSTALL := $(MONTAUKOS)/programs/bin/apps/netsurf +LEGACY_INSTALL := $(MONTAUKOS)/programs/bin/os # libutf8proc installs its header to include/libutf8proc/ but its .pc # advertises only -I${includedir}, while utils/idna.c includes . # Add the subdirectory here rather than staging a duplicate copy of the header. NETSURF_CFLAGS := \ $(PORT_CFLAGS_COMMON) \ + -DWITH_MONTAUK_FETCHER \ + -DWITH_JPEG \ -I $(COMPAT) \ -I $(PORT_SYSROOT_INC)/libutf8proc @@ -68,10 +68,11 @@ NETSURF_ARGS := \ CC=$(CC) \ AR=$(AR) \ NETSURF_USE_CURL=NO \ + NETSURF_USE_MONTAUK=YES \ NETSURF_USE_OPENSSL=NO \ NETSURF_USE_DUKTAPE=NO \ NETSURF_USE_PNG=NO \ - NETSURF_USE_JPEG=NO \ + NETSURF_USE_JPEG=YES \ NETSURF_USE_JPEGXL=NO \ NETSURF_USE_WEBP=NO \ NETSURF_USE_VIDEO=NO \ @@ -85,46 +86,69 @@ NETSURF_ARGS := \ # so -lparserutils lands to the right of this archive and resolves the symbols # it references. COMPAT_LIB := $(CURDIR)/compat/libnscompat.a +BRIDGE_OBJ := $(CURDIR)/compat/montauk_bridge.o +STB_OBJ := $(CURDIR)/compat/stb_truetype_impl.o +STB_IMAGE_OBJ := $(CURDIR)/compat/stb_image_impl.o # NetSurf formats floats (page load time in the status bar, plot coordinates). # The Montauk libc keeps %e/%f/%g in a separate archive member reached by a # WEAK reference, and a weak undefined ref does not extract a member -- without # this the status bar reads "Done (%fs)". See programs/lib/libc/printf_float.c. -NS_LDFLAGS := $(COMPAT_LIB) -Wl,-u,_pf_putfloat +# Link the SDK libc explicitly as well. The cross compiler has a libc snapshot +# in its private sysroot, but that snapshot is only for bootstrapping the +# toolchain and can lag the OS tree. In particular, the older allocator is not +# safe for NetSurf's worker-thread allocation traffic. +NS_LDFLAGS := $(COMPAT_LIB) -L$(MONTAUKOS)/programs/lib/libjpeg \ + $(MONTAUKOS)/programs/lib/libloader/liblibloader.a \ + $(LIBTLS_A) $(LIBBEARSSL_A) $(LIBC_A) -Wl,-u,_pf_putfloat -$(COMPAT_LIB): compat/iconv.c compat/iconv.h +$(COMPAT_LIB): compat/iconv.c compat/iconv.h $(BRIDGE_OBJ) $(STB_OBJ) $(STB_IMAGE_OBJ) $(CC) $(NETSURF_CFLAGS) -std=gnu99 -c compat/iconv.c -o compat/iconv.o - $(AR) rcs $@ compat/iconv.o + $(AR) rcs $@ compat/iconv.o $(BRIDGE_OBJ) $(STB_OBJ) $(STB_IMAGE_OBJ) @echo "Built: $@" +$(BRIDGE_OBJ): files/montauk_bridge.cpp files/montauk_bridge.h + $(CXX) $(PORT_CFLAGS_COMMON) -std=gnu++20 -fno-exceptions -fno-rtti \ + -I $(MONTAUKOS)/programs/lib/bearssl/inc -I files -c $< -o $@ + +$(STB_OBJ): files/stb_truetype_impl.cpp + $(CXX) $(PORT_CFLAGS_COMMON) -std=gnu++20 -fno-exceptions -fno-rtti -c $< -o $@ + +$(STB_IMAGE_OBJ): compat/stb_image_impl.c + $(CC) $(PORT_CFLAGS_COMMON) -std=gnu11 -c $< -o $@ + .PHONY: all clean netsurf-build all: netsurf-build netsurf-build: $(COMPAT_LIB) @$(call ns_patch,$(UPSTREAM)) + cp files/montauk.c files/montauk.h files/montauk_bridge.h $(UPSTREAM)/content/fetchers/ + cp files/montauk_ui.c files/montauk_ui.h $(UPSTREAM)/frontends/monkey/ + cp files/montauk_image.c $(UPSTREAM)/content/handlers/image/jpeg.c CFLAGS="$(NETSURF_CFLAGS)" LDFLAGS="$(NS_LDFLAGS)" $(NS_ENV) \ $(NS_MAKE) -C $(UPSTREAM) $(NETSURF_ARGS) @test -f $(NS_BUILD_OUT) || { echo "netsurf: expected $(NS_BUILD_OUT)" >&2; exit 1; } @echo "Built: $(NS_BUILD_OUT) ($$(stat -c%s $(NS_BUILD_OUT)) bytes)" -# Install as a MontaukOS system program plus its resource tree. Not an app -# bundle: monkey is a console REPL with no GUI, so it carries no manifest and -# no .port marker -- remember to uninstall before cutting a release ISO. +# Install a self-contained app bundle, including NetSurf's resource tree. .PHONY: install uninstall install: netsurf-build - mkdir -p $(NS_INSTALL)/netsurf - cp $(NS_BUILD_OUT) $(NS_INSTALL)/nsmonkey.elf + rm -rf $(LEGACY_INSTALL)/nsmonkey.elf $(LEGACY_INSTALL)/netsurf + mkdir -p $(APP_INSTALL)/res + cp $(NS_BUILD_OUT) $(APP_INSTALL)/netsurf.elf + cp files/manifest.toml $(APP_INSTALL)/manifest.toml + cp $(MONTAUKOS)/programs/bin/os/icons/web-browser.svg $(APP_INSTALL)/web-browser.svg # -L, not -r alone: nearly everything in a NetSurf frontend res/ dir is a # symlink into ../../../resources/. Copied as links they dangle outside # the installed tree, and the ramdisk turns a symlink into a ZERO-LENGTH # FILE (Fs/Ramdisk.cpp only handles typeflag '5'), so default.css and # welcome.html arrive empty and every page lays out to nothing. - cp -rL $(UPSTREAM)/frontends/monkey/res/. $(NS_INSTALL)/netsurf/ - @echo "installed: 0:/os/nsmonkey.elf + 0:/os/netsurf/ resources" + cp -rL $(UPSTREAM)/frontends/monkey/res/. $(APP_INSTALL)/res/ + @echo "installed: self-contained NetSurf app bundle" uninstall: - rm -rf $(NS_INSTALL)/nsmonkey.elf $(NS_INSTALL)/netsurf - @echo "removed nsmonkey.elf and its resources" + rm -rf $(APP_INSTALL) $(LEGACY_INSTALL)/nsmonkey.elf $(LEGACY_INSTALL)/netsurf + @echo "removed NetSurf app bundle" clean: @$(NS_ENV) $(NS_MAKE) -C $(UPSTREAM) $(NETSURF_ARGS) clean >/dev/null 2>&1 || true diff --git a/www-client/netsurf/compat/stb_image_impl.c b/www-client/netsurf/compat/stb_image_impl.c new file mode 100644 index 0000000..cbe8b0d --- /dev/null +++ b/www-client/netsurf/compat/stb_image_impl.c @@ -0,0 +1,18 @@ +#include +#include + +#define STBI_NO_BMP +#define STBI_NO_PSD +#define STBI_NO_TGA +#define STBI_NO_GIF +#define STBI_NO_HDR +#define STBI_NO_PIC +#define STBI_NO_PNM +#define STBI_NO_LINEAR +#define STBI_NO_STDIO +#define STBI_NO_THREAD_LOCALS +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_FREE(p) free(p) +#define STBI_REALLOC(p, newsz) realloc(p, newsz) +#define STB_IMAGE_IMPLEMENTATION +#include diff --git a/www-client/netsurf/files/manifest.toml b/www-client/netsurf/files/manifest.toml new file mode 100644 index 0000000..b1c21dd --- /dev/null +++ b/www-client/netsurf/files/manifest.toml @@ -0,0 +1,11 @@ +[app] +name = "NetSurf" +binary = "netsurf.elf" +icon = "web-browser.svg" + +[menu] +category = "Internet" +visible = true + +[launch] +pass_home_dir = false diff --git a/www-client/netsurf/files/montauk.c b/www-client/netsurf/files/montauk.c new file mode 100644 index 0000000..5468428 --- /dev/null +++ b/www-client/netsurf/files/montauk.c @@ -0,0 +1,477 @@ +/* Native HTTP/HTTPS fetcher for MontaukOS. The OS exposes TCP and DNS as + * syscalls rather than BSD sockets; HTTPS is provided by the OS BearSSL + * helper and its system certificate bundle. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils/corestrings.h" +#include "utils/log.h" +#include "utils/nsurl.h" +#include "utils/ring.h" +#include "content/fetch.h" +#include "content/fetchers.h" +#include "content/fetchers/montauk.h" +#include "content/fetchers/montauk_bridge.h" + +#define RESPONSE_LIMIT (8u * 1024u * 1024u) + +#define IO_TIMEOUT_MS 30000u + +struct fetch_montauk_context { + struct fetch *parent; + nsurl *url; + char *post; + char **headers; + volatile bool aborted; + bool locked; + volatile int worker_state; /* 0 queued, 1 running, 2 complete */ + int worker_tid; + void *worker_stack; + char *response; + size_t response_len; + const char *transport_error; + struct fetch_montauk_context *r_next, *r_prev; +}; + +static struct fetch_montauk_context *ring; +static bool worker_busy; + +static void callback(struct fetch_montauk_context *ctx, const fetch_msg *msg) +{ + ctx->locked = true; + fetch_send_callback(msg, ctx->parent); + ctx->locked = false; +} + +static void fail(struct fetch_montauk_context *ctx, const char *reason) +{ + fetch_msg msg = { .type = FETCH_ERROR }; + msg.data.error = reason; + callback(ctx, &msg); +} + +static bool send_all(int fd, const uint8_t *data, size_t len) +{ + size_t off = 0; + while (off < len) { + int n = mtk_send(fd, data + off, len - off); + if (n <= 0) return false; + off += (size_t)n; + } + return true; +} + +static bool append(char **buf, size_t *len, size_t *cap, + const void *data, size_t amount) +{ + if (amount > RESPONSE_LIMIT - *len) return false; + if (*len + amount + 1 > *cap) { + size_t next = *cap ? *cap * 2 : 32768; + while (next < *len + amount + 1) next *= 2; + if (next > RESPONSE_LIMIT + 1) next = RESPONSE_LIMIT + 1; + char *replacement = realloc(*buf, next); + if (replacement == NULL) return false; + *buf = replacement; + *cap = next; + } + memcpy(*buf + *len, data, amount); + *len += amount; + (*buf)[*len] = 0; + return true; +} + +static bool read_response(struct fetch_montauk_context *ctx, int fd, + char **response, size_t *response_len) +{ + char chunk[8192]; + size_t cap = 0; + uint64_t deadline = mtk_get_ms() + IO_TIMEOUT_MS; + + *response = NULL; + *response_len = 0; + for (;;) { + if (ctx->aborted) return false; + int n = mtk_recv(fd, chunk, sizeof(chunk)); + if (n > 0) { + if (!append(response, response_len, &cap, chunk, (size_t)n)) + return false; + deadline = mtk_get_ms() + IO_TIMEOUT_MS; + } else if (n < 0) { + return *response_len != 0; + } else { + if (mtk_get_ms() >= deadline) return false; + mtk_sleep_ms(1); + } + } +} + +static char *find_header_end(char *response) +{ + char *p = strstr(response, "\r\n\r\n"); + return p == NULL ? NULL : p + 4; +} + +static bool header_value(const char *headers, const char *name, + char *value, size_t value_size) +{ + size_t name_len = strlen(name); + const char *first_end = strstr(headers, "\r\n"); + const char *line = first_end == NULL ? NULL : first_end + 2; + while (line != NULL && *line != '\r' && *line != 0) { + const char *end = strstr(line, "\r\n"); + if (end == NULL) break; + if ((size_t)(end - line) > name_len && + strncasecmp(line, name, name_len) == 0 && line[name_len] == ':') { + const char *start = line + name_len + 1; + size_t len; + while (*start == ' ' || *start == '\t') start++; + len = (size_t)(end - start); + if (len >= value_size) len = value_size - 1; + memcpy(value, start, len); + value[len] = 0; + return true; + } + line = end + 2; + } + return false; +} + +static bool dechunk(char *body, size_t body_len, size_t *decoded_len) +{ + char *src = body, *dst = body, *limit = body + body_len; + while (src < limit) { + char *line_end = strstr(src, "\r\n"); + char *endptr; + unsigned long amount; + if (line_end == NULL || line_end >= limit) return false; + *line_end = 0; + amount = strtoul(src, &endptr, 16); + if (endptr == src) return false; + src = line_end + 2; + if (amount == 0) { + *decoded_len = (size_t)(dst - body); + return true; + } + if (amount > (unsigned long)(limit - src) || + amount + 2 > (unsigned long)(limit - src)) return false; + memmove(dst, src, amount); + dst += amount; + src += amount; + if (src[0] != '\r' || src[1] != '\n') return false; + src += 2; + } + return false; +} + +static bool transport(struct fetch_montauk_context *ctx) +{ + char *scheme = NULL, *host = NULL, *port_text = NULL, *path = NULL; + size_t ignored; + uint16_t port = 80; + bool https = false; + uint32_t ip; + int fd = -1; + char *request = NULL, *response = NULL; + size_t request_len = 0, request_cap = 0, response_len = 0; + char line[1024]; + bool ok = false; + + if (nsurl_get(ctx->url, NSURL_SCHEME, &scheme, &ignored) != NSERROR_OK || + nsurl_get(ctx->url, NSURL_HOST, &host, &ignored) != NSERROR_OK || + nsurl_get(ctx->url, NSURL_PATH | NSURL_QUERY, &path, &ignored) != NSERROR_OK) { + ctx->transport_error = "Invalid HTTP(S) URL"; + goto done; + } + https = strcasecmp(scheme, "https") == 0; + port = https ? 443 : 80; + if (nsurl_has_component(ctx->url, NSURL_PORT) && + nsurl_get(ctx->url, NSURL_PORT, &port_text, &ignored) == NSERROR_OK) + port = (uint16_t)strtoul(port_text, NULL, 10); + if (*path == 0) { free(path); path = strdup("/"); } + ip = mtk_resolve(host); + if (ip == 0) { ctx->transport_error = "DNS resolution failed"; goto done; } + if (!https) { + fd = mtk_socket(MTK_SOCK_TCP); + if (fd < 0 || mtk_connect(fd, ip, port) < 0) { + ctx->transport_error = "Could not connect to server"; goto done; + } + } + + int n = snprintf(line, sizeof(line), "%s %s HTTP/1.1\r\nHost: %s%s%s\r\nUser-Agent: NetSurf-MontaukOS\r\nAccept: */*\r\nAccept-Encoding: identity\r\nConnection: close\r\n", + ctx->post ? "POST" : "GET", path, host, + port == (https ? 443 : 80) ? "" : ":", + port == (https ? 443 : 80) ? "" : (port_text ? port_text : (https ? "443" : "80"))); + if (n < 0 || !append(&request, &request_len, &request_cap, line, (size_t)n)) goto oom; + if (ctx->headers != NULL) { + for (char **h = ctx->headers; *h != NULL; h++) { + if (!append(&request, &request_len, &request_cap, *h, strlen(*h)) || + !append(&request, &request_len, &request_cap, "\r\n", 2)) goto oom; + } + } + if (ctx->post != NULL) { + n = snprintf(line, sizeof(line), "Content-Type: application/x-www-form-urlencoded\r\nContent-Length: %zu\r\n", strlen(ctx->post)); + if (n < 0 || !append(&request, &request_len, &request_cap, line, (size_t)n)) goto oom; + } + if (!append(&request, &request_len, &request_cap, "\r\n", 2) || + (ctx->post != NULL && !append(&request, &request_len, &request_cap, ctx->post, strlen(ctx->post)))) goto oom; + if (https) { + response = malloc(RESPONSE_LIMIT + 1); + if (response == NULL) goto oom; + int received = mtk_netsurf_https_fetch(host, ip, port, request, + request_len, response, RESPONSE_LIMIT + 1); + if (received <= 0) { + ctx->transport_error = "HTTPS transport or certificate verification failed"; + goto done; + } + if ((size_t)received >= RESPONSE_LIMIT) { + ctx->transport_error = "HTTP response exceeds size limit"; + goto done; + } + response_len = (size_t)received; + response[response_len] = 0; + } else if (!send_all(fd, (const uint8_t *)request, request_len) || + !read_response(ctx, fd, &response, &response_len)) { + ctx->transport_error = ctx->aborted ? "Request cancelled" : "HTTP transport failed"; + goto done; + } + ctx->response = response; + ctx->response_len = response_len; + response = NULL; + ok = true; + goto done; +oom: + ctx->transport_error = "Out of memory while fetching HTTP URL"; +done: + if (fd >= 0) mtk_closesocket(fd); + free(scheme); free(host); free(port_text); free(path); free(request); free(response); + return ok; +} + +static bool deliver_response(struct fetch_montauk_context *ctx) +{ + char *response = ctx->response; + size_t response_len = ctx->response_len, body_len; + char *body; + char location[2048], transfer[64]; + int status; + fetch_msg msg; + bool ok = false; + + body = find_header_end(response); + char *status_space = strncmp(response, "HTTP/", 5) == 0 ? strchr(response, ' ') : NULL; + if (body == NULL || status_space == NULL) { + fail(ctx, "Malformed HTTP response"); goto done; + } + status = (int)strtoul(status_space + 1, NULL, 10); + if (status < 100 || status > 599) { + fail(ctx, "Malformed HTTP status"); goto done; + } + body_len = response_len - (size_t)(body - response); + fetch_set_http_code(ctx->parent, status); + + if (status >= 300 && status < 400 && + header_value(response, "Location", location, sizeof(location))) { + msg.type = FETCH_REDIRECT; + msg.data.redirect = location; + callback(ctx, &msg); + ok = true; + goto done; + } + /* header_value temporarily terminates its matching line; find the header + * boundary again only after looking up all values we need. */ + if (header_value(response, "Transfer-Encoding", transfer, sizeof(transfer)) && + strcasecmp(transfer, "chunked") == 0 && + !dechunk(body, body_len, &body_len)) { + fail(ctx, "Malformed chunked HTTP response"); goto done; + } + + /* Match libcurl's header callback contract exactly: the status line, each + * header, and the terminating blank line arrive separately and retain + * their CRLF. llcache uses the status and blank lines to drive its header + * state machine; omitting either makes an otherwise valid fetch fail. */ + char *h = response; + while (h != NULL && h < body && !ctx->aborted) { + char *end = strstr(h, "\r\n"); + if (end == NULL || end > body) break; + msg.type = FETCH_HEADER; + msg.data.header_or_data.buf = (const uint8_t *)h; + msg.data.header_or_data.len = (size_t)(end - h) + 2; + callback(ctx, &msg); + h = end + 2; + } + if (!ctx->aborted && body_len != 0) { + msg.type = FETCH_DATA; + msg.data.header_or_data.buf = (const uint8_t *)body; + msg.data.header_or_data.len = body_len; + callback(ctx, &msg); + } + if (!ctx->aborted) { + msg.type = FETCH_FINISHED; + callback(ctx, &msg); + } + ok = true; + goto done; +done: + return ok; +} + +static void worker_entry(struct fetch_montauk_context *ctx) +{ + transport(ctx); + __atomic_store_n(&ctx->worker_state, 2, __ATOMIC_RELEASE); + _mtk_syscall1(MTK_SYS_THREAD_EXIT, 0); + __builtin_unreachable(); +} + +static bool start_worker(struct fetch_montauk_context *ctx) +{ + const size_t stack_size = 256u * 1024u; + ctx->worker_stack = malloc(stack_size); + if (ctx->worker_stack == NULL) return false; + uintptr_t top = ((uintptr_t)ctx->worker_stack + stack_size) & ~(uintptr_t)15; + ctx->worker_state = 1; + ctx->worker_tid = (int)_mtk_syscall3(MTK_SYS_THREAD_SPAWN, + (long)worker_entry, (long)ctx, (long)top); + if (ctx->worker_tid < 0) { + ctx->worker_state = 0; + free(ctx->worker_stack); + ctx->worker_stack = NULL; + return false; + } + worker_busy = true; + return true; +} + +static void join_worker(struct fetch_montauk_context *ctx) +{ + if (ctx->worker_tid > 0) { + _mtk_syscall2(MTK_SYS_THREAD_JOIN, ctx->worker_tid, 0); + ctx->worker_tid = 0; + } + free(ctx->worker_stack); + ctx->worker_stack = NULL; + worker_busy = false; +} + +static bool initialise(lwc_string *scheme) { (void)scheme; return true; } +static void finalise(lwc_string *scheme) { (void)scheme; } +static bool acceptable(const nsurl *url) { (void)url; return true; } + +static void *setup(struct fetch *parent, nsurl *url, bool only_2xx, + bool downgrade_tls, const char *post_urlenc, + const struct fetch_multipart_data *post_multipart, const char **headers) +{ + struct fetch_montauk_context *ctx = calloc(1, sizeof(*ctx)); + (void)only_2xx; (void)downgrade_tls; (void)post_multipart; + if (ctx == NULL) return NULL; + ctx->parent = parent; + ctx->url = nsurl_ref(url); + ctx->post = post_urlenc == NULL ? NULL : strdup(post_urlenc); + if (post_urlenc != NULL && ctx->post == NULL) { + nsurl_unref(ctx->url); + free(ctx); + return NULL; + } + if (headers != NULL) { + size_t count = 0; + while (headers[count] != NULL) count++; + ctx->headers = calloc(count + 1, sizeof(*ctx->headers)); + if (ctx->headers == NULL) { + nsurl_unref(ctx->url); free(ctx->post); free(ctx); + return NULL; + } + for (size_t i = 0; i < count; i++) { + ctx->headers[i] = strdup(headers[i]); + if (ctx->headers[i] == NULL) { + while (i != 0) free(ctx->headers[--i]); + free(ctx->headers); nsurl_unref(ctx->url); + free(ctx->post); free(ctx); + return NULL; + } + } + } + RING_INSERT(ring, ctx); + return ctx; +} + +static bool start(void *ctx) { (void)ctx; return true; } +static void abort_fetch(void *vctx) { ((struct fetch_montauk_context *)vctx)->aborted = true; } +static void free_fetch(void *vctx) +{ + struct fetch_montauk_context *ctx = vctx; + if (__atomic_load_n(&ctx->worker_state, __ATOMIC_ACQUIRE) == 1) { + ctx->aborted = true; + _mtk_syscall2(MTK_SYS_THREAD_JOIN, ctx->worker_tid, 0); + ctx->worker_tid = 0; + free(ctx->worker_stack); + ctx->worker_stack = NULL; + worker_busy = false; + } else if (ctx->worker_tid > 0) { + join_worker(ctx); + } + nsurl_unref(ctx->url); + free(ctx->post); + free(ctx->response); + if (ctx->headers != NULL) { + for (size_t i = 0; ctx->headers[i] != NULL; i++) + free(ctx->headers[i]); + free(ctx->headers); + } + free(ctx); +} + +static void poll_fetch(lwc_string *scheme) +{ + struct fetch_montauk_context *ctx, *saved = NULL; + (void)scheme; + while (ring != NULL) { + ctx = ring; + RING_REMOVE(ring, ctx); + if (ctx->locked) { RING_INSERT(saved, ctx); continue; } + int state = __atomic_load_n(&ctx->worker_state, __ATOMIC_ACQUIRE); + if (state == 2) { + join_worker(ctx); + if (!ctx->aborted) { + if (ctx->transport_error != NULL) fail(ctx, ctx->transport_error); + else deliver_response(ctx); + } + assert(!ctx->locked); + fetch_remove_from_queues(ctx->parent); + fetch_free(ctx->parent); + } else if (state == 0 && ctx->aborted) { + fetch_remove_from_queues(ctx->parent); + fetch_free(ctx->parent); + } else if (state == 0 && !worker_busy) { + if (start_worker(ctx)) { + RING_INSERT(saved, ctx); + } else { + fail(ctx, "Could not start network worker"); + fetch_remove_from_queues(ctx->parent); + fetch_free(ctx->parent); + } + } else { + RING_INSERT(saved, ctx); + } + } + ring = saved; +} + +nserror fetch_montauk_register(void) +{ + const struct fetcher_operation_table ops = { + .initialise = initialise, .acceptable = acceptable, .setup = setup, + .start = start, .abort = abort_fetch, .free = free_fetch, + .poll = poll_fetch, .finalise = finalise + }; + nserror err = fetcher_add(lwc_string_ref(corestring_lwc_http), &ops); + if (err != NSERROR_OK) return err; + return fetcher_add(lwc_string_ref(corestring_lwc_https), &ops); +} diff --git a/www-client/netsurf/files/montauk.h b/www-client/netsurf/files/montauk.h new file mode 100644 index 0000000..bf491c3 --- /dev/null +++ b/www-client/netsurf/files/montauk.h @@ -0,0 +1,8 @@ +#ifndef NETSURF_CONTENT_FETCHERS_MONTAUK_H +#define NETSURF_CONTENT_FETCHERS_MONTAUK_H + +#include "utils/errors.h" + +nserror fetch_montauk_register(void); + +#endif diff --git a/www-client/netsurf/files/montauk_bridge.cpp b/www-client/netsurf/files/montauk_bridge.cpp new file mode 100644 index 0000000..a132ea4 --- /dev/null +++ b/www-client/netsurf/files/montauk_bridge.cpp @@ -0,0 +1,794 @@ +/* C ABI bridge to MontaukOS' native TrueType and BearSSL stacks. */ + +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +} + +#include "montauk_bridge.h" + +namespace { + +enum FontFace { + SANS_REGULAR, SANS_BOLD, SANS_ITALIC, SANS_BOLD_ITALIC, + SERIF_REGULAR, SERIF_BOLD, SERIF_ITALIC, SERIF_BOLD_ITALIC, + MONO_REGULAR, MONO_BOLD, MONO_ITALIC, MONO_BOLD_ITALIC, + UI_MEDIUM, + FONT_FACE_COUNT +}; + +gui::TrueTypeFont fonts[FONT_FACE_COUNT]; +bool fonts_attempted; +bool font_valid[FONT_FACE_COUNT]; + +gui::SvgIcon icons[MTK_NETSURF_ICON_COUNT] = {}; +int icons_scale = -1; +int icon_size = 16; + +const char *icon_paths[MTK_NETSURF_ICON_COUNT] = { + "0:/os/icons/go-previous-symbolic.svg", + "0:/os/icons/go-next-symbolic.svg", + "0:/os/icons/view-refresh-symbolic.svg", + "0:/os/icons/media-stop.svg", + "0:/os/icons/go-home-symbolic.svg", + "0:/os/icons/window-close-symbolic.svg" +}; + +struct AddressInputSlot { + uintptr_t key; + gui::mtk::TextInputState state; + int view_start; + bool used; +}; + +AddressInputSlot address_inputs[MTK_NETSURF_MAX_TABS] = {}; +uint32_t *address_scratch; +size_t address_scratch_pixels; + +const char *font_paths[FONT_FACE_COUNT] = { + "0:/os/fonts/Roboto-Regular.ttf", "0:/os/fonts/Roboto-Bold.ttf", + "0:/os/fonts/Roboto-Italic.ttf", "0:/os/fonts/Roboto-BoldItalic.ttf", + "0:/os/fonts/NotoSerif-Regular.ttf", "0:/os/fonts/NotoSerif-SemiBold.ttf", + "0:/os/fonts/NotoSerif-Italic.ttf", "0:/os/fonts/NotoSerif-BoldItalic.ttf", + "0:/os/fonts/JetBrainsMono-Regular.ttf", "0:/os/fonts/JetBrainsMono-Bold.ttf", + "0:/os/fonts/JetBrainsMono-Italic.ttf", "0:/os/fonts/JetBrainsMono-BoldItalic.ttf", + "0:/os/fonts/Roboto-Medium.ttf" +}; + +void init_fonts() +{ + if (fonts_attempted) return; + fonts_attempted = true; + for (int i = 0; i < FONT_FACE_COUNT; i++) + font_valid[i] = fonts[i].init(font_paths[i]); + gui::fonts::system_font = font_valid[UI_MEDIUM] ? &fonts[UI_MEDIUM] : + (font_valid[SANS_REGULAR] ? &fonts[SANS_REGULAR] : nullptr); + gui::fonts::system_bold = font_valid[SANS_BOLD] ? &fonts[SANS_BOLD] : gui::fonts::system_font; + gui::fonts::mono = font_valid[MONO_REGULAR] ? &fonts[MONO_REGULAR] : nullptr; + gui::fonts::mono_bold = font_valid[MONO_BOLD] ? &fonts[MONO_BOLD] : gui::fonts::mono; +} + +AddressInputSlot *address_input_slot(uintptr_t key, bool create) +{ + for (int i = 0; i < MTK_NETSURF_MAX_TABS; i++) + if (address_inputs[i].used && address_inputs[i].key == key) + return &address_inputs[i]; + if (!create) return nullptr; + for (int i = 0; i < MTK_NETSURF_MAX_TABS; i++) { + if (!address_inputs[i].used) { + address_inputs[i] = {}; + address_inputs[i].used = true; + address_inputs[i].key = key; + gui::mtk::text_input_reset(address_inputs[i].state, 0); + return &address_inputs[i]; + } + } + return nullptr; +} + +gui::TrueTypeFont *select_font(int family, int weight, unsigned flags) +{ + init_fonts(); + int base = family == 1 ? SERIF_REGULAR : + (family == 2 ? MONO_REGULAR : SANS_REGULAR); + bool bold = weight >= 600; + bool italic = (flags & 3u) != 0; + if (family == 0 && !bold && !italic && weight >= 500 && + font_valid[UI_MEDIUM]) return &fonts[UI_MEDIUM]; + int face = base + (bold ? 1 : 0) + (italic ? 2 : 0); + if (!font_valid[face]) face = base + (bold ? 1 : 0); + if (!font_valid[face]) face = base; + return font_valid[face] ? &fonts[face] : nullptr; +} + +uint32_t next_utf8(const char *text, size_t length, size_t *offset) +{ + if (*offset >= length) return 0; + uint8_t c = static_cast(text[(*offset)++]); + if (c < 0x80) return c; + unsigned extra; + uint32_t value; + uint32_t minimum; + if ((c & 0xe0) == 0xc0) { extra = 1; value = c & 0x1f; minimum = 0x80; } + else if ((c & 0xf0) == 0xe0) { extra = 2; value = c & 0x0f; minimum = 0x800; } + else if ((c & 0xf8) == 0xf0) { extra = 3; value = c & 0x07; minimum = 0x10000; } + else return 0xfffd; + if (extra > length - *offset) { *offset = length; return 0xfffd; } + for (unsigned i = 0; i < extra; i++) { + uint8_t trail = static_cast(text[(*offset)++]); + if ((trail & 0xc0) != 0x80) return 0xfffd; + value = (value << 6) | (trail & 0x3f); + } + if (value < minimum || value > 0x10ffff || + (value >= 0xd800 && value <= 0xdfff)) return 0xfffd; + return value; +} + +int glyph_advance(gui::TrueTypeFont *font, gui::GlyphCache *cache, uint32_t cp) +{ + if (cp < 256) { + gui::CachedGlyph *glyph = font->get_glyph(cache, static_cast(cp)); + return glyph != nullptr ? glyph->advance : 0; + } + int advance = 0, lsb = 0; + stbtt_GetCodepointHMetrics(&font->info, static_cast(cp), &advance, &lsb); + return static_cast(advance * cache->scale); +} + +void blend_glyph(uint32_t *pixels, int width, int height, + int clip_x0, int clip_y0, int clip_x1, int clip_y1, + int x, int baseline, const uint8_t *bitmap, int glyph_width, + int glyph_height, int xoff, int yoff, gui::Color colour) +{ + if (bitmap == nullptr) return; + int gx = x + xoff, gy = baseline + yoff; + for (int row = 0; row < glyph_height; row++) { + int dy = gy + row; + if (dy < clip_y0 || dy >= clip_y1 || dy < 0 || dy >= height) continue; + for (int col = 0; col < glyph_width; col++) { + int dx = gx + col; + if (dx < clip_x0 || dx >= clip_x1 || dx < 0 || dx >= width) continue; + uint8_t alpha = bitmap[row * glyph_width + col]; + if (alpha == 0) continue; + uint32_t dst = pixels[dy * width + dx]; + unsigned inv = 255 - alpha; + unsigned r = (alpha * colour.r + inv * ((dst >> 16) & 255) + 128) / 255; + unsigned g = (alpha * colour.g + inv * ((dst >> 8) & 255) + 128) / 255; + unsigned b = (alpha * colour.b + inv * (dst & 255) + 128) / 255; + pixels[dy * width + dx] = 0xff000000u | (r << 16) | (g << 8) | b; + } + } +} + +} // namespace + +extern "C" void mtk_netsurf_icons_cleanup(void) +{ + for (int i = 0; i < MTK_NETSURF_ICON_COUNT; i++) { + if (icons[i].pixels != nullptr) gui::svg_free(icons[i]); + icons[i] = {}; + } + icons_scale = -1; +} + +extern "C" void mtk_netsurf_icons_set_scale(int scale) +{ + if (scale < 0) scale = 0; + if (scale > 2) scale = 2; + if (icons_scale == scale) return; + mtk_netsurf_icons_cleanup(); + icons_scale = scale; + icon_size = scale == 0 ? 14 : (scale == 2 ? 20 : 16); + gui::Color colour = gui::Color::from_rgb(55, 55, 55); + for (int i = 0; i < MTK_NETSURF_ICON_COUNT; i++) + icons[i] = gui::svg_load(icon_paths[i], icon_size, icon_size, colour); +} + +extern "C" int mtk_netsurf_icon_size(void) +{ + return icon_size; +} + +extern "C" void mtk_netsurf_icon_draw(int icon, uint32_t *pixels, + int width, int height, int x, int y, unsigned opacity) +{ + if (icon < 0 || icon >= MTK_NETSURF_ICON_COUNT || pixels == nullptr || + icons[icon].pixels == nullptr || opacity == 0) return; + if (opacity > 255) opacity = 255; + const gui::SvgIcon &source = icons[icon]; + for (int row = 0; row < source.height; row++) { + int dy = y + row; + if (dy < 0 || dy >= height) continue; + for (int col = 0; col < source.width; col++) { + int dx = x + col; + if (dx < 0 || dx >= width) continue; + uint32_t src = source.pixels[row * source.width + col]; + unsigned alpha = ((src >> 24) & 255u) * opacity / 255u; + if (alpha == 0) continue; + uint32_t dst = pixels[dy * width + dx]; + unsigned inv = 255 - alpha; + unsigned r = (alpha * ((src >> 16) & 255u) + inv * ((dst >> 16) & 255u) + 128) / 255; + unsigned g = (alpha * ((src >> 8) & 255u) + inv * ((dst >> 8) & 255u) + 128) / 255; + unsigned b = (alpha * (src & 255u) + inv * (dst & 255u) + 128) / 255; + pixels[dy * width + dx] = 0xff000000u | (r << 16) | (g << 8) | b; + } + } +} + +extern "C" void mtk_netsurf_address_input_focus(uintptr_t key, + const char *text, int select_all) +{ + AddressInputSlot *slot = address_input_slot(key, true); + if (slot == nullptr) return; + int length = text != nullptr ? static_cast(strlen(text)) : 0; + if (select_all) { + slot->state.selection_anchor = 0; + slot->state.cursor = length; + } else { + gui::mtk::text_input_set_cursor(slot->state, length, + slot->state.cursor, false); + } + slot->state.dragging = false; + gui::mtk::context_menu_close(slot->state.context); +} + +extern "C" int mtk_netsurf_address_input_key(uintptr_t key, char *text, + int capacity, int scancode, int ascii, int pressed, int shift, + int ctrl, int alt) +{ + AddressInputSlot *slot = address_input_slot(key, true); + if (slot == nullptr) return 0; + montauk::abi::KeyEvent event = {}; + event.scancode = static_cast(scancode); + event.ascii = static_cast(ascii); + event.pressed = pressed != 0; + event.shift = shift != 0; + event.ctrl = ctrl != 0; + event.alt = alt != 0; + return gui::mtk::text_input_key(slot->state, text, capacity, event); +} + +extern "C" int mtk_netsurf_address_input_mouse(uintptr_t key, char *text, + int capacity, int x, int y, int buttons, int previous_buttons, + int field_x, int field_y, int field_w, int field_h, + int view_w, int view_h, int focused) +{ + AddressInputSlot *slot = address_input_slot(key, true); + if (slot == nullptr) return 0; + gui::Rect bounds = {field_x, field_y, field_w, field_h}; + if (slot->state.context.open) { + return gui::mtk::text_input_handle_mouse(slot->state, bounds, + text, capacity, x, y, static_cast(buttons), + static_cast(previous_buttons), view_w, view_h, + focused != 0); + } + bool right_pressed = (buttons & 2) != 0 && (previous_buttons & 2) == 0; + if (right_pressed && bounds.contains(x, y)) { + int visible = gui::mtk::text_input_index_from_x(bounds, + text + slot->view_start, x, false); + int cursor = slot->view_start + visible; + int length = gui::mtk::text_input_len(text, capacity); + int start = gui::mtk::text_input_selection_start(slot->state, length); + int end = gui::mtk::text_input_selection_end(slot->state, length); + if (start == end || cursor < start || cursor > end) + gui::mtk::text_input_set_cursor(slot->state, length, cursor, false); + slot->state.dragging = false; + gui::mtk::context_menu_open(slot->state.context, x, y); + return gui::mtk::TEXT_INPUT_CONSUMED; + } + + gui::mtk::TextInputState visible = slot->state; + visible.cursor = visible.cursor > slot->view_start ? + visible.cursor - slot->view_start : 0; + visible.selection_anchor = visible.selection_anchor > slot->view_start ? + visible.selection_anchor - slot->view_start : 0; + int result = gui::mtk::text_input_mouse(visible, bounds, + text + slot->view_start, x, y, static_cast(buttons), + static_cast(previous_buttons), focused != 0, false); + slot->state.cursor = visible.cursor + slot->view_start; + slot->state.selection_anchor = visible.selection_anchor + slot->view_start; + slot->state.dragging = visible.dragging; + return result; +} + +extern "C" void mtk_netsurf_address_input_destroy(uintptr_t key) +{ + AddressInputSlot *slot = address_input_slot(key, false); + if (slot != nullptr) *slot = {}; +} + +extern "C" int mtk_netsurf_page_scrollbar_mouse(int x, int y, int buttons, + int previous_buttons, int width, int height, int chrome_height, + int content_extent, int *scroll_y, int *hovered, int *dragging, + int *drag_offset) +{ + if (scroll_y == nullptr || hovered == nullptr || dragging == nullptr || + drag_offset == nullptr) return 0; + gui::Rect viewport = {0, chrome_height, width, height - chrome_height}; + gui::Rect track = gui::mtk::scrollbar_track_rect(viewport); + gui::Rect thumb = gui::mtk::scrollbar_thumb_rect(track, + content_extent, viewport.h, *scroll_y); + bool left_down = (buttons & 1) != 0; + bool just_pressed = left_down && (previous_buttons & 1) == 0; + bool just_released = !left_down && (previous_buttons & 1) != 0; + bool scrollable = !track.empty() && !thumb.empty(); + int result = 0; + + int hover_now = scrollable && track.contains(x, y) ? 1 : 0; + if (*hovered != hover_now) { + *hovered = hover_now; + result |= 2; + } + if (!scrollable) { + if (*dragging) { + *dragging = 0; + result |= 2; + } + return result; + } + + if (*dragging) { + if (left_down) { + int next = gui::mtk::scrollbar_offset_from_thumb_top(track, + content_extent, viewport.h, thumb.h, y - *drag_offset); + if (next != *scroll_y) { + *scroll_y = next; + result |= 2; + } + } + if (just_released) { + *dragging = 0; + result |= 2; + } + return result | 1; + } + + if (just_pressed && track.contains(x, y)) { + *dragging = 1; + if (thumb.contains(x, y)) { + *drag_offset = y - thumb.y; + } else { + *drag_offset = thumb.h / 2; + *scroll_y = gui::mtk::scrollbar_offset_from_thumb_top(track, + content_extent, viewport.h, thumb.h, y - *drag_offset); + } + return result | 3; + } + return result; +} + +extern "C" int mtk_netsurf_page_view_width(int width) +{ + int view_width = width - gui::mtk::SCROLLBAR_W; + return view_width > 1 ? view_width : 1; +} + +extern "C" void mtk_netsurf_chrome_metrics(int scale, int width, int tab_count, + struct mtk_netsurf_chrome_metrics *m) +{ + if (m == nullptr) return; + if (scale < 0) scale = 0; + if (scale > 2) scale = 2; + memset(m, 0, sizeof(*m)); + const int margin = scale == 2 ? 12 : 8; + const int nav_h = scale == 0 ? 36 : (scale == 2 ? 54 : 44); + m->tab_h = scale == 0 ? 30 : (scale == 2 ? 44 : 36); + m->chrome_h = m->tab_h + nav_h; + m->status_h = 0; + m->button_size = scale == 0 ? 26 : (scale == 2 ? 36 : 30); + m->button_gap = scale == 2 ? 7 : 5; + m->button_y = m->tab_h + (nav_h - m->button_size) / 2; + m->back_x = margin; + m->forward_x = m->back_x + m->button_size + m->button_gap; + m->reload_x = m->forward_x + m->button_size + m->button_gap; + m->address_x = m->reload_x + m->button_size + margin + 2; + m->address_y = m->button_y; + m->address_h = m->button_size; + m->menu_x = width - margin - m->button_size; + m->home_x = m->menu_x - m->button_gap - m->button_size; + m->address_w = m->home_x - m->button_gap - m->address_x; + if (m->address_w < 100) m->address_w = 100; + + m->tab_x = margin; + m->tab_y = scale == 2 ? 5 : 4; + m->add_w = m->tab_h - 8; + m->add_h = m->tab_h - 8; + m->add_y = 4; + int tabs_available = width - margin * 2 - m->add_w - m->button_gap; + if (tab_count < 1) tab_count = 1; + m->tab_w = tabs_available / tab_count; + int tab_max = scale == 0 ? 190 : (scale == 2 ? 260 : 220); + int tab_min = scale == 0 ? 50 : (scale == 2 ? 70 : 58); + if (m->tab_w > tab_max) m->tab_w = tab_max; + if (m->tab_w < tab_min) m->tab_w = tab_min; + m->tab_content_w = m->tab_w - m->button_gap; + m->tab_close_w = scale == 2 ? 30 : 26; + m->add_x = m->tab_x + tab_count * m->tab_w; + + /* MTK menus deliberately keep the same compact physical metrics as the + * desktop context menu. Browser chrome scales, but scaling this popup as + * well made it unusually sparse beside every other system menu. It needs + * a little more width than the desktop menu for keyboard shortcuts. */ + m->popup_w = 220; + m->popup_item_h = 28; + m->popup_x = width - margin - m->popup_w; + m->popup_y = m->chrome_h + 4; +} + +namespace { + +gui::Color chrome_colour(unsigned value) +{ + return gui::Color::from_rgb(value, value, value); +} + +bool chrome_contains(int px, int py, int x, int y, int w, int h) +{ + return px >= x && py >= y && px < x + w && py < y + h; +} + +int chrome_text_baseline(int y, int height, int weight, int size) +{ + gui::TrueTypeFont *font = select_font(0, weight, 0); + if (font == nullptr) return y + (height + size) / 2; + gui::GlyphCache *cache = font->get_cache(size > 0 ? size : 1); + return y + (height - cache->line_height) / 2 + cache->ascent; +} + +void chrome_text(const mtk_netsurf_chrome_model *model, int x, int baseline, + const char *value, gui::Color colour, int weight, int size) +{ + if (value == nullptr) return; + mtk_netsurf_font_draw(model->pixels, model->width, model->height, + 0, 0, model->width, model->height, x, baseline, value, strlen(value), + colour.to_pixel(), 0, weight, 0, size); +} + +void chrome_text_clipped(const mtk_netsurf_chrome_model *model, int x, + int baseline, int available, const char *value, gui::Color colour, + int weight, int size) +{ + if (value == nullptr || available <= 0) return; + if (mtk_netsurf_font_width(0, weight, 0, size, value, strlen(value)) <= available) { + chrome_text(model, x, baseline, value, colour, weight, size); + return; + } + char clipped[128]; + size_t length = strlen(value); + if (length > sizeof(clipped) - 4) length = sizeof(clipped) - 4; + memcpy(clipped, value, length); + while (length > 0) { + clipped[length] = 0; + strcpy(clipped + length, "..."); + if (mtk_netsurf_font_width(0, weight, 0, size, clipped, + length + 3) <= available) break; + do { length--; } + while (length > 0 && (static_cast(clipped[length]) & 0xc0) == 0x80); + } + chrome_text(model, x, baseline, clipped, colour, weight, size); +} + +void chrome_icon_button(gui::Canvas &canvas, + const mtk_netsurf_chrome_model *model, int x, int y, int size, + int icon, bool enabled, bool active = false) +{ + bool hover = chrome_contains(model->mouse_x, model->mouse_y, x, y, size, size); + gui::Color bg = active ? gui::Color::from_rgb(0xc0, 0xd0, 0xe8) : + (hover && enabled ? chrome_colour(0xdc) : chrome_colour(0xe8)); + canvas.fill_rounded_rect(x, y, size, size, 4, bg); + int is = mtk_netsurf_icon_size(); + mtk_netsurf_icon_draw(icon, model->pixels, model->width, model->height, + x + (size - is) / 2, y + (size - is) / 2, enabled ? 255 : 72); +} + +void chrome_address(gui::Canvas &canvas, const mtk_netsurf_chrome_model *model, + const struct mtk_netsurf_chrome_metrics &m, int font) +{ + AddressInputSlot *slot = address_input_slot(model->address_input_key, true); + if (slot == nullptr) return; + const char *text = model->address != nullptr ? model->address : ""; + int length = static_cast(strlen(text)); + slot->state.cursor = gui::mtk::text_input_clamp_cursor(slot->state.cursor, length); + slot->state.selection_anchor = gui::mtk::text_input_clamp_cursor( + slot->state.selection_anchor, length); + int start = 0; + if (model->address_focus) { + int cursor = slot->state.cursor; + int available = m.address_w - 18; + int used = 0; + start = cursor; + while (start > 0) { + int previous = start - 1; + while (previous > 0 && + (static_cast(text[previous]) & 0xc0) == 0x80) + previous--; + int glyph = mtk_netsurf_font_width(0, 500, 0, font, + text + previous, static_cast(start - previous)); + if (used + glyph > available) break; + used += glyph; + start = previous; + } + } + slot->view_start = start; + + size_t needed = static_cast(m.address_w) * m.address_h; + if (needed > address_scratch_pixels) { + uint32_t *replacement = static_cast(malloc(needed * sizeof(uint32_t))); + if (replacement != nullptr) { + free(address_scratch); + address_scratch = replacement; + address_scratch_pixels = needed; + } + } + if (address_scratch == nullptr) return; + gui::Canvas field(address_scratch, m.address_w, m.address_h); + gui::mtk::Theme theme = gui::mtk::make_theme(); + field.fill(chrome_colour(0xf5)); + gui::Rect bounds = {0, 0, m.address_w, m.address_h}; + int relative_cursor = slot->state.cursor - start; + int relative_anchor = slot->state.selection_anchor > start ? + slot->state.selection_anchor - start : 0; + gui::fonts::UI_SIZE = font; + gui::mtk::draw_text_field(field, bounds, text + start, + relative_cursor, model->address_focus != 0, false, theme, + relative_anchor); + for (int row = 0; row < m.address_h; row++) + for (int col = 0; col < m.address_w; col++) + model->pixels[(m.address_y + row) * model->width + m.address_x + col] = + address_scratch[row * m.address_w + col]; +} + +void chrome_address_context(gui::Canvas &canvas, + const mtk_netsurf_chrome_model *model) +{ + AddressInputSlot *slot = address_input_slot(model->address_input_key, false); + if (slot == nullptr || !slot->state.context.open) return; + gui::mtk::Theme theme = gui::mtk::make_theme(); + gui::mtk::draw_text_input_context_menu(canvas, slot->state, theme, + gui::mtk::text_input_has_selection(slot->state, + model->address != nullptr ? model->address : "")); +} + +void chrome_popup(gui::Canvas &canvas, const mtk_netsurf_chrome_model *model, + const struct mtk_netsurf_chrome_metrics &m) +{ + if (model->menu_mode == 0) return; + static const gui::mtk::ContextMenuItem menu_items[] = { + {"New Tab", 0, true, "Ctrl + T", false}, + {"Find in Page", 1, true, "Ctrl + F", false}, + {"Bookmark Page", 2, true, "Ctrl + D", false}, + {"Bookmarks", 3, true, nullptr, false}, + {"Copy Address", 4, true, nullptr, true}, + {"Zoom In", 5, true, "Ctrl + +", false}, + {"Zoom Out", 6, true, "Ctrl + -", false}, + {"Reset Zoom", 7, true, "Ctrl + 0", true}, + {"Home", 8, true, nullptr, false}, + {"Quit", 9, true, nullptr, false}, + }; + gui::mtk::ContextMenuItem bookmark_items[MTK_NETSURF_MAX_BOOKMARK_ROWS + 1] = {}; + const gui::mtk::ContextMenuItem *items = menu_items; + int rows = 10; + if (model->menu_mode != 1) { + rows = model->bookmark_count + 1; + bookmark_items[0] = {"Bookmarks", -1, true, nullptr, true}; + for (int row = 1; row < rows; row++) + bookmark_items[row] = { + model->bookmark_titles[row - 1], row - 1, true, nullptr, false}; + items = bookmark_items; + } + + gui::mtk::ContextMenuState state = {true, m.popup_x, m.popup_y, -1}; + state.hover = gui::mtk::context_menu_hit_item(state, rows, + model->mouse_x, model->mouse_y, model->width, model->height, + m.popup_w, m.popup_item_h); + gui::mtk::Theme theme = gui::mtk::make_theme(); + /* Use the context-menu widget's native system font. Passing the browser's + * TrueType chrome font here gave this otherwise-native widget a visibly + * different weight and baseline. */ + gui::mtk::draw_context_menu(canvas, state, items, rows, theme, + m.popup_w, m.popup_item_h); +} + +} // namespace + +extern "C" void mtk_netsurf_chrome_draw(const struct mtk_netsurf_chrome_model *model) +{ + if (model == nullptr || model->pixels == nullptr || model->width <= 0 || model->height <= 0) + return; + mtk_netsurf_icons_set_scale(model->scale); + struct mtk_netsurf_chrome_metrics m; + mtk_netsurf_chrome_metrics(model->scale, model->width, model->tab_count, &m); + gui::Canvas canvas(model->pixels, model->width, model->height); + int font = model->scale == 0 ? 14 : (model->scale == 2 ? 22 : 18); + gui::Color toolbar = gui::Color::from_rgb(0xf5, 0xf5, 0xf5); + canvas.fill_rect(0, 0, model->width, m.chrome_h, toolbar); + canvas.hline(0, m.chrome_h - 1, model->width, chrome_colour(0xca)); + + for (int i = 0; i < model->tab_count; i++) { + int x = m.tab_x + i * m.tab_w; + int h = m.tab_h - m.tab_y; + bool active = i == model->active_tab; + bool hover = chrome_contains(model->mouse_x, model->mouse_y, + x, m.tab_y, m.tab_content_w, h); + gui::Color bg = active ? gui::Color::from_rgb(0xff, 0xff, 0xff) : + (hover ? chrome_colour(0xe2) : chrome_colour(0xeb)); + canvas.fill_rounded_rect(x, m.tab_y, m.tab_content_w, h, 5, bg); + int close_x = x + m.tab_content_w - m.tab_close_w; + int is = mtk_netsurf_icon_size(); + if (chrome_contains(model->mouse_x, model->mouse_y, + close_x, m.tab_y, m.tab_close_w, h)) + canvas.fill_rounded_rect(close_x + 2, m.tab_y + (h - 24) / 2, + m.tab_close_w - 4, 24, 4, chrome_colour(0xdc)); + int tab_weight = active ? 600 : 500; + int tab_baseline = chrome_text_baseline(m.tab_y, h, tab_weight, font); + chrome_text_clipped(model, x + 11, tab_baseline, + m.tab_content_w - m.tab_close_w - 18, + model->tab_titles[i] != nullptr ? model->tab_titles[i] : "New Tab", + chrome_colour(0x32), tab_weight, font); + mtk_netsurf_icon_draw(MTK_NETSURF_ICON_CLOSE, model->pixels, model->width, + model->height, close_x + (m.tab_close_w - is) / 2, + m.tab_y + (h - is) / 2, hover || active ? 155 : 100); + } + bool add_hover = chrome_contains(model->mouse_x, model->mouse_y, + m.add_x, m.add_y, m.add_w, m.add_h); + canvas.fill_rounded_rect(m.add_x, m.add_y, m.add_w, m.add_h, 4, + add_hover ? chrome_colour(0xdc) : chrome_colour(0xe8)); + int plus_size = font + 2; + int plus_w = mtk_netsurf_font_width(0, 500, 0, plus_size, "+", 1); + int plus_baseline = chrome_text_baseline(m.add_y, m.add_h, 500, plus_size); + chrome_text(model, m.add_x + (m.add_w - plus_w) / 2, + plus_baseline, "+", chrome_colour(0x42), 500, plus_size); + + chrome_icon_button(canvas, model, m.back_x, m.button_y, m.button_size, + MTK_NETSURF_ICON_BACK, model->can_back != 0); + chrome_icon_button(canvas, model, m.forward_x, m.button_y, m.button_size, + MTK_NETSURF_ICON_FORWARD, model->can_forward != 0); + chrome_icon_button(canvas, model, m.reload_x, m.button_y, m.button_size, + model->loading ? MTK_NETSURF_ICON_STOP : MTK_NETSURF_ICON_RELOAD, true); + chrome_address(canvas, model, m, font); + chrome_icon_button(canvas, model, m.home_x, m.button_y, m.button_size, + MTK_NETSURF_ICON_HOME, true); + bool menu_hover = chrome_contains(model->mouse_x, model->mouse_y, + m.menu_x, m.button_y, m.button_size, m.button_size); + canvas.fill_rounded_rect(m.menu_x, m.button_y, m.button_size, m.button_size, 4, + model->menu_mode || menu_hover ? chrome_colour(0xdc) : chrome_colour(0xe8)); + for (int dot = -1; dot <= 1; dot++) + canvas.fill_rounded_rect(m.menu_x + m.button_size / 2 + dot * 5 - 1, + m.button_y + m.button_size / 2 - 1, 3, 3, 1, chrome_colour(0x37)); + + gui::Rect viewport = {0, m.chrome_h, model->width, + model->height - m.chrome_h}; + gui::Rect scrollbar_track = gui::mtk::scrollbar_track_rect(viewport); + gui::Rect scrollbar_thumb = gui::mtk::scrollbar_thumb_rect(scrollbar_track, + model->extent_height, viewport.h, model->scroll_y); + gui::mtk::Theme scrollbar_theme = gui::mtk::make_theme(); + gui::mtk::draw_scrollbar(canvas, scrollbar_track, scrollbar_thumb, + model->scrollbar_hover != 0, model->scrollbar_dragging != 0, + scrollbar_theme); + + if (model->find_focus) { + int fw = model->scale == 2 ? 520 : 410; + int fh = model->scale == 2 ? 46 : 36; + int fx = model->width - fw - 18, fy = m.chrome_h + 10; + canvas.fill_rounded_rect(fx + 3, fy + 4, fw, fh, 5, + chrome_colour(0xd2)); + canvas.fill_rounded_rect(fx, fy, fw, fh, 5, + gui::Color::from_rgb(0xff, 0xff, 0xff)); + chrome_text(model, fx + 12, fy + (fh + font - 2) / 2, + "Find", chrome_colour(0x46), 600, font - 2); + canvas.fill_rounded_rect(fx + 58, fy + 6, fw - 178, fh - 12, 3, + chrome_colour(0xf4)); + chrome_text_clipped(model, fx + 66, fy + (fh + font - 2) / 2, + fw - 196, model->find_text != nullptr ? model->find_text : "", + chrome_colour(0x28), 500, font - 2); + chrome_text(model, fx + fw - 108, fy + (fh + font - 3) / 2, + "Enter next", chrome_colour(0x78), 500, font - 3); + } + if (model->status != nullptr && model->status[0] != 0 && + strncmp(model->status, "Done", 4) != 0 && + strcmp(model->status, "Ready") != 0 && + strncmp(model->status, "Loading", 7) != 0) { + int status_font = font > 14 ? font - 3 : 12; + int sw = mtk_netsurf_font_width(0, 500, 0, status_font, + model->status, strlen(model->status)); + int max_w = model->width * 2 / 3; + if (sw + 20 > max_w) sw = max_w - 20; + int box_w = sw + 20, box_h = status_font + 12; + int sy = model->height - box_h - 8; + canvas.fill_rounded_rect(8, sy, box_w, box_h, 4, chrome_colour(0xc8)); + canvas.fill_rounded_rect(9, sy + 1, box_w - 2, box_h - 2, 3, + gui::Color::from_rgb(0xff, 0xff, 0xff)); + chrome_text_clipped(model, 18, sy + (box_h + status_font) / 2, + box_w - 20, model->status, chrome_colour(0x4a), 500, status_font); + } + chrome_popup(canvas, model, m); + chrome_address_context(canvas, model); +} + +extern "C" int mtk_netsurf_font_width(int family, int weight, unsigned flags, + int size, const char *text, size_t length) +{ + gui::TrueTypeFont *font = select_font(family, weight, flags); + if (font == nullptr || text == nullptr) return size * static_cast(length); + gui::GlyphCache *cache = font->get_cache(size > 0 ? size : 1); + int result = 0; + for (size_t offset = 0; offset < length; ) + result += glyph_advance(font, cache, next_utf8(text, length, &offset)); + return result; +} + +extern "C" void mtk_netsurf_font_draw(uint32_t *pixels, int width, int height, + int clip_x0, int clip_y0, int clip_x1, int clip_y1, + int x, int baseline, const char *text, size_t length, + uint32_t colour, int family, int weight, unsigned flags, int size) +{ + gui::TrueTypeFont *font = select_font(family, weight, flags); + if (font == nullptr || text == nullptr || pixels == nullptr) return; + int pixel_size = size > 0 ? size : 1; + gui::GlyphCache *cache = font->get_cache(pixel_size); + gui::Color c = gui::Color::from_rgb((colour >> 16) & 255, + (colour >> 8) & 255, colour & 255); + int cursor = x; + for (size_t offset = 0; offset < length; ) { + uint32_t cp = next_utf8(text, length, &offset); + if (cp < 256) { + gui::CachedGlyph *glyph = font->get_glyph(cache, static_cast(cp)); + if (glyph != nullptr) { + blend_glyph(pixels, width, height, clip_x0, clip_y0, clip_x1, clip_y1, + cursor, baseline, glyph->bitmap, glyph->width, glyph->height, + glyph->xoff, glyph->yoff, c); + cursor += glyph->advance; + } + continue; + } + int x0, y0, x1, y1; + stbtt_GetCodepointBitmapBox(&font->info, static_cast(cp), + cache->scale, cache->scale, &x0, &y0, &x1, &y1); + int glyph_width = x1 - x0, glyph_height = y1 - y0; + uint8_t *bitmap = nullptr; + if (glyph_width > 0 && glyph_height > 0 && + (size_t)glyph_width * glyph_height <= 1024u * 1024u) { + bitmap = static_cast(malloc((size_t)glyph_width * glyph_height)); + if (bitmap != nullptr) + stbtt_MakeCodepointBitmap(&font->info, bitmap, glyph_width, glyph_height, + glyph_width, cache->scale, cache->scale, static_cast(cp)); + } + blend_glyph(pixels, width, height, clip_x0, clip_y0, clip_x1, clip_y1, + cursor, baseline, bitmap, glyph_width, glyph_height, x0, y0, c); + free(bitmap); + cursor += glyph_advance(font, cache, cp); + } +} + +extern "C" int mtk_netsurf_https_fetch(const char *host, uint32_t ip, + uint16_t port, const char *request, size_t request_len, + char *response, size_t response_size) +{ + static tls::TrustAnchors anchors = {}; + static bool anchors_attempted; + if (!anchors_attempted) { + anchors = tls::load_trust_anchors(); + anchors_attempted = true; + } + if (anchors.count == 0 || request_len > 0x7fffffff || + response_size > 0x7fffffff) return -1; + return tls::https_fetch(host, ip, port, request, + static_cast(request_len), anchors, response, + static_cast(response_size)); +} + +extern "C" int mtk_netsurf_open_file(char *path, size_t path_size) +{ + if (path == nullptr || path_size == 0 || path_size > 0x7fffffff) return 0; + char message[128] = {}; + path[0] = 0; + return gui::dialogs::open_file("Select File", "", path, + static_cast(path_size), message, sizeof(message)) ? 1 : 0; +} diff --git a/www-client/netsurf/files/montauk_bridge.h b/www-client/netsurf/files/montauk_bridge.h new file mode 100644 index 0000000..f6a1b10 --- /dev/null +++ b/www-client/netsurf/files/montauk_bridge.h @@ -0,0 +1,98 @@ +#ifndef NETSURF_MONTAUK_BRIDGE_H +#define NETSURF_MONTAUK_BRIDGE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int mtk_netsurf_font_width(int family, int weight, unsigned flags, int size, + const char *text, size_t length); +void mtk_netsurf_font_draw(uint32_t *pixels, int width, int height, + int clip_x0, int clip_y0, int clip_x1, int clip_y1, + int x, int baseline, const char *text, size_t length, + uint32_t colour, int family, int weight, unsigned flags, int size); + +enum mtk_netsurf_icon { + MTK_NETSURF_ICON_BACK, + MTK_NETSURF_ICON_FORWARD, + MTK_NETSURF_ICON_RELOAD, + MTK_NETSURF_ICON_STOP, + MTK_NETSURF_ICON_HOME, + MTK_NETSURF_ICON_CLOSE, + MTK_NETSURF_ICON_COUNT +}; + +void mtk_netsurf_icons_set_scale(int scale); +void mtk_netsurf_icon_draw(int icon, uint32_t *pixels, int width, int height, + int x, int y, unsigned opacity); +int mtk_netsurf_icon_size(void); +void mtk_netsurf_icons_cleanup(void); + +#define MTK_NETSURF_MAX_TABS 16 +#define MTK_NETSURF_MAX_BOOKMARK_ROWS 8 + +struct mtk_netsurf_chrome_metrics { + int chrome_h, status_h, tab_h; + int tab_x, tab_y, tab_w, tab_content_w, tab_close_w; + int add_x, add_y, add_w, add_h; + int button_y, button_size, button_gap; + int back_x, forward_x, reload_x; + int address_x, address_y, address_w, address_h; + int home_x, menu_x; + int popup_x, popup_y, popup_w, popup_item_h; +}; + +struct mtk_netsurf_chrome_model { + uint32_t *pixels; + int width, height, scale; + int mouse_x, mouse_y; + int tab_count, active_tab; + const char *tab_titles[MTK_NETSURF_MAX_TABS]; + int can_back, can_forward, loading; + int address_focus; + uintptr_t address_input_key; + const char *address; + int menu_mode; + int bookmark_count; + const char *bookmark_titles[MTK_NETSURF_MAX_BOOKMARK_ROWS]; + int find_focus; + const char *find_text; + const char *status; + int extent_height, scroll_y; + int scrollbar_hover, scrollbar_dragging; +}; + +void mtk_netsurf_chrome_metrics(int scale, int width, int tab_count, + struct mtk_netsurf_chrome_metrics *metrics); +void mtk_netsurf_chrome_draw(const struct mtk_netsurf_chrome_model *model); + +void mtk_netsurf_address_input_focus(uintptr_t key, const char *text, + int select_all); +int mtk_netsurf_address_input_key(uintptr_t key, char *text, int capacity, + int scancode, int ascii, int pressed, int shift, int ctrl, int alt); +int mtk_netsurf_address_input_mouse(uintptr_t key, char *text, int capacity, + int x, int y, int buttons, int previous_buttons, + int field_x, int field_y, int field_w, int field_h, + int view_w, int view_h, int focused); +void mtk_netsurf_address_input_destroy(uintptr_t key); + +int mtk_netsurf_page_scrollbar_mouse(int x, int y, int buttons, + int previous_buttons, int width, int height, int chrome_height, + int content_extent, int *scroll_y, int *hovered, int *dragging, + int *drag_offset); +int mtk_netsurf_page_view_width(int width); + +int mtk_netsurf_https_fetch(const char *host, uint32_t ip, uint16_t port, + const char *request, size_t request_len, + char *response, size_t response_size); + +int mtk_netsurf_open_file(char *path, size_t path_size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/www-client/netsurf/files/montauk_image.c b/www-client/netsurf/files/montauk_image.c new file mode 100644 index 0000000..a123e45 --- /dev/null +++ b/www-client/netsurf/files/montauk_image.c @@ -0,0 +1,131 @@ +/* JPEG and PNG content handler using MontaukOS's stb_image decoder. */ +#include +#include +#include +#include +#include +#include +#include + +#include "utils/errors.h" +#include "utils/nsurl.h" +#include "utils/utils.h" +#include "netsurf/bitmap.h" +#include "content/llcache.h" +#include "content/content.h" +#include "content/content_protected.h" +#include "content/content_factory.h" +#include "desktop/gui_internal.h" +#include "desktop/bitmap.h" +#include "image/image_cache.h" +#include "image/jpeg.h" + +static nserror nsjpeg_create(const content_handler *handler, + lwc_string *mime, const struct http_parameter *params, + llcache_handle *llcache, const char *fallback_charset, + bool quirks, struct content **result) +{ + struct content *image = calloc(1, sizeof(*image)); + if (image == NULL) return NSERROR_NOMEM; + nserror err = content__init(image, handler, mime, params, llcache, + fallback_charset, quirks); + if (err != NSERROR_OK) { free(image); return err; } + *result = image; + return NSERROR_OK; +} + +static struct bitmap *montauk_image_decode(struct content *content) +{ + size_t size; + const uint8_t *source = content__get_source_data(content, &size); + if (source == NULL || size == 0 || size > INT_MAX) return NULL; + int width, height, channels; + if (!stbi_info_from_memory(source, (int)size, &width, &height, &channels) || + width <= 0 || height <= 0 || width > 8192 || height > 8192 || + (size_t)width * height > 32u * 1024u * 1024u) return NULL; + unsigned char *rgba = stbi_load_from_memory(source, (int)size, + &width, &height, &channels, 4); + if (rgba == NULL || width <= 0 || height <= 0) return NULL; + struct bitmap *bitmap = guit->bitmap->create(width, height, BITMAP_NONE); + if (bitmap == NULL) { stbi_image_free(rgba); return NULL; } + uint8_t *dest = guit->bitmap->get_buffer(bitmap); + size_t stride = guit->bitmap->get_rowstride(bitmap); + if (dest == NULL) { guit->bitmap->destroy(bitmap); stbi_image_free(rgba); return NULL; } + bool opaque = true; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + const uint8_t *src = rgba + ((size_t)y * width + x) * 4; + uint8_t *out = dest + (size_t)y * stride + (size_t)x * 4; + out[bitmap_layout.r] = src[0]; + out[bitmap_layout.g] = src[1]; + out[bitmap_layout.b] = src[2]; + out[bitmap_layout.a] = src[3]; + if (src[3] != 255) opaque = false; + } + } + stbi_image_free(rgba); + guit->bitmap->set_opaque(bitmap, opaque); + guit->bitmap->modified(bitmap); + return bitmap; +} + +static bool nsjpeg_convert(struct content *content) +{ + size_t size; + const uint8_t *source = content__get_source_data(content, &size); + int width, height, channels; + if (source == NULL || size == 0 || size > INT_MAX || + !stbi_info_from_memory(source, (int)size, &width, &height, &channels) || + width <= 0 || height <= 0 || width > 8192 || height > 8192 || + (size_t)width * height > 32u * 1024u * 1024u) { + union content_msg_data message; + message.errordata.errorcode = NSERROR_INVALID; + message.errordata.errormsg = "Invalid JPEG or PNG image"; + content_broadcast(content, CONTENT_MSG_ERROR, &message); + return false; + } + content->width = width; + content->height = height; + content->size = (size_t)width * height * 4; + image_cache_add(content, NULL, montauk_image_decode); + char title[96]; + snprintf(title, sizeof(title), "Image (%d x %d)", width, height); + content__set_title(content, strdup(title)); + content_set_ready(content); + content_set_done(content); + content_set_status(content, ""); + return true; +} + +static nserror nsjpeg_clone(const struct content *old, struct content **result) +{ + struct content *copy = calloc(1, sizeof(*copy)); + if (copy == NULL) return NSERROR_NOMEM; + nserror err = content__clone(old, copy); + if (err != NSERROR_OK) { content_destroy(copy); return err; } + if ((old->status == CONTENT_STATUS_READY || old->status == CONTENT_STATUS_DONE) && + !nsjpeg_convert(copy)) { + content_destroy(copy); + return NSERROR_CLONE_FAILED; + } + *result = copy; + return NSERROR_OK; +} + +static const content_handler montauk_image_handler = { + .create = nsjpeg_create, + .data_complete = nsjpeg_convert, + .destroy = image_cache_destroy, + .redraw = image_cache_redraw, + .clone = nsjpeg_clone, + .get_internal = image_cache_get_internal, + .type = image_cache_content_type, + .is_opaque = image_cache_is_opaque, + .no_share = false, +}; + +static const char *montauk_image_types[] = { + "image/jpeg", "image/jpg", "image/pjpeg", "image/png" +}; + +CONTENT_FACTORY_REGISTER_TYPES(nsjpeg, montauk_image_types, montauk_image_handler); diff --git a/www-client/netsurf/files/montauk_ui.c b/www-client/netsurf/files/montauk_ui.c new file mode 100644 index 0000000..3818739 --- /dev/null +++ b/www-client/netsurf/files/montauk_ui.c @@ -0,0 +1,538 @@ +/* Native MontaukOS browser shell for the NetSurf core. */ + +#include +#include +#include +#include +#include +#include + +#include "utils/errors.h" +#include "utils/nsurl.h" +#include "netsurf/browser_window.h" +#include "netsurf/clipboard.h" +#include "netsurf/search.h" +#include "netsurf/keypress.h" +#include "netsurf/mouse.h" +#include "desktop/browser_history.h" +#include "desktop/search.h" +#include "monkey/browser.h" +#include "monkey/plot.h" +#include "monkey/schedule.h" +#include "monkey/montauk_ui.h" +#include "content/fetchers/montauk_bridge.h" +#include "content/urldb.h" + +#define MENU_ITEMS 10 +#define MAX_TABS 16 +#define MAX_BOOKMARKS 64 +#define HOME_URL "https://html.duckduckgo.com/" + +static char bookmark_urls[MAX_BOOKMARKS][1024]; +static char bookmark_titles[MAX_BOOKMARKS][128]; +static size_t bookmark_count; +static char bookmark_path[256]; + +struct mtk_clipboard_info { + uint32_t text_bytes; + uint32_t pad; + uint64_t serial; +}; + +static void clipboard_get(char **buffer, size_t *length) +{ + struct mtk_clipboard_info info = {0}; + uint32_t actual = 0; + *buffer = NULL; + *length = 0; + if (_mtk_syscall1(MTK_SYS_CLIPBOARD_GET_INFO, (long)&info) != 0 || + info.text_bytes == 0 || info.text_bytes > 4u * 1024u * 1024u) + return; + char *copy = malloc((size_t)info.text_bytes + 1); + if (copy == NULL) return; + if (_mtk_syscall4(MTK_SYS_CLIPBOARD_GET_TEXT, (long)copy, + (long)info.text_bytes, (long)&actual, 0) != 0 || actual > info.text_bytes) { + free(copy); + return; + } + copy[actual] = 0; + *buffer = copy; + *length = actual; +} + +static void clipboard_set(const char *buffer, size_t length, + nsclipboard_styles styles[], int n_styles) +{ + (void)styles; + (void)n_styles; + if (buffer != NULL && length <= UINT32_MAX) + _mtk_syscall2(MTK_SYS_CLIPBOARD_SET_TEXT, (long)buffer, (long)length); +} + +struct gui_clipboard_table monkey_native_clipboard_table = { + .get = clipboard_get, + .set = clipboard_set, +}; + +static void search_status(bool found, void *private) +{ + struct gui_window *g = private; + if (g == NULL) return; + strcpy(g->status, found ? "Match found" : "No matches"); + g->dirty = true; +} + +static void search_hourglass(bool active, void *private) { (void)active; (void)private; } +static void search_recent(const char *value, void *private) { (void)value; (void)private; } +static void search_state(bool active, void *private) { (void)active; (void)private; } + +struct gui_search_table monkey_native_search_table = { + .status = search_status, + .hourglass = search_hourglass, + .add_recent = search_recent, + .forward_state = search_state, + .back_state = search_state, +}; + +static uint32_t rgb(unsigned r, unsigned g, unsigned b) +{ + return 0xff000000u | (r << 16) | (g << 8) | b; +} + +static void fill(struct gui_window *g, int x, int y, int w, int h, uint32_t c) +{ + if (x < 0) { w += x; x = 0; } + if (y < 0) { h += y; y = 0; } + if (x + w > g->width) w = g->width - x; + if (y + h > g->height) h = g->height - y; + if (w <= 0 || h <= 0) return; + for (int py = y; py < y + h; py++) + for (int px = x; px < x + w; px++) + g->pixels[py * g->width + px] = c; +} + + +static void new_tab(struct gui_window *g) +{ + if (monkey_native_tab_count() >= MAX_TABS) { + strcpy(g->status, "Tab limit reached"); g->dirty = true; return; + } + nsurl *url = NULL; + struct browser_window *created = NULL; + if (nsurl_create("about:blank", &url) != NSERROR_OK) return; + browser_window_create(BW_CREATE_HISTORY | BW_CREATE_TAB | + BW_CREATE_FOREGROUND | BW_CREATE_FOCUS_LOCATION, + url, NULL, g->bw, &created); + nsurl_unref(url); + struct gui_window *active = monkey_native_get_window(); + if (active != NULL) { + active->address[0] = 0; + active->address_focus = true; + mtk_netsurf_address_input_focus((uintptr_t)active, + active->address, true); + active->dirty = true; + } +} + +static void cycle_tab(struct gui_window *g, bool backwards) +{ + size_t count = monkey_native_tab_count(); + if (count < 2) return; + size_t current = 0; + for (; current < count; current++) + if (monkey_native_tab_at(current) == g) break; + if (current == count) return; + current = backwards ? (current + count - 1) % count : (current + 1) % count; + monkey_native_activate_window(monkey_native_tab_at(current)); +} + +static void bookmarks_save(void) +{ + if (bookmark_path[0] == 0) return; + int file = mtk_create(bookmark_path); + if (file < 0) return; + unsigned long offset = 0; + for (size_t i = 0; i < bookmark_count; i++) { + size_t len = strlen(bookmark_urls[i]); + mtk_write(file, (const uint8_t *)bookmark_urls[i], offset, len); offset += len; + mtk_write(file, (const uint8_t *)"\n", offset, 1); offset++; + } + mtk_close(file); +} + +static void bookmarks_load(void) +{ + char user[64] = {0}; + if (_mtk_syscall2(MTK_SYS_GETUSER, (long)user, sizeof(user)) < 0 || user[0] == 0) + strcpy(user, "admin"); + char directory[224]; + snprintf(directory, sizeof(directory), "0:/users/%s/.netsurf", user); + mtk_mkdir(directory); + snprintf(bookmark_path, sizeof(bookmark_path), "%s/bookmarks", directory); + int file = mtk_open(bookmark_path); + if (file < 0) return; + unsigned long size = mtk_getsize(file); + if (size > 128u * 1024u) size = 128u * 1024u; + char *data = malloc(size + 1); + if (data == NULL) { mtk_close(file); return; } + int got = mtk_read(file, (uint8_t *)data, 0, size); + mtk_close(file); + if (got < 0) { free(data); return; } + data[got] = 0; + char *line = data; + while (*line != 0 && bookmark_count < MAX_BOOKMARKS) { + char *end = strchr(line, '\n'); + if (end != NULL) *end = 0; + if (line[0] != 0) { + strncpy(bookmark_urls[bookmark_count], line, + sizeof(bookmark_urls[bookmark_count]) - 1); + strncpy(bookmark_titles[bookmark_count], line, + sizeof(bookmark_titles[bookmark_count]) - 1); + bookmark_count++; + } + if (end == NULL) break; + line = end + 1; + } + free(data); +} + +static void bookmark_page(struct gui_window *g) +{ + nsurl *url = browser_window_access_url(g->bw); + if (url == NULL) { strcpy(g->status, "Could not save bookmark"); g->dirty = true; return; } + const char *value = nsurl_access(url); + for (size_t i = 0; i < bookmark_count; i++) { + if (strcmp(bookmark_urls[i], value) == 0) { + strcpy(g->status, "Already bookmarked"); g->dirty = true; return; + } + } + if (bookmark_count >= MAX_BOOKMARKS) { + strcpy(g->status, "Bookmark limit reached"); g->dirty = true; return; + } + strncpy(bookmark_urls[bookmark_count], value, sizeof(bookmark_urls[0]) - 1); + strncpy(bookmark_titles[bookmark_count], g->title[0] ? g->title : value, + sizeof(bookmark_titles[0]) - 1); + bookmark_count++; + urldb_set_url_persistence(url, true); + bookmarks_save(); + strcpy(g->status, "Bookmark saved"); + g->dirty = true; +} + +static void close_all_tabs(void) +{ + while (monkey_native_tab_count() != 0) { + struct gui_window *tab = monkey_native_tab_at(0); + if (tab == NULL) break; + browser_window_destroy(tab->bw); + } +} + +void monkey_native_draw_chrome(struct gui_window *g) +{ + if (g == NULL || g->pixels == NULL) return; + struct mtk_netsurf_chrome_model model = {0}; + model.pixels = g->pixels; + model.width = g->width; + model.height = g->height; + model.scale = g->ui_scale; + model.mouse_x = g->mouse_x; + model.mouse_y = g->mouse_y; + model.can_back = browser_window_history_back_available(g->bw); + model.can_forward = browser_window_history_forward_available(g->bw); + model.loading = g->loading; + model.address_focus = g->address_focus; + model.address_input_key = (uintptr_t)g; + model.address = g->address; + model.menu_mode = g->menu_open; + model.find_focus = g->find_focus; + model.find_text = g->find_text; + model.status = g->status; + model.extent_height = g->extent_height; + model.scroll_y = g->scrolly; + model.scrollbar_hover = g->scrollbar_hover; + model.scrollbar_dragging = g->scrollbar_dragging; + model.tab_count = (int)monkey_native_tab_count(); + for (int i = 0; i < model.tab_count && i < MTK_NETSURF_MAX_TABS; i++) { + struct gui_window *tab = monkey_native_tab_at((size_t)i); + model.tab_titles[i] = tab != NULL && tab->title[0] ? tab->title : "New Tab"; + if (tab == g) model.active_tab = i; + } + model.bookmark_count = bookmark_count < MTK_NETSURF_MAX_BOOKMARK_ROWS ? + (int)bookmark_count : MTK_NETSURF_MAX_BOOKMARK_ROWS; + for (int i = 0; i < model.bookmark_count; i++) + model.bookmark_titles[i] = bookmark_titles[i]; + mtk_netsurf_chrome_draw(&model); + if (g->caret_visible) { + int cx = g->caret_x - g->scrollx; + int cy = g->chrome_height + g->caret_y - g->scrolly; + if (cx >= 0 && cx < mtk_netsurf_page_view_width(g->width) && + cy >= g->chrome_height && + cy + g->caret_height < g->height) + fill(g, cx, cy, 1, g->caret_height, rgb(25, 25, 25)); + } +} + +static void navigate(struct gui_window *g, const char *input) +{ + char expanded[2048]; + if (strstr(input, "://") == NULL && strncmp(input, "about:", 6) != 0) { + snprintf(expanded, sizeof(expanded), "https://%s", input); + input = expanded; + } + nsurl *url = NULL; + if (nsurl_create(input, &url) == NSERROR_OK) { + browser_window_navigate(g->bw, url, NULL, BW_NAVIGATE_HISTORY, + NULL, NULL, NULL); + nsurl_unref(url); + } +} + +static void key_event(struct gui_window *g, const mtk_key_event *key) +{ + if (!key->pressed) return; + if (key->scancode == 0x01 && g->menu_open) { g->menu_open = 0; g->dirty = true; return; } + if (key->ctrl && (key->ascii == 't' || key->ascii == 'T' || key->scancode == 0x14)) { new_tab(g); return; } + if (key->ctrl && (key->ascii == 'w' || key->ascii == 'W' || key->scancode == 0x11)) { + browser_window_destroy(g->bw); + return; + } + if (key->ctrl && key->scancode == 0x0f) { cycle_tab(g, key->shift); return; } + if (key->ctrl && (key->ascii == 'f' || key->ascii == 'F')) { + g->find_focus = true; g->dirty = true; return; + } + if (key->ctrl && (key->ascii == 'd' || key->ascii == 'D')) { + bookmark_page(g); return; + } + if (g->find_focus) { + if (key->scancode == 0x01) { + g->find_focus = false; browser_window_search_clear(g->bw); g->dirty = true; return; + } + if (key->scancode == 0x1c || key->ascii == '\r' || key->ascii == '\n') { + if (g->find_text[0]) browser_window_search(g->bw, g, SEARCH_FLAG_FORWARDS, g->find_text); + return; + } + if (key->ascii == '\b' || key->scancode == 0x0e) { + size_t n = strlen(g->find_text); if (n) g->find_text[n-1] = 0; + } else if (key->ascii >= 32 && key->ascii < 127) { + size_t n = strlen(g->find_text); + if (n + 1 < sizeof(g->find_text)) { g->find_text[n] = key->ascii; g->find_text[n+1] = 0; } + } + if (g->find_text[0]) browser_window_search(g->bw, g, SEARCH_FLAG_FORWARDS | SEARCH_FLAG_SHOWALL, g->find_text); + else browser_window_search_clear(g->bw); + g->dirty = true; return; + } + if (key->ctrl && (key->ascii == 'l' || key->ascii == 'L')) { + g->address_focus = true; + mtk_netsurf_address_input_focus((uintptr_t)g, g->address, true); + g->dirty = true; return; + } + if (g->address_focus) { + if (key->scancode == 0x01) { g->address_focus = false; g->dirty = true; return; } + if (key->scancode == 0x1c || key->ascii == '\r' || key->ascii == '\n') { + g->address_focus = false; + navigate(g, g->address); return; + } + int result = mtk_netsurf_address_input_key((uintptr_t)g, + g->address, (int)sizeof(g->address), key->scancode, + key->ascii, key->pressed, key->shift, key->ctrl, key->alt); + if (result != 0) g->dirty = true; + return; + } + if (key->ctrl && (key->ascii == 'r' || key->ascii == 'R')) { browser_window_reload(g->bw, false); return; } + if (key->ctrl && (key->ascii == '+' || key->ascii == '=')) { browser_window_set_scale(g->bw, .1f, false); return; } + if (key->ctrl && key->ascii == '-') { browser_window_set_scale(g->bw, -.1f, false); return; } + if (key->ctrl && key->ascii == '0') { browser_window_set_scale(g->bw, 1.0f, true); return; } + if (key->ctrl && (key->ascii == 'a' || key->ascii == 'A')) { browser_window_key_press(g->bw, NS_KEY_SELECT_ALL); return; } + if (key->ctrl && (key->ascii == 'c' || key->ascii == 'C')) { browser_window_key_press(g->bw, NS_KEY_COPY_SELECTION); return; } + if (key->ctrl && (key->ascii == 'x' || key->ascii == 'X')) { browser_window_key_press(g->bw, NS_KEY_CUT_SELECTION); return; } + if (key->ctrl && (key->ascii == 'v' || key->ascii == 'V')) { browser_window_key_press(g->bw, NS_KEY_PASTE); return; } + uint32_t nskey = 0; + switch (key->scancode) { + case 0x48: nskey = NS_KEY_UP; break; case 0x50: nskey = NS_KEY_DOWN; break; + case 0x4b: nskey = NS_KEY_LEFT; break; case 0x4d: nskey = NS_KEY_RIGHT; break; + case 0x49: nskey = NS_KEY_PAGE_UP; break; case 0x51: nskey = NS_KEY_PAGE_DOWN; break; + case 0x0e: nskey = NS_KEY_DELETE_LEFT; break; case 0x53: nskey = NS_KEY_DELETE_RIGHT; break; + case 0x0f: nskey = key->shift ? NS_KEY_SHIFT_TAB : NS_KEY_TAB; break; + case 0x1c: nskey = NS_KEY_CR; break; + default: if (key->ascii >= 32) nskey = (unsigned char)key->ascii; break; + } + if (nskey != 0 && browser_window_key_press(g->bw, nskey)) return; + if (key->scancode == 0x48) g->scrolly -= 40; + if (key->scancode == 0x50) g->scrolly += 40; + if (key->scancode == 0x49) g->scrolly -= g->height - g->chrome_height - g->status_height; + if (key->scancode == 0x51) g->scrolly += g->height - g->chrome_height - g->status_height; + g->dirty = true; +} + +static void clamp_scroll(struct gui_window *g) +{ + int view = g->height - g->chrome_height - g->status_height; + int max = g->extent_height > view ? g->extent_height - view : 0; + if (g->scrolly < 0) g->scrolly = 0; + if (g->scrolly > max) g->scrolly = max; +} + +static void mouse_event(struct gui_window *g, const mtk_win_event *ev) +{ + int x = ev->mouse.x, y = ev->mouse.y; + struct mtk_netsurf_chrome_metrics m; + mtk_netsurf_chrome_metrics(g->ui_scale, g->width, + (int)monkey_native_tab_count(), &m); + int ch = m.chrome_h, sh = m.status_h; + if (g->mouse_x != x || g->mouse_y != y) { + g->mouse_x = x; + g->mouse_y = y; + g->dirty = true; + } + bool left_press = (ev->mouse.buttons & 1) && !(ev->mouse.prev_buttons & 1); + bool left_release = !(ev->mouse.buttons & 1) && (ev->mouse.prev_buttons & 1); + bool address_hit = x >= m.address_x && x < m.address_x + m.address_w && + y >= m.address_y && y < m.address_y + m.address_h; + if (address_hit && ((ev->mouse.buttons & 3) != 0)) + g->address_focus = true; + if (g->address_focus || address_hit) { + int input_result = mtk_netsurf_address_input_mouse((uintptr_t)g, + g->address, (int)sizeof(g->address), x, y, + ev->mouse.buttons, ev->mouse.prev_buttons, + m.address_x, m.address_y, m.address_w, m.address_h, + g->width, g->height, g->address_focus); + if (input_result != 0) { + g->dirty = true; + return; + } + } + int scrollbar_result = mtk_netsurf_page_scrollbar_mouse(x, y, + ev->mouse.buttons, ev->mouse.prev_buttons, g->width, g->height, + ch, g->extent_height, &g->scrolly, &g->scrollbar_hover, + &g->scrollbar_dragging, &g->scrollbar_drag_offset); + if ((scrollbar_result & 2) != 0) g->dirty = true; + if ((scrollbar_result & 1) != 0) return; + if (left_press && !address_hit) g->address_focus = false; + if (ev->mouse.scroll != 0 && y >= ch) { + g->scrolly -= ev->mouse.scroll * 50; clamp_scroll(g); g->dirty = true; return; + } + if (left_press && y >= m.tab_y && y < m.tab_h) { + size_t count = monkey_native_tab_count(); + for (size_t i = 0; i < count; i++) { + int tx = m.tab_x + (int)i * m.tab_w; + if (x >= tx && x < tx + m.tab_content_w) { + struct gui_window *tab = monkey_native_tab_at(i); + if (tab != NULL && x >= tx + m.tab_content_w - m.tab_close_w) + browser_window_destroy(tab->bw); + else monkey_native_activate_window(tab); + return; + } + } + if (x >= m.add_x && x < m.add_x + m.add_w) new_tab(g); + return; + } + if (left_press && g->menu_open) { + if (y >= m.button_y && y < m.button_y + m.button_size && + x >= m.menu_x && x < m.menu_x + m.button_size) { + g->menu_open = false; g->dirty = true; return; + } + if (g->menu_open == 2 && x >= m.popup_x && x < m.popup_x + m.popup_w && y >= m.popup_y) { + int item = (y - m.popup_y - 4) / m.popup_item_h - 1; + if (item >= 0 && item < (int)bookmark_count && item < 8) + navigate(g, bookmark_urls[item]); + g->menu_open = 0; g->dirty = true; return; + } + if (g->menu_open == 1 && x >= m.popup_x && x < m.popup_x + m.popup_w && + y >= m.popup_y && y < m.popup_y + MENU_ITEMS * m.popup_item_h + 8) { + int item = (y - m.popup_y - 4) / m.popup_item_h; + g->menu_open = 0; + switch (item) { + case 0: new_tab(g); break; + case 1: g->find_focus = true; g->dirty = true; break; + case 2: bookmark_page(g); break; + case 3: g->menu_open = 2; g->dirty = true; break; + case 4: clipboard_set(g->address, strlen(g->address), NULL, 0); break; + case 5: browser_window_set_scale(g->bw, .1f, false); break; + case 6: browser_window_set_scale(g->bw, -.1f, false); break; + case 7: browser_window_set_scale(g->bw, 1.0f, true); break; + case 8: navigate(g, HOME_URL); break; + case 9: close_all_tabs(); break; + } + return; + } + g->menu_open = 0; g->dirty = true; + } + if (left_press && y >= m.button_y && y < m.button_y + m.button_size) { + if (x >= m.back_x && x < m.back_x + m.button_size && browser_window_history_back_available(g->bw)) + browser_window_history_back(g->bw, false); + else if (x >= m.forward_x && x < m.forward_x + m.button_size && browser_window_history_forward_available(g->bw)) + browser_window_history_forward(g->bw, false); + else if (x >= m.reload_x && x < m.reload_x + m.button_size) { + if (g->loading) browser_window_stop(g->bw); else browser_window_reload(g->bw, false); + } else if (x >= m.address_x && x < m.address_x + m.address_w) { + /* The MTK input handler above owns address-field interaction. */ + } else if (x >= m.home_x && x < m.home_x + m.button_size) { + navigate(g, HOME_URL); + } else if (x >= m.menu_x && x < m.menu_x + m.button_size) { + g->menu_open = g->menu_open ? 0 : 1; + } + g->dirty = true; return; + } + if (left_press && y >= ch && y < g->height - sh) { + g->address_focus = false; + browser_window_mouse_click(g->bw, BROWSER_MOUSE_PRESS_1, + x + g->scrollx, y - ch + g->scrolly); + /* A press can dispatch form/content callbacks and invalidate the + * browser window. Continue tracking on the next mouse event, after the + * event loop has reacquired the active gui_window. */ + return; + } + if (left_release && y >= ch && y < g->height - sh) { + browser_window_mouse_click(g->bw, BROWSER_MOUSE_CLICK_1, + x + g->scrollx, y - ch + g->scrolly); + /* A click may navigate, replace the current content, or destroy the + * target browser window. Do not dereference g or g->bw afterward. */ + return; + } + if (y >= ch && y < g->height - sh) + browser_window_mouse_track(g->bw, + (ev->mouse.buttons & 1) ? BROWSER_MOUSE_HOLDING_1 : BROWSER_MOUSE_HOVER, + x + g->scrollx, y - ch + g->scrolly); +} + +void monkey_native_run(const char *initial_url) +{ + nsurl *url = NULL; + bookmarks_load(); + if (initial_url == NULL || initial_url[0] == 0) initial_url = HOME_URL; + if (nsurl_create(initial_url, &url) != NSERROR_OK) return; + if (browser_window_create(BW_CREATE_HISTORY, url, NULL, NULL, NULL) != NSERROR_OK) { + nsurl_unref(url); return; + } + nsurl_unref(url); + + for (;;) { + struct gui_window *g = monkey_native_get_window(); + if (g == NULL) break; + monkey_schedule_run(); + mtk_win_event ev; + int events = 0; + while (events++ < 32 && mtk_win_poll(g->native_id, &ev) > 0) { + if (ev.type == MTK_EVENT_CLOSE) { close_all_tabs(); g = NULL; break; } + if (ev.type == MTK_EVENT_RESIZE || ev.type == MTK_EVENT_SCALE) { + if (ev.type == MTK_EVENT_RESIZE) { + uint32_t *pixels = (uint32_t *)(uintptr_t)mtk_win_resize(g->native_id, + ev.resize.w, ev.resize.h); + monkey_native_resize_tabs(g, ev.resize.w, ev.resize.h, pixels); + } + if (ev.type == MTK_EVENT_SCALE) + monkey_native_set_scale(g, mtk_win_getscale()); + g->dirty = true; + } else if (ev.type == MTK_EVENT_KEY) key_event(g, &ev.key); + else if (ev.type == MTK_EVENT_MOUSE) mouse_event(g, &ev); + g = monkey_native_get_window(); + if (g == NULL) break; + } + if (g == NULL) break; + clamp_scroll(g); + if (g->dirty) monkey_native_redraw(g); + mtk_sleep_ms(5); + } + mtk_netsurf_icons_cleanup(); +} diff --git a/www-client/netsurf/files/montauk_ui.h b/www-client/netsurf/files/montauk_ui.h new file mode 100644 index 0000000..fa63984 --- /dev/null +++ b/www-client/netsurf/files/montauk_ui.h @@ -0,0 +1,13 @@ +#ifndef NETSURF_MONTAUK_UI_H +#define NETSURF_MONTAUK_UI_H + +#if defined(__montauk__) +struct gui_clipboard_table; +struct gui_search_table; +void monkey_native_run(const char *initial_url); +void monkey_native_draw_chrome(struct gui_window *gw); +extern struct gui_clipboard_table monkey_native_clipboard_table; +extern struct gui_search_table monkey_native_search_table; +#endif + +#endif diff --git a/www-client/netsurf/files/stb_truetype_impl.cpp b/www-client/netsurf/files/stb_truetype_impl.cpp new file mode 100644 index 0000000..874acad --- /dev/null +++ b/www-client/netsurf/files/stb_truetype_impl.cpp @@ -0,0 +1,23 @@ +/* One implementation unit for the MontaukOS TrueType renderer. */ +#include +#include +#include +#include +#include + +#define STBTT_ifloor(x) ((int)stb_floor(x)) +#define STBTT_iceil(x) ((int)stb_ceil(x)) +#define STBTT_sqrt(x) stb_sqrt(x) +#define STBTT_pow(x,y) stb_pow(x,y) +#define STBTT_fmod(x,y) stb_fmod(x,y) +#define STBTT_cos(x) stb_cos(x) +#define STBTT_acos(x) stb_acos(x) +#define STBTT_fabs(x) stb_fabs(x) +#define STBTT_malloc(x,u) ((void)(u), montauk::malloc(x)) +#define STBTT_free(x,u) ((void)(u), montauk::mfree(x)) +#define STBTT_memcpy(d,s,n) montauk::memcpy(d,s,n) +#define STBTT_memset(d,v,n) montauk::memset(d,v,n) +#define STBTT_strlen(x) montauk::slen(x) +#define STBTT_assert(x) ((void)(x)) +#define STB_TRUETYPE_IMPLEMENTATION +#include diff --git a/www-client/netsurf/patches/0002-respath-separator-montaukos.patch b/www-client/netsurf/patches/0002-respath-separator-montaukos.patch index 358aee0..0b2e6a1 100644 --- a/www-client/netsurf/patches/0002-respath-separator-montaukos.patch +++ b/www-client/netsurf/patches/0002-respath-separator-montaukos.patch @@ -1,14 +1,34 @@ diff --git a/frontends/monkey/main.c b/frontends/monkey/main.c -index 463f0bea6..578d148e4 100644 +index 463f0bea6..04b0e1c9f 100644 --- a/frontends/monkey/main.c +++ b/frontends/monkey/main.c -@@ -422,7 +422,12 @@ main(int argc, char **argv) +@@ -422,7 +422,32 @@ main(int argc, char **argv) setbuf(stderr, NULL); /* Prep the search paths */ +#if defined(__montauk__) ++ char bundle_res[PATH_MAX]; ++ char resource_path[PATH_MAX * 2]; ++ const char *exec_path = argv[0]; ++ const char *last_slash = strrchr(exec_path, '/'); ++ size_t bundle_dir_len = last_slash == NULL ? 0 : ++ (size_t)(last_slash - exec_path + 1); ++ ++ /* Locate res/ beside the running ELF so the app bundle is relocatable. ++ * MONKEY_RESPATH remains as a fallback for older startup environments. */ ++ if (bundle_dir_len > 0 && ++ bundle_dir_len + sizeof("res/") <= sizeof(bundle_res)) { ++ memcpy(bundle_res, exec_path, bundle_dir_len); ++ memcpy(bundle_res + bundle_dir_len, "res/", sizeof("res/")); ++ } else { ++ snprintf(bundle_res, sizeof(bundle_res), "%s", MONKEY_RESPATH); ++ } ++ + /* see NS_RESPATH_SEP: ':' is part of a MontaukOS drive path */ -+ respaths = nsmonkey_init_resource("${HOME}/.netsurf/;${NETSURFRES};"MONKEY_RESPATH";./frontends/monkey/res"); ++ snprintf(resource_path, sizeof(resource_path), ++ "${HOME}/.netsurf/;${NETSURFRES};%s;%s;./frontends/monkey/res", ++ bundle_res, MONKEY_RESPATH); ++ respaths = nsmonkey_init_resource(resource_path); +#else respaths = nsmonkey_init_resource("${HOME}/.netsurf/:${NETSURFRES}:"MONKEY_RESPATH":./frontends/monkey/res"); +#endif diff --git a/www-client/netsurf/patches/0006-native-http-fetcher.patch b/www-client/netsurf/patches/0006-native-http-fetcher.patch new file mode 100644 index 0000000..d3efbbd --- /dev/null +++ b/www-client/netsurf/patches/0006-native-http-fetcher.patch @@ -0,0 +1,43 @@ +diff --git a/content/fetch.c b/content/fetch.c +index f066ea40c..a804002c4 100644 +--- a/content/fetch.c ++++ b/content/fetch.c +@@ -49,6 +49,9 @@ + #include "content/fetchers/data.h" + #include "content/fetchers/file/file.h" + #include "javascript/fetcher.h" ++#ifdef WITH_MONTAUK_FETCHER ++#include "content/fetchers/montauk.h" ++#endif + #include "content/urldb.h" + + /** The maximum number of fetchers that can be added */ +@@ -294,6 +297,13 @@ nserror fetcher_init(void) + } + #endif + ++#ifdef WITH_MONTAUK_FETCHER ++ ret = fetch_montauk_register(); ++ if (ret != NSERROR_OK) { ++ return ret; ++ } ++#endif ++ + ret = fetch_data_register(); + if (ret != NSERROR_OK) { + return ret; +diff --git a/content/fetchers/Makefile b/content/fetchers/Makefile +index f7752918a..74eddf5a3 100644 +--- a/content/fetchers/Makefile ++++ b/content/fetchers/Makefile +@@ -4,6 +4,10 @@ S_FETCHERS_YES := data.c resource.c + S_FETCHERS_NO := + S_FETCHERS_$(NETSURF_USE_CURL) += curl.c + ++ifeq ($(NETSURF_USE_MONTAUK),YES) ++S_FETCHERS_YES += montauk.c ++endif ++ + S_FETCHERS := $(addprefix fetchers/,$(S_FETCHERS_YES)) + + # about fetcher diff --git a/www-client/netsurf/patches/0007-montauk-window-plotter.patch b/www-client/netsurf/patches/0007-montauk-window-plotter.patch new file mode 100644 index 0000000..211bf0e --- /dev/null +++ b/www-client/netsurf/patches/0007-montauk-window-plotter.patch @@ -0,0 +1,236 @@ +diff --git a/frontends/monkey/browser.h b/frontends/monkey/browser.h +--- a/frontends/monkey/browser.h ++++ b/frontends/monkey/browser.h +@@ -37,1 +37,6 @@ struct gui_window { ++#if defined(__montauk__) ++ int native_id; ++ uint32_t *pixels; ++ int clip_x0, clip_y0, clip_x1, clip_y1; ++#endif + char *host; /* Ignore this, it's in case RING*() gets debugging for fetchers */ +diff --git a/frontends/monkey/browser.c b/frontends/monkey/browser.c +--- a/frontends/monkey/browser.c ++++ b/frontends/monkey/browser.c +@@ -21,4 +21,7 @@ + #include + #include + #include ++#if defined(__montauk__) ++#include ++#endif + +@@ -88,3 +91,14 @@ gui_window_create(struct browser_window *bw, + ret->width = 800; + ret->height = 600; ++#if defined(__montauk__) ++ mtk_win_result native; ++ if (mtk_win_create("NetSurf", ret->width, ret->height, &native) != 0) { ++ free(ret); ++ return NULL; ++ } ++ ret->native_id = native.id; ++ ret->pixels = (uint32_t *)(uintptr_t)native.pixels; ++ ret->clip_x1 = ret->width; ++ ret->clip_y1 = ret->height; ++#endif + +@@ -107,4 +121,7 @@ gui_window_destroy(struct gui_window *g) + { + moutf(MOUT_WINDOW, "DESTROY WIN %u", g->win_num); ++#if defined(__montauk__) ++ mtk_win_destroy(g->native_id); ++#endif + RING_REMOVE(gw_ring, g); + free(g); +@@ -569,6 +586,7 @@ monkey_window_handle_redraw(int argc, char **argv) + struct redraw_context ctx = { + .interactive = true, + .background_images = true, +- .plot = monkey_plotters ++ .plot = monkey_plotters, ++ .priv = NULL + }; + +@@ -597,7 +615,17 @@ monkey_window_handle_redraw(int argc, char **argv) + } + + NSLOG(netsurf, INFO, "Issue redraw"); ++#if defined(__montauk__) ++ ctx.priv = gw; ++ for (int y = 0; y < gw->height; y++) { ++ for (int x = 0; x < gw->width; x++) ++ gw->pixels[y * gw->width + x] = 0xffffffffu; ++ } ++#endif + moutf(MOUT_WINDOW, "REDRAW WIN %d START", atoi(argv[2])); + browser_window_redraw(gw->bw, gw->scrollx, gw->scrolly, &clip, &ctx); ++#if defined(__montauk__) ++ mtk_win_present(gw->native_id); ++#endif + moutf(MOUT_WINDOW, "REDRAW WIN %d STOP", atoi(argv[2])); + } +diff --git a/frontends/monkey/plot.c b/frontends/monkey/plot.c +--- a/frontends/monkey/plot.c ++++ b/frontends/monkey/plot.c +@@ -23,4 +23,83 @@ + #include "netsurf/plotters.h" + + #include "monkey/output.h" ++#include "monkey/browser.h" ++ ++#if defined(__montauk__) ++extern const unsigned char mtk_font_data[] __asm__("_ZN3gui9font_dataE"); ++ ++struct bitmap { ++ void *ptr; ++ size_t rowstride; ++ int width; ++ int height; ++ bool opaque; ++}; ++ ++static uint32_t native_colour(colour c) ++{ ++ if (c == NS_TRANSPARENT) return 0; ++ return 0xff000000u | ((c & 0x0000ffu) << 16) | ++ (c & 0x00ff00u) | ((c & 0xff0000u) >> 16); ++} ++ ++static void native_pixel(struct gui_window *gw, int x, int y, uint32_t colour) ++{ ++ if (gw == NULL || gw->pixels == NULL || x < gw->clip_x0 || y < gw->clip_y0 || ++ x >= gw->clip_x1 || y >= gw->clip_y1 || x < 0 || y < 0 || ++ x >= gw->width || y >= gw->height || colour == 0) return; ++ gw->pixels[y * gw->width + x] = colour; ++} ++ ++static void native_fill(struct gui_window *gw, int x0, int y0, int x1, int y1, ++ uint32_t colour) ++{ ++ if (gw == NULL || colour == 0) return; ++ if (x0 < gw->clip_x0) x0 = gw->clip_x0; ++ if (y0 < gw->clip_y0) y0 = gw->clip_y0; ++ if (x1 > gw->clip_x1) x1 = gw->clip_x1; ++ if (y1 > gw->clip_y1) y1 = gw->clip_y1; ++ x0 = x0 < 0 ? 0 : x0; y0 = y0 < 0 ? 0 : y0; ++ if (x1 > gw->width) x1 = gw->width; ++ if (y1 > gw->height) y1 = gw->height; ++ for (int y = y0; y < y1; y++) ++ for (int x = x0; x < x1; x++) gw->pixels[y * gw->width + x] = colour; ++} ++ ++static void native_line(struct gui_window *gw, int x0, int y0, int x1, int y1, ++ uint32_t colour) ++{ ++ int dx = x1 > x0 ? x1 - x0 : x0 - x1, sx = x0 < x1 ? 1 : -1; ++ int dy = -(y1 > y0 ? y1 - y0 : y0 - y1), sy = y0 < y1 ? 1 : -1; ++ int err = dx + dy; ++ for (;;) { ++ native_pixel(gw, x0, y0, colour); ++ if (x0 == x1 && y0 == y1) break; ++ int twice = 2 * err; ++ if (twice >= dy) { err += dy; x0 += sx; } ++ if (twice <= dx) { err += dx; y0 += sy; } ++ } ++} ++ ++static void native_text(struct gui_window *gw, int x, int baseline, ++ const char *text, size_t length, const struct plot_font_style *style) ++{ ++ int px = plot_style_fixed_to_int(style->size); ++ int sx = px > 8 ? px / 8 : 1; ++ int sy = px > 16 ? px / 16 : 1; ++ int top = baseline - 13 * sy; ++ uint32_t colour = native_colour(style->foreground); ++ for (size_t i = 0; i < length; i++) { ++ unsigned char ch = (unsigned char)text[i]; ++ if (ch >= 128) continue; ++ const unsigned char *glyph = mtk_font_data + ch * 16; ++ for (int row = 0; row < 16; row++) ++ for (int col = 0; col < 8; col++) ++ if (glyph[row] & (0x80u >> col)) ++ native_fill(gw, x + col * sx, top + row * sy, ++ x + (col + 1) * sx, top + (row + 1) * sy, colour); ++ x += px > 0 ? px : 8; ++ } ++} ++#endif + +@@ -37,3 +116,12 @@ monkey_plot_clip(const struct redraw_context *ctx, const struct rect *clip) + { ++#if defined(__montauk__) ++ struct gui_window *gw = ctx->priv; ++ if (gw != NULL) { ++ gw->clip_x0 = clip->x0; ++ gw->clip_y0 = clip->y0; ++ gw->clip_x1 = clip->x1; ++ gw->clip_y1 = clip->y1; ++ } ++#endif + moutf(MOUT_PLOT, "CLIP X0 %d Y0 %d X1 %d Y1 %d", + clip->x0, clip->y0, clip->x1, clip->y1); +@@ -108,3 +196,6 @@ monkey_plot_line(const struct redraw_context *ctx, + { ++#if defined(__montauk__) ++ native_line(ctx->priv, line->x0, line->y0, line->x1, line->y1, native_colour(style->stroke_colour)); ++#endif + moutf(MOUT_PLOT, "LINE X0 %d Y0 %d X1 %d Y1 %d", + line->x0, line->y0, line->x1, line->y1); +@@ -132,3 +223,16 @@ monkey_plot_rectangle(const struct redraw_context *ctx, + { ++#if defined(__montauk__) ++ struct gui_window *gw = ctx->priv; ++ if (style->fill_type != PLOT_OP_TYPE_NONE) ++ native_fill(gw, rect->x0, rect->y0, rect->x1, rect->y1, ++ native_colour(style->fill_colour)); ++ if (style->stroke_type != PLOT_OP_TYPE_NONE) { ++ uint32_t c = native_colour(style->stroke_colour); ++ native_line(gw, rect->x0, rect->y0, rect->x1 - 1, rect->y0, c); ++ native_line(gw, rect->x1 - 1, rect->y0, rect->x1 - 1, rect->y1 - 1, c); ++ native_line(gw, rect->x1 - 1, rect->y1 - 1, rect->x0, rect->y1 - 1, c); ++ native_line(gw, rect->x0, rect->y1 - 1, rect->x0, rect->y0, c); ++ } ++#endif + moutf(MOUT_PLOT, "RECT X0 %d Y0 %d X1 %d Y1 %d", + rect->x0, rect->y0, rect->x1, rect->y1); +@@ -222,3 +325,33 @@ monkey_plot_bitmap(const struct redraw_context *ctx, + { ++#if defined(__montauk__) ++ struct gui_window *gw = ctx->priv; ++ if (bitmap != NULL && bitmap->ptr != NULL && width > 0 && height > 0) { ++ uint32_t *src = bitmap->ptr; ++ for (int dy = 0; dy < height; dy++) { ++ int sy = (int)((long long)dy * bitmap->height / height); ++ for (int dx = 0; dx < width; dx++) { ++ int sx = (int)((long long)dx * bitmap->width / width); ++ uint32_t p = src[sy * (bitmap->rowstride / 4) + sx]; ++ unsigned a = p >> 24; ++ if (a == 0) continue; ++ /* NetSurf bitmap storage is RGBA in memory, which is this ++ * little-endian word layout; convert to ARGB framebuffer. */ ++ uint32_t out = 0xff000000u | ((p & 0xffu) << 16) | ++ (p & 0xff00u) | ((p >> 16) & 0xffu); ++ if (a != 255) { ++ int tx = x + dx, ty = y + dy; ++ if (tx >= 0 && ty >= 0 && tx < gw->width && ty < gw->height) { ++ uint32_t d = gw->pixels[ty * gw->width + tx]; ++ unsigned r = (((out >> 16) & 255) * a + ((d >> 16) & 255) * (255-a)) / 255; ++ unsigned g = (((out >> 8) & 255) * a + ((d >> 8) & 255) * (255-a)) / 255; ++ unsigned b = ((out & 255) * a + (d & 255) * (255-a)) / 255; ++ out = 0xff000000u | (r << 16) | (g << 8) | b; ++ } ++ } ++ native_pixel(gw, x + dx, y + dy, out); ++ } ++ } ++ } ++#endif + moutf(MOUT_PLOT, "BITMAP X %d Y %d WIDTH %d HEIGHT %d", + x, y, width, height); +@@ -247,3 +380,6 @@ monkey_plot_text(const struct redraw_context *ctx, + { ++#if defined(__montauk__) ++ native_text(ctx->priv, x, y, text, length, fstyle); ++#endif + moutf(MOUT_PLOT, "TEXT X %d Y %d STR %.*s", x, y, (int)length, text); + return NSERROR_OK; diff --git a/www-client/netsurf/patches/0008-montauk-auto-redraw.patch b/www-client/netsurf/patches/0008-montauk-auto-redraw.patch new file mode 100644 index 0000000..aa93c42 --- /dev/null +++ b/www-client/netsurf/patches/0008-montauk-auto-redraw.patch @@ -0,0 +1,28 @@ +diff --git a/frontends/monkey/browser.c b/frontends/monkey/browser.c +--- a/frontends/monkey/browser.c ++++ b/frontends/monkey/browser.c +@@ -172,4 +172,24 @@ static void + gui_window_stop_throbber(struct gui_window *g) + { ++#if defined(__montauk__) ++ struct rect clip = { ++ .x0 = 0, ++ .y0 = 0, ++ .x1 = g->width, ++ .y1 = g->height ++ }; ++ struct redraw_context ctx = { ++ .interactive = true, ++ .background_images = true, ++ .plot = monkey_plotters, ++ .priv = g ++ }; ++ for (int y = 0; y < g->height; y++) { ++ for (int x = 0; x < g->width; x++) ++ g->pixels[y * g->width + x] = 0xffffffffu; ++ } ++ browser_window_redraw(g->bw, g->scrollx, g->scrolly, &clip, &ctx); ++ mtk_win_present(g->native_id); ++#endif + moutf(MOUT_WINDOW, "STOP_THROBBER WIN %u", g->win_num); + } diff --git a/www-client/netsurf/patches/0009-native-font-metrics.patch b/www-client/netsurf/patches/0009-native-font-metrics.patch new file mode 100644 index 0000000..01d4548 --- /dev/null +++ b/www-client/netsurf/patches/0009-native-font-metrics.patch @@ -0,0 +1,135 @@ +diff --git a/frontends/monkey/plot.c b/frontends/monkey/plot.c +--- a/frontends/monkey/plot.c ++++ b/frontends/monkey/plot.c +@@ -27,7 +27,7 @@ + #include "monkey/browser.h" + + #if defined(__montauk__) +-extern const unsigned char mtk_font_data[] __asm__("_ZN3gui9font_dataE"); ++#include "content/fetchers/montauk_bridge.h" + + struct bitmap { + void *ptr; +@@ -69,20 +69,10 @@ static void native_text(struct gui_window *gw, int x, int baseline, + const char *text, size_t length, const struct plot_font_style *style) + { + int px = plot_style_fixed_to_int(style->size); +- int sx = px > 8 ? px / 8 : 1; +- int sy = px > 16 ? px / 16 : 1; +- int top = baseline - 13 * sy; +- uint32_t colour = native_colour(style->foreground); +- for (size_t i = 0; i < length; i++) { +- unsigned char ch = (unsigned char)text[i]; +- if (ch >= 128) continue; +- const unsigned char *glyph = mtk_font_data + ch * 16; +- for (int row = 0; row < 16; row++) +- for (int col = 0; col < 8; col++) +- if (glyph[row] & (0x80u >> col)) +- native_fill(gw, x + col * sx, top + row * sy, +- x + (col + 1) * sx, top + (row + 1) * sy, colour); +- x += px > 0 ? px : 8; +- } ++ if (gw == NULL || gw->pixels == NULL) return; ++ mtk_netsurf_font_draw(gw->pixels, gw->width, gw->height, ++ gw->clip_x0, gw->clip_y0, gw->clip_x1, gw->clip_y1, ++ x, baseline, text, length, native_colour(style->foreground), ++ style->family, style->weight, style->flags, px); + } + #endif +diff --git a/frontends/monkey/layout.c b/frontends/monkey/layout.c +--- a/frontends/monkey/layout.c ++++ b/frontends/monkey/layout.c +@@ -30,10 +30,25 @@ + + #include "monkey/layout.h" + ++#if defined(__montauk__) ++#include "content/fetchers/montauk_bridge.h" ++#endif ++ ++static int font_width(const plot_font_style_t *fstyle, ++ const char *string, size_t length) ++{ ++#if defined(__montauk__) ++ return mtk_netsurf_font_width(fstyle->family, fstyle->weight, ++ fstyle->flags, plot_style_fixed_to_int(fstyle->size), string, length); ++#else ++ return (fstyle->size * utf8_bounded_length(string, length)) / PLOT_STYLE_SCALE; ++#endif ++} ++ + static nserror nsfont_width(const plot_font_style_t *fstyle, + const char *string, size_t length, + int *width) + { +- *width = (fstyle->size * utf8_bounded_length(string, length)) / PLOT_STYLE_SCALE; ++ *width = font_width(fstyle, string, length); + return NSERROR_OK; + } +@@ -55,8 +70,22 @@ static nserror nsfont_position_in_string(const plot_font_style_t *fstyle, + int x, size_t *char_offset, int *actual_x) + { +- *char_offset = x / (fstyle->size / PLOT_STYLE_SCALE); +- if (*char_offset > length) +- *char_offset = length; +- *actual_x = *char_offset * (fstyle->size / PLOT_STYLE_SCALE); ++ size_t offset = 0, previous = 0; ++ int previous_width = 0; ++ while (offset < length) { ++ previous = offset; ++ offset = utf8_next(string, length, offset); ++ int next_width = font_width(fstyle, string, offset); ++ if (next_width > x) { ++ if (x - previous_width > next_width - x) { ++ *char_offset = offset; *actual_x = next_width; ++ } else { ++ *char_offset = previous; *actual_x = previous_width; ++ } ++ return NSERROR_OK; ++ } ++ previous_width = next_width; ++ } ++ *char_offset = length; ++ *actual_x = previous_width; + return NSERROR_OK; + } +@@ -90,21 +119,25 @@ static nserror nsfont_split(const plot_font_style_t *fstyle, + int x, size_t *char_offset, int *actual_x) + { +- int c_off = *char_offset = x / (fstyle->size / PLOT_STYLE_SCALE); +- if (*char_offset > length) { ++ size_t offset = 0, last_space = 0, first_over = length; ++ while (offset < length) { ++ size_t next = utf8_next(string, length, offset); ++ if (string[offset] == ' ') last_space = offset; ++ if (font_width(fstyle, string, next) > x) { ++ first_over = next; ++ break; ++ } ++ offset = next; ++ } ++ if (first_over == length && font_width(fstyle, string, length) <= x) { + *char_offset = length; ++ } else if (last_space != 0) { ++ *char_offset = last_space; + } else { +- while (*char_offset > 0) { +- if (string[*char_offset] == ' ') +- break; +- (*char_offset)--; +- } +- if (*char_offset == 0) { +- *char_offset = c_off; +- while (*char_offset < length && string[*char_offset] != ' ') { +- (*char_offset)++; +- } +- } ++ *char_offset = first_over; ++ while (*char_offset < length && string[*char_offset] != ' ') ++ (*char_offset)++; + } +- *actual_x = *char_offset * (fstyle->size / PLOT_STYLE_SCALE); ++ if (*char_offset == 0) *char_offset = first_over != 0 ? first_over : length; ++ *actual_x = font_width(fstyle, string, *char_offset); + return NSERROR_OK; + } diff --git a/www-client/netsurf/patches/0010-font-point-to-pixel-scaling.patch b/www-client/netsurf/patches/0010-font-point-to-pixel-scaling.patch new file mode 100644 index 0000000..22ef4e9 --- /dev/null +++ b/www-client/netsurf/patches/0010-font-point-to-pixel-scaling.patch @@ -0,0 +1,67 @@ +diff --git a/frontends/monkey/plot.c b/frontends/monkey/plot.c +--- a/frontends/monkey/plot.c ++++ b/frontends/monkey/plot.c +@@ -20,13 +20,21 @@ + + #include "utils/utils.h" + #include "utils/errors.h" ++#include "netsurf/browser.h" + #include "netsurf/plotters.h" + + #include "monkey/output.h" + #include "monkey/browser.h" + + #if defined(__montauk__) + #include "content/fetchers/montauk_bridge.h" ++ ++static int native_font_px(plot_style_fixed point_size) ++{ ++ int pixels = (int)(((int64_t)point_size * browser_get_dpi() + ++ 36 * PLOT_STYLE_SCALE) / (72 * PLOT_STYLE_SCALE)); ++ return pixels > 0 ? pixels : 1; ++} + + struct bitmap { + void *ptr; +@@ -70,6 +78,6 @@ static void native_text(struct gui_window *gw, int x, int baseline, + const char *text, size_t length, const struct plot_font_style *style) + { +- int px = plot_style_fixed_to_int(style->size); ++ int px = native_font_px(style->size); + if (gw == NULL || gw->pixels == NULL) return; + mtk_netsurf_font_draw(gw->pixels, gw->width, gw->height, + gw->clip_x0, gw->clip_y0, gw->clip_x1, gw->clip_y1, +diff --git a/frontends/monkey/layout.c b/frontends/monkey/layout.c +--- a/frontends/monkey/layout.c ++++ b/frontends/monkey/layout.c +@@ -25,13 +25,21 @@ + #include + + #include "utils/utf8.h" ++#include "netsurf/browser.h" + #include "netsurf/plot_style.h" + #include "netsurf/layout.h" + + #include "monkey/layout.h" + + #if defined(__montauk__) + #include "content/fetchers/montauk_bridge.h" ++ ++static int native_font_px(plot_style_fixed point_size) ++{ ++ int pixels = (int)(((int64_t)point_size * browser_get_dpi() + ++ 36 * PLOT_STYLE_SCALE) / (72 * PLOT_STYLE_SCALE)); ++ return pixels > 0 ? pixels : 1; ++} + #endif + + static int font_width(const plot_font_style_t *fstyle, +@@ -38,7 +46,7 @@ static int font_width(const plot_font_style_t *fstyle, + { + #if defined(__montauk__) + return mtk_netsurf_font_width(fstyle->family, fstyle->weight, +- fstyle->flags, plot_style_fixed_to_int(fstyle->size), string, length); ++ fstyle->flags, native_font_px(fstyle->size), string, length); + #else + return (fstyle->size * utf8_bounded_length(string, length)) / PLOT_STYLE_SCALE; + #endif diff --git a/www-client/netsurf/patches/0011-native-browser-shell.patch b/www-client/netsurf/patches/0011-native-browser-shell.patch new file mode 100644 index 0000000..302f4e3 --- /dev/null +++ b/www-client/netsurf/patches/0011-native-browser-shell.patch @@ -0,0 +1,544 @@ +diff --git a/frontends/monkey/Makefile b/frontends/monkey/Makefile +index d2dae3e..d83415f 100644 +--- a/frontends/monkey/Makefile ++++ b/frontends/monkey/Makefile +@@ -50,7 +50,7 @@ endif + # ---------------------------------------------------------------------------- + + # S_MONKEY are sources purely for the MONKEY build +-S_FRONTEND := main.c output.c filetype.c schedule.c bitmap.c plot.c browser.c \ ++S_FRONTEND := main.c output.c filetype.c schedule.c bitmap.c plot.c browser.c montauk_ui.c \ + download.c 401login.c layout.c dispatch.c fetch.c + + +diff --git a/frontends/monkey/browser.c b/frontends/monkey/browser.c +index 4a33fae..d2b0c78 100644 +--- a/frontends/monkey/browser.c ++++ b/frontends/monkey/browser.c +@@ -41,11 +41,92 @@ + #include "monkey/output.h" + #include "monkey/browser.h" + #include "monkey/plot.h" ++#if defined(__montauk__) ++#include "monkey/montauk_ui.h" ++#include "content/fetchers/montauk_bridge.h" ++#endif + + static uint32_t win_ctr = 0; + + static struct gui_window *gw_ring = NULL; + ++#if defined(__montauk__) ++static struct gui_window *native_active; ++ ++struct gui_window *monkey_native_get_window(void) ++{ ++ return native_active != NULL ? native_active : gw_ring; ++} ++ ++size_t monkey_native_tab_count(void) ++{ ++ size_t count = 0; ++ struct gui_window *item = gw_ring; ++ if (item != NULL) do { ++ if (native_active == NULL || item->native_id == native_active->native_id) count++; ++ item = item->r_next; ++ } while (item != gw_ring); ++ return count; ++} ++ ++struct gui_window *monkey_native_tab_at(size_t index) ++{ ++ struct gui_window *item = gw_ring; ++ if (item != NULL) do { ++ if (native_active == NULL || item->native_id == native_active->native_id) { ++ if (index == 0) return item; ++ index--; ++ } ++ item = item->r_next; ++ } while (item != gw_ring); ++ return NULL; ++} ++ ++void monkey_native_activate_window(struct gui_window *gw) ++{ ++ if (gw != NULL) { ++ native_active = gw; ++ gw->dirty = true; ++ } ++} ++ ++void monkey_native_resize_tabs(struct gui_window *gw, int width, int height, ++ uint32_t *pixels) ++{ ++ struct gui_window *item = gw_ring; ++ if (item != NULL) do { ++ if (item->native_id == gw->native_id) { ++ item->width = width; ++ item->height = height; ++ item->pixels = pixels; ++ item->dirty = true; ++ browser_window_schedule_reformat(item->bw); ++ } ++ item = item->r_next; ++ } while (item != gw_ring); ++} ++ ++void monkey_native_set_scale(struct gui_window *gw, int scale) ++{ ++ struct gui_window *item = gw_ring; ++ if (scale < 0) scale = 0; ++ if (scale > 2) scale = 2; ++ if (item != NULL) do { ++ if (item->native_id == gw->native_id) { ++ struct mtk_netsurf_chrome_metrics metrics; ++ mtk_netsurf_chrome_metrics(scale, item->width, ++ (int)monkey_native_tab_count(), &metrics); ++ item->ui_scale = scale; ++ item->chrome_height = metrics.chrome_h; ++ item->status_height = metrics.status_h; ++ item->dirty = true; ++ browser_window_schedule_reformat(item->bw); ++ } ++ item = item->r_next; ++ } while (item != gw_ring); ++} ++#endif ++ + /* exported function documented in monkey/browser.h */ + nserror monkey_warn_user(const char *warning, const char *detail) + { +@@ -88,18 +169,39 @@ gui_window_create(struct browser_window *bw, + ret->win_num = win_ctr++; + ret->bw = bw; + +- ret->width = 800; +- ret->height = 600; ++ ret->width = 1000; ++ ret->height = 700; + #if defined(__montauk__) +- mtk_win_result native; +- if (mtk_win_create("NetSurf", ret->width, ret->height, &native) != 0) { +- free(ret); +- return NULL; ++ if ((flags & GW_CREATE_TAB) && existing != NULL) { ++ ret->width = existing->width; ++ ret->height = existing->height; ++ ret->native_id = existing->native_id; ++ ret->pixels = existing->pixels; ++ ret->ui_scale = existing->ui_scale; ++ ret->chrome_height = existing->chrome_height; ++ ret->status_height = existing->status_height; ++ } else { ++ mtk_win_result native; ++ if (mtk_win_create("NetSurf", ret->width, ret->height, &native) != 0) { ++ free(ret); ++ return NULL; ++ } ++ ret->native_id = native.id; ++ ret->pixels = (uint32_t *)(uintptr_t)native.pixels; ++ ret->ui_scale = mtk_win_getscale(); ++ if (ret->ui_scale < 0) ret->ui_scale = 0; ++ if (ret->ui_scale > 2) ret->ui_scale = 2; ++ struct mtk_netsurf_chrome_metrics metrics; ++ mtk_netsurf_chrome_metrics(ret->ui_scale, ret->width, 1, &metrics); ++ ret->chrome_height = metrics.chrome_h; ++ ret->status_height = metrics.status_h; + } +- ret->native_id = native.id; +- ret->pixels = (uint32_t *)(uintptr_t)native.pixels; + ret->clip_x1 = ret->width; + ret->clip_y1 = ret->height; ++ ret->mouse_x = ret->mouse_y = -1; ++ ret->dirty = true; ++ strcpy(ret->title, "New Tab"); ++ strcpy(ret->status, "Loading..."); + #endif + + moutf(MOUT_WINDOW, +@@ -112,6 +214,10 @@ gui_window_create(struct browser_window *bw, + ret->win_num, ret->width, ret->height); + + RING_INSERT(gw_ring, ret); ++#if defined(__montauk__) ++ if (native_active == NULL || (flags & GW_CREATE_FOREGROUND)) ++ native_active = ret; ++#endif + + return ret; + } +@@ -121,7 +227,16 @@ gui_window_destroy(struct gui_window *g) + { + moutf(MOUT_WINDOW, "DESTROY WIN %u", g->win_num); + #if defined(__montauk__) +- mtk_win_destroy(g->native_id); ++ mtk_netsurf_address_input_destroy((uintptr_t)g); ++ bool shared = false; ++ struct gui_window *item = gw_ring; ++ if (item != NULL) do { ++ if (item != g && item->native_id == g->native_id) shared = true; ++ item = item->r_next; ++ } while (item != gw_ring); ++ if (!shared) mtk_win_destroy(g->native_id); ++ if (native_active == g) ++ native_active = shared ? (g->r_next != g ? g->r_next : NULL) : NULL; + #endif + RING_REMOVE(gw_ring, g); + free(g); +@@ -130,6 +245,13 @@ gui_window_destroy(struct gui_window *g) + static void + gui_window_set_title(struct gui_window *g, const char *title) + { ++#if defined(__montauk__) ++ if (title != NULL) { ++ strncpy(g->title, title, sizeof(g->title) - 1); ++ g->title[sizeof(g->title) - 1] = 0; ++ g->dirty = true; ++ } ++#endif + moutf(MOUT_WINDOW, "TITLE WIN %u STR %s", g->win_num, title); + } + +@@ -146,6 +268,12 @@ gui_window_get_dimensions(struct gui_window *g, int *width, int *height) + { + *width = g->width; + *height = g->height; ++#if defined(__montauk__) ++ *width = mtk_netsurf_page_view_width(*width); ++ *height -= g->chrome_height + g->status_height; ++ if (*width < 1) *width = 1; ++ if (*height < 1) *height = 1; ++#endif + + moutf(MOUT_WINDOW, + "GET_DIMENSIONS WIN %u WIDTH %d HEIGHT %d", +@@ -169,6 +297,10 @@ gui_window_set_icon(struct gui_window *g, struct hlcache_handle *icon) + static void + gui_window_start_throbber(struct gui_window *g) + { ++#if defined(__montauk__) ++ g->loading = true; ++ g->dirty = true; ++#endif + moutf(MOUT_WINDOW, "START_THROBBER WIN %u", g->win_num); + } + +@@ -176,24 +308,8 @@ static void + gui_window_stop_throbber(struct gui_window *g) + { + #if defined(__montauk__) +- struct rect clip = { +- .x0 = 0, +- .y0 = 0, +- .x1 = g->width, +- .y1 = g->height +- }; +- struct redraw_context ctx = { +- .interactive = true, +- .background_images = true, +- .plot = monkey_plotters, +- .priv = g +- }; +- for (int y = 0; y < g->height; y++) { +- for (int x = 0; x < g->width; x++) +- g->pixels[y * g->width + x] = 0xffffffffu; +- } +- browser_window_redraw(g->bw, g->scrollx, g->scrolly, &clip, &ctx); +- mtk_win_present(g->native_id); ++ g->loading = false; ++ g->dirty = true; + #endif + moutf(MOUT_WINDOW, "STOP_THROBBER WIN %u", g->win_num); + } +@@ -214,6 +330,9 @@ gui_window_set_scroll(struct gui_window *gw, const struct rect *rect) + { + gw->scrollx = rect->x0; + gw->scrolly = rect->y0; ++#if defined(__montauk__) ++ gw->dirty = true; ++#endif + + moutf(MOUT_WINDOW, "SET_SCROLL WIN %u X %d Y %d", + gw->win_num, rect->x0, rect->y0); +@@ -231,6 +350,9 @@ gui_window_set_scroll(struct gui_window *gw, const struct rect *rect) + static nserror + monkey_window_invalidate_area(struct gui_window *gw, const struct rect *rect) + { ++#if defined(__montauk__) ++ gw->dirty = true; ++#endif + if (rect != NULL) { + moutf(MOUT_WINDOW, + "INVALIDATE_AREA WIN %u X %d Y %d WIDTH %d HEIGHT %d", +@@ -249,8 +371,13 @@ gui_window_update_extent(struct gui_window *g) + { + int width, height; + +- if (browser_window_get_extents(g->bw, false, &width, &height) != NSERROR_OK) ++ if (browser_window_get_extents(g->bw, true, &width, &height) != NSERROR_OK) + return; ++#if defined(__montauk__) ++ g->extent_width = width; ++ g->extent_height = height; ++ g->dirty = true; ++#endif + + moutf(MOUT_WINDOW, "UPDATE_EXTENT WIN %u WIDTH %d HEIGHT %d", + g->win_num, width, height); +@@ -259,6 +386,13 @@ gui_window_update_extent(struct gui_window *g) + static void + gui_window_set_status(struct gui_window *g, const char *text) + { ++#if defined(__montauk__) ++ if (text != NULL) { ++ strncpy(g->status, text, sizeof(g->status) - 1); ++ g->status[sizeof(g->status) - 1] = 0; ++ g->dirty = true; ++ } ++#endif + moutf(MOUT_WINDOW, "SET_STATUS WIN %u STR %s", g->win_num, text); + } + +@@ -336,11 +470,39 @@ gui_window_set_pointer(struct gui_window *g, gui_pointer_shape shape) + static nserror + gui_window_set_url(struct gui_window *g, nsurl *url) + { ++#if defined(__montauk__) ++ strncpy(g->address, nsurl_access(url), sizeof(g->address) - 1); ++ g->address[sizeof(g->address) - 1] = 0; ++ g->dirty = true; ++#endif + moutf(MOUT_WINDOW, "SET_URL WIN %u URL %s", + g->win_num, nsurl_access(url)); + return NSERROR_OK; + } + ++#if defined(__montauk__) ++void monkey_native_redraw(struct gui_window *g) ++{ ++ if (g == NULL || g->pixels == NULL) return; ++ const int chrome_h = g->chrome_height, status_h = g->status_height; ++ int content_h = g->height - chrome_h - status_h; ++ if (content_h < 1) content_h = 1; ++ for (int y = chrome_h; y < g->height - status_h; y++) ++ for (int x = 0; x < g->width; x++) ++ g->pixels[y * g->width + x] = 0xffffffffu; ++ struct rect clip = { .x0 = 0, .y0 = 0, ++ .x1 = mtk_netsurf_page_view_width(g->width), .y1 = content_h }; ++ struct redraw_context ctx = { ++ .interactive = true, .background_images = true, ++ .plot = monkey_plotters, .priv = g ++ }; ++ browser_window_redraw(g->bw, -g->scrollx, -g->scrolly, &clip, &ctx); ++ monkey_native_draw_chrome(g); ++ mtk_win_present(g->native_id); ++ g->dirty = false; ++} ++#endif ++ + static bool + gui_window_get_scroll(struct gui_window *g, int *sx, int *sy) + { +@@ -364,6 +526,13 @@ static void + gui_window_place_caret(struct gui_window *g, int x, int y, int height, + const struct rect *clip) + { ++#if defined(__montauk__) ++ g->caret_x = x; ++ g->caret_y = y; ++ g->caret_height = height; ++ g->caret_visible = true; ++ g->dirty = true; ++#endif + moutf(MOUT_WINDOW, "PLACE_CARET WIN %u X %d Y %d HEIGHT %d", + g->win_num, x, y, height); + } +@@ -371,6 +540,10 @@ gui_window_place_caret(struct gui_window *g, int x, int y, int height, + static void + gui_window_remove_caret(struct gui_window *g) + { ++#if defined(__montauk__) ++ g->caret_visible = false; ++ g->dirty = true; ++#endif + moutf(MOUT_WINDOW, "REMOVE_CARET WIN %u", g->win_num); + } + +@@ -390,6 +563,18 @@ gui_window_save_link(struct gui_window *g, nsurl *url, const char *title) + return NSERROR_OK; + } + ++#if defined(__montauk__) ++static void gui_window_file_gadget_open(struct gui_window *g, ++ struct hlcache_handle *hl, struct form_control *gadget) ++{ ++ char path[512]; ++ (void)hl; ++ if (mtk_netsurf_open_file(path, sizeof(path))) ++ browser_window_set_gadget_filename(g->bw, gadget, path); ++ g->dirty = true; ++} ++#endif ++ + static void + gui_window_console_log(struct gui_window *g, + browser_window_console_source src, +@@ -448,6 +633,10 @@ static void + gui_window_report_page_info(struct gui_window *g) + { + const char *state = "***WAH***"; ++#if defined(__montauk__) ++ g->page_state = browser_window_get_page_info_state(g->bw); ++ g->dirty = true; ++#endif + + switch (browser_window_get_page_info_state(g->bw)) { + case PAGE_STATE_UNKNOWN: +@@ -911,6 +1100,9 @@ static struct gui_window_table window_table = { + .place_caret = gui_window_place_caret, + .drag_start = gui_window_drag_start, + .save_link = gui_window_save_link, ++#if defined(__montauk__) ++ .file_gadget_open = gui_window_file_gadget_open, ++#endif + + .console_log = gui_window_console_log, + }; +diff --git a/frontends/monkey/browser.h b/frontends/monkey/browser.h +index 8e90e0c..09399ab 100644 +--- a/frontends/monkey/browser.h ++++ b/frontends/monkey/browser.h +@@ -37,7 +37,19 @@ struct gui_window { + #if defined(__montauk__) + int native_id; + uint32_t *pixels; ++ int ui_scale, chrome_height, status_height; ++ int mouse_x, mouse_y; + int clip_x0, clip_y0, clip_x1, clip_y1; ++ int extent_width, extent_height; ++ uint8_t dirty, loading, address_focus; ++ uint8_t caret_visible; ++ uint8_t find_focus, menu_open, page_state; ++ int scrollbar_hover, scrollbar_dragging, scrollbar_drag_offset; ++ int caret_x, caret_y, caret_height; ++ char find_text[256]; ++ char address[2048]; ++ char title[128]; ++ char status[256]; + #endif + char *host; /* Ignore this, it's in case RING*() gets debugging for fetchers */ + +@@ -50,6 +62,17 @@ void monkey_window_process_reformats(void); + void monkey_window_handle_command(int argc, char **argv); + void monkey_kill_browser_windows(void); + ++#if defined(__montauk__) ++struct gui_window *monkey_native_get_window(void); ++size_t monkey_native_tab_count(void); ++struct gui_window *monkey_native_tab_at(size_t index); ++void monkey_native_activate_window(struct gui_window *gw); ++void monkey_native_resize_tabs(struct gui_window *gw, int width, int height, ++ uint32_t *pixels); ++void monkey_native_set_scale(struct gui_window *gw, int scale); ++void monkey_native_redraw(struct gui_window *gw); ++#endif ++ + nserror monkey_warn_user(const char *warning, const char *detail); + + #endif /* NETSURF_MONKEY_BROWSER_H */ +diff --git a/frontends/monkey/main.c b/frontends/monkey/main.c +index 578d148..ebd940a 100644 +--- a/frontends/monkey/main.c ++++ b/frontends/monkey/main.c +@@ -46,6 +46,9 @@ + #include "monkey/401login.h" + #include "monkey/filetype.h" + #include "monkey/fetch.h" ++#if defined(__montauk__) ++#include "monkey/montauk_ui.h" ++#endif + #include "monkey/schedule.h" + #include "monkey/bitmap.h" + #include "monkey/layout.h" +@@ -274,7 +277,7 @@ static struct gui_misc_table monkey_misc_table = { + .present_cookies = gui_present_cookies, + }; + +-static void monkey_run(void) ++static void __attribute__((unused)) monkey_run(void) + { + fd_set read_fd_set, write_fd_set, exc_fd_set; + int max_fd; +@@ -399,6 +402,10 @@ main(int argc, char **argv) + .fetch = monkey_fetch_table, + .bitmap = monkey_bitmap_table, + .layout = monkey_layout_table, ++#if defined(__montauk__) ++ .clipboard = &monkey_native_clipboard_table, ++ .search = &monkey_native_search_table, ++#endif + .llcache = filesystem_llcache_table, + }; + +@@ -491,7 +498,11 @@ main(int argc, char **argv) + + + moutf(MOUT_GENERIC, "STARTED"); ++#if defined(__montauk__) ++ monkey_native_run(argc > 1 ? argv[1] : NULL); ++#else + monkey_run(); ++#endif + + moutf(MOUT_GENERIC, "CLOSING_DOWN"); + monkey_kill_browser_windows(); +diff --git a/frontends/monkey/plot.c b/frontends/monkey/plot.c +index 6f762f9..f6bf09a 100644 +--- a/frontends/monkey/plot.c ++++ b/frontends/monkey/plot.c +@@ -53,6 +53,7 @@ static uint32_t native_colour(colour c) + + static void native_pixel(struct gui_window *gw, int x, int y, uint32_t colour) + { ++ y += gw->chrome_height; + if (gw == NULL || gw->pixels == NULL || x < gw->clip_x0 || y < gw->clip_y0 || + x >= gw->clip_x1 || y >= gw->clip_y1 || x < 0 || y < 0 || + x >= gw->width || y >= gw->height || colour == 0) return; +@@ -63,6 +64,7 @@ static void native_fill(struct gui_window *gw, int x0, int y0, int x1, int y1, + uint32_t colour) + { + if (gw == NULL || colour == 0) return; ++ y0 += gw->chrome_height; y1 += gw->chrome_height; + if (x0 < gw->clip_x0) x0 = gw->clip_x0; + if (y0 < gw->clip_y0) y0 = gw->clip_y0; + if (x1 > gw->clip_x1) x1 = gw->clip_x1; +@@ -96,7 +98,7 @@ static void native_text(struct gui_window *gw, int x, int baseline, + if (gw == NULL || gw->pixels == NULL) return; + mtk_netsurf_font_draw(gw->pixels, gw->width, gw->height, + gw->clip_x0, gw->clip_y0, gw->clip_x1, gw->clip_y1, +- x, baseline, text, length, native_colour(style->foreground), ++ x, baseline + gw->chrome_height, text, length, native_colour(style->foreground), + style->family, style->weight, style->flags, px); + } + #endif +@@ -116,9 +118,9 @@ monkey_plot_clip(const struct redraw_context *ctx, const struct rect *clip) + struct gui_window *gw = ctx->priv; + if (gw != NULL) { + gw->clip_x0 = clip->x0; +- gw->clip_y0 = clip->y0; ++ gw->clip_y0 = clip->y0 + gw->chrome_height; + gw->clip_x1 = clip->x1; +- gw->clip_y1 = clip->y1; ++ gw->clip_y1 = clip->y1 + gw->chrome_height; + } + #endif + moutf(MOUT_PLOT, "CLIP X0 %d Y0 %d X1 %d Y1 %d", +@@ -338,7 +340,7 @@ monkey_plot_bitmap(const struct redraw_context *ctx, + uint32_t out = 0xff000000u | ((p & 0xffu) << 16) | + (p & 0xff00u) | ((p >> 16) & 0xffu); + if (a != 255) { +- int tx = x + dx, ty = y + dy; ++ int tx = x + dx, ty = y + dy + gw->chrome_height; + if (tx >= 0 && ty >= 0 && tx < gw->width && ty < gw->height) { + uint32_t d = gw->pixels[ty * gw->width + tx]; + unsigned r = (((out >> 16) & 255) * a + ((d >> 16) & 255) * (255-a)) / 255; diff --git a/www-client/netsurf/patches/0012-native-downloads.patch b/www-client/netsurf/patches/0012-native-downloads.patch new file mode 100644 index 0000000..7ea7762 --- /dev/null +++ b/www-client/netsurf/patches/0012-native-downloads.patch @@ -0,0 +1,113 @@ +diff --git a/frontends/monkey/download.c b/frontends/monkey/download.c +index 1f77439..2eef168 100644 +--- a/frontends/monkey/download.c ++++ b/frontends/monkey/download.c +@@ -17,8 +17,13 @@ + */ + + #include ++#include + #include + #include ++#include ++#if defined(__montauk__) ++#include ++#endif + + #include "utils/errors.h" + #include "utils/ring.h" +@@ -36,6 +41,11 @@ struct gui_download_window { + struct gui_window *g; + download_context *dlctx; + uint32_t dwin_num; ++#if defined(__montauk__) ++ int file_handle; ++ unsigned long offset; ++ char path[256]; ++#endif + char *host; /* ignore */ + }; + +@@ -51,6 +61,34 @@ gui_download_window_create(download_context *ctx, + ret->g = parent; + ret->dwin_num = dwin_ctr++; + ret->dlctx = ctx; ++#if defined(__montauk__) ++ ret->file_handle = -1; ++ char user[64] = {0}; ++ if (_mtk_syscall2(MTK_SYS_GETUSER, (long)user, sizeof(user)) < 0 || user[0] == 0) ++ strcpy(user, "admin"); ++ char directory[192]; ++ snprintf(directory, sizeof(directory), "0:/users/%s/Downloads", user); ++ mtk_mkdir(directory); ++ const char *source = download_context_get_filename(ctx); ++ char filename[96]; ++ if (source == NULL || source[0] == 0) source = "download.bin"; ++ size_t n = 0; ++ for (; source[n] != 0 && n + 1 < sizeof(filename); n++) { ++ unsigned char c = (unsigned char)source[n]; ++ filename[n] = (c < 32 || c == '/' || c == '\\' || c == ':') ? '_' : (char)c; ++ } ++ filename[n] = 0; ++ snprintf(ret->path, sizeof(ret->path), "%s/%s", directory, filename); ++ ret->file_handle = mtk_create(ret->path); ++ if (ret->file_handle < 0) { ++ free(ret); ++ return NULL; ++ } ++ if (parent != NULL) { ++ snprintf(parent->status, sizeof(parent->status), "Downloading %s", filename); ++ parent->dirty = true; ++ } ++#endif + + RING_INSERT(dw_ring, ret); + +@@ -64,21 +102,47 @@ static nserror + gui_download_window_data(struct gui_download_window *dw, + const char *data, unsigned int size) + { ++#if defined(__montauk__) ++ if (dw->file_handle < 0 || mtk_write(dw->file_handle, ++ (const uint8_t *)data, dw->offset, size) != (int)size) ++ return NSERROR_SAVE_FAILED; ++ dw->offset += size; ++ if (dw->g != NULL) { ++ snprintf(dw->g->status, sizeof(dw->g->status), "Downloading: %lu bytes", dw->offset); ++ dw->g->dirty = true; ++ } ++ return NSERROR_OK; ++#else + moutf(MOUT_DOWNLOAD, "DATA DWIN %u SIZE %u DATA %s", + dw->dwin_num, size, data); + return NSERROR_OK; ++#endif + } + + static void + gui_download_window_error(struct gui_download_window *dw, + const char *error_msg) + { ++#if defined(__montauk__) ++ if (dw->file_handle >= 0) { mtk_close(dw->file_handle); dw->file_handle = -1; } ++ if (dw->g != NULL) { ++ snprintf(dw->g->status, sizeof(dw->g->status), "Download failed: %s", error_msg); ++ dw->g->dirty = true; ++ } ++#endif + moutf(MOUT_DOWNLOAD, "ERROR DWIN %u ERROR %s", dw->dwin_num, error_msg); + } + + static void + gui_download_window_done(struct gui_download_window *dw) + { ++#if defined(__montauk__) ++ if (dw->file_handle >= 0) { mtk_close(dw->file_handle); dw->file_handle = -1; } ++ if (dw->g != NULL) { ++ snprintf(dw->g->status, sizeof(dw->g->status), "Saved %s", dw->path); ++ dw->g->dirty = true; ++ } ++#endif + moutf(MOUT_DOWNLOAD, "DONE DWIN %u", dw->dwin_num); + RING_REMOVE(dw_ring, dw); + download_context_destroy(dw->dlctx); diff --git a/www-client/netsurf/patches/0013-bitmap-rowstride-and-bounds.patch b/www-client/netsurf/patches/0013-bitmap-rowstride-and-bounds.patch new file mode 100644 index 0000000..b72431c --- /dev/null +++ b/www-client/netsurf/patches/0013-bitmap-rowstride-and-bounds.patch @@ -0,0 +1,21 @@ +diff --git a/frontends/monkey/bitmap.c b/frontends/monkey/bitmap.c +index cc13c8b..60104ec 100644 +--- a/frontends/monkey/bitmap.c ++++ b/frontends/monkey/bitmap.c +@@ -36,12 +36,16 @@ struct bitmap { + + static void *bitmap_create(int width, int height, enum gui_bitmap_flags flags) + { ++ if (width <= 0 || height <= 0 || width > 8192 || height > 8192 || ++ (size_t)width * (size_t)height > 32u * 1024u * 1024u) ++ return NULL; + struct bitmap *ret = calloc(1, sizeof(*ret)); + if (ret == NULL) + return NULL; + + ret->width = width; + ret->height = height; ++ ret->rowstride = (size_t)width * 4; + ret->opaque = (flags & BITMAP_OPAQUE) == BITMAP_OPAQUE; + + ret->ptr = calloc(width, height * 4);