1"""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 warnings import warnpy3k 25warnpy3k("In 3.x, the aetools module is removed.", stacklevel=2) 26 27from types import * 28from Carbon import AE 29from Carbon import Evt 30from Carbon import AppleEvents 31import MacOS 32import sys 33import time 34 35from aetypes import * 36from aepack import packkey, pack, unpack, coerce, AEDescType 37 38Error = 'aetools.Error' 39 40# Amount of time to wait for program to be launched 41LAUNCH_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 46aekeywords = [ 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 60def missed(ae): 61 try: 62 desc = ae.AEGetAttributeDesc('miss', 'keyw') 63 except AE.Error, msg: 64 return None 65 return desc.data 66 67def 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 99def 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# 109def 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 k in keydict: 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 120def enumsubst(arguments, key, edict): 121 """Substitute a single enum keyword argument, if it occurs""" 122 if key not in arguments or edict is None: 123 return 124 v = arguments[key] 125 ok = edict.values() 126 if v in edict: 127 arguments[key] = Enum(edict[v]) 128 elif not v in ok: 129 raise TypeError, 'Unknown enumerator: %s'%v 130 131def decodeerror(arguments): 132 """Create the 'best' argument for a raise MacOS.Error""" 133 errn = arguments['errn'] 134 err_a1 = errn 135 if 'errs' in arguments: 136 err_a2 = arguments['errs'] 137 else: 138 err_a2 = MacOS.GetErrorString(errn) 139 if 'erob' in arguments: 140 err_a3 = arguments['erob'] 141 else: 142 err_a3 = None 143 144 return (err_a1, err_a2, err_a3) 145 146class 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 'errn' in _arguments: 255 raise Error, decodeerror(_arguments) 256 257 if '----' in _arguments: 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 '----' in _arguments: 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 name in self._elemdict: 297 cls = self._elemdict[name] 298 return DelayedComponentItem(cls, None) 299 if name in self._propdict: 300 cls = self._propdict[name] 301 return cls() 302 raise AttributeError, name 303 304# Tiny Finder class, for local use only 305 306class _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 'errn' in _arguments: 322 raise Error, decodeerror(_arguments) 323 # XXXX Optionally decode result 324 if '----' in _arguments: 325 return _arguments['----'] 326#pass 327 328_finder = _miniFinder('MACS') 329 330def _launch(appfile): 331 """Open a file thru the finder. Specify file by name or fsspec""" 332 _finder.open(_application_file(('ID ', appfile))) 333 334 335class _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 347def 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 361if __name__ == '__main__': 362 test() 363 sys.exit(1) 364