Index: .gitignore
===================================================================
--- .gitignore	(revision de18326f12c18fd92f275bf46bca509343b79063)
+++ .gitignore	(revision f93cc347fb2a2bbbda7429640ecb42470315a62d)
@@ -15,4 +15,5 @@
 autom4te.cache
 barnowl.bin
+tester.bin
 zcrypt
 blib
@@ -30,5 +31,4 @@
 owl_prototypes.h
 owl_prototypes.h.new
-perl_tester
 perlglue.c
 perlwrap.c
Index: Makefile.am
===================================================================
--- Makefile.am	(revision 5aa33fd73dce2c8d6c88559e62cd1ae78ef4a36d)
+++ Makefile.am	(revision 9d27ee8678625706f5eead430bbe2f956fc1eb41)
@@ -1,3 +1,4 @@
 ACLOCAL_AMFLAGS = -I m4
+CFLAGS += $(EXTRA_CFLAGS)
 
 GIT_DESCRIPTION := $(if $(wildcard .git),$(shell git describe --match='barnowl-*' HEAD 2>/dev/null))
@@ -18,4 +19,7 @@
      $(GEN_C) $(GEN_H)
 
+man_MANS = doc/barnowl.1
+doc_DATA = doc/intro.txt doc/advanced.txt
+
 barnowl_bin_LDADD = libfaim/libfaim.a
 
@@ -35,5 +39,5 @@
            $(GIT_FLAGS)
 
-BASE_SRCS=list.c message.c mainwin.c popwin.c zephyr.c messagelist.c \
+CODELIST_SRCS=list.c message.c mainwin.c popwin.c zephyr.c messagelist.c \
      commands.c global.c text.c fmtext.c editwin.c util.c logging.c \
      perlconfig.c keys.c functions.c zwrite.c viewwin.c help.c filter.c \
@@ -41,6 +45,10 @@
      keypress.c keymap.c keybinding.c cmd.c context.c \
      aim.c buddy.c buddylist.c style.c errqueue.c \
-     zbuddylist.c popexec.c obarray.c select.c wcwidth.c \
-     glib_compat.c filterproc.c
+     zbuddylist.c popexec.c select.c wcwidth.c \
+     glib_compat.c mainpanel.c msgwin.c sepbar.c
+
+NORMAL_SRCS = filterproc.c window.c windowcb.c
+
+BASE_SRCS = $(CODELIST_SRCS) $(NORMAL_SRCS)
 
 GEN_C = varstubs.c perlglue.c
@@ -64,5 +72,5 @@
 	$(AM_V_GEN)perl $< $(sort $(filter-out $<,$+)) > $@
 
-owl_prototypes.h.new: codelist.pl varstubs.c $(BASE_SRCS)
+owl_prototypes.h.new: codelist.pl varstubs.c $(CODELIST_SRCS)
 	$(AM_V_GEN)perl $< $(sort $(filter-out $<,$+)) > $@
 
Index: README
===================================================================
--- README	(revision 62c91c1f0cea6cc5450131f07cc577f8d54ba828)
+++ README	(revision aeadc748f353cd0be4ebfa805d85577c9c7498fa)
@@ -1,8 +1,7 @@
 BarnOwl - owl, with more ponies
 
+Source is freely available from http://github.com/barnowl/barnowl/
+
 Based on owl 2.1.11, by James Kretchmar (http://www.ktools.org)
-
-This project is a work in progress.
-We guarantee no stability of form or function.
 
 Notes:
Index: cmd.c
===================================================================
--- cmd.c	(revision 8d4b521bc5864ca28d5d575dc461859580d4cf28)
+++ cmd.c	(revision 1cc9b61526eafe04cf53d24712f11ab9ac0fbb8b)
@@ -52,4 +52,5 @@
   cmd = owl_malloc(sizeof(owl_cmd));
   owl_cmd_create_alias(cmd, alias_from, alias_to);
+  owl_perlconfig_new_command(cmd->name);
   owl_dict_insert_element(cd, cmd->name, cmd, (void (*)(void *))owl_cmd_delete);
   return(0);
@@ -73,4 +74,6 @@
   } else if (NULL != (cmd = owl_dict_find_element(cd, argv[0]))) {
     retval = owl_cmd_execute(cmd, cd, ctx, argc, argv, buff);
+    /* redraw the sepbar; TODO: don't violate layering */
+    owl_global_sepbar_dirty(&g);
   } else {
     owl_function_makemsg("Unknown command '%s'.", buff);
@@ -89,5 +92,4 @@
   if (argc < 0) {
     owl_free(tmpbuff);
-    sepbar(NULL);
     owl_function_makemsg("Unbalanced quotes");
     return NULL;
@@ -104,5 +106,4 @@
   owl_parse_delete(argv, argc);
   owl_free(tmpbuff);
-  sepbar(NULL);
   return retval;
 }
Index: commands.c
===================================================================
--- commands.c	(revision 7ba9e0de5c3ae61398fc37bbee797b006d03f947)
+++ commands.c	(revision 987cf3f48057443b84ecf140854129cf1a6cbbdc)
@@ -106,5 +106,5 @@
   OWLCMD_ARGS("zwrite", owl_command_zwrite, OWL_CTX_INTERACTIVE,
 	      "send a zephyr",
-	      "zwrite [-n] [-C] [-c class] [-i instance] [-r realm] [-O opcde] [<user> ...] [-m <message...>]",
+	      "zwrite [-n] [-C] [-c class] [-i instance] [-r realm] [-O opcode] [<user> ...] [-m <message...>]",
 	      "Zwrite send a zephyr to the one or more users specified.\n\n"
 	      "The following options are available:\n\n"
@@ -136,7 +136,7 @@
 	      "Send a local message.\n"),
 
-  OWLCMD_ARGS("zcrypt", owl_command_zcrypt, OWL_CTX_INTERACTIVE,
+  OWLCMD_ARGS("zcrypt", owl_command_zwrite, OWL_CTX_INTERACTIVE,
 	      "send an encrypted zephyr",
-	      "zcrypt [-n] [-C] [-c class] [-i instance] [-r realm] [-O opcde] [-m <message...>]\n",
+	      "zcrypt [-n] [-C] [-c class] [-i instance] [-r realm] [-O opcode] [-m <message...>]\n",
 	      "Behaves like zwrite but uses encryption.  Not for use with\n"
 	      "personal messages\n"),
@@ -148,5 +148,5 @@
 	      "allow editing.\n\n"
 	      "If 'sender' is specified, reply to the sender.\n\n"
-	      "If 'all' or no args are specified, reply publically to the\n"
+	      "If 'all' or no args are specified, reply publicly to the\n"
 	      "same class/instance for non-personal messages and to the\n"
 	      "sender for personal messages.\n\n"
@@ -266,5 +266,5 @@
 	      "zpunt <class> <instance> [recipient]\n"
 	      "zpunt <instance>",
-	      "The zpunt command will supress message to the specified\n"
+	      "The zpunt command will suppress messages to the specified\n"
 	      "zephyr triplet.  In the second usage messages are suppressed\n"
 	      "for class MESSAGE and the named instance.\n\n"
@@ -283,5 +283,5 @@
 	      "punt <filter-text>",
 	      "punt <filter-text (multiple words)>\n"
-	      "The punt command will supress message to the specified\n"
+	      "The punt command will suppress messages to the specified\n"
 	      "filter\n\n"
 	      "SEE ALSO:  unpunt, zpunt, show zpunts\n"),
@@ -586,5 +586,5 @@
               "name style after the -s argument.\n"
 	      "\n"
-	      "The other usages listed above are abbreivated forms that simply set\n"
+	      "The other usages listed above are abbreviated forms that simply set\n"
 	      "the filter of the current view. The -d option allows you to write a\n"
               "filter expression that will be dynamically created by owl and then\n"
@@ -594,5 +594,5 @@
   OWLCMD_ARGS("smartnarrow", owl_command_smartnarrow, OWL_CTX_INTERACTIVE,
 	      "view only messages similar to the current message",
-	      "smartnarrow [-i | --instance]  [-r | --relatde]",
+	      "smartnarrow [-i | --instance]  [-r | --related]",
 	      "If the curmsg is a personal message narrow\n"
 	      "   to the conversation with that user.\n"
@@ -614,5 +614,5 @@
 	      "   message, the filter is to that instance.\n"
 	      "If the curmsg is a class message, the filter is that class.\n"
-	      "If the curmsg is a class message and '-i' is specied\n"
+	      "If the curmsg is a class message and '-i' is specified\n"
 	      "    the filter is to that class and instance.\n"),
 
@@ -674,5 +674,5 @@
 	      "for formatting messages.\n\n"
 	      "Show variables will list the names of all variables.\n\n"
-	      "Show errors will show a list of errors ecountered by Owl.\n\n"
+	      "Show errors will show a list of errors encountered by Owl.\n\n"
 	      "SEE ALSO: filter, view, alias, bindkey, help\n"),
   
@@ -743,5 +743,5 @@
 	      "set the search highlight string without searching",
 	      "setsearch <string>",
-	      "The setsearch command highlights all occurences of its\n"
+	      "The setsearch command highlights all occurrences of its\n"
           "argument and makes it the default argument for future\n"
           "search commands, but does not move the cursor.  With\n"
@@ -1371,5 +1371,4 @@
 {
   owl_function_full_redisplay();
-  owl_global_set_needrefresh(&g);
 }
 
@@ -1387,6 +1386,4 @@
 {
   owl_global_set_rightshift(&g, shift);
-  owl_mainwin_redisplay(owl_global_get_mainwin(&g));
-  owl_global_set_needrefresh(&g);
 }
 
@@ -1914,5 +1911,5 @@
 char *owl_command_zwrite(int argc, const char *const *argv, const char *buff)
 {
-  owl_zwrite z;
+  owl_zwrite *z;
 
   if (!owl_global_is_havezephyr(&g)) {
@@ -1921,16 +1918,21 @@
   }
   /* check for a zwrite -m */
-  owl_zwrite_create_from_line(&z, buff);
-  if (owl_zwrite_is_message_set(&z)) {
-    owl_function_zwrite(buff, NULL);
-    owl_zwrite_cleanup(&z);
-    return (NULL);
-  }
-  owl_zwrite_cleanup(&z);
+  z = owl_zwrite_new(buff);
+  if (!z) {
+    owl_function_error("Error in zwrite arguments");
+    return NULL;
+  }
+
+  if (owl_zwrite_is_message_set(z)) {
+    owl_function_zwrite(z, NULL);
+    owl_zwrite_delete(z);
+    return NULL;
+  }
 
   if (argc < 2) {
+    owl_zwrite_delete(z);
     owl_function_makemsg("Not enough arguments to the zwrite command.");
   } else {
-    owl_function_zwrite_setup(buff);
+    owl_function_zwrite_setup(z);
   }
   return(NULL);
@@ -2022,29 +2024,4 @@
 }
 
-char *owl_command_zcrypt(int argc, const char *const *argv, const char *buff)
-{
-  owl_zwrite z;
-
-  if (!owl_global_is_havezephyr(&g)) {
-    owl_function_makemsg("Zephyr is not available");
-    return(NULL);
-  }
-  /* check for a zcrypt -m */
-  owl_zwrite_create_from_line(&z, buff);
-  if (owl_zwrite_is_message_set(&z)) {
-    owl_function_zcrypt(buff, NULL);
-    owl_zwrite_cleanup(&z);
-    return (NULL);
-  }
-  owl_zwrite_cleanup(&z);
-
-  if (argc < 2) {
-    owl_function_makemsg("Not enough arguments to the zcrypt command.");
-  } else {
-    owl_function_zwrite_setup(buff);
-  }
-  return(NULL);
-}
-
 char *owl_command_reply(int argc, const char *const *argv, const char *buff)
 {
@@ -2184,8 +2161,4 @@
       argc--;
       argv++;
-    } else if (!strcmp(argv[0], "-r")) {
-      const char *foo;
-      foo=owl_function_create_negative_filter(owl_view_get_filtname(owl_global_get_current_view(&g)));
-      owl_function_change_currentview_filter(foo);
     } else if (!strcmp(argv[0], "-s")) {
       if (argc<2) {
@@ -2717,5 +2690,4 @@
   }
 
-  owl_global_set_needrefresh(&g);
   owl_global_pop_context(&g);
 
@@ -2738,6 +2710,4 @@
     owl_editwin_clear(e);
     owl_editwin_insert_string(e, ptr);
-    owl_editwin_redisplay(e);
-    owl_global_set_needrefresh(&g);
   } else {
     owl_function_beep();
@@ -2755,6 +2725,4 @@
     owl_editwin_clear(e);
     owl_editwin_insert_string(e, ptr);
-    owl_editwin_redisplay(e);
-    owl_global_set_needrefresh(&g);
   } else {
     owl_function_beep();
@@ -2766,6 +2734,4 @@
   buff = skiptokens(buff, 1);
   owl_editwin_insert_string(e, buff);
-  owl_editwin_redisplay(e);
-  owl_global_set_needrefresh(&g);  
   return NULL;
 }
@@ -2782,5 +2748,4 @@
   owl_global_set_typwin_inactive(&g);
   owl_global_pop_context(&g);
-  owl_global_set_needrefresh(&g);
 
   owl_editwin_do_callback(e);
@@ -2804,7 +2769,6 @@
 void owl_command_popless_quit(owl_viewwin *vw)
 {
+  owl_viewwin_cleanup(vw);
   owl_popwin_close(owl_global_get_popwin(&g));
   owl_global_pop_context(&g);
-  owl_viewwin_cleanup(vw);
-  owl_global_set_needrefresh(&g);
-}
+}
Index: configure.ac
===================================================================
--- configure.ac	(revision 263320f0be9d4016164837d2de7310b687a6f076)
+++ configure.ac	(revision 04af908ca44749f771ae778a21315327b6d24135)
@@ -35,17 +35,24 @@
   [with_zephyr=check])
 
+AC_ARG_WITH([krb4],
+  AS_HELP_STRING([--with-krb4],
+                 [Build with kerberos IV]))
+
 AS_IF([test "x$with_zephyr" != xno],
-  [AC_MSG_CHECKING([for Kerberos IV])
-   AS_IF([krb5-config krb4 --libs >/dev/null 2>&1],
-     [AC_MSG_RESULT([yes])
-      AC_DEFINE([HAVE_KERBEROS_IV], [1], [Define if you have kerberos IV])
-      CFLAGS="${CFLAGS} `krb5-config krb4 --cflags`"
-      LIBS="${LIBS} `krb5-config krb4 --libs`"
-     ],
-     [AC_MSG_RESULT([no])
-      PKG_CHECK_MODULES([LIBCRYPTO], [libcrypto])
-      CFLAGS="${CFLAGS} ${LIBCRYPTO_CFLAGS}"
-      LIBS="${LIBS} ${LIBCRYPTO_LIBS}"
-     ])
+  [AS_IF([test "x$with_krb4" != "xno"],
+   [AC_MSG_CHECKING([for Kerberos IV])
+    AS_IF([krb5-config krb4 --libs >/dev/null 2>&1],
+      [AC_MSG_RESULT([yes])
+       AC_DEFINE([HAVE_KERBEROS_IV], [1], [Define if you have kerberos IV])
+       CFLAGS="${CFLAGS} `krb5-config krb4 --cflags`"
+       LIBS="${LIBS} `krb5-config krb4 --libs`"
+      ],
+      [AC_MSG_RESULT([no])
+       AS_IF([test "x$with_krb4" = "xyes"],
+             [AC_MSG_ERROR([Kerberos IV requested but not found])])
+       PKG_CHECK_MODULES([LIBCRYPTO], [libcrypto])
+       CFLAGS="${CFLAGS} ${LIBCRYPTO_CFLAGS}"
+       LIBS="${LIBS} ${LIBCRYPTO_LIBS}"
+     ])])
    AC_CHECK_LIB([zephyr], [ZGetSender],
    [LIBS="$LIBS -lzephyr"
@@ -108,5 +115,5 @@
 
 dnl Add CFLAGS and LDFLAGS for glib-2.0
-PKG_CHECK_MODULES(GLIB,glib-2.0)
+PKG_CHECK_MODULES(GLIB,[glib-2.0 gobject-2.0])
 
 AC_MSG_NOTICE([Adding glib-2.0 CFLAGS ${GLIB_CFLAGS}])
@@ -131,4 +138,6 @@
                                  test "$HAVE_DES_ECB_ENCRYPT"])
 
+CFLAGS="$CFLAGS -D_XOPEN_SOURCE"
+
 AC_SUBST([LIBFAIM_CFLAGS])
 
Index: context.c
===================================================================
--- context.c	(revision 2a17b635c6895121650446d2529f46d7e291a486)
+++ context.c	(revision 07b59ea301a55bf3db1724aa9571e7048fe89857)
@@ -9,4 +9,5 @@
   ctx->mode = OWL_CTX_STARTUP;
   ctx->data = NULL;
+  ctx->cursor = NULL;
   return 0;
 }
Index: doc/code.txt
===================================================================
--- doc/code.txt	(revision 12868933af20e72110e2fb1f11441b93f43f6f4c)
+++ doc/code.txt	(revision 44cc9abbc2c15abaf4cd5fe0d1fc62b0e0936913)
@@ -86,5 +86,5 @@
 	     Meta.  At any one time, there is exactly one active
 	     keymap which determines where keybindings are looked for
-	     (along with its submaps).
+	     (along with its parents).
 
 list:	     Simple list abstraction.  (Uses realloc to resize the list.)
Index: doc/owl-window.txt
===================================================================
--- doc/owl-window.txt	(revision 95d54e6fd988cf92c13614431dc8504cd5264e95)
+++ doc/owl-window.txt	(revision 95d54e6fd988cf92c13614431dc8504cd5264e95)
@@ -0,0 +1,191 @@
+				==========
+				owl_window
+				==========
+
+owl_window is intended to be a wrapper over ncurses WINDOW and PANEL
+objects to abstract away much of the nastiness that is ncurses. It
+provides redraw scheduling, and sane window moving and resizing.
+
+-------------------
+Hierarchy and types
+-------------------
+
+owl_window creates a hierarchy of window objects. The hierarchy
+currently uses ncurses' built-in window hierarchy system with
+subwins. This was easier to implement but has some caveats, detailed
+later.
+
+At the top level, we have the screen window which represents the
+actual screen. The screen window is not usually interacted with
+directly, but rather it is a handle to interface with the terminal as
+if it were a normal window.
+
+Underneath the screen, we have top-level windows, or panels. Panels
+are normal WINDOWs (newwin) connected to a PANEL (from
+libpanel). Panels may overlap freely and maintain a stacking
+order. (For now, this stacking order is not exposed except for a
+mechanism to move a panel to the top. This is not difficult to
+otherwise correct otherwise.)
+
+Under each panel is a tree of subwins. These are backed by ncurses
+subwins. Because ncurses subwins simply share their parent windows'
+buffer, we cannot provide as many nice guarantees about
+ordering. Sibling subwins may not overlap, and parents must
+(sometimes) take children position into account. More on this
+later. This model is sufficient for BarnOwl's current purposes. Should
+we need to, this may later be reworked. (Specifically, we'd want to
+back everything by pads and build a compositing window manager.)
+
+Each of these three types is temporarily stuffed into one type. We can
+later do subtyping of sorts, but that will require more heavy use of
+GObject.
+
+As an example, here is the graph used by BarnOwl's current interface:
+
+      	       	       	+==========+
+     	      	      	|| screen ||
+      	       	       	+==========+
+                          /      \
+                +-----------+   +--------+
+                | mainpanel |   | popwin |
+                +-----------+   +--------+
+               /   /    |    \          \
+         recwin sepwin msgwin typwin   viewwin
+
+
+A window may be unlinked from its parent with a call to
+owl_window_unlink. From then on, the object is still accessible, but
+it will not do anything. If desired, we may add the ability to relink
+a window in future. This behavior allows us to safely hold references
+to windows, even after they have been "destroyed".
+
+----------
+Visibility
+----------
+
+Each window maintains state for whether or not the user has requested
+it be visible. A user calls owl_window_show to flip the 'shown' bit on
+a window and owl_window_show_all to do so recursively. Likewise,
+owl_window_hide disables this bit. If a window and all its parents are
+shown, then the user has requested this window be visible.
+
+We say a window is realized if it has a corresponding on-screen
+window. A window will only be realized if it is requested to be
+shown. Furthermore, the window's parent must also be realized (unless
+it is the screen). If a window is failed to be created for any reason
+(most notably if its dimensions are zero), owl_window will cope and
+consider it unrealized.
+
+This realized/unrealized state fixes two nuisances with the old code:
+
+     First, owl_window can safely manage all NULL windows. Interacting
+     code needn't check to avoid segfaults; the owl_window is never
+     NULL, and code requesting the WINDOW will never be called when
+     NULL.
+
+     Second, we have a consistent handle to a logical window, despite
+     changes in the physical window. This is important for resizing
+     windows. It is difficult to safely resize windows in ncurses. It
+     is usually far easier to destroy everything and recreate it.
+
+Note that this means owl_window will intentionally never expose the
+underlying WINDOW except during appropriate events.
+
+--------
+Resizing
+--------
+
+Windows may be moved and resized with owl_window_move,
+owl_window_resize, and owl_window_set_position. Internally, resizing
+is very simple. We unrealize the window, set new position, and then
+realize it at the new location. When a window changes size, it emits a
+"resized" signal which windows may react to. It may actually possible
+to optimize moves, but this has a slight nuisance with incorrect
+begy/begx values which cursors currently rely on.
+
+It is intended that top-level windows connect to this signal to change
+themselves, while windows containing subwins connect to their own
+signals to relayout their subwins. This is because top-level windows
+may be sized independently, while sibling subwins should not
+overlap. The signals are implemented currently with GObject signals.
+
+---------
+Redrawing
+---------
+
+Currently, users of widgets in BarnOwl must remember to call
+owl_foo_redisplay when a widget needs to be redrawn. This is quite
+annoying and an implementation detail that functions like
+owl_editwin_insert_string should take care of. To allow widget
+implementations to redraw themselves without calling an expensive
+redisplay many times, owl_window_dirty flags a window as "dirty". The
+framework will promise to redraw that window before the next doupdate,
+while repeated owl_window_dirty calls remain cheap.
+
+Windows currently redraw with a "redraw" GObject signal. This and the
+resize signal, is somewhat awkward for the editwin which attaches to
+and releases windows, as we must remember signal ids. However, if we
+make widget objects (owl_editwin, owl_mainwin, etc.) also GObjects,
+these signals will automatically detach. We may also consider a
+different mechanism than continuously attaching/detaching
+things. That's kinda weird anyway.
+
+(We may want to replace it with a normal callback at some
+point. Mostly using the GObject signals to play with them, and because
+we'd likely get some form of automatic binding generation.)
+
+----------------------
+Known issues and notes
+----------------------
+
+- owl_window does not export the underlying WINDOW*. This is
+  intentional. To safely resize windows, owl_window reserves the right
+  to destroy/recreate the window whenever it feels like. We can add
+  "window-realized"/"window-unrealized" signals if people really want
+  though.
+
+- This is currently using GObject. This buys us a lot of niceness, but
+  it is adding a new library with full-blown (and somewhat
+  overengineered) object system. It's very useful for prototyping, but
+  if people want, it can be replaced. I think it's actually
+  worthwhile, as most of the overengineering is explicitly designed
+  for language bindings, something we could be better at.
+
+- There is this really sketchy "three types in one" thing going on
+  with the screen, panels, and subwins. I'd like to refactor that into
+  some sort of subtyping, but that would ideally involve even more use
+  of GObject.
+
+- owl_mainwin is not very well ported and the windows do not have a
+  very consistent implementation. This is a known problem that will be
+  addressed in the next iteration. The current ports were done partly
+  as experiments for conventions and mostly to get something working
+  as soon as possible. Among things that should change is for the
+  widgets to all use new/delete/pointers instead of
+  init/cleanup/embed-in-struct.
+
+- The editwin and a few others are strange and keep track of signal
+  ids. This is somewhat a side effect of us not using GObject
+  everywhere; the signals can automatically disconnect in the right
+  contexts if we do.
+
+- The sepbar depends on a value computed while the mainwin is drawn,
+  so we currently ensure the windows are created in the right order
+  for the repaints to occur correctly. This is rather poor and should
+  be refactored later.
+
+- The code fairly routinely does casts to add extra throw-away
+  parameters to functions. Most notably, casting functions of type
+
+      void do_something(void *obj)
+
+  to something of type
+
+      void do_something(void *obj, void *user_data)
+
+  (the latter is a GFunc) with the expectation that the user_data
+  argument is discarded. While calling with this cast is undefined by
+  the standard and depends on calling convention, glib uses it
+  internally /everywhere/ and much of glib and gobject API heavily
+  depends on it. As BarnOwl already depends on glib, we implicitly
+  assume this cast works.
Index: editwin.c
===================================================================
--- editwin.c	(revision 9d7a7205d6c9cbea0e8999e4c350440f8ef5fef8)
+++ editwin.c	(revision 3f11c00e8b62c8bf4c5a6b6f51f8f3e12cfd8fcf)
@@ -28,5 +28,7 @@
   int cursorx;
   int winlines, wincols, fillcol, wrapcol;
-  WINDOW *curswin;
+  owl_window *win;
+  gulong repaint_id;
+  gulong resized_id;
   int style;
   int lock;
@@ -40,4 +42,6 @@
 };
 
+static void oe_set_window(owl_editwin *e, owl_window *w, int winlines, int wincols);
+static void oe_redraw(owl_window *win, WINDOW *curswin, void *user_data);
 static void oe_reframe(owl_editwin *e);
 static void oe_save_excursion(owl_editwin *e, oe_excursion *x);
@@ -58,4 +62,6 @@
 static char *oe_chunk(owl_editwin *e, int start, int end);
 static void oe_destroy_cbdata(owl_editwin *e);
+static void oe_dirty(owl_editwin *e);
+static void oe_window_resized(owl_window *w, owl_editwin *e);
 
 #define INCR 4096
@@ -73,4 +79,8 @@
 void owl_editwin_delete(owl_editwin *e)
 {
+  if (e->win) {
+    g_signal_handler_disconnect(e->win, e->repaint_id);
+    g_signal_handler_disconnect(e->win, e->resized_id);
+  }
   owl_free(e->buff);
   owl_free(e->killbuf);
@@ -91,4 +101,5 @@
   }
   e->index = index;
+  oe_dirty(e);
 }
 
@@ -105,5 +116,4 @@
 
 static void _owl_editwin_init(owl_editwin *e,
-                              WINDOW *win,
                               int winlines,
                               int wincols,
@@ -130,23 +140,27 @@
     e->style=OWL_EDITWIN_STYLE_MULTILINE;
   }
-  owl_editwin_set_curswin(e, win, winlines, wincols);
   e->lock=0;
   e->dotsend=0;
   e->echochar='\0';
-
-  if (win) werase(win);
-}
-
-owl_editwin *owl_editwin_new(WINDOW *win, int winlines, int wincols, int style, owl_history *hist)
+}
+
+owl_editwin *owl_editwin_new(owl_window *win, int winlines, int wincols, int style, owl_history *hist)
 {
   owl_editwin *e = owl_editwin_allocate();
 
-  _owl_editwin_init(e, win, winlines, wincols, style, hist);
+  _owl_editwin_init(e, winlines, wincols, style, hist);
+  oe_set_window(e, win, winlines, wincols);
   return e;
 }
 
-void owl_editwin_set_curswin(owl_editwin *e, WINDOW *w, int winlines, int wincols)
-{
-  e->curswin=w;
+static void oe_window_resized(owl_window *w, owl_editwin *e)
+{
+  /* update the sizes */
+  owl_window_get_position(w, &e->winlines, &e->wincols, NULL, NULL);
+}
+
+static void oe_set_window(owl_editwin *e, owl_window *w, int winlines, int wincols)
+{
+  e->win=w;
   e->winlines=winlines;
   e->wincols=wincols;
@@ -156,4 +170,9 @@
   else
     e->wrapcol = 0;
+  if (e->win) {
+    e->repaint_id = g_signal_connect(w, "redraw", G_CALLBACK(oe_redraw), e);
+    e->resized_id = g_signal_connect(w, "resized", G_CALLBACK(oe_window_resized), e);
+    owl_window_dirty(e->win);
+  }
 }
 
@@ -165,9 +184,5 @@
 {
   e->echochar=ch;
-}
-
-WINDOW *owl_editwin_get_curswin(owl_editwin *e)
-{
-  return(e->curswin);
+  oe_dirty(e);
 }
 
@@ -239,5 +254,5 @@
   e->lock=e->bufflen;
   oe_set_index(e, e->lock);
-  owl_editwin_redisplay(e);
+  oe_dirty(e);
 }
 
@@ -265,5 +280,5 @@
 
   owl_free(e->buff);
-  _owl_editwin_init(e, e->curswin, e->winlines, e->wincols, e->style, e->hist);
+  _owl_editwin_init(e, e->winlines, e->wincols, e->style, e->hist);
 
   if (lock > 0) {
@@ -286,4 +301,5 @@
 {
   e->topindex = -1;
+  oe_dirty(e);
 }
 
@@ -467,28 +483,30 @@
 
   oe_restore_excursion(e, &x);
-}
-
-static void oe_addnec(owl_editwin *e, int count)
+  oe_dirty(e);
+}
+
+static void oe_addnec(owl_editwin *e, WINDOW *curswin, int count)
 {
   int i;
 
   for (i = 0; i < count; i++)
-    waddch(e->curswin, e->echochar);
-}
-
-static void oe_mvaddnec(owl_editwin *e, int y, int x, int count)
-{
-  wmove(e->curswin, y, x);
-  oe_addnec(e, count);
+    waddch(curswin, e->echochar);
+}
+
+static void oe_mvaddnec(owl_editwin *e, WINDOW *curswin, int y, int x, int count)
+{
+  wmove(curswin, y, x);
+  oe_addnec(e, curswin, count);
 }
 
 /* regenerate the text on the curses window */
-void owl_editwin_redisplay(owl_editwin *e)
+static void oe_redraw(owl_window *win, WINDOW *curswin, void *user_data)
 {
   int x = -1, y = -1, t, hard;
   int line, index, lineindex, times = 0;
+  owl_editwin *e = user_data;
 
   do {
-    werase(e->curswin);
+    werase(curswin);
 
     if (e->topindex == -1 || e->index < e->topindex)
@@ -505,22 +523,22 @@
       if (index - lineindex) {
 	if (!e->echochar)
-	  mvwaddnstr(e->curswin, line, 0,
+	  mvwaddnstr(curswin, line, 0,
 		     e->buff + lineindex,
 		     index - lineindex);
 	else {
 	  if(lineindex < e->lock) {
-	    mvwaddnstr(e->curswin, line, 0,
+	    mvwaddnstr(curswin, line, 0,
 		       e->buff + lineindex,
 		       MIN(index - lineindex,
 			   e->lock - lineindex));
 	    if (e->lock < index)
-	      oe_addnec(e,
+	      oe_addnec(e, curswin,
 			oe_region_width(e, e->lock, index,
 					oe_region_width(e, lineindex, e->lock, 0)));
 	  } else
-	    oe_mvaddnec(e, line, 0, oe_region_width(e, lineindex, index, 0));
+	    oe_mvaddnec(e, curswin, line, 0, oe_region_width(e, lineindex, index, 0));
 	}
         if (!hard)
-          waddch(e->curswin, '\\');
+          waddch(curswin, '\\');
       }
       line++;
@@ -531,5 +549,5 @@
   } while(x == -1 && times < 3);
 
-  wmove(e->curswin, y, x);
+  wmove(curswin, y, x);
   e->cursorx = x;
 }
@@ -624,4 +642,6 @@
   if (start <= e->topindex)
     owl_editwin_recenter(e);
+
+  oe_dirty(e);
 
   return change;
@@ -857,4 +877,5 @@
 
   e->goal_column = goal_column;
+  oe_dirty(e);
 
   return distance;
@@ -1211,5 +1232,4 @@
     return;
   }
-  owl_editwin_redisplay(e);
 }
 
@@ -1346,4 +1366,9 @@
 }
 
+static void oe_dirty(owl_editwin *e)
+{
+  if (e->win) owl_window_dirty(e->win);
+}
+
 
 
Index: functions.c
===================================================================
--- functions.c	(revision d12a8c71f5d91653b1bffd9fa566448eb52b5d43)
+++ functions.c	(revision d12a8c71f5d91653b1bffd9fa566448eb52b5d43)
@@ -216,5 +216,4 @@
   /* redisplay etc. */
   owl_mainwin_redisplay(owl_global_get_mainwin(&g));
-  owl_global_set_needrefresh(&g);
 }
 
@@ -223,17 +222,11 @@
  * that.
  */
