mirror of
https://github.com/labwc/labwc.git
synced 2025-11-04 13:30:07 -05:00
For views that are initially maximized or fullscreen and have no explicitly specified position, we need to center the stored natural geometry, or the view may end up partially offscreen once unmaximized/ unfullscreened.
901 lines
27 KiB
C
901 lines
27 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
#define _POSIX_C_SOURCE 200809L
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <wlr/xwayland.h>
|
|
#include "common/mem.h"
|
|
#include "labwc.h"
|
|
#include "node.h"
|
|
#include "ssd.h"
|
|
#include "view.h"
|
|
#include "view-impl-common.h"
|
|
#include "window-rules.h"
|
|
#include "workspaces.h"
|
|
#include "xwayland.h"
|
|
|
|
static struct view_size_hints
|
|
xwayland_view_get_size_hints(struct view *view)
|
|
{
|
|
xcb_size_hints_t *hints = xwayland_surface_from_view(view)->size_hints;
|
|
if (!hints) {
|
|
return (struct view_size_hints){0};
|
|
}
|
|
return (struct view_size_hints){
|
|
.min_width = hints->min_width,
|
|
.min_height = hints->min_height,
|
|
.width_inc = hints->width_inc,
|
|
.height_inc = hints->height_inc,
|
|
.base_width = hints->base_width,
|
|
.base_height = hints->base_height,
|
|
};
|
|
}
|
|
|
|
static enum view_wants_focus
|
|
xwayland_view_wants_focus(struct view *view)
|
|
{
|
|
struct wlr_xwayland_surface *xsurface =
|
|
xwayland_surface_from_view(view);
|
|
|
|
switch (wlr_xwayland_icccm_input_model(xsurface)) {
|
|
/*
|
|
* Abbreviated from ICCCM section 4.1.7 (Input Focus):
|
|
*
|
|
* Passive Input - The client expects keyboard input but never
|
|
* explicitly sets the input focus.
|
|
* Locally Active Input - The client expects keyboard input and
|
|
* explicitly sets the input focus, but it only does so when one
|
|
* of its windows already has the focus.
|
|
*
|
|
* Passive and Locally Active clients set the input field of
|
|
* WM_HINTS to True, which indicates that they require window
|
|
* manager assistance in acquiring the input focus.
|
|
*/
|
|
case WLR_ICCCM_INPUT_MODEL_PASSIVE:
|
|
case WLR_ICCCM_INPUT_MODEL_LOCAL:
|
|
return VIEW_WANTS_FOCUS_ALWAYS;
|
|
|
|
/*
|
|
* Globally Active Input - The client expects keyboard input and
|
|
* explicitly sets the input focus, even when it is in windows
|
|
* the client does not own. ... It wants to prevent the window
|
|
* manager from setting the input focus to any of its windows
|
|
* [because it may or may not want focus].
|
|
*
|
|
* Globally Active client windows may receive a WM_TAKE_FOCUS
|
|
* message from the window manager. If they want the focus, they
|
|
* should respond with a SetInputFocus request.
|
|
*
|
|
* [Currently, labwc does not fully support clients voluntarily
|
|
* taking focus via the WM_TAKE_FOCUS + SetInputFocus mechanism.
|
|
* Instead, we try to guess whether the window wants focus based
|
|
* on some heuristics -- see below.]
|
|
*/
|
|
case WLR_ICCCM_INPUT_MODEL_GLOBAL:
|
|
/*
|
|
* Assume the window does want focus if it wants window
|
|
* decorations (according to _MOTIF_WM_HINTS). This is
|
|
* a stop-gap fix to ensure that various applications
|
|
* (mainly Java-based ones such as IntelliJ IDEA) get
|
|
* focus normally and appear in the window switcher. It
|
|
* would be better to match based on _NET_WM_WINDOW_TYPE
|
|
* instead, but that property isn't currently available
|
|
* through wlroots API.
|
|
*/
|
|
return (xsurface->decorations ==
|
|
WLR_XWAYLAND_SURFACE_DECORATIONS_ALL) ?
|
|
VIEW_WANTS_FOCUS_ALWAYS : VIEW_WANTS_FOCUS_OFFER;
|
|
|
|
/*
|
|
* No Input - The client never expects keyboard input.
|
|
*
|
|
* No Input and Globally Active clients set the input field to
|
|
* False, which requests that the window manager not set the
|
|
* input focus to their top-level window.
|
|
*/
|
|
case WLR_ICCCM_INPUT_MODEL_NONE:
|
|
break;
|
|
}
|
|
|
|
return VIEW_WANTS_FOCUS_NEVER;
|
|
}
|
|
|
|
static struct wlr_xwayland_surface *
|
|
top_parent_of(struct view *view)
|
|
{
|
|
struct wlr_xwayland_surface *s = xwayland_surface_from_view(view);
|
|
while (s->parent) {
|
|
s = s->parent;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
static struct xwayland_view *
|
|
xwayland_view_from_view(struct view *view)
|
|
{
|
|
assert(view->type == LAB_XWAYLAND_VIEW);
|
|
return (struct xwayland_view *)view;
|
|
}
|
|
|
|
struct wlr_xwayland_surface *
|
|
xwayland_surface_from_view(struct view *view)
|
|
{
|
|
struct xwayland_view *xwayland_view = xwayland_view_from_view(view);
|
|
assert(xwayland_view->xwayland_surface);
|
|
return xwayland_view->xwayland_surface;
|
|
}
|
|
|
|
static void
|
|
ensure_initial_geometry_and_output(struct view *view)
|
|
{
|
|
if (wlr_box_empty(&view->current)) {
|
|
struct wlr_xwayland_surface *xwayland_surface =
|
|
xwayland_surface_from_view(view);
|
|
view->current.x = xwayland_surface->x;
|
|
view->current.y = xwayland_surface->y;
|
|
view->current.width = xwayland_surface->width;
|
|
view->current.height = xwayland_surface->height;
|
|
/*
|
|
* If there is no pending move/resize yet, then set
|
|
* current values (used in map()).
|
|
*/
|
|
if (wlr_box_empty(&view->pending)) {
|
|
view->pending = view->current;
|
|
}
|
|
}
|
|
if (!view->output) {
|
|
/*
|
|
* Just use the cursor output since we don't know yet
|
|
* whether the surface position is meaningful.
|
|
*/
|
|
view_set_output(view, output_nearest_to_cursor(view->server));
|
|
}
|
|
}
|
|
|
|
static bool
|
|
want_deco(struct wlr_xwayland_surface *xwayland_surface)
|
|
{
|
|
struct view *view = (struct view *)xwayland_surface->data;
|
|
|
|
/* Window-rules take priority if they exist for this view */
|
|
switch (window_rules_get_property(view, "serverDecoration")) {
|
|
case LAB_PROP_TRUE:
|
|
return true;
|
|
case LAB_PROP_FALSE:
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return xwayland_surface->decorations ==
|
|
WLR_XWAYLAND_SURFACE_DECORATIONS_ALL;
|
|
}
|
|
|
|
static void
|
|
handle_commit(struct wl_listener *listener, void *data)
|
|
{
|
|
struct view *view = wl_container_of(listener, view, commit);
|
|
assert(data && data == view->surface);
|
|
|
|
/* Must receive commit signal before accessing surface->current* */
|
|
struct wlr_surface_state *state = &view->surface->current;
|
|
struct wlr_box *current = &view->current;
|
|
|
|
/*
|
|
* If there is a pending move/resize, wait until the surface
|
|
* size changes to update geometry. The hope is to update both
|
|
* the position and the size of the view at the same time,
|
|
* reducing visual glitches.
|
|
*/
|
|
if (current->width != state->width || current->height != state->height) {
|
|
view_impl_apply_geometry(view, state->width, state->height);
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_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 view *view = wl_container_of(listener, view, request_move);
|
|
interactive_begin(view, LAB_INPUT_STATE_MOVE, 0);
|
|
}
|
|
|
|
static void
|
|
handle_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_xwayland_resize_event *event = data;
|
|
struct view *view = wl_container_of(listener, view, request_resize);
|
|
interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges);
|
|
}
|
|
|
|
static void
|
|
handle_map(struct wl_listener *listener, void *data)
|
|
{
|
|
struct view *view = wl_container_of(listener, view, map);
|
|
view->impl->map(view);
|
|
}
|
|
|
|
static void
|
|
handle_unmap(struct wl_listener *listener, void *data)
|
|
{
|
|
struct view *view = wl_container_of(listener, view, unmap);
|
|
view->impl->unmap(view, /* client_request */ true);
|
|
}
|
|
|
|
static void
|
|
handle_surface_destroy(struct wl_listener *listener, void *data)
|
|
{
|
|
struct view *view = wl_container_of(listener, view, surface_destroy);
|
|
assert(data && data == view->surface);
|
|
|
|
view->surface = NULL;
|
|
wl_list_remove(&view->surface_destroy.link);
|
|
}
|
|
|
|
static void
|
|
handle_destroy(struct wl_listener *listener, void *data)
|
|
{
|
|
struct view *view = wl_container_of(listener, view, destroy);
|
|
struct xwayland_view *xwayland_view = xwayland_view_from_view(view);
|
|
assert(xwayland_view->xwayland_surface->data == view);
|
|
|
|
if (view->surface) {
|
|
/*
|
|
* We got the destroy signal from
|
|
* wlr_xwayland_surface before the
|
|
* destroy signal from wlr_surface.
|
|
*/
|
|
wl_list_remove(&view->surface_destroy.link);
|
|
}
|
|
view->surface = NULL;
|
|
|
|
/*
|
|
* Break view <-> xsurface association. Note that the xsurface
|
|
* may not actually be destroyed at this point; it may become an
|
|
* "unmanaged" surface instead (in that case it is important
|
|
* that xsurface->data not point to the destroyed view).
|
|
*/
|
|
xwayland_view->xwayland_surface->data = NULL;
|
|
xwayland_view->xwayland_surface = NULL;
|
|
|
|
/* Remove XWayland view specific listeners */
|
|
wl_list_remove(&xwayland_view->request_activate.link);
|
|
wl_list_remove(&xwayland_view->request_configure.link);
|
|
wl_list_remove(&xwayland_view->set_app_id.link);
|
|
wl_list_remove(&xwayland_view->set_decorations.link);
|
|
wl_list_remove(&xwayland_view->override_redirect.link);
|
|
|
|
view_destroy(view);
|
|
}
|
|
|
|
static void
|
|
xwayland_view_configure(struct view *view, struct wlr_box geo)
|
|
{
|
|
view->pending = geo;
|
|
wlr_xwayland_surface_configure(xwayland_surface_from_view(view),
|
|
geo.x, geo.y, geo.width, geo.height);
|
|
|
|
/* If not resizing, process the move immediately */
|
|
if (view->current.width == geo.width
|
|
&& view->current.height == geo.height) {
|
|
view->current.x = geo.x;
|
|
view->current.y = geo.y;
|
|
view_moved(view);
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_request_configure(struct wl_listener *listener, void *data)
|
|
{
|
|
struct xwayland_view *xwayland_view =
|
|
wl_container_of(listener, xwayland_view, request_configure);
|
|
struct view *view = &xwayland_view->base;
|
|
struct wlr_xwayland_surface_configure_event *event = data;
|
|
|
|
int width = event->width;
|
|
int height = event->height;
|
|
view_adjust_size(view, &width, &height);
|
|
|
|
xwayland_view_configure(view,
|
|
(struct wlr_box){event->x, event->y, width, height});
|
|
}
|
|
|
|
static void
|
|
handle_request_activate(struct wl_listener *listener, void *data)
|
|
{
|
|
struct xwayland_view *xwayland_view =
|
|
wl_container_of(listener, xwayland_view, request_activate);
|
|
struct view *view = &xwayland_view->base;
|
|
|
|
if (window_rules_get_property(view, "ignoreFocusRequest") == LAB_PROP_TRUE) {
|
|
wlr_log(WLR_INFO, "Ignoring focus request due to window rule configuration");
|
|
return;
|
|
}
|
|
|
|
desktop_focus_view(view, /*raise*/ true);
|
|
}
|
|
|
|
static void
|
|
handle_request_minimize(struct wl_listener *listener, void *data)
|
|
{
|
|
struct wlr_xwayland_minimize_event *event = data;
|
|
struct view *view = wl_container_of(listener, view, request_minimize);
|
|
view_minimize(view, event->minimize);
|
|
}
|
|
|
|
static void
|
|
handle_request_maximize(struct wl_listener *listener, void *data)
|
|
{
|
|
struct view *view = wl_container_of(listener, view, request_maximize);
|
|
if (!view->mapped) {
|
|
ensure_initial_geometry_and_output(view);
|
|
/*
|
|
* Set decorations early to avoid changing geometry
|
|
* after maximize (reduces visual glitches).
|
|
*/
|
|
view_set_decorations(view,
|
|
want_deco(xwayland_surface_from_view(view)));
|
|
}
|
|
view_toggle_maximize(view);
|
|
}
|
|
|
|
static void
|
|
handle_request_fullscreen(struct wl_listener *listener, void *data)
|
|
{
|
|
struct view *view = wl_container_of(listener, view, request_fullscreen);
|
|
bool fullscreen = xwayland_surface_from_view(view)->fullscreen;
|
|
if (!view->mapped) {
|
|
ensure_initial_geometry_and_output(view);
|
|
}
|
|
view_set_fullscreen(view, fullscreen);
|
|
}
|
|
|
|
static void
|
|
handle_set_title(struct wl_listener *listener, void *data)
|
|
{
|
|
struct view *view = wl_container_of(listener, view, set_title);
|
|
view_update_title(view);
|
|
}
|
|
|
|
static void
|
|
handle_set_class(struct wl_listener *listener, void *data)
|
|
{
|
|
struct xwayland_view *xwayland_view =
|
|
wl_container_of(listener, xwayland_view, set_app_id);
|
|
struct view *view = &xwayland_view->base;
|
|
view_update_app_id(view);
|
|
}
|
|
|
|
static void
|
|
xwayland_view_close(struct view *view)
|
|
{
|
|
wlr_xwayland_surface_close(xwayland_surface_from_view(view));
|
|
}
|
|
|
|
static const char *
|
|
xwayland_view_get_string_prop(struct view *view, const char *prop)
|
|
{
|
|
struct xwayland_view *xwayland_view = xwayland_view_from_view(view);
|
|
struct wlr_xwayland_surface *xwayland_surface = xwayland_view->xwayland_surface;
|
|
if (!xwayland_surface) {
|
|
/*
|
|
* This may happen due to a matchOnce rule when
|
|
* a view is destroyed while A-Tab is open. See
|
|
* https://github.com/labwc/labwc/issues/1082#issuecomment-1716137180
|
|
*/
|
|
return "";
|
|
}
|
|
|
|
if (!strcmp(prop, "title")) {
|
|
return xwayland_surface->title;
|
|
}
|
|
if (!strcmp(prop, "class")) {
|
|
return xwayland_surface->class;
|
|
}
|
|
/* We give 'class' for wlr_foreign_toplevel_handle_v1_set_app_id() */
|
|
if (!strcmp(prop, "app_id")) {
|
|
return xwayland_surface->class;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
static void
|
|
handle_set_decorations(struct wl_listener *listener, void *data)
|
|
{
|
|
struct xwayland_view *xwayland_view =
|
|
wl_container_of(listener, xwayland_view, set_decorations);
|
|
struct view *view = &xwayland_view->base;
|
|
|
|
view_set_decorations(view, want_deco(xwayland_view->xwayland_surface));
|
|
}
|
|
|
|
static void
|
|
handle_override_redirect(struct wl_listener *listener, void *data)
|
|
{
|
|
struct xwayland_view *xwayland_view =
|
|
wl_container_of(listener, xwayland_view, override_redirect);
|
|
struct view *view = &xwayland_view->base;
|
|
struct wlr_xwayland_surface *xsurface = xwayland_view->xwayland_surface;
|
|
|
|
struct server *server = view->server;
|
|
bool mapped = xsurface->mapped;
|
|
if (mapped) {
|
|
handle_unmap(&view->unmap, xsurface);
|
|
}
|
|
handle_destroy(&view->destroy, xsurface);
|
|
/* view is invalid after this point */
|
|
xwayland_unmanaged_create(server, xsurface, mapped);
|
|
}
|
|
|
|
static void
|
|
set_initial_position(struct view *view,
|
|
struct wlr_xwayland_surface *xwayland_surface)
|
|
{
|
|
/* Don't center views with position explicitly specified */
|
|
bool has_position = xwayland_surface->size_hints &&
|
|
(xwayland_surface->size_hints->flags & (
|
|
XCB_ICCCM_SIZE_HINT_US_POSITION |
|
|
XCB_ICCCM_SIZE_HINT_P_POSITION));
|
|
|
|
if (has_position) {
|
|
/*
|
|
* Make sure a floating view is onscreen. For a
|
|
* maximized/fullscreen view, do nothing; if it is
|
|
* unmaximized/leaves fullscreen later, we will make
|
|
* sure it is on-screen at that point.
|
|
*/
|
|
if (view_is_floating(view)) {
|
|
view_adjust_for_layout_change(view);
|
|
}
|
|
} else {
|
|
if (view_is_floating(view)) {
|
|
view_center(view, NULL);
|
|
} else {
|
|
/*
|
|
* View is maximized/fullscreen. Center the
|
|
* stored natural geometry without actually
|
|
* moving the view.
|
|
*/
|
|
view_compute_centered_position(view, NULL,
|
|
view->natural_geometry.width,
|
|
view->natural_geometry.height,
|
|
&view->natural_geometry.x,
|
|
&view->natural_geometry.y);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
top_left_edge_boundary_check(struct view *view)
|
|
{
|
|
struct wlr_box deco = ssd_max_extents(view);
|
|
if (deco.x < 0) {
|
|
view->current.x -= deco.x;
|
|
}
|
|
if (deco.y < 0) {
|
|
view->current.y -= deco.y;
|
|
}
|
|
view->impl->configure(view, view->current);
|
|
}
|
|
|
|
static void
|
|
init_foreign_toplevel(struct view *view)
|
|
{
|
|
foreign_toplevel_handle_create(view);
|
|
|
|
struct wlr_xwayland_surface *surface = xwayland_surface_from_view(view);
|
|
if (!surface->parent) {
|
|
return;
|
|
}
|
|
struct view *parent = (struct view *)surface->parent->data;
|
|
if (!parent->toplevel.handle) {
|
|
return;
|
|
}
|
|
wlr_foreign_toplevel_handle_v1_set_parent(view->toplevel.handle, parent->toplevel.handle);
|
|
}
|
|
|
|
static void
|
|
xwayland_view_map(struct view *view)
|
|
{
|
|
struct wlr_xwayland_surface *xwayland_surface = xwayland_surface_from_view(view);
|
|
if (view->mapped) {
|
|
return;
|
|
}
|
|
if (!xwayland_surface->surface) {
|
|
/*
|
|
* We may get here if a user minimizes an xwayland dialog at the
|
|
* same time as the client requests unmap, which xwayland
|
|
* clients sometimes do without actually requesting destroy
|
|
* even if they don't intend to use that view/surface anymore
|
|
*/
|
|
wlr_log(WLR_DEBUG, "Cannot map view without wlr_surface");
|
|
return;
|
|
}
|
|
view->mapped = true;
|
|
ensure_initial_geometry_and_output(view);
|
|
wlr_scene_node_set_enabled(&view->scene_tree->node, true);
|
|
|
|
/*
|
|
* Per the Extended Window Manager Hints (EWMH) spec: "The Window
|
|
* Manager SHOULD honor _NET_WM_STATE whenever a withdrawn window
|
|
* requests to be mapped."
|
|
*
|
|
* The following order of operations is intended to reduce the
|
|
* number of resize (Configure) events:
|
|
* 1. set fullscreen state
|
|
* 2. set decorations (depends on fullscreen state)
|
|
* 3. set maximized (geometry depends on decorations)
|
|
*
|
|
* TODO: support separate horizontal/vertical maximize
|
|
*/
|
|
bool maximize = xwayland_surface->maximized_horz
|
|
&& xwayland_surface->maximized_vert;
|
|
view_set_fullscreen(view, xwayland_surface->fullscreen);
|
|
view_set_decorations(view, want_deco(xwayland_surface));
|
|
view_maximize(view, maximize, /*store_natural_geometry*/ true);
|
|
|
|
if (view->surface != xwayland_surface->surface) {
|
|
if (view->surface) {
|
|
wl_list_remove(&view->surface_destroy.link);
|
|
}
|
|
view->surface = xwayland_surface->surface;
|
|
|
|
/* Required to set the surface to NULL when destroyed by the client */
|
|
view->surface_destroy.notify = handle_surface_destroy;
|
|
wl_signal_add(&view->surface->events.destroy, &view->surface_destroy);
|
|
|
|
/* Will be free'd automatically once the surface is being destroyed */
|
|
struct wlr_scene_tree *tree = wlr_scene_subsurface_tree_create(
|
|
view->scene_tree, view->surface);
|
|
if (!tree) {
|
|
/* TODO: might need further clean up */
|
|
wl_resource_post_no_memory(view->surface->resource);
|
|
return;
|
|
}
|
|
view->scene_node = &tree->node;
|
|
}
|
|
|
|
if (!view->toplevel.handle) {
|
|
init_foreign_toplevel(view);
|
|
}
|
|
|
|
if (!view->been_mapped) {
|
|
set_initial_position(view, xwayland_surface);
|
|
/*
|
|
* When mapping the view for the first time, visual
|
|
* artifacts are reduced if we display it immediately at
|
|
* the final intended position/size rather than waiting
|
|
* for handle_commit().
|
|
*/
|
|
view->current = view->pending;
|
|
view_moved(view);
|
|
}
|
|
|
|
if (view->ssd_enabled && view_is_floating(view)) {
|
|
top_left_edge_boundary_check(view);
|
|
}
|
|
|
|
/* Add commit here, as xwayland map/unmap can change the wlr_surface */
|
|
wl_signal_add(&xwayland_surface->surface->events.commit, &view->commit);
|
|
view->commit.notify = handle_commit;
|
|
|
|
view_impl_map(view);
|
|
view->been_mapped = true;
|
|
}
|
|
|
|
static void
|
|
xwayland_view_unmap(struct view *view, bool client_request)
|
|
{
|
|
if (!view->mapped) {
|
|
goto out;
|
|
}
|
|
view->mapped = false;
|
|
wl_list_remove(&view->commit.link);
|
|
wlr_scene_node_set_enabled(&view->scene_tree->node, false);
|
|
view_impl_unmap(view);
|
|
|
|
/*
|
|
* If the view was explicitly unmapped by the client (rather
|
|
* than just minimized), destroy the foreign toplevel handle so
|
|
* the unmapped view doesn't show up in panels and the like.
|
|
*/
|
|
out:
|
|
if (client_request && view->toplevel.handle) {
|
|
wlr_foreign_toplevel_handle_v1_destroy(view->toplevel.handle);
|
|
view->toplevel.handle = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
xwayland_view_maximize(struct view *view, bool maximized)
|
|
{
|
|
wlr_xwayland_surface_set_maximized(xwayland_surface_from_view(view),
|
|
maximized);
|
|
}
|
|
|
|
static void
|
|
xwayland_view_minimize(struct view *view, bool minimized)
|
|
{
|
|
wlr_xwayland_surface_set_minimized(xwayland_surface_from_view(view),
|
|
minimized);
|
|
}
|
|
|
|
static struct view *
|
|
xwayland_view_get_root(struct view *view)
|
|
{
|
|
struct wlr_xwayland_surface *root = top_parent_of(view);
|
|
return (struct view *)root->data;
|
|
}
|
|
|
|
static void
|
|
xwayland_view_move_to_front(struct view *view)
|
|
{
|
|
struct view *root = xwayland_view_get_root(view);
|
|
/* FIXME: this exact code is repeated in xdg.c */
|
|
view_impl_move_to_front(root);
|
|
view_impl_move_sub_views(root, LAB_TO_FRONT);
|
|
/* make sure view is in front of other sub-views */
|
|
if (view != root) {
|
|
view_impl_move_to_front(view);
|
|
}
|
|
}
|
|
|
|
static void
|
|
xwayland_view_move_to_back(struct view *view)
|
|
{
|
|
struct view *root = xwayland_view_get_root(view);
|
|
/* FIXME: this exact code is repeated in xdg.c */
|
|
view_impl_move_sub_views(root, LAB_TO_BACK);
|
|
view_impl_move_to_back(root);
|
|
}
|
|
|
|
static void
|
|
xwayland_view_append_children(struct view *self, struct wl_array *children)
|
|
{
|
|
struct wlr_xwayland_surface *surface = xwayland_surface_from_view(self);
|
|
struct view *view;
|
|
|
|
wl_list_for_each_reverse(view, &self->server->views, link)
|
|
{
|
|
if (view == self) {
|
|
continue;
|
|
}
|
|
if (view->type != LAB_XWAYLAND_VIEW) {
|
|
continue;
|
|
}
|
|
/*
|
|
* This happens when a view has never been mapped or when a
|
|
* client has requested a `handle_unmap`.
|
|
*/
|
|
if (!view->surface) {
|
|
continue;
|
|
}
|
|
if (!view->mapped && !view->minimized) {
|
|
continue;
|
|
}
|
|
if (top_parent_of(view) != surface) {
|
|
continue;
|
|
}
|
|
struct view **child = wl_array_add(children, sizeof(*child));
|
|
*child = view;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
xwayland_view_is_related(struct view *view, struct wlr_surface *surface)
|
|
{
|
|
struct wlr_xwayland_surface *xsurface =
|
|
xwayland_surface_from_view(view);
|
|
struct wlr_xwayland_surface *xsurface2 =
|
|
wlr_surface_is_xwayland_surface(surface) ?
|
|
wlr_xwayland_surface_from_wlr_surface(surface) : NULL;
|
|
|
|
return (xsurface2 && xsurface2->pid == xsurface->pid);
|
|
}
|
|
|
|
static void
|
|
xwayland_view_set_activated(struct view *view, bool activated)
|
|
{
|
|
struct wlr_xwayland_surface *xwayland_surface =
|
|
xwayland_surface_from_view(view);
|
|
|
|
if (activated && xwayland_surface->minimized) {
|
|
wlr_xwayland_surface_set_minimized(xwayland_surface, false);
|
|
}
|
|
|
|
wlr_xwayland_surface_activate(xwayland_surface, activated);
|
|
if (activated) {
|
|
wlr_xwayland_surface_restack(xwayland_surface,
|
|
NULL, XCB_STACK_MODE_ABOVE);
|
|
/* Restack unmanaged surfaces on top */
|
|
struct xwayland_unmanaged *u;
|
|
struct wl_list *list = &view->server->unmanaged_surfaces;
|
|
wl_list_for_each(u, list, link) {
|
|
wlr_xwayland_surface_restack(u->xwayland_surface,
|
|
NULL, XCB_STACK_MODE_ABOVE);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
xwayland_view_set_fullscreen(struct view *view, bool fullscreen)
|
|
{
|
|
wlr_xwayland_surface_set_fullscreen(xwayland_surface_from_view(view),
|
|
fullscreen);
|
|
}
|
|
|
|
static const struct view_impl xwayland_view_impl = {
|
|
.configure = xwayland_view_configure,
|
|
.close = xwayland_view_close,
|
|
.get_string_prop = xwayland_view_get_string_prop,
|
|
.map = xwayland_view_map,
|
|
.set_activated = xwayland_view_set_activated,
|
|
.set_fullscreen = xwayland_view_set_fullscreen,
|
|
.unmap = xwayland_view_unmap,
|
|
.maximize = xwayland_view_maximize,
|
|
.minimize = xwayland_view_minimize,
|
|
.move_to_front = xwayland_view_move_to_front,
|
|
.move_to_back = xwayland_view_move_to_back,
|
|
.get_root = xwayland_view_get_root,
|
|
.append_children = xwayland_view_append_children,
|
|
.is_related = xwayland_view_is_related,
|
|
.get_size_hints = xwayland_view_get_size_hints,
|
|
.wants_focus = xwayland_view_wants_focus,
|
|
};
|
|
|
|
void
|
|
xwayland_view_create(struct server *server,
|
|
struct wlr_xwayland_surface *xsurface, bool mapped)
|
|
{
|
|
struct xwayland_view *xwayland_view = znew(*xwayland_view);
|
|
struct view *view = &xwayland_view->base;
|
|
|
|
view->server = server;
|
|
view->type = LAB_XWAYLAND_VIEW;
|
|
view->impl = &xwayland_view_impl;
|
|
|
|
/*
|
|
* Set two-way view <-> xsurface association. Usually the association
|
|
* remains until the xsurface is destroyed (which also destroys the
|
|
* view). The only exception is caused by setting override-redirect on
|
|
* the xsurface, which removes it from the view (destroying the view)
|
|
* and makes it an "unmanaged" surface.
|
|
*/
|
|
xwayland_view->xwayland_surface = xsurface;
|
|
xsurface->data = view;
|
|
|
|
view->workspace = server->workspace_current;
|
|
view->scene_tree = wlr_scene_tree_create(view->workspace->tree);
|
|
node_descriptor_create(&view->scene_tree->node, LAB_NODE_DESC_VIEW, view);
|
|
|
|
view->map.notify = handle_map;
|
|
wl_signal_add(&xsurface->events.map, &view->map);
|
|
view->unmap.notify = handle_unmap;
|
|
wl_signal_add(&xsurface->events.unmap, &view->unmap);
|
|
view->destroy.notify = handle_destroy;
|
|
wl_signal_add(&xsurface->events.destroy, &view->destroy);
|
|
view->request_minimize.notify = handle_request_minimize;
|
|
wl_signal_add(&xsurface->events.request_minimize, &view->request_minimize);
|
|
view->request_maximize.notify = handle_request_maximize;
|
|
wl_signal_add(&xsurface->events.request_maximize, &view->request_maximize);
|
|
view->request_fullscreen.notify = handle_request_fullscreen;
|
|
wl_signal_add(&xsurface->events.request_fullscreen, &view->request_fullscreen);
|
|
view->request_move.notify = handle_request_move;
|
|
wl_signal_add(&xsurface->events.request_move, &view->request_move);
|
|
view->request_resize.notify = handle_request_resize;
|
|
wl_signal_add(&xsurface->events.request_resize, &view->request_resize);
|
|
|
|
view->set_title.notify = handle_set_title;
|
|
wl_signal_add(&xsurface->events.set_title, &view->set_title);
|
|
|
|
/* Events specific to XWayland views */
|
|
xwayland_view->request_activate.notify = handle_request_activate;
|
|
wl_signal_add(&xsurface->events.request_activate, &xwayland_view->request_activate);
|
|
|
|
xwayland_view->request_configure.notify = handle_request_configure;
|
|
wl_signal_add(&xsurface->events.request_configure, &xwayland_view->request_configure);
|
|
|
|
xwayland_view->set_app_id.notify = handle_set_class;
|
|
wl_signal_add(&xsurface->events.set_class, &xwayland_view->set_app_id);
|
|
|
|
xwayland_view->set_decorations.notify = handle_set_decorations;
|
|
wl_signal_add(&xsurface->events.set_decorations, &xwayland_view->set_decorations);
|
|
|
|
xwayland_view->override_redirect.notify = handle_override_redirect;
|
|
wl_signal_add(&xsurface->events.set_override_redirect, &xwayland_view->override_redirect);
|
|
|
|
wl_list_insert(&view->server->views, &view->link);
|
|
|
|
if (mapped) {
|
|
xwayland_view_map(view);
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_new_surface(struct wl_listener *listener, void *data)
|
|
{
|
|
struct server *server =
|
|
wl_container_of(listener, server, xwayland_new_surface);
|
|
struct wlr_xwayland_surface *xsurface = data;
|
|
wlr_xwayland_surface_ping(xsurface);
|
|
|
|
/*
|
|
* We do not create 'views' for xwayland override_redirect surfaces,
|
|
* but add them to server.unmanaged_surfaces so that we can render them
|
|
*/
|
|
if (xsurface->override_redirect) {
|
|
xwayland_unmanaged_create(server, xsurface, /* mapped */ false);
|
|
} else {
|
|
xwayland_view_create(server, xsurface, /* mapped */ false);
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_ready(struct wl_listener *listener, void *data)
|
|
{
|
|
struct server *server =
|
|
wl_container_of(listener, server, xwayland_ready);
|
|
wlr_xwayland_set_seat(server->xwayland, server->seat.seat);
|
|
}
|
|
|
|
void
|
|
xwayland_server_init(struct server *server, struct wlr_compositor *compositor)
|
|
{
|
|
server->xwayland =
|
|
wlr_xwayland_create(server->wl_display, compositor, true);
|
|
if (!server->xwayland) {
|
|
wlr_log(WLR_ERROR, "cannot create xwayland server");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
server->xwayland_new_surface.notify = handle_new_surface;
|
|
wl_signal_add(&server->xwayland->events.new_surface,
|
|
&server->xwayland_new_surface);
|
|
|
|
server->xwayland_ready.notify = handle_ready;
|
|
wl_signal_add(&server->xwayland->events.ready,
|
|
&server->xwayland_ready);
|
|
|
|
if (setenv("DISPLAY", server->xwayland->display_name, true) < 0) {
|
|
wlr_log_errno(WLR_ERROR, "unable to set DISPLAY for xwayland");
|
|
} else {
|
|
wlr_log(WLR_DEBUG, "xwayland is running on display %s",
|
|
server->xwayland->display_name);
|
|
}
|
|
|
|
struct wlr_xcursor *xcursor;
|
|
xcursor = wlr_xcursor_manager_get_xcursor(
|
|
server->seat.xcursor_manager, XCURSOR_DEFAULT, 1);
|
|
if (xcursor) {
|
|
struct wlr_xcursor_image *image = xcursor->images[0];
|
|
wlr_xwayland_set_cursor(server->xwayland, image->buffer,
|
|
image->width * 4, image->width,
|
|
image->height, image->hotspot_x,
|
|
image->hotspot_y);
|
|
}
|
|
}
|
|
|
|
void
|
|
xwayland_server_finish(struct server *server)
|
|
{
|
|
struct wlr_xwayland *xwayland = server->xwayland;
|
|
/*
|
|
* Reset server->xwayland to NULL first to prevent callbacks (like
|
|
* server_global_filter) from accessing it as it is destroyed
|
|
*/
|
|
server->xwayland = NULL;
|
|
wlr_xwayland_destroy(xwayland);
|
|
}
|