[metacity] Add side-by-side tiling



commit 0eb770fe5b96144e9c79487bf13cb8448df72f32
Author: Alberts Muktupāvels <alberts muktupavels gmail com>
Date:   Mon May 12 19:39:04 2014 +0300

    Add side-by-side tiling
    
    1. Manually applied this patch:
    https://github.com/SolusOS-discontinued/consortium/commit/b463e03f5bdeab307ceee6b969c681f29537c76d
    2. Ported tile-preview.c to gtk3.

 src/Makefile.am                       |    2 +
 src/core/boxes.c                      |  125 ++++-----------
 src/core/constraints.c                |   70 ++++++++-
 src/core/core.c                       |   30 ++++
 src/core/display-private.h            |   12 ++
 src/core/display.c                    |   27 +++-
 src/core/frame.c                      |    6 +
 src/core/keybindings.c                |   43 +++++
 src/core/prefs.c                      |   18 ++
 src/core/screen-private.h             |    9 +-
 src/core/screen.c                     |   96 +++++++++++-
 src/core/testboxes.c                  |    8 +-
 src/core/window-private.h             |   23 +++-
 src/core/window.c                     |  289 ++++++++++++++++++++++++++++-----
 src/core/workspace.c                  |    3 +-
 src/include/boxes.h                   |    1 +
 src/include/common.h                  |    4 +-
 src/include/core.h                    |    4 +
 src/include/prefs.h                   |    2 +
 src/include/tile-preview.h            |   37 +++++
 src/include/ui.h                      |    1 +
 src/metacity-schemas.convert          |    1 +
 src/org.gnome.metacity.gschema.xml.in |    9 +
 src/ui/theme-parser.c                 |   44 +++++
 src/ui/theme.c                        |   63 +++++++-
 src/ui/theme.h                        |    8 +
 src/ui/tile-preview.c                 |  245 ++++++++++++++++++++++++++++
 27 files changed, 1037 insertions(+), 143 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 29a7de2..b11bb44 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -91,6 +91,8 @@ metacity_SOURCES=                             \
        include/resizepopup.h                   \
        ui/tabpopup.c                           \
        include/tabpopup.h                              \
+       ui/tile-preview.c \
+       include/tile-preview.h \
        ui/theme-parser.c                       \
        ui/theme-parser.h                       \
        ui/theme.c                              \
