~ubuntu-branches/ubuntu/saucy/pida/saucy

« back to all changes in this revision

Viewing changes to src/plugins/vim/gdkvim.py

  • Committer: Bazaar Package Importer
  • Author(s): Ante Karamatic
  • Date: 2005-09-27 10:19:38 UTC
  • Revision ID: james.westby@ubuntu.com-20050927101938-o7ak8uqyuvewfuw8
Tags: upstream-0.2.2
ImportĀ upstreamĀ versionĀ 0.2.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*- 
 
2
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
 
3
# $Id: gdkvim.py 453 2005-07-24 15:24:10Z aafshar $
 
4
#Copyright (c) 2005 Ali Afshar aafshar@gmail.com
 
5
 
 
6
#Permission is hereby granted, free of charge, to any person obtaining a copy
 
7
#of this software and associated documentation files (the "Software"), to deal
 
8
#in the Software without restriction, including without limitation the rights
 
9
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 
10
#copies of the Software, and to permit persons to whom the Software is
 
11
#furnished to do so, subject to the following conditions:
 
12
 
 
13
#The above copyright notice and this permission notice shall be included in
 
14
#all copies or substantial portions of the Software.
 
15
 
 
16
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 
17
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 
18
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 
19
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
20
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
21
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 
22
#SOFTWARE.
 
23
 
 
24
 
 
25
"""
 
26
A library to control vim -g using its X protocol interface (with gdk).
 
27
 
 
28
== How it works ==
 
29
 
 
30
=== General Communication ===
 
31
 
 
32
The Vim client/server protocol communicates by sending messages to and from an
 
33
X communication window. The details are explained in the Vim source.
 
34
Essentially, Vim understands two sorts of messages over this interface.
 
35
 
 
36
;asynchronous key sends : that are exactly equivalent to to the user of the
 
37
remote Vim typing commands.
 
38
 
 
39
;synchronous expression evaluations : these are Vim expressions that are
 
40
evaluated by the remote Vim, and an answer is replied with the result over the
 
41
same protocol.
 
42
 
 
43
Although the synchronous messages are called synchronous, the reply itself, in
 
44
programming terms is entirely asynchronous, in that there is no way of knowing
 
45
when a reply will be received, and one cannot block for it.
 
46
 
 
47
Thus, this library allows you to make both of these calls to remote Vims.
 
48
Synchronous expressions must provide a call back function that will be called
 
49
when the message is replied to.
 
50
 
 
51
=== The Server List ===
 
52
 
 
53
(It has been an utter nightmare.)
 
54
 
 
55
The primary problem is that GTK does not actually know accurately whether
 
56
a window with a given window ID has been destroyed. This is how Vim does
 
57
it (using the X libraries) after checking an attribute for registered Vim
 
58
sessions with the X root window. This way each Vim doesn't need to
 
59
unregister itself with the X root window on dying, it just assumes that
 
60
any other client attempting to connect to it will know that the window
 
61
has been destroyed. As mentioned, GTK totally fails to do what the X
 
62
library does, and ascertain whether the window is alive. It succeeds
 
63
sometimes, but not at others. The result is a GDK window that appears
 
64
alive, and ready to communicate with, but which causes an uncatchable and
 
65
fatal application error.
 
66
 
 
67
Step in other potential methods of getting an accurate list of servers.
 
68
Firstly, and most obviously, one can call the command 'vim --serverlist'
 
69
on a simple system pipe and read the list off. This is entirely reliable,
 
70
and effective, but the cost of forking a process and starting Vim each
 
71
time is not fun, and effectively blocks.
 
72
 
 
73
Another option is to force users to start Vim through Pida and keep an
 
74
account of the child processes. This would work very effectively, but it
 
75
restricts the user, and the entire system.
 
76
 
 
77
The final, and current solution is to start Vim itself on a
 
78
pseudoterminal as a hidden instance, and then communicate with that over
 
79
the Vim protocol. The reason this can be reliably done, is that since the
 
80
process is a child, it can be polled to check whether it is alive. This
 
81
is performed each time the serverlist is requested, and if the hidden
 
82
instance has been destroyed (eg by the user) a new one is spawned, thus
 
83
preventing an attempt to communicate with an already-destroyed GDK
 
84
window.
 
85
 
 
86
The cost of this solution is that we spawn an extra Vim process. I
 
87
believe that the added solidity it brings to the entire system is easily
 
88
worth it, and it ensures that Pida can communicate with Vim it started
 
89
and Vim it didn't start.
 
90
"""
 
