Index: global.c
===================================================================
--- global.c	(revision 23fddadbb7a375688e9be9627f1f5ca39aaa54d0)
+++ global.c	(revision 6fc40a789c812bcdaacbe64f88178bb1409d1071)
@@ -112,5 +112,5 @@
 
   owl_message_init_fmtext_cache();
-  owl_list_create(&(g->dispatchlist));
+  owl_list_create(&(g->io_dispatch_list));
   owl_list_create(&(g->psa_list));
   g->timerlist = NULL;
@@ -940,7 +940,7 @@
 }
 
-owl_list *owl_global_get_dispatchlist(owl_global *g)
-{
-  return &(g->dispatchlist);
+owl_list *owl_global_get_io_dispatch_list(owl_global *g)
+{
+  return &(g->io_dispatch_list);
 }
 
Index: owl.c
===================================================================
--- owl.c	(revision 23fddadbb7a375688e9be9627f1f5ca39aaa54d0)
+++ owl.c	(revision 18fdd5f9be6de827a19aceb95d4769058b145869)
@@ -315,5 +315,5 @@
 }
 
-void owl_process_input(owl_dispatch *d)
+void owl_process_input(const owl_io_dispatch *d, void *data)
 {
   owl_input j;
@@ -444,5 +444,5 @@
 
 /* Sends stderr (read from rfd) messages to the error console */
-void stderr_redirect_handler(owl_dispatch *d)
+void stderr_redirect_handler(const owl_io_dispatch *d, void *data)
 {
   int navail, bread;
@@ -521,25 +521,12 @@
   owl_global_set_haveaim(&g);
 
-  /* prepare stdin dispatch */
-  {
-    owl_dispatch *d = owl_malloc(sizeof(owl_dispatch));
-    d->fd = STDIN;
-    d->cfunc = &owl_process_input;
-    d->destroy = NULL;
-    owl_select_add_dispatch(d);
-  }
-
+  /* register STDIN dispatch; throw away return, we won't need it */
+  owl_select_add_io_dispatch(STDIN, OWL_IO_READ, &owl_process_input, NULL, NULL);
   owl_zephyr_initialize();
 
 #if OWL_STDERR_REDIR
   /* Do this only after we've started curses up... */
-  {
-    owl_dispatch *d = owl_malloc(sizeof(owl_dispatch));
-    owl_function_debugmsg("startup: doing stderr redirection");
-    d->fd = stderr_replace();
-    d->cfunc = stderr_redirect_handler;
-    d->destroy = NULL;
-    owl_select_add_dispatch(d);
-  }
+  owl_function_debugmsg("startup: doing stderr redirection");
+  owl_select_add_io_dispatch(stderr_replace(), OWL_IO_READ, &stderr_redirect_handler, NULL, NULL);
 #endif
 
Index: owl.h
===================================================================
--- owl.h	(revision 68c572a4377c03adaeca5bb25ec192e3515dc2df)
+++ owl.h	(revision 6fc40a789c812bcdaacbe64f88178bb1409d1071)
@@ -107,7 +107,7 @@
 #define OWL_MESSAGE_DIRECTION_OUT   2
 
-#define OWL_MUX_READ   1
-#define OWL_MUX_WRITE  2
-#define OWL_MUX_EXCEPT 4
+#define OWL_IO_READ   1
+#define OWL_IO_WRITE  2
+#define OWL_IO_EXCEPT 4
 
 #define OWL_DIRECTION_NONE      0
@@ -503,11 +503,12 @@
 } owl_obarray;
 
-typedef struct _owl_dispatch {
-  int fd;                                 /* FD to watch for dispatch. */
+typedef struct _owl_io_dispatch {
+  int fd;                                     /* FD to watch for dispatch. */
+  int mode;
   int needs_gc;
-  void (*cfunc)(struct _owl_dispatch*);   /* C function to dispatch to. */
-  void (*destroy)(struct _owl_dispatch*); /* Destructor */
+  void (*callback)(const struct _owl_io_dispatch *, void *); /* C function to dispatch to. */
+  void (*destroy)(const struct _owl_io_dispatch *);  /* Destructor */
   void *data;
-} owl_dispatch;
+} owl_io_dispatch;
 
 typedef struct _owl_ps_action {
@@ -523,5 +524,5 @@
   int winactive;
   pid_t pid;			/* or 0 if it has terminated */
-  owl_dispatch dispatch;
+  const owl_io_dispatch *dispatch;
 } owl_popexec;
 
@@ -596,5 +597,5 @@
   struct termios startup_tio;
   owl_obarray obarray;
-  owl_list dispatchlist;
+  owl_list io_dispatch_list;
   owl_list psa_list;
   GList *timerlist;
Index: perl/lib/BarnOwl.pm
===================================================================
--- perl/lib/BarnOwl.pm	(revision 3c428d48282cf713201d1675e0b568003c595d46)
+++ perl/lib/BarnOwl.pm	(revision ffc4df61a846d99a75783f7d348fb98dd19910aa)
@@ -13,5 +13,6 @@
                     error debug
                     create_style getnumcolors wordwrap
-                    add_dispath remove_dispatch
+                    add_dispatch remove_dispatch
+                    add_io_dispatch remove_io_dispatch
                     new_command
                     new_variable_int new_variable_bool new_variable_string
@@ -62,5 +63,4 @@
 Returns the current message as a C<BarnOwl::Message> subclass, or
 undef if there is no message selected
-
 =head2 getnumcols
 
@@ -160,7 +160,56 @@
 read from C<FD>.
 
+C<add_dispatch> has been deprecated in favor of C<add_io_dispatch>,
+and is now a wrapper for it called with C<mode> set to C<'r'>.
+
+=cut
+
+sub add_dispatch {
+    my $fd = shift;
+    my $cb = shift;
+    add_io_dispatch($fd, 'r', $cb);
+}
+
 =head2 remove_dispatch FD
 
 Remove a file descriptor previously registered via C<add_dispatch>
+
+C<remove_dispatch> has been deprecated in favor of
+C<remove_io_dispatch>.
+
+=cut
+
+*remove_dispatch = \&remove_io_dispatch;
+
+=head2 add_io_dispatch FD MODE CB
+
+Adds a file descriptor to C<BarnOwl>'s internal C<select()>
+loop. <MODE> can be 'r', 'w', or 'rw'. C<CALLBACK> will be invoked
+whenever C<FD> becomes ready, as specified by <MODE>.
+
+Only one callback can be registered per FD. If a new callback is
+registered, the old one is removed.
+
+=cut
+
+sub add_io_dispatch {
+    my $fd = shift;
+    my $modeStr = shift;
+    my $cb = shift;
+    my $mode = 0;
+
+    $mode |= 0x1 if ($modeStr =~ /r/i); # Read
+    $mode |= 0x2 if ($modeStr =~ /w/i); # Write
+    if ($mode) {
+        $mode |= 0x4;                  # Exceptional
+        BarnOwl::Internal::add_io_dispatch($fd, $mode, $cb);
+    } else {
+        die("Invalid I/O Dispatch mode: $modeStr");
+    }
+}
+
+=head2 remove_io_dispatch FD
+
+Remove a file descriptor previously registered via C<add_io_dispatch>
 
 =head2 create_style NAME OBJECT
Index: perl/modules/IRC/lib/BarnOwl/Module/IRC.pm
===================================================================
--- perl/modules/IRC/lib/BarnOwl/Module/IRC.pm	(revision da554dacc0fe3e67c020250564135f598780682f)
+++ perl/modules/IRC/lib/BarnOwl/Module/IRC.pm	(revision f1a273685f36f1fb7fac47ea3a1faae22b58a352)
@@ -381,5 +381,5 @@
         $ircnets{$alias} = $conn;
         my $fd = $conn->getSocket()->fileno();
-        BarnOwl::add_dispatch($fd, \&OwlProcess);
+        BarnOwl::add_io_dispatch($fd, 'r', \&OwlProcess);
         $conn->{FD} = $fd;
     } else {
Index: perl/modules/IRC/lib/BarnOwl/Module/IRC/Connection.pm
===================================================================
--- perl/modules/IRC/lib/BarnOwl/Module/IRC/Connection.pm	(revision 7cfb1df72c2e48efa3d036ead301b04c92f12c7f)
+++ perl/modules/IRC/lib/BarnOwl/Module/IRC/Connection.pm	(revision f1a273685f36f1fb7fac47ea3a1faae22b58a352)
@@ -210,5 +210,5 @@
         }
     }
