chiark / gitweb /
contrib/sshsvc.conf: Include configuration file for `sshsvc-mkauthkeys'.
[tripe] / mon / tripemon.in
index e666ab9f3c06c5bfee0d6c5f44dcccd2b55d47ea..2462fff262c0cf92cffca3bfab3b545ac8c9a4cf 100644 (file)
@@ -40,11 +40,34 @@ import time as TIME
 import re as RX
 from cStringIO import StringIO
 
-import pygtk
-pygtk.require('2.0')
-import gtk as G
-import gobject as GO
-import gtk.gdk as GDK
+try:
+  if OS.getenv('TRIPEMON_FORCE_GI'): raise ImportError
+  import pygtk
+  pygtk.require('2.0')
+  import gtk as G
+  import gobject as GO
+  import gtk.gdk as GDK
+  GL = GO
+  GDK.KEY_Escape = G.keysyms.Escape
+  def raise_window(w): w.window.raise_()
+  combo_box_text = G.combo_box_new_text
+  def set_entry_bg(e, c): e.modify_base(G.STATE_NORMAL, c)
+except ImportError:
+  from gi.repository import GObject as GO, GLib as GL, Gtk as G, Gdk as GDK
+  G.WINDOW_TOPLEVEL = G.WindowType.TOPLEVEL
+  G.EXPAND = G.AttachOptions.EXPAND
+  G.SHRINK = G.AttachOptions.SHRINK
+  G.FILL = G.AttachOptions.FILL
+  G.SORT_ASCENDING = G.SortType.ASCENDING
+  G.POLICY_AUTOMATIC = G.PolicyType.AUTOMATIC
+  G.SHADOW_IN = G.ShadowType.IN
+  G.SELECTION_NONE = G.SelectionMode.NONE
+  G.DIALOG_MODAL = G.DialogFlags.MODAL
+  G.RESPONSE_CANCEL = G.ResponseType.CANCEL
+  G.RESPONSE_NONE = G.ResponseType.NONE
+  def raise_window(w): getattr(w.get_window(), 'raise')()
+  combo_box_text = G.ComboBoxText
+  def set_entry_bg(e, c): e.modify_bg(G.StateType.NORMAL, c)
 
 if OS.getenv('TRIPE_DEBUG_MONITOR') is not None:
   T._debug = 1
@@ -56,46 +79,6 @@ def uncaught():
   """Report an uncaught exception."""
   excepthook(*exc_info())
 
-_idles = []
-def _runidles():
-  """Invoke the functions on the idles queue."""
-  global _idles
-  while _idles:
-    old = _idles
-    _idles = []
-    for func, args, kw in old:
-      try:
-        func(*args, **kw)
-      except:
-        uncaught()
-  return False
-
-def idly(func, *args, **kw):
-  """Invoke FUNC(*ARGS, **KW) at some later point in time."""
-  if not _idles:
-    GO.idle_add(_runidles)
-  _idles.append((func, args, kw))
-
-_asides = T.Queue()
-def _runasides():
-  """
-  Coroutine function: reads (FUNC, ARGS, KW) triples from a queue and invokes
-  FUNC(*ARGS, **KW)
-  """
-  while True:
-    func, args, kw = _asides.get()
-    try:
-      func(*args, **kw)
-    except:
-      uncaught()
-
-def aside(func, *args, **kw):
-  """
-  Arrange for FUNC(*ARGS, **KW) to be performed at some point in the future,
-  and not from the main coroutine.
-  """
-  idly(_asides.put, (func, args, kw))
-
 def xwrap(func):
   """
   Return a function which behaves like FUNC, but reports exceptions via
@@ -123,13 +106,15 @@ def invoker(func, *args, **kw):
 
 def cr(func, *args, **kw):
   """Return a function which invokes FUNC(*ARGS, **KW) in a coroutine."""
-  def _(*hunoz, **hukairz):
-    T.Coroutine(xwrap(func)).switch(*args, **kw)
-  return _
+  name = T.funargstr(func, args, kw)
+  return lambda *hunoz, **hukairz: \
+         T.Coroutine(xwrap(func), name = name).switch(*args, **kw)
 
 def incr(func):
   """Decorator: runs its function in a coroutine of its own."""
-  return lambda *args, **kw: T.Coroutine(func).switch(*args, **kw)
+  return lambda *args, **kw: \
+         (T.Coroutine(func, name = T.funargstr(func, args, kw))
+          .switch(*args, **kw))
 
 ###--------------------------------------------------------------------------
 ### Random bits of infrastructure.
@@ -170,12 +155,6 @@ class HookList (object):
       if rc is not None: return rc
     return None
 
-  def runidly(me, *args, **kw):
-    """
-    Invoke the hook functions as for run, but at some point in the future.
-    """
-    idly(me.run, *args, **kw)
-
 class HookClient (object):
   """
   Mixin for classes which are clients of hooks.
@@ -215,6 +194,23 @@ rx_time = RX.compile(r'^(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)$')
 ###--------------------------------------------------------------------------
 ### Connections.
 
+class GIOWatcher (object):
+  """
+  Monitor I/O events using glib.
+  """
+  def __init__(me, conn, mc = GL.main_context_default()):
+    me._conn = conn
+    me._watch = None
+    me._mc = mc
+  def connected(me, sock):
+    me._watch = GL.io_add_watch(sock, GL.IO_IN,
+                                lambda *hunoz: me._conn.receive())
+  def disconnected(me):
+    GL.source_remove(me._watch)
+    me._watch = None
+  def iterate(me):
+    me._mc.iteration(True)
+
 class Connection (T.TripeCommandDispatcher):
   """
   The main connection to the server.
@@ -245,18 +241,15 @@ class Connection (T.TripeCommandDispatcher):
     me.handler['NOTE'] = lambda _, *rest: me.notehook.run(*rest)
     me.handler['WARN'] = lambda _, *rest: me.warnhook.run(*rest)
     me.handler['TRACE'] = lambda _, *rest: me.tracehook.run(*rest)
-    me._watch = None
+    me.iowatch = GIOWatcher(me)
 
   def connected(me):
     """Handles reconnection to the server, and signals the hook."""
     T.TripeCommandDispatcher.connected(me)
-    me._watch = GO.io_add_watch(me.sock, GO.IO_IN, invoker(me.receive))
     me.connecthook.run()
 
   def disconnected(me, reason):
     """Handles disconnection from the server, and signals the hook."""
-    GO.source_remove(me._watch)
-    me._watch = None
     me.disconnecthook.run(reason)
     T.TripeCommandDispatcher.disconnected(me, reason)
 
@@ -319,11 +312,18 @@ class Peer (MonitorObject):
     """Initialize the object with the given name."""
     MonitorObject.__init__(me, name)
     me.pinghook = HookList()
+    me.__dict__.update(conn.algs(name))
     me.update()
 
   def update(me, hunoz = None):
     """Update the peer, fetching information about it from the server."""
-    addr = conn.addr(me.name)
+    me._setaddr(conn.addr(me.name))
+    me.ifname = conn.ifname(me.name)
+    me.__dict__.update(conn.peerinfo(me.name))
+    me.changehook.run()
+
+  def _setaddr(me, addr):
+    """Set the peer's address."""
     if addr[0] == 'INET':
       ipaddr, port = addr[1:]
       try:
@@ -333,8 +333,10 @@ class Peer (MonitorObject):
         me.addr = 'INET %s:%s' % (ipaddr, port)
     else:
       me.addr = ' '.join(addr)
-    me.ifname = conn.ifname(me.name)
-    me.__dict__.update(conn.peerinfo(me.name))
+
+  def setaddr(me, addr):
+    """Informs the object of a change to its address to ADDR."""
+    me._setaddr(addr)
     me.changehook.run()
 
   def setifname(me, newname):
@@ -482,7 +484,7 @@ class Monitor (HookClient):
     """Update the auto-peers list from the connect service."""
     if 'connect' in me.services.table:
       me.autopeers = [' '.join(line)
-                      for line in conn.svcsubmit('connect', 'list')]
+                      for line in conn.svcsubmit('connect', 'list-active')]
       me.autopeers.sort()
     else:
       me.autopeers = None
@@ -498,27 +500,32 @@ class Monitor (HookClient):
     the auto-peers list.
     """
     if code == 'ADD':
-      aside(me.peers.add, rest[0], None)
+      T.aside(me.peers.add, rest[0], None)
     elif code == 'KILL':
-      aside(me.peers.remove, rest[0])
+      T.aside(me.peers.remove, rest[0])
     elif code == 'NEWIFNAME':
       try:
         me.peers[rest[0]].setifname(rest[2])
       except KeyError:
         pass
+    elif code == 'NEWADDR':
+      try:
+        me.peers[rest[0]].setaddr(rest[1:])
+      except KeyError:
+        pass
     elif code == 'SVCCLAIM':
-      aside(me.services.add, rest[0], rest[1])
+      T.aside(me.services.add, rest[0], rest[1])
       if rest[0] == 'connect':
-        aside(me._updateautopeers)
+        T.aside(me._updateautopeers)
     elif code == 'SVCRELEASE':
-      aside(me.services.remove, rest[0])
+      T.aside(me.services.remove, rest[0])
       if rest[0] == 'connect':
-        aside(me._updateautopeers)
+        T.aside(me._updateautopeers)
     elif code == 'USER':
       if not rest: return
       if rest[0] == 'watch' and \
          rest[1] == 'peerdb-update':
-        aside(me._updateautopeers)
+        T.aside(me._updateautopeers)
 
 ###--------------------------------------------------------------------------
 ### Window management cruft.
@@ -551,6 +558,17 @@ class MyWindow (MyWindowMixin):
     G.Window.__init__(me, kind)
     me.mywininit()
 
+class TrivialWindowMixin (MyWindowMixin):
+  """A simple window which you can close with Escape."""
+  def mywininit(me):
+    super(TrivialWindowMixin, me).mywininit()
+    me.connect('key-press-event', me._keypress)
+  def _keypress(me, _, ev):
+    if ev.keyval == GDK.KEY_Escape: me.destroy()
+
+class TrivialWindow (MyWindow, TrivialWindowMixin):
+  pass
+
 class MyDialog (G.Dialog, MyWindowMixin, HookClient):
   """A dialogue box with a closehook and sensible button binding."""
 
@@ -681,7 +699,7 @@ class WindowSlot (HookClient):
   def open(me):
     """Opens the window, creating it if necessary."""
     if me.window:
-      me.window.window.raise_()
+      raise_window(me.window)
     else:
       me.window = me.createfunc()
       me.hook(me.window.closehook, me.closed)
@@ -706,7 +724,7 @@ class MyScrolledWindow (G.ScrolledWindow):
 rx_num = RX.compile(r'^[-+]?\d+$')
 
 ## The colour red.
-c_red = GDK.color_parse('red')
+c_red = GDK.color_parse('#ff6666')
 
 class ValidationError (Exception):
   """Raised by ValidatingEntry.get_text() if the text isn't valid."""
@@ -729,27 +747,26 @@ class ValidatingEntry (G.Entry):
     characters (ish).  Other arguments are passed to Entry.
     """
     G.Entry.__init__(me, *arg, **kw)
-    me.connect("changed", me.check)
+    me.connect("changed", me._check)
+    me.connect("state-changed", me._check)
     if callable(valid):
       me.validate = valid
     else:
       me.validate = RX.compile(valid).match
     me.ensure_style()
-    me.c_ok = me.get_style().text[G.STATE_NORMAL]
-    me.c_bad = c_red
     if size != -1: me.set_width_chars(size)
     me.set_activates_default(True)
     me.set_text(text)
-    me.check()
+    me._check()
 
-  def check(me, *hunoz):
+  def _check(me, *hunoz):
     """Check the current text and update validp and the text colour."""
     if me.validate(G.Entry.get_text(me)):
       me.validp = True
-      me.modify_text(G.STATE_NORMAL, me.c_ok)
+      set_entry_bg(me, None)
     else:
       me.validp = False
-      me.modify_text(G.STATE_NORMAL, me.c_bad)
+      set_entry_bg(me, me.is_sensitive() and c_red or None)
 
   def get_text(me):
     """
@@ -788,7 +805,7 @@ You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software Foundation,
 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA."""
 
-class AboutBox (G.AboutDialog, MyWindowMixin):
+class AboutBox (G.AboutDialog, TrivialWindowMixin):
   """The program `About' box."""
   def __init__(me):
     G.AboutDialog.__init__(me)