91
# Gtk imports
 
92
import gtk
 
93
import gobject
 
94
import gtk.gdk as gdk
 
95
# System imports
 
96
import os
 
97
import pty
 
98
import sys
 
99
import time
 
100
import pida.base as base
 
101
 
 
102
class VimHidden(base.pidaobject):
 
103
    """
 
104
    An instance of Vim on a pseudoterminal which can be reliably polled.
 
105
 
 
106
    This class is used to provide an instance of Vim which can be communicated
 
107
    with using the Vim client/server protocol, in order to retrieve an accurate
 
108
    and current server list, and also which can be polled accurately as to
 
109
    whether it is alive before communicating with it.
 
110
 
 
111
    This method is much cheaper in resources than running vim --serverlist each
 
112
    time, and much more accurate than using the root window's VimRegistry
 
113
    property, and also more accurate than using GDK methods for assessing
 
114
    whether a window is alive.
 
115
    """
 
116
 
 
117
    def do_init(self):
 
118
        """
 
119
        Constructor.
 
120
        
 
121
        Create a temporary and unique name for use as the servername, and
 
122
        initialise the instance variables.
 
123
 
 
124
        @param cb: An instance of the main application class.
 
125
        @type cb: pida.main.Application.
 
126
        """
 
127
        # Prefacing with '__' means it will be ignored in the internal server
 
128
        # list.
 
129
        self.name = '__%s_PIDA_HIDDEN' % time.time()
 
130
        # Checked to evaluate False on starting.
 
131
        self.pid = None
 
132
 
 
133
    def start(self):
 
134
        """
 
135
        Start the Vim instance if it is not already running.
 
136
        
 
137
        This command forks in a pseudoterminal, and starts Vim, if Vim is not
 
138
        already running. The pid is stored for later use.
 
139
        """
 
140
        if not self.pid:
 
141
            # Get the console vim executable path
 
142
            command = self.prop_main_registry.commands.vim.value()
 
143
            # Fork using pty.fork to prevent Vim taking the terminal
 
144
            sock = gtk.Socket()
 
145
            w = gtk.Window()
 
146
            w.realize()
 
147
            w.add(sock)
 
148
            xid = sock.get_id()
 
149
            pid, fd = pty.fork()
 
150
            if pid == 0:
 
151
                # Child, execute Vim with the correct servername argument
 
152
                os.execvp(command, ['gvim', '-f', '--servername', self.name,
 
153
                    '--socketid', '%s' % xid])
 
154
                    #'-v'])
 
155
                # os.system('%s -v --servername %s' % (command, self.name))
 
156
            else:
 
157
                # Parent, store the pid, and file descriptor for later.
 
158
                self.pid = pid
 
159
                self.childfd = fd
 
160
                self.do_action('accountfork', self.pid)
 
161
 
 
162
    def is_alive(self):
 
163
        """
 
164
        Check if the Vim instance is alive.
 
165
        
 
166
        This method uses os.waitpid, with no blocking to determine whether the
 
167
        process is still alive. If it is not, it sets the internal pid
 
168
        attribute to None, so that it may be restarted.
 
169
        
 
170
        @returns: alive
 
171
        @rtype alive: boolean
 
172
        """
 
173
        if self.pid:
 
174
            try:
 
175
                # call os.waitpid, returns 0 if the pid is alive
 
176
                pid, sts = os.waitpid(self.pid, os.WNOHANG)
 
177
            except OSError:
 
178
                # might still be starting up
 
179
                return False
 
180
            if pid == self.pid:
 
181
                # has shut down
 
182
                self.pid = None
 
183
                return False
 
184
            else:
 
185
                # is still alive
 
186
                return True
 
187
        else:
 
188
            # Not started yet
 
189
            return False
 
190
 
 
191
class VimWindow(base.pidaobject, gtk.Window):
 
