ReviewOS

also looking at this

craft-native/craft

feat(android): the clipboard pair — ratchet 100 -> 98

#144
Merged glennmichael123 wants to merge feat/android-clipboard into main
6 files +414 -3
packages/zig/src/android_dispatch.zigmodified+58-1
Changes to packages/zig/src/android_dispatch.zig
@@ -38,6 +38,7 @@ const builtin = ;
3838const jni = ;
3939const device = ;
4040const system = ;
41const clipboard = ;
4142
4243const Jni = jni.Jni;
4344
@@ -137,6 +138,16 @@ const natives = [_]jni.JNINativeMethod{
137138 .signature = "(Ljava/lang/String;)Z",
138139 .fnPtr = @ptrCast(&nativeLog),
139140 },
141 .{
142 .name = "nativeClipboardRead",
143 .signature = "(Landroid/app/Activity;)Ljava/lang/String;",
144 .fnPtr = @ptrCast(&nativeClipboardRead),
145 },
146 .{
147 .name = "nativeClipboardWrite",
148 .signature = "(Landroid/app/Activity;Ljava/lang/String;)Z",
149 .fnPtr = @ptrCast(&nativeClipboardWrite),
150 },
140151};
141152
142153/// How binding went. A value rather than a log line, so every path is
@@ -242,6 +253,52 @@ fn nativeLog(env: jni.JNIEnv, _: jni.jobject, message: jni.jstring) callconv(.c)
242253 return jni.JNI_TRUE;
243254}
244255
256/// `nativeClipboardRead(activity)`.
257///
258/// Null here means "ask the shim", and an empty *string* means "the clipboard
259/// is empty" — two different answers that a nullable String is exactly able to
260/// carry, and that a plain String would collapse into one.
261fn nativeClipboardRead(env: jni.JNIEnv, _: jni.jobject, activity: jni.jobject) callconv(.c) jni.jstring {
262 const j = Jni.init(env);
263
264 var arena = std.heap.ArenaAllocator.init(backing);
265 defer arena.deinit();
266 const allocator = arena.allocator();
267
268 const text = clipboard.read(allocator, j, activity) catch |err| {
269 std.log.warn("craft: clipboardRead fell through to the shim ({s})", .{@errorName(err)});
270 return null;
271 };
272
273 const owned = allocator.allocSentinel(u8, text.len, 0) catch return null;
274 @memcpy(owned, text);
275 return j.newStringUtf(owned.ptr) catch null;
276}
277
278/// `nativeClipboardWrite(activity, text)`.
279fn nativeClipboardWrite(
280 env: jni.JNIEnv,
281 _: jni.jobject,
282 activity: jni.jobject,
283 text: jni.jstring,
284) callconv(.c) jni.jboolean {
285 const j = Jni.init(env);
286
287 var arena = std.heap.ArenaAllocator.init(backing);
288 defer arena.deinit();
289 const allocator = arena.allocator();
290
291 const value = j.stringToUtf8(allocator, text) catch return jni.JNI_FALSE;
292 const owned = allocator.allocSentinel(u8, value.len, 0) catch return jni.JNI_FALSE;
293 @memcpy(owned, value);
294
295 clipboard.write(j, activity, owned.ptr) catch |err| {
296 std.log.warn("craft: clipboardWrite fell through to the shim ({s})", .{@errorName(err)});
297 return jni.JNI_FALSE;
298 };
299 return jni.JNI_TRUE;
300}
301
245302// =============================================================================
246303// Tests
247304// =============================================================================
@@ -353,7 +410,7 @@ test "the registered natives name methods the Kotlin actually declares" {
353410 // A descriptor is checked by the JVM at registration, so a wrong one fails
354411 // at load rather than at call — but only if the *name* matches something.
355412 // These two strings are the contract with CraftBridge.kt.
356 try testing.expectEqual(@as(usize, 3), natives.len);
413 try testing.expectEqual(@as(usize, 5), natives.len);
357414 try testing.expectEqualStrings("nativeGetDeviceInfo", std.mem.span(natives[0].name));
358415
359416 // And the class they bind to is the fixed one, not the templated bridge.