code wiki / _hdl_build / nx_browser.nx

nx_browser.nx source

↩ module page · 1243 lines · 63716 B

1// nx_browser.nx -- the LIVE, NAVIGABLE sovereign browser WINDOW. Fetches a URL over our own DNS+TLS1.3+ 2// HTTP, renders it with the full upgraded pipeline (chrome/hidden-drop -> complex-selector cascade -> 3// float/grid/table-cell/word-wrap layout), and shows it in a real scrollable X11 window (WSLg). CLICK a 4// link to navigate (own URL resolver), 'b' = back, scroll wheel / arrows, q quits. 100% sovereign -- no 5// Chrome/V8/curl/gcc. Initial URL from _url.txt. license_tier: ORIGINAL 6import "nx_syscalls.nx" 7import "nx_x509_trust_store.nx" 8import "nx_trust_store_load_from_certdata.nx" 9import "nx_https_get.nx" 10import "nx_http_response_parse.nx" 11import "nx_html_tokenizer.nx" 12import "nx_js_eval.nx" // R-JS-DOMWRITE: js_render_page + js_has_inline_script (run page JS, render DOM mutations) 13import "nx_dom_query.nx" 14import "nx_html_entities.nx" 15import "nx_url_resolve.nx" 16import "nx_render_html.nx" 17import "nx_css_color_decode.nx" 18import "nx_layout_box.nx" 19import "nx_layout_default_display.nx" 20import "nx_layout_from_dom.nx" 21import "nx_layout_block.nx" 22import "nx_x11_connect.nx" 23import "nx_x11_window.nx" 24import "nx_x11_paint.nx" 25import "nx_http_get_ua.nx" 26import "nx_dns_resolve_a_record.nx" 27import "nx_paint_solid_rect.nx" 28import "nx_paint_text.nx" 29import "nx_png_write.nx" 30import "nx_signal.nx" 31import "nx_browser_buyguard.nx" // "before you buy" pass: splice an opportunity-cost overlay into the page 32import "nx_intake_profile.nx" // read the operator's own intake numbers (their real $ vs what it costs) 33import "nx_media_harvest.nx" // 'm' key: panel emitter + kind labels for the media source list 34import "nx_media_deepcrawl.nx" // 'm' key: DEEP crawl -- follow the page's css/js (incl. JS-constructed URLs) and harvest their media 35 36struct Page { 37 buf: *u8, 38 tree: *LayoutTree, 39 computed: *CssComputedDecl, 40 ncomp: i64, 41 page_h: i64, 42 bhref_off: *i64, // per box_idx: href byte-offset into buf, or -1 43 bhref_len: *i64, 44 ok: i64, 45 raw: *u8, // fetched response body (kept so resize can re-layout WITHOUT re-fetching) 46 raw_len: i64 47} 48const NX_PAGE_BYTES: i64 = 80 49const NX_CHROME_H: i64 = 44 // browser chrome (toolbar + address bar) height; page viewport is below it 50 51func br_slen(s: *u8) -> i64 { var k: i64=0; while s[k]!=(0 as u8){k=k+1} return k } 52func br_p(fd: i64, s: *u8) -> i64 { sys_write(fd, s, br_slen(s)); return 0 } 53func br_n(fd: i64, v0: i64) -> i64 { var v: i64=v0; if v<0 { let m: *u8=sys_mmap(8); m[0]=45 as u8; sys_write(fd,m,1); v=0-v } let b: *u8=sys_mmap(24); var k: i64=0; if v==0{b[0]=48;k=1} while v>0{b[k]=(48+(v%10)) as u8;v=v/10;k=k+1} let o: *u8=sys_mmap(24); var j: i64=0; while j<k{o[j]=b[k-1-j];j=j+1} sys_write(fd,o,k); return 0 } 54 55// write buf[0..n) to path (O_CREAT|O_TRUNC|O_WRONLY, 0644). returns bytes written or -1. 56func br_write_file(path: *u8, buf: *u8, n: i64) -> i64 { 57 let fd: i64 = __syscall(257, 0-100, path as i64, 577, 420, 0, 0) // openat(AT_FDCWD, path, O_WRONLY|O_CREAT|O_TRUNC, 0644) 58 if fd < 0 { return 0 - 1 } 59 var done: i64 = 0 60 while done < n { let w: i64 = sys_write(fd, ((buf as i64)+done) as *u8, n-done); if w <= 0 { done = n } else { done = done + w } } 61 __syscall(3, fd, 0, 0, 0, 0, 0) // close 62 return n 63} 64 65// 'm' key: DEEP-harvest every image/gif/video/stream/audio SOURCE URL reachable through THIS page's resource 66// graph -- it follows the CSS/JS the page pulls in, INCLUDING asset URLs the JS constructs from string constants 67// (reconstructed by nx_js_urlfold WITHOUT executing anything), so runtime-loaded art on Unity/heavy-JS pages 68// surfaces with no manual network capture. Prints the list to the terminal + (re)writes the shared media panel. 69// Budget-bounded (16 fetches / depth 1) so the window only blocks briefly. Never runs the page's JS. 70func br_media_dump(store: *TrustStore, cur_url: *u8, ulen: i64) -> i64 { 71 br_p(1, "nx_browser: harvesting media (deep crawl -- following this page's css/js)...\n" as *u8) 72 let M: *MediaSet = nx_mediaset_new() 73 let fetched: i64 = nx_media_deepcrawl(cur_url, ulen, store, M, 16, 1, 1, "knowledge/status/media_panel_raw.html" as *u8) 74 br_p(1, "nx_browser: fetched " as *u8); br_n(1, fetched); br_p(1, " resources, media sources = " as *u8); br_n(1, M.cnt); br_p(1, "\n" as *u8) 75 var d: i64 = 0 76 while d < M.cnt { 77 let bp: *u8 = ((M.buf as i64) + M.off[d]) as *u8 78 br_p(1, " [" as *u8); br_p(1, nx_media_kind_label(M.kind[d])); br_p(1, "] " as *u8) 79 sys_write(1, bp, M.len[d]); br_p(1, "\n" as *u8) 80 d = d + 1 81 } 82 let H: *u8 = sys_mmap(12582912) 83 let hlen: i64 = nx_media_emit_panel(H, cur_url, ulen, M.buf, M.off, M.len, M.kind, M.cnt) 84 br_write_file("knowledge/status/media_panel.html" as *u8, H, hlen) 85 br_p(1, "nx_browser: media panel -> knowledge/status/media_panel.html (open in any browser)\n" as *u8) 86 return 0 87} 88 89func br_comp_int(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, prop: *u8, plen: i64, dflt: i64) -> i64 { 90 var j: i64 = ncomp - 1 91 while j >= 0 { 92 let cd: *CssComputedDecl = ((computed as i64) + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl 93 if cd.element_idx == box_idx { if cd.prop_len == plen { 94 var ok: i64 = 1; var k: i64 = 0 95 while k < plen { if (src[cd.prop_off+k]&0xff) != (prop[k]&0xff) { ok=0; k=plen } else { k=k+1 } } 96 if ok == 1 { 97 var v: i64 = 0; var any: i64 = 0; var i: i64 = 0 98 while i < cd.val_len { let c: i64=src[cd.val_off+i]&0xff; if c>=48 { if c<=57 { v=v*10+(c-48); any=1 } } i=i+1 } 99 if any == 1 { return v } 100 return dflt 101 } 102 } } 103 j = j - 1 104 } 105 return dflt 106} 107func br_comp_color(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, prop: *u8, plen: i64) -> i64 { 108 var j: i64 = ncomp - 1 109 while j >= 0 { 110 let cd: *CssComputedDecl = ((computed as i64) + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl 111 if cd.element_idx == box_idx { if cd.prop_len == plen { 112 var ok: i64 = 1; var k: i64 = 0 113 while k < plen { if (src[cd.prop_off+k]&0xff) != (prop[k]&0xff) { ok=0; k=plen } else { k=k+1 } } 114 if ok == 1 { 115 let col: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor 116 var got: i64 = 0 117 if cd.val_kind == NX_CSS_VAL_HEXCOLOR { got = nx_css_color_decode(src, cd.val_off, cd.val_len, col) } 118 else { if cd.val_kind == NX_CSS_VAL_IDENT { got = nx_css_color_named(src, cd.val_off, cd.val_len, col); if got != 1 { got = nx_css_color_rgb_func(src, cd.val_off, cd.val_len, col) } } } 119 if got == 1 { if col.a != 0 { return (col.r << 16) | (col.g << 8) | col.b } } 120 return 0 - 1 121 } 122 } } 123 j = j - 1 124 } 125 return 0 - 1 126} 127 128// Extract elements (tag/id/class) AND per-element href, mirroring rh_extract's chrome-skip EXACTLY (so the 129// element order == the layout box order). ehref_off/ehref_len get the <a href> span, or -1. 130func br_extract(html: *u8, hlen: i64, elements: *CssElement, ehref_off: *i64, ehref_len: *i64, maxn: i64) -> i64 { 131 let c: *HtmlCursor=sys_mmap(24) as *HtmlCursor; nx_html_cursor_init(c,html,hlen) 132 let tok: *HtmlToken=sys_mmap(56) as *HtmlToken 133 let ao: *i64=sys_mmap(8) as *i64; let al: *i64=sys_mmap(8) as *i64; let bo: *i64=sys_mmap(8) as *i64; let bl: *i64=sys_mmap(8) as *i64 134 let ho: *i64=sys_mmap(8) as *i64; let hl: *i64=sys_mmap(8) as *i64 135 var n: i64=0 136 var cskip: i64=0 137 while 1==1 { 138 nx_html_next_token(c,tok) 139 if tok.kind==NX_HTML_TOK_EOF { return n } 140 if n>=maxn { return n } 141 var ie: i64=0 142 if cskip > 0 { 143 if tok.kind==NX_HTML_TOK_START_TAG { cskip = cskip + 1 } 144 if tok.kind==NX_HTML_TOK_END_TAG { cskip = cskip - 1 } 145 } else { 146 if tok.kind==NX_HTML_TOK_START_TAG { 147 if _lfd_is_chrome_start(html, tok.name_off, tok.name_len, tok.src_off, tok.src_len) == 1 { cskip = 1 } 148 else { ie = 1 } 149 } 150 if tok.kind==NX_HTML_TOK_SELF_CLOSING { ie = 1 } 151 } 152 if ie==1 { 153 let e: *CssElement=((elements as i64)+n*NX_CSS_ELEMENT_BYTES) as *CssElement 154 e.src=html; e.tag_off=tok.name_off; e.tag_len=tok.name_len 155 if nx_dom_find_attr(html,tok.src_off,tok.src_len,"id\x00" as *u8,ao,al)==1 { e.id_off=ao[0]; e.id_len=al[0] } else { e.id_off=0; e.id_len=0 } 156 if nx_dom_find_attr(html,tok.src_off,tok.src_len,"class\x00" as *u8,bo,bl)==1 { e.class_off=bo[0]; e.class_len=bl[0] } else { e.class_off=0; e.class_len=0 } 157 ehref_off[n] = 0 - 1; ehref_len[n] = 0 - 1 158 if tok.name_len==1 { if (html[tok.name_off]&0xff)==97 { // <a> 159 if nx_dom_find_attr(html,tok.src_off,tok.src_len,"href\x00" as *u8,ho,hl)==1 { ehref_off[n]=ho[0]; ehref_len[n]=hl[0] } 160 } } 161 n=n+1 162 } 163 } 164 return n 165} 166 167// Fetch `url` (own DNS+TLS+HTTP), run the full pipeline, fill `page`. page.ok=1 on success. 168func br_isdigit(c: i64) -> i64 { if c >= 48 { if c <= 57 { return 1 } } return 0 } 169 170// "http://" (plain) but NOT "https://" -> 1 171func br_is_plain_http(url: *u8, ulen: i64) -> i64 { 172 if ulen < 7 { return 0 } 173 if (url[0] & 0xff) != 104 { return 0 } // h 174 if (url[1] & 0xff) != 116 { return 0 } // t 175 if (url[2] & 0xff) != 116 { return 0 } // t 176 if (url[3] & 0xff) != 112 { return 0 } // p 177 if (url[4] & 0xff) != 58 { return 0 } // : 178 if (url[5] & 0xff) != 47 { return 0 } // / 179 if (url[6] & 0xff) != 47 { return 0 } // / 180 return 1 181} 182 183// dotted-quad "a.b.c.d" -> packed (a<<24|b<<16|c<<8|d), or -1 if not a literal IPv4 184func br_parse_ipv4(s: *u8, len: i64) -> i64 { 185 var parts: i64 = 0 186 var cur: i64 = 0 187 var any: i64 = 0 188 var packed: i64 = 0 189 var i: i64 = 0 190 while i < len { 191 let c: i64 = s[i] & 0xff 192 if c == 46 { 193 if any == 0 { return 0 - 1 } 194 if cur > 255 { return 0 - 1 } 195 packed = (packed << 8) | cur 196 parts = parts + 1; cur = 0; any = 0 197 } else { 198 if br_isdigit(c) == 0 { return 0 - 1 } 199 cur = cur * 10 + (c - 48); any = 1 200 } 201 i = i + 1 202 } 203 if any == 0 { return 0 - 1 } 204 if cur > 255 { return 0 - 1 } 205 packed = (packed << 8) | cur 206 parts = parts + 1 207 if parts != 4 { return 0 - 1 } 208 return packed 209} 210 211// Sovereign plain-HTTP/1.1 fetch of an http:// URL into out (<=cap). IP-literal 212// hosts (localhost daemons) parsed directly; hostnames resolved via our own DNS. 213// Returns bytes read (full raw response, headers+body) or -1. NO TLS, NO 3rd party. 214func br_http_fetch(url: *u8, ulen: i64, out: *u8, cap: i64) -> i64 { 215 var p: i64 = 7 // skip "http://" 216 let host: *u8 = sys_mmap(512) 217 var hlen: i64 = 0 218 var go: i64 = 1 219 while go == 1 { 220 if p >= ulen { go = 0 } 221 else { 222 let c: i64 = url[p] & 0xff 223 if c == 58 { go = 0 } else { if c == 47 { go = 0 } else { host[hlen] = url[p]; hlen = hlen + 1; p = p + 1 } } 224 } 225 } 226 host[hlen] = 0 as u8 227 if hlen == 0 { return 0 - 1 } 228 229 var port: i64 = 80 230 if p < ulen { if (url[p] & 0xff) == 58 { 231 p = p + 1; port = 0 232 var g2: i64 = 1 233 while g2 == 1 { 234 if p >= ulen { g2 = 0 } 235 else { 236 let c: i64 = url[p] & 0xff 237 if br_isdigit(c) == 1 { port = port * 10 + (c - 48); p = p + 1 } else { g2 = 0 } 238 } 239 } 240 } } 241 242 let path: *u8 = sys_mmap(2048) 243 var plen: i64 = 0 244 while p < ulen { path[plen] = url[p]; plen = plen + 1; p = p + 1 } 245 if plen == 0 { path[0] = 47 as u8; plen = 1 } // "/" 246 path[plen] = 0 as u8 247 248 var ipv4: i64 = br_parse_ipv4(host, hlen) 249 if ipv4 < 0 { 250 let dns: *DnsResolveResult = nx_dns_resolve_default(host, hlen, 305419896) 251 if dns.verdict != NX_DNS_R_OK { return 0 - 1 } 252 if dns.ipv4_packed == 0 { return 0 - 1 } 253 ipv4 = dns.ipv4_packed 254 } 255 let sa: *u8 = sys_mmap(16) 256 nx_https_build_sockaddr(sa, ipv4, port) 257 let status: *i64 = sys_mmap(8) as *i64 258 let ua: *u8 = "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0\x00" 259 return nx_http_get_ua(sa, path, plen, host, hlen, ua, br_slen(ua), out, cap, status) 260} 261 262// layout-only pass: (re)build the style buffer + lay out page.raw at viewport width `vw`. 263// Split from the fetch so a window RESIZE re-lays-out at the new width with NO network round-trip. 264func br_layout(page: *Page, vw: i64) -> i64 { 265 page.ok = 0 266 let body: *u8 = page.raw 267 let body_len: i64 = page.raw_len 268 269 let css: *u8 = "h1{background-color:#a8c8ff;margin-top:24px;margin-bottom:10px;font-size:40px}h2{background-color:#d0d0d0;margin-top:20px;margin-bottom:8px;font-size:28px}h3{background-color:#e4e4e4;margin-top:16px;margin-bottom:6px;font-size:24px}body{background-color:#ffffff;padding-left:40px;padding-right:40px;padding-top:14px;max-width:900px}p{margin-top:12px;margin-bottom:12px}ul{padding-left:16px;margin-top:3px;margin-bottom:3px}ol{padding-left:24px;margin-top:3px;margin-bottom:3px}li{margin-bottom:2px}tr{display:flex}th{font-weight:bold;padding-left:4px;padding-right:4px}td{padding-left:4px;padding-right:4px}a{color:#3366cc}b{font-weight:bold}strong{font-weight:bold}i{font-style:italic}em{font-style:italic}.vector-menu-content-list{display:none}.vector-dropdown{display:none}.mw-interlanguage-selector{display:none}.wbc-editpage{display:none}.infobox{float:right;width:300px;margin-left:8px;background-color:#f8f8f8}.nishi-buyguard{background-color:#fff3cd;color:#332701;padding-left:14px;padding-right:14px;padding-top:8px;padding-bottom:8px;margin-bottom:10px;font-size:18px}\x00" 270 let clen: i64 = br_slen(css) 271 let STYCAP: i64 = 262144 272 let buf: *u8 = sys_mmap(body_len + STYCAP + clen + 16) 273 var bi: i64 = 0 274 while bi < body_len { buf[bi]=body[bi]; bi=bi+1 } 275 // CASCADE ORIGIN ORDER (07-09 root fix, twin of nx_browser_render.nx): UA defaults FIRST, author 276 // <style> AFTER -- the last-match resolvers now let author rules override the defaults (correct CSS 277 // origin order); before this the defaults silently beat every author color on styled pages. 278 var ci: i64 = 0 279 while ci < clen { buf[body_len + ci] = css[ci]; ci=ci+1 } 280 let rawsty: *u8 = sys_mmap(STYCAP) 281 let rawstylen: i64 = rh_extract_styles(body, body_len, rawsty, STYCAP) 282 let stylen: i64 = rh_filter_media(rawsty, 0, rawstylen, ((buf as i64)+body_len+clen) as *u8, STYCAP, 1000) 283 let total: i64 = body_len + clen + stylen 284 285 var VW: i64 = vw 286 if VW < 200 { VW = 200 } 287 let boxes: *LayoutBox = (sys_mmap(NX_LAYOUT_BOX_BYTES*16384+16)) as *LayoutBox 288 let tree: *LayoutTree = (sys_mmap(64)) as *LayoutTree 289 nx_layout_tree_init(tree, boxes, 16384) 290 let stk: *LayoutFromDomStack = (sys_mmap(NX_LAYOUT_FROM_DOM_STACK_BYTES+8)) as *LayoutFromDomStack 291 let sidx: *i64 = (sys_mmap(8*128)) as *i64 292 nx_layout_from_dom_stack_init(stk, sidx, 128) 293 nx_layout_from_dom(buf, body_len, tree, stk) 294 295 let ext: *CssElement = (sys_mmap(NX_CSS_ELEMENT_BYTES*16384 as nx_size)) as *CssElement 296 let ehoff: *i64 = sys_mmap(8*16384) as *i64 297 let ehlen: *i64 = sys_mmap(8*16384) as *i64 298 let n_ext: i64 = br_extract(buf, body_len, ext, ehoff, ehlen, 16384) 299 let boxel: *CssElement = (sys_mmap((NX_CSS_ELEMENT_BYTES*(tree.count+1)) as nx_size)) as *CssElement 300 let bhoff: *i64 = sys_mmap(8*(tree.count+1)) as *i64 301 let bhlen: *i64 = sys_mmap(8*(tree.count+1)) as *i64 302 var k: i64 = 0 303 var ii: i64 = 0 304 while ii < tree.count { 305 let b: *LayoutBox = ((tree.boxes as i64)+ii*NX_LAYOUT_BOX_BYTES) as *LayoutBox 306 let e: *CssElement = ((boxel as i64)+ii*NX_CSS_ELEMENT_BYTES) as *CssElement 307 e.src = buf 308 bhoff[ii] = 0 - 1; bhlen[ii] = 0 - 1 309 var mapped: i64 = 0 310 if rh_is_elem(b.kind)==1 { if b.parent_idx != (0-1) { if k < n_ext { 311 let s: *CssElement = ((ext as i64)+k*NX_CSS_ELEMENT_BYTES) as *CssElement 312 e.tag_off=s.tag_off; e.tag_len=s.tag_len; e.id_off=s.id_off; e.id_len=s.id_len; e.class_off=s.class_off; e.class_len=s.class_len 313 bhoff[ii] = ehoff[k]; bhlen[ii] = ehlen[k] 314 k=k+1; mapped=1 315 } } } 316 if mapped==0 { e.tag_off=0; e.tag_len=0; e.id_off=0; e.id_len=0; e.class_off=0; e.class_len=0 } 317 ii = ii + 1 318 } 319 320 let cur: *CssCursor = (sys_mmap(NX_CSS_CURSOR_BYTES) as nx_size) as *CssCursor 321 nx_css_cursor_init(cur, buf, total); cur.pos = body_len 322 let tok: *CssToken = (sys_mmap(NX_CSS_TOKEN_BYTES) as nx_size) as *CssToken 323 let rules: *CssRule = (sys_mmap(NX_CSS_RULE_BYTES*8192 as nx_size)) as *CssRule 324 let decls: *CssDeclaration = (sys_mmap(NX_CSS_DECLARATION_BYTES*32768 as nx_size)) as *CssDeclaration 325 let st: *CssParseState = (sys_mmap(128) as nx_size) as *CssParseState 326 nx_css_parse_state_init(st, cur, tok, rules, 8192, decls, 32768) 327 nx_css_parse(st) 328 let computed: *CssComputedDecl = (sys_mmap(NX_CSS_COMPUTED_DECL_BYTES*262144 as nx_size)) as *CssComputedDecl 329 let cb: *i64 = (sys_mmap(8)) as *i64 330 rh_cascade(buf, rules, st.rule_count, decls, st.decl_count, boxel, tree.count, tree, computed, 262144, cb) 331 let ncomp: i64 = cb[0] 332 rh_promote_display_kinds(tree, computed, ncomp, buf) // inline + display:flex/grid/block -> BLOCK (sized + painted) 333 334 let pbuf: *u8 = sys_mmap(256) 335 let ltable: *LayoutPropTable = (sys_mmap(NX_LAYOUT_PROP_TABLE_BYTES)) as *LayoutPropTable 336 nx_layout_prop_table_init(ltable, pbuf) 337 let resolve: *CssResolveCtx = (sys_mmap(NX_CSS_RESOLVE_CTX_BYTES)) as *CssResolveCtx 338 resolve.root_font_size_px = 16; resolve.parent_font_size_px = 16; resolve.parent_dimension_px = VW 339 let lctx: *LayoutCtx = (sys_mmap(NX_LAYOUT_CTX_BYTES)) as *LayoutCtx 340 nx_layout_ctx_init(lctx, tree, computed, ncomp, buf, VW, resolve) 341 let page_h: i64 = nx_layout_block_layout(lctx, ltable, 0) 342 343 page.buf = buf; page.tree = tree; page.computed = computed; page.ncomp = ncomp 344 page.page_h = page_h; page.bhref_off = bhoff; page.bhref_len = bhlen; page.ok = 1 345 return 0 346} 347 348// load the operator's OWN financial profile (from their intake survey) into the buyguard profile array. 349// This is the local browser on the operator's machine -> respondent "operator"; ip_get_pos falls back to 350// safe defaults for any field they have not entered yet (so the guard works before the survey is filled). 351func bg_load_profile(prof: *i64) -> i64 { 352 let P: *u8 = "knowledge/store/intake-\x00" as *u8 353 let who: *u8 = "operator\x00" as *u8 354 prof[BGW_AVAIL] = ip_get_pos(P, who, "available_cash\x00" as *u8, 50000) 355 prof[BGW_BAL] = ip_get_pos(P, who, "debt_balance\x00" as *u8, 2000000) 356 prof[BGW_APR] = mny_rate_from_pct(ip_get_pos(P, who, "debt_apr_pct\x00" as *u8, 24), 1) 357 prof[BGW_PAY] = ip_get_pos(P, who, "monthly_payment\x00" as *u8, 60000) 358 prof[BGW_WAGE] = ip_get_pos(P, who, "hourly_wage\x00" as *u8, 2500) 359 prof[BGW_GOAL] = ip_get_pos(P, who, "buffer_goal\x00" as *u8, 100000) 360 prof[BGW_THRESH] = ip_get_pos(P, who, "pause_threshold\x00" as *u8, 10000) 361 return 0 362} 363 364// ---- DOCUMENT OMNITOOL: the browser natively opens documents (.docx/.md/.csv/...) it navigates to ---- 365// A registered document extension diverts the fetched bytes through the nx_doc_view dispatch (data-driven 366// registry knowledge/doc_viewers.conf -> focused sovereign renderer organ), and the browser paints the 367// renderer's 0-JS HTML preview inline instead of dead-ending on a download. The renderers stay OUT of this 368// binary (fork-exec'd on demand) -- supporting more formats never grows the browser core. 369 370// lowercased extension of the URL's LAST path segment (after the host, before ?/#) into ext (NUL-term). 371// Returns 0 for: no path, no dot in the last segment, ext>12 chars, or html/htm (normal HTML pipeline). 372func br_doc_ext(url: *u8, ulen: i64, ext: *u8) -> i64 { 373 // end of path = first '?' or '#' 374 var pend: i64 = ulen 375 var i: i64 = 0 376 while i < ulen { 377 let c: i64 = url[i] & 0xff 378 if c == 63 { pend = i; i = ulen } else { if c == 35 { pend = i; i = ulen } else { i = i + 1 } } 379 } 380 // host starts after "://" (when present); a REAL path needs a '/' at/after hstart (bare hosts opt out) 381 var hstart: i64 = 0 382 var scanning: i64 = 1 383 i = 0 384 while scanning == 1 { 385 if i + 2 >= pend { scanning = 0 } else { 386 if url[i] == (58 as u8) { if url[i+1] == (47 as u8) { if url[i+2] == (47 as u8) { hstart = i + 3; scanning = 0 } } } 387 if scanning == 1 { i = i + 1 } 388 } 389 } 390 var slash: i64 = 0 - 1 391 var dot: i64 = 0 - 1 392 i = hstart 393 while i < pend { 394 let c: i64 = url[i] & 0xff 395 if c == 47 { slash = i; dot = 0 - 1 } else { if c == 46 { dot = i } } 396 i = i + 1 397 } 398 if slash < 0 { return 0 } 399 if dot < 0 { return 0 } 400 if dot <= slash { return 0 } 401 var k: i64 = 0 402 i = dot + 1 403 while i < pend { 404 if k >= 12 { return 0 } 405 var c2: i64 = url[i] & 0xff 406 if c2 >= 65 { if c2 <= 90 { c2 = c2 + 32 } } 407 ext[k] = c2 as u8 408 k = k + 1 409 i = i + 1 410 } 411 ext[k] = 0 as u8 412 if k == 0 { return 0 } 413 if k == 4 { if ext[0] == (104 as u8) { if ext[1] == (116 as u8) { if ext[2] == (109 as u8) { if ext[3] == (108 as u8) { return 0 } } } } } 414 if k == 3 { if ext[0] == (104 as u8) { if ext[1] == (116 as u8) { if ext[2] == (109 as u8) { return 0 } } } } 415 return k 416} 417 418// build the staging paths: pin = "/tmp/nx_bdoc_in.<ext>", pout = "/tmp/nx_bdoc_<ext>.html" 419func br_doc_paths(ext: *u8, extn: i64, pin: *u8, pout: *u8) -> i64 { 420 let a: *u8 = "/tmp/nx_bdoc_in.\x00" as *u8 421 var o: i64 = 0 422 while a[o] != 0 as u8 { pin[o] = a[o]; o = o + 1 } 423 var k: i64 = 0 424 while k < extn { pin[o + k] = ext[k]; k = k + 1 } 425 pin[o + k] = 0 as u8 426 let b: *u8 = "/tmp/nx_bdoc_\x00" as *u8 427 o = 0 428 while b[o] != 0 as u8 { pout[o] = b[o]; o = o + 1 } 429 k = 0 430 while k < extn { pout[o + k] = ext[k]; k = k + 1 } 431 o = o + k 432 let s: *u8 = ".html\x00" as *u8 433 k = 0 434 while s[k] != 0 as u8 { pout[o + k] = s[k]; k = k + 1 } 435 pout[o + k] = 0 as u8 436 return 0 437} 438 439// stage the fetched bytes and run the dispatch: 0 = converted (/tmp/nx_bdoc_<ext>.html written); 440// 3 = extension not registered; anything else = converter failure. All nonzero -> caller falls through. 441func br_doc_conv(ext: *u8, extn: i64, body: *u8, blen: i64, url: *u8) -> i64 { 442 let pin: *u8 = sys_mmap(64) 443 let pout: *u8 = sys_mmap(64) 444 br_doc_paths(ext, extn, pin, pout) 445 let fd: i64 = sys_openat_wr(pin, 0x1a4) 446 if fd < 0 { return 0 - 1 } 447 sys_write(fd, body, blen) 448 sys_close(fd) 449 let pid: i64 = sys_fork() 450 if pid == 0 { 451 let lfd: i64 = sys_openat_wr("/tmp/nx_bdoc.log\x00" as *u8, 0x1a4) 452 if lfd >= 0 { sys_dup3(lfd, 1, 0); sys_dup3(lfd, 2, 0) } 453 let argv: *i64 = sys_mmap(64) as *i64 454 argv[0] = "_offc/nx_doc_view.elf\x00" as *u8 as i64 455 argv[1] = pin as i64 456 argv[2] = pout as i64 457 argv[3] = url as i64 458 argv[4] = 0 459 let envp: *i64 = sys_mmap(16) as *i64 460 envp[0] = 0 461 sys_execve("_offc/nx_doc_view.elf\x00" as *u8, argv, envp) 462 sys_exit(127) 463 } 464 let st: *i64 = sys_mmap(16) as *i64 465 sys_wait4(pid, st, 0) 466 let sig: i64 = st[0] & 0x7f 467 if sig != 0 { return 128 + sig } 468 return (st[0] >> 8) & 0xff 469} 470 471// fetch <url> over our own DNS/TLS/HTTP, store the raw body in `page`, then lay it out at width `vw`. 472func load_page(url: *u8, ulen: i64, store: *TrustStore, cr: *u8, priv: *u8, page: *Page, vw: i64) -> i64 { 473 page.ok = 0 474 let now: i64 = sys_now_realtime_sec() 475 let cap: i64 = 8388608 476 let rbuf: *u8 = sys_mmap(cap) 477 // mutable current URL so we can FOLLOW REDIRECTS (apex->www, http->https, 30x Location) like a browser 478 let cururl: *u8 = sys_mmap(4096) 479 var curlen: i64 = ulen 480 if curlen > 4094 { curlen = 4094 } 481 var ci: i64 = 0 482 while ci < curlen { cururl[ci] = url[ci]; ci = ci + 1 } 483 cururl[curlen] = 0 as u8 484 let r: *i64 = nx_http_resp_alloc() 485 var n: i64 = 0 - 1 486 var pv: i64 = 0 - 1 487 var hops: i64 = 0 488 var go: i64 = 1 489 while go == 1 { 490 go = 0 491 n = 0 - 1 492 if br_is_plain_http(cururl, curlen) == 1 { n = br_http_fetch(cururl, curlen, rbuf, cap) } 493 else { n = nx_https_get(cururl, cr, priv, store, now, rbuf, cap) } 494 if n < 0 { return n } // propagate the specific failure code (-2 url, -3 connect/DNS, -4 handshake, -5 fetch) 495 pv = nx_http_response_parse(rbuf, n, r) 496 if pv == NX_HTTP_RESP_OK { if r[1] >= 300 { if r[1] < 400 { if hops < 6 { 497 let vo: *i64 = sys_mmap(16) as *i64 498 let vl: *i64 = sys_mmap(16) as *i64 499 if nx_http_response_header(rbuf, n, r, "location" as *u8, 8, vo, vl) == 1 { 500 let nb: *u8 = sys_mmap(4096) 501 let nl: *i64 = sys_mmap(16) as *i64 502 if nx_url_resolve(cururl, curlen, ((rbuf as i64) + vo[0]) as *u8, vl[0], nb, 4096, nl) == NX_URL_RESOLVE_OK { 503 var z: i64 = 0 504 while z < nl[0] { cururl[z] = nb[z]; z = z + 1 } 505 cururl[nl[0]] = 0 as u8; curlen = nl[0]; hops = hops + 1; go = 1 506 br_p(2, "nx_browser: redirect -> \x00" as *u8); sys_write(2, cururl, curlen); br_p(2, "\n\x00" as *u8) 507 } 508 } 509 } } } } 510 } 511 var body: *u8 = rbuf 512 var body_len: i64 = n 513 if pv == NX_HTTP_RESP_OK { 514 let bo: i64 = r[6]; let kind: i64 = r[8] 515 body = ((rbuf as i64) + bo) as *u8 516 body_len = n - bo 517 if kind == NX_HTTP_BODY_CONTENT_LENGTH { body_len = r[7]; if body_len > n - bo { body_len = n - bo } } 518 if kind == NX_HTTP_BODY_CHUNKED { let dcap: i64 = 8388608; let dbuf: *u8 = sys_mmap(dcap); let dl: i64 = nx_http_dechunk(body, n - bo, dbuf, dcap); if dl > 0 { body = dbuf; body_len = dl } } 519 } 520 // DOCUMENT OMNITOOL: registered doc extension -> convert via the nx_doc_view dispatch and paint the 521 // HTML preview inline (the browser opens .docx/.md/.csv/... natively). html/htm, unregistered exts and 522 // any converter failure fall through to the normal pipeline untouched -- graceful by construction. 523 let dext: *u8 = sys_mmap(32) 524 let dextn: i64 = br_doc_ext(cururl, curlen, dext) 525 if dextn > 0 { 526 let drc: i64 = br_doc_conv(dext, dextn, body, body_len, cururl) 527 if drc == 0 { 528 let pin2: *u8 = sys_mmap(64) 529 let pout2: *u8 = sys_mmap(64) 530 br_doc_paths(dext, dextn, pin2, pout2) 531 let dlp: *i64 = sys_mmap(16) as *i64 532 let dhtml: *u8 = sys_read_file(pout2, dlp) 533 if dlp[0] > 0 { 534 body = dhtml 535 body_len = dlp[0] 536 br_p(1, "nx_browser: doc omnitool rendered .\x00" as *u8) 537 sys_write(1, dext, dextn) 538 br_p(1, " inline\n\x00" as *u8) 539 } 540 } 541 } 542 // R-JS-DOMWRITE: if the page has an INLINE <script>, EXECUTE it against a mutable DOM and lay out the 543 // MUTATED html -- so JS-built content renders. RCDATA-safe round-trip; script-less pages skip this. 544 var rbody: *u8 = body 545 var rblen: i64 = body_len 546 if js_has_inline_script(body, body_len) == 1 { 547 let jscap: i64 = body_len + 1048576 548 let jsbuf: *u8 = sys_mmap(jscap) 549 let jslen: i64 = js_render_page(body, body_len, jsbuf, jscap) 550 if jslen > 0 { rbody = jsbuf; rblen = jslen } 551 } 552 // BUYGUARD: run the before-you-buy pass on the fetched page and splice the opportunity-cost overlay in 553 // as a top-of-page box (no price detected -> body returned untouched). Uses the operator's real numbers. 554 let bgprof: *i64 = sys_mmap(8 * 8) as *i64 555 bg_load_profile(bgprof) 556 let injcap: i64 = rblen + 65536 557 let injbuf: *u8 = sys_mmap(injcap) 558 let injlen: i64 = bgw_inject(rbody, rblen, bgprof, injbuf, injcap) 559 page.raw = injbuf; page.raw_len = injlen 560 return br_layout(page, vw) 561} 562 563// one toolbar button: a bordered face with a centered single-char label 564func br_button(c: *X11Conn, wid: i64, gc: i64, x: i64, label: *u8, fbody: i64) -> i64 { 565 nx_x11_set_gc_foreground(c, gc, 0x00B0B4BA); nx_x11_fill_rect(c, wid, gc, x, 8, 30, 28) // border 566 nx_x11_set_gc_foreground(c, gc, 0x00F6F8FA); nx_x11_fill_rect(c, wid, gc, x + 1, 9, 28, 26) // face 567 nx_x11_set_gc_font(c, gc, fbody); nx_x11_set_gc_foreground(c, gc, 0x00202124) 568 nx_x11_poly_text8(c, wid, gc, x + 11, 27, label, 1) 569 return 0 570} 571 572// the browser chrome: toolbar background + Back/Forward/Reload buttons + the address bar (URL display) 573func br_chrome(c: *X11Conn, wid: i64, gc: i64, win_w: i64, cur_url: *u8, ulen: i64, fbody: i64) -> i64 { 574 nx_x11_set_gc_foreground(c, gc, 0x00DDE1E6); nx_x11_fill_rect(c, wid, gc, 0, 0, win_w, NX_CHROME_H) 575 nx_x11_set_gc_foreground(c, gc, 0x00B0B4BA); nx_x11_fill_rect(c, wid, gc, 0, NX_CHROME_H - 1, win_w, 1) 576 br_button(c, wid, gc, 8, "<\x00" as *u8, fbody) 577 br_button(c, wid, gc, 44, ">\x00" as *u8, fbody) 578 br_button(c, wid, gc, 80, "R\x00" as *u8, fbody) 579 // address bar field (bordered white box) + the current URL 580 let bx: i64 = 118 581 let bw: i64 = win_w - bx - 10 582 nx_x11_set_gc_foreground(c, gc, 0x00B0B4BA); nx_x11_fill_rect(c, wid, gc, bx, 8, bw, 28) 583 nx_x11_set_gc_foreground(c, gc, 0x00FFFFFF); nx_x11_fill_rect(c, wid, gc, bx + 1, 9, bw - 2, 26) 584 var maxc: i64 = (bw - 14) / 7 585 if maxc < 0 { maxc = 0 } 586 var n: i64 = ulen 587 if n > maxc { n = maxc } 588 if n > 250 { n = 250 } 589 nx_x11_set_gc_font(c, gc, fbody); nx_x11_set_gc_foreground(c, gc, 0x00202124) 590 if n > 0 { nx_x11_poly_text8(c, wid, gc, bx + 7, 27, cur_url, n) } 591 return 0 592} 593 594// chrome hit-test: 1=Back, 2=Forward, 3=Reload, 0=none (call only when click y < NX_CHROME_H) 595func br_chrome_click(ex: i64) -> i64 { 596 if ex >= 8 { if ex < 38 { return 1 } } 597 if ex >= 44 { if ex < 74 { return 2 } } 598 if ex >= 80 { if ex < 110 { return 3 } } 599 return 0 600} 601 602// INHERITED color resolve (CSS: `color` inherits) -- twin of nx_browser_render.nx's helper; walk up the 603// parent chain to the nearest computed color decl (author body/.doc colors now reach p/li/td text). 604func br_comp_color_inh(src: *u8, computed: *CssComputedDecl, ncomp: i64, tree: *LayoutTree, box_idx: i64) -> i64 { 605 var cur: i64 = box_idx 606 var guard: i64 = 0 607 while cur >= 0 { 608 if guard > 64 { return 0 - 1 } 609 guard = guard + 1 610 let c: i64 = br_comp_color(src, computed, ncomp, cur, "color\x00" as *u8, 5) 611 if c >= 0 { return c } 612 let b: *LayoutBox = ((tree.boxes as i64) + cur * NX_LAYOUT_BOX_BYTES) as *LayoutBox 613 cur = b.parent_idx 614 } 615 return 0 - 1 616} 617 618// background-color, else the `background` shorthand's single-hex value (twin of nx_browser_render.nx). 619func br_comp_bg(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64) -> i64 { 620 let c: i64 = br_comp_color(src, computed, ncomp, box_idx, "background-color\x00" as *u8, 16) 621 if c >= 0 { return c } 622 return br_comp_color(src, computed, ncomp, box_idx, "background\x00" as *u8, 10) 623} 624 625// `border` shorthand: parse "<w>px ... #rrggbb" from the swept value span (twin of nx_browser_render.nx). 626func br_comp_border(src: *u8, computed: *CssComputedDecl, ncomp: i64, box_idx: i64, wout: *i64) -> i64 { 627 var j: i64 = ncomp - 1 628 while j >= 0 { 629 let cd: *CssComputedDecl = ((computed as i64) + j * NX_CSS_COMPUTED_DECL_BYTES) as *CssComputedDecl 630 if cd.element_idx == box_idx { if cd.prop_len == 6 { 631 let P: *u8 = "border\x00" as *u8 632 var ok: i64 = 1 633 var k: i64 = 0 634 while k < 6 { if (src[cd.prop_off+k]&0xff) != (P[k]&0xff) { ok=0; k=6 } else { k=k+1 } } 635 if ok == 1 { 636 var wv: i64 = 0 637 var i2: i64 = cd.val_off 638 let e2: i64 = cd.val_off + cd.val_len 639 while i2 < e2 { let ch: i64 = src[i2]&0xff; if ch>=48 { if ch<=57 { wv=wv*10+(ch-48); i2=i2+1 } else { i2=e2 } } else { i2=e2 } } 640 if wv <= 0 { wv = 1 } 641 if wv > 8 { wv = 8 } 642 var hp: i64 = 0 - 1 643 var i3: i64 = cd.val_off 644 while i3 < e2 { if src[i3]==(35 as u8) { hp=i3; i3=e2 } else { i3=i3+1 } } 645 if hp >= 0 { 646 let col: *CssColor = (sys_mmap(NX_CSS_COLOR_BYTES as nx_size)) as *CssColor 647 if nx_css_color_decode(src, hp+1, 6, col) == 1 { 648 wout[0] = wv 649 return (col.r << 16) | (col.g << 8) | col.b 650 } 651 } 652 return 0 - 1 653 } 654 } } 655 j = j - 1 656 } 657 return 0 - 1 658} 659 660func br_draw(c: *X11Conn, wid: i64, gc: i64, paper: i64, page: *Page, win_w: i64, win_h: i64, scroll_y: i64, 661 fbody: i64, fh2: i64, fh1: i64, cur_url: *u8, ulen: i64) -> i64 { 662 let tree: *LayoutTree = page.tree 663 let src: *u8 = page.buf 664 let computed: *CssComputedDecl = page.computed 665 let ncomp: i64 = page.ncomp 666 let decbuf: *u8 = sys_mmap(1100) // reused per text box: decode HTML entities before painting 667 nx_x11_fill_rect(c, wid, paper, 0, 0, win_w, win_h) 668 var i: i64 = 0 669 let bwp: *i64 = sys_mmap(16) as *i64 // border width out-slot (hoisted: no mmap in the loop) 670 while i < tree.count { 671 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 672 if b.kind == NX_LAYOUT_BOX_BLOCK { if b.w > 0 { if b.h > 0 { 673 let bg: i64 = br_comp_bg(src, computed, ncomp, i) 674 if bg >= 0 { if bg != 0x00FFFFFF { 675 let sy: i64 = b.y - scroll_y + NX_CHROME_H 676 if sy + b.h >= NX_CHROME_H { if sy < win_h { nx_x11_set_gc_foreground(c, gc, bg); nx_x11_fill_rect(c, wid, gc, b.x, sy, b.w, b.h) } } 677 } } 678 // CSS borders (the `border` shorthand): stroke the 4 edges 679 let bc: i64 = br_comp_border(src, computed, ncomp, i, bwp) 680 if bc >= 0 { 681 let sy3: i64 = b.y - scroll_y + NX_CHROME_H 682 if sy3 + b.h >= NX_CHROME_H { if sy3 < win_h { 683 let bw2: i64 = bwp[0] 684 nx_x11_set_gc_foreground(c, gc, bc) 685 nx_x11_fill_rect(c, wid, gc, b.x, sy3, b.w, bw2) 686 nx_x11_fill_rect(c, wid, gc, b.x, sy3 + b.h - bw2, b.w, bw2) 687 nx_x11_fill_rect(c, wid, gc, b.x, sy3, bw2, b.h) 688 nx_x11_fill_rect(c, wid, gc, b.x + b.w - bw2, sy3, bw2, b.h) 689 } } 690 } 691 } } } 692 i = i + 1 693 } 694 i = 0 695 while i < tree.count { 696 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 697 if b.kind == NX_LAYOUT_BOX_TEXT { if b.text_len > 0 { 698 let sy: i64 = b.y - scroll_y + NX_CHROME_H 699 if sy >= NX_CHROME_H - 40 { if sy < win_h { 700 let par: i64 = b.parent_idx 701 let fs: i64 = br_comp_int(src, computed, ncomp, par, "font-size\x00" as *u8, 9, 16) 702 var fid: i64 = fbody; var asc: i64 = 12 703 if fs > 18 { fid = fh2; asc = 16 } 704 if fs > 28 { fid = fh1; asc = 19 } 705 var col: i64 = br_comp_color_inh(src, computed, ncomp, tree, par) // CSS-inherited color 706 if col < 0 { col = 0 } 707 nx_x11_set_gc_font(c, gc, fid); nx_x11_set_gc_foreground(c, gc, col) 708 var nn: i64 = b.text_len 709 if nn > 200 { nn = 200 } 710 let dn: i64 = nx_html_decode_entities(src, b.text_off, nn, decbuf, 1024) 711 nx_x11_poly_text8(c, wid, gc, b.x + 4, sy + asc, decbuf, dn) 712 if col == 0x3366cc { nx_x11_fill_rect(c, wid, gc, b.x + 4, sy + asc + 2, b.w, 1) } // underline links 713 } } 714 } } 715 i = i + 1 716 } 717 br_chrome(c, wid, gc, win_w, cur_url, ulen, fbody) 718 nx_x11_flush_via_get_input_focus(c) 719 return 0 720} 721 722func br_color_from_int(c: i64) -> *CssColor { 723 let col: *CssColor = sys_mmap(NX_CSS_COLOR_BYTES) as *CssColor 724 col.r = (c >> 16) & 0xff 725 col.g = (c >> 8) & 0xff 726 col.b = c & 0xff 727 col.a = 255 728 return col 729} 730 731// 2x-scaled 5x7 text for the SHOT framebuffer (each glyph pixel -> 2x2 block = 10x14 glyph, 9px advance) 732// so the capture ~matches the live window's 9x15 font + the 9px layout metric (faithful self-verify). 733func br_paint_text2x(fb: *Framebuffer, x: i64, y: i64, text: *u8, text_len: i64, color: *CssColor) -> i64 { 734 var i: i64 = 0 735 while i < text_len { 736 let ch: i64 = text[i] & 0xff 737 let cx: i64 = x + i * 9 738 var row: i64 = 0 739 while row < 7 { 740 let bits: i64 = nx_font_5x7_glyph_row(ch, row) 741 if bits >= 0 { 742 var col: i64 = 0 743 while col < 5 { 744 if ((bits >> (4 - col)) & 1) == 1 { nx_paint_solid_rect(fb, cx + col * 2, y + row * 2, 2, 2, color) } 745 col = col + 1 746 } 747 } 748 row = row + 1 749 } 750 i = i + 1 751 } 752 return 0 753} 754 755// OFF-SCREEN render: rasterize the same page into an in-memory RGBA framebuffer (no X server) so the 756// render can be captured to a PNG and self-reviewed. Mirrors br_draw; uses the 5x7 bitmap-font paint 757// primitives (so headings aren't scaled here -- this is for verifying colors/layout/chrome, not pixel-parity). 758func br_draw_fb(fb: *Framebuffer, page: *Page, win_w: i64, cur_url: *u8, ulen: i64) -> i64 { 759 let tree: *LayoutTree = page.tree 760 let src: *u8 = page.buf 761 let computed: *CssComputedDecl = page.computed 762 let ncomp: i64 = page.ncomp 763 let fdec: *u8 = sys_mmap(1100) // reused per text box: decode HTML entities before painting 764 let ffold: *u8 = sys_mmap(1100) // reused: fold decoded UTF-8 -> ASCII (accents+punct) for the 5x7 font 765 nx_paint_solid_rect(fb, 0, 0, win_w, fb.height, br_color_from_int(0x00FFFFFF)) 766 var i: i64 = 0 767 let bwp2: *i64 = sys_mmap(16) as *i64 // border width out-slot (hoisted: no mmap in the loop) 768 while i < tree.count { 769 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 770 if b.kind == NX_LAYOUT_BOX_BLOCK { if b.w > 0 { if b.h > 0 { 771 let bg: i64 = br_comp_bg(src, computed, ncomp, i) 772 if bg >= 0 { if bg != 0x00FFFFFF { nx_paint_solid_rect(fb, b.x, b.y + NX_CHROME_H, b.w, b.h, br_color_from_int(bg)) } } 773 // CSS borders (the `border` shorthand): stroke the 4 edges 774 let bc: i64 = br_comp_border(src, computed, ncomp, i, bwp2) 775 if bc >= 0 { 776 let bw2: i64 = bwp2[0] 777 nx_paint_solid_rect(fb, b.x, b.y + NX_CHROME_H, b.w, bw2, br_color_from_int(bc)) 778 nx_paint_solid_rect(fb, b.x, b.y + NX_CHROME_H + b.h - bw2, b.w, bw2, br_color_from_int(bc)) 779 nx_paint_solid_rect(fb, b.x, b.y + NX_CHROME_H, bw2, b.h, br_color_from_int(bc)) 780 nx_paint_solid_rect(fb, b.x + b.w - bw2, b.y + NX_CHROME_H, bw2, b.h, br_color_from_int(bc)) 781 } 782 } } } 783 i = i + 1 784 } 785 i = 0 786 while i < tree.count { 787 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 788 if b.kind == NX_LAYOUT_BOX_TEXT { if b.text_len > 0 { 789 let par: i64 = b.parent_idx 790 var col: i64 = br_comp_color_inh(src, computed, ncomp, tree, par) // CSS-inherited color 791 if col < 0 { col = 0 } 792 var nn: i64 = b.text_len 793 if nn > 200 { nn = 200 } 794 let dn: i64 = nx_html_decode_entities(src, b.text_off, nn, fdec, 1024) 795 let fdn: i64 = nx_fold_ascii(fdec, 0, dn, ffold, 1100) // UTF-8 -> ASCII: punctuation + accents render, non-Latin skips cleanly (no blank gaps) 796 br_paint_text2x(fb, b.x + 4, b.y + NX_CHROME_H + 1, ffold, fdn, br_color_from_int(col)) 797 if col == 0x3366cc { nx_paint_solid_rect(fb, b.x + 4, b.y + NX_CHROME_H + 16, b.w, 1, br_color_from_int(col)) } // underline links 798 } } 799 i = i + 1 800 } 801 // chrome 802 nx_paint_solid_rect(fb, 0, 0, win_w, NX_CHROME_H, br_color_from_int(0x00DDE1E6)) 803 nx_paint_solid_rect(fb, 0, NX_CHROME_H - 1, win_w, 1, br_color_from_int(0x00B0B4BA)) 804 nx_paint_solid_rect(fb, 8, 8, 30, 28, br_color_from_int(0x00F6F8FA)) 805 nx_paint_solid_rect(fb, 44, 8, 30, 28, br_color_from_int(0x00F6F8FA)) 806 nx_paint_solid_rect(fb, 80, 8, 30, 28, br_color_from_int(0x00F6F8FA)) 807 br_paint_text2x(fb, 18, 16, "<\x00" as *u8, 1, br_color_from_int(0x00202124)) 808 br_paint_text2x(fb, 54, 16, ">\x00" as *u8, 1, br_color_from_int(0x00202124)) 809 br_paint_text2x(fb, 90, 16, "R\x00" as *u8, 1, br_color_from_int(0x00202124)) 810 let bx: i64 = 118 811 nx_paint_solid_rect(fb, bx, 8, win_w - bx - 10, 28, br_color_from_int(0x00FFFFFF)) 812 var un: i64 = ulen 813 if un > 96 { un = 96 } 814 br_paint_text2x(fb, bx + 6, 14, cur_url, un, br_color_from_int(0x00202124)) 815 return 0 816} 817 818// render the laid-out page to a 1000xSH RGBA framebuffer and write it as a PNG at `path`. SH tracks the FULL 819// page height (chrome + laid-out content) so a self-verify shot captures the whole page, not a fixed top crop 820// (the 1000px crop made tall pages -- e.g. /office at 2765px -- look empty below the fold). Clamped for safety. 821func br_shot_png(page: *Page, cur_url: *u8, ulen: i64, path: *u8) -> i64 { 822 let SW: i64 = 1000 823 var SH: i64 = page.page_h + NX_CHROME_H + 40 824 if SH < 400 { SH = 400 } 825 if SH > 6000 { SH = 6000 } 826 let fb: *Framebuffer = sys_mmap(NX_FRAMEBUFFER_BYTES) as *Framebuffer 827 let px: *u8 = sys_mmap(SW * SH * 4 + 64) 828 nx_framebuffer_init(fb, px, SW, SH) 829 br_draw_fb(fb, page, SW, cur_url, ulen) 830 let rgb: *u8 = sys_mmap(SW * SH * 3 + 64) 831 var p: i64 = 0 832 while p < SW * SH { 833 rgb[p * 3 + 0] = px[p * 4 + 0] 834 rgb[p * 3 + 1] = px[p * 4 + 1] 835 rgb[p * 3 + 2] = px[p * 4 + 2] 836 p = p + 1 837 } 838 nx_png_write_rgb(path, rgb, SW, SH) 839 return 0 840} 841 842// hit-test: find a box with an href whose rect contains doc-point (dx,dy). 1 + sets off/len, else 0. 843// CONTAINER ANCHORS (2026-07-03): an <a> wrapping block children lays out with a ZERO own-rect -- 844// hit-test the UNION of its descendants' rects (mirrors nx_browser_render.nx; found by 845// nx_aw_browser_gate on the real andelinwest card links). Purely additive. 846func br_hit_link(page: *Page, dx: i64, dy: i64, off: *i64, len: *i64) -> i64 { 847 let tree: *LayoutTree = page.tree 848 var i: i64 = 0 849 while i < tree.count { 850 if page.bhref_off[i] >= 0 { 851 let b: *LayoutBox = ((tree.boxes as i64) + i * NX_LAYOUT_BOX_BYTES) as *LayoutBox 852 var bx: i64 = b.x 853 var by: i64 = b.y 854 var bw: i64 = b.w 855 var bh: i64 = b.h 856 if bh <= 0 { 857 // SIBLING-SPAN effective rect (mirrors nx_browser_render.nx): union the rects of the 858 // boxes AFTER i while they stay inside i's parent's subtree, stopping at the next 859 // href box -- the layout flattens container-anchor children to siblings. 860 let par: i64 = b.parent_idx 861 var x0: i64 = 0 862 var y0: i64 = 0 863 var x1: i64 = 0 864 var y1: i64 = 0 865 var any: i64 = 0 866 var j: i64 = i + 1 867 var span: i64 = 1 868 while span == 1 { 869 if j >= tree.count { span = 0 } 870 else { if page.bhref_off[j] >= 0 { span = 0 } 871 else { 872 let cb: *LayoutBox = ((tree.boxes as i64) + j * NX_LAYOUT_BOX_BYTES) as *LayoutBox 873 var p: i64 = cb.parent_idx 874 var hop: i64 = 0 875 var inside: i64 = 0 876 while hop < 64 { 877 if p == (0 - 1) { hop = 64 } 878 else { if p == par { inside = 1; hop = 64 } 879 else { let pb: *LayoutBox = ((tree.boxes as i64) + p * NX_LAYOUT_BOX_BYTES) as *LayoutBox; p = pb.parent_idx; hop = hop + 1 } } 880 } 881 if inside == 0 { span = 0 } 882 else { 883 if cb.w > 0 { if cb.h > 0 { 884 if any == 0 { x0 = cb.x; y0 = cb.y; x1 = cb.x + cb.w; y1 = cb.y + cb.h; any = 1 } 885 else { 886 if cb.x < x0 { x0 = cb.x } 887 if cb.y < y0 { y0 = cb.y } 888 if cb.x + cb.w > x1 { x1 = cb.x + cb.w } 889 if cb.y + cb.h > y1 { y1 = cb.y + cb.h } 890 } 891 } } 892 j = j + 1 893 } 894 } } 895 } 896 if any == 1 { bx = x0; by = y0; bw = x1 - x0; bh = y1 - y0 } 897 } 898 var hitw: i64 = bw // layout metric now matches the 9px render font, so b.w is the true width 899 if hitw < 12 { hitw = 12 } 900 if dx >= bx { if dx < bx + hitw { if dy >= by { if dy < by + bh { 901 off[0] = page.bhref_off[i]; len[0] = page.bhref_len[i]; return 1 902 } } } } 903 } 904 i = i + 1 905 } 906 return 0 907} 908 909// ---- sovereign-game pages (nishi-game marker) -> run the NATIVE client, zero wasm ---- 910// Extract the `<meta name="nishi-game" content="...">` organ name from raw page bytes. 911// SECURITY (rule 12): the value is PAGE-CONTROLLED and becomes an exec path, so this is strict 912// fail-closed validation, not parsing: the name must be nx_[a-z0-9_]{1,60} and anything else -- 913// a slash, a dot, an uppercase byte, overlength -- REJECTS the whole marker (no truncate-accept). 914func br_game_organ(raw: *u8, n: i64, out: *u8) -> i64 { 915 let pat: *u8 = "name=\"nishi-game\"\x00" as *u8 916 let cpat: *u8 = "content=\"\x00" as *u8 917 var i: i64 = 0 918 while i < n - 17 { 919 var m: i64 = 0 920 while m < 17 { if (raw[i+m] & 0xff) != (pat[m] & 0xff) { m = 99 } else { m = m + 1 } } 921 if m == 17 { 922 // find content=" within the next 80 bytes 923 var j: i64 = i + 17 924 let jend: i64 = i + 97 925 while j < jend { 926 if j < n - 9 { 927 var c: i64 = 0 928 while c < 9 { if (raw[j+c] & 0xff) != (cpat[c] & 0xff) { c = 99 } else { c = c + 1 } } 929 if c == 9 { 930 var k: i64 = 0 931 var p: i64 = j + 9 932 var badc: i64 = 0 933 var closed: i64 = 0 934 while closed == 0 { 935 if p >= n { badc = 1; closed = 1 } 936 if closed == 0 { 937 let ch: i64 = raw[p] & 0xff 938 if ch == 34 { closed = 1 } 939 if closed == 0 { 940 var okc: i64 = 0 941 if ch >= 97 { if ch <= 122 { okc = 1 } } 942 if ch >= 48 { if ch <= 57 { okc = 1 } } 943 if ch == 95 { okc = 1 } 944 if okc == 0 { badc = 1 } 945 if k >= 60 { badc = 1 } 946 if badc == 0 { out[k] = ch as u8; k = k + 1 } 947 if badc == 1 { closed = 1 } 948 p = p + 1 949 } 950 } 951 } 952 if badc == 1 { return 0 } 953 if k < 4 { return 0 } 954 if (out[0] & 0xff) != 110 { return 0 } // n 955 if (out[1] & 0xff) != 120 { return 0 } // x 956 if (out[2] & 0xff) != 95 { return 0 } // _ 957 out[k] = 0 as u8 958 return k 959 } 960 } 961 j = j + 1 962 } 963 return 0 964 } 965 i = i + 1 966 } 967 return 0 968} 969 970// fork+exec the native client `_build/<name>.sov.elf` (name pre-validated by br_game_organ). 971// selftest=1: pass "selftest" and WAIT, returning the child's exit code (the chain proof). 972// selftest=0: fire-and-forget (the game gets its own window; zombie reaped at browser exit). 973func br_launch_game(name: *u8, nlen: i64, selftest: i64) -> i64 { 974 let path: *u8 = sys_mmap(128) 975 let pre: *u8 = "_build/\x00" as *u8 976 var k: i64 = 0 977 while k < 7 { path[k] = pre[k]; k = k + 1 } 978 var q: i64 = 0 979 while q < nlen { path[7+q] = name[q]; q = q + 1 } 980 let suf: *u8 = ".sov.elf\x00" as *u8 981 var t: i64 = 0 982 while t < 9 { path[7+nlen+t] = suf[t]; t = t + 1 } 983 let av: *i64 = sys_mmap(32) as *i64 984 av[0] = path as i64 985 av[1] = 0 986 av[2] = 0 987 if selftest == 1 { av[1] = ("selftest\x00" as *u8) as i64 } 988 let ep: *i64 = sys_mmap(16) as *i64 989 ep[0] = 0 990 let pid: i64 = sys_fork() 991 if pid == 0 { 992 sys_execve(path, av, ep) 993 sys_exit(127) 994 } 995 if pid < 0 { return 0 - 1 } 996 if selftest == 1 { 997 let st: *i64 = sys_mmap(16) as *i64 998 sys_wait4(pid, st, 0) 999 return (st[0] >> 8) & 0xff 1000 } 1001 return 0 1002} 1003 1004func main() -> i64 { 1005 nx_signal_ignore(NX_SIGPIPE) // a server reset mid-write must NOT kill us (was exit 141); fetch fails gracefully 1006 let lp: *i64 = sys_mmap(16) as *i64 1007 let urlraw: *u8 = sys_read_file("_url.txt\x00" as *u8, lp) 1008 var ulen: i64 = *lp 1009 if ulen <= 0 { br_p(2, "nx_browser: no _url.txt\n\x00" as *u8); return 1 } 1010 var trimming: i64 = 1 1011 while trimming == 1 { if ulen <= 0 { trimming = 0 } else { let ch: i64 = urlraw[ulen-1]&0xff; if ch==10 { ulen=ulen-1 } else { if ch==13 { ulen=ulen-1 } else { if ch==32 { ulen=ulen-1 } else { if ch==9 { ulen=ulen-1 } else { trimming=0 } } } } } } 1012 let URLCAP: i64 = 4096 1013 let cur_url: *u8 = sys_mmap(URLCAP) 1014 var u: i64 = 0 1015 while u < ulen { cur_url[u]=urlraw[u]; u=u+1 } 1016 cur_url[ulen] = 0 as u8 1017 1018 var tr: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt\x00" as *u8, 300, 4194304) 1019 if tr <= 0 { tr = nx_trust_store_load_from_certdata("/tmp/mozilla_certdata.txt\x00" as *u8, 300, 4194304) } 1020 if tr <= 0 { br_p(2, "nx_browser: CA bundle load failed\n\x00" as *u8); return 3 } 1021 let store: *TrustStore = tr as *TrustStore 1022 let cr: *u8 = sys_mmap(32); let priv: *u8 = sys_mmap(32) 1023 let ufd: i64 = sys_openat_rd("/dev/urandom\x00" as *u8) 1024 if ufd >= 0 { sys_read(ufd, cr, 32); sys_read(ufd, priv, 32); sys_close(ufd) } else { var z: i64=0; while z<32 { cr[z]=(0xC0+z) as u8; priv[z]=(0xA0+z) as u8; z=z+1 } } 1025 1026 let page: *Page = sys_mmap(NX_PAGE_BYTES) as *Page 1027 var WW: i64 = 1000 1028 var WH: i64 = 760 1029 1030 // SITE-TEST BATCH: if /tmp/nxsites exists (one URL per line), fetch+layout each through the REAL 1031 // pipeline and print per-site metrics (box count = did real content render; FETCH-FAIL = TLS/network/ 1032 // redirect), then exit. This is how we measure whether the render fixes generalize across the web. 1033 let stfd: i64 = sys_openat_rd("/tmp/nxsites\x00" as *u8) 1034 if stfd >= 0 { 1035 sys_close(stfd) 1036 let slp: *i64 = sys_mmap(16) as *i64 1037 let sbuf: *u8 = sys_read_file("/tmp/nxsites\x00" as *u8, slp) 1038 let sblen: i64 = slp[0] 1039 let one: *u8 = sys_mmap(URLCAP) 1040 var si: i64 = 0 1041 var idx: i64 = 0 1042 while si < sblen { 1043 var k: i64 = 0 1044 var go: i64 = 1 1045 while go == 1 { 1046 if si >= sblen { go = 0 } 1047 else { 1048 let ch: i64 = sbuf[si] & 0xff 1049 si = si + 1 1050 if ch == 10 { go = 0 } else { if ch != 13 { if k < URLCAP - 1 { one[k] = ch as u8; k = k + 1 } } } 1051 } 1052 } 1053 one[k] = 0 as u8 1054 if k > 0 { 1055 idx = idx + 1 1056 let rcL: i64 = load_page(one, k, store, cr, priv, page, WW) 1057 br_p(1, "SITETEST n=\x00" as *u8); br_n(1, idx) 1058 if rcL == 0 { br_p(1, " boxes=\x00" as *u8); br_n(1, page.tree.count); br_p(1, " page_h=\x00" as *u8); br_n(1, page.page_h) } 1059 else { br_p(1, " FETCH-FAIL rc=\x00" as *u8); br_n(1, rcL) } 1060 br_p(1, " url=\x00" as *u8); sys_write(1, one, k); br_p(1, "\n\x00" as *u8) 1061 } 1062 } 1063 br_p(1, "SITETEST done\n\x00" as *u8) 1064 return 0 1065 } 1066 1067 br_p(1, "nx_browser: fetching \x00" as *u8); sys_write(1, cur_url, ulen); br_p(1, "\n\x00" as *u8) 1068 if load_page(cur_url, ulen, store, cr, priv, page, WW) < 0 { br_p(2, "nx_browser: initial fetch failed\n\x00" as *u8); return 4 } 1069 if page.ok != 1 { return 4 } 1070 br_p(1, "nx_browser: laid out \x00" as *u8); br_n(1, page.tree.count); br_p(1, " boxes, page_h=\x00" as *u8); br_n(1, page.page_h); br_p(1, "\n\x00" as *u8) 1071 1072 // sovereign-game page? (nishi-game marker) -> the NATIVE client replaces the wasm entirely 1073 let gname: *u8 = sys_mmap(80) 1074 var glen: i64 = br_game_organ(page.raw, page.raw_len, gname) 1075 if glen > 0 { 1076 br_p(1, "nx_browser: SOVEREIGN GAME page -- press g to play NATIVE (\x00" as *u8) 1077 sys_write(1, gname, glen); br_p(1, "), zero wasm\n\x00" as *u8) 1078 } 1079 // chain-proof mode: knowledge/status/nxgame_chain present -> fetch->detect->exec selftest->exit 1080 let chfd: i64 = sys_openat_rd("knowledge/status/nxgame_chain\x00" as *u8) 1081 if chfd >= 0 { 1082 sys_close(chfd) 1083 if glen <= 0 { br_p(1, "NATIVE-CHAIN no-marker rc=90\n\x00" as *u8); return 90 } 1084 let crc: i64 = br_launch_game(gname, glen, 1) 1085 br_p(1, "NATIVE-CHAIN organ=\x00" as *u8); sys_write(1, gname, glen) 1086 br_p(1, " rc=\x00" as *u8); br_n(1, crc); br_p(1, "\n\x00" as *u8) 1087 return crc 1088 } 1089 1090 // SHOT MODE (self-verify, no X): if /tmp/nxshot exists, rasterize the page to an in-memory 1091 // framebuffer and write a PNG, then exit. Works headless -- sidesteps WSLg's GetImage limits. 1092 let shotfd: i64 = sys_openat_rd("/tmp/nxshot\x00" as *u8) 1093 if shotfd >= 0 { 1094 sys_close(shotfd) 1095 br_shot_png(page, cur_url, br_slen(cur_url), "/tmp/nxbrowser.png\x00" as *u8) 1096 br_p(1, "nx_browser: shot -> /tmp/nxbrowser.png\n\x00" as *u8) 1097 return 0 1098 } 1099 1100 let rc: i64 = nx_x11_connect_local(0) 1101 if rc < 0 { br_p(2, "nx_browser: no X display\n\x00" as *u8); return 6 } 1102 let cc: *X11Conn = rc as *X11Conn 1103 let mask: i64 = NX_X11_EVENT_EXPOSURE | NX_X11_EVENT_KEY_PRESS | NX_X11_EVENT_BUTTON_PRESS | NX_X11_EVENT_STRUCTURE 1104 let wid: i64 = nx_x11_create_window(cc, 40, 40, WW, WH, 0x00FFFFFF, mask) 1105 if wid < 0 { return 7 } 1106 let title: *u8 = "Nishi Browser (LIVE, sovereign) -- click links, b=back, m=media, q=quit\x00" 1107 nx_x11_set_wm_name(cc, wid, title, br_slen(title)) 1108 let gc: i64 = nx_x11_create_gc(cc, wid, 0x00000000) 1109 let paper: i64 = nx_x11_create_gc(cc, wid, 0x00FFFFFF) 1110 let fbody: i64 = nx_x11_open_font(cc, "9x15\x00" as *u8, 4) 1111 let fh2: i64 = nx_x11_open_font(cc, "10x20\x00" as *u8, 5) 1112 let fh1: i64 = nx_x11_open_font(cc, "12x24\x00" as *u8, 5) 1113 if nx_x11_map_window(cc, wid) < 0 { return 8 } 1114 nx_x11_flush_via_get_input_focus(cc) 1115 br_p(1, "nx_browser: WINDOW OPEN -- click a link to navigate, b=back, m=media sources, scroll wheel/arrows, q quits\n\x00" as *u8) 1116 1117 // history stacks of URLs (each a fixed URLCAP slot): back + forward 1118 let HISTMAX: i64 = 64 1119 let hist: *u8 = sys_mmap(URLCAP * HISTMAX) 1120 let histlen: *i64 = sys_mmap(8 * HISTMAX) as *i64 1121 var histn: i64 = 0 1122 let fhist: *u8 = sys_mmap(URLCAP * HISTMAX) 1123 let fhistlen: *i64 = sys_mmap(8 * HISTMAX) as *i64 1124 var fhistn: i64 = 0 1125 1126 var scroll_y: i64 = 0 1127 var max_scroll: i64 = page.page_h - (WH - NX_CHROME_H) 1128 if max_scroll < 0 { max_scroll = 0 } 1129 br_draw(cc, wid, gc, paper, page, WW, WH, scroll_y, fbody, fh2, fh1, cur_url, br_slen(cur_url)) 1130 let ev: *u8 = sys_mmap(64) 1131 let hoff: *i64 = sys_mmap(8) as *i64; let hlen2: *i64 = sys_mmap(8) as *i64 1132 let absbuf: *u8 = sys_mmap(URLCAP); let abslen: *i64 = sys_mmap(8) as *i64 1133 var running: i64 = 1 1134 while running == 1 { 1135 if nx_x11_read_event(cc, ev) != 0 { running = 0 } 1136 else { 1137 let kind: i64 = (ev[0] & 0x7f) as i64 1138 var dirty: i64 = 0 1139 var navurl: i64 = 0 // 1 if cur_url changed -> reload 1140 if kind == 12 { dirty = 1 } 1141 if kind == 22 { // ConfigureNotify: window resized -> reflow 1142 let nw: i64 = ((ev[20]&0xff) | ((ev[21]&0xff)<<8)) as i64 1143 let nh: i64 = ((ev[22]&0xff) | ((ev[23]&0xff)<<8)) as i64 1144 if nw > 0 { if nh > 0 { 1145 WH = nh 1146 if nw != WW { 1147 WW = nw 1148 br_layout(page, WW) // re-lay-out at the new width (NO refetch) 1149 max_scroll = page.page_h - (WH - NX_CHROME_H) 1150 if max_scroll < 0 { max_scroll = 0 } 1151 } 1152 dirty = 1 1153 } } 1154 } 1155 if kind == 4 { 1156 let btn: i64 = (ev[1] & 0xff) as i64 1157 if btn == 5 { scroll_y = scroll_y + 120; dirty = 1 } 1158 if btn == 4 { scroll_y = scroll_y - 120; dirty = 1 } 1159 if btn == 1 { 1160 // left click: window coords in ev[24..27]=x, ev[26..]=y per X event layout (event-x at 24, event-y at 26) 1161 let ex: i64 = ((ev[24]&0xff) | ((ev[25]&0xff)<<8)) as i64 1162 let ey: i64 = ((ev[26]&0xff) | ((ev[27]&0xff)<<8)) as i64 1163 if ey < NX_CHROME_H { 1164 // chrome click: Back / Forward / Reload 1165 let act: i64 = br_chrome_click(ex) 1166 if act == 1 { if histn > 0 { 1167 if fhistn < HISTMAX { var z: i64=0; let cl: i64=br_slen(cur_url); while z<cl { fhist[fhistn*URLCAP+z]=cur_url[z]; z=z+1 } fhistlen[fhistn]=cl; fhistn=fhistn+1 } 1168 histn = histn - 1; let cl2: i64=histlen[histn]; var z2: i64=0; while z2<cl2 { cur_url[z2]=hist[histn*URLCAP+z2]; z2=z2+1 } cur_url[cl2]=0 as u8; navurl = 1 1169 } } 1170 if act == 2 { if fhistn > 0 { 1171 if histn < HISTMAX { var z: i64=0; let cl: i64=br_slen(cur_url); while z<cl { hist[histn*URLCAP+z]=cur_url[z]; z=z+1 } histlen[histn]=cl; histn=histn+1 } 1172 fhistn = fhistn - 1; let cl2: i64=fhistlen[fhistn]; var z2: i64=0; while z2<cl2 { cur_url[z2]=fhist[fhistn*URLCAP+z2]; z2=z2+1 } cur_url[cl2]=0 as u8; navurl = 1 1173 } } 1174 if act == 3 { navurl = 1 } 1175 } else { 1176 let dx: i64 = ex 1177 let dy: i64 = (ey - NX_CHROME_H) + scroll_y 1178 if br_hit_link(page, dx, dy, hoff, hlen2) == 1 { 1179 if nx_url_resolve(cur_url, br_slen(cur_url), ((page.buf as i64)+hoff[0]) as *u8, hlen2[0], absbuf, URLCAP, abslen) == NX_URL_RESOLVE_OK { 1180 // only follow http(s) links 1181 if (absbuf[0]&0xff)==104 { // 'h' 1182 br_p(1, "nx_browser: -> \x00" as *u8); sys_write(1, absbuf, abslen[0]); br_p(1, "\n\x00" as *u8) 1183 // push current to back history; a NEW navigation clears the forward stack 1184 if histn < HISTMAX { var z: i64=0; let cl: i64=br_slen(cur_url); while z<cl { hist[histn*URLCAP+z]=cur_url[z]; z=z+1 } histlen[histn]=cl; histn=histn+1 } 1185 fhistn = 0 1186 var z2: i64=0; while z2<abslen[0] { cur_url[z2]=absbuf[z2]; z2=z2+1 } cur_url[abslen[0]]=0 as u8 1187 navurl = 1 1188 } 1189 } 1190 } 1191 } 1192 } 1193 } 1194 if kind == 2 { 1195 let kc: i64 = (ev[1] & 0xff) as i64 1196 if kc == 116 { scroll_y = scroll_y + 120; dirty = 1 } 1197 if kc == 111 { scroll_y = scroll_y - 120; dirty = 1 } 1198 if kc == 117 { scroll_y = scroll_y + 600; dirty = 1 } 1199 if kc == 112 { scroll_y = scroll_y - 600; dirty = 1 } 1200 if kc == 65 { scroll_y = scroll_y + 600; dirty = 1 } 1201 if kc == 56 { // 'b' = back 1202 if histn > 0 { 1203 if fhistn < HISTMAX { var zf: i64=0; let clf: i64=br_slen(cur_url); while zf<clf { fhist[fhistn*URLCAP+zf]=cur_url[zf]; zf=zf+1 } fhistlen[fhistn]=clf; fhistn=fhistn+1 } 1204 histn = histn - 1; let cl: i64=histlen[histn]; var z: i64=0; while z<cl { cur_url[z]=hist[histn*URLCAP+z]; z=z+1 } cur_url[cl]=0 as u8; navurl = 1 1205 } 1206 } 1207 if kc == 58 { br_media_dump(store, cur_url, br_slen(cur_url)) } // 'm' = deep-harvest + panel the gif/vid/image sources 1208 if kc == 42 { // 'g' = play this page's game NATIVELY 1209 if glen > 0 { 1210 br_p(1, "nx_browser: launching native \x00" as *u8); sys_write(1, gname, glen); br_p(1, "\n\x00" as *u8) 1211 br_launch_game(gname, glen, 0) 1212 } 1213 if glen <= 0 { br_p(1, "nx_browser: no sovereign game on this page\n\x00" as *u8) } 1214 } 1215 if kc == 24 { running = 0 } // 'q' 1216 if kc == 9 { running = 0 } // Esc 1217 } 1218 if kind == 17 { running = 0 } 1219 if navurl == 1 { 1220 br_p(1, "nx_browser: loading...\n\x00" as *u8) 1221 if load_page(cur_url, br_slen(cur_url), store, cr, priv, page, WW) == 0 { if page.ok == 1 { 1222 scroll_y = 0 1223 max_scroll = page.page_h - (WH - NX_CHROME_H) 1224 if max_scroll < 0 { max_scroll = 0 } 1225 dirty = 1 1226 glen = br_game_organ(page.raw, page.raw_len, gname) 1227 if glen > 0 { 1228 br_p(1, "nx_browser: SOVEREIGN GAME page -- press g to play NATIVE (\x00" as *u8) 1229 sys_write(1, gname, glen); br_p(1, ")\n\x00" as *u8) 1230 } 1231 } } 1232 } 1233 if dirty == 1 { 1234 if scroll_y < 0 { scroll_y = 0 } 1235 if scroll_y > max_scroll { scroll_y = max_scroll } 1236 br_draw(cc, wid, gc, paper, page, WW, WH, scroll_y, fbody, fh2, fh1, cur_url, br_slen(cur_url)) 1237 } 1238 } 1239 } 1240 sys_close(cc.fd) 1241 br_p(1, "nx_browser: closed\n\x00" as *u8) 1242 return 0 1243}