nandi/jolt-nativepublic Fork 0
4f920afa410da72bfdb7a07d7faa0264c7bb8a08
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.

Let the Android glue be built without the media plane 2bd393e · on 4f920afa410da72bfdb7a07d7faa0264c7bb8a08 · nandi · 8d ago
jolt_main.c · 245 lines · 9.1 KBC Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/*
 * The Jolt half of the Android app: an embedded Chez runtime and the boot image
 * built from the Jolt sources, reached from `libvidya.so`'s `android_main`.
 *
 * This is not the NativeActivity. On Android the activity's library must be the
 * one holding android-activity's glue, which is `libvidya.so` — see
 * `crates/jolt-vidya/src/android.rs`. That glue dlopens this library and calls
 * `vidya_jolt_main`, so the entry point below is an ordinary C function rather
 * than a `main`.
 *
 * Every foreign symbol is registered with Chez explicitly. Jolt resolves a
 * `defcfn` through Chez's foreign-entry table, and on Android nothing populates
 * that table from the dynamic loader: the symbols live in *different* shared
 * objects (`libvidya.so` and `libjoltmoq.so`, both DT_NEEDEDs of this one),
 * which is exactly the case the loader will not answer for. Registering them by
 * hand is what makes all three ABIs — the push/pop one, the retained tree one
 * glimmer binds, and the media plane — reachable.
 *
 * The media plane needs one thing more than a symbol table. Its camera is
 * Camera2, reached over JNI, and the handles that takes only arrive in
 * android-activity's glue inside libvidya. This file is the only place both
 * objects are in scope, so it is where they are handed across.
 */

#include <stddef.h>
#include <stdio.h>
#include <unistd.h>

#include <android/log.h>
#include <pthread.h>

/* JOLT_WITHOUT_MOQ builds this glue for an app that does not link
 * libjoltmoq. frq is one: its media plane is jolt now — MoQ through
 * libmoq_ffi, Opus, H.264, V4L2 and ALSA — so the Rust one is an object it
 * never opens, and seventeen megabytes of APK for nothing. Everything moq
 * below is behind this, because --no-undefined turns an unreferenced symbol
 * into a link error rather than a surprise on the phone. */
#ifndef JOLT_WITHOUT_MOQ
#include "joltmoq.h"
#endif
#include "scheme.h"
#include "vidya.h"
#include "vidya_tree.h"

/* The boot image, linked in as a binary blob by llvm-objcopy. */
extern const unsigned char _binary_jolt_boot_start[];
extern const unsigned char _binary_jolt_boot_end[];

#define REGISTER_VIDYA(name) Sregister_symbol(#name, (void *)&name)
#define REGISTER_JOLTMOQ(name) Sregister_symbol(#name, (void *)&name)