-owl_message *owl_function_make_outgoing_zephyr(const char *body, const char *zwriteline, const char *zsig)
+owl_message *owl_function_make_outgoing_zephyr(const owl_zwrite *z)
 {
   owl_message *m;
-  owl_zwrite zw;
-
-  owl_zwrite_create_from_line(&zw, zwriteline);
-  owl_zwrite_set_zsig(&zw, zsig);
 
   /* create the message */
   m=owl_malloc(sizeof(owl_message));
-  
-  owl_message_create_from_zwrite(m, &zw, body);
-  owl_zwrite_cleanup(&zw);
+  owl_message_create_from_zwrite(m, z, owl_zwrite_get_message(z));
 
   return(m);
@@ -293,8 +286,8 @@
   owl_editwin_set_cbdata(e, data, cleanup);
   owl_editwin_set_callback(e, callback);
-  owl_global_push_context(&g, OWL_CTX_EDITMULTI, e, "editmulti");
-}
-
-static void owl_function_write_setup(const char *line, const char *noun, void (*callback)(owl_editwin *))
+  owl_global_push_context(&g, OWL_CTX_EDITMULTI, e, "editmulti", owl_global_get_typwin_window(&g));
+}
+
+static void owl_function_write_setup(const char *noun)
 {
 
@@ -307,46 +300,41 @@
 			 "End with a dot on a line by itself.  ^C will quit.",
 			 noun);
-
-  owl_function_start_edit_win(line, callback,
+}
+
+void owl_function_zwrite_setup(owl_zwrite *z)
+{
+  /* send a ping if necessary */
+  if (owl_global_is_txping(&g)) {
+    owl_zwrite_send_ping(z);
+  }
+
+
+  owl_function_write_setup("zephyr");
+  owl_function_start_edit_win(z->zwriteline,
+                              &owl_callback_zwrite,
+                              z, (void(*)(void*))owl_zwrite_delete);
+}
+
+void owl_function_aimwrite_setup(const char *line)
+{
+  owl_function_write_setup("message");
+  owl_function_start_edit_win(line,
+                              &owl_callback_aimwrite,
                               owl_strdup(line),
                               owl_free);
-}
-
-void owl_function_zwrite_setup(const char *line)
-{
-  owl_zwrite z;
-  int ret;
-
-  /* check the arguments */
-  ret=owl_zwrite_create_from_line(&z, line);
-  if (ret) {
-    owl_function_error("Error in zwrite arguments");
-    owl_zwrite_cleanup(&z);
-    return;
-  }
-
-  /* send a ping if necessary */
-  if (owl_global_is_txping(&g)) {
-    owl_zwrite_send_ping(&z);
-  }
-  owl_zwrite_cleanup(&z);
-
-  owl_function_write_setup(line, "zephyr", &owl_callback_zwrite);
-}
-
-void owl_function_aimwrite_setup(const char *line)
-{
-  owl_function_write_setup(line, "message", &owl_callback_aimwrite);
+
 }
 
 void owl_function_loopwrite_setup(void)
 {
-  owl_function_write_setup("loopwrite", "message", owl_callback_loopwrite);
+  owl_function_write_setup("message");
+  owl_function_start_edit_win("loopwrite",
+                              &owl_callback_loopwrite,
+                              "loopwrite", NULL);
 }
 
 void owl_callback_zwrite(owl_editwin *e) {
-  char *command = owl_editwin_get_cbdata(e);
-  owl_function_zwrite(command,
-                      owl_editwin_get_text(e));
+  owl_zwrite *z = owl_editwin_get_cbdata(e);
+  owl_function_zwrite(z, owl_editwin_get_text(e));
 }
 
@@ -354,29 +342,25 @@
  * the message is expected to be set from the zwrite line itself
  */
-void owl_function_zwrite(const char *line, const char *msg)
-{
-  owl_zwrite z;
-  const char *mymsg;
+void owl_function_zwrite(owl_zwrite *z, const char *msg)
+{
   owl_message *m;
 
-  if(!strncmp(line, "zcrypt", strlen("zcrypt"))) {
-    owl_function_zcrypt(line, msg);
+  if(strcmp(z->cmd, "zcrypt") == 0) {
+    owl_function_zcrypt(z, msg);
     return;
   }
 
   /* create the zwrite and send the message */
-  owl_zwrite_create_from_line(&z, line);
-  owl_zwrite_populate_zsig(&z);
+  owl_zwrite_populate_zsig(z);
   if (msg) {
-    owl_zwrite_set_message(&z, msg);
-  }
-  owl_zwrite_send_message(&z);
+    owl_zwrite_set_message(z, msg);
+  }
+  owl_zwrite_send_message(z);
   owl_function_makemsg("Waiting for ack...");
 
   /* If it's personal */
-  if (owl_zwrite_is_personal(&z)) {
+  if (owl_zwrite_is_personal(z)) {
     /* create the outgoing message */
-    mymsg=owl_zwrite_get_message(&z);
-    m=owl_function_make_outgoing_zephyr(mymsg, line, owl_zwrite_get_zsig(&z));
+    m=owl_function_make_outgoing_zephyr(z);
 
     if (m) {
@@ -386,7 +370,4 @@
     }
   }
-
-  /* free the zwrite */
-  owl_zwrite_cleanup(&z);
 }
 
@@ -394,8 +375,6 @@
  * the message is expected to be set from the zwrite line itself
  */
-void owl_function_zcrypt(const char *line, const char *msg)
-{
-  owl_zwrite z;
-  const char *mymsg;
+void owl_function_zcrypt(owl_zwrite *z, const char *msg)
+{
   char *cryptmsg;
   owl_message *m;
@@ -403,22 +382,21 @@
   char *zcrypt;
   int rv, status;
+  char *old_msg;
 
   /* create the zwrite and send the message */
-  owl_zwrite_create_from_line(&z, line);
-  owl_zwrite_populate_zsig(&z);
+  owl_zwrite_populate_zsig(z);
   if (msg) {
-    owl_zwrite_set_message(&z, msg);
-  }
-
-  mymsg=owl_zwrite_get_message(&z);
+    owl_zwrite_set_message(z, msg);
+  }
+  old_msg = owl_strdup(owl_zwrite_get_message(z));
 
   zcrypt = owl_sprintf("%s/zcrypt", owl_get_bindir());
   argv[0] = "zcrypt";
   argv[1] = "-E";
-  argv[2] = "-c"; argv[3] = owl_zwrite_get_class(&z);
-  argv[4] = "-i"; argv[5] = owl_zwrite_get_instance(&z);
+  argv[2] = "-c"; argv[3] = owl_zwrite_get_class(z);
+  argv[4] = "-i"; argv[5] = owl_zwrite_get_instance(z);
   argv[6] = NULL;
 
-  rv = call_filter(zcrypt, argv, mymsg, &cryptmsg, &status);
+  rv = call_filter(zcrypt, argv, owl_zwrite_get_message(z), &cryptmsg, &status);
 
   owl_free(zcrypt);
@@ -426,21 +404,21 @@
   if (rv || status) {
     if(cryptmsg) owl_free(cryptmsg);
+    owl_free(old_msg);
     owl_function_error("Error in zcrypt, possibly no key found.  Message not sent.");
     owl_function_beep();
-    owl_zwrite_cleanup(&z);
     return;
   }
 
-  owl_zwrite_set_message(&z, cryptmsg);
-  owl_zwrite_set_opcode(&z, "crypt");
-    
-  owl_zwrite_send_message(&z);
+  owl_zwrite_set_message_raw(z, cryptmsg);
+  owl_zwrite_set_opcode(z, "crypt");
+
+  owl_zwrite_send_message(z);
   owl_function_makemsg("Waiting for ack...");
 
   /* If it's personal */
-  if (owl_zwrite_is_personal(&z)) {
-    /* create the outgoing message */
-    mymsg=owl_zwrite_get_message(&z);
-    m=owl_function_make_outgoing_zephyr(mymsg, line, owl_zwrite_get_zsig(&z));
+  if (owl_zwrite_is_personal(z)) {
+    /* Create the outgoing message. Restore the un-crypted message for display. */
+    owl_zwrite_set_message_raw(z, old_msg);
+    m=owl_function_make_outgoing_zephyr(z);
     if (m) {
       owl_global_messagequeue_addmsg(&g, m);
@@ -452,5 +430,4 @@
   /* free the zwrite */
   owl_free(cryptmsg);
-  owl_zwrite_cleanup(&z);
 }
 
@@ -808,6 +785,4 @@
 {
   owl_global_set_rightshift(&g, owl_global_get_rightshift(&g)+10);
-  owl_mainwin_redisplay(owl_global_get_mainwin(&g));
-  owl_global_set_needrefresh(&g);
 }
 
@@ -819,6 +794,4 @@
   if (shift > 0) {
     owl_global_set_rightshift(&g, MAX(shift - 10, 0));
-    owl_mainwin_redisplay(owl_global_get_mainwin(&g));
-    owl_global_set_needrefresh(&g);
   } else {
     owl_function_beep();
@@ -1223,5 +1196,4 @@
   if (owl_global_is_bell(&g)) {
     beep();
-    owl_global_set_needrefresh(&g); /* do we really need this? */
   }
 }
@@ -1252,25 +1224,14 @@
 }
 
-void owl_function_set_cursor(WINDOW *win)
-{
-  /* Be careful that this window is actually empty, otherwise panels get confused */
-  if (is_wintouched(win)) {
-    owl_function_debugmsg("Warning: owl_function_set_cursor called on dirty window");
-    update_panels();
-  }
-  wnoutrefresh(win);
+static void _dirty_everything(owl_window *w) {
+  if (!owl_window_is_realized(w))
+    return;
+  owl_window_dirty(w);
+  owl_window_children_foreach(w, (GFunc)_dirty_everything, NULL);
 }
 
 void owl_function_full_redisplay(void)
 {
-  touchwin(owl_global_get_curs_recwin(&g));
-  touchwin(owl_global_get_curs_sepwin(&g));
-  touchwin(owl_global_get_curs_typwin(&g));
-  touchwin(owl_global_get_curs_msgwin(&g));
-
-  sepbar("");
-  owl_function_makemsg("");
-
-  owl_global_set_needrefresh(&g);
+  _dirty_everything(owl_window_get_screen());
 }
 
@@ -1284,10 +1245,6 @@
 
   owl_popwin_up(pw);
-  owl_global_push_context(&g, OWL_CTX_POPLESS, v, "popless");
-  owl_viewwin_init_text(v, owl_popwin_get_curswin(pw),
-			owl_popwin_get_lines(pw), owl_popwin_get_cols(pw),
-			text);
-  owl_viewwin_redisplay(v);
-  owl_global_set_needrefresh(&g);
+  owl_global_push_context(&g, OWL_CTX_POPLESS, v, "popless", NULL);
+  owl_viewwin_init_text(v, owl_popwin_get_content(pw), text);
 }
 
@@ -1301,10 +1258,6 @@
 
   owl_popwin_up(pw);
-  owl_global_push_context(&g, OWL_CTX_POPLESS, v, "popless");
-  owl_viewwin_init_fmtext(v, owl_popwin_get_curswin(pw),
-		   owl_popwin_get_lines(pw), owl_popwin_get_cols(pw),
-		   fm);
-  owl_viewwin_redisplay(v);
-  owl_global_set_needrefresh(&g);
+  owl_global_push_context(&g, OWL_CTX_POPLESS, v, "popless", NULL);
+  owl_viewwin_init_fmtext(v, owl_popwin_get_content(pw), fm);
 }
 
@@ -1566,5 +1519,4 @@
   /* redisplay */
   owl_mainwin_redisplay(owl_global_get_mainwin(&g));
-  owl_global_set_needrefresh(&g);
 }
 
@@ -1572,5 +1524,5 @@
 {
   owl_global_set_typwin_lines(&g, newsize);
-  owl_global_set_relayout_pending(&g);
+  owl_mainpanel_layout_contents(&g.mainpanel);
 }
 
@@ -1686,5 +1638,4 @@
     }
     owl_mainwin_redisplay(owl_global_get_mainwin(&g));
-    owl_global_set_needrefresh(&g);
   } else {
     owl_function_error("No message with id %d: unable to mark for (un)delete",id);
@@ -1722,5 +1673,4 @@
   owl_mainwin_redisplay(owl_global_get_mainwin(&g));
   owl_function_makemsg("%i messages marked for deletion", count);
-  owl_global_set_needrefresh(&g);
 }
 
@@ -1927,10 +1877,8 @@
 
   owl_editwin_set_locktext(tw, "command: ");
-  owl_global_set_needrefresh(&g);
 
   owl_editwin_insert_string(tw, line);
-  owl_editwin_redisplay(tw);
-
-  owl_global_push_context(&g, OWL_CTX_EDITLINE, tw, "editline");
+
+  owl_global_push_context(&g, OWL_CTX_EDITLINE, tw, "editline", owl_global_get_typwin_window(&g));
   owl_editwin_set_callback(tw, owl_callback_command);
 }
@@ -1943,9 +1891,6 @@
 
   owl_editwin_set_locktext(tw, line);
-  owl_global_set_needrefresh(&g);
-
-  owl_editwin_redisplay(tw);
-
-  owl_global_push_context(&g, OWL_CTX_EDITRESPONSE, tw, "editresponse");
+
+  owl_global_push_context(&g, OWL_CTX_EDITRESPONSE, tw, "editresponse", owl_global_get_typwin_window(&g));
   return tw;
 }
@@ -1960,9 +1905,6 @@
 
   owl_editwin_set_locktext(tw, line);
-  owl_global_set_needrefresh(&g);
-
-  owl_editwin_redisplay(tw);
-
-  owl_global_push_context(&g, OWL_CTX_EDITRESPONSE, tw, "editresponse");
+
+  owl_global_push_context(&g, OWL_CTX_EDITRESPONSE, tw, "editresponse", owl_global_get_typwin_window(&g));
   return tw;
 }
@@ -1999,13 +1941,9 @@
     pclose(p);
     
-    if (type == OWL_OUTPUT_POPUP) {
-      owl_function_popless_text(out);
-    } else if (type == OWL_OUTPUT_RETURN) {
+    if (type == OWL_OUTPUT_RETURN) {
       owl_free(newbuff);
       return out;
     } else if (type == OWL_OUTPUT_ADMINMSG) {
       owl_function_adminmsg(buff, out);
-    } else {
-      owl_function_popless_text(out);
     }
     owl_free(out);
@@ -2040,6 +1978,4 @@
     } else if (type == OWL_OUTPUT_RETURN) {
       return perlout;
-    } else {
-      owl_function_popless_text(perlout);
     }
     owl_free(perlout);
@@ -2133,5 +2069,4 @@
     }
     owl_filter_set_fgcolor(f, owl_util_string_to_color(argv[3]));
-    owl_global_set_needrefresh(&g);
     owl_mainwin_redisplay(owl_global_get_mainwin(&g));
     return;
@@ -2148,5 +2083,4 @@
     }
     owl_filter_set_bgcolor(f, owl_util_string_to_color(argv[3]));
-    owl_global_set_needrefresh(&g);
     owl_mainwin_redisplay(owl_global_get_mainwin(&g));
     return;
@@ -2177,5 +2111,4 @@
     owl_function_change_currentview_filter(argv[1]);
   }
-  owl_global_set_needrefresh(&g);
   owl_mainwin_redisplay(owl_global_get_mainwin(&g));
 }
@@ -2505,4 +2438,32 @@
 
   owl_mainwin_redisplay(owl_global_get_mainwin(&g));  
+}
+
+static char *owl_function_smartfilter_cc(const owl_message *m) {
+  const char *ccs;
+  char *filtname;
+  char *text;
+  owl_filter *f;
+
+  ccs = owl_message_get_attribute_value(m, "zephyr_ccs");
+
+  filtname = owl_sprintf("conversation-%s", ccs);
+  owl_text_tr(filtname, ' ', '-');
+
+  if (owl_global_get_filter(&g, filtname)) {
+    return filtname;
+  }
+
+  text = owl_sprintf("type ^zephyr$ and filter personal and "
+                     "zephyr_ccs ^%s%s%s$",
+                     owl_getquoting(ccs), ccs, owl_getquoting(ccs));
+
+  f = owl_filter_new_fromstring(filtname, text);
+
+  owl_global_add_filter(&g, f);
+
+  owl_free(text);
+
+  return filtname;
 }
 
@@ -2560,4 +2521,8 @@
   if (owl_message_is_type_zephyr(m)) {
     if (owl_message_is_personal(m) || owl_message_is_loginout(m)) {
+      if (owl_message_get_attribute_value(m, "zephyr_ccs") != NULL) {
+        return owl_function_smartfilter_cc(m);
+      }
+
       if (owl_message_is_direction_in(m)) {
         zperson=short_zuser(owl_message_get_sender(m));
@@ -2689,5 +2654,4 @@
   owl_filter_set_fgcolor(f, owl_util_string_to_color(fgcolor));
   
-  owl_global_set_needrefresh(&g);
   owl_mainwin_redisplay(owl_global_get_mainwin(&g));
   return(0);
@@ -2853,5 +2817,5 @@
     km = owl_keyhandler_get_keymap(kh, kmname);
     owl_fmtext_append_bold(&fm, "\n\n----------------------------------------------------------------------------------------------------\n\n");
-    owl_keymap_get_details(km, &fm);    
+    owl_keymap_get_details(km, &fm, 0);
   }
   owl_fmtext_append_normal(&fm, "\n");
@@ -2879,5 +2843,5 @@
   km = owl_keyhandler_get_keymap(owl_global_get_keyhandler(&g), name);
   if (km) {
-    owl_keymap_get_details(km, &fm);
+    owl_keymap_get_details(km, &fm, 1);
   } else {
     owl_fmtext_append_normal(&fm, "No such keymap...\n");
@@ -3403,16 +3367,12 @@
 {
   va_list ap;
-  char buff[2048];
-
-  if (!owl_global_get_curs_msgwin(&g)) return;
+  char *str;
 
   va_start(ap, fmt);
-  werase(owl_global_get_curs_msgwin(&g));
-  
-  vsnprintf(buff, 2048, fmt, ap);
-  owl_function_debugmsg("makemsg: %s", buff);
-  waddstr(owl_global_get_curs_msgwin(&g), buff);  
-  owl_global_set_needrefresh(&g);
+  str = g_strdup_vprintf(fmt, ap);
   va_end(ap);
+
+  owl_function_debugmsg("makemsg: %s", str);
+  owl_msgwin_set_text(&g.msgwin, str);
 }
 
Index: global.c
===================================================================
--- global.c	(revision 26ad412433e18b39230531a2a518a03bffc1d3d4)
+++ global.c	(revision 26ad412433e18b39230531a2a518a03bffc1d3d4)
@@ -13,4 +13,6 @@
 #endif
 
+static void _owl_global_init_windows(owl_global *g);
+
 void owl_global_init(owl_global *g) {
   struct hostent *hent;
@@ -18,6 +20,5 @@
   char *cd;
 
-  g->malloced=0;
-  g->freed=0;
+  g_type_init();
 
   gethostname(hostname, MAXHOSTNAMELEN);
@@ -29,17 +30,19 @@
   }
 
+  g->lines=LINES;
+  g->cols=COLS;
+  /* We shouldn't need this if we initialize lines and cols before the first
+   * owl_window_get_screen, but to be safe, we synchronize. */
+  owl_window_resize(owl_window_get_screen(), g->lines, g->cols);
+
   g->context_stack = NULL;
-  owl_global_push_context(g, OWL_CTX_STARTUP, NULL, NULL);
+  owl_global_push_context(g, OWL_CTX_STARTUP, NULL, NULL, NULL);
 
   g->curmsg=0;
   g->topmsg=0;
   g->markedmsgid=-1;
-  g->needrefresh=1;
   g->startupargs=NULL;
 
   owl_variable_dict_setup(&(g->vars));
-
-  g->lines=LINES;
-  g->cols=COLS;
 
   g->rightshift=0;
@@ -53,9 +56,8 @@
   g->filterlist = NULL;
   owl_list_create(&(g->puntlist));
-  owl_list_create(&(g->messagequeue));
+  g->messagequeue = g_queue_new();
   owl_dict_create(&(g->styledict));
   g->curmsg_vert_offset=0;
   g->resizepending=0;
-  g->relayoutpending = 0;
   g->direction=OWL_DIRECTION_DOWNWARDS;
   g->zaway=0;
@@ -79,6 +81,4 @@
   g->nextmsgid=0;
 
-  _owl_global_setup_windows(g);
-
   /* Fill in some variables which don't have constant defaults */
   /* TODO: come back later and check passwd file first */
@@ -92,6 +92,6 @@
 
   owl_messagelist_create(&(g->msglist));
-  owl_mainwin_init(&(g->mw));
-  owl_popwin_init(&(g->pw));
+
+  _owl_global_init_windows(g);
 
   g->aim_screenname=NULL;
@@ -112,6 +112,4 @@
   g->zaldlist = NULL;
   g->pseudologin_notify = 0;
-
-  owl_obarray_init(&(g->obarray));
 
   owl_message_init_fmtext_cache();
@@ -120,4 +118,18 @@
   g->timerlist = NULL;
   g->interrupted = FALSE;
+}
+
+static void _owl_global_init_windows(owl_global *g)
+{
+  /* Create the main window */
+  owl_mainpanel_init(&(g->mainpanel));
+
+  /* Create the widgets */
+  owl_mainwin_init(&(g->mw), g->mainpanel.recwin);
+  owl_popwin_init(&(g->pw));
+  owl_msgwin_init(&(g->msgwin), g->mainpanel.msgwin);
+  owl_sepbar_init(g->mainpanel.sepwin);
+
+  owl_window_set_default_cursor(g->mainpanel.sepwin);
 
   /* set up a pad for input */
@@ -126,4 +138,9 @@
   keypad(g->input_pad, 1);
   meta(g->input_pad, 1);
+}
+
+void owl_global_sepbar_dirty(owl_global *g)
+{
+  owl_window_dirty(g->mainpanel.sepwin);
 }
 
@@ -158,34 +175,4 @@
 }
 
-void _owl_global_setup_windows(owl_global *g) {
-  int cols, typwin_lines;
-
-  cols=g->cols;
-  typwin_lines=owl_global_get_typwin_lines(g);
-
-  /* set the new window sizes */
-  g->recwinlines=g->lines-(typwin_lines+2);
-  if (g->recwinlines<0) {
-    /* gotta deal with this */
-    g->recwinlines=0;
-  }
-
-  /* create the new windows */
-  _owl_panel_set_window(&g->recpan, newwin(g->recwinlines, cols, 0, 0));
-  _owl_panel_set_window(&g->seppan, newwin(1, cols, g->recwinlines, 0));
-  _owl_panel_set_window(&g->msgpan, newwin(1, cols, g->recwinlines+1, 0));
-  _owl_panel_set_window(&g->typpan, newwin(typwin_lines, cols, g->recwinlines+2, 0));
-
-  if (g->tw)
-      owl_editwin_set_curswin(g->tw, owl_global_get_curs_typwin(g), typwin_lines, g->cols);
-
-  idlok(owl_global_get_curs_typwin(g), FALSE);
-  idlok(owl_global_get_curs_recwin(g), FALSE);
-  idlok(owl_global_get_curs_sepwin(g), FALSE);
-  idlok(owl_global_get_curs_msgwin(g), FALSE);
-
-  wmove(owl_global_get_curs_typwin(g), 0, 0);
-}
-
 owl_context *owl_global_get_context(owl_global *g) {
   if (!g->context_stack)
@@ -194,15 +181,17 @@
 }
 
-static void owl_global_lookup_keymap(owl_global *g) {
-  owl_context *c = owl_global_get_context(g);
-  if (!c || !c->keymap)
+static void owl_global_activate_context(owl_global *g, owl_context *c) {
+  if (!c)
     return;
 
-  if (!owl_keyhandler_activate(owl_global_get_keyhandler(g), c->keymap)) {
-    owl_function_error("Unable to activate keymap '%s'", c->keymap);
-  }
-}
-
-void owl_global_push_context(owl_global *g, int mode, void *data, const char *keymap) {
+  if (c->keymap) {
+    if (!owl_keyhandler_activate(owl_global_get_keyhandler(g), c->keymap)) {
+      owl_function_error("Unable to activate keymap '%s'", c->keymap);
+    }
+  }
+  owl_window_set_cursor(c->cursor);
+}
+
+void owl_global_push_context(owl_global *g, int mode, void *data, const char *keymap, owl_window *cursor) {
   owl_context *c;
   if (!(mode & OWL_CTX_MODE_BITS))
@@ -211,7 +200,8 @@
   c->mode = mode;
   c->data = data;
+  c->cursor = cursor ? g_object_ref(cursor) : NULL;
   c->keymap = owl_strdup(keymap);
   g->context_stack = g_list_prepend(g->context_stack, c);
-  owl_global_lookup_keymap(g);
+  owl_global_activate_context(g, owl_global_get_context(g));
 }
 
@@ -223,7 +213,9 @@
   g->context_stack = g_list_delete_link(g->context_stack,
                                         g->context_stack);
+  if (c->cursor)
+    g_object_unref(c->cursor);
   owl_free(c->keymap);
   owl_free(c);
-  owl_global_lookup_keymap(g);
+  owl_global_activate_context(g, owl_global_get_context(g));
 }
 
@@ -237,5 +229,5 @@
 
 int owl_global_get_recwin_lines(const owl_global *g) {
-  return(g->recwinlines);
+  return g->mainpanel.recwinlines;
 }
 
@@ -303,20 +295,8 @@
 }
 
-/* curses windows */
-
-WINDOW *owl_global_get_curs_recwin(const owl_global *g) {
-  return panel_window(g->recpan);
-}
-
-WINDOW *owl_global_get_curs_sepwin(const owl_global *g) {
-  return panel_window(g->seppan);
-}
-
-WINDOW *owl_global_get_curs_msgwin(const owl_global *g) {
-  return panel_window(g->msgpan);
-}
-
-WINDOW *owl_global_get_curs_typwin(const owl_global *g) {
-  return panel_window(g->typpan);
+/* underlying owl_windows */
+
+owl_window *owl_global_get_typwin_window(const owl_global *g) {
+  return g->mainpanel.typwin;
 }
 
@@ -327,19 +307,4 @@
 }
 
-/* refresh */
-
-int owl_global_is_needrefresh(const owl_global *g) {
-  if (g->needrefresh==1) return(1);
-  return(0);
-}
-
-void owl_global_set_needrefresh(owl_global *g) {
-  g->needrefresh=1;
-}
-
-void owl_global_set_noneedrefresh(owl_global *g) {
-  g->needrefresh=0;
-}
-
 /* variable dictionary */
 
@@ -357,5 +322,6 @@
 
 void owl_global_set_rightshift(owl_global *g, int i) {
-  g->rightshift=i;
+  g->rightshift = i;
+  owl_mainwin_redisplay(owl_global_get_mainwin(g));
 }
 
@@ -372,5 +338,10 @@
       owl_function_resize_typwin(owl_global_get_typwin_lines(g) + d);
 
-  g->tw = owl_editwin_new(owl_global_get_curs_typwin(g),
+  if (g->typwin_erase_id) {
+    g_signal_handler_disconnect(owl_global_get_typwin_window(g), g->typwin_erase_id);
+    g->typwin_erase_id = 0;
+  }
+
+  g->tw = owl_editwin_new(owl_global_get_typwin_window(g),
                           owl_global_get_typwin_lines(g),
                           g->cols,
@@ -385,5 +356,10 @@
       owl_function_resize_typwin(owl_global_get_typwin_lines(g) - d);
 
-  werase(owl_global_get_curs_typwin(g));
+  if (!g->typwin_erase_id) {
+    g->typwin_erase_id =
+      g_signal_connect(owl_global_get_typwin_window(g), "redraw", G_CALLBACK(owl_window_erase_cb), NULL);
+  }
+  owl_window_dirty(owl_global_get_typwin_window(g));
+
   g->tw = NULL;
 }
@@ -393,8 +369,4 @@
 void owl_global_set_resize_pending(owl_global *g) {
   g->resizepending=1;
-}
-
-void owl_global_set_relayout_pending(owl_global *g) {
-  g->relayoutpending = 1;
 }
 
@@ -476,5 +448,5 @@
  * fail.
  */
-static void _owl_global_get_size(int *lines, int *cols) {
+void owl_global_get_terminal_size(int *lines, int *cols) {
   struct winsize size;
   /* get the new size */
@@ -493,70 +465,13 @@
 }
 
-void owl_global_resize(owl_global *g, int x, int y) {
-  /* resize the screen.  If x or y is 0 use the terminal size */
+void owl_global_check_resize(owl_global *g) {
+  /* resize the screen.  If lines or cols is 0 use the terminal size */
   if (!g->resizepending) return;
   g->resizepending = 0;
 
-  _owl_global_get_size(&g->lines, &g->cols);
-  if (x != 0) {
-    g->lines = x;
-  }
-  if (y != 0) {
-    g->cols = y;
-  }
-
-  resizeterm(g->lines, g->cols);
+  owl_global_get_terminal_size(&g->lines, &g->cols);
+  owl_window_resize(owl_window_get_screen(), g->lines, g->cols);
 
   owl_function_debugmsg("New size is %i lines, %i cols.", g->lines, g->cols);
-  owl_global_set_relayout_pending(g);
-}
-
-void owl_global_relayout(owl_global *g) {
-  owl_popwin *pw;
-  owl_viewwin *vw;
-
-  if (!g->relayoutpending) return;
-  g->relayoutpending = 0;
-
-  owl_function_debugmsg("Relayouting...");
-
-  /* re-initialize the windows */
-  _owl_global_setup_windows(g);
-
-  /* in case any styles rely on the current width */
-  owl_messagelist_invalidate_formats(owl_global_get_msglist(g));
-
-  /* recalculate the topmsg to make sure the current message is on
-   * screen */
-  owl_function_calculate_topmsg(OWL_DIRECTION_NONE);
-
-  /* recreate the popwin */
-  pw = owl_global_get_popwin(g);
-  if (owl_popwin_is_active(pw)) {
-    /*
-     * This is somewhat hacky; we probably want a proper windowing layer. We
-     * destroy the popwin and recreate it. Then the viewwin is redirected to
-     * the new window.
-     */
-    vw = owl_global_get_viewwin(g);
-    owl_popwin_close(pw);
-    owl_popwin_up(pw);
-    owl_viewwin_set_curswin(vw, owl_popwin_get_curswin(pw),
-	owl_popwin_get_lines(pw), owl_popwin_get_cols(pw));
-    owl_viewwin_redisplay(vw);
-  }
-
-  /* refresh stuff */
-  g->needrefresh=1;
-  owl_mainwin_redisplay(&(g->mw));
-  sepbar(NULL);
-  if (g->tw)
-      owl_editwin_redisplay(g->tw);
-  else
-    werase(owl_global_get_curs_typwin(g));
-
-  owl_function_full_redisplay();
-
-  owl_function_makemsg("");
 }
 
@@ -628,5 +543,5 @@
 
 void owl_global_set_curmsg_vert_offset(owl_global *g, int i) {
-  g->curmsg_vert_offset=i;
+  g->curmsg_vert_offset = i;
 }
 
@@ -864,5 +779,5 @@
 void owl_global_messagequeue_addmsg(owl_global *g, owl_message *m)
 {
-  owl_list_append_element(&(g->messagequeue), m);
+  g_queue_push_tail(g->messagequeue, m);
 }
 
@@ -875,14 +790,13 @@
   owl_message *out;
 
-  if (owl_list_get_size(&(g->messagequeue))==0) return(NULL);
-  out=owl_list_get_element(&(g->messagequeue), 0);
-  owl_list_remove_element(&(g->messagequeue), 0);
-  return(out);
+  if (g_queue_is_empty(g->messagequeue))
+    return NULL;
+  out = g_queue_pop_head(g->messagequeue);
+  return out;
 }
 
 int owl_global_messagequeue_pending(owl_global *g)
 {
-  if (owl_list_get_size(&(g->messagequeue))==0) return(0);
-  return(1);
+  return !g_queue_is_empty(g->messagequeue);
 }
 
@@ -1012,9 +926,4 @@
 }
 
-const char * owl_global_intern(owl_global *g, const char * string)
-{
-  return owl_obarray_insert(&(g->obarray), string);
-}
-
 owl_list *owl_global_get_io_dispatch_list(owl_global *g)
 {
@@ -1052,6 +961,5 @@
   } filters[] = {
     { "personal",
-      "private ^true$ and ( not type ^zephyr$ or "
-      "( class ^message and ( instance ^personal$ or instance ^urgent$ ) ) )" },
+      "isprivate ^true$ and ( not type ^zephyr$ or ( class ^message  ) )" },
     { "trash",
       "class ^mail$ or opcode ^ping$ or type ^admin$ or ( not login ^none$ )" },
Index: keymap.c
===================================================================
--- keymap.c	(revision 8a921b5e61689654774fef672165f4c56c01036d)
+++ keymap.c	(revision 44cc9abbc2c15abaf4cd5fe0d1fc62b0e0936913)
@@ -1,4 +1,7 @@
 #include <string.h>
 #include "owl.h"
+
+static void _owl_keymap_format_bindings(const owl_keymap *km, owl_fmtext *fm);
+static void _owl_keymap_format_with_parents(const owl_keymap *km, owl_fmtext *fm);
 
 /* returns 0 on success */
@@ -9,5 +12,5 @@
   if ((km->desc = owl_strdup(desc)) == NULL) return(-1);
   if (0 != owl_list_create(&km->bindings)) return(-1);
-  km->submap = NULL;
+  km->parent = NULL;
   km->default_fn = default_fn;
   km->prealways_fn = prealways_fn;
@@ -24,7 +27,7 @@
 }
 
