diff --git a/Makefile.am b/Makefile.am
index 65c2393d3cc2d71c800994c2dfe766b1d67e9d3d..0ae1eba215aed3d4208a0ab85d121f4b517d7d26 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -7,7 +7,7 @@
 # See the file COPYING for details.
 
 AUTOMAKE_OPTIONS = foreign 1.2
-lib_LTLIBRARIES = liboop-adns.la liboop-glib.la liboop-www.la liboop-rl.la liboop.la
+lib_LTLIBRARIES = liboop-adns.la liboop-glib.la liboop-tcl.la liboop-www.la liboop-rl.la liboop.la
 INCLUDES = $(GLIB_INCLUDES) $(WWW_INCLUDES)
 
 # versions updated as of 0.7
@@ -22,6 +22,10 @@ liboop_glib_la_LDFLAGS = -version-info 1:0:0
 liboop_glib_la_LIBADD = $(GLIB_LIBS)
 liboop_glib_la_SOURCES = glib.c
 
+liboop_tcl_la_LDFLAGS = -version-info 0:0:0
+liboop_tcl_la_LIBADD = $(TCL_LIBS)
+liboop_tcl_la_SOURCES = tcl.c
+
 liboop_www_la_LDFLAGS = -version-info 0:0:0
 liboop_www_la_LIBADD = $(WWW_LIBS)
 liboop_www_la_SOURCES = www.c
@@ -30,7 +34,7 @@ liboop_rl_la_LDFLAGS = -version-info 0:0:0
 liboop_rl_la_LIBADD = $(READLINE_LIBS)
 liboop_rl_la_SOURCES = readline.c
 
-include_HEADERS = oop.h oop-adns.h oop-glib.h oop-www.h oop-rl.h oop-read.h
+include_HEADERS = oop.h oop-adns.h oop-glib.h oop-tcl.h oop-www.h oop-rl.h oop-read.h
 
 noinst_PROGRAMS = test-oop
 
diff --git a/configure.in b/configure.in
index 23ac539fab997b17cd02ea93d4dc36e5576f6188..8c4209476e21cd46cd283daac55ccd59cdb8d622 100644
--- a/configure.in
+++ b/configure.in
@@ -1,6 +1,6 @@
 dnl Process this file with autoconf to produce a configure script.
 AC_INIT(INSTALL)
-AM_INIT_AUTOMAKE(liboop,0.7)
+AM_INIT_AUTOMAKE(liboop,0.8)
 AC_CANONICAL_HOST
 
 dnl Use libtool for shared libraries
@@ -44,6 +44,15 @@ if test -n "$PROG_GLIB_CONFIG" ; then
   AC_DEFINE(HAVE_GLIB)
 fi
 
