nx_desktop_game.nx source
↩ module page · 186 lines · 7352 B
1// nx_desktop_game.nx -- the shared NATIVE GAME SURFACE (no wasm, no JS, no third-party anything):
2// window + server keymap + event pump into nx_input_abstract + banded framebuffer blit, extracted
3// from nx_desktop_breeders the moment a second native client (craft) needed the same machinery --
4// migrate-on-touch, one definition. A client composes: dg_setup -> dg_keymap -> loop { dg_pump ->
5// its own step/render -> dg_blit } -- the game organ stays byte-identical to its wasm build; only
6// the surface differs. dg_prove_surface is the shared selftest core (blit accepted + pixmap put->get
7// byte round-trip) so every client's proof battery stays honest without re-implementing it.
8// license_tier: ORIGINAL
9import "nx_syscalls.nx"
10import "nx_x11_paint.nx"
11import "nx_x11_pixmap.nx"
12import "nx_x11_get_image.nx"
13import "nx_x11_put_image.nx"
14import "nx_x11_keyboard.nx"
15import "nx_input_abstract.nx"
16const DG_MAGIC_4096: i64 = 4096
17
18const DG_FRAME_MS: i64 = 33
19
20// poll the X fd for readability; the timeout doubles as the frame pacer. pfd = caller's 8-byte buf.
21func dg_poll_in(pfd: *u8, fd: i64, timeout_ms: i64) -> i64 {
22 pfd[0] = (fd & 255) as u8
23 pfd[1] = ((fd >> 8) & 255) as u8
24 pfd[2] = ((fd >> 16) & 255) as u8
25 pfd[3] = ((fd >> 24) & 255) as u8
26 pfd[4] = 1 // POLLIN
27 pfd[5] = 0
28 pfd[6] = 0
29 pfd[7] = 0
30 let r: i64 = sys_poll(pfd, 1, timeout_ms)
31 if r <= 0 { return 0 }
32 if (pfd[6] & 0xff) != 0 { return 1 }
33 return 0
34}
35
36// one X event into the input layer. Returns 1 = quit (ESC / DestroyNotify). Synthetic events
37// (SendEvent) arrive with bit 0x80 set and take the SAME path -- deliberately.
38func dg_dispatch(inp: *i64, ev: *u8, vk_table: *i64) -> i64 {
39 let kind: i64 = ((ev[0] & 0xff) as i64) & 0x7f
40 if kind == 2 {
41 let vk: i64 = vk_table[(ev[1] & 0xff) as i64]
42 if vk == 27 { return 1 }
43 if vk != 0 { ia_key(inp, vk, 1) }
44 return 0
45 }
46 if kind == 3 {
47 let vk: i64 = vk_table[(ev[1] & 0xff) as i64]
48 if vk != 0 { ia_key(inp, vk, 0) }
49 return 0
50 }
51 if kind == 17 { return 1 } // DestroyNotify
52 return 0
53}
54
55// drain every pending event without blocking. Returns 1 = quit requested / connection gone.
56func dg_pump(conn: *X11Conn, inp: *i64, ev: *u8, pfd: *u8, vk_table: *i64) -> i64 {
57 var quit: i64 = 0
58 var more: i64 = 1
59 while more == 1 {
60 if dg_poll_in(pfd, conn.fd, 0) == 0 { more = 0 }
61 if more == 1 {
62 if nx_x11_read_event(conn, ev) < 0 { return 1 }
63 if dg_dispatch(inp, ev, vk_table) == 1 { quit = 1 }
64 }
65 }
66 return quit
67}
68
69// mouse-aware dispatch: everything dg_dispatch does PLUS mouse buttons through the standard-
70// gamepad door (button 1 -> pad 0 = A/break, button 3 -> pad 1 = B/place) and MotionNotify into
71// the analog look channel as relative deltas. mctx: [0]=lastx [1]=lasty [2]=have-last [3]=motion-seen.
72func dg_dispatch2(inp: *i64, ev: *u8, vk_table: *i64, mctx: *i64) -> i64 {
73 let kind: i64 = ((ev[0] & 0xff) as i64) & 0x7f
74 if kind == 4 {
75 let btn: i64 = (ev[1] & 0xff) as i64
76 if btn == 1 { ia_pad(inp, 0, 1) }
77 if btn == 3 { ia_pad(inp, 1, 1) }
78 return 0
79 }
80 if kind == 5 {
81 let btn: i64 = (ev[1] & 0xff) as i64
82 if btn == 1 { ia_pad(inp, 0, 0) }
83 if btn == 3 { ia_pad(inp, 1, 0) }
84 return 0
85 }
86 if kind == 6 {
87 let ex2: i64 = ((ev[24] & 0xff) as i64) | (((ev[25] & 0xff) as i64) << 8)
88 let ey2: i64 = ((ev[26] & 0xff) as i64) | (((ev[27] & 0xff) as i64) << 8)
89 if mctx[2] == 1 { ia_look(inp, ex2 - mctx[0], ey2 - mctx[1]) }
90 mctx[0] = ex2
91 mctx[1] = ey2
92 mctx[2] = 1
93 mctx[3] = 1
94 return 0
95 }
96 return dg_dispatch(inp, ev, vk_table)
97}
98
99// mouse-aware pump: drains events through dg_dispatch2, then recenters the pointer ONCE if any
100// motion arrived (the FPS warp -- deltas never die at the window edge). Returns 1 = quit.
101func dg_pump2(conn: *X11Conn, wid: i64, w: i64, h: i64,
102 inp: *i64, ev: *u8, pfd: *u8, vk_table: *i64, mctx: *i64) -> i64 {
103 var quit: i64 = 0
104 var more: i64 = 1
105 mctx[3] = 0
106 while more == 1 {
107 if dg_poll_in(pfd, conn.fd, 0) == 0 { more = 0 }
108 if more == 1 {
109 if nx_x11_read_event(conn, ev) < 0 { return 1 }
110 if dg_dispatch2(inp, ev, vk_table, mctx) == 1 { quit = 1 }
111 }
112 }
113 if mctx[3] == 1 {
114 nx_x11_warp_pointer(conn, wid, w/2, h/2)
115 mctx[0] = w/2
116 mctx[1] = h/2
117 }
118 return quit
119}
120
121// convert + banded-blit a packed-i64 framebuffer into the window. Returns bands or negative.
122func dg_blit(conn: *X11Conn, wid: i64, gc: i64, fbp: *i64, w: i64, h: i64, zbuf: *u8) -> i64 {
123 nx_fb_to_zpixmap(fbp, w*h, zbuf)
124 return nx_x11_put_image_banded(conn, wid, gc, w, h, zbuf)
125}
126
127// connect + window + name + gc + map + flush. out[0]=conn as i64, [1]=wid, [2]=gc, [3]=flush rc.
128// Returns 0 or a negative stage code.
129func dg_setup(title: *u8, tlen: i64, w: i64, h: i64, out: *i64) -> i64 {
130 let cr: i64 = nx_x11_connect_local(0)
131 if cr < 0 { return 0 - 1 }
132 let conn: *X11Conn = cr as *X11Conn
133 var emask: i64 = NX_X11_EVENT_KEY_PRESS | NX_X11_EVENT_KEY_RELEASE | NX_X11_EVENT_EXPOSURE | NX_X11_EVENT_STRUCTURE
134 emask = emask | NX_X11_EVENT_BUTTON_PRESS | NX_X11_EVENT_BUTTON_RELEASE | NX_X11_EVENT_POINTER_MOTION
135 let wid: i64 = nx_x11_create_window(conn, 40, 40, w, h, 0x08070f, emask)
136 if wid < 0 { return 0 - 2 }
137 nx_x11_set_wm_name(conn, wid, title, tlen)
138 let gc: i64 = nx_x11_create_gc(conn, wid, 0)
139 if gc < 0 { return 0 - 3 }
140 nx_x11_map_window(conn, wid)
141 out[0] = cr
142 out[1] = wid
143 out[2] = gc
144 out[3] = nx_x11_flush_via_get_input_focus(conn)
145 return 0
146}
147
148// first keycode mapped to a vk, 0 if none (selftest helper)
149func dg_kc_for(vk_table: *i64, vk: i64) -> i64 {
150 var i: i64 = 0
151 while i < NX_X11_KEYMAP_N {
152 if vk_table[i] == vk { return i }
153 i = i + 1
154 }
155 return 0
156}
157
158// the shared surface proof: blit the caller's frame, confirm the server accepted it, then round-trip
159// the SAME bytes through an off-screen pixmap and byte-compare B,G,R. out[0]=bands [1]=flush rc
160// [2]=get bytes [3]=mismatched BGR bytes. Returns 0, or negative on a wire failure.
161func dg_prove_surface(conn: *X11Conn, wid: i64, gc: i64, fbp: *i64,
162 w: i64, h: i64, zbuf: *u8, out: *i64) -> i64 {
163 out[0] = dg_blit(conn, wid, gc, fbp, w, h, zbuf)
164 out[1] = nx_x11_flush_via_get_input_focus(conn)
165 let pid: i64 = nx_x11_create_pixmap(conn, wid, w, h, conn.root_depth)
166 nx_x11_put_image_banded(conn, pid, gc, w, h, zbuf)
167 let gbuf: *u8 = sys_mmap(w*h*4 + DG_MAGIC_4096)
168 out[2] = nx_x11_get_image(conn, pid, 0, 0, w, h, gbuf, w*h*4 + DG_MAGIC_4096)
169 nx_x11_free_pixmap(conn, pid)
170 var mism: i64 = 0
171 var p: i64 = 0
172 while p < w*h {
173 if (gbuf[p*4] & 0xff) != (zbuf[p*4] & 0xff) { mism = mism + 1 }
174 if (gbuf[p*4+1] & 0xff) != (zbuf[p*4+1] & 0xff) { mism = mism + 1 }
175 if (gbuf[p*4+2] & 0xff) != (zbuf[p*4+2] & 0xff) { mism = mism + 1 }
176 p = p + 1
177 }
178 out[3] = mism
179 if out[0] < 0 { return 0 - 1 }
180 if out[2] < 0 { return 0 - 2 }
181 return 0
182}
183
184func main() -> i64 {
185 return 0
186}