-void owl_keymap_set_submap(owl_keymap *km, const owl_keymap *submap)
-{
-  km->submap = submap;
+void owl_keymap_set_parent(owl_keymap *km, const owl_keymap *parent)
+{
+  km->parent = parent;
 }
 
@@ -86,9 +89,6 @@
 
 /* Appends details about the keymap to fm */
-void owl_keymap_get_details(const owl_keymap *km, owl_fmtext *fm)
-{
-  int i, nbindings; 
-  const owl_keybinding *kb;
-  
+void owl_keymap_get_details(const owl_keymap *km, owl_fmtext *fm, int recurse)
+{
   owl_fmtext_append_bold(fm, "KEYMAP - ");
   owl_fmtext_append_bold(fm, km->name);
@@ -99,7 +99,7 @@
     owl_fmtext_append_normal(fm, "\n");
   }
-  if (km->submap) {
-    owl_fmtext_append_normal(fm, OWL_TABSTR "Has submap: ");
-    owl_fmtext_append_normal(fm, km->submap->name);
+  if (km->parent) {
+    owl_fmtext_append_normal(fm, OWL_TABSTR "Has parent: ");
+    owl_fmtext_append_normal(fm, km->parent->name);
     owl_fmtext_append_normal(fm, "\n");
   }
@@ -119,4 +119,29 @@
 
   owl_fmtext_append_bold(fm, "\nKey bindings:\n\n");  
+  if (recurse) {
+    _owl_keymap_format_with_parents(km, fm);
+  } else {
+    _owl_keymap_format_bindings(km, fm);
+  }
+}
+
+static void _owl_keymap_format_with_parents(const owl_keymap *km, owl_fmtext *fm)
+{
+  while (km) {
+    _owl_keymap_format_bindings(km, fm);
+    km = km->parent;
+    if (km) {
+      owl_fmtext_append_bold(fm, "\nInherited from ");
+      owl_fmtext_append_bold(fm, km->name);
+      owl_fmtext_append_bold(fm, ":\n\n");
+    }
+  }
+}
+
+static void _owl_keymap_format_bindings(const owl_keymap *km, owl_fmtext *fm)
+{
+  int i, nbindings;
+  const owl_keybinding *kb;
+  
   nbindings = owl_list_get_size(&km->bindings);
   for (i=0; i<nbindings; i++) {
@@ -248,6 +273,6 @@
   }
 
-  /* deal with the always_fn for the map and submaps */
-  for (km=kh->active; km; km=km->submap) {
+  /* deal with the always_fn for the map and parents */
+  for (km=kh->active; km; km=km->parent) {
     if (km->prealways_fn) {
       km->prealways_fn(j);
@@ -256,7 +281,7 @@
 
   /* search for a match.  goes through active keymap and then
-   * through submaps... TODO:  clean this up so we can pull
+   * through parents... TODO:  clean this up so we can pull
    * keyhandler and keymap apart.  */
-  for (km=kh->active; km; km=km->submap) {
+  for (km=kh->active; km; km=km->parent) {
     for (i=owl_list_get_size(&km->bindings)-1; i>=0; i--) {
       kb = owl_list_get_element(&km->bindings, i);
Index: keys.c
===================================================================
--- keys.c	(revision 8a5b5a12327c205b798d00f5cc5505f787e55331)
+++ keys.c	(revision 5cc7e5e45702da552c2ba2d3eb236eaeca359eb4)
@@ -30,5 +30,5 @@
        "Text editing and command window", 
        owl_keys_editwin_default, NULL, owl_keys_editwin_postalways);
-  owl_keymap_set_submap(km_editwin, km_global);
+  owl_keymap_set_parent(km_editwin, km_global);
   /*
   BIND_CMD("F1",          "help",            "");
@@ -99,5 +99,5 @@
        "Multi-line text editing", 
        owl_keys_editwin_default, NULL, owl_keys_editwin_postalways);
-  owl_keymap_set_submap(km_ew_multi, km_editwin);
+  owl_keymap_set_parent(km_ew_multi, km_editwin);
 
   BIND_CMD("UP",      "edit:move-up-line", "");
@@ -129,5 +129,5 @@
        "Single-line text editing", 
        owl_keys_editwin_default, NULL, owl_keys_editwin_postalways);
-  owl_keymap_set_submap(km_ew_onel, km_editwin);
+  owl_keymap_set_parent(km_ew_onel, km_editwin);
 
   BIND_CMD("C-u",         "edit:delete-all", "Clears the entire line");
@@ -154,5 +154,5 @@
        "Single-line response to question", 
        owl_keys_editwin_default, NULL, owl_keys_editwin_postalways);
-  owl_keymap_set_submap(km_ew_onel, km_editwin);
+  owl_keymap_set_parent(km_ew_onel, km_editwin);
 
   BIND_CMD("C-u",         "edit:delete-all", "Clears the entire line");
@@ -169,5 +169,5 @@
        "Pop-up window (eg, help)", 
        owl_keys_default_invalid, NULL, owl_keys_popless_postalways);
-  owl_keymap_set_submap(km_viewwin, km_global);
+  owl_keymap_set_parent(km_viewwin, km_global);
 
   BIND_CMD("SPACE",       "popless:scroll-down-page", "");
@@ -222,5 +222,5 @@
 	"Main window / message list",
         owl_keys_default_invalid, owl_keys_recwin_prealways, NULL);
-  owl_keymap_set_submap(km_mainwin, km_global);
+  owl_keymap_set_parent(km_mainwin, km_global);
   BIND_CMD("C-x C-c", "start-command quit", "");
   BIND_CMD("F1",      "help",           "");
@@ -338,15 +338,7 @@
     owl_editwin_post_process_char(e, j);
   }
-  owl_global_set_needrefresh(&g);
 }
 
 void owl_keys_popless_postalways(owl_input j) {
-  owl_viewwin *v = owl_global_get_viewwin(&g);
-  const owl_popwin *pw = owl_global_get_popwin(&g);
-
-  if (pw && owl_popwin_is_active(pw) && v) {
-    owl_viewwin_redisplay(v);
-    owl_global_set_needrefresh(&g);
-  }  
 }
 
Index: logging.c
===================================================================
--- logging.c	(revision 91634ec817b2d3dfda0c429002c8450e4f2404d5)
+++ logging.c	(revision 839697d886ff0c8987d4f6233e92a81a5157faed)
@@ -128,4 +128,5 @@
   char filename[MAXPATHLEN], *logpath;
   char *to, *temp;
+  GList *cc;
 
   /* expand ~ in path names */
@@ -135,15 +136,14 @@
   if (owl_message_is_type_zephyr(m)) {
     /* If this has CC's, do all but the "recipient" which we'll do below */
-    to = owl_message_get_cc_without_recipient(m);
-    if (to != NULL) {
-      temp = strtok(to, " ");
-      while (temp != NULL) {
-          temp = short_zuser(temp);
-          snprintf(filename, MAXPATHLEN, "%s/%s", logpath, temp);
-          owl_log_append(m, filename);
-          temp = strtok(NULL, " ");
-      }
-      owl_free(to);
-    }
+    cc = owl_message_get_cc_without_recipient(m);
+    while (cc != NULL) {
+      temp = short_zuser(cc->data);
+      snprintf(filename, MAXPATHLEN, "%s/%s", logpath, temp);
+      owl_log_append(m, filename);
+
+      owl_free(cc->data);
+      cc = g_list_delete_link(cc, cc);
+    }
+
     to = short_zuser(owl_message_get_recipient(m));
   } else if (owl_message_is_type_jabber(m)) {
@@ -325,17 +325,16 @@
      * the sender, as well.
      */
-    char *cc, *temp;
+    char *temp;
+    GList *cc;
     cc = owl_message_get_cc_without_recipient(m);
-    if (cc != NULL) {
-      temp = strtok(cc, " ");
-      while (temp != NULL) {
-        temp = short_zuser(temp);
-        if (strcasecmp(temp, frombuff) != 0) {
-          snprintf(filename, MAXPATHLEN, "%s/%s", logpath, temp);
-          owl_log_append(m, filename);
-        }
-        temp = strtok(NULL, " ");
+    while (cc != NULL) {
+      temp = short_zuser(cc->data);
+      if (strcasecmp(temp, frombuff) != 0) {
+        snprintf(filename, MAXPATHLEN, "%s/%s", logpath, temp);
+        owl_log_append(m, filename);
       }
-      owl_free(cc);
+
+      owl_free(cc->data);
+      cc = g_list_delete_link(cc, cc);
     }
   }
Index: mainpanel.c
===================================================================
--- mainpanel.c	(revision 4eee9482aeaa92aa1199b5d6ca4141afdf5f0791)
+++ mainpanel.c	(revision 4eee9482aeaa92aa1199b5d6ca4141afdf5f0791)
@@ -0,0 +1,57 @@
+#include "owl.h"
+
+void owl_mainpanel_init(owl_mainpanel *mp)
+{
+  /* Create windows */
+  mp->panel = owl_window_new(NULL);
+  /* HACK for now: the sepwin must be drawn /after/ the recwin for
+   * lastdisplayed to work */
+  mp->sepwin = owl_window_new(mp->panel);
+  mp->recwin = owl_window_new(mp->panel);
+  mp->msgwin = owl_window_new(mp->panel);
+  mp->typwin = owl_window_new(mp->panel);
+
+  /* Set up sizing hooks */
+  owl_signal_connect_object(owl_window_get_screen(), "resized", G_CALLBACK(owl_window_fill_parent_cb), mp->panel, 0);
+  g_signal_connect_swapped(mp->panel, "resized", G_CALLBACK(owl_mainpanel_layout_contents), mp);
+
+  /* Bootstrap the sizes and go */
+  owl_window_fill_parent_cb(owl_window_get_screen(), mp->panel);
+  owl_window_show_all(mp->panel);
+}
+
+void owl_mainpanel_layout_contents(owl_mainpanel *mp)
+{
+  int lines, cols, typwin_lines;
+
+  /* skip if we haven't been initialized */
+  if (!mp->panel) return;
+
+  owl_window_get_position(mp->panel, &lines, &cols, NULL, NULL);
+  typwin_lines = owl_global_get_typwin_lines(&g);
+
+  /* set the new window sizes */
+  mp->recwinlines=lines-(typwin_lines+2);
+  if (mp->recwinlines<0) {
+    /* gotta deal with this */
+    mp->recwinlines=0;
+    typwin_lines = lines - 2;
+  }
+
+  /* resize all the windows */
+  owl_window_set_position(mp->recwin, mp->recwinlines, cols, 0, 0);
+  owl_window_set_position(mp->sepwin, 1, cols, mp->recwinlines, 0);
+  owl_window_set_position(mp->msgwin, 1, cols, mp->recwinlines+1, 0);
+  owl_window_set_position(mp->typwin, typwin_lines, cols, mp->recwinlines+2, 0);
+}
+
+void owl_mainpanel_cleanup(owl_mainpanel *mp)
+{
+  owl_window_unlink(mp->panel);
+  g_object_unref(mp->panel);
+  g_object_unref(mp->recwin);
+  g_object_unref(mp->sepwin);
+  g_object_unref(mp->msgwin);
+  g_object_unref(mp->typwin);
+  mp->panel = mp->recwin = mp->sepwin = mp->msgwin = mp->typwin = NULL;
+}
Index: mainwin.c
===================================================================
--- mainwin.c	(revision f449096479541df1434d1d796890d0bd2939f072)
+++ mainwin.c	(revision 5cc7e5e45702da552c2ba2d3eb236eaeca359eb4)
@@ -1,11 +1,42 @@
 #include "owl.h"
 
-void owl_mainwin_init(owl_mainwin *mw)
+static void owl_mainwin_redraw(owl_window *w, WINDOW *recwin, void *user_data);
+static void owl_mainwin_resized(owl_window *w, void *user_data);
+
+void owl_mainwin_init(owl_mainwin *mw, owl_window *window)
 {
   mw->curtruncated=0;
   mw->lastdisplayed=-1;
+  mw->window = g_object_ref(window);
+  /* for now, just assume this object lasts forever */
+  g_signal_connect(window, "redraw", G_CALLBACK(owl_mainwin_redraw), mw);
+  g_signal_connect(window, "resized", G_CALLBACK(owl_mainwin_resized), mw);
+  owl_window_dirty(window);
+
+  /* For now, we do not bother with connecting up dependencies; that'll be a
+   * future refactor of the mainwin */
+}
+
+static void owl_mainwin_resized(owl_window *w, void *user_data)
+{
+  owl_mainwin *mw = user_data;
+
+  /* in case any styles rely on the current width */
+  owl_messagelist_invalidate_formats(owl_global_get_msglist(&g));
+
+  /* recalculate the topmsg to make sure the current message is on
+   * screen */
+  owl_function_calculate_topmsg(OWL_DIRECTION_NONE);
+
+  /* Schedule a redraw */
+  owl_window_dirty(mw->window);
 }
 
 void owl_mainwin_redisplay(owl_mainwin *mw)
+{
+  owl_window_dirty(mw->window);
+}
+
+static void owl_mainwin_redraw(owl_window *w, WINDOW *recwin, void *user_data)
 {
   owl_message *m;
@@ -13,10 +44,9 @@
   int x, y, savey, recwinlines, start;
   int topmsg, curmsg, markedmsgid, fgcolor, bgcolor;
-  WINDOW *recwin;
   const owl_view *v;
   GList *fl;
   const owl_filter *f;
+  owl_mainwin *mw = user_data;
 
-  recwin = owl_global_get_curs_recwin(&g);
   topmsg = owl_global_get_topmsg(&g);
   curmsg = owl_global_get_curmsg(&g);
@@ -43,5 +73,4 @@
     mw->curtruncated=0;
     mw->lastdisplayed=-1;
-    owl_global_set_needrefresh(&g);
     return;
   }
@@ -88,5 +117,5 @@
     if (y+lines > recwinlines-1) {
       isfull=1;
-      owl_message_curs_waddstr(m, owl_global_get_curs_recwin(&g),
+      owl_message_curs_waddstr(m, recwin,
 			       start,
 			       start+recwinlines-y,
@@ -96,5 +125,5 @@
     } else {
       /* otherwise print the whole thing */
-      owl_message_curs_waddstr(m, owl_global_get_curs_recwin(&g),
+      owl_message_curs_waddstr(m, recwin,
 			       start,
 			       start+lines,
@@ -139,6 +168,4 @@
   }
   mw->lastdisplayed=i-1;
-
-  owl_global_set_needrefresh(&g);
 }
 
Index: message.c
===================================================================
--- message.c	(revision 9a7b4f23cba7fc71c4e918b5cb8d5ca6789924e3)
+++ message.c	(revision c314f397830b8cfa20a9540a606bad42845e7d9f)
@@ -61,9 +61,11 @@
   owl_pair *p = NULL, *pair = NULL;
 
+  attrname = g_intern_string(attrname);
+
   /* look for an existing pair with this key, */
   j=owl_list_get_size(&(m->attributes));
   for (i=0; i<j; i++) {
     p=owl_list_get_element(&(m->attributes), i);
-    if (!strcmp(owl_pair_get_key(p), attrname)) {
+    if (owl_pair_get_key(p) == attrname) {
       owl_free(owl_pair_get_value(p));
       pair = p;
@@ -74,5 +76,5 @@
   if(pair ==  NULL) {
     pair = owl_malloc(sizeof(owl_pair));
-    owl_pair_create(pair, owl_global_intern(&g, attrname), NULL);
+    owl_pair_create(pair, attrname, NULL);
     owl_list_append_element(&(m->attributes), pair);
   }
@@ -87,9 +89,16 @@
   int i, j;
   owl_pair *p;
+  GQuark quark;
+
+  quark = g_quark_try_string(attrname);
+  if (quark == 0)
+    /* don't bother inserting into string table */
+    return NULL;
+  attrname = g_quark_to_string(quark);
 
   j=owl_list_get_size(&(m->attributes));
   for (i=0; i<j; i++) {
     p=owl_list_get_element(&(m->attributes), i);
-    if (!strcmp(owl_pair_get_key(p), attrname)) {
+    if (owl_pair_get_key(p) == attrname) {
       return(owl_pair_get_value(p));
     }
@@ -496,5 +505,5 @@
 void owl_message_set_hostname(owl_message *m, const char *hostname)
 {
-  m->hostname=owl_global_intern(&g, hostname);
+  m->hostname = g_intern_string(hostname);
 }
 
@@ -584,8 +593,9 @@
 
 /* caller must free return value */
-char *owl_message_get_cc_without_recipient(const owl_message *m)
-{
-  char *cc, *out, *end, *shortuser, *recip;
+GList *owl_message_get_cc_without_recipient(const owl_message *m)
+{
+  char *cc, *shortuser, *recip;
   const char *user;
+  GList *out = NULL;
 
   cc = owl_message_get_cc(m);
@@ -594,6 +604,4 @@
 
   recip = short_zuser(owl_message_get_recipient(m));
-  out = owl_malloc(strlen(cc) + 2);
-  end = out;
 
   user = strtok(cc, " ");
@@ -601,20 +609,12 @@
     shortuser = short_zuser(user);
     if (strcasecmp(shortuser, recip) != 0) {
-      strcpy(end, user);
-      end[strlen(user)] = ' ';
-      end += strlen(user) + 1;
+      out = g_list_prepend(out, owl_strdup(user));
     }
     owl_free(shortuser);
     user = strtok(NULL, " ");
   }
-  end[0] = '\0';
 
   owl_free(recip);
   owl_free(cc);
-
-  if (strlen(out) == 0) {
-    owl_free(out);
-    out = NULL;
-  }
 
   return(out);
@@ -732,4 +732,39 @@
   owl_message_set_recipient(m, "looprecip");
   owl_message_set_isprivate(m);
+}
+
+void owl_message_save_ccs(owl_message *m) {
+  GList *cc;
+  char *tmp;
+
+  cc = owl_message_get_cc_without_recipient(m);
+
+  if (cc != NULL) {
+    GString *recips = g_string_new("");
+    cc = g_list_prepend(cc, short_zuser(owl_message_get_sender(m)));
+    cc = g_list_prepend(cc, short_zuser(owl_message_get_recipient(m)));
+    cc = g_list_sort(cc, (GCompareFunc)strcasecmp);
+
+    while(cc != NULL) {
+      /* Collapse any identical entries */
+      while (cc->next && strcasecmp(cc->data, cc->next->data) == 0) {
+        owl_free(cc->data);
+        cc = g_list_delete_link(cc, cc);
+      }
+
+      tmp = short_zuser(cc->data);
+      g_string_append(recips, tmp);
+
+      owl_free(tmp);
+      owl_free(cc->data);
+      cc = g_list_delete_link(cc, cc);
+
+      if (cc)
+        g_string_append_c(recips, ' ');
+    }
+
+    owl_message_set_attribute(m, "zephyr_ccs", recips->str);
+    g_string_free(recips, true);
+  }
 }
 
@@ -869,4 +904,6 @@
     }
   }
+
+  owl_message_save_ccs(m);
 }
 #else
@@ -963,4 +1000,6 @@
     owl_message_set_isprivate(m);
   }
+
+  owl_message_save_ccs(m);
 }
 
Index: msgwin.c
===================================================================
--- msgwin.c	(revision 23b6c118279eabdaa1e3d8d2d600a97bce616017)
+++ msgwin.c	(revision 23b6c118279eabdaa1e3d8d2d600a97bce616017)
@@ -0,0 +1,46 @@
+#include "owl.h"
+
+static void owl_msgwin_redraw(owl_window *w, WINDOW *curswin, void *msgwin_);
+
+/* Maintains a small status window of text */
+
+void owl_msgwin_init(owl_msgwin *msgwin, owl_window *window)
+{
+  msgwin->msg = NULL;
+  msgwin->window = g_object_ref(window);
+  msgwin->redraw_id = g_signal_connect(window, "redraw", G_CALLBACK(owl_msgwin_redraw), msgwin);
+  owl_window_dirty(window);
+}
+
+static void owl_msgwin_redraw(owl_window *w, WINDOW *curswin, void *msgwin_)
+{
+  owl_msgwin *msgwin = msgwin_;
+
+  werase(curswin);
+  if (msgwin->msg)
+    waddstr(curswin, msgwin->msg);  
+}
+
+void owl_msgwin_set_text(owl_msgwin *msgwin, const char *msg)
+{
+  owl_msgwin_set_text_nocopy(msgwin, owl_strdup(msg));
+}
+
+void owl_msgwin_set_text_nocopy(owl_msgwin *msgwin, char *msg)
+{
+  owl_free(msgwin->msg);
+  msgwin->msg = msg;
+  owl_window_dirty(msgwin->window);
+}
+
+void owl_msgwin_cleanup(owl_msgwin *msgwin)
+{
+  owl_free(msgwin->msg);
+  msgwin->msg = NULL;
+  if (msgwin->window) {
+    g_signal_handler_disconnect(msgwin->window, msgwin->redraw_id);
+    g_object_unref(msgwin->window);
+    msgwin->window = NULL;
+    msgwin->redraw_id = 0;
+  }
+}
Index: array.c
===================================================================
--- obarray.c	(revision e9c6fc846b34351def7ebc5bf8e5c770d514cc14)
+++ 	(revision )
@@ -1,67 +1,0 @@
-#include <stdlib.h>
-#include <string.h>
-#include "owl.h"
-
-/*
- * At the moment, the obarray is represented as a sorted list of
- * strings. I judge this a reasonable tradeoff between simplicity and
- * reasonable efficient lookup (insertion should be comparatively
- * rare)
- */
-
-/* Helper method: Lookup a key in the obarray. If the key exists,
- * return its index, and the interned value in *val. Otherwise, return
- * the index it should be inserted at.
- */
-static int owl_obarray_lookup(const owl_obarray *oa, const char *key, const char **val)
-{
-  int first, last, mid;
-  const char * str;
-  int cmp;
-
-  mid = 0;
-  first = 0;
-  last = owl_list_get_size(&(oa->strings)) - 1;
-  while(first <= last) {
-    mid = first + (last - first)/2;
-    str = owl_list_get_element(&(oa->strings), mid);
-    cmp = strcmp(key, str);
-    if(cmp == 0) {
-      *val = str;
-      return mid;
-    } else if(cmp < 0) {
-      first = mid + 1;
-    } else {
-      last = mid - 1;
-    }
-  }
-  *val = NULL;
-  return mid;
-}
-
-/* Returns NULL if the string doesn't exist in the obarray */
-const char * owl_obarray_find(const owl_obarray *oa, const char * string)
-{
-  const char *v;
-  owl_obarray_lookup(oa, string, &v);
-  return v;
-}
-
-/* Inserts the string into the obarray if it doesn't exist */
-const char * owl_obarray_insert(owl_obarray *oa, const char * string)
-{
-  const char *v;
-  int i;
-  i = owl_obarray_lookup(oa, string, &v);
-  if(!v) {
-    char *v2 = owl_strdup(string);
-    owl_list_insert_element(&(oa->strings), i, v2);
-    return v2;
-  }
-  return v;
-}
-
-void owl_obarray_init(owl_obarray *oa)
-{
-  owl_list_create(&(oa->strings));
-}
Index: owl.c
===================================================================
--- owl.c	(revision d12a8c71f5d91653b1bffd9fa566448eb52b5d43)
+++ owl.c	(revision 205e1645be4619c39f0963639fd7bfb7b0ec5195)
@@ -137,6 +137,6 @@
   tcgetattr(0, &tio);
   tio.c_iflag &= ~(ISTRIP|IEXTEN);
-  tio.c_cc[VQUIT] = 0;
-  tio.c_cc[VSUSP] = 0;
+  tio.c_cc[VQUIT] = fpathconf(STDIN, _PC_VDISABLE);
+  tio.c_cc[VSUSP] = fpathconf(STDIN, _PC_VDISABLE);
   tcsetattr(0, TCSAFLUSH, &tio);
 
@@ -240,4 +240,6 @@
   /* log the message if we need to */
   owl_log_message(m);
+  /* redraw the sepbar; TODO: don't violate layering */
+  owl_global_sepbar_dirty(&g);
 
   return 1;
@@ -272,6 +274,4 @@
     /* this should be optimized to not run if the new messages won't be displayed */
     owl_mainwin_redisplay(owl_global_get_mainwin(&g));
-    sepbar(NULL);
-    owl_global_set_needrefresh(&g);
   }
   return newmsgs;
@@ -428,26 +428,7 @@
 {
   /* if a resize has been scheduled, deal with it */
-  owl_global_resize(&g, 0, 0);
-  /* also handle relayouts */
-  owl_global_relayout(&g);
-
+  owl_global_check_resize(&g);
   /* update the terminal if we need to */
-  if (owl_global_is_needrefresh(&g)) {
-    /* these are here in case a relayout changes the windows */
-    WINDOW *sepwin = owl_global_get_curs_sepwin(&g);
-    WINDOW *typwin = owl_global_get_curs_typwin(&g);
-
-    /* push all changed windows to screen */
-    update_panels();
-    /* leave the cursor in the appropriate window */
-    if (!owl_popwin_is_active(owl_global_get_popwin(&g))
-	&& owl_global_get_typwin(&g)) {
-      owl_function_set_cursor(typwin);
-    } else {
-      owl_function_set_cursor(sepwin);
-    }
-    doupdate();
-    owl_global_set_noneedrefresh(&g);
-  }
+  owl_window_redraw_scheduled();
   return 0;
 }
@@ -517,5 +498,5 @@
 
   owl_global_pop_context(&g);
-  owl_global_push_context(&g, OWL_CTX_READCONFIG, NULL, NULL);
+  owl_global_push_context(&g, OWL_CTX_READCONFIG, NULL, NULL, NULL);
 
   perlerr=owl_perlconfig_initperl(opts.configfile, &argc, &argv, &env);
@@ -561,5 +542,4 @@
     "-----------------------------------------------------------------m-m---\n"
   );
-  sepbar(NULL);
 
   /* process the startup file */
@@ -585,8 +565,5 @@
 
   owl_global_pop_context(&g);
-  owl_global_push_context(&g, OWL_CTX_READCONFIG|OWL_CTX_RECV, NULL, "recv");
-
-  /* If we ever deprecate the mainloop hook, remove this. */
-  owl_select_add_timer(0, 1, owl_perlconfig_mainloop, NULL, NULL);
+  owl_global_push_context(&g, OWL_CTX_READCONFIG|OWL_CTX_RECV, NULL, "recv", NULL);
 
   owl_select_add_pre_select_action(owl_refresh_pre_select_action, NULL, NULL);
Index: owl.h
===================================================================
--- owl.h	(revision 26ad412433e18b39230531a2a518a03bffc1d3d4)
+++ owl.h	(revision 26ad412433e18b39230531a2a518a03bffc1d3d4)
@@ -60,4 +60,6 @@
 typedef void HV;
 #endif
+
+#include "window.h"
 
 #ifdef  GIT_VERSION
@@ -308,4 +310,5 @@
   void *data;		/* determined by mode */
   char *keymap;
+  owl_window *cursor;
 } owl_context;
 
@@ -344,4 +347,5 @@
 
 typedef struct _owl_zwrite {
+  char *cmd;
   char *zwriteline;
   char *class;
@@ -394,4 +398,5 @@
   int lasttruncated;
   int lastdisplayed;
+  owl_window *window;
 } owl_mainwin;
 
@@ -401,6 +406,6 @@
   int topline;
   int rightshift;
-  int winlines, wincols;
-  WINDOW *curswin;
+  owl_window *window;
+  gulong sig_redraw_id;
   void (*onclose_hook) (struct _owl_viewwin *vwin, void *data);
   void *onclose_hook_data;
@@ -408,10 +413,14 @@
   
 typedef struct _owl_popwin {
-  PANEL *borderpanel;
-  PANEL *poppanel;
-  int lines;
-  int cols;
+  owl_window *border;
+  owl_window *content;
   int active;
 } owl_popwin;
+  
+typedef struct _owl_msgwin {
+  char *msg;
+  owl_window *window;
+  gulong redraw_id;
+} owl_msgwin;
 
 typedef struct _owl_messagelist {
@@ -463,4 +472,13 @@
 typedef struct _owl_editwin_excursion owl_editwin_excursion;
 
+typedef struct _owl_mainpanel {
+  owl_window *panel;
+  owl_window *typwin;
+  owl_window *sepwin;
+  owl_window *msgwin;
+  owl_window *recwin;
+  int recwinlines;
+} owl_mainpanel;
+
 typedef struct _owl_keybinding {
   int  *keys;			/* keypress stack */
@@ -476,5 +494,5 @@
   char     *desc;		/* description */
   owl_list  bindings;		/* key bindings */
-  const struct _owl_keymap *submap;	/* submap */
+  const struct _owl_keymap *parent;	/* parent */
   void (*default_fn)(owl_input j);	/* default action (takes a keypress) */
   void (*prealways_fn)(owl_input  j);	/* always called before a keypress is received */
@@ -521,8 +539,4 @@
   short **pairs;
 } owl_colorpair_mgr;
-
-typedef struct _owl_obarray {
-  owl_list strings;
-} owl_obarray;
 
 typedef struct _owl_io_dispatch {
@@ -550,7 +564,10 @@
 } owl_popexec;
 
+typedef struct _OwlGlobalNotifier OwlGlobalNotifier;
+
 typedef struct _owl_global {
   owl_mainwin mw;
   owl_popwin pw;
+  owl_msgwin msgwin;
   owl_history cmdhist;		/* command history */
   owl_history msghist;		/* outgoing message history */
@@ -570,10 +587,8 @@
   owl_messagelist msglist;
   WINDOW *input_pad;
-  PANEL *recpan, *seppan, *msgpan, *typpan;
-  int needrefresh;
+  owl_mainpanel mainpanel;
+  gulong typwin_erase_id;
   int rightshift;
   volatile sig_atomic_t resizepending;
-  int relayoutpending;
-  int recwinlines;
   char *thishost;
   char *homedir;
@@ -599,5 +614,4 @@
   owl_colorpair_mgr cpmgr;
   pid_t newmsgproc_pid;
-  int malloced, freed;
   owl_regex search_re;
   aim_session_t aimsess;
@@ -610,5 +624,5 @@
   char *aim_screenname_for_filters;     /* currently logged in AIM screen name */
   owl_buddylist buddylist;  /* list of logged in AIM buddies */
-  owl_list messagequeue;    /* for queueing up aim and other messages */
+  GQueue *messagequeue;     /* for queueing up aim and other messages */
   owl_dict styledict;       /* global dictionary of available styles */
   char *response;           /* response to the last question asked */
@@ -623,5 +637,4 @@
   int pseudologin_notify;
   struct termios startup_tio;
-  owl_obarray obarray;
   owl_list io_dispatch_list;
   owl_list psa_list;
Index: perl/lib/BarnOwl/Hooks.pm
===================================================================
--- perl/lib/BarnOwl/Hooks.pm	(revision b30c2569225bbfe24cb09819d5ac4dfa509b7f1c)
+++ perl/lib/BarnOwl/Hooks.pm	(revision 3aa05226f2c729940957d231891ed429669e255f)
@@ -79,9 +79,11 @@
 our %EXPORT_TAGS = (all => [@EXPORT_OK]);
 
+use BarnOwl::MainLoopCompatHook;
+
 our $startup = BarnOwl::Hook->new;
 our $shutdown = BarnOwl::Hook->new;
 our $receiveMessage = BarnOwl::Hook->new;
 our $newMessage = BarnOwl::Hook->new;
-our $mainLoop = BarnOwl::Hook->new;
+our $mainLoop = BarnOwl::MainLoopCompatHook->new;
 our $getBuddyList = BarnOwl::Hook->new;
 our $getQuickstart = BarnOwl::Hook->new;
@@ -162,4 +164,5 @@
     }
     
+    $mainLoop->check_owlconf();
     $startup->run(0);
     BarnOwl::startup() if *BarnOwl::startup{CODE};
@@ -186,9 +189,4 @@
     
     BarnOwl::new_msg($m) if *BarnOwl::new_msg{CODE};
-}
-
-sub _mainloop_hook {
-    $mainLoop->run;
-    BarnOwl::mainloop_hook() if *BarnOwl::mainloop_hook{CODE};
 }
 
Index: perl/lib/BarnOwl/MainLoopCompatHook.pm
===================================================================
--- perl/lib/BarnOwl/MainLoopCompatHook.pm	(revision 3aa05226f2c729940957d231891ed429669e255f)
+++ perl/lib/BarnOwl/MainLoopCompatHook.pm	(revision 3aa05226f2c729940957d231891ed429669e255f)
@@ -0,0 +1,64 @@
+use strict;
+use warnings;
+
+package BarnOwl::MainLoopCompatHook;
+
+use BarnOwl;
+use BarnOwl::Hook;
+use BarnOwl::Timer;
+use Scalar::Util qw(weaken);
+
+sub new {
+    my $class = shift;
+    my $hook = BarnOwl::Hook->new;
+    return bless {hook => $hook}, $class;
+}
+
+sub check_owlconf {
+  my $self = shift;
+  $self->_ensure_timer() if (*BarnOwl::mainloop_hook{CODE});
+}
+
+sub run {
+    my $self = shift;
+    return $self->{hook}->run(@_);
+}
+
+sub add {
+    my $self = shift;
+    $self->_ensure_timer();
+    $self->{hook}->add(@_);
+}
+
+sub clear {
+    my $self = shift;
+    $self->_kill_timer() unless *BarnOwl::mainloop_hook{CODE};
+    $self->{hook}->clear();
+}
+
+sub _ensure_timer {
+  my $self = shift;
+  BarnOwl::debug("Enabling backwards-compatibility \"main loop\" hook");
+  my $weak = $self;
+  weaken($weak);
+  unless ($self->{timer}) {
+    $self->{timer} = BarnOwl::Timer->new( {
+        after => 0,
+        interval => 1,
+        cb => sub {
+          $weak->run if $weak;
+          BarnOwl::mainloop_hook() if *BarnOwl::mainloop_hook{CODE};
+        },
+      } );
+  }
+}
+
+sub _kill_timer {
+  my $self = shift;
+  if ($self->{timer}) {
+    $self->{timer}->stop;
+    undef $self->{timer};
+  }
+}
+
+1;
Index: perl/lib/BarnOwl/Message/Generic.pm
===================================================================
--- perl/lib/BarnOwl/Message/Generic.pm	(revision ee183beb7b1f02ed315026b95d9d72e0913908bd)
+++ perl/lib/BarnOwl/Message/Generic.pm	(revision 5d1324f0b0edf2200def6c9e613517404a515c42)
@@ -6,4 +6,6 @@
 use base qw( BarnOwl::Message );
 
+sub body { "" }
+
 
 1;
Index: perl/lib/BarnOwl/Style/Default.pm
===================================================================
--- perl/lib/BarnOwl/Style/Default.pm	(revision 0fe69d275af9f0d0a83259b7494566c102b17902)
+++ perl/lib/BarnOwl/Style/Default.pm	(revision 08544e017df1dc0ff7d3735a4c93705de1007707)
@@ -32,4 +32,9 @@
     my $m = shift;
     return $m->is_personal && $m->direction eq "in";
+}
+
+sub maybe {
+    my $x = shift;
+    return defined($x) ? $x : "";
 }
 
@@ -99,5 +104,5 @@
             $header .= ' / ' . $self->humanize($m->subcontext, 1);
         }
-        $header .= ' / @b{' . $m->pretty_sender . '}';
+        $header .= ' / @b{' . maybe($m->pretty_sender) . '}';
     }
 
Index: perl/lib/BarnOwl/Timer.pm
===================================================================
--- perl/lib/BarnOwl/Timer.pm	(revision ee183beb7b1f02ed315026b95d9d72e0913908bd)
+++ perl/lib/BarnOwl/Timer.pm	(revision 8d16e58dfb7d32aaae1378039fbbde334d5ab660)
@@ -21,4 +21,12 @@
 }
 
+sub stop {
+    my $self = shift;
+    if(defined($self->{timer})) {
+        BarnOwl::Internal::remove_timer($self->{timer});
+        undef $self->{timer};
+    }
+}
+
 sub do_callback {
     my $self = shift;
@@ -28,7 +36,5 @@
 sub DESTROY {
     my $self = shift;
-    if(defined($self->{timer})) {
-        BarnOwl::Internal::remove_timer($self->{timer});
-    }
+    $self->stop;
 }
 
Index: perl/modules/Jabber/lib/BarnOwl/Message/Jabber.pm
===================================================================
--- perl/modules/Jabber/lib/BarnOwl/Message/Jabber.pm	(revision c854e74bbef86a0225ca76c3945b9f9485b829db)
+++ perl/modules/Jabber/lib/BarnOwl/Message/Jabber.pm	(revision 2f25537acb67e620a5be5052f5c2f9a1e5fe3abd)
@@ -20,4 +20,5 @@
 sub to { shift->{to} };
 sub room { shift->{room} };
+sub nick { shift->{nick} };
 sub subject { shift->{subject} };
 sub status { shift->{status} }
@@ -41,4 +42,11 @@
 sub long_sender {
     my $self = shift;
+    if ($self->jtype eq 'groupchat' && $self->nick) {
+        my $from_jid = Net::Jabber::JID->new($self->from);
+        if ($from_jid->GetJID('base') eq $self->room &&
+            $from_jid->GetResource() eq $self->nick) {
+            return $self->nick;
+        }
+    }
     return $self->from;
 }
@@ -77,7 +85,15 @@
     } elsif ($self->jtype eq 'groupchat') {
         my $room = $self->room;
-        $filter = "jabber-room-$room";
-        BarnOwl::command(qw[filter], $filter,
-                         qw[type ^jabber$ and room], "^\Q$room\E\$");
+        if ($inst) {
+            my $subject = $self->subject;
+            $filter = "jabber-room-$room-subject-$subject";
+            BarnOwl::command(qw[filter], $filter,
+                             qw[type ^jabber$ and room], "^\Q$room\E\$",
+                             qw[and subject], "^\Q$subject\E\$");
+        } else {
+            $filter = "jabber-room-$room";
+            BarnOwl::command(qw[filter], $filter,
+                             qw[type ^jabber$ and room], "^\Q$room\E\$");
+        }
         return $filter;
     } elsif ($self->login ne 'none') {
Index: perl/modules/Jabber/lib/BarnOwl/Module/Jabber.pm
===================================================================
--- perl/modules/Jabber/lib/BarnOwl/Module/Jabber.pm	(revision 87894109aeedcc8ba50bd3b983c0d9558f1e6b32)
+++ perl/modules/Jabber/lib/BarnOwl/Module/Jabber.pm	(revision 2f25537acb67e620a5be5052f5c2f9a1e5fe3abd)
@@ -285,5 +285,5 @@
         {
             summary => "Send a Jabber Message",
-            usage   => "jwrite <jid> [-t <thread>] [-s <subject>] [-a <account>]"
+            usage   => "jwrite <jid> [-t <thread>] [-s <subject>] [-a <account>] [-m <message>]"
         }
     );
@@ -308,5 +308,6 @@
             description => "jmuc sends Jabber commands related to MUC.\n\n"
               . "The following commands are available\n\n"
-              . "join <muc>  Join a MUC.\n\n"
+              . "join <muc>[/<nick>]\n"
+              . "            Join a MUC (with a given nickname, or otherwise your JID).\n\n"
               . "part <muc>  Part a MUC.\n"
               . "            The MUC is taken from the current message if not supplied.\n\n"
@@ -377,6 +378,5 @@
         my $cjidStr = $conn->baseJIDExists($jidStr);
         if ($cjidStr) {
-            BarnOwl::error("Already logged in as $cjidStr.");
-            return;
+            die("Already logged in as $cjidStr.\n");
         }
     }
@@ -387,11 +387,9 @@
 
     if ( !$uid || !$componentname ) {
-        BarnOwl::error("usage: $cmd JID");
-        return;
+        die("usage: $cmd JID\n");
     }
 
     if ( $conn->jidActive($jidStr) ) {
-        BarnOwl::error("Already logged in as $jidStr.");
-        return;
+        die("Already logged in as $jidStr.\n");
     } elsif ($conn->jidExists($jidStr)) {
         return $conn->tryReconnect($jidStr, 1);
@@ -526,6 +524,5 @@
 sub cmd_jlist {
     if ( !( scalar $conn->getJIDs() ) ) {
-        BarnOwl::error("You are not logged in to Jabber.");
-        return;
+        die("You are not logged in to Jabber.\n");
     }
     BarnOwl::popless_ztext( onGetBuddyList() );
@@ -534,6 +531,5 @@
 sub cmd_jwrite {
     if ( !$conn->connected() ) {
-        BarnOwl::error("You are not logged in to Jabber.");
-        return;
+        die("You are not logged in to Jabber.\n");
     }
 
@@ -543,4 +539,5 @@
     my $jwrite_thread  = "";
     my $jwrite_subject = "";
+    my $jwrite_body;
     my ($to, $from);
     my $jwrite_type    = "chat";
@@ -554,12 +551,11 @@
         'subject=s' => \$jwrite_subject,
         'account=s' => \$from,
-        'id=s'     =>  \$jwrite_sid,
+        'id=s'      => \$jwrite_sid,
+        'message=s' => \$jwrite_body,
     ) or die("Usage: jwrite <jid> [-t <thread>] [-s <subject>] [-a <account>]\n");
     $jwrite_type = 'groupchat' if $gc;
 
     if ( scalar @ARGV != 1 ) {
-        BarnOwl::error(
-            "Usage: jwrite <jid> [-t <thread>] [-s <subject>] [-a <account>]");
-        return;
+        die("Usage: jwrite <jid> [-t <thread>] [-s <subject>] [-a <account>]\n");
     }
     else {
@@ -570,5 +566,5 @@
 
     unless(scalar @candidates) {
-        die("Unable to resolve JID $to");
+        die("Unable to resolve JID $to\n");
     }
 
@@ -577,7 +573,7 @@
     unless(scalar @candidates) {
         if(!$from) {
-            die("You must specify an account with -a");
+            die("You must specify an account with -a\n");
         } else {
-            die("Unable to resolve account $from");
+            die("Unable to resolve account $from\n");
         }
     }
@@ -594,4 +590,9 @@
         type    => $jwrite_type
     };
+
+    if (defined($jwrite_body)) {
+        process_owl_jwrite($jwrite_body);
+        return;
+    }
 
     if(scalar @candidates > 1) {
@@ -631,6 +632,5 @@
     my $func = $jmuc_commands{$cmd};
     if ( !$func ) {
-        BarnOwl::error("jmuc: Unknown command: $cmd");
-        return;
+        die("jmuc: Unknown command: $cmd\n");
     }
 
@@ -654,5 +654,5 @@
         }
         else {
-            BarnOwl::error('You must specify an account with -a <jid>');
+            die("You must specify an account with -a <jid>\n");
         }
         return $func->( $jid, $muc, @ARGV );
@@ -667,5 +667,5 @@
 
     $muc = shift @ARGV
-      or die("Usage: jmuc join <muc> [-p <password>] [-a <account>]");
+      or die("Usage: jmuc join <muc> [-p <password>] [-a <account>]\n");
 
     die("Error: Must specify a fully-qualified MUC name (e.g. barnowl\@conference.mit.edu)\n")
@@ -680,5 +680,5 @@
                                                    MaxChars => 0
                                                   });
-    $completion_jids{$muc} = 1;
+    $completion_jids{$muc->GetJID('base')} = 1;
     return;
 }
@@ -688,10 +688,10 @@
 
     $muc = shift @args if scalar @args;
-    die("Usage: jmuc part [<muc>] [-a <account>]") unless $muc;
+    die("Usage: jmuc part [<muc>] [-a <account>]\n") unless $muc;
 
     if($conn->getConnectionFromJID($jid)->MUCLeave(JID => $muc)) {
         queue_admin_msg("$jid has left $muc.");
     } else {
-        die("Error: Not joined to $muc");
+        die("Error: Not joined to $muc\n");
     }
 }
@@ -703,5 +703,5 @@
     $muc = shift @args if scalar @args;
 
-    die('Usage: jmuc invite <jid> [<muc>] [-a <account>]')
+    die("Usage: jmuc invite <jid> [<muc>] [-a <account>]\n")
       unless $muc && $invite_jid;
 
@@ -718,5 +718,5 @@
     my ( $jid, $muc, @args ) = @_;
     $muc = shift @args if scalar @args;
-    die("Usage: jmuc configure [<muc>]") unless $muc;
+    die("Usage: jmuc configure [<muc>]\n") unless $muc;
     my $iq = Net::Jabber::IQ->new();
     $iq->SetTo($muc);
@@ -759,5 +759,5 @@
 
     $muc = shift @args if scalar @args;
-    die("Usage: jmuc presence [<muc>]") unless $muc;
+    die("Usage: jmuc presence [<muc>]\n") unless $muc;
 
     if ($muc eq '-a') {
@@ -774,5 +774,5 @@
     else {
         my $m = $conn->getConnectionFromJID($jid)->FindMUC(jid => $muc);
-        die("No such muc: $muc") unless $m;
+        die("No such muc: $muc\n") unless $m;
         BarnOwl::popless_ztext(jmuc_presence_single($m));
     }
@@ -801,6 +801,5 @@
     my $func = $jroster_commands{$cmd};
     if ( !$func ) {
-        BarnOwl::error("jroster: Unknown command: $cmd");
-        return;
+        die("jroster: Unknown command: $cmd\n");
     }
 
@@ -825,5 +824,5 @@
         }
         else {
-            BarnOwl::error('You must specify an account with -a <jid>');
+            die("You must specify an account with -a <jid>\n");
         }
         return $func->( $jid, $name, \@groups, $purgeGroups,  @ARGV );
@@ -849,5 +848,5 @@
     }
     else {
-        BarnOwl::error('You must specify an account with -a <jid>');
+        die("You must specify an account with -a <jid>\n");
     }
 
@@ -1234,4 +1233,13 @@
         $completion_jids{$room} = 1;
 
+        my $muc;
+        if ($dir eq 'in') {
+            my $connection = $conn->getConnectionFromSid($props{sid});
+            $muc = $connection->FindMUC(jid => $from);
+        } else {
+            my $connection = $conn->getConnectionFromJID($props{from});
+            $muc = $connection->FindMUC(jid => $to);
+        }
+        $props{from} = $muc->GetFullJID($from) || $props{from};
         $props{sender} = $nick || $room;
         $props{recipient} = $room;
@@ -1331,5 +1339,5 @@
         return $givenJIDStr if ($conn->jidExists($givenJIDStr) );
         return resolveConnectedJID($givenJID->GetJID('base')) if $loose;
-        die("Invalid account: $givenJIDStr");
+        die("Invalid account: $givenJIDStr\n");
     }
 
@@ -1384,5 +1392,5 @@
         # Not one of ours.
         else {
-            die("Invalid account: $givenJIDStr");
+            die("Invalid account: $givenJIDStr\n");
         }
 
@@ -1430,5 +1438,5 @@
     if($from) {
         $from_jid = resolveConnectedJID($from, 1);
-        die("Unable to resolve account $from") unless $from_jid;
+        die("Unable to resolve account $from\n") unless $from_jid;
         $to_jid = resolveDestJID($to, $from_jid);
         push @matches, [$from_jid, $to_jid] if $to_jid;
Index: perl/modules/Makefile.am
===================================================================
--- perl/modules/Makefile.am	(revision 636de2ae63b680cbfe7dc22e65fd61df39edb9bc)
+++ perl/modules/Makefile.am	(revision 1fd469d44fe3282a62be10576b6042e90eb43dbf)
@@ -1,3 +1,3 @@
-MODULES = Jabber IRC WordWrap
+MODULES = Jabber IRC WordWrap Twitter
 
 EXTRA_DIST = $(MODULES:=/Makefile.PL) $(MODULES:=/inc) $(MODULES:=/lib)
Index: perl/modules/Twitter/.gitignore
===================================================================
--- perl/modules/Twitter/.gitignore	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
+++ perl/modules/Twitter/.gitignore	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
@@ -0,0 +1,7 @@
+*~
+blib/
+META.yml
+Makefile
+Twitter.par
+pm_to_blib
+inc/.author
Index: perl/modules/Twitter/.hgignore
===================================================================
--- perl/modules/Twitter/.hgignore	(revision None)
+++ perl/modules/Twitter/.hgignore	(revision None)
@@ -0,0 +1,8 @@
+syntax: glob
+*~
+*.par
+inc/
+pm_to_blib
+Makefile
+META.yml
+blib/
Index: perl/modules/Twitter/Makefile.PL
===================================================================
--- perl/modules/Twitter/Makefile.PL	(revision None)
+++ perl/modules/Twitter/Makefile.PL	(revision None)
@@ -0,0 +1,10 @@
+use strict;
+use warnings;
+
+use inc::Module::Install;
+
+requires('Net::Twitter::Lite');
+
+barnowl_module('Twitter');
+
+WriteAll;
Index: perl/modules/Twitter/README
===================================================================
--- perl/modules/Twitter/README	(revision 5756999d4ea6b2438896951f83e962d1fc349a75)
+++ perl/modules/Twitter/README	(revision 5756999d4ea6b2438896951f83e962d1fc349a75)
@@ -0,0 +1,118 @@
+NAME
+  Twitter.par - Twitter support for BarnOwl
+
+DESCRIPTION
+  The Twitter module implements Twitter support for BarnOwl. It also
+  supports automatic mirroring of some subset of Zephyrs you send to
+  Twitter.
+
+SYNOPSIS
+  (1) Configure your Twitter credentials in ~/.owl/twitter in the
+  following format:
+----------8<----------cut here----------8<----------
+{
+ "user":"nelhage",
+ "password":"sekrit"
+}
+----------8<----------end cut ----------8<----------
+  (2) Run ":reload-module Twitter" inside BarnOwl
+  (3) Start receiving tweets in BarnOwl!
+      You can send tweets with the ":twitter" command.
+
+MIRRORING ZEPHYRS TO TWITTER
+  If you set the twitter:class, twitter:instance, and twitter:opcode
+  BarnOwl variables, BarnOwl will resend zephyrs matching those fields
+  as tweets coming from you.
+
+  By default, these default to -c $YOUR_USERNAME -i status -O twitter.
+  Setting twitter:opcode to "" will cause "normal" (i.e. no opcode)
+  messages to that class/instance to be mirrored. A value of '*' works
+  as a "match-anything" wildcard for any of the fields.
+
+POLLING
+  Twitter.par polls for normal messages once a minute, and direct
+  messages every other minute. To disable polling, you can unset the
+  'twitter:poll' variable in BarnOwl.
+
+IDENTI.CA SUPPORT
+  Twitter.par supports other Twitter-compatible microblogging services,
+  like identi.ca. To enable this, set the 'service' parameter in your
+  config hash to the API URL of your service. You may also need to set
+  'apihost' and 'apirealm'. See Net::Twitter or your blogging service's
+  documentation for more information.
+
+MULTIPLE ACCOUNTS
+
+  Twitter.par also supports using multiple Twitter and
+  Twitter-compatible microblogging accounts from the same instance.
+  To enable this, add additional hashes to your ~/.owl/twitter file as
+  a JSON list, e.g.
+----------8<----------cut here----------8<----------
+[
+  {
+   "account_nickname": "twitter",
+   "user": "nelhage",
+   "password": "sekrit",
+   "default": true,
+   "publish_tweets": true
+  },
+  {
+   "account_nickname": "identica",
+   "service": "http://identi.ca/api",
+   "user": "nelhage",
+   "password": "sekriter",
+   "publish_tweets": true
+  }
+  {
+   "account_nickname": "sipb",
+   "user": "sipb",
+   "password":"m0ars3krit",
+   "poll_for_tweets": false,
+   "poll_for_dms": false,
+  }
+]
+----------8<----------end cut ----------8<----------
+
+Outgoing tweets sent via ":twitter" without an explicit sender, or
+mirrored from zephyr, go to every account with "publish_tweets" set.
+
+Outgoing direct messages and @-replies without an explicit sender come
+from whichever account has "default" set. If no account has "default",
+the first account is assumed to have "default" set. It is an error to
+have more than one account with "default" set to true.
+
+There are several account-specific parameters that control the
+behavior of Twitter.par when using multiple accounts:
+
+* account_nickname (string, required if multiple accounts are in use)
+    Specify a short name by which you can refer to the account, eg.
+    "identica" (eg. :twitter-direct nelhage identica would send a
+    direct message to @nelhage on identi.ca from your account
+    nicknamed "identica").
+
+* default (boolean, default false)
+    If true, @-replies or direct messages you send without specifying
+    an account will be sent using this account.  If no account has
+    this parameter, such messages will be sent using the first account
+    listed.
+
+* poll_for_tweets (boolean, default true)
+    If true, tweets sent by users this account follows will be
+    displayed in your BarnOwl message list.
+
+* poll_for_dms (boolean, default true)
+    If true, direct messages sent to this account will be displayed in
+    your BarnOwl message list.
+
+* publish_tweets (boolean, default false)
+    If true, tweets you send without specifying an account (either
+    with the :twitter command or mirrored from a zephyr class) will be
+    published to this account.
+
+    If only one account is specified, it defaults to publish_tweets =
+    true.
+
+* show_mentions (boolean, default true)
+    If true, BarnOwl will show all tweets that mention this account's
+    username, regardless of whether or not this account is following
+    the sender. Has no effect if poll_for_tweets is false.
Index: perl/modules/Twitter/inc/Module/Install.pm
===================================================================
--- perl/modules/Twitter/inc/Module/Install.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
+++ perl/modules/Twitter/inc/Module/Install.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
@@ -0,0 +1,430 @@
+#line 1
+package Module::Install;
+
+# For any maintainers:
+# The load order for Module::Install is a bit magic.
+# It goes something like this...
+#
+# IF ( host has Module::Install installed, creating author mode ) {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to installed version of inc::Module::Install
+#     3. The installed version of inc::Module::Install loads
+#     4. inc::Module::Install calls "require Module::Install"
+#     5. The ./inc/ version of Module::Install loads
+# } ELSE {
+#     1. Makefile.PL calls "use inc::Module::Install"
+#     2. $INC{inc/Module/Install.pm} set to ./inc/ version of Module::Install
+#     3. The ./inc/ version of Module::Install loads
+# }
+
+use 5.005;
+use strict 'vars';
+
+use vars qw{$VERSION $MAIN};
+BEGIN {
+	# All Module::Install core packages now require synchronised versions.
+	# This will be used to ensure we don't accidentally load old or
+	# different versions of modules.
+	# This is not enforced yet, but will be some time in the next few
+	# releases once we can make sure it won't clash with custom
+	# Module::Install extensions.
+	$VERSION = '0.91';
+
+	# Storage for the pseudo-singleton
+	$MAIN    = undef;
+
+	*inc::Module::Install::VERSION = *VERSION;
+	@inc::Module::Install::ISA     = __PACKAGE__;
+
+}
+
+
+
+
+
+# Whether or not inc::Module::Install is actually loaded, the
+# $INC{inc/Module/Install.pm} is what will still get set as long as
+# the caller loaded module this in the documented manner.
+# If not set, the caller may NOT have loaded the bundled version, and thus
+# they may not have a MI version that works with the Makefile.PL. This would
+# result in false errors or unexpected behaviour. And we don't want that.
+my $file = join( '/', 'inc', split /::/, __PACKAGE__ ) . '.pm';
+unless ( $INC{$file} ) { die <<"END_DIE" }
+
+Please invoke ${\__PACKAGE__} with:
+
+	use inc::${\__PACKAGE__};
+
+not:
+
+	use ${\__PACKAGE__};
+
+END_DIE
+
+
+
+
+
+# If the script that is loading Module::Install is from the future,
+# then make will detect this and cause it to re-run over and over
+# again. This is bad. Rather than taking action to touch it (which
+# is unreliable on some platforms and requires write permissions)
+# for now we should catch this and refuse to run.
+if ( -f $0 ) {
+	my $s = (stat($0))[9];
+
+	# If the modification time is only slightly in the future,
+	# sleep briefly to remove the problem.
+	my $a = $s - time;
+	if ( $a > 0 and $a < 5 ) { sleep 5 }
+
+	# Too far in the future, throw an error.
+	my $t = time;
+	if ( $s > $t ) { die <<"END_DIE" }
+
+Your installer $0 has a modification time in the future ($s > $t).
+
+This is known to create infinite loops in make.
+
+Please correct this, then run $0 again.
+
+END_DIE
+}
+
+
+
+
+
+# Build.PL was formerly supported, but no longer is due to excessive
+# difficulty in implementing every single feature twice.
+if ( $0 =~ /Build.PL$/i ) { die <<"END_DIE" }
+
+Module::Install no longer supports Build.PL.
+
+It was impossible to maintain duel backends, and has been deprecated.
+
+Please remove all Build.PL files and only use the Makefile.PL installer.
+
+END_DIE
+
+
+
+
+
+# To save some more typing in Module::Install installers, every...
+# use inc::Module::Install
+# ...also acts as an implicit use strict.
+$^H |= strict::bits(qw(refs subs vars));
+
+
+
+
+
+use Cwd        ();
+use File::Find ();
+use File::Path ();
+use FindBin;
+
+sub autoload {
+	my $self = shift;
+	my $who  = $self->_caller;
+	my $cwd  = Cwd::cwd();
+	my $sym  = "${who}::AUTOLOAD";
+	$sym->{$cwd} = sub {
+		my $pwd = Cwd::cwd();
+		if ( my $code = $sym->{$pwd} ) {
+			# Delegate back to parent dirs
+			goto &$code unless $cwd eq $pwd;
+		}
+		$$sym =~ /([^:]+)$/ or die "Cannot autoload $who - $sym";
+		my $method = $1;
+		if ( uc($method) eq $method ) {
+			# Do nothing
+			return;
+		} elsif ( $method =~ /^_/ and $self->can($method) ) {
+			# Dispatch to the root M:I class
+			return $self->$method(@_);
+		}
+
+		# Dispatch to the appropriate plugin
+		unshift @_, ( $self, $1 );
+		goto &{$self->can('call')};
+	};
+}
+
+sub import {
+	my $class = shift;
+	my $self  = $class->new(@_);
+	my $who   = $self->_caller;
+
+	unless ( -f $self->{file} ) {
+		require "$self->{path}/$self->{dispatch}.pm";
+		File::Path::mkpath("$self->{prefix}/$self->{author}");
+		$self->{admin} = "$self->{name}::$self->{dispatch}"->new( _top => $self );
+		$self->{admin}->init;
+		@_ = ($class, _self => $self);
+		goto &{"$self->{name}::import"};
+	}
+
+	*{"${who}::AUTOLOAD"} = $self->autoload;
+	$self->preload;
+
+	# Unregister loader and worker packages so subdirs can use them again
+	delete $INC{"$self->{file}"};
+	delete $INC{"$self->{path}.pm"};
+
+	# Save to the singleton
+	$MAIN = $self;
+
+	return 1;
+}
+
+sub preload {
+	my $self = shift;
+	unless ( $self->{extensions} ) {
+		$self->load_extensions(
+			"$self->{prefix}/$self->{path}", $self
+		);
+	}
+
+	my @exts = @{$self->{extensions}};
+	unless ( @exts ) {
+		@exts = $self->{admin}->load_all_extensions;
+	}
+
+	my %seen;
+	foreach my $obj ( @exts ) {
+		while (my ($method, $glob) = each %{ref($obj) . '::'}) {
+			next unless $obj->can($method);
+			next if $method =~ /^_/;
+			next if $method eq uc($method);
+			$seen{$method}++;
+		}
+	}
+
+	my $who = $self->_caller;
+	foreach my $name ( sort keys %seen ) {
+		*{"${who}::$name"} = sub {
+			${"${who}::AUTOLOAD"} = "${who}::$name";
+			goto &{"${who}::AUTOLOAD"};
+		};
+	}
+}
+
+sub new {
+	my ($class, %args) = @_;
+
+	# ignore the prefix on extension modules built from top level.
+	my $base_path = Cwd::abs_path($FindBin::Bin);
+	unless ( Cwd::abs_path(Cwd::cwd()) eq $base_path ) {
+		delete $args{prefix};
+	}
+
+	return $args{_self} if $args{_self};
+
+	$args{dispatch} ||= 'Admin';
+	$args{prefix}   ||= 'inc';
+	$args{author}   ||= ($^O eq 'VMS' ? '_author' : '.author');
+	$args{bundle}   ||= 'inc/BUNDLES';
+	$args{base}     ||= $base_path;
+	$class =~ s/^\Q$args{prefix}\E:://;
+	$args{name}     ||= $class;
+	$args{version}  ||= $class->VERSION;
+	unless ( $args{path} ) {
+		$args{path}  = $args{name};
+		$args{path}  =~ s!::!/!g;
+	}
+	$args{file}     ||= "$args{base}/$args{prefix}/$args{path}.pm";
+	$args{wrote}      = 0;
+
+	bless( \%args, $class );
+}
+
+sub call {
+	my ($self, $method) = @_;
+	my $obj = $self->load($method) or return;
+        splice(@_, 0, 2, $obj);
+	goto &{$obj->can($method)};
+}
+
+sub load {
+	my ($self, $method) = @_;
+
+	$self->load_extensions(
+		"$self->{prefix}/$self->{path}", $self
+	) unless $self->{extensions};
+
+	foreach my $obj (@{$self->{extensions}}) {
+		return $obj if $obj->can($method);
+	}
+
+	my $admin = $self->{admin} or die <<"END_DIE";
+The '$method' method does not exist in the '$self->{prefix}' path!
+Please remove the '$self->{prefix}' directory and run $0 again to load it.
+END_DIE
+
+	my $obj = $admin->load($method, 1);
+	push @{$self->{extensions}}, $obj;
+
+	$obj;
+}
+
+sub load_extensions {
+	my ($self, $path, $top) = @_;
+
+	unless ( grep { ! ref $_ and lc $_ eq lc $self->{prefix} } @INC ) {
+		unshift @INC, $self->{prefix};
+	}
+
+	foreach my $rv ( $self->find_extensions($path) ) {
+		my ($file, $pkg) = @{$rv};
+		next if $self->{pathnames}{$pkg};
+
+		local $@;
+		my $new = eval { require $file; $pkg->can('new') };
+		unless ( $new ) {
+			warn $@ if $@;
+			next;
+		}
+		$self->{pathnames}{$pkg} = delete $INC{$file};
+		push @{$self->{extensions}}, &{$new}($pkg, _top => $top );
+	}
+
+	$self->{extensions} ||= [];
+}
+
+sub find_extensions {
+	my ($self, $path) = @_;
+
+	my @found;
+	File::Find::find( sub {
+		my $file = $File::Find::name;
+		return unless $file =~ m!^\Q$path\E/(.+)\.pm\Z!is;
+		my $subpath = $1;
+		return if lc($subpath) eq lc($self->{dispatch});
+
+		$file = "$self->{path}/$subpath.pm";
+		my $pkg = "$self->{name}::$subpath";
+		$pkg =~ s!/!::!g;
+
+		# If we have a mixed-case package name, assume case has been preserved
+		# correctly.  Otherwise, root through the file to locate the case-preserved
+		# version of the package name.
+		if ( $subpath eq lc($subpath) || $subpath eq uc($subpath) ) {
+			my $content = Module::Install::_read($subpath . '.pm');
+			my $in_pod  = 0;
+			foreach ( split //, $content ) {
+				$in_pod = 1 if /^=\w/;
+				$in_pod = 0 if /^=cut/;
+				next if ($in_pod || /^=cut/);  # skip pod text
+				next if /^\s*#/;               # and comments
+				if ( m/^\s*package\s+($pkg)\s*;/i ) {
+					$pkg = $1;
+					last;
+				}
+			}
+		}
+
+		push @found, [ $file, $pkg ];
+	}, $path ) if -d $path;
+
+	@found;
+}
+
+
+
+
+
+#####################################################################
+# Common Utility Functions
+
+sub _caller {
+	my $depth = 0;
+	my $call  = caller($depth);
+	while ( $call eq __PACKAGE__ ) {
+		$depth++;
+		$call = caller($depth);
+	}
+	return $call;
+}
+
+sub _read {
+	local *FH;
+	if ( $] >= 5.006 ) {
+		open( FH, '<', $_[0] ) or die "open($_[0]): $!";
+	} else {
+		open( FH, "< $_[0]"  ) or die "open($_[0]): $!";
+	}
+	my $string = do { local $/; <FH> };
+	close FH or die "close($_[0]): $!";
+	return $string;
+}
+
+sub _readperl {
+	my $string = Module::Install::_read($_[0]);
+	$string =~ s/(?:\015{1,2}\012|\015|\012)/\n/sg;
+	$string =~ s/(\n)\n*__(?:DATA|END)__\b.*\z/$1/s;
+	$string =~ s/\n\n=\w+.+?\n\n=cut\b.+?\n+/\n\n/sg;
+	return $string;
+}
+
+sub _readpod {
+	my $string = Module::Install::_read($_[0]);
+	$string =~ s/(?:\015{1,2}\012|\015|\012)/\n/sg;
+	return $string if $_[0] =~ /\.pod\z/;
+	$string =~ s/(^|\n=cut\b.+?\n+)[^=\s].+?\n(\n=\w+|\z)/$1$2/sg;
+	$string =~ s/\n*=pod\b[^\n]*\n+/\n\n/sg;
+	$string =~ s/\n*=cut\b[^\n]*\n+/\n\n/sg;
+	$string =~ s/^\n+//s;
+	return $string;
+}
+
+sub _write {
+	local *FH;
+	if ( $] >= 5.006 ) {
+		open( FH, '>', $_[0] ) or die "open($_[0]): $!";
+	} else {
+		open( FH, "> $_[0]"  ) or die "open($_[0]): $!";
+	}
+	foreach ( 1 .. $#_ ) {
+		print FH $_[$_] or die "print($_[0]): $!";
+	}
+	close FH or die "close($_[0]): $!";
+}
+
+# _version is for processing module versions (eg, 1.03_05) not
+# Perl versions (eg, 5.8.1).
+sub _version ($) {
+	my $s = shift || 0;
+	my $d =()= $s =~ /(\.)/g;
+	if ( $d >= 2 ) {
+		# Normalise multipart versions
+		$s =~ s/(\.)(\d{1,3})/sprintf("$1%03d",$2)/eg;
+	}
+	$s =~ s/^(\d+)\.?//;
+	my $l = $1 || 0;
+	my @v = map {
+		$_ . '0' x (3 - length $_)
+	} $s =~ /(\d{1,3})\D?/g;
+	$l = $l . '.' . join '', @v if @v;
+	return $l + 0;
+}
+
+sub _cmp ($$) {
+	_version($_[0]) <=> _version($_[1]);
+}
+
+# Cloned from Params::Util::_CLASS
+sub _CLASS ($) {
+	(
+		defined $_[0]
+		and
+		! ref $_[0]
+		and
+		$_[0] =~ m/^[^\W\d]\w*(?:::\w+)*\z/s
+	) ? $_[0] : undef;
+}
+
+1;
+
+# Copyright 2008 - 2009 Adam Kennedy.
Index: perl/modules/Twitter/inc/Module/Install/BarnOwl.pm
===================================================================
--- perl/modules/Twitter/inc/Module/Install/BarnOwl.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
+++ perl/modules/Twitter/inc/Module/Install/BarnOwl.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
@@ -0,0 +1,31 @@
+#line 1
+use warnings;
+use strict;
+
+#line 32
+
+package Module::Install::BarnOwl;
+
+use base qw(Module::Install::Base);
+
+sub barnowl_module {
+    my $self = shift;
+    my $name = ucfirst shift;
+    my $class = ref $self;
+
+    $self->name("BarnOwl-Module-$name");
+    $self->all_from("lib/BarnOwl/Module/$name.pm");
+
+    $self->postamble(<<"END_MAKEFILE");
+
+# --- $class section:
+
+$name.par: pm_to_blib
+\tcd blib; zip -q ../$name.par -r arch lib
+
+END_MAKEFILE
+}
+
+#line 60
+
+1;
Index: perl/modules/Twitter/inc/Module/Install/Base.pm
===================================================================
--- perl/modules/Twitter/inc/Module/Install/Base.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
+++ perl/modules/Twitter/inc/Module/Install/Base.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
@@ -0,0 +1,78 @@
+#line 1
+package Module::Install::Base;
+
+use strict 'vars';
+use vars qw{$VERSION};
+BEGIN {
+	$VERSION = '0.91';
+}
+
+# Suspend handler for "redefined" warnings
+BEGIN {
+	my $w = $SIG{__WARN__};
+	$SIG{__WARN__} = sub { $w };
+}
+
+#line 42
+
+sub new {
+	my $class = shift;
+	unless ( defined &{"${class}::call"} ) {
+		*{"${class}::call"} = sub { shift->_top->call(@_) };
+	}
+	unless ( defined &{"${class}::load"} ) {
+		*{"${class}::load"} = sub { shift->_top->load(@_) };
+	}
+	bless { @_ }, $class;
+}
+
+#line 61
+
+sub AUTOLOAD {
+	local $@;
+	my $func = eval { shift->_top->autoload } or return;
+	goto &$func;
+}
+
+#line 75
+
+sub _top {
+	$_[0]->{_top};
+}
+
+#line 90
+
+sub admin {
+	$_[0]->_top->{admin}
+	or
+	Module::Install::Base::FakeAdmin->new;
+}
+
+#line 106
+
+sub is_admin {
+	$_[0]->admin->VERSION;
+}
+
+sub DESTROY {}
+
+package Module::Install::Base::FakeAdmin;
+
+my $fake;
+
+sub new {
+	$fake ||= bless(\@_, $_[0]);
+}
+
+sub AUTOLOAD {}
+
+sub DESTROY {}
+
+# Restore warning handler
+BEGIN {
+	$SIG{__WARN__} = $SIG{__WARN__}->();
+}
+
+1;
+
+#line 154
Index: perl/modules/Twitter/inc/Module/Install/Can.pm
===================================================================
--- perl/modules/Twitter/inc/Module/Install/Can.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
+++ perl/modules/Twitter/inc/Module/Install/Can.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
@@ -0,0 +1,81 @@
+#line 1
+package Module::Install::Can;
+
+use strict;
+use Config                ();
+use File::Spec            ();
+use ExtUtils::MakeMaker   ();
+use Module::Install::Base ();
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '0.91';
+	@ISA     = 'Module::Install::Base';
+	$ISCORE  = 1;
+}
+
+# check if we can load some module
+### Upgrade this to not have to load the module if possible
+sub can_use {
+	my ($self, $mod, $ver) = @_;
+	$mod =~ s{::|\\}{/}g;
+	$mod .= '.pm' unless $mod =~ /\.pm$/i;
+
+	my $pkg = $mod;
+	$pkg =~ s{/}{::}g;
+	$pkg =~ s{\.pm$}{}i;
+
+	local $@;
+	eval { require $mod; $pkg->VERSION($ver || 0); 1 };
+}
+
+# check if we can run some command
+sub can_run {
+	my ($self, $cmd) = @_;
+
+	my $_cmd = $cmd;
+	return $_cmd if (-x $_cmd or $_cmd = MM->maybe_command($_cmd));
+
+	for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), '.') {
+		next if $dir eq '';
+		my $abs = File::Spec->catfile($dir, $_[1]);
+		return $abs if (-x $abs or $abs = MM->maybe_command($abs));
+	}
+
+	return;
+}
+
+# can we locate a (the) C compiler
+sub can_cc {
+	my $self   = shift;
+	my @chunks = split(/ /, $Config::Config{cc}) or return;
+
+	# $Config{cc} may contain args; try to find out the program part
+	while (@chunks) {
+		return $self->can_run("@chunks") || (pop(@chunks), next);
+	}
+
+	return;
+}
+
+# Fix Cygwin bug on maybe_command();
+if ( $^O eq 'cygwin' ) {
+	require ExtUtils::MM_Cygwin;
+	require ExtUtils::MM_Win32;
+	if ( ! defined(&ExtUtils::MM_Cygwin::maybe_command) ) {
+		*ExtUtils::MM_Cygwin::maybe_command = sub {
+			my ($self, $file) = @_;
+			if ($file =~ m{^/cygdrive/}i and ExtUtils::MM_Win32->can('maybe_command')) {
+				ExtUtils::MM_Win32->maybe_command($file);
+			} else {
+				ExtUtils::MM_Unix->maybe_command($file);
+			}
+		}
+	}
+}
+
+1;
+
+__END__
+
+#line 156
Index: perl/modules/Twitter/inc/Module/Install/Fetch.pm
===================================================================
--- perl/modules/Twitter/inc/Module/Install/Fetch.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
+++ perl/modules/Twitter/inc/Module/Install/Fetch.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
@@ -0,0 +1,93 @@
+#line 1
+package Module::Install::Fetch;
+
+use strict;
+use Module::Install::Base ();
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '0.91';
+	@ISA     = 'Module::Install::Base';
+	$ISCORE  = 1;
+}
+
+sub get_file {
+    my ($self, %args) = @_;
+    my ($scheme, $host, $path, $file) =
+        $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+
+    if ( $scheme eq 'http' and ! eval { require LWP::Simple; 1 } ) {
+        $args{url} = $args{ftp_url}
+            or (warn("LWP support unavailable!\n"), return);
+        ($scheme, $host, $path, $file) =
+            $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+    }
+
+    $|++;
+    print "Fetching '$file' from $host... ";
+
+    unless (eval { require Socket; Socket::inet_aton($host) }) {
+        warn "'$host' resolve failed!\n";
+        return;
+    }
+
+    return unless $scheme eq 'ftp' or $scheme eq 'http';
+
+    require Cwd;
+    my $dir = Cwd::getcwd();
+    chdir $args{local_dir} or return if exists $args{local_dir};
+
+    if (eval { require LWP::Simple; 1 }) {
+        LWP::Simple::mirror($args{url}, $file);
+    }
+    elsif (eval { require Net::FTP; 1 }) { eval {
+        # use Net::FTP to get past firewall
+        my $ftp = Net::FTP->new($host, Passive => 1, Timeout => 600);
+        $ftp->login("anonymous", 'anonymous@example.com');
+        $ftp->cwd($path);
+        $ftp->binary;
+        $ftp->get($file) or (warn("$!\n"), return);
+        $ftp->quit;
+    } }
+    elsif (my $ftp = $self->can_run('ftp')) { eval {
+        # no Net::FTP, fallback to ftp.exe
+        require FileHandle;
+        my $fh = FileHandle->new;
+
+        local $SIG{CHLD} = 'IGNORE';
+        unless ($fh->open("|$ftp -n")) {
+            warn "Couldn't open ftp: $!\n";
+            chdir $dir; return;
+        }
+
+        my @dialog = split(/\n/, <<"END_FTP");
+open $host
+user anonymous anonymous\@example.com
+cd $path
+binary
+get $file $file
+quit
+END_FTP
+        foreach (@dialog) { $fh->print("$_\n") }
+        $fh->close;
+    } }
+    else {
+        warn "No working 'ftp' program available!\n";
+        chdir $dir; return;
+    }
+
+    unless (-f $file) {
+        warn "Fetching failed: $@\n";
+        chdir $dir; return;
+    }
+
+    return if exists $args{size} and -s $file != $args{size};
+    system($args{run}) if exists $args{run};
+    unlink($file) if $args{remove};
+
+    print(((!exists $args{check_for} or -e $args{check_for})
+        ? "done!" : "failed! ($!)"), "\n");
+    chdir $dir; return !$?;
+}
+
+1;
Index: perl/modules/Twitter/inc/Module/Install/Makefile.pm
===================================================================
--- perl/modules/Twitter/inc/Module/Install/Makefile.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
+++ perl/modules/Twitter/inc/Module/Install/Makefile.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
@@ -0,0 +1,268 @@
+#line 1
+package Module::Install::Makefile;
+
+use strict 'vars';
+use ExtUtils::MakeMaker   ();
+use Module::Install::Base ();
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '0.91';
+	@ISA     = 'Module::Install::Base';
+	$ISCORE  = 1;
+}
+
+sub Makefile { $_[0] }
+
+my %seen = ();
+
+sub prompt {
+	shift;
+
+	# Infinite loop protection
+	my @c = caller();
+	if ( ++$seen{"$c[1]|$c[2]|$_[0]"} > 3 ) {
+		die "Caught an potential prompt infinite loop ($c[1]|$c[2]|$_[0])";
+	}
+
+	# In automated testing, always use defaults
+	if ( $ENV{AUTOMATED_TESTING} and ! $ENV{PERL_MM_USE_DEFAULT} ) {
+		local $ENV{PERL_MM_USE_DEFAULT} = 1;
+		goto &ExtUtils::MakeMaker::prompt;
+	} else {
+		goto &ExtUtils::MakeMaker::prompt;
+	}
+}
+
+sub makemaker_args {
+	my $self = shift;
+	my $args = ( $self->{makemaker_args} ||= {} );
+	%$args = ( %$args, @_ );
+	return $args;
+}
+
+# For mm args that take multiple space-seperated args,
+# append an argument to the current list.
+sub makemaker_append {
+	my $self = sShift;
+	my $name = shift;
+	my $args = $self->makemaker_args;
+	$args->{name} = defined $args->{$name}
+		? join( ' ', $args->{name}, @_ )
+		: join( ' ', @_ );
+}
+
+sub build_subdirs {
+	my $self    = shift;
+	my $subdirs = $self->makemaker_args->{DIR} ||= [];
+	for my $subdir (@_) {
+		push @$subdirs, $subdir;
+	}
+}
+
+sub clean_files {
+	my $self  = shift;
+	my $clean = $self->makemaker_args->{clean} ||= {};
+	  %$clean = (
+		%$clean,
+		FILES => join ' ', grep { length $_ } ($clean->{FILES} || (), @_),
+	);
+}
+
+sub realclean_files {
+	my $self      = shift;
+	my $realclean = $self->makemaker_args->{realclean} ||= {};
+	  %$realclean = (
+		%$realclean,
+		FILES => join ' ', grep { length $_ } ($realclean->{FILES} || (), @_),
+	);
+}
+
+sub libs {
+	my $self = shift;
+	my $libs = ref $_[0] ? shift : [ shift ];
+	$self->makemaker_args( LIBS => $libs );
+}
+
+sub inc {
+	my $self = shift;
+	$self->makemaker_args( INC => shift );
+}
+
+my %test_dir = ();
+
+sub _wanted_t {
+	/\.t$/ and -f $_ and $test_dir{$File::Find::dir} = 1;
+}
+
+sub tests_recursive {
+	my $self = shift;
+	if ( $self->tests ) {
+		die "tests_recursive will not work if tests are already defined";
+	}
+	my $dir = shift || 't';
+	unless ( -d $dir ) {
+		die "tests_recursive dir '$dir' does not exist";
+	}
+	%test_dir = ();
+	require File::Find;
+	File::Find::find( \&_wanted_t, $dir );
+	$self->tests( join ' ', map { "$_/*.t" } sort keys %test_dir );
+}
+
+sub write {
+	my $self = shift;
+	die "&Makefile->write() takes no arguments\n" if @_;
+
+	# Check the current Perl version
+	my $perl_version = $self->perl_version;
+	if ( $perl_version ) {
+		eval "use $perl_version; 1"
+			or die "ERROR: perl: Version $] is installed, "
+			. "but we need version >= $perl_version";
+	}
+
+	# Make sure we have a new enough MakeMaker
+	require ExtUtils::MakeMaker;
+
+	if ( $perl_version and $self->_cmp($perl_version, '5.006') >= 0 ) {
+		# MakeMaker can complain about module versions that include
+		# an underscore, even though its own version may contain one!
+		# Hence the funny regexp to get rid of it.  See RT #35800
+		# for details.
+		$self->build_requires( 'ExtUtils::MakeMaker' => $ExtUtils::MakeMaker::VERSION =~ /^(\d+\.\d+)/ );
+		$self->configure_requires( 'ExtUtils::MakeMaker' => $ExtUtils::MakeMaker::VERSION =~ /^(\d+\.\d+)/ );
+	} else {
+		# Allow legacy-compatibility with 5.005 by depending on the
+		# most recent EU:MM that supported 5.005.
+		$self->build_requires( 'ExtUtils::MakeMaker' => 6.42 );
+		$self->configure_requires( 'ExtUtils::MakeMaker' => 6.42 );
+	}
+
+	# Generate the MakeMaker params
+	my $args = $self->makemaker_args;
+	$args->{DISTNAME} = $self->name;
+	$args->{NAME}     = $self->module_name || $self->name;
+	$args->{VERSION}  = $self->version;
+	$args->{NAME}     =~ s/-/::/g;
+	if ( $self->tests ) {
+		$args->{test} = { TESTS => $self->tests };
+	}
+	if ( $] >= 5.005 ) {
+		$args->{ABSTRACT} = $self->abstract;
+		$args->{AUTHOR}   = $self->author;
+	}
+	if ( eval($ExtUtils::MakeMaker::VERSION) >= 6.10 ) {
+		$args->{NO_META} = 1;
+	}
+	if ( eval($ExtUtils::MakeMaker::VERSION) > 6.17 and $self->sign ) {
+		$args->{SIGN} = 1;
+	}
+	unless ( $self->is_admin ) {
+		delete $args->{SIGN};
+	}
+
+	# Merge both kinds of requires into prereq_pm
+	my $prereq = ($args->{PREREQ_PM} ||= {});
+	%$prereq = ( %$prereq,
+		map { @$_ }
+		map { @$_ }
+		grep $_,
+		($self->configure_requires, $self->build_requires, $self->requires)
+	);
+
+	# Remove any reference to perl, PREREQ_PM doesn't support it
+	delete $args->{PREREQ_PM}->{perl};
+
+	# merge both kinds of requires into prereq_pm
+	my $subdirs = ($args->{DIR} ||= []);
+	if ($self->bundles) {
+		foreach my $bundle (@{ $self->bundles }) {
+			my ($file, $dir) = @$bundle;
+			push @$subdirs, $dir if -d $dir;
+			delete $prereq->{$file};
+		}
+	}
+
+	if ( my $perl_version = $self->perl_version ) {
+		eval "use $perl_version; 1"
+			or die "ERROR: perl: Version $] is installed, "
+			. "but we need version >= $perl_version";
+	}
+
+	$args->{INSTALLDIRS} = $self->installdirs;
+
+	my %args = map { ( $_ => $args->{$_} ) } grep {defined($args->{$_})} keys %$args;
+
+	my $user_preop = delete $args{dist}->{PREOP};
+	if (my $preop = $self->admin->preop($user_preop)) {
+		foreach my $key ( keys %$preop ) {
+			$args{dist}->{$key} = $preop->{$key};
+		}
+	}
+
+	my $mm = ExtUtils::MakeMaker::WriteMakefile(%args);
+	$self->fix_up_makefile($mm->{FIRST_MAKEFILE} || 'Makefile');
+}
+
+sub fix_up_makefile {
+	my $self          = shift;
+	my $makefile_name = shift;
+	my $top_class     = ref($self->_top) || '';
+	my $top_version   = $self->_top->VERSION || '';
+
+	my $preamble = $self->preamble
+		? "# Preamble by $top_class $top_version\n"
+			. $self->preamble
+		: '';
+	my $postamble = "# Postamble by $top_class $top_version\n"
+		. ($self->postamble || '');
+
+	local *MAKEFILE;
+	open MAKEFILE, "< $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+	my $makefile = do { local $/; <MAKEFILE> };
+	close MAKEFILE or die $!;
+
+	$makefile =~ s/\b(test_harness\(\$\(TEST_VERBOSE\), )/$1'inc', /;
+	$makefile =~ s/( -I\$\(INST_ARCHLIB\))/ -Iinc$1/g;
+	$makefile =~ s/( "-I\$\(INST_LIB\)")/ "-Iinc"$1/g;
+	$makefile =~ s/^(FULLPERL = .*)/$1 "-Iinc"/m;
+	$makefile =~ s/^(PERL = .*)/$1 "-Iinc"/m;
+
+	# Module::Install will never be used to build the Core Perl
+	# Sometimes PERL_LIB and PERL_ARCHLIB get written anyway, which breaks
+	# PREFIX/PERL5LIB, and thus, install_share. Blank them if they exist
+	$makefile =~ s/^PERL_LIB = .+/PERL_LIB =/m;
+	#$makefile =~ s/^PERL_ARCHLIB = .+/PERL_ARCHLIB =/m;
+
+	# Perl 5.005 mentions PERL_LIB explicitly, so we have to remove that as well.
+	$makefile =~ s/(\"?)-I\$\(PERL_LIB\)\1//g;
+
+	# XXX - This is currently unused; not sure if it breaks other MM-users
+	# $makefile =~ s/^pm_to_blib\s+:\s+/pm_to_blib :: /mg;
+
+	open  MAKEFILE, "> $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+	print MAKEFILE  "$preamble$makefile$postamble" or die $!;
+	close MAKEFILE  or die $!;
+
+	1;
+}
+
+sub preamble {
+	my ($self, $text) = @_;
+	$self->{preamble} = $text . $self->{preamble} if defined $text;
+	$self->{preamble};
+}
+
+sub postamble {
+	my ($self, $text) = @_;
+	$self->{postamble} ||= $self->admin->postamble;
+	$self->{postamble} .= $text if defined $text;
+	$self->{postamble}
+}
+
+1;
+
+__END__
+
+#line 394
Index: perl/modules/Twitter/inc/Module/Install/Metadata.pm
===================================================================
--- perl/modules/Twitter/inc/Module/Install/Metadata.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
+++ perl/modules/Twitter/inc/Module/Install/Metadata.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
@@ -0,0 +1,624 @@
+#line 1
+package Module::Install::Metadata;
+
+use strict 'vars';
+use Module::Install::Base ();
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '0.91';
+	@ISA     = 'Module::Install::Base';
+	$ISCORE  = 1;
+}
+
+my @boolean_keys = qw{
+	sign
+};
+
+my @scalar_keys = qw{
+	name
+	module_name
+	abstract
+	author
+	version
+	distribution_type
+	tests
+	installdirs
+};
+
+my @tuple_keys = qw{
+	configure_requires
+	build_requires
+	requires
+	recommends
+	bundles
+	resources
+};
+
+my @resource_keys = qw{
+	homepage
+	bugtracker
+	repository
+};
+
+my @array_keys = qw{
+	keywords
+};
+
+sub Meta              { shift          }
+sub Meta_BooleanKeys  { @boolean_keys  }
+sub Meta_ScalarKeys   { @scalar_keys   }
+sub Meta_TupleKeys    { @tuple_keys    }
+sub Meta_ResourceKeys { @resource_keys }
+sub Meta_ArrayKeys    { @array_keys    }
+
+foreach my $key ( @boolean_keys ) {
+	*$key = sub {
+		my $self = shift;
+		if ( defined wantarray and not @_ ) {
+			return $self->{values}->{$key};
+		}
+		$self->{values}->{$key} = ( @_ ? $_[0] : 1 );
+		return $self;
+	};
+}
+
+foreach my $key ( @scalar_keys ) {
+	*$key = sub {
+		my $self = shift;
+		return $self->{values}->{$key} if defined wantarray and !@_;
+		$self->{values}->{$key} = shift;
+		return $self;
+	};
+}
+
+foreach my $key ( @array_keys ) {
+	*$key = sub {
+		my $self = shift;
+		return $self->{values}->{$key} if defined wantarray and !@_;
+		$self->{values}->{$key} ||= [];
+		push @{$self->{values}->{$key}}, @_;
+		return $self;
+	};
+}
+
+foreach my $key ( @resource_keys ) {
+	*$key = sub {
+		my $self = shift;
+		unless ( @_ ) {
+			return () unless $self->{values}->{resources};
+			return map  { $_->[1] }
+			       grep { $_->[0] eq $key }
+			       @{ $self->{values}->{resources} };
+		}
+		return $self->{values}->{resources}->{$key} unless @_;
+		my $uri = shift or die(
+			"Did not provide a value to $key()"
+		);
+		$self->resources( $key => $uri );
+		return 1;
+	};
+}
+
+foreach my $key ( grep { $_ ne "resources" } @tuple_keys) {
+	*$key = sub {
+		my $self = shift;
+		return $self->{values}->{$key} unless @_;
+		my @added;
+		while ( @_ ) {
+			my $module  = shift or last;
+			my $version = shift || 0;
+			push @added, [ $module, $version ];
+		}
+		push @{ $self->{values}->{$key} }, @added;
+		return map {@$_} @added;
+	};
+}
+
+# Resource handling
+my %lc_resource = map { $_ => 1 } qw{
+	homepage
+	license
+	bugtracker
+	repository
+};
+
+sub resources {
+	my $self = shift;
+	while ( @_ ) {
+		my $name  = shift or last;
+		my $value = shift or next;
+		if ( $name eq lc $name and ! $lc_resource{$name} ) {
+			die("Unsupported reserved lowercase resource '$name'");
+		}
+		$self->{values}->{resources} ||= [];
+		push @{ $self->{values}->{resources} }, [ $name, $value ];
+	}
+	$self->{values}->{resources};
+}
+
+# Aliases for build_requires that will have alternative
+# meanings in some future version of META.yml.
+sub test_requires     { shift->build_requires(@_) }
+sub install_requires  { shift->build_requires(@_) }
+
+# Aliases for installdirs options
+sub install_as_core   { $_[0]->installdirs('perl')   }
+sub install_as_cpan   { $_[0]->installdirs('site')   }
+sub install_as_site   { $_[0]->installdirs('site')   }
+sub install_as_vendor { $_[0]->installdirs('vendor') }
+
+sub dynamic_config {
+	my $self = shift;
+	unless ( @_ ) {
+		warn "You MUST provide an explicit true/false value to dynamic_config\n";
+		return $self;
+	}
+	$self->{values}->{dynamic_config} = $_[0] ? 1 : 0;
+	return 1;
+}
+
+sub perl_version {
+	my $self = shift;
+	return $self->{values}->{perl_version} unless @_;
+	my $version = shift or die(
+		"Did not provide a value to perl_version()"
+	);
+
+	# Normalize the version
+	$version = $self->_perl_version($version);
+
+	# We don't support the reall old versions
+	unless ( $version >= 5.005 ) {
+		die "Module::Install only supports 5.005 or newer (use ExtUtils::MakeMaker)\n";
+	}
+
+	$self->{values}->{perl_version} = $version;
+}
+
+#Stolen from M::B
+my %license_urls = (
+    perl         => 'http://dev.perl.org/licenses/',
+    apache       => 'http://apache.org/licenses/LICENSE-2.0',
+    artistic     => 'http://opensource.org/licenses/artistic-license.php',
+    artistic_2   => 'http://opensource.org/licenses/artistic-license-2.0.php',
+    lgpl         => 'http://opensource.org/licenses/lgpl-license.php',
+    lgpl2        => 'http://opensource.org/licenses/lgpl-2.1.php',
+    lgpl3        => 'http://opensource.org/licenses/lgpl-3.0.html',
+    bsd          => 'http://opensource.org/licenses/bsd-license.php',
+    gpl          => 'http://opensource.org/licenses/gpl-license.php',
+    gpl2         => 'http://opensource.org/licenses/gpl-2.0.php',
+    gpl3         => 'http://opensource.org/licenses/gpl-3.0.html',
+    mit          => 'http://opensource.org/licenses/mit-license.php',
+    mozilla      => 'http://opensource.org/licenses/mozilla1.1.php',
+    open_source  => undef,
+    unrestricted => undef,
+    restrictive  => undef,
+    unknown      => undef,
+);
+
+sub license {
+	my $self = shift;
+	return $self->{values}->{license} unless @_;
+	my $license = shift or die(
+		'Did not provide a value to license()'
+	);
+	$self->{values}->{license} = $license;
+
+	# Automatically fill in license URLs
+	if ( $license_urls{$license} ) {
+		$self->resources( license => $license_urls{$license} );
+	}
+
+	return 1;
+}
+
+sub all_from {
+	my ( $self, $file ) = @_;
+
+	unless ( defined($file) ) {
+		my $name = $self->name or die(
+			"all_from called with no args without setting name() first"
+		);
+		$file = join('/', 'lib', split(/-/, $name)) . '.pm';
+		$file =~ s{.*/}{} unless -e $file;
+		unless ( -e $file ) {
+			die("all_from cannot find $file from $name");
+		}
+	}
+	unless ( -f $file ) {
+		die("The path '$file' does not exist, or is not a file");
+	}
+
+	# Some methods pull from POD instead of code.
+	# If there is a matching .pod, use that instead
+	my $pod = $file;
+	$pod =~ s/\.pm$/.pod/i;
+	$pod = $file unless -e $pod;
+
+	# Pull the different values
+	$self->name_from($file)         unless $self->name;
+	$self->version_from($file)      unless $self->version;
+	$self->perl_version_from($file) unless $self->perl_version;
+	$self->author_from($pod)        unless $self->author;
+	$self->license_from($pod)       unless $self->license;
+	$self->abstract_from($pod)      unless $self->abstract;
+
+	return 1;
+}
+
+sub provides {
+	my $self     = shift;
+	my $provides = ( $self->{values}->{provides} ||= {} );
+	%$provides = (%$provides, @_) if @_;
+	return $provides;
+}
+
+sub auto_provides {
+	my $self = shift;
+	return $self unless $self->is_admin;
+	unless (-e 'MANIFEST') {
+		warn "Cannot deduce auto_provides without a MANIFEST, skipping\n";
+		return $self;
+	}
+	# Avoid spurious warnings as we are not checking manifest here.
+	local $SIG{__WARN__} = sub {1};
+	require ExtUtils::Manifest;
+	local *ExtUtils::Manifest::manicheck = sub { return };
+
+	require Module::Build;
+	my $build = Module::Build->new(
+		dist_name    => $self->name,
+		dist_version => $self->version,
+		license      => $self->license,
+	);
+	$self->provides( %{ $build->find_dist_packages || {} } );
+}
+
+sub feature {
+	my $self     = shift;
+	my $name     = shift;
+	my $features = ( $self->{values}->{features} ||= [] );
+	my $mods;
+
+	if ( @_ == 1 and ref( $_[0] ) ) {
+		# The user used ->feature like ->features by passing in the second
+		# argument as a reference.  Accomodate for that.
+		$mods = $_[0];
+	} else {
+		$mods = \@_;
+	}
+
+	my $count = 0;
+	push @$features, (
+		$name => [
+			map {
+				ref($_) ? ( ref($_) eq 'HASH' ) ? %$_ : @$_ : $_
+			} @$mods
+		]
+	);
+
+	return @$features;
+}
+
+sub features {
+	my $self = shift;
+	while ( my ( $name, $mods ) = splice( @_, 0, 2 ) ) {
+		$self->feature( $name, @$mods );
+	}
+	return $self->{values}->{features}
+		? @{ $self->{values}->{features} }
+		: ();
+}
+
+sub no_index {
+	my $self = shift;
+	my $type = shift;
+	push @{ $self->{values}->{no_index}->{$type} }, @_ if $type;
+	return $self->{values}->{no_index};
+}
+
+sub read {
+	my $self = shift;
+	$self->include_deps( 'YAML::Tiny', 0 );
+
+	require YAML::Tiny;
+	my $data = YAML::Tiny::LoadFile('META.yml');
+
+	# Call methods explicitly in case user has already set some values.
+	while ( my ( $key, $value ) = each %$data ) {
+		next unless $self->can($key);
+		if ( ref $value eq 'HASH' ) {
+			while ( my ( $module, $version ) = each %$value ) {
+				$self->can($key)->($self, $module => $version );
+			}
+		} else {
+			$self->can($key)->($self, $value);
+		}
+	}
+	return $self;
+}
+
+sub write {
+	my $self = shift;
+	return $self unless $self->is_admin;
+	$self->admin->write_meta;
+	return $self;
+}
+
+sub version_from {
+	require ExtUtils::MM_Unix;
+	my ( $self, $file ) = @_;
+	$self->version( ExtUtils::MM_Unix->parse_version($file) );
+}
+
+sub abstract_from {
+	require ExtUtils::MM_Unix;
+	my ( $self, $file ) = @_;
+	$self->abstract(
+		bless(
+			{ DISTNAME => $self->name },
+			'ExtUtils::MM_Unix'
+		)->parse_abstract($file)
+	 );
+}
+
+# Add both distribution and module name
+sub name_from {
+	my ($self, $file) = @_;
+	if (
+		Module::Install::_read($file) =~ m/
+		^ \s*
+		package \s*
+		([\w:]+)
+		\s* ;
+		/ixms
+	) {
+		my ($name, $module_name) = ($1, $1);
+		$name =~ s{::}{-}g;
+		$self->name($name);
+		unless ( $self->module_name ) {
+			$self->module_name($module_name);
+		}
+	} else {
+		die("Cannot determine name from $file\n");
+	}
+}
+
+sub perl_version_from {
+	my $self = shift;
+	if (
+		Module::Install::_read($_[0]) =~ m/
+		^
+		(?:use|require) \s*
+		v?
+		([\d_\.]+)
+		\s* ;
+		/ixms
+	) {
+		my $perl_version = $1;
+		$perl_version =~ s{_}{}g;
+		$self->perl_version($perl_version);
+	} else {
+		warn "Cannot determine perl version info from $_[0]\n";
+		return;
+	}
+}
+
+sub author_from {
+	my $self    = shift;
+	my $content = Module::Install::_read($_[0]);
+	if ($content =~ m/
+		=head \d \s+ (?:authors?)\b \s*
+		([^\n]*)
+		|
+		=head \d \s+ (?:licen[cs]e|licensing|copyright|legal)\b \s*
+		.*? copyright .*? \d\d\d[\d.]+ \s* (?:\bby\b)? \s*
+		([^\n]*)
+	/ixms) {
+		my $author = $1 || $2;
+		$author =~ s{E<lt>}{<}g;
+		$author =~ s{E<gt>}{>}g;
+		$self->author($author);
+	} else {
+		warn "Cannot determine author info from $_[0]\n";
+	}
+}
+
+sub license_from {
+	my $self = shift;
+	if (
+		Module::Install::_read($_[0]) =~ m/
+		(
+			=head \d \s+
+			(?:licen[cs]e|licensing|copyright|legal)\b
+			.*?
+		)
+		(=head\\d.*|=cut.*|)
+		\z
+	/ixms ) {
+		my $license_text = $1;
+		my @phrases      = (
+			'under the same (?:terms|license) as (?:perl|the perl programming language) itself' => 'perl', 1,
+			'GNU general public license'         => 'gpl',         1,
+			'GNU public license'                 => 'gpl',         1,
+			'GNU lesser general public license'  => 'lgpl',        1,
+			'GNU lesser public license'          => 'lgpl',        1,
+			'GNU library general public license' => 'lgpl',        1,
+			'GNU library public license'         => 'lgpl',        1,
+			'BSD license'                        => 'bsd',         1,
+			'Artistic license'                   => 'artistic',    1,
+			'GPL'                                => 'gpl',         1,
+			'LGPL'                               => 'lgpl',        1,
+			'BSD'                                => 'bsd',         1,
+			'Artistic'                           => 'artistic',    1,
+			'MIT'                                => 'mit',         1,
+			'proprietary'                        => 'proprietary', 0,
+		);
+		while ( my ($pattern, $license, $osi) = splice(@phrases, 0, 3) ) {
+			$pattern =~ s{\s+}{\\s+}g;
+			if ( $license_text =~ /\b$pattern\b/i ) {
+				$self->license($license);
+				return 1;
+			}
+		}
+	}
+
+	warn "Cannot determine license info from $_[0]\n";
+	return 'unknown';
+}
+
+sub _extract_bugtracker {
+	my @links   = $_[0] =~ m#L<(\Qhttp://rt.cpan.org/\E[^>]+)>#g;
+	my %links;
+	@links{@links}=();
+	@links=keys %links;
+	return @links;
+}
+
+sub bugtracker_from {
+	my $self    = shift;
+	my $content = Module::Install::_read($_[0]);
+	my @links   = _extract_bugtracker($content);
+	unless ( @links ) {
+		warn "Cannot determine bugtracker info from $_[0]\n";
+		return 0;
+	}
+	if ( @links > 1 ) {
+		warn "Found more than on rt.cpan.org link in $_[0]\n";
+		return 0;
+	}
+
+	# Set the bugtracker
+	bugtracker( $links[0] );
+	return 1;
+}
+
+sub requires_from {
+	my $self     = shift;
+	my $content  = Module::Install::_readperl($_[0]);
+	my @requires = $content =~ m/^use\s+([^\W\d]\w*(?:::\w+)*)\s+([\d\.]+)/mg;
+	while ( @requires ) {
+		my $module  = shift @requires;
+		my $version = shift @requires;
+		$self->requires( $module => $version );
+	}
+}
+
+sub test_requires_from {
+	my $self     = shift;
+	my $content  = Module::Install::_readperl($_[0]);
+	my @requires = $content =~ m/^use\s+([^\W\d]\w*(?:::\w+)*)\s+([\d\.]+)/mg;
+	while ( @requires ) {
+		my $module  = shift @requires;
+		my $version = shift @requires;
+		$self->test_requires( $module => $version );
+	}
+}
+
+# Convert triple-part versions (eg, 5.6.1 or 5.8.9) to
+# numbers (eg, 5.006001 or 5.008009).
+# Also, convert double-part versions (eg, 5.8)
+sub _perl_version {
+	my $v = $_[-1];
+	$v =~ s/^([1-9])\.([1-9]\d?\d?)$/sprintf("%d.%03d",$1,$2)/e;
+	$v =~ s/^([1-9])\.([1-9]\d?\d?)\.(0|[1-9]\d?\d?)$/sprintf("%d.%03d%03d",$1,$2,$3 || 0)/e;
+	$v =~ s/(\.\d\d\d)000$/$1/;
+	$v =~ s/_.+$//;
+	if ( ref($v) ) {
+		# Numify
+		$v = $v + 0;
+	}
+	return $v;
+}
+
+
+
+
+
+######################################################################
+# MYMETA Support
+
+sub WriteMyMeta {
+	die "WriteMyMeta has been deprecated";
+}
+
+sub write_mymeta_yaml {
+	my $self = shift;
+
+	# We need YAML::Tiny to write the MYMETA.yml file
+	unless ( eval { require YAML::Tiny; 1; } ) {
+		return 1;
+	}
+
+	# Generate the data
+	my $meta = $self->_write_mymeta_data or return 1;
+
+	# Save as the MYMETA.yml file
+	print "Writing MYMETA.yml\n";
+	YAML::Tiny::DumpFile('MYMETA.yml', $meta);
+}
+
+sub write_mymeta_json {
+	my $self = shift;
+
+	# We need JSON to write the MYMETA.json file
+	unless ( eval { require JSON; 1; } ) {
+		return 1;
+	}
+
+	# Generate the data
+	my $meta = $self->_write_mymeta_data or return 1;
+
+	# Save as the MYMETA.yml file
+	print "Writing MYMETA.json\n";
+	Module::Install::_write(
+		'MYMETA.json',
+		JSON->new->pretty(1)->canonical->encode($meta),
+	);
+}
+
+sub _write_mymeta_data {
+	my $self = shift;
+
+	# If there's no existing META.yml there is nothing we can do
+	return undef unless -f 'META.yml';
+
+	# We need Parse::CPAN::Meta to load the file
+	unless ( eval { require Parse::CPAN::Meta; 1; } ) {
+		return undef;
+	}
+
+	# Merge the perl version into the dependencies
+	my $val  = $self->Meta->{values};
+	my $perl = delete $val->{perl_version};
+	if ( $perl ) {
+		$val->{requires} ||= [];
+		my $requires = $val->{requires};
+
+		# Canonize to three-dot version after Perl 5.6
+		if ( $perl >= 5.006 ) {
+			$perl =~ s{^(\d+)\.(\d\d\d)(\d*)}{join('.', $1, int($2||0), int($3||0))}e
+		}
+		unshift @$requires, [ perl => $perl ];
+	}
+
+	# Load the advisory META.yml file
+	my @yaml = Parse::CPAN::Meta::LoadFile('META.yml');
+	my $meta = $yaml[0];
+
+	# Overwrite the non-configure dependency hashs
+	delete $meta->{requires};
+	delete $meta->{build_requires};
+	delete $meta->{recommends};
+	if ( exists $val->{requires} ) {
+		$meta->{requires} = { map { @$_ } @{ $val->{requires} } };
+	}
+	if ( exists $val->{build_requires} ) {
+		$meta->{build_requires} = { map { @$_ } @{ $val->{build_requires} } };
+	}
+
+	return $meta;
+}
+
+1;
Index: perl/modules/Twitter/inc/Module/Install/Win32.pm
===================================================================
--- perl/modules/Twitter/inc/Module/Install/Win32.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
+++ perl/modules/Twitter/inc/Module/Install/Win32.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
@@ -0,0 +1,64 @@
+#line 1
+package Module::Install::Win32;
+
+use strict;
+use Module::Install::Base ();
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '0.91';
+	@ISA     = 'Module::Install::Base';
+	$ISCORE  = 1;
+}
+
+# determine if the user needs nmake, and download it if needed
+sub check_nmake {
+	my $self = shift;
+	$self->load('can_run');
+	$self->load('get_file');
+
+	require Config;
+	return unless (
+		$^O eq 'MSWin32'                     and
+		$Config::Config{make}                and
+		$Config::Config{make} =~ /^nmake\b/i and
+		! $self->can_run('nmake')
+	);
+
+	print "The required 'nmake' executable not found, fetching it...\n";
+
+	require File::Basename;
+	my $rv = $self->get_file(
+		url       => 'http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe',
+		ftp_url   => 'ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe',
+		local_dir => File::Basename::dirname($^X),
+		size      => 51928,
+		run       => 'Nmake15.exe /o > nul',
+		check_for => 'Nmake.exe',
+		remove    => 1,
+	);
+
+	die <<'END_MESSAGE' unless $rv;
+
+-------------------------------------------------------------------------------
+
+Since you are using Microsoft Windows, you will need the 'nmake' utility
+before installation. It's available at:
+
+  http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe
+      or
+  ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe
+
+Please download the file manually, save it to a directory in %PATH% (e.g.
+C:\WINDOWS\COMMAND\), then launch the MS-DOS command line shell, "cd" to
+that directory, and run "Nmake15.exe" from there; that will create the
+'nmake.exe' file needed by this module.
+
+You may then resume the installation process described in README.
+
+-------------------------------------------------------------------------------
+END_MESSAGE
+
+}
+
+1;
Index: perl/modules/Twitter/inc/Module/Install/WriteAll.pm
===================================================================
--- perl/modules/Twitter/inc/Module/Install/WriteAll.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
+++ perl/modules/Twitter/inc/Module/Install/WriteAll.pm	(revision d27ecf382c5abebd36ad749a62b820ab427c04da)
@@ -0,0 +1,60 @@
+#line 1
+package Module::Install::WriteAll;
+
+use strict;
+use Module::Install::Base ();
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '0.91';;
+	@ISA     = qw{Module::Install::Base};
+	$ISCORE  = 1;
+}
+
+sub WriteAll {
+	my $self = shift;
+	my %args = (
+		meta        => 1,
+		sign        => 0,
+		inline      => 0,
+		check_nmake => 1,
+		@_,
+	);
+
+	$self->sign(1)                if $args{sign};
+	$self->admin->WriteAll(%args) if $self->is_admin;
+
+	$self->check_nmake if $args{check_nmake};
+	unless ( $self->makemaker_args->{PL_FILES} ) {
+		$self->makemaker_args( PL_FILES => {} );
+	}
+
+	# Until ExtUtils::MakeMaker support MYMETA.yml, make sure
+	# we clean it up properly ourself.
+	$self->realclean_files('MYMETA.yml');
+
+	if ( $args{inline} ) {
+		$self->Inline->write;
+	} else {
+		$self->Makefile->write;
+	}
+
+	# The Makefile write process adds a couple of dependencies,
+	# so write the META.yml files after the Makefile.
+	if ( $args{meta} ) {
+		$self->Meta->write;
+	}
+
+	# Experimental support for MYMETA
+	if ( $ENV{X_MYMETA} ) {
+		if ( $ENV{X_MYMETA} eq 'JSON' ) {
+			$self->Meta->write_mymeta_json;
+		} else {
+			$self->Meta->write_mymeta_yaml;
+		}
+	}
+
+	return 1;
+}
+
+1;
Index: perl/modules/Twitter/lib/BarnOwl/Message/Twitter.pm
===================================================================
--- perl/modules/Twitter/lib/BarnOwl/Message/Twitter.pm	(revision 5d59c1e2fa97f16e6d4846aafc08a19b4c51b145)
+++ perl/modules/Twitter/lib/BarnOwl/Message/Twitter.pm	(revision 5d59c1e2fa97f16e6d4846aafc08a19b4c51b145)
@@ -0,0 +1,62 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+BarnOwl::Message::Twitter
+
+=head1 DESCRIPTION
+
+=cut
+
+package BarnOwl::Message::Twitter;
+use base qw(BarnOwl::Message);
+
+sub context {'twitter'}
+sub subcontext {undef}
+sub service { return (shift->{"service"} || "http://twitter.com"); }
+sub account { return shift->{"account"}; }
+sub retweeted_by { shift->{retweeted_by}; }
+sub long_sender {
+    my $self = shift;
+    $self->service =~ m#^\s*(.*?://.*?)/.*$#;
+    my $service = $1 || $self->service;
+    my $long = $service . '/' . $self->sender;
+    if ($self->retweeted_by) {
+        $long = "(retweeted by " . $self->retweeted_by . ") $long";
+    }
+    return $long;
+}
+
+sub replycmd {
+    my $self = shift;
+    if($self->is_private) {
+        return $self->replysendercmd;
+    } elsif(exists($self->{status_id})) {
+        return 'twitter-atreply ' . $self->sender . " " . $self->{status_id} . " " . $self->account;
+    } else {
+        return 'twitter-atreply ' . $self->sender . " " . $self->account;
+    }
+}
+
+sub replysendercmd {
+    my $self = shift;
+    return 'twitter-direct ' . $self->sender . " " . $self->account;
+}
+
+sub smartfilter {
+    my $self = shift;
+    my $inst = shift;
+    my $filter;
+
+    if($inst) {
+        $filter = "twitter-" . $self->sender;
+        BarnOwl::command("filter", $filter,
+                         qw{type ^twitter$ and sender}, '^'.$self->sender.'$');
+    } else {
+        $filter = "twitter";
+    }
+    return $filter;
+}
+
+1;
Index: perl/modules/Twitter/lib/BarnOwl/Module/Twitter.pm
===================================================================
--- perl/modules/Twitter/lib/BarnOwl/Module/Twitter.pm	(revision f6e12629c3a5e306d146c633ddf9609593c7f785)
+++ perl/modules/Twitter/lib/BarnOwl/Module/Twitter.pm	(revision f6e12629c3a5e306d146c633ddf9609593c7f785)
@@ -0,0 +1,401 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+BarnOwl::Module::Twitter
+
+=head1 DESCRIPTION
+
+Post outgoing zephyrs from -c $USER -i status -O TWITTER to Twitter
+
+=cut
+
+package BarnOwl::Module::Twitter;
+
+our $VERSION = 0.2;
+
+use JSON;
+use List::Util qw(first);
+
+use BarnOwl;
+use BarnOwl::Hooks;
+use BarnOwl::Module::Twitter::Handle;
+use BarnOwl::Module::Twitter::Completion;
+
+our @twitter_handles = ();
+our $default_handle = undef;
+
+my $desc = <<'END_DESC';
+BarnOwl::Module::Twitter will watch for authentic zephyrs to
+-c $twitter:class -i $twitter:instance -O $twitter:opcode
+from your sender and mirror them to Twitter.
+
+A value of '*' in any of these fields acts a wildcard, accepting
+messages with any value of that field.
+END_DESC
+BarnOwl::new_variable_string(
+    'twitter:class',
+    {
+        default     => $ENV{USER},
+        summary     => 'Class to watch for Twitter messages',
+        description => $desc
+    }
+);
+BarnOwl::new_variable_string(
+    'twitter:instance',
+    {
+        default => 'status',
+        summary => 'Instance on twitter:class to watch for Twitter messages.',
+        description => $desc
+    }
+);
+BarnOwl::new_variable_string(
+    'twitter:opcode',
+    {
+        default => 'twitter',
+        summary => 'Opcode for zephyrs that will be sent as twitter updates',
+        description => $desc
+    }
+);
+
+BarnOwl::new_variable_bool(
+    'twitter:poll',
+    {
+        default => 1,
+        summary => 'Poll Twitter for incoming messages',
+        description => "If set, will poll Twitter every minute for normal updates,\n"
+        . 'and every two minutes for direct message'
+     }
+ );
+
+sub fail {
+    my $msg = shift;
+    undef @twitter_handles;
+    BarnOwl::admin_message('Twitter Error', $msg);
+    die("Twitter Error: $msg\n");
+}
+
+my $conffile = BarnOwl::get_config_dir() . "/twitter";
+
+if (open(my $fh, "<", "$conffile")) {
+    read_config($fh);
+    close($fh);
+}
+
+sub read_config {
+    my $fh = shift;
+
+    my $raw_cfg = do {local $/; <$fh>};
+    close($fh);
+    eval {
+        $raw_cfg = from_json($raw_cfg);
+    };
+    if($@) {
+        fail("Unable to parse $conffile: $@");
+    }
+
+    $raw_cfg = [$raw_cfg] unless UNIVERSAL::isa $raw_cfg, "ARRAY";
+
+    # Perform some sanity checking on the configuration.
+  {
+      my %nicks;
+      my $default = 0;
+
+      for my $cfg (@$raw_cfg) {
+          if(! exists $cfg->{user}) {
+              fail("Account has no username set.");
+          }
+          my $user = $cfg->{user};
+          if(! exists $cfg->{password}) {
+              fail("Account $user has no password set.");
+          }
+          if(@$raw_cfg > 1&&
+             !exists($cfg->{account_nickname}) ) {
+              fail("Account $user has no account_nickname set.");
+          }
+          if($cfg->{account_nickname}) {
+              if($nicks{$cfg->{account_nickname}}++) {
+                  fail("Nickname " . $cfg->{account_nickname} . " specified more than once.");
+              }
+          }
+          if($cfg->{default} || $cfg->{default_sender}) {
+              if($default++) {
+                  fail("Multiple accounts marked as 'default'.");
+              }
+          }
+      }
+  }
+
+    # If there is only a single account, make publish_tweets default to
+    # true.
+    if (scalar @$raw_cfg == 1 && !exists($raw_cfg->[0]{publish_tweets})) {
+        $raw_cfg->[0]{publish_tweets} = 1;
+    }
+
+    for my $cfg (@$raw_cfg) {
+        my $twitter_args = { username   => $cfg->{user},
+                             password   => $cfg->{password},
+                             source     => 'barnowl', 
+                         };
+        if (defined $cfg->{service}) {
+            my $service = $cfg->{service};
+            $twitter_args->{apiurl} = $service;
+            my $apihost = $service;
+            $apihost =~ s/^\s*http:\/\///;
+            $apihost =~ s/\/.*$//;
+            $apihost .= ':80' unless $apihost =~ /:\d+$/;
+            $twitter_args->{apihost} = $cfg->{apihost} || $apihost;
+            my $apirealm = "Laconica API";
+            $twitter_args->{apirealm} = $cfg->{apirealm} || $apirealm;
+        } else {
+            $cfg->{service} = 'http://twitter.com';
+        }
+
+        my $twitter_handle;
+        eval {
+            $twitter_handle = BarnOwl::Module::Twitter::Handle->new($cfg, %$twitter_args);
+        };
+        if ($@) {
+            BarnOwl::error($@);
+            next;
+        }
+        push @twitter_handles, $twitter_handle;
+    }
+
+    $default_handle = first {$_->{cfg}->{default}} @twitter_handles;
+    if (!$default_handle && @twitter_handles) {
+        $default_handle = $twitter_handles[0];
+    }
+
+}
+
+sub match {
+    my $val = shift;
+    my $pat = shift;
+    return $pat eq "*" || ($val eq $pat);
+}
+
+sub handle_message {
+    my $m = shift;
+    my ($class, $instance, $opcode) = map{BarnOwl::getvar("twitter:$_")} qw(class instance opcode);
+    if($m->type eq 'zephyr'
+       && $m->sender eq BarnOwl::zephyr_getsender()
+       && match($m->class, $class)
+       && match($m->instance, $instance)
+       && match($m->opcode, $opcode)
+       && $m->auth eq 'YES') {
+        for my $handle (@twitter_handles) {
+            $handle->twitter($m->body) if $handle->{cfg}->{publish_tweets};
+        }
+    }
+}
+
+sub poll_messages {
+    # If we are reloaded into a barnowl with the old
+    # BarnOwl::Module::Twitter loaded, it still has a main loop hook
+    # that will call this function every second. If we just delete it,
+    # it will get the old version, which will call poll on each of our
+    # handles every second. However, they no longer include the time
+    # check, and so we will poll a handle every second until
+    # ratelimited.
+
+    # So we include an empty function here.
+}
+
+sub find_account {
+    my $name = shift;
+    my $handle = first {$_->{cfg}->{account_nickname} eq $name} @twitter_handles;
+    if ($handle) {
+        return $handle;
+    } else {
+        die("No such Twitter account: $name\n");
+    }
+}
+
+sub find_account_default {
+    my $name = shift;
+    if(defined($name)) {
+        return find_account($name);
+    } else {
+        return $default_handle;
+    }
+}
+
+sub twitter {
+    my $account = shift;
+
+    my $sent = 0;
+    if (defined $account) {
+        my $handle = find_account($account);
+        $handle->twitter(@_);
+    } 
+    else {
+        # broadcast
+        for my $handle (@twitter_handles) {
+            $handle->twitter(@_) if $handle->{cfg}->{publish_tweets};
+        }
+    }
+}
+
+BarnOwl::new_command(twitter => \&cmd_twitter, {
+    summary     => 'Update Twitter from BarnOwl',
+    usage       => 'twitter [ACCOUNT] [MESSAGE]',
+    description => 'Update Twitter on ACCOUNT. If MESSAGE is provided, use it as your status.'
+    . "\nIf no ACCOUNT is provided, update all services which have publishing enabled."
+    . "\nOtherwise, prompt for a status message to use."
+   });
+
+BarnOwl::new_command('twitter-direct' => \&cmd_twitter_direct, {
+    summary     => 'Send a Twitter direct message',
+    usage       => 'twitter-direct USER [ACCOUNT]',
+    description => 'Send a Twitter Direct Message to USER on ACCOUNT (defaults to default_sender,'
+    . "\nor first service if no default is provided)"
+   });
+
+BarnOwl::new_command( 'twitter-atreply' => sub { cmd_twitter_atreply(@_); },
+    {
+    summary     => 'Send a Twitter @ message',
+    usage       => 'twitter-atreply USER [ACCOUNT]',
+    description => 'Send a Twitter @reply Message to USER on ACCOUNT (defaults to default_sender,' 
+    . "\nor first service if no default is provided)"
+    }
+);
+
+BarnOwl::new_command( 'twitter-retweet' => sub { cmd_twitter_retweet(@_) },
+    {
+    summary     => 'Retweet the current Twitter message',
+    usage       => 'twitter-retweet [ACCOUNT]',
+    description => <<END_DESCRIPTION
+Retweet the current Twitter message using ACCOUNT (defaults to the
+account that received the tweet).
+END_DESCRIPTION
+    }
+);
+
+BarnOwl::new_command( 'twitter-follow' => sub { cmd_twitter_follow(@_); },
+    {
+    summary     => 'Follow a user on Twitter',
+    usage       => 'twitter-follow USER [ACCOUNT]',
+    description => 'Follow USER on Twitter ACCOUNT (defaults to default_sender, or first service'
+    . "\nif no default is provided)"
+    }
+);
+
+BarnOwl::new_command( 'twitter-unfollow' => sub { cmd_twitter_unfollow(@_); },
+    {
+    summary     => 'Stop following a user on Twitter',
+    usage       => 'twitter-unfollow USER [ACCOUNT]',
+    description => 'Stop following USER on Twitter ACCOUNT (defaults to default_sender, or first'
+    . "\nservice if no default is provided)"
+    }
+);
+
+BarnOwl::new_command('twitter-count-chars' => \&cmd_count_chars, {
+    summary     => 'Count the number of characters in the edit window',
+    usage       => 'twitter-count-chars',
+    description => <<END_DESCRIPTION
+Displays the number of characters entered in the edit window so far.
+END_DESCRIPTION
+   });
+
+$BarnOwl::Hooks::getQuickstart->add( sub { twitter_quickstart(@_); } );
+
+sub twitter_quickstart {
+    return <<'EOF'
+@b[Twitter]:
+Add your Twitter account to ~/.owl/twitter, like:
+  {"user":"nelhage", "password":"sekrit"}
+Run :reload-module Twitter, then use :twitter to tweet.
+EOF
+}
+
+
+sub cmd_twitter {
+    my $cmd = shift;
+    my $account = shift;
+    if (defined $account) {
+        if(@_) {
+            my $status = join(" ", @_);
+            twitter($account, $status);
+            return;
+        }
+    }
+    BarnOwl::start_edit_win("What's happening?" . (defined $account ? " ($account)" : ""), sub{twitter($account, shift)});
+}
+
+sub cmd_twitter_direct {
+    my $cmd = shift;
+    my $user = shift;
+    die("Usage: $cmd USER\n") unless $user;
+    my $account = find_account_default(shift);
+    BarnOwl::start_edit_win("$cmd $user " . ($account->nickname||""),
+                            sub { $account->twitter_direct($user, shift) });
+}
+
+sub cmd_twitter_atreply {
+    my $cmd  = shift;
+    my $user = shift || die("Usage: $cmd USER [In-Reply-To ID]\n");
+    my $id   = shift;
+    my $account = find_account_default(shift);
+
+    BarnOwl::start_edit_win("Reply to \@" . $user . ($account->nickname ? (" on " . $account->nickname) : ""),
+                            sub { $account->twitter_atreply($user, $id, shift) });
+    BarnOwl::Editwin::insert_text("\@$user ");
+}
+
+sub cmd_twitter_retweet {
+    my $cmd = shift;
+    my $account = shift;
+    my $m = BarnOwl::getcurmsg();
+    if(!$m || $m->type ne 'Twitter') {
+        die("$cmd must be used with a Twitter message selected.\n");
+    }
+
+    $account = $m->account unless defined($account);
+    find_account($account)->twitter_retweet($m);
+    return;
+}
+
+sub cmd_twitter_follow {
+    my $cmd = shift;
+    my $user = shift;
+    die("Usage: $cmd USER\n") unless $user;
+    my $account = shift;
+    find_account_default($account)->twitter_follow($user);
+}
+
+sub cmd_twitter_unfollow {
+    my $cmd = shift;
+    my $user = shift;
+    die("Usage: $cmd USER\n") unless $user;
+    my $account = shift;
+    find_account_default($account)->twitter_unfollow($user);
+}
+
+use BarnOwl::Editwin qw(:all);
+sub cmd_count_chars {
+    my $cmd = shift;
+    my $text = save_excursion {
+        move_to_buffer_start();
+        set_mark();
+        move_to_buffer_end();
+        get_region();
+    };
+    my $len = length($text);
+    BarnOwl::message($len);
+    return $len;
+}
+
+eval {
+    $BarnOwl::Hooks::receiveMessage->add("BarnOwl::Module::Twitter::handle_message");
+};
+if($@) {
+    $BarnOwl::Hooks::receiveMessage->add(\&handle_message);
+}
+
+
+
+BarnOwl::filter(qw{twitter type ^twitter$});
+
+1;
Index: perl/modules/Twitter/lib/BarnOwl/Module/Twitter/Completion.pm
===================================================================
--- perl/modules/Twitter/lib/BarnOwl/Module/Twitter/Completion.pm	(revision None)
+++ perl/modules/Twitter/lib/BarnOwl/Module/Twitter/Completion.pm	(revision None)
@@ -0,0 +1,57 @@
+use strict;
+use warnings;
+
+package BarnOwl::Module::Twitter::Completion;
+
+my %users;
+
+sub complete_account {
+    my @nicks = grep {defined} map {$_->nickname} @BarnOwl::Module::Twitter::twitter_handles;
+}
+
+sub complete_user {
+    my $service = shift;
+    if(!$service) {
+        return map {keys %$_} values %users;
+    }
+    return keys %{$users{$service} || {}};
+}
+
+sub on_message {
+    my $m = shift;
+    return unless $m->type eq 'Twitter';
+
+    $users{$m->service}{$m->recipient} = 1;
+    $users{$m->service}{$m->sender} = 1;
+}
+
+sub complete_twitter {
+    my $ctx = shift;
+    return unless $ctx->word == 1;
+    return complete_account();
+}
+
+sub complete_twitter_user_account {
+    my $ctx = shift;
+    if ($ctx->word == 1) {
+        my $nick = $ctx->words->[2];
+        if ($nick) {
+            my  @completions = eval {
+                complete_user(BarnOwl::Module::Twitter::find_account($nick)->{cfg}{service});
+            };
+            return @completions if (!$@);
+        }
+        return complete_user();
+    } elsif ($ctx->word == 2) {
+        return complete_account();
+    }
+}
+
+BarnOwl::Completion::register_completer('twitter' => \&complete_twitter);
+BarnOwl::Completion::register_completer('twitter-direct' => \&complete_twitter_user_account);
+BarnOwl::Completion::register_completer('twitter-atreply' => \&complete_twitter_user_account);
+BarnOwl::Completion::register_completer('twitter-unfollow' => \&complete_twitter_user_account);
+
+$BarnOwl::Hooks::newMessage->add("BarnOwl::Module::Twitter::Completion::on_message");
+
+1;
Index: perl/modules/Twitter/lib/BarnOwl/Module/Twitter/Handle.pm
===================================================================
--- perl/modules/Twitter/lib/BarnOwl/Module/Twitter/Handle.pm	(revision 5d59c1e2fa97f16e6d4846aafc08a19b4c51b145)
+++ perl/modules/Twitter/lib/BarnOwl/Module/Twitter/Handle.pm	(revision 5d59c1e2fa97f16e6d4846aafc08a19b4c51b145)
@@ -0,0 +1,428 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+BarnOwl::Module::Twitter::Handle
+
+=head1 DESCRIPTION
+
+Contains everything needed to send and receive messages from a Twitter-like service.
+
+=cut
+
+package BarnOwl::Module::Twitter::Handle;
+
+use Net::Twitter::Lite;
+BEGIN {
+    # Backwards compatibility with version of Net::Twitter::Lite that
+    # lack home_timeline.
+    if(!defined(*Net::Twitter::Lite::home_timeline{CODE})) {
+        *Net::Twitter::Lite::home_timeline =
+          \&Net::Twitter::Lite::friends_timeline;
+    }
+}
+use HTML::Entities;
+
+use Scalar::Util qw(weaken);
+
+use BarnOwl;
+use BarnOwl::Message::Twitter;
+use POSIX qw(asctime);
+
+use constant BARNOWL_CONSUMER_KEY    => "9Py27vCQl6uB5V7ijmp31A";
+use constant BARNOWL_CONSUMER_SECRET => "GLhheSim8P5cVuk9FTM99KTEgWLW0LGl7gf54QWfg";
+
+sub fail {
+    my $self = shift;
+    my $msg = shift;
+    undef $self->{twitter};
+    my $nickname = $self->{cfg}->{account_nickname} || "";
+    die("[Twitter $nickname] Error: $msg\n");
+}
+
+sub new {
+    my $class = shift;
+    my $cfg = shift;
+
+    my $val;
+
+    if(!exists $cfg->{default} &&
+       defined($val = delete $cfg->{default_sender})) {
+        $cfg->{default} = $val;
+    }
+
+    if(!exists $cfg->{show_mentions} &&
+       defined($val = delete $cfg->{show_unsubscribed_replies})) {
+        $cfg->{show_mentions} = $val;
+    }
+
+    $cfg = {
+        account_nickname => '',
+        default          => 0,
+        poll_for_tweets  => 1,
+        poll_for_dms     => 1,
+        publish_tweets   => 0,
+        show_mentions    => 1,
+        %$cfg
+       };
+
+    my $self = {
+        'cfg'  => $cfg,
+        'twitter' => undef,
+        'last_id' => undef,
+        'last_direct' => undef,
+        'timer'        => undef,
+        'direct_timer' => undef
+    };
+
+    bless($self, $class);
+
+    my %twitter_args = @_;
+
+    my ($username, $password, $xauth);
+
+    if (*Net::Twitter::Lite::xauth{CODE}) {
+        $xauth = 1;
+        $username = delete $twitter_args{username};
+        $password = delete $twitter_args{password};
+        $twitter_args{consumer_key}    = BARNOWL_CONSUMER_KEY;
+        $twitter_args{consumer_secret} = BARNOWL_CONSUMER_SECRET;
+    } else {
+        BarnOwl::error("Please upgrade your version of Net::Twitter::Lite to support xAuth.");
+    }
+
+    $self->{twitter}  = Net::Twitter::Lite->new(%twitter_args,);
+
+    if ($xauth){
+        eval {
+            $self->{twitter}->xauth($username, $password);
+        };
+        if($@) {
+            $self->fail("Invalid credentials: $@");
+        }
+    }
+
+    my $timeline = eval { $self->{twitter}->home_timeline({count => 1}) };
+    warn "$@\n" if $@;
+
+    if(!defined($timeline) && !$xauth) {
+        $self->fail("Invalid credentials");
+    }
+
+    eval {
+        $self->{last_id} = $timeline->[0]{id};
+    };
+    $self->{last_id} = 1 unless defined($self->{last_id});
+
+    eval {
+        $self->{last_direct} = $self->{twitter}->direct_messages()->[0]{id};
+    };
+    warn "$@\n" if $@;
+    $self->{last_direct} = 1 unless defined($self->{last_direct});
+
+    eval {
+        $self->{twitter}->{ua}->timeout(1);
+    };
+    warn "$@\n" if $@;
+
+    $self->sleep(0);
+
+    return $self;
+}
+
+=head2 sleep SECONDS
+
+Stop polling Twitter for SECONDS seconds.
+
+=cut
+
+sub sleep {
+    my $self  = shift;
+    my $delay = shift;
+
+    my $weak = $self;
+    weaken($weak);
+
+    if($self->{cfg}->{poll_for_tweets}) {
+        $self->{timer} = BarnOwl::Timer->new({
+            after    => $delay,
+            interval => 90,
+            cb       => sub { $weak->poll_twitter if $weak }
+           });
+    }
+
+    if($self->{cfg}->{poll_for_dms}) {
+        $self->{direct_timer} = BarnOwl::Timer->new({
+            after    => $delay,
+            interval => 180,
+            cb       => sub { $weak->poll_direct if $weak }
+           });
+    }
+}
+
+=head2 twitter_command COMMAND ARGS...
+
+Call the specified method on $self->{twitter} with an extended
+timeout. This is intended for interactive commands, with the theory
+that if the user explicitly requested an action, it is slightly more
+acceptable to hang the UI for a second or two than to fail just
+because Twitter is being slightly slow. Polling commands should be
+called normally, with the default (short) timeout, to prevent
+background Twitter suckage from hosing the UI normally.
+
+=cut
+
+sub twitter_command {
+    my $self = shift;
+    my $cmd = shift;
+
+    eval { $self->{twitter}->{ua}->timeout(5); };
+    my $result = eval {
+        $self->{twitter}->$cmd(@_);
+    };
+    my $error = $@;
+    eval { $self->{twitter}->{ua}->timeout(1); };
+    if ($error) {
+        die($error);
+    }
+    return $result;
+}
+
+sub twitter_error {
+    my $self = shift;
+
+    my $ratelimit = eval { $self->{twitter}->rate_limit_status };
+    BarnOwl::debug($@) if $@;
+    unless(defined($ratelimit) && ref($ratelimit) eq 'HASH') {
+        # Twitter's probably just sucking, try again later.
+        $self->sleep(5*60);
+        return;
+    }
+
+    if(exists($ratelimit->{remaining_hits})
+       && $ratelimit->{remaining_hits} <= 0) {
+        my $timeout = $ratelimit->{reset_time_in_seconds};
+        $self->sleep($timeout - time + 60);
+        BarnOwl::error("Twitter" .
+                       ($self->{cfg}->{account_nickname} ?
+                        "[$self->{cfg}->{account_nickname}]" : "") .
+                        ": ratelimited until " . asctime(localtime($timeout)));
+    } elsif(exists($ratelimit->{error})) {
+        $self->sleep(60*20);
+        die("Twitter: ". $ratelimit->{error} . "\n");
+    }
+}
+
+sub poll_twitter {
+    my $self = shift;
+
+    return unless BarnOwl::getvar('twitter:poll') eq 'on';
+
+    BarnOwl::debug("Polling " . $self->{cfg}->{account_nickname});
+
+    my $timeline = eval { $self->{twitter}->home_timeline( { since_id => $self->{last_id} } ) };
+    BarnOwl::debug($@) if $@;
+    unless(defined($timeline) && ref($timeline) eq 'ARRAY') {
+        $self->twitter_error();
+        return;
+    };
+
+    if ($self->{cfg}->{show_mentions}) {
+        my $mentions = eval { $self->{twitter}->mentions( { since_id => $self->{last_id} } ) };
+        BarnOwl::debug($@) if $@;
+        unless (defined($mentions) && ref($mentions) eq 'ARRAY') {
+            $self->twitter_error();
+            return;
+        };
+        #combine, sort by id, and uniq
+        push @$timeline, @$mentions;
+        @$timeline = sort { $b->{id} <=> $a->{id} } @$timeline;
+        my $prev = { id => 0 };
+        @$timeline = grep($_->{id} != $prev->{id} && (($prev) = $_), @$timeline);
+    }
+
+    if ( scalar @$timeline ) {
+        for my $tweet ( reverse @$timeline ) {
+            if ( $tweet->{id} <= $self->{last_id} ) {
+                next;
+            }
+            my $orig = $tweet->{retweeted_status};
+            $orig = $tweet unless defined($orig);
+
+            my $msg = BarnOwl::Message->new(
+                type      => 'Twitter',
+                sender    => $orig->{user}{screen_name},
+                recipient => $self->{cfg}->{user} || $self->{user},
+                direction => 'in',
+                source    => decode_entities($orig->{source}),
+                location  => decode_entities($orig->{user}{location}||""),
+                body      => decode_entities($orig->{text}),
+                status_id => $tweet->{id},
+                service   => $self->{cfg}->{service},
+                account   => $self->{cfg}->{account_nickname},
+                $tweet->{retweeted_status} ? (retweeted_by => $tweet->{user}{screen_name}) : ()
+               );
+            BarnOwl::queue_message($msg);
+        }
+        $self->{last_id} = $timeline->[0]{id} if $timeline->[0]{id} > $self->{last_id};
+    } else {
+        # BarnOwl::message("No new tweets...");
+    }
+}
+
+sub poll_direct {
+    my $self = shift;
+
+    return unless BarnOwl::getvar('twitter:poll') eq 'on';
+
+    BarnOwl::debug("Polling direct for " . $self->{cfg}->{account_nickname});
+
+    my $direct = eval { $self->{twitter}->direct_messages( { since_id => $self->{last_direct} } ) };
+    BarnOwl::debug($@) if $@;
+    unless(defined($direct) && ref($direct) eq 'ARRAY') {
+        $self->twitter_error();
+        return;
+    };
+    if ( scalar @$direct ) {
+        for my $tweet ( reverse @$direct ) {
+            if ( $tweet->{id} <= $self->{last_direct} ) {
+                next;
+            }
+            my $msg = BarnOwl::Message->new(
+                type      => 'Twitter',
+                sender    => $tweet->{sender}{screen_name},
+                recipient => $self->{cfg}->{user} || $self->{user},
+                direction => 'in',
+                location  => decode_entities($tweet->{sender}{location}||""),
+                body      => decode_entities($tweet->{text}),
+                private => 'true',
+                service   => $self->{cfg}->{service},
+                account   => $self->{cfg}->{account_nickname},
+               );
+            BarnOwl::queue_message($msg);
+        }
+        $self->{last_direct} = $direct->[0]{id} if $direct->[0]{id} > $self->{last_direct};
+    } else {
+        # BarnOwl::message("No new tweets...");
+    }
+}
+
+sub _stripnl {
+    my $msg = shift;
+
+    # strip non-newline whitespace around newlines
+    $msg =~ s/[^\n\S]*(\n+)[^\n\S]*/$1/sg;
+    # change single newlines to a single space; leave multiple newlines
+    $msg =~ s/([^\n])\n([^\n])/$1 $2/sg;
+    # strip leading and trailing whitespace
+    $msg =~ s/\s+$//s;
+    $msg =~ s/^\s+//s;
+
+    return $msg;
+}
+
+sub twitter {
+    my $self = shift;
+
+    my $msg = _stripnl(shift);
+    my $reply_to = shift;
+
+    if($msg =~ m{\Ad\s+([^\s])+(.*)}sm) {
+        $self->twitter_direct($1, $2);
+    } elsif(defined $self->{twitter}) {
+        if(length($msg) > 140) {
+            die("Twitter: Message over 140 characters long.\n");
+        }
+        $self->twitter_command('update', {
+            status => $msg,
+            defined($reply_to) ? (in_reply_to_status_id => $reply_to) : ()
+           });
+    }
+    $self->poll_twitter if $self->{cfg}->{poll_for_tweets};
+}
+
+sub twitter_direct {
+    my $self = shift;
+
+    my $who = shift;
+    my $msg = _stripnl(shift);
+
+    if(defined $self->{twitter}) {
+        $self->twitter_command('new_direct_message', {
+            user => $who,
+            text => $msg
+           });
+        if(BarnOwl::getvar("displayoutgoing") eq 'on') {
+            my $tweet = BarnOwl::Message->new(
+                type      => 'Twitter',
+                sender    => $self->{cfg}->{user} || $self->{user},
+                recipient => $who, 
+                direction => 'out',
+                body      => $msg,
+                private => 'true',
+                service   => $self->{cfg}->{service},
+               );
+            BarnOwl::queue_message($tweet);
+        }
+    }
+}
+
+sub twitter_atreply {
+    my $self = shift;
+
+    my $to  = shift;
+    my $id  = shift;
+    my $msg = shift;
+    if(defined($id)) {
+        $self->twitter($msg, $id);
+    } else {
+        $self->twitter($msg);
+    }
+}
+
+sub twitter_retweet {
+    my $self = shift;
+    my $msg = shift;
+
+    if($msg->service ne $self->{cfg}->{service}) {
+        die("Cannot retweet a message from a different service.\n");
+    }
+    $self->twitter_command(retweet => $msg->{status_id});
+    $self->poll_twitter if $self->{cfg}->{poll_for_tweets};
+}
+
+sub twitter_follow {
+    my $self = shift;
+
+    my $who = shift;
+
+    my $user = $self->twitter_command('create_friend', $who);
+    # returns a string on error
+    if (defined $user && !ref $user) {
+        BarnOwl::message($user);
+    } else {
+        BarnOwl::message("Following " . $who);
+    }
+}
+
+sub twitter_unfollow {
+    my $self = shift;
+
+    my $who = shift;
+
+    my $user = $self->twitter_command('destroy_friend', $who);
+    # returns a string on error
+    if (defined $user && !ref $user) {
+        BarnOwl::message($user);
+    } else {
+        BarnOwl::message("No longer following " . $who);
+    }
+}
+
+sub nickname {
+    my $self = shift;
+    return $self->{cfg}->{account_nickname};
+}
+
+1;
Index: perl/modules/Twitter/twitter.example
===================================================================
--- perl/modules/Twitter/twitter.example	(revision None)
+++ perl/modules/Twitter/twitter.example	(revision None)
@@ -0,0 +1,4 @@
+{
+  "user":  "myusername",
+  "password": "mypassword"
+}
Index: perlconfig.c
===================================================================
--- perlconfig.c	(revision 5aa33fd73dce2c8d6c88559e62cd1ae78ef4a36d)
+++ perlconfig.c	(revision 5aa33fd73dce2c8d6c88559e62cd1ae78ef4a36d)
@@ -584,17 +584,4 @@
 }
 
