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

    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, 05 Mar 2006 17:10:53 +0200

diff -r efbcb443208b src/keybindings.c
--- a/src/keybindings.c	Sun Mar  5 09:31:26 2006 +0000
+++ b/src/keybindings.c	Sun Mar  5 17:06:52 2006 +0200
@@ -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 }
 };
 
@@ -1691,26 +1704,18 @@ meta_display_process_key_event (MetaDisp
 }
 
 static gboolean
-process_keyboard_move_grab (MetaDisplay *display,
-                            MetaScreen  *screen,
-                            MetaWindow  *window,
-                            XEvent      *event,
-                            KeySym       keysym)
+do_move_window (MetaDisplay         *display,
+                MetaScreen          *screen,
+                MetaWindow          *window,
+                gboolean             smart_snap,
+                gboolean             small_move,
+                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;
-
-  /* don't end grab on modifier key presses */
-  if (is_modifier (display, event->xkey.keycode))
-    return TRUE;
 
   if (display->grab_wireframe_active)
     {
@@ -1722,75 +1727,37 @@ process_keyboard_move_grab (MetaDisplay 
       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)
+  else if (small_move)
     incr = SMALL_INCREMENT;
   else
     incr = NORMAL_INCREMENT;
 
-  if (keysym == XK_Escape)
-    {
-      /* End resize and restore to original state.
-       * The move_resize is only needed when !wireframe
-       * since in wireframe we always moveresize at the end
-       * of the grab only.
-       */
-      if (!display->grab_wireframe_active)
-        meta_window_move_resize (display->grab_window,
-                                 TRUE,
-                                 display->grab_initial_window_pos.x,
-                                 display->grab_initial_window_pos.y,
-                                 display->grab_initial_window_pos.width,
-                                 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:
+  if (direction & META_DIRECTION_UP) {
       y -= incr;
       handled = TRUE;
-      break;
-    case XK_KP_End:
-    case XK_KP_Next:
-    case XK_Down:
-    case XK_KP_Down:
+  }
+  if (direction & META_DIRECTION_DOWN) {
       y += incr;
       handled = TRUE;
-      break;
-    }
-  
-  switch (keysym)
-    {
-    case XK_KP_Home:
-    case XK_KP_End:
-    case XK_Left:
-    case XK_KP_Left:
+  }
+  if (direction & META_DIRECTION_LEFT) {
       x -= incr;
       handled = TRUE;
-      break;
-    case XK_KP_Prior:
-    case XK_KP_Next:
-    case XK_Right:
-    case XK_KP_Right:
+  }
+  if (direction & META_DIRECTION_RIGHT) {
       x += incr;
       handled = TRUE;
-      break;
-    }
+  }
 
   if (handled)
     {
@@ -1817,21 +1784,105 @@ process_keyboard_move_grab (MetaDisplay 
                                             TRUE);
 
       if (display->grab_wireframe_active)
-        {
-          meta_window_update_wireframe (window, x, y,
-                                        display->grab_wireframe_rect.width,
-                                        display->grab_wireframe_rect.height);
-        }
+        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_move (window, TRUE, x, y);
+    }
+
+  return handled;
+}
+
+static gboolean
+process_keyboard_move_grab (MetaDisplay *display,
+                            MetaScreen  *screen,
+                            MetaWindow  *window,
+                            XEvent      *event,
+                            KeySym       keysym)
+{
+  gboolean smart_snap;
+  gboolean small_move;
+  MetaMotionDirection direction;
+  gboolean handled;
+  
+  handled = FALSE;
+  
+  /* don't care about releases, but eat them, don't end grab */
+  if (event->type == KeyRelease)
+    return TRUE;
+
+  /* don't end grab on modifier key presses */
+  if (is_modifier (display, event->xkey.keycode))
+    return TRUE;
+
+  if (keysym == XK_Escape)
+    {
+      /* End resize and restore to original state.
+       * The move_resize is only needed when !wireframe
+       * since in wireframe we always moveresize at the end
+       * of the grab only.
+       */
+      meta_window_move_resize (display->grab_window,
+                               TRUE,
+                               display->grab_initial_window_pos.x,
+                               display->grab_initial_window_pos.y,
+                               display->grab_initial_window_pos.width,
+                               display->grab_initial_window_pos.height);
+      display->grab_was_cancelled = TRUE;
+    }
+
+  small_move = (event->xkey.state & ControlMask) != 0;
+  smart_snap = (event->xkey.state & ShiftMask) != 0;
+
+  switch (keysym)
+    {
+    case XK_Up:
+    case XK_KP_Up:
+      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:
+      direction = META_DIRECTION_DOWN;
+      break;
+    default:
+      /* no direction recognized, end grab */
+      return FALSE;
+    }
+
+  handled = do_move_window (display,
+                            screen,
+                            window,
+                            smart_snap,
+                            small_move,
+                            direction);
+  
+  if (handled)
       meta_window_update_keyboard_move (window);
-    }
 
   return handled;
 }
+
 
 static gboolean
 process_keyboard_resize_grab_op_change (MetaDisplay *display,
@@ -3458,6 +3509,40 @@ handle_workspace_switch  (MetaDisplay   
     }
 }
 
+static void handle_pack_window (MetaDisplay    *display,
+                                MetaScreen     *screen,
+                                MetaWindow     *window,
+                                XEvent         *event,
+                                MetaKeyBinding *binding)
+{
+  gboolean handled;
+
+  meta_topic (META_DEBUG_KEYBINDINGS,
+              "Packing window\n");
+
+  /* We need to take a grab now: Moving windows in wireframe mode requires that
+   * display->grab_wireframe_rect is available.
+   */
+  meta_window_begin_grab_op (window,
+                             META_GRAB_OP_KEYBOARD_MOVING,
+                             meta_display_get_current_time(window->display));
+
+  handled = do_move_window (display,
+                            screen,
+                            window,
+                            TRUE,
+                            FALSE,
+                            GPOINTER_TO_INT(binding->handler->data));
+  
+  if (handled)
+    meta_window_update_keyboard_move (window);
+
+  meta_display_end_grab_op(display,
+                           meta_display_get_current_time(window->display));
+}
+
+
+
 #if 0
 static void
 handle_spew_mark (MetaDisplay    *display,
diff -r efbcb443208b src/metacity.schemas.in
--- a/src/metacity.schemas.in	Sun Mar  5 09:31:26 2006 +0000
+++ b/src/metacity.schemas.in	Sun Mar  5 17:06:52 2006 +0200
@@ -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 efbcb443208b src/prefs.c
--- a/src/prefs.c	Sun Mar  5 09:31:26 2006 +0000
+++ b/src/prefs.c	Sun Mar  5 17:06:52 2006 +0200
@@ -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 efbcb443208b src/prefs.h
--- a/src/prefs.h	Sun Mar  5 09:31:26 2006 +0000
+++ b/src/prefs.h	Sun Mar  5 17:06:52 2006 +0200
@@ -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
 {
