Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Beta Build 160008
-
I thought I'd start a new thread for this build, the old one is getting a bit unwieldy...
As promised in the release notes, here are a few examples of what you can do with the
ctypes
module... Be warned: This is pretty advanced stuff – it's very easy to shoot yourself in the foot with this, just like with plain old C... And again: I haven't been able to getctypes
fully working in 64-bit mode, but the examples here should be relatively "future-proof" (I've been able to get them to work in a 64-bit build).# coding: utf-8 # Some experimental `ctypes` demos for Pythonista on iOS from ctypes import c_void_p, c_char_p, c_double, c_float, cdll, util import os # Load Objective-C runtime: objc = cdll.LoadLibrary(util.find_library('objc')) objc.sel_getName.restype = c_char_p objc.sel_getName.argtypes = [c_void_p] objc.sel_registerName.restype = c_void_p objc.sel_registerName.argtypes = [c_char_p] objc.objc_getClass.argtypes = [c_char_p] objc.objc_getClass.restype = c_void_p # Some helper methods: def obj_to_str(obj): objc.objc_msgSend.argtypes = [c_void_p, c_void_p] objc.objc_msgSend.restype = c_void_p desc = objc.objc_msgSend(obj, objc.sel_registerName('description')) objc.objc_msgSend.argtypes = [c_void_p, c_void_p] objc.objc_msgSend.restype = c_char_p return objc.objc_msgSend(desc, objc.sel_registerName('UTF8String')) def msg(obj, restype, sel, argtypes=None, *args): if argtypes is None: argtypes = [] objc.objc_msgSend.argtypes = [c_void_p, c_void_p] + argtypes objc.objc_msgSend.restype = restype res = objc.objc_msgSend(obj, objc.sel_registerName(sel), *args) return res def cls(cls_name): return objc.objc_getClass(cls_name) def nsstr(s): return msg(cls('NSString'), c_void_p, 'stringWithUTF8String:', [c_char_p], s) # Demo: def print_ipod_title(): MPMusicPlayerController = cls('MPMusicPlayerController') player = msg(MPMusicPlayerController, c_void_p, 'iPodMusicPlayer') item = msg(player, c_void_p, 'nowPlayingItem') if item: artist = msg(item, c_void_p, 'valueForProperty:', [c_void_p], nsstr('artist')) title = msg(item, c_void_p, 'valueForProperty:', [c_void_p], nsstr('title')) print 'Now Playing: %s -- %s' % (obj_to_str(artist), obj_to_str (title)) else: print 'iPod not playing' def set_screen_brightness(value): UIScreen = cls('UIScreen') main_screen = msg(UIScreen, c_void_p, 'mainScreen') msg(main_screen, None, 'setBrightness:', [c_float], value) def save_video(video_file): uikit = cdll.LoadLibrary(util.find_library('UIKit')) save_func = uikit.UISaveVideoAtPathToSavedPhotosAlbum save_func.argtypes = [c_void_p] * 4 save_func(nsstr(os.path.abspath(video_file)), None, None, None) def download_and_save_video(): # Download a short sample file from Apple: # http://support.apple.com/en-us/HT201549 if not os.path.exists('sample_mpeg4.mp4'): print 'Downloading test video...' import urllib, zipfile urllib.urlretrieve('http://a1408.g.akamai.net/5/1408/1388/2005110405/1a1a1ad948be278cff2d96046ad90768d848b41947aa1986/sample_mpeg4.mp4.zip', 'temp.zip') zipfile.ZipFile('temp.zip').extractall() os.remove('temp.zip') print 'Saving video...' save_video('sample_mpeg4.mp4') print 'Done' if __name__ == '__main__': from time import sleep print 'Printing currently playing music (iPod/Music app only)...' print_ipod_title() sleep(1) print 'Downloading a video, and saving it to the camera roll...' download_and_save_video() print 'Making screen very bright...' sleep(1) set_screen_brightness(1.0) print '...very dark...' sleep(1) set_screen_brightness(0.0) print '...and something in-between.' sleep(1) set_screen_brightness(0.5)
While probably not terribly useful, these are all things you couldn't do before with Pythonista. :)
As I said – don't get too excited, there are a lot of things you can't do with this... Because of the 64-bit issues, anything that would require a callback is impossible (including things like defining new ObjC classes etc.), and it's also not possible (to my knowledge) to use any block-based APIs, which includes a lot of stuff in the more recent iOS SDKs...
-
Wow, this is cool! And Apple approves of this? Hopefully... But this is really cool! Does this kind of built a base to also load other kinds of 'modules', like requested a few times?
-
And Apple approves of this?
No idea, but hopefully, I'll find out soon...
Does this kind of built a base to also load other kinds of 'modules', like requested a few times?
Not really, you can basically just use this to call functions in the frameworks/libraries that are already linked with Pythonista. Anything else wouldn't work because of code signing/sandboxing.
-
In the unlikely case that Apple doesn't allow this in the Pythonista app would this at least be do-able in the snapshot?
-
So if I wanted to try and get some basic CoreMIDI access going this might be the way to go if you have included that framework into Pythonista. As one example - would it be possible to write the cb module using this capability? For CoreMIDI I can find plenty of C Python bindings examples so I suspect it may be straightforward to rewrite one of those using this method of interface.
-
That looks like a powerful thing indeed. Too bad I can barely read and understand, let alone write, C code. And Objective-C even less. Or any of the iOS SDKs for that matter. I know, it's not really code, just a direct interface to (Objective-)C datatypes/objects, but I still have no idea how to correctly use most of this :P
(Let's hope the app review team is sleepy enough when approving this update.)
-
Here's another fun example:
(Note: You'll get an iOS permission dialog about using the microphone when you run this; it obviously won't work if you don't give permission, and there isn't really any error handling...)
-
@omz Really hope this makes it. Could use an audio recorder feature like this (or maybe turn it into its own module?)
-
Midi?
-
import qrcode, sys url = ' '.join(sys.argv[1:]) or 'http://omz-software.com/pythonista/docs' qrcode.make(url).show()
-
@omz Very interesting indeed. I'm a bit curious as to what the 64-bit issue has to do with callbacks not being possible. I understand there are hurdles, but I thought they were a little different.
The obvious limitation on iOS is that you are not allowed to execute dynamically generated machine code. If that is how ctypes.CFUNCTYPE works, it cannot be used directly. But there are workarounds. For an arbitrary pure C callback, one may need to resort to tricks like defining lots of trampoline function slots. But in most cases (all?) in Objective-C, that won't be needed.
As you know, often in Cocoa you don't specify a C function pointer at all, but instead an Objective-C object and a selector. That is easily mapped to Python. You just need to supply an Objective-C wrapper class. And blocks, as you mentioned, are just a special case of this. A tiny bit of googling should tell you how. (Or start here.)
-
@omz you mentioned the possibility of symlink being added. Any update?
-
@briarfox Should be fixed in this build, forgot to mention.
-
@omz Long touch on clear button no longer allows for restart of pythonista
-
Long touch on clear button no longer allows for restart of pythonista
Yes, I intentionally removed that. Kinda hoped no one would notice. ;)
The problem was that on iOS 8, the notification that allows you to restart the app requires explicit user consent, and I didn't really want to have a "do you want to allow Pythonista to send push notifications?" dialog just for this... You could replicate the functionality with an action menu script that uses the notification module though... (and quit the app with
os._exit(0)
). -
@omz - that Audio Recorder sample is great. I would like to hear how you wrote that. Did you start from an Apple developer sample objc sample and convert to this?
Could you explain what parts of the frameworks are (will be) available? Is it only what you have specifically used within the Pythonista App itself or is everything available by loading dynamic libraries that are just out there (doubtful).
I was just looking at another project that has a few examples of ways to browse classes and such using ctypes : https://code.google.com/p/ios-python-objc/ - apparently written because PyObjC was broken??
-
Thanks omz, Twisted now works :)
-
In the unlikely case that Apple doesn't allow this in the Pythonista app would this at least be do-able in the snapshot?
You mean the Xcode template? That should actually work already (it's the primary reason why the template doesn't support the 64-bit simulator right now).
So if I wanted to try and get some basic CoreMIDI access going this might be the way to go if you have included that framework into Pythonista.
Theoretically yes, though I'm not currently linking against CoreMIDI – and there's another problem:
MIDISourceCreate
andMIDIDestinationCreate
require that the app declares the 'audio' background mode, which is a build-time thing that you can't modify after the app is installed... While declaring this would actually be useful for other things, I'm not sure if I could get the app through review with background audio enabled because it could be abused for all sorts of things (e.g. running in the background forever while looping a silent track...). I don't know, maybe I'll have to integrate a music player to have an official reason. ;)As one example - would it be possible to write the cb module using this capability?
In principle, maybe, though
CoreBluetooth
makes heavy use of blocks (which you can roughly think of as the C equivalent of lambdas), and I couldn't get those work withctypes
... Bluetooth support also requires some build-time settings (keys in the Info.plist) to work in the background.that Audio Recorder sample is great. I would like to hear how you wrote that. Did you start from an Apple developer sample objc sample and convert to this?
I basically went through the Objective-C runtime library documentation, built a few convenience wrappers to make the code a little less verbose (
msg
,cls
etc., already used in the other examples, and a few others to wrapNSNumber
andNSURL
), and then looked at a basic tutorial of how to useAVAudioRecorder
(can't remember which one)... I know Objective-C fairly well, so it was mostly a matter of translating the Objective-C syntax to the lower-level form I needed to work with the runtime library directly... -
Very interesting indeed. I'm a bit curious as to what the 64-bit issue has to do with callbacks not being possible. I understand there are hurdles, but I thought they were a little different.
The obvious limitation on iOS is that you are not allowed to execute dynamically generated machine code. If that is how ctypes.CFUNCTYPE works, it cannot be used directly. But there are workarounds. For an arbitrary pure C callback, one may need to resort to tricks like defining lots of trampoline function slots.
ctypes
is based on libffi, and from my (limited) understanding, this is how the ARM versions of the library work. ARM64 support has only been added to libffi quite recently, and in my testing, it always crashed when trying to useCFUNCTYPE
and friends... I haven't found a lot of information about this (there was a bug report on a mailing list somewhere that sounded related, but I can't find it anymore). It could very well be that I'm doing something wrong, but it also doesn't seem unlikely that the ARM64 support in libffi just isn't very robust yet.But in most cases (all?) in Objective-C, that won't be needed.
As you know, often in Cocoa you don't specify a C function pointer at all, but instead an Objective-C object and a selector. That is easily mapped to Python. You just need to supply an Objective-C wrapper class. And blocks, as you mentioned, are just a special case of this. A tiny bit of googling should tell you how. (Or start here.)
Well, yes, when you write actual Objective-C, you'll see very few function pointers, but if you want to do things like creating a new Objective-C class dynamically at runtime, you actually do need them (for things like
class_addMethod()
...).It might very well be possible to work around these issues by generating wrapper classes/functions etc., but I haven't looked into that very much yet.
-
@omz - I have been scrounging around for more educational info on how to use ctypes to interface to objc and ran into this project: http://pybee.org
They have a module called rubicon for Java and Objective C. Rubicon-ObjC is a bridge between the Objective C libraries and Python. It has been tested on both desktop/laptops, and on iOS devices.
It is basically a very well fleshed out set of wrappers that looks very much like the code you are writing in your tests and demos but completely generalized. The amount of code is huge! It is handling all kinds of hairy stuff like object reference counting and being able to dynamically add methods to classes.
It enables you to use Python to instantiate objects defined in Objective C, use Python to invoke methods on objects defined in Objective C, and subclass and extend Objective C classes in Python. It also includes wrappers for some of the some key data types from the Core Foundation framework (e.g., NSString and NSObject).
There seems to be support both 32 and 64 bit usage that you can see in the "CoreFoundation" module.
Other goodies include templates for building app that include Python and bunch of visual tools like a debugger that look very interesting.
Seems to be very well supported and actively being worked on. I downloaded it and was able to use it to list classes methods and ivars so it looks like it works. I have not found an example of it being used for block code or even just delegates or callbacks.
This group looks to be doing a lot of interesting things that are very synergistic with Pythonista.