blob: 347b52379653efd75e0c0f9a3b0b5c97b58442c0 [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
Jack Jansen0ae32202003-04-09 13:25:43 +000012 x = Character(1, Document("foobar"))
Jack Jansend0fc42f2001-08-19 22:05:06 +000013
14and pack(x) will create an AE object reference equivalent to AppleScript's
15
Jack Jansen0ae32202003-04-09 13:25:43 +000016 character 1 of document "foobar"
Jack Jansend0fc42f2001-08-19 22:05:06 +000017
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
Jack Jansen397e9142003-03-31 13:29:32 +000026from Carbon import Evt
Jack Jansen5a6fdcd2001-08-25 12:15:04 +000027from Carbon import AppleEvents
Jack Jansend0fc42f2001-08-19 22:05:06 +000028import MacOS
29import sys
Jack Jansend6ab1532003-03-28 23:42:37 +000030import time
Jack Jansend0fc42f2001-08-19 22:05:06 +000031
32from aetypes import *
Jack Jansen8b777672002-08-07 14:49:00 +000033from aepack import packkey, pack, unpack, coerce, AEDescType
Jack Jansend0fc42f2001-08-19 22:05:06 +000034
35Error = 'aetools.Error'
36
Jack Jansend6ab1532003-03-28 23:42:37 +000037# Amount of time to wait for program to be launched
38LAUNCH_MAX_WAIT_TIME=10
39
Jack Jansend0fc42f2001-08-19 22:05:06 +000040# Special code to unpack an AppleEvent (which is *not* a disguised record!)
41# Note by Jack: No??!? If I read the docs correctly it *is*....
42
43aekeywords = [
Jack Jansen0ae32202003-04-09 13:25:43 +000044 'tran',
45 'rtid',
46 'evcl',
47 'evid',
48 'addr',
49 'optk',
50 'timo',
51 'inte', # this attribute is read only - will be set in AESend
52 'esrc', # this attribute is read only
53 'miss', # this attribute is read only
54 'from' # new in 1.0.1
Jack Jansend0fc42f2001-08-19 22:05:06 +000055]
56
57def missed(ae):
Jack Jansen0ae32202003-04-09 13:25:43 +000058 try:
59 desc = ae.AEGetAttributeDesc('miss', 'keyw')
60 except AE.Error, msg:
61 return None
62 return desc.data
Jack Jansend0fc42f2001-08-19 22:05:06 +000063
Jack Jansen8b777672002-08-07 14:49:00 +000064def unpackevent(ae, formodulename=""):
Jack Jansen0ae32202003-04-09 13:25:43 +000065 parameters = {}
66 try:
67 dirobj = ae.AEGetParamDesc('----', '****')
68 except AE.Error:
69 pass
70 else:
71 parameters['----'] = unpack(dirobj, formodulename)
72 del dirobj
73 # Workaround for what I feel is a bug in OSX 10.2: 'errn' won't show up in missed...
74 try:
75 dirobj = ae.AEGetParamDesc('errn', '****')
76 except AE.Error:
77 pass
78 else:
79 parameters['errn'] = unpack(dirobj, formodulename)
80 del dirobj
81 while 1:
82 key = missed(ae)
83 if not key: break
84 parameters[key] = unpack(ae.AEGetParamDesc(key, '****'), formodulename)
85 attributes = {}
86 for key in aekeywords:
87 try:
88 desc = ae.AEGetAttributeDesc(key, '****')
89 except (AE.Error, MacOS.Error), msg:
90 if msg[0] != -1701 and msg[0] != -1704:
91 raise
92 continue
93 attributes[key] = unpack(desc, formodulename)
94 return parameters, attributes
Jack Jansend0fc42f2001-08-19 22:05:06 +000095
96def packevent(ae, parameters = {}, attributes = {}):
Jack Jansen0ae32202003-04-09 13:25:43 +000097 for key, value in parameters.items():
98 packkey(ae, key, value)
99 for key, value in attributes.items():
100 ae.AEPutAttributeDesc(key, pack(value))
Jack Jansend0fc42f2001-08-19 22:05:06 +0000101
102#
103# Support routine for automatically generated Suite interfaces
104# These routines are also useable for the reverse function.
105#
106def keysubst(arguments, keydict):
Jack Jansen0ae32202003-04-09 13:25:43 +0000107 """Replace long name keys by their 4-char counterparts, and check"""
108 ok = keydict.values()
109 for k in arguments.keys():
110 if keydict.has_key(k):
111 v = arguments[k]
112 del arguments[k]
113 arguments[keydict[k]] = v
114 elif k != '----' and k not in ok:
115 raise TypeError, 'Unknown keyword argument: %s'%k
116
Jack Jansend0fc42f2001-08-19 22:05:06 +0000117def enumsubst(arguments, key, edict):
Jack Jansen0ae32202003-04-09 13:25:43 +0000118 """Substitute a single enum keyword argument, if it occurs"""
119 if not arguments.has_key(key) or edict is None:
120 return
121 v = arguments[key]
122 ok = edict.values()
123 if edict.has_key(v):
124 arguments[key] = Enum(edict[v])
125 elif not v in ok:
126 raise TypeError, 'Unknown enumerator: %s'%v
127
Jack Jansend0fc42f2001-08-19 22:05:06 +0000128def decodeerror(arguments):
Jack Jansen0ae32202003-04-09 13:25:43 +0000129 """Create the 'best' argument for a raise MacOS.Error"""
130 errn = arguments['errn']
131 err_a1 = errn
132 if arguments.has_key('errs'):
133 err_a2 = arguments['errs']
134 else:
135 err_a2 = MacOS.GetErrorString(errn)
136 if arguments.has_key('erob'):
137 err_a3 = arguments['erob']
138 else:
139 err_a3 = None
140
141 return (err_a1, err_a2, err_a3)
Jack Jansend0fc42f2001-08-19 22:05:06 +0000142
143class TalkTo:
Jack Jansen0ae32202003-04-09 13:25:43 +0000144 """An AE connection to an application"""
145 _signature = None # Can be overridden by subclasses
Jack Jansen39c5d662003-06-18 14:19:08 +0000146 _moduleName = None # Can be overridden by subclasses
147 _elemdict = {} # Can be overridden by subclasses
148 _propdict = {} # Can be overridden by subclasses
Jack Jansen0ae32202003-04-09 13:25:43 +0000149
150 __eventloop_initialized = 0
151 def __ensure_WMAvailable(klass):
152 if klass.__eventloop_initialized: return 1
153 if not MacOS.WMAvailable(): return 0
154 # Workaround for a but in MacOSX 10.2: we must have an event
155 # loop before we can call AESend.
156 Evt.WaitNextEvent(0,0)
157 return 1
158 __ensure_WMAvailable = classmethod(__ensure_WMAvailable)
Jack Jansenc8882b12003-06-13 14:27:35 +0000159
Jack Jansen0ae32202003-04-09 13:25:43 +0000160 def __init__(self, signature=None, start=0, timeout=0):
161 """Create a communication channel with a particular application.
162
163 Addressing the application is done by specifying either a
164 4-byte signature, an AEDesc or an object that will __aepack__
165 to an AEDesc.
166 """
167 self.target_signature = None
168 if signature is None:
169 signature = self._signature
170 if type(signature) == AEDescType:
171 self.target = signature
172 elif type(signature) == InstanceType and hasattr(signature, '__aepack__'):
173 self.target = signature.__aepack__()
174 elif type(signature) == StringType and len(signature) == 4:
175 self.target = AE.AECreateDesc(AppleEvents.typeApplSignature, signature)
176 self.target_signature = signature
177 else:
178 raise TypeError, "signature should be 4-char string or AEDesc"
179 self.send_flags = AppleEvents.kAEWaitReply
180 self.send_priority = AppleEvents.kAENormalPriority
181 if timeout:
182 self.send_timeout = timeout
183 else:
184 self.send_timeout = AppleEvents.kAEDefaultTimeout
185 if start:
186 self._start()
187
188 def _start(self):
189 """Start the application, if it is not running yet"""
190 try:
191 self.send('ascr', 'noop')
192 except AE.Error:
193 _launch(self.target_signature)
194 for i in range(LAUNCH_MAX_WAIT_TIME):
195 try:
196 self.send('ascr', 'noop')
197 except AE.Error:
198 pass
199 else:
200 break
201 time.sleep(1)
202
203 def start(self):
204 """Deprecated, used _start()"""
205 self._start()
206
207 def newevent(self, code, subcode, parameters = {}, attributes = {}):
208 """Create a complete structure for an apple event"""
209
210 event = AE.AECreateAppleEvent(code, subcode, self.target,
211 AppleEvents.kAutoGenerateReturnID, AppleEvents.kAnyTransactionID)
212 packevent(event, parameters, attributes)
213 return event
214
215 def sendevent(self, event):
216 """Send a pre-created appleevent, await the reply and unpack it"""
217 if not self.__ensure_WMAvailable():
218 raise RuntimeError, "No window manager access, cannot send AppleEvent"
219 reply = event.AESend(self.send_flags, self.send_priority,
220 self.send_timeout)
221 parameters, attributes = unpackevent(reply, self._moduleName)
222 return reply, parameters, attributes
223
224 def send(self, code, subcode, parameters = {}, attributes = {}):
225 """Send an appleevent given code/subcode/pars/attrs and unpack the reply"""
226 return self.sendevent(self.newevent(code, subcode, parameters, attributes))
227
228 #
229 # The following events are somehow "standard" and don't seem to appear in any
230 # suite...
231 #
232 def activate(self):
233 """Send 'activate' command"""
234 self.send('misc', 'actv')
Jack Jansend0fc42f2001-08-19 22:05:06 +0000235
Jack Jansen0ae32202003-04-09 13:25:43 +0000236 def _get(self, _object, as=None, _attributes={}):
237 """_get: get data from an object
238 Required argument: the object
239 Keyword argument _attributes: AppleEvent attribute dictionary
240 Returns: the data
241 """
242 _code = 'core'
243 _subcode = 'getd'
Jack Jansend0fc42f2001-08-19 22:05:06 +0000244
Jack Jansen0ae32202003-04-09 13:25:43 +0000245 _arguments = {'----':_object}
246 if as:
247 _arguments['rtyp'] = mktype(as)
Jack Jansend0fc42f2001-08-19 22:05:06 +0000248
Jack Jansen0ae32202003-04-09 13:25:43 +0000249 _reply, _arguments, _attributes = self.send(_code, _subcode,
250 _arguments, _attributes)
251 if _arguments.has_key('errn'):
252 raise Error, decodeerror(_arguments)
Jack Jansend0fc42f2001-08-19 22:05:06 +0000253
Jack Jansen0ae32202003-04-09 13:25:43 +0000254 if _arguments.has_key('----'):
255 return _arguments['----']
256 if as:
257 item.__class__ = as
258 return item
259
260 get = _get
261
262 _argmap_set = {
263 'to' : 'data',
264 }
Jack Jansen8b777672002-08-07 14:49:00 +0000265
Jack Jansen0ae32202003-04-09 13:25:43 +0000266 def _set(self, _object, _attributes={}, **_arguments):
267 """set: Set an object's data.
268 Required argument: the object for the command
269 Keyword argument to: The new value.
270 Keyword argument _attributes: AppleEvent attribute dictionary
271 """
272 _code = 'core'
273 _subcode = 'setd'
Jack Jansen9dd78102003-04-01 22:27:18 +0000274
Jack Jansen0ae32202003-04-09 13:25:43 +0000275 keysubst(_arguments, self._argmap_set)
276 _arguments['----'] = _object
Jack Jansen8b777672002-08-07 14:49:00 +0000277
Jack Jansen9dd78102003-04-01 22:27:18 +0000278
Jack Jansen0ae32202003-04-09 13:25:43 +0000279 _reply, _arguments, _attributes = self.send(_code, _subcode,
280 _arguments, _attributes)
281 if _arguments.get('errn', 0):
282 raise Error, decodeerror(_arguments)
283 # XXXX Optionally decode result
284 if _arguments.has_key('----'):
285 return _arguments['----']
286
287 set = _set
Jack Jansend0fc42f2001-08-19 22:05:06 +0000288
Jack Jansen39c5d662003-06-18 14:19:08 +0000289 # Magic glue to allow suite-generated classes to function somewhat
290 # like the "application" class in OSA.
291
Jack Jansenc8882b12003-06-13 14:27:35 +0000292 def __getattr__(self, name):
293 if self._elemdict.has_key(name):
294 cls = self._elemdict[name]
295 return DelayedComponentItem(cls, None)
296 if self._propdict.has_key(name):
297 cls = self._propdict[name]
298 return cls()
299 raise AttributeError, name
300
Jack Jansend0fc42f2001-08-19 22:05:06 +0000301# Tiny Finder class, for local use only
302
303class _miniFinder(TalkTo):
Jack Jansen0ae32202003-04-09 13:25:43 +0000304 def open(self, _object, _attributes={}, **_arguments):
305 """open: Open the specified object(s)
306 Required argument: list of objects to open
307 Keyword argument _attributes: AppleEvent attribute dictionary
308 """
309 _code = 'aevt'
310 _subcode = 'odoc'
Jack Jansend0fc42f2001-08-19 22:05:06 +0000311
Jack Jansen0ae32202003-04-09 13:25:43 +0000312 if _arguments: raise TypeError, 'No optional args expected'
313 _arguments['----'] = _object
Jack Jansend0fc42f2001-08-19 22:05:06 +0000314
315
Jack Jansen0ae32202003-04-09 13:25:43 +0000316 _reply, _arguments, _attributes = self.send(_code, _subcode,
317 _arguments, _attributes)
318 if _arguments.has_key('errn'):
319 raise Error, decodeerror(_arguments)
320 # XXXX Optionally decode result
321 if _arguments.has_key('----'):
322 return _arguments['----']
Jack Jansend0fc42f2001-08-19 22:05:06 +0000323#pass
Jack Jansen0ae32202003-04-09 13:25:43 +0000324
Jack Jansend0fc42f2001-08-19 22:05:06 +0000325_finder = _miniFinder('MACS')
326
327def _launch(appfile):
Jack Jansen0ae32202003-04-09 13:25:43 +0000328 """Open a file thru the finder. Specify file by name or fsspec"""
329 _finder.open(_application_file(('ID ', appfile)))
Jack Jansend0fc42f2001-08-19 22:05:06 +0000330
331
332class _application_file(ComponentItem):
Jack Jansen0ae32202003-04-09 13:25:43 +0000333 """application file - An application's file on disk"""
334 want = 'appf'
335
Jack Jansend0fc42f2001-08-19 22:05:06 +0000336_application_file._propdict = {
337}
338_application_file._elemdict = {
339}
Jack Jansen0ae32202003-04-09 13:25:43 +0000340
Jack Jansend0fc42f2001-08-19 22:05:06 +0000341# Test program
342# XXXX Should test more, really...
343
344def test():
Jack Jansen0ae32202003-04-09 13:25:43 +0000345 target = AE.AECreateDesc('sign', 'quil')
346 ae = AE.AECreateAppleEvent('aevt', 'oapp', target, -1, 0)
347 print unpackevent(ae)
348 raw_input(":")
349 ae = AE.AECreateAppleEvent('core', 'getd', target, -1, 0)
350 obj = Character(2, Word(1, Document(1)))
351 print obj
352 print repr(obj)
353 packevent(ae, {'----': obj})
354 params, attrs = unpackevent(ae)
355 print params['----']
356 raw_input(":")
Jack Jansend0fc42f2001-08-19 22:05:06 +0000357
358if __name__ == '__main__':
Jack Jansen0ae32202003-04-09 13:25:43 +0000359 test()
360 sys.exit(1)