blob: 8ac293d642d3dcf3a4e435ca715cd0c7c46bdcdd [file] [log] [blame]
Jack Jansend0fc42f2001-08-19 22:05:06 +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
18Some of the stuff that appears to be exported from this module comes from other
19files: the pack stuff from aepack, the objects from aetypes.
20
21"""
22
23
24from types import *
Jack Jansen5a6fdcd2001-08-25 12:15:04 +000025from Carbon import AE
26from Carbon import AppleEvents
Jack Jansend0fc42f2001-08-19 22:05:06 +000027import MacOS
28import sys
29
30from aetypes import *
Jack Jansen8b777672002-08-07 14:49:00 +000031from aepack import packkey, pack, unpack, coerce, AEDescType
Jack Jansend0fc42f2001-08-19 22:05:06 +000032
33Error = 'aetools.Error'
34
35# Special code to unpack an AppleEvent (which is *not* a disguised record!)
36# Note by Jack: No??!? If I read the docs correctly it *is*....
37
38aekeywords = [
39 'tran',
40 'rtid',
41 'evcl',
42 'evid',
43 'addr',
44 'optk',
45 'timo',
46 'inte', # this attribute is read only - will be set in AESend
47 'esrc', # this attribute is read only
48 'miss', # this attribute is read only
49 'from' # new in 1.0.1
50]
51
52def missed(ae):
53 try:
54 desc = ae.AEGetAttributeDesc('miss', 'keyw')
55 except AE.Error, msg:
56 return None
57 return desc.data
58
Jack Jansen8b777672002-08-07 14:49:00 +000059def unpackevent(ae, formodulename=""):
Jack Jansend0fc42f2001-08-19 22:05:06 +000060 parameters = {}
61 try:
62 dirobj = ae.AEGetParamDesc('----', '****')
63 except AE.Error:
64 pass
65 else:
Jack Jansen8b777672002-08-07 14:49:00 +000066 parameters['----'] = unpack(dirobj, formodulename)
Jack Jansend0fc42f2001-08-19 22:05:06 +000067 del dirobj
Jack Jansenb1248ce2002-10-25 20:06:29 +000068 # Workaround for what I feel is a bug in OSX 10.2: 'errn' won't show up in missed...
69 try:
70 dirobj = ae.AEGetParamDesc('errn', '****')
71 except AE.Error:
72 pass
73 else:
74 parameters['errn'] = unpack(dirobj, formodulename)
75 del dirobj
Jack Jansend0fc42f2001-08-19 22:05:06 +000076 while 1:
77 key = missed(ae)
78 if not key: break
Jack Jansen8b777672002-08-07 14:49:00 +000079 parameters[key] = unpack(ae.AEGetParamDesc(key, '****'), formodulename)
Jack Jansend0fc42f2001-08-19 22:05:06 +000080 attributes = {}
81 for key in aekeywords:
82 try:
83 desc = ae.AEGetAttributeDesc(key, '****')
84 except (AE.Error, MacOS.Error), msg:
85 if msg[0] != -1701 and msg[0] != -1704:
Just van Rossuma006b8e2003-02-26 15:28:17 +000086 raise
Jack Jansend0fc42f2001-08-19 22:05:06 +000087 continue
Jack Jansen8b777672002-08-07 14:49:00 +000088 attributes[key] = unpack(desc, formodulename)
Jack Jansend0fc42f2001-08-19 22:05:06 +000089 return parameters, attributes
90
91def packevent(ae, parameters = {}, attributes = {}):
92 for key, value in parameters.items():
Jack Jansen8b777672002-08-07 14:49:00 +000093 packkey(ae, key, value)
Jack Jansend0fc42f2001-08-19 22:05:06 +000094 for key, value in attributes.items():
Jack Jansen5b733852003-03-05 21:16:06 +000095 ae.AEPutAttributeDesc(key, pack(value))
Jack Jansend0fc42f2001-08-19 22:05:06 +000096
97#
98# Support routine for automatically generated Suite interfaces
99# These routines are also useable for the reverse function.
100#
101def keysubst(arguments, keydict):
102 """Replace long name keys by their 4-char counterparts, and check"""
103 ok = keydict.values()
104 for k in arguments.keys():
105 if keydict.has_key(k):
106 v = arguments[k]
107 del arguments[k]
108 arguments[keydict[k]] = v
109 elif k != '----' and k not in ok:
110 raise TypeError, 'Unknown keyword argument: %s'%k
111
112def enumsubst(arguments, key, edict):
113 """Substitute a single enum keyword argument, if it occurs"""
114 if not arguments.has_key(key) or edict is None:
115 return
116 v = arguments[key]
117 ok = edict.values()
118 if edict.has_key(v):
Jack Jansen5b733852003-03-05 21:16:06 +0000119 arguments[key] = Enum(edict[v])
Jack Jansend0fc42f2001-08-19 22:05:06 +0000120 elif not v in ok:
121 raise TypeError, 'Unknown enumerator: %s'%v
122
123def decodeerror(arguments):
124 """Create the 'best' argument for a raise MacOS.Error"""
125 errn = arguments['errn']
126 err_a1 = errn
127 if arguments.has_key('errs'):
128 err_a2 = arguments['errs']
129 else:
130 err_a2 = MacOS.GetErrorString(errn)
131 if arguments.has_key('erob'):
132 err_a3 = arguments['erob']
133 else:
134 err_a3 = None
135
136 return (err_a1, err_a2, err_a3)
137
138class TalkTo:
139 """An AE connection to an application"""
140 _signature = None # Can be overridden by subclasses
Jack Jansen8b777672002-08-07 14:49:00 +0000141 _moduleName = None # Can be overridden by subclasses
Jack Jansend0fc42f2001-08-19 22:05:06 +0000142
143 def __init__(self, signature=None, start=0, timeout=0):
144 """Create a communication channel with a particular application.
145
146 Addressing the application is done by specifying either a
147 4-byte signature, an AEDesc or an object that will __aepack__
148 to an AEDesc.
149 """
150 self.target_signature = None
151 if signature is None:
152 signature = self._signature
153 if type(signature) == AEDescType:
154 self.target = signature
155 elif type(signature) == InstanceType and hasattr(signature, '__aepack__'):
156 self.target = signature.__aepack__()
157 elif type(signature) == StringType and len(signature) == 4:
158 self.target = AE.AECreateDesc(AppleEvents.typeApplSignature, signature)
159 self.target_signature = signature
160 else:
161 raise TypeError, "signature should be 4-char string or AEDesc"
162 self.send_flags = AppleEvents.kAEWaitReply
163 self.send_priority = AppleEvents.kAENormalPriority
164 if timeout:
165 self.send_timeout = timeout
166 else:
167 self.send_timeout = AppleEvents.kAEDefaultTimeout
168 if start:
Jack Jansenc8febec2002-01-23 22:46:30 +0000169 self._start()
Jack Jansend0fc42f2001-08-19 22:05:06 +0000170
Jack Jansenc8febec2002-01-23 22:46:30 +0000171 def _start(self):
Jack Jansend0fc42f2001-08-19 22:05:06 +0000172 """Start the application, if it is not running yet"""
173 try:
174 self.send('ascr', 'noop')
175 except AE.Error:
176 _launch(self.target_signature)
177
Jack Jansenc8febec2002-01-23 22:46:30 +0000178 def start(self):
179 """Deprecated, used _start()"""
180 self._start()
181
Jack Jansend0fc42f2001-08-19 22:05:06 +0000182 def newevent(self, code, subcode, parameters = {}, attributes = {}):
183 """Create a complete structure for an apple event"""
184
185 event = AE.AECreateAppleEvent(code, subcode, self.target,
186 AppleEvents.kAutoGenerateReturnID, AppleEvents.kAnyTransactionID)
187 packevent(event, parameters, attributes)
188 return event
189
190 def sendevent(self, event):
191 """Send a pre-created appleevent, await the reply and unpack it"""
192
193 reply = event.AESend(self.send_flags, self.send_priority,
194 self.send_timeout)
Jack Jansen8b777672002-08-07 14:49:00 +0000195 parameters, attributes = unpackevent(reply, self._moduleName)
Jack Jansend0fc42f2001-08-19 22:05:06 +0000196 return reply, parameters, attributes
197
198 def send(self, code, subcode, parameters = {}, attributes = {}):
199 """Send an appleevent given code/subcode/pars/attrs and unpack the reply"""
200 return self.sendevent(self.newevent(code, subcode, parameters, attributes))
201
202 #
203 # The following events are somehow "standard" and don't seem to appear in any
204 # suite...
205 #
206 def activate(self):
207 """Send 'activate' command"""
208 self.send('misc', 'actv')
209
210 def _get(self, _object, as=None, _attributes={}):
211 """_get: get data from an object
212 Required argument: the object
213 Keyword argument _attributes: AppleEvent attribute dictionary
214 Returns: the data
215 """
216 _code = 'core'
217 _subcode = 'getd'
218
219 _arguments = {'----':_object}
220 if as:
221 _arguments['rtyp'] = mktype(as)
222
223 _reply, _arguments, _attributes = self.send(_code, _subcode,
224 _arguments, _attributes)
225 if _arguments.has_key('errn'):
226 raise Error, decodeerror(_arguments)
227
228 if _arguments.has_key('----'):
229 return _arguments['----']
Jack Jansen8b777672002-08-07 14:49:00 +0000230 if as:
231 item.__class__ = as
232 return item
233
234 def _set(self, _object, _arguments = {}, _attributes = {}):
235 """ _set: set data for an object
236 Required argument: the object
237 Keyword argument _parameters: Parameter dictionary for the set operation
238 Keyword argument _attributes: AppleEvent attribute dictionary
239 Returns: the data
240 """
241 _code = 'core'
242 _subcode = 'setd'
243
244 _arguments['----'] = _object
245
246 _reply, _arguments, _attributes = self.send(_code, _subcode,
247 _arguments, _attributes)
248 if _arguments.has_key('errn'):
249 raise Error, decodeerror(_arguments)
250
251 if _arguments.has_key('----'):
252 return _arguments['----']
Jack Jansend0fc42f2001-08-19 22:05:06 +0000253
254# Tiny Finder class, for local use only
255
256class _miniFinder(TalkTo):
257 def open(self, _object, _attributes={}, **_arguments):
258 """open: Open the specified object(s)
259 Required argument: list of objects to open
260 Keyword argument _attributes: AppleEvent attribute dictionary
261 """
262 _code = 'aevt'
263 _subcode = 'odoc'
264
265 if _arguments: raise TypeError, 'No optional args expected'
266 _arguments['----'] = _object
267
268
269 _reply, _arguments, _attributes = self.send(_code, _subcode,
270 _arguments, _attributes)
271 if _arguments.has_key('errn'):
272 raise Error, decodeerror(_arguments)
273 # XXXX Optionally decode result
274 if _arguments.has_key('----'):
275 return _arguments['----']
276#pass
277
278_finder = _miniFinder('MACS')
279
280def _launch(appfile):
281 """Open a file thru the finder. Specify file by name or fsspec"""
282 _finder.open(_application_file(('ID ', appfile)))
283
284
285class _application_file(ComponentItem):
286 """application file - An application's file on disk"""
287 want = 'appf'
288
289_application_file._propdict = {
290}
291_application_file._elemdict = {
292}
293
294# Test program
295# XXXX Should test more, really...
296
297def test():
298 target = AE.AECreateDesc('sign', 'quil')
299 ae = AE.AECreateAppleEvent('aevt', 'oapp', target, -1, 0)
300 print unpackevent(ae)
301 raw_input(":")
302 ae = AE.AECreateAppleEvent('core', 'getd', target, -1, 0)
303 obj = Character(2, Word(1, Document(1)))
304 print obj
305 print repr(obj)
306 packevent(ae, {'----': obj})
307 params, attrs = unpackevent(ae)
308 print params['----']
309 raw_input(":")
310
311if __name__ == '__main__':
312 test()
313 sys.exit(1)