static void register_vidya_api(void) {
  /* vidya.h */
  REGISTER_VIDYA(vidya_open);
  REGISTER_VIDYA(vidya_close);
  REGISTER_VIDYA(vidya_should_close);
  REGISTER_VIDYA(vidya_set_target_fps);
  REGISTER_VIDYA(vidya_set_mode);
  REGISTER_VIDYA(vidya_get_mode);
  REGISTER_VIDYA(vidya_load_font);
  REGISTER_VIDYA(vidya_begin_frame);
  REGISTER_VIDYA(vidya_end_frame);
  REGISTER_VIDYA(vidya_page_begin);
  REGISTER_VIDYA(vidya_page_end);
  REGISTER_VIDYA(vidya_card_begin);
  REGISTER_VIDYA(vidya_card_end);
  REGISTER_VIDYA(vidya_gap);
  REGISTER_VIDYA(vidya_separator);
  REGISTER_VIDYA(vidya_title);
  REGISTER_VIDYA(vidya_title_2);
  REGISTER_VIDYA(vidya_body);
  REGISTER_VIDYA(vidya_dim_label);
  REGISTER_VIDYA(vidya_button);
  REGISTER_VIDYA(vidya_checkbox);
  REGISTER_VIDYA(vidya_checkbox_value);
  REGISTER_VIDYA(vidya_status);
  REGISTER_VIDYA(vidya_text_field);

  /* vidya_tree.h */
  REGISTER_VIDYA(vidya_tree_root);
  REGISTER_VIDYA(vidya_node_new);
  REGISTER_VIDYA(vidya_node_free);
  REGISTER_VIDYA(vidya_node_exists);
  REGISTER_VIDYA(vidya_node_set_str);
  REGISTER_VIDYA(vidya_node_set_num);
  REGISTER_VIDYA(vidya_node_set_bool);
  REGISTER_VIDYA(vidya_node_clear_props);
  REGISTER_VIDYA(vidya_node_get_str);
  REGISTER_VIDYA(vidya_node_get_num);
  REGISTER_VIDYA(vidya_node_get_bool);
  REGISTER_VIDYA(vidya_node_tag);
  REGISTER_VIDYA(vidya_node_child_count);
  REGISTER_VIDYA(vidya_node_child_at);
  REGISTER_VIDYA(vidya_node_append);
  REGISTER_VIDYA(vidya_node_remove);
  REGISTER_VIDYA(vidya_node_insert_after);
  REGISTER_VIDYA(vidya_node_replace);
  REGISTER_VIDYA(vidya_tree_frame);
  REGISTER_VIDYA(vidya_tree_poll_event);
  REGISTER_VIDYA(vidya_tree_event_node);
  REGISTER_VIDYA(vidya_tree_event_name);
  REGISTER_VIDYA(vidya_tree_event_text);
  REGISTER_VIDYA(vidya_tree_event_num);

  /* Neither of these paints anything; they are the platform reached through
     the same ABI. `vidya_open_url` is the one that matters here — signing in
     leaves for a browser and comes back. */
  REGISTER_VIDYA(vidya_clipboard_image_png);
  REGISTER_VIDYA(vidya_open_url);
  REGISTER_VIDYA(vidya_pick_image);
  REGISTER_VIDYA(vidya_picked_image);

  /* Live pixels, the window's own size, and the tree dump. Nothing here is
     Android-specific; they are on this list because the list is the whole of
     what an application can reach on Android, and a symbol left off it is not
     a link error but a call that fails at the moment a screen needs it. */
  REGISTER_VIDYA(vidya_set_title);
  REGISTER_VIDYA(vidya_frame_rgba);
  REGISTER_VIDYA(vidya_frame_drop);
  REGISTER_VIDYA(vidya_screen_width);
  REGISTER_VIDYA(vidya_screen_height);
  REGISTER_VIDYA(vidya_tree_dump);
}

#ifndef JOLT_WITHOUT_MOQ
/*
 * The media plane's symbols, registered for the same reason the Vidya ones
 * above are: they live in libjoltmoq.so, a third object, and Android's loader
 * will not answer a foreign-entry lookup that crosses one.
 *
 * A call reaches none of this without it — `joltmoq_start` would be a defcfn
 * that resolves to nothing at the moment someone dials.
 */
