Home | History | Annotate | Download | only in plat-mac
      1 """Tools for use in AppleEvent clients and servers:
      2 conversion between AE types and python types
      3 
      4 pack(x) converts a Python object to an AEDesc object
      5 unpack(desc) does the reverse
      6 coerce(x, wanted_sample) coerces a python object to another python object
      7 """
      8 
      9 #
     10 # This code was originally written by Guido, and modified/extended by Jack
     11 # to include the various types that were missing. The reference used is
     12 # Apple Event Registry, chapter 9.
     13 #
     14 
     15 from warnings import warnpy3k
     16 warnpy3k("In 3.x, the aepack module is removed.", stacklevel=2)
     17 
     18 import struct
     19 import types
     20 from types import *
     21 from Carbon import AE
     22 from Carbon.AppleEvents import *
     23 import MacOS
     24 import Carbon.File
     25 import aetypes
     26 from aetypes import mkenum, ObjectSpecifier
     27 
     28 # These ones seem to be missing from AppleEvents
     29 # (they're in AERegistry.h)
     30 
     31 #typeColorTable = 'clrt'
     32 #typeDrawingArea = 'cdrw'
     33 #typePixelMap = 'cpix'
     34 #typePixelMapMinus = 'tpmm'
     35 #typeRotation = 'trot'
     36 #typeTextStyles = 'tsty'
     37 #typeStyledText = 'STXT'
     38 #typeAEText = 'tTXT'
     39 #typeEnumeration = 'enum'
     40 
     41 #
     42 # Some AE types are immedeately coerced into something
     43 # we like better (and which is equivalent)
     44 #
     45 unpacker_coercions = {
     46     typeComp : typeFloat,
     47     typeColorTable : typeAEList,
     48     typeDrawingArea : typeAERecord,
     49     typeFixed : typeFloat,
     50     typeExtended : typeFloat,
     51     typePixelMap : typeAERecord,
     52     typeRotation : typeAERecord,
     53     typeStyledText : typeAERecord,
     54     typeTextStyles : typeAERecord,
     55 };
     56 
     57 #
     58 # Some python types we need in the packer:
     59 #
     60 AEDescType = AE.AEDescType
     61 try:
     62     FSSType = Carbon.File.FSSpecType
     63 except AttributeError:
     64     class FSSType:
     65         pass
     66 FSRefType = Carbon.File.FSRefType
     67 AliasType = Carbon.File.AliasType
     68 
     69 def packkey(ae, key, value):
     70     if hasattr(key, 'which'):
     71         keystr = key.which
     72     elif hasattr(key, 'want'):
     73         keystr = key.want
     74     else:
     75         keystr = key
     76     ae.AEPutParamDesc(keystr, pack(value))
     77 
     78 def pack(x, forcetype = None):
     79     """Pack a python object into an AE descriptor"""
     80 
     81     if forcetype:
     82         if type(x) is StringType:
     83             return AE.AECreateDesc(forcetype, x)
     84         else:
     85             return pack(x).AECoerceDesc(forcetype)
     86 
     87     if x is None:
     88         return AE.AECreateDesc('null', '')
     89 
     90     if isinstance(x, AEDescType):
     91         return x
     92     if isinstance(x, FSSType):
     93         return AE.AECreateDesc('fss ', x.data)
     94     if isinstance(x, FSRefType):
     95         return AE.AECreateDesc('fsrf', x.data)
     96     if isinstance(x, AliasType):
     97         return AE.AECreateDesc('alis', x.data)
     98     if isinstance(x, IntType):
     99         return AE.AECreateDesc('long', struct.pack('l', x))
    100     if isinstance(x, FloatType):
    101         return AE.AECreateDesc('doub', struct.pack('d', x))
    102     if isinstance(x, StringType):
    103         return AE.AECreateDesc('TEXT', x)
    104     if isinstance(x, UnicodeType):
    105         data = x.encode('utf16')
    106         if data[:2] == '\xfe\xff':
    107             data = data[2:]
    108         return AE.AECreateDesc('utxt', data)
    109     if isinstance(x, ListType):
    110         list = AE.AECreateList('', 0)
    111         for item in x:
    112             list.AEPutDesc(0, pack(item))
    113         return list
    114     if isinstance(x, DictionaryType):
    115         record = AE.AECreateList('', 1)
    116         for key, value in x.items():
    117             packkey(record, key, value)
    118             #record.AEPutParamDesc(key, pack(value))
    119         return record
    120     if type(x) == types.ClassType and issubclass(x, ObjectSpecifier):
    121         # Note: we are getting a class object here, not an instance
    122         return AE.AECreateDesc('type', x.want)
    123     if hasattr(x, '__aepack__'):
    124         return x.__aepack__()
    125     if hasattr(x, 'which'):
    126         return AE.AECreateDesc('TEXT', x.which)
    127     if hasattr(x, 'want'):
    128         return AE.AECreateDesc('TEXT', x.want)
    129     return AE.AECreateDesc('TEXT', repr(x)) # Copout
    130 
    131 def unpack(desc, formodulename=""):
    132     """Unpack an AE descriptor to a python object"""
    133     t = desc.type
    134 
    135     if t in unpacker_coercions:
    136         desc = desc.AECoerceDesc(unpacker_coercions[t])
    137         t = desc.type # This is a guess by Jack....
    138 
    139     if t == typeAEList:
    140         l = []
    141         for i in range(desc.AECountItems()):
    142             keyword, item = desc.AEGetNthDesc(i+1, '****')
    143             l.append(unpack(item, formodulename))
    144         return l
    145     if t == typeAERecord:
    146         d = {}
    147         for i in range(desc.AECountItems()):
    148             keyword, item = desc.AEGetNthDesc(i+1, '****')
    149             d[keyword] = unpack(item, formodulename)
    150         return d
    151     if t == typeAEText:
    152         record = desc.AECoerceDesc('reco')
    153         return mkaetext(unpack(record, formodulename))
    154     if t == typeAlias:
    155         return Carbon.File.Alias(rawdata=desc.data)
    156     # typeAppleEvent returned as unknown
    157     if t == typeBoolean:
    158         return struct.unpack('b', desc.data)[0]
    159     if t == typeChar:
    160         return desc.data
    161     if t == typeUnicodeText:
    162         return unicode(desc.data, 'utf16')
    163     # typeColorTable coerced to typeAEList
    164     # typeComp coerced to extended
    165     # typeData returned as unknown
    166     # typeDrawingArea coerced to typeAERecord
    167     if t == typeEnumeration:
    168         return mkenum(desc.data)
    169     # typeEPS returned as unknown
    170     if t == typeFalse:
    171         return 0
    172     if t == typeFloat:
    173         data = desc.data
    174         return struct.unpack('d', data)[0]
    175     if t == typeFSS:
    176         return Carbon.File.FSSpec(rawdata=desc.data)
    177     if t == typeFSRef:
    178         return Carbon.File.FSRef(rawdata=desc.data)
    179     if t == typeInsertionLoc:
    180         record = desc.AECoerceDesc('reco')
    181         return mkinsertionloc(unpack(record, formodulename))
    182     # typeInteger equal to typeLongInteger
    183     if t == typeIntlText:
    184         script, language = struct.unpack('hh', desc.data[:4])
    185         return aetypes.IntlText(script, language, desc.data[4:])
    186     if t == typeIntlWritingCode:
    187         script, language = struct.unpack('hh', desc.data)
    188         return aetypes.IntlWritingCode(script, language)
    189     if t == typeKeyword:
    190         return mkkeyword(desc.data)
    191     if t == typeLongInteger:
    192         return struct.unpack('l', desc.data)[0]
    193     if t == typeLongDateTime:
    194         a, b = struct.unpack('lL', desc.data)
    195         return (long(a) << 32) + b
    196     if t == typeNull:
    197         return None
    198     if t == typeMagnitude:
    199         v = struct.unpack('l', desc.data)
    200         if v < 0:
    201             v = 0x100000000L + v
    202         return v
    203     if t == typeObjectSpecifier:
    204         record = desc.AECoerceDesc('reco')
    205         # If we have been told the name of the module we are unpacking aedescs for,
    206         # we can attempt to create the right type of python object from that module.
    207         if formodulename:
    208             return mkobjectfrommodule(unpack(record, formodulename), formodulename)
    209         return mkobject(unpack(record, formodulename))
    210     # typePict returned as unknown
    211     # typePixelMap coerced to typeAERecord
    212     # typePixelMapMinus returned as unknown
    213     # typeProcessSerialNumber returned as unknown
    214     if t == typeQDPoint:
    215         v, h = struct.unpack('hh', desc.data)
    216         return aetypes.QDPoint(v, h)
    217     if t == typeQDRectangle:
    218         v0, h0, v1, h1 = struct.unpack('hhhh', desc.data)
    219         return aetypes.QDRectangle(v0, h0, v1, h1)
    220     if t == typeRGBColor:
    221         r, g, b = struct.unpack('hhh', desc.data)
    222         return aetypes.RGBColor(r, g, b)
    223     # typeRotation coerced to typeAERecord
    224     # typeScrapStyles returned as unknown
    225     # typeSessionID returned as unknown
    226     if t == typeShortFloat:
    227         return struct.unpack('f', desc.data)[0]
    228     if t == typeShortInteger:
    229         return struct.unpack('h', desc.data)[0]
    230     # typeSMFloat identical to typeShortFloat
    231     # typeSMInt indetical to typeShortInt
    232     # typeStyledText coerced to typeAERecord
    233     if t == typeTargetID:
    234         return mktargetid(desc.data)
    235     # typeTextStyles coerced to typeAERecord
    236     # typeTIFF returned as unknown
    237     if t == typeTrue:
    238         return 1
    239     if t == typeType:
    240         return mktype(desc.data, formodulename)
    241     #
    242     # The following are special
    243     #
    244     if t == 'rang':
    245         record = desc.AECoerceDesc('reco')
    246         return mkrange(unpack(record, formodulename))
    247     if t == 'cmpd':
    248         record = desc.AECoerceDesc('reco')
    249         return mkcomparison(unpack(record, formodulename))
    250     if t == 'logi':
    251         record = desc.AECoerceDesc('reco')
    252         return mklogical(unpack(record, formodulename))
    253     return mkunknown(desc.type, desc.data)
    254 
    255 def coerce(data, egdata):
    256     """Coerce a python object to another type using the AE coercers"""
    257     pdata = pack(data)
    258     pegdata = pack(egdata)
    259     pdata = pdata.AECoerceDesc(pegdata.type)
    260     return unpack(pdata)
    261 
    262 #
    263 # Helper routines for unpack
    264 #
    265 def mktargetid(data):
    266     sessionID = getlong(data[:4])
    267     name = mkppcportrec(data[4:4+72])
    268     location = mklocationnamerec(data[76:76+36])
    269     rcvrName = mkppcportrec(data[112:112+72])
    270     return sessionID, name, location, rcvrName
    271 
    272 def mkppcportrec(rec):
    273     namescript = getword(rec[:2])
    274     name = getpstr(rec[2:2+33])
    275     portkind = getword(rec[36:38])
    276     if portkind == 1:
    277         ctor = rec[38:42]
    278         type = rec[42:46]
    279         identity = (ctor, type)
    280     else:
    281         identity = getpstr(rec[38:38+33])
    282     return namescript, name, portkind, identity
    283 
    284 def mklocationnamerec(rec):
    285     kind = getword(rec[:2])
    286     stuff = rec[2:]
    287     if kind == 0: stuff = None
    288     if kind == 2: stuff = getpstr(stuff)
    289     return kind, stuff
    290 
    291 def mkunknown(type, data):
    292     return aetypes.Unknown(type, data)
    293 
    294 def getpstr(s):
    295     return s[1:1+ord(s[0])]
    296 
    297 def getlong(s):
    298     return (ord(s[0])<<24) | (ord(s[1])<<16) | (ord(s[2])<<8) | ord(s[3])
    299 
    300 def getword(s):
    301     return (ord(s[0])<<8) | (ord(s[1])<<0)
    302 
    303 def mkkeyword(keyword):
    304     return aetypes.Keyword(keyword)
    305 
    306 def mkrange(dict):
    307     return aetypes.Range(dict['star'], dict['stop'])
    308 
    309 def mkcomparison(dict):
    310     return aetypes.Comparison(dict['obj1'], dict['relo'].enum, dict['obj2'])
    311 
    312 def mklogical(dict):
    313     return aetypes.Logical(dict['logc'], dict['term'])
    314 
    315 def mkstyledtext(dict):
    316     return aetypes.StyledText(dict['ksty'], dict['ktxt'])
    317 
    318 def mkaetext(dict):
    319     return aetypes.AEText(dict[keyAEScriptTag], dict[keyAEStyles], dict[keyAEText])
    320 
    321 def mkinsertionloc(dict):
    322     return aetypes.InsertionLoc(dict[keyAEObject], dict[keyAEPosition])
    323 
    324 def mkobject(dict):
    325     want = dict['want'].type
    326     form = dict['form'].enum
    327     seld = dict['seld']
    328     fr   = dict['from']
    329     if form in ('name', 'indx', 'rang', 'test'):
    330         if want == 'text': return aetypes.Text(seld, fr)
    331         if want == 'cha ': return aetypes.Character(seld, fr)
    332         if want == 'cwor': return aetypes.Word(seld, fr)
    333         if want == 'clin': return aetypes.Line(seld, fr)
    334         if want == 'cpar': return aetypes.Paragraph(seld, fr)
    335         if want == 'cwin': return aetypes.Window(seld, fr)
    336         if want == 'docu': return aetypes.Document(seld, fr)
    337         if want == 'file': return aetypes.File(seld, fr)
    338         if want == 'cins': return aetypes.InsertionPoint(seld, fr)
    339     if want == 'prop' and form == 'prop' and aetypes.IsType(seld):
    340         return aetypes.Property(seld.type, fr)
    341     return aetypes.ObjectSpecifier(want, form, seld, fr)
    342 
    343 # Note by Jack: I'm not 100% sure of the following code. This was
    344 # provided by Donovan Preston, but I wonder whether the assignment
    345 # to __class__ is safe. Moreover, shouldn't there be a better
    346 # initializer for the classes in the suites?
    347 def mkobjectfrommodule(dict, modulename):
    348     if type(dict['want']) == types.ClassType and issubclass(dict['want'], ObjectSpecifier):
    349         # The type has already been converted to Python. Convert back:-(
    350         classtype = dict['want']
    351         dict['want'] = aetypes.mktype(classtype.want)
    352     want = dict['want'].type
    353     module = __import__(modulename)
    354     codenamemapper = module._classdeclarations
    355     classtype = codenamemapper.get(want, None)
    356     newobj = mkobject(dict)
    357     if classtype:
    358         assert issubclass(classtype, ObjectSpecifier)
    359         newobj.__class__ = classtype
    360     return newobj
    361 
    362 def mktype(typecode, modulename=None):
    363     if modulename:
    364         module = __import__(modulename)
    365         codenamemapper = module._classdeclarations
    366         classtype = codenamemapper.get(typecode, None)
    367         if classtype:
    368             return classtype
    369     return aetypes.mktype(typecode)
    370