diff --git a/src/core/boxes.c b/src/core/boxes.c
index 147bd80..55fd7b3 100644
--- a/src/core/boxes.c
+++ b/src/core/boxes.c
@@ -1793,6 +1793,7 @@ meta_rectangle_find_onscreen_edges (const MetaRectangle *basic_rect,
 
 GList*
 meta_rectangle_find_nonintersected_xinerama_edges (
+                                    const MetaRectangle *screen_rect,
                                     const GList         *xinerama_rects,
                                     const GSList        *all_struts)
 {
@@ -1815,99 +1816,41 @@ meta_rectangle_find_nonintersected_xinerama_edges (
   while (cur)
     {
       MetaRectangle *cur_rect = cur->data;
-      const GList *compare = xinerama_rects;
-      while (compare)
-        {
-          MetaRectangle *compare_rect = compare->data;
-
-          /* Check if cur might be horizontally adjacent to compare */
-          if (meta_rectangle_vert_overlap(cur_rect, compare_rect))
-            {
-              MetaSide side_type;
-              int y      = MAX (cur_rect->y, compare_rect->y);
-              int height = MIN (BOX_BOTTOM (*cur_rect) - y,
-                                BOX_BOTTOM (*compare_rect) - y);
-              int width  = 0;
-              int x;
-
-              if (BOX_LEFT (*cur_rect)  == BOX_RIGHT (*compare_rect))
-                {
-                  /* compare_rect is to the left of cur_rect */
-                  x = BOX_LEFT (*cur_rect);
-                  side_type = META_SIDE_LEFT;
-                }
-              else if (BOX_RIGHT (*cur_rect) == BOX_LEFT (*compare_rect))
-                {
-                  /* compare_rect is to the right of cur_rect */
-                  x = BOX_RIGHT (*cur_rect);
-                  side_type = META_SIDE_RIGHT;
-                }
-              else
-                /* These rectangles aren't adjacent after all */
-                x = INT_MIN;
+      MetaEdge *new_edge;
 
-              /* If the rectangles really are adjacent */
-              if (x != INT_MIN)
-                {
-                  /* We need a left edge for the xinerama on the right, and
-                   * a right edge for the xinerama on the left.  Just fill
-                   * up the edges and stick 'em on the list.
-                   */
-                  MetaEdge *new_edge  = g_new (MetaEdge, 1);
-
-                  new_edge->rect = meta_rect (x, y, width, height);
-                  new_edge->side_type = side_type;
-                  new_edge->edge_type = META_EDGE_XINERAMA;
-
-                  ret = g_list_prepend (ret, new_edge);
-                }
-            }
-
-          /* Check if cur might be vertically adjacent to compare */
-          if (meta_rectangle_horiz_overlap(cur_rect, compare_rect))
-            {
-              MetaSide side_type;
-              int x      = MAX (cur_rect->x, compare_rect->x);
-              int width  = MIN (BOX_RIGHT (*cur_rect) - x,
-                                BOX_RIGHT (*compare_rect) - x);
-              int height = 0;
-              int y;
-
-              if (BOX_TOP (*cur_rect)  == BOX_BOTTOM (*compare_rect))
-                {
-                  /* compare_rect is to the top of cur_rect */
-                  y = BOX_TOP (*cur_rect);
-                  side_type = META_SIDE_TOP;
-                }
-              else if (BOX_BOTTOM (*cur_rect) == BOX_TOP (*compare_rect))
-                {
-                  /* compare_rect is to the bottom of cur_rect */
-                  y = BOX_BOTTOM (*cur_rect);
-                  side_type = META_SIDE_BOTTOM;
-                }
-              else
-                /* These rectangles aren't adjacent after all */
-                y = INT_MIN;
-
-              /* If the rectangles really are adjacent */
-              if (y != INT_MIN)
-                {
-                  /* We need a top edge for the xinerama on the bottom, and
-                   * a bottom edge for the xinerama on the top.  Just fill
-                   * up the edges and stick 'em on the list.
-                   */
-                  MetaEdge *new_edge = g_new (MetaEdge, 1);
-
-                  new_edge->rect = meta_rect (x, y, width, height);
-                  new_edge->side_type = side_type;
-                  new_edge->edge_type = META_EDGE_XINERAMA;
-
-                  ret = g_list_prepend (ret, new_edge);
-                }
-            }
-
-          compare = compare->next;
+      if (BOX_LEFT(*cur_rect) != BOX_LEFT(*screen_rect))
+        {
+          new_edge  = g_new (MetaEdge, 1);
+          new_edge->rect = meta_rect (BOX_LEFT (*cur_rect), BOX_TOP (*cur_rect), 0, cur_rect->height);
+          new_edge->side_type = META_SIDE_LEFT;
+          new_edge->edge_type = META_EDGE_XINERAMA;
+          ret = g_list_prepend (ret, new_edge);
         }
+      if (BOX_RIGHT(*cur_rect) != BOX_RIGHT(*screen_rect))
+        {
+          new_edge  = g_new (MetaEdge, 1);
+          new_edge->rect = meta_rect (BOX_RIGHT (*cur_rect), BOX_TOP (*cur_rect), 0, cur_rect->height);
+          new_edge->side_type = META_SIDE_RIGHT;
+          new_edge->edge_type = META_EDGE_XINERAMA;
+          ret = g_list_prepend (ret, new_edge);
+        }
+      if (BOX_TOP(*cur_rect) != BOX_TOP(*screen_rect))
+        {
+          new_edge  = g_new (MetaEdge, 1);
+          new_edge->rect = meta_rect (BOX_LEFT (*cur_rect), BOX_TOP (*cur_rect), cur_rect->width, 0);
+          new_edge->side_type = META_SIDE_TOP;
+          new_edge->edge_type = META_EDGE_XINERAMA;
+          ret = g_list_prepend (ret, new_edge);
+        }
+      if (BOX_BOTTOM(*cur_rect) != BOX_BOTTOM(*screen_rect))
+        {
+          new_edge  = g_new (MetaEdge, 1);
+          new_edge->rect = meta_rect (BOX_LEFT (*cur_rect), BOX_BOTTOM (*cur_rect), cur_rect->width, 0);
+          new_edge->side_type = META_SIDE_BOTTOM;
+          new_edge->edge_type = META_EDGE_XINERAMA;
+          ret = g_list_prepend (ret, new_edge);
+        }
+
       cur = cur->next;
     }
 
diff --git a/src/core/constraints.c b/src/core/constraints.c
index 7c2960c..c783847 100644
--- a/src/core/constraints.c
+++ b/src/core/constraints.c
@@ -96,6 +96,7 @@ typedef enum
   PRIORITY_ENTIRELY_VISIBLE_ON_WORKAREA = 1,
   PRIORITY_SIZE_HINTS_INCREMENTS = 1,
   PRIORITY_MAXIMIZATION = 2,
+  PRIORITY_TILING = 2,
   PRIORITY_FULLSCREEN = 2,
   PRIORITY_SIZE_HINTS_LIMITS = 3,
   PRIORITY_TITLEBAR_VISIBLE = 4,
@@ -143,6 +144,10 @@ static gboolean constrain_maximization       (MetaWindow         *window,
                                               ConstraintInfo     *info,
                                               ConstraintPriority  priority,
                                               gboolean            check_only);
+static gboolean constrain_tiling             (MetaWindow         *window,
+                                              ConstraintInfo     *info,
+                                              ConstraintPriority  priority,
+                                              gboolean            check_only);
 static gboolean constrain_fullscreen         (MetaWindow         *window,
                                               ConstraintInfo     *info,
                                               ConstraintPriority  priority,
@@ -209,6 +214,7 @@ typedef struct {
 
 static const Constraint all_constraints[] = {
   {constrain_maximization,       "constrain_maximization"},
+  {constrain_tiling,             "constrain_tiling"},
   {constrain_fullscreen,         "constrain_fullscreen"},
   {constrain_size_increments,    "constrain_size_increments"},
   {constrain_size_limits,        "constrain_size_limits"},
@@ -742,12 +748,15 @@ constrain_maximization (MetaWindow         *window,
     return TRUE;
 
   /* Determine whether constraint applies; exit if it doesn't */
-  if (!window->maximized_horizontally && !window->maximized_vertically)
+  if ((!window->maximized_horizontally && !window->maximized_vertically) ||
+      META_WINDOW_TILED_SIDE_BY_SIDE (window))
     return TRUE;
 
   /* Calculate target_size = maximized size of (window + frame) */
-  if (window->maximized_horizontally && window->maximized_vertically)
-    target_size = info->work_area_xinerama;
+  if (META_WINDOW_MAXIMIZED (window))
+    {
+      target_size = info->work_area_xinerama;
+    }
   else
     {
       /* Amount of maximization possible in a single direction depends
@@ -810,6 +819,59 @@ constrain_maximization (MetaWindow         *window,
   return TRUE;
 }
 
+
+static gboolean
+constrain_tiling (MetaWindow *window,
+                  ConstraintInfo *info,
+                  ConstraintPriority priority,
+                  gboolean check_only)
+{
+  MetaRectangle target_size;
+  MetaRectangle min_size, max_size;
+  gboolean hminbad, vminbad;
+  gboolean horiz_equal, vert_equal;
+  gboolean constraint_already_satisfied;
+
+  if (priority > PRIORITY_TILING)
+    return TRUE;
+
+  /* Determine whether constraint applies; exit if it doesn't */
+  if (!META_WINDOW_TILED_SIDE_BY_SIDE (window))
+    return TRUE;
+
+  /* Calculate target_size - as the tile previews need this as well, we
+   * use an external function for the actual calculation
+   */
+  meta_window_get_current_tile_area (window, &target_size);
+  unextend_by_frame (&target_size, info->fgeom);
+
+  /* Check min size constraints; max size constraints are ignored as for
+   * maximized windows.
+   */
+  get_size_limits (window, info->fgeom, FALSE, &min_size, &max_size);
+  hminbad = target_size.width < min_size.width;
+  vminbad = target_size.height < min_size.height;
+  if (hminbad || vminbad)
+    return TRUE;
+
+  /* Determine whether constraint is already satisfied; exit if it is */
+  horiz_equal = target_size.x == info->current.x &&
+                target_size.width == info->current.width;
+  vert_equal = target_size.y == info->current.y &&
+                target_size.height == info->current.height;
+  constraint_already_satisfied = horiz_equal && vert_equal;
+  if (check_only || constraint_already_satisfied)
+    return constraint_already_satisfied;
+
+  /*** Enforce constraint ***/
+  info->current.x = target_size.x;
+  info->current.width = target_size.width;
+  info->current.y = target_size.y;
+  info->current.height = target_size.height;
+
+  return TRUE;
+}
+
 static gboolean
 constrain_fullscreen (MetaWindow         *window,
                       ConstraintInfo     *info,
@@ -861,6 +923,7 @@ constrain_size_increments (MetaWindow         *window,
 
   /* Determine whether constraint applies; exit if it doesn't */
   if (META_WINDOW_MAXIMIZED (window) || window->fullscreen || 
+      META_WINDOW_TILED_SIDE_BY_SIDE (window) ||
       info->action_type == ACTION_MOVE)
     return TRUE;
 
@@ -992,6 +1055,7 @@ constrain_aspect_ratio (MetaWindow         *window,
   constraints_are_inconsistent = minr > maxr;
   if (constraints_are_inconsistent ||
       META_WINDOW_MAXIMIZED (window) || window->fullscreen || 
+      META_WINDOW_TILED_SIDE_BY_SIDE (window) ||
       info->action_type == ACTION_MOVE)
     return TRUE;
 
diff --git a/src/core/core.c b/src/core/core.c
index b7806d4..509b352 100644
--- a/src/core/core.c
+++ b/src/core/core.c
@@ -26,6 +26,7 @@
 #include "frame-private.h"
 #include "workspace.h"
 #include "prefs.h"
+#include "errors.h"
 
 /* Looks up the MetaWindow representing the frame of the given X window.
  * Used as a helper function by a bunch of the functions below.
@@ -295,6 +296,35 @@ meta_core_user_lower_and_unfocus (Display *xdisplay,
 }
 
 void
+meta_core_lower_beneath_focus_window (Display *xdisplay,
+                                      Window xwindow,
+                                      guint32 timestamp)
+{
+  XWindowChanges changes;
+  MetaDisplay *display;
+  MetaScreen *screen;
+  MetaWindow *focus_window;
+
+  display = meta_display_for_x_display (xdisplay);
+  screen = meta_display_screen_for_xwindow (display, xwindow);
+  focus_window = meta_stack_get_top (screen->stack);
+
+  if (focus_window == NULL)
+    return;
+
+  changes.stack_mode = Below;
+  changes.sibling = focus_window->frame ? focus_window->frame->xwindow
+                                        : focus_window->xwindow;
+
+  meta_error_trap_push (display);
+  XConfigureWindow (xdisplay,
+                    xwindow,
+                    CWSibling | CWStackMode,
+                    &changes);
+  meta_error_trap_pop (display, FALSE);
+}
+
+void
 meta_core_user_focus (Display *xdisplay,
                       Window   frame_xwindow,
                       guint32  timestamp)
diff --git a/src/core/display-private.h b/src/core/display-private.h
index 6bf2c4d..31cc928 100644
--- a/src/core/display-private.h
+++ b/src/core/display-private.h
@@ -70,6 +70,13 @@ typedef void (* MetaWindowPingFunc) (MetaDisplay *display,
  */
 #define N_IGNORED_SERIALS           4
 
+typedef enum {
+  META_TILE_NONE,
+  META_TILE_LEFT,
+  META_TILE_RIGHT,
+  META_TILE_MAXIMIZED /* only used for previews */
+} MetaTileMode;
+
 struct _MetaDisplay
 {
   char *name;
@@ -160,6 +167,8 @@ struct _MetaDisplay
   int         grab_anchor_root_x;
   int         grab_anchor_root_y;
   MetaRectangle grab_anchor_window_pos;
+  MetaTileMode  grab_tile_mode;
+  int           grab_tile_monitor_number;
   int         grab_latest_motion_x;
   int         grab_latest_motion_y;
   gulong      grab_mask;
@@ -170,6 +179,9 @@ struct _MetaDisplay
   guint       grab_frame_action : 1;
   MetaRectangle grab_wireframe_rect;
   MetaRectangle grab_wireframe_last_xor_rect;
+  /* During a resize operation, the directions in which we've broken
+   * out of the initial maximization state */
+  guint       grab_resize_unmaximize : 2; /* MetaMaximizeFlags */
   MetaRectangle grab_initial_window_pos;
   int         grab_initial_x, grab_initial_y;  /* These are only relevant for */
   gboolean    grab_threshold_movement_reached; /* raise_on_click == FALSE.    */
diff --git a/src/core/display.c b/src/core/display.c
index 7619e79..f407583 100644
--- a/src/core/display.c
+++ b/src/core/display.c
@@ -457,6 +457,8 @@ meta_display_open (void)
   the_display->grab_window = NULL;
   the_display->grab_screen = NULL;
   the_display->grab_resize_popup = NULL;
+  the_display->grab_tile_mode = META_TILE_NONE;
+  the_display->grab_tile_monitor_number = -1;
 
   the_display->grab_edge_resistance_data = NULL;
 
@@ -3308,6 +3310,16 @@ meta_display_begin_grab_op (MetaDisplay *display,
   display->grab_xwindow = grab_xwindow;
   display->grab_button = button;
   display->grab_mask = modmask;
+  if (window)
+    {
+      display->grab_tile_mode = window->tile_mode;
+      display->grab_tile_monitor_number = window->tile_monitor_number;
+    }
+  else
+    {
+      display->grab_tile_mode = META_TILE_NONE;
+      display->grab_tile_monitor_number = -1;
+    }
   display->grab_anchor_root_x = root_x;
   display->grab_anchor_root_y = root_y;
   display->grab_latest_motion_x = root_x;
@@ -3493,7 +3505,12 @@ meta_display_end_grab_op (MetaDisplay *display,
 
   if (display->grab_window != NULL)
     display->grab_window->shaken_loose = FALSE;
-  
+
+  /*if(display->grab_window != NULL && display->grab_window->tile_mode == META_TILE_MAXIMIZED)
+    {
+      display->grab_window->tile_mode = META_TILE_NONE;
+    }*/
+
   if (display->grab_window != NULL &&
       !meta_prefs_get_raise_on_click () &&
       (meta_grab_op_is_moving (display->grab_op) ||
@@ -3595,10 +3612,16 @@ meta_display_end_grab_op (MetaDisplay *display,
       display->grab_sync_request_alarm = None;
     }
 #endif /* HAVE_XSYNC */
-  
+
+  /* Hide the tile preview if it exists */
+  if (display->grab_screen->tile_preview)
+    meta_tile_preview_hide (display->grab_screen->tile_preview);
+
   display->grab_window = NULL;
   display->grab_screen = NULL;
   display->grab_xwindow = None;
+  display->grab_tile_mode = META_TILE_NONE;
+  display->grab_tile_monitor_number = -1;
   display->grab_op = META_GRAB_OP_NONE;
 
   if (display->grab_resize_popup)
diff --git a/src/core/frame.c b/src/core/frame.c
index 0459bb6..b574948 100644
--- a/src/core/frame.c
+++ b/src/core/frame.c
@@ -280,6 +280,12 @@ meta_frame_get_flags (MetaFrame *frame)
   if (META_WINDOW_MAXIMIZED (frame->window))
     flags |= META_FRAME_MAXIMIZED;
 
+  if (META_WINDOW_TILED_LEFT (frame->window))
+    flags |= META_FRAME_TILED_LEFT;
+
+  if (META_WINDOW_TILED_RIGHT (frame->window))
+    flags |= META_FRAME_TILED_RIGHT;
+
   if (frame->window->fullscreen)
     flags |= META_FRAME_FULLSCREEN;
 
diff --git a/src/core/keybindings.c b/src/core/keybindings.c
index a2a4659..e0ff5f6 100644
--- a/src/core/keybindings.c
+++ b/src/core/keybindings.c
@@ -1444,6 +1444,10 @@ process_mouse_move_resize_grab (MetaDisplay *display,
 
   if (keysym == XK_Escape)
     {
+      /* Restore the original tile mode */
+      window->tile_mode = display->grab_tile_mode;
+      window->tile_monitor_number = display->grab_tile_monitor_number;
+
       /* End move or resize and restore to original state.  If the
        * window was a maximized window that had been "shaken loose" we
        * need to remaximize it.  In normal cases, we need to do a
@@ -1455,6 +1459,8 @@ process_mouse_move_resize_grab (MetaDisplay *display,
         meta_window_maximize (window,
                               META_MAXIMIZE_HORIZONTAL |
                               META_MAXIMIZE_VERTICAL);
+      else if (window->tile_mode == META_TILE_LEFT || window->tile_mode == META_TILE_RIGHT)
+        meta_window_tile (window);
       else if (!display->grab_wireframe_active)
         meta_window_move_resize (display->grab_window,
                                  TRUE,
@@ -2918,6 +2924,43 @@ handle_toggle_above       (MetaDisplay    *display,
     meta_window_make_above (window);
 }
 
+/* TODO: actually use this keybinding, without messing up the existing keybinding schema */
+static void
+handle_toggle_tiled (MetaDisplay *display,
+                     MetaScreen *screen,
+                     MetaWindow *window,
+                     XEvent *event,
+                     MetaKeyBinding *binding)
+{
+  MetaTileMode mode = binding->handler->data;
+
+  if ((META_WINDOW_TILED_LEFT (window) && mode == META_TILE_LEFT) ||
+      (META_WINDOW_TILED_RIGHT (window) && mode == META_TILE_RIGHT))
+    {
+      window->tile_mode = META_TILE_NONE;
+
+      if (window->saved_maximize)
+        meta_window_maximize (window, META_MAXIMIZE_VERTICAL |
+                                      META_MAXIMIZE_HORIZONTAL);
+      else
+        meta_window_unmaximize (window, META_MAXIMIZE_VERTICAL |
+                                        META_MAXIMIZE_HORIZONTAL);
+    }
+  else if (meta_window_can_tile_side_by_side (window))
+    {
+      window->tile_mode = mode;
+      window->tile_monitor_number = meta_screen_get_xinerama_for_window (window->screen, window)->number;
+      /* Maximization constraints beat tiling constraints, so if the window
+       * is maximized, tiling won't have any effect unless we unmaximize it
+       * horizontally first; rather than calling meta_window_unmaximize(),
+       * we just set the flag and rely on meta_window_tile() syncing it to
+       * save an additional roundtrip.
+       */
+      window->maximized_horizontally = FALSE;
+      meta_window_tile (window);
+    }
+}
+
 static void
 handle_toggle_maximized    (MetaDisplay    *display,
                            MetaScreen     *screen,
diff --git a/src/core/prefs.c b/src/core/prefs.c
index 3e93140..a76fbc4 100644
--- a/src/core/prefs.c
+++ b/src/core/prefs.c
@@ -88,6 +88,7 @@ static char *cursor_theme = NULL;
 static int   cursor_size = 24;
 static gboolean compositing_manager = FALSE;
 static gboolean resize_with_right_button = FALSE;
+static gboolean edge_tiling = FALSE;
 static gboolean force_fullscreen = TRUE;
 
 static GDesktopVisualBellType visual_bell_type = G_DESKTOP_VISUAL_BELL_FULLSCREEN_FLASH;
@@ -353,6 +354,14 @@ static MetaBoolPreference preferences_bool[] =
       &resize_with_right_button,
       FALSE,
     },
+    {
+      { "edge-tiling",
+        SCHEMA_METACITY,
+        META_PREF_EDGE_TILING,
+      },
+      &edge_tiling,
+      FALSE,
+    },
     { { NULL, 0, 0 }, NULL, FALSE },
   };
 
@@ -1416,6 +1425,9 @@ meta_preference_to_string (MetaPreference pref)
     case META_PREF_RESIZE_WITH_RIGHT_BUTTON:
       return "RESIZE_WITH_RIGHT_BUTTON";
 
+    case META_PREF_EDGE_TILING:
+      return "EDGE_TILING";
+
     case META_PREF_FORCE_FULLSCREEN:
       return "FORCE_FULLSCREEN";
 
@@ -1746,6 +1758,12 @@ meta_prefs_get_gnome_animations ()
   return gnome_animations;
 }
 
+gboolean
+meta_prefs_get_edge_tiling ()
+{
+  return edge_tiling;
+}
+
 MetaKeyBindingAction
 meta_prefs_get_keybinding_action (const char *name)
 {
diff --git a/src/core/screen-private.h b/src/core/screen-private.h
index 326cf61..f85e0a8 100644
--- a/src/core/screen-private.h
+++ b/src/core/screen-private.h
@@ -77,7 +77,10 @@ struct _MetaScreen
   MetaRectangle rect;  /* Size of screen; rect.x & rect.y are always 0 */
   MetaUI *ui;
   MetaTabPopup *tab_popup;
-  
+  MetaTilePreview *tile_preview;
+
+  guint tile_preview_timeout_id;
+
   MetaWorkspace *active_workspace;
 
   /* This window holds the focus when we don't want to focus
@@ -159,6 +162,10 @@ void          meta_screen_ensure_tab_popup    (MetaScreen                 *scree
                                                MetaTabShowType             show_type);
 void          meta_screen_ensure_workspace_popup (MetaScreen *screen);
 
+void          meta_screen_tile_preview_update          (MetaScreen    *screen,
+                                                        gboolean       delay);
+void          meta_screen_tile_preview_hide            (MetaScreen    *screen);
+
 MetaWindow*   meta_screen_get_mouse_window     (MetaScreen                 *screen,
                                                 MetaWindow                 *not_this_one);
 
diff --git a/src/core/screen.c b/src/core/screen.c
index a471e1a..c1e886b 100644
--- a/src/core/screen.c
+++ b/src/core/screen.c
@@ -587,7 +587,10 @@ meta_screen_new (MetaDisplay *display,
                             screen->xscreen);
 
   screen->tab_popup = NULL;
-  
+  screen->tile_preview = NULL;
+
+  screen->tile_preview_timeout_id = 0;
+
   screen->stack = meta_stack_new (screen);
 
   meta_prefs_add_listener (prefs_changed_callback, screen);
@@ -696,7 +699,13 @@ meta_screen_free (MetaScreen *screen,
   
   if (screen->xinerama_infos)
     g_free (screen->xinerama_infos);
-  
+
+  if (screen->tile_preview_timeout_id)
+    g_source_remove (screen->tile_preview_timeout_id);
+
+  if (screen->tile_preview)
+    meta_tile_preview_free (screen->tile_preview);
+
   g_free (screen->screen_name);
   g_free (screen);
 
@@ -1389,6 +1398,89 @@ meta_screen_ensure_workspace_popup (MetaScreen *screen)
   /* don't show tab popup, since proper space isn't selected yet */
 }
 
+static gboolean
+meta_screen_tile_preview_update_timeout (gpointer data)
+{
+  MetaScreen *screen = data;
+  MetaWindow *window = screen->display->grab_window;
+  gboolean composited = screen->display->compositor != NULL;
+  gboolean needs_preview = FALSE;
+
+  screen->tile_preview_timeout_id = 0;
+
+  if (!screen->tile_preview)
+    screen->tile_preview = meta_tile_preview_new (screen->number,
+                                                  composited);
+
+  if (window)
+    {
+      switch (window->tile_mode)
+        {
+          case META_TILE_LEFT:
+          case META_TILE_RIGHT:
+              if (!META_WINDOW_TILED_SIDE_BY_SIDE (window))
+                needs_preview = TRUE;
+              break;
+
+          case META_TILE_MAXIMIZED:
+              if (!META_WINDOW_MAXIMIZED (window))
+                needs_preview = TRUE;
+              break;
+
+          default:
+              needs_preview = FALSE;
+              break;
+        }
+    }
+
+  if (needs_preview)
+    {
+      MetaRectangle tile_rect;
+
+      meta_window_get_current_tile_area (window, &tile_rect);
+      meta_tile_preview_show (screen->tile_preview, &tile_rect);
+    }
+  else
+    meta_tile_preview_hide (screen->tile_preview);
+
+  return FALSE;
+}
+
+#define TILE_PREVIEW_TIMEOUT_MS 200
+
+void
+meta_screen_tile_preview_update (MetaScreen *screen,
+                                 gboolean delay)
+{
+  if (delay)
+    {
+      if (screen->tile_preview_timeout_id > 0)
+        return;
+
+      screen->tile_preview_timeout_id =
+        g_timeout_add (TILE_PREVIEW_TIMEOUT_MS,
+                       meta_screen_tile_preview_update_timeout,
+                       screen);
+    }
+  else
+    {
+      if (screen->tile_preview_timeout_id > 0)
+        g_source_remove (screen->tile_preview_timeout_id);
+
+      meta_screen_tile_preview_update_timeout ((gpointer)screen);
+    }
+}
+
+void
+meta_screen_tile_preview_hide (MetaScreen *screen)
+{
+  if (screen->tile_preview_timeout_id > 0)
+    g_source_remove (screen->tile_preview_timeout_id);
+
+  if (screen->tile_preview)
+    meta_tile_preview_hide (screen->tile_preview);
+}
+
 MetaWindow*
 meta_screen_get_mouse_window (MetaScreen  *screen,
                               MetaWindow  *not_this_one)
diff --git a/src/core/testboxes.c b/src/core/testboxes.c
index 953a66a..670731e 100644
--- a/src/core/testboxes.c
+++ b/src/core/testboxes.c
@@ -345,8 +345,14 @@ get_xinerama_edges (int which_xinerama_set, int which_strut_set)
 
   ret = NULL;
 
+  MetaRectangle screenrect;
+  screenrect.x = 0;
+  screenrect.y = 0;
+  screenrect.width = 1600;
+  screenrect.height = 1200;
+
   struts = get_strut_list (which_strut_set);
-  ret = meta_rectangle_find_nonintersected_xinerama_edges (xins, struts);
+  ret = meta_rectangle_find_nonintersected_xinerama_edges (&screenrect, xins, struts);
 
   free_strut_list (struts);
   meta_rectangle_free_list_and_elements (xins);
diff --git a/src/core/window-private.h b/src/core/window-private.h
index 85e3c5b..30f410d 100644
--- a/src/core/window-private.h
+++ b/src/core/window-private.h
@@ -136,6 +136,15 @@ struct _MetaWindow
   guint maximize_vertically_after_placement : 1;
   guint minimize_after_placement : 1;
 
+  /* The current or requested tile mode. If maximized_vertically is true,
+   * this is the current mode. If not, it is the mode which will be
+   * requested after the window grab is released */
+  guint tile_mode : 2;
+  /* The last "full" maximized/unmaximized state. We need to keep track of
+   * that to toggle between normal/tiled or maximized/tiled states. */
+  guint saved_maximize : 1;
+  int tile_monitor_number;
+
   /* Whether we're shaded */
   guint shaded : 1;
 
@@ -381,8 +390,15 @@ struct _MetaWindow
                                         (w)->maximized_vertically)
 #define META_WINDOW_MAXIMIZED_VERTICALLY(w)    ((w)->maximized_vertically)
 #define META_WINDOW_MAXIMIZED_HORIZONTALLY(w)  ((w)->maximized_horizontally)
+#define META_WINDOW_TILED_SIDE_BY_SIDE(w) ((w)->maximized_vertically && \
+                                           !(w)->maximized_horizontally && \
+                                           (w)->tile_mode != META_TILE_NONE)
+#define META_WINDOW_TILED_LEFT(w) (META_WINDOW_TILED_SIDE_BY_SIDE(w) && \
+                                   (w)->tile_mode == META_TILE_LEFT)
+#define META_WINDOW_TILED_RIGHT(w) (META_WINDOW_TILED_SIDE_BY_SIDE(w) && \
+                                    (w)->tile_mode == META_TILE_RIGHT)
 #define META_WINDOW_ALLOWS_MOVE(w)     ((w)->has_move_func && !(w)->fullscreen)
-#define META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS(w)   ((w)->has_resize_func && !META_WINDOW_MAXIMIZED (w) && 
!(w)->fullscreen && !(w)->shaded)
+#define META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS(w)   ((w)->has_resize_func && !META_WINDOW_MAXIMIZED (w) && 
!META_WINDOW_TILED_SIDE_BY_SIDE(w) && !(w)->fullscreen && !(w)->shaded)
 #define META_WINDOW_ALLOWS_RESIZE(w)   (META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS (w) &&                \
                                         (((w)->size_hints.min_width < (w)->size_hints.max_width) ||  \
                                          ((w)->size_hints.min_height < (w)->size_hints.max_height)))
@@ -401,6 +417,7 @@ void        meta_window_free               (MetaWindow  *window,
 void        meta_window_calc_showing       (MetaWindow  *window);
 void        meta_window_queue              (MetaWindow  *window,
                                             guint queuebits);
+void        meta_window_tile               (MetaWindow  *window);
 void        meta_window_minimize           (MetaWindow  *window);
 void        meta_window_unminimize         (MetaWindow  *window);
 void        meta_window_maximize           (MetaWindow        *window,
@@ -573,6 +590,8 @@ void meta_window_get_work_area_for_xinerama     (MetaWindow    *window,
 void meta_window_get_work_area_all_xineramas    (MetaWindow    *window,
                                                  MetaRectangle *area);
 
+void meta_window_get_current_tile_area         (MetaWindow    *window,
+                                                MetaRectangle *tile_area);
 
 gboolean meta_window_same_application (MetaWindow *window,
                                        MetaWindow *other_window);
@@ -637,4 +656,6 @@ void meta_window_update_icon_now (MetaWindow *window);
 void meta_window_update_role (MetaWindow *window);
 void meta_window_update_net_wm_type (MetaWindow *window);
 
+gboolean meta_window_can_tile_side_by_side (MetaWindow *window);
+
 #endif
diff --git a/src/core/window.c b/src/core/window.c
index f24d8af..4223150 100644
--- a/src/core/window.c
+++ b/src/core/window.c
@@ -468,6 +468,8 @@ meta_window_new_with_attrs (MetaDisplay       *display,
   window->require_on_single_xinerama = TRUE;
   window->require_titlebar_visible = TRUE;
   window->on_all_workspaces = FALSE;
+  window->tile_mode = META_TILE_NONE;
+  window->tile_monitor_number = -1;
   window->shaded = FALSE;
   window->initially_iconic = FALSE;
   window->minimized = FALSE;
@@ -2493,7 +2495,7 @@ ensure_size_hints_satisfied (MetaRectangle    *rect,
 static void
 meta_window_save_rect (MetaWindow *window)
 {
-  if (!(META_WINDOW_MAXIMIZED (window) || window->fullscreen))
+  if (!(META_WINDOW_MAXIMIZED (window) || META_WINDOW_TILED_SIDE_BY_SIDE (window) || window->fullscreen))
     {
       /* save size/pos as appropriate args for move_resize */
       if (!window->maximized_horizontally)
@@ -2535,7 +2537,7 @@ force_save_user_window_placement (MetaWindow *window)
 static void
 save_user_window_placement (MetaWindow *window)
 {
-  if (!(META_WINDOW_MAXIMIZED (window) || window->fullscreen))
+  if (!(META_WINDOW_MAXIMIZED (window) || META_WINDOW_TILED_SIDE_BY_SIDE (window) || window->fullscreen))
     {
       MetaRectangle user_rect;
 
@@ -2576,7 +2578,10 @@ meta_window_maximize_internal (MetaWindow        *window,
     window->saved_rect = *saved_rect;
   else
     meta_window_save_rect (window);
-  
+
+  if (maximize_horizontally && maximize_vertically)
+    window->saved_maximize = TRUE;
+
   window->maximized_horizontally = 
     window->maximized_horizontally || maximize_horizontally;
   window->maximized_vertically = 
@@ -2598,6 +2603,8 @@ void
 meta_window_maximize (MetaWindow        *window,
                       MetaMaximizeFlags  directions)
 {
+  MetaRectangle *saved_rect = NULL;
+
   /* At least one of the two directions ought to be set */
   gboolean maximize_horizontally, maximize_vertically;
   maximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL;
@@ -2623,19 +2630,21 @@ meta_window_maximize (MetaWindow        *window,
       /* if the window hasn't been placed yet, we'll maximize it then
        */
       if (!window->placed)
-       {
-         window->maximize_horizontally_after_placement = 
-            window->maximize_horizontally_after_placement || 
-            maximize_horizontally;
-         window->maximize_vertically_after_placement = 
-            window->maximize_vertically_after_placement || 
-            maximize_vertically;
-         return;
-       }
+        {
+          window->maximize_horizontally_after_placement = window->maximize_horizontally_after_placement || 
maximize_horizontally;
+          window->maximize_vertically_after_placement = window->maximize_vertically_after_placement || 
maximize_vertically;
+          return;
+        }
+
+      if (window->tile_mode != META_TILE_NONE)
+        {
+          saved_rect = &window->saved_rect;
+          window->maximized_vertically = FALSE;
+        }
 
       meta_window_maximize_internal (window, 
                                      directions,
-                                     NULL);
+                                     saved_rect);
 
       /* move_resize with new maximization constraints
        */
@@ -2676,15 +2685,80 @@ unmaximize_window_before_freeing (MetaWindow        *window)
 }
 
 void
+meta_window_tile (MetaWindow *window)
+{
+  /* Don't do anything if no tiling is requested */
+  if (window->tile_mode == META_TILE_NONE)
+    return;
+
+  meta_window_maximize_internal (window, META_MAXIMIZE_VERTICAL, NULL);
+
+  /* move_resize with new tiling constraints */
+  meta_window_queue (window, META_QUEUE_MOVE_RESIZE);
+}
+
+static gboolean
+meta_window_can_tile_maximized (MetaWindow *window)
+{
+  return window->has_maximize_func;
+}
+
+gboolean
+meta_window_can_tile_side_by_side (MetaWindow *window)
+{
+  const MetaXineramaScreenInfo *monitor;
+  MetaRectangle tile_area;
+
+  /*if (!META_WINDOW_ALLOWS_RESIZE (window))*/
+  if (!meta_window_can_tile_maximized (window))
+    return FALSE;
+
+  monitor = meta_screen_get_current_xinerama (window->screen);
+  meta_window_get_work_area_for_xinerama (window, monitor->number, &tile_area);
+
+  /* Do not allow tiling in portrait orientation */
+  if (tile_area.height > tile_area.width)
+    return FALSE;
+
+  tile_area.width /= 2;
+
+  if (window->frame)
+    {
+      MetaFrameGeometry fgeom;
+
+      meta_frame_calc_geometry (window->frame, &fgeom);
+
+      tile_area.width -= (fgeom.left_width + fgeom.right_width);
+      tile_area.height -= (fgeom.top_height + fgeom.bottom_height);
+    }
+
+  return tile_area.width >= window->size_hints.min_width &&
+         tile_area.height >= window->size_hints.min_height;
+}
+
+void
 meta_window_unmaximize (MetaWindow        *window,
                         MetaMaximizeFlags  directions)
 {
   /* At least one of the two directions ought to be set */
   gboolean unmaximize_horizontally, unmaximize_vertically;
+
+  /* Restore tiling if necessary */
+  if (window->tile_mode == META_TILE_LEFT ||
+      window->tile_mode == META_TILE_RIGHT)
+    {
+      window->maximized_horizontally = FALSE;
+      meta_window_tile (window);
+      return;
+    }
+
   unmaximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL;
   unmaximize_vertically   = directions & META_MAXIMIZE_VERTICAL;
   g_assert (unmaximize_horizontally || unmaximize_vertically);
 
+  if (unmaximize_horizontally && unmaximize_vertically)
+    window->saved_maximize = FALSE;
+
   /* Only do something if the window isn't already maximized in the
    * given direction(s).
    */
@@ -2725,17 +2799,6 @@ meta_window_unmaximize (MetaWindow        *window,
        */
       ensure_size_hints_satisfied (&target_rect, &window->size_hints);
 
-      /* When we unmaximize, if we're doing a mouse move also we could
-       * get the window suddenly jumping to the upper left corner of
-       * the workspace, since that's where it was when the grab op
-       * started.  So we need to update the grab state.
-       */
-      if (meta_grab_op_is_moving (window->display->grab_op) &&
-          window->display->grab_window == window)
-        {
-          window->display->grab_anchor_window_pos = target_rect;
-        }
-
       meta_window_move_resize (window,
                                FALSE,
                                target_rect.x,
@@ -2747,6 +2810,19 @@ meta_window_unmaximize (MetaWindow        *window,
        */
       force_save_user_window_placement (window);
 
+      /* When we unmaximize, if we're doing a mouse move also we could
+       * get the window suddenly jumping to the upper left corner of
+       * the workspace, since that's where it was when the grab op
+       * started. So we need to update the grab state. We have to do
+       * it after the actual operation, as the window may have been moved
+       * by constraints.
+       */
+      if (meta_grab_op_is_moving (window->display->grab_op) &&
+          window->display->grab_window == window)
+        {
+          window->display->grab_anchor_window_pos = window->user_rect;
+        }
+
       if (window->display->grab_wireframe_active)
         {
           window->display->grab_wireframe_rect = target_rect;
@@ -6918,21 +6994,84 @@ update_move (MetaWindow  *window,
   if (dx == 0 && dy == 0)
     return;
 
-  /* shake loose (unmaximize) maximized window if dragged beyond the threshold
-   * in the Y direction. You can't pull a window loose via X motion.
+  /* Originally for detaching maximized windows, but we use this
+   * for the zones at the sides of the monitor where trigger tiling
+   * because it's about the right size
    */
 
 #define DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR 6
   shake_threshold = meta_ui_get_drag_threshold (window->screen->ui) *
     DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR;
     
-  if (META_WINDOW_MAXIMIZED (window) && ABS (dy) >= shake_threshold)
+  if (snap)
+    {
+      /* We don't want to tile while snapping. Also, clear any previous tile
+         request. */
+      window->tile_mode = META_TILE_NONE;
+      window->tile_monitor_number = -1;
+    }
+  else if (meta_prefs_get_edge_tiling () &&
+           !META_WINDOW_MAXIMIZED (window) &&
+           !META_WINDOW_TILED_SIDE_BY_SIDE (window))
+    {
+      const MetaXineramaScreenInfo *monitor;
+      MetaRectangle work_area;
+
+      /* For side-by-side tiling we are interested in the inside vertical
+       * edges of the work area of the monitor where the pointer is located,
+       * and in the outside top edge for maximized tiling.
+       *
+       * For maximized tiling we use the outside edge instead of the
+       * inside edge, because we don't want to force users to maximize
+       * windows they are placing near the top of their screens.
+       *
+       * The "current" idea of meta_window_get_work_area_current_monitor() and
+       * meta_screen_get_current_monitor() is slightly different: the former
+       * refers to the monitor which contains the largest part of the window,
+       * the latter to the one where the pointer is located.
+       */
+      monitor = meta_screen_get_current_xinerama (window->screen);
+      meta_window_get_work_area_for_xinerama (window,
+                                              monitor->number,
+                                              &work_area);
+
+      /* Check if the cursor is in a position which triggers tiling
+       * and set tile_mode accordingly.
+       */
+      if (meta_window_can_tile_side_by_side (window) &&
+          x >= monitor->rect.x && x < (work_area.x + shake_threshold))
+        window->tile_mode = META_TILE_LEFT;
+      else if (meta_window_can_tile_side_by_side (window) &&
+               x >= work_area.x + work_area.width - shake_threshold &&
+               x < (monitor->rect.x + monitor->rect.width))
+        window->tile_mode = META_TILE_RIGHT;
+      else if (meta_window_can_tile_maximized (window) &&
+               y >= monitor->rect.y && y <= work_area.y)
+        window->tile_mode = META_TILE_MAXIMIZED;
+      else
+        window->tile_mode = META_TILE_NONE;
+
+      if (window->tile_mode != META_TILE_NONE)
+        window->tile_monitor_number = monitor->number;
+    }
+
+  /* shake loose (unmaximize) maximized or tiled window if dragged beyond
+   * the threshold in the Y direction. Tiled windows can also be pulled
+   * loose via X motion.
+   */
+
+  if ((META_WINDOW_MAXIMIZED (window) && ABS (dy) >= shake_threshold) ||
+      (META_WINDOW_TILED_SIDE_BY_SIDE (window) && (MAX (ABS (dx), ABS (dy)) >= shake_threshold)))
     {
       double prop;
 
-      /* Shake loose */
-      window->shaken_loose = TRUE;
-                  
+      /* Shake loose, so that the window snaps back to maximized
+       * when dragged near the top; do not snap back if the window
+       * was tiled.
+       */
+      window->shaken_loose = META_WINDOW_MAXIMIZED (window);
+      window->tile_mode = META_TILE_NONE;
+
       /* move the unmaximized window to the cursor */
       prop = 
         ((double)(x - display->grab_initial_window_pos.x)) / 
@@ -7005,7 +7144,9 @@ update_move (MetaWindow  *window,
               display->grab_anchor_root_x = x;
               display->grab_anchor_root_y = y;
               window->shaken_loose = FALSE;
-              
+
+              window->tile_mode = META_TILE_NONE;
+
               meta_window_maximize (window,
                                     META_MAXIMIZE_HORIZONTAL |
                                     META_MAXIMIZE_VERTICAL);
@@ -7015,13 +7156,20 @@ update_move (MetaWindow  *window,
         }
     }
 
+  /* Delay showing the tile preview slightly to make it more unlikely to
+   * trigger it unwittingly, e.g. when shaking loose the window or moving
+   * it to another monitor.
+   */
+  meta_screen_tile_preview_update (window->screen,
+                                   window->tile_mode != META_TILE_NONE);
+
   if (display->grab_wireframe_active)
     old = display->grab_wireframe_rect;
   else
     meta_window_get_client_root_coords (window, &old);
 
-  /* Don't allow movement in the maximized directions */
-  if (window->maximized_horizontally)
+  /* Don't allow movement in the maximized directions or while tiled */
+  if (window->maximized_horizontally || META_WINDOW_TILED_SIDE_BY_SIDE (window))
     new_x = old.x;
   if (window->maximized_vertically)
     new_y = old.y;
@@ -7044,7 +7192,7 @@ update_move (MetaWindow  *window,
       meta_compositor_update_move (display->compositor,
                                   window, root_x, root_y);
     }
-  
+
   if (display->grab_wireframe_active)
     meta_window_update_wireframe (window, new_x, new_y, 
                                   display->grab_wireframe_rect.width,
@@ -7369,6 +7517,19 @@ check_use_this_motion_notify (MetaWindow *window,
     }
 }
 
+static void
+update_tile_mode (MetaWindow *window)
+{
+  switch (window->tile_mode)
+    {
+      case META_TILE_LEFT:
+      case META_TILE_RIGHT:
+          if (!META_WINDOW_TILED_SIDE_BY_SIDE (window))
+              window->tile_mode = META_TILE_NONE;
+          break;
+    }
+}
+
 void
 meta_window_handle_mouse_grab_op_event (MetaWindow *window,
                                         XEvent     *event)
@@ -7437,7 +7598,17 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window,
         {
           if (meta_grab_op_is_moving (window->display->grab_op))
             {
-              if (event->xbutton.root == window->screen->xroot)
+              if (window->tile_mode == META_TILE_MAXIMIZED)
+                {
+                  meta_window_maximize (window, META_MAXIMIZE_VERTICAL |
+                                                META_MAXIMIZE_HORIZONTAL);
+                  window->tile_mode = META_TILE_NONE;
+                }
+              else if (window->tile_mode != META_TILE_NONE)
+                {
+                  meta_window_tile (window);
+                }
+              else if (event->xbutton.root == window->screen->xroot)
                 update_move (window, event->xbutton.state & ShiftMask,
                              event->xbutton.x_root, event->xbutton.y_root);
             }
@@ -7449,8 +7620,17 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window,
                                event->xbutton.x_root,
                                event->xbutton.y_root,
                                TRUE);
-             if (window->display->compositor)
-               meta_compositor_set_updates (window->display->compositor, window, TRUE);
+              if (window->display->compositor)
+                meta_compositor_set_updates (window->display->compositor, window, TRUE);
+
+              /* If a tiled window has been dragged free with a
+               * mouse resize without snapping back to the tiled
+               * state, it will end up with an inconsistent tile
+               * mode on mouse release; cleaning the mode earlier
+               * would break the ability to snap back to the tiled
+               * state, so we wait until mouse release.
+               */
+              update_tile_mode (window);
             }
         }
 
@@ -7595,6 +7775,41 @@ meta_window_get_work_area_all_xineramas (MetaWindow    *window,
               window->desc, area->x, area->y, area->width, area->height);
 }
 
+void
+meta_window_get_current_tile_area (MetaWindow *window,
+                                   MetaRectangle *tile_area)
+{
+  int tile_monitor_number;
+
+  g_return_if_fail (window->tile_mode != META_TILE_NONE);
+
+  /* I don't know how to detect monitor configuration changes, so I have to take into account that
+   * tile_monitor_number might be invalid. If this happens, I replace it with whatever monitor
+   * the window is currently on. This is usually the correct monitor anyway, only in some special
+   * cases is the real monitor number actually required (e.g. the window is being moved with the mouse but
+   * is still mostly on the wrong monitor).
+   */
+  if (window->tile_monitor_number >= window->screen->n_xinerama_infos)
+    {
+      window->tile_monitor_number = meta_screen_get_xinerama_for_window (window->screen, window)->number;
+    }
+
+  tile_monitor_number = window->tile_monitor_number;
+  if (tile_monitor_number < 0)
+    {
+      meta_warning ("%s called with an invalid monitor number; using 0 instead\n", G_STRFUNC);
+      tile_monitor_number = 0;
+    }
+
+  meta_window_get_work_area_for_xinerama (window, tile_monitor_number, tile_area);
+
+  if (window->tile_mode == META_TILE_LEFT ||
+      window->tile_mode == META_TILE_RIGHT)
+    tile_area->width /= 2;
+
+  if (window->tile_mode == META_TILE_RIGHT)
+    tile_area->x += tile_area->width;
+}
 
 gboolean
 meta_window_same_application (MetaWindow *window,
diff --git a/src/core/workspace.c b/src/core/workspace.c
index 8ba6673..e03a7e7 100644
--- a/src/core/workspace.c
+++ b/src/core/workspace.c
@@ -741,8 +741,7 @@ ensure_work_areas_validated (MetaWorkspace *workspace)
   for (i = 0; i < workspace->screen->n_xinerama_infos; i++)
     tmp = g_list_prepend (tmp, &workspace->screen->xinerama_infos[i].rect);
   workspace->xinerama_edges =
-    meta_rectangle_find_nonintersected_xinerama_edges (tmp,
-                                                       workspace->all_struts);
+    meta_rectangle_find_nonintersected_xinerama_edges (&workspace->screen->rect, tmp, workspace->all_struts);
   g_list_free (tmp);
 
   /* We're all done, YAAY!  Record that everything has been validated. */
diff --git a/src/include/boxes.h b/src/include/boxes.h
index 9ae87dc..9efaa3d 100644
--- a/src/include/boxes.h
+++ b/src/include/boxes.h
@@ -282,6 +282,7 @@ GList* meta_rectangle_find_onscreen_edges (const MetaRectangle *basic_rect,
  * struts.
  */
 GList* meta_rectangle_find_nonintersected_xinerama_edges (
+                                           const MetaRectangle *screen_rect,
                                            const GList         *xinerama_rects,
                                            const GSList        *all_struts);
 
diff --git a/src/include/common.h b/src/include/common.h
index d408748..c5171d1 100644
--- a/src/include/common.h
+++ b/src/include/common.h
@@ -48,7 +48,9 @@ typedef enum
   META_FRAME_ALLOWS_MOVE              = 1 << 11,
   META_FRAME_FULLSCREEN               = 1 << 12,
   META_FRAME_IS_FLASHING              = 1 << 13,
-  META_FRAME_ABOVE                    = 1 << 14
+  META_FRAME_ABOVE                    = 1 << 14,
+  META_FRAME_TILED_LEFT               = 1 << 15,
+  META_FRAME_TILED_RIGHT              = 1 << 16
 } MetaFrameFlags;
 
 typedef enum
diff --git a/src/include/core.h b/src/include/core.h
index 94302f0..ef20ee2 100644
--- a/src/include/core.h
+++ b/src/include/core.h
@@ -114,6 +114,10 @@ void meta_core_user_focus   (Display *xdisplay,
                              Window   frame_xwindow,
                              guint32  timestamp);
 
+void meta_core_lower_beneath_focus_window (Display *xdisplay,
+                                           Window xwindow,
+                                           guint32 timestamp);
+
 void meta_core_minimize         (Display *xdisplay,
                                  Window   frame_xwindow);
 void meta_core_toggle_maximize  (Display *xdisplay,
diff --git a/src/include/prefs.h b/src/include/prefs.h
index c251aba..33430c1 100644
--- a/src/include/prefs.h
+++ b/src/include/prefs.h
@@ -58,6 +58,7 @@ typedef enum
   META_PREF_CURSOR_SIZE,
   META_PREF_COMPOSITING_MANAGER,
   META_PREF_RESIZE_WITH_RIGHT_BUTTON,
+  META_PREF_EDGE_TILING,
   META_PREF_FORCE_FULLSCREEN,
   META_PREF_PLACEMENT_MODE
 } MetaPreference;
@@ -99,6 +100,7 @@ int                         meta_prefs_get_auto_raise_delay   (void);
 gboolean                    meta_prefs_get_reduced_resources  (void);
 gboolean                    meta_prefs_get_gnome_accessibility (void);
 gboolean                    meta_prefs_get_gnome_animations   (void);
+gboolean                    meta_prefs_get_edge_tiling        (void);
 
 const char*                 meta_prefs_get_screenshot_command (void);
 
diff --git a/src/include/tile-preview.h b/src/include/tile-preview.h
new file mode 100644
index 0000000..79312ac
--- /dev/null
+++ b/src/include/tile-preview.h
@@ -0,0 +1,37 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Meta tile preview */
+
+/*
+ * Copyright (C) 2010 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#ifndef META_TILE_PREVIEW_H
+#define META_TILE_PREVIEW_H
+
+#include "boxes.h"
+
+typedef struct _MetaTilePreview MetaTilePreview;
+
+MetaTilePreview   *meta_tile_preview_new    (int                screen_number,
+                                             gboolean           composited);
+void               meta_tile_preview_free   (MetaTilePreview   *preview);
+void               meta_tile_preview_show   (MetaTilePreview   *preview,
+                                             MetaRectangle     *rect);
+void               meta_tile_preview_hide   (MetaTilePreview   *preview);
+
+#endif /* META_TILE_PREVIEW_H */
diff --git a/src/include/ui.h b/src/include/ui.h
index e2bb9ed..0ca0f26 100644
--- a/src/include/ui.h
+++ b/src/include/ui.h
@@ -180,5 +180,6 @@ int      meta_ui_get_drag_threshold       (MetaUI *ui);
 MetaUIDirection meta_ui_get_direction (void);
 
 #include "tabpopup.h"
+#include "tile-preview.h"
 
 #endif
diff --git a/src/metacity-schemas.convert b/src/metacity-schemas.convert
index 46f3104..9823cfd 100644
--- a/src/metacity-schemas.convert
+++ b/src/metacity-schemas.convert
@@ -1,3 +1,4 @@
 [org.gnome.metacity]
 compositing-manager = /apps/metacity/general/compositing_manager
 reduced-resources = /apps/metacity/general/reduced_resources
+side-by-side-tiling = /apps/metacity/general/side_by_side_tiling
diff --git a/src/org.gnome.metacity.gschema.xml.in b/src/org.gnome.metacity.gschema.xml.in
index d1fd336..6b4eb0d 100644
--- a/src/org.gnome.metacity.gschema.xml.in
+++ b/src/org.gnome.metacity.gschema.xml.in
@@ -30,6 +30,15 @@
         However, the wireframe feature is disabled when accessibility is on.
       </_description>
     </key>
+    <key name="edge-tiling" type="b">
+      <default>true</default>
+      <_summary>Enable edge tiling when dropping windows on screen edges</_summary>
+      <_description>
+        If enabled, dropping windows on vertical screen edges maximizes them
+        vertically and resizes them horizontally to cover half of the available
+        area. Dropping windows on the top screen edge maximizes them completely.
+      </_description>
+    </key>
     <key name="placement-mode" enum="org.gnome.metacity.MetaPlacementMode">
       <default>'smart'</default>
       <_summary>Window placement behavior</_summary>
diff --git a/src/ui/theme-parser.c b/src/ui/theme-parser.c
index 77d80ca..774986c 100644
--- a/src/ui/theme-parser.c
+++ b/src/ui/theme-parser.c
@@ -3181,6 +3181,28 @@ parse_style_set_element (GMarkupParseContext  *context,
           meta_frame_style_ref (frame_style);
           info->style_set->maximized_styles[frame_focus] = frame_style;
           break;
+        case META_FRAME_STATE_TILED_LEFT:
+          if (info->style_set->tiled_left_styles[frame_focus])
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("Style has already been specified for state %s focus %s"),
+                         state, focus);
+              return;
+            }
+          meta_frame_style_ref (frame_style);
+          info->style_set->tiled_left_styles[frame_focus] = frame_style;
+          break;
+        case META_FRAME_STATE_TILED_RIGHT:
+          if (info->style_set->tiled_right_styles[frame_focus])
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("Style has already been specified for state %s focus %s"),
+                         state, focus);
+              return;
+            }
+          meta_frame_style_ref (frame_style);
+          info->style_set->tiled_right_styles[frame_focus] = frame_style;
+          break;
         case META_FRAME_STATE_SHADED:
           if (info->style_set->shaded_styles[frame_resize][frame_focus])
             {
@@ -3203,6 +3225,28 @@ parse_style_set_element (GMarkupParseContext  *context,
           meta_frame_style_ref (frame_style);
           info->style_set->maximized_and_shaded_styles[frame_focus] = frame_style;
           break;
+        case META_FRAME_STATE_TILED_LEFT_AND_SHADED:
+          if (info->style_set->tiled_left_and_shaded_styles[frame_focus])
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("Style has already been specified for state %s focus %s"),
+                         state, focus);
+              return;
+            }
+          meta_frame_style_ref (frame_style);
+          info->style_set->tiled_left_and_shaded_styles[frame_focus] = frame_style;
+          break;
+        case META_FRAME_STATE_TILED_RIGHT_AND_SHADED:
+          if (info->style_set->tiled_right_and_shaded_styles[frame_focus])
+            {
+              set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+                         _("Style has already been specified for state %s focus %s"),
+                         state, focus);
+              return;
+            }
+          meta_frame_style_ref (frame_style);
+          info->style_set->tiled_right_and_shaded_styles[frame_focus] = frame_style;
+          break;
         case META_FRAME_STATE_LAST:
           g_assert_not_reached ();
           break;
diff --git a/src/ui/theme.c b/src/ui/theme.c
index de039a5..ed25877 100644
--- a/src/ui/theme.c
+++ b/src/ui/theme.c
@@ -865,7 +865,9 @@ meta_frame_layout_calc_geometry (const MetaFrameLayout  *layout,
       rect->visible.width = button_width;
       rect->visible.height = button_height;
 
-      if (flags & META_FRAME_MAXIMIZED)
+      if (flags & META_FRAME_MAXIMIZED ||
+          flags & META_FRAME_TILED_LEFT ||
+          flags & META_FRAME_TILED_RIGHT)
         {
           rect->clickable.x = rect->visible.x;
           rect->clickable.y = 0;
@@ -4733,7 +4735,11 @@ meta_frame_style_set_unref (MetaFrameStyleSet *style_set)
         }
 
       free_focus_styles (style_set->maximized_styles);
+      free_focus_styles (style_set->tiled_left_styles);
+      free_focus_styles (style_set->tiled_right_styles);
       free_focus_styles (style_set->maximized_and_shaded_styles);
+      free_focus_styles (style_set->tiled_left_and_shaded_styles);
+      free_focus_styles (style_set->tiled_right_and_shaded_styles);
 
       if (style_set->parent)
         meta_frame_style_set_unref (style_set->parent);
@@ -4785,9 +4791,21 @@ get_style (MetaFrameStyleSet *style_set,
           case META_FRAME_STATE_MAXIMIZED:
             styles = style_set->maximized_styles;
             break;
+          case META_FRAME_STATE_TILED_LEFT:
+            styles = style_set->tiled_left_styles;
+            break;
+          case META_FRAME_STATE_TILED_RIGHT:
+            styles = style_set->tiled_right_styles;
+            break;
           case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
             styles = style_set->maximized_and_shaded_styles;
             break;
+          case META_FRAME_STATE_TILED_LEFT_AND_SHADED:
+            styles = style_set->tiled_left_and_shaded_styles;
+            break;
+          case META_FRAME_STATE_TILED_RIGHT_AND_SHADED:
+            styles = style_set->tiled_right_and_shaded_styles;
+            break;
           case META_FRAME_STATE_NORMAL:
           case META_FRAME_STATE_SHADED:
           case META_FRAME_STATE_LAST:
@@ -4797,6 +4815,19 @@ get_style (MetaFrameStyleSet *style_set,
 
         style = styles[focus];
 
+        /* Tiled states are optional, try falling back to non-tiled states */
+        if (style == NULL)
+          {
+            if (state == META_FRAME_STATE_TILED_LEFT ||
+                state == META_FRAME_STATE_TILED_RIGHT)
+              style = get_style (style_set, META_FRAME_STATE_NORMAL,
+                                 resize, focus);
+            else if (state == META_FRAME_STATE_TILED_LEFT_AND_SHADED ||
+                     state == META_FRAME_STATE_TILED_RIGHT_AND_SHADED)
+              style = get_style (style_set, META_FRAME_STATE_SHADED,
+                                 resize, focus);
+          }
+
         /* Try parent if we failed here */
         if (style == NULL && style_set->parent)
           style = get_style (style_set->parent, state, resize, focus);      
@@ -5142,7 +5173,7 @@ theme_get_style (MetaTheme     *theme,
   if (style_set == NULL)
     return NULL;
   
-  switch (flags & (META_FRAME_MAXIMIZED | META_FRAME_SHADED))
+  switch (flags & (META_FRAME_MAXIMIZED | META_FRAME_SHADED | META_FRAME_TILED_LEFT | 
META_FRAME_TILED_RIGHT))
     {
     case 0:
       state = META_FRAME_STATE_NORMAL;
@@ -5150,12 +5181,24 @@ theme_get_style (MetaTheme     *theme,
     case META_FRAME_MAXIMIZED:
       state = META_FRAME_STATE_MAXIMIZED;
       break;
+    case META_FRAME_TILED_LEFT:
+      state = META_FRAME_STATE_TILED_LEFT;
+      break;
+    case META_FRAME_TILED_RIGHT:
+      state = META_FRAME_STATE_TILED_RIGHT;
+      break;
     case META_FRAME_SHADED:
       state = META_FRAME_STATE_SHADED;
       break;
     case (META_FRAME_MAXIMIZED | META_FRAME_SHADED):
       state = META_FRAME_STATE_MAXIMIZED_AND_SHADED;
       break;
+    case (META_FRAME_TILED_LEFT | META_FRAME_SHADED):
+      state = META_FRAME_STATE_TILED_LEFT_AND_SHADED;
+      break;
+    case (META_FRAME_TILED_RIGHT | META_FRAME_SHADED):
+      state = META_FRAME_STATE_TILED_RIGHT_AND_SHADED;
+      break;
     default:
       g_assert_not_reached ();
       state = META_FRAME_STATE_LAST; /* compiler */
@@ -5852,10 +5895,18 @@ meta_frame_state_from_string (const char *str)
     return META_FRAME_STATE_NORMAL;
   else if (strcmp ("maximized", str) == 0)
     return META_FRAME_STATE_MAXIMIZED;
+  else if (strcmp ("tiled_left", str) == 0)
+    return META_FRAME_STATE_TILED_LEFT;
+  else if (strcmp ("tiled_right", str) == 0)
+    return META_FRAME_STATE_TILED_RIGHT;
   else if (strcmp ("shaded", str) == 0)
     return META_FRAME_STATE_SHADED;
   else if (strcmp ("maximized_and_shaded", str) == 0)
     return META_FRAME_STATE_MAXIMIZED_AND_SHADED;
+  else if (strcmp ("tiled_left_and_shaded", str) == 0)
+    return META_FRAME_STATE_TILED_LEFT_AND_SHADED;
+  else if (strcmp ("tiled_right_and_shaded", str) == 0)
+    return META_FRAME_STATE_TILED_RIGHT_AND_SHADED;
   else
     return META_FRAME_STATE_LAST;
 }
@@ -5869,10 +5920,18 @@ meta_frame_state_to_string (MetaFrameState state)
       return "normal";
     case META_FRAME_STATE_MAXIMIZED:
       return "maximized";
+    case META_FRAME_STATE_TILED_LEFT:
+      return "tiled_left";
+    case META_FRAME_STATE_TILED_RIGHT:
+      return "tiled_right";
     case META_FRAME_STATE_SHADED:
       return "shaded";
     case META_FRAME_STATE_MAXIMIZED_AND_SHADED:
       return "maximized_and_shaded";
+    case META_FRAME_STATE_TILED_LEFT_AND_SHADED:
+      return "tiled_left_and_shaded";
+    case META_FRAME_STATE_TILED_RIGHT_AND_SHADED:
+      return "tiled_right_and_shaded";
     case META_FRAME_STATE_LAST:
       break;
     }
diff --git a/src/ui/theme.h b/src/ui/theme.h
index f1b290e..cea966c 100644
--- a/src/ui/theme.h
+++ b/src/ui/theme.h
@@ -771,8 +771,12 @@ typedef enum
 {
   META_FRAME_STATE_NORMAL,
   META_FRAME_STATE_MAXIMIZED,
+  META_FRAME_STATE_TILED_LEFT,
+  META_FRAME_STATE_TILED_RIGHT,
   META_FRAME_STATE_SHADED,
   META_FRAME_STATE_MAXIMIZED_AND_SHADED,
+  META_FRAME_STATE_TILED_LEFT_AND_SHADED,
+  META_FRAME_STATE_TILED_RIGHT_AND_SHADED,
   META_FRAME_STATE_LAST
 } MetaFrameState;
 
@@ -809,8 +813,12 @@ struct _MetaFrameStyleSet
   MetaFrameStyleSet *parent;
   MetaFrameStyle *normal_styles[META_FRAME_RESIZE_LAST][META_FRAME_FOCUS_LAST];
   MetaFrameStyle *maximized_styles[META_FRAME_FOCUS_LAST];
+  MetaFrameStyle *tiled_left_styles[META_FRAME_FOCUS_LAST];
+  MetaFrameStyle *tiled_right_styles[META_FRAME_FOCUS_LAST];
   MetaFrameStyle *shaded_styles[META_FRAME_RESIZE_LAST][META_FRAME_FOCUS_LAST];
   MetaFrameStyle *maximized_and_shaded_styles[META_FRAME_FOCUS_LAST];
+  MetaFrameStyle *tiled_left_and_shaded_styles[META_FRAME_FOCUS_LAST];
+  MetaFrameStyle *tiled_right_and_shaded_styles[META_FRAME_FOCUS_LAST];
 };
 
 /**
diff --git a/src/ui/tile-preview.c b/src/ui/tile-preview.c
new file mode 100644
index 0000000..c898b2d
--- /dev/null
+++ b/src/ui/tile-preview.c
@@ -0,0 +1,245 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Metacity tile-preview marks the area a window will *ehm* snap to */
+
+/*
+ * Copyright (C) 2010 Florian Müllner
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include <gtk/gtk.h>
+#include <cairo.h>
+
+#include "tile-preview.h"
+#include "core.h"
+
+#define OUTLINE_WIDTH 5  /* frame width in non-composite case */
+
+
+struct _MetaTilePreview {
+  GtkWidget     *preview_window;
+
+  GdkColor      *preview_color;
+  guchar         preview_alpha;
+
+  MetaRectangle  tile_rect;
+
+  gboolean       has_alpha: 1;
+};
+
+static gboolean
+meta_tile_preview_draw (GtkWidget *widget,
+                        cairo_t   *cr,
+                        gpointer   user_data)
+{
+  MetaTilePreview *preview = user_data;
+  GdkRGBA preview_color;
+
+  preview_color.red = (double)preview->preview_color->red   / 0xFFFF;
+  preview_color.green = (double)preview->preview_color->green / 0xFFFF;
+  preview_color.blue = (double)preview->preview_color->blue  / 0xFFFF;
+  preview_color.alpha = (double)preview->preview_alpha / 0xFF;
+
+  cairo_set_line_width (cr, 1.0);
+
+  if (preview->has_alpha)
+    {
+      /* Fill the preview area with a transparent color */
+      gdk_cairo_set_source_rgba (cr, &preview_color);
+
+      cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+      cairo_paint (cr);
+
+      /* Use the opaque color for the border */
+      preview_color.alpha = 1.0;
+      gdk_cairo_set_source_rgba (cr, &preview_color);
+    }
+  else
+    {
+      GdkRGBA white = {1.0, 1.0, 1.0, 1.0};
+
+      gdk_cairo_set_source_rgba (cr, &white);
+
+      cairo_rectangle (cr,
+                       OUTLINE_WIDTH - 0.5, OUTLINE_WIDTH - 0.5,
+                       preview->tile_rect.width - 2 * (OUTLINE_WIDTH - 1) - 1,
+                       preview->tile_rect.height - 2 * (OUTLINE_WIDTH - 1) - 1);
+      cairo_stroke (cr);
+    }
+
+  cairo_rectangle (cr,
+                   0.5, 0.5,
+                   preview->tile_rect.width - 1,
+                   preview->tile_rect.height - 1);
+  cairo_stroke (cr);
+
+  return FALSE;
+}
+
+static void
+on_preview_window_style_set (GtkWidget *widget,
+                             GtkStyle  *previous,
+                             gpointer   user_data)
+{
+  MetaTilePreview *preview = user_data;
+  GtkStyle *style;
+
+  style = gtk_rc_get_style_by_paths (gtk_widget_get_settings (widget),
+                                     "GtkWindow.GtkIconView",
+                                     "GtkWindow.GtkIconView",
+                                     GTK_TYPE_ICON_VIEW);
+
+  if (style != NULL)
+    g_object_ref (style);
+  else
+    style = gtk_style_new ();
+
+  gtk_style_get (style, GTK_TYPE_ICON_VIEW,
+                 "selection-box-color", &preview->preview_color,
+                 "selection-box-alpha", &preview->preview_alpha,
+                 NULL);
+  if (!preview->preview_color)
+    {
+      GdkColor selection = style->base[GTK_STATE_SELECTED];
+      preview->preview_color = gdk_color_copy (&selection);
+    }
+
+  g_object_unref (style);
+}
+
+MetaTilePreview *
+meta_tile_preview_new (int      screen_number,
+                       gboolean composited)
+{
+  MetaTilePreview *preview;
+  GdkVisual *visual;
+  GdkScreen *screen;
+
+  screen = gdk_display_get_screen (gdk_display_get_default (), screen_number);
+  visual = gdk_screen_get_rgba_visual (screen);
+
+  preview = g_new (MetaTilePreview, 1);
+
+  preview->preview_window = gtk_window_new (GTK_WINDOW_POPUP);
+
+  gtk_window_set_screen (GTK_WINDOW (preview->preview_window), screen);
+  gtk_widget_set_app_paintable (preview->preview_window, TRUE);
+
+  preview->preview_color = NULL;
+  preview->preview_alpha = 0xFF;
+
+  preview->tile_rect.x = preview->tile_rect.y = 0;
+  preview->tile_rect.width = preview->tile_rect.height = 0;
+
+  preview->has_alpha = visual && composited;
+
+  if (preview->has_alpha)
+    {
+      gtk_widget_set_visual (preview->preview_window, visual);
+
+      g_signal_connect (preview->preview_window, "style-set",
+                        G_CALLBACK (on_preview_window_style_set), preview);
+    }
+
+  gtk_widget_realize (preview->preview_window);
+  /*gdk_window_set_back_pixmap (gtk_widget_get_window (preview->preview_window),
+                              NULL, FALSE);*/
+
+  g_signal_connect (preview->preview_window, "draw",
+                    G_CALLBACK (meta_tile_preview_draw), preview);
+
+  return preview;
+}
+
+void
+meta_tile_preview_free (MetaTilePreview *preview)
+{
+  gtk_widget_destroy (preview->preview_window);
+
+  if (preview->preview_color)
+    gdk_color_free (preview->preview_color);
+
+  g_free (preview);
+}
+
+void
+meta_tile_preview_show (MetaTilePreview *preview,
+                        MetaRectangle   *tile_rect)
+{
+  GdkWindow *window;
+  GdkRectangle old_rect;
+
+  if (gtk_widget_get_visible (preview->preview_window)
+      && preview->tile_rect.x == tile_rect->x
+      && preview->tile_rect.y == tile_rect->y
+      && preview->tile_rect.width == tile_rect->width
+      && preview->tile_rect.height == tile_rect->height)
+    return; /* nothing to do */
+
+  gtk_widget_show (preview->preview_window);
+  window = gtk_widget_get_window (preview->preview_window);
+  meta_core_lower_beneath_focus_window (gdk_x11_get_default_xdisplay(),
+                                        GDK_WINDOW_XID (window),
+                                        gtk_get_current_event_time ());
+
+  old_rect.x = old_rect.y = 0;
+  old_rect.width = preview->tile_rect.width;
+  old_rect.height = preview->tile_rect.height;
+
+  gdk_window_invalidate_rect (window, &old_rect, FALSE);
+
+  preview->tile_rect = *tile_rect;
+
+  gdk_window_move_resize (window,
+                          preview->tile_rect.x, preview->tile_rect.y,
+                          preview->tile_rect.width, preview->tile_rect.height);
+
+  if (!preview->has_alpha)
+    {
+      cairo_rectangle_int_t outer_rect, inner_rect;
+      cairo_region_t *outer_region, *inner_region;
+      GdkRGBA black = {.0, .0, .0, 1.0};
+
+      gdk_window_set_background_rgba (window, &black);
+
+      outer_rect.x = outer_rect.y = 0;
+      outer_rect.width = preview->tile_rect.width;
+      outer_rect.height = preview->tile_rect.height;
+
+      inner_rect.x = OUTLINE_WIDTH;
+      inner_rect.y = OUTLINE_WIDTH;
+      inner_rect.width = outer_rect.width - 2 * OUTLINE_WIDTH;
+      inner_rect.height = outer_rect.height - 2 * OUTLINE_WIDTH;
+
+      outer_region = cairo_region_create_rectangle (&outer_rect);
+      inner_region = cairo_region_create_rectangle (&inner_rect);
+
+      cairo_region_subtract (outer_region, inner_region);
+      cairo_region_destroy (inner_region);
+
+      gtk_widget_shape_combine_region (preview->preview_window, outer_region);
+      cairo_region_destroy (outer_region);
+    }
+}
+
+void
+meta_tile_preview_hide (MetaTilePreview *preview)
+{
+  gtk_widget_hide (preview->preview_window);
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]