192
    """
 
193
    A GTK window that can communicate with any number Vim instances.
 
194
 
 
195
    This is an actual GTK window (which it must be to accurately detect
 
196
    property events inside the GTK main loop) but has its GDK window correctly
 
197
    set to receive such events. This is notably the "Vim" property which must
 
198
    be present and set to a version string, in this case "6.0" is used.
 
199
    """
 
200
    def do_init(self):
 
201
        """
 
202
        Constructor.
 
203
 
 
204
        The Window is instantiated, the properties are correctly set, the event
 
205
        mask is modified, and the instance variables are initialized.
 
206
 
 
207
        @param cb: An instance of the main Application class.
 
208
        @type cb: pida.main.Application.
 
209
        """
 
210
        gtk.Window.__init__(self)
 
211
        # Window needs to be realized to do anything useful with it. Realizing
 
212
        # does not show the window to the user, so we can use it, but not have
 
213
        # an ugly blank frame while it loads.
 
214
        self.realize()
 
215
        # The "Vim" property
 
216
        self.window.property_change("Vim", gdk.SELECTION_TYPE_STRING, 8,
 
217
                            gdk.PROP_MODE_REPLACE, "6.0")
 
218
        # Set the correct event mask and connect the notify event
 
219
        self.add_events(gtk.gdk.PROPERTY_CHANGE_MASK)
 
220
        self.connect('property-notify-event', self.cb_notify)
 
221
        # The serial number used for sending synchronous messages
 
222
        self.serial = 1
 
223
        # A dictionary of callbacks for synchronous messages. The key is the
 
224
        # serial number, and the value is a callable that will be called with
 
225
        # the result of the synchronous evaluation.
 
226
        self.callbacks = {}
 
227
        # A dictionary to store the working directories for each Vim so they
 
228
        # only have to be fetched once.
 
229
        self.server_cwds = {}
 
230
        # A list of the last fetched server list, used todecide whther to feed
 
231
        # the most recent server list to the client.
 
232
        self.oldservers = None
 
233
        # An instance of the root window, so it only has to be fetched once.
 
234
        self.root_window = gdk.get_default_root_window()
 
235
        # Instantiate and start the hidden communication window, used for
 
236
        # fetching accurate and reliable server lists. 
 
237
        self.vim_hidden = VimHidden()
 
238
        self.vim_hidden.start()
 
239
        # Start the timer for fetching the server list at the specified number
 
240
        # of milliseconds (500).
 
241
        gobject.timeout_add(1000, self.fetch_serverlist)
 
242
    
 
243
    def fetch_serverlist(self):
 
244
        """
 
245
        Fetch the serverlist, and if it has changed, feed it to the client.
 
246
 
 
247
        The serverlist is requested asynchrnously, and passed the gotservers
 
248
        function as a callback. The gotservers function is then called with the
 
249
        server list, gets the appropriate working directory (if required) and
 
250
        feeds the new server list to the client if it has changed.
 
251
        """
 
252
        def gotservers(serverlist):
 
253
            """
 
254
            Called back on receiving the serverlist.
 
255
 
 
256
            Fetch working directories for new Vim instances, and feed the
 
257
            server list to the client if it has changed.
 
258
            """
 
259
            for server in serverlist:
 
260
                # Check if we already have the working directory.
 
261
                if server not in self.server_cwds:
 
262
                    # We don't, fetch it
 
263
                    self.fetch_cwd(server)
 
264
            # Check if the server list has changed
 
265
            if serverlist != self.oldservers:
 
266
                self.oldservers = serverlist
 
267
                # A ew serverlist to feed to the client.
 
268
                self.feed_serverlist(serverlist)
 
269
        # Fetch the server list from the hidden vim instance with the
 
270
        # gotservers function as a callback.
 
271
        self.get_hidden_serverlist(gotservers)
 
272
        # Return True so that the timer continues
 
273
        return True
 
274
 
 
275
    def get_rootwindow_serverlist(self):
 
