    Packing windows in Metacity
    ===========================
    
    This patch is against Metacity (CVS HEAD, 2006-03-25).

    The patch adds direct shortcut keybindings for packing windows
    around the screen, similar to holding shift down while in the
    "Move window" mode.

    What this patch does is:
    - It factors window moving code out from process_keyboard_move_grab
      to a dedicated function do_move_window.
    - It adds a new keybinding function handle_pack_window, and installs
      all necessary hooks.

    This is a revised version of my previous patch (from autumn 2005), 
    adapted to changes made in metacity HEAD.

    -- Pauli Virtanen <pauli.virtanen@iki.fi>, Sun, 26 Mar 2006 15:37:50 +0300

diff -r 3e0576340185 src/keybindings.c
--- a/src/keybindings.c	Sat Mar 11 20:20:07 2006 +0000
+++ b/src/keybindings.c	Sun Mar 26 15:37:34 2006 +0300
@@ -195,6 +195,11 @@ static void handle_run_terminal       (M
                                        MetaWindow     *window,
                                        XEvent         *event,
                                        MetaKeyBinding *binding);
+static void handle_pack_window        (MetaDisplay    *display,
+                                       MetaScreen     *screen,
+                                       MetaWindow     *window,
+                                       XEvent         *event,
+                                       MetaKeyBinding *binding);
 
 /* debug */
 static gboolean process_keyboard_move_grab (MetaDisplay *display,
@@ -417,6 +422,14 @@ static const MetaKeyHandler window_handl
   { META_KEYBINDING_LOWER, handle_lower, NULL},
   { META_KEYBINDING_MAXIMIZE_VERTICALLY, handle_maximize_vert, NULL },
   { META_KEYBINDING_MAXIMIZE_HORIZONTALLY, handle_maximize_horiz, NULL },
+  { META_KEYBINDING_PACK_WINDOW_LEFT, handle_pack_window,
+    GINT_TO_POINTER (META_DIRECTION_LEFT) },
+  { META_KEYBINDING_PACK_WINDOW_RIGHT, handle_pack_window,
+    GINT_TO_POINTER (META_DIRECTION_RIGHT) },
+  { META_KEYBINDING_PACK_WINDOW_UP, handle_pack_window,
+    GINT_TO_POINTER (META_DIRECTION_UP) },
+  { META_KEYBINDING_PACK_WINDOW_DOWN, handle_pack_window,
+    GINT_TO_POINTER (META_DIRECTION_DOWN) },
   { NULL, NULL, NULL }
 };
 
@@ -1690,6 +1703,115 @@ meta_display_process_key_event (MetaDisp
     }
 }
 