-    BarnOwl::remove_dispatch($self->{FD});
+    BarnOwl::remove_io_dispatch($self->{FD});
     BarnOwl::admin_message('IRC',
                            "[" . $self->alias . "] Disconnected from server");
Index: perl/modules/Jabber/lib/BarnOwl/Module/Jabber.pm
===================================================================
--- perl/modules/Jabber/lib/BarnOwl/Module/Jabber.pm	(revision f3678c324b8086ca07cd0baaa425a0aa99a75524)
+++ perl/modules/Jabber/lib/BarnOwl/Module/Jabber.pm	(revision f1a273685f36f1fb7fac47ea3a1faae22b58a352)
@@ -471,9 +471,9 @@
 		$conn->renameConnection($jidStr, $fullJid);
                 queue_admin_msg("Connected to jabber as $fullJid");
-                # The remove_dispatch() method is called from the
+                # The remove_io_dispatch() method is called from the
                 # ConnectionManager's removeConnection() method.
                 $client->{fileno} = $client->getSocket()->fileno();
                 #queue_admin_msg("Connected to jabber as $fullJid ($client->{fileno})");
-                BarnOwl::add_dispatch($client->{fileno}, sub { $client->OwlProcess($fullJid) });
+                BarnOwl::add_io_dispatch($client->{fileno}, 'r', sub { $client->OwlProcess($fullJid) });
 
                 # populate completion from roster.