276
        """
 
277
        Get the X root window's version of the current Vim serverlist.
 
278
 
 
279
        On starting with the client-server feature, GVim or Vim with the
 
280
        --servername option registers its server name and X window id as part
 
281
        of the "VimRegistry" parameter on the X root window.
 
282
 
 
283
        This method extracts and parses that property, and returns the server
 
284
        list.
 
285
 
 
286
        Note: Vim does not actually unregister itself with the root window on
 
287
        dying, so the presence of a server in the root window list is no
 
288
        gurantee that it is alive.
 
289
 
 
290
        @return: servers
 
291
        @rtype servers: dict of ("server", "window id") key, value
 
292
        """
 
293
        servers = {}
 
294
        # Read the property
 
295
        vimregistry = self.root_window.property_get("VimRegistry")
 
296
        # If it exists
 
297
        if vimregistry:
 
298
            # Get the list of servers by splitting with '\0'
 
299
            vimservers = vimregistry[-1].split('\0')
 
300
            # Parse each individual server and add to the results list
 
301
            for rawserver in vimservers:
 
302
                # Sometimes blank servers exist in the list
 
303
                if rawserver:
 
304
                    # split the raw value for the name and id
 
305
                    name_id = rawserver.split()
 
306
                    # Set the value in the results dict, remembering to convert
 
307
                    # the window id to a long int.
 
308
                    servers[name_id[1]] = long(int(name_id[0], 16))
 
309
        # return the list of resuts
 
310
        return servers
 
311
 
 
312
    def get_shell_serverlist(self):
 
313
        """
 
314
        Get the server list by starting console Vim on a Pipe.
 
315
 
 
316
        This blocks, so we don't use it. It is one of the alternative methods
 
317
        of retrieving an accurate serverlist. It is slow, and expensive.
 
318
        """
 
319
        vimcom = prop_main_registry.commands.vim_console.value()
 
320
        p = os.popen('%s --serverlist' % vimcom)
 
321
        servers = p.read()
 
322
        p.close()
 
323
        return servers.splitlines()
 
324
 
 
325
    def get_hidden_serverlist(self, callbackfunc):
 
326
        """
 
327
        Get the serverlist from the hidden Vim instance and call the callback
 
328
        function with the results.
 
329
 
 
330
        This method checks first whther the Vim instance is alive, and then
 
331
        evaluates the serverlist() function remotely in it, with a local call
 
332
        back function which parses the result and calls the user-provided
 
333
        callback function.
 
334
 
 
335
        @param callbackfunc: The call back function to be called with the
 
336
            server list.
 
337
        @type callbackfunc: callable
 
338
        """
 
339
        def cb(serverstring):
 
340
            """
 
341
            Called back with the raw server list.
 
342
 
 
343
            Parse the lines and call the call back function, ignoring any
 
344
            instances starting with "__" which represent hidden instances. If
 
345
            the hidden Vim instance is not alive, it is restarted.
 
346
            """
 
347
            servers = serverstring.splitlines()
 
348
            # Call the callback function
 
349
            callbackfunc([svr for svr in servers if not svr.startswith('__')])
 
350
        # Check if the hidden Vim is alive. 
 
351
        if self.vim_hidden.is_alive():
 
352
            # It is alive, get the serverlist.
 
353
            self.send_expr(self.vim_hidden.name, 'serverlist()', cb)
 
354
        else:
 
355
            # It is not alive, restart it.
 
356
            self.vim_hidden.start()
 
357
        
 
358
    def get_server_wid(self, servername):
 
359
        """
 
360
        Get the X Window id for a named Vim server.
 
361
 
 
362
        This function returns the id from the root window server list, if it
 
363
        exists, or None if it does not.
 
364
 
 
365
        @param servername: The name of the server
 
366
        @type servername: str
 
367
 
 
368
        @return: wid
 
369
        @rtype wid: long
 
370
        """
 
371
        try:
 
372
            # get the window id from the root window
 
373
            wid = self.get_rootwindow_serverlist()[servername]
 
374
        except KeyError:
 
375
            # The server is not registered in the root window so return None
 
376
            wid = None
 
377
        # Return wid if it is not none, or None
 
378
        return wid and long(wid) or None
 
379
 
 
380
    def get_server_window(self, wid):
 
