Split aepack and aetypes off from aetools (it was getting too big)
Added support for all basic types mentioned in Apple Event Registry
Added support for automatically-generated suites.
diff --git a/Mac/Lib/toolbox/aetools.py b/Mac/Lib/toolbox/aetools.py
index bb0d20f..565cdec 100644
--- a/Mac/Lib/toolbox/aetools.py
+++ b/Mac/Lib/toolbox/aetools.py
@@ -15,477 +15,22 @@
character 1 of document "foobar"
+Some of the stuff that appears to be exported from this module comes from other
+files: the pack stuff from aepack, the objects from aetypes.
+
"""
-import struct
-import string
-from string import strip
from types import *
import AE
+import AppleEvents
import MacOS
-import macfs
-import StringIO
-
-AEDescType = type(AE.AECreateDesc('TEXT', ''))
-
-FSSType = type(macfs.FSSpec(':'))
-
-
-def pack(x, forcetype = None):
- if forcetype:
- if type(x) is StringType:
- return AE.AECreateDesc(forcetype, x)
- else:
- return pack(x).AECoerceDesc(forcetype)
- if x == None:
- return AE.AECreateDesc('null', '')
- t = type(x)
- if t == AEDescType:
- return x
- if t == FSSType:
- vol, dir, filename = x.as_tuple()
- fnlen = len(filename)
- header = struct.pack('hlb', vol, dir, fnlen)
- padding = '\0'*(63-fnlen)
- return AE.AECreateDesc('fss ', header + filename + padding)
- if t == IntType:
- return AE.AECreateDesc('long', struct.pack('l', x))
- if t == FloatType:
- # XXX Weird thing -- Think C's "double" is 10 bytes, but
- # struct.pack('d') return 12 bytes (and struct.unpack requires
- # them, too). The first 2 bytes seem to be repeated...
- # Probably an alignment problem
- return AE.AECreateDesc('exte', struct.pack('d', x)[2:])
- if t == StringType:
- return AE.AECreateDesc('TEXT', x)
- if t == ListType:
- list = AE.AECreateList('', 0)
- for item in x:
- list.AEPutDesc(0, pack(item))
- return list
- if t == DictionaryType:
- record = AE.AECreateList('', 1)
- for key, value in x.items():
- record.AEPutParamDesc(key, pack(value))
- return record
- if t == InstanceType and hasattr(x, '__aepack__'):
- return x.__aepack__()
- return AE.AECreateDesc('TEXT', repr(x)) # Copout
-
-
-def unpack(desc):
- t = desc.type
- if t == 'TEXT':
- return desc.data
- if t == 'fals':
- return 0
- if t == 'true':
- return 1
- if t == 'enum':
- return mkenum(desc.data)
- if t == 'type':
- return mktype(desc.data)
- if t == 'long':
- return struct.unpack('l', desc.data)[0]
- if t == 'shor':
- return struct.unpack('h', desc.data)[0]
- if t == 'sing':
- return struct.unpack('f', desc.data)[0]
- if t == 'exte':
- data = desc.data
- # XXX See corresponding note for pack()
- return struct.unpack('d', data[:2] + data)[0]
- if t in ('doub', 'comp', 'magn'):
- return unpack(desc.AECoerceDesc('exte'))
- if t == 'null':
- return None
- if t == 'list':
- l = []
- for i in range(desc.AECountItems()):
- keyword, item = desc.AEGetNthDesc(i+1, '****')
- l.append(unpack(item))
- return l
- if t == 'reco':
- d = {}
- for i in range(desc.AECountItems()):
- keyword, item = desc.AEGetNthDesc(i+1, '****')
- d[keyword] = unpack(item)
- return d
- if t == 'obj ':
- record = desc.AECoerceDesc('reco')
- return mkobject(unpack(record))
- if t == 'rang':
- record = desc.AECoerceDesc('reco')
- return mkrange(unpack(record))
- if t == 'cmpd':
- record = desc.AECoerceDesc('reco')
- return mkcomparison(unpack(record))
- if t == 'logi':
- record = desc.AECoerceDesc('reco')
- return mklogical(unpack(record))
- if t == 'targ':
- return mktargetid(desc.data)
- if t == 'alis':
- # XXX Can't handle alias records yet, so coerce to FS spec...
- return unpack(desc.AECoerceDesc('fss '))
- if t == 'fss ':
- return mkfss(desc.data)
- return mkunknown(desc.type, desc.data)
-
-
-def mkfss(data):
- print "mkfss data =", `data`
- vol, dir, fnlen = struct.unpack('hlb', data[:7])
- filename = data[7:7+fnlen]
- print (vol, dir, fnlen, filename)
- return macfs.FSSpec((vol, dir, filename))
-
-
-def mktargetid(data):
- sessionID = getlong(data[:4])
- name = mkppcportrec(data[4:4+72])
- print len(name), `name`
- 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 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 mkunknown(type, data):
- return Unknown(type, data)
-
-class Unknown:
-
- def __init__(self, type, data):
- self.type = type
- self.data = data
-
- def __repr__(self):
- return "Unknown(%s, %s)" % (`self.type`, `self.data`)
-
- def __aepack__(self):
- return pack(self.data, self.type)
-
-
-def IsSubclass(cls, base):
- """Test whether CLASS1 is the same as or a subclass of CLASS2"""
- # Loop to optimize for single inheritance
- while 1:
- if cls is base: return 1
- if len(cls.__bases__) <> 1: break
- cls = cls.__bases__[0]
- # Recurse to cope with multiple inheritance
- for c in cls.__bases__:
- if IsSubclass(c, base): return 1
- return 0
-
-def IsInstance(x, cls):
- """Test whether OBJECT is an instance of (a subclass of) CLASS"""
- return type(x) is InstanceType and IsSubclass(x.__class__, cls)
-
-
-def nice(s):
- if type(s) is StringType: return repr(s)
- else: return str(s)
-
-
-def mkenum(enum):
- if IsEnum(enum): return enum
- return Enum(enum)
-
-class Enum:
-
- def __init__(self, enum):
- self.enum = "%-4.4s" % str(enum)
-
- def __repr__(self):
- return "Enum(%s)" % `self.enum`
-
- def __str__(self):
- return strip(self.enum)
-
- def __aepack__(self):
- return pack(self.enum, 'enum')
-
-def IsEnum(x):
- return IsInstance(x, Enum)
-
-
-def mktype(type):
- if IsType(type): return type
- return Type(type)
-
-class Type:
-
- def __init__(self, type):
- self.type = "%-4.4s" % str(type)
-
- def __repr__(self):
- return "Type(%s)" % `self.type`
-
- def __str__(self):
- return strip(self.type)
-
- def __aepack__(self):
- return pack(self.type, 'type')
-
-def IsType(x):
- return IsInstance(x, Type)
-
-
-def mkrange(dict):
- return Range(dict['star'], dict['stop'])
-
-class Range:
-
- def __init__(self, start, stop):
- self.start = start
- self.stop = stop
-
- def __repr__(self):
- return "Range(%s, %s)" % (`self.start`, `self.stop`)
-
- def __str__(self):
- return "%s thru %s" % (nice(self.start), nice(self.stop))
-
- def __aepack__(self):
- return pack({'star': self.start, 'stop': self.stop}, 'rang')
-
-def IsRange(x):
- return IsInstance(x, Range)
-
-
-def mkcomparison(dict):
- return Comparison(dict['obj1'], dict['relo'].enum, dict['obj2'])
-
-class Comparison:
-
- def __init__(self, obj1, relo, obj2):
- self.obj1 = obj1
- self.relo = "%-4.4s" % str(relo)
- self.obj2 = obj2
-
- def __repr__(self):
- return "Comparison(%s, %s, %s)" % (`self.obj1`, `self.relo`, `self.obj2`)
-
- def __str__(self):
- return "%s %s %s" % (nice(self.obj1), strip(self.relo), nice(self.obj2))
-
- def __aepack__(self):
- return pack({'obj1': self.obj1,
- 'relo': mkenum(self.relo),
- 'obj2': self.obj2},
- 'cmpd')
-
-def IsComparison(x):
- return IsInstance(x, Comparison)
-
-
-def mklogical(dict):
- return Logical(dict['logc'], dict['term'])
-
-class Logical:
-
- def __init__(self, logc, term):
- self.logc = "%-4.4s" % str(logc)
- self.term = term
-
- def __repr__(self):
- return "Logical(%s, %s)" % (`self.logc`, `self.term`)
-
- def __str__(self):
- if type(self.term) == ListType and len(self.term) == 2:
- return "%s %s %s" % (nice(self.term[0]),
- strip(self.logc),
- nice(self.term[1]))
- else:
- return "%s(%s)" % (strip(self.logc), nice(self.term))
-
- def __aepack__(self):
- return pack({'logc': mkenum(self.logc), 'term': self.term}, 'logi')
-
-def IsLogical(x):
- return IsInstance(x, Logical)
-
-
-class ObjectSpecifier:
-
- """A class for constructing and manipulation AE object specifiers in python.
-
- An object specifier is actually a record with four fields:
-
- key type description
- --- ---- -----------
-
- 'want' type what kind of thing we want,
- e.g. word, paragraph or property
-
- 'form' enum how we specify the thing(s) we want,
- e.g. by index, by range, by name, or by property specifier
-
- 'seld' any which thing(s) we want,
- e.g. its index, its name, or its property specifier
-
- 'from' object the object in which it is contained,
- or null, meaning look for it in the application
-
- Note that we don't call this class plain "Object", since that name
- is likely to be used by the application.
- """
-
- def __init__(self, want, form, seld, fr = None):
- self.want = want
- self.form = form
- self.seld = seld
- self.fr = fr
-
- def __repr__(self):
- s = "ObjectSpecifier(%s, %s, %s" % (`self.want`, `self.form`, `self.seld`)
- if self.fr:
- s = s + ", %s)" % `self.fr`
- else:
- s = s + ")"
- return s
-
- def __aepack__(self):
- return pack({'want': mktype(self.want),
- 'form': mkenum(self.form),
- 'seld': self.seld,
- 'from': self.fr},
- 'obj ')
-
-
-def IsObjectSpecifier(x):
- return IsInstance(x, ObjectSpecifier)
-
-
-class Property(ObjectSpecifier):
-
- def __init__(self, which, fr = None):
- ObjectSpecifier.__init__(self, 'prop', 'prop', mkenum(which), fr)
-
- def __repr__(self):
- if self.fr:
- return "Property(%s, %s)" % (`self.seld.enum`, `self.fr`)
- else:
- return "Property(%s)" % `self.seld.enum`
-
- def __str__(self):
- if self.fr:
- return "Property %s of %s" % (str(self.seld), str(self.fr))
- else:
- return "Property %s" % str(self.seld)
-
-
-class SelectableItem(ObjectSpecifier):
-
- def __init__(self, want, seld, fr = None):
- t = type(seld)
- if t == StringType:
- form = 'name'
- elif IsRange(seld):
- form = 'rang'
- elif IsComparison(seld) or IsLogical(seld):
- form = 'test'
- else:
- form = 'indx'
- ObjectSpecifier.__init__(self, want, form, seld, fr)
-
-
-class ComponentItem(SelectableItem):
- # Derived classes *must* set the *class attribute* 'want' to some constant
-
- def __init__(self, which, fr = None):
- SelectableItem.__init__(self, self.want, which, fr)
-
- def __repr__(self):
- if not self.fr:
- return "%s(%s)" % (self.__class__.__name__, `self.seld`)
- return "%s(%s, %s)" % (self.__class__.__name__, `self.seld`, `self.fr`)
-
- def __str__(self):
- seld = self.seld
- if type(seld) == StringType:
- ss = repr(seld)
- elif IsRange(seld):
- start, stop = seld.start, seld.stop
- if type(start) == InstanceType == type(stop) and \
- start.__class__ == self.__class__ == stop.__class__:
- ss = str(start.seld) + " thru " + str(stop.seld)
- else:
- ss = str(seld)
- else:
- ss = str(seld)
- s = "%s %s" % (self.__class__.__name__, ss)
- if self.fr: s = s + " of %s" % str(self.fr)
- return s
-
-
-template = """
-class %s(ComponentItem): want = '%s'
-"""
-
-exec template % ("Text", 'text')
-exec template % ("Character", 'cha ')
-exec template % ("Word", 'cwor')
-exec template % ("Line", 'clin')
-exec template % ("Paragraph", 'cpar')
-exec template % ("Window", 'cwin')
-exec template % ("Document", 'docu')
-exec template % ("File", 'file')
-exec template % ("InsertionPoint", 'cins')
-
-
-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 Text(seld, fr)
- if want == 'cha ': return Character(seld, fr)
- if want == 'cwor': return Word(seld, fr)
- if want == 'clin': return Line(seld, fr)
- if want == 'cpar': return Paragraph(seld, fr)
- if want == 'cwin': return Window(seld, fr)
- if want == 'docu': return Document(seld, fr)
- if want == 'file': return File(seld, fr)
- if want == 'cins': return InsertionPoint(seld, fr)
- if want == 'prop' and form == 'prop' and IsType(seld):
- return Property(seld.type, fr)
- return ObjectSpecifier(want, form, seld, fr)
-
+from aetypes import *
+from aepack import pack, unpack, coerce, AEDescType
# Special code to unpack an AppleEvent (which is *not* a disguised record!)
+# Note by Jack: No??!? If I read the docs correctly it *is*....
aekeywords = [
'tran',
@@ -531,8 +76,90 @@
for key, value in attributes.items():
ae.AEPutAttributeDesc(key, pack(value))
+#
+# Support routine for automatically generated Suite interfaces
+#
+def keysubst(arguments, keydict):
+ """Replace long name keys by their 4-char counterparts, and check"""
+ ok = keydict.values()
+ for k in arguments.keys():
+ if keydict.has_key(k):
+ v = arguments[k]
+ del arguments[k]
+ arguments[keydict[k]] = v
+ elif k != '----' and k not in ok:
+ raise TypeError, 'Unknown keyword argument: %s'%k
+
+def enumsubst(arguments, key, edict):
+ """Substitute a single enum keyword argument, if it occurs"""
+ if not arguments.has_key(key):
+ return
+ v = arguments[key]
+ ok = edict.values()
+ if edict.has_key(v):
+ arguments[key] = edict[v]
+ elif not v in ok:
+ raise TypeError, 'Unknown enumerator: %s'%v
+
+def decodeerror(arguments):
+ """Create the 'best' argument for a raise MacOS.Error"""
+ errn = arguments['errn']
+ errarg = (errn, MacOS.GetErrorString(errn))
+ if arguments.has_key('errs'):
+ errarg = errarg + (arguments['errs'],)
+ if arguments.has_key('erob'):
+ errarg = errarg + (arguments['erob'],)
+ return errarg
+class TalkTo:
+ """An AE connection to an application"""
+
+ def __init__(self, signature):
+ """Create a communication channel with a particular application.
+
+ Addressing the application is done by specifying either a
+ 4-byte signature, an AEDesc or an object that will __aepack__
+ to an AEDesc.
+ """
+ if type(signature) == AEDescType:
+ self.target = signature
+ elif type(signature) == InstanceType and hasattr(signature, '__aepack__'):
+ self.target = signature.__aepack__()
+ elif type(signature) == StringType and len(signature) != 4:
+ self.target = AE.AECreateDesc(AppleEvents.typeApplSignature, signature)
+ else:
+ raise TypeError, "signature should be 4-char string or AEDesc"
+ self.send_flags = AppleEvents.kAEWaitReply
+ self.send_priority = AppleEvents.kAENormalPriority
+ self.send_timeout = AppleEvents.kAEDefaultTimeout
+
+ def newevent(self, code, subcode, parameters = {}, attributes = {}):
+ """Create a complete structure for an apple event"""
+
+ event = AE.AECreateAppleEvent(code, subcode, self.target,
+ AppleEvents.kAutoGenerateReturnID, AppleEvents.kAnyTransactionID)
+ packevent(event, parameters, attributes)
+ return event
+
+ def sendevent(self, event):
+ """Send a pre-created appleevent, await the reply and unpack it"""
+
+ reply = event.AESend(self.send_flags, self.send_priority,
+ self.send_timeout)
+ parameters, attributes = unpackevent(reply)
+ return reply, parameters, attributes
+
+ def send(self, code, subcode, parameters = {}, attributes = {}):
+ """Send an appleevent given code/subcode/pars/attrs and unpack the reply"""
+ return self.sendevent(self.newevent(code, subcode, parameters, attributes))
+
+ def activate(self):
+ """Send 'activate' command"""
+ self.send('misc', 'actv')
+
+
# Test program
+# XXXX Should test more, really...
def test():
target = AE.AECreateDesc('sign', 'KAHL')