Index: perl/modules/Jabber/lib/BarnOwl/Module/Jabber/ConnectionManager.pm
===================================================================
--- perl/modules/Jabber/lib/BarnOwl/Module/Jabber/ConnectionManager.pm	(revision 859077469433dd85e1c9ebff365cb2cc6d237c6a)
+++ perl/modules/Jabber/lib/BarnOwl/Module/Jabber/ConnectionManager.pm	(revision f1a273685f36f1fb7fac47ea3a1faae22b58a352)
@@ -37,5 +37,5 @@
     return 0 unless exists $self->{$jidStr};
 
-    BarnOwl::remove_dispatch($self->{$jidStr}->{Client}->{fileno}) if $self->{$jidStr}->{Client}->{fileno};
+    BarnOwl::remove_io_dispatch($self->{$jidStr}->{Client}->{fileno}) if $self->{$jidStr}->{Client}->{fileno};
     $self->{$jidStr}->{Client}->Disconnect()
       if $self->{$jidStr}->{Client};
@@ -55,5 +55,5 @@
     }
 
-    BarnOwl::remove_dispatch($self->{$jidStr}->{Client}->{fileno}) if $self->{$jidStr}->{Client}->{fileno};
+    BarnOwl::remove_io_dispatch($self->{$jidStr}->{Client}->{fileno}) if $self->{$jidStr}->{Client}->{fileno};
     $self->{$jidStr}->{Client}->Disconnect()
       if $self->{$jidStr}->{Client};
Index: perlconfig.c
===================================================================
--- perlconfig.c	(revision e7f5970bf7083cf17c2ed23381f4ff53816d7ffb)
+++ perlconfig.c	(revision ffc4df61a846d99a75783f7d348fb98dd19910aa)
@@ -542,8 +542,7 @@
 }
 
-void owl_perlconfig_dispatch_free(owl_dispatch *d)
+void owl_perlconfig_io_dispatch_destroy(const owl_io_dispatch *d)
 {
   SvREFCNT_dec(d->data);
-  owl_free(d);
 }
 
