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