diff --git a/aisleriot/game.c b/aisleriot/game.c
index 2f53bad..ba350bf 100644
--- a/aisleriot/game.c
+++ b/aisleriot/game.c
@@ -79,8 +79,9 @@ struct _AisleriotGame
   guint click_to_move : 1;
   guint can_undo : 1;
   guint can_redo : 1;
+  guint can_deal : 1;
   guint show_score : 1;
-  guint features : 2; /* enough bits for ALL_FEATURES */
+  guint features : 3; /* enough bits for ALL_FEATURES */
   guint state : 3; /* enough bits for LAST_GAME_STATE */
   guint had_exception : 1;
   guint paused : 1;
@@ -94,6 +95,7 @@ enum
   PROP_0,
   PROP_CAN_UNDO,
   PROP_CAN_REDO,
+  PROP_CAN_DEAL,
   PROP_GAME_FILE,
   PROP_SCORE,
   PROP_STATE,
@@ -165,6 +167,17 @@ set_game_redoable (AisleriotGame *game,
   }
 }
 
+static void
+set_game_dealable (AisleriotGame *game,
+                   gboolean enabled)
+{
+  if (enabled != game->can_deal) {
+    game->can_deal = enabled;
+
+    g_object_notify (G_OBJECT (game), "can-deal");
+  }
+}
+
 /* Yes, there is a gconf race condition here, but since this is all
    per user they're going to have to be either playing impossibly fast
    or using sharing an account. In the later case they won't be caring
@@ -632,6 +645,18 @@ scm_redo_set_sensitive (SCM in_state)
 }
 
 static SCM
+scm_dealable_set_sensitive (SCM in_state)
+{
+  AisleriotGame *game = app_game;
+  gboolean state;
+
+  state = SCM_NFALSEP (in_state) ? TRUE : FALSE;
+  set_game_dealable (game, state);
+
+  return SCM_EOL;
+}
+
+static SCM
 scm_get_feature_word ()
 {
   AisleriotGame *game = app_game;
@@ -963,6 +988,7 @@ cscm_init ()
   scm_c_define_gsubr ("_", 1, 0, 0, scm_gettext);
   scm_c_define_gsubr ("undo-set-sensitive", 1, 0, 0, scm_undo_set_sensitive);
   scm_c_define_gsubr ("redo-set-sensitive", 1, 0, 0, scm_redo_set_sensitive);
+  scm_c_define_gsubr ("dealable-set-sensitive", 1, 0, 0, scm_dealable_set_sensitive);
 }
 
 static void
@@ -1112,6 +1138,9 @@ aisleriot_game_get_property (GObject *object,
     case PROP_CAN_REDO:
       g_value_set_boolean (value, game->can_redo);
       break;
+    case PROP_CAN_DEAL:
+      g_value_set_boolean (value, game->can_deal);
+      break;
     case PROP_GAME_FILE:
       g_value_set_string (value, game->game_file);
       break;
@@ -1208,6 +1237,13 @@ aisleriot_game_class_init (AisleriotGameClass *klass)
 
   g_object_class_install_property
     (gobject_class,
+     PROP_CAN_DEAL,
+     g_param_spec_boolean ("can-deal", NULL, NULL,
+                           FALSE,
+                           G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
+
+  g_object_class_install_property
+    (gobject_class,
      PROP_GAME_FILE,
      g_param_spec_string ("game-file", NULL, NULL,
                           NULL,
@@ -1423,6 +1459,7 @@ aisleriot_game_load_game (AisleriotGame *game,
   set_game_score (game, 0);
   set_game_undoable (game, FALSE);
   set_game_redoable (game, FALSE);
+  set_game_dealable (game, FALSE);
   game->features = 0;
   game->had_exception = FALSE;
 
@@ -1949,3 +1986,9 @@ aisleriot_game_generate_exception (AisleriotGame *game)
 {
   cscmi_eval_string ("(how-about-a-nice-crash?)");
 }
+
+void
+aisleriot_game_deal_next_round (AisleriotGame *game)
+{
+  cscmi_eval_string ("(deal-next-round)");
+}
diff --git a/aisleriot/game.h b/aisleriot/game.h
index 4187cf2..ff259cc 100644
--- a/aisleriot/game.h
+++ b/aisleriot/game.h
@@ -89,9 +89,10 @@ enum {
 };
 
 typedef enum {
-  FEATURE_DROPPABLE = 1 << 0,
-  FEATURE_SCORE_HIDDEN = 1 << 1,
-  ALL_FEATURES = 0x3
+  FEATURE_DROPPABLE     = 1 << 0,
+  FEATURE_SCORE_HIDDEN  = 1 << 1,
+  FEATURE_DEALABLE      = 1 << 2,
+  ALL_FEATURES          = 0x7
 } AisleriotGameFeatures;
 
 typedef enum {
@@ -184,6 +185,8 @@ void aisleriot_game_test_end_of_game (AisleriotGame * game);
 
 void aisleriot_game_generate_exception (AisleriotGame * game);
 
+void aisleriot_game_deal_next_round (AisleriotGame *game);
+
 G_END_DECLS
 
 #endif /* !AISLERIOT_GAME_H */
