Spaces:
Runtime error
Runtime error
text-2-character-anim
/
pyrender
/.eggs
/pyglet-2.0.5-py3.10.egg
/pyglet
/libs
/darwin
/cocoapy
/runtime.py
# objective-ctypes | |
# | |
# Copyright (c) 2011, Phillip Nguyen | |
# All rights reserved. | |
# | |
# Redistribution and use in source and binary forms, with or without | |
# modification, are permitted provided that the following conditions | |
# are met: | |
# | |
# Redistributions of source code must retain the above copyright | |
# notice, this list of conditions and the following disclaimer. | |
# Redistributions in binary form must reproduce the above copyright | |
# notice, this list of conditions and the following disclaimer in the | |
# documentation and/or other materials provided with the distribution. | |
# Neither the name of objective-ctypes nor the names of its | |
# contributors may be used to endorse or promote products derived from | |
# this software without specific prior written permission. | |
# | |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
# POSSIBILITY OF SUCH DAMAGE. | |
import sys | |
import platform | |
import struct | |
from ctypes import * | |
from ctypes import util | |
from .cocoatypes import * | |
__LP64__ = (8 * struct.calcsize("P") == 64) | |
__i386__ = (platform.machine() == 'i386') | |
__arm64__ = (platform.machine() == 'arm64') | |
if sizeof(c_void_p) == 4: | |
c_ptrdiff_t = c_int32 | |
elif sizeof(c_void_p) == 8: | |
c_ptrdiff_t = c_int64 | |
###################################################################### | |
lib = util.find_library('objc') | |
# Hack for compatibility with macOS > 11.0 | |
if lib is None: | |
lib = '/usr/lib/libobjc.dylib' | |
objc = cdll.LoadLibrary(lib) | |
###################################################################### | |
# BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types) | |
objc.class_addIvar.restype = c_bool | |
objc.class_addIvar.argtypes = [c_void_p, c_char_p, c_size_t, c_uint8, c_char_p] | |
# BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) | |
objc.class_addMethod.restype = c_bool | |
# BOOL class_addProtocol(Class cls, Protocol *protocol) | |
objc.class_addProtocol.restype = c_bool | |
objc.class_addProtocol.argtypes = [c_void_p, c_void_p] | |
# BOOL class_conformsToProtocol(Class cls, Protocol *protocol) | |
objc.class_conformsToProtocol.restype = c_bool | |
objc.class_conformsToProtocol.argtypes = [c_void_p, c_void_p] | |
# Ivar * class_copyIvarList(Class cls, unsigned int *outCount) | |
# Returns an array of pointers of type Ivar describing instance variables. | |
# The array has *outCount pointers followed by a NULL terminator. | |
# You must free() the returned array. | |
objc.class_copyIvarList.restype = POINTER(c_void_p) | |
objc.class_copyIvarList.argtypes = [c_void_p, POINTER(c_uint)] | |
# Method * class_copyMethodList(Class cls, unsigned int *outCount) | |
# Returns an array of pointers of type Method describing instance methods. | |
# The array has *outCount pointers followed by a NULL terminator. | |
# You must free() the returned array. | |
objc.class_copyMethodList.restype = POINTER(c_void_p) | |
objc.class_copyMethodList.argtypes = [c_void_p, POINTER(c_uint)] | |
# objc_property_t * class_copyPropertyList(Class cls, unsigned int *outCount) | |
# Returns an array of pointers of type objc_property_t describing properties. | |
# The array has *outCount pointers followed by a NULL terminator. | |
# You must free() the returned array. | |
objc.class_copyPropertyList.restype = POINTER(c_void_p) | |
objc.class_copyPropertyList.argtypes = [c_void_p, POINTER(c_uint)] | |
# Protocol ** class_copyProtocolList(Class cls, unsigned int *outCount) | |
# Returns an array of pointers of type Protocol* describing protocols. | |
# The array has *outCount pointers followed by a NULL terminator. | |
# You must free() the returned array. | |
objc.class_copyProtocolList.restype = POINTER(c_void_p) | |
objc.class_copyProtocolList.argtypes = [c_void_p, POINTER(c_uint)] | |
# id class_createInstance(Class cls, size_t extraBytes) | |
objc.class_createInstance.restype = c_void_p | |
objc.class_createInstance.argtypes = [c_void_p, c_size_t] | |
# Method class_getClassMethod(Class aClass, SEL aSelector) | |
# Will also search superclass for implementations. | |
objc.class_getClassMethod.restype = c_void_p | |
objc.class_getClassMethod.argtypes = [c_void_p, c_void_p] | |
# Ivar class_getClassVariable(Class cls, const char* name) | |
objc.class_getClassVariable.restype = c_void_p | |
objc.class_getClassVariable.argtypes = [c_void_p, c_char_p] | |
# Method class_getInstanceMethod(Class aClass, SEL aSelector) | |
# Will also search superclass for implementations. | |
objc.class_getInstanceMethod.restype = c_void_p | |
objc.class_getInstanceMethod.argtypes = [c_void_p, c_void_p] | |
# size_t class_getInstanceSize(Class cls) | |
objc.class_getInstanceSize.restype = c_size_t | |
objc.class_getInstanceSize.argtypes = [c_void_p] | |
# Ivar class_getInstanceVariable(Class cls, const char* name) | |
objc.class_getInstanceVariable.restype = c_void_p | |
objc.class_getInstanceVariable.argtypes = [c_void_p, c_char_p] | |
# const char *class_getIvarLayout(Class cls) | |
objc.class_getIvarLayout.restype = c_char_p | |
objc.class_getIvarLayout.argtypes = [c_void_p] | |
# IMP class_getMethodImplementation(Class cls, SEL name) | |
objc.class_getMethodImplementation.restype = c_void_p | |
objc.class_getMethodImplementation.argtypes = [c_void_p, c_void_p] | |
# The function is marked as OBJC_ARM64_UNAVAILABLE. | |
if not __arm64__: | |
# IMP class_getMethodImplementation_stret(Class cls, SEL name) | |
objc.class_getMethodImplementation_stret.restype = c_void_p | |
objc.class_getMethodImplementation_stret.argtypes = [c_void_p, c_void_p] | |
# const char * class_getName(Class cls) | |
objc.class_getName.restype = c_char_p | |
objc.class_getName.argtypes = [c_void_p] | |
# objc_property_t class_getProperty(Class cls, const char *name) | |
objc.class_getProperty.restype = c_void_p | |
objc.class_getProperty.argtypes = [c_void_p, c_char_p] | |
# Class class_getSuperclass(Class cls) | |
objc.class_getSuperclass.restype = c_void_p | |
objc.class_getSuperclass.argtypes = [c_void_p] | |
# int class_getVersion(Class theClass) | |
objc.class_getVersion.restype = c_int | |
objc.class_getVersion.argtypes = [c_void_p] | |
# const char *class_getWeakIvarLayout(Class cls) | |
objc.class_getWeakIvarLayout.restype = c_char_p | |
objc.class_getWeakIvarLayout.argtypes = [c_void_p] | |
# BOOL class_isMetaClass(Class cls) | |
objc.class_isMetaClass.restype = c_bool | |
objc.class_isMetaClass.argtypes = [c_void_p] | |
# IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) | |
objc.class_replaceMethod.restype = c_void_p | |
objc.class_replaceMethod.argtypes = [c_void_p, c_void_p, c_void_p, c_char_p] | |
# BOOL class_respondsToSelector(Class cls, SEL sel) | |
objc.class_respondsToSelector.restype = c_bool | |
objc.class_respondsToSelector.argtypes = [c_void_p, c_void_p] | |
# void class_setIvarLayout(Class cls, const char *layout) | |
objc.class_setIvarLayout.restype = None | |
objc.class_setIvarLayout.argtypes = [c_void_p, c_char_p] | |
# Class class_setSuperclass(Class cls, Class newSuper) | |
objc.class_setSuperclass.restype = c_void_p | |
objc.class_setSuperclass.argtypes = [c_void_p, c_void_p] | |
# void class_setVersion(Class theClass, int version) | |
objc.class_setVersion.restype = None | |
objc.class_setVersion.argtypes = [c_void_p, c_int] | |
# void class_setWeakIvarLayout(Class cls, const char *layout) | |
objc.class_setWeakIvarLayout.restype = None | |
objc.class_setWeakIvarLayout.argtypes = [c_void_p, c_char_p] | |
###################################################################### | |
# const char * ivar_getName(Ivar ivar) | |
objc.ivar_getName.restype = c_char_p | |
objc.ivar_getName.argtypes = [c_void_p] | |
# ptrdiff_t ivar_getOffset(Ivar ivar) | |
objc.ivar_getOffset.restype = c_ptrdiff_t | |
objc.ivar_getOffset.argtypes = [c_void_p] | |
# const char * ivar_getTypeEncoding(Ivar ivar) | |
objc.ivar_getTypeEncoding.restype = c_char_p | |
objc.ivar_getTypeEncoding.argtypes = [c_void_p] | |
###################################################################### | |
# char * method_copyArgumentType(Method method, unsigned int index) | |
# You must free() the returned string. | |
objc.method_copyArgumentType.restype = c_char_p | |
objc.method_copyArgumentType.argtypes = [c_void_p, c_uint] | |
# char * method_copyReturnType(Method method) | |
# You must free() the returned string. | |
objc.method_copyReturnType.restype = c_char_p | |
objc.method_copyReturnType.argtypes = [c_void_p] | |
# void method_exchangeImplementations(Method m1, Method m2) | |
objc.method_exchangeImplementations.restype = None | |
objc.method_exchangeImplementations.argtypes = [c_void_p, c_void_p] | |
# void method_getArgumentType(Method method, unsigned int index, char *dst, size_t dst_len) | |
# Functionally similar to strncpy(dst, parameter_type, dst_len). | |
objc.method_getArgumentType.restype = None | |
objc.method_getArgumentType.argtypes = [c_void_p, c_uint, c_char_p, c_size_t] | |
# IMP method_getImplementation(Method method) | |
objc.method_getImplementation.restype = c_void_p | |
objc.method_getImplementation.argtypes = [c_void_p] | |
# SEL method_getName(Method method) | |
objc.method_getName.restype = c_void_p | |
objc.method_getName.argtypes = [c_void_p] | |
# unsigned method_getNumberOfArguments(Method method) | |
objc.method_getNumberOfArguments.restype = c_uint | |
objc.method_getNumberOfArguments.argtypes = [c_void_p] | |
# void method_getReturnType(Method method, char *dst, size_t dst_len) | |
# Functionally similar to strncpy(dst, return_type, dst_len) | |
objc.method_getReturnType.restype = None | |
objc.method_getReturnType.argtypes = [c_void_p, c_char_p, c_size_t] | |
# const char * method_getTypeEncoding(Method method) | |
objc.method_getTypeEncoding.restype = c_char_p | |
objc.method_getTypeEncoding.argtypes = [c_void_p] | |
# IMP method_setImplementation(Method method, IMP imp) | |
objc.method_setImplementation.restype = c_void_p | |
objc.method_setImplementation.argtypes = [c_void_p, c_void_p] | |
###################################################################### | |
# Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) | |
objc.objc_allocateClassPair.restype = c_void_p | |
objc.objc_allocateClassPair.argtypes = [c_void_p, c_char_p, c_size_t] | |
# Protocol **objc_copyProtocolList(unsigned int *outCount) | |
# Returns an array of *outcount pointers followed by NULL terminator. | |
# You must free() the array. | |
objc.objc_copyProtocolList.restype = POINTER(c_void_p) | |
objc.objc_copyProtocolList.argtypes = [POINTER(c_int)] | |
# id objc_getAssociatedObject(id object, void *key) | |
objc.objc_getAssociatedObject.restype = c_void_p | |
objc.objc_getAssociatedObject.argtypes = [c_void_p, c_void_p] | |
# id objc_getClass(const char *name) | |
objc.objc_getClass.restype = c_void_p | |
objc.objc_getClass.argtypes = [c_char_p] | |
# int objc_getClassList(Class *buffer, int bufferLen) | |
# Pass None for buffer to obtain just the total number of classes. | |
objc.objc_getClassList.restype = c_int | |
objc.objc_getClassList.argtypes = [c_void_p, c_int] | |
# id objc_getMetaClass(const char *name) | |
objc.objc_getMetaClass.restype = c_void_p | |
objc.objc_getMetaClass.argtypes = [c_char_p] | |
# Protocol *objc_getProtocol(const char *name) | |
objc.objc_getProtocol.restype = c_void_p | |
objc.objc_getProtocol.argtypes = [c_char_p] | |
# You should set return and argument types depending on context. | |
# id objc_msgSend(id theReceiver, SEL theSelector, ...) | |
# id objc_msgSendSuper(struct objc_super *super, SEL op, ...) | |
# The function is marked as OBJC_ARM64_UNAVAILABLE. | |
if not __arm64__: | |
# void objc_msgSendSuper_stret(struct objc_super *super, SEL op, ...) | |
objc.objc_msgSendSuper_stret.restype = None | |
# double objc_msgSend_fpret(id self, SEL op, ...) | |
# objc.objc_msgSend_fpret.restype = c_double | |
# The function is marked as OBJC_ARM64_UNAVAILABLE. | |
if not __arm64__: | |
# void objc_msgSend_stret(void * stretAddr, id theReceiver, SEL theSelector, ...) | |
objc.objc_msgSend_stret.restype = None | |
# void objc_registerClassPair(Class cls) | |
objc.objc_registerClassPair.restype = None | |
objc.objc_registerClassPair.argtypes = [c_void_p] | |
# void objc_removeAssociatedObjects(id object) | |
objc.objc_removeAssociatedObjects.restype = None | |
objc.objc_removeAssociatedObjects.argtypes = [c_void_p] | |
# void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) | |
objc.objc_setAssociatedObject.restype = None | |
objc.objc_setAssociatedObject.argtypes = [c_void_p, c_void_p, c_void_p, c_int] | |
###################################################################### | |
# id object_copy(id obj, size_t size) | |
objc.object_copy.restype = c_void_p | |
objc.object_copy.argtypes = [c_void_p, c_size_t] | |
# id object_dispose(id obj) | |
objc.object_dispose.restype = c_void_p | |
objc.object_dispose.argtypes = [c_void_p] | |
# Class object_getClass(id object) | |
objc.object_getClass.restype = c_void_p | |
objc.object_getClass.argtypes = [c_void_p] | |
# const char *object_getClassName(id obj) | |
objc.object_getClassName.restype = c_char_p | |
objc.object_getClassName.argtypes = [c_void_p] | |
# Ivar object_getInstanceVariable(id obj, const char *name, void **outValue) | |
objc.object_getInstanceVariable.restype = c_void_p | |
objc.object_getInstanceVariable.argtypes = [c_void_p, c_char_p, c_void_p] | |
# id object_getIvar(id object, Ivar ivar) | |
objc.object_getIvar.restype = c_void_p | |
objc.object_getIvar.argtypes = [c_void_p, c_void_p] | |
# Class object_setClass(id object, Class cls) | |
objc.object_setClass.restype = c_void_p | |
objc.object_setClass.argtypes = [c_void_p, c_void_p] | |
# Ivar object_setInstanceVariable(id obj, const char *name, void *value) | |
# Set argtypes based on the data type of the instance variable. | |
objc.object_setInstanceVariable.restype = c_void_p | |
# void object_setIvar(id object, Ivar ivar, id value) | |
objc.object_setIvar.restype = None | |
objc.object_setIvar.argtypes = [c_void_p, c_void_p, c_void_p] | |
###################################################################### | |
# const char *property_getAttributes(objc_property_t property) | |
objc.property_getAttributes.restype = c_char_p | |
objc.property_getAttributes.argtypes = [c_void_p] | |
# const char *property_getName(objc_property_t property) | |
objc.property_getName.restype = c_char_p | |
objc.property_getName.argtypes = [c_void_p] | |
###################################################################### | |
# BOOL protocol_conformsToProtocol(Protocol *proto, Protocol *other) | |
objc.protocol_conformsToProtocol.restype = c_bool | |
objc.protocol_conformsToProtocol.argtypes = [c_void_p, c_void_p] | |
class OBJC_METHOD_DESCRIPTION(Structure): | |
_fields_ = [("name", c_void_p), ("types", c_char_p)] | |
# struct objc_method_description *protocol_copyMethodDescriptionList(Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount) | |
# You must free() the returned array. | |
objc.protocol_copyMethodDescriptionList.restype = POINTER(OBJC_METHOD_DESCRIPTION) | |
objc.protocol_copyMethodDescriptionList.argtypes = [c_void_p, c_bool, c_bool, POINTER(c_uint)] | |
# objc_property_t * protocol_copyPropertyList(Protocol *protocol, unsigned int *outCount) | |
objc.protocol_copyPropertyList.restype = c_void_p | |
objc.protocol_copyPropertyList.argtypes = [c_void_p, POINTER(c_uint)] | |
# Protocol **protocol_copyProtocolList(Protocol *proto, unsigned int *outCount) | |
objc.protocol_copyProtocolList = POINTER(c_void_p) | |
objc.protocol_copyProtocolList.argtypes = [c_void_p, POINTER(c_uint)] | |
# struct objc_method_description protocol_getMethodDescription(Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod) | |
objc.protocol_getMethodDescription.restype = OBJC_METHOD_DESCRIPTION | |
objc.protocol_getMethodDescription.argtypes = [c_void_p, c_void_p, c_bool, c_bool] | |
# const char *protocol_getName(Protocol *p) | |
objc.protocol_getName.restype = c_char_p | |
objc.protocol_getName.argtypes = [c_void_p] | |
###################################################################### | |
# const char* sel_getName(SEL aSelector) | |
objc.sel_getName.restype = c_char_p | |
objc.sel_getName.argtypes = [c_void_p] | |
# SEL sel_getUid(const char *str) | |
# Use sel_registerName instead. | |
# BOOL sel_isEqual(SEL lhs, SEL rhs) | |
objc.sel_isEqual.restype = c_bool | |
objc.sel_isEqual.argtypes = [c_void_p, c_void_p] | |
# SEL sel_registerName(const char *str) | |
objc.sel_registerName.restype = c_void_p | |
objc.sel_registerName.argtypes = [c_char_p] | |
###################################################################### | |
def ensure_bytes(x): | |
if isinstance(x, bytes): | |
return x | |
return x.encode('ascii') | |
###################################################################### | |
def get_selector(name): | |
return c_void_p(objc.sel_registerName(ensure_bytes(name))) | |
def get_class(name): | |
return c_void_p(objc.objc_getClass(ensure_bytes(name))) | |
def get_object_class(obj): | |
return c_void_p(objc.object_getClass(obj)) | |
def get_metaclass(name): | |
return c_void_p(objc.objc_getMetaClass(ensure_bytes(name))) | |
def get_superclass_of_object(obj): | |
cls = c_void_p(objc.object_getClass(obj)) | |
return c_void_p(objc.class_getSuperclass(cls)) | |
# http://www.sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html | |
# http://www.x86-64.org/documentation/abi-0.99.pdf (pp.17-23) | |
# executive summary: on x86-64, who knows? | |
def x86_should_use_stret(restype): | |
"""Try to figure out when a return type will be passed on stack.""" | |
if type(restype) != type(Structure): | |
return False | |
if not __LP64__ and sizeof(restype) <= 8: | |
return False | |
if __LP64__ and sizeof(restype) <= 16: # maybe? I don't know? | |
return False | |
return True | |
# http://www.sealiesoftware.com/blog/archive/2008/11/16/objc_explain_objc_msgSend_fpret.html | |
def should_use_fpret(restype): | |
"""Determine if objc_msgSend_fpret is required to return a floating point type.""" | |
if not __i386__: | |
# Unneeded on non-intel processors | |
return False | |
if __LP64__ and restype == c_longdouble: | |
# Use only for long double on x86_64 | |
return True | |
if not __LP64__ and restype in (c_float, c_double, c_longdouble): | |
return True | |
return False | |
# By default, assumes that restype is c_void_p | |
# and that all arguments are wrapped inside c_void_p. | |
# Use the restype and argtypes keyword arguments to | |
# change these values. restype should be a ctypes type | |
# and argtypes should be a list of ctypes types for | |
# the arguments of the message only. | |
def send_message(receiver, selName, *args, **kwargs): | |
if isinstance(receiver, str): | |
receiver = get_class(receiver) | |
selector = get_selector(selName) | |
restype = kwargs.get('restype', c_void_p) | |
# print('send_message', receiver, selName, args, kwargs) | |
argtypes = kwargs.get('argtypes', []) | |
# Choose the correct version of objc_msgSend based on return type. | |
if should_use_fpret(restype): | |
objc.objc_msgSend_fpret.restype = restype | |
objc.objc_msgSend_fpret.argtypes = [c_void_p, c_void_p] + argtypes | |
result = objc.objc_msgSend_fpret(receiver, selector, *args) | |
elif x86_should_use_stret(restype): | |
objc.objc_msgSend_stret.argtypes = [POINTER(restype), c_void_p, c_void_p] + argtypes | |
result = restype() | |
objc.objc_msgSend_stret(byref(result), receiver, selector, *args) | |
else: | |
objc.objc_msgSend.restype = restype | |
objc.objc_msgSend.argtypes = [c_void_p, c_void_p] + argtypes | |
result = objc.objc_msgSend(receiver, selector, *args) | |
if restype == c_void_p: | |
result = c_void_p(result) | |
return result | |
class OBJC_SUPER(Structure): | |
_fields_ = [('receiver', c_void_p), ('class', c_void_p)] | |
OBJC_SUPER_PTR = POINTER(OBJC_SUPER) | |
# http://stackoverflow.com/questions/3095360/what-exactly-is-super-in-objective-c | |
# | |
# `superclass_name` is optional and can be used to force finding the superclass | |
# by name. It is used to circumvent a bug in which the superclass was resolved | |
# incorrectly which lead to an infinite recursion: | |
# https://github.com/pyglet/pyglet/issues/5 | |
def send_super(receiver, selName, *args, superclass_name=None, **kwargs): | |
if hasattr(receiver, '_as_parameter_'): | |
receiver = receiver._as_parameter_ | |
if superclass_name is None: | |
superclass = get_superclass_of_object(receiver) | |
else: | |
superclass = get_class(superclass_name) | |
super_struct = OBJC_SUPER(receiver, superclass) | |
selector = get_selector(selName) | |
restype = kwargs.get('restype', c_void_p) | |
argtypes = kwargs.get('argtypes', None) | |
objc.objc_msgSendSuper.restype = restype | |
if argtypes: | |
objc.objc_msgSendSuper.argtypes = [OBJC_SUPER_PTR, c_void_p] + argtypes | |
else: | |
objc.objc_msgSendSuper.argtypes = None | |
result = objc.objc_msgSendSuper(byref(super_struct), selector, *args) | |
if restype == c_void_p: | |
result = c_void_p(result) | |
return result | |
###################################################################### | |
cfunctype_table = {} | |
def parse_type_encoding(encoding): | |
"""Takes a type encoding string and outputs a list of the separated type codes. | |
Currently does not handle unions or bitfields and strips out any field width | |
specifiers or type specifiers from the encoding. For Python 3.2+, encoding is | |
assumed to be a bytes object and not unicode. | |
Examples: | |
parse_type_encoding('^v16@0:8') --> ['^v', '@', ':'] | |
parse_type_encoding('{CGSize=dd}40@0:8{CGSize=dd}16Q32') --> ['{CGSize=dd}', '@', ':', '{CGSize=dd}', 'Q'] | |
""" | |
type_encodings = [] | |
brace_count = 0 # number of unclosed curly braces | |
bracket_count = 0 # number of unclosed square brackets | |
typecode = b'' | |
for c in encoding: | |
# In Python 3, c comes out as an integer in the range 0-255. In Python 2, c is a single character string. | |
# To fix the disparity, we convert c to a bytes object if necessary. | |
if isinstance(c, int): | |
c = bytes([c]) | |
if c == b'{': | |
# Check if this marked the end of previous type code. | |
if typecode and typecode[-1:] != b'^' and brace_count == 0 and bracket_count == 0: | |
type_encodings.append(typecode) | |
typecode = b'' | |
typecode += c | |
brace_count += 1 | |
elif c == b'}': | |
typecode += c | |
brace_count -= 1 | |
assert (brace_count >= 0) | |
elif c == b'[': | |
# Check if this marked the end of previous type code. | |
if typecode and typecode[-1:] != b'^' and brace_count == 0 and bracket_count == 0: | |
type_encodings.append(typecode) | |
typecode = b'' | |
typecode += c | |
bracket_count += 1 | |
elif c == b']': | |
typecode += c | |
bracket_count -= 1 | |
assert (bracket_count >= 0) | |
elif brace_count or bracket_count: | |
# Anything encountered while inside braces or brackets gets stuck on. | |
typecode += c | |
elif c in b'0123456789': | |
# Ignore field width specifiers for now. | |
pass | |
elif c in b'rnNoORV': | |
# Also ignore type specifiers. | |
pass | |
elif c in b'^cislqCISLQfdBv*@#:b?': | |
if typecode and typecode[-1:] == b'^': | |
# Previous char was pointer specifier, so keep going. | |
typecode += c | |
else: | |
# Add previous type code to the list. | |
if typecode: | |
type_encodings.append(typecode) | |
# Start a new type code. | |
typecode = c | |
# Add the last type code to the list | |
if typecode: | |
type_encodings.append(typecode) | |
return type_encodings | |
# Limited to basic types and pointers to basic types. | |
# Does not try to handle arrays, arbitrary structs, unions, or bitfields. | |
# Assume that encoding is a bytes object and not unicode. | |
def cfunctype_for_encoding(encoding): | |
# Check if we've already created a CFUNCTYPE for this encoding. | |
# If so, then return the cached CFUNCTYPE. | |
if encoding in cfunctype_table: | |
return cfunctype_table[encoding] | |
# Otherwise, create a new CFUNCTYPE for the encoding. | |
typecodes = {b'c': c_char, b'i': c_int, b's': c_short, b'l': c_long, b'q': c_longlong, | |
b'C': c_ubyte, b'I': c_uint, b'S': c_ushort, b'L': c_ulong, b'Q': c_ulonglong, | |
b'f': c_float, b'd': c_double, b'B': c_bool, b'v': None, b'*': c_char_p, | |
b'@': c_void_p, b'#': c_void_p, b':': c_void_p, NSPointEncoding: NSPoint, | |
NSSizeEncoding: NSSize, NSRectEncoding: NSRect, NSRangeEncoding: NSRange, | |
PyObjectEncoding: py_object} | |
argtypes = [] | |
for code in parse_type_encoding(encoding): | |
if code in typecodes: | |
argtypes.append(typecodes[code]) | |
elif code[0:1] == b'^' and code[1:] in typecodes: | |
argtypes.append(POINTER(typecodes[code[1:]])) | |
else: | |
raise Exception('unknown type encoding: ' + code) | |
cfunctype = CFUNCTYPE(*argtypes) | |
# Cache the new CFUNCTYPE in the cfunctype_table. | |
# We do this mainly because it prevents the CFUNCTYPE | |
# from being garbage-collected while we need it. | |
cfunctype_table[encoding] = cfunctype | |
return cfunctype | |
###################################################################### | |
# After calling create_subclass, you must first register | |
# it with register_subclass before you may use it. | |
# You can add new methods after the class is registered, | |
# but you cannot add any new ivars. | |
def create_subclass(superclass, name): | |
if isinstance(superclass, str): | |
superclass = get_class(superclass) | |
return c_void_p(objc.objc_allocateClassPair(superclass, ensure_bytes(name), 0)) | |
def register_subclass(subclass): | |
objc.objc_registerClassPair(subclass) | |
# types is a string encoding the argument types of the method. | |
# The first type code of types is the return type (e.g. 'v' if void) | |
# The second type code must be '@' for id self. | |
# The third type code must be ':' for SEL cmd. | |
# Additional type codes are for types of other arguments if any. | |
def add_method(cls, selName, method, types): | |
type_encodings = parse_type_encoding(types) | |
assert (type_encodings[1] == b'@') # ensure id self typecode | |
assert (type_encodings[2] == b':') # ensure SEL cmd typecode | |
selector = get_selector(selName) | |
cfunctype = cfunctype_for_encoding(types) | |
imp = cfunctype(method) | |
objc.class_addMethod.argtypes = [c_void_p, c_void_p, cfunctype, c_char_p] | |
objc.class_addMethod(cls, selector, imp, types) | |
return imp | |
def add_ivar(cls, name, vartype): | |
return objc.class_addIvar(cls, ensure_bytes(name), sizeof(vartype), alignment(vartype), encoding_for_ctype(vartype)) | |
def set_instance_variable(obj, varname, value, vartype): | |
objc.object_setInstanceVariable.argtypes = [c_void_p, c_char_p, vartype] | |
objc.object_setInstanceVariable(obj, ensure_bytes(varname), value) | |
def get_instance_variable(obj, varname, vartype): | |
variable = vartype() | |
objc.object_getInstanceVariable(obj, ensure_bytes(varname), byref(variable)) | |
return variable.value | |
###################################################################### | |
class ObjCMethod: | |
"""This represents an unbound Objective-C method (really an IMP).""" | |
# Note, need to map 'c' to c_byte rather than c_char, because otherwise | |
# ctypes converts the value into a one-character string which is generally | |
# not what we want at all, especially when the 'c' represents a bool var. | |
typecodes = {b'c': c_byte, b'i': c_int, b's': c_short, b'l': c_long, b'q': c_longlong, | |
b'C': c_ubyte, b'I': c_uint, b'S': c_ushort, b'L': c_ulong, b'Q': c_ulonglong, | |
b'f': c_float, b'd': c_double, b'B': c_bool, b'v': None, b'Vv': None, b'*': c_char_p, | |
b'@': c_void_p, b'#': c_void_p, b':': c_void_p, b'^v': c_void_p, b'?': c_void_p, | |
NSPointEncoding: NSPoint, NSSizeEncoding: NSSize, NSRectEncoding: NSRect, | |
NSRangeEncoding: NSRange, | |
PyObjectEncoding: py_object} | |
cfunctype_table = {} | |
def __init__(self, method): | |
"""Initialize with an Objective-C Method pointer. We then determine | |
the return type and argument type information of the method.""" | |
self.selector = c_void_p(objc.method_getName(method)) | |
self.name = objc.sel_getName(self.selector) | |
self.pyname = self.name.replace(b':', b'_') | |
self.encoding = objc.method_getTypeEncoding(method) | |
self.return_type = objc.method_copyReturnType(method) | |
self.nargs = objc.method_getNumberOfArguments(method) | |
self.imp = c_void_p(objc.method_getImplementation(method)) | |
self.argument_types = [] | |
for i in range(self.nargs): | |
buffer = c_buffer(512) | |
objc.method_getArgumentType(method, i, buffer, len(buffer)) | |
self.argument_types.append(buffer.value) | |
# Get types for all the arguments. | |
try: | |
self.argtypes = [self.ctype_for_encoding(t) for t in self.argument_types] | |
except: | |
# print(f'no argtypes encoding for {self.name} ({self.argument_types})') | |
self.argtypes = None | |
# Get types for the return type. | |
try: | |
if self.return_type == b'@': | |
self.restype = ObjCInstance | |
elif self.return_type == b'#': | |
self.restype = ObjCClass | |
else: | |
self.restype = self.ctype_for_encoding(self.return_type) | |
except: | |
# print(f'no restype encoding for {self.name} ({self.return_type})') | |
self.restype = None | |
self.func = None | |
def ctype_for_encoding(self, encoding): | |
"""Return ctypes type for an encoded Objective-C type.""" | |
if encoding in self.typecodes: | |
return self.typecodes[encoding] | |
elif encoding[0:1] == b'^' and encoding[1:] in self.typecodes: | |
return POINTER(self.typecodes[encoding[1:]]) | |
elif encoding[0:1] == b'^' and encoding[1:] in [CGImageEncoding, NSZoneEncoding]: | |
# special cases | |
return c_void_p | |
elif encoding[0:1] == b'r' and encoding[1:] in self.typecodes: | |
# const decorator, don't care | |
return self.typecodes[encoding[1:]] | |
elif encoding[0:2] == b'r^' and encoding[2:] in self.typecodes: | |
# const pointer, also don't care | |
return POINTER(self.typecodes[encoding[2:]]) | |
else: | |
raise Exception('unknown encoding for %s: %s' % (self.name, encoding)) | |
def get_prototype(self): | |
"""Returns a ctypes CFUNCTYPE for the method.""" | |
if self.restype == ObjCInstance or self.restype == ObjCClass: | |
# Some hacky stuff to get around ctypes issues on 64-bit. Can't let | |
# ctypes convert the return value itself, because it truncates the pointer | |
# along the way. So instead, we must do set the return type to c_void_p to | |
# ensure we get 64-bit addresses and then convert the return value manually. | |
self.prototype = CFUNCTYPE(c_void_p, *self.argtypes) | |
else: | |
self.prototype = CFUNCTYPE(self.restype, *self.argtypes) | |
return self.prototype | |
def __repr__(self): | |
return "<ObjCMethod: %s %s>" % (self.name, self.encoding) | |
def get_callable(self): | |
"""Returns a python-callable version of the method's IMP.""" | |
if not self.func: | |
prototype = self.get_prototype() | |
self.func = cast(self.imp, prototype) | |
if self.restype == ObjCInstance or self.restype == ObjCClass: | |
self.func.restype = c_void_p | |
else: | |
self.func.restype = self.restype | |
self.func.argtypes = self.argtypes | |
return self.func | |
def __call__(self, objc_id, *args): | |
"""Call the method with the given id and arguments. You do not need | |
to pass in the selector as an argument since it will be automatically | |
provided.""" | |
f = self.get_callable() | |
try: | |
result = f(objc_id, self.selector, *args) | |
# Convert result to python type if it is a instance or class pointer. | |
if self.restype == ObjCInstance: | |
result = ObjCInstance(result) | |
elif self.restype == ObjCClass: | |
result = ObjCClass(result) | |
return result | |
except ArgumentError as error: | |
# Add more useful info to argument error exceptions, then reraise. | |
error.args += ('selector = ' + str(self.name), | |
'argtypes =' + str(self.argtypes), | |
'encoding = ' + str(self.encoding)) | |
raise | |
###################################################################### | |
class ObjCBoundMethod: | |
"""This represents an Objective-C method (an IMP) which has been bound | |
to some id which will be passed as the first parameter to the method.""" | |
def __init__(self, method, objc_id): | |
"""Initialize with a method and ObjCInstance or ObjCClass object.""" | |
self.method = method | |
self.objc_id = objc_id | |
def __repr__(self): | |
return '<ObjCBoundMethod %s (%s)>' % (self.method.name, self.objc_id) | |
def __call__(self, *args): | |
"""Call the method with the given arguments.""" | |
return self.method(self.objc_id, *args) | |
###################################################################### | |
class ObjCClass: | |
"""Python wrapper for an Objective-C class.""" | |
# We only create one Python object for each Objective-C class. | |
# Any future calls with the same class will return the previously | |
# created Python object. Note that these aren't weak references. | |
# After you create an ObjCClass, it will exist until the end of the | |
# program. | |
_registered_classes = {} | |
def __new__(cls, class_name_or_ptr): | |
"""Create a new ObjCClass instance or return a previously created | |
instance for the given Objective-C class. The argument may be either | |
the name of the class to retrieve, or a pointer to the class.""" | |
# Determine name and ptr values from passed in argument. | |
if isinstance(class_name_or_ptr, str): | |
name = class_name_or_ptr | |
ptr = get_class(name) | |
else: | |
ptr = class_name_or_ptr | |
# Make sure that ptr value is wrapped in c_void_p object | |
# for safety when passing as ctypes argument. | |
if not isinstance(ptr, c_void_p): | |
ptr = c_void_p(ptr) | |
name = objc.class_getName(ptr) | |
# Check if we've already created a Python object for this class | |
# and if so, return it rather than making a new one. | |
if name in cls._registered_classes: | |
return cls._registered_classes[name] | |
# Otherwise create a new Python object and then initialize it. | |
objc_class = super(ObjCClass, cls).__new__(cls) | |
objc_class.ptr = ptr | |
objc_class.name = name | |
objc_class.instance_methods = {} # mapping of name -> instance method | |
objc_class.class_methods = {} # mapping of name -> class method | |
objc_class._as_parameter_ = ptr # for ctypes argument passing | |
# Store the new class in dictionary of registered classes. | |
cls._registered_classes[name] = objc_class | |
# Not sure this is necessary... | |
objc_class.cache_instance_methods() | |
objc_class.cache_class_methods() | |
return objc_class | |
def __repr__(self): | |
return "<ObjCClass: %s at %s>" % (self.name, str(self.ptr.value)) | |
def cache_instance_methods(self): | |
"""Create and store python representations of all instance methods | |
implemented by this class (but does not find methods of superclass).""" | |
count = c_uint() | |
method_array = objc.class_copyMethodList(self.ptr, byref(count)) | |
for i in range(count.value): | |
method = c_void_p(method_array[i]) | |
objc_method = ObjCMethod(method) | |
self.instance_methods[objc_method.pyname] = objc_method | |
def cache_class_methods(self): | |
"""Create and store python representations of all class methods | |
implemented by this class (but does not find methods of superclass).""" | |
count = c_uint() | |
method_array = objc.class_copyMethodList(objc.object_getClass(self.ptr), byref(count)) | |
for i in range(count.value): | |
method = c_void_p(method_array[i]) | |
objc_method = ObjCMethod(method) | |
self.class_methods[objc_method.pyname] = objc_method | |
def get_instance_method(self, name): | |
"""Returns a python representation of the named instance method, | |
either by looking it up in the cached list of methods or by searching | |
for and creating a new method object.""" | |
if name in self.instance_methods: | |
return self.instance_methods[name] | |
else: | |
# If method name isn't in the cached list, it might be a method of | |
# the superclass, so call class_getInstanceMethod to check. | |
selector = get_selector(name.replace(b'_', b':')) | |
method = c_void_p(objc.class_getInstanceMethod(self.ptr, selector)) | |
if method.value: | |
objc_method = ObjCMethod(method) | |
self.instance_methods[name] = objc_method | |
return objc_method | |
return None | |
def get_class_method(self, name): | |
"""Returns a python representation of the named class method, | |
either by looking it up in the cached list of methods or by searching | |
for and creating a new method object.""" | |
if name in self.class_methods: | |
return self.class_methods[name] | |
else: | |
# If method name isn't in the cached list, it might be a method of | |
# the superclass, so call class_getInstanceMethod to check. | |
selector = get_selector(name.replace(b'_', b':')) | |
method = c_void_p(objc.class_getClassMethod(self.ptr, selector)) | |
if method.value: | |
objc_method = ObjCMethod(method) | |
self.class_methods[name] = objc_method | |
return objc_method | |
return None | |
def __getattr__(self, name): | |
"""Returns a callable method object with the given name.""" | |
# If name refers to a class method, then return a callable object | |
# for the class method with self.ptr as hidden first parameter. | |
name = ensure_bytes(name) | |
method = self.get_class_method(name) | |
if method: | |
return ObjCBoundMethod(method, self.ptr) | |
# If name refers to an instance method, then simply return the method. | |
# The caller will need to supply an instance as the first parameter. | |
method = self.get_instance_method(name) | |
if method: | |
return method | |
# Otherwise, raise an exception. | |
raise AttributeError('ObjCClass %s has no attribute %s' % (self.name, name)) | |
###################################################################### | |
class ObjCInstance: | |
"""Python wrapper for an Objective-C instance.""" | |
_cached_objects = {} | |
def __new__(cls, object_ptr): | |
"""Create a new ObjCInstance or return a previously created one | |
for the given object_ptr which should be an Objective-C id.""" | |
# Make sure that object_ptr is wrapped in a c_void_p. | |
if not isinstance(object_ptr, c_void_p): | |
object_ptr = c_void_p(object_ptr) | |
# If given a nil pointer, return None. | |
if not object_ptr.value: | |
return None | |
# Check if we've already created an python ObjCInstance for this | |
# object_ptr id and if so, then return it. A single ObjCInstance will | |
# be created for any object pointer when it is first encountered. | |
# This same ObjCInstance will then persist until the object is | |
# deallocated. | |
if object_ptr.value in cls._cached_objects: | |
return cls._cached_objects[object_ptr.value] | |
# Otherwise, create a new ObjCInstance. | |
objc_instance = super(ObjCInstance, cls).__new__(cls) | |
objc_instance.ptr = object_ptr | |
objc_instance._as_parameter_ = object_ptr | |
# Determine class of this object. | |
class_ptr = c_void_p(objc.object_getClass(object_ptr)) | |
objc_instance.objc_class = ObjCClass(class_ptr) | |
# Store new object in the dictionary of cached objects, keyed | |
# by the (integer) memory address pointed to by the object_ptr. | |
cls._cached_objects[object_ptr.value] = objc_instance | |
# Create a DeallocationObserver and associate it with this object. | |
# When the Objective-C object is deallocated, the observer will remove | |
# the ObjCInstance corresponding to the object from the cached objects | |
# dictionary, effectively destroying the ObjCInstance. | |
observer = send_message(send_message('DeallocationObserver', 'alloc'), 'initWithObject:', objc_instance) | |
objc.objc_setAssociatedObject(objc_instance, observer, observer, 0x301) | |
# The observer is retained by the object we associate it to. We release | |
# the observer now so that it will be deallocated when the associated | |
# object is deallocated. | |
send_message(observer, 'release') | |
return objc_instance | |
def __repr__(self): | |
if self.objc_class.name == b'NSCFString': | |
# Display contents of NSString objects | |
from .cocoalibs import cfstring_to_string | |
string = cfstring_to_string(self) | |
return "<ObjCInstance %#x: %s (%s) at %s>" % (id(self), self.objc_class.name, string, str(self.ptr.value)) | |
return "<ObjCInstance %#x: %s at %s>" % (id(self), self.objc_class.name, str(self.ptr.value)) | |
def __getattr__(self, name): | |
"""Returns a callable method object with the given name.""" | |
# Search for named instance method in the class object and if it | |
# exists, return callable object with self as hidden argument. | |
# Note: you should give self and not self.ptr as a parameter to | |
# ObjCBoundMethod, so that it will be able to keep the ObjCInstance | |
# alive for chained calls like MyClass.alloc().init() where the | |
# object created by alloc() is not assigned to a variable. | |
name = ensure_bytes(name) | |
method = self.objc_class.get_instance_method(name) | |
if method: | |
return ObjCBoundMethod(method, self) | |
# Else, search for class method with given name in the class object. | |
# If it exists, return callable object with a pointer to the class | |
# as a hidden argument. | |
method = self.objc_class.get_class_method(name) | |
if method: | |
return ObjCBoundMethod(method, self.objc_class.ptr) | |
# Otherwise raise an exception. | |
raise AttributeError('ObjCInstance %s has no attribute %s' % (self.objc_class.name, name)) | |
###################################################################### | |
def convert_method_arguments(encoding, args): | |
"""Used by ObjCSubclass to convert Objective-C method arguments to | |
Python values before passing them on to the Python-defined method.""" | |
new_args = [] | |
arg_encodings = parse_type_encoding(encoding)[3:] | |
for e, a in zip(arg_encodings, args): | |
if e == b'@': | |
new_args.append(ObjCInstance(a)) | |
elif e == b'#': | |
new_args.append(ObjCClass(a)) | |
else: | |
new_args.append(a) | |
return new_args | |
# ObjCSubclass is used to define an Objective-C subclass of an existing | |
# class registered with the runtime. When you create an instance of | |
# ObjCSubclass, it registers the new subclass with the Objective-C | |
# runtime and creates a set of function decorators that you can use to | |
# add instance methods or class methods to the subclass. | |
# | |
# Typical usage would be to first create and register the subclass: | |
# | |
# MySubclass = ObjCSubclass('NSObject', 'MySubclassName') | |
# | |
# then add methods with: | |
# | |
# @MySubclass.method('v') | |
# def methodThatReturnsVoid(self): | |
# pass | |
# | |
# @MySubclass.method('Bi') | |
# def boolReturningMethodWithInt_(self, x): | |
# return True | |
# | |
# @MySubclass.classmethod('@') | |
# def classMethodThatReturnsId(self): | |
# return self | |
# | |
# It is probably a good idea to organize the code related to a single | |
# subclass by either putting it in its own module (note that you don't | |
# actually need to expose any of the method names or the ObjCSubclass) | |
# or by bundling it all up inside a python class definition, perhaps | |
# called MySubclassImplementation. | |
# | |
# It is also possible to add Objective-C ivars to the subclass, however | |
# if you do so, you must call the __init__ method with register=False, | |
# and then call the register method after the ivars have been added. | |
# But rather than creating the ivars in Objective-C land, it is easier | |
# to just define python-based instance variables in your subclass's init | |
# method. | |
# | |
# This class is used only to *define* the interface and implementation | |
# of an Objective-C subclass from python. It should not be used in | |
# any other way. If you want a python representation of the resulting | |
# class, create it with ObjCClass. | |
# | |
# Instances are created as a pointer to the objc object by using: | |
# | |
# myinstance = send_message('MySubclassName', 'alloc') | |
# myinstance = send_message(myinstance, 'init') | |
# | |
# or wrapped inside an ObjCInstance object by using: | |
# | |
# myclass = ObjCClass('MySubclassName') | |
# myinstance = myclass.alloc().init() | |
# | |
class ObjCSubclass: | |
"""Use this to create a subclass of an existing Objective-C class. | |
It consists primarily of function decorators which you use to add methods | |
to the subclass.""" | |
def __init__(self, superclass, name, register=True): | |
self._imp_table = {} | |
self.name = name | |
self.objc_cls = create_subclass(superclass, name) | |
self._as_parameter_ = self.objc_cls | |
if register: | |
self.register() | |
def register(self): | |
"""Register the new class with the Objective-C runtime.""" | |
objc.objc_registerClassPair(self.objc_cls) | |
# We can get the metaclass only after the class is registered. | |
self.objc_metaclass = get_metaclass(self.name) | |
def add_ivar(self, varname, vartype): | |
"""Add instance variable named varname to the subclass. | |
varname should be a string. | |
vartype is a ctypes type. | |
The class must be registered AFTER adding instance variables.""" | |
return add_ivar(self.objc_cls, varname, vartype) | |
def add_method(self, method, name, encoding): | |
imp = add_method(self.objc_cls, name, method, encoding) | |
self._imp_table[name] = imp | |
# http://iphonedevelopment.blogspot.com/2008/08/dynamically-adding-class-objects.html | |
def add_class_method(self, method, name, encoding): | |
imp = add_method(self.objc_metaclass, name, method, encoding) | |
self._imp_table[name] = imp | |
def rawmethod(self, encoding): | |
"""Decorator for instance methods without any fancy shenanigans. | |
The function must have the signature f(self, cmd, *args) | |
where both self and cmd are just pointers to objc objects.""" | |
# Add encodings for hidden self and cmd arguments. | |
encoding = ensure_bytes(encoding) | |
typecodes = parse_type_encoding(encoding) | |
typecodes.insert(1, b'@:') | |
encoding = b''.join(typecodes) | |
def decorator(f): | |
name = f.__name__.replace('_', ':') | |
self.add_method(f, name, encoding) | |
return f | |
return decorator | |
def method(self, encoding): | |
"""Function decorator for instance methods.""" | |
# Add encodings for hidden self and cmd arguments. | |
encoding = ensure_bytes(encoding) | |
typecodes = parse_type_encoding(encoding) | |
typecodes.insert(1, b'@:') | |
encoding = b''.join(typecodes) | |
def decorator(f): | |
def objc_method(objc_self, objc_cmd, *args): | |
py_self = ObjCInstance(objc_self) | |
py_self.objc_cmd = objc_cmd | |
args = convert_method_arguments(encoding, args) | |
result = f(py_self, *args) | |
if isinstance(result, ObjCClass): | |
result = result.ptr.value | |
elif isinstance(result, ObjCInstance): | |
result = result.ptr.value | |
return result | |
name = f.__name__.replace('_', ':') | |
self.add_method(objc_method, name, encoding) | |
return objc_method | |
return decorator | |
def classmethod(self, encoding): | |
"""Function decorator for class methods.""" | |
# Add encodings for hidden self and cmd arguments. | |
encoding = ensure_bytes(encoding) | |
typecodes = parse_type_encoding(encoding) | |
typecodes.insert(1, b'@:') | |
encoding = b''.join(typecodes) | |
def decorator(f): | |
def objc_class_method(objc_cls, objc_cmd, *args): | |
py_cls = ObjCClass(objc_cls) | |
py_cls.objc_cmd = objc_cmd | |
args = convert_method_arguments(encoding, args) | |
result = f(py_cls, *args) | |
if isinstance(result, ObjCClass): | |
result = result.ptr.value | |
elif isinstance(result, ObjCInstance): | |
result = result.ptr.value | |
return result | |
name = f.__name__.replace('_', ':') | |
self.add_class_method(objc_class_method, name, encoding) | |
return objc_class_method | |
return decorator | |
###################################################################### | |
# Instances of DeallocationObserver are associated with every | |
# Objective-C object that gets wrapped inside an ObjCInstance. | |
# Their sole purpose is to watch for when the Objective-C object | |
# is deallocated, and then remove the object from the dictionary | |
# of cached ObjCInstance objects kept by the ObjCInstance class. | |
# | |
# The methods of the class defined below are decorated with | |
# rawmethod() instead of method() because DeallocationObservers | |
# are created inside of ObjCInstance's __new__ method and we have | |
# to be careful to not create another ObjCInstance here (which | |
# happens when the usual method decorator turns the self argument | |
# into an ObjCInstance), or else get trapped in an infinite recursion. | |
class DeallocationObserver_Implementation: | |
DeallocationObserver = ObjCSubclass('NSObject', 'DeallocationObserver', register=False) | |
DeallocationObserver.add_ivar('observed_object', c_void_p) | |
DeallocationObserver.register() | |
def initWithObject_(self, cmd, anObject): | |
self = send_super(self, 'init') | |
self = self.value | |
set_instance_variable(self, 'observed_object', anObject, c_void_p) | |
return self | |
def dealloc(self, cmd): | |
anObject = get_instance_variable(self, 'observed_object', c_void_p) | |
ObjCInstance._cached_objects.pop(anObject, None) | |
send_super(self, 'dealloc') | |
def finalize(self, cmd): | |
# Called instead of dealloc if using garbage collection. | |
# (which would have to be explicitly started with | |
# objc_startCollectorThread(), so probably not too much reason | |
# to have this here, but I guess it can't hurt.) | |
anObject = get_instance_variable(self, 'observed_object', c_void_p) | |
ObjCInstance._cached_objects.pop(anObject, None) | |
send_super(self, 'finalize') | |