• shinyformica

    Yeah, IFTTT came up when I was googling around about this. This isn't actually a request for something for myself, but an ask by someone else...and it needs to be extra-simple in UX terms: show button or two buttons, allow button press to signal pythonista or invoke pythonista.

    Unfortunately, Apple seems to have removed the ability to show buttons of that nature on the Apple Watch when they bought Workflows and turned it into Shortcuts, now you can only invoke those via Siri, it seems...otherwise that would have been perfect.

    I think this may be a case where just writing a super simple Swift-based app with a watchOS part to show the buttons is the simplest route.

    posted in Pythonista read more
  • shinyformica

    Wild question from someone who has never written an watchOS app, or done any work using shortcuts: is there a way to display some kind of button or link on an Apple Watch, via some mechanism which will let it tell pythonista to run a script on the phone?

    Just curious if anything like this is possible with the current Pythonista.

    posted in Pythonista read more
  • shinyformica

    In answer to my own question at the end there: that "locations" parameter is not a "const float *", it is actually "const CGFloat *" which can be 32 or 64 bits depending on platform architecture, and therefore a C float is not necessarily compatible...so I needed to explicitly use the CGFloat datatype:

    CGFloat_p = ctypes.POINTER(objc_util.CGFloat)
    CGGradientCreateWithColors = objc_util.c.CGGradientCreateWithColors
    CGGradientCreateWithColors.restype = c_void_p
    CGGradientCreateWithColors.argtypes = [c_void_p, c_void_p, CGFloat_p]
    
    locations = (objc_util.CGFloat * 2)(0.0,1.0)
    gradient = CGGradientCreateWithColors(colorSpace, objc_util.ns(colors),  ctypes.cast(locations, CGFloat_p))
    

    and it works fine. As always, thanks all!

    posted in Pythonista read more
  • shinyformica

    @cvp yep...just realized that must be the reason, since CGFloat can be 32 or 64 bits, so isn't always compatible with a float.
    I just changed things to this:

    CGFloat_p = ctypes.POINTER(objc_util.CGFloat)
    CGGradientCreateWithColors = objc_util.c.CGGradientCreateWithColors
    CGGradientCreateWithColors.restype = c_void_p
    CGGradientCreateWithColors.argtypes = [c_void_p, c_void_p, CGFloat_p]
    
    locations = (objc_util.CGFloat * 2)(0.0,1.0)
    gradient = CGGradientCreateWithColors(colorSpace, objc_util.ns(colors),  ctypes.cast(locations, CGFloat_p))
    

    and of course it works fine now. Should have realized that right away. I'll update the original thread as well.

    posted in Pythonista read more
  • shinyformica

    So this is only tangentially pythonista-related, but I encountered it recently while trying to do some objective-c drawing via CGContext function calls, which required a deeper understanding of calling raw functions from python.

    One of the functions I wanted to call was this one:

    CGGradientRef CGGradientCreateWithColors(CGColorSpaceRef space, CFArrayRef colors, const CGFloat *locations);
    

    So I defined the ctypes function call like this:

    c_float_p = ctypes.POINTER(ctypes.c_float)
    CGGradientCreateWithColors = objc_util.c.CGGradientCreateWithColors
    CGGradientCreateWithColors.restype = c_void_p
    CGGradientCreateWithColors.argtypes = [c_void_p, c_void_p, c_float_p]
    

    Which seemed to match the required types.
    Then I tried to call it using this setup:

    locations = (ctypes.c_float * 2)(0.0,1.0)
    gradient = CGGradientCreateWithColors(colorSpace, objc_util.ns(colors),  ctypes.cast(locations, c_float_p))
    

    and while it doesn't crash, it doesn't seem to be getting the location float values correctly within objc. The result is a gradient with the right colors, but with the wrong interpolation. But I can't find the right way to give the float * array of values to it so that it interprets them properly. So, perhaps someone with better knowledge will be able to point to what I'm doing wrong, either in my definition of the argtypes or in how I generate the values to send. Is it, perhaps that CGFloat is not compatible with c_float?

    posted in Pythonista read more
  • shinyformica

    @mikael, for the moment, I wanted to be able to use the nice gradient drawing functions that CGContext's provide. So, for example, I can draw a nice clean, fast, linear gradient between two colors with:

    UIGraphicsGetCurrentContext = objc_util.c.UIGraphicsGetCurrentContext
    UIGraphicsGetCurrentContext.restype = ctypes.c_void_p
    UIGraphicsGetCurrentContext.argtypes = []
    
    CGColorSpaceCreateDeviceRGB = objc_util.c.CGColorSpaceCreateDeviceRGB
    CGColorSpaceCreateDeviceRGB.restype = c_void_p
    CGColorSpaceCreateDeviceRGB.argtypes = []
    
    CGGradientCreateWithColors = objc_util.c.CGGradientCreateWithColors
    CGGradientCreateWithColors.restype = c_void_p
    CGGradientCreateWithColors.argtypes = [c_void_p, c_void_p,
                                            ctypes.POINTER(ctypes.c_float)]
    
    CGContextDrawLinearGradient = objc_util.c.CGContextDrawLinearGradient
    CGContextDrawLinearGradient.restype = None
    CGContextDrawLinearGradient.argtypes = [c_void_p, c_void_p,
                                            objc_util.CGPoint, objc_util.CGPoint,
                                            objc_util.NSUInteger]
    
    with ui.ImageContext(300,300) as context:
            c = UIGraphicsGetCurrentContext()
            colorSpace = CGColorSpaceCreateDeviceRGB()
    
            start = objc_util.UIColor.colorWithRed_green_blue_alpha_(1.0,0.0,0.0,1.0)
            end = objc_util.UIColor.colorWithRed_green_blue_alpha_(0.0,0.0,1.0,1.0)
            colors = [start.CGColor(), end.CGColor()]
            gradient = CGGradientCreateWithColors(colorSpace, objc_util.ns(colors), None)
            
            CGContextDrawLinearGradient(c, gradient,
                                    objc_util.CGPoint(0,0),
                                    objc_util.CGPoint(300,0),
                                    0)
    

    There's radial gradients as well, of course.

    The above code works fine...but I did encounter one thing which I don't understand, and it's probably just because I don't fully get how python/ctypes/objc type conversion works...
    In the above code, on this line:

    gradient = CGGradientCreateWithColors(colorSpace, objc_util.ns(colors), None)
    

    it should be possible to set that "None" parameter to an array of floats representing the normalized locations of the colors in the gradient. However, the docs say that final parameter is passed in as "const float *" which meant I thought I had to do something like this:

    c_float_p = ctypes.POINTER(ctypes.c_float)
    locations = (ctypes.c_float * 2)(0.0,1.0)
    gradient = CGGradientCreateWithColors(
                            colorSpace, objc_util.ns(colors),
                            ctypes.cast(locations, c_float_p))
    

    which should produce the same result as the previous version...but it doesn't, it ends up making a gradient with the second color coming first, and as a tiny sliver...so I must be mistaken in how I am creating and passing that locations parameter.

    Here's the apple docs on that CGGradientCreateWithColors() function:
    https://developer.apple.com/documentation/coregraphics/1398458-cggradientcreatewithcolors?language=objc

    posted in Pythonista read more
  • shinyformica

    Just for completeness, in case anyone reads through this thread:

    objc_util.c is the right place to access raw functions like this, of course, but as @JonB said, you have to be sure to set the right argtypes/restype for the function in order to call it successfully.

    So, for example, for my attempt to call UIGraphicsGetCurrentContext(), I needed:

    UIGraphicsGetCurrentContext = objc_util.c.UIGraphicsGetCurrentContext
    UIGraphicsGetCurrentContext.restype = ctypes.c_void_p
    UIGraphicsGetCurrentContext.argtypes = []

    which ensures that the function will return a reference to a C struct (in this case a CGContextRef), which will be a c_void_p type. The function takes no arguments, so an empty list is fine for argtypes.

    Now, to use that context reference to draw something in a pythonista ui.ImageContext, I can do something like:

    import ui
    import objc_util
    import ctypes
    
    UIGraphicsGetCurrentContext = objc_util.c.UIGraphicsGetCurrentContext
    UIGraphicsGetCurrentContext.restype = ctypes.c_void_p
    UIGraphicsGetCurrentContext.argtypes = []
    
    CGContextFillRect = objc_util.c.CGContextFillRect
    CGContextFillRect.restype = None
    CGContextFillRect.argtypes = [ctypes.c_void_p, objc_util.CGRect]
    
    def drawWithGraphicsContext():
        image = None
        with ui.ImageContext(300,300) as context:
            path = ui.Path.rect(10,10,280,280)
            path.line_width = 1
            path.stroke()
            c = UIGraphicsGetCurrentContext()
            r = objc_util.CGRect(objc_util.CGPoint(0,0),objc_util.CGSize(200,200))
            CGContextFillRect(c, r)
            image = context.get_image()
        return image
    
    m = drawWithGraphicsContext()
    m.show()
    

    and the lovely intermixing of pythonista and objc calls works fine.

    posted in Pythonista read more
  • shinyformica

    @JonB nevermind, could've just googled it first. C Foreign Function Interface...cool stuff.

    posted in Pythonista read more
  • shinyformica

    Thanks, that's what I was looking for.

    What is "cffi", exactly? A command-line to generate the info from the header somehow?

    posted in Pythonista read more
  • shinyformica

    I have a need to do some drawing using some of the abilities that the objective-c CGContext api has available to it, but I am having trouble figuring out how to be able to make calls to the various CGContext-related functions.

    I found this old thread, which does some things with a graphics context:

    https://forum.omz-software.com/topic/2327/uiimagejpegrepresentation/1

    But it doesn't illustrate very clearly how it manages to successfully call functions like:

    UIGraphicsGetCurrentContext()
    
    UIGraphicsGetImageFromCurrentImageContext()
    

    It seems like I can call:

    context = objc_util.c.UIGraphicsGetCurrentContext()
    

    but if I try to use that context ref value in any CGContext draw function, like

    objc_util.c.CGContextFillRect(uicontext, r)
    

    it crashes hard. I was pretty sure that was invalid...but I'm not clear on how to call plain objective-c functions like this. There's examples of more complex creation/calling of functions in that thread I linked to above:

    def CGBitmapContextCreate(baseAddress, width, height, param_0, bytesPerRow, colorSpace, flags):
        func = c.CGBitmapContextCreate
        func.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_void_p, ctypes.c_int32]
        func.restype = ctypes.c_void_p
        print ObjCInstance(colorSpace).ptr
        result = func(baseAddress, width, height, param_0, bytesPerRow, ObjCInstance(colorSpace).ptr, flags)
        print result
        if result is not None:
            return result
        else:
            raise RuntimeError('Failed to create context')
    

    But I'm not clear on when/how/why you use that sort of setup.

    posted in Pythonista read more

Internal error.

Oops! Looks like something went wrong!