|
1 """Tools for use in AppleEvent clients and servers. |
|
2 |
|
3 pack(x) converts a Python object to an AEDesc object |
|
4 unpack(desc) does the reverse |
|
5 |
|
6 packevent(event, parameters, attributes) sets params and attrs in an AEAppleEvent record |
|
7 unpackevent(event) returns the parameters and attributes from an AEAppleEvent record |
|
8 |
|
9 Plus... Lots of classes and routines that help representing AE objects, |
|
10 ranges, conditionals, logicals, etc., so you can write, e.g.: |
|
11 |
|
12 x = Character(1, Document("foobar")) |
|
13 |
|
14 and pack(x) will create an AE object reference equivalent to AppleScript's |
|
15 |
|
16 character 1 of document "foobar" |
|
17 |
|
18 Some of the stuff that appears to be exported from this module comes from other |
|
19 files: the pack stuff from aepack, the objects from aetypes. |
|
20 |
|
21 """ |
|
22 |
|
23 |
|
24 from warnings import warnpy3k |
|
25 warnpy3k("In 3.x, the aetools module is removed.", stacklevel=2) |
|
26 |
|
27 from types import * |
|
28 from Carbon import AE |
|
29 from Carbon import Evt |
|
30 from Carbon import AppleEvents |
|
31 import MacOS |
|
32 import sys |
|
33 import time |
|
34 |
|
35 from aetypes import * |
|
36 from aepack import packkey, pack, unpack, coerce, AEDescType |
|
37 |
|
38 Error = 'aetools.Error' |
|
39 |
|
40 # Amount of time to wait for program to be launched |
|
41 LAUNCH_MAX_WAIT_TIME=10 |
|
42 |
|
43 # Special code to unpack an AppleEvent (which is *not* a disguised record!) |
|
44 # Note by Jack: No??!? If I read the docs correctly it *is*.... |
|
45 |
|
46 aekeywords = [ |
|
47 'tran', |
|
48 'rtid', |
|
49 'evcl', |
|
50 'evid', |
|
51 'addr', |
|
52 'optk', |
|
53 'timo', |
|
54 'inte', # this attribute is read only - will be set in AESend |
|
55 'esrc', # this attribute is read only |
|
56 'miss', # this attribute is read only |
|
57 'from' # new in 1.0.1 |
|
58 ] |
|
59 |
|
60 def missed(ae): |
|
61 try: |
|
62 desc = ae.AEGetAttributeDesc('miss', 'keyw') |
|
63 except AE.Error, msg: |
|
64 return None |
|
65 return desc.data |
|
66 |
|
67 def unpackevent(ae, formodulename=""): |
|
68 parameters = {} |
|
69 try: |
|
70 dirobj = ae.AEGetParamDesc('----', '****') |
|
71 except AE.Error: |
|
72 pass |
|
73 else: |
|
74 parameters['----'] = unpack(dirobj, formodulename) |
|
75 del dirobj |
|
76 # Workaround for what I feel is a bug in OSX 10.2: 'errn' won't show up in missed... |
|
77 try: |
|
78 dirobj = ae.AEGetParamDesc('errn', '****') |
|
79 except AE.Error: |
|
80 pass |
|
81 else: |
|
82 parameters['errn'] = unpack(dirobj, formodulename) |
|
83 del dirobj |
|
84 while 1: |
|
85 key = missed(ae) |
|
86 if not key: break |
|
87 parameters[key] = unpack(ae.AEGetParamDesc(key, '****'), formodulename) |
|
88 attributes = {} |
|
89 for key in aekeywords: |
|
90 try: |
|
91 desc = ae.AEGetAttributeDesc(key, '****') |
|
92 except (AE.Error, MacOS.Error), msg: |
|
93 if msg[0] != -1701 and msg[0] != -1704: |
|
94 raise |
|
95 continue |
|
96 attributes[key] = unpack(desc, formodulename) |
|
97 return parameters, attributes |
|
98 |
|
99 def packevent(ae, parameters = {}, attributes = {}): |
|
100 for key, value in parameters.items(): |
|
101 packkey(ae, key, value) |
|
102 for key, value in attributes.items(): |
|
103 ae.AEPutAttributeDesc(key, pack(value)) |
|
104 |
|
105 # |
|
106 # Support routine for automatically generated Suite interfaces |
|
107 # These routines are also useable for the reverse function. |
|
108 # |
|
109 def keysubst(arguments, keydict): |
|
110 """Replace long name keys by their 4-char counterparts, and check""" |
|
111 ok = keydict.values() |
|
112 for k in arguments.keys(): |
|
113 if keydict.has_key(k): |
|
114 v = arguments[k] |
|
115 del arguments[k] |
|
116 arguments[keydict[k]] = v |
|
117 elif k != '----' and k not in ok: |
|
118 raise TypeError, 'Unknown keyword argument: %s'%k |
|
119 |
|
120 def enumsubst(arguments, key, edict): |
|
121 """Substitute a single enum keyword argument, if it occurs""" |
|
122 if not arguments.has_key(key) or edict is None: |
|
123 return |
|
124 v = arguments[key] |
|
125 ok = edict.values() |
|
126 if edict.has_key(v): |
|
127 arguments[key] = Enum(edict[v]) |
|
128 elif not v in ok: |
|
129 raise TypeError, 'Unknown enumerator: %s'%v |
|
130 |
|
131 def decodeerror(arguments): |
|
132 """Create the 'best' argument for a raise MacOS.Error""" |
|
133 errn = arguments['errn'] |
|
134 err_a1 = errn |
|
135 if arguments.has_key('errs'): |
|
136 err_a2 = arguments['errs'] |
|
137 else: |
|
138 err_a2 = MacOS.GetErrorString(errn) |
|
139 if arguments.has_key('erob'): |
|
140 err_a3 = arguments['erob'] |
|
141 else: |
|
142 err_a3 = None |
|
143 |
|
144 return (err_a1, err_a2, err_a3) |
|
145 |
|
146 class TalkTo: |
|
147 """An AE connection to an application""" |
|
148 _signature = None # Can be overridden by subclasses |
|
149 _moduleName = None # Can be overridden by subclasses |
|
150 _elemdict = {} # Can be overridden by subclasses |
|
151 _propdict = {} # Can be overridden by subclasses |
|
152 |
|
153 __eventloop_initialized = 0 |
|
154 def __ensure_WMAvailable(klass): |
|
155 if klass.__eventloop_initialized: return 1 |
|
156 if not MacOS.WMAvailable(): return 0 |
|
157 # Workaround for a but in MacOSX 10.2: we must have an event |
|
158 # loop before we can call AESend. |
|
159 Evt.WaitNextEvent(0,0) |
|
160 return 1 |
|
161 __ensure_WMAvailable = classmethod(__ensure_WMAvailable) |
|
162 |
|
163 def __init__(self, signature=None, start=0, timeout=0): |
|
164 """Create a communication channel with a particular application. |
|
165 |
|
166 Addressing the application is done by specifying either a |
|
167 4-byte signature, an AEDesc or an object that will __aepack__ |
|
168 to an AEDesc. |
|
169 """ |
|
170 self.target_signature = None |
|
171 if signature is None: |
|
172 signature = self._signature |
|
173 if type(signature) == AEDescType: |
|
174 self.target = signature |
|
175 elif type(signature) == InstanceType and hasattr(signature, '__aepack__'): |
|
176 self.target = signature.__aepack__() |
|
177 elif type(signature) == StringType and len(signature) == 4: |
|
178 self.target = AE.AECreateDesc(AppleEvents.typeApplSignature, signature) |
|
179 self.target_signature = signature |
|
180 else: |
|
181 raise TypeError, "signature should be 4-char string or AEDesc" |
|
182 self.send_flags = AppleEvents.kAEWaitReply |
|
183 self.send_priority = AppleEvents.kAENormalPriority |
|
184 if timeout: |
|
185 self.send_timeout = timeout |
|
186 else: |
|
187 self.send_timeout = AppleEvents.kAEDefaultTimeout |
|
188 if start: |
|
189 self._start() |
|
190 |
|
191 def _start(self): |
|
192 """Start the application, if it is not running yet""" |
|
193 try: |
|
194 self.send('ascr', 'noop') |
|
195 except AE.Error: |
|
196 _launch(self.target_signature) |
|
197 for i in range(LAUNCH_MAX_WAIT_TIME): |
|
198 try: |
|
199 self.send('ascr', 'noop') |
|
200 except AE.Error: |
|
201 pass |
|
202 else: |
|
203 break |
|
204 time.sleep(1) |
|
205 |
|
206 def start(self): |
|
207 """Deprecated, used _start()""" |
|
208 self._start() |
|
209 |
|
210 def newevent(self, code, subcode, parameters = {}, attributes = {}): |
|
211 """Create a complete structure for an apple event""" |
|
212 |
|
213 event = AE.AECreateAppleEvent(code, subcode, self.target, |
|
214 AppleEvents.kAutoGenerateReturnID, AppleEvents.kAnyTransactionID) |
|
215 packevent(event, parameters, attributes) |
|
216 return event |
|
217 |
|
218 def sendevent(self, event): |
|
219 """Send a pre-created appleevent, await the reply and unpack it""" |
|
220 if not self.__ensure_WMAvailable(): |
|
221 raise RuntimeError, "No window manager access, cannot send AppleEvent" |
|
222 reply = event.AESend(self.send_flags, self.send_priority, |
|
223 self.send_timeout) |
|
224 parameters, attributes = unpackevent(reply, self._moduleName) |
|
225 return reply, parameters, attributes |
|
226 |
|
227 def send(self, code, subcode, parameters = {}, attributes = {}): |
|
228 """Send an appleevent given code/subcode/pars/attrs and unpack the reply""" |
|
229 return self.sendevent(self.newevent(code, subcode, parameters, attributes)) |
|
230 |
|
231 # |
|
232 # The following events are somehow "standard" and don't seem to appear in any |
|
233 # suite... |
|
234 # |
|
235 def activate(self): |
|
236 """Send 'activate' command""" |
|
237 self.send('misc', 'actv') |
|
238 |
|
239 def _get(self, _object, asfile=None, _attributes={}): |
|
240 """_get: get data from an object |
|
241 Required argument: the object |
|
242 Keyword argument _attributes: AppleEvent attribute dictionary |
|
243 Returns: the data |
|
244 """ |
|
245 _code = 'core' |
|
246 _subcode = 'getd' |
|
247 |
|
248 _arguments = {'----':_object} |
|
249 if asfile: |
|
250 _arguments['rtyp'] = mktype(asfile) |
|
251 |
|
252 _reply, _arguments, _attributes = self.send(_code, _subcode, |
|
253 _arguments, _attributes) |
|
254 if _arguments.has_key('errn'): |
|
255 raise Error, decodeerror(_arguments) |
|
256 |
|
257 if _arguments.has_key('----'): |
|
258 return _arguments['----'] |
|
259 if asfile: |
|
260 item.__class__ = asfile |
|
261 return item |
|
262 |
|
263 get = _get |
|
264 |
|
265 _argmap_set = { |
|
266 'to' : 'data', |
|
267 } |
|
268 |
|
269 def _set(self, _object, _attributes={}, **_arguments): |
|
270 """set: Set an object's data. |
|
271 Required argument: the object for the command |
|
272 Keyword argument to: The new value. |
|
273 Keyword argument _attributes: AppleEvent attribute dictionary |
|
274 """ |
|
275 _code = 'core' |
|
276 _subcode = 'setd' |
|
277 |
|
278 keysubst(_arguments, self._argmap_set) |
|
279 _arguments['----'] = _object |
|
280 |
|
281 |
|
282 _reply, _arguments, _attributes = self.send(_code, _subcode, |
|
283 _arguments, _attributes) |
|
284 if _arguments.get('errn', 0): |
|
285 raise Error, decodeerror(_arguments) |
|
286 # XXXX Optionally decode result |
|
287 if _arguments.has_key('----'): |
|
288 return _arguments['----'] |
|
289 |
|
290 set = _set |
|
291 |
|
292 # Magic glue to allow suite-generated classes to function somewhat |
|
293 # like the "application" class in OSA. |
|
294 |
|
295 def __getattr__(self, name): |
|
296 if self._elemdict.has_key(name): |
|
297 cls = self._elemdict[name] |
|
298 return DelayedComponentItem(cls, None) |
|
299 if self._propdict.has_key(name): |
|
300 cls = self._propdict[name] |
|
301 return cls() |
|
302 raise AttributeError, name |
|
303 |
|
304 # Tiny Finder class, for local use only |
|
305 |
|
306 class _miniFinder(TalkTo): |
|
307 def open(self, _object, _attributes={}, **_arguments): |
|
308 """open: Open the specified object(s) |
|
309 Required argument: list of objects to open |
|
310 Keyword argument _attributes: AppleEvent attribute dictionary |
|
311 """ |
|
312 _code = 'aevt' |
|
313 _subcode = 'odoc' |
|
314 |
|
315 if _arguments: raise TypeError, 'No optional args expected' |
|
316 _arguments['----'] = _object |
|
317 |
|
318 |
|
319 _reply, _arguments, _attributes = self.send(_code, _subcode, |
|
320 _arguments, _attributes) |
|
321 if _arguments.has_key('errn'): |
|
322 raise Error, decodeerror(_arguments) |
|
323 # XXXX Optionally decode result |
|
324 if _arguments.has_key('----'): |
|
325 return _arguments['----'] |
|
326 #pass |
|
327 |
|
328 _finder = _miniFinder('MACS') |
|
329 |
|
330 def _launch(appfile): |
|
331 """Open a file thru the finder. Specify file by name or fsspec""" |
|
332 _finder.open(_application_file(('ID ', appfile))) |
|
333 |
|
334 |
|
335 class _application_file(ComponentItem): |
|
336 """application file - An application's file on disk""" |
|
337 want = 'appf' |
|
338 |
|
339 _application_file._propdict = { |
|
340 } |
|
341 _application_file._elemdict = { |
|
342 } |
|
343 |
|
344 # Test program |
|
345 # XXXX Should test more, really... |
|
346 |
|
347 def test(): |
|
348 target = AE.AECreateDesc('sign', 'quil') |
|
349 ae = AE.AECreateAppleEvent('aevt', 'oapp', target, -1, 0) |
|
350 print unpackevent(ae) |
|
351 raw_input(":") |
|
352 ae = AE.AECreateAppleEvent('core', 'getd', target, -1, 0) |
|
353 obj = Character(2, Word(1, Document(1))) |
|
354 print obj |
|
355 print repr(obj) |
|
356 packevent(ae, {'----': obj}) |
|
357 params, attrs = unpackevent(ae) |
|
358 print params['----'] |
|
359 raw_input(":") |
|
360 |
|
361 if __name__ == '__main__': |
|
362 test() |
|
363 sys.exit(1) |