+typedef enum
+{
+  NORMAL_MOVE,
+  SMALL_STEP,
+  SMART_SNAP,
+} WindowMoveStyle;
+
+static gboolean
+do_move_window (MetaDisplay         *display,
+                MetaScreen          *screen,
+                MetaWindow          *window,
+                WindowMoveStyle      move_style,
+                MetaDirection        direction)
+{
+  gboolean handled;
+  gboolean smart_snap;
+  int x, y;
+  int incr;
+  
+  handled = FALSE;
+
+  if (display->grab_wireframe_active)
+    {
+      x = display->grab_wireframe_rect.x;
+      y = display->grab_wireframe_rect.y;
+    }
+  else
+    {
+      meta_window_get_position (window, &x, &y);
+    }
+
+#define SMALL_INCREMENT 1
+#define NORMAL_INCREMENT 10
+
+  smart_snap = FALSE;
+
+  if (move_style == SMART_SNAP)
+    {
+      incr = 1;
+      smart_snap = TRUE;
+    }
+  else if (move_style == SMALL_STEP)
+    incr = SMALL_INCREMENT;
+  else
+    incr = NORMAL_INCREMENT;
+
+  /* When moving by increments, we still snap to edges if the move
+   * to the edge is smaller than the increment. This is because
+   * Shift + arrow to snap is sort of a hidden feature. This way
+   * people using just arrows shouldn't get too frustrated.
+   */
+  if (direction & META_DIRECTION_UP)
+    {
+      y -= incr;
+      handled = TRUE;
+    }
+  if (direction & META_DIRECTION_DOWN)
+    {
+      y += incr;
+      handled = TRUE;
+    }
+  if (direction & META_DIRECTION_LEFT)
+    {
+      x -= incr;
+      handled = TRUE;
+    }
+  if (direction & META_DIRECTION_RIGHT)
+    {
+      x += incr;
+      handled = TRUE;
+    }
+
+  if (handled)
+    {
+      MetaRectangle old_rect;
+      meta_topic (META_DEBUG_KEYBINDINGS,
+                  "Computed new window location %d,%d due to keypress\n",
+                  x, y);
+
+      if (display->grab_wireframe_active)
+        old_rect = display->grab_wireframe_rect;
+      else
+        {
+          old_rect = window->rect;
+          meta_window_get_position (window, &old_rect.x, &old_rect.y);
+        }
+
+      meta_window_edge_resistance_for_move (window, 
+                                            old_rect.x,
+                                            old_rect.y,
+                                            &x,
+                                            &y,
+                                            NULL,
+                                            smart_snap,
+                                            TRUE);
+
+      if (display->grab_wireframe_active)
+        meta_window_update_wireframe (window, x, y,
+                                      display->grab_wireframe_rect.width,
+                                      display->grab_wireframe_rect.height);
+      else
+        meta_window_move (window, TRUE, x, y);
+
+      meta_window_update_keyboard_move (window);
+    }
+
+  return handled;
+}
+
 static gboolean
 process_keyboard_move_grab (MetaDisplay *display,
                             MetaScreen  *screen,
@@ -1697,13 +1819,12 @@ process_keyboard_move_grab (MetaDisplay 
                             XEvent      *event,
                             KeySym       keysym)
 {
+  WindowMoveStyle move_style;
+  MetaDirection direction;
   gboolean handled;
-  int x, y;
-  int incr;
-  gboolean smart_snap;
   
   handled = FALSE;
-
+  
   /* don't care about releases, but eat them, don't end grab */
   if (event->type == KeyRelease)
     return TRUE;
@@ -1711,28 +1832,6 @@ process_keyboard_move_grab (MetaDisplay 
   /* don't end grab on modifier key presses */
   if (is_modifier (display, event->xkey.keycode))
     return TRUE;
-
-  if (display->grab_wireframe_active)
-    {
-      x = display->grab_wireframe_rect.x;
-      y = display->grab_wireframe_rect.y;
-    }
-  else
-    {
-      meta_window_get_position (window, &x, &y);
-    }
-
-  smart_snap = (event->xkey.state & ShiftMask) != 0;
-  
-#define SMALL_INCREMENT 1
-#define NORMAL_INCREMENT 10
-
-  if (smart_snap)
-    incr = 1;
-  else if (event->xkey.state & ControlMask)
-    incr = SMALL_INCREMENT;
-  else
-    incr = NORMAL_INCREMENT;
 
   if (keysym == XK_Escape)
     {
@@ -1750,88 +1849,58 @@ process_keyboard_move_grab (MetaDisplay 
                                  display->grab_initial_window_pos.height);
       display->grab_was_cancelled = TRUE;
     }
-  
-  /* When moving by increments, we still snap to edges if the move
-   * to the edge is smaller than the increment. This is because
-   * Shift + arrow to snap is sort of a hidden feature. This way
-   * people using just arrows shouldn't get too frustrated.
-   */
+
   switch (keysym)
     {
-    case XK_KP_Home:
-    case XK_KP_Prior:
     case XK_Up:
     case XK_KP_Up:
-      y -= incr;
-      handled = TRUE;
+      direction = META_DIRECTION_UP;
+      break;
+    case XK_KP_Home:
+      direction = META_DIRECTION_UP | META_DIRECTION_LEFT;
+      break;
+    case XK_KP_Prior:
+      direction = META_DIRECTION_UP | META_DIRECTION_RIGHT;
+      break;
+    case XK_Left:
+    case XK_KP_Left:
+      direction = META_DIRECTION_LEFT;
+      break;
+    case XK_Right:
+    case XK_KP_Right:
+      direction = META_DIRECTION_RIGHT;
       break;
     case XK_KP_End:
+      direction = META_DIRECTION_DOWN | META_DIRECTION_LEFT;
+      break;
     case XK_KP_Next:
+      direction = META_DIRECTION_DOWN | META_DIRECTION_RIGHT;
+      break;
     case XK_Down:
     case XK_KP_Down:
-      y += incr;
-      handled = TRUE;
+      direction = META_DIRECTION_DOWN;
       break;
-    }
-  
-  switch (keysym)
-    {
-    case XK_KP_Home:
-    case XK_KP_End:
-    case XK_Left:
-    case XK_KP_Left:
-      x -= incr;
-      handled = TRUE;
-      break;
-    case XK_KP_Prior:
-    case XK_KP_Next:
-    case XK_Right:
-    case XK_KP_Right:
-      x += incr;
-      handled = TRUE;
-      break;
-    }
-
-  if (handled)
-    {
-      MetaRectangle old_rect;
-      meta_topic (META_DEBUG_KEYBINDINGS,
-                  "Computed new window location %d,%d due to keypress\n",
-                  x, y);
-
-      if (display->grab_wireframe_active)
-        old_rect = display->grab_wireframe_rect;
-      else
-        {
-          old_rect = window->rect;
-          meta_window_get_position (window, &old_rect.x, &old_rect.y);
-        }
-
-      meta_window_edge_resistance_for_move (window, 
-                                            old_rect.x,
-                                            old_rect.y,
-                                            &x,
-                                            &y,
-                                            NULL,
-                                            smart_snap,
-                                            TRUE);
-
-      if (display->grab_wireframe_active)
-        {
-          meta_window_update_wireframe (window, x, y,
-                                        display->grab_wireframe_rect.width,
-                                        display->grab_wireframe_rect.height);
-        }
-      else
-        {
-          meta_window_move (window, TRUE, x, y);
-        }
-      
-      meta_window_update_keyboard_move (window);
-    }
+    default:
+      /* no direction recognized, end grab */
+      return FALSE;
+    }
+
+  if (event->xkey.state & ControlMask)
+    move_style = SMALL_STEP;
+  else if (event->xkey.state & ShiftMask)
+    move_style = SMART_SNAP;
+  else
+    move_style = NORMAL_MOVE;
+
+  handled = do_move_window (display,
+                            screen,
+                            window,
+                            move_style,
+                            direction);
 
   return handled;
 }
+
 
 static gboolean
 process_keyboard_resize_grab_op_change (MetaDisplay *display,
@@ -3458,6 +3527,43 @@ handle_workspace_switch  (MetaDisplay   
     }
 }
 
+static void handle_pack_window (MetaDisplay    *display,
+                                MetaScreen     *screen,
+                                MetaWindow     *window,
+                                XEvent         *event,
+                                MetaKeyBinding *binding)
+{
+  if (window && window->has_move_func)
+    {
+      /* We need to take a grab now: Moving windows in wireframe mode
+       * requires that display->grab_wireframe_rect is available.
+       */
+      if (meta_display_begin_grab_op (display,
+                                      screen,
+                                      window,
+                                      META_GRAB_OP_KEYBOARD_MOVING,
+                                      FALSE,
+                                      event->xkey.serial,
+                                      0,
+                                      0,
+                                      event->xkey.time,
+                                      0, 0))
+        {
+          meta_topic (META_DEBUG_KEYBINDINGS, "Packing window\n");
+
+          do_move_window (display,
+                          screen,
+                          window,
+                          SMART_SNAP,
+                          GPOINTER_TO_INT (binding->handler->data));
+          
+          meta_display_end_grab_op (display, event->xkey.time);
+        }
+    }
+}
+
+
+
 #if 0
 static void
 handle_spew_mark (MetaDisplay    *display,
diff -r 3e0576340185 src/metacity.schemas.in
--- a/src/metacity.schemas.in	Sat Mar 11 20:20:07 2006 +0000
+++ b/src/metacity.schemas.in	Sun Mar 26 15:37:34 2006 +0300
@@ -1083,6 +1083,102 @@ you set
       </locale>
     </schema>
 
+    <schema>
+      <key>/schemas/apps/metacity/window_keybindings/pack_window_left</key>
+      <applyto>/apps/metacity/window_keybindings/pack_window_left</applyto>
+      <owner>metacity</owner>
+      <type>string</type>
+      <!-- no default for this one -->
+      <locale name="C">
+         <short>Pack window to left</short>
+         <long>
+            This keybinding moves a window to the left so far that it hits
+            the edge of the screen or another window.
+
+            The format looks like "&lt;Control&gt;a" or
+            "&lt;Shift&gt;&lt;Alt&gt;F1".
+
+            The parser is fairly liberal and allows lower or upper case,
+            and also abbreviations such as "&lt;Ctl&gt;" and
+            "&lt;Ctrl&gt;". If you set the option to the special string
+            "disabled", then there will be no keybinding for this
+            action.
+         </long>
+      </locale>
+    </schema>
+
+    <schema>
+      <key>/schemas/apps/metacity/window_keybindings/pack_window_right</key>
+      <applyto>/apps/metacity/window_keybindings/pack_window_right</applyto>
+      <owner>metacity</owner>
+      <type>string</type>
+      <!-- no default for this one -->
+      <locale name="C">
+         <short>Pack window to right</short>
+         <long>
+            This keybinding moves a window to the right so far that it hits
+            the edge of the screen or another window.
+
+            The format looks like "&lt;Control&gt;a" or
+            "&lt;Shift&gt;&lt;Alt&gt;F1".
+
+            The parser is fairly liberal and allows lower or upper case,
+            and also abbreviations such as "&lt;Ctl&gt;" and
+            "&lt;Ctrl&gt;". If you set the option to the special string
+            "disabled", then there will be no keybinding for this
+            action.
+         </long>
+      </locale>
+    </schema>
+
+    <schema>
+      <key>/schemas/apps/metacity/window_keybindings/pack_window_up</key>
+      <applyto>/apps/metacity/window_keybindings/pack_window_up</applyto>
+      <owner>metacity</owner>
+      <type>string</type>
+      <!-- no default for this one -->
+      <locale name="C">
+         <short>Pack window up</short>
+         <long>
+            This keybinding moves a window up so far that it hits
+            the edge of the screen or another window.
+
+            The format looks like "&lt;Control&gt;a" or
+            "&lt;Shift&gt;&lt;Alt&gt;F1".
+
+            The parser is fairly liberal and allows lower or upper case,
+            and also abbreviations such as "&lt;Ctl&gt;" and
+            "&lt;Ctrl&gt;". If you set the option to the special string
+            "disabled", then there will be no keybinding for this
+            action.
+         </long>
+      </locale>
+    </schema>
+
+    <schema>
+      <key>/schemas/apps/metacity/window_keybindings/pack_window_down</key>
+      <applyto>/apps/metacity/window_keybindings/pack_window_down</applyto>
+      <owner>metacity</owner>
+      <type>string</type>
+      <!-- no default for this one -->
+      <locale name="C">
+         <short>Pack window down</short>
+         <long>
+            This keybinding moves a window down so far that it hits
+            the edge of the screen or another window.
+
+            The format looks like "&lt;Control&gt;a" or
+            "&lt;Shift&gt;&lt;Alt&gt;F1".
+
+            The parser is fairly liberal and allows lower or upper case,
+            and also abbreviations such as "&lt;Ctl&gt;" and
+            "&lt;Ctrl&gt;". If you set the option to the special string
+            "disabled", then there will be no keybinding for this
+            action.
+         </long>
+      </locale>
+    </schema>
+
     <!-- Global Keybindings -->
 
 
diff -r 3e0576340185 src/prefs.c
--- a/src/prefs.c	Sat Mar 11 20:20:07 2006 +0000
+++ b/src/prefs.c	Sun Mar 26 15:37:34 2006 +0300
@@ -1801,6 +1801,10 @@ static MetaKeyPref window_bindings[] = {
   { META_KEYBINDING_LOWER, 0, 0, FALSE },
   { META_KEYBINDING_MAXIMIZE_VERTICALLY, 0, 0, FALSE },
   { META_KEYBINDING_MAXIMIZE_HORIZONTALLY, 0, 0, FALSE },
+  { META_KEYBINDING_PACK_WINDOW_LEFT, 0, 0, FALSE },
+  { META_KEYBINDING_PACK_WINDOW_RIGHT, 0, 0, FALSE  },
+  { META_KEYBINDING_PACK_WINDOW_UP, 0, 0, FALSE },
+  { META_KEYBINDING_PACK_WINDOW_DOWN, 0, 0, FALSE },
   { NULL, 0, 0, FALSE }
 };
 
diff -r 3e0576340185 src/prefs.h
--- a/src/prefs.h	Sat Mar 11 20:20:07 2006 +0000
+++ b/src/prefs.h	Sun Mar 26 15:37:34 2006 +0300
@@ -199,6 +199,10 @@ gboolean    meta_prefs_get_compositing_m
 #define META_KEYBINDING_LOWER                    "lower"
 #define META_KEYBINDING_MAXIMIZE_VERTICALLY      "maximize_vertically"
 #define META_KEYBINDING_MAXIMIZE_HORIZONTALLY    "maximize_horizontally"
+#define META_KEYBINDING_PACK_WINDOW_LEFT         "pack_window_left"
+#define META_KEYBINDING_PACK_WINDOW_RIGHT        "pack_window_right"
+#define META_KEYBINDING_PACK_WINDOW_UP           "pack_window_up"
+#define META_KEYBINDING_PACK_WINDOW_DOWN         "pack_window_down"
 
 typedef enum _MetaKeyBindingAction
 {