+for version in 8.4 8.3 8.2 8.1 8.0 ; do
+  AC_CHECK_LIB(tcl$version,Tcl_Main,[
+    AC_DEFINE(HAVE_TCL)
+    TCL_INCLUDES="-I/usr/include/tcl$version"
+    TCL_LIBS="-ltcl$version"
+    break
+  ])
+done
+
 # the libwww RPM puts headers here:
 AC_CHECK_LIB(wwwcore,HTEvent_setRegisterCallback,[
   AC_DEFINE(HAVE_WWW)
@@ -66,6 +75,8 @@ CFLAGS="-Wall -Wno-comment -Wmissing-prototypes -Wstrict-prototypes -Wpointer-ar
 AC_SUBST(PROG_LDCONFIG)
 AC_SUBST(GLIB_INCLUDES)
 AC_SUBST(GLIB_LIBS)
+AC_SUBST(TCL_INCLUDES)
+AC_SUBST(TCL_LIBS)
 AC_SUBST(ADNS_LIBS)
 AC_SUBST(WWW_INCLUDES)
 AC_SUBST(WWW_LIBS)
diff --git a/oop-tcl.h b/oop-tcl.h
new file mode 100644
index 0000000000000000000000000000000000000000..f2a339ab3418f5452f851b479ea1f088bd67fc6f
--- /dev/null
+++ b/oop-tcl.h
@@ -0,0 +1,18 @@
+/* oop-glib.h, liboop, copyright 1999 Dan Egnor
+
+   This is free software; you can redistribute it and/or modify it under the
+   terms of the GNU Lesser General Public License, version 2.1 or later.
+   See the file COPYING for details. */
+
+#ifndef OOP_TCL_H
+#define OOP_TCL_H
+
+#include "oop.h"
+
+/* Create an event source based on the Tcl event loop. */
+oop_source *oop_tcl_new(void);
+
+/* Delete the event source so created.  (Uses reference counting.) */
+void oop_tcl_done(void);
+
+#endif
diff --git a/tcl.c b/tcl.c
new file mode 100644
index 0000000000000000000000000000000000000000..f1d5c6315e767d387c0f84600053cbc9f02345a2
--- /dev/null
+++ b/tcl.c
@@ -0,0 +1,191 @@
+/* glib.c, liboop, copyright 1999 Dan Egnor
+
+   This is free software; you can redistribute it and/or modify it under the
+   terms of the GNU Lesser General Public License, version 2.1 or later.
+   See the file COPYING for details. */
+
+#ifdef HAVE_TCL
+
+#include "oop-tcl.h"
+#include <tcl.h>
+#include <assert.h>
+
+struct file_handler {
+        oop_call_fd *f[OOP_NUM_EVENTS];
+        void *d[OOP_NUM_EVENTS];
+};
+
+struct timer_handler {
+        struct timeval t;
+        oop_call_time *f;
+        void *d;
+        Tcl_TimerToken token;
+        struct timer_handler *next;
+};
+
+static int use_count = 0;
+static struct oop_source source;
+static struct oop_adapter_signal *signal;
+
+static int array_size;
+static struct file_handler *array;
+static struct timer_handler *list;
+
+static void file_call(ClientData data,int mask) {
+        const int fd = (int) data;
+        oop_source * const oop = oop_tcl_new();
+        const struct file_handler *h;
+
+        if (fd >= array_size) {
+                oop_tcl_done();
+                return;
+        }
+
+        /* BUG: what if !OOP_CONTINUE? */
+
+        h = &array[fd];
+        if ((mask & TCL_READABLE) && NULL != h->f[OOP_READ])
+                h->f[OOP_READ](oop,fd,OOP_READ,h->d[OOP_READ]);
+
+        h = &array[fd];
+        if ((mask & TCL_WRITABLE) && NULL != h->f[OOP_WRITE])
+                h->f[OOP_WRITE](oop,fd,OOP_WRITE,h->d[OOP_WRITE]);
+
+        h = &array[fd];
+        if ((mask & TCL_EXCEPTION) && NULL != h->f[OOP_EXCEPTION])
+                h->f[OOP_EXCEPTION](oop,fd,OOP_EXCEPTION,h->d[OOP_EXCEPTION]);
+
+        oop_tcl_done();
+}
+
+static void set_mask(int fd) {
+        int mask = 0;
+        const struct file_handler * const h = &array[fd];
+        if (NULL != h->f[OOP_READ]) mask |= TCL_READABLE;
+        if (NULL != h->f[OOP_WRITE]) mask |= TCL_WRITABLE;
+        if (NULL != h->f[OOP_EXCEPTION]) mask |= TCL_EXCEPTION;
+
+        if (0 == mask)
+                Tcl_DeleteFileHandler(fd);
+        else
+                Tcl_CreateFileHandler(fd,mask,file_call,(ClientData) fd);
+}
+
+static void on_fd(oop_source *x,int fd,oop_event event,oop_call_fd *f,void *d) {
+        struct file_handler *h;
+
+        if (fd >= array_size) {
+                const int new_size = fd + 1;
+                struct file_handler * const new_array = 
+                        oop_realloc(array,new_size * sizeof(*new_array));
+                if (NULL == new_array) return; /* YUCK */
+
+                array = new_array;
+                while (array_size != new_size) {
+                        int i;
+                        for (i = 0; i < OOP_NUM_EVENTS; ++i)
+                                array[array_size].f[i] = NULL;
+                        ++array_size;
+                }
+        }
+
+        h = &array[fd];
+        assert(NULL == h->f[event] && NULL != f);
+        h->f[event] = f;
+        h->d[event] = d;
+        set_mask(fd);
+}
+
+static void cancel_fd(oop_source *x,int fd,oop_event event) {
+        if (fd < array_size) {
+                struct file_handler * const h = &array[fd];
+                h->f[event] = NULL;
+                set_mask(fd);
+        }
+}
+
+static void timer_call(ClientData data) {
+        struct timer_handler * const timer = (struct timer_handler *) data;
+        struct timer_handler **ptr;
+
+        Tcl_DeleteTimerHandler(timer->token);
+        for (ptr = &list; timer != *ptr; ptr = &(*ptr)->next) ;
+        *ptr = timer->next;
+
+        /* BUG: What if !OOP_CONTINUE? */
+        timer->f(oop_signal_source(signal),timer->t,timer->d);
+        oop_free(timer);
+}
+
+static void on_time(oop_source *x,struct timeval t,oop_call_time *f,void *d) {
+        struct timer_handler * const timer = oop_malloc(sizeof(*timer));
+        struct timeval now;
+        int msec;
+        if (NULL == timer) return; /* YUCK */
+
+        gettimeofday(&now,NULL);
+        if (t.tv_sec < now.tv_sec
+        || (t.tv_sec == now.tv_sec && t.tv_usec < now.tv_usec))
+                msec = 0;
+        else {
+                msec = 1000 * (t.tv_sec - now.tv_sec);
+                msec = msec + (t.tv_usec - now.tv_usec) / 1000;
+        }
+
+        assert(msec >= 0);
+        timer->t = t;
+        timer->f = f;
+        timer->d = d;
+        timer->next = list;
+        timer->token = Tcl_CreateTimerHandler(msec,timer_call,timer);
+        list = timer;
+}
+
+static void cancel_time(oop_source *x,struct timeval t,oop_call_time *f,void *d) {
+        struct timer_handler **timer;
+        for (timer = &list; NULL != *timer; timer = &(*timer)->next)
+                if ((*timer)->d == d && (*timer)->f == f
+                &&  (*timer)->t.tv_sec == t.tv_sec
+                &&  (*timer)->t.tv_usec == t.tv_usec) {
+                        struct timer_handler *dead = *timer;
+                        *timer = dead->next;
+                        Tcl_DeleteTimerHandler(dead->token);
+                        oop_free(dead);
+                        return;
+                }
+}
+
+oop_source *oop_tcl_new(void) {
+        if (0 == use_count) {
+                source.on_fd = on_fd;
+                source.cancel_fd = cancel_fd;
+                source.on_time = on_time;
+                source.cancel_time = cancel_time;
+                source.on_signal = NULL;
+                source.cancel_signal = NULL;
+                array = NULL;
+                array_size = 0;
+                list = NULL;
+
+                /* Do this last, after everything is set up. */
+                signal = oop_signal_new(&source);
+                if (NULL == signal) return NULL;
+        }
+
+        ++use_count;
+        return oop_signal_source(signal);
+}
+
+void oop_tcl_done(void) {
+        if (0 == --use_count) {
+                int i,j;
+                for (i = 0; i < array_size; ++i)
+                        for (j = 0; j < OOP_NUM_EVENTS; ++j)
+                                assert(NULL == array[i].f[j]);
+                oop_free(array);
+                assert(NULL == list);
+                oop_signal_delete(signal);
+        }
+}
+
+#endif
diff --git a/test-oop.c b/test-oop.c
index 82c38d55c17fddb05d7d2a8fa4580aa922de9a81..f9b522ed044b1fe90a3267fbe884e446efee64f9 100644
--- a/test-oop.c
+++ b/test-oop.c
@@ -25,6 +25,11 @@
 GMainLoop *glib_loop;
 #endif
 
+#ifdef HAVE_TCL
+#include <tcl.h>
+#include "oop-tcl.h"
+#endif
+
 #ifdef HAVE_WWW
 /* Yuck: */
 #define HAVE_CONFIG_H
@@ -60,6 +65,9 @@ static void usage(void) {
 #ifdef HAVE_GLIB
 "         glib     GLib source adapter\n"
 #endif
+#ifdef HAVE_TCL
+"         tcl      Tcl source adapter\n"
+#endif
 "sinks:   timer    some timers\n"
 "         signal   some signal handlers\n"
 "         echo     a stdin->stdout copy\n"
@@ -603,19 +611,46 @@ static void add_sink(oop_source *src,const char *name) {
 	usage();
 }
 
-int main(int argc,char *argv[]) {
-	oop_source *source;
+static void init(oop_source *source,int count,char *sinks[]) {
 	int i;
-
-	if (argc < 3) usage();
+	source->on_signal(source,SIGQUIT,stop_loop,NULL);
 	puts("test-oop: use ^\\ (SIGQUIT) for clean shutdown or "
 	     "^C (SIGINT) to stop abruptly.");
-	source = create_source(argv[1]);
-	source->on_signal(source,SIGQUIT,stop_loop,NULL);
-	for (i = 2; i < argc; ++i)
-		add_sink(source,argv[i]);
 
-	run_source(argv[1]);
-	delete_source(argv[1]);
+	for (i = 0; i < count; ++i)
+		add_sink(source,sinks[i]);
+}
+
+/* -- tcl source ----------------------------------------------------------- */
+
+#ifdef HAVE_TCL
+static int tcl_count;
+static char **tcl_sinks;
+
+static int tcl_init(Tcl_Interp *interp) {
+	init(oop_tcl_new(),tcl_count,tcl_sinks);
+	return TCL_OK;
+} 
+#endif
+
+/* -- main ----------------------------------------------------------------- */
+
+int main(int argc,char *argv[]) {
+	if (argc < 3) usage();
+
+#ifdef HAVE_TCL
+	if (!strcmp(argv[1],"tcl")) { /* Tcl is a little ... different. */
+		tcl_count = argc - 2;
+		tcl_sinks = argv + 2;
+		Tcl_Main(1,argv,tcl_init);
+	} else
+#endif
+	{
+		oop_source * const source = create_source(argv[1]);
+		init(source,argc - 2,argv + 2);
+		run_source(argv[1]);
+		delete_source(argv[1]);
+	}
+
 	return 0;
 }