@@ -593,7 +592,7 @@
 }
 
-void owl_perlconfig_dispatch(owl_dispatch *d)
-{
-  SV *cb = d->data;
+void owl_perlconfig_io_dispatch(const owl_io_dispatch *d, void *data)
+{
+  SV *cb = data;
   dSP;
   if(cb == NULL) {
@@ -607,5 +606,5 @@
   PUSHMARK(SP);
   PUTBACK;
-  
+
   call_sv(cb, G_DISCARD|G_KEEPERR|G_EVAL);
 
Index: perlglue.xs
===================================================================
--- perlglue.xs	(revision fe7616e5988beed102273ee0ed321a352ba80297)
+++ perlglue.xs	(revision ffc4df61a846d99a75783f7d348fb98dd19910aa)
@@ -318,16 +318,8 @@
 
 void
-add_dispatch(fd, cb)
+remove_io_dispatch(fd)
 	int fd
-	SV * cb
-	CODE:
-	owl_select_add_perl_dispatch(fd, SvREFCNT_inc(cb));
-
-void
-remove_dispatch(fd)
-	int fd
-	CODE:
-	owl_select_remove_perl_dispatch(fd);
-
+	CODE:
+	owl_select_remove_perl_io_dispatch(fd);
 
 AV*
@@ -504,4 +496,12 @@
 				      ival);
 
+void
+add_io_dispatch(fd, mode, cb)
+	int fd
+	int mode
+	SV * cb
+	CODE:
+	owl_select_add_perl_io_dispatch(fd, mode, SvREFCNT_inc(cb));
+
 IV
 add_timer(after, interval, cb)
Index: popexec.c
===================================================================
--- popexec.c	(revision 0e5afa2bbf43713baa4056d267c8f99c4104238a)
+++ popexec.c	(revision 18fdd5f9be6de827a19aceb95d4769058b145869)
@@ -53,9 +53,5 @@
     pe->pid=pid;
     pe->winactive=1;
-    pe->dispatch.fd = parent_read_fd;
-    pe->dispatch.cfunc = owl_popexec_inputhandler;
-    pe->dispatch.destroy = owl_popexec_free_dispatch;
-    pe->dispatch.data = pe;
-    owl_select_add_dispatch(&pe->dispatch);
+    pe->dispatch = owl_select_add_io_dispatch(parent_read_fd, OWL_IO_READ|OWL_IO_EXCEPT, &owl_popexec_inputhandler, &owl_popexec_delete_dispatch, pe);
     pe->refcount++;
   } else {
@@ -78,7 +74,7 @@
 }
 
-void owl_popexec_inputhandler(owl_dispatch *d)
+void owl_popexec_inputhandler(const owl_io_dispatch *d, void *data)
 {
-  owl_popexec *pe = d->data;
+  owl_popexec *pe = data;
   int navail, bread, rv_navail;
   char *buf;
@@ -99,5 +95,5 @@
   /* the viewwin has closed */
   if (!pe->pid && !pe->winactive) {
-    owl_select_remove_dispatch(d->fd);
+    owl_select_remove_io_dispatch(d);
     return;
   }
@@ -116,5 +112,5 @@
       owl_viewwin_redisplay(pe->vwin, 1);
     }
-    owl_select_remove_dispatch(d->fd);
+    owl_select_remove_io_dispatch(d);
     return;
   }
@@ -146,5 +142,5 @@
 }
 