381
        """
 
382
        Create and return a GDK window for a given window ID.
 
383
        
 
384
        This method simply calls gdk.window_foreign_new, which should return
 
385
        None if the window has been destroyed, but does not, in some cases.
 
386
 
 
387
        @param wid: The window ID.
 
388
        @type wid: long
 
389
        """
 
390
        return gtk.gdk.window_foreign_new(wid)
 
391
 
 
392
    def feed_serverlist(self, serverlist):
 
393
        """
 
394
        Feed the given list of servers to the client.
 
395
 
 
396
        This is achieved by calling the clients serverlist event. In Pida, this
 
397
        event is passed on to all the plugins.
 
398
 
 
399
        @param serverlist: The list of servers.
 
400
        @type serverlist: list
 
401
        """
 
402
        # Call the event.
 
403
        self.do_evt('serverlist', serverlist)
 
404
 
 
405
    def fetch_cwd(self, servername):
 
406
        """
 
407
        Fetch the working directory for a named server and store the result.
 
408
        """
 
409
        def gotcwd(cwd):
 
410
            """
 
411
            Called back on receiving the working directory, store it for later
 
412
            use.
 
413
            """
 
414
            self.server_cwds[servername] = cwd
 
415
        # Evaluate the expression with the gotcwd callback
 
416
        self.send_expr(servername, "getcwd()", gotcwd)
 
417
 
 
418
    def abspath(self, servername, filename):
 
419
        """
 
420
        Return the absolute path of a buffer name in the context of the named
 
421
        server.
 
422
        """
 
423
        # Only alter non-absolute paths
 
424
        if not filename.startswith('/'):
 
425
            try:
 
426
                # Try to find the current working directory
 
427
                cwd = self.server_cwds[servername]
 
428
            except KeyError:
 
429
                # The working directory is not set
 
430
                # Use a sane default, and fetch it
 
431
                cwd = os.path.expanduser('~')
 
432
                self.fetch_cwd(servername)
 
433
            filename = os.path.join(cwd, filename)
 
434
        return filename
 
435
 
 
436
    def generate_message(self, server, cork, message, sourceid):
 
437
        """
 
438
        Generate a message.
 
439
        """
 
440
        # Increment the serial number used for synchronous messages
 
441
        if cork:
 
442
            self.serial = self.serial + 1
 
443
            # Pick an arbitrary number where we recycle.
 
444
            if self.serial > 65530:
 
445
                self.serial = 1
 
446
        # return the generated string
 
447
        return '\0%s\0-n %s\0-s %s\0-r %x %s\0' % (cork,
 
448
                                                   server,
 
449
                                                   message,
 
450
                                                   sourceid,
 
451
                                                   self.serial)
 
452
 
 
453
    def parse_message(self, message):
 
454
        """
 
455
        Parse a received message and return the message atributes as a
 
456
        dictionary.
 
457
        """
 
458
        messageattrs = {}
 
459
        for t in [s.split(' ') for s in message.split('\0')]:
 
460
            if t and len(t[0]):
 
461
                if t[0].startswith('-'):
 
462
                    #attributes start with a '-', strip it and set the value
 
463
                    if len(t) > 1:
 
464
                        messageattrs[t[0][1:]] = t[1]
 
465
                else:
 
466
                    # Otherwise set the t attribute
 
467
                    messageattrs['t'] = t[0]
 
468
        return messageattrs
 
469
 
 
470
 
 
471
    def send_message(self, servername, message, asexpr, callback):
 
472
        wid = self.get_server_wid(servername)
 
473
        if wid:
 
474
            cork = (asexpr and 'c') or 'k'
 
475
            sw = self.get_server_window(wid)
 
476
            if sw and sw.property_get("Vim"):
 
477
                mp = self.generate_message(servername, cork, message,
 
478
                                        self.window.xid)
 
479
                sw.property_change("Comm", gdk.TARGET_STRING, 8,
 
480
                                        gdk.PROP_MODE_APPEND, mp)
 
481
                if asexpr and callback:
 
482
                    self.callbacks['%s' % (self.serial)] = callback
 
483
 
 
484
    def send_expr(self, server, message, callback):
 
485
        self.send_message(server, message, True, callback)
 