-void owl_perlconfig_mainloop(owl_timer *t, void *data)
-{
-  dSP;
-  if (!owl_perlconfig_is_function("BarnOwl::Hooks::_mainloop_hook"))
-    return;
-  PUSHMARK(SP) ;
-  call_pv("BarnOwl::Hooks::_mainloop_hook", G_DISCARD|G_EVAL);
-  if(SvTRUE(ERRSV)) {
-    owl_function_error("%s", SvPV_nolen(ERRSV));
-  }
-  return;
-}
-
 void owl_perlconfig_io_dispatch(const owl_io_dispatch *d, void *data)
 {
Index: popexec.c
===================================================================
--- popexec.c	(revision 125fd21c2d564a4a7d0a027552b7156a4d694243)
+++ popexec.c	(revision 07b59ea301a55bf3db1724aa9571e7048fe89857)
@@ -27,10 +27,6 @@
 
   owl_popwin_up(pw);
-  owl_global_push_context(&g, OWL_CTX_POPLESS, v, "popless");
-  owl_viewwin_init_text(v, owl_popwin_get_curswin(pw),
-			owl_popwin_get_lines(pw), owl_popwin_get_cols(pw),
-			"");
-  owl_viewwin_redisplay(v);
-  owl_global_set_needrefresh(&g);
+  owl_global_push_context(&g, OWL_CTX_POPLESS, v, "popless", NULL);
+  owl_viewwin_init_text(v, owl_popwin_get_content(pw), "");
   owl_viewwin_set_onclose_hook(v, owl_popexec_viewwin_onclose, pe);
   pe->refcount++;
@@ -111,6 +107,4 @@
     if (pe->winactive) { 
       owl_viewwin_append_text(pe->vwin, "\n");
-      owl_viewwin_redisplay(pe->vwin);
-      owl_global_set_needrefresh(&g);
     }
     owl_select_remove_io_dispatch(d);
@@ -139,6 +133,4 @@
   if (pe->winactive) {
     owl_viewwin_append_text(pe->vwin, buf);
-    owl_viewwin_redisplay(pe->vwin);
-    owl_global_set_needrefresh(&g);
   }
   owl_free(buf);
Index: popwin.c
===================================================================
--- popwin.c	(revision 0881cdda52baa4677179208b39a3392f06182cf5)
+++ popwin.c	(revision 05ca0d8685143d49b9e1c7d2949a55f6976c27ff)
@@ -4,6 +4,4 @@
 {
   pw->active=0;
-  pw->lines=0;
-  pw->cols=0;
   return(0);
 }
@@ -11,24 +9,47 @@
 int owl_popwin_up(owl_popwin *pw)
 {
-  int glines, gcols, startcol, startline;
-  WINDOW *popwin, *borderwin;
+  pw->border = owl_window_new(NULL);
+  pw->content = owl_window_new(pw->border);
+  g_signal_connect(pw->border, "redraw", G_CALLBACK(owl_popwin_draw_border), 0);
+  owl_signal_connect_object(owl_window_get_screen(), "resized", G_CALLBACK(owl_popwin_size_border), pw->border, 0);
+  owl_signal_connect_object(pw->border, "resized", G_CALLBACK(owl_popwin_size_content), pw->content, 0);
 
-  /* calculate the size of the popwin */
-  glines=owl_global_get_lines(&g);
-  gcols=owl_global_get_cols(&g);
+  /* bootstrap sizing */
+  owl_popwin_size_border(owl_window_get_screen(), pw->border);
 
-  pw->lines = owl_util_min(glines,24)*3/4 + owl_util_max(glines-24,0)/2;
-  startline = (glines-pw->lines)/2;
+  owl_window_show_all(pw->border);
 
-  pw->cols = owl_util_min(gcols,90)*15/16 + owl_util_max(gcols-90,0)/2;
-  startcol = (gcols-pw->cols)/2;
+  pw->active=1;
+  return(0);
+}
 
-  borderwin = newwin(pw->lines, pw->cols, startline, startcol);
-  pw->borderpanel = new_panel(borderwin);
-  popwin = newwin(pw->lines-2, pw->cols-2, startline+1, startcol+1);
-  pw->poppanel = new_panel(popwin);
+void owl_popwin_size_border(owl_window *parent, void *user_data)
+{
+  int lines, cols, startline, startcol;
+  int glines, gcols;
+  owl_window *border = user_data;
 
-  werase(popwin);
-  werase(borderwin);
+  owl_window_get_position(parent, &glines, &gcols, 0, 0);
+
+  lines = owl_util_min(glines,24)*3/4 + owl_util_max(glines-24,0)/2;
+  startline = (glines-lines)/2;
+  cols = owl_util_min(gcols,90)*15/16 + owl_util_max(gcols-90,0)/2;
+  startcol = (gcols-cols)/2;
+
+  owl_window_set_position(border, lines, cols, startline, startcol);
+}
+
+void owl_popwin_size_content(owl_window *parent, void *user_data)
+{
+  int lines, cols;
+  owl_window *content = user_data;
+  owl_window_get_position(parent, &lines, &cols, 0, 0);
+  owl_window_set_position(content, lines-2, cols-2, 1, 1);
+}
+
+void owl_popwin_draw_border(owl_window *w, WINDOW *borderwin, void *user_data)
+{
+  int lines, cols;
+  owl_window_get_position(w, &lines, &cols, 0, 0);
   if (owl_global_is_fancylines(&g)) {
     box(borderwin, 0, 0);
@@ -37,31 +58,22 @@
     wmove(borderwin, 0, 0);
     waddch(borderwin, '+');
-    wmove(borderwin, pw->lines-1, 0);
+    wmove(borderwin, lines-1, 0);
     waddch(borderwin, '+');
-    wmove(borderwin, pw->lines-1, pw->cols-1);
+    wmove(borderwin, lines-1, cols-1);
     waddch(borderwin, '+');
-    wmove(borderwin, 0, pw->cols-1);
+    wmove(borderwin, 0, cols-1);
     waddch(borderwin, '+');
   }
-    
-  owl_global_set_needrefresh(&g);
-  pw->active=1;
-  return(0);
 }
 
 int owl_popwin_close(owl_popwin *pw)
 {
-  WINDOW *popwin, *borderwin;
+  owl_window_unlink(pw->border);
+  g_object_unref(pw->border);
+  g_object_unref(pw->content);
 
-  popwin = panel_window(pw->poppanel);
-  borderwin = panel_window(pw->borderpanel);
-
-  del_panel(pw->poppanel);
-  del_panel(pw->borderpanel);
-  delwin(popwin);
-  delwin(borderwin);
-
+  pw->border = 0;
+  pw->content = 0;
   pw->active=0;
-  owl_global_set_needrefresh(&g);
   return(0);
 }
@@ -72,16 +84,6 @@
 }
 
