blob: 7ba5074120ba80101dfd49743da1e5826ffb312e [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
Jack Jansen5a6fdcd2001-08-25 12:15:04 +000024from Carbon import AE
Jack Jansen397e9142003-03-31 13:29:32 +000025from Carbon import Evt
Jack Jansen5a6fdcd2001-08-25 12:15:04 +000026from 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 = [
Jack Jansen0ae32202003-04-09 13:25:43 +000043 '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
Jack Jansend0fc42f2001-08-19 22:05:06 +000054]
55
56def missed(ae):
Jack Jansen0ae32202003-04-09 13:25:43 +000057 try:
58 desc = ae.AEGetAttributeDesc('miss', 'keyw')
Guido van Rossumb940e112007-01-10 16:19:56 +000059 except AE.Error as msg:
Jack Jansen0ae32202003-04-09 13:25:43 +000060 return None
61 return desc.data
Jack Jansend0fc42f2001-08-19 22:05:06 +000062
Jack Jansen8b777672002-08-07 14:49:00 +000063def unpackevent(ae, formodulename=""):
Jack Jansen0ae32202003-04-09 13:25:43 +000064 parameters = {}
65 try:
66 dirobj = ae.AEGetParamDesc('----', '****')
67 except AE.Error:
68 pass
69 else:
70 parameters['----'] = unpack(dirobj, formodulename)
71 del dirobj
72 # 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
80 while 1:
81 key = missed(ae)
82 if not key: break
83 parameters[key] = unpack(ae.AEGetParamDesc(key, '****'), formodulename)
84 attributes = {}
85 for key in aekeywords:
86 try:
87 desc = ae.AEGetAttributeDesc(key, '****')
Guido van Rossumb940e112007-01-10 16:19:56 +000088 except (AE.Error, MacOS.Error) as msg:
Georg Brandld11b68a2008-01-06 21:13:42 +000089 if msg.args[0] not in (-1701, -1704):
Jack Jansen0ae32202003-04-09 13:25:43 +000090 raise
91 continue
92 attributes[key] = unpack(desc, formodulename)
93 return parameters, attributes
Jack Jansend0fc42f2001-08-19 22:05:06 +000094
95def packevent(ae, parameters = {}, attributes = {}):
Jack Jansen0ae32202003-04-09 13:25:43 +000096 for key, value in parameters.items():
97 packkey(ae, key, value)
98 for key, value in attributes.items():
99 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):
Jack Jansen0ae32202003-04-09 13:25:43 +0000106 """Replace long name keys by their 4-char counterparts, and check"""
107 ok = keydict.values()
108 for k in arguments.keys():
Neal Norwitzf1a69c12006-08-20 16:25:10 +0000109 if k in keydict:
Jack Jansen0ae32202003-04-09 13:25:43 +0000110 v = arguments[k]
111 del arguments[k]
112 arguments[keydict[k]] = v
113 elif k != '----' and k not in ok:
Collin Wintere45be282007-08-23 00:01:55 +0000114 raise TypeError('Unknown keyword argument: %s'%k)
Tim Peters182b5ac2004-07-18 06:16:08 +0000115
Jack Jansend0fc42f2001-08-19 22:05:06 +0000116def enumsubst(arguments, key, edict):
Jack Jansen0ae32202003-04-09 13:25:43 +0000117 """Substitute a single enum keyword argument, if it occurs"""
Neal Norwitzf1a69c12006-08-20 16:25:10 +0000118 if key not in arguments or edict is None:
Jack Jansen0ae32202003-04-09 13:25:43 +0000119 return
120 v = arguments[key]
121 ok = edict.values()
Neal Norwitzf1a69c12006-08-20 16:25:10 +0000122 if v in edict:
Jack Jansen0ae32202003-04-09 13:25:43 +0000123 arguments[key] = Enum(edict[v])
124 elif not v in ok:
Collin Wintere45be282007-08-23 00:01:55 +0000125 raise TypeError('Unknown enumerator: %s'%v)
Tim Peters182b5ac2004-07-18 06:16:08 +0000126
Jack Jansend0fc42f2001-08-19 22:05:06 +0000127def decodeerror(arguments):
Jack Jansen0ae32202003-04-09 13:25:43 +0000128 """Create the 'best' argument for a raise MacOS.Error"""
129 errn = arguments['errn']
130 err_a1 = errn
Neal Norwitzf1a69c12006-08-20 16:25:10 +0000131 if 'errs' in arguments:
Jack Jansen0ae32202003-04-09 13:25:43 +0000132 err_a2 = arguments['errs']
133 else:
134 err_a2 = MacOS.GetErrorString(errn)
Neal Norwitzf1a69c12006-08-20 16:25:10 +0000135 if 'erob' in arguments:
Jack Jansen0ae32202003-04-09 13:25:43 +0000136 err_a3 = arguments['erob']
137 else:
138 err_a3 = None
Tim Peters182b5ac2004-07-18 06:16:08 +0000139
Jack Jansen0ae32202003-04-09 13:25:43 +0000140 return (err_a1, err_a2, err_a3)
Jack Jansend0fc42f2001-08-19 22:05:06 +0000141
142class TalkTo:
Jack Jansen0ae32202003-04-09 13:25:43 +0000143 """An AE connection to an application"""
144 _signature = None # Can be overridden by subclasses
Jack Jansen39c5d662003-06-18 14:19:08 +0000145 _moduleName = None # Can be overridden by subclasses
146 _elemdict = {} # Can be overridden by subclasses
147 _propdict = {} # Can be overridden by subclasses
Tim Peters182b5ac2004-07-18 06:16:08 +0000148
Jack Jansen0ae32202003-04-09 13:25:43 +0000149 __eventloop_initialized = 0
150 def __ensure_WMAvailable(klass):
151 if klass.__eventloop_initialized: return 1
152 if not MacOS.WMAvailable(): return 0
153 # Workaround for a but in MacOSX 10.2: we must have an event
154 # loop before we can call AESend.
155 Evt.WaitNextEvent(0,0)
156 return 1
157 __ensure_WMAvailable = classmethod(__ensure_WMAvailable)
Tim Peters182b5ac2004-07-18 06:16:08 +0000158
Jack Jansen0ae32202003-04-09 13:25:43 +0000159 def __init__(self, signature=None, start=0, timeout=0):
160 """Create a communication channel with a particular application.
Tim Peters182b5ac2004-07-18 06:16:08 +0000161
Jack Jansen0ae32202003-04-09 13:25:43 +0000162 Addressing the application is done by specifying either a
163 4-byte signature, an AEDesc or an object that will __aepack__
164 to an AEDesc.
165 """
166 self.target_signature = None
167 if signature is None:
168 signature = self._signature
Neal Norwitzf9b95d42007-08-07 05:42:45 +0000169 if isinstance(signature, AEDescType):
Jack Jansen0ae32202003-04-09 13:25:43 +0000170 self.target = signature
Guido van Rossum3cf5b1e2006-07-27 21:53:35 +0000171 elif hasattr(signature, '__aepack__'):
Jack Jansen0ae32202003-04-09 13:25:43 +0000172 self.target = signature.__aepack__()
Neal Norwitzf9b95d42007-08-07 05:42:45 +0000173 elif isinstance(signature, str) and len(signature) == 4:
Jack Jansen0ae32202003-04-09 13:25:43 +0000174 self.target = AE.AECreateDesc(AppleEvents.typeApplSignature, signature)
175 self.target_signature = signature
176 else:
Collin Wintere45be282007-08-23 00:01:55 +0000177 raise TypeError("signature should be 4-char string or AEDesc")
Jack Jansen0ae32202003-04-09 13:25:43 +0000178 self.send_flags = AppleEvents.kAEWaitReply
179 self.send_priority = AppleEvents.kAENormalPriority
180 if timeout:
181 self.send_timeout = timeout
182 else:
183 self.send_timeout = AppleEvents.kAEDefaultTimeout
184 if start:
185 self._start()
Tim Peters182b5ac2004-07-18 06:16:08 +0000186
Jack Jansen0ae32202003-04-09 13:25:43 +0000187 def _start(self):
188 """Start the application, if it is not running yet"""
189 try:
190 self.send('ascr', 'noop')
191 except AE.Error:
192 _launch(self.target_signature)
193 for i in range(LAUNCH_MAX_WAIT_TIME):
194 try:
195 self.send('ascr', 'noop')
196 except AE.Error:
197 pass
198 else:
199 break
200 time.sleep(1)
Tim Peters182b5ac2004-07-18 06:16:08 +0000201
Jack Jansen0ae32202003-04-09 13:25:43 +0000202 def start(self):
203 """Deprecated, used _start()"""
204 self._start()
Tim Peters182b5ac2004-07-18 06:16:08 +0000205
Jack Jansen0ae32202003-04-09 13:25:43 +0000206 def newevent(self, code, subcode, parameters = {}, attributes = {}):
207 """Create a complete structure for an apple event"""
Tim Peters182b5ac2004-07-18 06:16:08 +0000208
Jack Jansen0ae32202003-04-09 13:25:43 +0000209 event = AE.AECreateAppleEvent(code, subcode, self.target,
210 AppleEvents.kAutoGenerateReturnID, AppleEvents.kAnyTransactionID)
211 packevent(event, parameters, attributes)
212 return event
Tim Peters182b5ac2004-07-18 06:16:08 +0000213
Jack Jansen0ae32202003-04-09 13:25:43 +0000214 def sendevent(self, event):
215 """Send a pre-created appleevent, await the reply and unpack it"""
216 if not self.__ensure_WMAvailable():
Collin Wintere45be282007-08-23 00:01:55 +0000217 raise RuntimeError("No window manager access, cannot send AppleEvent")
Jack Jansen0ae32202003-04-09 13:25:43 +0000218 reply = event.AESend(self.send_flags, self.send_priority,
219 self.send_timeout)
220 parameters, attributes = unpackevent(reply, self._moduleName)
221 return reply, parameters, attributes
Tim Peters182b5ac2004-07-18 06:16:08 +0000222
Jack Jansen0ae32202003-04-09 13:25:43 +0000223 def send(self, code, subcode, parameters = {}, attributes = {}):
224 """Send an appleevent given code/subcode/pars/attrs and unpack the reply"""
225 return self.sendevent(self.newevent(code, subcode, parameters, attributes))
Tim Peters182b5ac2004-07-18 06:16:08 +0000226
Jack Jansen0ae32202003-04-09 13:25:43 +0000227 #
228 # The following events are somehow "standard" and don't seem to appear in any
229 # suite...
230 #
231 def activate(self):
232 """Send 'activate' command"""
233 self.send('misc', 'actv')
Jack Jansend0fc42f2001-08-19 22:05:06 +0000234
Neal Norwitza0bc30f2006-03-22 09:34:44 +0000235 def _get(self, _object, asfile=None, _attributes={}):
Jack Jansen0ae32202003-04-09 13:25:43 +0000236 """_get: get data from an object
237 Required argument: the object
238 Keyword argument _attributes: AppleEvent attribute dictionary
239 Returns: the data
240 """
241 _code = 'core'
242 _subcode = 'getd'
Jack Jansend0fc42f2001-08-19 22:05:06 +0000243
Jack Jansen0ae32202003-04-09 13:25:43 +0000244 _arguments = {'----':_object}
Neal Norwitza0bc30f2006-03-22 09:34:44 +0000245 if asfile:
246 _arguments['rtyp'] = mktype(asfile)
Jack Jansend0fc42f2001-08-19 22:05:06 +0000247
Jack Jansen0ae32202003-04-09 13:25:43 +0000248 _reply, _arguments, _attributes = self.send(_code, _subcode,
249 _arguments, _attributes)
Neal Norwitzf1a69c12006-08-20 16:25:10 +0000250 if 'errn' in _arguments:
Collin Wintere45be282007-08-23 00:01:55 +0000251 raise Error(decodeerror(_arguments))
Jack Jansend0fc42f2001-08-19 22:05:06 +0000252
Neal Norwitzf1a69c12006-08-20 16:25:10 +0000253 if '----' in _arguments:
Jack Jansen0ae32202003-04-09 13:25:43 +0000254 return _arguments['----']
Neal Norwitza0bc30f2006-03-22 09:34:44 +0000255 if asfile:
256 item.__class__ = asfile
Jack Jansen0ae32202003-04-09 13:25:43 +0000257 return item
Tim Peters182b5ac2004-07-18 06:16:08 +0000258
Jack Jansen0ae32202003-04-09 13:25:43 +0000259 get = _get
Tim Peters182b5ac2004-07-18 06:16:08 +0000260
Jack Jansen0ae32202003-04-09 13:25:43 +0000261 _argmap_set = {
262 'to' : 'data',
263 }
Jack Jansen8b777672002-08-07 14:49:00 +0000264
Jack Jansen0ae32202003-04-09 13:25:43 +0000265 def _set(self, _object, _attributes={}, **_arguments):
266 """set: Set an object's data.
267 Required argument: the object for the command
268 Keyword argument to: The new value.
269 Keyword argument _attributes: AppleEvent attribute dictionary
270 """
271 _code = 'core'
272 _subcode = 'setd'
Jack Jansen9dd78102003-04-01 22:27:18 +0000273
Jack Jansen0ae32202003-04-09 13:25:43 +0000274 keysubst(_arguments, self._argmap_set)
275 _arguments['----'] = _object
Jack Jansen8b777672002-08-07 14:49:00 +0000276
Jack Jansen9dd78102003-04-01 22:27:18 +0000277
Jack Jansen0ae32202003-04-09 13:25:43 +0000278 _reply, _arguments, _attributes = self.send(_code, _subcode,
279 _arguments, _attributes)
280 if _arguments.get('errn', 0):
Collin Wintere45be282007-08-23 00:01:55 +0000281 raise Error(decodeerror(_arguments))
Jack Jansen0ae32202003-04-09 13:25:43 +0000282 # XXXX Optionally decode result
Neal Norwitzf1a69c12006-08-20 16:25:10 +0000283 if '----' in _arguments:
Jack Jansen0ae32202003-04-09 13:25:43 +0000284 return _arguments['----']
Tim Peters182b5ac2004-07-18 06:16:08 +0000285
Jack Jansen0ae32202003-04-09 13:25:43 +0000286 set = _set
Jack Jansend0fc42f2001-08-19 22:05:06 +0000287
Jack Jansen39c5d662003-06-18 14:19:08 +0000288 # Magic glue to allow suite-generated classes to function somewhat
289 # like the "application" class in OSA.
Tim Peters182b5ac2004-07-18 06:16:08 +0000290
Jack Jansenc8882b12003-06-13 14:27:35 +0000291 def __getattr__(self, name):
Neal Norwitzf1a69c12006-08-20 16:25:10 +0000292 if name in self._elemdict:
Jack Jansenc8882b12003-06-13 14:27:35 +0000293 cls = self._elemdict[name]
294 return DelayedComponentItem(cls, None)
Neal Norwitzf1a69c12006-08-20 16:25:10 +0000295 if name in self._propdict:
Jack Jansenc8882b12003-06-13 14:27:35 +0000296 cls = self._propdict[name]
297 return cls()
Collin Wintere45be282007-08-23 00:01:55 +0000298 raise AttributeError(name)
Tim Peters182b5ac2004-07-18 06:16:08 +0000299
Jack Jansend0fc42f2001-08-19 22:05:06 +0000300# Tiny Finder class, for local use only
301
302class _miniFinder(TalkTo):
Jack Jansen0ae32202003-04-09 13:25:43 +0000303 def open(self, _object, _attributes={}, **_arguments):
304 """open: Open the specified object(s)
305 Required argument: list of objects to open
306 Keyword argument _attributes: AppleEvent attribute dictionary
307 """
308 _code = 'aevt'
309 _subcode = 'odoc'
Jack Jansend0fc42f2001-08-19 22:05:06 +0000310
Collin Wintere45be282007-08-23 00:01:55 +0000311 if _arguments: raise TypeError('No optional args expected')
Jack Jansen0ae32202003-04-09 13:25:43 +0000312 _arguments['----'] = _object
Jack Jansend0fc42f2001-08-19 22:05:06 +0000313
314
Jack Jansen0ae32202003-04-09 13:25:43 +0000315 _reply, _arguments, _attributes = self.send(_code, _subcode,
316 _arguments, _attributes)
Neal Norwitzf1a69c12006-08-20 16:25:10 +0000317 if 'errn' in _arguments:
Collin Wintere45be282007-08-23 00:01:55 +0000318 raise Error(decodeerror(_arguments))
Jack Jansen0ae32202003-04-09 13:25:43 +0000319 # XXXX Optionally decode result
Neal Norwitzf1a69c12006-08-20 16:25:10 +0000320 if '----' in _arguments:
Jack Jansen0ae32202003-04-09 13:25:43 +0000321 return _arguments['----']
Jack Jansend0fc42f2001-08-19 22:05:06 +0000322#pass
Tim Peters182b5ac2004-07-18 06:16:08 +0000323
Jack Jansend0fc42f2001-08-19 22:05:06 +0000324_finder = _miniFinder('MACS')
325
326def _launch(appfile):
Jack Jansen0ae32202003-04-09 13:25:43 +0000327 """Open a file thru the finder. Specify file by name or fsspec"""
328 _finder.open(_application_file(('ID ', appfile)))
Jack Jansend0fc42f2001-08-19 22:05:06 +0000329
330
331class _application_file(ComponentItem):
Jack Jansen0ae32202003-04-09 13:25:43 +0000332 """application file - An application's file on disk"""
333 want = 'appf'
Tim Peters182b5ac2004-07-18 06:16:08 +0000334
Jack Jansend0fc42f2001-08-19 22:05:06 +0000335_application_file._propdict = {
336}
337_application_file._elemdict = {
338}
Tim Peters182b5ac2004-07-18 06:16:08 +0000339
Jack Jansend0fc42f2001-08-19 22:05:06 +0000340# Test program
341# XXXX Should test more, really...
342
343def test():
Neal Norwitzce96f692006-03-17 06:49:51 +0000344 def raw_input(prompt):
345 sys.stdout.write(prompt)
346 sys.stdout.flush()
347 return sys.stdin.readline()
348
Jack Jansen0ae32202003-04-09 13:25:43 +0000349 target = AE.AECreateDesc('sign', 'quil')
350 ae = AE.AECreateAppleEvent('aevt', 'oapp', target, -1, 0)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000351 print(unpackevent(ae))
Jack Jansen0ae32202003-04-09 13:25:43 +0000352 raw_input(":")
353 ae = AE.AECreateAppleEvent('core', 'getd', target, -1, 0)
354 obj = Character(2, Word(1, Document(1)))
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000355 print(obj)
356 print(repr(obj))
Jack Jansen0ae32202003-04-09 13:25:43 +0000357 packevent(ae, {'----': obj})
358 params, attrs = unpackevent(ae)
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000359 print(params['----'])
Jack Jansen0ae32202003-04-09 13:25:43 +0000360 raw_input(":")
Jack Jansend0fc42f2001-08-19 22:05:06 +0000361
362if __name__ == '__main__':
Jack Jansen0ae32202003-04-09 13:25:43 +0000363 test()
364 sys.exit(1)