486
 
 
487
    def send_keys(self, server, message):
 
488
        self.send_message(server, message, False, False)
 
489
 
 
490
    def send_esc(self, server):
 
491
        self.send_keys(server, '<C-\><C-N>')
 
492
 
 
493
    def send_ret(self, server):
 
494
        self.send_keys(server, '<RETURN>')
 
495
 
 
496
    def send_ex(self, server, message):
 
497
        self.send_esc(server)
 
498
        self.send_keys(server, ':%s' % message)
 
499
        self.send_ret(server)
 
500
 
 
501
    def get_option(self, server, option, callbackfunc):
 
502
        self.send_expr(server, '&%s' % option, callbackfunc)
 
503
    
 
504
 
 
505
    def foreground(self, server):
 
506
        def cb(*args):
 
507
            pass
 
508
        self.send_expr(server, 'foreground()', cb)
 
509
        
 
510
    def change_buffer(self, server, nr):
 
511
        self.send_ex(server, 'b!%s' % nr)
 
512
 
 
513
    def close_buffer(self, server):
 
514
        self.send_ex(server, 'confirm bw')
 
515
 
 
516
    def change_cursor(self, server, x, y):
 
517
        self.send_message(server, 'cursor(%s, %s)' % (y, x), True, False)
 
518
        self.send_esc(server)
 
519
 
 
520
    def save_session(self, name):
 
521
        self.send_ex('mks %s' % name)
 
522
 
 
523
    def escape_filename(self, name):
 
524
        for s in ['\\', '?', '*', ' ', "'", '"', '[', ' ', '$']:
 
525
            name = name.replace (s, '\\%s' % s)
 
526
        return name
 
527
 
 
528
    def open_file(self, server, name):
 
529
        self.send_ex(server, 'confirm e %s' % self.escape_filename(name))
 
530
 
 
531
    def preview_file(self, server, fn):
 
532
        self.send_ex(server, 'pc')
 
533
        self.send_ex(server, 'set nopreviewwindow')
 
534
        self.send_ex(server, 'pedit %s' % fn)
 
535
 
 
536
    def get_bufferlist(self, server):
 
537
        def cb(bl):
 
538
            if bl:
 
539
                l = [i.split(':') for i in bl.strip(';').split(';')]
 
540
                L = []
 
541
                for n in l:
 
542
                    if not n[0].startswith('E'):
 
543
                        L.append([n[0], self.abspath(server, n[1])])
 
544
                self.do_evt('bufferlist', L)
 
545
        #self.get_cwd(server)
 
546
        self.send_expr(server, 'Bufferlist()', cb)
 
547
 
 
548
    def get_current_buffer(self, server):
 
549
        def cb(bs):
 
550
            bn = bs.split(',')
 
551
            bn[1] = self.abspath(server, bn[1])
 
552
            self.do_evt('bufferchange', *bn)
 
553
        #self.get_cwd(server)
 
554
        self.send_expr(server, "bufnr('%').','.bufname('%')", cb)
 
555
 
 
556
    def quit(self, server):
 
557
        self.send_ex(server, 'q')
 
558
 
 
559
    def cb_notify(self, *a):
 
560
        win, ev =  a
 
561
        if hasattr(ev, 'atom'):
 
562
            if ev.atom == 'Comm':
 
563
                message = self.window.property_get('Comm', pdelete=True)
 
564
                if message:
 
565
                    self.cb_reply(message[-1])
 
566
        return True
 
567
 
 
568
    def cb_reply(self, data):
 
569
        mdict = self.parse_message(data)
 
570
        if mdict['t'] == 'r':
 
571
            if mdict['s'] in self.callbacks:
 
572
                self.callbacks[mdict['s']](mdict['r'])
 
573
        else:
 
574
            s = [t for t in data.split('\0') if t.startswith('-n')].pop()[3:]
 
575
            self.cb_reply_async(s)
 
576
 
 
577
    def cb_reply_async(self, data):
 
578
        if data.count(','):
 
579
            evt, d = data.split(',', 1)
 
580
            self.do_evt(evt, *d.split(','))
 
581
        else:
 
582
            self.do_log('bad async reply', data, 10)
 
583