-void owl_popexec_free_dispatch(owl_dispatch *d)
+void owl_popexec_delete_dispatch(const owl_io_dispatch *d)
 {
   owl_popexec *pe = d->data;
@@ -159,6 +155,6 @@
 
   pe->winactive = 0;
-  if (pe->dispatch.fd>0) {
-    owl_select_remove_dispatch(pe->dispatch.fd);
+  if (pe->dispatch->fd > 0) {
+    owl_select_remove_io_dispatch(pe->dispatch);
   }
   if (pe->pid) {
Index: select.c
===================================================================
--- select.c	(revision 4f2166b9a2565b2ccc5fbcbb9869b2a41119a342)
+++ select.c	(revision 6fc40a789c812bcdaacbe64f88178bb1409d1071)
@@ -81,145 +81,60 @@
 }
 
-/* Returns the index of the dispatch for the file descriptor. */
-int owl_select_find_dispatch(int fd)
+static const owl_io_dispatch *owl_select_find_io_dispatch_by_fd(const int fd)
 {
   int i, len;
   const owl_list *dl;
-  const owl_dispatch *d;
-  
-  dl = owl_global_get_dispatchlist(&g);
+  owl_io_dispatch *d;
+  dl = owl_global_get_io_dispatch_list(&g);
   len = owl_list_get_size(dl);
   for(i = 0; i < len; i++) {
     d = owl_list_get_element(dl, i);
-    if (d->fd == fd) return i;
+    if (d->fd == fd) return d;
+  }
+  return NULL;
+}
+
+static int owl_select_find_io_dispatch(const owl_io_dispatch *in)
+{
+  int i, len;
+  const owl_list *dl;
+
+  if (in != NULL) {
+    dl = owl_global_get_io_dispatch_list(&g);
+    len = owl_list_get_size(dl);
+    for(i = 0; i < len; i++) {
+      const owl_io_dispatch *d = owl_list_get_element(dl, i);
+      if (d == in) return i;
+    }
   }
   return -1;
 }
 
-void owl_select_remove_dispatch_at(int elt) /* noproto */
-{
-  owl_list *dl;
-  owl_dispatch *d;
-
-  dl = owl_global_get_dispatchlist(&g);
-  d = owl_list_get_element(dl, elt);
-  owl_list_remove_element(dl, elt);
-  if (d->destroy) {
-    d->destroy(d);
-  }
-}
-
-/* Adds a new owl_dispatch to the list, replacing existing ones if needed. */
-void owl_select_add_dispatch(owl_dispatch *d)
+void owl_select_remove_io_dispatch(const owl_io_dispatch *in)
 {
   int elt;
-  owl_list *dl;
-
-  d->needs_gc = 0;
-
-  elt = owl_select_find_dispatch(d->fd);
-  dl = owl_global_get_dispatchlist(&g);
-  
-  if (elt != -1) {  /* If we have a dispatch for this FD */
-    owl_dispatch *d_old;
-    d_old = owl_list_get_element(dl, elt);
-    /* Ignore if we're adding the same dispatch again.  Otherwise
-       replace the old dispatch. */
-    if (d_old != d) {
-      owl_select_remove_dispatch_at(elt);
-    }
-  }
-  owl_list_append_element(dl, d);
-}
-
-/* Removes an owl_dispatch to the list, based on it's file descriptor. */
-void owl_select_remove_dispatch(int fd)
-{
-  int elt;
-  owl_list *dl;
-  owl_dispatch *d;
-
-  elt = owl_select_find_dispatch(fd);
-  if(elt == -1) {
-    return;
-  } else if(dispatch_active) {
-    /* Defer the removal until dispatch is done walking the list */
-    dl = owl_global_get_dispatchlist(&g);
-    d = owl_list_get_element(dl, elt);
-    d->needs_gc = 1;
-  } else {
-    owl_select_remove_dispatch_at(elt);
-  }
-}
-
-int owl_select_dispatch_count(void)
-{
-  return owl_list_get_size(owl_global_get_dispatchlist(&g));
-}
-
-int owl_select_add_perl_dispatch(int fd, SV *cb)
-{
-  int elt;
-  owl_dispatch *d;
-  elt = owl_select_find_dispatch(fd);
-  if (elt != -1) {
-    d = owl_list_get_element(owl_global_get_dispatchlist(&g), elt);
-    if (d->cfunc != owl_perlconfig_dispatch) {
-      /* don't mess with non-perl dispatch functions from here. */
-      return 1;
-    }
-  }
-
-  d = owl_malloc(sizeof(owl_dispatch));
-  d->fd = fd;
-  d->cfunc = owl_perlconfig_dispatch;
-  d->destroy = owl_perlconfig_dispatch_free;
-  d->data = cb;
-  owl_select_add_dispatch(d);
-  return 0;
-}
-
-int owl_select_remove_perl_dispatch(int fd)
-{
-  int elt;
-  owl_dispatch *d;
-  
-  elt = owl_select_find_dispatch(fd);
-  if (elt != -1) {
-    d = owl_list_get_element(owl_global_get_dispatchlist(&g), elt);
-    if (d->cfunc == owl_perlconfig_dispatch) {
-      owl_select_remove_dispatch_at(elt);
-      return 0;
-    }
-  }
-  return 1;
-}
-
-int owl_select_dispatch_prepare_fd_sets(fd_set *r, fd_set *e)
-{
-  int i, len, max_fd;
-  owl_dispatch *d;
-  const owl_list *dl;
-
-  dl = owl_global_get_dispatchlist(&g);
-  FD_ZERO(r);
-  FD_ZERO(e);
-  max_fd = 0;
-  len = owl_select_dispatch_count();
-  for(i = 0; i < len; i++) {
-    d = owl_list_get_element(dl, i);
-    FD_SET(d->fd, r);
-    FD_SET(d->fd, e);
-    if (max_fd < d->fd) max_fd = d->fd;
-  }
-  return max_fd + 1;
-}
-
-void owl_select_gc(void)
+  if (in != NULL) {
+    elt = owl_select_find_io_dispatch(in);
+    if (elt != -1) {
+      owl_list *dl = owl_global_get_io_dispatch_list(&g);
+      owl_io_dispatch *d = owl_list_get_element(dl, elt);
+      if (dispatch_active)
+        d->needs_gc = 1;
+      else {
+        owl_list_remove_element(dl, elt);
+        if (d->destroy)
+          d->destroy(d);
+        owl_free(d);
+      }
+    }
+  }
+}
+
+void owl_select_io_dispatch_gc(void)
 {
   int i;
   owl_list *dl;
 
-  dl = owl_global_get_dispatchlist(&g);
+  dl = owl_global_get_io_dispatch_list(&g);
   /*
    * Count down so we aren't set off by removing items from the list
@@ -227,35 +142,94 @@
    */
   for(i = owl_list_get_size(dl) - 1; i >= 0; i--) {
-    const owl_dispatch *d = owl_list_get_element(dl, i);
+    owl_io_dispatch *d = owl_list_get_element(dl, i);
     if(d->needs_gc) {
-      owl_select_remove_dispatch_at(i);
-    }
-  }
-}
-
-void owl_select_dispatch(fd_set *fds, int max_fd)
+      owl_select_remove_io_dispatch(d);
+    }
+  }
+}
+
+/* Each FD may have at most one dispatcher.
+ * If a new dispatch is added for an FD, the old one is removed.
+ * mode determines what types of events are watched for, and may be any combination of:
+ * OWL_IO_READ, OWL_IO_WRITE, OWL_IO_EXCEPT
+ */
+const owl_io_dispatch *owl_select_add_io_dispatch(int fd, int mode, void (*cb)(const owl_io_dispatch *, void *), void (*destroy)(const owl_io_dispatch *), void *data)
+{
+  owl_io_dispatch *d = owl_malloc(sizeof(owl_io_dispatch));
+  owl_list *dl = owl_global_get_io_dispatch_list(&g);
+
+  d->fd = fd;
+  d->needs_gc = 0;
+  d->mode = mode;
+  d->callback = cb;
+  d->destroy = destroy;
+  d->data = data;
+
+  owl_select_remove_io_dispatch(owl_select_find_io_dispatch_by_fd(fd));
+  owl_list_append_element(dl, d);
+
+  return d;
+}
+
+int owl_select_prepare_io_dispatch_fd_sets(fd_set *rfds, fd_set *wfds, fd_set *efds) {
+  int i, len, max_fd;
+  owl_io_dispatch *d;
+  owl_list *dl = owl_global_get_io_dispatch_list(&g);
+
+  max_fd = 0;
+  len = owl_list_get_size(dl);
+  for (i = 0; i < len; i++) {
+    d = owl_list_get_element(dl, i);
+    if (d->mode & (OWL_IO_READ | OWL_IO_WRITE | OWL_IO_EXCEPT)) {
+      if (max_fd < d->fd) max_fd = d->fd;
+      if (d->mode & OWL_IO_READ) FD_SET(d->fd, rfds);
+      if (d->mode & OWL_IO_WRITE) FD_SET(d->fd, wfds);
+      if (d->mode & OWL_IO_EXCEPT) FD_SET(d->fd, efds);
+    }
+  }
+  return max_fd + 1;
+}
+
+void owl_select_io_dispatch(const fd_set *rfds, const fd_set *wfds, const fd_set *efds, const int max_fd)
 {
   int i, len;
-  owl_dispatch *d;
-  const owl_list *dl;
-
-  dl = owl_global_get_dispatchlist(&g);
-  len = owl_select_dispatch_count();
+  owl_io_dispatch *d;
+  owl_list *dl = owl_global_get_io_dispatch_list(&g);
 
   dispatch_active = 1;
-
-  for(i = 0; i < len; i++) {
+  len = owl_list_get_size(dl);
+  for (i = 0; i < len; i++) {
     d = owl_list_get_element(dl, i);
-    /* While d shouldn't normally be null, the list may be altered by
-     * functions we dispatch to. */
-    if (d != NULL && !d->needs_gc && FD_ISSET(d->fd, fds)) {
-      if (d->cfunc != NULL) {
-        d->cfunc(d);
-      }
-    }
-  }
-
+    if (d->fd < max_fd && d->callback != NULL &&
+        ((d->mode & OWL_IO_READ && FD_ISSET(d->fd, rfds)) ||
+         (d->mode & OWL_IO_WRITE && FD_ISSET(d->fd, wfds)) ||
+         (d->mode & OWL_IO_EXCEPT && FD_ISSET(d->fd, efds)))) {
+      d->callback(d, d->data);
+    }
+  }
   dispatch_active = 0;
-  owl_select_gc();
+  owl_select_io_dispatch_gc();
+}
+
+int owl_select_add_perl_io_dispatch(int fd, int mode, SV *cb)
+{
+  const owl_io_dispatch *d = owl_select_find_io_dispatch_by_fd(fd);
+  if (d != NULL && d->callback != owl_perlconfig_io_dispatch) {
+    /* Don't mess with non-perl dispatch functions from here. */
+    return 1;
+  }
+  owl_select_add_io_dispatch(fd, mode, owl_perlconfig_io_dispatch, owl_perlconfig_io_dispatch_destroy, cb);
+  return 0;
+}
+
+int owl_select_remove_perl_io_dispatch(int fd)
+{
+  const owl_io_dispatch *d = owl_select_find_io_dispatch_by_fd(fd);
+  if (d != NULL && d->callback == owl_perlconfig_io_dispatch) {
+    /* Only remove perl io dispatchers from here. */
+    owl_select_remove_io_dispatch(d);
+    return 0;
+  }
+  return 1;
 }
 