@@ -813,7 +830,7 @@ def moanbox(msg):
                buttons = ((G.STOCK_OK, G.RESPONSE_NONE)))
   label = G.Label(msg)
   label.set_padding(20, 20)
-  d.vbox.pack_start(label)
+  d.vbox.pack_start(label, True, True, 0)
   label.show()
   d.run()
   d.destroy()
@@ -869,7 +886,7 @@ class WarningLogModel (LogModel):
     """Call with a new warning message."""
     me.add(tag, ' '.join([T.quotify(w) for w in rest]))
 
-class LogViewer (MyWindow):
+class LogViewer (TrivialWindow):
   """
   A log viewer window.
 
@@ -885,7 +902,7 @@ class LogViewer (MyWindow):
     """
     Create a log viewer showing the LogModel MODEL.
     """
-    MyWindow.__init__(me)
+    TrivialWindow.__init__(me)
     me.model = model
     scr = MyScrolledWindow()
     me.list = MyTreeView(me.model)
@@ -949,14 +966,14 @@ class Pinger (T.Coroutine, HookClient):
     me.hook(conn.connecthook, me._connected)
     me.hook(conn.disconnecthook, me._disconnected)
     me.hook(monitor.peers.addhook,
-            lambda p: idly(me._q.put, (p, 'ADD', None)))
+            lambda p: T.defer(me._q.put, (p, 'ADD', None)))
     me.hook(monitor.peers.delhook,
-            lambda p: idly(me._q.put, (p, 'KILL', None)))
+            lambda p: T.defer(me._q.put, (p, 'KILL', None)))
     if conn.connectedp(): me.connected()
 
   def _connected(me):
     """Respond to connection: start pinging thngs."""
-    me._timer = GO.timeout_add(1000, me._timerfunc)
+    me._timer = GL.timeout_add(1000, me._timerfunc)
 
   def _timerfunc(me):
     """Timer function: put a timer event on the queue."""
@@ -965,7 +982,7 @@ class Pinger (T.Coroutine, HookClient):
 
   def _disconnected(me, reason):
     """Respond to disconnection: stop pinging."""
-    GO.source_remove(me._timer)
+    GL.source_remove(me._timer)
 
   def run(me):
     """
