• 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
        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
    def thing_callback(number: NSInteger) -> None:
    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

Internal error.

Oops! Looks like something went wrong!