nandi/jolt-nativepublic Fork 0
d942053
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Build libvidya for the phone, and hold the glue that boots it

The deps were already target-gated in the Cargo manifests — arboard has no
Android backend, jni is only reachable there, winit's android-native-activity
is what pulls in the glue that owns the event loop — and the BUCK files said
none of it, because there was no Android configuration for them to say it
about. Now there is.

android/jolt_main.c and the vidya.h beside it come from the repo this ABI
used to live in. They belong here: crates/jolt-vidya/src/android.rs already
names android/jolt_main.c in its own comments and dlopens vidya_jolt_main out
of it, so the two halves of the Android entry point were in different
repositories. Every symbol the glue registers with Chez is exported by the
library this now builds, so it moved unchanged apart from a path in a comment.

`just ffi-android` stages the result where an APK build can find it, under
the name vidya's own recipe used, because that is what frq's build-apk.sh
calls. There is no Android libjoltmoq: the media plane is V4L2 and PipeWire,
and the phone has neither.
nandi committed 2026-08-31T00:31:41-07:00 Browse files
d942053 parent: 0238622
modified BUCK +18 -0
@@ -20,3 +20,21 @@ filegroup(
2020 srcs = [":libjoltmoq", ":libvidya"],
2121 visibility = ["PUBLIC"],
2222 )
23+
24+# The same C ABI, cross-compiled for the phone. The sources and the deps are
25+# the ones above; only the target configuration moves, which is what
26+# configured_alias is for — build scripts and proc macros still run on the
27+# host. There is no Android libjoltmoq: the media plane is V4L2 and PipeWire.
28+configured_alias(
29+ name = "vidya-ffi-android",
30+ actual = "//crates/jolt-vidya:vidya-ffi",
31+ platform = "//platforms:android-arm64",
32+ visibility = ["PUBLIC"],
33+)
34+
35+genrule(
36+ name = "libvidya-android",
37+ out = "libvidya.so",
38+ cmd = "cp $(location :vidya-ffi-android[shared]) $OUT",
39+ visibility = ["PUBLIC"],
40+)
@@ -20,3 +20,21 @@ filegroup(
20 srcs = [":libjoltmoq", ":libvidya"],20 srcs = [":libjoltmoq", ":libvidya"],
21 visibility = ["PUBLIC"],21 visibility = ["PUBLIC"],
22 )22 )
23+
24+# The same C ABI, cross-compiled for the phone. The sources and the deps are
25+# the ones above; only the target configuration moves, which is what
26+# configured_alias is for — build scripts and proc macros still run on the
27+# host. There is no Android libjoltmoq: the media plane is V4L2 and PipeWire.
28+configured_alias(
29+ name = "vidya-ffi-android",
30+ actual = "//crates/jolt-vidya:vidya-ffi",
31+ platform = "//platforms:android-arm64",
32+ visibility = ["PUBLIC"],
33+)
34+
35+genrule(
36+ name = "libvidya-android",
37+ out = "libvidya.so",
38+ cmd = "cp $(location :vidya-ffi-android[shared]) $OUT",
39+ visibility = ["PUBLIC"],
40+)
added android/jolt_main.c +154 -0
new file mode 100644
@@ -0,0 +1,154 @@
1+/*
2+ * The Jolt half of the Android app: an embedded Chez runtime and the boot image
3+ * built from the Jolt sources, reached from `libvidya.so`'s `android_main`.
4+ *
5+ * This is not the NativeActivity. On Android the activity's library must be the
6+ * one holding android-activity's glue, which is `libvidya.so` — see
7+ * `crates/jolt-vidya/src/android.rs`. That glue dlopens this library and calls
8+ * `vidya_jolt_main`, so the entry point below is an ordinary C function rather
9+ * than a `main`.
10+ *
11+ * Every Vidya symbol is registered with Chez explicitly. Jolt resolves a
12+ * `defcfn` through Chez's foreign-entry table, and on Android nothing populates
13+ * that table from the dynamic loader: the symbols live in a *different* shared
14+ * object (`libvidya.so`, a DT_NEEDED of this one), which is exactly the case
15+ * the loader will not answer for. Registering them by hand is what makes both
16+ * ABIs — the push/pop one and the retained tree one glimmer binds — reachable.
17+ */
18+
19+#include <stddef.h>
20+#include <stdio.h>
21+#include <unistd.h>
22+
23+#include <android/log.h>
24+#include <pthread.h>
25+
26+#include "scheme.h"
27+#include "vidya.h"
28+#include "vidya_tree.h"
29+
30+/* The boot image, linked in as a binary blob by llvm-objcopy. */
31+extern const unsigned char _binary_jolt_boot_start[];
32+extern const unsigned char _binary_jolt_boot_end[];
33+
34+#define REGISTER_VIDYA(name) Sregister_symbol(#name, (void *)&name)
35+
36+static void register_vidya_api(void) {
37+ /* vidya.h */
38+ REGISTER_VIDYA(vidya_open);
39+ REGISTER_VIDYA(vidya_close);
40+ REGISTER_VIDYA(vidya_should_close);
41+ REGISTER_VIDYA(vidya_set_target_fps);
42+ REGISTER_VIDYA(vidya_set_mode);
43+ REGISTER_VIDYA(vidya_get_mode);
44+ REGISTER_VIDYA(vidya_load_font);
45+ REGISTER_VIDYA(vidya_begin_frame);
46+ REGISTER_VIDYA(vidya_end_frame);
47+ REGISTER_VIDYA(vidya_page_begin);
48+ REGISTER_VIDYA(vidya_page_end);
49+ REGISTER_VIDYA(vidya_card_begin);
50+ REGISTER_VIDYA(vidya_card_end);
51+ REGISTER_VIDYA(vidya_gap);
52+ REGISTER_VIDYA(vidya_separator);
53+ REGISTER_VIDYA(vidya_title);
54+ REGISTER_VIDYA(vidya_title_2);
55+ REGISTER_VIDYA(vidya_body);
56+ REGISTER_VIDYA(vidya_dim_label);
57+ REGISTER_VIDYA(vidya_button);
58+ REGISTER_VIDYA(vidya_checkbox);
59+ REGISTER_VIDYA(vidya_checkbox_value);
60+ REGISTER_VIDYA(vidya_status);
61+ REGISTER_VIDYA(vidya_text_field);
62+
63+ /* vidya_tree.h */
64+ REGISTER_VIDYA(vidya_tree_root);
65+ REGISTER_VIDYA(vidya_node_new);
66+ REGISTER_VIDYA(vidya_node_free);
67+ REGISTER_VIDYA(vidya_node_exists);
68+ REGISTER_VIDYA(vidya_node_set_str);
69+ REGISTER_VIDYA(vidya_node_set_num);
70+ REGISTER_VIDYA(vidya_node_set_bool);
71+ REGISTER_VIDYA(vidya_node_clear_props);
72+ REGISTER_VIDYA(vidya_node_get_str);
73+ REGISTER_VIDYA(vidya_node_get_num);
74+ REGISTER_VIDYA(vidya_node_get_bool);
75+ REGISTER_VIDYA(vidya_node_tag);
76+ REGISTER_VIDYA(vidya_node_child_count);
77+ REGISTER_VIDYA(vidya_node_child_at);
78+ REGISTER_VIDYA(vidya_node_append);
79+ REGISTER_VIDYA(vidya_node_remove);
80+ REGISTER_VIDYA(vidya_node_insert_after);
81+ REGISTER_VIDYA(vidya_node_replace);
82+ REGISTER_VIDYA(vidya_tree_frame);
83+ REGISTER_VIDYA(vidya_tree_poll_event);
84+ REGISTER_VIDYA(vidya_tree_event_node);
85+ REGISTER_VIDYA(vidya_tree_event_name);
86+ REGISTER_VIDYA(vidya_tree_event_text);
87+ REGISTER_VIDYA(vidya_tree_event_num);
88+
89+ /* Neither of these paints anything; they are the platform reached through
90+ the same ABI. `vidya_open_url` is the one that matters here — signing in
91+ leaves for a browser and comes back. */
92+ REGISTER_VIDYA(vidya_clipboard_image_png);
93+ REGISTER_VIDYA(vidya_open_url);
94+ REGISTER_VIDYA(vidya_pick_image);
95+ REGISTER_VIDYA(vidya_picked_image);
96+
97+ /* Live pixels, the window's own size, and the tree dump. Nothing here is
98+ Android-specific; they are on this list because the list is the whole of
99+ what an application can reach on Android, and a symbol left off it is not
100+ a link error but a call that fails at the moment a screen needs it. */
101+ REGISTER_VIDYA(vidya_frame_rgba);
102+ REGISTER_VIDYA(vidya_frame_drop);
103+ REGISTER_VIDYA(vidya_screen_width);
104+ REGISTER_VIDYA(vidya_screen_height);
105+ REGISTER_VIDYA(vidya_tree_dump);
106+}
107+
108+/*
109+ * Chez writes to stdout/stderr, which on Android goes nowhere. Pump both into
110+ * logcat so a Scheme-level error is visible rather than a silent exit.
111+ */
112+static void *log_scheme_output(void *context) {
113+ int fd = *(int *)context;
114+ char buffer[1024];
115+ FILE *stream = fdopen(fd, "r");
116+ if (stream == NULL) return NULL;
117+
118+ while (fgets(buffer, sizeof(buffer), stream) != NULL) {
119+ __android_log_write(ANDROID_LOG_ERROR, "VidyaJolt", buffer);
120+ }
121+ fclose(stream);
122+ return NULL;
123+}
124+
125+int vidya_jolt_main(void) {
126+ static const char *argv[] = {"vidya", NULL};
127+ int output_pipe[2];
128+ pthread_t logger;
129+
130+ if (pipe(output_pipe) == 0) {
131+ dup2(output_pipe[1], STDOUT_FILENO);
132+ dup2(output_pipe[1], STDERR_FILENO);
133+ close(output_pipe[1]);
134+ pthread_create(&logger, NULL, log_scheme_output, &output_pipe[0]);
135+ pthread_detach(logger);
136+ }
137+
138+ __android_log_print(ANDROID_LOG_INFO, "VidyaJolt",
139+ "initializing embedded Chez runtime");
140+ Sscheme_init(NULL);
141+ Sregister_boot_file_bytes(
142+ "jolt",
143+ (void *)_binary_jolt_boot_start,
144+ (iptr)(_binary_jolt_boot_end - _binary_jolt_boot_start));
145+ Sbuild_heap(NULL, NULL);
146+ register_vidya_api();
147+
148+ __android_log_print(ANDROID_LOG_INFO, "VidyaJolt", "starting Jolt application");
149+ int status = Sscheme_start(1, argv);
150+ __android_log_print(ANDROID_LOG_ERROR, "VidyaJolt",
151+ "Jolt application exited with status %d", status);
152+ Sscheme_deinit();
153+ return status;
154+}
new file mode 100644
@@ -0,0 +1,154 @@
1+/*
2+ * The Jolt half of the Android app: an embedded Chez runtime and the boot image
3+ * built from the Jolt sources, reached from `libvidya.so`'s `android_main`.
4+ *
5+ * This is not the NativeActivity. On Android the activity's library must be the
6+ * one holding android-activity's glue, which is `libvidya.so` — see
7+ * `crates/jolt-vidya/src/android.rs`. That glue dlopens this library and calls
8+ * `vidya_jolt_main`, so the entry point below is an ordinary C function rather
9+ * than a `main`.
10+ *
11+ * Every Vidya symbol is registered with Chez explicitly. Jolt resolves a
12+ * `defcfn` through Chez's foreign-entry table, and on Android nothing populates
13+ * that table from the dynamic loader: the symbols live in a *different* shared
14+ * object (`libvidya.so`, a DT_NEEDED of this one), which is exactly the case
15+ * the loader will not answer for. Registering them by hand is what makes both
16+ * ABIs — the push/pop one and the retained tree one glimmer binds — reachable.
17+ */
18+
19+#include <stddef.h>
20+#include <stdio.h>
21+#include <unistd.h>
22+
23+#include <android/log.h>
24+#include <pthread.h>
25+
26+#include "scheme.h"
27+#include "vidya.h"
28+#include "vidya_tree.h"
29+
30+/* The boot image, linked in as a binary blob by llvm-objcopy. */
31+extern const unsigned char _binary_jolt_boot_start[];
32+extern const unsigned char _binary_jolt_boot_end[];
33+
34+#define REGISTER_VIDYA(name) Sregister_symbol(#name, (void *)&name)
35+
36+static void register_vidya_api(void) {
37+ /* vidya.h */
38+ REGISTER_VIDYA(vidya_open);
39+ REGISTER_VIDYA(vidya_close);
40+ REGISTER_VIDYA(vidya_should_close);
41+ REGISTER_VIDYA(vidya_set_target_fps);
42+ REGISTER_VIDYA(vidya_set_mode);
43+ REGISTER_VIDYA(vidya_get_mode);
44+ REGISTER_VIDYA(vidya_load_font);
45+ REGISTER_VIDYA(vidya_begin_frame);
46+ REGISTER_VIDYA(vidya_end_frame);
47+ REGISTER_VIDYA(vidya_page_begin);
48+ REGISTER_VIDYA(vidya_page_end);
49+ REGISTER_VIDYA(vidya_card_begin);
50+ REGISTER_VIDYA(vidya_card_end);
51+ REGISTER_VIDYA(vidya_gap);
52+ REGISTER_VIDYA(vidya_separator);
53+ REGISTER_VIDYA(vidya_title);
54+ REGISTER_VIDYA(vidya_title_2);
55+ REGISTER_VIDYA(vidya_body);
56+ REGISTER_VIDYA(vidya_dim_label);
57+ REGISTER_VIDYA(vidya_button);
58+ REGISTER_VIDYA(vidya_checkbox);
59+ REGISTER_VIDYA(vidya_checkbox_value);
60+ REGISTER_VIDYA(vidya_status);
61+ REGISTER_VIDYA(vidya_text_field);
62+
63+ /* vidya_tree.h */
64+ REGISTER_VIDYA(vidya_tree_root);
65+ REGISTER_VIDYA(vidya_node_new);
66+ REGISTER_VIDYA(vidya_node_free);
67+ REGISTER_VIDYA(vidya_node_exists);
68+ REGISTER_VIDYA(vidya_node_set_str);
69+ REGISTER_VIDYA(vidya_node_set_num);
70+ REGISTER_VIDYA(vidya_node_set_bool);
71+ REGISTER_VIDYA(vidya_node_clear_props);
72+ REGISTER_VIDYA(vidya_node_get_str);
73+ REGISTER_VIDYA(vidya_node_get_num);
74+ REGISTER_VIDYA(vidya_node_get_bool);
75+ REGISTER_VIDYA(vidya_node_tag);
76+ REGISTER_VIDYA(vidya_node_child_count);
77+ REGISTER_VIDYA(vidya_node_child_at);
78+ REGISTER_VIDYA(vidya_node_append);
79+ REGISTER_VIDYA(vidya_node_remove);
80+ REGISTER_VIDYA(vidya_node_insert_after);
81+ REGISTER_VIDYA(vidya_node_replace);
82+ REGISTER_VIDYA(vidya_tree_frame);
83+ REGISTER_VIDYA(vidya_tree_poll_event);
84+ REGISTER_VIDYA(vidya_tree_event_node);
85+ REGISTER_VIDYA(vidya_tree_event_name);
86+ REGISTER_VIDYA(vidya_tree_event_text);
87+ REGISTER_VIDYA(vidya_tree_event_num);
88+
89+ /* Neither of these paints anything; they are the platform reached through
90+ the same ABI. `vidya_open_url` is the one that matters here — signing in
91+ leaves for a browser and comes back. */
92+ REGISTER_VIDYA(vidya_clipboard_image_png);
93+ REGISTER_VIDYA(vidya_open_url);
94+ REGISTER_VIDYA(vidya_pick_image);
95+ REGISTER_VIDYA(vidya_picked_image);
96+
97+ /* Live pixels, the window's own size, and the tree dump. Nothing here is
98+ Android-specific; they are on this list because the list is the whole of
99+ what an application can reach on Android, and a symbol left off it is not
100+ a link error but a call that fails at the moment a screen needs it. */
101+ REGISTER_VIDYA(vidya_frame_rgba);
102+ REGISTER_VIDYA(vidya_frame_drop);
103+ REGISTER_VIDYA(vidya_screen_width);
104+ REGISTER_VIDYA(vidya_screen_height);
105+ REGISTER_VIDYA(vidya_tree_dump);
106+}
107+
108+/*
109+ * Chez writes to stdout/stderr, which on Android goes nowhere. Pump both into
110+ * logcat so a Scheme-level error is visible rather than a silent exit.
111+ */
112+static void *log_scheme_output(void *context) {
113+ int fd = *(int *)context;
114+ char buffer[1024];
115+ FILE *stream = fdopen(fd, "r");
116+ if (stream == NULL) return NULL;
117+
118+ while (fgets(buffer, sizeof(buffer), stream) != NULL) {
119+ __android_log_write(ANDROID_LOG_ERROR, "VidyaJolt", buffer);
120+ }
121+ fclose(stream);
122+ return NULL;
123+}
124+
125+int vidya_jolt_main(void) {
126+ static const char *argv[] = {"vidya", NULL};
127+ int output_pipe[2];
128+ pthread_t logger;
129+
130+ if (pipe(output_pipe) == 0) {
131+ dup2(output_pipe[1], STDOUT_FILENO);
132+ dup2(output_pipe[1], STDERR_FILENO);
133+ close(output_pipe[1]);
134+ pthread_create(&logger, NULL, log_scheme_output, &output_pipe[0]);
135+ pthread_detach(logger);
136+ }
137+
138+ __android_log_print(ANDROID_LOG_INFO, "VidyaJolt",
139+ "initializing embedded Chez runtime");
140+ Sscheme_init(NULL);
141+ Sregister_boot_file_bytes(
142+ "jolt",
143+ (void *)_binary_jolt_boot_start,
144+ (iptr)(_binary_jolt_boot_end - _binary_jolt_boot_start));
145+ Sbuild_heap(NULL, NULL);
146+ register_vidya_api();
147+
148+ __android_log_print(ANDROID_LOG_INFO, "VidyaJolt", "starting Jolt application");
149+ int status = Sscheme_start(1, argv);
150+ __android_log_print(ANDROID_LOG_ERROR, "VidyaJolt",
151+ "Jolt application exited with status %d", status);
152+ Sscheme_deinit();
153+ return status;
154+}
modified crates/jolt-vidya/BUCK +16 -3
@@ -13,9 +13,16 @@ rust_library(
1313 visibility = ["PUBLIC"],
1414 # Cargo renames the dependency so the cdylib itself can keep the `vidya`
1515 # crate name; mirror that here.
16- named_deps = {"vidya_core": "//crates/vidya-core:vidya"},
16+ named_deps = select({
17+ "DEFAULT": {"vidya_core": "//crates/vidya-core:vidya"},
18+ # jni is renamed in third-party/rust/Cargo.toml, for the reason given
19+ # there; the crate is `jni` to the source either way.
20+ "prelude//os:android": {
21+ "jni": "//third-party/rust:jni-0-22",
22+ "vidya_core": "//crates/vidya-core:vidya",
23+ },
24+ }),
1725 deps = [
18- "//third-party/rust:arboard",
1926 "//third-party/rust:egui",
2027 "//third-party/rust:egui-winit",
2128 "//third-party/rust:egui_glow",
@@ -24,5 +31,11 @@ rust_library(
2431 "//third-party/rust:glutin-winit",
2532 "//third-party/rust:png",
2633 "//third-party/rust:winit",
27- ],
34+ ] + select({
35+ # Reading a pasted picture off the clipboard, which arboard has no
36+ # Android backend for; `vidya_open_url` is an ACTION_VIEW intent there
37+ # and a JNI call. Mirrors the target-gated deps in Cargo.toml.
38+ "DEFAULT": ["//third-party/rust:arboard"],
39+ "prelude//os:android": [],
40+ }),
2841 )
@@ -13,9 +13,16 @@ rust_library(
13 visibility = ["PUBLIC"],13 visibility = ["PUBLIC"],
14 # Cargo renames the dependency so the cdylib itself can keep the `vidya`14 # Cargo renames the dependency so the cdylib itself can keep the `vidya`
15 # crate name; mirror that here.15 # crate name; mirror that here.
16- named_deps = {"vidya_core": "//crates/vidya-core:vidya"},16+ named_deps = select({
17+ "DEFAULT": {"vidya_core": "//crates/vidya-core:vidya"},
18+ # jni is renamed in third-party/rust/Cargo.toml, for the reason given
19+ # there; the crate is `jni` to the source either way.
20+ "prelude//os:android": {
21+ "jni": "//third-party/rust:jni-0-22",
22+ "vidya_core": "//crates/vidya-core:vidya",
23+ },
24+ }),
17 deps = [25 deps = [
18- "//third-party/rust:arboard",
19 "//third-party/rust:egui",26 "//third-party/rust:egui",
20 "//third-party/rust:egui-winit",27 "//third-party/rust:egui-winit",
21 "//third-party/rust:egui_glow",28 "//third-party/rust:egui_glow",
@@ -24,5 +31,11 @@ rust_library(
24 "//third-party/rust:glutin-winit",31 "//third-party/rust:glutin-winit",
25 "//third-party/rust:png",32 "//third-party/rust:png",
26 "//third-party/rust:winit",33 "//third-party/rust:winit",
27- ],34+ ] + select({
35+ # Reading a pasted picture off the clipboard, which arboard has no
36+ # Android backend for; `vidya_open_url` is an ACTION_VIEW intent there
37+ # and a JNI call. Mirrors the target-gated deps in Cargo.toml.
38+ "DEFAULT": ["//third-party/rust:arboard"],
39+ "prelude//os:android": [],
40+ }),
28 )41 )
added crates/jolt-vidya/include/vidya.h +75 -0
new file mode 100644
@@ -0,0 +1,75 @@
1+#ifndef VIDYA_H
2+#define VIDYA_H
3+
4+#include <stddef.h>
5+
6+#if defined(_WIN32)
7+# if defined(VIDYA_BUILD)
8+# define VIDYA_API __declspec(dllexport)
9+# else
10+# define VIDYA_API __declspec(dllimport)
11+# endif
12+#elif defined(__GNUC__)
13+# define VIDYA_API __attribute__((visibility("default")))
14+#else
15+# define VIDYA_API
16+#endif
17+
18+#ifdef __cplusplus
19+extern "C" {
20+#endif
21+
22+typedef enum VidyaMode {
23+ VIDYA_DARK = 0,
24+ VIDYA_LIGHT = 1
25+} VidyaMode;
26+
27+typedef enum VidyaButtonKind {
28+ VIDYA_BUTTON_DEFAULT = 0,
29+ VIDYA_BUTTON_PRIMARY = 1,
30+ VIDYA_BUTTON_DESTRUCTIVE = 2
31+} VidyaButtonKind;
32+
33+/* Window and frame lifecycle. There is one UI context per process for now.
34+ * All calls must remain on the window thread. */
35+VIDYA_API int vidya_open(int width, int height, const char *title);
36+VIDYA_API void vidya_close(void);
37+VIDYA_API int vidya_should_close(void);
38+VIDYA_API void vidya_set_target_fps(int fps);
39+VIDYA_API void vidya_set_mode(int mode);
40+VIDYA_API int vidya_get_mode(void);
41+/* Replace the UI font. Returns 1 on success. atlas_size is the size the direct
42+ * backend bakes its atlas at, which controls then scale down to the semantic
43+ * type sizes; 32 is a good default. Call between frames, not inside one. */
44+VIDYA_API int vidya_load_font(const char *path, int atlas_size);
45+VIDYA_API void vidya_begin_frame(void);
46+VIDYA_API void vidya_end_frame(void);
47+
48+/* A frame is a vertical immediate-mode page. Containers save and restore its
49+ * horizontal bounds; every leaf advances the vertical cursor. */
50+VIDYA_API void vidya_page_begin(float max_width);
51+VIDYA_API void vidya_page_end(void);
52+VIDYA_API void vidya_card_begin(void);
53+VIDYA_API void vidya_card_end(void);
54+VIDYA_API void vidya_gap(float pixels);
55+VIDYA_API void vidya_separator(void);
56+
57+/* Semantic text roles. */
58+VIDYA_API void vidya_title(const char *text);
59+VIDYA_API void vidya_title_2(const char *text);
60+VIDYA_API void vidya_body(const char *text);
61+VIDYA_API void vidya_dim_label(const char *text);
62+
63+/* Controls return 1 only on activation/change during the current frame. */
64+VIDYA_API int vidya_button(const char *label, int kind);
65+VIDYA_API int vidya_checkbox(const char *label, int *checked);
66+/* FFI-friendly checkbox variant: returns the current value after handling input. */
67+VIDYA_API int vidya_checkbox_value(const char *label, int checked);
68+VIDYA_API void vidya_status(const char *label, int live);
69+VIDYA_API int vidya_text_field(char *text, size_t capacity, const char *placeholder);
70+
71+#ifdef __cplusplus
72+}
73+#endif
74+
75+#endif
new file mode 100644
@@ -0,0 +1,75 @@
1+#ifndef VIDYA_H
2+#define VIDYA_H
3+
4+#include <stddef.h>
5+
6+#if defined(_WIN32)
7+# if defined(VIDYA_BUILD)
8+# define VIDYA_API __declspec(dllexport)
9+# else
10+# define VIDYA_API __declspec(dllimport)
11+# endif
12+#elif defined(__GNUC__)
13+# define VIDYA_API __attribute__((visibility("default")))
14+#else
15+# define VIDYA_API
16+#endif
17+
18+#ifdef __cplusplus
19+extern "C" {
20+#endif
21+
22+typedef enum VidyaMode {
23+ VIDYA_DARK = 0,
24+ VIDYA_LIGHT = 1
25+} VidyaMode;
26+
27+typedef enum VidyaButtonKind {
28+ VIDYA_BUTTON_DEFAULT = 0,
29+ VIDYA_BUTTON_PRIMARY = 1,
30+ VIDYA_BUTTON_DESTRUCTIVE = 2
31+} VidyaButtonKind;
32+
33+/* Window and frame lifecycle. There is one UI context per process for now.
34+ * All calls must remain on the window thread. */
35+VIDYA_API int vidya_open(int width, int height, const char *title);
36+VIDYA_API void vidya_close(void);
37+VIDYA_API int vidya_should_close(void);
38+VIDYA_API void vidya_set_target_fps(int fps);
39+VIDYA_API void vidya_set_mode(int mode);
40+VIDYA_API int vidya_get_mode(void);
41+/* Replace the UI font. Returns 1 on success. atlas_size is the size the direct
42+ * backend bakes its atlas at, which controls then scale down to the semantic
43+ * type sizes; 32 is a good default. Call between frames, not inside one. */
44+VIDYA_API int vidya_load_font(const char *path, int atlas_size);
45+VIDYA_API void vidya_begin_frame(void);
46+VIDYA_API void vidya_end_frame(void);
47+
48+/* A frame is a vertical immediate-mode page. Containers save and restore its
49+ * horizontal bounds; every leaf advances the vertical cursor. */
50+VIDYA_API void vidya_page_begin(float max_width);
51+VIDYA_API void vidya_page_end(void);
52+VIDYA_API void vidya_card_begin(void);
53+VIDYA_API void vidya_card_end(void);
54+VIDYA_API void vidya_gap(float pixels);
55+VIDYA_API void vidya_separator(void);
56+
57+/* Semantic text roles. */
58+VIDYA_API void vidya_title(const char *text);
59+VIDYA_API void vidya_title_2(const char *text);
60+VIDYA_API void vidya_body(const char *text);
61+VIDYA_API void vidya_dim_label(const char *text);
62+
63+/* Controls return 1 only on activation/change during the current frame. */
64+VIDYA_API int vidya_button(const char *label, int kind);
65+VIDYA_API int vidya_checkbox(const char *label, int *checked);
66+/* FFI-friendly checkbox variant: returns the current value after handling input. */
67+VIDYA_API int vidya_checkbox_value(const char *label, int checked);
68+VIDYA_API void vidya_status(const char *label, int live);
69+VIDYA_API int vidya_text_field(char *text, size_t capacity, const char *placeholder);
70+
71+#ifdef __cplusplus
72+}
73+#endif
74+
75+#endif
modified crates/vidya-core/BUCK +6 -1
@@ -14,5 +14,10 @@ rust_library(
1414 "//third-party/rust:egui",
1515 "//third-party/rust:png",
1616 "//third-party/rust:zip",
17- ],
17+ ] + select({
18+ "DEFAULT": [],
19+ # `sync_system_chrome_from_android` reads the insets off the
20+ # NativeActivity handle. Mirrors the target-gated dep in Cargo.toml.
21+ "prelude//os:android": ["//third-party/rust:winit"],
22+ }),
1823 )
@@ -14,5 +14,10 @@ rust_library(
14 "//third-party/rust:egui",14 "//third-party/rust:egui",
15 "//third-party/rust:png",15 "//third-party/rust:png",
16 "//third-party/rust:zip",16 "//third-party/rust:zip",
17- ],17+ ] + select({
18+ "DEFAULT": [],
19+ # `sync_system_chrome_from_android` reads the insets off the
20+ # NativeActivity handle. Mirrors the target-gated dep in Cargo.toml.
21+ "prelude//os:android": ["//third-party/rust:winit"],
22+ }),
18 )23 )
modified justfile +17 -0
@@ -49,6 +49,23 @@ buck-build *args:
4949 buck-libdir:
5050 @echo "{{justfile_directory()}}/build/lib"
5151
52+# libvidya alone, cross-compiled for a 64-bit Android device, staged where an
53+# APK build can pick it up. There is no libjoltmoq here: the media plane is
54+# V4L2 and PipeWire, neither of which the phone has.
55+#
56+# The NDK is fetched by buck2 from the pin in scripts/android-ndk.dotslash, so
57+# the only thing a machine needs installed for this is nothing.
58+ffi-android:
59+ @just buck build //:libvidya-android
60+ mkdir -p build/android/arm64-v8a
61+ cp "$(just buck build --show-output //:libvidya-android | awk '/libvidya.so/{print $2}')" \
62+ build/android/arm64-v8a/libvidya.so
63+ @echo "{{justfile_directory()}}/build/android/arm64-v8a"
64+
65+# Where an APK build finds the Android libvidya, and the glue beside it.
66+android-libdir:
67+ @echo "{{justfile_directory()}}/build/android/arm64-v8a"
68+
5269 test *args:
5370 cargo test --workspace {{args}}
5471
@@ -49,6 +49,23 @@ buck-build *args:
49 buck-libdir:49 buck-libdir:
50 @echo "{{justfile_directory()}}/build/lib"50 @echo "{{justfile_directory()}}/build/lib"
51 51
52+# libvidya alone, cross-compiled for a 64-bit Android device, staged where an
53+# APK build can pick it up. There is no libjoltmoq here: the media plane is
54+# V4L2 and PipeWire, neither of which the phone has.
55+#
56+# The NDK is fetched by buck2 from the pin in scripts/android-ndk.dotslash, so
57+# the only thing a machine needs installed for this is nothing.
58+ffi-android:
59+ @just buck build //:libvidya-android
60+ mkdir -p build/android/arm64-v8a
61+ cp "$(just buck build --show-output //:libvidya-android | awk '/libvidya.so/{print $2}')" \
62+ build/android/arm64-v8a/libvidya.so
63+ @echo "{{justfile_directory()}}/build/android/arm64-v8a"
64+
65+# Where an APK build finds the Android libvidya, and the glue beside it.
66+android-libdir:
67+ @echo "{{justfile_directory()}}/build/android/arm64-v8a"
68+
52 test *args:69 test *args:
53 cargo test --workspace {{args}}70 cargo test --workspace {{args}}
54 71