diff options
author | 2022-04-22 18:09:32 -0700 | |
---|---|---|
committer | 2022-04-22 18:09:32 -0700 | |
commit | e9787a81b41c0b98ecf4658c584348cb839f7ca1 (patch) | |
tree | e743a750fc2fbb702ebd7943b7ce00d349ff1a15 | |
parent | ab1d83fe8d888bbb63ada992b2628d1635630e03 (diff) | |
download | bun-jarred/clipboard-objc.tar.gz bun-jarred/clipboard-objc.tar.zst bun-jarred/clipboard-objc.zip |
There are dylib issues and I don't want to invest more time in this right nowjarred/clipboard-objc
-rw-r--r-- | integration/bunjs-only-snippets/clipboard.test.js | 7 | ||||
-rw-r--r-- | src/deps/objc.zig | 499 | ||||
-rw-r--r-- | src/javascript/jsc/api/bun.zig | 164 |
3 files changed, 670 insertions, 0 deletions
diff --git a/integration/bunjs-only-snippets/clipboard.test.js b/integration/bunjs-only-snippets/clipboard.test.js new file mode 100644 index 000000000..c23f74210 --- /dev/null +++ b/integration/bunjs-only-snippets/clipboard.test.js @@ -0,0 +1,7 @@ +import { it } from "bun:test"; + +it("read", async () => { + // This doesn't run on Linux but it shouldn't throw at least + const text = await Bun.Clipboard.readText(); + expect(text).toBe("hello"); +}); diff --git a/src/deps/objc.zig b/src/deps/objc.zig new file mode 100644 index 000000000..6b7c38a28 --- /dev/null +++ b/src/deps/objc.zig @@ -0,0 +1,499 @@ +// Objective-C runtime headers without the extern +// intended for dlopen + +const std = @import("std"); + +pub const ObjC = struct { + // copypasta because the autocomplete for it is broken + pub const DlDynlib = struct { + const os = std.os; + const system = std.os.system; + + pub const Error = error{FileNotFound}; + + handle: *anyopaque, + + pub fn open(path: []const u8) !DlDynlib { + const path_c = try os.toPosixPath(path); + return openZ(&path_c); + } + + pub fn openZ(path_c: [*:0]const u8) !DlDynlib { + return DlDynlib{ + .handle = system.dlopen(path_c, system.RTLD.LAZY) orelse { + return error.FileNotFound; + }, + }; + } + + pub fn close(self: *DlDynlib) void { + _ = system.dlclose(self.handle); + self.* = undefined; + } + + pub fn lookup(self: *DlDynlib, comptime T: type, name: [:0]const u8) ?T { + // dlsym (and other dl-functions) secretly take shadow parameter - return address on stack + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66826 + if (@call(.{ .modifier = .never_tail }, system.dlsym, .{ self.handle, name.ptr })) |symbol| { + @setRuntimeSafety(false); + return @ptrCast(T, @alignCast(@alignOf(T), symbol)); + } else { + return null; + } + } + }; + + pub const C = struct { + objc_lookUpClass: Types.objc_lookUpClass, + sel_getUid: Types.sel_getUid, + objc_msgSend: fn (...) callconv(.C) Types.id, + objc_dylib: DlDynlib, + appkit_dylib: DlDynlib, + CFStringCreateWithBytesNoCopy: Types.CFStringCreateWithBytesNoCopy, + NSPasteboardTypeString: Types.CFStringRef, + NSPasteboardTypePNG: Types.CFStringRef, + // NSPasteboardTypeURL: *Types.id, + CFArrayCreate: Types.CFArrayCreate, + kCFAllocatorNull: Types.kCFAllocatorNull, + CFArrayContainsValue: Types.CFArrayContainsValue, + CFRetain: Types.CFRetain, + CFRelease: Types.CFRelease, + CFDataGetLength: Types.CFDataGetLength, + CFDataGetBytePtr: Types.CFDataGetBytePtr, + CFDataCreateWithBytesNoCopy: Types.CFDataCreateWithBytesNoCopy, + CFArrayGetCount: Types.CFArrayGetCount, + CFArrayGetValueAtIndex: Types.CFArrayGetValueAtIndex, + + const id = Types.id; + + pub fn class(c: *C, s: [*c]const u8) Types.id { + return @ptrCast(C.id, @alignCast(@alignOf(C.id), c.objc_lookUpClass(s))); + } + + pub fn call(c: *C, obj: Types.id, sel_name: [*c]const u8) Types.id { + var f = @ptrCast( + fn (C.id, Types.SEL) callconv(.C) C.id, + c.objc_msgSend, + ); + return f(obj, c.sel_getUid(sel_name)); + } + + pub fn call_(c: *C, obj: Types.id, sel_name: [*c]const u8, arg: anytype) C.id { + // objc_msgSend has the prototype "void objc_msgSend(void)", + // so we have to cast it based on the types of our arguments + // (https://www.mikeash.com/pyblog/objc_msgsends-new-prototype.html) + var f = @ptrCast( + fn (Types.id, Types.SEL, @TypeOf(arg)) callconv(.C) Types.id, + c.objc_msgSend, + ); + return f(obj, c.sel_getUid(sel_name), arg); + } + + pub fn call2(c: *C, obj: Types.id, sel_name: [*c]const u8, arg: anytype, arg2: anytype) C.id { + // objc_msgSend has the prototype "void objc_msgSend(void)", + // so we have to cast it based on the types of our arguments + // (https://www.mikeash.com/pyblog/objc_msgsends-new-prototype.html) + var f = @ptrCast( + fn (Types.id, Types.SEL, @TypeOf(arg), @TypeOf(arg2)) callconv(.C) C.id, + c.objc_msgSend, + ); + return f(obj, c.sel_getUid(sel_name), arg, arg2); + } + }; + + pub const Clipboard = struct { + pub const Data = enum { + png, + string, + // url, + }; + pub fn get(allocator: std.mem.Allocator, tag: Data) ![]u8 { + if (!objc_loaded) { + try load(); + } + + const pasteboard_class = objc.class("NSPasteboard"); + const pb = objc.call(pasteboard_class, "generalPasteboard"); + var kind = switch (tag) { + .png => objc.NSPasteboardTypePNG, + .string => objc.NSPasteboardTypeString, + // .url => objc.NSPasteboardTypeURL, + }; + var array = [_]?*const anyopaque{kind}; + var supported_types = objc.CFArrayCreate(null, &array, 1, null); + defer objc.CFRelease(supported_types); + const item = objc.call_(pb, "availableTypeFromArray:", supported_types.toNSArray()); + const data = objc.call_( + item, + "dataForType:", + @ptrCast(Types.id, @alignCast(@alignOf(Types.id), kind)), + ); + const size: usize = @ptrToInt(objc.call(data, "length")); + if (size == 0) + return &[_]u8{}; + + var bytes = try allocator.alloc(u8, size); + _ = objc.call2(data, "getBytes:length:", bytes.ptr, size); + return bytes; + } + + pub fn set(tag: Data, blob: []const u8) !void { + if (!objc_loaded) { + try load(); + } + + const pasteboard_class = objc.class("NSPasteboard"); + const pb = objc.call(pasteboard_class, "generalPasteboard"); + + const NSData = objc.class("NSData"); + var data = objc.call2(NSData, "dataWithBytes:length", blob.ptr, blob.len); + var kind = switch (tag) { + .png => objc.NSPasteboardTypePNG, + .string => objc.NSPasteboardTypeString, + // .url => objc.NSPasteboardTypeURL, + }; + _ = objc.call2(pb, "declareTypes:owner", kind, @as(Types.id, null)); + + _ = objc.call2( + pb, + "setData:forType", + data, + @ptrCast(Types.id, @alignCast(@alignOf(Types.id), kind)), + ); + } + }; + + pub var objc: C = undefined; + pub var objc_loaded: bool = false; + + pub fn load() !void { + var dylib = try DlDynlib.openZ("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"); + var appkit = try DlDynlib.openZ("/System/Library/Frameworks/AppKit.framework/AppKit"); + var CFStringCreateWithBytesNoCopy = dylib.lookup(Types.CFStringCreateWithBytesNoCopy, "CFStringCreateWithBytesNoCopy").?; + const kCFAllocatorNull = dylib.lookup(Types.kCFAllocatorNull, "kCFAllocatorNull").?; + var NSPasteboardTypeString = CFStringCreateWithBytesNoCopy( + null, + "public.utf8-plain-text", + "public.utf8-plain-text".len, + Types.CFStringEncoding.ASCII, + 1, + null, + ); + var NSPasteboardTypePNG = CFStringCreateWithBytesNoCopy( + null, + "public.png", + "public.png".len, + Types.CFStringEncoding.ASCII, + 1, + null, + ); + + objc = C{ + .kCFAllocatorNull = kCFAllocatorNull, + + .objc_lookUpClass = dylib.lookup(Types.objc_lookUpClass, "objc_lookUpClass").?, + .sel_getUid = dylib.lookup(Types.sel_getUid, "sel_getUid").?, + .objc_msgSend = dylib.lookup(fn (...) callconv(.C) C.id, "objc_msgSend").?, + .appkit_dylib = appkit, + .objc_dylib = dylib, + .CFStringCreateWithBytesNoCopy = CFStringCreateWithBytesNoCopy, + .NSPasteboardTypeString = NSPasteboardTypeString, + .NSPasteboardTypePNG = NSPasteboardTypePNG, + .CFArrayCreate = dylib.lookup(@TypeOf(objc.CFArrayCreate), "CFArrayCreate").?, + .CFArrayContainsValue = dylib.lookup(@TypeOf(objc.CFArrayContainsValue), "CFArrayContainsValue").?, + .CFRetain = dylib.lookup(@TypeOf(objc.CFRetain), "CFRetain").?, + .CFRelease = dylib.lookup(@TypeOf(objc.CFRelease), "CFRelease").?, + .CFDataGetLength = dylib.lookup(@TypeOf(objc.CFDataGetLength), "CFDataGetLength").?, + .CFDataGetBytePtr = dylib.lookup(@TypeOf(objc.CFDataGetBytePtr), "CFDataGetBytePtr").?, + .CFDataCreateWithBytesNoCopy = dylib.lookup(@TypeOf(objc.CFDataCreateWithBytesNoCopy), "CFDataCreateWithBytesNoCopy").?, + .CFArrayGetCount = dylib.lookup(@TypeOf(objc.CFArrayGetCount), "CFArrayGetCount").?, + .CFArrayGetValueAtIndex = dylib.lookup(@TypeOf(objc.CFArrayGetValueAtIndex), "CFArrayGetValueAtIndex").?, + }; + objc_loaded = true; + } + + pub const Types = struct { + pub const CFRange = extern struct { + location: CFIndex, + length: CFIndex, + }; + const UInt8 = u8; + pub const CFData = opaque { + pub fn init(objc_id: id) *CFData { + return @ptrCast(*CFData, objc_id.isa.?); + } + }; + const UInt32 = u32; + pub const CFStringEncoding = enum(UInt32) { + MacRoman = 0, + WindowsLatin1 = 1280, + ISOLatin1 = 513, + NextStepLatin = 2817, + ASCII = 1536, + UTF8 = 134217984, + NonLossyASCII = 3071, + UTF16 = 256, + UTF16BE = 268435712, + UTF16LE = 335544576, + UTF32 = 201326848, + UTF32BE = 402653440, + UTF32LE = 469762304, + _, + }; + pub const CFStringBuiltInEncodings = CFStringEncoding; + pub const CFStringCreateWithBytesNoCopy = fn (alloc: CFAllocatorRef, bytes: [*]const UInt8, numBytes: CFIndex, encoding: CFStringEncoding, isExternalRepresentation: Boolean, contentsDeallocator: CFAllocatorRef) callconv(.C) CFStringRef; + + pub const CFDataRef = ?*const CFData; + pub const CFArrayCreate = fn (allocator: CFAllocatorRef, values: [*]?*const anyopaque, numValues: CFIndex, callBacks: ?*const CFArrayCallBacks) callconv(.C) CFArrayRef; + pub const CFArrayContainsValue = fn (theArray: CFArrayRef, range: CFRange, value: ?*const anyopaque) callconv(.C) Boolean; + pub const CFRetain = fn (cf: CFTypeRef) callconv(.C) CFTypeRef; + pub const CFRelease = fn (cf: CFTypeRef) callconv(.C) void; + pub const CFDataGetLength = fn (theData: CFDataRef) callconv(.C) CFIndex; + pub const CFDataGetBytePtr = fn (theData: CFDataRef) callconv(.C) [*c]const UInt8; + pub const CFDataCreateWithBytesNoCopy = fn (allocator: CFAllocatorRef, bytes: [*c]const UInt8, length: CFIndex, bytesDeallocator: CFAllocatorRef) callconv(.C) CFDataRef; + pub const Boolean = u8; + pub const CFTypeRef = ?*anyopaque; + pub const struct___CFAllocator = opaque {}; + pub const CFAllocatorRef = ?*const struct___CFAllocator; + pub const CFString = opaque {}; + pub const CFStringRef = *CFString; + + pub const kCFAllocatorNull = CFAllocatorRef; + pub const CFArrayRetainCallBack = ?fn (CFAllocatorRef, ?*const anyopaque) callconv(.C) ?*const anyopaque; + pub const CFArrayReleaseCallBack = ?fn (CFAllocatorRef, ?*const anyopaque) callconv(.C) void; + pub const CFArrayCopyDescriptionCallBack = ?fn (?*const anyopaque) callconv(.C) CFStringRef; + pub const CFArrayEqualCallBack = ?fn (?*const anyopaque, ?*const anyopaque) callconv(.C) Boolean; + pub const CFArrayCallBacks = extern struct { + version: CFIndex, + retain: CFArrayRetainCallBack, + release: CFArrayReleaseCallBack, + copyDescription: CFArrayCopyDescriptionCallBack, + equal: CFArrayEqualCallBack, + }; + pub extern const kCFTypeArrayCallBacks: CFArrayCallBacks; + pub const CFArrayApplierFunction = ?fn (?*const anyopaque, ?*anyopaque) callconv(.C) void; + pub const CFArray = opaque { + pub fn toNSArray(this: *CFArray) id { + return @ptrCast(id, @alignCast(@alignOf(id), this)); + } + }; + pub const CFArrayRef = *CFArray; + pub const CFMutableArrayRef = ?*CFArray; + pub const CFArrayGetCount = fn (theArray: CFArrayRef) callconv(.C) CFIndex; + pub const CFArrayGetValueAtIndex = fn (theArray: CFArrayRef, idx: CFIndex) callconv(.C) ?*const anyopaque; + pub const CFIndex = c_long; + + pub const struct_objc_class = opaque {}; + pub const Class = ?*struct_objc_class; + pub const struct_objc_object = extern struct { + isa: Class, + }; + pub const id = ?*struct_objc_object; + pub const struct_objc_selector = opaque {}; + pub const SEL = ?*struct_objc_selector; + pub const IMP = ?fn () callconv(.C) void; + pub const BOOL = bool; + pub const objc_objectptr_t = ?*const anyopaque; + + pub const arith_t = c_long; + pub const uarith_t = c_ulong; + pub const STR = [*c]u8; + pub const ptrdiff_t = c_long; + pub const wchar_t = c_int; + pub const max_align_t = c_longdouble; + pub const struct_objc_method = opaque {}; + pub const Method = ?*struct_objc_method; + pub const struct_objc_ivar = opaque {}; + pub const Ivar = ?*struct_objc_ivar; + pub const struct_objc_category = opaque {}; + pub const Category = ?*struct_objc_category; + pub const struct_objc_property = opaque {}; + pub const objc_property_t = ?*struct_objc_property; + pub const Protocol = struct_objc_object; + pub const struct_objc_method_description = extern struct { + name: SEL, + types: [*c]u8, + }; + pub const objc_property_attribute_t = extern struct { + name: [*c]const u8, + value: [*c]const u8, + }; + + pub const sel_getName = fn (sel: SEL) callconv(.C) [*c]const u8; + pub const sel_registerName = fn (str: [*c]const u8) callconv(.C) SEL; + pub const object_getClassName = fn (obj: id) callconv(.C) [*c]const u8; + pub const object_getIndexedIvars = fn (obj: id) callconv(.C) ?*anyopaque; + pub const sel_isMapped = fn (sel: SEL) callconv(.C) BOOL; + pub const sel_getUid = fn (str: [*c]const u8) callconv(.C) SEL; + pub const objc_retainedObject = fn (obj: objc_objectptr_t) callconv(.C) id; + pub const objc_unretainedObject = fn (obj: objc_objectptr_t) callconv(.C) id; + pub const objc_unretainedPointer = fn (obj: id) callconv(.C) objc_objectptr_t; + pub const object_copy = fn (obj: id, size: usize) callconv(.C) id; + pub const object_dispose = fn (obj: id) callconv(.C) id; + pub const object_getClass = fn (obj: id) callconv(.C) Class; + pub const object_setClass = fn (obj: id, cls: Class) callconv(.C) Class; + pub const object_isClass = fn (obj: id) callconv(.C) BOOL; + pub const object_getIvar = fn (obj: id, ivar: Ivar) callconv(.C) id; + pub const object_setIvar = fn (obj: id, ivar: Ivar, value: id) callconv(.C) void; + pub const object_setIvarWithStrongDefault = fn (obj: id, ivar: Ivar, value: id) callconv(.C) void; + pub const object_setInstanceVariable = fn (obj: id, name: [*c]const u8, value: ?*anyopaque) callconv(.C) Ivar; + pub const object_setInstanceVariableWithStrongDefault = fn (obj: id, name: [*c]const u8, value: ?*anyopaque) callconv(.C) Ivar; + pub const object_getInstanceVariable = fn (obj: id, name: [*c]const u8, outValue: [*c]?*anyopaque) callconv(.C) Ivar; + pub const objc_getClass = fn (name: [*c]const u8) callconv(.C) Class; + pub const objc_getMetaClass = fn (name: [*c]const u8) callconv(.C) Class; + pub const objc_lookUpClass = fn (name: [*c]const u8) callconv(.C) Class; + pub const objc_getRequiredClass = fn (name: [*c]const u8) callconv(.C) Class; + pub const objc_getClassList = fn (buffer: [*c]Class, bufferCount: c_int) callconv(.C) c_int; + pub const objc_copyClassList = fn (outCount: [*c]c_uint) callconv(.C) [*c]Class; + pub const class_getName = fn (cls: Class) callconv(.C) [*c]const u8; + pub const class_isMetaClass = fn (cls: Class) callconv(.C) BOOL; + pub const class_getSuperclass = fn (cls: Class) callconv(.C) Class; + pub const class_setSuperclass = fn (cls: Class, newSuper: Class) callconv(.C) Class; + pub const class_getVersion = fn (cls: Class) callconv(.C) c_int; + pub const class_setVersion = fn (cls: Class, version: c_int) callconv(.C) void; + pub const class_getInstanceSize = fn (cls: Class) callconv(.C) usize; + pub const class_getInstanceVariable = fn (cls: Class, name: [*c]const u8) callconv(.C) Ivar; + pub const class_getClassVariable = fn (cls: Class, name: [*c]const u8) callconv(.C) Ivar; + pub const class_copyIvarList = fn (cls: Class, outCount: [*c]c_uint) callconv(.C) [*c]Ivar; + pub const class_getInstanceMethod = fn (cls: Class, name: SEL) callconv(.C) Method; + pub const class_getClassMethod = fn (cls: Class, name: SEL) callconv(.C) Method; + pub const class_getMethodImplementation = fn (cls: Class, name: SEL) callconv(.C) IMP; + pub const class_getMethodImplementation_stret = fn (cls: Class, name: SEL) callconv(.C) IMP; + pub const class_respondsToSelector = fn (cls: Class, sel: SEL) callconv(.C) BOOL; + pub const class_copyMethodList = fn (cls: Class, outCount: [*c]c_uint) callconv(.C) [*c]Method; + pub const class_conformsToProtocol = fn (cls: Class, protocol: [*c]Protocol) callconv(.C) BOOL; + pub const class_copyProtocolList = fn (cls: Class, outCount: [*c]c_uint) callconv(.C) [*c][*c]Protocol; + pub const class_getProperty = fn (cls: Class, name: [*c]const u8) callconv(.C) objc_property_t; + pub const class_copyPropertyList = fn (cls: Class, outCount: [*c]c_uint) callconv(.C) [*c]objc_property_t; + pub const class_getIvarLayout = fn (cls: Class) callconv(.C) [*c]const u8; + pub const class_getWeakIvarLayout = fn (cls: Class) callconv(.C) [*c]const u8; + pub const class_addMethod = fn (cls: Class, name: SEL, imp: IMP, types: [*c]const u8) callconv(.C) BOOL; + pub const class_replaceMethod = fn (cls: Class, name: SEL, imp: IMP, types: [*c]const u8) callconv(.C) IMP; + pub const class_addIvar = fn (cls: Class, name: [*c]const u8, size: usize, alignment: u8, types: [*c]const u8) callconv(.C) BOOL; + pub const class_addProtocol = fn (cls: Class, protocol: [*c]Protocol) callconv(.C) BOOL; + pub const class_addProperty = fn (cls: Class, name: [*c]const u8, attributes: [*c]const objc_property_attribute_t, attributeCount: c_uint) callconv(.C) BOOL; + pub const class_replaceProperty = fn (cls: Class, name: [*c]const u8, attributes: [*c]const objc_property_attribute_t, attributeCount: c_uint) callconv(.C) void; + pub const class_setIvarLayout = fn (cls: Class, layout: [*c]const u8) callconv(.C) void; + pub const class_setWeakIvarLayout = fn (cls: Class, layout: [*c]const u8) callconv(.C) void; + pub const objc_getFutureClass = fn (name: [*c]const u8) callconv(.C) Class; + pub const class_createInstance = fn (cls: Class, extraBytes: usize) callconv(.C) id; + pub const objc_constructInstance = fn (cls: Class, bytes: ?*anyopaque) callconv(.C) id; + pub const objc_destructInstance = fn (obj: id) callconv(.C) ?*anyopaque; + pub const objc_allocateClassPair = fn (superclass: Class, name: [*c]const u8, extraBytes: usize) callconv(.C) Class; + pub const objc_registerClassPair = fn (cls: Class) callconv(.C) void; + pub const objc_duplicateClass = fn (original: Class, name: [*c]const u8, extraBytes: usize) callconv(.C) Class; + pub const objc_disposeClassPair = fn (cls: Class) callconv(.C) void; + pub const method_getName = fn (m: Method) callconv(.C) SEL; + pub const method_getImplementation = fn (m: Method) callconv(.C) IMP; + pub const method_getTypeEncoding = fn (m: Method) callconv(.C) [*c]const u8; + pub const method_getNumberOfArguments = fn (m: Method) callconv(.C) c_uint; + pub const method_copyReturnType = fn (m: Method) callconv(.C) [*c]u8; + pub const method_copyArgumentType = fn (m: Method, index: c_uint) callconv(.C) [*c]u8; + pub const method_getReturnType = fn (m: Method, dst: [*c]u8, dst_len: usize) callconv(.C) void; + pub const method_getArgumentType = fn (m: Method, index: c_uint, dst: [*c]u8, dst_len: usize) callconv(.C) void; + pub const method_getDescription = fn (m: Method) callconv(.C) [*c]struct_objc_method_description; + pub const method_setImplementation = fn (m: Method, imp: IMP) callconv(.C) IMP; + pub const method_exchangeImplementations = fn (m1: Method, m2: Method) callconv(.C) void; + pub const ivar_getName = fn (v: Ivar) callconv(.C) [*c]const u8; + pub const ivar_getTypeEncoding = fn (v: Ivar) callconv(.C) [*c]const u8; + pub const ivar_getOffset = fn (v: Ivar) callconv(.C) ptrdiff_t; + pub const property_getName = fn (property: objc_property_t) callconv(.C) [*c]const u8; + pub const property_getAttributes = fn (property: objc_property_t) callconv(.C) [*c]const u8; + pub const property_copyAttributeList = fn (property: objc_property_t, outCount: [*c]c_uint) callconv(.C) [*c]objc_property_attribute_t; + pub const property_copyAttributeValue = fn (property: objc_property_t, attributeName: [*c]const u8) callconv(.C) [*c]u8; + pub const objc_getProtocol = fn (name: [*c]const u8) callconv(.C) [*c]Protocol; + pub const objc_copyProtocolList = fn (outCount: [*c]c_uint) callconv(.C) [*c][*c]Protocol; + pub const protocol_conformsToProtocol = fn (proto: [*c]Protocol, other: [*c]Protocol) callconv(.C) BOOL; + pub const protocol_isEqual = fn (proto: [*c]Protocol, other: [*c]Protocol) callconv(.C) BOOL; + pub const protocol_getName = fn (proto: [*c]Protocol) callconv(.C) [*c]const u8; + pub const protocol_getMethodDescription = fn (proto: [*c]Protocol, aSel: SEL, isRequiredMethod: BOOL, isInstanceMethod: BOOL) callconv(.C) struct_objc_method_description; + pub const protocol_copyMethodDescriptionList = fn (proto: [*c]Protocol, isRequiredMethod: BOOL, isInstanceMethod: BOOL, outCount: [*c]c_uint) callconv(.C) [*c]struct_objc_method_description; + pub const protocol_getProperty = fn (proto: [*c]Protocol, name: [*c]const u8, isRequiredProperty: BOOL, isInstanceProperty: BOOL) callconv(.C) objc_property_t; + pub const protocol_copyPropertyList = fn (proto: [*c]Protocol, outCount: [*c]c_uint) callconv(.C) [*c]objc_property_t; + pub const protocol_copyPropertyList2 = fn (proto: [*c]Protocol, outCount: [*c]c_uint, isRequiredProperty: BOOL, isInstanceProperty: BOOL) callconv(.C) [*c]objc_property_t; + pub const protocol_copyProtocolList = fn (proto: [*c]Protocol, outCount: [*c]c_uint) callconv(.C) [*c][*c]Protocol; + pub const objc_allocateProtocol = fn (name: [*c]const u8) callconv(.C) [*c]Protocol; + pub const objc_registerProtocol = fn (proto: [*c]Protocol) callconv(.C) void; + pub const protocol_addMethodDescription = fn (proto: [*c]Protocol, name: SEL, types: [*c]const u8, isRequiredMethod: BOOL, isInstanceMethod: BOOL) callconv(.C) void; + pub const protocol_addProtocol = fn (proto: [*c]Protocol, addition: [*c]Protocol) callconv(.C) void; + pub const protocol_addProperty = fn (proto: [*c]Protocol, name: [*c]const u8, attributes: [*c]const objc_property_attribute_t, attributeCount: c_uint, isRequiredProperty: BOOL, isInstanceProperty: BOOL) callconv(.C) void; + pub const objc_copyImageNames = fn (outCount: [*c]c_uint) callconv(.C) [*c][*c]const u8; + pub const class_getImageName = fn (cls: Class) callconv(.C) [*c]const u8; + pub const objc_copyClassNamesForImage = fn (image: [*c]const u8, outCount: [*c]c_uint) callconv(.C) [*c][*c]const u8; + pub const sel_isEqual = fn (lhs: SEL, rhs: SEL) callconv(.C) BOOL; + pub const objc_enumerationMutation = fn (obj: id) callconv(.C) void; + pub const objc_setEnumerationMutationHandler = fn (handler: ?fn (id) callconv(.C) void) callconv(.C) void; + pub const objc_setForwardHandler = fn (fwd: ?*anyopaque, fwd_stret: ?*anyopaque) callconv(.C) void; + pub const imp_implementationWithBlock = fn (block: id) callconv(.C) IMP; + pub const imp_getBlock = fn (anImp: IMP) callconv(.C) id; + pub const imp_removeBlock = fn (anImp: IMP) callconv(.C) BOOL; + pub const objc_loadWeak = fn (location: [*c]id) callconv(.C) id; + pub const objc_storeWeak = fn (location: [*c]id, obj: id) callconv(.C) id; + pub const objc_AssociationPolicy = usize; + pub const OBJC_ASSOCIATION_ASSIGN: c_int = 0; + pub const OBJC_ASSOCIATION_RETAIN_NONATOMIC: c_int = 1; + pub const OBJC_ASSOCIATION_COPY_NONATOMIC: c_int = 3; + pub const OBJC_ASSOCIATION_RETAIN: c_int = 769; + pub const OBJC_ASSOCIATION_COPY: c_int = 771; + const enum_unnamed_1 = c_uint; + pub const objc_setAssociatedObject = fn (object: id, key: ?*const anyopaque, value: id, policy: objc_AssociationPolicy) callconv(.C) void; + pub const objc_getAssociatedObject = fn (object: id, key: ?*const anyopaque) callconv(.C) id; + pub const objc_removeAssociatedObjects = fn (object: id) callconv(.C) void; + pub const objc_hook_getImageName = ?fn (Class, [*c][*c]const u8) callconv(.C) BOOL; + pub const objc_setHook_getImageName = fn (newValue: objc_hook_getImageName, outOldValue: [*c]objc_hook_getImageName) callconv(.C) void; + pub const objc_hook_getClass = ?fn ([*c]const u8, [*c]Class) callconv(.C) BOOL; + pub const objc_setHook_getClass = fn (newValue: objc_hook_getClass, outOldValue: [*c]objc_hook_getClass) callconv(.C) void; + pub const struct_mach_header = opaque {}; + pub const objc_func_loadImage = ?fn (?*const struct_mach_header) callconv(.C) void; + pub const objc_addLoadImageFunc = fn (func: objc_func_loadImage) callconv(.C) void; + pub const objc_hook_lazyClassNamer = ?fn (Class) callconv(.C) [*c]const u8; + pub const objc_setHook_lazyClassNamer = fn (newValue: objc_hook_lazyClassNamer, oldOutValue: [*c]objc_hook_lazyClassNamer) callconv(.C) void; + pub const _objc_swiftMetadataInitializer = ?fn (Class, ?*anyopaque) callconv(.C) Class; + pub const _objc_realizeClassFromSwift = fn (cls: Class, previously: ?*anyopaque) callconv(.C) Class; + pub const struct_objc_method_list = opaque {}; + pub const class_lookupMethod = fn (cls: Class, sel: SEL) callconv(.C) IMP; + pub const class_respondsToMethod = fn (cls: Class, sel: SEL) callconv(.C) BOOL; + pub const _objc_flush_caches = fn (cls: Class) callconv(.C) void; + pub const object_copyFromZone = fn (anObject: id, nBytes: usize, z: ?*anyopaque) callconv(.C) id; + pub const class_createInstanceFromZone = fn (Class, idxIvars: usize, z: ?*anyopaque) callconv(.C) id; + pub inline fn __P(protos: anytype) @TypeOf(protos) { + return protos; + } + pub const @"bool" = bool; + pub const @"true" = @as(c_int, 1); + pub const @"false" = @as(c_int, 0); + pub const __bool_true_false_are_defined = @as(c_int, 1); + pub const OBJC_BOOL_IS_BOOL = @as(c_int, 1); + pub const OBJC_BOOL_DEFINED = ""; + pub const Nil = @as(usize, 0); + pub const nil = @as(usize, 0); + pub const __autoreleasing = ""; + pub const ARITH_SHIFT = @as(c_int, 32); + pub inline fn ISSELECTOR(sel: anytype) @TypeOf(sel_isMapped(sel)) { + return sel_isMapped(sel); + } + pub inline fn SELNAME(sel: anytype) @TypeOf(sel_getName(sel)) { + return sel_getName(sel); + } + pub inline fn SELUID(str: anytype) @TypeOf(sel_getUid(str)) { + return sel_getUid(str); + } + pub inline fn NAMEOF(obj: anytype) @TypeOf(object_getClassName(obj)) { + return object_getClassName(obj); + } + pub inline fn IV(obj: anytype) @TypeOf(object_getIndexedIvars(obj)) { + return object_getIndexedIvars(obj); + } + pub const NULL = @import("std").zig.c_translation.cast(?*anyopaque, @as(c_int, 0)); + pub const objc_class = struct_objc_class; + pub const objc_object = struct_objc_object; + pub const objc_selector = struct_objc_selector; + pub const objc_method = struct_objc_method; + pub const objc_ivar = struct_objc_ivar; + pub const objc_category = struct_objc_category; + pub const objc_property = struct_objc_property; + pub const objc_method_description = struct_objc_method_description; + pub const mach_header = struct_mach_header; + pub const objc_method_list = struct_objc_method_list; + }; +}; diff --git a/src/javascript/jsc/api/bun.zig b/src/javascript/jsc/api/bun.zig index de85d6c8f..5b4fbfdf1 100644 --- a/src/javascript/jsc/api/bun.zig +++ b/src/javascript/jsc/api/bun.zig @@ -1202,9 +1202,173 @@ pub const Class = NewClass( .SHA512_256 = .{ .get = Crypto.SHA512_256.getter, }, + .Clipboard = .{ + .get = Clipboard.getter, + }, }, ); +pub const Clipboard = struct { + const macOS = @import("../../../deps/objc.zig").ObjC; + pub const Class = NewClass( + void, + .{ .name = "Clipboard" }, + .{ + .readText = .{ + .rfn = JSC.wrapWithHasContainer(Clipboard, "readText", false, false), + }, + .readPNG = .{ + .rfn = JSC.wrapWithHasContainer(Clipboard, "readPNG", false, false), + }, + .writeText = .{ + .rfn = JSC.wrapWithHasContainer(Clipboard, "writeText", false, false), + }, + .writePNG = .{ + .rfn = JSC.wrapWithHasContainer(Clipboard, "writePNG", false, false), + }, + }, + .{}, + ); + + pub fn getter( + _: void, + ctx: js.JSContextRef, + _: js.JSValueRef, + _: js.JSStringRef, + _: js.ExceptionRef, + ) js.JSValueRef { + var existing = ctx.ptr().getCachedObject(&ZigString.init("BunClipboard")); + if (existing.isEmpty()) { + return ctx.ptr().putCachedObject( + &ZigString.init("BunClipboard"), + JSC.JSValue.c(JSC.C.JSObjectMake(ctx, Clipboard.Class.get().*, null)), + ).asObjectRef(); + } + + return existing.asObjectRef(); + } + + pub fn readText( + global: *JSC.JSGlobalObject, + exception: JSC.C.ExceptionRef, + ) JSC.JSValue { + if (comptime !Environment.isMac) { + return JSC.JSValue.jsUndefined(); + } + + var data = macOS.Clipboard.get(bun.default_allocator, .string) catch |err| { + JSC.JSError(bun.default_allocator, "Error reading clipboard {s}", .{@errorName(err)}, global.ref(), exception); + return JSC.JSValue.jsUndefined(); + }; + var text = ZigString.init(data).withEncoding(); + if (text.isUTF8()) { + var ret = text.toValueGC(global); + bun.default_allocator.free(data); + return JSC.JSPromise.resolvedPromiseValue(global, ret); + } + text.mark(); + return JSC.JSPromise.resolvedPromiseValue(global, text.toExternalValue(global)); + } + pub fn readPNG( + global: *JSC.JSGlobalObject, + exception: JSC.C.ExceptionRef, + ) JSC.JSValue { + if (comptime !Environment.isMac) { + return JSC.JSValue.jsUndefined(); + } + + var data = macOS.Clipboard.get(bun.default_allocator, .png) catch |err| { + JSC.JSError(bun.default_allocator, "Error reading clipboard {s}", .{@errorName(err)}, global.ref(), exception); + return JSC.JSValue.jsUndefined(); + }; + if (data.len == 0) { + return JSC.JSPromise.resolvedPromiseValue(global, JSC.JSValue.jsUndefined()); + } + + var blob = JSC.WebCore.Blob.initWithStore(JSC.WebCore.Blob.Store.init(data, bun.default_allocator) catch unreachable, global); + var ptr = bun.default_allocator.create(JSC.WebCore.Blob) catch unreachable; + ptr.* = blob; + ptr.allocator = bun.default_allocator; + return JSC.JSPromise.resolvedPromiseValue(global, JSC.JSValue.c(JSC.WebCore.Blob.Class.make(global.ref(), ptr))); + } + pub fn writeText( + global: *JSGlobalObject, + text: ZigString, + ) JSC.JSValue { + if (comptime !Environment.isMac) { + return JSC.JSValue.jsUndefined(); + } + + var slice = text.toSlice(bun.default_allocator); + defer slice.deinit(); + var data = slice.slice(); + macOS.Clipboard.set(.string, data) catch { + return ZigString.init("Error writing to clipboard").toErrorInstance(global); + }; + return JSC.JSPromise.resolvedPromiseValue(global, JSC.JSValue.jsUndefined()); + } + pub fn writePNG( + global: *JSGlobalObject, + blob_value: JSValue, + ) JSC.JSValue { + if (comptime !Environment.isMac) { + return JSC.JSValue.jsUndefined(); + } + + var blob = blob_value.as(JSC.WebCore.Blob) orelse { + var err = ZigString.init("Expected Blob").toErrorInstance(global); + return JSC.JSPromise.rejectedPromiseValue(global, err); + }; + if (blob.needsToReadFile()) { + var sink = bun.default_allocator.create(ClipboardPromise) catch unreachable; + sink.* = .{ + .promise = JSC.JSPromise.create(global), + .global = global, + }; + blob.doReadFileInternal(*ClipboardPromise, sink, ClipboardPromise.onFinishedLoading, global); + return sink.promise.asValue(global); + } + + doWritePNG(blob.sharedView()) catch { + return JSC.JSPromise.rejectedPromiseValue(global, ZigString.init("Error writing to clipboard").toErrorInstance(global)); + }; + + return JSC.JSPromise.resolvedPromiseValue(global, JSC.JSValue.jsUndefined()); + } + + pub fn doWritePNG(blob: []const u8) !void { + if (comptime !Environment.isMac) { + return; + } + + try macOS.Clipboard.set(.png, blob); + } + + const ClipboardPromise = struct { + promise: *JSC.JSPromise, + global: *JSGlobalObject, + + pub fn onFinishedLoading(sink: *ClipboardPromise, bytes: JSC.WebCore.Blob.Store.ReadFile.ResultType) void { + switch (bytes) { + .err => |err| { + sink.promise.reject(sink.global, err.toErrorInstance(sink.global)); + bun.default_allocator.destroy(sink); + return; + }, + .result => |data| { + doWritePNG(data.buf) catch { + sink.promise.reject(sink.global, ZigString.init("Error writing to clipboard").toErrorInstance(sink.global)); + bun.default_allocator.destroy(sink); + return; + }; + sink.promise.resolve(sink.global, JSC.JSValue.jsUndefined()); + bun.default_allocator.destroy(sink); + }, + } + } + }; +}; + pub const Crypto = struct { const Hashers = @import("../../../sha.zig"); |