fix: update NetSurf port & montauk frontend to use new paths for fonts and icons

This commit is contained in:
daniel committed 2026-09-30 08:47:44 +02:00
1 parent 6f3cf99667
commit 35e3d1a0bb
21 files changed
+3376 -34

No files matched your search

+54 -30
View File
@@ -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 <utf8proc.h>.
# 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
@@ -0,0 +1,18 @@
#include <stdlib.h>
#include <string.h>
#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 <gui/stb_image.h>
+11
View File
@@ -0,0 +1,11 @@
[app]
name = "NetSurf"
binary = "netsurf.elf"
icon = "web-browser.svg"
[menu]
category = "Internet"
visible = true
[launch]
pass_home_dir = false
+477
View File
@@ -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 <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <montauk.h>
#include <libwapcaplet/libwapcaplet.h>
#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);
}
+8
View File
@@ -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
+794
View File
@@ -0,0 +1,794 @@
/* C ABI bridge to MontaukOS' native TrueType and BearSSL stacks. */
#include <gui/truetype.hpp>
#include <gui/dialogs.hpp>
#include <gui/svg.hpp>
#include <gui/canvas.hpp>
#include <gui/mtk/widgets.hpp>
#include <tls/tls.hpp>
extern "C" {
#include <stdlib.h>
#include <string.h>
}
#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<uint8_t>(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<uint8_t>(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<int>(cp));
return glyph != nullptr ? glyph->advance : 0;
}
int advance = 0, lsb = 0;
stbtt_GetCodepointHMetrics(&font->info, static_cast<int>(cp), &advance, &lsb);
return static_cast<int>(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<int>(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<uint8_t>(scancode);
event.ascii = static_cast<char>(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<uint8_t>(buttons),
static_cast<uint8_t>(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<uint8_t>(buttons),
static_cast<uint8_t>(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<unsigned char>(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<int>(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<unsigned char>(text[previous]) & 0xc0) == 0x80)
previous--;
int glyph = mtk_netsurf_font_width(0, 500, 0, font,
text + previous, static_cast<size_t>(start - previous));
if (used + glyph > available) break;
used += glyph;
start = previous;
}
}
slot->view_start = start;
size_t needed = static_cast<size_t>(m.address_w) * m.address_h;
if (needed > address_scratch_pixels) {
uint32_t *replacement = static_cast<uint32_t *>(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<int>(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<int>(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<int>(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<uint8_t *>(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<int>(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<int>(request_len), anchors, response,
static_cast<int>(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<int>(path_size), message, sizeof(message)) ? 1 : 0;
}
+98
View File
@@ -0,0 +1,98 @@
#ifndef NETSURF_MONTAUK_BRIDGE_H
#define NETSURF_MONTAUK_BRIDGE_H
#include <stddef.h>
#include <stdint.h>
#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
+131
View File
@@ -0,0 +1,131 @@
/* JPEG and PNG content handler using MontaukOS's stb_image decoder. */
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gui/stb_image.h>
#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);
+538
View File
@@ -0,0 +1,538 @@
/* Native MontaukOS browser shell for the NetSurf core. */
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <montauk.h>
#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();
}
+13
View File
@@ -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
@@ -0,0 +1,23 @@
/* One implementation unit for the MontaukOS TrueType renderer. */
#include <cstdint>
#include <cstddef>
#include <montauk/heap.h>
#include <montauk/string.h>
#include <gui/stb_math.h>
#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 <gui/stb_truetype.h>
@@ -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
@@ -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
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
+#if defined(__montauk__)
+#include <montauk.h>
+#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;
@@ -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);
}
@@ -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;
}
@@ -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 <stddef.h>
#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
@@ -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;
@@ -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 <stdio.h>
+#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
+#include <string.h>
+#if defined(__montauk__)
+#include <montauk.h>
+#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);
@@ -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);