| 1 | // Editor's natives: crates/mobile's C surface for the Java host. |
| 2 | #include <android/log.h> |
| 3 | #include <android/native_window_jni.h> |
| 4 | #include <jni.h> |
| 5 | #include <pthread.h> |
| 6 | #include <stdint.h> |
| 7 | #include <stdio.h> |
| 8 | #include <stdlib.h> |
| 9 | #include <string.h> |
| 10 | #include <unistd.h> |
| 11 | #include "snowbound.h" |
| 12 | |
| 13 | #define JNI(name) Java_net_paperclover_snowbound_Editor_##name |
| 14 | |
| 15 | // Rust reports problems on stderr, which Android discards; these lines go to logcat instead. |
| 16 | static void *log_stderr(void *pipe) { |
| 17 | FILE *lines = fdopen((int)(intptr_t)pipe, "r"); |
| 18 | char line[1024]; |
| 19 | while (lines && fgets(line, sizeof line, lines)) __android_log_write(ANDROID_LOG_WARN, "snowbound", line); |
| 20 | return NULL; |
| 21 | } |
| 22 | |
| 23 | static JavaVM *machine; |
| 24 | static jclass editor, provider; |
| 25 | static jmethodID woken_method, document_method, children_method, open_method, close_method, rename_method; |
| 26 | static const sb_provider documents; |
| 27 | |
| 28 | JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { |
| 29 | (void)reserved; |
| 30 | machine = vm; |
| 31 | JNIEnv *env; |
| 32 | if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) return JNI_ERR; |
| 33 | jclass local = (*env)->FindClass(env, "net/paperclover/snowbound/Editor"); |
| 34 | if (!local) return JNI_ERR; |
| 35 | editor = (*env)->NewGlobalRef(env, local); |
| 36 | (*env)->DeleteLocalRef(env, local); |
| 37 | woken_method = (*env)->GetStaticMethodID(env, editor, "woken", "()V"); |
| 38 | local = (*env)->FindClass(env, "net/paperclover/snowbound/Provider"); |
| 39 | if (!woken_method || !local) return JNI_ERR; |
| 40 | provider = (*env)->NewGlobalRef(env, local); |
| 41 | (*env)->DeleteLocalRef(env, local); |
| 42 | document_method = (*env)->GetStaticMethodID(env, provider, "document", "(Ljava/lang/String;JJ)I"); |
| 43 | children_method = (*env)->GetStaticMethodID(env, provider, "children", "(Ljava/lang/String;JJ)I"); |
| 44 | open_method = (*env)->GetStaticMethodID(env, provider, "open", "(Ljava/lang/String;Ljava/lang/String;[I[J)I"); |
| 45 | close_method = (*env)->GetStaticMethodID(env, provider, "close", "(J)I"); |
| 46 | rename_method = (*env)->GetStaticMethodID(env, provider, "rename", "(Ljava/lang/String;Ljava/lang/String;JJ)I"); |
| 47 | if (!document_method || !children_method || !open_method || !close_method || !rename_method) return JNI_ERR; |
| 48 | sb_set_provider(&documents); |
| 49 | int ends[2]; |
| 50 | pthread_t reader; |
| 51 | if (!pipe(ends) && dup2(ends[1], STDERR_FILENO) >= 0 && |
| 52 | !pthread_create(&reader, NULL, log_stderr, (void *)(intptr_t)ends[0])) |
| 53 | pthread_detach(reader); |
| 54 | return JNI_VERSION_1_6; |
| 55 | } |
| 56 | |
| 57 | // JNI's own string calls speak modified UTF-8, which differs from Rust's for NUL and |
| 58 | // characters beyond the BMP. |
| 59 | static jstring java_text(JNIEnv *env, const char *text) { |
| 60 | jsize size = (jsize)strlen(text); |
| 61 | jbyteArray bytes = (*env)->NewByteArray(env, size); |
| 62 | if (!bytes) return NULL; |
| 63 | (*env)->SetByteArrayRegion(env, bytes, 0, size, (const jbyte *)text); |
| 64 | jclass string = (*env)->FindClass(env, "java/lang/String"); |
| 65 | jmethodID decode = (*env)->GetMethodID(env, string, "<init>", "([BLjava/lang/String;)V"); |
| 66 | jstring encoding = (*env)->NewStringUTF(env, "UTF-8"); |
| 67 | jstring result = (*env)->NewObject(env, string, decode, bytes, encoding); |
| 68 | (*env)->DeleteLocalRef(env, bytes); |
| 69 | (*env)->DeleteLocalRef(env, encoding); |
| 70 | (*env)->DeleteLocalRef(env, string); |
| 71 | return result; |
| 72 | } |
| 73 | |
| 74 | // Throws IllegalStateException with `error`, which Rust allocated, or `fallback` without one. |
| 75 | static void fail(JNIEnv *env, char *error, const char *fallback) { |
| 76 | jstring message = java_text(env, error ? error : fallback); |
| 77 | if (error) sb_string_free(error); |
| 78 | if (!message) return; |
| 79 | jclass type = (*env)->FindClass(env, "java/lang/IllegalStateException"); |
| 80 | jmethodID make = (*env)->GetMethodID(env, type, "<init>", "(Ljava/lang/String;)V"); |
| 81 | jthrowable exception = (*env)->NewObject(env, type, make, message); |
| 82 | if (exception) (*env)->Throw(env, exception); |
| 83 | (*env)->DeleteLocalRef(env, type); |
| 84 | (*env)->DeleteLocalRef(env, message); |
| 85 | } |
| 86 | |
| 87 | // A Java string as NUL-terminated UTF-8 to free, or null, throwing, if it holds a NUL or an |
| 88 | // unpaired surrogate. |
| 89 | static char *utf8(JNIEnv *env, jstring text) { |
| 90 | jsize count = (*env)->GetStringLength(env, text); |
| 91 | const jchar *units = (*env)->GetStringChars(env, text, NULL); |
| 92 | if (!units) return NULL; |
| 93 | char *result = malloc((size_t)count * 3 + 1); |
| 94 | size_t used = 0; |
| 95 | bool valid = result != NULL; |
| 96 | for (jsize i = 0; valid && i < count; i++) { |
| 97 | uint32_t c = units[i]; |
| 98 | if (c >= 0xd800 && c <= 0xdbff && i + 1 < count && units[i + 1] >= 0xdc00 && units[i + 1] <= 0xdfff) |
| 99 | c = 0x10000 + ((c - 0xd800) << 10) + (units[++i] - 0xdc00); |
| 100 | else if (!c || (c >= 0xd800 && c <= 0xdfff)) { |
| 101 | valid = false; |
| 102 | break; |
| 103 | } |
| 104 | if (c < 0x80) { |
| 105 | result[used++] = (char)c; |
| 106 | } else if (c < 0x800) { |
| 107 | result[used++] = (char)(0xc0 | c >> 6); |
| 108 | result[used++] = (char)(0x80 | (c & 63)); |
| 109 | } else if (c < 0x10000) { |
| 110 | result[used++] = (char)(0xe0 | c >> 12); |
| 111 | result[used++] = (char)(0x80 | (c >> 6 & 63)); |
| 112 | result[used++] = (char)(0x80 | (c & 63)); |
| 113 | } else { |
| 114 | result[used++] = (char)(0xf0 | c >> 18); |
| 115 | result[used++] = (char)(0x80 | (c >> 12 & 63)); |
| 116 | result[used++] = (char)(0x80 | (c >> 6 & 63)); |
| 117 | result[used++] = (char)(0x80 | (c & 63)); |
| 118 | } |
| 119 | } |
| 120 | (*env)->ReleaseStringChars(env, text, units); |
| 121 | if (valid) { |
| 122 | result[used] = 0; |
| 123 | return result; |
| 124 | } |
| 125 | free(result); |
| 126 | fail(env, NULL, "Text holds a NUL or an unpaired surrogate"); |
| 127 | return NULL; |
| 128 | } |
| 129 | |
| 130 | // Rust's string as Java's, freeing it. |
| 131 | static jstring output(JNIEnv *env, char *text) { |
| 132 | if (!text) { |
| 133 | fail(env, NULL, "Snowbound returned nothing"); |
| 134 | return NULL; |
| 135 | } |
| 136 | jstring result = java_text(env, text); |
| 137 | sb_string_free(text); |
| 138 | return result; |
| 139 | } |
| 140 | |
| 141 | // The JVM for a thread Rust may have started; `attached` says to detach once done. |
| 142 | static JNIEnv *jvm(bool *attached) { |
| 143 | JNIEnv *env; |
| 144 | *attached = (*machine)->GetEnv(machine, (void **)&env, JNI_VERSION_1_6) == JNI_EDETACHED; |
| 145 | return *attached && (*machine)->AttachCurrentThread(machine, &env, NULL) != JNI_OK ? NULL : env; |
| 146 | } |
| 147 | |
| 148 | static void woken(uintptr_t token) { |
| 149 | (void)token; |
| 150 | bool attached; |
| 151 | JNIEnv *env = jvm(&attached); |
| 152 | if (!env) return; |
| 153 | (*env)->CallStaticVoidMethod(env, editor, woken_method); |
| 154 | if ((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); |
| 155 | if (attached) (*machine)->DetachCurrentThread(machine); |
| 156 | } |
| 157 | |
| 158 | // sb_provider, through Provider.java's methods of the same names; 4 where Java throws. |
| 159 | static int32_t provided(JNIEnv *env, bool attached, jint status) { |
| 160 | if ((*env)->ExceptionCheck(env)) { |
| 161 | (*env)->ExceptionDescribe(env); |
| 162 | status = 4; |
| 163 | } |
| 164 | if (attached) (*machine)->DetachCurrentThread(machine); |
| 165 | return status; |
| 166 | } |
| 167 | |
| 168 | static int32_t listed(jmethodID method, const char *id, sb_document_found found, void *context) { |
| 169 | bool attached; |
| 170 | JNIEnv *env = jvm(&attached); |
| 171 | if (!env) return 4; |
| 172 | jstring i = java_text(env, id); |
| 173 | jint status = i ? (*env)->CallStaticIntMethod(env, provider, method, i, (jlong)(intptr_t)found, |
| 174 | (jlong)(intptr_t)context) : 4; |
| 175 | if (i) (*env)->DeleteLocalRef(env, i); |
| 176 | return provided(env, attached, status); |
| 177 | } |
| 178 | |
| 179 | static int32_t provider_document(const char *id, sb_document_found found, void *context) { |
| 180 | return listed(document_method, id, found, context); |
| 181 | } |
| 182 | |
| 183 | static int32_t provider_children(const char *id, sb_document_found found, void *context) { |
| 184 | return listed(children_method, id, found, context); |
| 185 | } |
| 186 | |
| 187 | static int32_t provider_open(const char *id, const char *mode, int32_t *fd, uint64_t *handle) { |
| 188 | bool attached; |
| 189 | JNIEnv *env = jvm(&attached); |
| 190 | if (!env) return 4; |
| 191 | jstring i = java_text(env, id), m = i ? java_text(env, mode) : NULL; |
| 192 | jintArray fds = m ? (*env)->NewIntArray(env, 1) : NULL; |
| 193 | jlongArray handles = fds ? (*env)->NewLongArray(env, 1) : NULL; |
| 194 | jint status = handles ? (*env)->CallStaticIntMethod(env, provider, open_method, i, m, fds, handles) : 4; |
| 195 | if (!status && !(*env)->ExceptionCheck(env)) { |
| 196 | jint f; |
| 197 | jlong h; |
| 198 | (*env)->GetIntArrayRegion(env, fds, 0, 1, &f); |
| 199 | (*env)->GetLongArrayRegion(env, handles, 0, 1, &h); |
| 200 | *fd = f; |
| 201 | *handle = (uint64_t)h; |
| 202 | } |
| 203 | if (i) (*env)->DeleteLocalRef(env, i); |
| 204 | if (m) (*env)->DeleteLocalRef(env, m); |
| 205 | if (fds) (*env)->DeleteLocalRef(env, fds); |
| 206 | if (handles) (*env)->DeleteLocalRef(env, handles); |
| 207 | return provided(env, attached, status); |
| 208 | } |
| 209 | |
| 210 | static int32_t provider_close(uint64_t handle) { |
| 211 | bool attached; |
| 212 | JNIEnv *env = jvm(&attached); |
| 213 | if (!env) return 4; |
| 214 | return provided(env, attached, (*env)->CallStaticIntMethod(env, provider, close_method, (jlong)handle)); |
| 215 | } |
| 216 | |
| 217 | static int32_t provider_rename(const char *id, const char *name, void (*renamed)(void *, const char *), void *context) { |
| 218 | bool attached; |
| 219 | JNIEnv *env = jvm(&attached); |
| 220 | if (!env) return 4; |
| 221 | jstring i = java_text(env, id), n = i ? java_text(env, name) : NULL; |
| 222 | jint status = n ? (*env)->CallStaticIntMethod(env, provider, rename_method, i, n, (jlong)(intptr_t)renamed, |
| 223 | (jlong)(intptr_t)context) : 4; |
| 224 | if (i) (*env)->DeleteLocalRef(env, i); |
| 225 | if (n) (*env)->DeleteLocalRef(env, n); |
| 226 | return provided(env, attached, status); |
| 227 | } |
| 228 | |
| 229 | static const sb_provider documents = {provider_document, provider_children, provider_open, provider_close, |
| 230 | provider_rename}; |
| 231 | |
| 232 | #define PROVIDER(name) Java_net_paperclover_snowbound_Provider_##name |
| 233 | |
| 234 | JNIEXPORT void JNICALL PROVIDER(found)(JNIEnv *env, jclass cls, jlong found, jlong context, jstring id, jstring name, |
| 235 | jboolean directory, jlong size, jlong modified, jboolean writable) { |
| 236 | (void)cls; |
| 237 | char *i = utf8(env, id), *n = i ? utf8(env, name) : NULL; |
| 238 | if (n) ((sb_document_found)(intptr_t)found)((void *)(intptr_t)context, i, n, directory, size, modified, writable); |
| 239 | free(i), free(n); |
| 240 | } |
| 241 | |
| 242 | JNIEXPORT void JNICALL PROVIDER(renamed)(JNIEnv *env, jclass cls, jlong renamed, jlong context, jstring id) { |
| 243 | (void)cls; |
| 244 | char *i = utf8(env, id); |
| 245 | if (i) ((void (*)(void *, const char *))(intptr_t)renamed)((void *)(intptr_t)context, i); |
| 246 | free(i); |
| 247 | } |
| 248 | |
| 249 | JNIEXPORT jlong JNICALL JNI(provided)(JNIEnv *env, jclass cls, jstring root, jboolean folder, jboolean local, |
| 250 | jstring device, jstring cache) { |
| 251 | (void)cls; |
| 252 | char *r = utf8(env, root), *d = r ? utf8(env, device) : NULL, *c = d ? utf8(env, cache) : NULL; |
| 253 | char *error = NULL; |
| 254 | const Library *library = c ? sb_library_provider(r, folder, local, d, c, &error) : NULL; |
| 255 | if (c && !library) fail(env, error, "The notebook can't be opened"); |
| 256 | free(r), free(d), free(c); |
| 257 | return (jlong)(intptr_t)library; |
| 258 | } |
| 259 | |
| 260 | JNIEXPORT jstring JNICALL JNI(syncStatus)(JNIEnv *env, jclass cls, jlong library) { |
| 261 | (void)cls; |
| 262 | return output(env, sb_library_sync_status((const Library *)(intptr_t)library)); |
| 263 | } |
| 264 | |
| 265 | JNIEXPORT void JNICALL JNI(create)(JNIEnv *env, jclass cls, jstring notebook, jstring cache, jstring date, jstring time) { |
| 266 | (void)cls; |
| 267 | char *n = utf8(env, notebook), *c = n ? utf8(env, cache) : NULL, *d = c ? utf8(env, date) : NULL, |
| 268 | *t = d ? utf8(env, time) : NULL, *error = NULL; |
| 269 | if (t && !sb_notebook_create(n, c, "", d, t, &error)) fail(env, error, "The notebook can't be created"); |
| 270 | free(n), free(c), free(d), free(t); |
| 271 | } |
| 272 | |
| 273 | JNIEXPORT jlong JNICALL JNI(library)(JNIEnv *env, jclass cls, jstring notebook, jstring cache) { |
| 274 | (void)cls; |
| 275 | char *n = utf8(env, notebook), *c = n ? utf8(env, cache) : NULL, *error = NULL; |
| 276 | const Library *library = c ? sb_library_open(n, c, true, &error) : NULL; |
| 277 | if (c && !library) fail(env, error, "The notebook can't be opened"); |
| 278 | free(n), free(c); |
| 279 | return (jlong)(intptr_t)library; |
| 280 | } |
| 281 | |
| 282 | JNIEXPORT jstring JNICALL JNI(sections)(JNIEnv *env, jclass cls, jlong library) { |
| 283 | (void)cls; |
| 284 | return output(env, sb_library_sections((const Library *)(intptr_t)library)); |
| 285 | } |
| 286 | |
| 287 | JNIEXPORT jlong JNICALL JNI(section)(JNIEnv *env, jclass cls, jlong library, jstring path) { |
| 288 | (void)cls; |
| 289 | char *p = utf8(env, path), *error = NULL; |
| 290 | Section *section = p ? sb_section_open((const Library *)(intptr_t)library, p, "", woken, 0, &error) : NULL; |
| 291 | if (p && !section) fail(env, error, "The section can't be opened"); |
| 292 | free(p); |
| 293 | return (jlong)(intptr_t)section; |
| 294 | } |
| 295 | |
| 296 | JNIEXPORT jstring JNICALL JNI(pages)(JNIEnv *env, jclass cls, jlong section) { |
| 297 | (void)cls; |
| 298 | return output(env, sb_section_pages((const Section *)(intptr_t)section)); |
| 299 | } |
| 300 | |
| 301 | JNIEXPORT jlong JNICALL JNI(view)(JNIEnv *env, jclass cls, jobject surface, jlong section, jstring page, |
| 302 | jfloat width, jfloat height, jfloat scale) { |
| 303 | (void)cls; |
| 304 | char *id = utf8(env, page); |
| 305 | ANativeWindow *window = id ? ANativeWindow_fromSurface(env, surface) : NULL; |
| 306 | View *view = window ? sb_view_new_android(window, (const Section *)(intptr_t)section, id, width, height, scale) : NULL; |
| 307 | if (window) ANativeWindow_release(window); |
| 308 | if (id && !view) fail(env, NULL, "The page can't be drawn"); |
| 309 | free(id); |
| 310 | return (jlong)(intptr_t)view; |
| 311 | } |
| 312 | |
| 313 | JNIEXPORT void JNICALL JNI(attach)(JNIEnv *env, jclass cls, jlong view, jobject surface) { |
| 314 | (void)cls; |
| 315 | ANativeWindow *window = ANativeWindow_fromSurface(env, surface); |
| 316 | bool attached = window && sb_view_attach_android((View *)(intptr_t)view, window); |
| 317 | if (window) ANativeWindow_release(window); |
| 318 | if (!attached) fail(env, NULL, "The page can't be drawn"); |
| 319 | } |
| 320 | |
| 321 | JNIEXPORT void JNICALL JNI(detach)(JNIEnv *env, jclass cls, jlong view) { |
| 322 | (void)env, (void)cls; |
| 323 | sb_view_detach_android((View *)(intptr_t)view); |
| 324 | } |
| 325 | |
| 326 | JNIEXPORT void JNICALL JNI(resize)(JNIEnv *env, jclass cls, jlong view, jfloat width, jfloat height, jfloat scale) { |
| 327 | (void)env, (void)cls; |
| 328 | sb_view_resize((View *)(intptr_t)view, width, height, scale); |
| 329 | } |
| 330 | |
| 331 | JNIEXPORT void JNICALL JNI(render)(JNIEnv *env, jclass cls, jlong view) { |
| 332 | (void)env, (void)cls; |
| 333 | sb_view_render((View *)(intptr_t)view); |
| 334 | } |
| 335 | |
| 336 | JNIEXPORT jboolean JNICALL JNI(framePending)(JNIEnv *env, jclass cls, jlong view) { |
| 337 | (void)env, (void)cls; |
| 338 | return sb_view_frame_pending((const View *)(intptr_t)view); |
| 339 | } |
| 340 | |
| 341 | JNIEXPORT void JNICALL JNI(focus)(JNIEnv *env, jclass cls, jlong view, jboolean focused) { |
| 342 | (void)env, (void)cls; |
| 343 | sb_view_focus((View *)(intptr_t)view, focused); |
| 344 | } |
| 345 | |
| 346 | JNIEXPORT void JNICALL JNI(press)(JNIEnv *env, jclass cls, jlong view, jfloat x, jfloat y) { |
| 347 | (void)env, (void)cls; |
| 348 | sb_view_press((View *)(intptr_t)view, x, y); |
| 349 | } |
| 350 | |
| 351 | JNIEXPORT void JNICALL JNI(drag)(JNIEnv *env, jclass cls, jlong view, jfloat x, jfloat y) { |
| 352 | (void)env, (void)cls; |
| 353 | sb_view_drag((View *)(intptr_t)view, x, y); |
| 354 | } |
| 355 | |
| 356 | JNIEXPORT void JNICALL JNI(release)(JNIEnv *env, jclass cls, jlong view) { |
| 357 | (void)env, (void)cls; |
| 358 | sb_view_release((View *)(intptr_t)view); |
| 359 | } |
| 360 | |
| 361 | JNIEXPORT jint JNICALL JNI(length)(JNIEnv *env, jclass cls, jlong view) { |
| 362 | (void)env, (void)cls; |
| 363 | return (jint)sb_text_length((const View *)(intptr_t)view); |
| 364 | } |
| 365 | |
| 366 | JNIEXPORT jstring JNICALL JNI(text)(JNIEnv *env, jclass cls, jlong view, jint start, jint end) { |
| 367 | (void)cls; |
| 368 | return output(env, sb_text((const View *)(intptr_t)view, (uint32_t)start, (uint32_t)end)); |
| 369 | } |
| 370 | |
| 371 | static jintArray range(JNIEnv *env, const uint32_t range[2]) { |
| 372 | jint values[2] = {(jint)range[0], (jint)range[1]}; |
| 373 | jintArray result = (*env)->NewIntArray(env, 2); |
| 374 | if (result) (*env)->SetIntArrayRegion(env, result, 0, 2, values); |
| 375 | return result; |
| 376 | } |
| 377 | |
| 378 | JNIEXPORT jintArray JNICALL JNI(selection)(JNIEnv *env, jclass cls, jlong view) { |
| 379 | (void)cls; |
| 380 | uint32_t selected[2] = {0, 0}; |
| 381 | sb_selection((const View *)(intptr_t)view, selected); |
| 382 | return range(env, selected); |
| 383 | } |
| 384 | |
| 385 | JNIEXPORT jintArray JNICALL JNI(marked)(JNIEnv *env, jclass cls, jlong view) { |
| 386 | (void)cls; |
| 387 | uint32_t marked[2]; |
| 388 | return sb_marked((const View *)(intptr_t)view, marked) ? range(env, marked) : NULL; |
| 389 | } |
| 390 | |
| 391 | JNIEXPORT jboolean JNICALL JNI(select)(JNIEnv *env, jclass cls, jlong view, jint start, jint end) { |
| 392 | (void)env, (void)cls; |
| 393 | return sb_select((View *)(intptr_t)view, (uint32_t)start, (uint32_t)end); |
| 394 | } |
| 395 | |
| 396 | JNIEXPORT jboolean JNICALL JNI(insert)(JNIEnv *env, jclass cls, jlong view, jstring text) { |
| 397 | (void)cls; |
| 398 | char *t = utf8(env, text); |
| 399 | bool changed = t && sb_insert((View *)(intptr_t)view, t); |
| 400 | free(t); |
| 401 | return changed; |
| 402 | } |
| 403 | |
| 404 | JNIEXPORT jboolean JNICALL JNI(setMarked)(JNIEnv *env, jclass cls, jlong view, jstring text, jint start, jint end) { |
| 405 | (void)cls; |
| 406 | char *t = utf8(env, text); |
| 407 | bool changed = t && sb_set_marked((View *)(intptr_t)view, t, (uint32_t)start, (uint32_t)end); |
| 408 | free(t); |
| 409 | return changed; |
| 410 | } |
| 411 | |
| 412 | JNIEXPORT void JNICALL JNI(unmark)(JNIEnv *env, jclass cls, jlong view) { |
| 413 | (void)env, (void)cls; |
| 414 | sb_unmark((View *)(intptr_t)view); |
| 415 | } |
| 416 | |
| 417 | JNIEXPORT jboolean JNICALL JNI(deleteBackward)(JNIEnv *env, jclass cls, jlong view) { |
| 418 | (void)env, (void)cls; |
| 419 | return sb_delete_backward((View *)(intptr_t)view); |
| 420 | } |
| 421 | |
| 422 | // The caret at `start` when `end` is the same, else the rectangles covering the range: each |
| 423 | // `x, y, width, height` in the view's points. |
| 424 | JNIEXPORT jfloatArray JNICALL JNI(rects)(JNIEnv *env, jclass cls, jlong view, jint start, jint end) { |
| 425 | (void)cls; |
| 426 | const View *v = (const View *)(intptr_t)view; |
| 427 | float caret[1][4]; |
| 428 | size_t count = start == end ? sb_caret_rect(v, (uint32_t)start, caret[0]) |
| 429 | : sb_range_rects(v, (uint32_t)start, (uint32_t)end, NULL, 0); |
| 430 | float (*rects)[4] = start == end ? caret : malloc(sizeof(*rects) * (count ? count : 1)); |
| 431 | if (!rects) return NULL; |
| 432 | if (start != end) { |
| 433 | size_t written = sb_range_rects(v, (uint32_t)start, (uint32_t)end, rects, count); |
| 434 | if (written < count) count = written; |
| 435 | } |
| 436 | jfloatArray result = (*env)->NewFloatArray(env, (jsize)(count * 4)); |
| 437 | if (result) (*env)->SetFloatArrayRegion(env, result, 0, (jsize)(count * 4), (const jfloat *)rects); |
| 438 | if (rects != caret) free(rects); |
| 439 | return result; |
| 440 | } |
| 441 | |
| 442 | JNIEXPORT jint JNICALL JNI(closest)(JNIEnv *env, jclass cls, jlong view, jfloat x, jfloat y) { |
| 443 | (void)env, (void)cls; |
| 444 | return (jint)sb_closest((const View *)(intptr_t)view, x, y); |
| 445 | } |
| 446 | |
| 447 | // The section's events, then its saving status. |
| 448 | JNIEXPORT jintArray JNICALL JNI(poll)(JNIEnv *env, jclass cls, jlong section) { |
| 449 | (void)cls; |
| 450 | uint8_t status = 0; |
| 451 | uint32_t polled[2] = {sb_section_poll((const Section *)(intptr_t)section, &status), 0}; |
| 452 | polled[1] = status; |
| 453 | return range(env, polled); |
| 454 | } |
| 455 | |
| 456 | JNIEXPORT jboolean JNICALL JNI(flush)(JNIEnv *env, jclass cls, jlong section, jdouble seconds) { |
| 457 | (void)env, (void)cls; |
| 458 | return sb_section_flush((const Section *)(intptr_t)section, seconds); |
| 459 | } |
| 460 | |
| 461 | JNIEXPORT jboolean JNICALL JNI(keep)(JNIEnv *env, jclass cls, jlong view) { |
| 462 | (void)env, (void)cls; |
| 463 | return sb_view_keep((const View *)(intptr_t)view); |
| 464 | } |
| 465 | |
| 466 | JNIEXPORT jboolean JNICALL JNI(reloadReady)(JNIEnv *env, jclass cls, jlong view) { |
| 467 | (void)env, (void)cls; |
| 468 | return sb_view_reload_ready((const View *)(intptr_t)view); |
| 469 | } |
| 470 | |
| 471 | JNIEXPORT void JNICALL JNI(reload)(JNIEnv *env, jclass cls, jlong view, jboolean discard) { |
| 472 | (void)env, (void)cls; |
| 473 | sb_view_reload((View *)(intptr_t)view, discard, NULL); |
| 474 | } |
| 475 | |
| 476 | JNIEXPORT void JNICALL JNI(freeView)(JNIEnv *env, jclass cls, jlong view) { |
| 477 | (void)env, (void)cls; |
| 478 | sb_view_free((View *)(intptr_t)view); |
| 479 | } |
| 480 | |
| 481 | // Publishes the notebook's edits, the open section's included, then frees the section and lets go |
| 482 | // of the notebook, keeping its offline copies: false, the edits kept to publish when it next |
| 483 | // opens, when they don't publish within `seconds`. Finishing the activity is not Close Notebook |
| 484 | // (sb_library_close). |
| 485 | JNIEXPORT jboolean JNICALL JNI(close)(JNIEnv *env, jclass cls, jlong library, jlong section, jdouble seconds) { |
| 486 | (void)env, (void)cls; |
| 487 | char *error = NULL; |
| 488 | bool closed = sb_library_flush((const Library *)(intptr_t)library, seconds, &error); |
| 489 | if (section) sb_section_free((Section *)(intptr_t)section); |
| 490 | if (error) { |
| 491 | fprintf(stderr, "snowbound: %s\n", error); |
| 492 | sb_string_free(error); |
| 493 | } |
| 494 | sb_library_free((const Library *)(intptr_t)library); |
| 495 | return closed; |
| 496 | } |