    
    This is against Metacity (CVS, 2005-05-22), and adds shortcut
    keybindings for packing windows around the screen.
    
    This is my first try at hacking Metacity, so please review this
    patch carefully.

    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 (hopefully) hooks.

    -- Pauli Virtanen <pauli.virtanen@hut.fi>
        
Index: src/common.h
===================================================================
RCS file: /cvs/gnome/metacity/src/common.h,v
retrieving revision 1.34
diff -u -p -r1.34 common.h
--- src/common.h	21 Jun 2004 16:47:27 -0000	1.34
+++ src/common.h	22 May 2005 11:23:58 -0000
@@ -127,6 +127,18 @@ typedef enum
 
 typedef enum
 {
+  META_DIRECTION_NORTH,
+  META_DIRECTION_NE,
+  META_DIRECTION_EAST,
+  META_DIRECTION_SE,
+  META_DIRECTION_SOUTH,
+  META_DIRECTION_SW,
+  META_DIRECTION_WEST,
+  META_DIRECTION_NW
+} MetaDirection;
+
+typedef enum
+{
   META_CURSOR_DEFAULT,
   META_CURSOR_NORTH_RESIZE,
   META_CURSOR_SOUTH_RESIZE,
Index: src/keybindings.c
===================================================================
RCS file: /cvs/gnome/metacity/src/keybindings.c,v
retrieving revision 1.110
diff -u -p -r1.110 keybindings.c
--- src/keybindings.c	22 Feb 2005 02:11:25 -0000	1.110
+++ src/keybindings.c	22 May 2005 11:23:59 -0000
@@ -193,6 +193,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,
@@ -415,6 +420,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_WEST) },
+  { META_KEYBINDING_PACK_WINDOW_RIGHT, handle_pack_window,
+    GINT_TO_POINTER (META_DIRECTION_EAST) },
+  { META_KEYBINDING_PACK_WINDOW_UP, handle_pack_window,
+    GINT_TO_POINTER (META_DIRECTION_NORTH) },
+  { META_KEYBINDING_PACK_WINDOW_DOWN, handle_pack_window,
+    GINT_TO_POINTER (META_DIRECTION_SOUTH) },
   { NULL, NULL, NULL }
 };
 