static void register_joltmoq_api(void) {
  REGISTER_JOLTMOQ(joltmoq_init_logging);

  /* Lifecycle. */
  REGISTER_JOLTMOQ(joltmoq_start);
  REGISTER_JOLTMOQ(joltmoq_stop);
  REGISTER_JOLTMOQ(joltmoq_is_live);

  /* In-call controls. */
  REGISTER_JOLTMOQ(joltmoq_set_muted);
  REGISTER_JOLTMOQ(joltmoq_set_speaker_muted);
  REGISTER_JOLTMOQ(joltmoq_set_camera);
  REGISTER_JOLTMOQ(joltmoq_set_camera_device);
  REGISTER_JOLTMOQ(joltmoq_set_mic_device);
  REGISTER_JOLTMOQ(joltmoq_set_speaker_device);
  REGISTER_JOLTMOQ(joltmoq_mic_level);

  /* Status, drained rather than delivered. */
  REGISTER_JOLTMOQ(joltmoq_poll_status);
  REGISTER_JOLTMOQ(joltmoq_status_text);
  REGISTER_JOLTMOQ(joltmoq_status_has_camera);
  REGISTER_JOLTMOQ(joltmoq_status_has_mic);

  /* Remote video, borrowed a frame at a time. */
  REGISTER_JOLTMOQ(joltmoq_frame_poll);
  REGISTER_JOLTMOQ(joltmoq_frame_key);
  REGISTER_JOLTMOQ(joltmoq_frame_width);
  REGISTER_JOLTMOQ(joltmoq_frame_height);
  REGISTER_JOLTMOQ(joltmoq_frame_rgba);
  REGISTER_JOLTMOQ(joltmoq_video_keys);

  /* Devices. On the phone the cameras come from Camera2 and the microphone
     and speaker lists are empty — the platform picks those itself. */
  REGISTER_JOLTMOQ(joltmoq_cameras);
  REGISTER_JOLTMOQ(joltmoq_microphones);
  REGISTER_JOLTMOQ(joltmoq_speakers);

  /* Dialling. */
  REGISTER_JOLTMOQ(joltmoq_sfu_url);
  REGISTER_JOLTMOQ(joltmoq_can_dial);
  REGISTER_JOLTMOQ(joltmoq_new_instance);
}
#endif /* JOLT_WITHOUT_MOQ */

/*
 * Chez writes to stdout/stderr, which on Android goes nowhere. Pump both into
 * logcat so a Scheme-level error is visible rather than a silent exit.
 */
static void *log_scheme_output(void *context) {
  int fd = *(int *)context;
  char buffer[1024];
  FILE *stream = fdopen(fd, "r");
  if (stream == NULL) return NULL;

  while (fgets(buffer, sizeof(buffer), stream) != NULL) {
    __android_log_write(ANDROID_LOG_ERROR, "VidyaJolt", buffer);
  }
  fclose(stream);
  return NULL;
}

int vidya_jolt_main(void) {
  static const char *argv[] = {"vidya", NULL};
  int output_pipe[2];
  pthread_t logger;

  if (pipe(output_pipe) == 0) {
    dup2(output_pipe[1], STDOUT_FILENO);
    dup2(output_pipe[1], STDERR_FILENO);
    close(output_pipe[1]);
    pthread_create(&logger, NULL, log_scheme_output, &output_pipe[0]);
    pthread_detach(logger);
  }

  __android_log_print(ANDROID_LOG_INFO, "VidyaJolt",
                      "initializing embedded Chez runtime");
  Sscheme_init(NULL);
  Sregister_boot_file_bytes(
      "jolt",
      (void *)_binary_jolt_boot_start,
      (iptr)(_binary_jolt_boot_end - _binary_jolt_boot_start));
  Sbuild_heap(NULL, NULL);
  register_vidya_api();
#ifndef JOLT_WITHOUT_MOQ
  register_joltmoq_api();
#endif

#ifndef JOLT_WITHOUT_MOQ
  /* The media plane's own handles, which it cannot get for itself: they arrive
     in android-activity's glue, and the glue is in libvidya.so. This is the
     only place both objects are in scope. Before Scheme starts, so no call can
     reach the camera ahead of it.

     Logged because there is otherwise no way to see it happen: joltmoq's own
     logging goes through env_logger, which nothing has initialised this early,
     so a failure here would be silent until a camera failed to open much later
     for reasons that would look like anything but this. */
  void *moq_vm = vidya_android_vm();
  void *moq_activity = vidya_android_activity();
  __android_log_print(moq_vm && moq_activity ? ANDROID_LOG_INFO : ANDROID_LOG_ERROR,
                      "VidyaJolt", "media plane JNI handles: vm=%p activity=%p",
                      moq_vm, moq_activity);
  joltmoq_android_init(moq_vm, moq_activity);
#endif

  __android_log_print(ANDROID_LOG_INFO, "VidyaJolt", "starting Jolt application");
  int status = Sscheme_start(1, argv);
  __android_log_print(ANDROID_LOG_ERROR, "VidyaJolt",
                      "Jolt application exited with status %d", status);
  Sscheme_deinit();
  return status;
}