wlroots-hyprland/types/xdg_shell/wlr_xdg_popup.c

533 lines
16 KiB
C

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "types/wlr_xdg_shell.h"
void handle_xdg_popup_ack_configure(
struct wlr_xdg_popup *popup,
struct wlr_xdg_popup_configure *configure) {
popup->pending.geometry = configure->geometry;
popup->pending.reactive = configure->rules.reactive;
}
struct wlr_xdg_popup_configure *send_xdg_popup_configure(
struct wlr_xdg_popup *popup) {
struct wlr_xdg_popup_configure *configure = calloc(1, sizeof(*configure));
if (configure == NULL) {
wl_resource_post_no_memory(popup->resource);
return NULL;
}
*configure = popup->scheduled;
uint32_t version = wl_resource_get_version(popup->resource);
if ((configure->fields & WLR_XDG_POPUP_CONFIGURE_REPOSITION_TOKEN) &&
version >= XDG_POPUP_REPOSITIONED_SINCE_VERSION) {
xdg_popup_send_repositioned(popup->resource,
configure->reposition_token);
}
struct wlr_box *geometry = &configure->geometry;
assert(geometry->width > 0 && geometry->height > 0);
xdg_popup_send_configure(popup->resource,
geometry->x, geometry->y,
geometry->width, geometry->height);
popup->scheduled.fields = 0;
return configure;
}
static void xdg_popup_grab_end(struct wlr_xdg_popup_grab *popup_grab) {
struct wlr_xdg_popup *popup, *tmp;
wl_list_for_each_safe(popup, tmp, &popup_grab->popups, grab_link) {
xdg_popup_send_popup_done(popup->resource);
}
wlr_seat_pointer_end_grab(popup_grab->seat);
wlr_seat_keyboard_end_grab(popup_grab->seat);
wlr_seat_touch_end_grab(popup_grab->seat);
}
static void xdg_pointer_grab_enter(struct wlr_seat_pointer_grab *grab,
struct wlr_surface *surface, double sx, double sy) {
struct wlr_xdg_popup_grab *popup_grab = grab->data;
if (wl_resource_get_client(surface->resource) == popup_grab->client) {
wlr_seat_pointer_enter(grab->seat, surface, sx, sy);
} else {
wlr_seat_pointer_clear_focus(grab->seat);
}
}
static void xdg_pointer_grab_clear_focus(struct wlr_seat_pointer_grab *grab) {
wlr_seat_pointer_clear_focus(grab->seat);
}
static void xdg_pointer_grab_motion(struct wlr_seat_pointer_grab *grab,
uint32_t time, double sx, double sy) {
wlr_seat_pointer_send_motion(grab->seat, time, sx, sy);
}
static uint32_t xdg_pointer_grab_button(struct wlr_seat_pointer_grab *grab,
uint32_t time, uint32_t button, uint32_t state) {
uint32_t serial =
wlr_seat_pointer_send_button(grab->seat, time, button, state);
if (serial) {
return serial;
} else {
xdg_popup_grab_end(grab->data);
return 0;
}
}
static void xdg_pointer_grab_axis(struct wlr_seat_pointer_grab *grab,
uint32_t time, enum wl_pointer_axis orientation, double value,
int32_t value_discrete, enum wl_pointer_axis_source source,
enum wl_pointer_axis_relative_direction relative_direction) {
wlr_seat_pointer_send_axis(grab->seat, time, orientation, value,
value_discrete, source, relative_direction);
}
static void xdg_pointer_grab_frame(struct wlr_seat_pointer_grab *grab) {
wlr_seat_pointer_send_frame(grab->seat);
}
static void xdg_pointer_grab_cancel(struct wlr_seat_pointer_grab *grab) {
xdg_popup_grab_end(grab->data);
}
static const struct wlr_pointer_grab_interface xdg_pointer_grab_impl = {
.enter = xdg_pointer_grab_enter,
.clear_focus = xdg_pointer_grab_clear_focus,
.motion = xdg_pointer_grab_motion,
.button = xdg_pointer_grab_button,
.cancel = xdg_pointer_grab_cancel,
.axis = xdg_pointer_grab_axis,
.frame = xdg_pointer_grab_frame,
};
static void xdg_keyboard_grab_enter(struct wlr_seat_keyboard_grab *grab,
struct wlr_surface *surface, const uint32_t keycodes[], size_t num_keycodes,
const struct wlr_keyboard_modifiers *modifiers) {
// keyboard focus should remain on the popup
}
static void xdg_keyboard_grab_clear_focus(struct wlr_seat_keyboard_grab *grab) {
// keyboard focus should remain on the popup
}
static void xdg_keyboard_grab_key(struct wlr_seat_keyboard_grab *grab, uint32_t time,
uint32_t key, uint32_t state) {
wlr_seat_keyboard_send_key(grab->seat, time, key, state);
}
static void xdg_keyboard_grab_modifiers(struct wlr_seat_keyboard_grab *grab,
const struct wlr_keyboard_modifiers *modifiers) {
wlr_seat_keyboard_send_modifiers(grab->seat, modifiers);
}
static void xdg_keyboard_grab_cancel(struct wlr_seat_keyboard_grab *grab) {
wlr_seat_pointer_end_grab(grab->seat);
}
static const struct wlr_keyboard_grab_interface xdg_keyboard_grab_impl = {
.enter = xdg_keyboard_grab_enter,
.clear_focus = xdg_keyboard_grab_clear_focus,
.key = xdg_keyboard_grab_key,
.modifiers = xdg_keyboard_grab_modifiers,
.cancel = xdg_keyboard_grab_cancel,
};
static uint32_t xdg_touch_grab_down(struct wlr_seat_touch_grab *grab,
uint32_t time, struct wlr_touch_point *point) {
struct wlr_xdg_popup_grab *popup_grab = grab->data;
if (wl_resource_get_client(point->surface->resource) != popup_grab->client) {
xdg_popup_grab_end(grab->data);
return 0;
}
return wlr_seat_touch_send_down(grab->seat, point->surface, time,
point->touch_id, point->sx, point->sy);
}
static void xdg_touch_grab_up(struct wlr_seat_touch_grab *grab,
uint32_t time, struct wlr_touch_point *point) {
wlr_seat_touch_send_up(grab->seat, time, point->touch_id);
}
static void xdg_touch_grab_motion(struct wlr_seat_touch_grab *grab,
uint32_t time, struct wlr_touch_point *point) {
wlr_seat_touch_send_motion(grab->seat, time, point->touch_id, point->sx,
point->sy);
}
static void xdg_touch_grab_enter(struct wlr_seat_touch_grab *grab,
uint32_t time, struct wlr_touch_point *point) {
}
static void xdg_touch_grab_frame(struct wlr_seat_touch_grab *grab) {
wlr_seat_touch_send_frame(grab->seat);
}
static void xdg_touch_grab_cancel(struct wlr_seat_touch_grab *grab) {
wlr_seat_touch_end_grab(grab->seat);
}
static const struct wlr_touch_grab_interface xdg_touch_grab_impl = {
.down = xdg_touch_grab_down,
.up = xdg_touch_grab_up,
.motion = xdg_touch_grab_motion,
.enter = xdg_touch_grab_enter,
.frame = xdg_touch_grab_frame,
.cancel = xdg_touch_grab_cancel
};
static void destroy_xdg_popup_grab(struct wlr_xdg_popup_grab *xdg_grab) {
if (xdg_grab == NULL) {
return;
}
wl_list_remove(&xdg_grab->seat_destroy.link);
struct wlr_xdg_popup *popup, *tmp;
wl_list_for_each_safe(popup, tmp, &xdg_grab->popups, grab_link) {
wlr_xdg_popup_destroy(popup);
}
wl_list_remove(&xdg_grab->link);
free(xdg_grab);
}
static void xdg_popup_grab_handle_seat_destroy(
struct wl_listener *listener, void *data) {
struct wlr_xdg_popup_grab *xdg_grab =
wl_container_of(listener, xdg_grab, seat_destroy);
destroy_xdg_popup_grab(xdg_grab);
}
static struct wlr_xdg_popup_grab *get_xdg_shell_popup_grab_from_seat(
struct wlr_xdg_shell *shell, struct wlr_seat *seat) {
struct wlr_xdg_popup_grab *xdg_grab;
wl_list_for_each(xdg_grab, &shell->popup_grabs, link) {
if (xdg_grab->seat == seat) {
return xdg_grab;
}
}
xdg_grab = calloc(1, sizeof(*xdg_grab));
if (!xdg_grab) {
return NULL;
}
xdg_grab->pointer_grab.data = xdg_grab;
xdg_grab->pointer_grab.interface = &xdg_pointer_grab_impl;
xdg_grab->keyboard_grab.data = xdg_grab;
xdg_grab->keyboard_grab.interface = &xdg_keyboard_grab_impl;
xdg_grab->touch_grab.data = xdg_grab;
xdg_grab->touch_grab.interface = &xdg_touch_grab_impl;
wl_list_init(&xdg_grab->popups);
wl_list_insert(&shell->popup_grabs, &xdg_grab->link);
xdg_grab->seat = seat;
xdg_grab->seat_destroy.notify = xdg_popup_grab_handle_seat_destroy;
wl_signal_add(&seat->events.destroy, &xdg_grab->seat_destroy);
return xdg_grab;
}
void handle_xdg_popup_client_commit(struct wlr_xdg_popup *popup) {
if (!popup->parent) {
wlr_surface_reject_pending(popup->base->surface, popup->base->resource,
XDG_SURFACE_ERROR_NOT_CONSTRUCTED, "xdg_popup has no parent");
return;
}
}
static const struct xdg_popup_interface xdg_popup_implementation;
struct wlr_xdg_popup *wlr_xdg_popup_from_resource(
struct wl_resource *resource) {
assert(wl_resource_instance_of(resource, &xdg_popup_interface,
&xdg_popup_implementation));
return wl_resource_get_user_data(resource);
}
struct wlr_xdg_popup *wlr_xdg_popup_try_from_wlr_surface(struct wlr_surface *surface) {
struct wlr_xdg_surface *xdg_surface = wlr_xdg_surface_try_from_wlr_surface(surface);
if (xdg_surface == NULL || xdg_surface->role != WLR_XDG_SURFACE_ROLE_POPUP) {
return NULL;
}
return xdg_surface->popup;
}
static void xdg_popup_handle_grab(struct wl_client *client,
struct wl_resource *resource, struct wl_resource *seat_resource,
uint32_t serial) {
struct wlr_xdg_popup *popup =
wlr_xdg_popup_from_resource(resource);
if (!popup) {
return;
}
struct wlr_seat_client *seat_client =
wlr_seat_client_from_resource(seat_resource);
if (seat_client == NULL) {
wlr_xdg_popup_destroy(popup);
return;
}
if (popup->base->surface->mapped) {
wl_resource_post_error(popup->resource,
XDG_POPUP_ERROR_INVALID_GRAB,
"xdg_popup is already mapped");
return;
}
struct wlr_xdg_popup_grab *popup_grab = get_xdg_shell_popup_grab_from_seat(
popup->base->client->shell, seat_client->seat);
if (!wl_list_empty(&popup->base->popups)) {
wl_resource_post_error(popup->base->client->resource,
XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP,
"xdg_popup was not created on the topmost popup");
return;
}
popup_grab->client = popup->base->client->client;
popup->seat = seat_client->seat;
wl_list_insert(&popup_grab->popups, &popup->grab_link);
wlr_seat_pointer_start_grab(seat_client->seat,
&popup_grab->pointer_grab);
wlr_seat_keyboard_start_grab(seat_client->seat,
&popup_grab->keyboard_grab);
wlr_seat_touch_start_grab(seat_client->seat,
&popup_grab->touch_grab);
}
static void xdg_popup_handle_reposition(
struct wl_client *client, struct wl_resource *resource,
struct wl_resource *positioner_resource, uint32_t token) {
struct wlr_xdg_popup *popup =
wlr_xdg_popup_from_resource(resource);
if (!popup) {
return;
}
struct wlr_xdg_positioner *positioner =
wlr_xdg_positioner_from_resource(positioner_resource);
wlr_xdg_positioner_rules_get_geometry(
&positioner->rules, &popup->scheduled.geometry);
popup->scheduled.rules = positioner->rules;
popup->scheduled.fields |= WLR_XDG_POPUP_CONFIGURE_REPOSITION_TOKEN;
popup->scheduled.reposition_token = token;
wlr_xdg_surface_schedule_configure(popup->base);
wl_signal_emit_mutable(&popup->events.reposition, NULL);
}
static void xdg_popup_handle_destroy(struct wl_client *client,
struct wl_resource *resource) {
struct wlr_xdg_popup *popup =
wlr_xdg_popup_from_resource(resource);
if (popup && !wl_list_empty(&popup->base->popups)) {
wl_resource_post_error(popup->base->client->resource,
XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP,
"xdg_popup was destroyed while it was not the topmost popup");
return;
}
wl_resource_destroy(resource);
}
static const struct xdg_popup_interface xdg_popup_implementation = {
.destroy = xdg_popup_handle_destroy,
.grab = xdg_popup_handle_grab,
.reposition = xdg_popup_handle_reposition,
};
static const struct wlr_surface_synced_impl surface_synced_impl = {
.state_size = sizeof(struct wlr_xdg_popup_state),
};
static void xdg_popup_handle_resource_destroy(struct wl_resource *resource) {
struct wlr_xdg_popup *popup =
wlr_xdg_popup_from_resource(resource);
if (popup == NULL) {
return;
}
wlr_xdg_popup_destroy(popup);
}
void create_xdg_popup(struct wlr_xdg_surface *surface,
struct wlr_xdg_surface *parent,
struct wlr_xdg_positioner *positioner, uint32_t id) {
if (positioner->rules.size.width == 0 ||
positioner->rules.anchor_rect.width == 0) {
wl_resource_post_error(surface->client->resource,
XDG_WM_BASE_ERROR_INVALID_POSITIONER,
"positioner object is not complete");
return;
}
if (!set_xdg_surface_role(surface, WLR_XDG_SURFACE_ROLE_POPUP)) {
return;
}
if (parent != NULL && parent->role == WLR_XDG_SURFACE_ROLE_NONE) {
wl_resource_post_error(surface->client->resource, XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT,
"a popup parent must have a role");
return;
}
assert(surface->popup == NULL);
surface->popup = calloc(1, sizeof(*surface->popup));
if (!surface->popup) {
wl_resource_post_no_memory(surface->resource);
return;
}
surface->popup->base = surface;
if (!wlr_surface_synced_init(&surface->popup->synced, surface->surface,
&surface_synced_impl, &surface->popup->pending,
&surface->popup->current)) {
goto error_popup;
}
surface->popup->resource = wl_resource_create(
surface->client->client, &xdg_popup_interface,
wl_resource_get_version(surface->resource), id);
if (surface->popup->resource == NULL) {
goto error_synced;
}
wl_resource_set_implementation(surface->popup->resource,
&xdg_popup_implementation, surface->popup,
xdg_popup_handle_resource_destroy);
surface->role = WLR_XDG_SURFACE_ROLE_POPUP;
wlr_xdg_positioner_rules_get_geometry(
&positioner->rules, &surface->popup->scheduled.geometry);
surface->popup->scheduled.rules = positioner->rules;
wl_signal_init(&surface->popup->events.destroy);
wl_signal_init(&surface->popup->events.reposition);
if (parent) {
surface->popup->parent = parent->surface;
wl_list_insert(&parent->popups, &surface->popup->link);
wl_signal_emit_mutable(&parent->events.new_popup, surface->popup);
} else {
wl_list_init(&surface->popup->link);
}
set_xdg_surface_role_object(surface, surface->popup->resource);
wl_signal_emit_mutable(&surface->client->shell->events.new_popup, surface->popup);
return;
error_synced:
wlr_surface_synced_finish(&surface->popup->synced);
error_popup:
free(surface->popup);
surface->popup = NULL;
wl_resource_post_no_memory(surface->resource);
}
void reset_xdg_popup(struct wlr_xdg_popup *popup) {
if (popup->seat != NULL) {
struct wlr_xdg_popup_grab *grab =
get_xdg_shell_popup_grab_from_seat(
popup->base->client->shell, popup->seat);
wl_list_remove(&popup->grab_link);
if (wl_list_empty(&grab->popups)) {
if (grab->seat->pointer_state.grab == &grab->pointer_grab) {
wlr_seat_pointer_end_grab(grab->seat);
}
if (grab->seat->keyboard_state.grab == &grab->keyboard_grab) {
wlr_seat_keyboard_end_grab(grab->seat);
}
if (grab->seat->touch_state.grab == &grab->touch_grab) {
wlr_seat_touch_end_grab(grab->seat);
}
destroy_xdg_popup_grab(grab);
}
popup->seat = NULL;
}
}
void destroy_xdg_popup(struct wlr_xdg_popup *popup) {
wlr_surface_unmap(popup->base->surface);
reset_xdg_popup(popup);
wl_signal_emit_mutable(&popup->events.destroy, NULL);
wlr_surface_synced_finish(&popup->synced);
popup->base->popup = NULL;
wl_list_remove(&popup->link);
wl_resource_set_user_data(popup->resource, NULL);
free(popup);
}
void wlr_xdg_popup_destroy(struct wlr_xdg_popup *popup) {
if (popup == NULL) {
return;
}
struct wlr_xdg_popup *child, *child_tmp;
wl_list_for_each_safe(child, child_tmp, &popup->base->popups, link) {
wlr_xdg_popup_destroy(child);
}
xdg_popup_send_popup_done(popup->resource);
destroy_xdg_popup(popup);
}
void wlr_xdg_popup_get_toplevel_coords(struct wlr_xdg_popup *popup,
int popup_sx, int popup_sy, int *toplevel_sx, int *toplevel_sy) {
struct wlr_surface *parent = popup->parent;
struct wlr_xdg_surface *xdg_surface;
while ((xdg_surface = wlr_xdg_surface_try_from_wlr_surface(parent))) {
if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP && xdg_surface->popup != NULL) {
popup_sx += xdg_surface->popup->current.geometry.x;
popup_sy += xdg_surface->popup->current.geometry.y;
parent = xdg_surface->popup->parent;
} else {
popup_sx += xdg_surface->current.geometry.x;
popup_sy += xdg_surface->current.geometry.y;
break;
}
}
assert(parent);
*toplevel_sx = popup_sx;
*toplevel_sy = popup_sy;
}
void wlr_xdg_popup_unconstrain_from_box(struct wlr_xdg_popup *popup,
const struct wlr_box *toplevel_space_box) {
int toplevel_sx, toplevel_sy;
wlr_xdg_popup_get_toplevel_coords(popup,
0, 0, &toplevel_sx, &toplevel_sy);
struct wlr_box popup_constraint = {
.x = toplevel_space_box->x - toplevel_sx,
.y = toplevel_space_box->y - toplevel_sy,
.width = toplevel_space_box->width,
.height = toplevel_space_box->height,
};
wlr_xdg_positioner_rules_unconstrain_box(&popup->scheduled.rules,
&popup_constraint, &popup->scheduled.geometry);
wlr_xdg_surface_schedule_configure(popup->base);
}