@@ -266,6 +240,4 @@
   int max_fd;
 
-  FD_ZERO(rfds);
-  FD_ZERO(wfds);
   max_fd = 0;
   sess = owl_global_get_aimsess(&g);
@@ -414,6 +386,7 @@
 void owl_select(void)
 {
-  int i, max_fd, aim_max_fd, aim_done, ret;
+  int i, max_fd, max_fd2, aim_done, ret;
   fd_set r;
+  fd_set w;
   fd_set e;
   fd_set aim_rfds, aim_wfds;
@@ -430,6 +403,9 @@
     return;
   }
-
-  max_fd = owl_select_dispatch_prepare_fd_sets(&r, &e);
+  FD_ZERO(&r);
+  FD_ZERO(&w);
+  FD_ZERO(&e);
+
+  max_fd = owl_select_prepare_io_dispatch_fd_sets(&r, &w, &e);
 
   /* AIM HACK: 
@@ -448,11 +424,15 @@
   if (owl_global_is_doaimevents(&g)) {
     aim_done = 0;
-    aim_max_fd = owl_select_aim_hack(&aim_rfds, &aim_wfds);
-    if (max_fd < aim_max_fd) max_fd = aim_max_fd;
-    for(i = 0; i <= aim_max_fd; i++) {
+    max_fd2 = owl_select_aim_hack(&aim_rfds, &aim_wfds);
+    if (max_fd < max_fd2) max_fd = max_fd2;
+    for(i = 0; i <= max_fd2; i++) {
       if (FD_ISSET(i, &aim_rfds)) {
         FD_SET(i, &r);
         FD_SET(i, &e);
       }
+      if (FD_ISSET(i, &aim_wfds)) {
+        FD_SET(i, &w);
+        FD_SET(i, &e);
+      }
     }
   }
@@ -464,5 +444,5 @@
   }
 
-  ret = pselect(max_fd+1, &r, &aim_wfds, &e, &timeout, &mask);
+  ret = pselect(max_fd+1, &r, &w, &e, &timeout, &mask);
 
   if(ret < 0 && errno == EINTR) {
@@ -482,5 +462,5 @@
       /* Merge all interesting FDs into one set, since we have a
          single dispatch per FD. */
