diff --git a/examples/bloatpad.c b/examples/bloatpad.c
index 8cd41c1..ea58196 100644
--- a/examples/bloatpad.c
+++ b/examples/bloatpad.c
@@ -72,6 +72,7 @@ new_window (GApplication *app,
   GtkWidget *window, *grid, *scrolled, *view;
 
   window = gtk_application_window_new (GTK_APPLICATION (app));
+  //gtk_application_window_set_show_menubar (window, FALSE);
   gtk_window_set_default_size ((GtkWindow*)window, 640, 480);
   g_action_map_add_action_entries (G_ACTION_MAP (window), win_entries, G_N_ELEMENTS (win_entries), window);
   gtk_window_set_title (GTK_WINDOW (window), "Bloatpad");
@@ -246,6 +247,8 @@ main (int argc, char **argv)
   int status;
 
   bloat_pad = bloat_pad_new ();
+  gtk_application_add_accelerator (GTK_APPLICATION (bloat_pad),
+                                   "F11", "win.fullscreen", NULL);
   status = g_application_run (G_APPLICATION (bloat_pad), argc, argv);
   g_object_unref (bloat_pad);
 
diff --git a/gtk/gtkaccelmap.c b/gtk/gtkaccelmap.c
index 0c96cdc..efc06ba 100644
--- a/gtk/gtkaccelmap.c
+++ b/gtk/gtkaccelmap.c
@@ -179,9 +179,8 @@ accel_path_lookup (const gchar *accel_path)
 void
 _gtk_accel_map_init (void)
 {
-  g_assert (accel_entry_ht == NULL);
-
-  accel_entry_ht = g_hash_table_new (accel_entry_hash, accel_entry_equal);
+  if (accel_entry_ht == NULL)
+    accel_entry_ht = g_hash_table_new (accel_entry_hash, accel_entry_equal);
 }
 
 gboolean
diff --git a/gtk/gtkapplication.c b/gtk/gtkapplication.c
index f86c4b4..0b512ff 100644
--- a/gtk/gtkapplication.c
+++ b/gtk/gtkapplication.c
@@ -34,6 +34,7 @@
 #include "gtkcheckmenuitem.h"
 #include "gtkseparatormenuitem.h"
 #include "gtkmenu.h"
+#include "gtkaccelmap.h"
 
 #include <gdk/gdk.h>
 #ifdef GDK_WINDOWING_X11
@@ -353,3 +354,75 @@ gtk_application_get_windows (GtkApplication *application)
 
   return application->priv->windows;
 }