-WINDOW *owl_popwin_get_curswin(const owl_popwin *pw)
+owl_window *owl_popwin_get_content(const owl_popwin *pw)
 {
-  return panel_window(pw->poppanel);
+  return pw->content;
 }
-
-int owl_popwin_get_lines(const owl_popwin *pw)
-{
-  return(pw->lines-2);
-}
-
-int owl_popwin_get_cols(const owl_popwin *pw)
-{
-  return(pw->cols-2);
-}
Index: sepbar.c
===================================================================
--- sepbar.c	(revision 044f19f7c86e3f63b353e7fff2865f139319a45a)
+++ sepbar.c	(revision 044f19f7c86e3f63b353e7fff2865f139319a45a)
@@ -0,0 +1,116 @@
+#include "owl.h"
+
+static void sepbar_redraw(owl_window *w, WINDOW *sepwin, void *user_data);
+
+void owl_sepbar_init(owl_window *w)
+{
+  g_signal_connect(w, "redraw", G_CALLBACK(sepbar_redraw), NULL);
+  /* TODO: handle dirtiness in the sepbar */
+  owl_window_dirty(w);
+}
+
+static void sepbar_redraw(owl_window *w, WINDOW *sepwin, void *user_data)
+{
+  const owl_messagelist *ml;
+  const owl_view *v;
+  int x, y, i;
+  const char *foo, *appendtosepbar;
+
+  ml=owl_global_get_msglist(&g);
+  v=owl_global_get_current_view(&g);
+
+  werase(sepwin);
+  wattron(sepwin, A_REVERSE);
+  if (owl_global_is_fancylines(&g)) {
+    whline(sepwin, ACS_HLINE, owl_global_get_cols(&g));
+  } else {
+    whline(sepwin, '-', owl_global_get_cols(&g));
+  }
+
+  if (owl_global_is_sepbar_disable(&g)) {
+    getyx(sepwin, y, x);
+    wmove(sepwin, y, owl_global_get_cols(&g)-1);
+    return;
+  }
+
+  wmove(sepwin, 0, 2);
+
+  if (owl_messagelist_get_size(ml) == 0)
+    waddstr(sepwin, " (-/-) ");
+  else
+    wprintw(sepwin, " (%i/%i/%i) ", owl_global_get_curmsg(&g) + 1,
+            owl_view_get_size(v),
+            owl_messagelist_get_size(ml));
+
+  foo=owl_view_get_filtname(v);
+  if (strcmp(foo, owl_global_get_view_home(&g)))
+      wattroff(sepwin, A_REVERSE);
+  wprintw(sepwin, " %s ", owl_view_get_filtname(v));
+  if (strcmp(foo, owl_global_get_view_home(&g)))
+      wattron(sepwin, A_REVERSE);
+
+  if (owl_mainwin_is_curmsg_truncated(owl_global_get_mainwin(&g))) {
+    getyx(sepwin, y, x);
+    wmove(sepwin, y, x+2);
+    wattron(sepwin, A_BOLD);
+    waddstr(sepwin, " <truncated> ");
+    wattroff(sepwin, A_BOLD);
+  }
+
+  i=owl_mainwin_get_last_msg(owl_global_get_mainwin(&g));
+  if ((i != -1) &&
+      (i < owl_view_get_size(v)-1)) {
+    getyx(sepwin, y, x);
+    wmove(sepwin, y, x+2);
+    wattron(sepwin, A_BOLD);
+    waddstr(sepwin, " <more> ");
+    wattroff(sepwin, A_BOLD);
+  }
+
+  if (owl_global_get_rightshift(&g)>0) {
+    getyx(sepwin, y, x);
+    wmove(sepwin, y, x+2);
+    wprintw(sepwin, " right: %i ", owl_global_get_rightshift(&g));
+  }
+
+  if (owl_global_is_zaway(&g) || owl_global_is_aaway(&g)) {
+    getyx(sepwin, y, x);
+    wmove(sepwin, y, x+2);
+    wattron(sepwin, A_BOLD);
+    wattroff(sepwin, A_REVERSE);
+    if (owl_global_is_zaway(&g) && owl_global_is_aaway(&g)) {
+      waddstr(sepwin, " AWAY ");
+    } else if (owl_global_is_zaway(&g)) {
+      waddstr(sepwin, " Z-AWAY ");
+    } else if (owl_global_is_aaway(&g)) {
+      waddstr(sepwin, " A-AWAY ");
+    }
+    wattron(sepwin, A_REVERSE);
+    wattroff(sepwin, A_BOLD);
+  }
+
+  if (owl_global_get_curmsg_vert_offset(&g)) {
+    getyx(sepwin, y, x);
+    wmove(sepwin, y, x+2);
+    wattron(sepwin, A_BOLD);
+    wattroff(sepwin, A_REVERSE);
+    waddstr(sepwin, " SCROLL ");
+    wattron(sepwin, A_REVERSE);
+    wattroff(sepwin, A_BOLD);
+  }
+
+  appendtosepbar = owl_global_get_appendtosepbar(&g);
+  if (appendtosepbar && *appendtosepbar) {
+    getyx(sepwin, y, x);
+    wmove(sepwin, y, x+2);
+    waddstr(sepwin, " ");
+    waddstr(sepwin, owl_global_get_appendtosepbar(&g));
+    waddstr(sepwin, " ");
+  }
+
+  getyx(sepwin, y, x);
+  wmove(sepwin, y, owl_global_get_cols(&g)-1);
+    
+  wattroff(sepwin, A_BOLD);
+  wattroff(sepwin, A_REVERSE);
+}
Index: stubgen.pl
===================================================================
--- stubgen.pl	(revision d7cc50b9bac64c7e71cb55c8686076c33f1f07dd)
+++ stubgen.pl	(revision ea7daa88a058dbbe1dfd9aa2f138f80d030b2f9c)
@@ -12,21 +12,37 @@
     my $altvarname = $2;
     $altvarname = $3 if ($3);
