feat: common TLS library, weather app, UI scaling fix in Window Server, and more
This commit is contained in:
@@ -789,6 +789,16 @@ namespace Zenith {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Window scale syscalls ----
|
||||||
|
|
||||||
|
static int Sys_WinSetScale(int scale) {
|
||||||
|
return WinServer::SetScale(scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Sys_WinGetScale() {
|
||||||
|
return WinServer::GetScale();
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Window server syscalls ----
|
// ---- Window server syscalls ----
|
||||||
|
|
||||||
static int Sys_WinCreate(const char* title, int w, int h, WinCreateResult* result) {
|
static int Sys_WinCreate(const char* title, int w, int h, WinCreateResult* result) {
|
||||||
@@ -1003,6 +1013,10 @@ namespace Zenith {
|
|||||||
return (int64_t)Sys_Kill((int)frame->arg1);
|
return (int64_t)Sys_Kill((int)frame->arg1);
|
||||||
case SYS_DEVLIST:
|
case SYS_DEVLIST:
|
||||||
return (int64_t)Sys_DevList((DevInfo*)frame->arg1, (int)frame->arg2);
|
return (int64_t)Sys_DevList((DevInfo*)frame->arg1, (int)frame->arg2);
|
||||||
|
case SYS_WINSETSCALE:
|
||||||
|
return (int64_t)Sys_WinSetScale((int)frame->arg1);
|
||||||
|
case SYS_WINGETSCALE:
|
||||||
|
return (int64_t)Sys_WinGetScale();
|
||||||
default:
|
default:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ namespace Zenith {
|
|||||||
static constexpr uint64_t SYS_WINMAP = 59;
|
static constexpr uint64_t SYS_WINMAP = 59;
|
||||||
static constexpr uint64_t SYS_WINSENDEVENT = 60;
|
static constexpr uint64_t SYS_WINSENDEVENT = 60;
|
||||||
static constexpr uint64_t SYS_WINRESIZE = 64;
|
static constexpr uint64_t SYS_WINRESIZE = 64;
|
||||||
|
static constexpr uint64_t SYS_WINSETSCALE = 65;
|
||||||
|
static constexpr uint64_t SYS_WINGETSCALE = 66;
|
||||||
|
|
||||||
// Process management syscalls
|
// Process management syscalls
|
||||||
static constexpr uint64_t SYS_PROCLIST = 61;
|
static constexpr uint64_t SYS_PROCLIST = 61;
|
||||||
@@ -135,12 +137,13 @@ namespace Zenith {
|
|||||||
|
|
||||||
// Window server shared types
|
// Window server shared types
|
||||||
struct WinEvent {
|
struct WinEvent {
|
||||||
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close
|
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close, 4=scale
|
||||||
uint8_t _pad[3];
|
uint8_t _pad[3];
|
||||||
union {
|
union {
|
||||||
KeyEvent key;
|
KeyEvent key;
|
||||||
struct { int32_t x, y, scroll; uint8_t buttons, prev_buttons; } mouse;
|
struct { int32_t x, y, scroll; uint8_t buttons, prev_buttons; } mouse;
|
||||||
struct { int32_t w, h; } resize;
|
struct { int32_t w, h; } resize;
|
||||||
|
struct { int32_t scale; } scale;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
namespace WinServer {
|
namespace WinServer {
|
||||||
|
|
||||||
static WindowSlot g_slots[MaxWindows];
|
static WindowSlot g_slots[MaxWindows];
|
||||||
|
static int g_uiScale = 1;
|
||||||
|
|
||||||
int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h,
|
int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h,
|
||||||
uint64_t& heapNext, uint64_t& outVa) {
|
uint64_t& heapNext, uint64_t& outVa) {
|
||||||
@@ -201,6 +202,28 @@ namespace WinServer {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int SetScale(int scale) {
|
||||||
|
if (scale < 0) scale = 0;
|
||||||
|
if (scale > 2) scale = 2;
|
||||||
|
g_uiScale = scale;
|
||||||
|
|
||||||
|
// Broadcast scale event to all active windows
|
||||||
|
Zenith::WinEvent ev;
|
||||||
|
memset(&ev, 0, sizeof(ev));
|
||||||
|
ev.type = 4;
|
||||||
|
ev.scale.scale = scale;
|
||||||
|
for (int i = 0; i < MaxWindows; i++) {
|
||||||
|
if (g_slots[i].used) {
|
||||||
|
SendEvent(i, &ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetScale() {
|
||||||
|
return g_uiScale;
|
||||||
|
}
|
||||||
|
|
||||||
void CleanupProcess(int pid) {
|
void CleanupProcess(int pid) {
|
||||||
for (int i = 0; i < MaxWindows; i++) {
|
for (int i = 0; i < MaxWindows; i++) {
|
||||||
if (g_slots[i].used && g_slots[i].ownerPid == pid) {
|
if (g_slots[i].used && g_slots[i].ownerPid == pid) {
|
||||||
|
|||||||
@@ -40,5 +40,7 @@ namespace WinServer {
|
|||||||
int Resize(int windowId, int callerPid, uint64_t ownerPml4, int newW, int newH,
|
int Resize(int windowId, int callerPid, uint64_t ownerPml4, int newW, int newH,
|
||||||
uint64_t& heapNext, uint64_t& outVa);
|
uint64_t& heapNext, uint64_t& outVa);
|
||||||
void CleanupProcess(int pid);
|
void CleanupProcess(int pid);
|
||||||
|
int SetScale(int scale);
|
||||||
|
int GetScale();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-9
@@ -57,7 +57,7 @@ BINDIR := bin
|
|||||||
PROGRAMS := $(notdir $(wildcard src/*))
|
PROGRAMS := $(notdir $(wildcard src/*))
|
||||||
|
|
||||||
# Programs with custom Makefiles (built separately).
|
# Programs with custom Makefiles (built separately).
|
||||||
CUSTOM_BUILDS := doom fetch wiki wikipedia desktop
|
CUSTOM_BUILDS := doom fetch wiki wikipedia weather desktop
|
||||||
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
|
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
|
||||||
|
|
||||||
# Build targets: system programs go to bin/os/, games are handled separately.
|
# Build targets: system programs go to bin/os/, games are handled separately.
|
||||||
@@ -82,9 +82,9 @@ CA_CERTS := $(BINDIR)/etc/ca-certificates.crt
|
|||||||
# Home directory placeholder.
|
# Home directory placeholder.
|
||||||
HOMEKEEP := $(BINDIR)/home/.keep
|
HOMEKEEP := $(BINDIR)/home/.keep
|
||||||
|
|
||||||
.PHONY: all clean doom fetch wiki wikipedia desktop icons fonts bearssl libc
|
.PHONY: all clean doom fetch wiki wikipedia weather desktop icons fonts bearssl libc tls
|
||||||
|
|
||||||
all: bearssl libc $(TARGETS) fetch wiki wikipedia doom desktop icons fonts $(GAME_DATA) $(MANDST) $(WWWDST) $(CA_CERTS) $(HOMEKEEP)
|
all: bearssl libc tls $(TARGETS) fetch wiki wikipedia weather doom desktop icons fonts $(GAME_DATA) $(MANDST) $(WWWDST) $(CA_CERTS) $(HOMEKEEP)
|
||||||
|
|
||||||
# Build BearSSL static library.
|
# Build BearSSL static library.
|
||||||
bearssl:
|
bearssl:
|
||||||
@@ -94,18 +94,26 @@ bearssl:
|
|||||||
libc:
|
libc:
|
||||||
$(MAKE) -C lib/libc
|
$(MAKE) -C lib/libc
|
||||||
|
|
||||||
# Build fetch via its own Makefile (depends on bearssl and libc).
|
# Build shared TLS helper library.
|
||||||
fetch: bearssl libc
|
tls: bearssl libc
|
||||||
|
$(MAKE) -C lib/tls
|
||||||
|
|
||||||
|
# Build fetch via its own Makefile (depends on bearssl, libc, and tls).
|
||||||
|
fetch: bearssl libc tls
|
||||||
$(MAKE) -C src/fetch
|
$(MAKE) -C src/fetch
|
||||||
|
|
||||||
# Build wiki via its own Makefile (depends on bearssl and libc).
|
# Build wiki via its own Makefile (depends on bearssl, libc, and tls).
|
||||||
wiki: bearssl libc
|
wiki: bearssl libc tls
|
||||||
$(MAKE) -C src/wiki
|
$(MAKE) -C src/wiki
|
||||||
|
|
||||||
# Build wikipedia standalone GUI client (depends on bearssl and libc).
|
# Build wikipedia standalone GUI client (depends on bearssl, libc, and tls).
|
||||||
wikipedia: bearssl libc
|
wikipedia: bearssl libc tls
|
||||||
$(MAKE) -C src/wikipedia
|
$(MAKE) -C src/wikipedia
|
||||||
|
|
||||||
|
# Build weather standalone GUI client (depends on bearssl, libc, and tls).
|
||||||
|
weather: bearssl libc tls
|
||||||
|
$(MAKE) -C src/weather
|
||||||
|
|
||||||
# Build desktop via its own Makefile.
|
# Build desktop via its own Makefile.
|
||||||
desktop:
|
desktop:
|
||||||
$(MAKE) -C src/desktop
|
$(MAKE) -C src/desktop
|
||||||
@@ -157,7 +165,9 @@ clean:
|
|||||||
rm -rf $(BINDIR) obj
|
rm -rf $(BINDIR) obj
|
||||||
$(MAKE) -C lib/bearssl clean
|
$(MAKE) -C lib/bearssl clean
|
||||||
$(MAKE) -C lib/libc clean
|
$(MAKE) -C lib/libc clean
|
||||||
|
$(MAKE) -C lib/tls clean
|
||||||
$(MAKE) -C src/fetch clean
|
$(MAKE) -C src/fetch clean
|
||||||
$(MAKE) -C src/doom clean
|
$(MAKE) -C src/doom clean
|
||||||
$(MAKE) -C src/desktop clean
|
$(MAKE) -C src/desktop clean
|
||||||
$(MAKE) -C src/wikipedia clean
|
$(MAKE) -C src/wikipedia clean
|
||||||
|
$(MAKE) -C src/weather clean
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ namespace Zenith {
|
|||||||
static constexpr uint64_t SYS_WINMAP = 59;
|
static constexpr uint64_t SYS_WINMAP = 59;
|
||||||
static constexpr uint64_t SYS_WINSENDEVENT = 60;
|
static constexpr uint64_t SYS_WINSENDEVENT = 60;
|
||||||
static constexpr uint64_t SYS_WINRESIZE = 64;
|
static constexpr uint64_t SYS_WINRESIZE = 64;
|
||||||
|
static constexpr uint64_t SYS_WINSETSCALE = 65;
|
||||||
|
static constexpr uint64_t SYS_WINGETSCALE = 66;
|
||||||
|
|
||||||
// Process management syscalls
|
// Process management syscalls
|
||||||
static constexpr uint64_t SYS_PROCLIST = 61;
|
static constexpr uint64_t SYS_PROCLIST = 61;
|
||||||
@@ -135,12 +137,13 @@ namespace Zenith {
|
|||||||
|
|
||||||
// Window server shared types
|
// Window server shared types
|
||||||
struct WinEvent {
|
struct WinEvent {
|
||||||
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close
|
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close, 4=scale
|
||||||
uint8_t _pad[3];
|
uint8_t _pad[3];
|
||||||
union {
|
union {
|
||||||
KeyEvent key;
|
KeyEvent key;
|
||||||
struct { int32_t x, y, scroll; uint8_t buttons, prev_buttons; } mouse;
|
struct { int32_t x, y, scroll; uint8_t buttons, prev_buttons; } mouse;
|
||||||
struct { int32_t w, h; } resize;
|
struct { int32_t w, h; } resize;
|
||||||
|
struct { int32_t scale; } scale;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ struct DesktopState {
|
|||||||
SvgIcon icon_settings;
|
SvgIcon icon_settings;
|
||||||
SvgIcon icon_reboot;
|
SvgIcon icon_reboot;
|
||||||
|
|
||||||
|
SvgIcon icon_weather;
|
||||||
|
|
||||||
SvgIcon icon_doom;
|
SvgIcon icon_doom;
|
||||||
SvgIcon icon_procmgr;
|
SvgIcon icon_procmgr;
|
||||||
SvgIcon icon_mandelbrot;
|
SvgIcon icon_mandelbrot;
|
||||||
|
|||||||
@@ -77,6 +77,52 @@ struct SvgGradientTable {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// CSS class color table (.ClassName { color: #rrggbb; })
|
||||||
|
// Used to resolve fill:currentColor via the element's class= attribute.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static constexpr int SVG_MAX_CSS_CLASSES = 8;
|
||||||
|
|
||||||
|
struct SvgCssClass {
|
||||||
|
char name[48];
|
||||||
|
Color color;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SvgCssTable {
|
||||||
|
SvgCssClass entries[SVG_MAX_CSS_CLASSES];
|
||||||
|
int count;
|
||||||
|
|
||||||
|
void clear() { count = 0; }
|
||||||
|
|
||||||
|
void add(const char* name, Color c) {
|
||||||
|
if (count >= SVG_MAX_CSS_CLASSES) return;
|
||||||
|
int i = 0;
|
||||||
|
while (name[i] && i < 47) { entries[count].name[i] = name[i]; i++; }
|
||||||
|
entries[count].name[i] = '\0';
|
||||||
|
entries[count].color = c;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up class by name (first token if multiple classes are listed).
|
||||||
|
// Returns true if found.
|
||||||
|
bool lookup(const char* cls, Color* out) const {
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
const char* a = entries[i].name;
|
||||||
|
const char* b = cls;
|
||||||
|
bool match = true;
|
||||||
|
while (*a && *b) {
|
||||||
|
if (*a != *b) { match = false; break; }
|
||||||
|
a++; b++;
|
||||||
|
}
|
||||||
|
if (match && *a == '\0' && (*b == '\0' || *b == ' ')) {
|
||||||
|
*out = entries[i].color;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Fixed-point number parser (NO floating point)
|
// Fixed-point number parser (NO floating point)
|
||||||
// Parses strings like "3.25", "-0.5", ".1115", "16"
|
// Parses strings like "3.25", "-0.5", ".1115", "16"
|
||||||
@@ -815,6 +861,52 @@ inline void svg_rasterize(const SvgEdgeList& el, uint32_t* pixels, int w, int h,
|
|||||||
zenith::free(isect);
|
zenith::free(isect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Parse CSS class color mappings out of a <style> block.
|
||||||
|
// Handles: .ClassName { color: #rrggbb; }
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
inline void svg_parse_css_table(const char* style_data, int style_len, SvgCssTable* css) {
|
||||||
|
const char* p = style_data;
|
||||||
|
const char* end = style_data + style_len;
|
||||||
|
while (p < end && css->count < SVG_MAX_CSS_CLASSES) {
|
||||||
|
// Find next '.' that starts a class selector
|
||||||
|
while (p < end && *p != '.') ++p;
|
||||||
|
if (p >= end) break;
|
||||||
|
++p; // skip '.'
|
||||||
|
|
||||||
|
// Read class name (stops at whitespace or '{')
|
||||||
|
char name[48]; int ni = 0;
|
||||||
|
while (p < end && *p != ' ' && *p != '\t' && *p != '{' && ni < 47)
|
||||||
|
name[ni++] = *p++;
|
||||||
|
name[ni] = '\0';
|
||||||
|
|
||||||
|
// Find opening brace
|
||||||
|
while (p < end && *p != '{') ++p;
|
||||||
|
if (p >= end) break;
|
||||||
|
const char* brace_open = p++;
|
||||||
|
|
||||||
|
// Find closing brace
|
||||||
|
const char* brace_close = brace_open;
|
||||||
|
while (brace_close < end && *brace_close != '}') ++brace_close;
|
||||||
|
|
||||||
|
// Search for "color:" inside the braces (not "background-color:" etc.)
|
||||||
|
int block_len = (int)(brace_close - brace_open);
|
||||||
|
const char* cp = svg_strstr(brace_open, block_len, "color:");
|
||||||
|
if (cp) {
|
||||||
|
// Make sure it's not prefixed (e.g. "background-color:")
|
||||||
|
if (cp > brace_open && *(cp - 1) != '-') {
|
||||||
|
cp += 6;
|
||||||
|
while (cp < brace_close && (*cp == ' ' || *cp == '\t')) ++cp;
|
||||||
|
if (cp < brace_close && *cp == '#') {
|
||||||
|
Color c = svg_parse_hex_color(cp);
|
||||||
|
css->add(name, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p = brace_close + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Resolve a fill value that might be url(#id) against gradient table
|
// Resolve a fill value that might be url(#id) against gradient table
|
||||||
// Returns: 1 = resolved, 0 = not a url() ref, -1 = fill="none"
|
// Returns: 1 = resolved, 0 = not a url() ref, -1 = fill="none"
|
||||||
@@ -840,11 +932,29 @@ inline int svg_resolve_fill_value(const char* val, Color* out_color, const SvgGr
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Per-element fill color extraction
|
// Per-element fill color extraction
|
||||||
// Returns: 1 = color found in out_color, 0 = use default, -1 = fill="none"
|
// Returns: 1 = color found in out_color, 0 = use default, -1 = fill="none"
|
||||||
|
// When fill:currentColor is encountered and css != nullptr, the element's
|
||||||
|
// class= attribute is looked up to resolve the actual color.
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
inline int svg_get_element_fill(const char* elem, int elemLen, Color* out_color,
|
inline int svg_get_element_fill(const char* elem, int elemLen, Color* out_color,
|
||||||
const SvgGradientTable* grads = nullptr) {
|
const SvgGradientTable* grads = nullptr,
|
||||||
|
const SvgCssTable* css = nullptr) {
|
||||||
char buf[128];
|
char buf[128];
|
||||||
|
|
||||||
|
// Helper: try to resolve currentColor via the element's class= attribute.
|
||||||
|
// ColorScheme-Text is the "foreground" color — callers control it via fill_color,
|
||||||
|
// so we skip it here and let the fallback fill_color apply.
|
||||||
|
// Accent classes (Highlight, NeutralText, etc.) use their CSS-defined colors.
|
||||||
|
auto resolve_current_color = [&]() -> int {
|
||||||
|
if (!css || css->count == 0) return 0; // fall back to fill_color
|
||||||
|
char cls[64];
|
||||||
|
int cLen = svg_get_attr(elem, elemLen, " class", cls, sizeof(cls));
|
||||||
|
if (cLen <= 0) return 0;
|
||||||
|
if (svg_strncmp(cls, "ColorScheme-Text", 16)) return 0; // use fill_color
|
||||||
|
Color c;
|
||||||
|
if (css->lookup(cls, &c)) { *out_color = c; return 1; }
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
// Check style="..." first (higher CSS priority)
|
// Check style="..." first (higher CSS priority)
|
||||||
int sLen = svg_get_attr(elem, elemLen, " style", buf, sizeof(buf));
|
int sLen = svg_get_attr(elem, elemLen, " style", buf, sizeof(buf));
|
||||||
if (sLen > 0) {
|
if (sLen > 0) {
|
||||||
@@ -857,6 +967,9 @@ inline int svg_get_element_fill(const char* elem, int elemLen, Color* out_color,
|
|||||||
} else {
|
} else {
|
||||||
fp += 5;
|
fp += 5;
|
||||||
while (*fp == ' ') ++fp;
|
while (*fp == ' ') ++fp;
|
||||||
|
// Check for currentColor before calling resolve_fill_value
|
||||||
|
if (svg_strncmp(fp, "currentColor", 12))
|
||||||
|
return resolve_current_color();
|
||||||
return svg_resolve_fill_value(fp, out_color, grads);
|
return svg_resolve_fill_value(fp, out_color, grads);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -865,6 +978,8 @@ inline int svg_get_element_fill(const char* elem, int elemLen, Color* out_color,
|
|||||||
// Check fill="..." attribute
|
// Check fill="..." attribute
|
||||||
int fLen = svg_get_attr(elem, elemLen, " fill", buf, sizeof(buf));
|
int fLen = svg_get_attr(elem, elemLen, " fill", buf, sizeof(buf));
|
||||||
if (fLen > 0) {
|
if (fLen > 0) {
|
||||||
|
if (svg_strncmp(buf, "currentColor", 12))
|
||||||
|
return resolve_current_color();
|
||||||
return svg_resolve_fill_value(buf, out_color, grads);
|
return svg_resolve_fill_value(buf, out_color, grads);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1070,6 +1185,21 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-parse CSS class color mappings from <style> block
|
||||||
|
SvgCssTable css;
|
||||||
|
css.clear();
|
||||||
|
{
|
||||||
|
const char* sp = svg_strstr(svg_data, svg_len, "<style");
|
||||||
|
if (sp) {
|
||||||
|
// Advance past the opening tag to the content
|
||||||
|
while (sp < svg_data + svg_len && *sp != '>') ++sp;
|
||||||
|
if (sp < svg_data + svg_len) ++sp; // skip '>'
|
||||||
|
const char* se = svg_strstr(sp, (int)(svg_data + svg_len - sp), "</style>");
|
||||||
|
if (se)
|
||||||
|
svg_parse_css_table(sp, (int)(se - sp), &css);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Shared edge list (cleared per element for multi-color support)
|
// Shared edge list (cleared per element for multi-color support)
|
||||||
SvgEdgeList el;
|
SvgEdgeList el;
|
||||||
el.init(SVG_MAX_EDGES);
|
el.init(SVG_MAX_EDGES);
|
||||||
@@ -1113,7 +1243,7 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
|
|||||||
|
|
||||||
// Determine fill color for this element
|
// Determine fill color for this element
|
||||||
Color elem_color = fill_color;
|
Color elem_color = fill_color;
|
||||||
int fillResult = svg_get_element_fill(elem_start, elem_len, &elem_color, &grads);
|
int fillResult = svg_get_element_fill(elem_start, elem_len, &elem_color, &grads, &css);
|
||||||
if (fillResult == -1) { p = elem_end; continue; } // fill="none"
|
if (fillResult == -1) { p = elem_end; continue; } // fill="none"
|
||||||
|
|
||||||
int alpha = svg_get_element_opacity(elem_start, elem_len);
|
int alpha = svg_get_element_opacity(elem_start, elem_len);
|
||||||
@@ -1146,7 +1276,7 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
|
|||||||
}
|
}
|
||||||
|
|
||||||
Color elem_color = fill_color;
|
Color elem_color = fill_color;
|
||||||
int fillResult = svg_get_element_fill(elem_start, elem_len, &elem_color, &grads);
|
int fillResult = svg_get_element_fill(elem_start, elem_len, &elem_color, &grads, &css);
|
||||||
if (fillResult == -1) { p = elem_end; continue; }
|
if (fillResult == -1) { p = elem_end; continue; }
|
||||||
|
|
||||||
int alpha = svg_get_element_opacity(elem_start, elem_len);
|
int alpha = svg_get_element_opacity(elem_start, elem_len);
|
||||||
@@ -1189,7 +1319,7 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
|
|||||||
}
|
}
|
||||||
|
|
||||||
Color elem_color = fill_color;
|
Color elem_color = fill_color;
|
||||||
int fillResult = svg_get_element_fill(elem_start, elem_len, &elem_color, &grads);
|
int fillResult = svg_get_element_fill(elem_start, elem_len, &elem_color, &grads, &css);
|
||||||
if (fillResult == -1) { p = elem_end; continue; }
|
if (fillResult == -1) { p = elem_end; continue; }
|
||||||
|
|
||||||
int alpha = svg_get_element_opacity(elem_start, elem_len);
|
int alpha = svg_get_element_opacity(elem_start, elem_len);
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ struct CachedGlyph {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct GlyphCache {
|
struct GlyphCache {
|
||||||
CachedGlyph glyphs[128];
|
CachedGlyph glyphs[256]; // Latin-1 Supplement (U+0080..U+00FF) included
|
||||||
int pixel_size;
|
int pixel_size;
|
||||||
float scale;
|
float scale;
|
||||||
int ascent, descent, line_gap;
|
int ascent, descent, line_gap;
|
||||||
@@ -127,7 +127,7 @@ struct TrueTypeFont {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CachedGlyph* get_glyph(GlyphCache* gc, int codepoint) {
|
CachedGlyph* get_glyph(GlyphCache* gc, int codepoint) {
|
||||||
if (codepoint < 0 || codepoint >= 128) return nullptr;
|
if (codepoint < 0 || codepoint >= 256) return nullptr;
|
||||||
CachedGlyph* g = &gc->glyphs[codepoint];
|
CachedGlyph* g = &gc->glyphs[codepoint];
|
||||||
if (g->loaded) return g;
|
if (g->loaded) return g;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* tls.hpp
|
||||||
|
* Shared TLS helper library for ZenithOS
|
||||||
|
* Trust anchor loading, BearSSL time, TLS I/O, HTTPS fetch
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <bearssl.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace tls {
|
||||||
|
|
||||||
|
struct TrustAnchors {
|
||||||
|
br_x509_trust_anchor* anchors;
|
||||||
|
size_t count;
|
||||||
|
size_t capacity;
|
||||||
|
};
|
||||||
|
|
||||||
|
TrustAnchors load_trust_anchors();
|
||||||
|
void get_bearssl_time(uint32_t* days, uint32_t* seconds);
|
||||||
|
int tls_send_all(int fd, const unsigned char* data, size_t len);
|
||||||
|
int tls_recv_some(int fd, unsigned char* buf, size_t maxlen);
|
||||||
|
|
||||||
|
// Optional abort callback (for Ctrl+Q in terminal apps). nullptr = no abort.
|
||||||
|
using AbortCheckFn = bool (*)();
|
||||||
|
|
||||||
|
int tls_exchange(int fd, br_ssl_engine_context* eng,
|
||||||
|
const char* request, int reqLen,
|
||||||
|
char* respBuf, int respMax,
|
||||||
|
AbortCheckFn abort_check = nullptr);
|
||||||
|
|
||||||
|
// High-level: socket -> TLS setup -> exchange -> cleanup, all in one call.
|
||||||
|
int https_fetch(const char* host, uint32_t ip, uint16_t port,
|
||||||
|
const char* request, int reqLen,
|
||||||
|
const TrustAnchors& tas,
|
||||||
|
char* respBuf, int respMax,
|
||||||
|
AbortCheckFn abort_check = nullptr);
|
||||||
|
|
||||||
|
} // namespace tls
|
||||||
@@ -320,5 +320,11 @@ namespace zenith {
|
|||||||
inline uint64_t win_resize(int id, int w, int h) {
|
inline uint64_t win_resize(int id, int w, int h) {
|
||||||
return (uint64_t)syscall3(Zenith::SYS_WINRESIZE, (uint64_t)id, (uint64_t)w, (uint64_t)h);
|
return (uint64_t)syscall3(Zenith::SYS_WINRESIZE, (uint64_t)id, (uint64_t)w, (uint64_t)h);
|
||||||
}
|
}
|
||||||
|
inline int win_setscale(int scale) {
|
||||||
|
return (int)syscall1(Zenith::SYS_WINSETSCALE, (uint64_t)scale);
|
||||||
|
}
|
||||||
|
inline int win_getscale() {
|
||||||
|
return (int)syscall0(Zenith::SYS_WINGETSCALE);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# Makefile for shared TLS helper library on ZenithOS
|
||||||
|
# Copyright (c) 2026 Daniel Hammer
|
||||||
|
|
||||||
|
MAKEFLAGS += -rR
|
||||||
|
.SUFFIXES:
|
||||||
|
|
||||||
|
# ---- Toolchain ----
|
||||||
|
|
||||||
|
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
|
||||||
|
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
|
||||||
|
CXX := $(TOOLCHAIN_PREFIX)g++
|
||||||
|
AR := $(TOOLCHAIN_PREFIX)ar
|
||||||
|
else
|
||||||
|
CXX := g++
|
||||||
|
AR := ar
|
||||||
|
endif
|
||||||
|
|
||||||
|
# ---- Paths ----
|
||||||
|
|
||||||
|
BEARSSL := ../bearssl
|
||||||
|
LIBC_INC := ../../include/libc
|
||||||
|
PROG_INC := ../../include
|
||||||
|
OBJDIR := obj
|
||||||
|
|
||||||
|
# ---- Compiler flags ----
|
||||||
|
|
||||||
|
CXXFLAGS := \
|
||||||
|
-std=gnu++20 \
|
||||||
|
-g -O2 -pipe \
|
||||||
|
-Wall \
|
||||||
|
-Wextra \
|
||||||
|
-Wno-unused-parameter \
|
||||||
|
-nostdinc \
|
||||||
|
-ffreestanding \
|
||||||
|
-fno-stack-protector \
|
||||||
|
-fno-stack-check \
|
||||||
|
-fno-PIC \
|
||||||
|
-fno-rtti \
|
||||||
|
-fno-exceptions \
|
||||||
|
-ffunction-sections \
|
||||||
|
-fdata-sections \
|
||||||
|
-m64 \
|
||||||
|
-march=x86-64 \
|
||||||
|
-mno-80387 \
|
||||||
|
-mno-mmx \
|
||||||
|
-mno-sse \
|
||||||
|
-mno-sse2 \
|
||||||
|
-mno-red-zone \
|
||||||
|
-mcmodel=small \
|
||||||
|
-I $(PROG_INC) \
|
||||||
|
-isystem $(LIBC_INC) \
|
||||||
|
-I $(BEARSSL)/inc \
|
||||||
|
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
|
||||||
|
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include
|
||||||
|
|
||||||
|
# ---- Target ----
|
||||||
|
|
||||||
|
TARGET := libtls.a
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(OBJDIR)/tls.o
|
||||||
|
$(AR) rcs $@ $^
|
||||||
|
|
||||||
|
$(OBJDIR)/tls.o: tls.cpp Makefile
|
||||||
|
@mkdir -p $(OBJDIR)
|
||||||
|
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(OBJDIR) $(TARGET)
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,279 @@
|
|||||||
|
/*
|
||||||
|
* tls.cpp
|
||||||
|
* Shared TLS helper library for ZenithOS
|
||||||
|
* Extracted from fetch, wiki, wikipedia, and weather apps
|
||||||
|
* Copyright (c) 2025-2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <tls/tls.hpp>
|
||||||
|
#include <zenith/syscall.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// File-local: PEM/DER certificate parsing helpers
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct DerAccum { unsigned char* data; size_t len, cap; };
|
||||||
|
struct DnAccum { unsigned char* data; size_t len, cap; };
|
||||||
|
|
||||||
|
void der_append(void* ctx, const void* buf, size_t len) {
|
||||||
|
DerAccum* a = (DerAccum*)ctx;
|
||||||
|
if (a->len + len > a->cap) {
|
||||||
|
size_t nc = a->cap * 2;
|
||||||
|
if (nc < a->len + len) nc = a->len + len + 4096;
|
||||||
|
unsigned char* nb = (unsigned char*)malloc(nc);
|
||||||
|
if (!nb) return;
|
||||||
|
if (a->data) { memcpy(nb, a->data, a->len); free(a->data); }
|
||||||
|
a->data = nb; a->cap = nc;
|
||||||
|
}
|
||||||
|
memcpy(a->data + a->len, buf, len);
|
||||||
|
a->len += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dn_append(void* ctx, const void* buf, size_t len) {
|
||||||
|
DnAccum* a = (DnAccum*)ctx;
|
||||||
|
if (a->len + len > a->cap) {
|
||||||
|
size_t nc = a->cap * 2;
|
||||||
|
if (nc < a->len + len) nc = a->len + len + 256;
|
||||||
|
unsigned char* nb = (unsigned char*)malloc(nc);
|
||||||
|
if (!nb) return;
|
||||||
|
if (a->data) { memcpy(nb, a->data, a->len); free(a->data); }
|
||||||
|
a->data = nb; a->cap = nc;
|
||||||
|
}
|
||||||
|
memcpy(a->data + a->len, buf, len);
|
||||||
|
a->len += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ta_add(tls::TrustAnchors* tas, const br_x509_trust_anchor* ta) {
|
||||||
|
if (tas->count >= tas->capacity) {
|
||||||
|
size_t nc = tas->capacity == 0 ? 64 : tas->capacity * 2;
|
||||||
|
br_x509_trust_anchor* na = (br_x509_trust_anchor*)malloc(nc * sizeof(*na));
|
||||||
|
if (!na) return;
|
||||||
|
if (tas->anchors) { memcpy(na, tas->anchors, tas->count * sizeof(*na)); free(tas->anchors); }
|
||||||
|
tas->anchors = na; tas->capacity = nc;
|
||||||
|
}
|
||||||
|
tas->anchors[tas->count++] = *ta;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool process_cert_der(tls::TrustAnchors* tas, const unsigned char* der, size_t der_len) {
|
||||||
|
static br_x509_decoder_context dc; // ~2KB+, keep off stack
|
||||||
|
DnAccum dn = {nullptr, 0, 0};
|
||||||
|
br_x509_decoder_init(&dc, dn_append, &dn);
|
||||||
|
br_x509_decoder_push(&dc, der, der_len);
|
||||||
|
br_x509_pkey* pk = br_x509_decoder_get_pkey(&dc);
|
||||||
|
if (!pk) { if (dn.data) free(dn.data); return false; }
|
||||||
|
|
||||||
|
br_x509_trust_anchor ta;
|
||||||
|
memset(&ta, 0, sizeof(ta));
|
||||||
|
ta.dn.data = dn.data; ta.dn.len = dn.len; ta.flags = 0;
|
||||||
|
if (br_x509_decoder_isCA(&dc)) ta.flags |= BR_X509_TA_CA;
|
||||||
|
|
||||||
|
switch (pk->key_type) {
|
||||||
|
case BR_KEYTYPE_RSA:
|
||||||
|
ta.pkey.key_type = BR_KEYTYPE_RSA;
|
||||||
|
ta.pkey.key.rsa.nlen = pk->key.rsa.nlen;
|
||||||
|
ta.pkey.key.rsa.n = (unsigned char*)malloc(pk->key.rsa.nlen);
|
||||||
|
if (ta.pkey.key.rsa.n) memcpy(ta.pkey.key.rsa.n, pk->key.rsa.n, pk->key.rsa.nlen);
|
||||||
|
ta.pkey.key.rsa.elen = pk->key.rsa.elen;
|
||||||
|
ta.pkey.key.rsa.e = (unsigned char*)malloc(pk->key.rsa.elen);
|
||||||
|
if (ta.pkey.key.rsa.e) memcpy(ta.pkey.key.rsa.e, pk->key.rsa.e, pk->key.rsa.elen);
|
||||||
|
break;
|
||||||
|
case BR_KEYTYPE_EC:
|
||||||
|
ta.pkey.key_type = BR_KEYTYPE_EC;
|
||||||
|
ta.pkey.key.ec.curve = pk->key.ec.curve;
|
||||||
|
ta.pkey.key.ec.qlen = pk->key.ec.qlen;
|
||||||
|
ta.pkey.key.ec.q = (unsigned char*)malloc(pk->key.ec.qlen);
|
||||||
|
if (ta.pkey.key.ec.q) memcpy(ta.pkey.key.ec.q, pk->key.ec.q, pk->key.ec.qlen);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (dn.data) free(dn.data);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ta_add(tas, &ta);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Exported functions
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
namespace tls {
|
||||||
|
|
||||||
|
TrustAnchors load_trust_anchors() {
|
||||||
|
TrustAnchors tas = {nullptr, 0, 0};
|
||||||
|
int fh = zenith::open("0:/etc/ca-certificates.crt");
|
||||||
|
if (fh < 0) return tas;
|
||||||
|
uint64_t fsize = zenith::getsize(fh);
|
||||||
|
if (fsize == 0 || fsize > 512 * 1024) { zenith::close(fh); return tas; }
|
||||||
|
|
||||||
|
unsigned char* pem = (unsigned char*)malloc(fsize + 1);
|
||||||
|
if (!pem) { zenith::close(fh); return tas; }
|
||||||
|
zenith::read(fh, pem, 0, fsize);
|
||||||
|
zenith::close(fh);
|
||||||
|
pem[fsize] = 0;
|
||||||
|
|
||||||
|
static br_pem_decoder_context pc; // keep off stack
|
||||||
|
br_pem_decoder_init(&pc);
|
||||||
|
DerAccum der = {nullptr, 0, 0};
|
||||||
|
bool inCert = false;
|
||||||
|
size_t offset = 0;
|
||||||
|
|
||||||
|
while (offset < fsize) {
|
||||||
|
size_t pushed = br_pem_decoder_push(&pc, pem + offset, fsize - offset);
|
||||||
|
offset += pushed;
|
||||||
|
int ev = br_pem_decoder_event(&pc);
|
||||||
|
if (ev == BR_PEM_BEGIN_OBJ) {
|
||||||
|
inCert = (strcmp(br_pem_decoder_name(&pc), "CERTIFICATE") == 0);
|
||||||
|
br_pem_decoder_setdest(&pc, inCert ? der_append : nullptr, inCert ? &der : nullptr);
|
||||||
|
if (inCert) der.len = 0;
|
||||||
|
} else if (ev == BR_PEM_END_OBJ) {
|
||||||
|
if (inCert && der.len > 0) process_cert_der(&tas, der.data, der.len);
|
||||||
|
inCert = false;
|
||||||
|
} else if (ev == BR_PEM_ERROR) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (der.data) free(der.data);
|
||||||
|
free(pem);
|
||||||
|
return tas;
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_bearssl_time(uint32_t* days, uint32_t* seconds) {
|
||||||
|
Zenith::DateTime dt;
|
||||||
|
zenith::gettime(&dt);
|
||||||
|
int y = dt.Year, m = dt.Month, d = dt.Day;
|
||||||
|
uint32_t total = 365u * (uint32_t)y
|
||||||
|
+ (uint32_t)(y/4) - (uint32_t)(y/100) + (uint32_t)(y/400);
|
||||||
|
const int md[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
|
||||||
|
for (int mo = 1; mo < m && mo <= 12; mo++) total += md[mo];
|
||||||
|
if (y%4==0 && (y%100!=0 || y%400==0) && m > 2) total++;
|
||||||
|
total += d - 1;
|
||||||
|
*days = total;
|
||||||
|
*seconds = (uint32_t)(dt.Hour*3600 + dt.Minute*60 + dt.Second);
|
||||||
|
}
|
||||||
|
|
||||||
|
int tls_send_all(int fd, const unsigned char* data, size_t len) {
|
||||||
|
size_t sent = 0;
|
||||||
|
uint64_t deadline = zenith::get_milliseconds() + 15000;
|
||||||
|
while (sent < len) {
|
||||||
|
int r = zenith::send(fd, data + sent, (uint32_t)(len - sent));
|
||||||
|
if (r > 0) { sent += r; deadline = zenith::get_milliseconds() + 15000; }
|
||||||
|
else if (r < 0) return -1;
|
||||||
|
else { if (zenith::get_milliseconds() >= deadline) return -1; zenith::sleep_ms(1); }
|
||||||
|
}
|
||||||
|
return (int)sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tls_recv_some(int fd, unsigned char* buf, size_t maxlen) {
|
||||||
|
uint64_t deadline = zenith::get_milliseconds() + 15000;
|
||||||
|
while (true) {
|
||||||
|
int r = zenith::recv(fd, buf, (uint32_t)maxlen);
|
||||||
|
if (r > 0) return r;
|
||||||
|
if (r < 0) return -1;
|
||||||
|
if (zenith::get_milliseconds() >= deadline) return -1;
|
||||||
|
zenith::sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int tls_exchange(int fd, br_ssl_engine_context* eng,
|
||||||
|
const char* request, int reqLen,
|
||||||
|
char* respBuf, int respMax,
|
||||||
|
AbortCheckFn abort_check) {
|
||||||
|
bool requestSent = false;
|
||||||
|
int respLen = 0;
|
||||||
|
uint64_t deadline = zenith::get_milliseconds() + 30000;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
unsigned state = br_ssl_engine_current_state(eng);
|
||||||
|
if (state & BR_SSL_CLOSED) {
|
||||||
|
int err = br_ssl_engine_last_error(eng);
|
||||||
|
if (err != BR_ERR_OK && err != BR_ERR_IO && respLen == 0) return -1;
|
||||||
|
return respLen;
|
||||||
|
}
|
||||||
|
if (abort_check && abort_check()) {
|
||||||
|
br_ssl_engine_close(eng);
|
||||||
|
return respLen > 0 ? respLen : -1;
|
||||||
|
}
|
||||||
|
if (state & BR_SSL_SENDREC) {
|
||||||
|
size_t len; unsigned char* buf = br_ssl_engine_sendrec_buf(eng, &len);
|
||||||
|
int sent = tls_send_all(fd, buf, len);
|
||||||
|
if (sent < 0) { br_ssl_engine_close(eng); return respLen > 0 ? respLen : -1; }
|
||||||
|
br_ssl_engine_sendrec_ack(eng, len);
|
||||||
|
deadline = zenith::get_milliseconds() + 30000; continue;
|
||||||
|
}
|
||||||
|
if (state & BR_SSL_RECVAPP) {
|
||||||
|
size_t len; unsigned char* buf = br_ssl_engine_recvapp_buf(eng, &len);
|
||||||
|
size_t toCopy = len;
|
||||||
|
if (respLen + (int)toCopy > respMax - 1) toCopy = respMax - 1 - respLen;
|
||||||
|
if (toCopy > 0) { memcpy(respBuf + respLen, buf, toCopy); respLen += toCopy; }
|
||||||
|
br_ssl_engine_recvapp_ack(eng, len);
|
||||||
|
deadline = zenith::get_milliseconds() + 30000; continue;
|
||||||
|
}
|
||||||
|
if ((state & BR_SSL_SENDAPP) && !requestSent) {
|
||||||
|
size_t len; unsigned char* buf = br_ssl_engine_sendapp_buf(eng, &len);
|
||||||
|
size_t toWrite = (size_t)reqLen;
|
||||||
|
if (toWrite > len) toWrite = len;
|
||||||
|
memcpy(buf, request, toWrite);
|
||||||
|
br_ssl_engine_sendapp_ack(eng, toWrite);
|
||||||
|
br_ssl_engine_flush(eng, 0);
|
||||||
|
requestSent = true;
|
||||||
|
deadline = zenith::get_milliseconds() + 30000; continue;
|
||||||
|
}
|
||||||
|
if (state & BR_SSL_RECVREC) {
|
||||||
|
size_t len; unsigned char* buf = br_ssl_engine_recvrec_buf(eng, &len);
|
||||||
|
int got = tls_recv_some(fd, buf, len);
|
||||||
|
if (got < 0) { br_ssl_engine_close(eng); return respLen > 0 ? respLen : -1; }
|
||||||
|
br_ssl_engine_recvrec_ack(eng, got);
|
||||||
|
deadline = zenith::get_milliseconds() + 30000; continue;
|
||||||
|
}
|
||||||
|
if (zenith::get_milliseconds() >= deadline) return respLen > 0 ? respLen : -1;
|
||||||
|
zenith::sleep_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int https_fetch(const char* host, uint32_t ip, uint16_t port,
|
||||||
|
const char* request, int reqLen,
|
||||||
|
const TrustAnchors& tas,
|
||||||
|
char* respBuf, int respMax,
|
||||||
|
AbortCheckFn abort_check) {
|
||||||
|
int fd = zenith::socket(Zenith::SOCK_TCP);
|
||||||
|
if (fd < 0) return -1;
|
||||||
|
if (zenith::connect(fd, ip, port) < 0) { zenith::closesocket(fd); return -1; }
|
||||||
|
|
||||||
|
br_ssl_client_context* cc = (br_ssl_client_context*)malloc(sizeof(*cc));
|
||||||
|
br_x509_minimal_context* xc = (br_x509_minimal_context*)malloc(sizeof(*xc));
|
||||||
|
void* iobuf = malloc(BR_SSL_BUFSIZE_BIDI);
|
||||||
|
if (!cc || !xc || !iobuf) {
|
||||||
|
free(cc); free(xc); free(iobuf);
|
||||||
|
zenith::closesocket(fd); return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
br_ssl_client_init_full(cc, xc, tas.anchors, tas.count);
|
||||||
|
uint32_t days, secs;
|
||||||
|
get_bearssl_time(&days, &secs);
|
||||||
|
br_x509_minimal_set_time(xc, days, secs);
|
||||||
|
|
||||||
|
unsigned char seed[32];
|
||||||
|
zenith::getrandom(seed, sizeof(seed));
|
||||||
|
br_ssl_engine_set_buffer(&cc->eng, iobuf, BR_SSL_BUFSIZE_BIDI, 1);
|
||||||
|
br_ssl_engine_inject_entropy(&cc->eng, seed, sizeof(seed));
|
||||||
|
|
||||||
|
if (!br_ssl_client_reset(cc, host, 0)) {
|
||||||
|
zenith::closesocket(fd); free(cc); free(xc); free(iobuf); return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int respLen = tls_exchange(fd, &cc->eng, request, reqLen, respBuf, respMax, abort_check);
|
||||||
|
zenith::closesocket(fd);
|
||||||
|
free(cc); free(xc); free(iobuf);
|
||||||
|
return respLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace tls
|
||||||
@@ -61,7 +61,7 @@ LDFLAGS := \
|
|||||||
|
|
||||||
# ---- C++ source files ----
|
# ---- C++ source files ----
|
||||||
|
|
||||||
SRCS := main.cpp font_data.cpp stb_truetype_impl.cpp app_terminal.cpp app_filemanager.cpp app_sysinfo.cpp app_calculator.cpp app_texteditor.cpp app_klog.cpp app_wiki.cpp app_procmgr.cpp app_mandelbrot.cpp app_devexplorer.cpp app_settings.cpp app_doom.cpp
|
SRCS := main.cpp font_data.cpp stb_truetype_impl.cpp app_terminal.cpp app_filemanager.cpp app_sysinfo.cpp app_calculator.cpp app_texteditor.cpp app_klog.cpp app_wiki.cpp app_weather.cpp app_procmgr.cpp app_mandelbrot.cpp app_devexplorer.cpp app_settings.cpp app_doom.cpp
|
||||||
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||||
|
|
||||||
# ---- Target ----
|
# ---- Target ----
|
||||||
|
|||||||
@@ -458,18 +458,21 @@ static void settings_on_mouse(Window* win, MouseEvent& ev) {
|
|||||||
if (mx >= bx && mx < bx + sbw && cy >= y && cy < y + btn_h) {
|
if (mx >= bx && mx < bx + sbw && cy >= y && cy < y + btn_h) {
|
||||||
s.ui_scale = 0;
|
s.ui_scale = 0;
|
||||||
apply_ui_scale(0);
|
apply_ui_scale(0);
|
||||||
|
zenith::win_setscale(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Default
|
// Default
|
||||||
if (mx >= bx + sbw + 8 && mx < bx + sbw * 2 + 8 && cy >= y && cy < y + btn_h) {
|
if (mx >= bx + sbw + 8 && mx < bx + sbw * 2 + 8 && cy >= y && cy < y + btn_h) {
|
||||||
s.ui_scale = 1;
|
s.ui_scale = 1;
|
||||||
apply_ui_scale(1);
|
apply_ui_scale(1);
|
||||||
|
zenith::win_setscale(1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Large
|
// Large
|
||||||
if (mx >= bx + (sbw + 8) * 2 && mx < bx + sbw * 3 + 16 && cy >= y && cy < y + btn_h) {
|
if (mx >= bx + (sbw + 8) * 2 && mx < bx + sbw * 3 + 16 && cy >= y && cy < y + btn_h) {
|
||||||
s.ui_scale = 2;
|
s.ui_scale = 2;
|
||||||
apply_ui_scale(2);
|
apply_ui_scale(2);
|
||||||
|
zenith::win_setscale(2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* app_weather.cpp
|
||||||
|
* ZenithOS Desktop - Weather app launcher
|
||||||
|
* Spawns weather.elf as a standalone Window Server process
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "apps_common.hpp"
|
||||||
|
|
||||||
|
void open_weather(DesktopState* ds) {
|
||||||
|
(void)ds;
|
||||||
|
zenith::spawn("0:/os/weather.elf");
|
||||||
|
}
|
||||||
@@ -167,6 +167,7 @@ void open_texteditor(DesktopState* ds);
|
|||||||
void open_texteditor_with_file(DesktopState* ds, const char* path);
|
void open_texteditor_with_file(DesktopState* ds, const char* path);
|
||||||
void open_klog(DesktopState* ds);
|
void open_klog(DesktopState* ds);
|
||||||
void open_wiki(DesktopState* ds);
|
void open_wiki(DesktopState* ds);
|
||||||
|
void open_weather(DesktopState* ds);
|
||||||
void open_procmgr(DesktopState* ds);
|
void open_procmgr(DesktopState* ds);
|
||||||
void open_mandelbrot(DesktopState* ds);
|
void open_mandelbrot(DesktopState* ds);
|
||||||
void open_devexplorer(DesktopState* ds);
|
void open_devexplorer(DesktopState* ds);
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ void gui::desktop_init(DesktopState* ds) {
|
|||||||
ds->icon_settings = svg_load("0:/icons/help-about.svg", 20, 20, defColor);
|
ds->icon_settings = svg_load("0:/icons/help-about.svg", 20, 20, defColor);
|
||||||
ds->icon_reboot = svg_load("0:/icons/system-reboot.svg", 20, 20, defColor);
|
ds->icon_reboot = svg_load("0:/icons/system-reboot.svg", 20, 20, defColor);
|
||||||
|
|
||||||
|
ds->icon_weather = svg_load("0:/icons/weather-widget.svg", 20, 20, defColor);
|
||||||
|
|
||||||
ds->icon_doom = svg_load("0:/icons/doom.svg", 20, 20, defColor);
|
ds->icon_doom = svg_load("0:/icons/doom.svg", 20, 20, defColor);
|
||||||
ds->icon_procmgr = svg_load("0:/icons/system-monitor.svg", 20, 20, defColor);
|
ds->icon_procmgr = svg_load("0:/icons/system-monitor.svg", 20, 20, defColor);
|
||||||
ds->icon_mandelbrot = svg_load("0:/icons/applications-science.svg", 20, 20, defColor);
|
ds->icon_mandelbrot = svg_load("0:/icons/applications-science.svg", 20, 20, defColor);
|
||||||
@@ -76,6 +78,7 @@ void gui::desktop_init(DesktopState* ds) {
|
|||||||
ds->settings.show_shadows = true;
|
ds->settings.show_shadows = true;
|
||||||
ds->settings.clock_24h = true;
|
ds->settings.clock_24h = true;
|
||||||
ds->settings.ui_scale = 1;
|
ds->settings.ui_scale = 1;
|
||||||
|
zenith::win_setscale(1);
|
||||||
|
|
||||||
ds->ctx_menu_open = false;
|
ds->ctx_menu_open = false;
|
||||||
ds->ctx_menu_x = 0;
|
ds->ctx_menu_x = 0;
|
||||||
@@ -421,7 +424,7 @@ struct MenuRow {
|
|||||||
int app_id; // -1 for category headers / dividers
|
int app_id; // -1 for category headers / dividers
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr int MENU_ROW_COUNT = 18;
|
static constexpr int MENU_ROW_COUNT = 19;
|
||||||
static const MenuRow menu_rows[MENU_ROW_COUNT] = {
|
static const MenuRow menu_rows[MENU_ROW_COUNT] = {
|
||||||
{ true, "Applications", -1 },
|
{ true, "Applications", -1 },
|
||||||
{ false, "Terminal", 0 },
|
{ false, "Terminal", 0 },
|
||||||
@@ -430,6 +433,7 @@ static const MenuRow menu_rows[MENU_ROW_COUNT] = {
|
|||||||
{ false, "Calculator", 3 },
|
{ false, "Calculator", 3 },
|
||||||
{ true, "Internet", -1 },
|
{ true, "Internet", -1 },
|
||||||
{ false, "Wikipedia", 9 },
|
{ false, "Wikipedia", 9 },
|
||||||
|
{ false, "Weather", 13 },
|
||||||
{ true, "System", -1 },
|
{ true, "System", -1 },
|
||||||
{ false, "System Info", 2 },
|
{ false, "System Info", 2 },
|
||||||
{ false, "Kernel Log", 5 },
|
{ false, "Kernel Log", 5 },
|
||||||
@@ -470,7 +474,7 @@ static void desktop_draw_app_menu(DesktopState* ds) {
|
|||||||
draw_rect(fb, menu_x, menu_y, MENU_W, menu_h, colors::BORDER);
|
draw_rect(fb, menu_x, menu_y, MENU_W, menu_h, colors::BORDER);
|
||||||
|
|
||||||
// Icon lookup by app_id
|
// Icon lookup by app_id
|
||||||
SvgIcon* icons[13] = {
|
SvgIcon* icons[14] = {
|
||||||
&ds->icon_terminal, // 0
|
&ds->icon_terminal, // 0
|
||||||
&ds->icon_filemanager, // 1
|
&ds->icon_filemanager, // 1
|
||||||
&ds->icon_sysinfo, // 2
|
&ds->icon_sysinfo, // 2
|
||||||
@@ -484,6 +488,7 @@ static void desktop_draw_app_menu(DesktopState* ds) {
|
|||||||
&ds->icon_doom, // 10
|
&ds->icon_doom, // 10
|
||||||
&ds->icon_settings, // 11
|
&ds->icon_settings, // 11
|
||||||
&ds->icon_reboot, // 12
|
&ds->icon_reboot, // 12
|
||||||
|
&ds->icon_weather, // 13
|
||||||
};
|
};
|
||||||
|
|
||||||
int mx = ds->mouse.x;
|
int mx = ds->mouse.x;
|
||||||
@@ -519,7 +524,7 @@ static void desktop_draw_app_menu(DesktopState* ds) {
|
|||||||
// Icon
|
// Icon
|
||||||
int icon_x = item_rect.x + 8;
|
int icon_x = item_rect.x + 8;
|
||||||
int icon_y = item_rect.y + (row_h - 20) / 2;
|
int icon_y = item_rect.y + (row_h - 20) / 2;
|
||||||
if (row.app_id >= 0 && row.app_id < 13) {
|
if (row.app_id >= 0 && row.app_id < 14) {
|
||||||
SvgIcon* icon = icons[row.app_id];
|
SvgIcon* icon = icons[row.app_id];
|
||||||
if (icon && icon->pixels) {
|
if (icon && icon->pixels) {
|
||||||
fb.blit_alpha(icon_x, icon_y, icon->width, icon->height, icon->pixels);
|
fb.blit_alpha(icon_x, icon_y, icon->width, icon->height, icon->pixels);
|
||||||
@@ -1099,6 +1104,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
|||||||
case 10: open_doom(ds); break;
|
case 10: open_doom(ds); break;
|
||||||
case 11: open_settings(ds); break;
|
case 11: open_settings(ds); break;
|
||||||
case 12: open_reboot_dialog(ds); break;
|
case 12: open_reboot_dialog(ds); break;
|
||||||
|
case 13: open_weather(ds); break;
|
||||||
}
|
}
|
||||||
ds->app_menu_open = false;
|
ds->app_menu_open = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ endif
|
|||||||
# ---- Paths ----
|
# ---- Paths ----
|
||||||
|
|
||||||
BEARSSL := ../../lib/bearssl
|
BEARSSL := ../../lib/bearssl
|
||||||
|
TLS_LIB := ../../lib/tls
|
||||||
LIBC_LIB := ../../lib/libc
|
LIBC_LIB := ../../lib/libc
|
||||||
LIBC_INC := ../../include/libc
|
LIBC_INC := ../../include/libc
|
||||||
PROG_INC := ../../include
|
PROG_INC := ../../include
|
||||||
@@ -67,7 +68,7 @@ LDFLAGS := \
|
|||||||
|
|
||||||
# ---- Libraries ----
|
# ---- Libraries ----
|
||||||
|
|
||||||
LIBS := $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
|
LIBS := $(TLS_LIB)/libtls.a $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
|
||||||
|
|
||||||
# ---- Target ----
|
# ---- Target ----
|
||||||
|
|
||||||
|
|||||||
+29
-423
@@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
#include <zenith/syscall.h>
|
#include <zenith/syscall.h>
|
||||||
#include <zenith/string.h>
|
#include <zenith/string.h>
|
||||||
|
#include <tls/tls.hpp>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <bearssl.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -156,366 +156,15 @@ static void parse_status_text(const char* buf, int len, char* out, int outMax) {
|
|||||||
out[j] = '\0';
|
out[j] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Trust anchor loading ----
|
// ---- Keyboard abort check for TLS ----
|
||||||
|
|
||||||
struct TrustAnchors {
|
static bool check_keyboard_abort() {
|
||||||
br_x509_trust_anchor* anchors;
|
|
||||||
size_t count;
|
|
||||||
size_t capacity;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Accumulate DER-decoded certificate data
|
|
||||||
struct DerAccum {
|
|
||||||
unsigned char* data;
|
|
||||||
size_t len;
|
|
||||||
size_t cap;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void der_append(void* ctx, const void* buf, size_t len) {
|
|
||||||
DerAccum* a = (DerAccum*)ctx;
|
|
||||||
if (a->len + len > a->cap) {
|
|
||||||
size_t newcap = a->cap * 2;
|
|
||||||
if (newcap < a->len + len) newcap = a->len + len + 4096;
|
|
||||||
unsigned char* nb = (unsigned char*)malloc(newcap);
|
|
||||||
if (!nb) return;
|
|
||||||
if (a->data) {
|
|
||||||
memcpy(nb, a->data, a->len);
|
|
||||||
free(a->data);
|
|
||||||
}
|
|
||||||
a->data = nb;
|
|
||||||
a->cap = newcap;
|
|
||||||
}
|
|
||||||
memcpy(a->data + a->len, buf, len);
|
|
||||||
a->len += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accumulate DN data from X.509 decoder
|
|
||||||
struct DnAccum {
|
|
||||||
unsigned char* data;
|
|
||||||
size_t len;
|
|
||||||
size_t cap;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void dn_append(void* ctx, const void* buf, size_t len) {
|
|
||||||
DnAccum* a = (DnAccum*)ctx;
|
|
||||||
if (a->len + len > a->cap) {
|
|
||||||
size_t newcap = a->cap * 2;
|
|
||||||
if (newcap < a->len + len) newcap = a->len + len + 256;
|
|
||||||
unsigned char* nb = (unsigned char*)malloc(newcap);
|
|
||||||
if (!nb) return;
|
|
||||||
if (a->data) {
|
|
||||||
memcpy(nb, a->data, a->len);
|
|
||||||
free(a->data);
|
|
||||||
}
|
|
||||||
a->data = nb;
|
|
||||||
a->cap = newcap;
|
|
||||||
}
|
|
||||||
memcpy(a->data + a->len, buf, len);
|
|
||||||
a->len += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ta_add(TrustAnchors* tas, const br_x509_trust_anchor* ta) {
|
|
||||||
if (tas->count >= tas->capacity) {
|
|
||||||
size_t newcap = tas->capacity == 0 ? 64 : tas->capacity * 2;
|
|
||||||
br_x509_trust_anchor* na = (br_x509_trust_anchor*)malloc(
|
|
||||||
newcap * sizeof(br_x509_trust_anchor));
|
|
||||||
if (!na) return;
|
|
||||||
if (tas->anchors) {
|
|
||||||
memcpy(na, tas->anchors, tas->count * sizeof(br_x509_trust_anchor));
|
|
||||||
free(tas->anchors);
|
|
||||||
}
|
|
||||||
tas->anchors = na;
|
|
||||||
tas->capacity = newcap;
|
|
||||||
}
|
|
||||||
tas->anchors[tas->count++] = *ta;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process a single DER certificate into a trust anchor
|
|
||||||
static bool process_cert_der(TrustAnchors* tas, const unsigned char* der, size_t der_len) {
|
|
||||||
br_x509_decoder_context dc;
|
|
||||||
DnAccum dn = {nullptr, 0, 0};
|
|
||||||
|
|
||||||
br_x509_decoder_init(&dc, dn_append, &dn);
|
|
||||||
br_x509_decoder_push(&dc, der, der_len);
|
|
||||||
|
|
||||||
br_x509_pkey* pk = br_x509_decoder_get_pkey(&dc);
|
|
||||||
if (!pk) {
|
|
||||||
if (dn.data) free(dn.data);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
br_x509_trust_anchor ta;
|
|
||||||
memset(&ta, 0, sizeof(ta));
|
|
||||||
|
|
||||||
// Copy DN
|
|
||||||
ta.dn.data = dn.data;
|
|
||||||
ta.dn.len = dn.len;
|
|
||||||
|
|
||||||
ta.flags = 0;
|
|
||||||
if (br_x509_decoder_isCA(&dc))
|
|
||||||
ta.flags |= BR_X509_TA_CA;
|
|
||||||
|
|
||||||
// Deep-copy public key data
|
|
||||||
switch (pk->key_type) {
|
|
||||||
case BR_KEYTYPE_RSA: {
|
|
||||||
ta.pkey.key_type = BR_KEYTYPE_RSA;
|
|
||||||
ta.pkey.key.rsa.nlen = pk->key.rsa.nlen;
|
|
||||||
ta.pkey.key.rsa.n = (unsigned char*)malloc(pk->key.rsa.nlen);
|
|
||||||
if (ta.pkey.key.rsa.n) memcpy(ta.pkey.key.rsa.n, pk->key.rsa.n, pk->key.rsa.nlen);
|
|
||||||
ta.pkey.key.rsa.elen = pk->key.rsa.elen;
|
|
||||||
ta.pkey.key.rsa.e = (unsigned char*)malloc(pk->key.rsa.elen);
|
|
||||||
if (ta.pkey.key.rsa.e) memcpy(ta.pkey.key.rsa.e, pk->key.rsa.e, pk->key.rsa.elen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case BR_KEYTYPE_EC: {
|
|
||||||
ta.pkey.key_type = BR_KEYTYPE_EC;
|
|
||||||
ta.pkey.key.ec.curve = pk->key.ec.curve;
|
|
||||||
ta.pkey.key.ec.qlen = pk->key.ec.qlen;
|
|
||||||
ta.pkey.key.ec.q = (unsigned char*)malloc(pk->key.ec.qlen);
|
|
||||||
if (ta.pkey.key.ec.q) memcpy(ta.pkey.key.ec.q, pk->key.ec.q, pk->key.ec.qlen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if (dn.data) free(dn.data);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ta_add(tas, &ta);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static TrustAnchors load_trust_anchors(bool verbose) {
|
|
||||||
TrustAnchors tas = {nullptr, 0, 0};
|
|
||||||
|
|
||||||
int fh = zenith::open("0:/etc/ca-certificates.crt");
|
|
||||||
if (fh < 0) {
|
|
||||||
printf("Warning: could not open CA certificate bundle\n");
|
|
||||||
return tas;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t fsize = zenith::getsize(fh);
|
|
||||||
if (fsize == 0 || fsize > 512 * 1024) {
|
|
||||||
zenith::close(fh);
|
|
||||||
printf("Warning: CA cert file invalid size\n");
|
|
||||||
return tas;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char* pem = (unsigned char*)malloc(fsize + 1);
|
|
||||||
if (!pem) {
|
|
||||||
zenith::close(fh);
|
|
||||||
printf("Warning: out of memory loading CA certs\n");
|
|
||||||
return tas;
|
|
||||||
}
|
|
||||||
|
|
||||||
zenith::read(fh, pem, 0, fsize);
|
|
||||||
zenith::close(fh);
|
|
||||||
pem[fsize] = 0;
|
|
||||||
|
|
||||||
// Parse PEM -> DER certificates -> trust anchors
|
|
||||||
br_pem_decoder_context pc;
|
|
||||||
br_pem_decoder_init(&pc);
|
|
||||||
|
|
||||||
DerAccum der = {nullptr, 0, 0};
|
|
||||||
bool inCert = false;
|
|
||||||
|
|
||||||
size_t offset = 0;
|
|
||||||
while (offset < fsize) {
|
|
||||||
size_t pushed = br_pem_decoder_push(&pc, pem + offset, fsize - offset);
|
|
||||||
offset += pushed;
|
|
||||||
|
|
||||||
int event = br_pem_decoder_event(&pc);
|
|
||||||
if (event == BR_PEM_BEGIN_OBJ) {
|
|
||||||
const char* name = br_pem_decoder_name(&pc);
|
|
||||||
inCert = (strcmp(name, "CERTIFICATE") == 0);
|
|
||||||
if (inCert) {
|
|
||||||
der.len = 0;
|
|
||||||
br_pem_decoder_setdest(&pc, der_append, &der);
|
|
||||||
} else {
|
|
||||||
br_pem_decoder_setdest(&pc, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
} else if (event == BR_PEM_END_OBJ) {
|
|
||||||
if (inCert && der.len > 0) {
|
|
||||||
process_cert_der(&tas, der.data, der.len);
|
|
||||||
}
|
|
||||||
inCert = false;
|
|
||||||
} else if (event == BR_PEM_ERROR) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (der.data) free(der.data);
|
|
||||||
free(pem);
|
|
||||||
|
|
||||||
if (verbose) {
|
|
||||||
char msg[64];
|
|
||||||
snprintf(msg, sizeof(msg), "Loaded %u trust anchors\n", (unsigned)tas.count);
|
|
||||||
zenith::print(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tas;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Time conversion for certificate validation ----
|
|
||||||
|
|
||||||
// Returns days since January 1, 0 AD (Gregorian) and seconds within the day.
|
|
||||||
// This is the format BearSSL's br_x509_minimal_set_time() expects.
|
|
||||||
static void get_bearssl_time(uint32_t* days, uint32_t* seconds) {
|
|
||||||
Zenith::DateTime dt;
|
|
||||||
zenith::gettime(&dt);
|
|
||||||
|
|
||||||
int y = dt.Year;
|
|
||||||
int m = dt.Month;
|
|
||||||
int d = dt.Day;
|
|
||||||
|
|
||||||
// Days from year 0 to start of year y (Gregorian proleptic calendar)
|
|
||||||
uint32_t total_days = 365 * (uint32_t)y
|
|
||||||
+ (uint32_t)(y / 4)
|
|
||||||
- (uint32_t)(y / 100)
|
|
||||||
+ (uint32_t)(y / 400);
|
|
||||||
|
|
||||||
// Add days for completed months in this year
|
|
||||||
const int mdays[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
|
||||||
for (int mo = 1; mo < m && mo <= 12; mo++) {
|
|
||||||
total_days += mdays[mo];
|
|
||||||
}
|
|
||||||
// Leap day for this year if we're past February
|
|
||||||
bool leap = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0));
|
|
||||||
if (leap && m > 2) total_days++;
|
|
||||||
|
|
||||||
total_days += d - 1;
|
|
||||||
|
|
||||||
*days = total_days;
|
|
||||||
*seconds = (uint32_t)(dt.Hour * 3600 + dt.Minute * 60 + dt.Second);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- TLS I/O loop ----
|
|
||||||
|
|
||||||
static int tls_send_all(int fd, const unsigned char* data, size_t len) {
|
|
||||||
size_t sent = 0;
|
|
||||||
uint64_t deadline = zenith::get_milliseconds() + 15000;
|
|
||||||
while (sent < len) {
|
|
||||||
int r = zenith::send(fd, data + sent, (uint32_t)(len - sent));
|
|
||||||
if (r > 0) {
|
|
||||||
sent += r;
|
|
||||||
deadline = zenith::get_milliseconds() + 15000;
|
|
||||||
} else if (r < 0) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
if (zenith::get_milliseconds() >= deadline) return -1;
|
|
||||||
zenith::sleep_ms(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (int)sent;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int tls_recv_some(int fd, unsigned char* buf, size_t maxlen) {
|
|
||||||
uint64_t deadline = zenith::get_milliseconds() + 15000;
|
|
||||||
while (true) {
|
|
||||||
int r = zenith::recv(fd, buf, (uint32_t)maxlen);
|
|
||||||
if (r > 0) return r;
|
|
||||||
if (r < 0) return -1;
|
|
||||||
if (zenith::get_milliseconds() >= deadline) return -1;
|
|
||||||
zenith::sleep_ms(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run BearSSL I/O loop until handshake + app data exchange is done
|
|
||||||
// Returns: number of response bytes in respBuf, or -1 on error
|
|
||||||
static int tls_exchange(int fd, br_ssl_engine_context* eng,
|
|
||||||
const char* request, int reqLen,
|
|
||||||
char* respBuf, int respMax, bool verbose) {
|
|
||||||
bool requestSent = false;
|
|
||||||
int respLen = 0;
|
|
||||||
uint64_t deadline = zenith::get_milliseconds() + 30000;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
unsigned state = br_ssl_engine_current_state(eng);
|
|
||||||
|
|
||||||
if (state & BR_SSL_CLOSED) {
|
|
||||||
int err = br_ssl_engine_last_error(eng);
|
|
||||||
if (err != BR_ERR_OK && err != BR_ERR_IO) {
|
|
||||||
char msg[64];
|
|
||||||
snprintf(msg, sizeof(msg), "TLS error: %d\n", err);
|
|
||||||
zenith::print(msg);
|
|
||||||
if (respLen == 0) return -1;
|
|
||||||
}
|
|
||||||
return respLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for keyboard abort
|
|
||||||
if (zenith::is_key_available()) {
|
if (zenith::is_key_available()) {
|
||||||
Zenith::KeyEvent ev;
|
Zenith::KeyEvent ev;
|
||||||
zenith::getkey(&ev);
|
zenith::getkey(&ev);
|
||||||
if (ev.pressed && ev.ctrl && ev.ascii == 'q') {
|
if (ev.pressed && ev.ctrl && ev.ascii == 'q') return true;
|
||||||
br_ssl_engine_close(eng);
|
|
||||||
return respLen > 0 ? respLen : -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send record data to network
|
|
||||||
if (state & BR_SSL_SENDREC) {
|
|
||||||
size_t len;
|
|
||||||
unsigned char* buf = br_ssl_engine_sendrec_buf(eng, &len);
|
|
||||||
int sent = tls_send_all(fd, buf, len);
|
|
||||||
if (sent < 0) {
|
|
||||||
br_ssl_engine_close(eng);
|
|
||||||
return respLen > 0 ? respLen : -1;
|
|
||||||
}
|
|
||||||
br_ssl_engine_sendrec_ack(eng, len);
|
|
||||||
deadline = zenith::get_milliseconds() + 30000;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read application data from TLS
|
|
||||||
if (state & BR_SSL_RECVAPP) {
|
|
||||||
size_t len;
|
|
||||||
unsigned char* buf = br_ssl_engine_recvapp_buf(eng, &len);
|
|
||||||
size_t toCopy = len;
|
|
||||||
if (respLen + (int)toCopy > respMax - 1)
|
|
||||||
toCopy = respMax - 1 - respLen;
|
|
||||||
if (toCopy > 0) {
|
|
||||||
memcpy(respBuf + respLen, buf, toCopy);
|
|
||||||
respLen += toCopy;
|
|
||||||
}
|
|
||||||
br_ssl_engine_recvapp_ack(eng, len);
|
|
||||||
deadline = zenith::get_milliseconds() + 30000;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send application data (HTTP request) into TLS
|
|
||||||
if ((state & BR_SSL_SENDAPP) && !requestSent) {
|
|
||||||
size_t len;
|
|
||||||
unsigned char* buf = br_ssl_engine_sendapp_buf(eng, &len);
|
|
||||||
size_t toWrite = (size_t)reqLen;
|
|
||||||
if (toWrite > len) toWrite = len;
|
|
||||||
memcpy(buf, request, toWrite);
|
|
||||||
br_ssl_engine_sendapp_ack(eng, toWrite);
|
|
||||||
br_ssl_engine_flush(eng, 0);
|
|
||||||
requestSent = true;
|
|
||||||
deadline = zenith::get_milliseconds() + 30000;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receive record data from network
|
|
||||||
if (state & BR_SSL_RECVREC) {
|
|
||||||
size_t len;
|
|
||||||
unsigned char* buf = br_ssl_engine_recvrec_buf(eng, &len);
|
|
||||||
int got = tls_recv_some(fd, buf, len);
|
|
||||||
if (got < 0) {
|
|
||||||
br_ssl_engine_close(eng);
|
|
||||||
return respLen > 0 ? respLen : -1;
|
|
||||||
}
|
|
||||||
br_ssl_engine_recvrec_ack(eng, got);
|
|
||||||
deadline = zenith::get_milliseconds() + 30000;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nothing actionable — wait
|
|
||||||
if (zenith::get_milliseconds() >= deadline) {
|
|
||||||
return respLen > 0 ? respLen : -1;
|
|
||||||
}
|
|
||||||
zenith::sleep_ms(1);
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Plain HTTP exchange (no TLS) ----
|
// ---- Plain HTTP exchange (no TLS) ----
|
||||||
@@ -706,19 +355,6 @@ extern "C" void _start() {
|
|||||||
zenith::print(msg);
|
zenith::print(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and connect socket
|
|
||||||
int fd = zenith::socket(Zenith::SOCK_TCP);
|
|
||||||
if (fd < 0) {
|
|
||||||
zenith::print("Error: failed to create socket\n");
|
|
||||||
zenith::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (zenith::connect(fd, serverIp, port) < 0) {
|
|
||||||
zenith::print("Error: connection failed\n");
|
|
||||||
zenith::closesocket(fd);
|
|
||||||
zenith::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build HTTP request
|
// Build HTTP request
|
||||||
char request[1024];
|
char request[1024];
|
||||||
int reqLen = snprintf(request, sizeof(request),
|
int reqLen = snprintf(request, sizeof(request),
|
||||||
@@ -740,7 +376,6 @@ extern "C" void _start() {
|
|||||||
char* respBuf = (char*)malloc(RESP_MAX);
|
char* respBuf = (char*)malloc(RESP_MAX);
|
||||||
if (!respBuf) {
|
if (!respBuf) {
|
||||||
zenith::print("Error: out of memory\n");
|
zenith::print("Error: out of memory\n");
|
||||||
zenith::closesocket(fd);
|
|
||||||
zenith::exit(1);
|
zenith::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,35 +383,21 @@ extern "C" void _start() {
|
|||||||
|
|
||||||
if (useHttps) {
|
if (useHttps) {
|
||||||
// ---- TLS handshake and exchange ----
|
// ---- TLS handshake and exchange ----
|
||||||
|
tls::TrustAnchors tas = tls::load_trust_anchors();
|
||||||
// Load trust anchors
|
if (verbose) {
|
||||||
TrustAnchors tas = load_trust_anchors(verbose);
|
char msg[64];
|
||||||
|
snprintf(msg, sizeof(msg), "Loaded %u trust anchors\n", (unsigned)tas.count);
|
||||||
|
zenith::print(msg);
|
||||||
|
}
|
||||||
if (tas.count == 0) {
|
if (tas.count == 0) {
|
||||||
zenith::print("Error: no trust anchors loaded\n");
|
zenith::print("Error: no trust anchors loaded\n");
|
||||||
free(respBuf);
|
free(respBuf);
|
||||||
zenith::closesocket(fd);
|
|
||||||
zenith::exit(1);
|
zenith::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize BearSSL client
|
|
||||||
// Allocate contexts on heap to avoid stack overflow
|
|
||||||
br_ssl_client_context* cc = (br_ssl_client_context*)malloc(sizeof(br_ssl_client_context));
|
|
||||||
br_x509_minimal_context* xc = (br_x509_minimal_context*)malloc(sizeof(br_x509_minimal_context));
|
|
||||||
if (!cc || !xc) {
|
|
||||||
zenith::print("Error: out of memory for TLS context\n");
|
|
||||||
free(respBuf);
|
|
||||||
zenith::closesocket(fd);
|
|
||||||
zenith::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
br_ssl_client_init_full(cc, xc, tas.anchors, tas.count);
|
|
||||||
|
|
||||||
// Set time for certificate validation
|
|
||||||
uint32_t days, secs;
|
|
||||||
get_bearssl_time(&days, &secs);
|
|
||||||
br_x509_minimal_set_time(xc, days, secs);
|
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
|
uint32_t days, secs;
|
||||||
|
tls::get_bearssl_time(&days, &secs);
|
||||||
Zenith::DateTime dt;
|
Zenith::DateTime dt;
|
||||||
zenith::gettime(&dt);
|
zenith::gettime(&dt);
|
||||||
char tmsg[128];
|
char tmsg[128];
|
||||||
@@ -785,54 +406,39 @@ extern "C" void _start() {
|
|||||||
(unsigned)dt.Hour, (unsigned)dt.Minute, (unsigned)dt.Second,
|
(unsigned)dt.Hour, (unsigned)dt.Minute, (unsigned)dt.Second,
|
||||||
(unsigned)days, (unsigned)secs);
|
(unsigned)days, (unsigned)secs);
|
||||||
zenith::print(tmsg);
|
zenith::print(tmsg);
|
||||||
}
|
|
||||||
|
|
||||||
// Seed the PRNG with RDRAND entropy
|
|
||||||
unsigned char seed[32];
|
|
||||||
zenith::getrandom(seed, sizeof(seed));
|
|
||||||
br_ssl_engine_set_buffer(&cc->eng, malloc(BR_SSL_BUFSIZE_BIDI),
|
|
||||||
BR_SSL_BUFSIZE_BIDI, 1);
|
|
||||||
|
|
||||||
// Inject entropy
|
|
||||||
br_ssl_engine_inject_entropy(&cc->eng, seed, sizeof(seed));
|
|
||||||
|
|
||||||
// Reset client with server name for SNI
|
|
||||||
if (!br_ssl_client_reset(cc, hostStr, 0)) {
|
|
||||||
int err = br_ssl_engine_last_error(&cc->eng);
|
|
||||||
char msg[64];
|
|
||||||
snprintf(msg, sizeof(msg), "Error: TLS reset failed (err=%d)\n", err);
|
|
||||||
zenith::print(msg);
|
|
||||||
free(respBuf);
|
|
||||||
zenith::closesocket(fd);
|
|
||||||
zenith::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verbose) {
|
|
||||||
zenith::print("TLS handshake...\n");
|
zenith::print("TLS handshake...\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run TLS I/O loop
|
respLen = tls::https_fetch(hostStr, serverIp, port,
|
||||||
respLen = tls_exchange(fd, &cc->eng, request, reqLen,
|
request, reqLen, tas,
|
||||||
respBuf, RESP_MAX, verbose);
|
respBuf, RESP_MAX, check_keyboard_abort);
|
||||||
|
|
||||||
if (verbose && respLen > 0) {
|
if (verbose && respLen > 0) {
|
||||||
zenith::print("TLS connection established\n");
|
zenith::print("TLS connection established\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup (we don't bother freeing everything since we're exiting)
|
|
||||||
} else {
|
} else {
|
||||||
// ---- Plain HTTP ----
|
// ---- Plain HTTP ----
|
||||||
|
int fd = zenith::socket(Zenith::SOCK_TCP);
|
||||||
|
if (fd < 0) {
|
||||||
|
zenith::print("Error: failed to create socket\n");
|
||||||
|
zenith::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zenith::connect(fd, serverIp, port) < 0) {
|
||||||
|
zenith::print("Error: connection failed\n");
|
||||||
|
zenith::closesocket(fd);
|
||||||
|
zenith::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
respLen = plain_http_exchange(fd, request, reqLen, respBuf, RESP_MAX);
|
respLen = plain_http_exchange(fd, request, reqLen, respBuf, RESP_MAX);
|
||||||
|
zenith::closesocket(fd);
|
||||||
|
|
||||||
if (respLen == -2) {
|
if (respLen == -2) {
|
||||||
zenith::print("\nAborted.\n");
|
zenith::print("\nAborted.\n");
|
||||||
zenith::closesocket(fd);
|
|
||||||
zenith::exit(0);
|
zenith::exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
zenith::closesocket(fd);
|
|
||||||
|
|
||||||
if (respLen <= 0) {
|
if (respLen <= 0) {
|
||||||
zenith::print("Error: no response received\n");
|
zenith::print("Error: no response received\n");
|
||||||
zenith::exit(1);
|
zenith::exit(1);
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# Makefile for weather (standalone Weather GUI client) on ZenithOS
|
||||||
|
# Copyright (c) 2026 Daniel Hammer
|
||||||
|
|
||||||
|
MAKEFLAGS += -rR
|
||||||
|
.SUFFIXES:
|
||||||
|
|
||||||
|
# ---- Toolchain ----
|
||||||
|
|
||||||
|
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
|
||||||
|
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
|
||||||
|
CXX := $(TOOLCHAIN_PREFIX)g++
|
||||||
|
else
|
||||||
|
CXX := g++
|
||||||
|
endif
|
||||||
|
|
||||||
|
# ---- Paths ----
|
||||||
|
|
||||||
|
BEARSSL := ../../lib/bearssl
|
||||||
|
TLS_LIB := ../../lib/tls
|
||||||
|
LIBC_LIB := ../../lib/libc
|
||||||
|
LIBC_INC := ../../include/libc
|
||||||
|
PROG_INC := ../../include
|
||||||
|
LINK_LD := ../../link.ld
|
||||||
|
BINDIR := ../../bin
|
||||||
|
OBJDIR := obj
|
||||||
|
|
||||||
|
# ---- Compiler flags ----
|
||||||
|
|
||||||
|
CXXFLAGS := \
|
||||||
|
-std=gnu++20 \
|
||||||
|
-g -O2 -pipe \
|
||||||
|
-Wall \
|
||||||
|
-Wextra \
|
||||||
|
-Wno-unused-parameter \
|
||||||
|
-Wno-unused-function \
|
||||||
|
-nostdinc \
|
||||||
|
-ffreestanding \
|
||||||
|
-fno-stack-protector \
|
||||||
|
-fno-stack-check \
|
||||||
|
-fno-PIC \
|
||||||
|
-fno-rtti \
|
||||||
|
-fno-exceptions \
|
||||||
|
-ffunction-sections \
|
||||||
|
-fdata-sections \
|
||||||
|
-m64 \
|
||||||
|
-march=x86-64 \
|
||||||
|
-msse \
|
||||||
|
-msse2 \
|
||||||
|
-mno-red-zone \
|
||||||
|
-mcmodel=small \
|
||||||
|
-I $(PROG_INC) \
|
||||||
|
-isystem $(LIBC_INC) \
|
||||||
|
-I $(BEARSSL)/inc \
|
||||||
|
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
|
||||||
|
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include
|
||||||
|
|
||||||
|
# ---- Linker flags ----
|
||||||
|
|
||||||
|
LDFLAGS := \
|
||||||
|
-nostdlib \
|
||||||
|
-static \
|
||||||
|
-Wl,--build-id=none \
|
||||||
|
-Wl,--gc-sections \
|
||||||
|
-Wl,-m,elf_x86_64 \
|
||||||
|
-z max-page-size=0x1000 \
|
||||||
|
-T $(LINK_LD)
|
||||||
|
|
||||||
|
# ---- Libraries ----
|
||||||
|
|
||||||
|
LIBS := $(TLS_LIB)/libtls.a $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
|
||||||
|
|
||||||
|
# ---- Source files ----
|
||||||
|
|
||||||
|
SRCS := main.cpp stb_truetype_impl.cpp
|
||||||
|
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||||
|
|
||||||
|
# ---- Target ----
|
||||||
|
|
||||||
|
TARGET := $(BINDIR)/os/weather.elf
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(OBJS) $(LIBS) $(LINK_LD) Makefile
|
||||||
|
mkdir -p $(BINDIR)/os
|
||||||
|
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $@
|
||||||
|
|
||||||
|
$(OBJDIR)/%.o: %.cpp Makefile
|
||||||
|
mkdir -p $(OBJDIR)
|
||||||
|
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(OBJDIR) $(TARGET)
|
||||||
@@ -0,0 +1,575 @@
|
|||||||
|
/*
|
||||||
|
* main.cpp
|
||||||
|
* ZenithOS Weather app - standalone Window Server process
|
||||||
|
* Fetches current weather from wttr.in via HTTPS (BearSSL)
|
||||||
|
* Displays temperature, description, feels like, and location
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zenith/syscall.h>
|
||||||
|
#include <zenith/string.h>
|
||||||
|
#include <zenith/heap.h>
|
||||||
|
#include <gui/gui.hpp>
|
||||||
|
#include <gui/svg.hpp>
|
||||||
|
#include <gui/truetype.hpp>
|
||||||
|
#include <tls/tls.hpp>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace gui;
|
||||||
|
using namespace gui::colors;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Constants
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static constexpr int INIT_W = 380;
|
||||||
|
static constexpr int INIT_H = 280;
|
||||||
|
static constexpr int HEADER_H = 160;
|
||||||
|
static constexpr int FOOTER_H = 50;
|
||||||
|
static constexpr int ICON_SIZE = 80;
|
||||||
|
static constexpr int ICON_X = 28;
|
||||||
|
static constexpr int ICON_Y = 40;
|
||||||
|
static constexpr int INFO_X = ICON_X + ICON_SIZE + 20; // 128
|
||||||
|
static constexpr int TEMP_Y = 40;
|
||||||
|
static int TEMP_SIZE = 40;
|
||||||
|
static constexpr int DESC_Y = 92;
|
||||||
|
static int DESC_SIZE = 17;
|
||||||
|
static constexpr int FEELS_Y = 116;
|
||||||
|
static int LABEL_SIZE = 15;
|
||||||
|
static constexpr int RESP_MAX = 65536;
|
||||||
|
|
||||||
|
static const char WTTR_HOST[] = "wttr.in";
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// App state
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
enum class AppPhase { IDLE, LOADING, DONE, ERR };
|
||||||
|
|
||||||
|
static AppPhase g_phase = AppPhase::IDLE;
|
||||||
|
static char g_temp[32] = {};
|
||||||
|
static char g_desc[128] = {};
|
||||||
|
static char g_feels[48] = {};
|
||||||
|
static char g_location[128] = {};
|
||||||
|
static char g_status[256] = {};
|
||||||
|
static int g_win_w = INIT_W;
|
||||||
|
static int g_win_h = INIT_H;
|
||||||
|
|
||||||
|
static char* g_resp_buf = nullptr;
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
static TrueTypeFont* g_font = nullptr; // Roboto Medium
|
||||||
|
static TrueTypeFont* g_font_bold = nullptr; // Roboto Bold
|
||||||
|
|
||||||
|
// Weather icon (loaded per-fetch based on weather code)
|
||||||
|
static SvgIcon g_icon = {nullptr, 0, 0};
|
||||||
|
static char g_icon_name[64] = {};
|
||||||
|
|
||||||
|
// TLS state (lazy-init on first fetch)
|
||||||
|
static bool g_tls_ready = false;
|
||||||
|
static uint32_t g_server_ip = 0;
|
||||||
|
static tls::TrustAnchors g_tas = {nullptr, 0, 0};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Pixel buffer helpers
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static void px_fill(uint32_t* px, int bw, int bh,
|
||||||
|
int x, int y, int w, int h, Color c) {
|
||||||
|
uint32_t v = c.to_pixel();
|
||||||
|
int x0 = x < 0 ? 0 : x, y0 = y < 0 ? 0 : y;
|
||||||
|
int x1 = x + w > bw ? bw : x + w;
|
||||||
|
int y1 = y + h > bh ? bh : y + h;
|
||||||
|
for (int row = y0; row < y1; row++)
|
||||||
|
for (int col = x0; col < x1; col++)
|
||||||
|
px[row * bw + col] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void px_hline(uint32_t* px, int bw, int bh, int x, int y, int len, Color c) {
|
||||||
|
if (y < 0 || y >= bh) return;
|
||||||
|
uint32_t v = c.to_pixel();
|
||||||
|
int x1 = x + len > bw ? bw : x + len;
|
||||||
|
for (int col = x < 0 ? 0 : x; col < x1; col++)
|
||||||
|
px[y * bw + col] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Integer square root (Newton's method).
|
||||||
|
static int isqrt(int n) {
|
||||||
|
if (n <= 0) return 0;
|
||||||
|
int x = n, y = (x + 1) / 2;
|
||||||
|
while (y < x) { x = y; y = (x + n / x) / 2; }
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill a rounded rectangle directly onto the pixel buffer.
|
||||||
|
static void px_fill_rounded(uint32_t* px, int bw, int bh,
|
||||||
|
int x, int y, int w, int h, int r, Color c) {
|
||||||
|
if (r <= 0) { px_fill(px, bw, bh, x, y, w, h, c); return; }
|
||||||
|
if (r > w / 2) r = w / 2;
|
||||||
|
if (r > h / 2) r = h / 2;
|
||||||
|
|
||||||
|
uint32_t v = c.to_pixel();
|
||||||
|
// Corner arc centers
|
||||||
|
int cx_l = x + r;
|
||||||
|
int cx_r = x + w - 1 - r;
|
||||||
|
|
||||||
|
for (int row = y; row < y + h; row++) {
|
||||||
|
if (row < 0 || row >= bh) continue;
|
||||||
|
|
||||||
|
int left, right;
|
||||||
|
if (row < y + r) {
|
||||||
|
int dy = y + r - row; // 1..r
|
||||||
|
int dx = isqrt(r * r - dy * dy);
|
||||||
|
left = cx_l - dx;
|
||||||
|
right = cx_r + dx + 1;
|
||||||
|
} else if (row >= y + h - r) {
|
||||||
|
int dy = row - (y + h - 1 - r); // 1..r
|
||||||
|
int dx = isqrt(r * r - dy * dy);
|
||||||
|
left = cx_l - dx;
|
||||||
|
right = cx_r + dx + 1;
|
||||||
|
} else {
|
||||||
|
left = x;
|
||||||
|
right = x + w;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left < 0) left = 0;
|
||||||
|
if (right > bw) right = bw;
|
||||||
|
for (int col = left; col < right; col++)
|
||||||
|
px[row * bw + col] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alpha-composite an SvgIcon onto the pixel buffer.
|
||||||
|
static void draw_icon(uint32_t* px, int bw, int bh,
|
||||||
|
int dx, int dy, const SvgIcon& icon) {
|
||||||
|
if (!icon.pixels) return;
|
||||||
|
for (int row = 0; row < icon.height; row++) {
|
||||||
|
int ty = dy + row;
|
||||||
|
if (ty < 0 || ty >= bh) continue;
|
||||||
|
for (int col = 0; col < icon.width; col++) {
|
||||||
|
int tx = dx + col;
|
||||||
|
if (tx < 0 || tx >= bw) continue;
|
||||||
|
uint32_t s = icon.pixels[row * icon.width + col];
|
||||||
|
uint8_t sa = (s >> 24) & 0xFF;
|
||||||
|
if (sa == 0) continue;
|
||||||
|
if (sa == 255) { px[ty * bw + tx] = s; continue; }
|
||||||
|
uint8_t sr = (s >> 16) & 0xFF, sg = (s >> 8) & 0xFF, sb = s & 0xFF;
|
||||||
|
uint32_t d = px[ty * bw + tx];
|
||||||
|
uint8_t dr = (d >> 16) & 0xFF, dg = (d >> 8) & 0xFF, db = d & 0xFF;
|
||||||
|
uint32_t inv = 255 - sa;
|
||||||
|
px[ty * bw + tx] = 0xFF000000u
|
||||||
|
| (((sr * sa + dr * inv) / 255u) << 16)
|
||||||
|
| (((sg * sa + dg * inv) / 255u) << 8)
|
||||||
|
| ((sb * sa + db * inv) / 255u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// HTTP parsing
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static int find_header_end(const char* buf, int len) {
|
||||||
|
for (int i = 0; i + 3 < len; i++)
|
||||||
|
if (buf[i]=='\r' && buf[i+1]=='\n' && buf[i+2]=='\r' && buf[i+3]=='\n')
|
||||||
|
return i + 4;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_status_code(const char* buf, int len) {
|
||||||
|
int i = 0;
|
||||||
|
while (i < len && buf[i] != ' ') i++;
|
||||||
|
if (i >= len || i + 3 >= len) return -1;
|
||||||
|
i++;
|
||||||
|
if (buf[i] < '0' || buf[i] > '9') return -1;
|
||||||
|
return (buf[i]-'0')*100 + (buf[i+1]-'0')*10 + (buf[i+2]-'0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// JSON parsing
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Extract value of a simple JSON string field: "key":"value"
|
||||||
|
static int extract_json_string(const char* buf, int len, const char* key,
|
||||||
|
char* out, int maxOut) {
|
||||||
|
int klen = (int)strlen(key);
|
||||||
|
for (int i = 0; i < len - klen - 3; i++) {
|
||||||
|
if (buf[i] != '"') continue;
|
||||||
|
if (memcmp(buf + i + 1, key, klen) != 0) continue;
|
||||||
|
if (buf[i + 1 + klen] != '"') continue;
|
||||||
|
if (buf[i + 2 + klen] != ':') continue;
|
||||||
|
|
||||||
|
int p = i + 3 + klen;
|
||||||
|
while (p < len && (buf[p]==' ' || buf[p]=='\t')) p++;
|
||||||
|
if (p >= len || buf[p] != '"') continue;
|
||||||
|
p++;
|
||||||
|
|
||||||
|
int j = 0;
|
||||||
|
while (p < len && j < maxOut - 1 && buf[p] != '"') {
|
||||||
|
if (buf[p] == '\\' && p + 1 < len) { p++; out[j++] = buf[p]; }
|
||||||
|
else out[j++] = buf[p];
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
out[j] = '\0';
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
out[0] = '\0';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first occurrence of needle in buf[0..len-1]. Returns offset or -1.
|
||||||
|
static int find_substr(const char* buf, int len, const char* needle) {
|
||||||
|
int nlen = (int)strlen(needle);
|
||||||
|
if (nlen > len) return -1;
|
||||||
|
for (int i = 0; i <= len - nlen; i++) {
|
||||||
|
if (memcmp(buf + i, needle, nlen) == 0) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the "value" string within a named JSON array-of-objects field.
|
||||||
|
// e.g., "weatherDesc":[{"value":"Partly cloudy"}] → "Partly cloudy"
|
||||||
|
// Works by finding section_key first, then the first "value" after it.
|
||||||
|
static int extract_array_value(const char* buf, int len, const char* section_key,
|
||||||
|
char* out, int maxOut) {
|
||||||
|
int pos = find_substr(buf, len, section_key);
|
||||||
|
if (pos < 0) { out[0] = '\0'; return 0; }
|
||||||
|
return extract_json_string(buf + pos, len - pos, "value", out, maxOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a signed integer from a decimal string.
|
||||||
|
static int parse_int(const char* s) {
|
||||||
|
int sign = 1, val = 0, i = 0;
|
||||||
|
if (s[0] == '-') { sign = -1; i = 1; }
|
||||||
|
while (s[i] >= '0' && s[i] <= '9') val = val * 10 + (s[i++] - '0');
|
||||||
|
return sign * val;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Weather code → icon filename
|
||||||
|
// Maps wttr.in WMO-style weather codes to Flat-Remix panel icon names.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static const char* weather_code_to_icon(int code) {
|
||||||
|
switch (code) {
|
||||||
|
case 113: return "weather-clear.svg";
|
||||||
|
case 116: return "weather-few-clouds.svg";
|
||||||
|
case 119: return "weather-clouds.svg";
|
||||||
|
case 122: return "weather-overcast.svg";
|
||||||
|
case 143: return "weather-mist.svg";
|
||||||
|
case 248: case 260: return "weather-fog.svg";
|
||||||
|
case 176: case 263: case 266: case 353: return "weather-showers-scattered.svg";
|
||||||
|
case 293: case 296: case 299: case 302:
|
||||||
|
case 305: case 308: case 356: case 359: return "weather-showers.svg";
|
||||||
|
case 179: case 362: case 365: case 368: return "weather-snow-scattered.svg";
|
||||||
|
case 323: case 326: case 329: case 332:
|
||||||
|
case 335: case 338: case 371: case 374: return "weather-snow.svg";
|
||||||
|
case 227: case 230: return "weather-snow.svg";
|
||||||
|
case 182: case 311: case 314: case 317: case 320: return "weather-snow-rain.svg";
|
||||||
|
case 185: case 281: case 284: return "weather-freezing-rain.svg";
|
||||||
|
case 350: case 377: return "weather-hail.svg";
|
||||||
|
case 200: case 386: case 389: case 392: case 395: return "weather-storm.svg";
|
||||||
|
default: return "weather-none-available.svg";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the weather icon for the given icon filename (caches the last result).
|
||||||
|
static void load_weather_icon(const char* icon_name) {
|
||||||
|
if (strcmp(g_icon_name, icon_name) == 0 && g_icon.pixels) return;
|
||||||
|
|
||||||
|
if (g_icon.pixels) svg_free(g_icon);
|
||||||
|
|
||||||
|
static char path[80];
|
||||||
|
snprintf(path, sizeof(path), "0:/icons/%s", icon_name);
|
||||||
|
// ColorScheme-Text paths (main shape) use this fill_color; accent classes
|
||||||
|
// (ColorScheme-Highlight=blue, ColorScheme-NeutralText=yellow, etc.) use their
|
||||||
|
// CSS-defined colors as parsed from the SVG's <style> block by svg_load.
|
||||||
|
static constexpr Color ICON_FG = Color::from_rgb(0x5C, 0x61, 0x6C); // dark neutral
|
||||||
|
g_icon = svg_load(path, ICON_SIZE, ICON_SIZE, ICON_FG);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (icon_name[i] && i < 63) { g_icon_name[i] = icon_name[i]; i++; }
|
||||||
|
g_icon_name[i] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// UI scale
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static void apply_scale(int scale) {
|
||||||
|
switch (scale) {
|
||||||
|
case 0: TEMP_SIZE=32; DESC_SIZE=14; LABEL_SIZE=12; break;
|
||||||
|
case 2: TEMP_SIZE=50; DESC_SIZE=21; LABEL_SIZE=19; break;
|
||||||
|
default: TEMP_SIZE=40; DESC_SIZE=17; LABEL_SIZE=15; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Network fetch (blocking — called from the event loop)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static void do_fetch() {
|
||||||
|
// Lazy init: resolve DNS and load CA certificates once
|
||||||
|
if (!g_tls_ready) {
|
||||||
|
g_server_ip = zenith::resolve(WTTR_HOST);
|
||||||
|
if (g_server_ip == 0) {
|
||||||
|
snprintf(g_status, sizeof(g_status),
|
||||||
|
"Error: could not resolve %s", WTTR_HOST);
|
||||||
|
g_phase = AppPhase::ERR; return;
|
||||||
|
}
|
||||||
|
g_tas = tls::load_trust_anchors();
|
||||||
|
if (g_tas.count == 0) {
|
||||||
|
snprintf(g_status, sizeof(g_status), "Error: no CA certificates loaded");
|
||||||
|
g_phase = AppPhase::ERR; return;
|
||||||
|
}
|
||||||
|
g_tls_ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char request[512];
|
||||||
|
int reqLen = snprintf(request, sizeof(request),
|
||||||
|
"GET /?format=j1 HTTP/1.0\r\n"
|
||||||
|
"Host: %s\r\n"
|
||||||
|
"User-Agent: ZenithOS/1.0 weather\r\n"
|
||||||
|
"Accept: application/json\r\n"
|
||||||
|
"Connection: close\r\n"
|
||||||
|
"\r\n",
|
||||||
|
WTTR_HOST);
|
||||||
|
|
||||||
|
int respLen = tls::https_fetch(WTTR_HOST, g_server_ip, 443,
|
||||||
|
request, reqLen, g_tas,
|
||||||
|
g_resp_buf, RESP_MAX);
|
||||||
|
if (respLen <= 0) {
|
||||||
|
snprintf(g_status, sizeof(g_status), "Error: no response from server");
|
||||||
|
g_phase = AppPhase::ERR; return;
|
||||||
|
}
|
||||||
|
g_resp_buf[respLen] = '\0';
|
||||||
|
|
||||||
|
int headerEnd = find_header_end(g_resp_buf, respLen);
|
||||||
|
if (headerEnd < 0) {
|
||||||
|
snprintf(g_status, sizeof(g_status), "Error: malformed HTTP response");
|
||||||
|
g_phase = AppPhase::ERR; return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = parse_status_code(g_resp_buf, headerEnd);
|
||||||
|
if (status != 200) {
|
||||||
|
snprintf(g_status, sizeof(g_status), "Error: HTTP %d from server", status);
|
||||||
|
g_phase = AppPhase::ERR; return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* body = g_resp_buf + headerEnd;
|
||||||
|
int bodyLen = respLen - headerEnd;
|
||||||
|
|
||||||
|
// Extract core weather fields
|
||||||
|
static char temp_raw[16], feels_raw[16], code_raw[8];
|
||||||
|
extract_json_string(body, bodyLen, "temp_C", temp_raw, sizeof(temp_raw));
|
||||||
|
extract_json_string(body, bodyLen, "FeelsLikeC", feels_raw, sizeof(feels_raw));
|
||||||
|
extract_json_string(body, bodyLen, "weatherCode", code_raw, sizeof(code_raw));
|
||||||
|
|
||||||
|
extract_array_value(body, bodyLen, "\"weatherDesc\"", g_desc, sizeof(g_desc));
|
||||||
|
|
||||||
|
static char area[64], country[64];
|
||||||
|
extract_array_value(body, bodyLen, "\"areaName\"", area, sizeof(area));
|
||||||
|
extract_array_value(body, bodyLen, "\"country\"", country, sizeof(country));
|
||||||
|
|
||||||
|
// Degree sign is U+00B0 = 0xB0 in Latin-1 (single byte, within the 256-entry glyph cache)
|
||||||
|
snprintf(g_temp, sizeof(g_temp), "%s\xb0""C", temp_raw);
|
||||||
|
snprintf(g_feels, sizeof(g_feels), "Feels like: %s\xb0""C", feels_raw);
|
||||||
|
|
||||||
|
if (area[0] && country[0])
|
||||||
|
snprintf(g_location, sizeof(g_location), "%s, %s", area, country);
|
||||||
|
else if (area[0])
|
||||||
|
snprintf(g_location, sizeof(g_location), "%s", area);
|
||||||
|
else
|
||||||
|
snprintf(g_location, sizeof(g_location), "Unknown location");
|
||||||
|
|
||||||
|
// Load matching weather icon
|
||||||
|
int code = parse_int(code_raw);
|
||||||
|
load_weather_icon(weather_code_to_icon(code));
|
||||||
|
|
||||||
|
g_phase = AppPhase::DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Theme colors
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static constexpr Color CONTENT_BG = Color::from_rgb(0xFF, 0xFF, 0xFF); // white
|
||||||
|
static constexpr Color FOOTER_BG = Color::from_rgb(0xF5, 0xF5, 0xF5); // light gray
|
||||||
|
static constexpr Color DIVIDER = Color::from_rgb(0xCC, 0xCC, 0xCC); // subtle border
|
||||||
|
static constexpr Color DARK_TEXT = Color::from_rgb(0x33, 0x33, 0x33);
|
||||||
|
static constexpr Color MID_TEXT = Color::from_rgb(0x88, 0x88, 0x88);
|
||||||
|
static constexpr Color HINT_TEXT = Color::from_rgb(0x99, 0x99, 0x99);
|
||||||
|
static constexpr Color ERR_TEXT = Color::from_rgb(0xCC, 0x22, 0x22);
|
||||||
|
static constexpr Color BTN_BG = Color::from_rgb(0x36, 0x7B, 0xF0); // accent blue
|
||||||
|
static constexpr Color WHITE_TEXT = Color::from_rgb(0xFF, 0xFF, 0xFF);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Rendering
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static constexpr int BTN_W = 110;
|
||||||
|
static constexpr int BTN_H = 28;
|
||||||
|
static constexpr int BTN_RADIUS = 6;
|
||||||
|
|
||||||
|
static void render(uint32_t* pixels) {
|
||||||
|
// ── Background ───────────────────────────────────────────────────────────
|
||||||
|
px_fill(pixels, g_win_w, g_win_h, 0, 0,
|
||||||
|
g_win_w, g_win_h - FOOTER_H, CONTENT_BG);
|
||||||
|
px_fill(pixels, g_win_w, g_win_h, 0, g_win_h - FOOTER_H,
|
||||||
|
g_win_w, FOOTER_H, FOOTER_BG);
|
||||||
|
|
||||||
|
// Divider between content and location strip
|
||||||
|
px_hline(pixels, g_win_w, g_win_h, 0, HEADER_H, g_win_w, DIVIDER);
|
||||||
|
// Divider above footer
|
||||||
|
px_hline(pixels, g_win_w, g_win_h, 0, g_win_h - FOOTER_H, g_win_w, DIVIDER);
|
||||||
|
|
||||||
|
if (!g_font) return;
|
||||||
|
|
||||||
|
// ── Main content area (y=0..HEADER_H) ────────────────────────────────────
|
||||||
|
if (g_phase == AppPhase::LOADING) {
|
||||||
|
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||||
|
20, HEADER_H / 2 - 9,
|
||||||
|
"Fetching weather data...", HINT_TEXT, 18);
|
||||||
|
|
||||||
|
} else if (g_phase == AppPhase::ERR) {
|
||||||
|
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||||
|
20, 20, g_status, ERR_TEXT, 15);
|
||||||
|
|
||||||
|
} else if (g_phase == AppPhase::IDLE) {
|
||||||
|
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||||
|
20, HEADER_H / 2 - 9,
|
||||||
|
"Click Refresh to check weather.", HINT_TEXT, 18);
|
||||||
|
|
||||||
|
} else { // DONE
|
||||||
|
// Weather icon
|
||||||
|
draw_icon(pixels, g_win_w, g_win_h, ICON_X, ICON_Y, g_icon);
|
||||||
|
|
||||||
|
// Temperature (large bold)
|
||||||
|
TrueTypeFont* temp_font = g_font_bold ? g_font_bold : g_font;
|
||||||
|
temp_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||||
|
INFO_X, TEMP_Y, g_temp, DARK_TEXT, TEMP_SIZE);
|
||||||
|
|
||||||
|
// Weather description
|
||||||
|
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||||
|
INFO_X, DESC_Y, g_desc, DARK_TEXT, DESC_SIZE);
|
||||||
|
|
||||||
|
// Feels like
|
||||||
|
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||||
|
INFO_X, FEELS_Y, g_feels, MID_TEXT, LABEL_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Location strip (y=HEADER_H..g_win_h-FOOTER_H) ────────────────────────
|
||||||
|
if (g_phase == AppPhase::DONE) {
|
||||||
|
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||||
|
20, HEADER_H + 14, g_location, DARK_TEXT, LABEL_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Refresh button (rounded, in footer) ───────────────────────────────────
|
||||||
|
int btn_x = (g_win_w - BTN_W) / 2;
|
||||||
|
int btn_y = g_win_h - FOOTER_H + (FOOTER_H - BTN_H) / 2;
|
||||||
|
|
||||||
|
px_fill_rounded(pixels, g_win_w, g_win_h,
|
||||||
|
btn_x, btn_y, BTN_W, BTN_H, BTN_RADIUS, BTN_BG);
|
||||||
|
|
||||||
|
if (g_phase == AppPhase::LOADING) {
|
||||||
|
const char* lbl = "Loading...";
|
||||||
|
int sw = g_font->measure_text(lbl, 14);
|
||||||
|
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||||
|
btn_x + (BTN_W - sw) / 2, btn_y + (BTN_H - 14) / 2,
|
||||||
|
lbl, WHITE_TEXT, 14);
|
||||||
|
} else {
|
||||||
|
const char* lbl = "Refresh";
|
||||||
|
int sw = g_font->measure_text(lbl, 15);
|
||||||
|
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||||
|
btn_x + (BTN_W - sw) / 2, btn_y + (BTN_H - 15) / 2,
|
||||||
|
lbl, WHITE_TEXT, 15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Entry point
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
extern "C" void _start() {
|
||||||
|
// Allocate response buffer from heap
|
||||||
|
g_resp_buf = (char*)malloc(RESP_MAX + 1);
|
||||||
|
if (!g_resp_buf) zenith::exit(1);
|
||||||
|
|
||||||
|
// Load fonts
|
||||||
|
auto load_font = [](const char* path) -> TrueTypeFont* {
|
||||||
|
TrueTypeFont* f = (TrueTypeFont*)zenith::malloc(sizeof(TrueTypeFont));
|
||||||
|
if (!f) return nullptr;
|
||||||
|
zenith::memset(f, 0, sizeof(TrueTypeFont));
|
||||||
|
if (!f->init(path)) { zenith::mfree(f); return nullptr; }
|
||||||
|
return f;
|
||||||
|
};
|
||||||
|
g_font = load_font("0:/fonts/Roboto-Medium.ttf");
|
||||||
|
g_font_bold = load_font("0:/fonts/Roboto-Bold.ttf");
|
||||||
|
if (!g_font) zenith::exit(1);
|
||||||
|
|
||||||
|
apply_scale(zenith::win_getscale());
|
||||||
|
|
||||||
|
// Create window
|
||||||
|
Zenith::WinCreateResult wres;
|
||||||
|
if (zenith::win_create("Weather", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
|
||||||
|
zenith::exit(1);
|
||||||
|
|
||||||
|
int win_id = wres.id;
|
||||||
|
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||||
|
|
||||||
|
// Initial fetch on startup
|
||||||
|
g_phase = AppPhase::LOADING;
|
||||||
|
render(pixels);
|
||||||
|
zenith::win_present(win_id);
|
||||||
|
do_fetch();
|
||||||
|
|
||||||
|
// Event loop
|
||||||
|
while (true) {
|
||||||
|
Zenith::WinEvent ev;
|
||||||
|
int r = zenith::win_poll(win_id, &ev);
|
||||||
|
|
||||||
|
if (r < 0) break;
|
||||||
|
|
||||||
|
if (r == 0) {
|
||||||
|
zenith::sleep_ms(16);
|
||||||
|
render(pixels);
|
||||||
|
zenith::win_present(win_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev.type == 3) break; // close
|
||||||
|
|
||||||
|
if (ev.type == 4) {
|
||||||
|
apply_scale(ev.scale.scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev.type == 1) {
|
||||||
|
// Mouse — check for Refresh button click
|
||||||
|
bool just_clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
|
||||||
|
if (just_clicked && g_phase != AppPhase::LOADING) {
|
||||||
|
int btn_x = (g_win_w - BTN_W) / 2;
|
||||||
|
int btn_y = g_win_h - FOOTER_H + (FOOTER_H - BTN_H) / 2;
|
||||||
|
int mx = ev.mouse.x, my = ev.mouse.y;
|
||||||
|
if (mx >= btn_x && mx < btn_x + BTN_W &&
|
||||||
|
my >= btn_y && my < btn_y + BTN_H) {
|
||||||
|
g_phase = AppPhase::LOADING;
|
||||||
|
render(pixels);
|
||||||
|
zenith::win_present(win_id);
|
||||||
|
do_fetch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(pixels);
|
||||||
|
zenith::win_present(win_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_icon.pixels) svg_free(g_icon);
|
||||||
|
zenith::win_destroy(win_id);
|
||||||
|
zenith::exit(0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* stb_truetype_impl.cpp
|
||||||
|
* Single compilation unit for stb_truetype in ZenithOS freestanding environment
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <zenith/heap.h>
|
||||||
|
#include <zenith/string.h>
|
||||||
|
#include <gui/stb_math.h>
|
||||||
|
|
||||||
|
// Override all stb_truetype dependencies before including the implementation
|
||||||
|
|
||||||
|
#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), zenith::malloc(x))
|
||||||
|
#define STBTT_free(x,u) ((void)(u), zenith::mfree(x))
|
||||||
|
|
||||||
|
#define STBTT_memcpy(d,s,n) zenith::memcpy(d,s,n)
|
||||||
|
#define STBTT_memset(d,v,n) zenith::memset(d,v,n)
|
||||||
|
|
||||||
|
#define STBTT_strlen(x) zenith::slen(x)
|
||||||
|
|
||||||
|
#define STBTT_assert(x) ((void)(x))
|
||||||
|
|
||||||
|
#define STB_TRUETYPE_IMPLEMENTATION
|
||||||
|
#include <gui/stb_truetype.h>
|
||||||
@@ -16,6 +16,7 @@ endif
|
|||||||
# ---- Paths ----
|
# ---- Paths ----
|
||||||
|
|
||||||
BEARSSL := ../../lib/bearssl
|
BEARSSL := ../../lib/bearssl
|
||||||
|
TLS_LIB := ../../lib/tls
|
||||||
LIBC_LIB := ../../lib/libc
|
LIBC_LIB := ../../lib/libc
|
||||||
LIBC_INC := ../../include/libc
|
LIBC_INC := ../../include/libc
|
||||||
PROG_INC := ../../include
|
PROG_INC := ../../include
|
||||||
@@ -67,7 +68,7 @@ LDFLAGS := \
|
|||||||
|
|
||||||
# ---- Libraries ----
|
# ---- Libraries ----
|
||||||
|
|
||||||
LIBS := $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
|
LIBS := $(TLS_LIB)/libtls.a $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
|
||||||
|
|
||||||
# ---- Target ----
|
# ---- Target ----
|
||||||
|
|
||||||
|
|||||||
+11
-340
@@ -10,9 +10,9 @@
|
|||||||
|
|
||||||
#include <zenith/syscall.h>
|
#include <zenith/syscall.h>
|
||||||
#include <zenith/string.h>
|
#include <zenith/string.h>
|
||||||
|
#include <tls/tls.hpp>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <bearssl.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -37,14 +37,8 @@ struct WikiLine {
|
|||||||
|
|
||||||
// ---- Global state (loaded once, reused across fetches) ----
|
// ---- Global state (loaded once, reused across fetches) ----
|
||||||
|
|
||||||
struct TrustAnchors {
|
|
||||||
br_x509_trust_anchor* anchors;
|
|
||||||
size_t count;
|
|
||||||
size_t capacity;
|
|
||||||
};
|
|
||||||
|
|
||||||
static uint32_t g_serverIp = 0;
|
static uint32_t g_serverIp = 0;
|
||||||
static TrustAnchors g_tas = {nullptr, 0, 0};
|
static tls::TrustAnchors g_tas = {nullptr, 0, 0};
|
||||||
|
|
||||||
// ---- Screen buffer for flicker-free rendering ----
|
// ---- Screen buffer for flicker-free rendering ----
|
||||||
|
|
||||||
@@ -75,340 +69,20 @@ static void sb_cursor_to(int row, int col) {
|
|||||||
sb_putc('H');
|
sb_putc('H');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Trust anchor loading ----
|
// ---- Keyboard abort check for TLS ----
|
||||||
|
|
||||||
struct DerAccum {
|
|
||||||
unsigned char* data;
|
|
||||||
size_t len;
|
|
||||||
size_t cap;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void der_append(void* ctx, const void* buf, size_t len) {
|
|
||||||
DerAccum* a = (DerAccum*)ctx;
|
|
||||||
if (a->len + len > a->cap) {
|
|
||||||
size_t newcap = a->cap * 2;
|
|
||||||
if (newcap < a->len + len) newcap = a->len + len + 4096;
|
|
||||||
unsigned char* nb = (unsigned char*)malloc(newcap);
|
|
||||||
if (!nb) return;
|
|
||||||
if (a->data) {
|
|
||||||
memcpy(nb, a->data, a->len);
|
|
||||||
free(a->data);
|
|
||||||
}
|
|
||||||
a->data = nb;
|
|
||||||
a->cap = newcap;
|
|
||||||
}
|
|
||||||
memcpy(a->data + a->len, buf, len);
|
|
||||||
a->len += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DnAccum {
|
|
||||||
unsigned char* data;
|
|
||||||
size_t len;
|
|
||||||
size_t cap;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void dn_append(void* ctx, const void* buf, size_t len) {
|
|
||||||
DnAccum* a = (DnAccum*)ctx;
|
|
||||||
if (a->len + len > a->cap) {
|
|
||||||
size_t newcap = a->cap * 2;
|
|
||||||
if (newcap < a->len + len) newcap = a->len + len + 256;
|
|
||||||
unsigned char* nb = (unsigned char*)malloc(newcap);
|
|
||||||
if (!nb) return;
|
|
||||||
if (a->data) {
|
|
||||||
memcpy(nb, a->data, a->len);
|
|
||||||
free(a->data);
|
|
||||||
}
|
|
||||||
a->data = nb;
|
|
||||||
a->cap = newcap;
|
|
||||||
}
|
|
||||||
memcpy(a->data + a->len, buf, len);
|
|
||||||
a->len += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ta_add(TrustAnchors* tas, const br_x509_trust_anchor* ta) {
|
|
||||||
if (tas->count >= tas->capacity) {
|
|
||||||
size_t newcap = tas->capacity == 0 ? 64 : tas->capacity * 2;
|
|
||||||
br_x509_trust_anchor* na = (br_x509_trust_anchor*)malloc(
|
|
||||||
newcap * sizeof(br_x509_trust_anchor));
|
|
||||||
if (!na) return;
|
|
||||||
if (tas->anchors) {
|
|
||||||
memcpy(na, tas->anchors, tas->count * sizeof(br_x509_trust_anchor));
|
|
||||||
free(tas->anchors);
|
|
||||||
}
|
|
||||||
tas->anchors = na;
|
|
||||||
tas->capacity = newcap;
|
|
||||||
}
|
|
||||||
tas->anchors[tas->count++] = *ta;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool process_cert_der(TrustAnchors* tas, const unsigned char* der, size_t der_len) {
|
|
||||||
static br_x509_decoder_context dc; // ~2KB+, keep off stack
|
|
||||||
DnAccum dn = {nullptr, 0, 0};
|
|
||||||
|
|
||||||
br_x509_decoder_init(&dc, dn_append, &dn);
|
|
||||||
br_x509_decoder_push(&dc, der, der_len);
|
|
||||||
|
|
||||||
br_x509_pkey* pk = br_x509_decoder_get_pkey(&dc);
|
|
||||||
if (!pk) {
|
|
||||||
if (dn.data) free(dn.data);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
br_x509_trust_anchor ta;
|
|
||||||
memset(&ta, 0, sizeof(ta));
|
|
||||||
ta.dn.data = dn.data;
|
|
||||||
ta.dn.len = dn.len;
|
|
||||||
ta.flags = 0;
|
|
||||||
if (br_x509_decoder_isCA(&dc))
|
|
||||||
ta.flags |= BR_X509_TA_CA;
|
|
||||||
|
|
||||||
switch (pk->key_type) {
|
|
||||||
case BR_KEYTYPE_RSA: {
|
|
||||||
ta.pkey.key_type = BR_KEYTYPE_RSA;
|
|
||||||
ta.pkey.key.rsa.nlen = pk->key.rsa.nlen;
|
|
||||||
ta.pkey.key.rsa.n = (unsigned char*)malloc(pk->key.rsa.nlen);
|
|
||||||
if (ta.pkey.key.rsa.n) memcpy(ta.pkey.key.rsa.n, pk->key.rsa.n, pk->key.rsa.nlen);
|
|
||||||
ta.pkey.key.rsa.elen = pk->key.rsa.elen;
|
|
||||||
ta.pkey.key.rsa.e = (unsigned char*)malloc(pk->key.rsa.elen);
|
|
||||||
if (ta.pkey.key.rsa.e) memcpy(ta.pkey.key.rsa.e, pk->key.rsa.e, pk->key.rsa.elen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case BR_KEYTYPE_EC: {
|
|
||||||
ta.pkey.key_type = BR_KEYTYPE_EC;
|
|
||||||
ta.pkey.key.ec.curve = pk->key.ec.curve;
|
|
||||||
ta.pkey.key.ec.qlen = pk->key.ec.qlen;
|
|
||||||
ta.pkey.key.ec.q = (unsigned char*)malloc(pk->key.ec.qlen);
|
|
||||||
if (ta.pkey.key.ec.q) memcpy(ta.pkey.key.ec.q, pk->key.ec.q, pk->key.ec.qlen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if (dn.data) free(dn.data);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ta_add(tas, &ta);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static TrustAnchors load_trust_anchors() {
|
|
||||||
TrustAnchors tas = {nullptr, 0, 0};
|
|
||||||
|
|
||||||
int fh = zenith::open("0:/etc/ca-certificates.crt");
|
|
||||||
if (fh < 0) return tas;
|
|
||||||
|
|
||||||
uint64_t fsize = zenith::getsize(fh);
|
|
||||||
if (fsize == 0 || fsize > 512 * 1024) {
|
|
||||||
zenith::close(fh);
|
|
||||||
return tas;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char* pem = (unsigned char*)malloc(fsize + 1);
|
|
||||||
if (!pem) { zenith::close(fh); return tas; }
|
|
||||||
|
|
||||||
zenith::read(fh, pem, 0, fsize);
|
|
||||||
zenith::close(fh);
|
|
||||||
pem[fsize] = 0;
|
|
||||||
|
|
||||||
static br_pem_decoder_context pc; // keep off stack
|
|
||||||
br_pem_decoder_init(&pc);
|
|
||||||
|
|
||||||
DerAccum der = {nullptr, 0, 0};
|
|
||||||
bool inCert = false;
|
|
||||||
|
|
||||||
size_t offset = 0;
|
|
||||||
while (offset < fsize) {
|
|
||||||
size_t pushed = br_pem_decoder_push(&pc, pem + offset, fsize - offset);
|
|
||||||
offset += pushed;
|
|
||||||
|
|
||||||
int event = br_pem_decoder_event(&pc);
|
|
||||||
if (event == BR_PEM_BEGIN_OBJ) {
|
|
||||||
const char* name = br_pem_decoder_name(&pc);
|
|
||||||
inCert = (strcmp(name, "CERTIFICATE") == 0);
|
|
||||||
if (inCert) {
|
|
||||||
der.len = 0;
|
|
||||||
br_pem_decoder_setdest(&pc, der_append, &der);
|
|
||||||
} else {
|
|
||||||
br_pem_decoder_setdest(&pc, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
} else if (event == BR_PEM_END_OBJ) {
|
|
||||||
if (inCert && der.len > 0)
|
|
||||||
process_cert_der(&tas, der.data, der.len);
|
|
||||||
inCert = false;
|
|
||||||
} else if (event == BR_PEM_ERROR) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (der.data) free(der.data);
|
|
||||||
free(pem);
|
|
||||||
return tas;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- Time conversion for certificate validation ----
|
|
||||||
|
|
||||||
static void get_bearssl_time(uint32_t* days, uint32_t* seconds) {
|
|
||||||
Zenith::DateTime dt;
|
|
||||||
zenith::gettime(&dt);
|
|
||||||
|
|
||||||
int y = dt.Year;
|
|
||||||
int m = dt.Month;
|
|
||||||
int d = dt.Day;
|
|
||||||
|
|
||||||
uint32_t total_days = 365 * (uint32_t)y
|
|
||||||
+ (uint32_t)(y / 4)
|
|
||||||
- (uint32_t)(y / 100)
|
|
||||||
+ (uint32_t)(y / 400);
|
|
||||||
|
|
||||||
const int mdays[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
|
||||||
for (int mo = 1; mo < m && mo <= 12; mo++)
|
|
||||||
total_days += mdays[mo];
|
|
||||||
bool leap = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0));
|
|
||||||
if (leap && m > 2) total_days++;
|
|
||||||
total_days += d - 1;
|
|
||||||
|
|
||||||
*days = total_days;
|
|
||||||
*seconds = (uint32_t)(dt.Hour * 3600 + dt.Minute * 60 + dt.Second);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- TLS I/O ----
|
|
||||||
|
|
||||||
static int tls_send_all(int fd, const unsigned char* data, size_t len) {
|
|
||||||
size_t sent = 0;
|
|
||||||
uint64_t deadline = zenith::get_milliseconds() + 15000;
|
|
||||||
while (sent < len) {
|
|
||||||
int r = zenith::send(fd, data + sent, (uint32_t)(len - sent));
|
|
||||||
if (r > 0) { sent += r; deadline = zenith::get_milliseconds() + 15000; }
|
|
||||||
else if (r < 0) return -1;
|
|
||||||
else { if (zenith::get_milliseconds() >= deadline) return -1; zenith::sleep_ms(1); }
|
|
||||||
}
|
|
||||||
return (int)sent;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int tls_recv_some(int fd, unsigned char* buf, size_t maxlen) {
|
|
||||||
uint64_t deadline = zenith::get_milliseconds() + 15000;
|
|
||||||
while (true) {
|
|
||||||
int r = zenith::recv(fd, buf, (uint32_t)maxlen);
|
|
||||||
if (r > 0) return r;
|
|
||||||
if (r < 0) return -1;
|
|
||||||
if (zenith::get_milliseconds() >= deadline) return -1;
|
|
||||||
zenith::sleep_ms(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int tls_exchange(int fd, br_ssl_engine_context* eng,
|
|
||||||
const char* request, int reqLen,
|
|
||||||
char* respBuf, int respMax) {
|
|
||||||
bool requestSent = false;
|
|
||||||
int respLen = 0;
|
|
||||||
uint64_t deadline = zenith::get_milliseconds() + 30000;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
unsigned state = br_ssl_engine_current_state(eng);
|
|
||||||
|
|
||||||
if (state & BR_SSL_CLOSED) {
|
|
||||||
int err = br_ssl_engine_last_error(eng);
|
|
||||||
if (err != BR_ERR_OK && err != BR_ERR_IO) {
|
|
||||||
if (respLen == 0) return -1;
|
|
||||||
}
|
|
||||||
return respLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
static bool check_keyboard_abort() {
|
||||||
if (zenith::is_key_available()) {
|
if (zenith::is_key_available()) {
|
||||||
Zenith::KeyEvent ev;
|
Zenith::KeyEvent ev;
|
||||||
zenith::getkey(&ev);
|
zenith::getkey(&ev);
|
||||||
if (ev.pressed && ev.ctrl && ev.ascii == 'q') {
|
if (ev.pressed && ev.ctrl && ev.ascii == 'q') return true;
|
||||||
br_ssl_engine_close(eng);
|
|
||||||
return respLen > 0 ? respLen : -1;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state & BR_SSL_SENDREC) {
|
// ---- HTTPS fetch wrapper ----
|
||||||
size_t len;
|
|
||||||
unsigned char* buf = br_ssl_engine_sendrec_buf(eng, &len);
|
|
||||||
int sent = tls_send_all(fd, buf, len);
|
|
||||||
if (sent < 0) { br_ssl_engine_close(eng); return respLen > 0 ? respLen : -1; }
|
|
||||||
br_ssl_engine_sendrec_ack(eng, len);
|
|
||||||
deadline = zenith::get_milliseconds() + 30000;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state & BR_SSL_RECVAPP) {
|
|
||||||
size_t len;
|
|
||||||
unsigned char* buf = br_ssl_engine_recvapp_buf(eng, &len);
|
|
||||||
size_t toCopy = len;
|
|
||||||
if (respLen + (int)toCopy > respMax - 1)
|
|
||||||
toCopy = respMax - 1 - respLen;
|
|
||||||
if (toCopy > 0) { memcpy(respBuf + respLen, buf, toCopy); respLen += toCopy; }
|
|
||||||
br_ssl_engine_recvapp_ack(eng, len);
|
|
||||||
deadline = zenith::get_milliseconds() + 30000;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((state & BR_SSL_SENDAPP) && !requestSent) {
|
|
||||||
size_t len;
|
|
||||||
unsigned char* buf = br_ssl_engine_sendapp_buf(eng, &len);
|
|
||||||
size_t toWrite = (size_t)reqLen;
|
|
||||||
if (toWrite > len) toWrite = len;
|
|
||||||
memcpy(buf, request, toWrite);
|
|
||||||
br_ssl_engine_sendapp_ack(eng, toWrite);
|
|
||||||
br_ssl_engine_flush(eng, 0);
|
|
||||||
requestSent = true;
|
|
||||||
deadline = zenith::get_milliseconds() + 30000;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state & BR_SSL_RECVREC) {
|
|
||||||
size_t len;
|
|
||||||
unsigned char* buf = br_ssl_engine_recvrec_buf(eng, &len);
|
|
||||||
int got = tls_recv_some(fd, buf, len);
|
|
||||||
if (got < 0) { br_ssl_engine_close(eng); return respLen > 0 ? respLen : -1; }
|
|
||||||
br_ssl_engine_recvrec_ack(eng, got);
|
|
||||||
deadline = zenith::get_milliseconds() + 30000;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (zenith::get_milliseconds() >= deadline)
|
|
||||||
return respLen > 0 ? respLen : -1;
|
|
||||||
zenith::sleep_ms(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- HTTPS fetch wrapper (reusable for multiple requests) ----
|
|
||||||
|
|
||||||
static int wiki_fetch(const char* path, char* respBuf, int respMax) {
|
static int wiki_fetch(const char* path, char* respBuf, int respMax) {
|
||||||
int fd = zenith::socket(Zenith::SOCK_TCP);
|
|
||||||
if (fd < 0) return -1;
|
|
||||||
|
|
||||||
if (zenith::connect(fd, g_serverIp, 443) < 0) {
|
|
||||||
zenith::closesocket(fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
br_ssl_client_context* cc = (br_ssl_client_context*)malloc(sizeof(br_ssl_client_context));
|
|
||||||
br_x509_minimal_context* xc = (br_x509_minimal_context*)malloc(sizeof(br_x509_minimal_context));
|
|
||||||
void* iobuf = malloc(BR_SSL_BUFSIZE_BIDI);
|
|
||||||
if (!cc || !xc || !iobuf) {
|
|
||||||
zenith::closesocket(fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
br_ssl_client_init_full(cc, xc, g_tas.anchors, g_tas.count);
|
|
||||||
|
|
||||||
uint32_t days, secs;
|
|
||||||
get_bearssl_time(&days, &secs);
|
|
||||||
br_x509_minimal_set_time(xc, days, secs);
|
|
||||||
|
|
||||||
unsigned char seed[32];
|
|
||||||
zenith::getrandom(seed, sizeof(seed));
|
|
||||||
br_ssl_engine_set_buffer(&cc->eng, iobuf, BR_SSL_BUFSIZE_BIDI, 1);
|
|
||||||
br_ssl_engine_inject_entropy(&cc->eng, seed, sizeof(seed));
|
|
||||||
|
|
||||||
if (!br_ssl_client_reset(cc, WIKI_HOST, 0)) {
|
|
||||||
zenith::closesocket(fd);
|
|
||||||
free(cc); free(xc); free(iobuf);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char request[2560]; // keep off stack
|
static char request[2560]; // keep off stack
|
||||||
int reqLen = snprintf(request, sizeof(request),
|
int reqLen = snprintf(request, sizeof(request),
|
||||||
"GET %s HTTP/1.0\r\n"
|
"GET %s HTTP/1.0\r\n"
|
||||||
@@ -418,12 +92,9 @@ static int wiki_fetch(const char* path, char* respBuf, int respMax) {
|
|||||||
"Connection: close\r\n"
|
"Connection: close\r\n"
|
||||||
"\r\n",
|
"\r\n",
|
||||||
path, WIKI_HOST);
|
path, WIKI_HOST);
|
||||||
|
return tls::https_fetch(WIKI_HOST, g_serverIp, 443,
|
||||||
int respLen = tls_exchange(fd, &cc->eng, request, reqLen, respBuf, respMax);
|
request, reqLen, g_tas,
|
||||||
|
respBuf, respMax, check_keyboard_abort);
|
||||||
zenith::closesocket(fd);
|
|
||||||
free(cc); free(xc); free(iobuf);
|
|
||||||
return respLen;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- HTTP response parsing ----
|
// ---- HTTP response parsing ----
|
||||||
@@ -1030,7 +701,7 @@ extern "C" void _start() {
|
|||||||
zenith::exit(1);
|
zenith::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_tas = load_trust_anchors();
|
g_tas = tls::load_trust_anchors();
|
||||||
if (g_tas.count == 0) {
|
if (g_tas.count == 0) {
|
||||||
if (mode == MODE_DUMP) { zenith::print("\x01"); zenith::sleep_ms(100); zenith::exit(1); }
|
if (mode == MODE_DUMP) { zenith::print("\x01"); zenith::sleep_ms(100); zenith::exit(1); }
|
||||||
zenith::print("\033[1;31mError:\033[0m no CA certificates loaded\n");
|
zenith::print("\033[1;31mError:\033[0m no CA certificates loaded\n");
|
||||||
|
|||||||
Binary file not shown.
@@ -16,6 +16,7 @@ endif
|
|||||||
# ---- Paths ----
|
# ---- Paths ----
|
||||||
|
|
||||||
BEARSSL := ../../lib/bearssl
|
BEARSSL := ../../lib/bearssl
|
||||||
|
TLS_LIB := ../../lib/tls
|
||||||
LIBC_LIB := ../../lib/libc
|
LIBC_LIB := ../../lib/libc
|
||||||
LIBC_INC := ../../include/libc
|
LIBC_INC := ../../include/libc
|
||||||
PROG_INC := ../../include
|
PROG_INC := ../../include
|
||||||
@@ -66,7 +67,7 @@ LDFLAGS := \
|
|||||||
|
|
||||||
# ---- Libraries ----
|
# ---- Libraries ----
|
||||||
|
|
||||||
LIBS := $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
|
LIBS := $(TLS_LIB)/libtls.a $(BEARSSL)/libbearssl.a $(LIBC_LIB)/liblibc.a
|
||||||
|
|
||||||
# ---- Source files ----
|
# ---- Source files ----
|
||||||
|
|
||||||
|
|||||||
+27
-255
@@ -10,9 +10,9 @@
|
|||||||
#include <zenith/heap.h>
|
#include <zenith/heap.h>
|
||||||
#include <gui/gui.hpp>
|
#include <gui/gui.hpp>
|
||||||
#include <gui/truetype.hpp>
|
#include <gui/truetype.hpp>
|
||||||
|
#include <tls/tls.hpp>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <bearssl.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -27,11 +27,11 @@ using namespace gui::colors;
|
|||||||
|
|
||||||
static constexpr int INIT_W = 820;
|
static constexpr int INIT_W = 820;
|
||||||
static constexpr int INIT_H = 580;
|
static constexpr int INIT_H = 580;
|
||||||
static constexpr int TOOLBAR_H = 42;
|
static int TOOLBAR_H = 42;
|
||||||
static constexpr int SCROLLBAR_W = 14;
|
static constexpr int SCROLLBAR_W = 14;
|
||||||
static constexpr int FONT_SIZE = 18;
|
static int FONT_SIZE = 18;
|
||||||
static constexpr int TITLE_SIZE = 32;
|
static int TITLE_SIZE = 32;
|
||||||
static constexpr int SECTION_SIZE = 24;
|
static int SECTION_SIZE = 24;
|
||||||
static constexpr int TEXT_PAD = 16;
|
static constexpr int TEXT_PAD = 16;
|
||||||
static constexpr int RESP_MAX = 131072;
|
static constexpr int RESP_MAX = 131072;
|
||||||
static constexpr int MAX_LINES = 2000;
|
static constexpr int MAX_LINES = 2000;
|
||||||
@@ -79,13 +79,7 @@ static TrueTypeFont* g_font_serif = nullptr; // NotoSerif SemiBold (headings)
|
|||||||
// TLS state (lazy-init on first search)
|
// TLS state (lazy-init on first search)
|
||||||
static bool g_tls_ready = false;
|
static bool g_tls_ready = false;
|
||||||
static uint32_t g_server_ip = 0;
|
static uint32_t g_server_ip = 0;
|
||||||
|
static tls::TrustAnchors g_tas = {nullptr, 0, 0};
|
||||||
struct TrustAnchors {
|
|
||||||
br_x509_trust_anchor* anchors;
|
|
||||||
size_t count;
|
|
||||||
size_t capacity;
|
|
||||||
};
|
|
||||||
static TrustAnchors g_tas = {nullptr, 0, 0};
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Pixel buffer helpers
|
// Pixel buffer helpers
|
||||||
@@ -124,252 +118,23 @@ static void px_rect_outline(uint32_t* px, int bw, int x, int y, int w, int h, Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Trust anchor loading
|
// UI scale
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
struct DerAccum { unsigned char* data; size_t len, cap; };
|
static void apply_scale(int scale) {
|
||||||
struct DnAccum { unsigned char* data; size_t len, cap; };
|
switch (scale) {
|
||||||
|
case 0: FONT_SIZE=14; TITLE_SIZE=26; SECTION_SIZE=20; TOOLBAR_H=34; break;
|
||||||
static void der_append(void* ctx, const void* buf, size_t len) {
|
case 2: FONT_SIZE=22; TITLE_SIZE=40; SECTION_SIZE=30; TOOLBAR_H=52; break;
|
||||||
DerAccum* a = (DerAccum*)ctx;
|
default: FONT_SIZE=18; TITLE_SIZE=32; SECTION_SIZE=24; TOOLBAR_H=42; break;
|
||||||
if (a->len + len > a->cap) {
|
|
||||||
size_t nc = a->cap * 2;
|
|
||||||
if (nc < a->len + len) nc = a->len + len + 4096;
|
|
||||||
unsigned char* nb = (unsigned char*)malloc(nc);
|
|
||||||
if (!nb) return;
|
|
||||||
if (a->data) { memcpy(nb, a->data, a->len); free(a->data); }
|
|
||||||
a->data = nb; a->cap = nc;
|
|
||||||
}
|
}
|
||||||
memcpy(a->data + a->len, buf, len);
|
if (g_font) g_line_h = g_font->get_line_height(FONT_SIZE) + 4;
|
||||||
a->len += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void dn_append(void* ctx, const void* buf, size_t len) {
|
|
||||||
DnAccum* a = (DnAccum*)ctx;
|
|
||||||
if (a->len + len > a->cap) {
|
|
||||||
size_t nc = a->cap * 2;
|
|
||||||
if (nc < a->len + len) nc = a->len + len + 256;
|
|
||||||
unsigned char* nb = (unsigned char*)malloc(nc);
|
|
||||||
if (!nb) return;
|
|
||||||
if (a->data) { memcpy(nb, a->data, a->len); free(a->data); }
|
|
||||||
a->data = nb; a->cap = nc;
|
|
||||||
}
|
|
||||||
memcpy(a->data + a->len, buf, len);
|
|
||||||
a->len += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ta_add(TrustAnchors* tas, const br_x509_trust_anchor* ta) {
|
|
||||||
if (tas->count >= tas->capacity) {
|
|
||||||
size_t nc = tas->capacity == 0 ? 64 : tas->capacity * 2;
|
|
||||||
br_x509_trust_anchor* na = (br_x509_trust_anchor*)malloc(nc * sizeof(*na));
|
|
||||||
if (!na) return;
|
|
||||||
if (tas->anchors) { memcpy(na, tas->anchors, tas->count * sizeof(*na)); free(tas->anchors); }
|
|
||||||
tas->anchors = na; tas->capacity = nc;
|
|
||||||
}
|
|
||||||
tas->anchors[tas->count++] = *ta;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool process_cert_der(TrustAnchors* tas, const unsigned char* der, size_t der_len) {
|
|
||||||
static br_x509_decoder_context dc;
|
|
||||||
DnAccum dn = {nullptr, 0, 0};
|
|
||||||
br_x509_decoder_init(&dc, dn_append, &dn);
|
|
||||||
br_x509_decoder_push(&dc, der, der_len);
|
|
||||||
br_x509_pkey* pk = br_x509_decoder_get_pkey(&dc);
|
|
||||||
if (!pk) { if (dn.data) free(dn.data); return false; }
|
|
||||||
|
|
||||||
br_x509_trust_anchor ta;
|
|
||||||
memset(&ta, 0, sizeof(ta));
|
|
||||||
ta.dn.data = dn.data; ta.dn.len = dn.len; ta.flags = 0;
|
|
||||||
if (br_x509_decoder_isCA(&dc)) ta.flags |= BR_X509_TA_CA;
|
|
||||||
|
|
||||||
switch (pk->key_type) {
|
|
||||||
case BR_KEYTYPE_RSA:
|
|
||||||
ta.pkey.key_type = BR_KEYTYPE_RSA;
|
|
||||||
ta.pkey.key.rsa.nlen = pk->key.rsa.nlen;
|
|
||||||
ta.pkey.key.rsa.n = (unsigned char*)malloc(pk->key.rsa.nlen);
|
|
||||||
if (ta.pkey.key.rsa.n) memcpy(ta.pkey.key.rsa.n, pk->key.rsa.n, pk->key.rsa.nlen);
|
|
||||||
ta.pkey.key.rsa.elen = pk->key.rsa.elen;
|
|
||||||
ta.pkey.key.rsa.e = (unsigned char*)malloc(pk->key.rsa.elen);
|
|
||||||
if (ta.pkey.key.rsa.e) memcpy(ta.pkey.key.rsa.e, pk->key.rsa.e, pk->key.rsa.elen);
|
|
||||||
break;
|
|
||||||
case BR_KEYTYPE_EC:
|
|
||||||
ta.pkey.key_type = BR_KEYTYPE_EC;
|
|
||||||
ta.pkey.key.ec.curve = pk->key.ec.curve;
|
|
||||||
ta.pkey.key.ec.qlen = pk->key.ec.qlen;
|
|
||||||
ta.pkey.key.ec.q = (unsigned char*)malloc(pk->key.ec.qlen);
|
|
||||||
if (ta.pkey.key.ec.q) memcpy(ta.pkey.key.ec.q, pk->key.ec.q, pk->key.ec.qlen);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (dn.data) free(dn.data);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ta_add(tas, &ta);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static TrustAnchors load_trust_anchors() {
|
|
||||||
TrustAnchors tas = {nullptr, 0, 0};
|
|
||||||
int fh = zenith::open("0:/etc/ca-certificates.crt");
|
|
||||||
if (fh < 0) return tas;
|
|
||||||
uint64_t fsize = zenith::getsize(fh);
|
|
||||||
if (fsize == 0 || fsize > 512 * 1024) { zenith::close(fh); return tas; }
|
|
||||||
|
|
||||||
unsigned char* pem = (unsigned char*)malloc(fsize + 1);
|
|
||||||
if (!pem) { zenith::close(fh); return tas; }
|
|
||||||
zenith::read(fh, pem, 0, fsize);
|
|
||||||
zenith::close(fh);
|
|
||||||
pem[fsize] = 0;
|
|
||||||
|
|
||||||
static br_pem_decoder_context pc;
|
|
||||||
br_pem_decoder_init(&pc);
|
|
||||||
DerAccum der = {nullptr, 0, 0};
|
|
||||||
bool inCert = false;
|
|
||||||
size_t offset = 0;
|
|
||||||
|
|
||||||
while (offset < fsize) {
|
|
||||||
size_t pushed = br_pem_decoder_push(&pc, pem + offset, fsize - offset);
|
|
||||||
offset += pushed;
|
|
||||||
int ev = br_pem_decoder_event(&pc);
|
|
||||||
if (ev == BR_PEM_BEGIN_OBJ) {
|
|
||||||
inCert = (strcmp(br_pem_decoder_name(&pc), "CERTIFICATE") == 0);
|
|
||||||
br_pem_decoder_setdest(&pc, inCert ? der_append : nullptr, inCert ? &der : nullptr);
|
|
||||||
if (inCert) der.len = 0;
|
|
||||||
} else if (ev == BR_PEM_END_OBJ) {
|
|
||||||
if (inCert && der.len > 0) process_cert_der(&tas, der.data, der.len);
|
|
||||||
inCert = false;
|
|
||||||
} else if (ev == BR_PEM_ERROR) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (der.data) free(der.data);
|
|
||||||
free(pem);
|
|
||||||
return tas;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// BearSSL time
|
// HTTPS fetch wrapper
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
static void get_bearssl_time(uint32_t* days, uint32_t* seconds) {
|
|
||||||
Zenith::DateTime dt;
|
|
||||||
zenith::gettime(&dt);
|
|
||||||
int y = dt.Year, m = dt.Month, d = dt.Day;
|
|
||||||
uint32_t total = 365u * (uint32_t)y
|
|
||||||
+ (uint32_t)(y/4) - (uint32_t)(y/100) + (uint32_t)(y/400);
|
|
||||||
const int md[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
|
|
||||||
for (int mo = 1; mo < m && mo <= 12; mo++) total += md[mo];
|
|
||||||
if (y%4==0 && (y%100!=0 || y%400==0) && m > 2) total++;
|
|
||||||
total += d - 1;
|
|
||||||
*days = total;
|
|
||||||
*seconds = (uint32_t)(dt.Hour*3600 + dt.Minute*60 + dt.Second);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// TLS I/O
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
static int tls_send_all(int fd, const unsigned char* data, size_t len) {
|
|
||||||
size_t sent = 0;
|
|
||||||
uint64_t deadline = zenith::get_milliseconds() + 15000;
|
|
||||||
while (sent < len) {
|
|
||||||
int r = zenith::send(fd, data + sent, (uint32_t)(len - sent));
|
|
||||||
if (r > 0) { sent += r; deadline = zenith::get_milliseconds() + 15000; }
|
|
||||||
else if (r < 0) return -1;
|
|
||||||
else { if (zenith::get_milliseconds() >= deadline) return -1; zenith::sleep_ms(1); }
|
|
||||||
}
|
|
||||||
return (int)sent;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int tls_recv_some(int fd, unsigned char* buf, size_t maxlen) {
|
|
||||||
uint64_t deadline = zenith::get_milliseconds() + 15000;
|
|
||||||
while (true) {
|
|
||||||
int r = zenith::recv(fd, buf, (uint32_t)maxlen);
|
|
||||||
if (r > 0) return r;
|
|
||||||
if (r < 0) return -1;
|
|
||||||
if (zenith::get_milliseconds() >= deadline) return -1;
|
|
||||||
zenith::sleep_ms(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int tls_exchange(int fd, br_ssl_engine_context* eng,
|
|
||||||
const char* request, int reqLen,
|
|
||||||
char* respBuf, int respMax) {
|
|
||||||
bool requestSent = false;
|
|
||||||
int respLen = 0;
|
|
||||||
uint64_t deadline = zenith::get_milliseconds() + 30000;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
unsigned state = br_ssl_engine_current_state(eng);
|
|
||||||
if (state & BR_SSL_CLOSED) {
|
|
||||||
int err = br_ssl_engine_last_error(eng);
|
|
||||||
if (err != BR_ERR_OK && err != BR_ERR_IO && respLen == 0) return -1;
|
|
||||||
return respLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state & BR_SSL_SENDREC) {
|
|
||||||
size_t len; unsigned char* buf = br_ssl_engine_sendrec_buf(eng, &len);
|
|
||||||
int sent = tls_send_all(fd, buf, len);
|
|
||||||
if (sent < 0) { br_ssl_engine_close(eng); return respLen > 0 ? respLen : -1; }
|
|
||||||
br_ssl_engine_sendrec_ack(eng, len);
|
|
||||||
deadline = zenith::get_milliseconds() + 30000; continue;
|
|
||||||
}
|
|
||||||
if (state & BR_SSL_RECVAPP) {
|
|
||||||
size_t len; unsigned char* buf = br_ssl_engine_recvapp_buf(eng, &len);
|
|
||||||
size_t toCopy = len;
|
|
||||||
if (respLen + (int)toCopy > respMax - 1) toCopy = respMax - 1 - respLen;
|
|
||||||
if (toCopy > 0) { memcpy(respBuf + respLen, buf, toCopy); respLen += toCopy; }
|
|
||||||
br_ssl_engine_recvapp_ack(eng, len);
|
|
||||||
deadline = zenith::get_milliseconds() + 30000; continue;
|
|
||||||
}
|
|
||||||
if ((state & BR_SSL_SENDAPP) && !requestSent) {
|
|
||||||
size_t len; unsigned char* buf = br_ssl_engine_sendapp_buf(eng, &len);
|
|
||||||
size_t toWrite = (size_t)reqLen;
|
|
||||||
if (toWrite > len) toWrite = len;
|
|
||||||
memcpy(buf, request, toWrite);
|
|
||||||
br_ssl_engine_sendapp_ack(eng, toWrite);
|
|
||||||
br_ssl_engine_flush(eng, 0);
|
|
||||||
requestSent = true;
|
|
||||||
deadline = zenith::get_milliseconds() + 30000; continue;
|
|
||||||
}
|
|
||||||
if (state & BR_SSL_RECVREC) {
|
|
||||||
size_t len; unsigned char* buf = br_ssl_engine_recvrec_buf(eng, &len);
|
|
||||||
int got = tls_recv_some(fd, buf, len);
|
|
||||||
if (got < 0) { br_ssl_engine_close(eng); return respLen > 0 ? respLen : -1; }
|
|
||||||
br_ssl_engine_recvrec_ack(eng, got);
|
|
||||||
deadline = zenith::get_milliseconds() + 30000; continue;
|
|
||||||
}
|
|
||||||
if (zenith::get_milliseconds() >= deadline) return respLen > 0 ? respLen : -1;
|
|
||||||
zenith::sleep_ms(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int wiki_fetch(const char* path, char* respBuf, int respMax) {
|
static int wiki_fetch(const char* path, char* respBuf, int respMax) {
|
||||||
int fd = zenith::socket(Zenith::SOCK_TCP);
|
|
||||||
if (fd < 0) return -1;
|
|
||||||
if (zenith::connect(fd, g_server_ip, 443) < 0) { zenith::closesocket(fd); return -1; }
|
|
||||||
|
|
||||||
br_ssl_client_context* cc = (br_ssl_client_context*)malloc(sizeof(*cc));
|
|
||||||
br_x509_minimal_context* xc = (br_x509_minimal_context*)malloc(sizeof(*xc));
|
|
||||||
void* iobuf = malloc(BR_SSL_BUFSIZE_BIDI);
|
|
||||||
if (!cc || !xc || !iobuf) {
|
|
||||||
if (cc) free(cc); if (xc) free(xc); if (iobuf) free(iobuf);
|
|
||||||
zenith::closesocket(fd); return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
br_ssl_client_init_full(cc, xc, g_tas.anchors, g_tas.count);
|
|
||||||
uint32_t days, secs;
|
|
||||||
get_bearssl_time(&days, &secs);
|
|
||||||
br_x509_minimal_set_time(xc, days, secs);
|
|
||||||
|
|
||||||
unsigned char seed[32];
|
|
||||||
zenith::getrandom(seed, sizeof(seed));
|
|
||||||
br_ssl_engine_set_buffer(&cc->eng, iobuf, BR_SSL_BUFSIZE_BIDI, 1);
|
|
||||||
br_ssl_engine_inject_entropy(&cc->eng, seed, sizeof(seed));
|
|
||||||
|
|
||||||
if (!br_ssl_client_reset(cc, WIKI_HOST, 0)) {
|
|
||||||
zenith::closesocket(fd); free(cc); free(xc); free(iobuf); return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char request[2560];
|
static char request[2560];
|
||||||
int reqLen = snprintf(request, sizeof(request),
|
int reqLen = snprintf(request, sizeof(request),
|
||||||
"GET %s HTTP/1.0\r\n"
|
"GET %s HTTP/1.0\r\n"
|
||||||
@@ -379,11 +144,9 @@ static int wiki_fetch(const char* path, char* respBuf, int respMax) {
|
|||||||
"Connection: close\r\n"
|
"Connection: close\r\n"
|
||||||
"\r\n",
|
"\r\n",
|
||||||
path, WIKI_HOST);
|
path, WIKI_HOST);
|
||||||
|
return tls::https_fetch(WIKI_HOST, g_server_ip, 443,
|
||||||
int respLen = tls_exchange(fd, &cc->eng, request, reqLen, respBuf, respMax);
|
request, reqLen, g_tas,
|
||||||
zenith::closesocket(fd);
|
respBuf, respMax);
|
||||||
free(cc); free(xc); free(iobuf);
|
|
||||||
return respLen;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -624,7 +387,7 @@ static void do_search(const char* query) {
|
|||||||
"Error: could not resolve en.wikipedia.org");
|
"Error: could not resolve en.wikipedia.org");
|
||||||
g_phase = AppPhase::ERR; return;
|
g_phase = AppPhase::ERR; return;
|
||||||
}
|
}
|
||||||
g_tas = load_trust_anchors();
|
g_tas = tls::load_trust_anchors();
|
||||||
if (g_tas.count == 0) {
|
if (g_tas.count == 0) {
|
||||||
snprintf(g_status, sizeof(g_status), "Error: no CA certificates loaded");
|
snprintf(g_status, sizeof(g_status), "Error: no CA certificates loaded");
|
||||||
g_phase = AppPhase::ERR; return;
|
g_phase = AppPhase::ERR; return;
|
||||||
@@ -793,6 +556,8 @@ extern "C" void _start() {
|
|||||||
|
|
||||||
g_line_h = g_font->get_line_height(FONT_SIZE) + 4;
|
g_line_h = g_font->get_line_height(FONT_SIZE) + 4;
|
||||||
|
|
||||||
|
apply_scale(zenith::win_getscale());
|
||||||
|
|
||||||
// Create window
|
// Create window
|
||||||
Zenith::WinCreateResult wres;
|
Zenith::WinCreateResult wres;
|
||||||
if (zenith::win_create("Wikipedia", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
|
if (zenith::win_create("Wikipedia", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
|
||||||
@@ -823,6 +588,13 @@ extern "C" void _start() {
|
|||||||
// ---- Handle event ----
|
// ---- Handle event ----
|
||||||
if (ev.type == 3) break; // close
|
if (ev.type == 3) break; // close
|
||||||
|
|
||||||
|
if (ev.type == 4) {
|
||||||
|
apply_scale(ev.scale.scale);
|
||||||
|
if (g_phase == AppPhase::DONE && g_line_count > 0) {
|
||||||
|
build_display_lines(g_title, g_extract_buf, g_extract_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ev.type == 2) {
|
if (ev.type == 2) {
|
||||||
int new_w = ev.resize.w;
|
int new_w = ev.resize.w;
|
||||||
int new_h = ev.resize.h;
|
int new_h = ev.resize.h;
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -57,6 +57,30 @@ ICONS=(
|
|||||||
"apps/scalable/applications-science.svg"
|
"apps/scalable/applications-science.svg"
|
||||||
"apps/scalable/hardware.svg"
|
"apps/scalable/hardware.svg"
|
||||||
"apps/scalable/unsettings.svg" # settings icon; toolbox in flat remix
|
"apps/scalable/unsettings.svg" # settings icon; toolbox in flat remix
|
||||||
|
# Weather app icon (for desktop app menu launcher)
|
||||||
|
"apps/scalable/weather-widget.svg"
|
||||||
|
# Weather condition icons (panel, colorful, used by weather.elf)
|
||||||
|
"panel/weather-clear.svg"
|
||||||
|
"panel/weather-clear-night.svg"
|
||||||
|
"panel/weather-few-clouds.svg"
|
||||||
|
"panel/weather-few-clouds-night.svg"
|
||||||
|
"panel/weather-clouds.svg"
|
||||||
|
"panel/weather-clouds-night.svg"
|
||||||
|
"panel/weather-overcast.svg"
|
||||||
|
"panel/weather-mist.svg"
|
||||||
|
"panel/weather-fog.svg"
|
||||||
|
"panel/weather-showers.svg"
|
||||||
|
"panel/weather-showers-scattered.svg"
|
||||||
|
"panel/weather-snow.svg"
|
||||||
|
"panel/weather-snow-scattered.svg"
|
||||||
|
"panel/weather-snow-rain.svg"
|
||||||
|
"panel/weather-freezing-rain.svg"
|
||||||
|
"panel/weather-hail.svg"
|
||||||
|
"panel/weather-storm.svg"
|
||||||
|
"panel/weather-many-clouds.svg"
|
||||||
|
"panel/weather-windy.svg"
|
||||||
|
"panel/weather-severe-alert.svg"
|
||||||
|
"panel/weather-none-available.svg"
|
||||||
)
|
)
|
||||||
|
|
||||||
copied=0
|
copied=0
|
||||||
|
|||||||
Reference in New Issue
Block a user