AtlatestRepositorysigil-app
1
/*2
* desktop.c - the (sigil desktop) module3
*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 native6
* Wayland client or under X11 on Linux, picked at startup; GLFW loads the7
* 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 (in19
* sigil-graphics) brings its own. */20
#define GLFW_INCLUDE_NONE21
#include <GLFW/glfw3.h>23
#include <stdio.h>24
#include <stdlib.h>25
#include <string.h>27
/* Global app state */28
DesktopState *g_desktop = NULL;30
/*31
* Initialize app state32
*/33
void sigil_desktop_state_init(SigilVM *vm)34
{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;46
}48
void sigil_desktop_state_shutdown(void)49
{50
if (g_desktop) {51
free(g_desktop);52
g_desktop = NULL;53
}54
}56
/*57
* Clear per-frame input state58
*/59
void sigil_desktop_clear_frame_input(void)60
{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;68
}70
int sigil_desktop_framebuffer_width(void)71
{72
return (g_desktop && g_desktop->fb_width > 0) ? g_desktop->fb_width : 1;73
}75
int sigil_desktop_framebuffer_height(void)76
{77
return (g_desktop && g_desktop->fb_height > 0) ? g_desktop->fb_height : 1;78
}80
/*81
* Convert Scheme key symbol to a GLFW key code (GLFW_KEY_UNKNOWN when the82
* 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 apostrophe86
* minus equal left-bracket right-bracket backslash grave, or the character87
* itself as a symbol) and the keypad (kp-0..kp-9, kp-enter, kp-add,88
* kp-subtract).89
*/90
int sigil_desktop_key_code_from_symbol(SigilVM *vm, Value sym)91
{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 FT2127
* table: function keys, paging, the punctuation row, the right128
* modifiers). The state arrays already span every GLFW code; only129
* 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;165
}167
/*168
* Convert mouse button symbol to index169
*/170
static int mouse_button_from_symbol(SigilVM *vm, Value sym)171
{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;183
}185
/* ============================================================186
* GLFW CALLBACKS187
* ============================================================ */189
/* Check for VM errors and print them */190
static void check_vm_error(const char *context)191
{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
}200
}202
static void glfw_error_callback(int code, const char *description)203
{204
fprintf(stderr, "sigil-desktop: GLFW error 0x%X: %s\n", code, description);205
}207
static void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods)208
{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
}223
}225
static void mouse_button_callback(GLFWwindow *window, int button, int action, int mods)226
{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
}240
}242
static void scroll_callback(GLFWwindow *window, double dx, double dy)243
{244
(void)window;245
if (!g_desktop) return;246
g_desktop->wheel_x += dx;247
g_desktop->wheel_y += dy;248
}250
static void cursor_pos_callback(GLFWwindow *window, double x, double y)251
{252
(void)window;253
if (!g_desktop) return;254
g_desktop->cursor_x = x;255
g_desktop->cursor_y = y;256
}258
static void window_close_callback(GLFWwindow *window)259
{260
(void)window;261
if (!g_desktop) return;262
/* The window manager asked; the game sees (quit-requested?) => #t263
* and the loop ends after this frame, as with sokol_app's264
* QUIT_REQUESTED event. */265
g_desktop->quit_requested = true;266
}268
static void framebuffer_size_callback(GLFWwindow *window, int width, int height)269
{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
}279
}281
static void window_size_callback(GLFWwindow *window, int width, int height)282
{283
(void)window;284
if (!g_desktop) return;285
g_desktop->win_width = width;286
g_desktop->win_height = height;287
}289
/* Cursor position scaled from window units into framebuffer pixels, so290
* mouse-x/mouse-y and frame-width/frame-height share one coordinate291
* system whatever the output scale is. */292
static double cursor_to_fb_x(void)293
{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);296
}298
static double cursor_to_fb_y(void)299
{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);302
}304
/* ============================================================305
* PLATFORM SELECTION306
* ============================================================ */308
/*309
* GLFW picks Wayland when XDG_SESSION_TYPE=wayland and WAYLAND_DISPLAY is310
* set, X11 when XDG_SESSION_TYPE=x11 and DISPLAY is set, and otherwise311
* tries Wayland then X11. SIGIL_DESKTOP_PLATFORM=wayland|x11 forces one, for312
* tests that run under a private compositor or X server and for users313
* whose session variables lie.314
*/315
static void apply_platform_hint(void)316
{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
}330
}332
static const char *platform_name(int platform)333
{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
}342
}344
/* ============================================================345
* PUBLIC C SURFACE (include/sigil-desktop.h)346
* ============================================================ */348
void sigil_desktop_framebuffer_size(int *width, int *height)349
{350
if (width) *width = sigil_desktop_framebuffer_width();351
if (height) *height = sigil_desktop_framebuffer_height();352
}354
void *sigil_desktop_gl_get_proc_address(const char *name)355
{356
if (!g_desktop || !g_desktop->window || !name) return NULL;357
return (void *)glfwGetProcAddress(name);358
}360
const char *sigil_desktop_platform(void)361
{362
if (!g_desktop || !g_desktop->window) return "none";363
return platform_name(glfwGetPlatform());364
}366
/* ============================================================367
* MAIN LOOP368
* ============================================================ */370
/* Returns false when no window could be opened; the GLFW error callback371
* has already said why on stderr. */372
static bool run_main_loop(const char *title, int width, int height)373
{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.1383
* on macOS (the newest it offers); sokol_gfx gates a few features on the384
* reported version, and Mesa and NVIDIA hand back the highest core385
* version either way. Forward-compat as sokol_app set it too (required386
* on macOS). Depth 24 / stencil 8 are GLFW's defaults and match the387
* 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
#else392
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);393
#endif394
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 a398
* compositor rule (sway: for_window [app_id="Crash The Stack"]) can399
* name the window on either platform. GLFW's X11 backend already400
* 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
#endif431
}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 not449
* 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;482
}484
/* ============================================================485
* NATIVE FUNCTIONS486
* ============================================================ */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 startup493
* frame-proc: called each frame with delta-time argument494
* cleanup-proc: called before shutdown495
*/496
static Value native_app_run(SigilVM *vm, int argc, Value *args)497
{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;545
}547
/*548
* (frame-width) -> integer, framebuffer pixels549
*/550
static Value native_frame_width(SigilVM *vm, int argc, Value *args)551
{552
(void)vm; (void)argc; (void)args;553
return sigil_fixnum(sigil_desktop_framebuffer_width());554
}556
/*557
* (frame-height) -> integer, framebuffer pixels558
*/559
static Value native_frame_height(SigilVM *vm, int argc, Value *args)560
{561
(void)vm; (void)argc; (void)args;562
return sigil_fixnum(sigil_desktop_framebuffer_height());563
}565
/*566
* (frame-time) -> float (seconds since last frame)567
*/568
static Value native_frame_time(SigilVM *vm, int argc, Value *args)569
{570
(void)vm; (void)argc; (void)args;571
if (!g_desktop) return sigil_flonum(0.0);572
return sigil_flonum(g_desktop->frame_time);573
}575
/*576
* (time-elapsed) -> float (seconds since app start)577
*/578
static Value native_time_elapsed(SigilVM *vm, int argc, Value *args)579
{580
(void)vm; (void)argc; (void)args;581
if (!g_desktop) return sigil_flonum(0.0);582
return sigil_flonum(g_desktop->time_elapsed);583
}585
/*586
* (mouse-x) -> float, framebuffer pixels587
*/588
static Value native_mouse_x(SigilVM *vm, int argc, Value *args)589
{590
(void)vm; (void)argc; (void)args;591
return sigil_flonum(cursor_to_fb_x());592
}594
/*595
* (mouse-wheel) -> float: the wheel's vertical travel since the last frame596
* 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
*/600
static Value native_mouse_wheel(SigilVM *vm, int argc, Value *args)601
{602
(void)vm; (void)argc; (void)args;603
return sigil_flonum(g_desktop ? g_desktop->wheel_y : 0.0);604
}606
static Value native_mouse_wheel_x(SigilVM *vm, int argc, Value *args)607
{608
(void)vm; (void)argc; (void)args;609
return sigil_flonum(g_desktop ? g_desktop->wheel_x : 0.0);610
}612
/*613
* (mouse-y) -> float, framebuffer pixels614
*/615
static Value native_mouse_y(SigilVM *vm, int argc, Value *args)616
{617
(void)vm; (void)argc; (void)args;618
return sigil_flonum(cursor_to_fb_y());619
}621
/*622
* (mouse-down? button) -> boolean623
*/624
static Value native_mouse_down(SigilVM *vm, int argc, Value *args)625
{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;630
}632
/*633
* (mouse-pressed? button) -> boolean634
*/635
static Value native_mouse_pressed(SigilVM *vm, int argc, Value *args)636
{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;641
}643
/*644
* (mouse-released? button) -> boolean645
*/646
static Value native_mouse_released(SigilVM *vm, int argc, Value *args)647
{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;652
}654
/*655
* (key-down? key) -> boolean656
*/657
static Value native_key_down(SigilVM *vm, int argc, Value *args)658
{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;663
}665
/*666
* (key-pressed? key) -> boolean667
*/668
static Value native_key_pressed(SigilVM *vm, int argc, Value *args)669
{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;674
}676
/*677
* (key-released? key) -> boolean678
*/679
static Value native_key_released(SigilVM *vm, int argc, Value *args)680
{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;685
}687
/*688
* (key-code key) -> integer | #f689
*690
* The GLFW key code a key symbol names, or #f when the symbol names no691
* key. Needs no window: the one way to test the symbol table, and how a692
* program can ask whether a binding will ever fire before it is used.693
*/694
static Value native_key_code(SigilVM *vm, int argc, Value *args)695
{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);700
}702
/*703
* (quit-requested?) -> boolean704
*/705
static Value native_quit_requested(SigilVM *vm, int argc, Value *args)706
{707
(void)vm; (void)argc; (void)args;708
if (!g_desktop) return SIGIL_FALSE;709
return g_desktop->quit_requested ? SIGIL_TRUE : SIGIL_FALSE;710
}712
/*713
* (request-quit)714
*715
* The loop ends after the current frame; (quit-requested?) answers #t716
* from now on, as after sokol_app's sapp_request_quit.717
*/718
static Value native_request_quit(SigilVM *vm, int argc, Value *args)719
{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;725
}727
/* ============================================================728
* MODULE INITIALIZATION729
* ============================================================ */731
void sigil__init_sigil_desktop_module(SigilVM *vm)732
{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);802
}