diff --git a/unix/main.c b/unix/main.c
index 8016cef1..6a289996 100644
--- a/unix/main.c
+++ b/unix/main.c
@@ -20,7 +20,7 @@ const char *uiInit(uiInitOptions *o)
 
 void uiUninit(void)
 {
-	// TODO free menus
+	uninitMenus();
 	uninitAlloc();
 }
 
diff --git a/unix/menu.c b/unix/menu.c
index 48f9c594..3010cae0 100644
--- a/unix/menu.c
+++ b/unix/menu.c
@@ -346,3 +346,26 @@ void freeMenubar(GtkWidget *mb)
 	gtk_container_foreach(GTK_CONTAINER(mb), freeMenu, &i);
 	// no need to worry about destroying any widgets; destruction of the window they're in will do it for us
 }
+
+void uninitMenus(void)
+{
+	struct menu *m;
+	struct menuItem *item;
+	guint i, j;
+
+	for (i = 0; i < menus->len; i++) {
+		m = g_array_index(menus, struct menu *, i);
+		g_free(m->name);
+		for (j = 0; j < m->items->len; j++) {
+			item = g_array_index(m->items, struct menuItem *, j);
+			if (g_hash_table_size(item->windows) != 0)
+				complain("menu item %p (%s) still has uiWindows attached; did you forget to free some?", item, item->name);
+			g_free(item->name);
+			g_hash_table_destroy(item->windows);
+			uiFree(item);
+		}
+		g_array_free(m->items, TRUE);
+		uiFree(m);
+	}
+	g_array_free(menus, TRUE);
+}
diff --git a/unix/uipriv_unix.h b/unix/uipriv_unix.h
index 4607e38f..74589625 100644
--- a/unix/uipriv_unix.h
+++ b/unix/uipriv_unix.h
@@ -14,6 +14,7 @@
 // menu.c
 extern GtkWidget *makeMenubar(uiWindow *);
 extern void freeMenubar(GtkWidget *);
+extern void uninitMenus(void);
 
 // alloc.c
 extern void initAlloc(void);