@@ -1679,28 +1692,20 @@ 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;
   int edge;
   
   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)
     {
       x = display->grab_wireframe_rect.x;
@@ -1715,7 +1720,6 @@ process_keyboard_move_grab (MetaDisplay 
    * since the edge-find routines use window->rect. Window
    * constraints are also broken with wireframe.
    */  
-  smart_snap = (event->xkey.state & ShiftMask) != 0;
   if (display->grab_wireframe_active)
     smart_snap = FALSE;
   
@@ -1724,38 +1728,21 @@ process_keyboard_move_grab (MetaDisplay 
 
   if (smart_snap)
     incr = 0;
-  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.
-       */
-      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)
+  switch (direction)
     {
-    case XK_KP_Home:
-    case XK_KP_Prior:
-    case XK_Up:
-    case XK_KP_Up:
+    case META_DIRECTION_NORTH:
+    case META_DIRECTION_NE:
+    case META_DIRECTION_NW:
       y -= incr;
 
       if (!display->grab_wireframe_active)
@@ -1767,10 +1754,9 @@ process_keyboard_move_grab (MetaDisplay 
           
       handled = TRUE;
       break;
-    case XK_KP_End:
-    case XK_KP_Next:
-    case XK_Down:
-    case XK_KP_Down:
+    case META_DIRECTION_SOUTH:
+    case META_DIRECTION_SE:
+    case META_DIRECTION_SW:
       y += incr;
 
       if (!display->grab_wireframe_active)
@@ -1782,14 +1768,16 @@ process_keyboard_move_grab (MetaDisplay 
           
       handled = TRUE;
       break;
+    case META_DIRECTION_EAST:
+    case META_DIRECTION_WEST:
+      break;
     }
-  
-  switch (keysym)
+
+  switch (direction)
     {
-    case XK_KP_Home:
-    case XK_KP_End:
-    case XK_Left:
-    case XK_KP_Left:
+    case META_DIRECTION_WEST:
+    case META_DIRECTION_NW:
+    case META_DIRECTION_SW:
       x -= incr;
 
       if (!display->grab_wireframe_active)
@@ -1801,10 +1789,9 @@ process_keyboard_move_grab (MetaDisplay 
 
       handled = TRUE;
       break;
-    case XK_KP_Prior:
-    case XK_KP_Next:
-    case XK_Right:
-    case XK_KP_Right:
+    case META_DIRECTION_EAST:
+    case META_DIRECTION_NE:
+    case META_DIRECTION_SE:
       x += incr;
 
       if (!display->grab_wireframe_active)
@@ -1816,6 +1803,9 @@ process_keyboard_move_grab (MetaDisplay 
       
       handled = TRUE;
       break;
+    case META_DIRECTION_NORTH:
+    case META_DIRECTION_SOUTH:
+      break;
     }
 
   if (handled)
@@ -1833,23 +1823,109 @@ process_keyboard_move_grab (MetaDisplay 
           meta_window_get_xor_rect (window, &display->grab_wireframe_rect,
                                     &new_xor);
           
-          meta_effects_update_wireframe (window->screen,
-                                         &display->grab_wireframe_last_xor_rect,
-                                         &new_xor);
+          meta_effects_update_wireframe(window->screen,
+					&display->grab_wireframe_last_xor_rect,
+					&new_xor);
           display->grab_wireframe_last_xor_rect = new_xor;
         }
       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,
+                            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_NORTH;
+      break;
+    case XK_KP_Home:
+      direction = META_DIRECTION_NW;
+      break;
+    case XK_KP_Prior:
+      direction = META_DIRECTION_NE;
+      break;
+    case XK_Left:
+    case XK_KP_Left:
+      direction = META_DIRECTION_WEST;
+      break;
+    case XK_Right:
+    case XK_KP_Right:
+      direction = META_DIRECTION_EAST;
+      break;
+    case XK_KP_End:
+      direction = META_DIRECTION_SW;
+      break;
+    case XK_KP_Next:
+      direction = META_DIRECTION_SE;
+      break;
+    case XK_Down:
+    case XK_KP_Down:
+      direction = META_DIRECTION_SOUTH;
+      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 (MetaDisplay *display,
                               MetaScreen  *screen,
                               MetaWindow  *window,
@@ -3468,6 +3544,28 @@ handle_workspace_switch  (MetaDisplay   
     }
 }
 
+static void handle_pack_window (MetaDisplay    *display,
+				MetaScreen     *screen,
+				MetaWindow     *window,
+				XEvent         *event,
+				MetaKeyBinding *binding)
+{
+  gboolean handled;
+  
+  handled = do_move_window(display,
+			   screen,
+			   window,
+			   TRUE,
+			   FALSE,
+			   GPOINTER_TO_INT(binding->handler->data));
+  if (handled) {
+    meta_window_update_keyboard_no_grab_move (window);
+  }
+  return handled;
+}
+
+
+
 #if 0
 static void
 handle_spew_mark (MetaDisplay    *display,
Index: src/metacity.schemas.in
===================================================================
RCS file: /cvs/gnome/metacity/src/metacity.schemas.in,v
retrieving revision 1.42
diff -u -p -r1.42 metacity.schemas.in
--- src/metacity.schemas.in	5 Feb 2005 03:57:41 -0000	1.42
+++ src/metacity.schemas.in	22 May 2005 11:23:59 -0000
@@ -1029,6 +1029,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 -->
 
 
Index: src/prefs.c
===================================================================
RCS file: /cvs/gnome/metacity/src/prefs.c,v
retrieving revision 1.50
diff -u -p -r1.50 prefs.c
--- src/prefs.c	26 Jan 2005 08:43:38 -0000	1.50
+++ src/prefs.c	22 May 2005 11:23:59 -0000
@@ -1585,6 +1585,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 }
 };
 
Index: src/prefs.h
===================================================================
RCS file: /cvs/gnome/metacity/src/prefs.h,v
retrieving revision 1.36
diff -u -p -r1.36 prefs.h
--- src/prefs.h	26 Jan 2005 08:43:38 -0000	1.36
+++ src/prefs.h	22 May 2005 11:23:59 -0000
@@ -189,6 +189,10 @@ void        meta_prefs_change_workspace_
 #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
 {
Index: src/window.c
===================================================================
RCS file: /cvs/gnome/metacity/src/window.c,v
retrieving revision 1.382
diff -u -p -r1.382 window.c
--- src/window.c	23 Feb 2005 17:42:51 -0000	1.382
+++ src/window.c	22 May 2005 11:24:00 -0000
@@ -7333,6 +7333,16 @@ meta_window_update_keyboard_move (MetaWi
 }
 
 void
+meta_window_update_keyboard_no_grab_move (MetaWindow *window)
+{
+  int x, y;
+  
+  warp_grab_pointer (window,
+                     META_GRAB_OP_KEYBOARD_MOVING,
+                     &x, &y);
+}
+
+void
 meta_window_update_layer (MetaWindow *window)
 {
   MetaGroup *group;
Index: src/window.h
===================================================================
RCS file: /cvs/gnome/metacity/src/window.h,v
retrieving revision 1.115
diff -u -p -r1.115 window.h
--- src/window.h	20 Feb 2005 17:14:16 -0000	1.115
+++ src/window.h	22 May 2005 11:24:00 -0000
@@ -517,6 +517,7 @@ void meta_window_begin_grab_op (MetaWind
 void meta_window_update_keyboard_resize (MetaWindow *window,
                                          gboolean    update_cursor);
 void meta_window_update_keyboard_move   (MetaWindow *window);
+void meta_window_update_keyboard_no_grab_move (MetaWindow *window);
 
 void meta_window_update_layer (MetaWindow *window);
 
