1"""Tools for use in AppleEvent clients and servers: 2conversion between AE types and python types 3 4pack(x) converts a Python object to an AEDesc object 5unpack(desc) does the reverse 6coerce(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 15from warnings import warnpy3k 16warnpy3k("In 3.x, the aepack module is removed.", stacklevel=2) 17 18import struct 19import types 20from types import * 21from Carbon import AE 22from Carbon.AppleEvents import * 23import MacOS 24import Carbon.File 25import aetypes 26from 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# 45unpacker_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# 60AEDescType = AE.AEDescType 61try: 62 FSSType = Carbon.File.FSSpecType 63except AttributeError: 64 class FSSType: 65 pass 66FSRefType = Carbon.File.FSRefType 67AliasType = Carbon.File.AliasType 68 69def 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 78def 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 131def 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 255def 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# 265def 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 272def 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 284def 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 291def mkunknown(type, data): 292 return aetypes.Unknown(type, data) 293 294def getpstr(s): 295 return s[1:1+ord(s[0])] 296 297def getlong(s): 298 return (ord(s[0])<<24) | (ord(s[1])<<16) | (ord(s[2])<<8) | ord(s[3]) 299 300def getword(s): 301 return (ord(s[0])<<8) | (ord(s[1])<<0) 302 303def mkkeyword(keyword): 304 return aetypes.Keyword(keyword) 305 306def mkrange(dict): 307 return aetypes.Range(dict['star'], dict['stop']) 308 309def mkcomparison(dict): 310 return aetypes.Comparison(dict['obj1'], dict['relo'].enum, dict['obj2']) 311 312def mklogical(dict): 313 return aetypes.Logical(dict['logc'], dict['term']) 314 315def mkstyledtext(dict): 316 return aetypes.StyledText(dict['ksty'], dict['ktxt']) 317 318def mkaetext(dict): 319 return aetypes.AEText(dict[keyAEScriptTag], dict[keyAEStyles], dict[keyAEText]) 320 321def mkinsertionloc(dict): 322 return aetypes.InsertionLoc(dict[keyAEObject], dict[keyAEPosition]) 323 324def 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? 347def 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 362def 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