/* Wayland compositor running on top of an X server.
Copyright (C) 2022 to various contributors.
This file is part of 12to11.
12to11 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 3 of the License, or (at your
option) any later version.
12to11 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 12to11. If not, see . */
#include
#include
#include "xdg-shell.h"
#include "compositor.h"
typedef enum _AnchorGravity Anchor;
typedef enum _AnchorGravity Gravity;
enum _AnchorGravity
{
AnchorGravityNone,
AnchorGravityTop,
AnchorGravityBottom,
AnchorGravityLeft,
AnchorGravityRight,
AnchorGravityTopLeft,
AnchorGravityBottomLeft,
AnchorGravityTopRight,
AnchorGravityBottomRight,
};
struct _Positioner
{
/* The fields below mean what they do in the xdg_shell protocol
spec. */
int width, height;
int anchor_x, anchor_y;
int anchor_width, anchor_height;
unsigned int anchor, gravity, constraint;
int offset_x, offset_y;
Bool reactive;
int parent_width, parent_height;
uint32_t constraint_adjustment;
/* The wl_resource corresponding to this positioner. */
struct wl_resource *resource;
/* The number of references to this positioner. */
int refcount;
};
/* Scale factor used during constraint adjustment calculation. */
static double scale_adjustment_factor;
static void
Destroy (struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static void
SetSize (struct wl_client *client, struct wl_resource *resource,
int32_t width, int32_t height)
{
Positioner *positioner;
if (width < 1 || height < 1)
{
wl_resource_post_error (resource, XDG_POSITIONER_ERROR_INVALID_INPUT,
"invalid size %d %d", width, height);
return;
}
positioner = wl_resource_get_user_data (resource);
positioner->width = width;
positioner->height = height;
}
static void
SetAnchorRect (struct wl_client *client, struct wl_resource *resource,
int32_t x, int32_t y, int32_t width, int32_t height)
{
Positioner *positioner;
if (width < 1 || height < 1)
{
wl_resource_post_error (resource, XDG_POSITIONER_ERROR_INVALID_INPUT,
"invalid size %d %d", width, height);
return;
}
positioner = wl_resource_get_user_data (resource);
positioner->anchor_x = x;
positioner->anchor_y = y;
positioner->anchor_width = width;
positioner->anchor_height = height;
}
static void
SetAnchor (struct wl_client *client, struct wl_resource *resource,
uint32_t anchor)
{
Positioner *positioner;
if (anchor > XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT)
{
wl_resource_post_error (resource, XDG_POSITIONER_ERROR_INVALID_INPUT,
"not an anchor");
return;
}
positioner = wl_resource_get_user_data (resource);
positioner->anchor = anchor;
}
static void
SetGravity (struct wl_client *client, struct wl_resource *resource,
uint32_t gravity)
{
Positioner *positioner;
if (gravity > XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)
{
wl_resource_post_error (resource, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT,
"not a gravity");
return;
}
positioner = wl_resource_get_user_data (resource);
positioner->gravity = gravity;
}
static void
SetConstraintAdjustment (struct wl_client *client, struct wl_resource *resource,
uint32_t constraint_adjustment)
{
Positioner *positioner;
positioner = wl_resource_get_user_data (resource);
positioner->constraint_adjustment = constraint_adjustment;
}
static void
SetOffset (struct wl_client *client, struct wl_resource *resource,
int32_t x, int32_t y)
{
Positioner *positioner;
positioner = wl_resource_get_user_data (resource);
positioner->offset_x = x;
positioner->offset_y = y;
}
static void
SetReactive (struct wl_client *client, struct wl_resource *resource)
{
Positioner *positioner;
positioner = wl_resource_get_user_data (resource);
positioner->reactive = True;
}
static void
SetParentSize (struct wl_client *client, struct wl_resource *resource,
int width, int height)
{
Positioner *positioner;
positioner = wl_resource_get_user_data (resource);
positioner->parent_width = width;
positioner->parent_height = height;
}
static void
SetParentConfigure (struct wl_client *client, struct wl_resource *resource,
uint32_t configure)
{
/* Unused. */
return;
}
static const struct xdg_positioner_interface xdg_positioner_impl =
{
.destroy = Destroy,
.set_size = SetSize,
.set_anchor_rect = SetAnchorRect,
.set_anchor = SetAnchor,
.set_gravity = SetGravity,
.set_constraint_adjustment = SetConstraintAdjustment,
.set_offset = SetOffset,
.set_reactive = SetReactive,
.set_parent_size = SetParentSize,
.set_parent_configure = SetParentConfigure,
};
static void
RetainPositioner (Positioner *positioner)
{
positioner->refcount++;
}
static void
ReleasePositioner (Positioner *positioner)
{
if (--positioner->refcount)
return;
XLFree (positioner);
}
static void
HandleResourceDestroy (struct wl_resource *resource)
{
Positioner *positioner;
positioner = wl_resource_get_user_data (resource);
ReleasePositioner (positioner);
}
static void
CalculatePosition (Positioner *positioner, int *x_out, int *y_out)
{
int x, y, anchor_x, anchor_y, anchor_width, anchor_height;
/* Code mostly copied from weston. I cannot understand
xdg_positioner due to the terrible documentation. */
x = positioner->offset_x;
y = positioner->offset_y;
anchor_x = positioner->anchor_x;
anchor_y = positioner->anchor_y;
anchor_width = positioner->anchor_width;
anchor_height = positioner->anchor_height;
switch (positioner->anchor)
{
case AnchorGravityTop:
case AnchorGravityTopLeft:
case AnchorGravityTopRight:
y += anchor_y;
break;
case AnchorGravityBottom:
case AnchorGravityBottomLeft:
case AnchorGravityBottomRight:
y += anchor_y + anchor_height;
break;
default:
y += anchor_y + anchor_height / 2;
}
switch (positioner->anchor)
{
case AnchorGravityLeft:
case AnchorGravityTopLeft:
case AnchorGravityBottomLeft:
x += anchor_x;
break;
case AnchorGravityRight:
case AnchorGravityTopRight:
case AnchorGravityBottomRight:
x += anchor_x + anchor_width;
break;
default:
x += anchor_x + anchor_width / 2;
}
switch (positioner->gravity)
{
case AnchorGravityTop:
case AnchorGravityTopLeft:
case AnchorGravityTopRight:
y -= positioner->height;
break;
case AnchorGravityBottom:
case AnchorGravityBottomLeft:
case AnchorGravityBottomRight:
y = y;
break;
default:
y -= positioner->height / 2;
}
switch (positioner->gravity)
{
case AnchorGravityLeft:
case AnchorGravityTopLeft:
case AnchorGravityBottomLeft:
x -= positioner->width;
break;
case AnchorGravityRight:
case AnchorGravityTopRight:
case AnchorGravityBottomRight:
break;
default:
x -= positioner->width / 2;
}
if (x_out)
*x_out = x;
if (y_out)
*y_out = y;
}
static int
TrySlideX (Positioner *positioner, int x, int width, int cx, int cwidth)
{
int cx1, x1;
int new_x;
cx1 = cx + cwidth - 1;
x1 = x + width - 1;
/* See if the rect is unconstrained on the X axis. */
if (x >= cx && x1 <= cx1)
return x;
new_x = x;
/* Which of these conditions to use first depends on the gravity.
If the gravity is leftwards, then we try to first keep new_x less
than cx1. Otherwise, we first try to keep new_x bigger than
cx. */
switch (positioner->gravity)
{
case AnchorGravityLeft:
case AnchorGravityTopLeft:
case AnchorGravityBottomLeft:
if (x < cx)
/* If x is less than cx, move it to cx. */
new_x = cx;
else if (x1 > cx1)
/* If x1 extends past cx1, move it back. */
new_x = x - (x1 - cx1);
break;
case AnchorGravityRight:
case AnchorGravityTopRight:
case AnchorGravityBottomRight:
if (x1 > cx1)
/* If x1 extends past cx1, move it back. */
new_x = x - (x1 - cx1);
else if (x < cx)
/* If x is less than cx, move it to cx. */
new_x = cx;
break;
}
return new_x;
}
static int
TrySlideY (Positioner *positioner, int y, int height, int cy, int cheight)
{
int cy1, y1;
int new_y;
cy1 = cy + cheight - 1;
y1 = y + height - 1;
/* See if the rect is unconstrained on the Y axis. */
if (y >= cy && y1 <= cy1)
return y;
new_y = y;
/* Which of these conditions to use first depends on the gravity.
If the gravity is topwards, then we try to first keep new_y less
than cy1. Otherwise, we first try to keep new_y bigger than
cy. */
switch (positioner->gravity)
{
case AnchorGravityTop:
case AnchorGravityTopLeft:
case AnchorGravityTopRight:
if (y < cy)
/* If y is less than cy, move it to cy. */
new_y = cy;
else if (y1 > cy1)
/* If y1 eytends past cy1, move it back. */
new_y = y - (y1 - cy1);
break;
case AnchorGravityBottom:
case AnchorGravityBottomLeft:
case AnchorGravityBottomRight:
if (y1 > cy1)
/* If y1 eytends past cy1, move it back. */
new_y = y - (y1 - cy1);
else if (y < cy)
/* If y is less than cy, move it to cy. */
new_y = cy;
break;
}
return new_y;
}
static int
TryFlipX (Positioner *positioner, int x, int width, int cx, int cwidth,
int offset)
{
int cx1, x1;
int new_x;
Positioner new;
cx1 = cx + cwidth - 1;
x1 = x + width - 1;
/* If the rect is unconstrained, don't flip anything. */
if (x >= cx && x1 <= cx1)
return x;
/* Otherwise, create a copy of the positioner, but with the X
gravity and X anchor flipped. */
new = *positioner;
switch (positioner->gravity)
{
case AnchorGravityLeft:
new.gravity = AnchorGravityRight;
break;
case AnchorGravityTopLeft:
new.gravity = AnchorGravityTopRight;
break;
case AnchorGravityBottomLeft:
new.gravity = AnchorGravityBottomRight;
break;
case AnchorGravityRight:
new.gravity = AnchorGravityLeft;
break;
case AnchorGravityTopRight:
new.gravity = AnchorGravityTopLeft;
break;
case AnchorGravityBottomRight:
new.gravity = AnchorGravityBottomRight;
break;
}
switch (positioner->anchor)
{
case AnchorGravityLeft:
new.anchor = AnchorGravityRight;
break;
case AnchorGravityTopLeft:
new.anchor = AnchorGravityTopRight;
break;
case AnchorGravityBottomLeft:
new.anchor = AnchorGravityBottomRight;
break;
case AnchorGravityRight:
new.anchor = AnchorGravityLeft;
break;
case AnchorGravityTopRight:
new.anchor = AnchorGravityTopLeft;
break;
case AnchorGravityBottomRight:
new.anchor = AnchorGravityBottomRight;
break;
}
/* If neither the gravity nor the anchor changed, punt, since
flipping won't help. */
if (positioner->gravity == new.gravity
&& positioner->anchor == new.anchor)
return x;
/* Otherwise, compute a new position using the new positioner. */
CalculatePosition (&new, &new_x, NULL);
/* Scale that position. */
new_x *= scale_adjustment_factor;
/* If new_x is still constrained, use the previous position. */
if (new_x + offset < cx
|| new_x + offset + width - 1 > cx1)
return x;
/* Return the new X. */
return new_x + offset;
}
static int
TryFlipY (Positioner *positioner, int y, int height, int cy, int cheight,
int offset)
{
int cy1, y1;
int new_y;
Positioner new;
cy1 = cy + cheight - 1;
y1 = y + height - 1;
/* If the rect is unconstrained, don't flip anything. */
if (y >= cy && y1 <= cy1)
return y;
/* Otherwise, create a copy of the positioner, but with the Y
gravity and Y anchor flipped. */
new = *positioner;
switch (positioner->gravity)
{
case AnchorGravityTop:
new.gravity = AnchorGravityBottom;
break;
case AnchorGravityTopLeft:
new.gravity = AnchorGravityBottomLeft;
break;
case AnchorGravityTopRight:
new.gravity = AnchorGravityBottomRight;
break;
case AnchorGravityBottom:
new.gravity = AnchorGravityTop;
break;
case AnchorGravityBottomLeft:
new.gravity = AnchorGravityTopLeft;
break;
case AnchorGravityBottomRight:
new.gravity = AnchorGravityTopRight;
break;
}
switch (positioner->anchor)
{
case AnchorGravityTop:
new.anchor = AnchorGravityBottom;
break;
case AnchorGravityTopLeft:
new.anchor = AnchorGravityBottomLeft;
break;
case AnchorGravityTopRight:
new.anchor = AnchorGravityBottomRight;
break;
case AnchorGravityBottom:
new.anchor = AnchorGravityTop;
break;
case AnchorGravityBottomLeft:
new.anchor = AnchorGravityTopLeft;
break;
case AnchorGravityBottomRight:
new.anchor = AnchorGravityTopRight;
break;
}
/* If neither the gravity nor the anchor changed, punt, since
flipping won't help. */
if (positioner->gravity == new.gravity
&& positioner->anchor == new.anchor)
return y;
/* Otherwise, compute a new position using the new positioner. */
CalculatePosition (&new, NULL, &new_y);
/* Scale that position. */
new_y *= scale_adjustment_factor;
/* If new_y is still constrained, use the previous position. */
if (new_y + offset < cy
|| new_y + offset + height - 1 > cy1)
return y;
/* Return the new Y. */
return new_y + offset;
}
static void
TryResizeX (int x, int width, int cx, int cwidth, int offset,
int *new_x, int *new_width)
{
int x1, cx1, result_width, result_x;
x1 = x + width - 1;
cx1 = cx + cwidth - 1;
if (x >= cx && x1 <= cx1)
/* The popup is not constrained on the X axis. */
return;
/* Otherwise, resize the popup to fit inside cx, cx1.
If new_width ends up less than 1, punt. */
result_x = MAX (cx, x) - offset;
result_width = MIN (cx1, x1) - (*new_x + offset) + 1;
if (result_width <= 0)
return;
*new_x = result_x;
*new_width = result_width;
}
static void
TryResizeY (int y, int height, int cy, int cheight, int offset,
int *new_y, int *new_height)
{
int y1, cy1, result_height, result_y;
y1 = y + height - 1;
cy1 = cy + cheight - 1;
if (y >= cy && y1 <= cy1)
/* The popup is not constrained on the Y axis. */
return;
/* Otherwise, resize the popup to fit inside cy, cy1.
If new_height ends up less than 1, punt. */
result_y = MAX (cy, y) - offset;
result_height = MIN (cy1, y1) - (*new_y + offset) + 1;
if (result_height <= 0)
return;
*new_y = result_y;
*new_height = result_height;
}
static void
GetAdjustmentOffset (Role *parent, int *off_x, int *off_y)
{
int root_x, root_y, parent_gx, parent_gy;
XLXdgRoleGetCurrentGeometry (parent, &parent_gx,
&parent_gy, NULL, NULL);
XLXdgRoleCurrentRootPosition (parent, &root_x, &root_y);
*off_x = root_x + parent_gx * parent->surface->factor;
*off_y = root_y + parent_gy * parent->surface->factor;
}
static void
ApplyConstraintAdjustment (Positioner *positioner, Role *parent, int x,
int y, int *x_out, int *y_out, int *width_out,
int *height_out)
{
int width, height, cx, cy, cwidth, cheight, off_x, off_y;
width = positioner->width * scale_adjustment_factor;
height = positioner->height * scale_adjustment_factor;
/* Set the factor describing how to convert surface coordinates to
window ones. */
scale_adjustment_factor = parent->surface->factor;
/* Constraint calculations are simplest if we use scaled
coordinates, and then unscale them later. */
x *= scale_adjustment_factor;
y *= scale_adjustment_factor;
if (positioner->constraint_adjustment
== XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE)
/* There is no constraint adjustment. */
goto finish;
/* Compute the current offset. */
GetAdjustmentOffset (parent, &off_x, &off_y);
if (!XLGetOutputRectAt (off_x + x, off_y + y, &cx, &cy,
&cwidth, &cheight))
/* There is no output in which to constrain this popup. */
goto finish;
if (positioner->constraint_adjustment
& XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X)
x = TrySlideX (positioner, x + off_x, width,
cx, cwidth) - off_x;
if (positioner->constraint_adjustment
& XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y)
y = TrySlideY (positioner, y + off_y, height,
cy, cheight) - off_y;
if (positioner->constraint_adjustment
& XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X)
x = TryFlipX (positioner, x + off_x, width,
cx, cwidth, off_x) - off_x;
if (positioner->constraint_adjustment
& XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y)
y = TryFlipY (positioner, y + off_y, height,
cy, cheight, off_y) - off_y;
if (positioner->constraint_adjustment
& XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X)
TryResizeX (x + off_x, width, cx, cwidth,
off_x, &x, &width);
if (positioner->constraint_adjustment
& XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y)
TryResizeY (y + off_y, height, cy, cheight,
off_y, &y, &height);
finish:
*x_out = x / scale_adjustment_factor;
*y_out = y / scale_adjustment_factor;
*width_out = width / scale_adjustment_factor;
*height_out = height / scale_adjustment_factor;
}
void
XLPositionerCalculateGeometry (Positioner *positioner, Role *parent,
int *x_out, int *y_out, int *width_out,
int *height_out)
{
int x, y, width, height;
CalculatePosition (positioner, &x, &y);
ApplyConstraintAdjustment (positioner, parent, x, y,
&x, &y, &width, &height);
*x_out = x;
*y_out = y;
*width_out = width;
*height_out = height;
}
void
XLCreateXdgPositioner (struct wl_client *client, struct wl_resource *resource,
uint32_t id)
{
Positioner *positioner;
positioner = XLSafeMalloc (sizeof *positioner);
if (!positioner)
{
wl_client_post_no_memory (client);
return;
}
memset (positioner, 0, sizeof *positioner);
positioner->resource
= wl_resource_create (client, &xdg_positioner_interface,
wl_resource_get_version (resource),
id);
if (!positioner->resource)
{
wl_resource_post_no_memory (resource);
XLFree (positioner);
return;
}
wl_resource_set_implementation (positioner->resource,
&xdg_positioner_impl,
positioner,
HandleResourceDestroy);
RetainPositioner (positioner);
}
void
XLRetainPositioner (Positioner *positioner)
{
RetainPositioner (positioner);
}
void
XLReleasePositioner (Positioner *positioner)
{
ReleasePositioner (positioner);
}
Bool
XLPositionerIsReactive (Positioner *positioner)
{
return positioner->reactive;
}