diff --git a/aisleriot/rules/spider.scm b/aisleriot/rules/spider.scm
index 534b12b..668d953 100644
--- a/aisleriot/rules/spider.scm
+++ b/aisleriot/rules/spider.scm
@@ -77,7 +77,9 @@
   (deal-initial-setup)
 
   (give-status-message)
-  
+
+  (dealable-set-sensitive #t)
+
   (list 10 4))
 
 (define (give-status-message)
@@ -185,8 +187,22 @@
 	   (begin
 	     (deal-new-cards tableau)
 	     (give-status-message)
+             (dealable-set-sensitive (not (empty-slot? stock)))
 	     #t))))
 
+(define (deal-next-round)
+   (if (and (not (empty-slot? stock))
+            (not allow-empty-slots)
+            (any-slot-empty? tableau))
+       (begin
+         (set-statusbar-message (_"Please fill in empty pile first."))
+         #f)
+       (begin
+         (deal-new-cards tableau)
+         (give-status-message)
+         (dealable-set-sensitive (not (empty-slot? stock)))
+         #t)))
+
 (define (button-double-clicked slot)
   #f)
 
@@ -286,6 +302,6 @@
 
 (define (timeout) #f)
 
-(set-features droppable-feature)
+(set-features droppable-feature dealable-feature)
 
 (set-lambda new-game button-pressed button-released button-clicked button-double-clicked game-over game-won get-hint get-options apply-options timeout droppable?)
diff --git a/aisleriot/sol.scm b/aisleriot/sol.scm
index 9880fb0..8df6d83 100644
--- a/aisleriot/sol.scm
+++ b/aisleriot/sol.scm
@@ -28,6 +28,7 @@
 ;; Feature masks:
 (define droppable-feature 1)
 (define scores-disabled 2)
+(define dealable-feature 4)
 
 (define (reset-features) (set-feature-word! 0))
 
diff --git a/aisleriot/window.c b/aisleriot/window.c
index 4eeef4d..f87fcf4 100644
--- a/aisleriot/window.c
+++ b/aisleriot/window.c
@@ -87,6 +87,7 @@ enum
   ACTION_FULLSCREEN,
   ACTION_HELP_GAME,
   ACTION_OPTIONS_MENU,
+  ACTION_DEAL,
   ACTION_HINT,
 #ifdef HAVE_MAEMO
   ACTION_ACCEL_UNDO_MOVE,
@@ -1063,6 +1064,15 @@ show_hint_cb (GtkAction *action,
   g_free (message);
 }
 
+static void
+deal_cb (GtkAction *action,
+         AisleriotWindow *window)
+{
+  AisleriotWindowPrivate *priv = window->priv;
+
+  aisleriot_game_deal_next_round (priv->game);
+}
+
 /* Make something that's easier to store in conf */
 static guint
 compress_options_to_int (SCM options_list)
@@ -1622,13 +1632,27 @@ sync_game_redoable (AisleriotGame *game,
 }
 
 static void
+sync_game_dealable (AisleriotGame *game,
+                    GParamSpec *pspec,
+                    AisleriotWindow *window)
+{
+  AisleriotWindowPrivate *priv = window->priv;
+  gboolean enabled;
+
+  g_object_get (game, "can-deal", &enabled, NULL);
+
+  gtk_action_set_sensitive (priv->action[ACTION_DEAL], enabled);
+}
+
+static void
 game_type_changed_cb (AisleriotGame *game,
                       AisleriotWindow *window)
 {
   AisleriotWindowPrivate *priv = window->priv;
   char *game_name;
-#ifndef HAVE_MAEMO
   guint features;
+  gboolean dealable;
+#ifndef HAVE_MAEMO
   gboolean show_scores;
 #endif /* !HAVE_MAEMO */
 
@@ -1651,6 +1675,11 @@ game_type_changed_cb (AisleriotGame *game,
 
   update_statistics_display (window);
 
+  features = aisleriot_game_get_features (game);
+
+  dealable = (features & FEATURE_DEALABLE) != 0;
+  gtk_action_set_visible (priv->action[ACTION_DEAL], dealable);
+
 #ifdef HAVE_MAEMO
   gtk_label_set_text (priv->game_message_label, NULL);
 #else
@@ -1658,8 +1687,6 @@ game_type_changed_cb (AisleriotGame *game,
 
   gtk_statusbar_pop (priv->statusbar, priv->game_message_id);
 
-  features = aisleriot_game_get_features (game);
-
   show_scores = (features & FEATURE_SCORE_HIDDEN) == 0;
   g_object_set (priv->score_box, "visible", show_scores, NULL);
 #endif
@@ -1936,6 +1963,9 @@ aisleriot_window_init (AisleriotWindow *window)
       G_CALLBACK (undo_cb) },
     { "RedoMove", GAMES_STOCK_REDO_MOVE, NULL, NULL, NULL,
       G_CALLBACK (redo_cb) },
+    { "Deal", GTK_STOCK_OK /* FIXMEchpe */, _("Deal"), "<control>D",
+      ACTION_TOOLTIP (N_("Deal next round")),
+      G_CALLBACK (deal_cb) },
     { "Hint", GAMES_STOCK_HINT, NULL, NULL, NULL,
       G_CALLBACK (show_hint_cb) },
     { "Contents", GAMES_STOCK_CONTENTS, NULL, NULL,
@@ -2006,6 +2036,7 @@ aisleriot_window_init (AisleriotWindow *window)
     "Fullscreen",
     "HelpGame",
     "OptionsMenu",
+    "Deal",
     "Hint",
 #ifdef HAVE_MAEMO
     "AccelUndoMove",
@@ -2037,6 +2068,7 @@ aisleriot_window_init (AisleriotWindow *window)
         "<menu action='ControlMenu'>"
           "<menuitem action='UndoMove'/>"
           "<menuitem action='RedoMove'/>"
+          "<menuitem action='Deal'/>"
           "<menuitem action='Hint'/>"
           "<separator/>"
           "<menuitem action='ClickToMove'/>"
@@ -2076,6 +2108,7 @@ aisleriot_window_init (AisleriotWindow *window)
         "<menu action='ControlMenu'>"
           "<menuitem action='UndoMove'/>"
           "<menuitem action='RedoMove'/>"
+          "<menuitem action='Deal'/>"
           "<menuitem action='Hint'/>"
           "<separator/>"
           "<menuitem action='ClickToMove'/>"
@@ -2106,6 +2139,7 @@ aisleriot_window_init (AisleriotWindow *window)
         "<separator/>"
         "<toolitem action='UndoMove'/>"
         "<toolitem action='RedoMove'/>"
+        "<toolitem action='Deal'/>"
         "<toolitem action='Hint'/>"
 #ifndef HAVE_MAEMO
         "<toolitem action='LeaveFullscreen'/>"
@@ -2202,6 +2236,9 @@ aisleriot_window_init (AisleriotWindow *window)
     g_assert (priv->action[i]);
   }
 
+  /* Hide the "Deal" action initially, since not all games support it */
+  gtk_action_set_visible (priv->action[ACTION_DEAL], FALSE);
+
 #ifndef HAVE_MAEMO
   statusbar = priv->statusbar = GTK_STATUSBAR (gtk_statusbar_new ());
   priv->game_message_id = gtk_statusbar_get_context_id (priv->statusbar, "game-message");
@@ -2324,6 +2361,9 @@ aisleriot_window_init (AisleriotWindow *window)
   sync_game_redoable (priv->game, NULL, window);
   g_signal_connect (priv->game, "notify::can-redo",
                     G_CALLBACK (sync_game_redoable), window);
+  sync_game_dealable (priv->game, NULL, window);
+  g_signal_connect (priv->game, "notify::can-deal",
+                    G_CALLBACK (sync_game_dealable), window);
 
   g_signal_connect (priv->game, "game-type",
                     G_CALLBACK (game_type_changed_cb), window);
