diyac/xdg.c
2024-04-17 01:08:26 +02:00

456 lines
18 KiB
C

#define _POSIX_C_SOURCE 200112L
#include <wlr/util/log.h>
#include <assert.h>
#include <stdlib.h>
#include "xdg.h"
#include "cursor.h"
#include "node.h"
#include "view.h"
#include "output.h"
#include "foreign.h"
static void xdg_popup_create(struct diyac_view *view, struct wlr_xdg_popup *wlr_popup);
static void begin_interactive(struct diyac_view *toplevel,
enum diyac_cursor_mode mode, uint32_t edges)
{
/* This function sets up an interactive move or resize operation, where the
* compositor stops propagating pointer events to clients and instead
* consumes them itself, to move or resize windows. */
struct diyac_server *server = toplevel->server;
struct wlr_surface *focused_surface =
server->seat.wlr_seat->pointer_state.focused_surface;
if (toplevel->xdg_toplevel->base->surface !=
wlr_surface_get_root_surface(focused_surface))
{
/* Deny move/resize requests from unfocused clients. */
return;
}
server->grabbed_view = toplevel;
server->seat.cursor_mode = mode;
if (mode == DIYAC_CURSOR_MOVE)
{
server->grab_x = server->seat.cursor->x - toplevel->scene_tree->node.x;
server->grab_y = server->seat.cursor->y - toplevel->scene_tree->node.y;
}
else
{
struct wlr_box geo_box;
wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &geo_box);
double border_x = (toplevel->scene_tree->node.x + geo_box.x) +
((edges & WLR_EDGE_RIGHT) ? geo_box.width : 0);
double border_y = (toplevel->scene_tree->node.y + geo_box.y) +
((edges & WLR_EDGE_BOTTOM) ? geo_box.height : 0);
server->grab_x = server->seat.cursor->x - border_x;
server->grab_y = server->seat.cursor->y - border_y;
server->grab_geobox = geo_box;
server->grab_geobox.x += toplevel->scene_tree->node.x;
server->grab_geobox.y += toplevel->scene_tree->node.y;
server->resize_edges = edges;
}
}
static void xdg_toplevel_commit(struct wl_listener *listener, void *data)
{
struct diyac_view *view = wl_container_of(listener, view, commit);
uint32_t serial = view->configuration_serial;
if (serial > 0 && serial == view->xdg_surface->current.configure_serial)
{
wl_event_source_remove(view->configuration_timeout);
view->configuration_serial = 0;
view->configuration_timeout = NULL;
// TODO move view
}
diyac_view_sync_geo(view);
}
static void xdg_toplevel_map(struct wl_listener *listener, void *data)
{
/* Called when the surface is mapped, or ready to display on-screen. */
struct diyac_view *toplevel = wl_container_of(listener, toplevel, map);
if (toplevel->mapped)
{
return;
}
/*
wlr_xdg_toplevel_set_wm_capabilities(toplevel->xdg_toplevel,
WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE |
WLR_XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE);
//WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN
*/
wlr_xdg_surface_get_geometry(toplevel->xdg_toplevel->base, &toplevel->pending_size);
wlr_scene_node_set_enabled(&toplevel->scene_tree->node, true);
toplevel->mapped = true;
wl_list_insert(&toplevel->server->views, &toplevel->link);
toplevel->commit.notify = xdg_toplevel_commit;
wl_signal_add(&toplevel->xdg_surface->surface->events.commit, &toplevel->commit);
diyac_view_update_app_id(toplevel);
diyac_view_update_title(toplevel);
toplevel->pending_size.x = (toplevel->output->usable_area.width - toplevel->pending_size.width) / 2;
toplevel->pending_size.y = (toplevel->output->usable_area.height - toplevel->pending_size.height) / 2;
if (toplevel->pending_size.width > toplevel->output->usable_area.width)
{
toplevel->pending_size.width = toplevel->output->usable_area.width;
}
if (toplevel->pending_size.height > toplevel->output->usable_area.height)
{
toplevel->pending_size.height = toplevel->output->usable_area.height;
}
diyac_view_update_geometry(toplevel, false);
diyac_focus_view(toplevel, false);
}
static void xdg_toplevel_unmap(struct wl_listener *listener, void *data)
{
/* Called when the surface is unmapped, and should no longer be shown. */
struct diyac_view *toplevel = wl_container_of(listener, toplevel, unmap);
toplevel->mapped = false;
/* Reset the cursor mode if the grabbed toplevel was unmapped. */
if (toplevel == toplevel->server->grabbed_view)
{
diyac_reset_cursor_mode(toplevel->server);
}
if (toplevel->state.fullscreen && toplevel->output)
{
/**
* When a client exit during fullscreen mode, the top layer shall
* be restored as it is currently disabled
*/
wlr_scene_node_set_enabled(&toplevel->output->scenes.top->node, true);
}
if (toplevel->server->active_view == toplevel)
{
toplevel->server->active_view = NULL;
}
struct diyac_view *root = diyac_get_root_view(toplevel);
if (root && root->mapped)
{
diyac_focus_view(root, true);
// wlr_log(WLR_INFO, "focus root");
}
else
{
// wlr_log(WLR_INFO, "focus topmost");
diyac_focus_topmost_view(toplevel->server, true);
}
wl_list_remove(&toplevel->link);
}
static void xdg_toplevel_request_move(
struct wl_listener *listener, void *data)
{
/* This event is raised when a client would like to begin an interactive
* move, typically because the user clicked on their client-side
* decorations. Note that a more sophisticated compositor should check the
* provided serial against a list of button press serials sent to this
* client, to prevent the client from requesting this whenever they want. */
struct diyac_view *toplevel = wl_container_of(listener, toplevel, request_move);
begin_interactive(toplevel, DIYAC_CURSOR_MOVE, 0);
}
static void xdg_toplevel_request_resize(
struct wl_listener *listener, void *data)
{
/* This event is raised when a client would like to begin an interactive
* resize, typically because the user clicked on their client-side
* decorations. Note that a more sophisticated compositor should check the
* provided serial against a list of button press serials sent to this
* client, to prevent the client from requesting this whenever they want. */
struct wlr_xdg_toplevel_resize_event *event = data;
struct diyac_view *toplevel = wl_container_of(listener, toplevel, request_resize);
begin_interactive(toplevel, DIYAC_CURSOR_RESIZE, event->edges);
}
static void xdg_toplevel_request_maximize(
struct wl_listener *listener, void *data)
{
/* This event is raised when a client would like to maximize itself,
* typically because the user clicked on the maximize button on
* client-side decorations. diyac doesn't support maximization, but
* to conform to xdg-shell protocol we still must send a configure.
* wlr_xdg_surface_schedule_configure() is used to send an empty reply. */
struct diyac_view *toplevel =
wl_container_of(listener, toplevel, request_maximize);
diyac_view_set_maximize(toplevel, toplevel->xdg_toplevel->requested.maximized);
}
static void xdg_toplevel_request_fullscreen(struct wl_listener *listener, void *data)
{
struct diyac_view *toplevel =
wl_container_of(listener, toplevel, request_fullscreen);
diyac_view_set_fullscreen(toplevel, toplevel->xdg_toplevel->requested.fullscreen);
}
static void xdg_toplevel_request_minimize(struct wl_listener *listener, void *data)
{
struct diyac_view *toplevel =
wl_container_of(listener, toplevel, request_minimize);
diyac_view_set_mimimize(toplevel, toplevel->xdg_toplevel->requested.minimized);
}
static void xdg_toplevel_destroy(struct wl_listener *listener, void *data)
{
/* Called when the xdg_toplevel is destroyed. */
struct diyac_view *toplevel = wl_container_of(listener, toplevel, destroy);
if (toplevel->toplevel.handle)
{
wlr_foreign_toplevel_handle_v1_destroy(toplevel->toplevel.handle);
}
if (toplevel->configuration_timeout)
{
wl_event_source_remove(toplevel->configuration_timeout);
toplevel->configuration_timeout = NULL;
}
if(toplevel->mapped)
{
wl_list_remove(&toplevel->commit.link);
}
wl_list_remove(&toplevel->map.link);
wl_list_remove(&toplevel->unmap.link);
wl_list_remove(&toplevel->destroy.link);
wl_list_remove(&toplevel->request_move.link);
wl_list_remove(&toplevel->request_resize.link);
wl_list_remove(&toplevel->request_maximize.link);
wl_list_remove(&toplevel->request_fullscreen.link);
wl_list_remove(&toplevel->set_app_id.link);
wl_list_remove(&toplevel->new_popup.link);
wl_list_remove(&toplevel->set_title.link);
free(toplevel);
}
static void handle_xdg_popup_destroy(struct wl_listener *listener, void *data)
{
struct diyac_popup *popup = wl_container_of(listener, popup, destroy);
wl_list_remove(&popup->destroy.link);
wl_list_remove(&popup->new_popup.link);
/* Usually already removed unless there was no commit at all */
if (popup->commit.notify)
{
wl_list_remove(&popup->commit.link);
}
free(popup);
}
static void popup_unconstrain(struct diyac_popup *popup)
{
struct diyac_view *view = popup->parent;
if(!view->output)
{
return;
}
struct diyac_server *server = view->server;
struct wlr_output_layout *output_layout = server->output_layout;
struct wlr_output *wlr_output = view->output->wlr_output;
struct wlr_box usable = {
.x = 0,
.y = 0,
.width = view->output->wlr_output->width,
.height = view->output->wlr_output->height};
struct diyac_view *root = diyac_get_root_view(view);
if (!root || !root->state.fullscreen)
{
usable = view->output->usable_area;
}
struct wlr_box geo_box = diyac_view_get_geometry(view);
struct wlr_box output_toplevel_box = {
.x = usable.x - geo_box.x,
.y = usable.y - geo_box.y,
.width = usable.width,
.height = usable.height,
};
wlr_xdg_popup_unconstrain_from_box(popup->wlr_popup, &output_toplevel_box);
}
static void handle_xdg_popup_commit(struct wl_listener *listener, void *data)
{
struct diyac_popup *popup = wl_container_of(listener, popup, commit);
struct wlr_box popup_box;
wlr_xdg_surface_get_geometry(popup->wlr_popup->base, &popup_box);
popup_unconstrain(popup);
if (!wlr_box_empty(&popup_box))
// if (popup->wlr_popup->base->initial_commit)
{
struct diyac_view *view = popup->parent;
// wlr_output_commit(view->output->wlr_output);
/* Prevent getting called over and over again */
wl_list_remove(&popup->commit.link);
popup->commit.notify = NULL;
// force commit output
}
}
static void handle_xdg_popup_new(struct wl_listener *listener, void *data)
{
struct diyac_popup *popup = wl_container_of(listener, popup, new_popup);
struct wlr_xdg_popup *wlr_popup = data;
xdg_popup_create(popup->parent, wlr_popup);
}
static void xdg_popup_create(struct diyac_view *view, struct wlr_xdg_popup *wlr_popup)
{
struct wlr_xdg_surface *parent =
wlr_xdg_surface_try_from_wlr_surface(wlr_popup->parent);
if (!parent)
{
wlr_log(WLR_ERROR, "parent is not a valid XDG surface");
return;
}
struct diyac_popup *popup = calloc(1, sizeof(*popup));
popup->parent = view;
popup->wlr_popup = wlr_popup;
popup->destroy.notify = handle_xdg_popup_destroy;
wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy);
popup->new_popup.notify = handle_xdg_popup_new;
wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup);
popup->commit.notify = handle_xdg_popup_commit;
wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit);
/*
* We must add xdg popups to the scene graph so they get rendered. The
* wlroots scene graph provides a helper for this, but to use it we must
* provide the proper parent scene node of the xdg popup. To enable
* this, we always set the user data field of xdg_surfaces to the
* corresponding scene node.
*
* xdg-popups live in server->xdg_popup_tree so that they can be
* rendered above always-on-top windows
*/
struct wlr_scene_tree *parent_tree = NULL;
if (parent->role == WLR_XDG_SURFACE_ROLE_POPUP)
{
parent_tree = parent->surface->data;
}
else
{
parent_tree = view->server->xdg_popup_tree;
struct wlr_box box = diyac_view_get_geometry(view);
wlr_scene_node_set_position(&view->server->xdg_popup_tree->node,
box.x, box.y);
}
struct wlr_scene_tree *tree = wlr_scene_xdg_surface_create(parent_tree, wlr_popup->base);
wlr_popup->base->surface->data = tree;
// wlr_scene_node_set_enabled(&parent_tree->node, false);
diyac_node_descriptor_create(wlr_popup->base->surface->data,
DIYAC_NODE_XDG_POPUP, view);
}
static void xdg_set_appid_notify(struct wl_listener *listener, void *data)
{
struct diyac_view *view = wl_container_of(listener, view, set_app_id);
diyac_view_update_app_id(view);
}
static void xdg_set_title_notify(struct wl_listener *listener, void *data)
{
struct diyac_view *view = wl_container_of(listener, view, set_title);
diyac_view_update_title(view);
}
static void xdg_new_popup_notify(struct wl_listener *listener, void *data)
{
struct diyac_view *view =
wl_container_of(listener, view, new_popup);
struct wlr_xdg_popup *wlr_popup = data;
xdg_popup_create(view, wlr_popup);
}
void diyac_new_xdg_surface(struct wl_listener *listener, void *data)
{
/* This event is raised when wlr_xdg_shell receives a new xdg surface from a
* client, either a toplevel (application window) or popup. */
struct diyac_server *server =
wl_container_of(listener, server, new_xdg_surface);
struct wlr_xdg_surface *xdg_surface = data;
/* We must add xdg popups to the scene graph so they get rendered. The
* wlroots scene graph provides a helper for this, but to use it we must
* provide the proper parent scene node of the xdg popup. To enable this,
* we always set the user data field of xdg_surfaces to the corresponding
* scene node. */
if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP)
{
/*struct wlr_xdg_surface *parent =
wlr_xdg_surface_try_from_wlr_surface(xdg_surface->popup->parent);
assert(parent != NULL);
struct wlr_scene_tree *parent_tree = parent->data;
xdg_surface->data = wlr_scene_xdg_surface_create(
parent_tree, xdg_surface);
return;*/
wlr_log(WLR_INFO, "diyac_new_xdg_surface: Creating new dialog using view popup");
return;
}
assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
wlr_log(WLR_INFO, "diyac_new_xdg_surface: Creating new application windows");
wlr_xdg_surface_ping(xdg_surface);
/* Allocate a diyac_view for this surface */
struct diyac_view *toplevel = calloc(1, sizeof(*toplevel));
memset(&toplevel->state, 0, sizeof(toplevel->state));
memset(&toplevel->requested, 0, sizeof(toplevel->requested));
toplevel->server = server;
toplevel->xdg_toplevel = xdg_surface->toplevel;
toplevel->xdg_surface = xdg_surface;
toplevel->mapped = false;
toplevel->configuration_timeout = NULL;
toplevel->output = diyac_output_from_cursor(server);
toplevel->scene_tree = wlr_scene_xdg_surface_create(
toplevel->server->view_tree, toplevel->xdg_toplevel->base);
xdg_surface->data = toplevel;
if (toplevel->output)
{
wlr_fractional_scale_v1_notify_scale(xdg_surface->surface,
toplevel->output->wlr_output->scale);
}
wlr_scene_node_set_enabled(&toplevel->scene_tree->node, false);
diyac_node_descriptor_create(&toplevel->scene_tree->node,
DIYAC_NODE_VIEW, toplevel);
diyac_init_foreign_toplevel(toplevel);
/* Listen to the various events it can emit */
toplevel->map.notify = xdg_toplevel_map;
wl_signal_add(&xdg_surface->surface->events.map, &toplevel->map);
toplevel->unmap.notify = xdg_toplevel_unmap;
wl_signal_add(&xdg_surface->surface->events.unmap, &toplevel->unmap);
toplevel->destroy.notify = xdg_toplevel_destroy;
wl_signal_add(&xdg_surface->events.destroy, &toplevel->destroy);
/* cotd */
struct wlr_xdg_toplevel *xdg_toplevel = xdg_surface->toplevel;
toplevel->request_move.notify = xdg_toplevel_request_move;
wl_signal_add(&xdg_toplevel->events.request_move, &toplevel->request_move);
toplevel->request_resize.notify = xdg_toplevel_request_resize;
wl_signal_add(&xdg_toplevel->events.request_resize, &toplevel->request_resize);
toplevel->request_minimize.notify = xdg_toplevel_request_minimize;
wl_signal_add(&xdg_toplevel->events.request_minimize, &toplevel->request_minimize);
toplevel->request_maximize.notify = xdg_toplevel_request_maximize;
wl_signal_add(&xdg_toplevel->events.request_maximize,
&toplevel->request_maximize);
toplevel->request_fullscreen.notify = xdg_toplevel_request_fullscreen;
wl_signal_add(&xdg_toplevel->events.request_fullscreen,
&toplevel->request_fullscreen);
toplevel->new_popup.notify = xdg_new_popup_notify;
wl_signal_add(&xdg_surface->events.new_popup, &toplevel->new_popup);
toplevel->set_app_id.notify = xdg_set_appid_notify;
wl_signal_add(&xdg_toplevel->events.set_app_id, &toplevel->set_app_id);
toplevel->set_title.notify = xdg_set_title_notify;
wl_signal_add(&xdg_toplevel->events.set_title, &toplevel->set_title);
}