v / vlib / sokol / sapp / sapp_wayland_linux.v
1226 lines · 1101 sloc · 40.9 KB · ddd4ac7880f392ad6c39719af05f01d29eb0ad3a
Raw
1module sapp
2
3// Wayland backend implementation for sokol_app.
4// Ports the C _sapp_wl_* functions from sokol_app.h to V.
5// This file is only compiled when -d sokol_wayland is used.
6
7$if sokol_wayland ? {
8 const fallback_default_window_width = 640
9 const fallback_default_window_height = 480
10
11 // XKB keysym constants
12 const xkb_key_space = u32(0x0020)
13 const xkb_key_apostrophe = u32(0x0027)
14 const xkb_key_comma = u32(0x002c)
15 const xkb_key_minus = u32(0x002d)
16 const xkb_key_period = u32(0x002e)
17 const xkb_key_slash = u32(0x002f)
18 const xkb_key_0 = u32(0x0030)
19 const xkb_key_1 = u32(0x0031)
20 const xkb_key_2 = u32(0x0032)
21 const xkb_key_3 = u32(0x0033)
22 const xkb_key_4 = u32(0x0034)
23 const xkb_key_5 = u32(0x0035)
24 const xkb_key_6 = u32(0x0036)
25 const xkb_key_7 = u32(0x0037)
26 const xkb_key_8 = u32(0x0038)
27 const xkb_key_9 = u32(0x0039)
28 const xkb_key_semicolon = u32(0x003b)
29 const xkb_key_equal = u32(0x003d)
30 const xkb_key_bracketleft = u32(0x005b)
31 const xkb_key_backslash = u32(0x005c)
32 const xkb_key_bracketright = u32(0x005d)
33 const xkb_key_grave = u32(0x0060)
34 const xkb_key_a = u32(0x0061)
35 const xkb_key_b = u32(0x0062)
36 const xkb_key_c = u32(0x0063)
37 const xkb_key_d = u32(0x0064)
38 const xkb_key_e = u32(0x0065)
39 const xkb_key_f = u32(0x0066)
40 const xkb_key_g = u32(0x0067)
41 const xkb_key_h = u32(0x0068)
42 const xkb_key_i = u32(0x0069)
43 const xkb_key_j = u32(0x006a)
44 const xkb_key_k = u32(0x006b)
45 const xkb_key_l = u32(0x006c)
46 const xkb_key_m = u32(0x006d)
47 const xkb_key_n = u32(0x006e)
48 const xkb_key_o = u32(0x006f)
49 const xkb_key_p = u32(0x0070)
50 const xkb_key_q = u32(0x0071)
51 const xkb_key_r = u32(0x0072)
52 const xkb_key_s = u32(0x0073)
53 const xkb_key_t = u32(0x0074)
54 const xkb_key_u = u32(0x0075)
55 const xkb_key_v = u32(0x0076)
56 const xkb_key_w = u32(0x0077)
57 const xkb_key_x = u32(0x0078)
58 const xkb_key_y = u32(0x0079)
59 const xkb_key_z = u32(0x007a)
60 const xkb_key_backspace = u32(0xff08)
61 const xkb_key_tab = u32(0xff09)
62 const xkb_key_return = u32(0xff0d)
63 const xkb_key_pause = u32(0xff13)
64 const xkb_key_scroll_lock = u32(0xff14)
65 const xkb_key_escape = u32(0xff1b)
66 const xkb_key_home = u32(0xff50)
67 const xkb_key_left = u32(0xff51)
68 const xkb_key_up = u32(0xff52)
69 const xkb_key_right = u32(0xff53)
70 const xkb_key_down = u32(0xff54)
71 const xkb_key_page_up = u32(0xff55)
72 const xkb_key_page_down = u32(0xff56)
73 const xkb_key_end = u32(0xff57)
74 const xkb_key_print = u32(0xff61)
75 const xkb_key_insert = u32(0xff63)
76 const xkb_key_menu = u32(0xff67)
77 const xkb_key_num_lock = u32(0xff7f)
78 const xkb_key_kp_enter = u32(0xff8d)
79 const xkb_key_kp_multiply = u32(0xffaa)
80 const xkb_key_kp_add = u32(0xffab)
81 const xkb_key_kp_subtract = u32(0xffad)
82 const xkb_key_kp_decimal = u32(0xffae)
83 const xkb_key_kp_divide = u32(0xffaf)
84 const xkb_key_kp_0 = u32(0xffb0)
85 const xkb_key_kp_1 = u32(0xffb1)
86 const xkb_key_kp_2 = u32(0xffb2)
87 const xkb_key_kp_3 = u32(0xffb3)
88 const xkb_key_kp_4 = u32(0xffb4)
89 const xkb_key_kp_5 = u32(0xffb5)
90 const xkb_key_kp_6 = u32(0xffb6)
91 const xkb_key_kp_7 = u32(0xffb7)
92 const xkb_key_kp_8 = u32(0xffb8)
93 const xkb_key_kp_9 = u32(0xffb9)
94 const xkb_key_kp_equal = u32(0xffbd)
95 const xkb_key_f1 = u32(0xffbe)
96 const xkb_key_f2 = u32(0xffbf)
97 const xkb_key_f3 = u32(0xffc0)
98 const xkb_key_f4 = u32(0xffc1)
99 const xkb_key_f5 = u32(0xffc2)
100 const xkb_key_f6 = u32(0xffc3)
101 const xkb_key_f7 = u32(0xffc4)
102 const xkb_key_f8 = u32(0xffc5)
103 const xkb_key_f9 = u32(0xffc6)
104 const xkb_key_f10 = u32(0xffc7)
105 const xkb_key_f11 = u32(0xffc8)
106 const xkb_key_f12 = u32(0xffc9)
107 const xkb_key_f13 = u32(0xffca)
108 const xkb_key_f14 = u32(0xffcb)
109 const xkb_key_f15 = u32(0xffcc)
110 const xkb_key_f16 = u32(0xffcd)
111 const xkb_key_f17 = u32(0xffce)
112 const xkb_key_f18 = u32(0xffcf)
113 const xkb_key_f19 = u32(0xffd0)
114 const xkb_key_f20 = u32(0xffd1)
115 const xkb_key_f21 = u32(0xffd2)
116 const xkb_key_f22 = u32(0xffd3)
117 const xkb_key_f23 = u32(0xffd4)
118 const xkb_key_f24 = u32(0xffd5)
119 const xkb_key_f25 = u32(0xffd6)
120 const xkb_key_shift_l = u32(0xffe1)
121 const xkb_key_shift_r = u32(0xffe2)
122 const xkb_key_control_l = u32(0xffe3)
123 const xkb_key_control_r = u32(0xffe4)
124 const xkb_key_caps_lock = u32(0xffe5)
125 const xkb_key_alt_l = u32(0xffe9)
126 const xkb_key_alt_r = u32(0xffea)
127 const xkb_key_super_l = u32(0xffeb)
128 const xkb_key_delete = u32(0xffff)
129
130 // === Key translation ===
131
132 fn wl_translate_key(keysym Xkb_keysym_t) KeyCode {
133 key := linux_translate_navigation_or_keypad_keysym(u32(keysym))
134 if key != .invalid {
135 return key
136 }
137 return match keysym {
138 xkb_key_space { .space }
139 xkb_key_apostrophe { .apostrophe }
140 xkb_key_comma { .comma }
141 xkb_key_minus { .minus }
142 xkb_key_period { .period }
143 xkb_key_slash { .slash }
144 xkb_key_0 { ._0 }
145 xkb_key_1 { ._1 }
146 xkb_key_2 { ._2 }
147 xkb_key_3 { ._3 }
148 xkb_key_4 { ._4 }
149 xkb_key_5 { ._5 }
150 xkb_key_6 { ._6 }
151 xkb_key_7 { ._7 }
152 xkb_key_8 { ._8 }
153 xkb_key_9 { ._9 }
154 xkb_key_semicolon { .semicolon }
155 xkb_key_equal { .equal }
156 xkb_key_a { .a }
157 xkb_key_b { .b }
158 xkb_key_c { .c }
159 xkb_key_d { .d }
160 xkb_key_e { .e }
161 xkb_key_f { .f }
162 xkb_key_g { .g }
163 xkb_key_h { .h }
164 xkb_key_i { .i }
165 xkb_key_j { .j }
166 xkb_key_k { .k }
167 xkb_key_l { .l }
168 xkb_key_m { .m }
169 xkb_key_n { .n }
170 xkb_key_o { .o }
171 xkb_key_p { .p }
172 xkb_key_q { .q }
173 xkb_key_r { .r }
174 xkb_key_s { .s }
175 xkb_key_t { .t }
176 xkb_key_u { .u }
177 xkb_key_v { .v }
178 xkb_key_w { .w }
179 xkb_key_x { .x }
180 xkb_key_y { .y }
181 xkb_key_z { .z }
182 xkb_key_bracketleft { .left_bracket }
183 xkb_key_backslash { .backslash }
184 xkb_key_bracketright { .right_bracket }
185 xkb_key_grave { .grave_accent }
186 xkb_key_escape { .escape }
187 xkb_key_return { .enter }
188 xkb_key_tab { .tab }
189 xkb_key_backspace { .backspace }
190 xkb_key_caps_lock { .caps_lock }
191 xkb_key_scroll_lock { .scroll_lock }
192 xkb_key_num_lock { .num_lock }
193 xkb_key_print { .print_screen }
194 xkb_key_pause { .pause }
195 xkb_key_f1 { .f1 }
196 xkb_key_f2 { .f2 }
197 xkb_key_f3 { .f3 }
198 xkb_key_f4 { .f4 }
199 xkb_key_f5 { .f5 }
200 xkb_key_f6 { .f6 }
201 xkb_key_f7 { .f7 }
202 xkb_key_f8 { .f8 }
203 xkb_key_f9 { .f9 }
204 xkb_key_f10 { .f10 }
205 xkb_key_f11 { .f11 }
206 xkb_key_f12 { .f12 }
207 xkb_key_f13 { .f13 }
208 xkb_key_f14 { .f14 }
209 xkb_key_f15 { .f15 }
210 xkb_key_f16 { .f16 }
211 xkb_key_f17 { .f17 }
212 xkb_key_f18 { .f18 }
213 xkb_key_f19 { .f19 }
214 xkb_key_f20 { .f20 }
215 xkb_key_f21 { .f21 }
216 xkb_key_f22 { .f22 }
217 xkb_key_f23 { .f23 }
218 xkb_key_f24 { .f24 }
219 xkb_key_f25 { .f25 }
220 xkb_key_shift_l { .left_shift }
221 xkb_key_control_l { .left_control }
222 xkb_key_alt_l { .left_alt }
223 xkb_key_super_l { .left_super }
224 xkb_key_shift_r { .right_shift }
225 xkb_key_control_r { .right_control }
226 xkb_key_alt_r { .right_alt }
227 xkb_key_menu { .menu }
228 else { .invalid }
229 }
230 }
231
232 // === Modifier helpers ===
233
234 fn wl_get_modifiers() u32 {
235 mut mods := u32(0)
236 if g_sapp_state.wl.xkb_state != unsafe { nil } {
237 if C.xkb_state_mod_name_is_active(g_sapp_state.wl.xkb_state, xkb_mod_name_shift,
238 xkb_state_mods_effective) > 0 {
239 mods |= u32(Modifier.shift)
240 }
241 if C.xkb_state_mod_name_is_active(g_sapp_state.wl.xkb_state, xkb_mod_name_ctrl,
242 xkb_state_mods_effective) > 0 {
243 mods |= u32(Modifier.ctrl)
244 }
245 if C.xkb_state_mod_name_is_active(g_sapp_state.wl.xkb_state, xkb_mod_name_alt,
246 xkb_state_mods_effective) > 0 {
247 mods |= u32(Modifier.alt)
248 }
249 if C.xkb_state_mod_name_is_active(g_sapp_state.wl.xkb_state, xkb_mod_name_logo,
250 xkb_state_mods_effective) > 0 {
251 mods |= u32(Modifier.super)
252 }
253 }
254 return mods
255 }
256
257 fn wl_app_event(etype EventType) {
258 if g_sapp_state.desc.event_cb != unsafe { nil }
259 || g_sapp_state.desc.event_userdata_cb != unsafe { nil } {
260 init_sapp_event(etype)
261 call_sapp_event(&g_sapp_state.event)
262 }
263 }
264
265 // === UTF-8 decoding ===
266
267 fn utf8_decode(buf &u8, count int) u32 {
268 if count <= 0 {
269 return 0
270 }
271 b0 := unsafe { buf[0] }
272 if (b0 & 0x80) == 0 {
273 return u32(b0)
274 } else if (b0 & 0xE0) == 0xC0 && count >= 2 {
275 return (u32(b0 & 0x1F) << 6) | u32(unsafe { buf[1] } & 0x3F)
276 } else if (b0 & 0xF0) == 0xE0 && count >= 3 {
277 return (u32(b0 & 0x0F) << 12) | (u32(unsafe { buf[1] } & 0x3F) << 6) | u32(unsafe { buf[2] } & 0x3F)
278 } else if (b0 & 0xF8) == 0xF0 && count >= 4 {
279 return (u32(b0 & 0x07) << 18) | (u32(unsafe { buf[1] } & 0x3F) << 12) | (u32(unsafe { buf[2] } & 0x3F) << 6) | u32(unsafe { buf[3] } & 0x3F)
280 }
281 return 0
282 }
283
284 // === Key repeat ===
285
286 fn wl_setup_key_repeat_timer() {
287 if g_sapp_state.wl.key_repeat_timer_fd >= 0 {
288 C.close(g_sapp_state.wl.key_repeat_timer_fd)
289 }
290 g_sapp_state.wl.key_repeat_timer_fd = C.timerfd_create(clock_monotonic,
291 tfd_cloexec | tfd_nonblock)
292 }
293
294 fn wl_start_key_repeat(scancode u32) {
295 if g_sapp_state.wl.key_repeat_timer_fd < 0 || g_sapp_state.wl.key_repeat_rate <= 0 {
296 return
297 }
298 g_sapp_state.wl.key_repeat_keycode = scancode
299
300 delay_ms := g_sapp_state.wl.key_repeat_delay
301 rate := g_sapp_state.wl.key_repeat_rate
302 repeat_interval_ns := i64(1000000000) / i64(rate)
303
304 timer := C.itimerspec{
305 it_value: C.timespec{
306 tv_sec: i64(delay_ms / 1000)
307 tv_nsec: i64((delay_ms % 1000)) * 1000000
308 }
309 it_interval: C.timespec{
310 tv_sec: repeat_interval_ns / i64(1000000000)
311 tv_nsec: repeat_interval_ns % i64(1000000000)
312 }
313 }
314 C.timerfd_settime(g_sapp_state.wl.key_repeat_timer_fd, 0, &timer, unsafe { nil })
315 }
316
317 fn wl_stop_key_repeat() {
318 if g_sapp_state.wl.key_repeat_timer_fd < 0 {
319 return
320 }
321 timer := C.itimerspec{}
322 C.timerfd_settime(g_sapp_state.wl.key_repeat_timer_fd, 0, &timer, unsafe { nil })
323 g_sapp_state.wl.key_repeat_keycode = 0
324 }
325
326 fn wl_handle_key_repeat() {
327 if g_sapp_state.wl.key_repeat_timer_fd < 0 || g_sapp_state.wl.key_repeat_keycode == 0 {
328 return
329 }
330 mut expirations := u64(0)
331 if C.read(g_sapp_state.wl.key_repeat_timer_fd, &expirations, sizeof(expirations)) > 0
332 && expirations > 0 {
333 if g_sapp_state.wl.xkb_state == unsafe { nil } {
334 return
335 }
336 keysym := C.xkb_state_key_get_one_sym(g_sapp_state.wl.xkb_state,
337 g_sapp_state.wl.key_repeat_keycode)
338 sapp_key := wl_translate_key(keysym)
339
340 if sapp_key != .invalid {
341 init_sapp_event(.key_down)
342 g_sapp_state.event.key_code = sapp_key
343 g_sapp_state.event.key_repeat = true
344 g_sapp_state.event.modifiers = wl_get_modifiers()
345 call_sapp_event(&g_sapp_state.event)
346 }
347
348 mut buf := [8]u8{}
349 count := C.xkb_state_key_get_utf8(g_sapp_state.wl.xkb_state,
350 g_sapp_state.wl.key_repeat_keycode, &char(&buf[0]), usize(buf.len))
351 if count > 0 && count < buf.len {
352 codepoint := utf8_decode(&buf[0], count)
353 if codepoint > 0 && codepoint < 0x110000 {
354 init_sapp_event(.char)
355 g_sapp_state.event.char_code = codepoint
356 g_sapp_state.event.key_repeat = true
357 g_sapp_state.event.modifiers = wl_get_modifiers()
358 call_sapp_event(&g_sapp_state.event)
359 }
360 }
361 }
362 }
363
364 // === Keyboard listener callbacks ===
365
366 fn wl_keyboard_handle_keymap(data voidptr, keyboard &C.wl_keyboard, format u32, fd int, size u32) {
367 if format != wl_keyboard_keymap_format_xkb_v1 {
368 C.close(fd)
369 return
370 }
371 map_str := C.mmap(unsafe { nil }, usize(size), prot_read, map_private, fd, 0)
372 if map_str == map_failed {
373 C.close(fd)
374 return
375 }
376 if g_sapp_state.wl.xkb_keymap != unsafe { nil } {
377 C.xkb_keymap_unref(g_sapp_state.wl.xkb_keymap)
378 }
379 g_sapp_state.wl.xkb_keymap = C.xkb_keymap_new_from_string(g_sapp_state.wl.xkb_context,
380 map_str, xkb_keymap_format_text_v1, xkb_keymap_compile_no_flags)
381 C.munmap(map_str, usize(size))
382 C.close(fd)
383
384 if g_sapp_state.wl.xkb_keymap == unsafe { nil } {
385 return
386 }
387 if g_sapp_state.wl.xkb_state != unsafe { nil } {
388 C.xkb_state_unref(g_sapp_state.wl.xkb_state)
389 }
390 g_sapp_state.wl.xkb_state = C.xkb_state_new(g_sapp_state.wl.xkb_keymap)
391 }
392
393 fn wl_keyboard_handle_enter(data voidptr, keyboard &C.wl_keyboard, serial u32, surface &C.wl_surface, keys &C.wl_array) {
394 g_sapp_state.wl.focused = true
395 wl_app_event(.focused)
396 }
397
398 fn wl_keyboard_handle_leave(data voidptr, keyboard &C.wl_keyboard, serial u32, surface &C.wl_surface) {
399 g_sapp_state.wl.focused = false
400 wl_app_event(.unfocused)
401 }
402
403 fn wl_keyboard_handle_key(data voidptr, keyboard &C.wl_keyboard, serial u32, time u32, key u32, state u32) {
404 if g_sapp_state.wl.xkb_state == unsafe { nil } {
405 return
406 }
407 // Wayland key codes are Linux input codes + 8
408 keycode := key + 8
409 keysym := C.xkb_state_key_get_one_sym(g_sapp_state.wl.xkb_state, keycode)
410 sapp_key := wl_translate_key(keysym)
411
412 if sapp_key != .invalid {
413 etype := if state == wl_keyboard_key_state_pressed {
414 EventType.key_down
415 } else {
416 EventType.key_up
417 }
418 init_sapp_event(etype)
419 g_sapp_state.event.key_code = sapp_key
420 g_sapp_state.event.key_repeat = false
421 g_sapp_state.event.modifiers = wl_get_modifiers()
422 call_sapp_event(&g_sapp_state.event)
423
424 if state == wl_keyboard_key_state_pressed {
425 wl_start_key_repeat(keycode)
426 } else {
427 if g_sapp_state.wl.key_repeat_keycode == keycode {
428 wl_stop_key_repeat()
429 }
430 }
431 }
432
433 // Generate CHAR event for key press
434 if state == wl_keyboard_key_state_pressed {
435 mut buf := [8]u8{}
436 count := C.xkb_state_key_get_utf8(g_sapp_state.wl.xkb_state, keycode, &char(&buf[0]),
437 usize(buf.len))
438 if count > 0 && count < buf.len {
439 codepoint := utf8_decode(&buf[0], count)
440 if codepoint > 0 && codepoint < 0x110000 {
441 init_sapp_event(.char)
442 g_sapp_state.event.char_code = codepoint
443 g_sapp_state.event.key_repeat = false
444 g_sapp_state.event.modifiers = wl_get_modifiers()
445 call_sapp_event(&g_sapp_state.event)
446 }
447 }
448 }
449 }
450
451 fn wl_keyboard_handle_modifiers(data voidptr, keyboard &C.wl_keyboard, serial u32, mods_depressed u32, mods_latched u32, mods_locked u32, group u32) {
452 if g_sapp_state.wl.xkb_state != unsafe { nil } {
453 C.xkb_state_update_mask(g_sapp_state.wl.xkb_state, mods_depressed, mods_latched,
454 mods_locked, 0, 0, group)
455 }
456 }
457
458 fn wl_keyboard_handle_repeat_info(data voidptr, keyboard &C.wl_keyboard, rate int, delay int) {
459 g_sapp_state.wl.key_repeat_rate = rate
460 g_sapp_state.wl.key_repeat_delay = delay
461 }
462
463 // === Pointer listener callbacks ===
464
465 fn wl_pointer_handle_enter(data voidptr, pointer &C.wl_pointer, serial u32, surface &C.wl_surface, sx i32, sy i32) {
466 mut x := f32(C.wl_fixed_to_double(sx))
467 mut y := f32(C.wl_fixed_to_double(sy))
468 if g_sapp_state.wl.viewport != unsafe { nil } && g_sapp_state.wl.scale > 1.0 {
469 x *= g_sapp_state.wl.scale
470 y *= g_sapp_state.wl.scale
471 }
472 g_sapp_state.mouse.x = x
473 g_sapp_state.mouse.y = y
474 g_sapp_state.wl.pointer_enter_serial = serial
475
476 init_sapp_event(.mouse_enter)
477 g_sapp_state.event.mouse_x = g_sapp_state.mouse.x
478 g_sapp_state.event.mouse_y = g_sapp_state.mouse.y
479 call_sapp_event(&g_sapp_state.event)
480 }
481
482 fn wl_pointer_handle_leave(data voidptr, pointer &C.wl_pointer, serial u32, surface &C.wl_surface) {
483 init_sapp_event(.mouse_leave)
484 call_sapp_event(&g_sapp_state.event)
485 }
486
487 fn wl_pointer_handle_motion(data voidptr, pointer &C.wl_pointer, time u32, sx i32, sy i32) {
488 mut new_x := f32(C.wl_fixed_to_double(sx))
489 mut new_y := f32(C.wl_fixed_to_double(sy))
490 if g_sapp_state.wl.viewport != unsafe { nil } && g_sapp_state.wl.scale > 1.0 {
491 new_x *= g_sapp_state.wl.scale
492 new_y *= g_sapp_state.wl.scale
493 }
494 g_sapp_state.mouse.dx = new_x - g_sapp_state.mouse.x
495 g_sapp_state.mouse.dy = new_y - g_sapp_state.mouse.y
496 g_sapp_state.mouse.x = new_x
497 g_sapp_state.mouse.y = new_y
498
499 init_sapp_event(.mouse_move)
500 g_sapp_state.event.mouse_x = g_sapp_state.mouse.x
501 g_sapp_state.event.mouse_y = g_sapp_state.mouse.y
502 g_sapp_state.event.mouse_dx = g_sapp_state.mouse.dx
503 g_sapp_state.event.mouse_dy = g_sapp_state.mouse.dy
504 call_sapp_event(&g_sapp_state.event)
505 }
506
507 fn wl_pointer_handle_button(data voidptr, pointer &C.wl_pointer, serial u32, time u32, button u32, state u32) {
508 sapp_btn := match button {
509 u32(btn_left) { MouseButton.left }
510 u32(btn_right) { MouseButton.right }
511 u32(btn_middle) { MouseButton.middle }
512 else { MouseButton.invalid }
513 }
514
515 if sapp_btn != .invalid {
516 etype := if state == wl_pointer_button_state_pressed {
517 EventType.mouse_down
518 } else {
519 EventType.mouse_up
520 }
521 init_sapp_event(etype)
522 g_sapp_state.event.mouse_button = sapp_btn
523 g_sapp_state.event.modifiers = wl_get_modifiers()
524 call_sapp_event(&g_sapp_state.event)
525 }
526 }
527
528 fn wl_pointer_handle_axis(data voidptr, pointer &C.wl_pointer, time u32, axis u32, value i32) {
529 scroll_value := f32(C.wl_fixed_to_double(value))
530 init_sapp_event(.mouse_scroll)
531 if axis == wl_pointer_axis_vertical_scroll {
532 g_sapp_state.event.scroll_y = -scroll_value / 10.0
533 } else if axis == wl_pointer_axis_horizontal_scroll {
534 g_sapp_state.event.scroll_x = scroll_value / 10.0
535 }
536 g_sapp_state.event.modifiers = wl_get_modifiers()
537 call_sapp_event(&g_sapp_state.event)
538 }
539
540 fn wl_pointer_handle_frame(data voidptr, pointer &C.wl_pointer) {}
541
542 fn wl_pointer_handle_axis_source(data voidptr, pointer &C.wl_pointer, axis_source u32) {}
543
544 fn wl_pointer_handle_axis_stop(data voidptr, pointer &C.wl_pointer, time u32, axis u32) {}
545
546 fn wl_pointer_handle_axis_discrete(data voidptr, pointer &C.wl_pointer, axis u32, discrete i32) {}
547
548 fn wl_pointer_handle_axis_value120(data voidptr, pointer &C.wl_pointer, axis u32, value120 i32) {}
549
550 fn wl_pointer_handle_axis_relative_direction(data voidptr, pointer &C.wl_pointer, axis u32, direction u32) {}
551
552 // === Seat listener callbacks ===
553
554 fn wl_seat_handle_capabilities(data voidptr, seat &C.wl_seat, caps u32) {
555 // Keyboard
556 if (caps & wl_seat_capability_keyboard) != 0 && g_sapp_state.wl.keyboard == unsafe { nil } {
557 g_sapp_state.wl.keyboard = C.wl_seat_get_keyboard(seat)
558 C.wl_keyboard_add_listener(g_sapp_state.wl.keyboard, &wl_keyboard_listener,
559 unsafe { nil })
560 } else if (caps & wl_seat_capability_keyboard) == 0
561 && g_sapp_state.wl.keyboard != unsafe { nil } {
562 C.wl_keyboard_destroy(g_sapp_state.wl.keyboard)
563 g_sapp_state.wl.keyboard = unsafe { nil }
564 }
565
566 // Pointer
567 if (caps & wl_seat_capability_pointer) != 0 && g_sapp_state.wl.pointer == unsafe { nil } {
568 g_sapp_state.wl.pointer = C.wl_seat_get_pointer(seat)
569 C.wl_pointer_add_listener(g_sapp_state.wl.pointer, &wl_pointer_listener, unsafe { nil })
570
571 if g_sapp_state.wl.cursor_shape_manager != unsafe { nil }
572 && g_sapp_state.wl.cursor_shape_device == unsafe { nil } {
573 g_sapp_state.wl.cursor_shape_device = C.wp_cursor_shape_manager_v1_get_pointer(g_sapp_state.wl.cursor_shape_manager,
574 g_sapp_state.wl.pointer)
575 }
576 if g_sapp_state.wl.relative_pointer_mgr != unsafe { nil }
577 && g_sapp_state.wl.relative_pointer == unsafe { nil } {
578 g_sapp_state.wl.relative_pointer = C.zwp_relative_pointer_manager_v1_get_relative_pointer(g_sapp_state.wl.relative_pointer_mgr,
579 g_sapp_state.wl.pointer)
580 }
581 if g_sapp_state.wl.data_device_manager != unsafe { nil }
582 && g_sapp_state.wl.data_device == unsafe { nil } {
583 g_sapp_state.wl.data_device =
584 C.wl_data_device_manager_get_data_device(g_sapp_state.wl.data_device_manager, seat)
585 C.wl_data_device_add_listener(g_sapp_state.wl.data_device,
586 &wl_data_device_listener, unsafe { nil })
587 }
588 } else if (caps & wl_seat_capability_pointer) == 0
589 && g_sapp_state.wl.pointer != unsafe { nil } {
590 if g_sapp_state.wl.cursor_shape_device != unsafe { nil } {
591 C.wp_cursor_shape_device_v1_destroy(g_sapp_state.wl.cursor_shape_device)
592 g_sapp_state.wl.cursor_shape_device = unsafe { nil }
593 }
594 if g_sapp_state.wl.relative_pointer != unsafe { nil } {
595 C.zwp_relative_pointer_v1_destroy(g_sapp_state.wl.relative_pointer)
596 g_sapp_state.wl.relative_pointer = unsafe { nil }
597 }
598 if g_sapp_state.wl.data_device != unsafe { nil } {
599 C.wl_data_device_destroy(g_sapp_state.wl.data_device)
600 g_sapp_state.wl.data_device = unsafe { nil }
601 }
602 C.wl_pointer_destroy(g_sapp_state.wl.pointer)
603 g_sapp_state.wl.pointer = unsafe { nil }
604 }
605
606 // Touch
607 if (caps & wl_seat_capability_touch) != 0 && g_sapp_state.wl.touch == unsafe { nil } {
608 g_sapp_state.wl.touch = C.wl_seat_get_touch(seat)
609 } else if (caps & wl_seat_capability_touch) == 0 && g_sapp_state.wl.touch != unsafe { nil } {
610 C.wl_touch_destroy(g_sapp_state.wl.touch)
611 g_sapp_state.wl.touch = unsafe { nil }
612 }
613 }
614
615 fn wl_seat_handle_name(data voidptr, seat &C.wl_seat, name &char) {
616 }
617
618 // === Registry listener callbacks ===
619
620 fn wl_registry_handle_global(data voidptr, registry &C.wl_registry, name u32, iface &char, version u32) {
621 unsafe {
622 if C.strcmp(iface, c'wl_compositor') == 0 {
623 g_sapp_state.wl.compositor = &C.wl_compositor(C.wl_registry_bind(registry, name,
624 &C.wl_compositor_interface, 4))
625 } else if C.strcmp(iface, c'xdg_wm_base') == 0 {
626 g_sapp_state.wl.xdg_wm_base = &C.xdg_wm_base(C.wl_registry_bind(registry, name,
627 &C.xdg_wm_base_interface, 1))
628 } else if C.strcmp(iface, c'wl_seat') == 0 {
629 g_sapp_state.wl.seat = &C.wl_seat(C.wl_registry_bind(registry, name,
630 &C.wl_seat_interface, 5))
631 C.wl_seat_add_listener(g_sapp_state.wl.seat, &wl_seat_listener, nil)
632 } else if C.strcmp(iface, c'wl_shm') == 0 {
633 g_sapp_state.wl.shm = &C.wl_shm(C.wl_registry_bind(registry, name,
634 &C.wl_shm_interface, 1))
635 } else if C.strcmp(iface, c'wl_data_device_manager') == 0 {
636 g_sapp_state.wl.data_device_manager = &C.wl_data_device_manager(C.wl_registry_bind(registry,
637 name, &C.wl_data_device_manager_interface, 3))
638 } else if C.strcmp(iface, c'wp_fractional_scale_manager_v1') == 0 {
639 g_sapp_state.wl.fractional_scale_mgr = &C.wp_fractional_scale_manager_v1(C.wl_registry_bind(registry,
640 name, &C.wp_fractional_scale_manager_v1_interface, 1))
641 } else if C.strcmp(iface, c'wp_viewporter') == 0 {
642 g_sapp_state.wl.viewporter = &C.wp_viewporter(C.wl_registry_bind(registry, name,
643 &C.wp_viewporter_interface, 1))
644 } else if C.strcmp(iface, c'wp_cursor_shape_manager_v1') == 0 {
645 g_sapp_state.wl.cursor_shape_manager = &C.wp_cursor_shape_manager_v1(C.wl_registry_bind(registry,
646 name, &C.wp_cursor_shape_manager_v1_interface, 1))
647 } else if C.strcmp(iface, c'zwp_pointer_constraints_v1') == 0 {
648 g_sapp_state.wl.pointer_constraints = &C.zwp_pointer_constraints_v1(C.wl_registry_bind(registry,
649 name, &C.zwp_pointer_constraints_v1_interface, 1))
650 } else if C.strcmp(iface, c'zwp_relative_pointer_manager_v1') == 0 {
651 g_sapp_state.wl.relative_pointer_mgr = &C.zwp_relative_pointer_manager_v1(C.wl_registry_bind(registry,
652 name, &C.zwp_relative_pointer_manager_v1_interface, 1))
653 } else if C.strcmp(iface, c'zxdg_decoration_manager_v1') == 0 {
654 g_sapp_state.wl.decoration_manager = &C.zxdg_decoration_manager_v1(C.wl_registry_bind(registry,
655 name, &C.zxdg_decoration_manager_v1_interface, 1))
656 }
657 }
658 }
659
660 fn wl_registry_handle_global_remove(data voidptr, registry &C.wl_registry, name u32) {
661 }
662
663 // === XDG surface / toplevel callbacks ===
664
665 fn wl_xdg_surface_configure(data voidptr, xdg_surface &C.xdg_surface, serial u32) {
666 C.xdg_surface_ack_configure(xdg_surface, serial)
667 }
668
669 fn wl_xdg_toplevel_configure(data voidptr, xdg_toplevel &C.xdg_toplevel, width int, height int, states &C.wl_array) {
670 // Iterate wl_array of uint32_t states
671 mut fullscreen := false
672 mut maximized := false
673 if states.size > 0 {
674 state_ptr := &u32(states.data)
675 num_states := int(states.size / sizeof(u32))
676 for i in 0 .. num_states {
677 s := unsafe { state_ptr[i] }
678 if s == xdg_toplevel_state_fullscreen {
679 fullscreen = true
680 }
681 if s == xdg_toplevel_state_maximized {
682 maximized = true
683 }
684 }
685 }
686 g_sapp_state.fullscreen = fullscreen
687 _ = maximized
688
689 if width > 0 && height > 0 {
690 size_changed := g_sapp_state.wl.width != width || g_sapp_state.wl.height != height
691
692 g_sapp_state.wl.width = width
693 g_sapp_state.wl.height = height
694
695 // Apply fractional scale if available and high_dpi is enabled
696 if g_sapp_state.wl.scale_numerator > 0 && g_sapp_state.desc.high_dpi {
697 g_sapp_state.wl.scale = f32(g_sapp_state.wl.scale_numerator) / 120.0
698 g_sapp_state.dpi_scale = if g_sapp_state.wl.viewport != unsafe { nil } {
699 f32(1.0)
700 } else {
701 g_sapp_state.wl.scale
702 }
703 }
704
705 g_sapp_state.wl.fb_width = int(f32(g_sapp_state.wl.width) * g_sapp_state.wl.scale)
706 g_sapp_state.wl.fb_height = int(f32(g_sapp_state.wl.height) * g_sapp_state.wl.scale)
707
708 g_sapp_state.window_width = width
709 g_sapp_state.window_height = height
710 g_sapp_state.framebuffer_width = g_sapp_state.wl.fb_width
711 g_sapp_state.framebuffer_height = g_sapp_state.wl.fb_height
712
713 if g_sapp_state.wl.egl_window != unsafe { nil } {
714 C.wl_egl_window_resize(g_sapp_state.wl.egl_window, g_sapp_state.wl.fb_width,
715 g_sapp_state.wl.fb_height, 0, 0)
716 }
717 if g_sapp_state.wl.viewport != unsafe { nil } {
718 C.wp_viewport_set_destination(g_sapp_state.wl.viewport, width, height)
719 }
720 if size_changed && !g_sapp_state.first_frame {
721 wl_app_event(.resized)
722 }
723 }
724 }
725
726 fn wl_xdg_toplevel_close(data voidptr, xdg_toplevel &C.xdg_toplevel) {
727 g_sapp_state.quit_requested = true
728 }
729
730 // === Fractional scale callback ===
731
732 fn wl_fractional_scale_preferred_scale(data voidptr, fractional_scale &C.wp_fractional_scale_v1, scale u32) {
733 g_sapp_state.wl.scale_numerator = scale
734
735 if g_sapp_state.desc.high_dpi {
736 g_sapp_state.wl.scale = f32(scale) / 120.0
737 g_sapp_state.dpi_scale = if g_sapp_state.wl.viewport != unsafe { nil } {
738 f32(1.0)
739 } else {
740 g_sapp_state.wl.scale
741 }
742 } else {
743 g_sapp_state.wl.scale = 1.0
744 g_sapp_state.dpi_scale = 1.0
745 }
746
747 g_sapp_state.wl.fb_width = int(f32(g_sapp_state.wl.width) * g_sapp_state.wl.scale)
748 g_sapp_state.wl.fb_height = int(f32(g_sapp_state.wl.height) * g_sapp_state.wl.scale)
749 g_sapp_state.framebuffer_width = g_sapp_state.wl.fb_width
750 g_sapp_state.framebuffer_height = g_sapp_state.wl.fb_height
751
752 if g_sapp_state.wl.egl_window != unsafe { nil } {
753 C.wl_egl_window_resize(g_sapp_state.wl.egl_window, g_sapp_state.wl.fb_width,
754 g_sapp_state.wl.fb_height, 0, 0)
755 }
756 if g_sapp_state.wl.viewport != unsafe { nil } {
757 C.wp_viewport_set_destination(g_sapp_state.wl.viewport, g_sapp_state.wl.width,
758 g_sapp_state.wl.height)
759 }
760 }
761
762 // === Data device callbacks (drag and drop) ===
763
764 fn wl_data_offer_offer(data voidptr, offer &C.wl_data_offer, mime_type &char) {
765 if C.strcmp(mime_type, c'text/uri-list') == 0 {
766 g_sapp_state.wl.data_offer = unsafe { offer }
767 }
768 }
769
770 fn wl_data_offer_source_actions(data voidptr, offer &C.wl_data_offer, source_actions u32) {
771 }
772
773 fn wl_data_offer_action(data voidptr, offer &C.wl_data_offer, dnd_action u32) {
774 }
775
776 fn wl_data_device_data_offer(data voidptr, device &C.wl_data_device, offer &C.wl_data_offer) {
777 C.wl_data_offer_add_listener(offer, &wl_data_offer_listener, unsafe { nil })
778 }
779
780 fn wl_data_device_enter(data voidptr, device &C.wl_data_device, serial u32, surface &C.wl_surface, x i32, y i32, offer &C.wl_data_offer) {
781 if offer != unsafe { nil } && g_sapp_state.wl.data_offer == offer {
782 C.wl_data_offer_accept(offer, serial, c'text/uri-list')
783 C.wl_data_offer_set_actions(offer, wl_data_device_manager_dnd_action_copy,
784 wl_data_device_manager_dnd_action_copy)
785 }
786 }
787
788 fn wl_data_device_leave(data voidptr, device &C.wl_data_device) {
789 if g_sapp_state.wl.data_offer != unsafe { nil } {
790 C.wl_data_offer_destroy(g_sapp_state.wl.data_offer)
791 g_sapp_state.wl.data_offer = unsafe { nil }
792 }
793 }
794
795 fn wl_data_device_motion(data voidptr, device &C.wl_data_device, time u32, x i32, y i32) {
796 }
797
798 fn wl_data_device_drop(data voidptr, device &C.wl_data_device) {
799 if g_sapp_state.wl.data_offer == unsafe { nil } {
800 return
801 }
802 mut fds := [2]int{}
803 if C.pipe(&fds[0]) == -1 {
804 C.wl_data_offer_destroy(g_sapp_state.wl.data_offer)
805 g_sapp_state.wl.data_offer = unsafe { nil }
806 return
807 }
808 C.wl_data_offer_receive(g_sapp_state.wl.data_offer, c'text/uri-list', fds[1])
809 C.close(fds[1])
810 C.wl_display_flush(g_sapp_state.wl.display)
811
812 mut buffer := [8192]u8{}
813 mut total_read := isize(0)
814 for {
815 n := C.read(fds[0], unsafe { &buffer[0] + total_read }, usize(buffer.len -
816 int(total_read) - 1))
817 if n <= 0 {
818 break
819 }
820 total_read += n
821 if total_read >= isize(buffer.len - 1) {
822 break
823 }
824 }
825 C.close(fds[0])
826
827 if total_read > 0 && g_sapp_state.drop.enabled {
828 unsafe {
829 buffer[total_read] = 0
830 }
831 // Clear drop buffer
832 if g_sapp_state.drop.buffer != unsafe { nil } {
833 unsafe { C.memset(g_sapp_state.drop.buffer, 0, usize(g_sapp_state.drop.buf_size)) }
834 }
835 g_sapp_state.drop.num_files = 0
836
837 mut line := C.strtok(&char(&buffer[0]), c'\r\n')
838 for line != unsafe { nil } && g_sapp_state.drop.num_files < g_sapp_state.drop.max_files {
839 if C.strlen(line) == 0 {
840 line = C.strtok(unsafe { nil }, c'\r\n')
841 continue
842 }
843 mut file_path := line
844 if C.strncmp(line, c'file://', 7) == 0 {
845 file_path = unsafe { line + 7 }
846 }
847 // URL decode into drop buffer
848 mut dst := sapp_dropped_file_path_ptr(g_sapp_state.drop.num_files)
849 dst_end := unsafe { dst + (g_sapp_state.drop.max_path_length - 1) }
850 mut i := usize(0)
851 for unsafe { file_path[i] } != 0 && dst < dst_end {
852 ch := unsafe { u8(file_path[i]) }
853 if ch == `%` && unsafe { file_path[i + 1] } != 0
854 && unsafe { file_path[i + 2] } != 0 {
855 mut hex := [3]u8{}
856 hex[0] = unsafe { u8(file_path[i + 1]) }
857 hex[1] = unsafe { u8(file_path[i + 2]) }
858 hex[2] = 0
859 unsafe {
860 *dst = u8(C.strtol(&char(&hex[0]), nil, 16))
861 }
862 dst = unsafe { dst + 1 }
863 i += 3
864 } else {
865 unsafe {
866 *dst = ch
867 }
868 dst = unsafe { dst + 1 }
869 i++
870 }
871 }
872 unsafe {
873 *dst = 0
874 }
875 g_sapp_state.drop.num_files++
876 line = C.strtok(unsafe { nil }, c'\r\n')
877 }
878
879 if g_sapp_state.drop.num_files > 0 {
880 C.wl_data_offer_finish(g_sapp_state.wl.data_offer)
881 init_sapp_event(.files_dropped)
882 g_sapp_state.event.modifiers = wl_get_modifiers()
883 call_sapp_event(&g_sapp_state.event)
884 }
885 }
886
887 C.wl_data_offer_destroy(g_sapp_state.wl.data_offer)
888 g_sapp_state.wl.data_offer = unsafe { nil }
889 }
890
891 fn wl_data_device_selection(data voidptr, device &C.wl_data_device, offer &C.wl_data_offer) {
892 }
893
894 // === Listener struct definitions ===
895 // These are arrays of function pointers matching the C listener struct layouts.
896
897 const wl_keyboard_listener = [
898 voidptr(wl_keyboard_handle_keymap),
899 voidptr(wl_keyboard_handle_enter),
900 voidptr(wl_keyboard_handle_leave),
901 voidptr(wl_keyboard_handle_key),
902 voidptr(wl_keyboard_handle_modifiers),
903 voidptr(wl_keyboard_handle_repeat_info),
904 ]!
905
906 const wl_pointer_listener = [
907 voidptr(wl_pointer_handle_enter),
908 voidptr(wl_pointer_handle_leave),
909 voidptr(wl_pointer_handle_motion),
910 voidptr(wl_pointer_handle_button),
911 voidptr(wl_pointer_handle_axis),
912 voidptr(wl_pointer_handle_frame),
913 voidptr(wl_pointer_handle_axis_source),
914 voidptr(wl_pointer_handle_axis_stop),
915 voidptr(wl_pointer_handle_axis_discrete),
916 voidptr(wl_pointer_handle_axis_value120),
917 voidptr(wl_pointer_handle_axis_relative_direction),
918 ]!
919
920 const wl_seat_listener = [
921 voidptr(wl_seat_handle_capabilities),
922 voidptr(wl_seat_handle_name),
923 ]!
924
925 const wl_registry_listener = [
926 voidptr(wl_registry_handle_global),
927 voidptr(wl_registry_handle_global_remove),
928 ]!
929
930 const wl_xdg_surface_listener = [
931 voidptr(wl_xdg_surface_configure),
932 ]!
933
934 const wl_xdg_toplevel_listener = [
935 voidptr(wl_xdg_toplevel_configure),
936 voidptr(wl_xdg_toplevel_close),
937 ]!
938
939 const wl_fractional_scale_listener = [
940 voidptr(wl_fractional_scale_preferred_scale),
941 ]!
942
943 const wl_data_offer_listener = [
944 voidptr(wl_data_offer_offer),
945 voidptr(wl_data_offer_source_actions),
946 voidptr(wl_data_offer_action),
947 ]!
948
949 const wl_data_device_listener = [
950 voidptr(wl_data_device_data_offer),
951 voidptr(wl_data_device_enter),
952 voidptr(wl_data_device_leave),
953 voidptr(wl_data_device_motion),
954 voidptr(wl_data_device_drop),
955 voidptr(wl_data_device_selection),
956 ]!
957 // === Main Wayland run function ===
958
959 fn wl_set_resizable(resizable bool) {
960 if g_sapp_state.wl.xdg_toplevel == unsafe { nil }
961 || g_sapp_state.wl.surface == unsafe { nil } {
962 return
963 }
964 if resizable {
965 C.xdg_toplevel_set_min_size(g_sapp_state.wl.xdg_toplevel, 0, 0)
966 C.xdg_toplevel_set_max_size(g_sapp_state.wl.xdg_toplevel, 0, 0)
967 } else {
968 w := if g_sapp_state.window_width > 0 {
969 g_sapp_state.window_width
970 } else if g_sapp_state.wl.width > 0 {
971 g_sapp_state.wl.width
972 } else {
973 fallback_default_window_width
974 }
975 h := if g_sapp_state.window_height > 0 {
976 g_sapp_state.window_height
977 } else if g_sapp_state.wl.height > 0 {
978 g_sapp_state.wl.height
979 } else {
980 fallback_default_window_height
981 }
982 C.xdg_toplevel_set_min_size(g_sapp_state.wl.xdg_toplevel, i32(w), i32(h))
983 C.xdg_toplevel_set_max_size(g_sapp_state.wl.xdg_toplevel, i32(w), i32(h))
984 }
985 C.wl_surface_commit(g_sapp_state.wl.surface)
986 if g_sapp_state.wl.display != unsafe { nil } {
987 C.wl_display_flush(g_sapp_state.wl.display)
988 }
989 }
990
991 pub fn wl_run(desc &Desc) {
992 sapp_init_state(desc)
993
994 // Initialize key repeat
995 g_sapp_state.wl.key_repeat_timer_fd = -1
996 g_sapp_state.wl.key_repeat_rate = 25
997 g_sapp_state.wl.key_repeat_delay = 600
998
999 // Connect to Wayland display
1000 g_sapp_state.wl.display = C.wl_display_connect(unsafe { nil })
1001 if g_sapp_state.wl.display == unsafe { nil } {
1002 eprintln('sokol_app: failed to connect to Wayland display')
1003 return
1004 }
1005
1006 // Get registry and bind global objects
1007 g_sapp_state.wl.registry = C.wl_display_get_registry(g_sapp_state.wl.display)
1008 C.wl_registry_add_listener(g_sapp_state.wl.registry, &wl_registry_listener, unsafe { nil })
1009 C.wl_display_roundtrip(g_sapp_state.wl.display)
1010
1011 if g_sapp_state.wl.compositor == unsafe { nil }
1012 || g_sapp_state.wl.xdg_wm_base == unsafe { nil } {
1013 eprintln('sokol_app: Wayland compositor or xdg_wm_base not available')
1014 C.wl_display_disconnect(g_sapp_state.wl.display)
1015 return
1016 }
1017
1018 // Create surface
1019 g_sapp_state.wl.surface = C.wl_compositor_create_surface(g_sapp_state.wl.compositor)
1020
1021 // Initialize XKB
1022 g_sapp_state.wl.xkb_context = C.xkb_context_new(xkb_context_no_flags)
1023 if g_sapp_state.wl.xkb_context == unsafe { nil } {
1024 eprintln('sokol_app: failed to create XKB context')
1025 return
1026 }
1027
1028 // Setup key repeat timer
1029 wl_setup_key_repeat_timer()
1030
1031 g_sapp_state.wl.width = g_sapp_state.window_width
1032 g_sapp_state.wl.height = g_sapp_state.window_height
1033 if g_sapp_state.wl.width == 0 {
1034 g_sapp_state.wl.width = fallback_default_window_width
1035 }
1036 if g_sapp_state.wl.height == 0 {
1037 g_sapp_state.wl.height = fallback_default_window_height
1038 }
1039
1040 g_sapp_state.wl.scale = 1.0
1041 g_sapp_state.dpi_scale = 1.0
1042 g_sapp_state.wl.fb_width = int(f32(g_sapp_state.wl.width) * g_sapp_state.wl.scale)
1043 g_sapp_state.wl.fb_height = int(f32(g_sapp_state.wl.height) * g_sapp_state.wl.scale)
1044
1045 // Create EGL window
1046 g_sapp_state.wl.egl_window = C.wl_egl_window_create(g_sapp_state.wl.surface,
1047 g_sapp_state.wl.fb_width, g_sapp_state.wl.fb_height)
1048 if g_sapp_state.wl.egl_window == unsafe { nil } {
1049 eprintln('sokol_app: failed to create Wayland EGL window')
1050 return
1051 }
1052
1053 // Create XDG surface and toplevel
1054 g_sapp_state.wl.xdg_surface = C.xdg_wm_base_get_xdg_surface(g_sapp_state.wl.xdg_wm_base,
1055 g_sapp_state.wl.surface)
1056 C.xdg_surface_add_listener(g_sapp_state.wl.xdg_surface, &wl_xdg_surface_listener,
1057 unsafe { nil })
1058
1059 g_sapp_state.wl.xdg_toplevel = C.xdg_surface_get_toplevel(g_sapp_state.wl.xdg_surface)
1060 C.xdg_toplevel_add_listener(g_sapp_state.wl.xdg_toplevel, &wl_xdg_toplevel_listener,
1061 unsafe { nil })
1062 C.xdg_toplevel_set_title(g_sapp_state.wl.xdg_toplevel, &char(&g_sapp_state.window_title[0]))
1063
1064 // Request server-side decorations if available
1065 if g_sapp_state.wl.decoration_manager != unsafe { nil } {
1066 g_sapp_state.wl.toplevel_decoration = C.zxdg_decoration_manager_v1_get_toplevel_decoration(g_sapp_state.wl.decoration_manager,
1067 g_sapp_state.wl.xdg_toplevel)
1068 if g_sapp_state.wl.toplevel_decoration != unsafe { nil } {
1069 C.zxdg_toplevel_decoration_v1_set_mode(g_sapp_state.wl.toplevel_decoration,
1070 zxdg_toplevel_decoration_v1_mode_server_side)
1071 }
1072 }
1073
1074 // Enable fractional scaling if available and high_dpi is enabled
1075 if g_sapp_state.desc.high_dpi && g_sapp_state.wl.fractional_scale_mgr != unsafe { nil } {
1076 g_sapp_state.wl.fractional_scale = C.wp_fractional_scale_manager_v1_get_fractional_scale(g_sapp_state.wl.fractional_scale_mgr,
1077 g_sapp_state.wl.surface)
1078 if g_sapp_state.wl.fractional_scale != unsafe { nil } {
1079 C.wp_fractional_scale_v1_add_listener(g_sapp_state.wl.fractional_scale,
1080 &wl_fractional_scale_listener, unsafe { nil })
1081 }
1082 }
1083
1084 // Create viewport for setting logical size with fractional scaling
1085 if g_sapp_state.desc.high_dpi && g_sapp_state.wl.viewporter != unsafe { nil } {
1086 g_sapp_state.wl.viewport = C.wp_viewporter_get_viewport(g_sapp_state.wl.viewporter,
1087 g_sapp_state.wl.surface)
1088 if g_sapp_state.wl.viewport != unsafe { nil } {
1089 C.wp_viewport_set_destination(g_sapp_state.wl.viewport, g_sapp_state.wl.width,
1090 g_sapp_state.wl.height)
1091 }
1092 }
1093
1094 // Set initial fullscreen state if requested
1095 if g_sapp_state.desc.fullscreen {
1096 C.xdg_toplevel_set_fullscreen(g_sapp_state.wl.xdg_toplevel, unsafe { nil })
1097 }
1098
1099 // Commit the surface to trigger initial configure
1100 C.wl_surface_commit(g_sapp_state.wl.surface)
1101 C.wl_display_roundtrip(g_sapp_state.wl.display)
1102
1103 // Initialize EGL
1104 sapp_egl_init_wayland()
1105 sapp_egl_create_surface(voidptr(g_sapp_state.wl.egl_window))
1106 sapp_egl_make_current()
1107
1108 // Update public API values
1109 g_sapp_state.window_width = g_sapp_state.wl.width
1110 g_sapp_state.window_height = g_sapp_state.wl.height
1111 g_sapp_state.framebuffer_width = g_sapp_state.wl.fb_width
1112 g_sapp_state.framebuffer_height = g_sapp_state.wl.fb_height
1113
1114 g_sapp_state.valid = true
1115
1116 // Main event loop
1117 for !g_sapp_state.quit_ordered {
1118 C.wl_display_dispatch_pending(g_sapp_state.wl.display)
1119
1120 wl_handle_key_repeat()
1121
1122 // Handle quit requested
1123 if g_sapp_state.quit_requested && !g_sapp_state.quit_ordered {
1124 wl_app_event(.quit_requested)
1125 if g_sapp_state.quit_requested {
1126 g_sapp_state.quit_ordered = true
1127 }
1128 }
1129
1130 sapp_do_frame()
1131
1132 C.eglSwapBuffers(g_sapp_state.egl.display, g_sapp_state.egl.surface)
1133 C.wl_display_flush(g_sapp_state.wl.display)
1134 }
1135
1136 sapp_call_cleanup()
1137
1138 // Cleanup
1139 sapp_egl_destroy()
1140
1141 if g_sapp_state.wl.egl_window != unsafe { nil } {
1142 C.wl_egl_window_destroy(g_sapp_state.wl.egl_window)
1143 }
1144 if g_sapp_state.wl.toplevel_decoration != unsafe { nil } {
1145 C.zxdg_toplevel_decoration_v1_destroy(g_sapp_state.wl.toplevel_decoration)
1146 }
1147 if g_sapp_state.wl.fractional_scale != unsafe { nil } {
1148 C.wp_fractional_scale_v1_destroy(g_sapp_state.wl.fractional_scale)
1149 }
1150 if g_sapp_state.wl.viewport != unsafe { nil } {
1151 C.wp_viewport_destroy(g_sapp_state.wl.viewport)
1152 }
1153 if g_sapp_state.wl.viewporter != unsafe { nil } {
1154 C.wp_viewporter_destroy(g_sapp_state.wl.viewporter)
1155 }
1156 if g_sapp_state.wl.xdg_toplevel != unsafe { nil } {
1157 C.xdg_toplevel_destroy(g_sapp_state.wl.xdg_toplevel)
1158 }
1159 if g_sapp_state.wl.xdg_surface != unsafe { nil } {
1160 C.xdg_surface_destroy(g_sapp_state.wl.xdg_surface)
1161 }
1162 if g_sapp_state.wl.surface != unsafe { nil } {
1163 C.wl_surface_destroy(g_sapp_state.wl.surface)
1164 }
1165 if g_sapp_state.wl.xkb_state != unsafe { nil } {
1166 C.xkb_state_unref(g_sapp_state.wl.xkb_state)
1167 }
1168 if g_sapp_state.wl.xkb_keymap != unsafe { nil } {
1169 C.xkb_keymap_unref(g_sapp_state.wl.xkb_keymap)
1170 }
1171 if g_sapp_state.wl.xkb_context != unsafe { nil } {
1172 C.xkb_context_unref(g_sapp_state.wl.xkb_context)
1173 }
1174 if g_sapp_state.wl.key_repeat_timer_fd >= 0 {
1175 C.close(g_sapp_state.wl.key_repeat_timer_fd)
1176 g_sapp_state.wl.key_repeat_timer_fd = -1
1177 }
1178 if g_sapp_state.wl.locked_pointer != unsafe { nil } {
1179 C.zwp_locked_pointer_v1_destroy(g_sapp_state.wl.locked_pointer)
1180 }
1181 if g_sapp_state.wl.relative_pointer != unsafe { nil } {
1182 C.zwp_relative_pointer_v1_destroy(g_sapp_state.wl.relative_pointer)
1183 }
1184 if g_sapp_state.wl.cursor_shape_device != unsafe { nil } {
1185 C.wp_cursor_shape_device_v1_destroy(g_sapp_state.wl.cursor_shape_device)
1186 }
1187 if g_sapp_state.wl.data_device != unsafe { nil } {
1188 C.wl_data_device_destroy(g_sapp_state.wl.data_device)
1189 }
1190 if g_sapp_state.wl.data_offer != unsafe { nil } {
1191 C.wl_data_offer_destroy(g_sapp_state.wl.data_offer)
1192 }
1193 if g_sapp_state.wl.data_source != unsafe { nil } {
1194 C.wl_data_source_destroy(g_sapp_state.wl.data_source)
1195 }
1196 if g_sapp_state.wl.data_device_manager != unsafe { nil } {
1197 C.wl_data_device_manager_destroy(g_sapp_state.wl.data_device_manager)
1198 }
1199 if g_sapp_state.wl.pointer_constraints != unsafe { nil } {
1200 C.zwp_pointer_constraints_v1_destroy(g_sapp_state.wl.pointer_constraints)
1201 }
1202 if g_sapp_state.wl.relative_pointer_mgr != unsafe { nil } {
1203 C.zwp_relative_pointer_manager_v1_destroy(g_sapp_state.wl.relative_pointer_mgr)
1204 }
1205 if g_sapp_state.wl.cursor_shape_manager != unsafe { nil } {
1206 C.wp_cursor_shape_manager_v1_destroy(g_sapp_state.wl.cursor_shape_manager)
1207 }
1208 if g_sapp_state.wl.fractional_scale_mgr != unsafe { nil } {
1209 C.wp_fractional_scale_manager_v1_destroy(g_sapp_state.wl.fractional_scale_mgr)
1210 }
1211 if g_sapp_state.wl.decoration_manager != unsafe { nil } {
1212 C.zxdg_decoration_manager_v1_destroy(g_sapp_state.wl.decoration_manager)
1213 }
1214 if g_sapp_state.wl.compositor != unsafe { nil } {
1215 C.wl_compositor_destroy(g_sapp_state.wl.compositor)
1216 }
1217 if g_sapp_state.wl.registry != unsafe { nil } {
1218 C.wl_registry_destroy(g_sapp_state.wl.registry)
1219 }
1220 if g_sapp_state.wl.display != unsafe { nil } {
1221 C.wl_display_disconnect(g_sapp_state.wl.display)
1222 }
1223
1224 sapp_discard_state()
1225 }
1226}
1227