@@ -1037,7 +1054,7 @@ class AddPeerDialog (MyDialog):
   def _setup(me):
     """Coroutine function: background setup for AddPeerDialog."""
     table = GridPacker()
-    me.vbox.pack_start(table)
+    me.vbox.pack_start(table, True, True, 0)
     me.e_name = table.labelled('Name',
                                ValidatingEntry(r'^[^\s.:]+$', '', 16),
                                width = 3)
@@ -1048,54 +1065,71 @@ class AddPeerDialog (MyDialog):
                                ValidatingEntry(numericvalidate(0, 65535),
                                                '4070',
                                                5))
-    me.c_keepalive = G.CheckButton('Keepalives')
-    me.l_tunnel = table.labelled('Tunnel',
-                                 G.combo_box_new_text(),
+    me.l_tunnel = table.labelled('Tunnel', combo_box_text(),
                                  newlinep = True, width = 3)
     me.tuns = conn.tunnels()
     for t in me.tuns:
       me.l_tunnel.append_text(t)
     me.l_tunnel.set_active(0)
+
+    def tickybox_sensitivity(tickybox, target):
+      tickybox.connect('toggled',
+                       lambda t: target.set_sensitive (t.get_active()))
+
+    me.c_keepalive = G.CheckButton('Keepalives')
     table.pack(me.c_keepalive, newlinep = True, xopt = G.FILL)
-    me.c_keepalive.connect('toggled',
-                           lambda t: me.e_keepalive.set_sensitive\
-                                      (t.get_active()))
     me.e_keepalive = ValidatingEntry(r'^\d+[hms]?$', '', 5)
     me.e_keepalive.set_sensitive(False)
+    tickybox_sensitivity(me.c_keepalive, me.e_keepalive)
     table.pack(me.e_keepalive, width = 3)
+
+    me.c_mobile = G.CheckButton('Mobile')
+    table.pack(me.c_mobile, newlinep = True, width = 4, xopt = G.FILL)
+
+    me.c_peerkey = G.CheckButton('Peer key tag')
+    table.pack(me.c_peerkey, newlinep = True, xopt = G.FILL)
+    me.e_peerkey = ValidatingEntry(r'^[^.:\s]+$', '', 16)
+    me.e_peerkey.set_sensitive(False)
+    tickybox_sensitivity(me.c_peerkey, me.e_peerkey)
+    table.pack(me.e_peerkey, width = 3)
+
+    me.c_privkey = G.CheckButton('Private key tag')
+    table.pack(me.c_privkey, newlinep = True, xopt = G.FILL)
+    me.e_privkey = ValidatingEntry(r'^[^.:\s]+$', '', 16)
+    me.e_privkey.set_sensitive(False)
+    tickybox_sensitivity(me.c_privkey, me.e_privkey)
+    table.pack(me.e_privkey, width = 3)
+
     me.show_all()
 
   def ok(me):
     """Handle an OK press: create the peer."""
     try:
-      if me.c_keepalive.get_active():
-        ka = me.e_keepalive.get_text()
-      else:
-        ka = None
       t = me.l_tunnel.get_active()
-      if t == 0:
-        tun = None
-      else:
-        tun = me.tuns[t]
-        me._addpeer(me.e_name.get_text(),
-                    me.e_addr.get_text(),
-                    me.e_port.get_text(),
-                    ka,
-                    tun)
+      me._addpeer(me.e_name.get_text(),
+                  me.e_addr.get_text(),
+                  me.e_port.get_text(),
+                  keepalive = (me.c_keepalive.get_active() and
+                               me.e_keepalive.get_text() or None),
+                  tunnel = t and me.tuns[t] or None,
+                  key = (me.c_peerkey.get_active() and
+                         me.e_peerkey.get_text() or None),
+                  priv = (me.c_privkey.get_active() and
+                          me.e_privkey.get_text() or None))
     except ValidationError:
       GDK.beep()
       return
 
   @incr
-  def _addpeer(me, name, addr, port, keepalive, tunnel):
+  def _addpeer(me, *args, **kw):
     """Coroutine function: actually do the ADD command."""
     try:
-      conn.add(name, addr, port, keepalive = keepalive, tunnel = tunnel)
+      conn.add(*args, **kw)
       me.destroy()
     except T.TripeError, exc:
-      idly(moanbox, ' '.join(exc))
+      T.defer(moanbox, ' '.join(exc))
 
-class ServInfo (MyWindow):
+class ServInfo (TrivialWindow):
   """
   Show information about the server and available services.
 
@@ -1106,7 +1140,7 @@ class ServInfo (MyWindow):
   """
 
   def __init__(me):
-    MyWindow.__init__(me)
+    TrivialWindow.__init__(me)
     me.set_title('TrIPE server info')
     table = GridPacker()
     me.add(table)
@@ -1165,7 +1199,7 @@ class TraceOptions (MyDialog):
       text = desc[0].upper() + desc[1:]
       ticky = G.CheckButton(text)
       ticky.set_active(st == '+')
-      me.vbox.pack_start(ticky)
+      me.vbox.pack_start(ticky, True, True, 0)
       me.opts.append((ch, ticky))
     me.show_all()
   def ok(me):
@@ -1223,6 +1257,15 @@ statsxlate = \
 ## the entry.
 statslayout = \
   [('Start time', '%(start-time)s'),
+   ('Private key', '%(current-key)s'),
+   ('Diffie-Hellman group',
+    '%(kx-group)s '
+    '(%(kx-group-order-bits)s-bit order, '
+    '%(kx-group-elt-bits)s-bit elements)'),
+   ('Cipher',
+    '%(cipher)s (%(cipher-keysz)s-bit key, %(cipher-blksz)s-bit block)'),
+   ('Mac', '%(mac)s (%(mac-keysz)s-bit key, %(mac-tagsz)s-bit tag)'),
+   ('Hash', '%(hash)s (%(hash-sz)s-bit output)'),
    ('Last key-exchange', '%(last-keyexch-time)s'),
    ('Last packet', '%(last-packet-time)s'),
    ('Packets in/out',
@@ -1233,7 +1276,7 @@ statslayout = \
     '%(ip-packets-in)s (%(ip-bytes-in)s) / %(ip-packets-out)s (%(ip-bytes-out)s)'),
    ('Rejected packets', '%(rejected-packets)s')]
 
-class PeerWindow (MyWindow):
+class PeerWindow (TrivialWindow):
   """
   Show information about a peer.
 
@@ -1251,7 +1294,7 @@ class PeerWindow (MyWindow):
   def __init__(me, peer):
     """Construct a PeerWindow, showing information about PEER."""
 
-    MyWindow.__init__(me)
+    TrivialWindow.__init__(me)
     me.set_title('TrIPE statistics: %s' % peer.name)
     me.peer = peer
 
@@ -1310,16 +1353,18 @@ class PeerWindow (MyWindow):
       stat = conn.stats(me.peer.name)
       for s, trans in statsxlate:
         stat[s] = trans(stat[s])
+      stat.update(me.peer.__dict__)
       for label, format in statslayout:
         me.e[label].set_text(format % stat)
-      GO.timeout_add(1000, lambda: me.cr.switch() and False)
+      GL.timeout_add(1000, lambda: me.cr.switch() and False)
       me.cr.parent.switch()
     me.cr = None
 
   def tryupdate(me):
     """Start the updater coroutine, if it's not going already."""
     if me.cr is None:
-      me.cr = T.Coroutine(me._update)
+      me.cr = T.Coroutine(me._update,
+                          name = 'update-peer-window %s' % me.peer.name)
       me.cr.switch()
 
   def stopupdate(me, *hunoz, **hukairz):
@@ -1344,12 +1389,12 @@ class PeerWindow (MyWindow):
 ###--------------------------------------------------------------------------
 ### Cryptographic status.
 
-class CryptoInfo (MyWindow):
+class CryptoInfo (TrivialWindow):
   """Simple display of cryptographic algorithms in use."""
   def __init__(me):
-    MyWindow.__init__(me)
+    TrivialWindow.__init__(me)
     me.set_title('Cryptographic algorithms')
-    aside(me.populate)
+    T.aside(me.populate)
   def populate(me):
     table = GridPacker()
     me.add(table)
@@ -1507,7 +1552,7 @@ class MonitorWindow (MyWindow):
     me.ui.add_ui_from_string(uidef)
 
     ## Construct the menu bar.
-    vbox.pack_start(me.ui.get_widget('/menubar'), expand = False)
+    vbox.pack_start(me.ui.get_widget('/menubar'), False, True, 0)
     me.add_accel_group(me.ui.get_accel_group())
 
     ## Construct and attach the auto-peers menu.  (This is a horrible bodge
@@ -1548,12 +1593,12 @@ class MonitorWindow (MyWindow):
     me.list.set_reorderable(True)
     me.list.get_selection().set_mode(G.SELECTION_NONE)
     scr.add(me.list)
-    vbox.pack_start(scr)
+    vbox.pack_start(scr, True, True, 0)
 
     ## Construct the status bar, and listen on hooks which report changes to
     ## connection status.
     me.status = G.Statusbar()
-    vbox.pack_start(me.status, expand = False)
+    vbox.pack_start(me.status, False, True, 0)
     me.hook(conn.connecthook, cr(me.connected))
     me.hook(conn.disconnecthook, me.disconnected)
     me.hook(conn.notehook, me.notify)
@@ -1611,7 +1656,7 @@ class MonitorWindow (MyWindow):
       else:
 
         ## Insert the new items into the menu.  (XXX this seems buggy XXX)
-       ## Tick the peers which are actually connected.
+        ## Tick the peers which are actually connected.
         i = j = 0
         for peer in monitor.autopeers:
           if j < len(existing) and \
@@ -1646,7 +1691,8 @@ class MonitorWindow (MyWindow):
     """
     if me._kidding:
       return
-    T.Coroutine(me._addautopeer_hack).switch(peer)
+    T.Coroutine(me._addautopeer_hack,
+                name = '_addautopeerhack %s' % peer).switch(peer)
 
   def _addautopeer_hack(me, peer):
     """Make an automated connection to PEER in response to a user click."""
@@ -1655,7 +1701,7 @@ class MonitorWindow (MyWindow):
     try:
       T._simple(conn.svcsubmit('connect', 'active', peer))
     except T.TripeError, exc:
-      idly(moanbox, ' '.join(exc.args))
+      T.defer(moanbox, ' '.join(exc.args))
     me.apchange()
 
   def activate(me, l, path, col):
@@ -1780,9 +1826,6 @@ def init(opts):
 
   global conn, monitor, pinger
 
-  ## Run jobs put off for later.
-  T.Coroutine(_runasides).switch()
-
   ## Try to establish a connection.
   conn = Connection(opts.tripesock)
 
@@ -1800,7 +1843,7 @@ def main():
 
   ## Main loop.
   HookClient().hook(root.closehook, exit)
-  G.main()
+  conn.mainloop()
 
 if __name__ == '__main__':
   opts = parse_options()