+    my $detailname = $altvarname;
+    $detailname =~ s/[^a-zA-Z0-9]/-/g;
+    $detailname =~ s/^[^a-zA-Z]+//;
     if ($vartype =~ /^BOOL/) {
-	print "void owl_global_set_${altvarname}_on(owl_global *g) {\n";
-	print "  owl_variable_set_bool_on(&g->vars, \"$varname\");\n}\n";
-	print "void owl_global_set_${altvarname}_off(owl_global *g) {\n";
-	print "  owl_variable_set_bool_off(&g->vars, \"$varname\");\n}\n";
-	print "int owl_global_is_$altvarname(const owl_global *g) {\n";
-	print "  return owl_variable_get_bool(&g->vars, \"$varname\");\n}\n";
+        print <<EOT;
+void owl_global_set_${altvarname}_on(owl_global *g) {
+  owl_variable_set_bool_on(&g->vars, "$altvarname");
+}
+void owl_global_set_${altvarname}_off(owl_global *g) {
+  owl_variable_set_bool_off(&g->vars, "$altvarname");
+}
+int owl_global_is_$altvarname(const owl_global *g) {
+  return owl_variable_get_bool(&g->vars, "$varname");
+}
+EOT
     } elsif ($vartype =~ /^PATH/ or $vartype =~ /^STRING/) {
-	print "void owl_global_set_$altvarname(owl_global *g, const char *text) {\n";
-	print "  owl_variable_set_string(&g->vars, \"$varname\", text);\n}\n";
-	print "const char *owl_global_get_$altvarname(const owl_global *g) {\n";
-	print "  return owl_variable_get_string(&g->vars, \"$varname\");\n}\n";
+        print <<EOT;
+void owl_global_set_${altvarname}(owl_global *g, const char *text) {
+  owl_variable_set_string(&g->vars, "$altvarname", text);
+}
+const char *owl_global_get_$altvarname(const owl_global *g) {
+  return owl_variable_get_string(&g->vars, "$varname");
+}
+EOT
     } elsif ($vartype =~ /^INT/ or $vartype =~ /^ENUM/) {
-	print "void owl_global_set_$altvarname(owl_global *g, int n) {\n";
-	print "  owl_variable_set_int(&g->vars, \"$varname\", n);\n}\n";
-	print "int owl_global_get_$altvarname(const owl_global *g) {\n";
-	print "  return owl_variable_get_int(&g->vars, \"$varname\");\n}\n";
+        print <<EOT;
+void owl_global_set_${altvarname}(owl_global *g, int n) {
+  owl_variable_set_int(&g->vars, "$altvarname", n);
+}
+int owl_global_get_$altvarname(const owl_global *g) {
+  return owl_variable_get_int(&g->vars, "$varname");
+}
+EOT
     } 
     }