-      if (FD_ISSET(i, &r) || FD_ISSET(i, &aim_wfds) || FD_ISSET(i, &e)) {
+      if (FD_ISSET(i, &r) || FD_ISSET(i, &w) || FD_ISSET(i, &e)) {
         /* AIM HACK: no separate dispatch, just process here if
            needed, and only once per run through. */
@@ -496,5 +476,5 @@
     /* NOTE: the same dispatch function is called for both exceptional
        and read ready FDs. */
-    owl_select_dispatch(&r, max_fd);
-  }
-}
+    owl_select_io_dispatch(&r, &w, &e, max_fd);
+  }
+}
Index: zephyr.c
===================================================================
--- zephyr.c	(revision 12e291a330f117a61b8b222315c1f39822010a2a)
+++ zephyr.c	(revision 18fdd5f9be6de827a19aceb95d4769058b145869)
@@ -36,5 +36,4 @@
   ZNotice_t req;
   Code_t code;
-  owl_dispatch *dispatch;
 
   /*
@@ -80,17 +79,12 @@
   }
 
-  dispatch = owl_malloc(sizeof(*dispatch));
-  dispatch->fd = ZGetFD();
-  dispatch->cfunc = owl_zephyr_finish_initialization;
-  dispatch->destroy = (void(*)(owl_dispatch*))owl_free;
-
-  owl_select_add_dispatch(dispatch);
-}
-
-void owl_zephyr_finish_initialization(owl_dispatch *d) {
+  owl_select_add_io_dispatch(ZGetFD(), OWL_IO_READ|OWL_IO_EXCEPT, &owl_zephyr_finish_initialization, NULL, NULL);
+}
+
+void owl_zephyr_finish_initialization(const owl_io_dispatch *d, void *data) {
   Code_t code;
   char *perl;
 
-  owl_select_remove_dispatch(d->fd);
+  owl_select_remove_io_dispatch(d);
 
   ZClosePort();
@@ -106,9 +100,6 @@
   }
 
-  d = owl_malloc(sizeof(owl_dispatch));
-  d->fd = ZGetFD();
-  d->cfunc = &owl_zephyr_process_events;
-  d->destroy = NULL;
-  owl_select_add_dispatch(d);
+  owl_select_add_io_dispatch(ZGetFD(), OWL_IO_READ|OWL_IO_EXCEPT, &owl_zephyr_process_events, NULL, NULL);
+
   owl_global_set_havezephyr(&g);
 
@@ -1350,5 +1341,5 @@
 }
 
-void owl_zephyr_process_events(owl_dispatch *d)
+void owl_zephyr_process_events(const owl_io_dispatch *d, void *data)
 {
   _owl_zephyr_process_events();