+
+/* keep this in sync with gtkmodelmenuitem.c */
+static gchar *
+get_accel_path (const gchar *action_name,
+                GVariant    *parameter)
+{
+  GString *s;
+
+  s = g_string_new ("<Actions>/");
+  g_string_append (s, action_name);
+  if (parameter)
+    {
+      g_string_append_c (s, '/');
+      g_variant_print_string (parameter, s, FALSE);
+    }
+  return g_string_free (s, FALSE);
+}
+
+void
+gtk_application_add_accelerator (GtkApplication *application,
+                                 const gchar    *accelerator,
+                                 const gchar    *action_name,
+                                 GVariant       *parameter)
+{
+  gchar *accel_path;
+  guint accel_key;
+  GdkModifierType accel_mods;
+
+  g_return_if_fail (GTK_IS_APPLICATION (application));
+
+  /* FIXME */
+  _gtk_accel_map_init ();
+
+  gtk_accelerator_parse (accelerator, &accel_key, &accel_mods);
+
+  if (accel_key == 0)
+    {
+      g_warning ("Failed to parse accelerator: '%s'\n", accelerator);
+      return;
+    }
+
+  accel_path = get_accel_path (action_name, parameter);
+
+  if (gtk_accel_map_lookup_entry (accel_path, NULL))
+    gtk_accel_map_change_entry (accel_path, accel_key, accel_mods, TRUE);
+  else
+    gtk_accel_map_add_entry (accel_path, accel_key, accel_mods);
+
+  g_free (accel_path);
+}
+
+void
+gtk_application_remove_accelerator (GtkApplication *application,
+                                    const gchar    *action_name,
+                                    GVariant       *parameter)
+{
+  gchar *accel_path;
+
+  g_return_if_fail (GTK_IS_APPLICATION (application));
+
+  accel_path = get_accel_path (action_name, parameter);
+
+  if (!gtk_accel_map_lookup_entry (accel_path, NULL))
+    {
+      g_warning ("No accelerator found for '%s'\n", accel_path);
+      g_free (accel_path);
+      return;
+    }
+
+  gtk_accel_map_change_entry (accel_path, 0, 0, FALSE);
+  g_free (accel_path);
+}
diff --git a/gtk/gtkapplication.h b/gtk/gtkapplication.h
index e1d189f..2e7b8b3 100644
--- a/gtk/gtkapplication.h
+++ b/gtk/gtkapplication.h
@@ -77,6 +77,13 @@ void             gtk_application_remove_window (GtkApplication    *application,
 
 GList *          gtk_application_get_windows   (GtkApplication    *application);
 
+void             gtk_application_add_accelerator    (GtkApplication  *application,
+                                                     const gchar     *accelerator,
+                                                     const gchar     *action_name,
+                                                     GVariant        *parameter);
+void             gtk_application_remove_accelerator (GtkApplication *application,
+                                                     const gchar    *action_name,
+                                                     GVariant       *parameter);
 
 G_END_DECLS
 
diff --git a/gtk/gtkapplicationwindow.c b/gtk/gtkapplicationwindow.c
index f68a0a2..1d3723a 100644
--- a/gtk/gtkapplicationwindow.c
+++ b/gtk/gtkapplicationwindow.c
@@ -28,6 +28,7 @@
 #include "gtkcheckmenuitem.h"
 #include "gtkmenubar.h"
 #include "gactionmuxer.h"
+#include "gtkaccelmap.h"
 #include "gtkintl.h"
 
 /**
@@ -59,6 +60,8 @@ struct _GtkApplicationWindowPrivate
 {
   GSimpleActionGroup *actions;
   GtkWidget *menubar;
+  GtkAccelGroup *accel_group;
+  GSList *accel_closures;
 
   GMenu *app_menu_section;
   GMenu *menubar_section;
@@ -66,8 +69,9 @@ struct _GtkApplicationWindowPrivate
 };
 
 static GtkWidget *
-gtk_application_window_create_menubar (GMenuModel        *model,
-                                       GActionObservable *actions);
+gtk_application_window_create_menubar (GtkApplicationWindow *window,
+                                       GMenuModel           *model,
+                                       GActionObservable    *actions);
 
 
 static void
@@ -103,7 +107,7 @@ gtk_application_window_update_menubar (GtkApplicationWindow *window)
       g_menu_append_section (combined, NULL, G_MENU_MODEL (window->priv->app_menu_section));
       g_menu_append_section (combined, NULL, G_MENU_MODEL (window->priv->menubar_section));
 
-      window->priv->menubar = gtk_application_window_create_menubar (G_MENU_MODEL (combined), G_ACTION_OBSERVABLE (muxer));
+      window->priv->menubar = gtk_application_window_create_menubar (window, G_MENU_MODEL (combined), G_ACTION_OBSERVABLE (muxer));
       gtk_widget_set_parent (window->priv->menubar, GTK_WIDGET (window));
       gtk_widget_show_all (window->priv->menubar);
       g_object_unref (combined);
@@ -113,6 +117,143 @@ gtk_application_window_update_menubar (GtkApplicationWindow *window)
     }
 }
 
+typedef struct {
+  GClosure closure;
+  gchar *action_name;
+  GVariant *parameter;
+} AccelClosure;
+
+static void
+accel_activate (GClosure     *closure,
+                GValue       *return_value,
+                guint         n_param_values,
+                const GValue *param_values,
+                gpointer      invocation_hint,
+                gpointer      marshal_data)
+{
+  AccelClosure *aclosure = (AccelClosure*)closure;
+  GActionGroup *actions;
+
+  actions = G_ACTION_GROUP (closure->data);
+  if (g_action_group_get_action_enabled (actions, aclosure->action_name))
+    {
+       g_action_group_activate_action (actions, aclosure->action_name, aclosure->parameter);
+
+      /* we handled the accelerator */
+      g_value_set_boolean (return_value, TRUE);
+    }
+}
+
+static void
+free_accel_closures (GtkApplicationWindow *window)
+{
+  GSList *l;
+
+  for (l = window->priv->accel_closures; l; l = l->next)
+    {
+       AccelClosure *closure = l->data;
+
+       g_object_unref (closure->closure.data);
+       if (closure->parameter)
+         g_variant_unref (closure->parameter);
+       g_free (closure->action_name);
+       g_closure_invalidate (&closure->closure);
+       g_closure_unref (&closure->closure);
+    }
+  g_slist_free (window->priv->accel_closures);
+  window->priv->accel_closures = NULL;
+}
+
+typedef struct {
+  GtkApplicationWindow *window;
+  GActionGroup *actions;
+} AccelData;
+
+/* Hack. We need to iterate over the accel map instead of
+ * the actions, in order to pull the parameters out of
+ * accel map entries
+ */
+static void
+add_accel_closure (gpointer         data,
+                   const gchar     *accel_path,
+                   guint            accel_key,
+                   GdkModifierType  accel_mods,
+                   gboolean         changed)
+{
+  AccelData *d = data;
+  const gchar *path;
+  const gchar *p;
+  gchar *action_name;
+  GVariant *parameter;
+  AccelClosure *closure;
+
+  if (accel_key == 0)
+    return;
+
+  if (!g_str_has_prefix (accel_path, "<Actions>/"))
+    return;
+
+  path = accel_path + strlen ("<Actions>/");
+  p = strchr (path, '/');
+  if (p)
+    {
+      action_name = g_strndup (path, p - path);
+      parameter = g_variant_parse (NULL, p + 1, NULL, NULL, NULL);
+      if (!parameter)
+        g_warning ("Failed to parse parameter from '%s'\n", accel_path);
+    }
+  else
+    {
+      action_name = g_strdup (path);
+      parameter = NULL;
+    }
+
+  if (g_action_group_has_action (d->actions, action_name))
+    {
+      closure = (AccelClosure*) g_closure_new_object (sizeof (AccelClosure), g_object_ref (d->actions));
+      g_closure_set_marshal (&closure->closure, accel_activate);
+
+      closure->action_name = g_strdup (action_name);
+      if (parameter)
+        closure->parameter = g_variant_ref_sink (parameter);
+      else
+        closure->parameter = NULL;
+
+      d->window->priv->accel_closures = g_slist_prepend (d->window->priv->accel_closures, g_closure_ref (&closure->closure));
+      g_closure_sink (&closure->closure);
+
+      gtk_accel_group_connect_by_path (d->window->priv->accel_group, accel_path, &closure->closure);
+    }
+
+  g_free (action_name);
+}
+
+static void
+gtk_application_window_update_accels (GtkApplicationWindow *window)
+{
+  AccelData data;
+  GActionMuxer *muxer;
+
+  if (gtk_window_get_application (GTK_WINDOW (window)) == NULL)
+    return;
+
+  /* FIXME: we should keep the muxer around, and listen for changes.
+   * Also, we should listen for accel map changes
+   */
+  muxer = g_action_muxer_new ();
+  g_action_muxer_insert (muxer, "app", G_ACTION_GROUP (gtk_window_get_application (GTK_WINDOW (window))));
+  g_action_muxer_insert (muxer, "win", G_ACTION_GROUP (window));
+
+  free_accel_closures (window);
+
+  data.window = window;
+  data.actions = G_ACTION_GROUP (muxer);
+
+  gtk_accel_map_foreach (&data, add_accel_closure);
+
+  g_object_unref (muxer);
+}
+
 static void
 gtk_application_window_update_shell_shows_app_menu (GtkApplicationWindow *window)
 {
@@ -419,6 +560,7 @@ gtk_application_window_real_realize (GtkWidget *widget)
   gtk_application_window_update_shell_shows_app_menu (window);
   gtk_application_window_update_shell_shows_menubar (window);
   gtk_application_window_update_menubar (window);
+  gtk_application_window_update_accels (window);
 
   GTK_WIDGET_CLASS (gtk_application_window_parent_class)
     ->realize (widget);
@@ -505,6 +647,8 @@ gtk_application_window_dispose (GObject *object)
   g_clear_object (&window->priv->app_menu_section);
   g_clear_object (&window->priv->menubar_section);
   g_clear_object (&window->priv->actions);
+  g_clear_object (&window->priv->accel_group);
+  free_accel_closures (window);
 
   G_OBJECT_CLASS (gtk_application_window_parent_class)
     ->dispose (object);
@@ -518,6 +662,8 @@ gtk_application_window_init (GtkApplicationWindow *window)
   window->priv->actions = g_simple_action_group_new ();
   window->priv->app_menu_section = g_menu_new ();
   window->priv->menubar_section = g_menu_new ();
+  window->priv->accel_group = gtk_accel_group_new ();
+  gtk_window_add_accel_group (GTK_WINDOW (window), window->priv->accel_group);
 
   /* window->priv->actions is the one and only ref on the group, so when
    * we dispose, the action group will die, disconnecting all signals.
@@ -609,14 +755,16 @@ gtk_application_window_set_show_menubar (GtkApplicationWindow *window,
 
 static void populate_menu_from_model (GtkMenuShell *menu,
                                       GMenuModel   *model,
-                                      GActionObservable *actions);
+                                      GActionObservable *actions,
+                                      GtkApplicationWindow *window);
 
 static void
-append_items_from_model (GtkMenuShell      *menu,
-                         GMenuModel        *model,
-                         GActionObservable *actions,
-                         gboolean          *need_separator,
-                         const gchar       *heading)
+append_items_from_model (GtkMenuShell         *menu,
+                         GMenuModel           *model,
+                         GActionObservable    *actions,
+                         gboolean             *need_separator,
+                         const gchar          *heading,
+                         GtkApplicationWindow *window)
 {
   gint n;
   gint i;
@@ -650,7 +798,7 @@ append_items_from_model (GtkMenuShell      *menu,
         {
           label = NULL;
           g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &label);
-          append_items_from_model (menu, m, actions, need_separator, label);
+          append_items_from_model (menu, m, actions, need_separator, label, window);
           g_object_unref (m);
           g_free (label);
 
@@ -670,7 +818,8 @@ append_items_from_model (GtkMenuShell      *menu,
       if ((m = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU)))
         {
           submenu = gtk_menu_new ();
-          populate_menu_from_model (GTK_MENU_SHELL (submenu), m, actions);
+          gtk_menu_set_accel_group (GTK_MENU (submenu), window->priv->accel_group);
+          populate_menu_from_model (GTK_MENU_SHELL (submenu), m, actions, window);
           gtk_menu_item_set_submenu (menuitem, submenu);
           g_object_unref (m);
         }
@@ -685,12 +834,13 @@ append_items_from_model (GtkMenuShell      *menu,
 static void
 populate_menu_from_model (GtkMenuShell      *menu,
                           GMenuModel        *model,
-                          GActionObservable *actions)
+                          GActionObservable *actions,
+                          GtkApplicationWindow *window)
 {
   gboolean need_separator;
 
   need_separator = FALSE;
-  append_items_from_model (menu, model, actions, &need_separator, NULL);
+  append_items_from_model (menu, model, actions, &need_separator, NULL, window);
 }
 
 typedef struct {
@@ -699,6 +849,7 @@ typedef struct {
   GtkMenuShell      *menu;
   guint              update_idle;
   GHashTable        *connected;
+  GtkApplicationWindow *window;
 } ItemsChangedData;
 
 static void
@@ -708,6 +859,7 @@ free_items_changed_data (gpointer data)
 
   g_object_unref (d->actions);
   g_object_unref (d->model);
+  g_object_unref (d->window);
 
   if (d->update_idle != 0)
     g_source_remove (d->update_idle);
@@ -733,7 +885,7 @@ repopulate_menu (gpointer data)
     }
   g_list_free (children);
 
-  populate_menu_from_model (d->menu, d->model, d->actions);
+  populate_menu_from_model (d->menu, d->model, d->actions, d->window);
   d->update_idle = 0;
 
   return FALSE;
@@ -783,8 +935,9 @@ items_changed (GMenuModel *model,
 }
 
 static GtkWidget *
-gtk_application_window_create_menubar (GMenuModel        *model,
-                                       GActionObservable *actions)
+gtk_application_window_create_menubar (GtkApplicationWindow *window,
+                                       GMenuModel           *model,
+                                       GActionObservable    *actions)
 {
   ItemsChangedData *data;
   GtkWidget *menubar;
@@ -794,6 +947,7 @@ gtk_application_window_create_menubar (GMenuModel        *model,
   data = g_new (ItemsChangedData, 1);
   data->model = g_object_ref (model);
   data->actions = g_object_ref (actions);
+  data->window = g_object_ref (window);
   data->menu = GTK_MENU_SHELL (menubar);
   data->update_idle = 0;
   data->connected = g_hash_table_new (NULL, NULL);
diff --git a/gtk/gtkmodelmenuitem.c b/gtk/gtkmodelmenuitem.c
index 08a3ac1..c997395 100644
--- a/gtk/gtkmodelmenuitem.c
+++ b/gtk/gtkmodelmenuitem.c
@@ -22,6 +22,7 @@
 #include "config.h"
 
 #include "gtkmodelmenuitem.h"
+#include "gtkaccelmap.h"
 
 struct _GtkModelMenuItem
 {
@@ -186,6 +187,23 @@ gtk_model_menu_item_action_removed (GActionObserver   *observer,
   gtk_widget_queue_resize (GTK_WIDGET (item));
 }
 
+/* keep this in sync with gtkapplication.c */
+static gchar *
+get_accel_path (const gchar *action_name,
+                GVariant    *parameter)
+{
+  GString *s;
+
+  s = g_string_new ("<Actions>/");
+  g_string_append (s, action_name);
+  if (parameter)
+    {
+      g_string_append_c (s, '/');
+      g_variant_print_string (parameter, s, FALSE);
+    }
+  return g_string_free (s, FALSE);
+}
+
 static void
 gtk_model_menu_item_setup (GtkModelMenuItem  *item,
                            GMenuModel        *model,
@@ -219,6 +237,7 @@ gtk_model_menu_item_setup (GtkModelMenuItem  *item,
       const GVariantType *type;
       gboolean enabled;
       GVariant *state;
+      gchar *path;
 
       /* observer already causes us to hold a hard ref on the group */
       item->actions = G_ACTION_GROUP (actions);
@@ -233,6 +252,10 @@ gtk_model_menu_item_setup (GtkModelMenuItem  *item,
 
       if (state != NULL)
         g_variant_unref (state);
+
+      path = get_accel_path (item->action_name, item->target);
+      gtk_menu_item_set_accel_path (GTK_MENU_ITEM (item), path);
+      g_free (path);
     }
 }
 
