From 8833991201ce689de894720f1651dfeae516866d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Mon, 1 Feb 2016 18:46:11 +0800 Subject: [PATCH] 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 7b237976d..7c4d61ada 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, ¤t_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, ¤t_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 fb2631eae..db71b83ce 100644 --- a/src/core/place.c +++ b/src/core/place.c @@ -598,6 +598,61 @@ find_first_fit (MetaWindow *window, return retval; } +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, @@ -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 5f602259a..ea0a3304f 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 f99e10403..7d30652ed 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 1d5882a40..9cfb09535 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 9b5ec5ffd..43ffe6941 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 9287db8de..1847f7078 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 #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