gtk/gdk/wayland/gdkpopup-wayland.c
Jonas Ådahl c88f2caa04 wayland/popup: Handle late received reposition feedback
If we map, reposition, unmap, remap, the reposition feedback from the
last time a popup was mapped might be received while we're dealing with
the new version of the popup. At this point, the old reposition token
has no meating, so lets drop it. Also reset the reposition tokens when
creating new protocol objects, so that the reposition token are as if
we're in the initial state.

This fixes an issue where we'd get stuck if repeatedly smashing a button
that'd create popups that'd immediately get dismissed by the compositor.
2023-03-27 21:27:44 +02:00

1472 lines
45 KiB
C

/*
* Copyright © 2010 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gdksurface-wayland.h"
#include "gdkdeviceprivate.h"
#include "gdkdisplay-wayland.h"
#include "gdkdragsurfaceprivate.h"
#include "gdkeventsprivate.h"
#include "gdkframeclockidleprivate.h"
#include "gdkglcontext-wayland.h"
#include "gdkmonitor-wayland.h"
#include "gdkpopupprivate.h"
#include "gdkprivate-wayland.h"
#include "gdkprivate-wayland.h"
#include "gdkseat-wayland.h"
#include "gdksurfaceprivate.h"
#include "gdktoplevelprivate.h"
#include "gdkdevice-wayland-private.h"
#include <wayland/xdg-shell-unstable-v6-client-protocol.h>
#include <wayland/xdg-foreign-unstable-v2-client-protocol.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <unistd.h>
#include "gdksurface-wayland-private.h"
static void update_popup_layout_state (GdkWaylandPopup *wayland_popup,
int x,
int y,
int width,
int height,
GdkPopupLayout *layout);
/* {{{ Utilities */
static gboolean
is_realized_shell_surface (GdkWaylandSurface *impl)
{
return (impl->display_server.xdg_surface ||
impl->display_server.zxdg_surface_v6);
}
static enum xdg_positioner_anchor
rect_anchor_to_anchor (GdkGravity rect_anchor)
{
switch (rect_anchor)
{
case GDK_GRAVITY_NORTH_WEST:
case GDK_GRAVITY_STATIC:
return XDG_POSITIONER_ANCHOR_TOP_LEFT;
case GDK_GRAVITY_NORTH:
return XDG_POSITIONER_ANCHOR_TOP;
case GDK_GRAVITY_NORTH_EAST:
return XDG_POSITIONER_ANCHOR_TOP_RIGHT;
case GDK_GRAVITY_WEST:
return XDG_POSITIONER_ANCHOR_LEFT;
case GDK_GRAVITY_CENTER:
return XDG_POSITIONER_ANCHOR_NONE;
case GDK_GRAVITY_EAST:
return XDG_POSITIONER_ANCHOR_RIGHT;
case GDK_GRAVITY_SOUTH_WEST:
return XDG_POSITIONER_ANCHOR_BOTTOM_LEFT;
case GDK_GRAVITY_SOUTH:
return XDG_POSITIONER_ANCHOR_BOTTOM;
case GDK_GRAVITY_SOUTH_EAST:
return XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT;
default:
g_assert_not_reached ();
}
}
static enum xdg_positioner_gravity
surface_anchor_to_gravity (GdkGravity rect_anchor)
{
switch (rect_anchor)
{
case GDK_GRAVITY_NORTH_WEST:
case GDK_GRAVITY_STATIC:
return XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT;
case GDK_GRAVITY_NORTH:
return XDG_POSITIONER_GRAVITY_BOTTOM;
case GDK_GRAVITY_NORTH_EAST:
return XDG_POSITIONER_GRAVITY_BOTTOM_LEFT;
case GDK_GRAVITY_WEST:
return XDG_POSITIONER_GRAVITY_RIGHT;
case GDK_GRAVITY_CENTER:
return XDG_POSITIONER_GRAVITY_NONE;
case GDK_GRAVITY_EAST:
return XDG_POSITIONER_GRAVITY_LEFT;
case GDK_GRAVITY_SOUTH_WEST:
return XDG_POSITIONER_GRAVITY_TOP_RIGHT;
case GDK_GRAVITY_SOUTH:
return XDG_POSITIONER_GRAVITY_TOP;
case GDK_GRAVITY_SOUTH_EAST:
return XDG_POSITIONER_GRAVITY_TOP_LEFT;
default:
g_assert_not_reached ();
}
}
static enum zxdg_positioner_v6_anchor
rect_anchor_to_anchor_legacy (GdkGravity rect_anchor)
{
switch (rect_anchor)
{
case GDK_GRAVITY_NORTH_WEST:
case GDK_GRAVITY_STATIC:
return (ZXDG_POSITIONER_V6_ANCHOR_TOP |
ZXDG_POSITIONER_V6_ANCHOR_LEFT);
case GDK_GRAVITY_NORTH:
return ZXDG_POSITIONER_V6_ANCHOR_TOP;
case GDK_GRAVITY_NORTH_EAST:
return (ZXDG_POSITIONER_V6_ANCHOR_TOP |
ZXDG_POSITIONER_V6_ANCHOR_RIGHT);
case GDK_GRAVITY_WEST:
return ZXDG_POSITIONER_V6_ANCHOR_LEFT;
case GDK_GRAVITY_CENTER:
return ZXDG_POSITIONER_V6_ANCHOR_NONE;
case GDK_GRAVITY_EAST:
return ZXDG_POSITIONER_V6_ANCHOR_RIGHT;
case GDK_GRAVITY_SOUTH_WEST:
return (ZXDG_POSITIONER_V6_ANCHOR_BOTTOM |
ZXDG_POSITIONER_V6_ANCHOR_LEFT);
case GDK_GRAVITY_SOUTH:
return ZXDG_POSITIONER_V6_ANCHOR_BOTTOM;
case GDK_GRAVITY_SOUTH_EAST:
return (ZXDG_POSITIONER_V6_ANCHOR_BOTTOM |
ZXDG_POSITIONER_V6_ANCHOR_RIGHT);
default:
g_assert_not_reached ();
}
return (ZXDG_POSITIONER_V6_ANCHOR_TOP |
ZXDG_POSITIONER_V6_ANCHOR_LEFT);
}
static enum zxdg_positioner_v6_gravity
surface_anchor_to_gravity_legacy (GdkGravity rect_anchor)
{
switch (rect_anchor)
{
case GDK_GRAVITY_NORTH_WEST:
case GDK_GRAVITY_STATIC:
return (ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
ZXDG_POSITIONER_V6_GRAVITY_RIGHT);
case GDK_GRAVITY_NORTH:
return ZXDG_POSITIONER_V6_GRAVITY_BOTTOM;
case GDK_GRAVITY_NORTH_EAST:
return (ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
ZXDG_POSITIONER_V6_GRAVITY_LEFT);
case GDK_GRAVITY_WEST:
return ZXDG_POSITIONER_V6_GRAVITY_RIGHT;
case GDK_GRAVITY_CENTER:
return ZXDG_POSITIONER_V6_GRAVITY_NONE;
case GDK_GRAVITY_EAST:
return ZXDG_POSITIONER_V6_GRAVITY_LEFT;
case GDK_GRAVITY_SOUTH_WEST:
return (ZXDG_POSITIONER_V6_GRAVITY_TOP |
ZXDG_POSITIONER_V6_GRAVITY_RIGHT);
case GDK_GRAVITY_SOUTH:
return ZXDG_POSITIONER_V6_GRAVITY_TOP;
case GDK_GRAVITY_SOUTH_EAST:
return (ZXDG_POSITIONER_V6_GRAVITY_TOP |
ZXDG_POSITIONER_V6_GRAVITY_LEFT);
default:
g_assert_not_reached ();
}
return (ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
ZXDG_POSITIONER_V6_GRAVITY_RIGHT);
}
/* }}} */
/* {{{ GdkWaylandPopup definition */
/**
* GdkWaylandPopup:
*
* The Wayland implementation of `GdkPopup`.
*/
struct _GdkWaylandPopup
{
GdkWaylandSurface parent_instance;
struct {
struct xdg_popup *xdg_popup;
struct zxdg_popup_v6 *zxdg_popup_v6;
} display_server;
PopupState state;
unsigned int thaw_upon_show : 1;
GdkPopupLayout *layout;
int unconstrained_width;
int unconstrained_height;
struct {
int x;
int y;
int width;
int height;
uint32_t repositioned_token;
gboolean has_repositioned_token;
} pending;
struct {
int x;
int y;
} next_layout;
uint32_t reposition_token;
uint32_t received_reposition_token;
GdkSeat *grab_input_seat;
};
typedef struct
{
GdkWaylandSurfaceClass parent_class;
} GdkWaylandPopupClass;
static void gdk_wayland_popup_iface_init (GdkPopupInterface *iface);
G_DEFINE_TYPE_WITH_CODE (GdkWaylandPopup, gdk_wayland_popup, GDK_TYPE_WAYLAND_SURFACE,
G_IMPLEMENT_INTERFACE (GDK_TYPE_POPUP,
gdk_wayland_popup_iface_init))
/* }}} */
/* {{{ Popup implementation */
static GdkSurface *
get_popup_toplevel (GdkSurface *surface)
{
if (surface->parent)
return get_popup_toplevel (surface->parent);
else
return surface;
}
static void
freeze_popup_toplevel_state (GdkWaylandPopup *wayland_popup)
{
GdkSurface *toplevel;
toplevel = get_popup_toplevel (GDK_SURFACE (wayland_popup));
gdk_wayland_surface_freeze_state (toplevel);
}
static void
thaw_popup_toplevel_state (GdkWaylandPopup *wayland_popup)
{
GdkSurface *toplevel;
toplevel = get_popup_toplevel (GDK_SURFACE (wayland_popup));
gdk_wayland_surface_thaw_state (toplevel);
}
static void
gdk_wayland_popup_hide_surface (GdkWaylandSurface *wayland_surface)
{
GdkWaylandPopup *popup = GDK_WAYLAND_POPUP (wayland_surface);
GdkSurface *surface = GDK_SURFACE (popup);
GdkDisplay *display = gdk_surface_get_display (surface);
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
g_clear_pointer (&popup->display_server.xdg_popup, xdg_popup_destroy);
g_clear_pointer (&popup->display_server.zxdg_popup_v6, zxdg_popup_v6_destroy);
display_wayland->current_popups =
g_list_remove (display_wayland->current_popups, surface);
display_wayland->current_grabbing_popups =
g_list_remove (display_wayland->current_grabbing_popups, surface);
popup->thaw_upon_show = TRUE;
gdk_surface_freeze_updates (surface);
switch (popup->state)
{
case POPUP_STATE_WAITING_FOR_REPOSITIONED:
gdk_surface_thaw_updates (surface);
G_GNUC_FALLTHROUGH;
case POPUP_STATE_WAITING_FOR_CONFIGURE:
case POPUP_STATE_WAITING_FOR_FRAME:
thaw_popup_toplevel_state (popup);
break;
case POPUP_STATE_IDLE:
break;
default:
g_assert_not_reached ();
}
popup->state = POPUP_STATE_IDLE;
g_clear_pointer (&popup->layout, gdk_popup_layout_unref);
}
static gboolean
is_realized_popup (GdkWaylandSurface *impl)
{
GdkWaylandPopup *popup;
if (!GDK_IS_WAYLAND_POPUP (impl))
return FALSE;
popup = GDK_WAYLAND_POPUP (impl);
return (popup->display_server.xdg_popup ||
popup->display_server.zxdg_popup_v6);
}
static void
gdk_wayland_popup_handle_frame (GdkWaylandSurface *surface)
{
GdkWaylandPopup *wayland_popup = GDK_WAYLAND_POPUP (surface);
switch (wayland_popup->state)
{
case POPUP_STATE_IDLE:
case POPUP_STATE_WAITING_FOR_REPOSITIONED:
case POPUP_STATE_WAITING_FOR_CONFIGURE:
break;
case POPUP_STATE_WAITING_FOR_FRAME:
wayland_popup->state = POPUP_STATE_IDLE;
thaw_popup_toplevel_state (wayland_popup);
break;
default:
g_assert_not_reached ();
}
}
static gboolean
gdk_wayland_popup_compute_size (GdkSurface *surface)
{
GdkWaylandPopup *wayland_popup = GDK_WAYLAND_POPUP (surface);
GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (surface);
if (wayland_surface->next_layout.surface_geometry_dirty)
{
int x, y, width, height;
x = wayland_popup->next_layout.x - wayland_surface->shadow_left;
y = wayland_popup->next_layout.y - wayland_surface->shadow_top;
width = wayland_surface->next_layout.configured_width +
(wayland_surface->shadow_left + wayland_surface->shadow_right);
height = wayland_surface->next_layout.configured_height +
(wayland_surface->shadow_top + wayland_surface->shadow_bottom);
gdk_wayland_surface_move_resize (surface, x, y, width, height);
wayland_surface->next_layout.surface_geometry_dirty = FALSE;
}
return FALSE;
}
static void
gdk_wayland_popup_handle_configure (GdkWaylandSurface *wayland_surface)
{
GdkSurface *surface = GDK_SURFACE (wayland_surface);
GdkWaylandPopup *wayland_popup = GDK_WAYLAND_POPUP (wayland_surface);
GdkRectangle parent_geometry;
int x, y, width, height;
if (wayland_popup->display_server.xdg_popup)
{
xdg_surface_ack_configure (wayland_surface->display_server.xdg_surface,
wayland_surface->pending.serial);
}
else if (wayland_popup->display_server.zxdg_popup_v6)
{
zxdg_surface_v6_ack_configure (wayland_surface->display_server.zxdg_surface_v6,
wayland_surface->pending.serial);
}
else
g_warn_if_reached ();
if (wayland_popup->pending.has_repositioned_token)
{
wayland_popup->received_reposition_token =
wayland_popup->pending.repositioned_token;
wayland_popup->pending.has_repositioned_token = FALSE;
}
switch (wayland_popup->state)
{
case POPUP_STATE_WAITING_FOR_REPOSITIONED:
if (wayland_popup->received_reposition_token != wayland_popup->reposition_token)
return;
else
gdk_surface_thaw_updates (surface);
G_GNUC_FALLTHROUGH;
case POPUP_STATE_WAITING_FOR_CONFIGURE:
wayland_popup->state = POPUP_STATE_WAITING_FOR_FRAME;
break;
case POPUP_STATE_IDLE:
case POPUP_STATE_WAITING_FOR_FRAME:
break;
default:
g_assert_not_reached ();
}
x = wayland_popup->pending.x;
y = wayland_popup->pending.y;
width = wayland_popup->pending.width;
height = wayland_popup->pending.height;
gdk_wayland_surface_get_window_geometry (surface->parent, &parent_geometry);
x += parent_geometry.x;
y += parent_geometry.y;
update_popup_layout_state (wayland_popup,
x, y,
width, height,
wayland_popup->layout);
wayland_popup->next_layout.x = x;
wayland_popup->next_layout.y = y;
wayland_surface->next_layout.configured_width = width;
wayland_surface->next_layout.configured_height = height;
wayland_surface->next_layout.surface_geometry_dirty = TRUE;
gdk_surface_request_layout (surface);
}
static void
gdk_wayland_surface_handle_configure_popup (GdkWaylandPopup *wayland_popup,
int32_t x,
int32_t y,
int32_t width,
int32_t height)
{
wayland_popup->pending.x = x;
wayland_popup->pending.y = y;
wayland_popup->pending.width = width;
wayland_popup->pending.height = height;
}
static void
xdg_popup_configure (void *data,
struct xdg_popup *xdg_popup,
int32_t x,
int32_t y,
int32_t width,
int32_t height)
{
GdkWaylandPopup *wayland_popup = GDK_WAYLAND_POPUP (data);
gdk_wayland_surface_handle_configure_popup (wayland_popup, x, y, width, height);
}
static void
xdg_popup_done (void *data,
struct xdg_popup *xdg_popup)
{
GdkWaylandPopup *wayland_popup = GDK_WAYLAND_POPUP (data);
GdkSurface *surface = GDK_SURFACE (wayland_popup);
GDK_DISPLAY_DEBUG (gdk_surface_get_display (surface), EVENTS, "done %p", surface);
gdk_surface_hide (surface);
}
static void
xdg_popup_repositioned (void *data,
struct xdg_popup *xdg_popup,
uint32_t token)
{
GdkWaylandPopup *wayland_popup = GDK_WAYLAND_POPUP (data);
GDK_DISPLAY_DEBUG (gdk_surface_get_display (GDK_SURFACE (wayland_popup)), EVENTS,
"repositioned %p", wayland_popup);
if (wayland_popup->state != POPUP_STATE_WAITING_FOR_REPOSITIONED)
{
g_warning ("Unexpected xdg_popup.repositioned event, probably buggy compositor");
return;
}
wayland_popup->pending.repositioned_token = token;
wayland_popup->pending.has_repositioned_token = TRUE;
}
static const struct xdg_popup_listener xdg_popup_listener = {
xdg_popup_configure,
xdg_popup_done,
xdg_popup_repositioned,
};
static void
zxdg_popup_v6_configure (void *data,
struct zxdg_popup_v6 *xdg_popup,
int32_t x,
int32_t y,
int32_t width,
int32_t height)
{
GdkWaylandPopup *wayland_popup = GDK_WAYLAND_POPUP (data);
gdk_wayland_surface_handle_configure_popup (wayland_popup, x, y, width, height);
}
static void
zxdg_popup_v6_done (void *data,
struct zxdg_popup_v6 *xdg_popup)
{
GdkWaylandPopup *wayland_popup = GDK_WAYLAND_POPUP (data);
GdkSurface *surface = GDK_SURFACE (wayland_popup);
GDK_DEBUG (EVENTS, "done %p", surface);
gdk_surface_hide (surface);
}
static const struct zxdg_popup_v6_listener zxdg_popup_v6_listener = {
zxdg_popup_v6_configure,
zxdg_popup_v6_done,
};
static void
calculate_popup_rect (GdkWaylandPopup *wayland_popup,
GdkPopupLayout *layout,
GdkRectangle *out_rect)
{
int width, height;
GdkRectangle anchor_rect;
int dx, dy;
int shadow_left, shadow_right, shadow_top, shadow_bottom;
int x = 0, y = 0;
gdk_popup_layout_get_shadow_width (layout,
&shadow_left,
&shadow_right,
&shadow_top,
&shadow_bottom);
width = (wayland_popup->unconstrained_width - (shadow_left + shadow_right));
height = (wayland_popup->unconstrained_height - (shadow_top + shadow_bottom));
anchor_rect = *gdk_popup_layout_get_anchor_rect (layout);
gdk_popup_layout_get_offset (layout, &dx, &dy);
anchor_rect.x += dx;
anchor_rect.y += dy;
switch (gdk_popup_layout_get_rect_anchor (layout))
{
default:
case GDK_GRAVITY_STATIC:
case GDK_GRAVITY_NORTH_WEST:
x = anchor_rect.x;
y = anchor_rect.y;
break;
case GDK_GRAVITY_NORTH:
x = anchor_rect.x + (anchor_rect.width / 2);
y = anchor_rect.y;
break;
case GDK_GRAVITY_NORTH_EAST:
x = anchor_rect.x + anchor_rect.width;
y = anchor_rect.y;
break;
case GDK_GRAVITY_WEST:
x = anchor_rect.x;
y = anchor_rect.y + (anchor_rect.height / 2);
break;
case GDK_GRAVITY_CENTER:
x = anchor_rect.x + (anchor_rect.width / 2);
y = anchor_rect.y + (anchor_rect.height / 2);
break;
case GDK_GRAVITY_EAST:
x = anchor_rect.x + anchor_rect.width;
y = anchor_rect.y + (anchor_rect.height / 2);
break;
case GDK_GRAVITY_SOUTH_WEST:
x = anchor_rect.x;
y = anchor_rect.y + anchor_rect.height;
break;
case GDK_GRAVITY_SOUTH:
x = anchor_rect.x + (anchor_rect.width / 2);
y = anchor_rect.y + anchor_rect.height;
break;
case GDK_GRAVITY_SOUTH_EAST:
x = anchor_rect.x + anchor_rect.width;
y = anchor_rect.y + anchor_rect.height;
break;
}
switch (gdk_popup_layout_get_surface_anchor (layout))
{
default:
case GDK_GRAVITY_STATIC:
case GDK_GRAVITY_NORTH_WEST:
break;
case GDK_GRAVITY_NORTH:
x -= width / 2;
break;
case GDK_GRAVITY_NORTH_EAST:
x -= width;
break;
case GDK_GRAVITY_WEST:
y -= height / 2;
break;
case GDK_GRAVITY_CENTER:
x -= width / 2;
y -= height / 2;
break;
case GDK_GRAVITY_EAST:
x -= width;
y -= height / 2;
break;
case GDK_GRAVITY_SOUTH_WEST:
y -= height;
break;
case GDK_GRAVITY_SOUTH:
x -= width / 2;
y -= height;
break;
case GDK_GRAVITY_SOUTH_EAST:
x -= width;
y -= height;
break;
}
*out_rect = (GdkRectangle) {
.x = x,
.y = y,
.width = width,
.height = height
};
}
static void
update_popup_layout_state (GdkWaylandPopup *wayland_popup,
int x,
int y,
int width,
int height,
GdkPopupLayout *layout)
{
GdkRectangle best_rect;
GdkRectangle flipped_rect;
GdkGravity rect_anchor;
GdkGravity surface_anchor;
GdkAnchorHints anchor_hints;
rect_anchor = gdk_popup_layout_get_rect_anchor (layout);
surface_anchor = gdk_popup_layout_get_surface_anchor (layout);
anchor_hints = gdk_popup_layout_get_anchor_hints (layout);
calculate_popup_rect (wayland_popup, layout, &best_rect);
flipped_rect = best_rect;
if (x != best_rect.x &&
anchor_hints & GDK_ANCHOR_FLIP_X)
{
GdkRectangle flipped_x_rect;
GdkGravity flipped_rect_anchor;
GdkGravity flipped_surface_anchor;
GdkPopupLayout *flipped_layout;
flipped_rect_anchor = gdk_gravity_flip_horizontally (rect_anchor);
flipped_surface_anchor = gdk_gravity_flip_horizontally (surface_anchor);
flipped_layout = gdk_popup_layout_copy (layout);
gdk_popup_layout_set_rect_anchor (flipped_layout,
flipped_rect_anchor);
gdk_popup_layout_set_surface_anchor (flipped_layout,
flipped_surface_anchor);
calculate_popup_rect (wayland_popup,
flipped_layout,
&flipped_x_rect);
gdk_popup_layout_unref (flipped_layout);
if (flipped_x_rect.x == x)
flipped_rect.x = x;
}
if (y != best_rect.y &&
anchor_hints & GDK_ANCHOR_FLIP_Y)
{
GdkRectangle flipped_y_rect;
GdkGravity flipped_rect_anchor;
GdkGravity flipped_surface_anchor;
GdkPopupLayout *flipped_layout;
flipped_rect_anchor = gdk_gravity_flip_vertically (rect_anchor);
flipped_surface_anchor = gdk_gravity_flip_vertically (surface_anchor);
flipped_layout = gdk_popup_layout_copy (layout);
gdk_popup_layout_set_rect_anchor (flipped_layout,
flipped_rect_anchor);
gdk_popup_layout_set_surface_anchor (flipped_layout,
flipped_surface_anchor);
calculate_popup_rect (wayland_popup,
flipped_layout,
&flipped_y_rect);
gdk_popup_layout_unref (flipped_layout);
if (flipped_y_rect.y == y)
flipped_rect.y = y;
}
if (flipped_rect.x != best_rect.x)
{
rect_anchor = gdk_gravity_flip_horizontally (rect_anchor);
surface_anchor = gdk_gravity_flip_horizontally (surface_anchor);
}
if (flipped_rect.y != best_rect.y)
{
rect_anchor = gdk_gravity_flip_vertically (rect_anchor);
surface_anchor = gdk_gravity_flip_vertically (surface_anchor);
}
GDK_SURFACE (wayland_popup)->popup.rect_anchor = rect_anchor;
GDK_SURFACE (wayland_popup)->popup.surface_anchor = surface_anchor;
}
static gpointer
create_dynamic_positioner (GdkWaylandPopup *wayland_popup,
int width,
int height,
GdkPopupLayout *layout,
gboolean ack_parent_configure)
{
GdkSurface *surface = GDK_SURFACE (wayland_popup);
GdkSurface *parent = surface->parent;
GdkWaylandSurface *parent_impl = GDK_WAYLAND_SURFACE (parent);
GdkWaylandDisplay *display =
GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
GdkRectangle geometry;
uint32_t constraint_adjustment = ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_NONE;
const GdkRectangle *anchor_rect;
int real_anchor_rect_x, real_anchor_rect_y;
int anchor_rect_width, anchor_rect_height;
int rect_anchor_dx;
int rect_anchor_dy;
GdkGravity rect_anchor;
GdkGravity surface_anchor;
GdkAnchorHints anchor_hints;
GdkRectangle parent_geometry;
int shadow_left;
int shadow_right;
int shadow_top;
int shadow_bottom;
gdk_popup_layout_get_shadow_width (layout,
&shadow_left,
&shadow_right,
&shadow_top,
&shadow_bottom);
geometry = (GdkRectangle) {
.x = shadow_left,
.y = shadow_top,
.width = width - (shadow_left + shadow_right),
.height = height - (shadow_top + shadow_bottom),
};
gdk_wayland_surface_get_window_geometry (surface->parent, &parent_geometry);
anchor_rect = gdk_popup_layout_get_anchor_rect (layout);
real_anchor_rect_x = anchor_rect->x - parent_geometry.x;
real_anchor_rect_y = anchor_rect->y - parent_geometry.y;
anchor_rect_width = MAX (anchor_rect->width, 1);
anchor_rect_height = MAX (anchor_rect->height, 1);
gdk_popup_layout_get_offset (layout, &rect_anchor_dx, &rect_anchor_dy);
rect_anchor = gdk_popup_layout_get_rect_anchor (layout);
surface_anchor = gdk_popup_layout_get_surface_anchor (layout);
anchor_hints = gdk_popup_layout_get_anchor_hints (layout);
switch (display->shell_variant)
{
case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
{
struct xdg_positioner *positioner;
enum xdg_positioner_anchor anchor;
enum xdg_positioner_gravity gravity;
positioner = xdg_wm_base_create_positioner (display->xdg_wm_base);
xdg_positioner_set_size (positioner, geometry.width, geometry.height);
xdg_positioner_set_anchor_rect (positioner,
real_anchor_rect_x,
real_anchor_rect_y,
anchor_rect_width,
anchor_rect_height);
xdg_positioner_set_offset (positioner, rect_anchor_dx, rect_anchor_dy);
anchor = rect_anchor_to_anchor (rect_anchor);
xdg_positioner_set_anchor (positioner, anchor);
gravity = surface_anchor_to_gravity (surface_anchor);
xdg_positioner_set_gravity (positioner, gravity);
if (anchor_hints & GDK_ANCHOR_FLIP_X)
constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X;
if (anchor_hints & GDK_ANCHOR_FLIP_Y)
constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y;
if (anchor_hints & GDK_ANCHOR_SLIDE_X)
constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X;
if (anchor_hints & GDK_ANCHOR_SLIDE_Y)
constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y;
if (anchor_hints & GDK_ANCHOR_RESIZE_X)
constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X;
if (anchor_hints & GDK_ANCHOR_RESIZE_Y)
constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y;
xdg_positioner_set_constraint_adjustment (positioner,
constraint_adjustment);
if (xdg_positioner_get_version (positioner) >=
XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION)
xdg_positioner_set_reactive (positioner);
if (ack_parent_configure &&
xdg_positioner_get_version (positioner) >=
XDG_POSITIONER_SET_PARENT_CONFIGURE_SINCE_VERSION)
{
xdg_positioner_set_parent_size (positioner,
parent_geometry.width,
parent_geometry.height);
xdg_positioner_set_parent_configure (positioner,
parent_impl->last_configure_serial);
}
return positioner;
}
case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
{
struct zxdg_positioner_v6 *positioner;
enum zxdg_positioner_v6_anchor anchor;
enum zxdg_positioner_v6_gravity gravity;
positioner = zxdg_shell_v6_create_positioner (display->zxdg_shell_v6);
zxdg_positioner_v6_set_size (positioner, geometry.width, geometry.height);
zxdg_positioner_v6_set_anchor_rect (positioner,
real_anchor_rect_x,
real_anchor_rect_y,
anchor_rect_width,
anchor_rect_height);
zxdg_positioner_v6_set_offset (positioner,
rect_anchor_dx,
rect_anchor_dy);
anchor = rect_anchor_to_anchor_legacy (rect_anchor);
zxdg_positioner_v6_set_anchor (positioner, anchor);
gravity = surface_anchor_to_gravity_legacy (surface_anchor);
zxdg_positioner_v6_set_gravity (positioner, gravity);
if (anchor_hints & GDK_ANCHOR_FLIP_X)
constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X;
if (anchor_hints & GDK_ANCHOR_FLIP_Y)
constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y;
if (anchor_hints & GDK_ANCHOR_SLIDE_X)
constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X;
if (anchor_hints & GDK_ANCHOR_SLIDE_Y)
constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y;
if (anchor_hints & GDK_ANCHOR_RESIZE_X)
constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_X;
if (anchor_hints & GDK_ANCHOR_RESIZE_Y)
constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y;
zxdg_positioner_v6_set_constraint_adjustment (positioner,
constraint_adjustment);
return positioner;
}
default:
g_assert_not_reached ();
}
g_assert_not_reached ();
}
static gboolean
can_map_grabbing_popup (GdkSurface *surface,
GdkSurface *parent)
{
GdkDisplay *display = gdk_surface_get_display (surface);
GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display);
GdkSurface *top_most_popup;
if (!display_wayland->current_grabbing_popups)
return TRUE;
top_most_popup = g_list_first (display_wayland->current_grabbing_popups)->data;
return top_most_popup == parent;
}
static gboolean
gdk_wayland_surface_create_xdg_popup (GdkWaylandPopup *wayland_popup,
GdkSurface *parent,
GdkWaylandSeat *grab_input_seat,
int width,
int height,
GdkPopupLayout *layout)
{
GdkSurface *surface = GDK_SURFACE (wayland_popup);
GdkWaylandDisplay *display = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface));
GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
GdkWaylandSurface *parent_impl = GDK_WAYLAND_SURFACE (parent);
gpointer positioner;
if (!impl->display_server.wl_surface)
return FALSE;
if (!is_realized_shell_surface (parent_impl))
return FALSE;
if (is_realized_popup (impl))
{
g_warning ("Can't map popup, already mapped");
return FALSE;
}
if (grab_input_seat &&
!can_map_grabbing_popup (surface, parent))
{
g_warning ("Tried to map a grabbing popup with a non-top most parent");
return FALSE;
}
gdk_surface_freeze_updates (surface);
positioner = create_dynamic_positioner (wayland_popup, width, height, layout, FALSE);
gdk_wayland_surface_create_xdg_surface_resources (surface);
switch (display->shell_variant)
{
case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
wayland_popup->display_server.xdg_popup =
xdg_surface_get_popup (impl->display_server.xdg_surface,
parent_impl->display_server.xdg_surface,
positioner);
xdg_popup_add_listener (wayland_popup->display_server.xdg_popup,
&xdg_popup_listener,
wayland_popup);
xdg_positioner_destroy (positioner);
break;
case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
wayland_popup->display_server.zxdg_popup_v6 =
zxdg_surface_v6_get_popup (impl->display_server.zxdg_surface_v6,
parent_impl->display_server.zxdg_surface_v6,
positioner);
zxdg_popup_v6_add_listener (wayland_popup->display_server.zxdg_popup_v6,
&zxdg_popup_v6_listener,
wayland_popup);
zxdg_positioner_v6_destroy (positioner);
break;
default:
g_assert_not_reached ();
}
wayland_popup->received_reposition_token = 0;
wayland_popup->reposition_token = 0;
gdk_popup_layout_get_shadow_width (layout,
&impl->shadow_left,
&impl->shadow_right,
&impl->shadow_top,
&impl->shadow_bottom);
if (grab_input_seat)
{
struct wl_seat *seat;
guint32 serial;
seat = gdk_wayland_seat_get_wl_seat (GDK_SEAT (grab_input_seat));
serial = _gdk_wayland_seat_get_last_implicit_grab_serial (grab_input_seat, NULL);
switch (display->shell_variant)
{
case GDK_WAYLAND_SHELL_VARIANT_XDG_SHELL:
xdg_popup_grab (wayland_popup->display_server.xdg_popup, seat, serial);
break;
case GDK_WAYLAND_SHELL_VARIANT_ZXDG_SHELL_V6:
zxdg_popup_v6_grab (wayland_popup->display_server.zxdg_popup_v6, seat, serial);
break;
default:
g_assert_not_reached ();
}
}
gdk_profiler_add_mark (GDK_PROFILER_CURRENT_TIME, 0, "wayland", "surface commit");
wl_surface_commit (impl->display_server.wl_surface);
if (GDK_IS_POPUP (surface))
{
g_assert (wayland_popup->state == POPUP_STATE_IDLE);
wayland_popup->state = POPUP_STATE_WAITING_FOR_CONFIGURE;
freeze_popup_toplevel_state (wayland_popup);
}
display->current_popups = g_list_append (display->current_popups, surface);
if (grab_input_seat)
{
display->current_grabbing_popups =
g_list_prepend (display->current_grabbing_popups, surface);
}
return TRUE;
}
#define LAST_PROP 1
static void
gdk_wayland_popup_init (GdkWaylandPopup *popup)
{
}
static void
gdk_wayland_popup_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GdkSurface *surface = GDK_SURFACE (object);
switch (prop_id)
{
case LAST_PROP + GDK_POPUP_PROP_PARENT:
g_value_set_object (value, surface->parent);
break;
case LAST_PROP + GDK_POPUP_PROP_AUTOHIDE:
g_value_set_boolean (value, surface->autohide);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gdk_wayland_popup_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GdkSurface *surface = GDK_SURFACE (object);
switch (prop_id)
{
case LAST_PROP + GDK_POPUP_PROP_PARENT:
surface->parent = g_value_dup_object (value);
if (surface->parent != NULL)
surface->parent->children = g_list_prepend (surface->parent->children, surface);
break;
case LAST_PROP + GDK_POPUP_PROP_AUTOHIDE:
surface->autohide = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gdk_wayland_popup_class_init (GdkWaylandPopupClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GdkSurfaceClass *surface_class = GDK_SURFACE_CLASS (class);
GdkWaylandSurfaceClass *wayland_surface_class = GDK_WAYLAND_SURFACE_CLASS (class);
object_class->get_property = gdk_wayland_popup_get_property;
object_class->set_property = gdk_wayland_popup_set_property;
surface_class->compute_size = gdk_wayland_popup_compute_size;
wayland_surface_class->handle_configure = gdk_wayland_popup_handle_configure;
wayland_surface_class->handle_frame = gdk_wayland_popup_handle_frame;
wayland_surface_class->hide_surface = gdk_wayland_popup_hide_surface;
gdk_popup_install_properties (object_class, 1);
}
static gboolean
is_fallback_relayout_possible (GdkWaylandPopup *wayland_popup)
{
GList *l;
for (l = GDK_SURFACE (wayland_popup)->children; l; l = l->next)
{
GdkSurface *child = l->data;
if (GDK_WAYLAND_SURFACE (child)->mapped)
return FALSE;
}
return TRUE;
}
static gboolean gdk_wayland_surface_present_popup (GdkWaylandPopup *wayland_popup,
int width,
int height,
GdkPopupLayout *layout);
static void
queue_relayout_fallback (GdkWaylandPopup *wayland_popup,
GdkPopupLayout *layout)
{
if (!is_fallback_relayout_possible (wayland_popup))
return;
gdk_wayland_surface_hide_surface (GDK_SURFACE (wayland_popup));
gdk_wayland_surface_present_popup (wayland_popup,
wayland_popup->unconstrained_width,
wayland_popup->unconstrained_height,
layout);
}
static void
do_queue_relayout (GdkWaylandPopup *wayland_popup,
int width,
int height,
GdkPopupLayout *layout)
{
struct xdg_positioner *positioner;
g_assert (is_realized_popup (GDK_WAYLAND_SURFACE (wayland_popup)));
g_assert (wayland_popup->state == POPUP_STATE_IDLE ||
wayland_popup->state == POPUP_STATE_WAITING_FOR_FRAME);
g_clear_pointer (&wayland_popup->layout, gdk_popup_layout_unref);
wayland_popup->layout = gdk_popup_layout_copy (layout);
wayland_popup->unconstrained_width = width;
wayland_popup->unconstrained_height = height;
if (!wayland_popup->display_server.xdg_popup ||
xdg_popup_get_version (wayland_popup->display_server.xdg_popup) <
XDG_POPUP_REPOSITION_SINCE_VERSION)
{
g_warning_once ("Compositor doesn't support moving popups, "
"relying on remapping");
queue_relayout_fallback (wayland_popup, layout);
return;
}
positioner = create_dynamic_positioner (wayland_popup,
width, height, layout,
TRUE);
xdg_popup_reposition (wayland_popup->display_server.xdg_popup,
positioner,
++wayland_popup->reposition_token);
xdg_positioner_destroy (positioner);
gdk_surface_freeze_updates (GDK_SURFACE (wayland_popup));
switch (wayland_popup->state)
{
case POPUP_STATE_IDLE:
freeze_popup_toplevel_state (wayland_popup);
break;
case POPUP_STATE_WAITING_FOR_FRAME:
break;
case POPUP_STATE_WAITING_FOR_CONFIGURE:
case POPUP_STATE_WAITING_FOR_REPOSITIONED:
default:
g_assert_not_reached ();
}
wayland_popup->state = POPUP_STATE_WAITING_FOR_REPOSITIONED;
}
static gboolean
is_relayout_finished (GdkSurface *surface)
{
GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface);
if (!impl->initial_configure_received)
return FALSE;
if (GDK_IS_WAYLAND_POPUP (surface))
{
GdkWaylandPopup *popup = GDK_WAYLAND_POPUP (surface);
if (popup->reposition_token != popup->received_reposition_token)
return FALSE;
}
return TRUE;
}
static GdkWaylandSeat *
find_grab_input_seat (GdkSurface *surface,
GdkSurface *parent)
{
GdkWaylandPopup *popup = GDK_WAYLAND_POPUP (surface);
GdkWaylandPopup *tmp_popup;
/* Use the device that was used for the grab as the device for
* the popup surface setup - so this relies on GTK taking the
* grab before showing the popup surface.
*/
if (popup->grab_input_seat)
return GDK_WAYLAND_SEAT (popup->grab_input_seat);
while (parent)
{
if (!GDK_IS_WAYLAND_POPUP (parent))
break;
tmp_popup = GDK_WAYLAND_POPUP (parent);
if (tmp_popup->grab_input_seat)
return GDK_WAYLAND_SEAT (tmp_popup->grab_input_seat);
parent = parent->parent;
}
return NULL;
}
static void
gdk_wayland_surface_map_popup (GdkWaylandPopup *wayland_popup,
int width,
int height,
GdkPopupLayout *layout)
{
GdkSurface *surface = GDK_SURFACE (wayland_popup);
GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_popup);
GdkSurface *parent;
GdkWaylandSeat *grab_input_seat;
parent = surface->parent;
if (!parent)
{
g_warning ("Couldn't map as surface %p as popup because it doesn't have a parent",
surface);
return;
}
if (surface->autohide)
grab_input_seat = find_grab_input_seat (surface, parent);
else
grab_input_seat = NULL;
if (!gdk_wayland_surface_create_xdg_popup (wayland_popup,
parent,
grab_input_seat,
width, height,
layout))
return;
wayland_popup->layout = gdk_popup_layout_copy (layout);
wayland_popup->unconstrained_width = width;
wayland_popup->unconstrained_height = height;
wayland_surface->mapped = TRUE;
}
static void
show_popup (GdkWaylandPopup *wayland_popup,
int width,
int height,
GdkPopupLayout *layout)
{
GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_popup);
if (!wayland_surface->display_server.wl_surface)
gdk_wayland_surface_create_wl_surface (GDK_SURFACE (wayland_popup));
if (wayland_popup->thaw_upon_show)
{
wayland_popup->thaw_upon_show = FALSE;
gdk_surface_thaw_updates (GDK_SURFACE (wayland_popup));
}
gdk_wayland_surface_map_popup (wayland_popup, width, height, layout);
}
typedef struct
{
int width;
int height;
GdkPopupLayout *layout;
} GrabPrepareData;
static void
show_grabbing_popup (GdkSeat *seat,
GdkSurface *surface,
gpointer user_data)
{
GrabPrepareData *data = user_data;
g_return_if_fail (GDK_IS_WAYLAND_POPUP (surface));
GdkWaylandPopup *wayland_popup = GDK_WAYLAND_POPUP (surface);
show_popup (wayland_popup, data->width, data->height, data->layout);
}
static void
reposition_popup (GdkWaylandPopup *wayland_popup,
int width,
int height,
GdkPopupLayout *layout)
{
switch (wayland_popup->state)
{
case POPUP_STATE_IDLE:
case POPUP_STATE_WAITING_FOR_FRAME:
do_queue_relayout (wayland_popup, width, height, layout);
break;
case POPUP_STATE_WAITING_FOR_REPOSITIONED:
case POPUP_STATE_WAITING_FOR_CONFIGURE:
g_warn_if_reached ();
break;
default:
g_assert_not_reached ();
}
}
static gboolean
gdk_wayland_surface_present_popup (GdkWaylandPopup *wayland_popup,
int width,
int height,
GdkPopupLayout *layout)
{
GdkSurface *surface = GDK_SURFACE (wayland_popup);
GdkWaylandSurface *wayland_surface = GDK_WAYLAND_SURFACE (wayland_popup);
if (!wayland_surface->mapped)
{
if (surface->autohide)
{
GdkSeat *seat;
seat = gdk_display_get_default_seat (surface->display);
if (seat)
{
GrabPrepareData data;
GdkGrabStatus result;
data = (GrabPrepareData) {
.width = width,
.height = height,
.layout = layout,
};
result = gdk_seat_grab (seat,
surface,
GDK_SEAT_CAPABILITY_ALL,
TRUE,
NULL, NULL,
show_grabbing_popup, &data);
if (result != GDK_GRAB_SUCCESS)
{
const char *grab_status[] = {
"success", "already grabbed", "invalid time",
"not viewable", "frozen", "failed"
};
g_warning ("Grab failed: %s", grab_status[result]);
}
}
}
else
{
show_popup (wayland_popup, width, height, layout);
}
}
else
{
if (wayland_popup->unconstrained_width == width &&
wayland_popup->unconstrained_height == height &&
gdk_popup_layout_equal (wayland_popup->layout, layout))
return TRUE;
reposition_popup (wayland_popup, width, height, layout);
}
while (wayland_popup->display_server.xdg_popup && !is_relayout_finished (surface))
{
gdk_wayland_display_dispatch_queue (surface->display,
wayland_surface->event_queue);
}
if (wayland_popup->display_server.xdg_popup)
{
gdk_surface_invalidate_rect (surface, NULL);
return TRUE;
}
else
{
return FALSE;
}
}
static gboolean
gdk_wayland_popup_present (GdkPopup *popup,
int width,
int height,
GdkPopupLayout *layout)
{
return gdk_wayland_surface_present_popup (GDK_WAYLAND_POPUP (popup), width, height, layout);
}
static GdkGravity
gdk_wayland_popup_get_surface_anchor (GdkPopup *popup)
{
return GDK_SURFACE (popup)->popup.surface_anchor;
}
static GdkGravity
gdk_wayland_popup_get_rect_anchor (GdkPopup *popup)
{
return GDK_SURFACE (popup)->popup.rect_anchor;
}
static int
gdk_wayland_popup_get_position_x (GdkPopup *popup)
{
return GDK_SURFACE (popup)->x;
}
static int
gdk_wayland_popup_get_position_y (GdkPopup *popup)
{
return GDK_SURFACE (popup)->y;
}
static void
gdk_wayland_popup_iface_init (GdkPopupInterface *iface)
{
iface->present = gdk_wayland_popup_present;
iface->get_surface_anchor = gdk_wayland_popup_get_surface_anchor;
iface->get_rect_anchor = gdk_wayland_popup_get_rect_anchor;
iface->get_position_x = gdk_wayland_popup_get_position_x;
iface->get_position_y = gdk_wayland_popup_get_position_y;
}
/* }}} */
/* {{{ Private Popup API */
void
_gdk_wayland_surface_set_grab_seat (GdkSurface *surface,
GdkSeat *seat)
{
GdkWaylandPopup *popup;
g_return_if_fail (surface != NULL);
popup = GDK_WAYLAND_POPUP (surface);
popup->grab_input_seat = seat;
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */