• 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

    posted in Pythonista read more
  • 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.

    posted in Pythonista read more
  • 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 a self) 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.

    posted in Pythonista read more
  • dgelessus

    @omz This is probably a spam post... I've seen multiple new accounts (with no other posts) post this exact message, sometimes in other obvious spam threads.

    posted in General Discussion read more
  • dgelessus

    If I'm reading the error message correctly, rubicon.objc is installed properly. The error about ctypes.util/ctypes.macholib is a known issue in Pythonista: https://github.com/omz/Pythonista-Issues/issues/311

    Sadly 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

    posted in Pythonista read more
  • dgelessus

    The second argument of dispatch_async needs to be a block object. You can't pass in a normal Python function, because ctypes 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 the ObjCBlock section of the objc_util documentation.

    By the way, dispatch_async doesn't return anything, so you should set its restype to None and not c_void_p. (If you use c_void_p it will probably still work, but the return value won't be anything useful.)

    posted in Pythonista read more
  • dgelessus

    dispatch_get_main_queue is not a normal C function, so you can't access it normally via ctypes/objc_util. Instead, you need to reproduce dispatch_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.)

    posted in Pythonista read more
  • 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 treats ConstantName 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 the NSString object), but as a c_void_p object, so you need to cast it to ObjCInstance manually so that you can use it. Once you do that, you have a regular NSString 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.)

    posted in Pythonista read more
  • 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.

    posted in Pythonista read more

Internal error.

Oops! Looks like something went wrong!