Index: tester.c
===================================================================
--- tester.c	(revision 95414bfd3960e215dd54e6916a12f56abbf0c820)
+++ tester.c	(revision 95414bfd3960e215dd54e6916a12f56abbf0c820)
@@ -105,5 +105,4 @@
   numfailures += owl_variable_regtest();
   numfailures += owl_filter_regtest();
-  numfailures += owl_obarray_regtest();
   numfailures += owl_editwin_regtest();
   if (numfailures) {
@@ -368,33 +367,4 @@
 }
 
-
-int owl_obarray_regtest(void) {
-  int numfailed = 0;
-  const char *p,*p2;
-
-  owl_obarray oa;
-  owl_obarray_init(&oa);
-
-  printf("# BEGIN testing owl_obarray\n");
-
-  p = owl_obarray_insert(&oa, "test");
-  FAIL_UNLESS("returned string is equal", p && !strcmp(p, "test"));
-  p2 = owl_obarray_insert(&oa, "test");
-  FAIL_UNLESS("returned string is equal", p2 && !strcmp(p2, "test"));
-  FAIL_UNLESS("returned the same string", p2 && p == p2);
-
-  p = owl_obarray_insert(&oa, "test2");
-  FAIL_UNLESS("returned string is equal", p && !strcmp(p, "test2"));
-  p2 = owl_obarray_find(&oa, "test2");
-  FAIL_UNLESS("returned the same string", p2 && !strcmp(p2, "test2"));
-
-  p = owl_obarray_find(&oa, "nothere");
-  FAIL_UNLESS("Didn't find a string that isn't there", p == NULL);
-
-  printf("# END testing owl_obarray (%d failures)\n", numfailed);
-
-  return numfailed;
-}
-
 int owl_editwin_regtest(void) {
   int numfailed = 0;
Index: util.c
===================================================================
--- util.c	(revision 9a7b4f23cba7fc71c4e918b5cb8d5ca6789924e3)
+++ util.c	(revision c1f1e1e3f61fcf6b7a60ce2c7a2656f31c6277c4)
@@ -8,117 +8,5 @@
 #include <sys/types.h>
 
-void sepbar(const char *in)
-{
-  WINDOW *sepwin;
-  const owl_messagelist *ml;
-  const owl_view *v;
-  int x, y, i;
-  const char *foo, *appendtosepbar;
-
-  sepwin=owl_global_get_curs_sepwin(&g);
-  ml=owl_global_get_msglist(&g);
-  v=owl_global_get_current_view(&g);
-
-  werase(sepwin);
-  wattron(sepwin, A_REVERSE);
-  if (owl_global_is_fancylines(&g)) {
-    whline(sepwin, ACS_HLINE, owl_global_get_cols(&g));
-  } else {
-    whline(sepwin, '-', owl_global_get_cols(&g));
-  }
-
-  if (owl_global_is_sepbar_disable(&g)) {
-    getyx(sepwin, y, x);
-    wmove(sepwin, y, owl_global_get_cols(&g)-1);
-    return;
-  }
-
-  wmove(sepwin, 0, 2);
-
-  if (owl_messagelist_get_size(ml) == 0)
-    waddstr(sepwin, " (-/-) ");
-  else
-    wprintw(sepwin, " (%i/%i/%i) ", owl_global_get_curmsg(&g) + 1,
-            owl_view_get_size(v),
-            owl_messagelist_get_size(ml));
-
-  foo=owl_view_get_filtname(v);
-  if (strcmp(foo, owl_global_get_view_home(&g)))
-      wattroff(sepwin, A_REVERSE);
-  wprintw(sepwin, " %s ", owl_view_get_filtname(v));
-  if (strcmp(foo, owl_global_get_view_home(&g)))
-      wattron(sepwin, A_REVERSE);
-
-  if (owl_mainwin_is_curmsg_truncated(owl_global_get_mainwin(&g))) {
-    getyx(sepwin, y, x);
-    wmove(sepwin, y, x+2);
-    wattron(sepwin, A_BOLD);
-    waddstr(sepwin, " <truncated> ");
-    wattroff(sepwin, A_BOLD);
-  }
-
-  i=owl_mainwin_get_last_msg(owl_global_get_mainwin(&g));
-  if ((i != -1) &&
-      (i < owl_view_get_size(v)-1)) {
-    getyx(sepwin, y, x);
-    wmove(sepwin, y, x+2);
-    wattron(sepwin, A_BOLD);
-    waddstr(sepwin, " <more> ");
-    wattroff(sepwin, A_BOLD);
-  }
-
-  if (owl_global_get_rightshift(&g)>0) {
-    getyx(sepwin, y, x);
-    wmove(sepwin, y, x+2);
-    wprintw(sepwin, " right: %i ", owl_global_get_rightshift(&g));
-  }
-
-  if (owl_global_is_zaway(&g) || owl_global_is_aaway(&g)) {
-    getyx(sepwin, y, x);
-    wmove(sepwin, y, x+2);
-    wattron(sepwin, A_BOLD);
-    wattroff(sepwin, A_REVERSE);
-    if (owl_global_is_zaway(&g) && owl_global_is_aaway(&g)) {
-      waddstr(sepwin, " AWAY ");
-    } else if (owl_global_is_zaway(&g)) {
-      waddstr(sepwin, " Z-AWAY ");
-    } else if (owl_global_is_aaway(&g)) {
-      waddstr(sepwin, " A-AWAY ");
-    }
-    wattron(sepwin, A_REVERSE);
-    wattroff(sepwin, A_BOLD);
-  }
-
-  if (owl_global_get_curmsg_vert_offset(&g)) {
-    getyx(sepwin, y, x);
-    wmove(sepwin, y, x+2);
-    wattron(sepwin, A_BOLD);
-    wattroff(sepwin, A_REVERSE);
-    waddstr(sepwin, " SCROLL ");
-    wattron(sepwin, A_REVERSE);
-    wattroff(sepwin, A_BOLD);
-  }
-  
-  if (in) {
-    getyx(sepwin, y, x);
-    wmove(sepwin, y, x+2);
-    waddstr(sepwin, in);
-  }
-
-  appendtosepbar = owl_global_get_appendtosepbar(&g);
-  if (appendtosepbar && *appendtosepbar) {
-    getyx(sepwin, y, x);
-    wmove(sepwin, y, x+2);
-    waddstr(sepwin, " ");
-    waddstr(sepwin, owl_global_get_appendtosepbar(&g));
-    waddstr(sepwin, " ");
-  }
-
-  getyx(sepwin, y, x);
-  wmove(sepwin, y, owl_global_get_cols(&g)-1);
-    
-  wattroff(sepwin, A_BOLD);
-  wattroff(sepwin, A_REVERSE);
-}
+#include <glib-object.h>
 
 char **atokenize(const char *buffer, const char *sep, int *i)
@@ -786,2 +674,77 @@
   return buf;
 }
+
+gulong owl_dirty_window_on_signal(owl_window *w, gpointer sender, const gchar *detailed_signal)
+{
+  return owl_signal_connect_object(sender, detailed_signal, G_CALLBACK(owl_window_dirty), w, G_CONNECT_SWAPPED);
+}
+
+typedef struct { /*noproto*/
+  GObject  *sender;
+  gulong    signal_id;
+} SignalData;
+
+static void _closure_invalidated(gpointer data, GClosure *closure);
+
+/*
+ * GObject's g_signal_connect_object has a documented bug. This function is
+ * identical except it does not leak the signal handler.
+ */
+gulong owl_signal_connect_object(gpointer sender, const gchar *detailed_signal, GCallback c_handler, gpointer receiver, GConnectFlags connect_flags)
+{
+  g_return_val_if_fail (G_TYPE_CHECK_INSTANCE (sender), 0);
+  g_return_val_if_fail (detailed_signal != NULL, 0);
+  g_return_val_if_fail (c_handler != NULL, 0);
+
+  if (receiver) {
+    SignalData *sdata;
+    GClosure *closure;
+    gulong signal_id;
+
+    g_return_val_if_fail (G_IS_OBJECT (receiver), 0);
+
+    closure = ((connect_flags & G_CONNECT_SWAPPED) ? g_cclosure_new_object_swap : g_cclosure_new_object) (c_handler, receiver);
+    signal_id = g_signal_connect_closure (sender, detailed_signal, closure, connect_flags & G_CONNECT_AFTER);
+
+    /* Register the missing hooks */
+    sdata = g_slice_new0(SignalData);
+    sdata->sender = sender;
+    sdata->signal_id = signal_id;
+
+    g_closure_add_invalidate_notifier(closure, sdata, _closure_invalidated);
+
+    return signal_id;
+  } else {
+    return g_signal_connect_data(sender, detailed_signal, c_handler, NULL, NULL, connect_flags);
+  }
+}
+
+/*
+ * There are three ways the signal could come to an end:
+ * 
+ * 1. The user explicitly disconnects it with the returned signal_id.
+ *    - In that case, the disconnection unref's the closure, causing it
+ *      to first be invalidated. The handler's already disconnected, so
+ *      we have no work to do.
+ * 2. The sender gets destroyed.
+ *    - GObject will disconnect each signal which then goes into the above
+ *      case. Our handler does no work.
+ * 3. The receiver gets destroyed.
+ *    - The GClosure was created by g_cclosure_new_object_{,swap} which gets
+ *      invalidated when the receiver is destroyed. We then follow through case 1
+ *      again, but *this* time, the handler has not been disconnected. We then
+ *      clean up ourselves.
+ *
+ * We can't actually hook into this process earlier with weakrefs as GObject
+ * will, on object dispose, first disconnect signals, then invalidate closures,
+ * and notify weakrefs last.
+ */
+static void _closure_invalidated(gpointer data, GClosure *closure)
+{
+  SignalData *sdata = data;
+  if (g_signal_handler_is_connected(sdata->sender, sdata->signal_id)) {
+    g_signal_handler_disconnect(sdata->sender, sdata->signal_id);
+  }
+  g_slice_free(SignalData, sdata);
+}
+
Index: variable.c
===================================================================
--- variable.c	(revision 26ad412433e18b39230531a2a518a03bffc1d3d4)
+++ variable.c	(revision 26ad412433e18b39230531a2a518a03bffc1d3d4)
@@ -110,5 +110,5 @@
 
   OWLVAR_ENUM( "loggingdirection" /* %OwlVarStub */, OWL_LOGGING_DIRECTION_BOTH,
-	       "specifices which kind of messages should be logged",
+	       "specifies which kind of messages should be logged",
 	       "Can be one of 'both', 'in', or 'out'.  If 'in' is\n"
 	       "selected, only incoming messages are logged, if 'out'\n"
@@ -138,5 +138,5 @@
 		    "location of users in your .anyone file.  If a user is present\n"
 		    "but sent no login message, or a user is not present that sent no\n"
-		    "logout message, a pseudo login or logout message wil be created\n",
+		    "logout message, a pseudo login or logout message will be created\n",
 		    NULL, owl_variable_pseudologins_set, NULL),
 
