• 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
  • dgelessus

    From an Objective-C perspective, CFURLRef is basically identical to NSURL *, because of "toll-free bridging" between Core Foundation and Objective-C/Foundation. (This also applies to other CF types, like CFStringRef/NSString *, CFArrayRef/NSArray *, etc.) So you should be able to create a NSURL from a string normally (using objc_util.nsurl) and pass the ObjCInstance into the parameter that expects a CFURLRef.

    posted in Pythonista read more
  • dgelessus

    @mikael I'm not very creative with these things - do you have some example code based on objc_util that I could translate to Rubicon?

    In general the syntax for classes looks like this:

    from rubicon.objc import NSInteger, NSObject, ObjCProtocol, objc_method
    
    WhateverDelegate = ObjCProtocol("WhateverDelegate")
    
    class MyCustomDelegate(NSObject, protocols=[WhateverDelegate]):
        # the protocols=[...] part is optional of course, if you don't need to implement any protocol
        @objc_method
        def someThing_didAThingWithCount_(self, something, count: NSInteger) -> None:
            print(something, count)
    
    something.delegate = MyCustomDelegate.new()
    

    The Python 3 type annotation syntax is used to set each method's return and argument types. You use regular ctypes type objects for the types, and Rubicon predefines some common typedefs like NSInteger. Anything you don't annotate is assumed to be an Objective-C object (ObjCInstance).

    Blocks are similar:

    from rubicon.objc import Block, NSInteger
    
    @Block
    def thing_callback(number: NSInteger) -> None:
        print(number)
    
    whatever.doThing("abc", withCallback=callback)
    

    posted in Pythonista read more
  • dgelessus

    There should be no problems with using both Rubicon and objc_util at the same time, they don't interfere with each other. As usual, there's no proper compatibility between the two libraries, so for example to convert between objc_util.ObjCInstance and rubicon.objc.ObjCInstance, you have to cast to c_void_p in between. Anything on the ObjC side will interact properly though, so you can define an ObjC class using Rubicon and then look it up using objc_util's ObjCClass.

    I'd say the main advantages of Rubicon right now are really what @JonB has already said above: the class definition syntax (it's just much more convenient to use the standard Python syntax) and the improved type handling (almost all type encodings are predefined or handled automatically, and when you do need custom structure types, they only need to be "registered" once instead of having to set restype/argtypes on every call). There are also a few other features that are not particularly exciting individually, but they are nice to have:

    • Slightly better Python interaction for standard ObjC types. Like with objc_util you can use obj[key] for NSArray and NSDictionary, but you also get Python-style methods (append, extend, etc. for NSArray and items, values, etc. for NSDictionary). NSString also gets operators/methods similar to Python str, so you often don't need to explicitly convert NSStrings to/from str.
    • isinstance and issubclass work with ObjC classes - you can do isinstance(thing, NSString) instead of thing.isKindOfClass(NSString).
    • Class definitions can have properties and ivars (though you don't need this very often in practice).
    • You can define custom protocols, just like classes (though you don't need this very often in practice, either).
    • Calling low-level ObjC runtime functions is more convenient - there are proper classes for objc_id, Class, SEL, etc. instead of just c_void_p.
    • You can return structs from ctypes callbacks (CFUNCTYPE) and from things that use callbacks internally (custom ObjC methods and blocks), which is normally not possible because of a ctypes limitation.
    • Rubicon is not specific to Pythonista, so Rubicon-based code can be reused in Pyto (which has Rubicon bundled as its objc_util equivalent) or even in a standalone app.

    Of course there's also the disadvantage that Rubicon is not installed by default in Pythonista, so to run Rubicon-based code you need to install Rubicon first (but this should be easy to do using Stash pip, because Rubicon is on PyPI).

    posted in Pythonista read more
  • dgelessus

    @mikael Rubicon runs without changes on Pythonista, as far as I know. The main use case for Rubicon is writing Python-based apps using BeeWare's Python-Apple-support, but the Python environment from that build is very similar to Pythonista (I think Pythonista even uses some of the patches from that repo), so Rubicon generally works in Pythonista just as it would in a standalone app.

    If any parts of Rubicon don't work in Pythonista, please open an issue on the Rubicon repo. Although Pythonista isn't officially supported, I try to keep it as compatible as possible (Pythonista is my usual environment for testing/debugging Rubicon on iOS). Also, nearly all Rubicon bugs that appear in Pythonista also affect standalone iOS environments, so fixing them in Rubicon is better for everyone.

    Though I want to point out a few things that you should know if you want to switch to Rubicon:

    • Rubicon's API is not directly compatible with objc_util, so you can't just do import rubicon.objc as objc_util in your existing code. The API isn't that different though, for the most part you only need to change some function names and rewrite classes to the new syntax.
      • You could probably even write a "compatibility module" that has the same API as objc_util, but calls Rubicon internally. I wanted to try that at some point, but haven't gotten around to it.
    • Rubicon doesn't integrate with other Pythonista modules like objc_util does. ui.View().objc_instance will still return an ObjCInstance from objc_util, and with Rubicon you can't do ObjCInstance(ui.View()) (you have to write ObjCInstance(ui.View()._objc_ptr) instead).
      • This could probably be added at runtime using monkey-patching though.
    • Rubicon isn't very convenient to use in the REPL yet, you don't get code completion suggestions for ObjC methods (I think there's no proper introspection support at all right now). So at the moment objc_util is still better for exploring and debugging interactively.
    • The documentation is not quite complete yet, though I have a work in progress PR that significantly expands the docs. I really need to finish that at some point.

    posted in Pythonista read more
  • dgelessus

    @pulbrich FYI, this is a spam thread. The spambot copied the text from an old forum thread (https://forum.omz-software.com/topic/3789/keyboard-issues) and added some spam links in the middle.

    The first reply (the one above yours) is also very likely another spammer that is trying to give the thread more visibility. The account that made the reply is new and hasn't made any other posts on the forum, and the reply text is extremely generic (it doesn't mention any specific details about the problem - this is so that the same message can be copied into threads about different topics).

    There's sadly not much that we can do about this as normal forum users. I've already reported both posts, but @omz is the only admin/moderator on the forum and he isn't very active currently, so these spam threads often take a few days to get deleted.

    posted in Editorial read more
  • dgelessus

    There are also a couple of CFFI-based libraries that I wrote, which have been linked in the other post. It seems that I never made a proper post on the forums about them, so I'll do that here:

    The first one is cffipp (found in my pythonista-c-utils repo), which adds a proper C preprocessor to CFFI. By default CFFI's C parsing understands almost no C preprocessing features (only simple constant #defines are supported). This library adds support for all important C preprocessor directives and features, which makes it possible to load standard iOS headers into CFFI (as long as they are pure C, such as Core Foundation). The repo includes patched versions of a few headers, to make them compatible with CFFI and improve parsing performance.

    Unfortunately, cffipp is now quite outdated - I wrote it three years ago based on CFFI 1.6 and the headers for iOS 9. The CFFI and header patches include a lot of copied code, so updating it to newer CFFI and iOS SDK versions would take a bit of work. If anyone is interested in using this, let me know, I might be able to get it updated.

    The second library is objc-cffi, which is basically a CFFI-based version of objc_util with a few extra convenience features. This library is fairly incomplete - although it has a few features that objc_util doesn't (introspection, better automatic type detection for methods) it's missing some very important features for practical use (class definitions, block support, collection syntax).

    I also found out quite soon that CFFI is not well suited as the base for this library. To interact with the Objective-C runtime I need to create a lot of types dynamically, which is very slow with CFFI, because every type needs to be constructed as C source code and then parsed by CFFI. I also ran into name collision issues, because every FFI object has a single namespace and does not allow multiple definitions (the Objective-C runtime sometimes returns different type information for the same type, depending on context).

    Around this time I also started contributing to rubicon-objc, which has also been mentioned a few times on this forum - it's basically objc_util, but not Pythonista-specific (also works on Mac and other platforms with Objective-C). I've ported most of objc-cffi's convenience features over to rubicon-objc now, and I'm still working on improving rubicon-objc, so I would definitely recommend that over objc-cffi.

    posted in Pythonista read more
  • dgelessus

    This is in response to the questions/replies from @cvp and @JonB in https://forum.omz-software.com/topic/5660/install-pymunk-on-pythonista-get-cffi-error about how to use CFFI in Pythonista. I'm putting it in a separate thread since it's not really relevant to the original question (getting a specific library working in Pythonista).

    For context: CFFI is a library for calling C code from Python. It has a similar purpose as ctypes, with one big difference being that CFFI lets you write type and function declarations using standard C syntax, without having to "translate" them to another syntax/API first as with ctypes.

    By default, CFFI uses its own custom C extension to call C code, and also has various features that make use of a proper C preprocessor/compiler. Of course this doesn't work by default on Pythonista, since there is no C toolchain and you can't compile or load custom extension modules. However, in addition to the default C extension backend, CFFI also includes a ctypes-based pure-Python backend (cffi.backend_ctypes.CTypesBackend), which works in Pythonista, with no modifications needed to CFFI itself.

    The installation process is very straightforward: you download the source and copy the cffi package into site-packages, as with any other module. We can safely ignore the C parts, since we don't use them. (You can probably also install CFFI via Stash, but I haven't tried it.) Once it's installed, you can create an FFI object with the ctypes backend like this:

    import cffi.backend_ctypes
    
    ffi = cffi.FFI(backend=cffi.backend_ctypes.CTypesBackend())
    

    After that, you can use the ffi object as you normally would - see the CFFI docs. Note that the docs sometimes refer to different modes of operation for CFFI (ABI/API and in-line/out-of-line). Only in-line ABI mode can be used with the ctypes backend in Pythonista, all other modes require a working C compiler.

    Unfortunately, this often doesn't help with getting CFFI-based libraries to work on Pythonista - in many cases these libraries rely on third-party or custom C code that isn't available in Pythonista. CFFI is simply the interface between Python and C, so even if you have CFFI working it won't be of any use without the C libraries that you want to call.

    That's not to say that CFFI is of no use in Pythonista. Almost everything that you can do with ctypes can also be done with CFFI, and usually with a much nicer and more convenient API, which makes it very useful for calling C APIs (Unix/iOS and CPython).

    posted in Pythonista read more
  • dgelessus

    @techyogi The current release of Pythonista (version 3.2) includes Python 3.6.

    posted in Pythonista read more
  • dgelessus

    @T451f The bottom row of Pythonista's extended keyboard uses the system standard shortcut bar (where in other apps you have buttons for undo, redo, cut, copy, paste). Check in your iOS keyboard settings (in the Settings app, not in Pythonista) that you have the shortcut bar enabled.

    @cvp I think the second keyboard row is only available on old iOS versions (iOS 10 and below IIRC) because of technical reasons. If your iOS version is newer than that, you can't use the second keyboard row.

    posted in Pythonista read more
Internal error.

Oops! Looks like something went wrong!