code wiki / (root) / nx_browser_demo_test.nx

nx_browser_demo_test.nx source

↩ module page · 583 lines · 24938 B

1// nx_browser_demo_test.nx -- Session 3 of bits-up browser arc. 2// 3// Pops a real Windows window via WSLg and paints actual browser 4// chrome: gray URL bar with back/forward buttons + text area below. 5// Subscribes to Expose events; redraws when needed. Exits after 6// a 10-second visible window so user can see the layout. 7// 8// Compose-only: nx_x11_connect + nx_x11_window + nx_x11_paint. 9// 10// expect_exit: 0. 11// license_tier: ORIGINAL 12 13import "nx_syscalls.nx" 14import "nx_x11_connect.nx" 15import "nx_x11_window.nx" 16import "nx_x11_paint.nx" 17import "nx_x11_paint_text_5x7.nx" 18import "nx_x11_pixmap.nx" 19import "nx_x11_screenshot.nx" 20import "nx_render_journal.nx" 21import "nx_ux_standard.nx" 22import "nx_ux_score.nx" 23import "nx_ux_triangulate.nx" 24 25// SIGPIPE handling -- writes to a closed/disconnected X11 socket 26// generate SIGPIPE by default, which kills our process with signal 27// 13 (exit code 141). Setting SIG_IGN via rt_sigaction makes the 28// failing write just return -EPIPE instead, so the event loop can 29// exit cleanly when the user closes the window. 30// 31// rt_sigaction(int signum, const struct sigaction *act, ... 32// const struct sigaction *oldact, size_t sigsetsize) 33// sigaction struct (RV64 layout): sa_handler(8) + sa_flags(8) + 34// sa_restorer(8) + sa_mask(8) 35// SIG_IGN = 1, SIGPIPE = 13, NSIG/8 = 8 (sigset size in bytes) 36const NX_SIGPIPE: i64 = 13 37const NX_SIG_IGN: i64 = 1 38const NX_SYS_RT_SIGACTION: i64 = 134 39 40func _ignore_sigpipe() -> i64 { 41 let sa: *u8 = sys_mmap(64) 42 var i: i64 = 0 43 while i < 64 { sa[i] = 0; i = i + 1 } 44 // sa_handler = SIG_IGN at offset 0 45 sa[0] = NX_SIG_IGN as u8 46 __syscall(NX_SYS_RT_SIGACTION, NX_SIGPIPE, sa as i64, 0, 8, 0, 0) 47 return 0 48} 49 50func dump_dec(l0: i64, l1: i64, v: i64) -> i64 { 51 let lab: *u8 = sys_mmap(8) 52 lab[0]=l0 as u8; lab[1]=l1 as u8; lab[2]=0x3D 53 sys_write(2, lab, 3) 54 var av: i64 = v 55 if av < 0 { 56 let neg: *u8 = sys_mmap(8); neg[0]=0x2D; sys_write(2, neg, 1) 57 av = 0 - av 58 } 59 let buf: *u8 = sys_mmap(16) 60 var pos: i64 = 0 61 if av == 0 { buf[0]=0x30; pos = 1 } 62 var x: i64 = av 63 while x > 0 { 64 buf[pos] = (0x30 + (x % 10)) as u8 65 x = x / 10; pos = pos + 1 66 } 67 let out: *u8 = sys_mmap(16); var oi: i64 = 0 68 while oi < pos { out[oi] = buf[pos - 1 - oi]; oi = oi + 1 } 69 sys_write(2, out, pos) 70 let nl: *u8 = sys_mmap(8); nl[0]=0x0A; sys_write(2, nl, 1) 71 return 0 72} 73 74// Paint the browser chrome. Window is 800x500. 75// RECTANGLES-ONLY (no text yet). ImageText8 requires the GC to have 76// a font set; that wiring is Session 3b (OpenFont + SetFont, or 77// alternatively rasterise our 5x7 bitmap glyphs via PutImage). 78// Today's goal: prove visible chrome structure -- header bar, buttons, 79// URL box, content paragraphs, status bar -- all visible as colored 80// rectangles on the real Windows desktop. 81// Navigation state -- 10-slot history ring with a current index. 82// Each "URL" is represented by a synthetic color (different shade 83// per index) so the user can SEE position-in-history change as 84// they click back/forward. Real text URLs land in Session 6. 85struct NavState { 86 history_len: i64, // how many URLs visited so far (1..10) 87 history_idx: i64, // current position (0..history_len-1) 88 last_clicked: i64, // 0=back, 1=fwd, 2=reload, -1=none 89 flash_iters: i64, // remaining repaint iterations to flash 90 focused_idx: i64, // WCAG 2.4.7 focus -- which button has kb focus (-1 = none) 91 last_key: i64 // diagnostic 92} 93 94const NX_NAV_STATE_BYTES: i64 = 48 95 96// Per-index synthetic content color (cycles through a palette). 97func nav_color_for(idx: i64) -> i64 { 98 let palette_len: i64 = 6 99 let slot: i64 = idx - (idx / palette_len) * palette_len 100 if slot == 0 { return 0x00CC2233 } // red 101 if slot == 1 { return 0x002288CC } // blue 102 if slot == 2 { return 0x0033AA66 } // green 103 if slot == 3 { return 0x00FF8800 } // orange 104 if slot == 4 { return 0x009955CC } // purple 105 return 0x00666666 // gray 106} 107 108// Hit-test a click point against the 3 chrome buttons. 109// Returns 0=back, 1=forward, 2=reload, -1=miss. 110func hit_button(x: i64, y: i64) -> i64 { 111 if y < 4 { return 0 - 1 } 112 if y >= 36 { return 0 - 1 } 113 if x >= 4 { if x < 36 { return 0 } } 114 if x >= 40 { if x < 72 { return 1 } } 115 if x >= 76 { if x < 108 { return 2 } } 116 return 0 - 1 117} 118 119// Responsive chrome layout -- all positions/sizes computed relative 120// to the current window (w, h). URL bar pinned to top; status bar 121// pinned to bottom; content stretches to fill in between. 122// Paint a rect AND log it to the render journal with a semantic 123// intent tag. The journal is the substrate-honest alternative to 124// screen-scraping for introspection. j == NULL skips journaling. 125func paint_and_log(c: *X11Conn, wid: i64, j: *RenderJournal, 126 intent: *u8, 127 gc_color: i64, 128 x: i64, y: i64, w: i64, h: i64) -> i64 { 129 let gc: i64 = nx_x11_create_gc(c, wid, gc_color) 130 nx_x11_fill_rect(c, wid, gc, x, y, w, h) 131 if (j as i64) != 0 { 132 nx_journal_log_rect(j, intent, x, y, w, h, gc_color) 133 } 134 return 0 135} 136 137func paint_chrome(c: *X11Conn, wid: i64, w: i64, h: i64, 138 nav: *NavState, j: *RenderJournal) -> i64 { 139 let has_j: i64 = (j as i64) 140 141 // White content background fills the full window. 142 let gc_white: i64 = nx_x11_create_gc(c, wid, 0x00FFFFFF) 143 nx_x11_fill_rect(c, wid, gc_white, 0, 0, w, h) 144 if has_j != 0 { nx_journal_log_rect(j, "background" as *u8, 0, 0, w, h, 0x00FFFFFF) } 145 146 // Gray URL bar (full-width, 40px tall, anchored to top). 147 let gc_gray: i64 = nx_x11_create_gc(c, wid, 0x00334455) 148 nx_x11_fill_rect(c, wid, gc_gray, 0, 0, w, 40) 149 if has_j != 0 { nx_journal_log_rect(j, "url_bar_bg" as *u8, 0, 0, w, 40, 0x00334455) } 150 151 // 3 buttons. Each renders as the base color UNLESS it was the 152 // most recent click AND flash_iters > 0 -- in that case render 153 // brighter (yellow flash) so the user sees their click registered. 154 let base: i64 = 0x004488CC 155 let flash: i64 = 0x00FFCC22 156 var c0: i64 = base 157 var c1: i64 = base 158 var c2: i64 = base 159 // Dim back button if no history to go back to. 160 if nav.history_idx == 0 { c0 = 0x00779999 } 161 // Dim forward if at end. 162 if nav.history_idx + 1 >= nav.history_len { c1 = 0x00779999 } 163 if nav.flash_iters > 0 { 164 if nav.last_clicked == 0 { c0 = flash } 165 if nav.last_clicked == 1 { c1 = flash } 166 if nav.last_clicked == 2 { c2 = flash } 167 } 168 let gc_b0: i64 = nx_x11_create_gc(c, wid, c0) 169 let gc_b1: i64 = nx_x11_create_gc(c, wid, c1) 170 let gc_b2: i64 = nx_x11_create_gc(c, wid, c2) 171 nx_x11_fill_rect(c, wid, gc_b0, 4, 4, 32, 32) 172 nx_x11_fill_rect(c, wid, gc_b1, 40, 4, 32, 32) 173 nx_x11_fill_rect(c, wid, gc_b2, 76, 4, 32, 32) 174 if has_j != 0 { 175 nx_journal_log_rect(j, "back_button" as *u8, 4, 4, 32, 32, c0) 176 nx_journal_log_rect(j, "forward_button" as *u8, 40, 4, 32, 32, c1) 177 nx_journal_log_rect(j, "reload_button" as *u8, 76, 4, 32, 32, c2) 178 } 179 180 // URL input box: starts after buttons (x=120) + ends 10 px from right. 181 let url_x: i64 = 120 182 var url_w: i64 = w - url_x - 10 183 if url_w < 1 { url_w = 1 } 184 nx_x11_fill_rect(c, wid, gc_white, url_x, 6, url_w, 28) 185 let gc_black: i64 = nx_x11_create_gc(c, wid, 0x00000000) 186 nx_x11_fill_rect(c, wid, gc_black, url_x, 6, url_w, 1) 187 nx_x11_fill_rect(c, wid, gc_black, url_x, 33, url_w, 1) 188 nx_x11_fill_rect(c, wid, gc_black, url_x, 6, 1, 28) 189 nx_x11_fill_rect(c, wid, gc_black, url_x + url_w - 1, 6, 1, 28) 190 if has_j != 0 { 191 nx_journal_log_rect(j, "url_field_bg" as *u8, url_x, 6, url_w, 28, 0x00FFFFFF) 192 nx_journal_log_rect(j, "url_field_border" as *u8, url_x, 6, url_w, 28, 0x00000000) 193 } 194 195 // History breadcrumb -- one small bar inside the URL field per 196 // history slot, current slot rendered in the page's title color 197 // for visible "you-are-here" feedback. Hovers under the URL 198 // field would normally show the URL text; that lands once text 199 // rendering ships. 200 let bc_pad: i64 = 4 201 let bc_x: i64 = url_x + bc_pad 202 let bc_y: i64 = 14 203 let bc_h: i64 = 12 204 let bc_w_each: i64 = 14 205 let bc_gap: i64 = 2 206 var hi: i64 = 0 207 while hi < nav.history_len { 208 var slot_color: i64 = 0x00CCCCCC 209 if hi == nav.history_idx { slot_color = nav_color_for(hi) } 210 let gc_slot: i64 = nx_x11_create_gc(c, wid, slot_color) 211 let sx: i64 = bc_x + hi * (bc_w_each + bc_gap) 212 nx_x11_fill_rect(c, wid, gc_slot, sx, bc_y, bc_w_each, bc_h) 213 if has_j != 0 { 214 var lbl: *u8 = "breadcrumb_past" as *u8 215 if hi == nav.history_idx { lbl = "breadcrumb_current" as *u8 } 216 nx_journal_log_rect(j, lbl, sx, bc_y, bc_w_each, bc_h, slot_color) 217 } 218 hi = hi + 1 219 } 220 221 // Content area starts at y=50 (10 px below URL bar) and stretches. 222 // Title color cycles with history_idx so the user can SEE the 223 // current "page" change when they navigate. 224 let content_x: i64 = 16 225 var content_w: i64 = w - 32 226 if content_w < 1 { content_w = 1 } 227 let title_w: i64 = 200 228 let title_color: i64 = nav_color_for(nav.history_idx) 229 let gc_title: i64 = nx_x11_create_gc(c, wid, title_color) 230 nx_x11_fill_rect(c, wid, gc_title, content_x, 60, title_w, 24) 231 if has_j != 0 { 232 nx_journal_log_rect(j, "title_block" as *u8, content_x, 60, title_w, 24, title_color) 233 } 234 235 // Real text using the bits-up 5x7 bitmap font. Each glyph is 236 // rendered as px_size=2 (10x14 visible per char incl. spacing), 237 // dark text on the white content area. 238 let gc_text: i64 = nx_x11_create_gc(c, wid, 0x00112233) 239 let title_text: *u8 = "Example Domain" 240 nx_x11_paint_text_5x7(c, wid, gc_text, content_x + 4, 64, title_text, 14, 2) 241 let p1_text: *u8 = "This domain is for use in documentation examples." 242 nx_x11_paint_text_5x7(c, wid, gc_text, content_x, 100, p1_text, 49, 2) 243 let p2_text: *u8 = "Avoid use in operations." 244 nx_x11_paint_text_5x7(c, wid, gc_text, content_x, 130, p2_text, 24, 2) 245 if has_j != 0 { 246 nx_journal_log_rect(j, "text_title" as *u8, content_x + 4, 64, 168, 14, 0x00112233) 247 nx_journal_log_rect(j, "text_para_0" as *u8, content_x, 100, 588, 14, 0x00112233) 248 nx_journal_log_rect(j, "text_para_1" as *u8, content_x, 130, 288, 14, 0x00112233) 249 } 250 251 // Link block. 252 let gc_link: i64 = nx_x11_create_gc(c, wid, 0x00FF8800) 253 nx_x11_fill_rect(c, wid, gc_link, content_x, 210, 220, 12) 254 if has_j != 0 { 255 nx_journal_log_rect(j, "link_block" as *u8, content_x, 210, 220, 12, 0x00FF8800) 256 } 257 258 // Status bar pinned to BOTTOM (20 px tall, full width). 259 let status_y: i64 = h - 20 260 if status_y > 0 { 261 nx_x11_fill_rect(c, wid, gc_gray, 0, status_y, w, 20) 262 if has_j != 0 { 263 nx_journal_log_rect(j, "status_bar" as *u8, 0, status_y, w, 20, 0x00334455) 264 } 265 } 266 267 // ---- WCAG 2.4.7 focus indicator --------------------------------- 268 // Paint a 2-px-thick high-contrast yellow ring around the focused 269 // button so keyboard users can see where they are. Composed of 270 // 4 thin rects (top/bottom/left/right) AROUND the button so the 271 // button's own pixel area stays intact. 272 if nav.focused_idx >= 0 { 273 let fy: i64 = 2 274 var fx: i64 = 4 + nav.focused_idx * 36 // back=4, fwd=40, reload=76 275 if nav.focused_idx == 1 { fx = 40 } 276 if nav.focused_idx == 2 { fx = 76 } 277 let fw: i64 = 36 278 let fh: i64 = 36 279 let gc_focus: i64 = nx_x11_create_gc(c, wid, 0x00FFCC22) // yellow 280 nx_x11_fill_rect(c, wid, gc_focus, fx, fy, fw, 2) // top 281 nx_x11_fill_rect(c, wid, gc_focus, fx, fy + fh - 2, fw, 2) // bottom 282 nx_x11_fill_rect(c, wid, gc_focus, fx, fy, 2, fh) // left 283 nx_x11_fill_rect(c, wid, gc_focus, fx + fw - 2, fy, 2, fh) // right 284 if has_j != 0 { 285 nx_journal_log_rect(j, "focus_indicator" as *u8, fx, fy, fw, fh, 0x00FFCC22) 286 } 287 } 288 289 // ---- WCAG 4.1.2 name/role/value: emit a11y_role_* intents ------- 290 // The accessibility tree IS the journal entries with these tags; 291 // an AT-bridge future primitive walks them to expose to screen 292 // readers. Today we just declare the semantic roles so the 293 // triangulator can score them. 294 if has_j != 0 { 295 nx_journal_log_rect(j, "a11y_role_button_back" as *u8, 4, 4, 32, 32, 0) 296 nx_journal_log_rect(j, "a11y_role_button_forward" as *u8, 40, 4, 32, 32, 0) 297 nx_journal_log_rect(j, "a11y_role_button_reload" as *u8, 76, 4, 32, 32, 0) 298 nx_journal_log_rect(j, "a11y_role_textbox_url" as *u8, 120, 6, w - 130, 28, 0) 299 nx_journal_log_rect(j, "a11y_role_region_content" as *u8, 0, 40, w, h - 60, 0) 300 nx_journal_log_rect(j, "a11y_role_status" as *u8, 0, h - 20, w, 20, 0) 301 } 302 return 0 303} 304 305func main() -> i64 { 306 // Ignore SIGPIPE so we exit cleanly when the user closes the 307 // window and writes start failing. 308 _ignore_sigpipe() 309 310 let rc: i64 = nx_x11_connect_local(0) 311 if rc < 0 { return 1 } 312 let c: *X11Conn = rc as *X11Conn 313 dump_dec(0x46, 0x44, c.fd) 314 315 // Subscribe to Expose + StructureNotify + ButtonPress + KeyPress. 316 let event_mask: i64 = NX_X11_EVENT_EXPOSURE 317 | NX_X11_EVENT_STRUCTURE 318 | NX_X11_EVENT_BUTTON_PRESS 319 | NX_X11_EVENT_KEY_PRESS 320 let bg: i64 = 0x00F0F0F0 321 var win_w: i64 = 800 322 var win_h: i64 = 500 323 324 // Navigation state -- start with 3 history entries so back/forward 325 // are immediately exercisable. 326 let nav_raw: *u8 = sys_mmap(NX_NAV_STATE_BYTES + 16) 327 let nav: *NavState = nav_raw as *NavState 328 nav.history_len = 3 329 nav.history_idx = 1 // start in middle so both arrows work 330 nav.last_clicked = 0 - 1 331 nav.flash_iters = 0 332 nav.focused_idx = 0 // Tab cycles; start on back button 333 nav.last_key = 0 334 let wid: i64 = nx_x11_create_window(c, 100, 100, win_w, win_h, bg, event_mask) 335 if wid < 0 { return 2 } 336 dump_dec(0x57, 0x49, wid) 337 338 let title: *u8 = "Nishi Bits-Up Browser" 339 nx_x11_set_wm_name(c, wid, title, 21) 340 341 if nx_x11_map_window(c, wid) < 0 { return 3 } 342 if nx_x11_flush_via_get_input_focus(c) < 0 { return 4 } 343 344 // ---- Substrate-honest UX/CX intelligence ------------------------- 345 // Allocate a structured render journal. Every paint logs its 346 // semantic intent (e.g. "back_button", "url_bar_bg") + coords 347 // + color into the journal -- NOT screen-scraped from rendered 348 // pixels. Substrate truth lives in this data, not in pixel 349 // observation. Dump to /tmp/nishi_render_journal.log so the 350 // developer (or Claude via Read) can inspect what was drawn 351 // and WHY without snooping on the user's screen. 352 let j: *RenderJournal = nx_journal_new() 353 354 // ---- Snapshot pass: paint to pixmap + dump PPM as backup channel 355 let snap_w: i64 = 800 356 let snap_h: i64 = 500 357 let pix: i64 = nx_x11_create_pixmap(c, wid, snap_w, snap_h, c.root_depth) 358 if pix >= 0 { 359 paint_chrome(c, pix, snap_w, snap_h, nav, j) 360 nx_x11_flush_via_get_input_focus(c) 361 let path: *u8 = "/tmp/nishi_render.ppm\x00" 362 let sv: i64 = nx_x11_save_drawable_as_ppm(c, pix, snap_w, snap_h, path) 363 dump_dec(0x53, 0x4E, sv) 364 nx_x11_free_pixmap(c, pix) 365 } 366 // Dump the journal -- the PRIMARY introspection channel. 367 let log_path: *u8 = "/tmp/nishi_render_journal.log\x00" 368 nx_journal_dump_to_file(j, log_path) 369 370 // ---- UX Triangulation: NN/g + WCAG + Apple HIG + ISO 9241 ------- 371 // Score the journal against every clause we have a rubric for, 372 // then run the ≥3-distinct-sources gate. Writes a structured 373 // report to /tmp/nishi_ux_triangulation.log so anyone (operator, 374 // Claude, future Nishi tooling) can audit the S-class claim. 375 let clause_ids_raw: *u8 = sys_mmap(64 * 8) 376 let clause_ids: *i64 = clause_ids_raw as *i64 377 clause_ids[0] = NX_UX_NN1_VISIBILITY 378 clause_ids[1] = NX_UX_NN3_USER_CONTROL 379 clause_ids[2] = NX_UX_NN4_CONSISTENCY 380 clause_ids[3] = NX_UX_NN6_RECOGNITION 381 clause_ids[4] = NX_UX_APPLE_FEEDBACK 382 clause_ids[5] = NX_UX_APPLE_CLARITY 383 clause_ids[6] = NX_UX_ISO_SELFDESCRIPTIVE 384 clause_ids[7] = NX_UX_WCAG_2_1_1_KEYBOARD 385 clause_ids[8] = NX_UX_WCAG_2_4_7_FOCUS 386 clause_ids[9] = NX_UX_WCAG_4_1_2_NRV 387 clause_ids[10] = NX_UX_NN5_ERROR_PREVENTION 388 clause_ids[11] = NX_UX_W3C_HTML_SEMANTIC 389 clause_ids[12] = NX_UX_W3C_ARIA_ROLES 390 clause_ids[13] = NX_UX_W3C_ARIA_LABELS 391 clause_ids[14] = NX_UX_W3C_DOM_TREE 392 clause_ids[15] = NX_UX_W3C_CSS_CONTRAST 393 clause_ids[16] = NX_UX_MS_INCL_EXCLUSION 394 let n_clauses: i64 = 17 395 396 let scores_raw: *u8 = sys_mmap(n_clauses * NX_UX_SCORE_BYTES + 16) 397 let scores: *UxScore = scores_raw as *UxScore 398 399 // Open the report file once. 400 let report_path: *u8 = "/tmp/nishi_ux_triangulation.log\x00" 401 let rfd: i64 = sys_openat_wr(report_path, 0x1A4) 402 if rfd > 0 { 403 sys_write(rfd, "---- Nishi UX triangulation report ----\n" as *u8, 40) 404 } 405 406 let note: *u8 = sys_mmap(256) 407 var i: i64 = 0 408 while i < n_clauses { 409 let cid: i64 = clause_ids[i] 410 let s: i64 = nx_ux_score_clause(j, cid, note, 256) 411 let sx: *UxScore = _ux_at(scores, i) 412 sx.clause_id = cid 413 sx.score = s 414 if rfd > 0 { 415 // line: " [src] clause-label : score=N" 416 let src: i64 = nx_ux_clause_source(cid) 417 let line: *u8 = sys_mmap(256) 418 line[0] = 0x20; line[1] = 0x20; line[2] = 0x5B 419 sys_write(rfd, line, 3) 420 let src_lbl: *u8 = nx_ux_source_label(src) 421 var k: i64 = 0 422 while src_lbl[k] != 0 { sys_write(rfd, src_lbl + k, 1); k = k + 1 } 423 sys_write(rfd, "] " as *u8, 2) 424 let lbl: *u8 = nx_ux_clause_label(cid) 425 k = 0 426 while lbl[k] != 0 { sys_write(rfd, lbl + k, 1); k = k + 1 } 427 sys_write(rfd, " : score=" as *u8, 9) 428 // decimal score 429 let d: *u8 = sys_mmap(8) 430 if s >= 10 { 431 d[0] = 0x31; d[1] = 0x30; sys_write(rfd, d, 2) 432 } else { 433 d[0] = (0x30 + s) as u8; sys_write(rfd, d, 1) 434 } 435 sys_write(rfd, "\n" as *u8, 1) 436 } 437 i = i + 1 438 } 439 440 let verdict: i64 = nx_ux_triangulate(scores, n_clauses) 441 if rfd > 0 { 442 sys_write(rfd, "VERDICT (global): " as *u8, 18) 443 let vlbl: *u8 = nx_ux_verdict_label(verdict) 444 var k: i64 = 0 445 while vlbl[k] != 0 { sys_write(rfd, vlbl + k, 1); k = k + 1 } 446 sys_write(rfd, "\n\n--- per-cluster verdicts ---\n" as *u8, 30) 447 var cl: i64 = 1 448 while cl < NX_UX_CLUSTER_N { 449 let cv: i64 = nx_ux_triangulate_cluster(scores, n_clauses, cl) 450 sys_write(rfd, " cluster=" as *u8, 10) 451 let clbl: *u8 = nx_ux_cluster_label(cl) 452 k = 0 453 while clbl[k] != 0 { sys_write(rfd, clbl + k, 1); k = k + 1 } 454 sys_write(rfd, " : " as *u8, 3) 455 let vlbl2: *u8 = nx_ux_verdict_label(cv) 456 k = 0 457 while vlbl2[k] != 0 { sys_write(rfd, vlbl2 + k, 1); k = k + 1 } 458 sys_write(rfd, "\n" as *u8, 1) 459 cl = cl + 1 460 } 461 sys_close(rfd) 462 } 463 dump_dec(0x55, 0x58, verdict) // UX= triangulation verdict 464 465 paint_chrome(c, wid, win_w, win_h, nav, 0 as *RenderJournal) 466 if nx_x11_flush_via_get_input_focus(c) < 0 { return 5 } 467 468 // Event loop. 469 let evt: *u8 = sys_mmap(64) 470 var n_iter: i64 = 0 471 let MAX_ITER: i64 = 100000 472 var keep: i64 = 1 473 while keep == 1 { 474 if n_iter >= MAX_ITER { keep = 0 } 475 if nx_x11_read_event(c, evt) < 0 { keep = 0 } 476 let kind: i64 = (evt[0] & 0xff) as i64 477 var need_repaint: i64 = 0 478 // 12 = Expose -- redraw current chrome 479 if kind == 12 { need_repaint = 1 } 480 // 22 = ConfigureNotify -- window resized 481 if kind == 22 { 482 let nw: i64 = ((evt[20] & 0xff) as i64) 483 | (((evt[21] & 0xff) as i64) << 8) 484 let nh: i64 = ((evt[22] & 0xff) as i64) 485 | (((evt[23] & 0xff) as i64) << 8) 486 if nw > 0 { win_w = nw } 487 if nh > 0 { win_h = nh } 488 need_repaint = 1 489 } 490 // 4 = ButtonPress -- bytes 32-33 = root_x, 34-35 = root_y, 491 // 36-37 = event_x, 38-39 = event_y, 41=button. 492 // But the X protocol event layout puts event_x/y at offsets 493 // 24-25 / 26-27. Use those for window-relative coords. 494 // 2 = KeyPress -- byte 1 holds keycode. Hardcoded XKB 495 // standard PC/AT keycodes: Tab=23, Enter=36, Space=65. 496 // Tab cycles focus across the 3 buttons; Enter/Space 497 // activates the focused button using the SAME dispatch 498 // as ButtonPress (WCAG 2.1.1 keyboard accessible). 499 if kind == 2 { 500 let keycode: i64 = (evt[1] & 0xff) as i64 501 nav.last_key = keycode 502 // Tab cycles forward (Shift+Tab variant queued). 503 if keycode == 23 { 504 nav.focused_idx = (nav.focused_idx + 1) - ((nav.focused_idx + 1) / 3) * 3 505 need_repaint = 1 506 } 507 // Enter or Space activate the focused button. 508 if keycode == 36 { 509 if nav.focused_idx >= 0 { 510 let btn: i64 = nav.focused_idx 511 nav.last_clicked = btn 512 nav.flash_iters = 1 513 if btn == 0 { if nav.history_idx > 0 { nav.history_idx = nav.history_idx - 1 } } 514 if btn == 1 { if nav.history_idx + 1 < nav.history_len { nav.history_idx = nav.history_idx + 1 } } 515 if btn == 2 { if nav.history_len < 10 { 516 nav.history_len = nav.history_len + 1 517 nav.history_idx = nav.history_len - 1 518 } } 519 need_repaint = 1 520 } 521 } 522 if keycode == 65 { 523 if nav.focused_idx >= 0 { 524 let btn: i64 = nav.focused_idx 525 nav.last_clicked = btn 526 nav.flash_iters = 1 527 if btn == 0 { if nav.history_idx > 0 { nav.history_idx = nav.history_idx - 1 } } 528 if btn == 1 { if nav.history_idx + 1 < nav.history_len { nav.history_idx = nav.history_idx + 1 } } 529 if btn == 2 { if nav.history_len < 10 { 530 nav.history_len = nav.history_len + 1 531 nav.history_idx = nav.history_len - 1 532 } } 533 need_repaint = 1 534 } 535 } 536 } 537 if kind == 4 { 538 let ex: i64 = ((evt[24] & 0xff) as i64) 539 | (((evt[25] & 0xff) as i64) << 8) 540 let ey: i64 = ((evt[26] & 0xff) as i64) 541 | (((evt[27] & 0xff) as i64) << 8) 542 let btn: i64 = hit_button(ex, ey) 543 if btn >= 0 { 544 nav.last_clicked = btn 545 nav.flash_iters = 1 546 // back: idx-- (clamped) 547 if btn == 0 { 548 if nav.history_idx > 0 { 549 nav.history_idx = nav.history_idx - 1 550 } 551 } 552 // forward: idx++ (clamped) 553 if btn == 1 { 554 if nav.history_idx + 1 < nav.history_len { 555 nav.history_idx = nav.history_idx + 1 556 } 557 } 558 // reload: append a new history entry after current (truncating forward stack) 559 if btn == 2 { 560 if nav.history_len < 10 { 561 nav.history_len = nav.history_len + 1 562 nav.history_idx = nav.history_len - 1 563 } 564 } 565 need_repaint = 1 566 } 567 } 568 if need_repaint == 1 { 569 paint_chrome(c, wid, win_w, win_h, nav) 570 nx_x11_flush_via_get_input_focus(c) 571 // Decay flash on next paint. 572 if nav.flash_iters > 0 { 573 nav.flash_iters = nav.flash_iters - 1 574 } 575 } 576 // 17 = DestroyNotify -- user closed window 577 if kind == 17 { keep = 0 } 578 n_iter = n_iter + 1 579 } 580 581 sys_close(c.fd) 582 return 0 583}