Python and AppleEvents on Mac OS X Donovan Preston dp@ulaluma.com
Survey • How many people have heard of Python? • How many people have used Python? • How many people have heard of AppleScript? • How many people have used AppleScript? • How many people have heard of AppleEvents? • How many people have used AppleEvents? • Did you think this talk was cancelled?
What is Python? • High level scripting language • Object oriented • Written in C • Very easy to extend in C or C++ • Python makes a very good glue language • Dynamically Typed • The type of a variable doesn't matter, but objects have a type
What are AppleEvents? • Language-independent binary IPC protocol • Applications expose: • Verbs • Objects (Components) • Properties • AppleEvent data structures are created and manipulated using a C API • Events are posted to an app's event queue • Application handles event, constructs reply
Language-Independent?!? • OSA: The Open Scripting Architecture • AppleEvents are the low-level description of what should occur and what is occurring • Applications provide a “Dictionary” mapping between low-level binary codes and english • 'odoc': open • 'clos': close • 'crel': make • High-level languages use the Dictionary (also called Terminology) to map between low-level data structures and high-level syntax
Python-C Glue • At the C API Level, AppleEvent data structures are populated mostly with 32-bit integers • 4 characters fit in a 32-bit integer, thus all the Four Character Codes used throughout AE • The MacPython distribution comes with the extension module “Carbon.AppleEvents” • This module wraps all C functions required to create and send AppleEvents • Using it is tedious • We want to write Python, not C!
Carbon.AppleEvents Example from Carbon import AppleEvents from Carbon import AE ## Create an AEDesc describing the Application we ## want to target, the Finder target = AE.AECreateDesc(AppleEvents.typeApplSignature, 'MACS') ## Create an AEDesc describing the event we want to send, noop noop = AE.AECreateAppleEvent( 'ascr', 'noop', target, AppleEvents.kAutoGenerateReturnID, AppleEvents.kAnyTransactionID) ## Send the event! noop.AESend(AppleEvents.kAEWaitReply, AppleEvents.kAENormalPriority, AppleEvents.kAEDefaultTimeout)
aepack, aetools, aetypes • Carbon.AppleEvents is too low level • The MacPython distribution comes with three Pure-Python modules which make creating and sending events easier aepack aetools aetypes Conversion Creation, to/from packing, Python classes Python Types sending, representing AE (str, int, list, etc) unpacking Types. events
ae* Example import aetools, aetypes, aepack ## Send the "activate" to the Finder finder = aetools.TalkTo('MACS') finder.send('misc', 'actv') ## Request the name property from document 1 of OmniGraffle omnigraffle = aetools.TalkTo('OGfl') aedesc, reply, params = omnigraffle.send( 'core', 'getd', {'----': aetypes.Property( 'pnam', fr=aetypes.SelectableItem( 'docu', 1) )} ) ## Print the result print reply['----']
Still Too Low-Level! • Using the ae* modules shortens the code significantly • But we want to use nice-looking Python objects and methods • NOT Four Character Codes! Applications provide the Dictionary (also called Terminology) for translation between Four Character Codes and English (and other languages) — We need to harness this
gensuitemodule.py • gensuitemodule is a Python script which creates “Suite Modules” • 'aete' (AppleEvent Terminology) resources are organized into “Suites” • Core Suite • Verbs: run, open, etc • Components: document, page, etc • Properties: document.name, window.name, etc. • Application Specific Suites • Application specific verbs, components, and properties • gensuitemodule parses the aete and generates Python code—one module per suite
Running gensuitemodule • gensuitemodule is in lib/python2.3/plat-mac • You will need 2.3b2 Framework Build or later % setenv LIB \ /Library/Frameworks/Python.framework\ /Versions/Current/lib/python2.3/plat-mac % pythonw $LIB/gensuitemodule.py --output iCal \ /Applications/iCal.app • The Python package iCal is created, with one Module per suite and an __init__
Or...
Suite Module Example (use pythonw) import iCal i = iCal.iCal() ## Creates a custom TalkTo ## Create an ObjectSpecifier specifying the first calendar holidays = iCal.calendar(1) numEvents = i.count(holidays, each=iCal.event) for eventNum in range(numEvents): ## Create a Specifier for the summary property of an event summarySpecifier = holidays.event(eventNum + 1).summary ## Execute the 'get' verb, passing this specifier print i.get(summarySpecifier)
gensuitemodule weirdness • You must iterate items by calling count yourself and creating ObjectSpecifiers for each • You must use get and set, passing a Property specifier, to retrieve and affect data • You cannot access a Property instance without first having an ObjectSpecifier instance The following code works: i.make( new=iCal.calendar, with_properties={iCal.calendar(1).title: 'hello'}) But is strange because we can't just say 'calendar.title'; we must say 'calendar(1).title'
A Look Into the Future • Bob Ippolito and I are planning on doing a ground-up rewrite of gensuitemodule • We will take advantage of Python 2.2's properties and generators • Python Modules created by this will be significantly easier to use • The rewrite will probably not be called gensuitemodule because it may not be backwards-compatible (API compatible)
Possible Future Syntax library = iTunes.source['Library'] for song in library.songs: print song.location library.song[1].name = "SomeName" • This nice, natural looking syntax will be accomplished by: • Passing a reference to the TalkTo to each ComponentItem • Using Python 2.2 properties instead of __getattr__ as ComponentItem and Property factories • Defining an __iter__ which uses the TalkTo to call count automatically, and returns n ComponentItems
Q & A Interactive Demos
Recommend
More recommend