[mutter] core: Add support for custom window placement rules



commit 8833991201ce689de894720f1651dfeae516866d
Author: Jonas Ådahl <jadahl gmail com>
Date:   Mon Feb 1 18:46:11 2016 +0800

    core: Add support for custom window placement rules
    
    Add support for assigning a window a custom window placement rule used
    for calculating the initial window position as well as defining how a
    window is constrained.
    
    The custom rule is a declarative rule which defines a set of parameters
    which the placing algorithm and constrain algorithm uses for
    calculating the position of a window. It is meant to be used to
    implement positioning of menus and other popup windows created via
    Wayland.
    
    A custom placement rule replaces any other placement or constraint
    rule.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=769936

 src/core/constraints.c            |  237 ++++++++++++++++++++++++++++++++++++-
 src/core/place.c                  |   65 ++++++++++
 src/core/place.h                  |    5 +
 src/core/window-private.h         |   47 ++++++++
 src/core/window.c                 |   11 ++-
 src/wayland/meta-window-wayland.c |   13 ++
 src/wayland/meta-window-wayland.h |    4 +
 7 files changed, 374 insertions(+), 8 deletions(-)
---
diff --git a/src/core/constraints.c b/src/core/constraints.c
index 7b23797..7c4d61a 100644
--- a/src/core/constraints.c
+++ b/src/core/constraints.c
@@ -102,6 +102,7 @@ typedef enum
   PRIORITY_SIZE_HINTS_LIMITS = 3,
   PRIORITY_TITLEBAR_VISIBLE = 4,
   PRIORITY_PARTIALLY_VISIBLE_ON_WORKAREA = 4,
+  PRIORITY_CUSTOM_RULE = 4,
   PRIORITY_MAXIMUM = 4 /* Dummy value used for loop end = max(all priorities) */
 } ConstraintPriority;
 
@@ -144,6 +145,10 @@ static gboolean do_screen_and_monitor_relative_constraints (MetaWindow     *wind
                                                             GList          *region_spanning_rectangles,
                                                             ConstraintInfo *info,
                                                             gboolean        check_only);
