| """Tools for use in AppleEvent clients and servers: | 
 | conversion between AE types and python types | 
 |  | 
 | pack(x) converts a Python object to an AEDesc object | 
 | unpack(desc) does the reverse | 
 | coerce(x, wanted_sample) coerces a python object to another python object | 
 | """ | 
 |  | 
 | # | 
 | # This code was originally written by Guido, and modified/extended by Jack | 
 | # to include the various types that were missing. The reference used is | 
 | # Apple Event Registry, chapter 9. | 
 | # | 
 |  | 
 | from warnings import warnpy3k | 
 | warnpy3k("In 3.x, the aepack module is removed.", stacklevel=2) | 
 |  | 
 | import struct | 
 | import types | 
 | from types import * | 
 | from Carbon import AE | 
 | from Carbon.AppleEvents import * | 
 | import MacOS | 
 | import Carbon.File | 
 | import aetypes | 
 | from aetypes import mkenum, ObjectSpecifier | 
 |  | 
 | # These ones seem to be missing from AppleEvents | 
 | # (they're in AERegistry.h) | 
 |  | 
 | #typeColorTable = 'clrt' | 
 | #typeDrawingArea = 'cdrw' | 
 | #typePixelMap = 'cpix' | 
 | #typePixelMapMinus = 'tpmm' | 
 | #typeRotation = 'trot' | 
 | #typeTextStyles = 'tsty' | 
 | #typeStyledText = 'STXT' | 
 | #typeAEText = 'tTXT' | 
 | #typeEnumeration = 'enum' | 
 |  | 
 | # | 
 | # Some AE types are immedeately coerced into something | 
 | # we like better (and which is equivalent) | 
 | # | 
 | unpacker_coercions = { | 
 |     typeComp : typeFloat, | 
 |     typeColorTable : typeAEList, | 
 |     typeDrawingArea : typeAERecord, | 
 |     typeFixed : typeFloat, | 
 |     typeExtended : typeFloat, | 
 |     typePixelMap : typeAERecord, | 
 |     typeRotation : typeAERecord, | 
 |     typeStyledText : typeAERecord, | 
 |     typeTextStyles : typeAERecord, | 
 | }; | 
 |  | 
 | # | 
 | # Some python types we need in the packer: | 
 | # | 
 | AEDescType = AE.AEDescType | 
 | try: | 
 |     FSSType = Carbon.File.FSSpecType | 
 | except AttributeError: | 
 |     class FSSType: | 
 |         pass | 
 | FSRefType = Carbon.File.FSRefType | 
 | AliasType = Carbon.File.AliasType | 
 |  | 
 | def packkey(ae, key, value): | 
 |     if hasattr(key, 'which'): | 
 |         keystr = key.which | 
 |     elif hasattr(key, 'want'): | 
 |         keystr = key.want | 
 |     else: | 
 |         keystr = key | 
 |     ae.AEPutParamDesc(keystr, pack(value)) | 
 |  | 
 | def pack(x, forcetype = None): | 
 |     """Pack a python object into an AE descriptor""" | 
 |  | 
 |     if forcetype: | 
 |         if type(x) is StringType: | 
 |             return AE.AECreateDesc(forcetype, x) | 
 |         else: | 
 |             return pack(x).AECoerceDesc(forcetype) | 
 |  | 
 |     if x is None: | 
 |         return AE.AECreateDesc('null', '') | 
 |  | 
 |     if isinstance(x, AEDescType): | 
 |         return x | 
 |     if isinstance(x, FSSType): | 
 |         return AE.AECreateDesc('fss ', x.data) | 
 |     if isinstance(x, FSRefType): | 
 |         return AE.AECreateDesc('fsrf', x.data) | 
 |     if isinstance(x, AliasType): | 
 |         return AE.AECreateDesc('alis', x.data) | 
 |     if isinstance(x, IntType): | 
 |         return AE.AECreateDesc('long', struct.pack('l', x)) | 
 |     if isinstance(x, FloatType): | 
 |         return AE.AECreateDesc('doub', struct.pack('d', x)) | 
 |     if isinstance(x, StringType): | 
 |         return AE.AECreateDesc('TEXT', x) | 
 |     if isinstance(x, UnicodeType): | 
 |         data = x.encode('utf16') | 
 |         if data[:2] == '\xfe\xff': | 
 |             data = data[2:] | 
 |         return AE.AECreateDesc('utxt', data) | 
 |     if isinstance(x, ListType): | 
 |         list = AE.AECreateList('', 0) | 
 |         for item in x: | 
 |             list.AEPutDesc(0, pack(item)) | 
 |         return list | 
 |     if isinstance(x, DictionaryType): | 
 |         record = AE.AECreateList('', 1) | 
 |         for key, value in x.items(): | 
 |             packkey(record, key, value) | 
 |             #record.AEPutParamDesc(key, pack(value)) | 
 |         return record | 
 |     if type(x) == types.ClassType and issubclass(x, ObjectSpecifier): | 
 |         # Note: we are getting a class object here, not an instance | 
 |         return AE.AECreateDesc('type', x.want) | 
 |     if hasattr(x, '__aepack__'): | 
 |         return x.__aepack__() | 
 |     if hasattr(x, 'which'): | 
 |         return AE.AECreateDesc('TEXT', x.which) | 
 |     if hasattr(x, 'want'): | 
 |         return AE.AECreateDesc('TEXT', x.want) | 
 |     return AE.AECreateDesc('TEXT', repr(x)) # Copout | 
 |  | 
 | def unpack(desc, formodulename=""): | 
 |     """Unpack an AE descriptor to a python object""" | 
 |     t = desc.type | 
 |  | 
 |     if t in unpacker_coercions: | 
 |         desc = desc.AECoerceDesc(unpacker_coercions[t]) | 
 |         t = desc.type # This is a guess by Jack.... | 
 |  | 
 |     if t == typeAEList: | 
 |         l = [] | 
 |         for i in range(desc.AECountItems()): | 
 |             keyword, item = desc.AEGetNthDesc(i+1, '****') | 
 |             l.append(unpack(item, formodulename)) | 
 |         return l | 
 |     if t == typeAERecord: | 
 |         d = {} | 
 |         for i in range(desc.AECountItems()): | 
 |             keyword, item = desc.AEGetNthDesc(i+1, '****') | 
 |             d[keyword] = unpack(item, formodulename) | 
 |         return d | 
 |     if t == typeAEText: | 
 |         record = desc.AECoerceDesc('reco') | 
 |         return mkaetext(unpack(record, formodulename)) | 
 |     if t == typeAlias: | 
 |         return Carbon.File.Alias(rawdata=desc.data) | 
 |     # typeAppleEvent returned as unknown | 
 |     if t == typeBoolean: | 
 |         return struct.unpack('b', desc.data)[0] | 
 |     if t == typeChar: | 
 |         return desc.data | 
 |     if t == typeUnicodeText: | 
 |         return unicode(desc.data, 'utf16') | 
 |     # typeColorTable coerced to typeAEList | 
 |     # typeComp coerced to extended | 
 |     # typeData returned as unknown | 
 |     # typeDrawingArea coerced to typeAERecord | 
 |     if t == typeEnumeration: | 
 |         return mkenum(desc.data) | 
 |     # typeEPS returned as unknown | 
 |     if t == typeFalse: | 
 |         return 0 | 
 |     if t == typeFloat: | 
 |         data = desc.data | 
 |         return struct.unpack('d', data)[0] | 
 |     if t == typeFSS: | 
 |         return Carbon.File.FSSpec(rawdata=desc.data) | 
 |     if t == typeFSRef: | 
 |         return Carbon.File.FSRef(rawdata=desc.data) | 
 |     if t == typeInsertionLoc: | 
 |         record = desc.AECoerceDesc('reco') | 
 |         return mkinsertionloc(unpack(record, formodulename)) | 
 |     # typeInteger equal to typeLongInteger | 
 |     if t == typeIntlText: | 
 |         script, language = struct.unpack('hh', desc.data[:4]) | 
 |         return aetypes.IntlText(script, language, desc.data[4:]) | 
 |     if t == typeIntlWritingCode: | 
 |         script, language = struct.unpack('hh', desc.data) | 
 |         return aetypes.IntlWritingCode(script, language) | 
 |     if t == typeKeyword: | 
 |         return mkkeyword(desc.data) | 
 |     if t == typeLongInteger: | 
 |         return struct.unpack('l', desc.data)[0] | 
 |     if t == typeLongDateTime: | 
 |         a, b = struct.unpack('lL', desc.data) | 
 |         return (long(a) << 32) + b | 
 |     if t == typeNull: | 
 |         return None | 
 |     if t == typeMagnitude: | 
 |         v = struct.unpack('l', desc.data) | 
 |         if v < 0: | 
 |             v = 0x100000000L + v | 
 |         return v | 
 |     if t == typeObjectSpecifier: | 
 |         record = desc.AECoerceDesc('reco') | 
 |         # If we have been told the name of the module we are unpacking aedescs for, | 
 |         # we can attempt to create the right type of python object from that module. | 
 |         if formodulename: | 
 |             return mkobjectfrommodule(unpack(record, formodulename), formodulename) | 
 |         return mkobject(unpack(record, formodulename)) | 
 |     # typePict returned as unknown | 
 |     # typePixelMap coerced to typeAERecord | 
 |     # typePixelMapMinus returned as unknown | 
 |     # typeProcessSerialNumber returned as unknown | 
 |     if t == typeQDPoint: | 
 |         v, h = struct.unpack('hh', desc.data) | 
 |         return aetypes.QDPoint(v, h) | 
 |     if t == typeQDRectangle: | 
 |         v0, h0, v1, h1 = struct.unpack('hhhh', desc.data) | 
 |         return aetypes.QDRectangle(v0, h0, v1, h1) | 
 |     if t == typeRGBColor: | 
 |         r, g, b = struct.unpack('hhh', desc.data) | 
 |         return aetypes.RGBColor(r, g, b) | 
 |     # typeRotation coerced to typeAERecord | 
 |     # typeScrapStyles returned as unknown | 
 |     # typeSessionID returned as unknown | 
 |     if t == typeShortFloat: | 
 |         return struct.unpack('f', desc.data)[0] | 
 |     if t == typeShortInteger: | 
 |         return struct.unpack('h', desc.data)[0] | 
 |     # typeSMFloat identical to typeShortFloat | 
 |     # typeSMInt indetical to typeShortInt | 
 |     # typeStyledText coerced to typeAERecord | 
 |     if t == typeTargetID: | 
 |         return mktargetid(desc.data) | 
 |     # typeTextStyles coerced to typeAERecord | 
 |     # typeTIFF returned as unknown | 
 |     if t == typeTrue: | 
 |         return 1 | 
 |     if t == typeType: | 
 |         return mktype(desc.data, formodulename) | 
 |     # | 
 |     # The following are special | 
 |     # | 
 |     if t == 'rang': | 
 |         record = desc.AECoerceDesc('reco') | 
 |         return mkrange(unpack(record, formodulename)) | 
 |     if t == 'cmpd': | 
 |         record = desc.AECoerceDesc('reco') | 
 |         return mkcomparison(unpack(record, formodulename)) | 
 |     if t == 'logi': | 
 |         record = desc.AECoerceDesc('reco') | 
 |         return mklogical(unpack(record, formodulename)) | 
 |     return mkunknown(desc.type, desc.data) | 
 |  | 
 | def coerce(data, egdata): | 
 |     """Coerce a python object to another type using the AE coercers""" | 
 |     pdata = pack(data) | 
 |     pegdata = pack(egdata) | 
 |     pdata = pdata.AECoerceDesc(pegdata.type) | 
 |     return unpack(pdata) | 
 |  | 
 | # | 
 | # Helper routines for unpack | 
 | # | 
 | def mktargetid(data): | 
 |     sessionID = getlong(data[:4]) | 
 |     name = mkppcportrec(data[4:4+72]) | 
 |     location = mklocationnamerec(data[76:76+36]) | 
 |     rcvrName = mkppcportrec(data[112:112+72]) | 
 |     return sessionID, name, location, rcvrName | 
 |  | 
 | def mkppcportrec(rec): | 
 |     namescript = getword(rec[:2]) | 
 |     name = getpstr(rec[2:2+33]) | 
 |     portkind = getword(rec[36:38]) | 
 |     if portkind == 1: | 
 |         ctor = rec[38:42] | 
 |         type = rec[42:46] | 
 |         identity = (ctor, type) | 
 |     else: | 
 |         identity = getpstr(rec[38:38+33]) | 
 |     return namescript, name, portkind, identity | 
 |  | 
 | def mklocationnamerec(rec): | 
 |     kind = getword(rec[:2]) | 
 |     stuff = rec[2:] | 
 |     if kind == 0: stuff = None | 
 |     if kind == 2: stuff = getpstr(stuff) | 
 |     return kind, stuff | 
 |  | 
 | def mkunknown(type, data): | 
 |     return aetypes.Unknown(type, data) | 
 |  | 
 | def getpstr(s): | 
 |     return s[1:1+ord(s[0])] | 
 |  | 
 | def getlong(s): | 
 |     return (ord(s[0])<<24) | (ord(s[1])<<16) | (ord(s[2])<<8) | ord(s[3]) | 
 |  | 
 | def getword(s): | 
 |     return (ord(s[0])<<8) | (ord(s[1])<<0) | 
 |  | 
 | def mkkeyword(keyword): | 
 |     return aetypes.Keyword(keyword) | 
 |  | 
 | def mkrange(dict): | 
 |     return aetypes.Range(dict['star'], dict['stop']) | 
 |  | 
 | def mkcomparison(dict): | 
 |     return aetypes.Comparison(dict['obj1'], dict['relo'].enum, dict['obj2']) | 
 |  | 
 | def mklogical(dict): | 
 |     return aetypes.Logical(dict['logc'], dict['term']) | 
 |  | 
 | def mkstyledtext(dict): | 
 |     return aetypes.StyledText(dict['ksty'], dict['ktxt']) | 
 |  | 
 | def mkaetext(dict): | 
 |     return aetypes.AEText(dict[keyAEScriptTag], dict[keyAEStyles], dict[keyAEText]) | 
 |  | 
 | def mkinsertionloc(dict): | 
 |     return aetypes.InsertionLoc(dict[keyAEObject], dict[keyAEPosition]) | 
 |  | 
 | def mkobject(dict): | 
 |     want = dict['want'].type | 
 |     form = dict['form'].enum | 
 |     seld = dict['seld'] | 
 |     fr   = dict['from'] | 
 |     if form in ('name', 'indx', 'rang', 'test'): | 
 |         if want == 'text': return aetypes.Text(seld, fr) | 
 |         if want == 'cha ': return aetypes.Character(seld, fr) | 
 |         if want == 'cwor': return aetypes.Word(seld, fr) | 
 |         if want == 'clin': return aetypes.Line(seld, fr) | 
 |         if want == 'cpar': return aetypes.Paragraph(seld, fr) | 
 |         if want == 'cwin': return aetypes.Window(seld, fr) | 
 |         if want == 'docu': return aetypes.Document(seld, fr) | 
 |         if want == 'file': return aetypes.File(seld, fr) | 
 |         if want == 'cins': return aetypes.InsertionPoint(seld, fr) | 
 |     if want == 'prop' and form == 'prop' and aetypes.IsType(seld): | 
 |         return aetypes.Property(seld.type, fr) | 
 |     return aetypes.ObjectSpecifier(want, form, seld, fr) | 
 |  | 
 | # Note by Jack: I'm not 100% sure of the following code. This was | 
 | # provided by Donovan Preston, but I wonder whether the assignment | 
 | # to __class__ is safe. Moreover, shouldn't there be a better | 
 | # initializer for the classes in the suites? | 
 | def mkobjectfrommodule(dict, modulename): | 
 |     if type(dict['want']) == types.ClassType and issubclass(dict['want'], ObjectSpecifier): | 
 |         # The type has already been converted to Python. Convert back:-( | 
 |         classtype = dict['want'] | 
 |         dict['want'] = aetypes.mktype(classtype.want) | 
 |     want = dict['want'].type | 
 |     module = __import__(modulename) | 
 |     codenamemapper = module._classdeclarations | 
 |     classtype = codenamemapper.get(want, None) | 
 |     newobj = mkobject(dict) | 
 |     if classtype: | 
 |         assert issubclass(classtype, ObjectSpecifier) | 
 |         newobj.__class__ = classtype | 
 |     return newobj | 
 |  | 
 | def mktype(typecode, modulename=None): | 
 |     if modulename: | 
 |         module = __import__(modulename) | 
 |         codenamemapper = module._classdeclarations | 
 |         classtype = codenamemapper.get(typecode, None) | 
 |         if classtype: | 
 |             return classtype | 
 |     return aetypes.mktype(typecode) |