AtlatestRepositorysigil-app

sigil-app / tree / src / cdesktop.c

1/*
2 * desktop.c - the (sigil desktop) module
3 *
4 * The desktop host layer: window, GL context, input, frame clock and quit,
5 * over GLFW (vendored under vendor/glfw). One binary runs as a native
6 * Wayland client or under X11 on Linux, picked at startup; GLFW loads the
7 * platform libraries (libwayland-client, libxkbcommon, libX11, libGL/EGL,
8 * ...) with dlopen, so nothing platform-specific is linked.
9 *
10 * The exported Sigil API is the one (sigil app) had over sokol_app: app-run,
11 * frame-*, mouse-*, key-*, quit-requested?, request-quit.
12 */
14#include "desktop-internal.h"
15#include "sigil-desktop.h"
16#include "sigil-internal.h"
18/* No GL header: this layer never calls GL itself; sokol_gfx (in
19 * sigil-graphics) brings its own. */
20#define GLFW_INCLUDE_NONE
21#include <GLFW/glfw3.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
27/* Global app state */
28DesktopState *g_desktop = NULL;
30/*
31 * Initialize app state
32 */
33void sigil_desktop_state_init(SigilVM *vm)
35 if (g_desktop) return;
37 g_desktop = calloc(1, sizeof(DesktopState));
38 g_desktop->vm = vm;
39 g_desktop->init_callback = SIGIL_FALSE;
40 g_desktop->frame_callback = SIGIL_FALSE;
41 g_desktop->cleanup_callback = SIGIL_FALSE;
42 g_desktop->fb_width = 1;
43 g_desktop->fb_height = 1;
44 g_desktop->win_width = 1;
45 g_desktop->win_height = 1;
48void sigil_desktop_state_shutdown(void)
50 if (g_desktop) {
51 free(g_desktop);
52 g_desktop = NULL;
53 }
56/*
57 * Clear per-frame input state
58 */
59void sigil_desktop_clear_frame_input(void)
61 if (!g_desktop) return;
62 memset(g_desktop->keys_pressed, 0, sizeof(g_desktop->keys_pressed));
63 memset(g_desktop->keys_released, 0, sizeof(g_desktop->keys_released));
64 memset(g_desktop->mouse_pressed, 0, sizeof(g_desktop->mouse_pressed));
65 memset(g_desktop->mouse_released, 0, sizeof(g_desktop->mouse_released));
66 g_desktop->wheel_x = 0.0;
67 g_desktop->wheel_y = 0.0;
70int sigil_desktop_framebuffer_width(void)
72 return (g_desktop && g_desktop->fb_width > 0) ? g_desktop->fb_width : 1;
75int sigil_desktop_framebuffer_height(void)
77 return (g_desktop && g_desktop->fb_height > 0) ? g_desktop->fb_height : 1;
80/*
81 * Convert Scheme key symbol to a GLFW key code (GLFW_KEY_UNKNOWN when the
82 * symbol names no key). Letters, digits, space, return/enter, escape/esc,
83 * tab, backspace, the arrows, shift/ctrl/alt (left), and since 0.10.1 f1..f25,
84 * page-up/page-down, home/end, insert/delete, right-shift/ctrl/alt,
85 * caps-lock, the punctuation row (comma period slash semicolon apostrophe
86 * minus equal left-bracket right-bracket backslash grave, or the character
87 * itself as a symbol) and the keypad (kp-0..kp-9, kp-enter, kp-add,
88 * kp-subtract).
89 */
90int sigil_desktop_key_code_from_symbol(SigilVM *vm, Value sym)
92 (void)vm;
93 if (!sigil_is_symbol(sym)) return GLFW_KEY_UNKNOWN;
95 SigilSymbol *s = (SigilSymbol *)sigil_as_ptr(sym);
96 const char *name = s->name;
98 /* Letters */
99 if (s->length == 1 && name[0] >= 'a' && name[0] <= 'z') {
100 return GLFW_KEY_A + (name[0] - 'a');
101 }
103 /* Numbers */
104 if (s->length == 1 && name[0] >= '0' && name[0] <= '9') {
105 return GLFW_KEY_0 + (name[0] - '0');
106 }
108 /* Special keys */
109 if (strcmp(name, "space") == 0) return GLFW_KEY_SPACE;
110 if (strcmp(name, "return") == 0 || strcmp(name, "enter") == 0) return GLFW_KEY_ENTER;
111 if (strcmp(name, "escape") == 0 || strcmp(name, "esc") == 0) return GLFW_KEY_ESCAPE;
112 if (strcmp(name, "tab") == 0) return GLFW_KEY_TAB;
113 if (strcmp(name, "backspace") == 0) return GLFW_KEY_BACKSPACE;
115 /* Arrow keys */
116 if (strcmp(name, "left") == 0) return GLFW_KEY_LEFT;
117 if (strcmp(name, "right") == 0) return GLFW_KEY_RIGHT;
118 if (strcmp(name, "up") == 0) return GLFW_KEY_UP;
119 if (strcmp(name, "down") == 0) return GLFW_KEY_DOWN;
121 /* Modifiers */
122 if (strcmp(name, "shift") == 0) return GLFW_KEY_LEFT_SHIFT;
123 if (strcmp(name, "ctrl") == 0 || strcmp(name, "control") == 0) return GLFW_KEY_LEFT_CONTROL;
124 if (strcmp(name, "alt") == 0) return GLFW_KEY_LEFT_ALT;
126 /* 0.10.1: the rest of a keyboard an editor needs (a tracker's FT2
127 * table: function keys, paging, the punctuation row, the right
128 * modifiers). The state arrays already span every GLFW code; only
129 * these names were missing. */
130 if (s->length >= 2 && s->length <= 3 && name[0] == 'f' && name[1] >= '1' && name[1] <= '9'
131 && (s->length == 2 || (name[2] >= '0' && name[2] <= '9'))) {
132 int n = (name[1] - '0');
133 if (s->length == 3) n = n * 10 + (name[2] - '0');
134 if (n >= 1 && n <= 25) return GLFW_KEY_F1 + (n - 1);
135 }
136 if (strcmp(name, "page-up") == 0 || strcmp(name, "pageup") == 0) return GLFW_KEY_PAGE_UP;
137 if (strcmp(name, "page-down") == 0 || strcmp(name, "pagedown") == 0) return GLFW_KEY_PAGE_DOWN;
138 if (strcmp(name, "home") == 0) return GLFW_KEY_HOME;
139 if (strcmp(name, "end") == 0) return GLFW_KEY_END;
140 if (strcmp(name, "insert") == 0) return GLFW_KEY_INSERT;
141 if (strcmp(name, "delete") == 0) return GLFW_KEY_DELETE;
142 if (strcmp(name, "right-shift") == 0 || strcmp(name, "rshift") == 0) return GLFW_KEY_RIGHT_SHIFT;
143 if (strcmp(name, "right-ctrl") == 0 || strcmp(name, "rctrl") == 0) return GLFW_KEY_RIGHT_CONTROL;
144 if (strcmp(name, "right-alt") == 0 || strcmp(name, "ralt") == 0) return GLFW_KEY_RIGHT_ALT;
145 if (strcmp(name, "caps-lock") == 0) return GLFW_KEY_CAPS_LOCK;
146 if (strcmp(name, "comma") == 0 || strcmp(name, ",") == 0) return GLFW_KEY_COMMA;
147 if (strcmp(name, "period") == 0 || strcmp(name, ".") == 0) return GLFW_KEY_PERIOD;
148 if (strcmp(name, "slash") == 0 || strcmp(name, "/") == 0) return GLFW_KEY_SLASH;
149 if (strcmp(name, "semicolon") == 0 || strcmp(name, ";") == 0) return GLFW_KEY_SEMICOLON;
150 if (strcmp(name, "apostrophe") == 0 || strcmp(name, "'") == 0) return GLFW_KEY_APOSTROPHE;
151 if (strcmp(name, "minus") == 0 || strcmp(name, "-") == 0) return GLFW_KEY_MINUS;
152 if (strcmp(name, "equal") == 0 || strcmp(name, "=") == 0) return GLFW_KEY_EQUAL;
153 if (strcmp(name, "left-bracket") == 0 || strcmp(name, "[") == 0) return GLFW_KEY_LEFT_BRACKET;
154 if (strcmp(name, "right-bracket") == 0 || strcmp(name, "]") == 0) return GLFW_KEY_RIGHT_BRACKET;
155 if (strcmp(name, "backslash") == 0 || strcmp(name, "\\") == 0) return GLFW_KEY_BACKSLASH;
156 if (strcmp(name, "grave") == 0 || strcmp(name, "backquote") == 0 || strcmp(name, "`") == 0) return GLFW_KEY_GRAVE_ACCENT;
157 if (strcmp(name, "kp-enter") == 0) return GLFW_KEY_KP_ENTER;
158 if (strcmp(name, "kp-add") == 0) return GLFW_KEY_KP_ADD;
159 if (strcmp(name, "kp-subtract") == 0) return GLFW_KEY_KP_SUBTRACT;
160 if (s->length == 4 && strncmp(name, "kp-", 3) == 0 && name[3] >= '0' && name[3] <= '9') {
161 return GLFW_KEY_KP_0 + (name[3] - '0');
162 }
164 return GLFW_KEY_UNKNOWN;
167/*
168 * Convert mouse button symbol to index
169 */
170static int mouse_button_from_symbol(SigilVM *vm, Value sym)
172 (void)vm;
173 if (!sigil_is_symbol(sym)) return -1;
175 SigilSymbol *s = (SigilSymbol *)sigil_as_ptr(sym);
176 const char *name = s->name;
178 if (strcmp(name, "left") == 0) return GLFW_MOUSE_BUTTON_LEFT;
179 if (strcmp(name, "right") == 0) return GLFW_MOUSE_BUTTON_RIGHT;
180 if (strcmp(name, "middle") == 0) return GLFW_MOUSE_BUTTON_MIDDLE;
182 return -1;
185/* ============================================================
186 * GLFW CALLBACKS
187 * ============================================================ */
189/* Check for VM errors and print them */
190static void check_vm_error(const char *context)
192 if (!g_desktop) return;
193 const char *err = sigil_error_message(g_desktop->vm);
194 if (err) {
195 fprintf(stderr, "Scheme error in %s: %s\n", context, err);
196 sigil_error_clear(g_desktop->vm);
197 g_desktop->quit_requested = true;
198 if (g_desktop->window) glfwSetWindowShouldClose(g_desktop->window, GLFW_TRUE);
199 }
202static void glfw_error_callback(int code, const char *description)
204 fprintf(stderr, "sigil-desktop: GLFW error 0x%X: %s\n", code, description);
207static void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods)
209 (void)window; (void)scancode; (void)mods;
210 if (!g_desktop) return;
211 if (key < 0 || key >= SIGIL_DESKTOP_MAX_KEYS) return;
213 if (action == GLFW_PRESS || action == GLFW_REPEAT) {
214 /* A repeat is a held key, not a new press: pressed? fires once. */
215 if (!g_desktop->keys_down[key]) {
216 g_desktop->keys_pressed[key] = true;
217 }
218 g_desktop->keys_down[key] = true;
219 } else if (action == GLFW_RELEASE) {
220 g_desktop->keys_down[key] = false;
221 g_desktop->keys_released[key] = true;
222 }
225static void mouse_button_callback(GLFWwindow *window, int button, int action, int mods)
227 (void)window; (void)mods;
228 if (!g_desktop) return;
229 if (button < 0 || button >= SIGIL_DESKTOP_MAX_MOUSE_BUTTONS) return;
231 if (action == GLFW_PRESS) {
232 if (!g_desktop->mouse_buttons[button]) {
233 g_desktop->mouse_pressed[button] = true;
234 }
235 g_desktop->mouse_buttons[button] = true;
236 } else if (action == GLFW_RELEASE) {
237 g_desktop->mouse_buttons[button] = false;
238 g_desktop->mouse_released[button] = true;
239 }
242static void scroll_callback(GLFWwindow *window, double dx, double dy)
244 (void)window;
245 if (!g_desktop) return;
246 g_desktop->wheel_x += dx;
247 g_desktop->wheel_y += dy;
250static void cursor_pos_callback(GLFWwindow *window, double x, double y)
252 (void)window;
253 if (!g_desktop) return;
254 g_desktop->cursor_x = x;
255 g_desktop->cursor_y = y;
258static void window_close_callback(GLFWwindow *window)
260 (void)window;
261 if (!g_desktop) return;
262 /* The window manager asked; the game sees (quit-requested?) => #t
263 * and the loop ends after this frame, as with sokol_app's
264 * QUIT_REQUESTED event. */
265 g_desktop->quit_requested = true;
268static void framebuffer_size_callback(GLFWwindow *window, int width, int height)
270 if (!g_desktop) return;
271 g_desktop->fb_width = width;
272 g_desktop->fb_height = height;
273 if (getenv("SIGIL_DESKTOP_VERBOSE")) {
274 float sx = 1.0f, sy = 1.0f;
275 glfwGetWindowContentScale(window, &sx, &sy);
276 fprintf(stderr, "sigil-desktop: framebuffer=%dx%d window=%dx%d content-scale=%.2fx%.2f\n",
277 width, height, g_desktop->win_width, g_desktop->win_height, sx, sy);
278 }
281static void window_size_callback(GLFWwindow *window, int width, int height)
283 (void)window;
284 if (!g_desktop) return;
285 g_desktop->win_width = width;
286 g_desktop->win_height = height;
289/* Cursor position scaled from window units into framebuffer pixels, so
290 * mouse-x/mouse-y and frame-width/frame-height share one coordinate
291 * system whatever the output scale is. */
292static double cursor_to_fb_x(void)
294 if (!g_desktop || g_desktop->win_width <= 0) return 0.0;
295 return g_desktop->cursor_x * ((double)g_desktop->fb_width / (double)g_desktop->win_width);
298static double cursor_to_fb_y(void)
300 if (!g_desktop || g_desktop->win_height <= 0) return 0.0;
301 return g_desktop->cursor_y * ((double)g_desktop->fb_height / (double)g_desktop->win_height);
304/* ============================================================
305 * PLATFORM SELECTION
306 * ============================================================ */
308/*
309 * GLFW picks Wayland when XDG_SESSION_TYPE=wayland and WAYLAND_DISPLAY is
310 * set, X11 when XDG_SESSION_TYPE=x11 and DISPLAY is set, and otherwise
311 * tries Wayland then X11. SIGIL_DESKTOP_PLATFORM=wayland|x11 forces one, for
312 * tests that run under a private compositor or X server and for users
313 * whose session variables lie.
314 */
315static void apply_platform_hint(void)
317 const char *want = getenv("SIGIL_DESKTOP_PLATFORM");
318 if (!want || !*want) return;
319 if (strcmp(want, "wayland") == 0) {
320 glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_WAYLAND);
321 } else if (strcmp(want, "x11") == 0) {
322 glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11);
323 } else if (strcmp(want, "win32") == 0) {
324 glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_WIN32);
325 } else if (strcmp(want, "cocoa") == 0) {
326 glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_COCOA);
327 } else {
328 fprintf(stderr, "sigil-desktop: SIGIL_DESKTOP_PLATFORM=%s is not one of wayland, x11, win32, cocoa; ignored\n", want);
329 }
332static const char *platform_name(int platform)
334 switch (platform) {
335 case GLFW_PLATFORM_WAYLAND: return "wayland";
336 case GLFW_PLATFORM_X11: return "x11";
337 case GLFW_PLATFORM_WIN32: return "win32";
338 case GLFW_PLATFORM_COCOA: return "cocoa";
339 case GLFW_PLATFORM_NULL: return "null";
340 default: return "unknown";
341 }
344/* ============================================================
345 * PUBLIC C SURFACE (include/sigil-desktop.h)
346 * ============================================================ */
348void sigil_desktop_framebuffer_size(int *width, int *height)
350 if (width) *width = sigil_desktop_framebuffer_width();
351 if (height) *height = sigil_desktop_framebuffer_height();
354void *sigil_desktop_gl_get_proc_address(const char *name)
356 if (!g_desktop || !g_desktop->window || !name) return NULL;
357 return (void *)glfwGetProcAddress(name);
360const char *sigil_desktop_platform(void)
362 if (!g_desktop || !g_desktop->window) return "none";
363 return platform_name(glfwGetPlatform());
366/* ============================================================
367 * MAIN LOOP
368 * ============================================================ */
370/* Returns false when no window could be opened; the GLFW error callback
371 * has already said why on stderr. */
372static bool run_main_loop(const char *title, int width, int height)
374 glfwSetErrorCallback(glfw_error_callback);
375 apply_platform_hint();
377 if (!glfwInit()) {
378 fprintf(stderr, "sigil-desktop: glfwInit failed; no usable display platform\n");
379 return false;
380 }
382 /* The GL core profile sokol_app asked for: 4.3 on Linux and Windows, 4.1
383 * on macOS (the newest it offers); sokol_gfx gates a few features on the
384 * reported version, and Mesa and NVIDIA hand back the highest core
385 * version either way. Forward-compat as sokol_app set it too (required
386 * on macOS). Depth 24 / stencil 8 are GLFW's defaults and match the
387 * sokol_app swapchain the graphics layer expects. */
388 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
389#if defined(__APPLE__)
390 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
391#else
392 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
393#endif
394 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
395 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
396 glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE);
397 /* Wayland app_id and X11 class both default to the title, so a
398 * compositor rule (sway: for_window [app_id="Crash The Stack"]) can
399 * name the window on either platform. GLFW's X11 backend already
400 * falls back to the title; Wayland's default is empty. */
401 glfwWindowHintString(GLFW_WAYLAND_APP_ID, title);
403 GLFWwindow *window = glfwCreateWindow(width, height, title, NULL, NULL);
404 if (!window) {
405 fprintf(stderr, "sigil-desktop: window creation failed (platform %s)\n",
406 platform_name(glfwGetPlatform()));
407 glfwTerminate();
408 return false;
409 }
410 g_desktop->window = window;
412 glfwMakeContextCurrent(window);
413 glfwSwapInterval(1);
415 glfwGetFramebufferSize(window, &g_desktop->fb_width, &g_desktop->fb_height);
416 glfwGetWindowSize(window, &g_desktop->win_width, &g_desktop->win_height);
417 glfwGetCursorPos(window, &g_desktop->cursor_x, &g_desktop->cursor_y);
419 if (getenv("SIGIL_DESKTOP_VERBOSE")) {
420 float sx = 1.0f, sy = 1.0f;
421 glfwGetWindowContentScale(window, &sx, &sy);
422 fprintf(stderr, "sigil-desktop: platform=%s window=%dx%d framebuffer=%dx%d content-scale=%.2fx%.2f gl=%d.%d\n",
423 platform_name(glfwGetPlatform()),
424 g_desktop->win_width, g_desktop->win_height,
425 g_desktop->fb_width, g_desktop->fb_height, sx, sy,
426 glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MAJOR),
427 glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MINOR));
428#if defined(__linux__)
429 fprintf(stderr, "sigil-desktop: dlopen fallback dirs: %s\n", sigil_desktop_dlopen_search_summary());
430#endif
431 }
433 glfwSetKeyCallback(window, key_callback);
434 glfwSetMouseButtonCallback(window, mouse_button_callback);
435 glfwSetCursorPosCallback(window, cursor_pos_callback);
436 glfwSetScrollCallback(window, scroll_callback);
437 glfwSetWindowCloseCallback(window, window_close_callback);
438 glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
439 glfwSetWindowSizeCallback(window, window_size_callback);
441 /* init */
442 g_desktop->last_time = glfwGetTime();
443 if (!sigil_is_false(g_desktop->init_callback)) {
444 sigil_apply0(g_desktop->vm, g_desktop->init_callback);
445 check_vm_error("init");
446 }
448 /* frames; the clock restarts after init so a long asset load is not
449 * frame 1's dt (sokol_app handed the load time to the first frame). */
450 g_desktop->last_time = glfwGetTime();
451 while (!glfwWindowShouldClose(window)) {
452 glfwPollEvents();
454 double now = glfwGetTime();
455 g_desktop->frame_time = now - g_desktop->last_time;
456 g_desktop->time_elapsed += g_desktop->frame_time;
457 g_desktop->last_time = now;
459 if (!sigil_is_false(g_desktop->frame_callback)) {
460 Value dt = sigil_flonum(g_desktop->frame_time);
461 sigil_apply1(g_desktop->vm, g_desktop->frame_callback, dt);
462 check_vm_error("frame");
463 }
465 /* The frame is drawn; present it. */
466 glfwSwapBuffers(window);
468 /* Clear per-frame input state for next frame */
469 sigil_desktop_clear_frame_input();
470 }
472 /* cleanup */
473 if (!sigil_is_false(g_desktop->cleanup_callback)) {
474 sigil_apply0(g_desktop->vm, g_desktop->cleanup_callback);
475 check_vm_error("cleanup");
476 }
478 g_desktop->window = NULL;
479 glfwDestroyWindow(window);
480 glfwTerminate();
481 return true;
484/* ============================================================
485 * NATIVE FUNCTIONS
486 * ============================================================ */
488/*
489 * (app-run init-proc frame-proc cleanup-proc [title] [width] [height])
490 *
491 * Run the application main loop.
492 * init-proc: called once at startup
493 * frame-proc: called each frame with delta-time argument
494 * cleanup-proc: called before shutdown
495 */
496static Value native_app_run(SigilVM *vm, int argc, Value *args)
498 if (argc < 3) {
499 sigil__vm_error(vm, SIGIL_ERR_ARITY, "app-run: requires init, frame, and cleanup procedures");
500 return SIGIL_UNDEFINED;
501 }
503 /* Initialize app state */
504 sigil_desktop_state_init(vm);
505 if (g_desktop->window) {
506 sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "app-run: already running; app-run does not nest");
507 return SIGIL_UNDEFINED;
508 }
510 /* Store callbacks */
511 g_desktop->init_callback = args[0];
512 g_desktop->frame_callback = args[1];
513 g_desktop->cleanup_callback = args[2];
515 /* Parse optional arguments */
516 const char *title = "Sigil App";
517 int width = 800;
518 int height = 600;
520 if (argc > 3 && sigil_is_string(args[3])) {
521 SigilString *s = (SigilString *)sigil_as_ptr(args[3]);
522 title = s->data;
523 }
524 if (argc > 4 && sigil_is_fixnum(args[4])) {
525 width = (int)sigil_as_fixnum(args[4]);
526 }
527 if (argc > 5 && sigil_is_fixnum(args[5])) {
528 height = (int)sigil_as_fixnum(args[5]);
529 }
530 /* 0 (or less) means the default, as with sokol_app. */
531 if (width <= 0) width = 640;
532 if (height <= 0) height = 480;
534 bool ran = run_main_loop(title, width, height);
536 /* Cleanup */
537 sigil_desktop_state_shutdown();
539 if (!ran) {
540 sigil__vm_error(vm, SIGIL_ERR_RUNTIME,
541 "app-run: could not open a window (see the sigil-desktop: lines above)");
542 return SIGIL_UNDEFINED;
543 }
544 return SIGIL_NIL;
547/*
548 * (frame-width) -> integer, framebuffer pixels
549 */
550static Value native_frame_width(SigilVM *vm, int argc, Value *args)
552 (void)vm; (void)argc; (void)args;
553 return sigil_fixnum(sigil_desktop_framebuffer_width());
556/*
557 * (frame-height) -> integer, framebuffer pixels
558 */
559static Value native_frame_height(SigilVM *vm, int argc, Value *args)
561 (void)vm; (void)argc; (void)args;
562 return sigil_fixnum(sigil_desktop_framebuffer_height());
565/*
566 * (frame-time) -> float (seconds since last frame)
567 */
568static Value native_frame_time(SigilVM *vm, int argc, Value *args)
570 (void)vm; (void)argc; (void)args;
571 if (!g_desktop) return sigil_flonum(0.0);
572 return sigil_flonum(g_desktop->frame_time);
575/*
576 * (time-elapsed) -> float (seconds since app start)
577 */
578static Value native_time_elapsed(SigilVM *vm, int argc, Value *args)
580 (void)vm; (void)argc; (void)args;
581 if (!g_desktop) return sigil_flonum(0.0);
582 return sigil_flonum(g_desktop->time_elapsed);
585/*
586 * (mouse-x) -> float, framebuffer pixels
587 */
588static Value native_mouse_x(SigilVM *vm, int argc, Value *args)
590 (void)vm; (void)argc; (void)args;
591 return sigil_flonum(cursor_to_fb_x());
594/*
595 * (mouse-wheel) -> float: the wheel's vertical travel since the last frame
596 * in GLFW's units (a notch is 1.0 on most mice; + is away from the user),
597 * summed over the frame and cleared with the other per-frame input.
598 * (mouse-wheel-x) the horizontal travel the same way.
599 */
600static Value native_mouse_wheel(SigilVM *vm, int argc, Value *args)
602 (void)vm; (void)argc; (void)args;
603 return sigil_flonum(g_desktop ? g_desktop->wheel_y : 0.0);
606static Value native_mouse_wheel_x(SigilVM *vm, int argc, Value *args)
608 (void)vm; (void)argc; (void)args;
609 return sigil_flonum(g_desktop ? g_desktop->wheel_x : 0.0);
612/*
613 * (mouse-y) -> float, framebuffer pixels
614 */
615static Value native_mouse_y(SigilVM *vm, int argc, Value *args)
617 (void)vm; (void)argc; (void)args;
618 return sigil_flonum(cursor_to_fb_y());
621/*
622 * (mouse-down? button) -> boolean
623 */
624static Value native_mouse_down(SigilVM *vm, int argc, Value *args)
626 if (argc < 1) return SIGIL_FALSE;
627 int btn = mouse_button_from_symbol(vm, args[0]);
628 if (btn < 0 || !g_desktop) return SIGIL_FALSE;
629 return g_desktop->mouse_buttons[btn] ? SIGIL_TRUE : SIGIL_FALSE;
632/*
633 * (mouse-pressed? button) -> boolean
634 */
635static Value native_mouse_pressed(SigilVM *vm, int argc, Value *args)
637 if (argc < 1) return SIGIL_FALSE;
638 int btn = mouse_button_from_symbol(vm, args[0]);
639 if (btn < 0 || !g_desktop) return SIGIL_FALSE;
640 return g_desktop->mouse_pressed[btn] ? SIGIL_TRUE : SIGIL_FALSE;
643/*
644 * (mouse-released? button) -> boolean
645 */
646static Value native_mouse_released(SigilVM *vm, int argc, Value *args)
648 if (argc < 1) return SIGIL_FALSE;
649 int btn = mouse_button_from_symbol(vm, args[0]);
650 if (btn < 0 || !g_desktop) return SIGIL_FALSE;
651 return g_desktop->mouse_released[btn] ? SIGIL_TRUE : SIGIL_FALSE;
654/*
655 * (key-down? key) -> boolean
656 */
657static Value native_key_down(SigilVM *vm, int argc, Value *args)
659 if (argc < 1) return SIGIL_FALSE;
660 int key = sigil_desktop_key_code_from_symbol(vm, args[0]);
661 if (key == GLFW_KEY_UNKNOWN || !g_desktop) return SIGIL_FALSE;
662 return g_desktop->keys_down[key] ? SIGIL_TRUE : SIGIL_FALSE;
665/*
666 * (key-pressed? key) -> boolean
667 */
668static Value native_key_pressed(SigilVM *vm, int argc, Value *args)
670 if (argc < 1) return SIGIL_FALSE;
671 int key = sigil_desktop_key_code_from_symbol(vm, args[0]);
672 if (key == GLFW_KEY_UNKNOWN || !g_desktop) return SIGIL_FALSE;
673 return g_desktop->keys_pressed[key] ? SIGIL_TRUE : SIGIL_FALSE;
676/*
677 * (key-released? key) -> boolean
678 */
679static Value native_key_released(SigilVM *vm, int argc, Value *args)
681 if (argc < 1) return SIGIL_FALSE;
682 int key = sigil_desktop_key_code_from_symbol(vm, args[0]);
683 if (key == GLFW_KEY_UNKNOWN || !g_desktop) return SIGIL_FALSE;
684 return g_desktop->keys_released[key] ? SIGIL_TRUE : SIGIL_FALSE;
687/*
688 * (key-code key) -> integer | #f
689 *
690 * The GLFW key code a key symbol names, or #f when the symbol names no
691 * key. Needs no window: the one way to test the symbol table, and how a
692 * program can ask whether a binding will ever fire before it is used.
693 */
694static Value native_key_code(SigilVM *vm, int argc, Value *args)
696 if (argc < 1) return SIGIL_FALSE;
697 int key = sigil_desktop_key_code_from_symbol(vm, args[0]);
698 if (key == GLFW_KEY_UNKNOWN) return SIGIL_FALSE;
699 return sigil_fixnum(key);
702/*
703 * (quit-requested?) -> boolean
704 */
705static Value native_quit_requested(SigilVM *vm, int argc, Value *args)
707 (void)vm; (void)argc; (void)args;
708 if (!g_desktop) return SIGIL_FALSE;
709 return g_desktop->quit_requested ? SIGIL_TRUE : SIGIL_FALSE;
712/*
713 * (request-quit)
714 *
715 * The loop ends after the current frame; (quit-requested?) answers #t
716 * from now on, as after sokol_app's sapp_request_quit.
717 */
718static Value native_request_quit(SigilVM *vm, int argc, Value *args)
720 (void)vm; (void)argc; (void)args;
721 if (!g_desktop) return SIGIL_NIL;
722 g_desktop->quit_requested = true;
723 if (g_desktop->window) glfwSetWindowShouldClose(g_desktop->window, GLFW_TRUE);
724 return SIGIL_NIL;
727/* ============================================================
728 * MODULE INITIALIZATION
729 * ============================================================ */
731void sigil__init_sigil_desktop_module(SigilVM *vm)
733 SigilModule *module = sigil_begin_module(vm, "(sigil desktop)");
734 if (!module) return;
736 /* Application lifecycle */
737 sigil_module_register_native(vm, "app-run", native_app_run,
738 SIGIL_ARITY_RANGE(3, 6),
739 "Run application with init/frame/cleanup callbacks");
741 /* Frame info */
742 sigil_module_register_native(vm, "frame-width", native_frame_width,
743 SIGIL_ARITY_EXACT(0), "Get frame buffer width");
744 sigil_module_register_native(vm, "frame-height", native_frame_height,
745 SIGIL_ARITY_EXACT(0), "Get frame buffer height");
746 sigil_module_register_native(vm, "frame-time", native_frame_time,
747 SIGIL_ARITY_EXACT(0), "Seconds since last frame");
748 sigil_module_register_native(vm, "time-elapsed", native_time_elapsed,
749 SIGIL_ARITY_EXACT(0), "Seconds since app start");
751 /* Mouse input */
752 sigil_module_register_native(vm, "mouse-x", native_mouse_x,
753 SIGIL_ARITY_EXACT(0), "Mouse X position");
754 sigil_module_register_native(vm, "mouse-y", native_mouse_y,
755 SIGIL_ARITY_EXACT(0), "Mouse Y position");
756 sigil_module_register_native(vm, "mouse-wheel", native_mouse_wheel,
757 SIGIL_ARITY_EXACT(0), "Wheel travel since the last frame (vertical)");
758 sigil_module_register_native(vm, "mouse-wheel-x", native_mouse_wheel_x,
759 SIGIL_ARITY_EXACT(0), "Wheel travel since the last frame (horizontal)");
760 sigil_module_register_native(vm, "mouse-down?", native_mouse_down,
761 SIGIL_ARITY_EXACT(1), "Is mouse button held?");
762 sigil_module_register_native(vm, "mouse-pressed?", native_mouse_pressed,
763 SIGIL_ARITY_EXACT(1), "Was mouse button just pressed?");
764 sigil_module_register_native(vm, "mouse-released?", native_mouse_released,
765 SIGIL_ARITY_EXACT(1), "Was mouse button just released?");
767 /* Keyboard input */
768 sigil_module_register_native(vm, "key-down?", native_key_down,
769 SIGIL_ARITY_EXACT(1), "Is key held?");
770 sigil_module_register_native(vm, "key-pressed?", native_key_pressed,
771 SIGIL_ARITY_EXACT(1), "Was key just pressed?");
772 sigil_module_register_native(vm, "key-released?", native_key_released,
773 SIGIL_ARITY_EXACT(1), "Was key just released?");
774 sigil_module_register_native(vm, "key-code", native_key_code,
775 SIGIL_ARITY_EXACT(1), "GLFW key code of a key symbol, or #f");
777 /* Quit handling */
778 sigil_module_register_native(vm, "quit-requested?", native_quit_requested,
779 SIGIL_ARITY_EXACT(0), "Was quit requested?");
780 sigil_module_register_native(vm, "request-quit", native_request_quit,
781 SIGIL_ARITY_EXACT(0), "Request application quit");
783 /* Export all */
784 sigil_module_export(vm, "app-run");
785 sigil_module_export(vm, "frame-width");
786 sigil_module_export(vm, "frame-height");
787 sigil_module_export(vm, "frame-time");
788 sigil_module_export(vm, "time-elapsed");
789 sigil_module_export(vm, "mouse-x");
790 sigil_module_export(vm, "mouse-y");
791 sigil_module_export(vm, "mouse-down?");
792 sigil_module_export(vm, "mouse-pressed?");
793 sigil_module_export(vm, "mouse-released?");
794 sigil_module_export(vm, "key-down?");
795 sigil_module_export(vm, "key-pressed?");
796 sigil_module_export(vm, "key-released?");
797 sigil_module_export(vm, "key-code");
798 sigil_module_export(vm, "quit-requested?");
799 sigil_module_export(vm, "request-quit");
801 sigil_end_module(vm);