blob: 06395416a6cd233cb1eac7c851e41d8d7f5132f1 [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
Jack Jansend6ab1532003-03-28 23:42:37 +000029import time
Jack Jansend0fc42f2001-08-19 22:05:06 +000030
31from aetypes import *
Jack Jansen8b777672002-08-07 14:49:00 +000032from aepack import packkey, pack, unpack, coerce, AEDescType
Jack Jansend0fc42f2001-08-19 22:05:06 +000033
34Error = 'aetools.Error'
35
Jack Jansend6ab1532003-03-28 23:42:37 +000036# Amount of time to wait for program to be launched
37LAUNCH_MAX_WAIT_TIME=10
38
Jack Jansend0fc42f2001-08-19 22:05:06 +000039# Special code to unpack an AppleEvent (which is *not* a disguised record!)
40# Note by Jack: No??!? If I read the docs correctly it *is*....
41
42aekeywords = [
43 'tran',
44 'rtid',
45 'evcl',
46 'evid',
47 'addr',
48 'optk',
49 'timo',
50 'inte', # this attribute is read only - will be set in AESend
51 'esrc', # this attribute is read only
52 'miss', # this attribute is read only
53 'from' # new in 1.0.1
54]
55
56def missed(ae):
57 try:
58 desc = ae.AEGetAttributeDesc('miss', 'keyw')
59 except AE.Error, msg:
60 return None
61 return desc.data
62
Jack Jansen8b777672002-08-07 14:49:00 +000063def unpackevent(ae, formodulename=""):
Jack Jansend0fc42f2001-08-19 22:05:06 +000064 parameters = {}
65 try:
66 dirobj = ae.AEGetParamDesc('----', '****')
67 except AE.Error:
68 pass
69 else:
Jack Jansen8b777672002-08-07 14:49:00 +000070 parameters['----'] = unpack(dirobj, formodulename)
Jack Jansend0fc42f2001-08-19 22:05:06 +000071 del dirobj
Jack Jansenb1248ce2002-10-25 20:06:29 +000072 # Workaround for what I feel is a bug in OSX 10.2: 'errn' won't show up in missed...
73 try:
74 dirobj = ae.AEGetParamDesc('errn', '****')
75 except AE.Error:
76 pass
77 else:
78 parameters['errn'] = unpack(dirobj, formodulename)
79 del dirobj
Jack Jansend0fc42f2001-08-19 22:05:06 +000080 while 1:
81 key = missed(ae)
82 if not key: break
Jack Jansen8b777672002-08-07 14:49:00 +000083 parameters[key] = unpack(ae.AEGetParamDesc(key, '****'), formodulename)
Jack Jansend0fc42f2001-08-19 22:05:06 +000084 attributes = {}
85 for key in aekeywords:
86 try:
87 desc = ae.AEGetAttributeDesc(key, '****')
88 except (AE.Error, MacOS.Error), msg:
89 if msg[0] != -1701 and msg[0] != -1704:
Just van Rossuma006b8e2003-02-26 15:28:17 +000090 raise
Jack Jansend0fc42f2001-08-19 22:05:06 +000091 continue
Jack Jansen8b777672002-08-07 14:49:00 +000092 attributes[key] = unpack(desc, formodulename)
Jack Jansend0fc42f2001-08-19 22:05:06 +000093 return parameters, attributes
94
95def packevent(ae, parameters = {}, attributes = {}):
96 for key, value in parameters.items():
Jack Jansen8b777672002-08-07 14:49:00 +000097 packkey(ae, key, value)
Jack Jansend0fc42f2001-08-19 22:05:06 +000098 for key, value in attributes.items():
Jack Jansen5b733852003-03-05 21:16:06 +000099 ae.AEPutAttributeDesc(key, pack(value))
Jack Jansend0fc42f2001-08-19 22:05:06 +0000100
101#
102# Support routine for automatically generated Suite interfaces
103# These routines are also useable for the reverse function.
104#
105def keysubst(arguments, keydict):
106 """Replace long name keys by their 4-char counterparts, and check"""
107 ok = keydict.values()
108 for k in arguments.keys():
109 if keydict.has_key(k):
110 v = arguments[k]
111 del arguments[k]
112 arguments[keydict[k]] = v
113 elif k != '----' and k not in ok:
114 raise TypeError, 'Unknown keyword argument: %s'%k
115
116def enumsubst(arguments, key, edict):
117 """Substitute a single enum keyword argument, if it occurs"""
118 if not arguments.has_key(key) or edict is None:
119 return
120 v = arguments[key]
121 ok = edict.values()
122 if edict.has_key(v):
Jack Jansen5b733852003-03-05 21:16:06 +0000123 arguments[key] = Enum(edict[v])
Jack Jansend0fc42f2001-08-19 22:05:06 +0000124 elif not v in ok:
125 raise TypeError, 'Unknown enumerator: %s'%v
126
127def decodeerror(arguments):
128 """Create the 'best' argument for a raise MacOS.Error"""
129 errn = arguments['errn']
130 err_a1 = errn
131 if arguments.has_key('errs'):
132 err_a2 = arguments['errs']
133 else:
134 err_a2 = MacOS.GetErrorString(errn)
135 if arguments.has_key('erob'):
136 err_a3 = arguments['erob']
137 else:
138 err_a3 = None
139
140 return (err_a1, err_a2, err_a3)
141
142class TalkTo:
143 """An AE connection to an application"""
144 _signature = None # Can be overridden by subclasses
Jack Jansen8b777672002-08-07 14:49:00 +0000145 _moduleName = None # Can be overridden by subclasses
Jack Jansend0fc42f2001-08-19 22:05:06 +0000146
147 def __init__(self, signature=None, start=0, timeout=0):
148 """Create a communication channel with a particular application.
149
150 Addressing the application is done by specifying either a
151 4-byte signature, an AEDesc or an object that will __aepack__
152 to an AEDesc.
153 """
154 self.target_signature = None
155 if signature is None:
156 signature = self._signature
157 if type(signature) == AEDescType:
158 self.target = signature
159 elif type(signature) == InstanceType and hasattr(signature, '__aepack__'):
160 self.target = signature.__aepack__()
161 elif type(signature) == StringType and len(signature) == 4:
162 self.target = AE.AECreateDesc(AppleEvents.typeApplSignature, signature)
163 self.target_signature = signature
164 else:
165 raise TypeError, "signature should be 4-char string or AEDesc"
166 self.send_flags = AppleEvents.kAEWaitReply
167 self.send_priority = AppleEvents.kAENormalPriority
168 if timeout:
169 self.send_timeout = timeout
170 else:
171 self.send_timeout = AppleEvents.kAEDefaultTimeout
172 if start:
Jack Jansenc8febec2002-01-23 22:46:30 +0000173 self._start()
Jack Jansend0fc42f2001-08-19 22:05:06 +0000174
Jack Jansenc8febec2002-01-23 22:46:30 +0000175 def _start(self):
Jack Jansend0fc42f2001-08-19 22:05:06 +0000176 """Start the application, if it is not running yet"""
177 try:
178 self.send('ascr', 'noop')
179 except AE.Error:
180 _launch(self.target_signature)
Jack Jansend6ab1532003-03-28 23:42:37 +0000181 for i in range(LAUNCH_MAX_WAIT_TIME):
182 try:
183 self.send('ascr', 'noop')
184 except AE.Error:
185 pass
186 else:
187 break
188 time.sleep(1)
Jack Jansend0fc42f2001-08-19 22:05:06 +0000189
Jack Jansenc8febec2002-01-23 22:46:30 +0000190 def start(self):
191 """Deprecated, used _start()"""
192 self._start()
193
Jack Jansend0fc42f2001-08-19 22:05:06 +0000194 def newevent(self, code, subcode, parameters = {}, attributes = {}):
195 """Create a complete structure for an apple event"""
196
197 event = AE.AECreateAppleEvent(code, subcode, self.target,
198 AppleEvents.kAutoGenerateReturnID, AppleEvents.kAnyTransactionID)
199 packevent(event, parameters, attributes)
200 return event
201
202 def sendevent(self, event):
203 """Send a pre-created appleevent, await the reply and unpack it"""
204
205 reply = event.AESend(self.send_flags, self.send_priority,
206 self.send_timeout)
Jack Jansen8b777672002-08-07 14:49:00 +0000207 parameters, attributes = unpackevent(reply, self._moduleName)
Jack Jansend0fc42f2001-08-19 22:05:06 +0000208 return reply, parameters, attributes
209
210 def send(self, code, subcode, parameters = {}, attributes = {}):
211 """Send an appleevent given code/subcode/pars/attrs and unpack the reply"""
212 return self.sendevent(self.newevent(code, subcode, parameters, attributes))
213
214 #
215 # The following events are somehow "standard" and don't seem to appear in any
216 # suite...
217 #
218 def activate(self):
219 """Send 'activate' command"""
220 self.send('misc', 'actv')
221
222 def _get(self, _object, as=None, _attributes={}):
223 """_get: get data from an object
224 Required argument: the object
225 Keyword argument _attributes: AppleEvent attribute dictionary
226 Returns: the data
227 """
228 _code = 'core'
229 _subcode = 'getd'
230
231 _arguments = {'----':_object}
232 if as:
233 _arguments['rtyp'] = mktype(as)
234
235 _reply, _arguments, _attributes = self.send(_code, _subcode,
236 _arguments, _attributes)
237 if _arguments.has_key('errn'):
238 raise Error, decodeerror(_arguments)
239
240 if _arguments.has_key('----'):
241 return _arguments['----']
Jack Jansen8b777672002-08-07 14:49:00 +0000242 if as:
243 item.__class__ = as
244 return item
245
246 def _set(self, _object, _arguments = {}, _attributes = {}):
247 """ _set: set data for an object
248 Required argument: the object
249 Keyword argument _parameters: Parameter dictionary for the set operation
250 Keyword argument _attributes: AppleEvent attribute dictionary
251 Returns: the data
252 """
253 _code = 'core'
254 _subcode = 'setd'
255
256 _arguments['----'] = _object
257
258 _reply, _arguments, _attributes = self.send(_code, _subcode,
259 _arguments, _attributes)
260 if _arguments.has_key('errn'):
261 raise Error, decodeerror(_arguments)
262
263 if _arguments.has_key('----'):
264 return _arguments['----']
Jack Jansend0fc42f2001-08-19 22:05:06 +0000265
266# Tiny Finder class, for local use only
267
268class _miniFinder(TalkTo):
269 def open(self, _object, _attributes={}, **_arguments):
270 """open: Open the specified object(s)
271 Required argument: list of objects to open
272 Keyword argument _attributes: AppleEvent attribute dictionary
273 """
274 _code = 'aevt'
275 _subcode = 'odoc'
276
277 if _arguments: raise TypeError, 'No optional args expected'
278 _arguments['----'] = _object
279
280
281 _reply, _arguments, _attributes = self.send(_code, _subcode,
282 _arguments, _attributes)
283 if _arguments.has_key('errn'):
284 raise Error, decodeerror(_arguments)
285 # XXXX Optionally decode result
286 if _arguments.has_key('----'):
287 return _arguments['----']
288#pass
289
290_finder = _miniFinder('MACS')
291
292def _launch(appfile):
293 """Open a file thru the finder. Specify file by name or fsspec"""
294 _finder.open(_application_file(('ID ', appfile)))
295
296
297class _application_file(ComponentItem):
298 """application file - An application's file on disk"""
299 want = 'appf'
300
301_application_file._propdict = {
302}
303_application_file._elemdict = {
304}
305
306# Test program
307# XXXX Should test more, really...
308
309def test():
310 target = AE.AECreateDesc('sign', 'quil')
311 ae = AE.AECreateAppleEvent('aevt', 'oapp', target, -1, 0)
312 print unpackevent(ae)
313 raw_input(":")
314 ae = AE.AECreateAppleEvent('core', 'getd', target, -1, 0)
315 obj = Character(2, Word(1, Document(1)))
316 print obj
317 print repr(obj)
318 packevent(ae, {'----': obj})
319 params, attrs = unpackevent(ae)
320 print params['----']
321 raw_input(":")
322
323if __name__ == '__main__':
324 test()
325 sys.exit(1)