+static gboolean constrain_custom_rule        (MetaWindow         *window,
+                                              ConstraintInfo     *info,
+                                              ConstraintPriority  priority,
+                                              gboolean            check_only);
 static gboolean constrain_modal_dialog       (MetaWindow         *window,
                                               ConstraintInfo     *info,
                                               ConstraintPriority  priority,
@@ -211,6 +216,7 @@ typedef struct {
 } Constraint;
 
 static const Constraint all_constraints[] = {
+  {constrain_custom_rule,        "constrain_custom_rule"},
   {constrain_modal_dialog,       "constrain_modal_dialog"},
   {constrain_maximization,       "constrain_maximization"},
   {constrain_tiling,             "constrain_tiling"},
@@ -641,6 +647,222 @@ get_size_limits (MetaWindow    *window,
   meta_window_client_rect_to_frame_rect (window, max_size, max_size);
 }
 
+static void
+placement_rule_flip_horizontally (MetaPlacementRule *placement_rule)
+{
+  if (placement_rule->anchor & META_PLACEMENT_ANCHOR_LEFT)
+    {
+      placement_rule->anchor &= ~META_PLACEMENT_ANCHOR_LEFT;
+      placement_rule->anchor |= META_PLACEMENT_ANCHOR_RIGHT;
+    }
+  else if (placement_rule->anchor & META_PLACEMENT_ANCHOR_RIGHT)
+    {
+      placement_rule->anchor &= ~META_PLACEMENT_ANCHOR_RIGHT;
+      placement_rule->anchor |= META_PLACEMENT_ANCHOR_LEFT;
+    }
+
+  if (placement_rule->gravity & META_PLACEMENT_GRAVITY_LEFT)
+    {
+      placement_rule->gravity &= ~META_PLACEMENT_GRAVITY_LEFT;
+      placement_rule->gravity |= META_PLACEMENT_GRAVITY_RIGHT;
+    }
+  else if (placement_rule->gravity & META_PLACEMENT_GRAVITY_RIGHT)
+    {
+      placement_rule->gravity &= ~META_PLACEMENT_GRAVITY_RIGHT;
+      placement_rule->gravity |= META_PLACEMENT_GRAVITY_LEFT;
+    }
+}
+
+static void
+placement_rule_flip_vertically (MetaPlacementRule *placement_rule)
+{
+  if (placement_rule->anchor & META_PLACEMENT_ANCHOR_TOP)
+    {
+      placement_rule->anchor &= ~META_PLACEMENT_ANCHOR_TOP;
+      placement_rule->anchor |= META_PLACEMENT_ANCHOR_BOTTOM;
+    }
+  else if (placement_rule->anchor & META_PLACEMENT_ANCHOR_BOTTOM)
+    {
+      placement_rule->anchor &= ~META_PLACEMENT_ANCHOR_BOTTOM;
+      placement_rule->anchor |= META_PLACEMENT_ANCHOR_TOP;
+    }
+
+  if (placement_rule->gravity & META_PLACEMENT_GRAVITY_TOP)
+    {
+      placement_rule->gravity &= ~META_PLACEMENT_GRAVITY_TOP;
+      placement_rule->gravity |= META_PLACEMENT_GRAVITY_BOTTOM;
+    }
+  else if (placement_rule->gravity & META_PLACEMENT_GRAVITY_BOTTOM)
+    {
+      placement_rule->gravity &= ~META_PLACEMENT_GRAVITY_BOTTOM;
+      placement_rule->gravity |= META_PLACEMENT_GRAVITY_TOP;
+    }
+}
+
+static void
+try_flip_window_position (MetaWindow                       *window,
+                          ConstraintInfo                   *info,
+                          MetaPlacementRule                *placement_rule,
+                          MetaPlacementConstraintAdjustment constraint_adjustment,
+                          MetaRectangle                    *rect,
+                          MetaRectangle                    *intersection)
+{
+  MetaPlacementRule flipped_rule = *placement_rule;;
+  MetaRectangle flipped_rect;
+  MetaRectangle flipped_intersection;
+
+  switch (constraint_adjustment)
+    {
+    case META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_X:
+      placement_rule_flip_horizontally (&flipped_rule);
+      break;
+    case META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_Y:
+      placement_rule_flip_vertically (&flipped_rule);
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+
+  flipped_rect = info->current;
+  meta_window_process_placement (window, &flipped_rule,
+                                 &flipped_rect.x, &flipped_rect.y);
+  meta_rectangle_intersect (&flipped_rect, &info->work_area_monitor,
+                            &flipped_intersection);
+
+  if ((constraint_adjustment == META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_X &&
+       flipped_intersection.width == flipped_rect.width) ||
+      (constraint_adjustment == META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_Y &&
+       flipped_intersection.height == flipped_rect.height))
+    {
+      *placement_rule = flipped_rule;
+      *rect = flipped_rect;
+      *intersection = flipped_intersection;
+    }
+}
+
+static gboolean
+is_custom_rule_satisfied (ConstraintInfo    *info,
+                          MetaPlacementRule *placement_rule,
+                          MetaRectangle     *intersection)
+{
+  uint32_t x_constrain_actions, y_constrain_actions;
+
+  x_constrain_actions = (META_PLACEMENT_CONSTRAINT_ADJUSTMENT_SLIDE_X |
+                         META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_X);
+  y_constrain_actions = (META_PLACEMENT_CONSTRAINT_ADJUSTMENT_SLIDE_Y |
+                         META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_Y);
+  if ((placement_rule->constraint_adjustment & x_constrain_actions &&
+       info->current.width != intersection->width) ||
+      (placement_rule->constraint_adjustment & y_constrain_actions &&
+       info->current.height != intersection->height))
+    return FALSE;
+  else
+    return TRUE;
+}
+
+static gboolean
+constrain_custom_rule (MetaWindow         *window,
+                       ConstraintInfo     *info,
+                       ConstraintPriority  priority,
+                       gboolean            check_only)
+{
+  MetaPlacementRule *placement_rule;
+  MetaRectangle intersection;
+  gboolean constraint_satisfied;
+  MetaPlacementRule current_rule;
+
+  if (priority > PRIORITY_CUSTOM_RULE)
+    return TRUE;
+
+  placement_rule = meta_window_get_placement_rule (window);
+  if (!placement_rule)
+    return TRUE;
+
+  if (!meta_rectangle_could_fit_rect (&info->work_area_monitor,
+                                      &info->current))
+    return TRUE;
+
+  meta_rectangle_intersect (&info->current, &info->work_area_monitor,
+                            &intersection);
+
+  constraint_satisfied = is_custom_rule_satisfied (info,
+                                                   placement_rule,
+                                                   &intersection);
+
+  if (constraint_satisfied || check_only)
+    return constraint_satisfied;
+
+  current_rule = *placement_rule;
+
+  if (info->current.width != intersection.width &&
+      (current_rule.constraint_adjustment &
+       META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_X))
+    {
+      try_flip_window_position (window, info, &current_rule,
+                                META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_X,
+                                &info->current, &intersection);
+    }
+  if (info->current.height != intersection.height &&
+      (current_rule.constraint_adjustment &
+       META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_Y))
+    {
+      try_flip_window_position (window, info, &current_rule,
+                                META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_Y,
+                                &info->current, &intersection);
+    }
+
+  meta_rectangle_intersect (&info->current, &info->work_area_monitor,
+                            &intersection);
+  constraint_satisfied = is_custom_rule_satisfied (info,
+                                                   placement_rule,
+                                                   &intersection);
+
+  if (constraint_satisfied)
+    return TRUE;
+
+  if (current_rule.constraint_adjustment &
+      META_PLACEMENT_CONSTRAINT_ADJUSTMENT_SLIDE_X)
+    {
+      if (info->current.x != intersection.x)
+        info->current.x = intersection.x;
+      else if (info->current.width != intersection.width)
+        info->current.x -= info->current.width - intersection.width;
+    }
+  if (current_rule.constraint_adjustment &
+      META_PLACEMENT_CONSTRAINT_ADJUSTMENT_SLIDE_Y)
+    {
+      if (info->current.y != intersection.y)
+        info->current.y = intersection.y;
+      else if (info->current.height != intersection.height)
+        info->current.y -= info->current.height - intersection.height;
+    }
+
+  meta_rectangle_intersect (&info->current, &info->work_area_monitor,
+                            &intersection);
+  constraint_satisfied = is_custom_rule_satisfied (info,
+                                                   placement_rule,
+                                                   &intersection);
+
+  if (constraint_satisfied)
+    return TRUE;
+
+  if (current_rule.constraint_adjustment &
+      META_PLACEMENT_CONSTRAINT_ADJUSTMENT_RESIZE_X)
+    {
+      info->current.x = intersection.x;
+      info->current.width = intersection.width;
+    }
+  if (current_rule.constraint_adjustment &
+      META_PLACEMENT_CONSTRAINT_ADJUSTMENT_RESIZE_Y)
+    {
+      info->current.y = intersection.y;
+      info->current.height = intersection.height;
+    }
+
+  return TRUE;
+}
+
 static gboolean
 constrain_modal_dialog (MetaWindow         *window,
                         ConstraintInfo     *info,
@@ -652,7 +874,8 @@ constrain_modal_dialog (MetaWindow         *window,
   MetaRectangle child_rect, parent_rect;
   gboolean constraint_already_satisfied;
 
-  if (!meta_window_is_attached_dialog (window))
+  if (!meta_window_is_attached_dialog (window) ||
+      meta_window_get_placement_rule (window))
     return TRUE;
 
   /* We want to center the dialog on the parent, including the decorations
@@ -1230,7 +1453,8 @@ constrain_to_single_monitor (MetaWindow         *window,
       window->screen->n_monitor_infos == 1  ||
       !window->require_on_single_monitor    ||
       !window->frame                        ||
-      info->is_user_action)
+      info->is_user_action                  ||
+      meta_window_get_placement_rule (window))
     return TRUE;
 
   /* Have a helper function handle the constraint for us */
@@ -1257,7 +1481,8 @@ constrain_fully_onscreen (MetaWindow         *window,
       window->type == META_WINDOW_DOCK    ||
       window->fullscreen                  ||
       !window->require_fully_onscreen     ||
-      info->is_user_action)
+      info->is_user_action                ||
+      meta_window_get_placement_rule (window))
     return TRUE;
 
   /* Have a helper function handle the constraint for us */
@@ -1296,7 +1521,8 @@ constrain_titlebar_visible (MetaWindow         *window,
       window->type == META_WINDOW_DOCK    ||
       window->fullscreen                  ||
       !window->require_titlebar_visible   ||
-      unconstrained_user_action)
+      unconstrained_user_action           ||
+      meta_window_get_placement_rule (window))
     return TRUE;
 
   /* Determine how much offscreen things are allowed.  We first need to
@@ -1373,7 +1599,8 @@ constrain_partially_onscreen (MetaWindow         *window,
    * "onscreen" by their own strut).
    */
   if (window->type == META_WINDOW_DESKTOP ||
-      window->type == META_WINDOW_DOCK)
+      window->type == META_WINDOW_DOCK    ||
+      meta_window_get_placement_rule (window))
     return TRUE;
 
   /* Determine how much offscreen things are allowed.  We first need to
diff --git a/src/core/place.c b/src/core/place.c
index fb2631e..db71b83 100644
--- a/src/core/place.c
+++ b/src/core/place.c
@@ -599,6 +599,61 @@ find_first_fit (MetaWindow *window,
 }
 
 void
+meta_window_process_placement (MetaWindow        *window,
+                               MetaPlacementRule *placement_rule,
+                               int               *x,
+                               int               *y)
+{
+  MetaWindow *parent = meta_window_get_transient_for (window);
+  MetaRectangle parent_rect;
+  MetaRectangle anchor_rect;
+  int window_width, window_height;
+
+  window_width = placement_rule->width;
+  window_height = placement_rule->height;
+  meta_window_get_frame_rect (parent, &parent_rect);
+
+  anchor_rect = (MetaRectangle) {
+    .x = parent_rect.x + placement_rule->anchor_rect.x,
+    .y = parent_rect.y + placement_rule->anchor_rect.y,
+    .width = placement_rule->anchor_rect.width,
+    .height = placement_rule->anchor_rect.height,
+  };
+
+  /* Place at anchor point. */
+  if (placement_rule->anchor & META_PLACEMENT_ANCHOR_LEFT)
+    *x = anchor_rect.x;
+  else if (placement_rule->anchor & META_PLACEMENT_ANCHOR_RIGHT)
+    *x = anchor_rect.x + anchor_rect.width;
+  else
+    *x = anchor_rect.x + (anchor_rect.width / 2);
+  if (placement_rule->anchor & META_PLACEMENT_ANCHOR_TOP)
+    *y = anchor_rect.y;
+  else if (placement_rule->anchor & META_PLACEMENT_ANCHOR_BOTTOM)
+    *y = anchor_rect.y + anchor_rect.height;
+  else
+    *y = anchor_rect.y + (anchor_rect.height / 2);
+
+  /* Shift according to gravity. */
+  if (placement_rule->gravity & META_PLACEMENT_GRAVITY_LEFT)
+    *x -= window_width;
+  else if (placement_rule->gravity & META_PLACEMENT_GRAVITY_RIGHT)
+    *x = *x;
+  else
+    *x -= window_width / 2;
+  if (placement_rule->gravity & META_PLACEMENT_GRAVITY_TOP)
+    *y -= window_height;
+  else if (placement_rule->gravity & META_PLACEMENT_GRAVITY_BOTTOM)
+    *y = *y;
+  else
+    *y -= window_height / 2;
+
+  /* Offset according to offset. */
+  *x += placement_rule->offset_x;
+  *y += placement_rule->offset_y;
+}
+
+void
 meta_window_place (MetaWindow        *window,
                    int                x,
                    int                y,
@@ -610,6 +665,16 @@ meta_window_place (MetaWindow        *window,
 
   meta_topic (META_DEBUG_PLACEMENT, "Placing window %s\n", window->desc);
 
+  /* If the window has a custom placement rule, always run only that. */
+  if (window->placement_rule)
+    {
+      meta_window_process_placement (window,
+                                     window->placement_rule,
+                                     &x, &y);
+
+      goto done;
+    }
+
   switch (window->type)
     {
       /* Run placement algorithm on these. */
diff --git a/src/core/place.h b/src/core/place.h
index 5f60225..ea0a330 100644
--- a/src/core/place.h
+++ b/src/core/place.h
@@ -25,6 +25,11 @@
 #include "window-private.h"
 #include "frame.h"
 
+void meta_window_process_placement (MetaWindow        *window,
+                                    MetaPlacementRule *placement_rule,
+                                    int               *x,
+                                    int               *y);
+
 void meta_window_place (MetaWindow *window,
                         int         x,
                         int         y,
diff --git a/src/core/window-private.h b/src/core/window-private.h
index f99e104..7d30652 100644
--- a/src/core/window-private.h
+++ b/src/core/window-private.h
@@ -88,6 +88,47 @@ typedef enum
   META_MOVE_RESIZE_RESULT_FRAME_SHAPE_CHANGED = 1 << 2,
 } MetaMoveResizeResultFlags;
 
+typedef enum
+{
+  META_PLACEMENT_GRAVITY_NONE   = 0,
+  META_PLACEMENT_GRAVITY_TOP    = 1 << 0,
+  META_PLACEMENT_GRAVITY_BOTTOM = 1 << 1,
+  META_PLACEMENT_GRAVITY_LEFT   = 1 << 2,
+  META_PLACEMENT_GRAVITY_RIGHT  = 1 << 3,
+} MetaPlacementGravity;
+
+typedef enum
+{
+  META_PLACEMENT_ANCHOR_NONE   = 0,
+  META_PLACEMENT_ANCHOR_TOP    = 1 << 0,
+  META_PLACEMENT_ANCHOR_BOTTOM = 1 << 1,
+  META_PLACEMENT_ANCHOR_LEFT   = 1 << 2,
+  META_PLACEMENT_ANCHOR_RIGHT  = 1 << 3,
+} MetaPlacementAnchor;
+
+typedef enum
+{
+  META_PLACEMENT_CONSTRAINT_ADJUSTMENT_NONE     = 0,
+  META_PLACEMENT_CONSTRAINT_ADJUSTMENT_SLIDE_X  = 1 << 0,
+  META_PLACEMENT_CONSTRAINT_ADJUSTMENT_SLIDE_Y  = 1 << 1,
+  META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_X   = 1 << 2,
+  META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_Y   = 1 << 3,
+  META_PLACEMENT_CONSTRAINT_ADJUSTMENT_RESIZE_X = 1 << 4,
+  META_PLACEMENT_CONSTRAINT_ADJUSTMENT_RESIZE_Y = 1 << 5,
+} MetaPlacementConstraintAdjustment;
+
+typedef struct _MetaPlacementRule
+{
+  MetaRectangle anchor_rect;
+  MetaPlacementGravity gravity;
+  MetaPlacementAnchor anchor;
+  MetaPlacementConstraintAdjustment constraint_adjustment;
+  int offset_x;
+  int offset_y;
+  int width;
+  int height;
+} MetaPlacementRule;
+
 struct _MetaWindow
 {
   GObject parent_instance;
@@ -448,6 +489,8 @@ struct _MetaWindow
 
   /* Bypass compositor hints */
   guint bypass_compositor;
+
+  MetaPlacementRule *placement_rule;
 };
 
 struct _MetaWindowClass
@@ -698,4 +741,8 @@ gboolean meta_window_has_pointer (MetaWindow *window);
 
 void meta_window_emit_size_changed (MetaWindow *window);
 
+MetaPlacementRule *meta_window_get_placement_rule (MetaWindow *window);
+
+void meta_window_force_placement (MetaWindow *window);
+
 #endif
diff --git a/src/core/window.c b/src/core/window.c
index 1d5882a..9cfb095 100644
--- a/src/core/window.c
+++ b/src/core/window.c
@@ -84,8 +84,6 @@ static void     set_net_wm_state          (MetaWindow     *window);
 static void     meta_window_set_above     (MetaWindow     *window,
                                            gboolean        new_value);
 
-static void     meta_window_force_placement (MetaWindow     *window);
-
 static void     meta_window_show          (MetaWindow     *window);
 static void     meta_window_hide          (MetaWindow     *window);
 
@@ -296,6 +294,7 @@ meta_window_finalize (GObject *object)
   g_free (window->gtk_window_object_path);
   g_free (window->gtk_app_menu_object_path);
   g_free (window->gtk_menubar_object_path);
+  g_free (window->placement_rule);
 
   G_OBJECT_CLASS (meta_window_parent_class)->finalize (object);
 }
@@ -2168,7 +2167,7 @@ window_would_be_covered (const MetaWindow *newbie)
   return FALSE; /* none found */
 }
 
-static void
+void
 meta_window_force_placement (MetaWindow *window)
 {
   if (window->placed)
@@ -7883,3 +7882,9 @@ meta_window_emit_size_changed (MetaWindow *window)
 {
   g_signal_emit (window, window_signals[SIZE_CHANGED], 0);
 }
+
+MetaPlacementRule *
+meta_window_get_placement_rule (MetaWindow *window)
+{
+  return window->placement_rule;
+}
diff --git a/src/wayland/meta-window-wayland.c b/src/wayland/meta-window-wayland.c
index 9b5ec5f..43ffe69 100644
--- a/src/wayland/meta-window-wayland.c
+++ b/src/wayland/meta-window-wayland.c
@@ -640,3 +640,16 @@ meta_window_wayland_place_relative_to (MetaWindow *window,
                           other->buffer_rect.y + (y * monitor_scale));
   window->placed = TRUE;
 }
+
+void
+meta_window_place_with_placement_rule (MetaWindow        *window,
+                                       MetaPlacementRule *placement_rule)
+{
+  g_clear_pointer (&window->placement_rule, g_free);
+  window->placement_rule = g_new0 (MetaPlacementRule, 1);
+  *window->placement_rule = *placement_rule;
+
+  window->unconstrained_rect.width = placement_rule->width;
+  window->unconstrained_rect.height = placement_rule->height;
+  meta_window_force_placement (window);
+}
diff --git a/src/wayland/meta-window-wayland.h b/src/wayland/meta-window-wayland.h
index 9287db8..1847f70 100644
--- a/src/wayland/meta-window-wayland.h
+++ b/src/wayland/meta-window-wayland.h
@@ -25,6 +25,7 @@
 #ifndef META_WINDOW_WAYLAND_H
 #define META_WINDOW_WAYLAND_H
 
+#include "core/window-private.h"
 #include <meta/window.h>
 #include "wayland/meta-wayland-types.h"
 
@@ -57,4 +58,7 @@ void meta_window_wayland_place_relative_to (MetaWindow *window,
                                             int         x,
                                             int         y);
 
+void meta_window_place_with_placement_rule (MetaWindow        *window,
+                                            MetaPlacementRule *placement_rule);
+
 #endif


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