@@ -151,5 +151,5 @@
 
 		 "If non empty, any messages matching the given filter will be logged.\n"
-		 "This is a completely separate mechanisim from the other logging\n"
+		 "This is a completely separate mechanism from the other logging\n"
 		 "variables like logging, classlogging, loglogins, loggingdirection,\n"
 		 "etc.  If you want this variable to control all logging, make sure\n"
@@ -199,5 +199,5 @@
   OWLVAR_PATH( "newmsgproc" /* %OwlVarStub:newmsgproc */, NULL,
 	       "name of a program to run when new messages are present",
-	       "The named program will be run when owl recevies new.\n"
+	       "The named program will be run when owl receives new\n"
 	       "messages.  It will not be run again until the first\n"
 	       "instance exits"),
@@ -214,5 +214,5 @@
 		 "Called every time you start a zephyrgram without an\n"
 		 "explicit zsig.  The default setting implements the policy\n"
-		 "descripted in the documentation for the 'zsig' variable.\n"),
+		 "described in the documentation for the 'zsig' variable.\n"),
 
   OWLVAR_STRING( "zsig" /* %OwlVarStub */, "",
@@ -298,5 +298,5 @@
 	      "number of seconds after AIM login to ignore login messages",
 	      "This specifies the number of seconds to wait after an\n"
-	      "AIM login before allowing the recipt of AIM login notifications.\n"
+	      "AIM login before allowing the receipt of AIM login notifications.\n"
 	      "By default this is set to 15.  If you would like to view login\n"
 	      "notifications of buddies as soon as you login, set it to 0 instead."),
@@ -401,5 +401,5 @@
   int rv;
   rv = owl_variable_int_set_default(v, newval);
-  if (0 == rv) owl_global_set_relayout_pending(&g);
+  if (0 == rv) owl_mainpanel_layout_contents(&g.mainpanel);
   return(rv);
 }
Index: viewwin.c
===================================================================
--- viewwin.c	(revision fe4c786091ae4f138f1edc26a13b2b8d0b3a034d)
+++ viewwin.c	(revision 0b9e6070c08d1acf0e1258b9938f43062c01e491)
@@ -4,8 +4,10 @@
 #define BOTTOM_OFFSET 1
 
+static void owl_viewwin_redraw(owl_window *w, WINDOW *curswin, void *user_data);
+
 /* initialize the viewwin e.  'win' is an already initialzed curses
  * window that will be used by viewwin
  */
-void owl_viewwin_init_text(owl_viewwin *v, WINDOW *win, int winlines, int wincols, const char *text)
+void owl_viewwin_init_text(owl_viewwin *v, owl_window *win, const char *text)
 {
   owl_fmtext_init_null(&(v->fmtext));
@@ -19,8 +21,7 @@
   v->topline=0;
   v->rightshift=0;
-  v->winlines=winlines;
-  v->wincols=wincols;
-  v->curswin=win;
   v->onclose_hook = NULL;
+
+  owl_viewwin_set_window(v, win);
 }
 
@@ -28,4 +29,5 @@
     owl_fmtext_append_normal(&(v->fmtext), text);
     v->textlines=owl_fmtext_num_lines(&(v->fmtext));  
+    owl_window_dirty(v->window);
 }
 
@@ -33,5 +35,5 @@
  * window that will be used by viewwin
  */
-void owl_viewwin_init_fmtext(owl_viewwin *v, WINDOW *win, int winlines, int wincols, const owl_fmtext *fmtext)
+void owl_viewwin_init_fmtext(owl_viewwin *v, owl_window *win, const owl_fmtext *fmtext)
 {
   char *text;
@@ -46,14 +48,19 @@
   v->topline=0;
   v->rightshift=0;
-  v->winlines=winlines;
-  v->wincols=wincols;
-  v->curswin=win;
+
+  owl_viewwin_set_window(v, win);
 }
 
-void owl_viewwin_set_curswin(owl_viewwin *v, WINDOW *w, int winlines, int wincols)
+void owl_viewwin_set_window(owl_viewwin *v, owl_window *w)
 {
-  v->curswin=w;
-  v->winlines=winlines;
-  v->wincols=wincols;
+  if (v->window) {
+    g_signal_handler_disconnect(v->window, v->sig_redraw_id);
+    g_object_unref(v->window);
+  }
+  v->window = w;
+  if (w) {
+    g_object_ref(v->window);
+    v->sig_redraw_id = g_signal_connect(w, "redraw", G_CALLBACK(owl_viewwin_redraw), v);
+  }
 }
 
@@ -64,32 +71,32 @@
 
 /* regenerate text on the curses window. */
-void owl_viewwin_redisplay(owl_viewwin *v)
+static void owl_viewwin_redraw(owl_window *w, WINDOW *curswin, void *user_data)
 {
   owl_fmtext fm1, fm2;
+  owl_viewwin *v = user_data;
+  int winlines, wincols;
 
-  /* avoid segfault when screen too small to create curswin */
-  if (v->curswin == NULL)
-    return;
+  owl_window_get_position(w, &winlines, &wincols, 0, 0);
   
-  werase(v->curswin);
-  wmove(v->curswin, 0, 0);
+  werase(curswin);
+  wmove(curswin, 0, 0);
 
   owl_fmtext_init_null(&fm1);
   owl_fmtext_init_null(&fm2);
   
-  owl_fmtext_truncate_lines(&(v->fmtext), v->topline, v->winlines-BOTTOM_OFFSET, &fm1);
-  owl_fmtext_truncate_cols(&fm1, v->rightshift, v->wincols-1+v->rightshift, &fm2);
+  owl_fmtext_truncate_lines(&(v->fmtext), v->topline, winlines-BOTTOM_OFFSET, &fm1);
+  owl_fmtext_truncate_cols(&fm1, v->rightshift, wincols-1+v->rightshift, &fm2);
 
-  owl_fmtext_curs_waddstr_without_search(&fm2, v->curswin);
+  owl_fmtext_curs_waddstr_without_search(&fm2, curswin);
 
   /* print the message at the bottom */
-  wmove(v->curswin, v->winlines-1, 0);
-  wattrset(v->curswin, A_REVERSE);
-  if (v->textlines - v->topline > v->winlines-BOTTOM_OFFSET) {
-    waddstr(v->curswin, "--More-- (Space to see more, 'q' to quit)");
+  wmove(curswin, winlines-1, 0);
+  wattrset(curswin, A_REVERSE);
+  if (v->textlines - v->topline > winlines-BOTTOM_OFFSET) {
+    waddstr(curswin, "--More-- (Space to see more, 'q' to quit)");
   } else {
-    waddstr(v->curswin, "--End-- (Press 'q' to quit)");
+    waddstr(curswin, "--End-- (Press 'q' to quit)");
   }
-  wattroff(v->curswin, A_REVERSE);
+  wattroff(curswin, A_REVERSE);
 
   owl_fmtext_cleanup(&fm1);
@@ -99,22 +106,31 @@
 void owl_viewwin_pagedown(owl_viewwin *v)
 {
-  v->topline+=v->winlines - BOTTOM_OFFSET;
-  if ( (v->topline+v->winlines-BOTTOM_OFFSET) > v->textlines) {
-    v->topline = v->textlines - v->winlines + BOTTOM_OFFSET;
+  int winlines;
+  owl_window_get_position(v->window, &winlines, 0, 0, 0);
+  v->topline+=winlines - BOTTOM_OFFSET;
+  if ( (v->topline+winlines-BOTTOM_OFFSET) > v->textlines) {
+    v->topline = v->textlines - winlines + BOTTOM_OFFSET;
   }
+  owl_window_dirty(v->window);
 }
 
 void owl_viewwin_linedown(owl_viewwin *v)
 {
+  int winlines;
+  owl_window_get_position(v->window, &winlines, 0, 0, 0);
   v->topline++;
-  if ( (v->topline+v->winlines-BOTTOM_OFFSET) > v->textlines) {
-    v->topline = v->textlines - v->winlines + BOTTOM_OFFSET;
+  if ( (v->topline+winlines-BOTTOM_OFFSET) > v->textlines) {
+    v->topline = v->textlines - winlines + BOTTOM_OFFSET;
   }
+  owl_window_dirty(v->window);
 }
 
 void owl_viewwin_pageup(owl_viewwin *v)
 {
-  v->topline-=v->winlines;
+  int winlines;
+  owl_window_get_position(v->window, &winlines, 0, 0, 0);
+  v->topline-=winlines;
   if (v->topline<0) v->topline=0;
+  owl_window_dirty(v->window);
 }
 
@@ -123,4 +139,5 @@
   v->topline--;
   if (v->topline<0) v->topline=0;
+  owl_window_dirty(v->window);
 }
 
@@ -128,4 +145,5 @@
 {
   v->rightshift+=n;
+  owl_window_dirty(v->window);
 }
 
@@ -134,4 +152,5 @@
   v->rightshift-=n;
   if (v->rightshift<0) v->rightshift=0;
+  owl_window_dirty(v->window);
 }
 
@@ -140,13 +159,18 @@
   v->topline=0;
   v->rightshift=0;
+  owl_window_dirty(v->window);
 }
 
 void owl_viewwin_bottom(owl_viewwin *v)
 {
-  v->topline = v->textlines - v->winlines + BOTTOM_OFFSET;
+  int winlines;
+  owl_window_get_position(v->window, &winlines, 0, 0, 0);
+  v->topline = v->textlines - winlines + BOTTOM_OFFSET;
+  owl_window_dirty(v->window);
 }
 
 void owl_viewwin_cleanup(owl_viewwin *v)
 {
+  owl_viewwin_set_window(v, NULL);
   if (v->onclose_hook) {
     v->onclose_hook(v, v->onclose_hook_data);
Index: window.c
===================================================================
--- window.c	(revision 5cc7e5e45702da552c2ba2d3eb236eaeca359eb4)
+++ window.c	(revision 5cc7e5e45702da552c2ba2d3eb236eaeca359eb4)
@@ -0,0 +1,496 @@
+#include "owl.h"
+
+#include <assert.h>
+
+struct _owl_window { /*noproto*/
+  GObject object;
+  /* hierarchy information */
+  owl_window *parent;
+  owl_window *child;
+  owl_window *next, *prev;
+  /* flags */
+  int dirty : 1;
+  int dirty_subtree : 1;
+  int shown : 1;
+  int is_screen : 1;
+  /* window information */
+  WINDOW *win;
+  PANEL *pan;
+  int nlines, ncols;
+  int begin_y, begin_x;
+};
+
+enum {
+  REDRAW,
+  RESIZED,
+  LAST_SIGNAL
+};
+
+static guint window_signals[LAST_SIGNAL] = { 0 };
+
+static void owl_window_dispose(GObject *gobject);
+static void owl_window_finalize(GObject *gobject);
+
+static owl_window *_owl_window_new(owl_window *parent, int nlines, int ncols, int begin_y, int begin_x);
+
+static void _owl_window_link(owl_window *w, owl_window *parent);
+
+static void _owl_window_create_curses(owl_window *w);
+static void _owl_window_destroy_curses(owl_window *w);
+
+static void _owl_window_realize(owl_window *w);
+static void _owl_window_unrealize(owl_window *w);
+
+static owl_window *cursor_owner;
+static owl_window *default_cursor;
+
+G_DEFINE_TYPE (OwlWindow, owl_window, G_TYPE_OBJECT)
+
+static void owl_window_class_init (OwlWindowClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  /* Set up the vtabl */
+  gobject_class->dispose = owl_window_dispose;
+  gobject_class->finalize = owl_window_finalize;
+
+  klass->redraw = NULL;
+  klass->resized = NULL;
+
+  /* Create the signals, remember IDs */
+  window_signals[REDRAW] =
+    g_signal_new("redraw",
+                 G_TYPE_FROM_CLASS(gobject_class),
+                 G_SIGNAL_RUN_CLEANUP,
+                 G_STRUCT_OFFSET(OwlWindowClass, redraw),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__POINTER,
+                 G_TYPE_NONE,
+                 1,
+                 G_TYPE_POINTER, NULL);
+
+  /* TODO: maybe type should be VOID__INT_INT_INT_INT; will need to generate a
+   * marshaller */
+  window_signals[RESIZED] =
+    g_signal_new("resized",
+                 G_TYPE_FROM_CLASS(gobject_class),
+                 G_SIGNAL_RUN_FIRST,
+                 G_STRUCT_OFFSET(OwlWindowClass, resized),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__VOID,
+                 G_TYPE_NONE,
+                 0,
+                 NULL);
+}
+
+static void owl_window_dispose (GObject *object)
+{
+  owl_window *w = OWL_WINDOW (object);
+
+  /* Unmap the window */
+  owl_window_hide (w);
+
+  /* Unlink and unref all children */
+  while (w->child) {
+    owl_window *child = w->child;
+    owl_window_unlink (child);
+  }
+
+  /* Remove from hierarchy */
+  owl_window_unlink (w);
+
+  G_OBJECT_CLASS (owl_window_parent_class)->dispose (object);
+}
+
+static void owl_window_finalize (GObject *object)
+{
+  owl_window *w = OWL_WINDOW(object);
+
+  if (w->pan) {
+    del_panel(w->pan);
+    w->pan = NULL;
+  }
+
+  G_OBJECT_CLASS (owl_window_parent_class)->finalize (object);
+}
+
+static void owl_window_init (owl_window *w)
+{
+}
+
+/** singletons **/
+
+static WINDOW *_dummy_window(void)
+{
+  static WINDOW *dummy = NULL;
+  if (!dummy) {
+    dummy = newwin(1, 1, 0, 0);
+  }
+  return dummy;
+}
+
+owl_window *owl_window_get_screen(void)
+{
+  static owl_window *screen = NULL;
+  if (!screen) {
+    /* The screen is magical. It's 'shown', but the only mean of it going
+     * invisible is if we're tore down curses (i.e. screen resize) */
+    screen = _owl_window_new(NULL, g.lines, g.cols, 0, 0);
+    screen->is_screen = 1;
+    owl_window_show(screen);
+  }
+  return screen;
+}
+
+/** Creation and Destruction **/
+
+owl_window *owl_window_new(owl_window *parent)
+{
+  if (!parent)
+    parent = owl_window_get_screen();
+  return _owl_window_new(parent, 0, 0, 0, 0);
+}
+
+static owl_window *_owl_window_new(owl_window *parent, int nlines, int ncols, int begin_y, int begin_x)
+{
+  owl_window *w;
+
+  w = g_object_new (OWL_TYPE_WINDOW, NULL);
+  if (w == NULL) g_error("Failed to create owl_window instance");
+
+  w->nlines = nlines;
+  w->ncols = ncols;
+  w->begin_y = begin_y;
+  w->begin_x = begin_x;
+
+  _owl_window_link(w, parent);
+  if (parent && parent->is_screen) {
+    w->pan = new_panel(_dummy_window());
+    set_panel_userptr(w->pan, w);
+  }
+
+  return w;
+}
+
+/** Hierarchy **/
+
+void owl_window_unlink(owl_window *w)
+{
+  /* make sure the window is unmapped first */
+  _owl_window_unrealize(w);
+  /* unlink parent/child information */
+  if (w->parent) {
+    if (w->prev)
+      w->prev->next = w->next;
+    if (w->next)
+      w->next->prev = w->prev;
+    if (w->parent->child == w)
+      w->parent->child = w->next;
+    w->parent = NULL;
+    g_object_unref (w);
+  }
+}
+
+static void _owl_window_link(owl_window *w, owl_window *parent)
+{
+  if (w->parent == parent)
+    return;
+
+  owl_window_unlink(w);
+  if (parent) {
+    w->parent = parent;
+    w->next = parent->child;
+    parent->child = w;
+    g_object_ref (w);
+  }
+}
+
+/* mimic g_list_foreach for consistency */
+void owl_window_children_foreach(owl_window *parent, GFunc func, gpointer user_data)
+{
+  owl_window *w;
+  for (w = parent->child;
+       w != NULL;
+       w = w->next) {
+    func(w, user_data);
+  }
+}
+
+owl_window *owl_window_parent(owl_window *w)
+{
+  return w->parent;
+}
+
+owl_window *owl_window_first_child(owl_window *w)
+{
+  return w->child;
+}
+
+owl_window *owl_window_next_sibling(owl_window *w)
+{
+  return w->next;
+}
+
+owl_window *owl_window_previous_sibling(owl_window *w)
+{
+  return w->prev;
+}
+
+/** Internal window management **/
+
+static void _owl_window_create_curses(owl_window *w)
+{
+  if (w->is_screen) {
+    resizeterm(w->nlines, w->ncols);
+    w->win = stdscr;
+  } else {
+    /* Explicitly disallow realizing an unlinked non-root */
+    if (w->parent == NULL || w->parent->win == NULL)
+      return;
+    if (w->pan) {
+      w->win = newwin(w->nlines, w->ncols, w->begin_y, w->begin_x);
+      replace_panel(w->pan, w->win);
+    } else {
+      w->win = derwin(w->parent->win, w->nlines, w->ncols, w->begin_y, w->begin_x);
+    }
+  }
+}
+
+static void _owl_window_destroy_curses(owl_window *w)
+{
+  if (w->is_screen) {
+    /* don't deallocate the dummy */
+    w->win = NULL;
+  } else {
+    if (w->pan) {
+      /* panels assume their windows always exist, so we put in a fake one */
+      replace_panel(w->pan, _dummy_window());
+    }
+    if (w->win) {
+      /* and destroy our own window */
+      delwin(w->win);
+      w->win = NULL;
+    }
+  }
+}
+
+void owl_window_show(owl_window *w)
+{
+  w->shown = 1;
+  _owl_window_realize(w);
+  if (w->pan)
+    show_panel(w->pan);
+}
+
+void owl_window_show_all(owl_window *w)
+{
+  owl_window_show(w);
+  owl_window_children_foreach(w, (GFunc)owl_window_show, 0);
+}
+
+void owl_window_hide(owl_window *w)
+{
+  /* you can't unmap the screen */
+  if (w->is_screen)
+    return;
+  w->shown = 0;
+  if (w->pan)
+    hide_panel(w->pan);
+  _owl_window_unrealize(w);
+}
+
+int owl_window_is_shown(owl_window *w)
+{
+  return w->shown;
+}
+
+int owl_window_is_realized(owl_window *w)
+{
+  return w->win != NULL;
+}
+
+int owl_window_is_toplevel(owl_window *w)
+{
+  return w->pan != NULL;
+}
+
+int owl_window_is_subwin(owl_window *w)
+{
+  return w->pan == NULL && !w->is_screen;
+}
+
+static void _owl_window_realize(owl_window *w)
+{
+  /* check if we can create a window */
+  if ((w->parent && w->parent->win == NULL)
+      || !w->shown
+      || w->win != NULL)
+    return;
+  if (w->nlines <= 0 || w->ncols <= 0)
+    return;
+  _owl_window_create_curses(w);
+  if (w->win == NULL)
+    return;
+  /* schedule a repaint */
+  owl_window_dirty(w);
+  /* map the children */
+  owl_window_children_foreach(w, (GFunc)_owl_window_realize, 0);
+}
+
+static void _owl_window_unrealize(owl_window *w)
+{
+  if (w->win == NULL)
+    return;
+  /* unmap all the children */
+  owl_window_children_foreach(w, (GFunc)_owl_window_unrealize, 0);
+  _owl_window_destroy_curses(w);
+  w->dirty = w->dirty_subtree = 0;
+  /* subwins leave a mess in the parent; dirty it */
+  if (w->parent)
+    owl_window_dirty(w->parent);
+}
+
+/** Painting and book-keeping **/
+
+void owl_window_set_cursor(owl_window *w)
+{
+  if (cursor_owner)
+    g_object_remove_weak_pointer(G_OBJECT(cursor_owner), (gpointer*) &cursor_owner);
+  cursor_owner = w;
+  if (w)
+    g_object_add_weak_pointer(G_OBJECT(w), (gpointer*) &cursor_owner);
+  owl_window_dirty(owl_window_get_screen());
+}
+
+void owl_window_set_default_cursor(owl_window *w)
+{
+  if (default_cursor)
+    g_object_remove_weak_pointer(G_OBJECT(default_cursor), (gpointer*) &default_cursor);
+  default_cursor = w;
+  if (w)
+    g_object_add_weak_pointer(G_OBJECT(w), (gpointer*) &default_cursor);
+  owl_window_dirty(owl_window_get_screen());
+}
+
+static owl_window *_get_cursor(void)
+{
+  if (cursor_owner && owl_window_is_realized(cursor_owner))
+    return cursor_owner;
+  if (default_cursor && owl_window_is_realized(default_cursor))
+    return default_cursor;
+  return owl_window_get_screen();
+}
+
+void owl_window_dirty(owl_window *w)
+{
+  if (!owl_window_is_realized(w))
+    return;
+  if (!w->dirty) {
+    w->dirty = 1;
+    while (w && !w->dirty_subtree) {
+      w->dirty_subtree = 1;
+      w = w->parent;
+    }
+  }
+}
+
+void owl_window_dirty_children(owl_window *w)
+{
+  owl_window_children_foreach(w, (GFunc)owl_window_dirty, 0);
+}
+
+static void _owl_window_redraw(owl_window *w)
+{
+  if (!w->dirty) return;
+  if (w->win && !w->is_screen) {
+    if (owl_window_is_subwin(w)) {
+      /* If a subwin, we might have gotten random touched lines from wsyncup or
+       * past drawing. That information is useless, so we discard it all */
+      untouchwin(w->win);
+    }
+    g_signal_emit(w, window_signals[REDRAW], 0, w->win);
+    wsyncup(w->win);
+  }
+  w->dirty = 0;
+}
+
+static void _owl_window_redraw_subtree(owl_window *w)
+{
+  if (!w->dirty_subtree)
+    return;
+  _owl_window_redraw(w);
+  owl_window_children_foreach(w, (GFunc)_owl_window_redraw_subtree, 0);
+  w->dirty_subtree = 0;
+}
+
+/*
+Redraw all the windows with scheduled redraws.
+NOTE: This function shouldn't be called outside the event loop
+*/
+void owl_window_redraw_scheduled(void)
+{
+  owl_window *cursor;
+  owl_window *screen = owl_window_get_screen();
+
+  if (!screen->dirty_subtree)
+    return;
+  _owl_window_redraw_subtree(screen);
+  update_panels();
+  cursor = _get_cursor();
+  if (cursor && cursor->win) {
+    /* untouch it to avoid drawing; window should be clean now, but we must
+     * clean up in case there was junk left over on a subwin (cleaning up after
+     * subwin drawing isn't sufficient because a wsyncup messes up subwin
+     * ancestors */
+    untouchwin(cursor->win);
+    wnoutrefresh(cursor->win);
+  }
+  doupdate();
+}
+
+/** Window position **/
+
+void owl_window_get_position(owl_window *w, int *nlines, int *ncols, int *begin_y, int *begin_x)
+{
+  if (nlines)  *nlines  = w->nlines;
+  if (ncols)   *ncols   = w->ncols;
+  if (begin_y) *begin_y = w->begin_y;
+  if (begin_x) *begin_x = w->begin_x;
+}
+
+void owl_window_move(owl_window *w, int begin_y, int begin_x)
+{
+  /* It is possible to move a window efficiently with move_panel and mvderwin,
+   * but begy and begx are then wrong. Currently, this only effects the
+   * wnoutrefresh to move cursor. TODO: fix that and reinstate that
+   * optimization if it's worth the trouble */
+  owl_window_set_position(w, w->nlines, w->ncols, begin_y, begin_x);
+}
+
+void owl_window_set_position(owl_window *w, int nlines, int ncols, int begin_y, int begin_x)
+{
+  int resized;
+
+  if (w->nlines == nlines && w->ncols == ncols
+      && w->begin_y == begin_y && w->begin_x == begin_x) {
+    return;
+  }
+  resized = w->nlines != nlines || w->ncols != ncols;
+
+  _owl_window_unrealize(w);
+  w->begin_y = begin_y;
+  w->begin_x = begin_x;
+  w->nlines = nlines;
+  w->ncols = ncols;
+  if (resized)
+    g_signal_emit(w, window_signals[RESIZED], 0);
+  if (w->shown) {
+    /* ncurses is screwy: give up and recreate windows at the right place */
+    _owl_window_realize(w);
+  }
+}
+
+void owl_window_resize(owl_window *w, int nlines, int ncols)
+{
+  owl_window_set_position(w, nlines, ncols, w->begin_y, w->begin_x);
+}
Index: window.h
===================================================================
--- window.h	(revision 9379760d70b9135d8e94813d2edd59dfa9c7516c)
+++ window.h	(revision 9379760d70b9135d8e94813d2edd59dfa9c7516c)
@@ -0,0 +1,69 @@
+#ifndef __BARNOWL_WINDOW_H__
+#define __BARNOWL_WINDOW_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define OWL_TYPE_WINDOW                  (owl_window_get_type ())
+#define OWL_WINDOW(obj)                  (G_TYPE_CHECK_INSTANCE_CAST ((obj), OWL_TYPE_WINDOW, OwlWindow))
+#define OWL_IS_WINDOW(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), OWL_TYPE_WINDOW))
+#define OWL_WINDOW_CLASS(klass)          (G_TYPE_CHECK_CLASS_CAST ((klass), OWL_TYPE_WINDOW, OwlWindowClass))
+#define OWL_IS_WINDOW_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass), OWL_TYPE_WINDOW))
+#define OWL_WINDOW_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj), OWL_TYPE_WINDOW, OwlWindowClass))
+
+typedef struct _owl_window OwlWindow;
+typedef struct _OwlWindowClass OwlWindowClass;
+typedef OwlWindow owl_window; /* meh */
+
+struct _OwlWindowClass
+{
+  GObjectClass parent_class;
+  /* default implementations for signals */
+  void (*redraw)(owl_window *, WINDOW *);
+  void (*resized)(owl_window *);
+};
+
+GType owl_window_get_type(void);
+
+owl_window *owl_window_get_screen(void);
+
+owl_window *owl_window_new(owl_window *parent);
+void owl_window_unlink(owl_window *w);
+
+void owl_window_children_foreach(owl_window *parent, GFunc func, gpointer user_data);
+
+owl_window *owl_window_parent(owl_window *parent);
+owl_window *owl_window_first_child(owl_window *parent);
+owl_window *owl_window_next_sibling(owl_window *w);
+owl_window *owl_window_previous_sibling(owl_window *w);
+
+void owl_window_show(owl_window *w);
+void owl_window_show_all(owl_window *w);
+void owl_window_hide(owl_window *w);
+int owl_window_is_shown(owl_window *w);
+int owl_window_is_realized(owl_window *w);
+int owl_window_is_toplevel(owl_window *w);
+int owl_window_is_subwin(owl_window *w);
+
+void owl_window_set_cursor(owl_window *w);
+void owl_window_set_default_cursor(owl_window *w);
+
+void owl_window_dirty(owl_window *w);
+void owl_window_dirty_children(owl_window *w);
+void owl_window_redraw_scheduled(void);
+
+void owl_window_get_position(owl_window *w, int *nlines, int *ncols, int *begin_y, int *begin_x);
+void owl_window_set_position(owl_window *w, int nlines, int ncols, int begin_y, int begin_x);
+void owl_window_move(owl_window *w, int begin_y, int begin_x);
+void owl_window_resize(owl_window *w, int nlines, int ncols);
+
+/* Standard callback functions in windowcb.c */
+
+void owl_window_erase_cb(owl_window *w, WINDOW *win, void *user_data);
+void owl_window_fill_parent_cb(owl_window *parent, void *user_data);
+
+G_END_DECLS
+
+#endif /* __BARNOWL_WINDOW_H__ */
Index: windowcb.c
===================================================================
--- windowcb.c	(revision 853f397531e75fe1ed03d6f52bcfdd77d837a815)
+++ windowcb.c	(revision 853f397531e75fe1ed03d6f52bcfdd77d837a815)
@@ -0,0 +1,17 @@
+#include "owl.h"
+
+void owl_window_erase_cb(owl_window *w, WINDOW *win, void *user_data)
+{
+  werase(win);
+  owl_window_dirty_children(w);
+}
+
+void owl_window_fill_parent_cb(owl_window *parent, void *user_data)
+{
+  int lines, cols;
+  owl_window *window = user_data;
+
+  /* Make this panel full-screen */
+  owl_window_get_position(parent, &lines, &cols, NULL, NULL);
+  owl_window_set_position(window, lines, cols, 0, 0);
+}
Index: zcrypt.c
===================================================================
--- zcrypt.c	(revision 60fcd71f8988494c502d1533cd073473759ca198)
+++ zcrypt.c	(revision 3c2c7fc157943127dac6b1c438a38607139dccba)
@@ -88,6 +88,6 @@
 
 cipher_pair ciphers[NCIPHER] = {
-  [CIPHER_DES] { do_encrypt_des, do_decrypt_des},
-  [CIPHER_AES] { do_encrypt_aes, do_decrypt_aes},
+  [CIPHER_DES] = { do_encrypt_des, do_decrypt_des},
+  [CIPHER_AES] = { do_encrypt_aes, do_decrypt_aes},
 };
 
@@ -113,6 +113,4 @@
   int mode = M_NONE;
 
-  extern int optind, opterr;
-  extern char *optarg;
   char c;
 
Index: zephyr.c
===================================================================
--- zephyr.c	(revision 8ab1f2801e62b0ef27d25c62c5b64ee194ad3f15)
+++ zephyr.c	(revision 8ab1f2801e62b0ef27d25c62c5b64ee194ad3f15)
@@ -265,4 +265,5 @@
 
   if (stat(subsfile, &statbuff) != 0) {
+    owl_free(subsfile);
     if (error_on_nofile == 1)
       return -1;
@@ -380,6 +381,8 @@
   subsfile = owl_zephyr_dotfile(".anyone", filename);
 
-  if (stat(subsfile, &statbuff) == -1)
+  if (stat(subsfile, &statbuff) == -1) {
+    owl_free(subsfile);
     return 0;
+  }
 
   ZResetAuthentication();
@@ -856,4 +859,5 @@
   char *tmpbuff, *myuser, *to;
   owl_message *mout;
+  owl_zwrite *z;
   
   /* bail if it doesn't look like a message we should reply to.  Some
@@ -891,8 +895,13 @@
   owl_free(to);
 
+  z = owl_zwrite_new(tmpbuff);
+  owl_zwrite_set_message(z, owl_global_get_zaway_msg(&g));
+  owl_zwrite_set_zsig(z, "Automated reply:");
+
   /* display the message as an admin message in the receive window */
-  mout=owl_function_make_outgoing_zephyr(owl_global_get_zaway_msg(&g), tmpbuff, "Automated reply:");
+  mout=owl_function_make_outgoing_zephyr(z);
   owl_global_messagequeue_addmsg(&g, mout);
   owl_free(tmpbuff);
+  owl_zwrite_delete(z);
 #endif
 }
Index: zwrite.c
===================================================================
--- zwrite.c	(revision c230bc1b418cbace9d748df62640c26a59aa1622)
+++ zwrite.c	(revision 7bfc61380289737bd52f9cae9175a60839766b6a)
@@ -5,4 +5,14 @@
 #include "owl.h"
 
+owl_zwrite *owl_zwrite_new(const char *line)
+{
+  owl_zwrite *z = owl_malloc(sizeof *z);
+  if (owl_zwrite_create_from_line(z, line) < 0) {
+    owl_zwrite_delete(z);
+    return NULL;
+  }
+  return z;
+}
+
 int owl_zwrite_create_from_line(owl_zwrite *z, const char *line)
 {
@@ -15,4 +25,5 @@
   
   /* start with null entries */
+  z->cmd=NULL;
   z->realm=NULL;
   z->class=NULL;
@@ -35,4 +46,5 @@
   myargc=argc;
   if (myargc && *(myargv[0])!='-') {
+    z->cmd=owl_strdup(myargv[0]);
     myargc--;
     myargv++;
@@ -187,4 +199,11 @@
 }
 
+/* Set the message with no post-processing*/
+void owl_zwrite_set_message_raw(owl_zwrite *z, const char *msg)
+{
+  if (z->message) owl_free(z->message);
+  z->message = owl_validate_utf8(msg);
+}
+
 void owl_zwrite_set_message(owl_zwrite *z, const char *msg)
 {
@@ -350,4 +369,10 @@
 }
 
+void owl_zwrite_delete(owl_zwrite *z)
+{
+  owl_zwrite_cleanup(z);
+  owl_free(z);
+}
+
 void owl_zwrite_cleanup(owl_zwrite *z)
 {
