PageRenderTime 66ms CodeModel.GetById 43ms app.highlight 19ms RepoModel.GetById 1ms app.codeStats 0ms

/Lib/plat-mac/aetools.py

http://unladen-swallow.googlecode.com/
Python | 363 lines | 352 code | 0 blank | 11 comment | 1 complexity | 828453d5baa34e23b236e75f5f3977ee MD5 | raw file
  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 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
119
120def enumsubst(arguments, key, edict):
121    """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
130
131def decodeerror(arguments):
132    """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
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 _arguments.has_key('errn'):
255            raise Error, decodeerror(_arguments)
256
257        if _arguments.has_key('----'):
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 _arguments.has_key('----'):
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 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
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 _arguments.has_key('errn'):
322            raise Error, decodeerror(_arguments)
323        # XXXX Optionally decode result
324        if _arguments.has_key('----'):
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)