blob: bb0d20f2eb18b0abc78e6a0052d787c6d282db16 [file] [log] [blame]
Guido van Rossum31559231995-02-05 16:59:27 +00001"""Tools for use in AppleEvent clients and servers.
2
3pack(x) converts a Python object to an AEDesc object
4unpack(desc) does the reverse
5
6packevent(event, parameters, attributes) sets params and attrs in an AEAppleEvent record
7unpackevent(event) returns the parameters and attributes from an AEAppleEvent record
8
9Plus... Lots of classes and routines that help representing AE objects,
10ranges, conditionals, logicals, etc., so you can write, e.g.:
11
12 x = Character(1, Document("foobar"))
13
14and pack(x) will create an AE object reference equivalent to AppleScript's
15
16 character 1 of document "foobar"
17
18"""
19
20
Guido van Rossum17448e21995-01-30 11:53:55 +000021import struct
Guido van Rossum31559231995-02-05 16:59:27 +000022import string
23from string import strip
24from types import *
Guido van Rossum17448e21995-01-30 11:53:55 +000025import AE
26import MacOS
Guido van Rossum31559231995-02-05 16:59:27 +000027import macfs
Guido van Rossum17448e21995-01-30 11:53:55 +000028import StringIO
29
Guido van Rossum31559231995-02-05 16:59:27 +000030
Guido van Rossum17448e21995-01-30 11:53:55 +000031AEDescType = type(AE.AECreateDesc('TEXT', ''))
32
Guido van Rossum31559231995-02-05 16:59:27 +000033FSSType = type(macfs.FSSpec(':'))
34
35
36def pack(x, forcetype = None):
37 if forcetype:
38 if type(x) is StringType:
39 return AE.AECreateDesc(forcetype, x)
40 else:
41 return pack(x).AECoerceDesc(forcetype)
Guido van Rossum17448e21995-01-30 11:53:55 +000042 if x == None:
43 return AE.AECreateDesc('null', '')
44 t = type(x)
45 if t == AEDescType:
46 return x
Guido van Rossum31559231995-02-05 16:59:27 +000047 if t == FSSType:
48 vol, dir, filename = x.as_tuple()
49 fnlen = len(filename)
50 header = struct.pack('hlb', vol, dir, fnlen)
51 padding = '\0'*(63-fnlen)
52 return AE.AECreateDesc('fss ', header + filename + padding)
53 if t == IntType:
Guido van Rossum17448e21995-01-30 11:53:55 +000054 return AE.AECreateDesc('long', struct.pack('l', x))
Guido van Rossum31559231995-02-05 16:59:27 +000055 if t == FloatType:
56 # XXX Weird thing -- Think C's "double" is 10 bytes, but
57 # struct.pack('d') return 12 bytes (and struct.unpack requires
58 # them, too). The first 2 bytes seem to be repeated...
59 # Probably an alignment problem
Guido van Rossum17448e21995-01-30 11:53:55 +000060 return AE.AECreateDesc('exte', struct.pack('d', x)[2:])
Guido van Rossum31559231995-02-05 16:59:27 +000061 if t == StringType:
Guido van Rossum17448e21995-01-30 11:53:55 +000062 return AE.AECreateDesc('TEXT', x)
Guido van Rossum31559231995-02-05 16:59:27 +000063 if t == ListType:
Guido van Rossum17448e21995-01-30 11:53:55 +000064 list = AE.AECreateList('', 0)
65 for item in x:
66 list.AEPutDesc(0, pack(item))
67 return list
Guido van Rossum31559231995-02-05 16:59:27 +000068 if t == DictionaryType:
Guido van Rossum17448e21995-01-30 11:53:55 +000069 record = AE.AECreateList('', 1)
70 for key, value in x.items():
Jack Jansenc7cfb951995-06-05 22:34:12 +000071 record.AEPutParamDesc(key, pack(value))
Guido van Rossum31559231995-02-05 16:59:27 +000072 return record
73 if t == InstanceType and hasattr(x, '__aepack__'):
Guido van Rossum17448e21995-01-30 11:53:55 +000074 return x.__aepack__()
75 return AE.AECreateDesc('TEXT', repr(x)) # Copout
76
Guido van Rossum31559231995-02-05 16:59:27 +000077
Guido van Rossum17448e21995-01-30 11:53:55 +000078def unpack(desc):
79 t = desc.type
80 if t == 'TEXT':
81 return desc.data
82 if t == 'fals':
83 return 0
84 if t == 'true':
85 return 1
Guido van Rossum31559231995-02-05 16:59:27 +000086 if t == 'enum':
87 return mkenum(desc.data)
88 if t == 'type':
89 return mktype(desc.data)
Guido van Rossum17448e21995-01-30 11:53:55 +000090 if t == 'long':
91 return struct.unpack('l', desc.data)[0]
92 if t == 'shor':
93 return struct.unpack('h', desc.data)[0]
94 if t == 'sing':
95 return struct.unpack('f', desc.data)[0]
96 if t == 'exte':
97 data = desc.data
Guido van Rossum31559231995-02-05 16:59:27 +000098 # XXX See corresponding note for pack()
Guido van Rossum17448e21995-01-30 11:53:55 +000099 return struct.unpack('d', data[:2] + data)[0]
100 if t in ('doub', 'comp', 'magn'):
101 return unpack(desc.AECoerceDesc('exte'))
Guido van Rossum17448e21995-01-30 11:53:55 +0000102 if t == 'null':
103 return None
104 if t == 'list':
105 l = []
106 for i in range(desc.AECountItems()):
107 keyword, item = desc.AEGetNthDesc(i+1, '****')
108 l.append(unpack(item))
109 return l
110 if t == 'reco':
111 d = {}
112 for i in range(desc.AECountItems()):
113 keyword, item = desc.AEGetNthDesc(i+1, '****')
114 d[keyword] = unpack(item)
115 return d
116 if t == 'obj ':
Guido van Rossum31559231995-02-05 16:59:27 +0000117 record = desc.AECoerceDesc('reco')
118 return mkobject(unpack(record))
119 if t == 'rang':
120 record = desc.AECoerceDesc('reco')
121 return mkrange(unpack(record))
122 if t == 'cmpd':
123 record = desc.AECoerceDesc('reco')
124 return mkcomparison(unpack(record))
125 if t == 'logi':
126 record = desc.AECoerceDesc('reco')
127 return mklogical(unpack(record))
128 if t == 'targ':
129 return mktargetid(desc.data)
130 if t == 'alis':
131 # XXX Can't handle alias records yet, so coerce to FS spec...
132 return unpack(desc.AECoerceDesc('fss '))
133 if t == 'fss ':
134 return mkfss(desc.data)
135 return mkunknown(desc.type, desc.data)
Guido van Rossum17448e21995-01-30 11:53:55 +0000136
Guido van Rossum17448e21995-01-30 11:53:55 +0000137
Guido van Rossum31559231995-02-05 16:59:27 +0000138def mkfss(data):
Guido van Rossum97842951995-02-19 15:59:49 +0000139 print "mkfss data =", `data`
Guido van Rossum31559231995-02-05 16:59:27 +0000140 vol, dir, fnlen = struct.unpack('hlb', data[:7])
141 filename = data[7:7+fnlen]
Guido van Rossum97842951995-02-19 15:59:49 +0000142 print (vol, dir, fnlen, filename)
Guido van Rossum31559231995-02-05 16:59:27 +0000143 return macfs.FSSpec((vol, dir, filename))
144
145
146def mktargetid(data):
147 sessionID = getlong(data[:4])
148 name = mkppcportrec(data[4:4+72])
149 print len(name), `name`
150 location = mklocationnamerec(data[76:76+36])
151 rcvrName = mkppcportrec(data[112:112+72])
152 return sessionID, name, location, rcvrName
153
154def mkppcportrec(rec):
155 namescript = getword(rec[:2])
156 name = getpstr(rec[2:2+33])
157 portkind = getword(rec[36:38])
158 if portkind == 1:
159 ctor = rec[38:42]
160 type = rec[42:46]
161 identity = (ctor, type)
Guido van Rossum17448e21995-01-30 11:53:55 +0000162 else:
Guido van Rossum31559231995-02-05 16:59:27 +0000163 identity = getpstr(rec[38:38+33])
164 return namescript, name, portkind, identity
Guido van Rossum17448e21995-01-30 11:53:55 +0000165
Guido van Rossum31559231995-02-05 16:59:27 +0000166def mklocationnamerec(rec):
167 kind = getword(rec[:2])
168 stuff = rec[2:]
169 if kind == 0: stuff = None
170 if kind == 2: stuff = getpstr(stuff)
171 return kind, stuff
Guido van Rossum17448e21995-01-30 11:53:55 +0000172
Guido van Rossum31559231995-02-05 16:59:27 +0000173def getpstr(s):
174 return s[1:1+ord(s[0])]
Guido van Rossum17448e21995-01-30 11:53:55 +0000175
Guido van Rossum31559231995-02-05 16:59:27 +0000176def getlong(s):
Guido van Rossum17448e21995-01-30 11:53:55 +0000177 return (ord(s[0])<<24) | (ord(s[1])<<16) | (ord(s[2])<<8) | ord(s[3])
178
Guido van Rossum31559231995-02-05 16:59:27 +0000179def getword(s):
180 return (ord(s[0])<<8) | (ord(s[1])<<0)
Guido van Rossum17448e21995-01-30 11:53:55 +0000181
182
Guido van Rossum31559231995-02-05 16:59:27 +0000183def mkunknown(type, data):
184 return Unknown(type, data)
Guido van Rossum17448e21995-01-30 11:53:55 +0000185
Guido van Rossum31559231995-02-05 16:59:27 +0000186class Unknown:
187
188 def __init__(self, type, data):
189 self.type = type
190 self.data = data
191
192 def __repr__(self):
193 return "Unknown(%s, %s)" % (`self.type`, `self.data`)
194
195 def __aepack__(self):
196 return pack(self.data, self.type)
Guido van Rossum17448e21995-01-30 11:53:55 +0000197
Guido van Rossum17448e21995-01-30 11:53:55 +0000198
Guido van Rossum31559231995-02-05 16:59:27 +0000199def IsSubclass(cls, base):
200 """Test whether CLASS1 is the same as or a subclass of CLASS2"""
201 # Loop to optimize for single inheritance
202 while 1:
203 if cls is base: return 1
204 if len(cls.__bases__) <> 1: break
205 cls = cls.__bases__[0]
206 # Recurse to cope with multiple inheritance
207 for c in cls.__bases__:
208 if IsSubclass(c, base): return 1
209 return 0
Guido van Rossum17448e21995-01-30 11:53:55 +0000210
Guido van Rossum31559231995-02-05 16:59:27 +0000211def IsInstance(x, cls):
212 """Test whether OBJECT is an instance of (a subclass of) CLASS"""
213 return type(x) is InstanceType and IsSubclass(x.__class__, cls)
Guido van Rossum17448e21995-01-30 11:53:55 +0000214
Guido van Rossum17448e21995-01-30 11:53:55 +0000215
Guido van Rossum31559231995-02-05 16:59:27 +0000216def nice(s):
217 if type(s) is StringType: return repr(s)
218 else: return str(s)
Guido van Rossum17448e21995-01-30 11:53:55 +0000219
220
Guido van Rossum31559231995-02-05 16:59:27 +0000221def mkenum(enum):
222 if IsEnum(enum): return enum
223 return Enum(enum)
224
225class Enum:
226
227 def __init__(self, enum):
228 self.enum = "%-4.4s" % str(enum)
229
230 def __repr__(self):
231 return "Enum(%s)" % `self.enum`
232
233 def __str__(self):
234 return strip(self.enum)
235
236 def __aepack__(self):
237 return pack(self.enum, 'enum')
238
239def IsEnum(x):
240 return IsInstance(x, Enum)
241
242
243def mktype(type):
244 if IsType(type): return type
245 return Type(type)
246
247class Type:
248
249 def __init__(self, type):
250 self.type = "%-4.4s" % str(type)
251
252 def __repr__(self):
253 return "Type(%s)" % `self.type`
254
255 def __str__(self):
256 return strip(self.type)
257
258 def __aepack__(self):
259 return pack(self.type, 'type')
260
261def IsType(x):
262 return IsInstance(x, Type)
263
264
265def mkrange(dict):
266 return Range(dict['star'], dict['stop'])
267
268class Range:
269
270 def __init__(self, start, stop):
271 self.start = start
272 self.stop = stop
273
274 def __repr__(self):
275 return "Range(%s, %s)" % (`self.start`, `self.stop`)
276
277 def __str__(self):
278 return "%s thru %s" % (nice(self.start), nice(self.stop))
279
280 def __aepack__(self):
281 return pack({'star': self.start, 'stop': self.stop}, 'rang')
282
283def IsRange(x):
284 return IsInstance(x, Range)
285
286
287def mkcomparison(dict):
288 return Comparison(dict['obj1'], dict['relo'].enum, dict['obj2'])
289
290class Comparison:
291
292 def __init__(self, obj1, relo, obj2):
293 self.obj1 = obj1
294 self.relo = "%-4.4s" % str(relo)
295 self.obj2 = obj2
296
297 def __repr__(self):
298 return "Comparison(%s, %s, %s)" % (`self.obj1`, `self.relo`, `self.obj2`)
299
300 def __str__(self):
301 return "%s %s %s" % (nice(self.obj1), strip(self.relo), nice(self.obj2))
302
303 def __aepack__(self):
304 return pack({'obj1': self.obj1,
305 'relo': mkenum(self.relo),
306 'obj2': self.obj2},
307 'cmpd')
308
309def IsComparison(x):
310 return IsInstance(x, Comparison)
311
312
313def mklogical(dict):
314 return Logical(dict['logc'], dict['term'])
315
316class Logical:
317
318 def __init__(self, logc, term):
319 self.logc = "%-4.4s" % str(logc)
320 self.term = term
321
322 def __repr__(self):
323 return "Logical(%s, %s)" % (`self.logc`, `self.term`)
324
325 def __str__(self):
326 if type(self.term) == ListType and len(self.term) == 2:
327 return "%s %s %s" % (nice(self.term[0]),
328 strip(self.logc),
329 nice(self.term[1]))
330 else:
331 return "%s(%s)" % (strip(self.logc), nice(self.term))
332
333 def __aepack__(self):
334 return pack({'logc': mkenum(self.logc), 'term': self.term}, 'logi')
335
336def IsLogical(x):
337 return IsInstance(x, Logical)
338
339
340class ObjectSpecifier:
341
342 """A class for constructing and manipulation AE object specifiers in python.
343
344 An object specifier is actually a record with four fields:
345
346 key type description
347 --- ---- -----------
348
349 'want' type what kind of thing we want,
350 e.g. word, paragraph or property
351
352 'form' enum how we specify the thing(s) we want,
353 e.g. by index, by range, by name, or by property specifier
354
355 'seld' any which thing(s) we want,
356 e.g. its index, its name, or its property specifier
357
358 'from' object the object in which it is contained,
359 or null, meaning look for it in the application
360
361 Note that we don't call this class plain "Object", since that name
362 is likely to be used by the application.
363 """
364
365 def __init__(self, want, form, seld, fr = None):
366 self.want = want
367 self.form = form
368 self.seld = seld
369 self.fr = fr
370
371 def __repr__(self):
372 s = "ObjectSpecifier(%s, %s, %s" % (`self.want`, `self.form`, `self.seld`)
373 if self.fr:
374 s = s + ", %s)" % `self.fr`
375 else:
376 s = s + ")"
377 return s
378
379 def __aepack__(self):
380 return pack({'want': mktype(self.want),
381 'form': mkenum(self.form),
382 'seld': self.seld,
383 'from': self.fr},
384 'obj ')
385
386
387def IsObjectSpecifier(x):
388 return IsInstance(x, ObjectSpecifier)
389
390
391class Property(ObjectSpecifier):
392
393 def __init__(self, which, fr = None):
394 ObjectSpecifier.__init__(self, 'prop', 'prop', mkenum(which), fr)
395
396 def __repr__(self):
397 if self.fr:
398 return "Property(%s, %s)" % (`self.seld.enum`, `self.fr`)
399 else:
400 return "Property(%s)" % `self.seld.enum`
401
402 def __str__(self):
403 if self.fr:
404 return "Property %s of %s" % (str(self.seld), str(self.fr))
405 else:
406 return "Property %s" % str(self.seld)
407
408
409class SelectableItem(ObjectSpecifier):
410
411 def __init__(self, want, seld, fr = None):
412 t = type(seld)
413 if t == StringType:
414 form = 'name'
415 elif IsRange(seld):
416 form = 'rang'
417 elif IsComparison(seld) or IsLogical(seld):
418 form = 'test'
419 else:
420 form = 'indx'
421 ObjectSpecifier.__init__(self, want, form, seld, fr)
422
423
424class ComponentItem(SelectableItem):
425 # Derived classes *must* set the *class attribute* 'want' to some constant
426
427 def __init__(self, which, fr = None):
428 SelectableItem.__init__(self, self.want, which, fr)
429
430 def __repr__(self):
431 if not self.fr:
432 return "%s(%s)" % (self.__class__.__name__, `self.seld`)
433 return "%s(%s, %s)" % (self.__class__.__name__, `self.seld`, `self.fr`)
434
435 def __str__(self):
436 seld = self.seld
437 if type(seld) == StringType:
438 ss = repr(seld)
439 elif IsRange(seld):
440 start, stop = seld.start, seld.stop
441 if type(start) == InstanceType == type(stop) and \
442 start.__class__ == self.__class__ == stop.__class__:
443 ss = str(start.seld) + " thru " + str(stop.seld)
444 else:
445 ss = str(seld)
446 else:
447 ss = str(seld)
448 s = "%s %s" % (self.__class__.__name__, ss)
449 if self.fr: s = s + " of %s" % str(self.fr)
450 return s
451
452
453template = """
454class %s(ComponentItem): want = '%s'
455"""
456
457exec template % ("Text", 'text')
458exec template % ("Character", 'cha ')
459exec template % ("Word", 'cwor')
460exec template % ("Line", 'clin')
461exec template % ("Paragraph", 'cpar')
462exec template % ("Window", 'cwin')
463exec template % ("Document", 'docu')
464exec template % ("File", 'file')
465exec template % ("InsertionPoint", 'cins')
466
467
468def mkobject(dict):
469 want = dict['want'].type
470 form = dict['form'].enum
471 seld = dict['seld']
472 fr = dict['from']
473 if form in ('name', 'indx', 'rang', 'test'):
474 if want == 'text': return Text(seld, fr)
475 if want == 'cha ': return Character(seld, fr)
476 if want == 'cwor': return Word(seld, fr)
477 if want == 'clin': return Line(seld, fr)
478 if want == 'cpar': return Paragraph(seld, fr)
479 if want == 'cwin': return Window(seld, fr)
480 if want == 'docu': return Document(seld, fr)
481 if want == 'file': return File(seld, fr)
482 if want == 'cins': return InsertionPoint(seld, fr)
483 if want == 'prop' and form == 'prop' and IsType(seld):
484 return Property(seld.type, fr)
485 return ObjectSpecifier(want, form, seld, fr)
486
487
488# Special code to unpack an AppleEvent (which is *not* a disguised record!)
489
Guido van Rossum17448e21995-01-30 11:53:55 +0000490aekeywords = [
491 'tran',
492 'rtid',
493 'evcl',
494 'evid',
495 'addr',
496 'optk',
497 'timo',
498 'inte', # this attribute is read only - will be set in AESend
499 'esrc', # this attribute is read only
500 'miss', # this attribute is read only
501 'from' # new in 1.0.1
502]
503
504def missed(ae):
505 try:
506 desc = ae.AEGetAttributeDesc('miss', 'keyw')
507 except AE.Error, msg:
508 return None
509 return desc.data
510
511def unpackevent(ae):
512 parameters = {}
513 while 1:
514 key = missed(ae)
515 if not key: break
516 parameters[key] = unpack(ae.AEGetParamDesc(key, '****'))
517 attributes = {}
518 for key in aekeywords:
519 try:
520 desc = ae.AEGetAttributeDesc(key, '****')
521 except (AE.Error, MacOS.Error), msg:
522 if msg[0] != -1701:
523 raise sys.exc_type, sys.exc_value
524 continue
525 attributes[key] = unpack(desc)
526 return parameters, attributes
527
528def packevent(ae, parameters = {}, attributes = {}):
529 for key, value in parameters.items():
530 ae.AEPutParamDesc(key, pack(value))
531 for key, value in attributes.items():
532 ae.AEPutAttributeDesc(key, pack(value))
533
Guido van Rossum31559231995-02-05 16:59:27 +0000534
535# Test program
536
Guido van Rossum17448e21995-01-30 11:53:55 +0000537def test():
538 target = AE.AECreateDesc('sign', 'KAHL')
539 ae = AE.AECreateAppleEvent('aevt', 'oapp', target, -1, 0)
540 print unpackevent(ae)
Guido van Rossum31559231995-02-05 16:59:27 +0000541 raw_input(":")
542 ae = AE.AECreateAppleEvent('core', 'getd', target, -1, 0)
543 obj = Character(2, Word(1, Document(1)))
544 print obj
545 print repr(obj)
546 packevent(ae, {'----': obj})
547 params, attrs = unpackevent(ae)
548 print params['----']
549 raw_input(":")
Guido van Rossum17448e21995-01-30 11:53:55 +0000550
551if __name__ == '__main__':
552 test()