blob: bf27663dfe5d15acc336044a6a06e0d6e006e52a [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
Benjamin Peterson23681932008-05-12 21:42:13 +000024from warnings import warnpy3k
Benjamin Petersona6864e02008-07-14 17:42:17 +000025warnpy3k("In 3.x, the aetools module is removed.", stacklevel=2)
Benjamin Peterson23681932008-05-12 21:42:13 +000026
Jack Jansend0fc42f2001-08-19 22:05:06 +000027from types import *
Jack Jansen5a6fdcd2001-08-25 12:15:04 +000028from Carbon import AE
Jack Jansen397e9142003-03-31 13:29:32 +000029from Carbon import Evt
Jack Jansen5a6fdcd2001-08-25 12:15:04 +000030from Carbon import AppleEvents
Jack Jansend0fc42f2001-08-19 22:05:06 +000031import MacOS
32import sys
Jack Jansend6ab1532003-03-28 23:42:37 +000033import time
Jack Jansend0fc42f2001-08-19 22:05:06 +000034
35from aetypes import *
Jack Jansen8b777672002-08-07 14:49:00 +000036from aepack import packkey, pack, unpack, coerce, AEDescType
Jack Jansend0fc42f2001-08-19 22:05:06 +000037
38Error = 'aetools.Error'
39
Jack Jansend6ab1532003-03-28 23:42:37 +000040# Amount of time to wait for program to be launched
41LAUNCH_MAX_WAIT_TIME=10
42
Jack Jansend0fc42f2001-08-19 22:05:06 +000043# 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
46aekeywords = [
Jack Jansen0ae32202003-04-09 13:25:43 +000047 '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
Jack Jansend0fc42f2001-08-19 22:05:06 +000058]
59
60def missed(ae):
Jack Jansen0ae32202003-04-09 13:25:43 +000061 try:
62 desc = ae.AEGetAttributeDesc('miss', 'keyw')
63 except AE.Error, msg:
64 return None
65 return desc.data
Jack Jansend0fc42f2001-08-19 22:05:06 +000066
Jack Jansen8b777672002-08-07 14:49:00 +000067def unpackevent(ae, formodulename=""):
Jack Jansen0ae32202003-04-09 13:25:43 +000068 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
Jack Jansend0fc42f2001-08-19 22:05:06 +000098
99def packevent(ae, parameters = {}, attributes = {}):
Jack Jansen0ae32202003-04-09 13:25:43 +0000100 for key, value in parameters.items():
101 packkey(ae, key, value)
102 for key, value in attributes.items():
103 ae.AEPutAttributeDesc(key, pack(value))
Jack Jansend0fc42f2001-08-19 22:05:06 +0000104
105#
106# Support routine for automatically generated Suite interfaces
107# These routines are also useable for the reverse function.
108#
109def keysubst(arguments, keydict):
Jack Jansen0ae32202003-04-09 13:25:43 +0000110 """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
Tim Peters182b5ac2004-07-18 06:16:08 +0000119
Jack Jansend0fc42f2001-08-19 22:05:06 +0000120def enumsubst(arguments, key, edict):
Jack Jansen0ae32202003-04-09 13:25:43 +0000121 """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
Tim Peters182b5ac2004-07-18 06:16:08 +0000130
Jack Jansend0fc42f2001-08-19 22:05:06 +0000131def decodeerror(arguments):
Jack Jansen0ae32202003-04-09 13:25:43 +0000132 """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
Tim Peters182b5ac2004-07-18 06:16:08 +0000143
Jack Jansen0ae32202003-04-09 13:25:43 +0000144 return (err_a1, err_a2, err_a3)
Jack Jansend0fc42f2001-08-19 22:05:06 +0000145
146class TalkTo:
Jack Jansen0ae32202003-04-09 13:25:43 +0000147 """An AE connection to an application"""
148 _signature = None # Can be overridden by subclasses
Jack Jansen39c5d662003-06-18 14:19:08 +0000149 _moduleName = None # Can be overridden by subclasses
150 _elemdict = {} # Can be overridden by subclasses
151 _propdict = {} # Can be overridden by subclasses
Tim Peters182b5ac2004-07-18 06:16:08 +0000152
Jack Jansen0ae32202003-04-09 13:25:43 +0000153 __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)
Tim Peters182b5ac2004-07-18 06:16:08 +0000162
Jack Jansen0ae32202003-04-09 13:25:43 +0000163 def __init__(self, signature=None, start=0, timeout=0):
164 """Create a communication channel with a particular application.
Tim Peters182b5ac2004-07-18 06:16:08 +0000165
Jack Jansen0ae32202003-04-09 13:25:43 +0000166 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()
Tim Peters182b5ac2004-07-18 06:16:08 +0000190
Jack Jansen0ae32202003-04-09 13:25:43 +0000191 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)
Tim Peters182b5ac2004-07-18 06:16:08 +0000205
Jack Jansen0ae32202003-04-09 13:25:43 +0000206 def start(self):
207 """Deprecated, used _start()"""
208 self._start()
Tim Peters182b5ac2004-07-18 06:16:08 +0000209
Jack Jansen0ae32202003-04-09 13:25:43 +0000210 def newevent(self, code, subcode, parameters = {}, attributes = {}):
211 """Create a complete structure for an apple event"""
Tim Peters182b5ac2004-07-18 06:16:08 +0000212
Jack Jansen0ae32202003-04-09 13:25:43 +0000213 event = AE.AECreateAppleEvent(code, subcode, self.target,
214 AppleEvents.kAutoGenerateReturnID, AppleEvents.kAnyTransactionID)
215 packevent(event, parameters, attributes)
216 return event
Tim Peters182b5ac2004-07-18 06:16:08 +0000217
Jack Jansen0ae32202003-04-09 13:25:43 +0000218 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
Tim Peters182b5ac2004-07-18 06:16:08 +0000226
Jack Jansen0ae32202003-04-09 13:25:43 +0000227 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))
Tim Peters182b5ac2004-07-18 06:16:08 +0000230
Jack Jansen0ae32202003-04-09 13:25:43 +0000231 #
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')
Jack Jansend0fc42f2001-08-19 22:05:06 +0000238
Neal Norwitzca460d92006-09-06 06:28:06 +0000239 def _get(self, _object, asfile=None, _attributes={}):
Jack Jansen0ae32202003-04-09 13:25:43 +0000240 """_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'
Jack Jansend0fc42f2001-08-19 22:05:06 +0000247
Jack Jansen0ae32202003-04-09 13:25:43 +0000248 _arguments = {'----':_object}
Neal Norwitzca460d92006-09-06 06:28:06 +0000249 if asfile:
250 _arguments['rtyp'] = mktype(asfile)
Jack Jansend0fc42f2001-08-19 22:05:06 +0000251
Jack Jansen0ae32202003-04-09 13:25:43 +0000252 _reply, _arguments, _attributes = self.send(_code, _subcode,
253 _arguments, _attributes)
254 if _arguments.has_key('errn'):
255 raise Error, decodeerror(_arguments)
Jack Jansend0fc42f2001-08-19 22:05:06 +0000256
Jack Jansen0ae32202003-04-09 13:25:43 +0000257 if _arguments.has_key('----'):
258 return _arguments['----']
Neal Norwitzca460d92006-09-06 06:28:06 +0000259 if asfile:
260 item.__class__ = asfile
Jack Jansen0ae32202003-04-09 13:25:43 +0000261 return item
Tim Peters182b5ac2004-07-18 06:16:08 +0000262
Jack Jansen0ae32202003-04-09 13:25:43 +0000263 get = _get
Tim Peters182b5ac2004-07-18 06:16:08 +0000264
Jack Jansen0ae32202003-04-09 13:25:43 +0000265 _argmap_set = {
266 'to' : 'data',
267 }
Jack Jansen8b777672002-08-07 14:49:00 +0000268
Jack Jansen0ae32202003-04-09 13:25:43 +0000269 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'
Jack Jansen9dd78102003-04-01 22:27:18 +0000277
Jack Jansen0ae32202003-04-09 13:25:43 +0000278 keysubst(_arguments, self._argmap_set)
279 _arguments['----'] = _object
Jack Jansen8b777672002-08-07 14:49:00 +0000280
Jack Jansen9dd78102003-04-01 22:27:18 +0000281
Jack Jansen0ae32202003-04-09 13:25:43 +0000282 _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['----']
Tim Peters182b5ac2004-07-18 06:16:08 +0000289
Jack Jansen0ae32202003-04-09 13:25:43 +0000290 set = _set
Jack Jansend0fc42f2001-08-19 22:05:06 +0000291
Jack Jansen39c5d662003-06-18 14:19:08 +0000292 # Magic glue to allow suite-generated classes to function somewhat
293 # like the "application" class in OSA.
Tim Peters182b5ac2004-07-18 06:16:08 +0000294
Jack Jansenc8882b12003-06-13 14:27:35 +0000295 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
Tim Peters182b5ac2004-07-18 06:16:08 +0000303
Jack Jansend0fc42f2001-08-19 22:05:06 +0000304# Tiny Finder class, for local use only
305
306class _miniFinder(TalkTo):
Jack Jansen0ae32202003-04-09 13:25:43 +0000307 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'
Jack Jansend0fc42f2001-08-19 22:05:06 +0000314
Jack Jansen0ae32202003-04-09 13:25:43 +0000315 if _arguments: raise TypeError, 'No optional args expected'
316 _arguments['----'] = _object
Jack Jansend0fc42f2001-08-19 22:05:06 +0000317
318
Jack Jansen0ae32202003-04-09 13:25:43 +0000319 _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['----']
Jack Jansend0fc42f2001-08-19 22:05:06 +0000326#pass
Tim Peters182b5ac2004-07-18 06:16:08 +0000327
Jack Jansend0fc42f2001-08-19 22:05:06 +0000328_finder = _miniFinder('MACS')
329
330def _launch(appfile):
Jack Jansen0ae32202003-04-09 13:25:43 +0000331 """Open a file thru the finder. Specify file by name or fsspec"""
332 _finder.open(_application_file(('ID ', appfile)))
Jack Jansend0fc42f2001-08-19 22:05:06 +0000333
334
335class _application_file(ComponentItem):
Jack Jansen0ae32202003-04-09 13:25:43 +0000336 """application file - An application's file on disk"""
337 want = 'appf'
Tim Peters182b5ac2004-07-18 06:16:08 +0000338
Jack Jansend0fc42f2001-08-19 22:05:06 +0000339_application_file._propdict = {
340}
341_application_file._elemdict = {
342}
Tim Peters182b5ac2004-07-18 06:16:08 +0000343
Jack Jansend0fc42f2001-08-19 22:05:06 +0000344# Test program
345# XXXX Should test more, really...
346
347def test():
Jack Jansen0ae32202003-04-09 13:25:43 +0000348 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(":")
Jack Jansend0fc42f2001-08-19 22:05:06 +0000360
361if __name__ == '__main__':
Jack Jansen0ae32202003-04-09 13:25:43 +0000362 test()
363 sys.exit(1)