
-
dgelessus
For reference, @JonB has opened an issue for this feature on the rubicon.objc issue tracker: https://github.com/beeware/rubicon-objc/issues/181
-
dgelessus
Sorry for the late reply - I forgot to check the forum recently... The version of ctypes_patch that @JonB linked to is the original version from the pull request that added it. There have been some updates to it that fix a few problems, including the "The ctype {} already has a getfunc" error that @ryubai posted above. I would recommend using the current version of ctypes_patch from the repo and not the old one from the pull request.
-
dgelessus
I'm also not aware of any existing project that allows calling Swift from Python. As others have said above, the other direction (calling Python from Swift) is quite well-supported, but that doesn't help us much here.
In principle, it is entirely possible to call Swift from Python. It would require more work than with Objective-C though.
Objective-C's calling conventions are completely compatible with C, which allows using the standard
ctypes
library to call Objective-C code. The Objective-C API also exposes a lot of information at runtime, which allows inferring method return/argument types so you don't have to set them manually in most cases.With Swift the situation is quite different. As far as I know, Swift itself does not provide nearly as many runtime API functions as Objective-C does, so there is no easy way to get function/method type information at runtime. Swift might not even store this type information in the compiled binaries at all (I haven't checked recently).
There's also the more fundamental problem that Swift's type system and calling convention are completely different from C. In some simple cases the calling conventions overlap enough that you can call some Swift functions using
ctypes
, but IIRC all instance methods (anything with aself
) and functions/methods with certain complex return types (tuples, etc.) are incompatible with the C calling convention.This means that calling Swift from Python would require a custom extension module similar to
ctypes
, but for the Swift calling convention. Internally,ctypes
uses the libffi library, but libffi does not support the Swift ABI (yet?). In any case, since a custom native module is required, a library like this wouldn't be usable in Pythonista unless @omz compiles and includes it in the app.TLDR: It would be possible, but would require quite a bit of work, and couldn't be developed in Pythonista.
-
dgelessus
If I'm reading the error message correctly,
rubicon.objc
is installed properly. The error aboutctypes.util
/ctypes.macholib
is a known issue in Pythonista: https://github.com/omz/Pythonista-Issues/issues/311Sadly it hasn't been fixed yet, but there is a workaround - you can copy the missing files from the CPython source code. See this comment on the issue for instructions: https://github.com/omz/Pythonista-Issues/issues/311#issuecomment-398715503
-
dgelessus
The second argument of
dispatch_async
needs to be a block object. You can't pass in a normal Python function, becausectypes
won't know how to convert it. Instead, you have to create the block object manually:def block_code(_self): print("ok") block = ObjCBlock(block_code, None, [c_void_p]) dispatch_async(dispatch_get_main_queue(), byref(block._as_parameter_))
For more details about how
ObjCBlock
works, you can look at theObjCBlock
section of theobjc_util
documentation.By the way,
dispatch_async
doesn't return anything, so you should set itsrestype
toNone
and notc_void_p
. (If you usec_void_p
it will probably still work, but the return value won't be anything useful.) -
dgelessus
dispatch_get_main_queue
is not a normal C function, so you can't access it normally viactypes
/objc_util
. Instead, you need to reproducedispatch_get_main_queue
's functionality in Python using some custom code:from ctypes import Structure, byref, cast, c_void_p from objc_util import ObjCInstance, c class struct_dispatch_queue_s(Structure): pass _dispatch_main_q = struct_dispatch_queue_s.in_dll(c, "_dispatch_main_q") def dispatch_get_main_queue(): return ObjCInstance(cast(byref(_dispatch_main_q), c_void_p))
You don't need to understand what this code does exactly - you can just copy-paste it into your code and use
dispatch_get_main_queue()
like you would normally.(If anyone is interested in the exact details of what the above code does and where it comes from, see this page. The original C code is very complex, so you need to be familiar with both
ctypes
and the C preprocessor to understand all the translation steps. But again, you don't have to understand all of this - you can just copy-paste and use the finished Python code.) -
dgelessus
@shinyformica Correct - these string constants are actually global variables of type
NSString *
, which means that they contain a pointer to the actual string object.To read the pointer from the global variable, you need to use
ctypes.c_void_p.in_dll(objc_util.c, "ConstantName")
, as @mikael already found above.objc_util.c.ConstantName
will "work" (it doesn't crash), but that treatsConstantName
as a function and not a variable, so it returns a function pointer (which will crash when you try to call it, because normal variables are not valid functions).The value returned by
in_dll
is a regular Objective-C object pointer (which points to theNSString
object), but as ac_void_p
object, so you need to cast it toObjCInstance
manually so that you can use it. Once you do that, you have a regularNSString
object with the value you're looking for.In short:
>>> objc_util.ObjCInstance(ctypes.c_void_p.in_dll(objc_util.c, 'UICollectionElementKindSectionHeader')) <b'__NSCFConstantString': UICollectionElementKindSectionHeader> >>> objc_util.ObjCInstance(ctypes.c_void_p.in_dll(objc_util.c, 'UICollectionElementKindSectionFooter')) <b'__NSCFConstantString': UICollectionElementKindSectionFooter>
Strangely, the value of both string constants is literally their name, so the approach suggested by @mikael (just pass the name of the constant as a string) should have worked. Maybe UIKit expects you to pass in the exact pointer, and not just any string with the same contents? (That would be quite strange though.)
(By the way, please do @-mention me if there's something where you think I can help. Sometimes I only skim the forums very quickly, so I might miss some questions by accident.)
-
dgelessus
This is probably not possible.
In general, iOS does not allow one app to control another app. Apps can provide Siri shortcuts or a URL scheme to allow limited control by other apps, but this has to be specifically supported by the app, and the developer decides what actions can be triggered by other apps.
If the Sonos app provides Siri shortcuts or a URL scheme to control music playback, you can use that, otherwise you're out of luck. It doesn't make a difference whether you use Pythonista or the Shortcuts app to do the automation - both apps only have access to the control endpoints provided by the other app, and nothing more.