2021-09-24 21:45:48 +01:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2023-01-07 17:50:33 -05:00
|
|
|
#define _POSIX_C_SOURCE 200809L
|
2020-09-28 20:53:59 +01:00
|
|
|
#include <assert.h>
|
2023-01-07 17:50:33 -05:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <wlr/xwayland.h>
|
2023-11-21 03:09:22 +01:00
|
|
|
#include "common/array.h"
|
2023-10-16 02:01:35 -04:00
|
|
|
#include "common/macros.h"
|
2022-09-16 18:41:02 -04:00
|
|
|
#include "common/mem.h"
|
2024-07-03 13:43:04 -04:00
|
|
|
#include "config/rcxml.h"
|
2024-07-20 04:40:11 -04:00
|
|
|
#include "config/session.h"
|
2019-12-26 21:37:31 +00:00
|
|
|
#include "labwc.h"
|
2022-03-02 21:07:04 +00:00
|
|
|
#include "node.h"
|
2021-03-21 20:54:55 +00:00
|
|
|
#include "ssd.h"
|
2022-11-21 10:10:39 -05:00
|
|
|
#include "view.h"
|
2023-02-05 19:29:24 +00:00
|
|
|
#include "view-impl-common.h"
|
2023-04-28 21:41:41 +01:00
|
|
|
#include "window-rules.h"
|
2022-06-15 02:02:50 +02:00
|
|
|
#include "workspaces.h"
|
2023-01-07 17:50:33 -05:00
|
|
|
#include "xwayland.h"
|
2019-12-26 21:37:31 +00:00
|
|
|
|
2024-04-19 20:15:49 +02:00
|
|
|
xcb_atom_t atoms[WINDOW_TYPE_LEN] = {0};
|
2024-01-24 18:06:57 +01:00
|
|
|
|
2023-06-15 02:35:43 -07:00
|
|
|
static void xwayland_view_unmap(struct view *view, bool client_request);
|
|
|
|
|
|
2024-04-19 20:15:49 +02:00
|
|
|
static bool
|
2024-01-24 18:06:57 +01:00
|
|
|
xwayland_surface_contains_window_type(
|
2024-04-19 20:15:49 +02:00
|
|
|
struct wlr_xwayland_surface *surface, enum window_type window_type)
|
2024-01-24 18:06:57 +01:00
|
|
|
{
|
|
|
|
|
assert(surface);
|
|
|
|
|
for (size_t i = 0; i < surface->window_type_len; i++) {
|
|
|
|
|
if (surface->window_type[i] == atoms[window_type]) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-19 20:15:49 +02:00
|
|
|
static bool
|
|
|
|
|
xwayland_view_contains_window_type(struct view *view, int32_t window_type)
|
|
|
|
|
{
|
|
|
|
|
assert(view);
|
|
|
|
|
struct wlr_xwayland_surface *surface = xwayland_surface_from_view(view);
|
|
|
|
|
return xwayland_surface_contains_window_type(surface, window_type);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-22 01:22:19 -04:00
|
|
|
static struct view_size_hints
|
|
|
|
|
xwayland_view_get_size_hints(struct view *view)
|
2023-01-04 04:18:00 +01:00
|
|
|
{
|
2023-09-22 01:22:19 -04:00
|
|
|
xcb_size_hints_t *hints = xwayland_surface_from_view(view)->size_hints;
|
|
|
|
|
if (!hints) {
|
|
|
|
|
return (struct view_size_hints){0};
|
2023-08-10 15:48:52 +01:00
|
|
|
}
|
2023-09-22 01:22:19 -04:00
|
|
|
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,
|
|
|
|
|
};
|
2023-08-17 19:00:56 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-15 01:11:12 -04:00
|
|
|
static enum view_wants_focus
|
2023-09-23 11:51:47 -04:00
|
|
|
xwayland_view_wants_focus(struct view *view)
|
|
|
|
|
{
|
2023-10-15 01:11:12 -04:00
|
|
|
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;
|
|
|
|
|
|
2023-09-27 19:19:58 -04:00
|
|
|
/*
|
2023-10-15 01:11:12 -04:00
|
|
|
* 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].
|
2023-09-27 19:19:58 -04:00
|
|
|
*
|
2023-10-15 01:11:12 -04:00
|
|
|
* 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.
|
2023-09-27 19:19:58 -04:00
|
|
|
*
|
2023-10-15 01:11:12 -04:00
|
|
|
* [Currently, labwc does not fully support clients voluntarily
|
2023-10-15 21:33:18 -04:00
|
|
|
* 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.]
|
2023-09-27 19:19:58 -04:00
|
|
|
*/
|
2023-10-15 01:11:12 -04:00
|
|
|
case WLR_ICCCM_INPUT_MODEL_GLOBAL:
|
2023-10-15 21:33:18 -04:00
|
|
|
/*
|
2024-02-14 21:11:03 -05:00
|
|
|
* Assume that NORMAL and DIALOG windows always want
|
|
|
|
|
* focus. These window types should show up in the
|
|
|
|
|
* Alt-Tab switcher and be automatically focused when
|
|
|
|
|
* they become topmost.
|
2023-10-15 21:33:18 -04:00
|
|
|
*/
|
2024-02-14 21:11:03 -05:00
|
|
|
return (xwayland_surface_contains_window_type(xsurface,
|
|
|
|
|
NET_WM_WINDOW_TYPE_NORMAL)
|
|
|
|
|
|| xwayland_surface_contains_window_type(xsurface,
|
|
|
|
|
NET_WM_WINDOW_TYPE_DIALOG)) ?
|
2023-10-15 21:33:18 -04:00
|
|
|
VIEW_WANTS_FOCUS_ALWAYS : VIEW_WANTS_FOCUS_OFFER;
|
2023-10-15 01:11:12 -04:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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;
|
2023-09-23 11:51:47 -04:00
|
|
|
}
|
|
|
|
|
|
2023-11-27 17:11:29 -05:00
|
|
|
static bool
|
|
|
|
|
xwayland_view_has_strut_partial(struct view *view)
|
|
|
|
|
{
|
|
|
|
|
struct wlr_xwayland_surface *xsurface =
|
|
|
|
|
xwayland_surface_from_view(view);
|
|
|
|
|
return (bool)xsurface->strut_partial;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-07 17:50:33 -05:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-22 20:13:06 +00:00
|
|
|
static struct xwayland_view *
|
|
|
|
|
xwayland_view_from_view(struct view *view)
|
|
|
|
|
{
|
|
|
|
|
assert(view->type == LAB_XWAYLAND_VIEW);
|
|
|
|
|
return (struct xwayland_view *)view;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-25 13:41:12 -05:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-07 21:12:56 -05:00
|
|
|
static void
|
2023-02-03 14:45:04 -05:00
|
|
|
ensure_initial_geometry_and_output(struct view *view)
|
2023-02-07 21:12:56 -05:00
|
|
|
{
|
2023-02-08 23:19:14 -05:00
|
|
|
if (wlr_box_empty(&view->current)) {
|
2023-02-07 21:12:56 -05:00
|
|
|
struct wlr_xwayland_surface *xwayland_surface =
|
|
|
|
|
xwayland_surface_from_view(view);
|
2023-02-08 23:19:14 -05:00
|
|
|
view->current.x = xwayland_surface->x;
|
|
|
|
|
view->current.y = xwayland_surface->y;
|
|
|
|
|
view->current.width = xwayland_surface->width;
|
|
|
|
|
view->current.height = xwayland_surface->height;
|
2023-02-07 21:12:56 -05:00
|
|
|
/*
|
|
|
|
|
* If there is no pending move/resize yet, then set
|
|
|
|
|
* current values (used in map()).
|
|
|
|
|
*/
|
2023-02-08 23:19:14 -05:00
|
|
|
if (wlr_box_empty(&view->pending)) {
|
|
|
|
|
view->pending = view->current;
|
2023-02-07 21:12:56 -05:00
|
|
|
}
|
|
|
|
|
}
|
2023-02-03 14:45:04 -05:00
|
|
|
if (!view->output) {
|
|
|
|
|
/*
|
|
|
|
|
* Just use the cursor output since we don't know yet
|
|
|
|
|
* whether the surface position is meaningful.
|
|
|
|
|
*/
|
2023-02-28 11:46:48 -05:00
|
|
|
view_set_output(view, output_nearest_to_cursor(view->server));
|
2023-02-03 14:45:04 -05:00
|
|
|
}
|
2023-02-07 21:12:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
want_deco(struct wlr_xwayland_surface *xwayland_surface)
|
|
|
|
|
{
|
2023-04-28 21:41:41 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-07 21:12:56 -05:00
|
|
|
return xwayland_surface->decorations ==
|
|
|
|
|
WLR_XWAYLAND_SURFACE_DECORATIONS_ALL;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
static void
|
|
|
|
|
handle_commit(struct wl_listener *listener, void *data)
|
2020-08-31 08:12:44 +01:00
|
|
|
{
|
|
|
|
|
struct view *view = wl_container_of(listener, view, commit);
|
2022-11-11 15:54:26 -05:00
|
|
|
assert(data && data == view->surface);
|
2020-08-31 08:12:44 +01:00
|
|
|
|
|
|
|
|
/* Must receive commit signal before accessing surface->current* */
|
2022-02-25 21:31:21 +01:00
|
|
|
struct wlr_surface_state *state = &view->surface->current;
|
2023-02-08 23:19:14 -05:00
|
|
|
struct wlr_box *current = &view->current;
|
2022-02-25 21:31:21 +01:00
|
|
|
|
2023-02-25 12:05:22 -05:00
|
|
|
/*
|
|
|
|
|
* 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);
|
2020-12-23 18:36:40 +00:00
|
|
|
}
|
2020-08-31 08:12:44 +01:00
|
|
|
}
|
|
|
|
|
|
2021-10-17 17:11:41 +00:00
|
|
|
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
|
2023-02-25 12:05:22 -05:00
|
|
|
* the provided serial against a list of button press serials sent to
|
2022-03-28 20:51:59 +01:00
|
|
|
* this client, to prevent the client from requesting this whenever they
|
2021-10-17 17:11:41 +00:00
|
|
|
* want.
|
|
|
|
|
*/
|
|
|
|
|
struct view *view = wl_container_of(listener, view, request_move);
|
2024-03-08 00:53:39 +09:00
|
|
|
if (view == view->server->seat.pressed.view) {
|
|
|
|
|
interactive_begin(view, LAB_INPUT_STATE_MOVE, 0);
|
|
|
|
|
}
|
2021-10-17 17:11:41 +00:00
|
|
|
}
|
|
|
|
|
|
2021-10-17 17:12:51 +00:00
|
|
|
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
|
2023-02-25 12:05:22 -05:00
|
|
|
* the provided serial against a list of button press serials sent to
|
2022-03-28 20:51:59 +01:00
|
|
|
* this client, to prevent the client from requesting this whenever they
|
2021-10-17 17:12:51 +00:00
|
|
|
* want.
|
|
|
|
|
*/
|
|
|
|
|
struct wlr_xwayland_resize_event *event = data;
|
|
|
|
|
struct view *view = wl_container_of(listener, view, request_resize);
|
2024-03-08 00:53:39 +09:00
|
|
|
if (view == view->server->seat.pressed.view) {
|
|
|
|
|
interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges);
|
|
|
|
|
}
|
2021-10-17 17:12:51 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
static void
|
2023-06-15 02:35:43 -07:00
|
|
|
handle_associate(struct wl_listener *listener, void *data)
|
2019-12-27 21:22:45 +00:00
|
|
|
{
|
2023-06-15 02:35:43 -07:00
|
|
|
struct xwayland_view *xwayland_view =
|
|
|
|
|
wl_container_of(listener, xwayland_view, associate);
|
|
|
|
|
assert(xwayland_view->xwayland_surface &&
|
|
|
|
|
xwayland_view->xwayland_surface->surface);
|
|
|
|
|
|
|
|
|
|
view_connect_map(&xwayland_view->base,
|
|
|
|
|
xwayland_view->xwayland_surface->surface);
|
2019-12-26 21:37:31 +00:00
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
static void
|
2023-06-15 02:35:43 -07:00
|
|
|
handle_dissociate(struct wl_listener *listener, void *data)
|
2019-12-27 21:22:45 +00:00
|
|
|
{
|
2023-06-15 02:35:43 -07:00
|
|
|
struct xwayland_view *xwayland_view =
|
|
|
|
|
wl_container_of(listener, xwayland_view, dissociate);
|
|
|
|
|
|
2024-08-12 22:07:53 +01:00
|
|
|
/* https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4524 */
|
|
|
|
|
assert(xwayland_view->base.mappable.connected);
|
2023-06-15 02:35:43 -07:00
|
|
|
mappable_disconnect(&xwayland_view->base.mappable);
|
2019-12-26 21:37:31 +00:00
|
|
|
}
|
|
|
|
|
|
2022-09-04 01:32:12 +02:00
|
|
|
static void
|
|
|
|
|
handle_surface_destroy(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct view *view = wl_container_of(listener, view, surface_destroy);
|
2022-11-11 15:54:26 -05:00
|
|
|
assert(data && data == view->surface);
|
2022-09-04 01:32:12 +02:00
|
|
|
|
|
|
|
|
view->surface = NULL;
|
|
|
|
|
wl_list_remove(&view->surface_destroy.link);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
static void
|
|
|
|
|
handle_destroy(struct wl_listener *listener, void *data)
|
2019-12-27 21:22:45 +00:00
|
|
|
{
|
2019-12-27 20:48:58 +00:00
|
|
|
struct view *view = wl_container_of(listener, view, destroy);
|
2022-11-25 13:41:12 -05:00
|
|
|
struct xwayland_view *xwayland_view = xwayland_view_from_view(view);
|
|
|
|
|
assert(xwayland_view->xwayland_surface->data == view);
|
2022-04-23 03:44:41 +02:00
|
|
|
|
2022-09-04 01:32:12 +02:00
|
|
|
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;
|
2022-11-11 15:54:26 -05:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Break view <-> xsurface association. Note that the xsurface
|
|
|
|
|
* may not actually be destroyed at this point; it may become an
|
2023-09-30 01:31:37 -04:00
|
|
|
* "unmanaged" surface instead (in that case it is important
|
|
|
|
|
* that xsurface->data not point to the destroyed view).
|
2022-11-11 15:54:26 -05:00
|
|
|
*/
|
2022-11-25 13:41:12 -05:00
|
|
|
xwayland_view->xwayland_surface->data = NULL;
|
|
|
|
|
xwayland_view->xwayland_surface = NULL;
|
2022-04-23 03:44:41 +02:00
|
|
|
|
2023-02-24 21:45:03 +00:00
|
|
|
/* Remove XWayland view specific listeners */
|
2023-06-15 02:35:43 -07:00
|
|
|
wl_list_remove(&xwayland_view->associate.link);
|
|
|
|
|
wl_list_remove(&xwayland_view->dissociate.link);
|
2023-02-24 21:27:11 +00:00
|
|
|
wl_list_remove(&xwayland_view->request_activate.link);
|
2022-11-22 20:13:06 +00:00
|
|
|
wl_list_remove(&xwayland_view->request_configure.link);
|
2023-10-16 02:01:35 -04:00
|
|
|
wl_list_remove(&xwayland_view->set_class.link);
|
2022-11-22 20:13:06 +00:00
|
|
|
wl_list_remove(&xwayland_view->set_decorations.link);
|
2023-10-16 02:01:35 -04:00
|
|
|
wl_list_remove(&xwayland_view->set_override_redirect.link);
|
2022-11-22 05:11:50 -05:00
|
|
|
wl_list_remove(&xwayland_view->set_strut_partial.link);
|
2024-01-24 18:06:57 +01:00
|
|
|
wl_list_remove(&xwayland_view->set_window_type.link);
|
2024-02-12 19:52:36 -05:00
|
|
|
wl_list_remove(&xwayland_view->map_request.link);
|
2022-11-22 20:13:06 +00:00
|
|
|
|
2022-04-23 03:44:41 +02:00
|
|
|
view_destroy(view);
|
2019-12-26 21:37:31 +00:00
|
|
|
}
|
|
|
|
|
|
2022-07-17 23:42:04 -04:00
|
|
|
static void
|
2023-02-06 20:01:18 +00:00
|
|
|
xwayland_view_configure(struct view *view, struct wlr_box geo)
|
2022-07-17 23:42:04 -04:00
|
|
|
{
|
2023-02-08 23:19:14 -05:00
|
|
|
view->pending = geo;
|
2022-11-25 13:41:12 -05:00
|
|
|
wlr_xwayland_surface_configure(xwayland_surface_from_view(view),
|
|
|
|
|
geo.x, geo.y, geo.width, geo.height);
|
2022-07-17 23:42:04 -04:00
|
|
|
|
2023-11-28 00:38:18 -05:00
|
|
|
/*
|
|
|
|
|
* For unknown reasons, XWayland surfaces that are completely
|
|
|
|
|
* offscreen seem not to generate commit events. In rare cases,
|
|
|
|
|
* this can prevent an offscreen window from moving onscreen
|
|
|
|
|
* (since we wait for a commit event that never occurs). As a
|
|
|
|
|
* workaround, move offscreen surfaces immediately.
|
|
|
|
|
*/
|
|
|
|
|
bool is_offscreen = !wlr_box_empty(&view->current) &&
|
|
|
|
|
!wlr_output_layout_intersects(view->server->output_layout, NULL,
|
|
|
|
|
&view->current);
|
|
|
|
|
|
2022-07-17 23:42:04 -04:00
|
|
|
/* If not resizing, process the move immediately */
|
2023-11-28 00:38:18 -05:00
|
|
|
if (is_offscreen || (view->current.width == geo.width
|
|
|
|
|
&& view->current.height == geo.height)) {
|
2023-02-08 23:19:14 -05:00
|
|
|
view->current.x = geo.x;
|
|
|
|
|
view->current.y = geo.y;
|
2022-07-17 23:42:04 -04:00
|
|
|
view_moved(view);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
static void
|
|
|
|
|
handle_request_configure(struct wl_listener *listener, void *data)
|
2019-12-27 21:22:45 +00:00
|
|
|
{
|
2022-11-22 20:13:06 +00:00
|
|
|
struct xwayland_view *xwayland_view =
|
|
|
|
|
wl_container_of(listener, xwayland_view, request_configure);
|
|
|
|
|
struct view *view = &xwayland_view->base;
|
2019-12-26 21:37:31 +00:00
|
|
|
struct wlr_xwayland_surface_configure_event *event = data;
|
2024-04-13 20:36:09 +02:00
|
|
|
bool ignore_configure_requests = window_rules_get_property(
|
|
|
|
|
view, "ignoreConfigureRequest") == LAB_PROP_TRUE;
|
2021-10-20 20:34:47 +01:00
|
|
|
|
2024-04-13 20:36:09 +02:00
|
|
|
if (view_is_floating(view) && !ignore_configure_requests) {
|
2023-02-17 14:08:27 -05:00
|
|
|
/* Honor client configure requests for floating views */
|
|
|
|
|
struct wlr_box box = {.x = event->x, .y = event->y,
|
|
|
|
|
.width = event->width, .height = event->height};
|
|
|
|
|
view_adjust_size(view, &box.width, &box.height);
|
|
|
|
|
xwayland_view_configure(view, box);
|
|
|
|
|
} else {
|
|
|
|
|
/*
|
|
|
|
|
* Do not allow clients to request geometry other than
|
|
|
|
|
* what we computed for maximized/fullscreen/tiled
|
|
|
|
|
* views. Ignore the client request and send back a
|
|
|
|
|
* ConfigureNotify event with the computed geometry.
|
|
|
|
|
*/
|
|
|
|
|
xwayland_view_configure(view, view->pending);
|
|
|
|
|
}
|
2019-12-26 21:37:31 +00:00
|
|
|
}
|
|
|
|
|
|
2021-12-31 02:58:55 +00:00
|
|
|
static void
|
|
|
|
|
handle_request_activate(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
2023-02-24 21:27:11 +00:00
|
|
|
struct xwayland_view *xwayland_view =
|
|
|
|
|
wl_container_of(listener, xwayland_view, request_activate);
|
|
|
|
|
struct view *view = &xwayland_view->base;
|
2023-09-03 14:01:40 +02:00
|
|
|
|
|
|
|
|
if (window_rules_get_property(view, "ignoreFocusRequest") == LAB_PROP_TRUE) {
|
|
|
|
|
wlr_log(WLR_INFO, "Ignoring focus request due to window rule configuration");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-29 02:53:53 +01:00
|
|
|
if (view->server->osd_state.cycle_view) {
|
|
|
|
|
wlr_log(WLR_INFO, "Preventing focus request while in window switcher");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-27 18:37:28 -04:00
|
|
|
desktop_focus_view(view, /*raise*/ true);
|
2021-12-31 02:58:55 +00:00
|
|
|
}
|
|
|
|
|
|
2021-12-31 02:45:19 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-05 12:18:10 +01:00
|
|
|
static void
|
|
|
|
|
handle_request_maximize(struct wl_listener *listener, void *data)
|
2021-02-27 17:10:53 -05:00
|
|
|
{
|
|
|
|
|
struct view *view = wl_container_of(listener, view, request_maximize);
|
2023-02-03 14:45:04 -05:00
|
|
|
if (!view->mapped) {
|
|
|
|
|
ensure_initial_geometry_and_output(view);
|
2023-02-07 21:12:56 -05:00
|
|
|
/*
|
|
|
|
|
* Set decorations early to avoid changing geometry
|
|
|
|
|
* after maximize (reduces visual glitches).
|
|
|
|
|
*/
|
2024-04-18 09:46:36 +02:00
|
|
|
if (want_deco(xwayland_surface_from_view(view))) {
|
|
|
|
|
view_set_ssd_mode(view, LAB_SSD_MODE_FULL);
|
|
|
|
|
} else {
|
|
|
|
|
view_set_ssd_mode(view, LAB_SSD_MODE_NONE);
|
|
|
|
|
}
|
2023-02-07 21:12:56 -05:00
|
|
|
}
|
view: implement separate horizontal/vertical maximize
This is a useful (if lesser-known) feature of at least a few popular X11
window managers, for example Openbox and XFWM4. Typically right-click on
the maximize button toggles horizontal maximize, while middle-click
toggles vertical maximize.
Support in labwc uses the same configuration syntax as Openbox, where the
Maximize/ToggleMaximize actions have an optional "direction" argument:
horizontal, vertical, or both (default). The default mouse bindings match
the XFWM4 defaults (not sure what Openbox has by default).
Most of the external protocols still assume "maximized" is a Boolean,
which is no longer true internally. For the sake of the outside world,
a view is only "maximized" if maximized in both directions.
Internally, I've taken the following approach:
- SSD code decorates the view as "maximized" (i.e. hiding borders) only
if maximized in both directions.
- Layout code (interactive move/resize, tiling, etc.) generally treats
the view as "maximized" (with the restrictions that entails) if
maximized in either direction. For example, moving a vertically-
maximized view first restores the natural geometry (this differs from
Openbox, which instead allows the view to move only horizontally.)
v2: use enum view_axis for view->maximized
v3:
- update docs
- allow resizing if partly maximized
- add TODOs & corrections noted by Consolatis
2023-10-26 00:38:29 -04:00
|
|
|
view_toggle_maximize(view, VIEW_AXIS_BOTH);
|
2021-02-27 17:10:53 -05:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 22:05:30 +01:00
|
|
|
static void
|
|
|
|
|
handle_request_fullscreen(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct view *view = wl_container_of(listener, view, request_fullscreen);
|
2022-11-25 13:41:12 -05:00
|
|
|
bool fullscreen = xwayland_surface_from_view(view)->fullscreen;
|
2023-02-03 14:45:04 -05:00
|
|
|
if (!view->mapped) {
|
|
|
|
|
ensure_initial_geometry_and_output(view);
|
2023-02-07 21:12:56 -05:00
|
|
|
}
|
2023-02-20 13:36:15 -05:00
|
|
|
view_set_fullscreen(view, fullscreen);
|
2021-08-23 22:05:30 +01:00
|
|
|
}
|
|
|
|
|
|
2021-08-05 12:18:10 +01:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-16 21:50:56 +01:00
|
|
|
static void
|
|
|
|
|
handle_set_class(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
2022-11-22 20:13:06 +00:00
|
|
|
struct xwayland_view *xwayland_view =
|
2023-10-16 02:01:35 -04:00
|
|
|
wl_container_of(listener, xwayland_view, set_class);
|
2022-11-22 20:13:06 +00:00
|
|
|
struct view *view = &xwayland_view->base;
|
2021-10-16 21:50:56 +01:00
|
|
|
view_update_app_id(view);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
static void
|
2023-02-06 20:01:18 +00:00
|
|
|
xwayland_view_close(struct view *view)
|
2020-09-02 21:00:28 +01:00
|
|
|
{
|
2022-11-25 13:41:12 -05:00
|
|
|
wlr_xwayland_surface_close(xwayland_surface_from_view(view));
|
2020-09-02 20:20:52 +01:00
|
|
|
}
|
|
|
|
|
|
2021-08-05 12:18:10 +01:00
|
|
|
static const char *
|
2023-02-06 20:01:18 +00:00
|
|
|
xwayland_view_get_string_prop(struct view *view, const char *prop)
|
2021-08-05 12:18:10 +01:00
|
|
|
{
|
2023-09-21 18:13:44 +02:00
|
|
|
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 "";
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-05 12:18:10 +01:00
|
|
|
if (!strcmp(prop, "title")) {
|
2022-11-25 13:41:12 -05:00
|
|
|
return xwayland_surface->title;
|
2021-08-05 12:18:10 +01:00
|
|
|
}
|
2021-08-16 07:16:56 +01:00
|
|
|
if (!strcmp(prop, "class")) {
|
2022-11-25 13:41:12 -05:00
|
|
|
return xwayland_surface->class;
|
2021-08-16 07:16:56 +01:00
|
|
|
}
|
2021-10-16 21:26:57 +01:00
|
|
|
/* We give 'class' for wlr_foreign_toplevel_handle_v1_set_app_id() */
|
|
|
|
|
if (!strcmp(prop, "app_id")) {
|
2022-11-25 13:41:12 -05:00
|
|
|
return xwayland_surface->class;
|
2021-10-16 21:26:57 +01:00
|
|
|
}
|
2021-08-16 07:16:56 +01:00
|
|
|
return "";
|
2021-08-05 12:18:10 +01:00
|
|
|
}
|
|
|
|
|
|
2021-10-20 16:06:32 +00:00
|
|
|
static void
|
2021-11-13 21:45:12 +00:00
|
|
|
handle_set_decorations(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
2022-11-22 20:13:06 +00:00
|
|
|
struct xwayland_view *xwayland_view =
|
|
|
|
|
wl_container_of(listener, xwayland_view, set_decorations);
|
|
|
|
|
struct view *view = &xwayland_view->base;
|
2022-11-25 13:41:12 -05:00
|
|
|
|
2024-04-18 09:46:36 +02:00
|
|
|
if (want_deco(xwayland_view->xwayland_surface)) {
|
|
|
|
|
view_set_ssd_mode(view, LAB_SSD_MODE_FULL);
|
|
|
|
|
} else {
|
|
|
|
|
view_set_ssd_mode(view, LAB_SSD_MODE_NONE);
|
|
|
|
|
}
|
2021-10-20 16:06:32 +00:00
|
|
|
}
|
|
|
|
|
|
2024-01-24 18:06:57 +01:00
|
|
|
static void
|
|
|
|
|
handle_set_window_type(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
|
|
|
|
/* Intentionally left blank */
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-03 19:44:31 +01:00
|
|
|
static void
|
2023-10-16 02:01:35 -04:00
|
|
|
handle_set_override_redirect(struct wl_listener *listener, void *data)
|
2022-05-03 19:44:31 +01:00
|
|
|
{
|
2022-11-22 20:13:06 +00:00
|
|
|
struct xwayland_view *xwayland_view =
|
2023-10-16 02:01:35 -04:00
|
|
|
wl_container_of(listener, xwayland_view, set_override_redirect);
|
2022-11-22 20:13:06 +00:00
|
|
|
struct view *view = &xwayland_view->base;
|
2023-07-17 02:33:40 -04:00
|
|
|
struct wlr_xwayland_surface *xsurface = xwayland_view->xwayland_surface;
|
2022-11-25 13:41:12 -05:00
|
|
|
|
2022-05-03 19:44:31 +01:00
|
|
|
struct server *server = view->server;
|
2023-06-15 02:35:43 -07:00
|
|
|
bool mapped = xsurface->surface && xsurface->surface->mapped;
|
2022-05-03 19:44:31 +01:00
|
|
|
if (mapped) {
|
2023-06-15 02:35:43 -07:00
|
|
|
xwayland_view_unmap(view, /* client_request */ true);
|
2022-05-03 19:44:31 +01:00
|
|
|
}
|
2022-11-11 15:54:26 -05:00
|
|
|
handle_destroy(&view->destroy, xsurface);
|
|
|
|
|
/* view is invalid after this point */
|
2023-01-07 17:50:33 -05:00
|
|
|
xwayland_unmanaged_create(server, xsurface, mapped);
|
2022-05-03 19:44:31 +01:00
|
|
|
}
|
|
|
|
|
|
2022-11-22 05:11:50 -05:00
|
|
|
static void
|
|
|
|
|
handle_set_strut_partial(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct xwayland_view *xwayland_view =
|
|
|
|
|
wl_container_of(listener, xwayland_view, set_strut_partial);
|
|
|
|
|
struct view *view = &xwayland_view->base;
|
|
|
|
|
|
|
|
|
|
if (view->mapped) {
|
|
|
|
|
output_update_all_usable_areas(view->server, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-12 19:52:36 -05:00
|
|
|
/*
|
|
|
|
|
* Sets the initial geometry of maximized/fullscreen views before
|
|
|
|
|
* actually mapping them, so that they can do their initial layout and
|
|
|
|
|
* drawing with the correct geometry. This avoids visual glitches and
|
|
|
|
|
* also avoids undesired layout changes with some apps (e.g. HomeBank).
|
|
|
|
|
*/
|
|
|
|
|
static void
|
|
|
|
|
handle_map_request(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct xwayland_view *xwayland_view =
|
|
|
|
|
wl_container_of(listener, xwayland_view, map_request);
|
|
|
|
|
struct view *view = &xwayland_view->base;
|
|
|
|
|
struct wlr_xwayland_surface *xsurface = xwayland_view->xwayland_surface;
|
|
|
|
|
|
|
|
|
|
if (view->mapped) {
|
|
|
|
|
/* Probably shouldn't happen, but be sure */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Keep the view invisible until actually mapped */
|
|
|
|
|
wlr_scene_node_set_enabled(&view->scene_tree->node, false);
|
|
|
|
|
ensure_initial_geometry_and_output(view);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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)
|
|
|
|
|
*/
|
|
|
|
|
view_set_fullscreen(view, xsurface->fullscreen);
|
|
|
|
|
if (!view->been_mapped) {
|
|
|
|
|
if (want_deco(xsurface)) {
|
|
|
|
|
view_set_ssd_mode(view, LAB_SSD_MODE_FULL);
|
|
|
|
|
} else {
|
|
|
|
|
view_set_ssd_mode(view, LAB_SSD_MODE_NONE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
enum view_axis axis = VIEW_AXIS_NONE;
|
|
|
|
|
if (xsurface->maximized_horz) {
|
|
|
|
|
axis |= VIEW_AXIS_HORIZONTAL;
|
|
|
|
|
}
|
|
|
|
|
if (xsurface->maximized_vert) {
|
|
|
|
|
axis |= VIEW_AXIS_VERTICAL;
|
|
|
|
|
}
|
|
|
|
|
view_maximize(view, axis, /*store_natural_geometry*/ true);
|
|
|
|
|
/*
|
|
|
|
|
* We could also call set_initial_position() here, but it's not
|
|
|
|
|
* really necessary until the view is actually mapped (and at
|
|
|
|
|
* that point the output layout is known for sure).
|
|
|
|
|
*/
|
|
|
|
|
}
|
|
|
|
|
|
xdg: handle initially maximized xdg-shell views better
Currently, initially maximized (or fullscreen) xdg-shell views exhibit
one of two issues:
- some (e.g. GTK and Qt apps) paint an initial frame un-maximized
(before the "map" event) and only maximize in a later commit
- others (e.g. foot) maximize immediately without flicker, but never
store a valid natural size, so we end up using a fallback (640x480)
Under KWin, neither of these issues occur, so I looked into what labwc
is doing wrong. It seems that:
- wlroots internally sends an initial configure event with a size of
0x0 to all xdg-shell views. This requests the client to set its own
preferred (a.k.a. natural) size.
- For an initially maximized/fullscreen view, the initial configure
event should contain the maximized/fullscreen size rather than 0x0.
In labwc, this means we have to call wlr_xdg_toplevel_set_size()
earlier, i.e. from the new_surface event. Tracing with WAYLAND_DEBUG
shows that the initial configure event now has the correct geometry,
matching KWin behavior. With this change, GTK and Qt apps no longer
paint an incorrect un-maximized frame.
- However, this means that all xdg-shell views now suffer from the same
issue as foot, where we never receive a commit with the un-maximized
(natural) geometry. The correct way to get the natural geometry seems
to be to wait until we want to un-maximize, and send a configure
event of 0x0 at that point.
Sending a configure event of 0x0 when un-maximizing is a bit annoying as
it breaks some assumptions in labwc code. In particular:
- view->natural_geometry may now be unknown (0x0), requiring various
wlr_box_empty() checks sprinkled around. I added these in all the
obvious places, but there could be some code paths that I missed.
- Positioning the newly un-maximized view within view_maximize() no
longer works since we don't know the natural size. Instead we have to
run the positioning logic from the surface commit handler. This
results in some extra complexity, especially for interactive move.
See the new do_late_positioning() function in xdg.c.
Some TODOs/FIXMEs (non-blocking in my opinion):
- The view_wants_decorations() check is now duplicated in both the
new_surface and map event handlers. I'm not sure if this is necessary
but it seemed like the safest approach for now. More testing would be
nice, particularly with various combinations of config and client SSD
preferences.
- Aside from the interactive move case, the "late positioning" logic
always centers the view when un-maximizing, and does not invoke any
of the smart placement logic. If we want to invoke smart placement
here, I'd appreciate someone with more knowledge of that code to take
a look and figure out how to do that correctly.
2024-07-02 07:24:29 -04:00
|
|
|
static void
|
|
|
|
|
check_natural_geometry(struct view *view)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Some applications (example: Thonny) don't set a reasonable
|
|
|
|
|
* un-maximized size when started maximized. Try to detect this
|
|
|
|
|
* and set a fallback size.
|
|
|
|
|
*/
|
2024-08-18 10:49:18 +02:00
|
|
|
int min_view_width = rc.theme->window_button_width * (
|
|
|
|
|
wl_list_length(&rc.title_buttons_left) + wl_list_length(&rc.title_buttons_right));
|
xdg: handle initially maximized xdg-shell views better
Currently, initially maximized (or fullscreen) xdg-shell views exhibit
one of two issues:
- some (e.g. GTK and Qt apps) paint an initial frame un-maximized
(before the "map" event) and only maximize in a later commit
- others (e.g. foot) maximize immediately without flicker, but never
store a valid natural size, so we end up using a fallback (640x480)
Under KWin, neither of these issues occur, so I looked into what labwc
is doing wrong. It seems that:
- wlroots internally sends an initial configure event with a size of
0x0 to all xdg-shell views. This requests the client to set its own
preferred (a.k.a. natural) size.
- For an initially maximized/fullscreen view, the initial configure
event should contain the maximized/fullscreen size rather than 0x0.
In labwc, this means we have to call wlr_xdg_toplevel_set_size()
earlier, i.e. from the new_surface event. Tracing with WAYLAND_DEBUG
shows that the initial configure event now has the correct geometry,
matching KWin behavior. With this change, GTK and Qt apps no longer
paint an incorrect un-maximized frame.
- However, this means that all xdg-shell views now suffer from the same
issue as foot, where we never receive a commit with the un-maximized
(natural) geometry. The correct way to get the natural geometry seems
to be to wait until we want to un-maximize, and send a configure
event of 0x0 at that point.
Sending a configure event of 0x0 when un-maximizing is a bit annoying as
it breaks some assumptions in labwc code. In particular:
- view->natural_geometry may now be unknown (0x0), requiring various
wlr_box_empty() checks sprinkled around. I added these in all the
obvious places, but there could be some code paths that I missed.
- Positioning the newly un-maximized view within view_maximize() no
longer works since we don't know the natural size. Instead we have to
run the positioning logic from the surface commit handler. This
results in some extra complexity, especially for interactive move.
See the new do_late_positioning() function in xdg.c.
Some TODOs/FIXMEs (non-blocking in my opinion):
- The view_wants_decorations() check is now duplicated in both the
new_surface and map event handlers. I'm not sure if this is necessary
but it seemed like the safest approach for now. More testing would be
nice, particularly with various combinations of config and client SSD
preferences.
- Aside from the interactive move case, the "late positioning" logic
always centers the view when un-maximizing, and does not invoke any
of the smart placement logic. If we want to invoke smart placement
here, I'd appreciate someone with more knowledge of that code to take
a look and figure out how to do that correctly.
2024-07-02 07:24:29 -04:00
|
|
|
if (!view_is_floating(view)
|
2024-07-20 11:33:57 +03:00
|
|
|
&& (view->natural_geometry.width < min_view_width
|
xdg: handle initially maximized xdg-shell views better
Currently, initially maximized (or fullscreen) xdg-shell views exhibit
one of two issues:
- some (e.g. GTK and Qt apps) paint an initial frame un-maximized
(before the "map" event) and only maximize in a later commit
- others (e.g. foot) maximize immediately without flicker, but never
store a valid natural size, so we end up using a fallback (640x480)
Under KWin, neither of these issues occur, so I looked into what labwc
is doing wrong. It seems that:
- wlroots internally sends an initial configure event with a size of
0x0 to all xdg-shell views. This requests the client to set its own
preferred (a.k.a. natural) size.
- For an initially maximized/fullscreen view, the initial configure
event should contain the maximized/fullscreen size rather than 0x0.
In labwc, this means we have to call wlr_xdg_toplevel_set_size()
earlier, i.e. from the new_surface event. Tracing with WAYLAND_DEBUG
shows that the initial configure event now has the correct geometry,
matching KWin behavior. With this change, GTK and Qt apps no longer
paint an incorrect un-maximized frame.
- However, this means that all xdg-shell views now suffer from the same
issue as foot, where we never receive a commit with the un-maximized
(natural) geometry. The correct way to get the natural geometry seems
to be to wait until we want to un-maximize, and send a configure
event of 0x0 at that point.
Sending a configure event of 0x0 when un-maximizing is a bit annoying as
it breaks some assumptions in labwc code. In particular:
- view->natural_geometry may now be unknown (0x0), requiring various
wlr_box_empty() checks sprinkled around. I added these in all the
obvious places, but there could be some code paths that I missed.
- Positioning the newly un-maximized view within view_maximize() no
longer works since we don't know the natural size. Instead we have to
run the positioning logic from the surface commit handler. This
results in some extra complexity, especially for interactive move.
See the new do_late_positioning() function in xdg.c.
Some TODOs/FIXMEs (non-blocking in my opinion):
- The view_wants_decorations() check is now duplicated in both the
new_surface and map event handlers. I'm not sure if this is necessary
but it seemed like the safest approach for now. More testing would be
nice, particularly with various combinations of config and client SSD
preferences.
- Aside from the interactive move case, the "late positioning" logic
always centers the view when un-maximizing, and does not invoke any
of the smart placement logic. If we want to invoke smart placement
here, I'd appreciate someone with more knowledge of that code to take
a look and figure out how to do that correctly.
2024-07-02 07:24:29 -04:00
|
|
|
|| view->natural_geometry.height < LAB_MIN_VIEW_HEIGHT)) {
|
|
|
|
|
view_set_fallback_natural_geometry(view);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-04 21:45:03 -04:00
|
|
|
static void
|
2022-11-25 13:41:12 -05:00
|
|
|
set_initial_position(struct view *view,
|
|
|
|
|
struct wlr_xwayland_surface *xwayland_surface)
|
2022-09-04 21:45:03 -04:00
|
|
|
{
|
|
|
|
|
/* Don't center views with position explicitly specified */
|
2022-11-25 13:41:12 -05:00
|
|
|
bool has_position = xwayland_surface->size_hints &&
|
2023-01-31 11:43:45 +01:00
|
|
|
(xwayland_surface->size_hints->flags & (
|
|
|
|
|
XCB_ICCCM_SIZE_HINT_US_POSITION |
|
|
|
|
|
XCB_ICCCM_SIZE_HINT_P_POSITION));
|
2022-09-04 21:45:03 -04:00
|
|
|
|
2024-02-12 19:52:36 -05:00
|
|
|
if (!has_position) {
|
2024-01-08 20:12:45 -05:00
|
|
|
view_constrain_size_to_that_of_usable_area(view);
|
|
|
|
|
|
2023-10-17 23:06:12 -04:00
|
|
|
if (view_is_floating(view)) {
|
2024-05-07 09:46:05 -04:00
|
|
|
view_place_by_policy(view,
|
|
|
|
|
/* allow_cursor */ true, rc.placement_policy);
|
2023-10-17 23:06:12 -04:00
|
|
|
} 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);
|
|
|
|
|
}
|
2022-09-04 21:45:03 -04:00
|
|
|
}
|
2024-02-12 19:52:36 -05:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Always make sure the view is onscreen and adjusted for any
|
|
|
|
|
* layout changes that could have occurred between map_request
|
|
|
|
|
* and the actual map event.
|
|
|
|
|
*/
|
|
|
|
|
view_adjust_for_layout_change(view);
|
2022-09-04 21:45:03 -04:00
|
|
|
}
|
|
|
|
|
|
2023-08-03 21:32:58 +01:00
|
|
|
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;
|
2023-12-22 11:20:42 +01:00
|
|
|
if (!parent || !parent->toplevel.handle) {
|
2023-08-03 21:32:58 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
wlr_foreign_toplevel_handle_v1_set_parent(view->toplevel.handle, parent->toplevel.handle);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
static void
|
2023-02-06 20:01:18 +00:00
|
|
|
xwayland_view_map(struct view *view)
|
2020-09-03 20:50:35 +01:00
|
|
|
{
|
2024-02-12 19:52:36 -05:00
|
|
|
struct xwayland_view *xwayland_view = xwayland_view_from_view(view);
|
|
|
|
|
struct wlr_xwayland_surface *xwayland_surface =
|
|
|
|
|
xwayland_view->xwayland_surface;
|
|
|
|
|
assert(xwayland_surface);
|
|
|
|
|
|
2022-02-23 01:32:07 +01:00
|
|
|
if (view->mapped) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-08-04 21:58:52 +01:00
|
|
|
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;
|
|
|
|
|
}
|
2024-02-12 19:52:36 -05:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The map_request event may not be received when an unmanaged
|
|
|
|
|
* (override-redirect) surface becomes managed. To make sure we
|
|
|
|
|
* have valid geometry in that case, call handle_map_request()
|
|
|
|
|
* explicitly (calling it twice is harmless).
|
|
|
|
|
*/
|
|
|
|
|
handle_map_request(&xwayland_view->map_request, NULL);
|
|
|
|
|
|
2020-09-03 20:50:35 +01:00
|
|
|
view->mapped = true;
|
2022-02-23 01:32:07 +01:00
|
|
|
wlr_scene_node_set_enabled(&view->scene_tree->node, true);
|
2023-10-17 22:40:57 -04:00
|
|
|
|
2023-11-25 17:40:44 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-17 18:10:29 +01:00
|
|
|
if (!view->been_mapped) {
|
xdg: handle initially maximized xdg-shell views better
Currently, initially maximized (or fullscreen) xdg-shell views exhibit
one of two issues:
- some (e.g. GTK and Qt apps) paint an initial frame un-maximized
(before the "map" event) and only maximize in a later commit
- others (e.g. foot) maximize immediately without flicker, but never
store a valid natural size, so we end up using a fallback (640x480)
Under KWin, neither of these issues occur, so I looked into what labwc
is doing wrong. It seems that:
- wlroots internally sends an initial configure event with a size of
0x0 to all xdg-shell views. This requests the client to set its own
preferred (a.k.a. natural) size.
- For an initially maximized/fullscreen view, the initial configure
event should contain the maximized/fullscreen size rather than 0x0.
In labwc, this means we have to call wlr_xdg_toplevel_set_size()
earlier, i.e. from the new_surface event. Tracing with WAYLAND_DEBUG
shows that the initial configure event now has the correct geometry,
matching KWin behavior. With this change, GTK and Qt apps no longer
paint an incorrect un-maximized frame.
- However, this means that all xdg-shell views now suffer from the same
issue as foot, where we never receive a commit with the un-maximized
(natural) geometry. The correct way to get the natural geometry seems
to be to wait until we want to un-maximize, and send a configure
event of 0x0 at that point.
Sending a configure event of 0x0 when un-maximizing is a bit annoying as
it breaks some assumptions in labwc code. In particular:
- view->natural_geometry may now be unknown (0x0), requiring various
wlr_box_empty() checks sprinkled around. I added these in all the
obvious places, but there could be some code paths that I missed.
- Positioning the newly un-maximized view within view_maximize() no
longer works since we don't know the natural size. Instead we have to
run the positioning logic from the surface commit handler. This
results in some extra complexity, especially for interactive move.
See the new do_late_positioning() function in xdg.c.
Some TODOs/FIXMEs (non-blocking in my opinion):
- The view_wants_decorations() check is now duplicated in both the
new_surface and map event handlers. I'm not sure if this is necessary
but it seemed like the safest approach for now. More testing would be
nice, particularly with various combinations of config and client SSD
preferences.
- Aside from the interactive move case, the "late positioning" logic
always centers the view when un-maximizing, and does not invoke any
of the smart placement logic. If we want to invoke smart placement
here, I'd appreciate someone with more knowledge of that code to take
a look and figure out how to do that correctly.
2024-07-02 07:24:29 -04:00
|
|
|
check_natural_geometry(view);
|
2023-10-17 23:06:12 -04:00
|
|
|
set_initial_position(view, xwayland_surface);
|
2023-02-07 21:12:56 -05:00
|
|
|
/*
|
|
|
|
|
* 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().
|
|
|
|
|
*/
|
2023-02-08 23:19:14 -05:00
|
|
|
view->current = view->pending;
|
2022-09-04 22:47:54 -04:00
|
|
|
view_moved(view);
|
2022-03-11 06:23:08 +01:00
|
|
|
}
|
|
|
|
|
|
2024-08-13 10:01:49 +09:00
|
|
|
/*
|
|
|
|
|
* Exclude unfocusable views from wlr-foreign-toplevel. These
|
|
|
|
|
* views (notifications, floating toolbars, etc.) should not be
|
|
|
|
|
* shown in taskbars/docks/etc.
|
|
|
|
|
*/
|
|
|
|
|
if (!view->toplevel.handle && view_is_focusable(view)) {
|
|
|
|
|
init_foreign_toplevel(view);
|
|
|
|
|
foreign_toplevel_update_outputs(view);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-04 20:25:20 +01:00
|
|
|
/* Add commit here, as xwayland map/unmap can change the wlr_surface */
|
2022-11-25 13:41:12 -05:00
|
|
|
wl_signal_add(&xwayland_surface->surface->events.commit, &view->commit);
|
2020-09-03 20:50:35 +01:00
|
|
|
view->commit.notify = handle_commit;
|
|
|
|
|
|
2021-10-16 21:26:57 +01:00
|
|
|
view_impl_map(view);
|
2023-04-28 21:41:41 +01:00
|
|
|
view->been_mapped = true;
|
2022-11-22 05:11:50 -05:00
|
|
|
|
|
|
|
|
/* Update usable area to account for XWayland "struts" (panels) */
|
|
|
|
|
if (xwayland_surface->strut_partial) {
|
|
|
|
|
output_update_all_usable_areas(view->server, false);
|
|
|
|
|
}
|
2020-09-03 20:50:35 +01:00
|
|
|
}
|
|
|
|
|
|
2020-09-28 20:41:41 +01:00
|
|
|
static void
|
2023-07-01 12:37:47 -04:00
|
|
|
xwayland_view_unmap(struct view *view, bool client_request)
|
2020-09-03 20:50:35 +01:00
|
|
|
{
|
2022-05-17 18:10:29 +01:00
|
|
|
if (!view->mapped) {
|
2023-08-04 22:04:42 +01:00
|
|
|
goto out;
|
2021-12-11 23:24:44 +00:00
|
|
|
}
|
2022-05-17 18:10:29 +01:00
|
|
|
view->mapped = false;
|
|
|
|
|
wl_list_remove(&view->commit.link);
|
|
|
|
|
wlr_scene_node_set_enabled(&view->scene_tree->node, false);
|
2023-10-14 22:29:24 -04:00
|
|
|
view_impl_unmap(view);
|
2023-07-01 12:37:47 -04:00
|
|
|
|
2022-11-22 05:11:50 -05:00
|
|
|
/* Update usable area to account for XWayland "struts" (panels) */
|
|
|
|
|
if (xwayland_surface_from_view(view)->strut_partial) {
|
|
|
|
|
output_update_all_usable_areas(view->server, false);
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-01 12:37:47 -04:00
|
|
|
/*
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2023-08-04 22:04:42 +01:00
|
|
|
out:
|
2023-07-01 12:37:47 -04:00
|
|
|
if (client_request && view->toplevel.handle) {
|
|
|
|
|
wlr_foreign_toplevel_handle_v1_destroy(view->toplevel.handle);
|
|
|
|
|
}
|
2020-09-03 20:50:35 +01:00
|
|
|
}
|
|
|
|
|
|
2021-02-27 17:10:53 -05:00
|
|
|
static void
|
2023-02-06 20:01:18 +00:00
|
|
|
xwayland_view_maximize(struct view *view, bool maximized)
|
2021-02-27 17:10:53 -05:00
|
|
|
{
|
2022-11-25 13:41:12 -05:00
|
|
|
wlr_xwayland_surface_set_maximized(xwayland_surface_from_view(view),
|
|
|
|
|
maximized);
|
2021-02-27 17:10:53 -05:00
|
|
|
}
|
|
|
|
|
|
2023-06-25 11:37:56 +01:00
|
|
|
static void
|
|
|
|
|
xwayland_view_minimize(struct view *view, bool minimized)
|
|
|
|
|
{
|
|
|
|
|
wlr_xwayland_surface_set_minimized(xwayland_surface_from_view(view),
|
|
|
|
|
minimized);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-21 01:29:19 -04:00
|
|
|
static void
|
|
|
|
|
xwayland_view_move_to_front(struct view *view)
|
|
|
|
|
{
|
|
|
|
|
view_impl_move_to_front(view);
|
2024-05-04 23:10:45 +02:00
|
|
|
|
|
|
|
|
if (view->shaded) {
|
|
|
|
|
/*
|
|
|
|
|
* Ensure that we don't raise a shaded window
|
|
|
|
|
* to the front which then steals mouse events.
|
|
|
|
|
*/
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-21 01:29:19 -04:00
|
|
|
/*
|
|
|
|
|
* Update XWayland stacking order.
|
|
|
|
|
*
|
|
|
|
|
* FIXME: it would be better to restack above the next lower
|
|
|
|
|
* view, rather than on top of all other surfaces. Restacking
|
|
|
|
|
* the unmanaged surfaces afterward is ugly and still doesn't
|
|
|
|
|
* account for always-on-top views.
|
|
|
|
|
*/
|
|
|
|
|
wlr_xwayland_surface_restack(xwayland_surface_from_view(view),
|
|
|
|
|
NULL, XCB_STACK_MODE_ABOVE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
xwayland_view_move_to_back(struct view *view)
|
|
|
|
|
{
|
|
|
|
|
view_impl_move_to_back(view);
|
|
|
|
|
/* Update XWayland stacking order */
|
|
|
|
|
wlr_xwayland_surface_restack(xwayland_surface_from_view(view),
|
|
|
|
|
NULL, XCB_STACK_MODE_BELOW);
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-19 17:19:37 +01:00
|
|
|
static struct view *
|
2023-08-02 20:57:39 +01:00
|
|
|
xwayland_view_get_root(struct view *view)
|
2023-07-19 17:19:37 +01:00
|
|
|
{
|
|
|
|
|
struct wlr_xwayland_surface *root = top_parent_of(view);
|
xwayland: fix segv bug when starting game
...for example `Fall Guys`. It is believed to be caused by setting
override-redirect on an xwayland-surface with a child window, thus
breaking the way root-toplevels are obtained.
```
(threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0)
at pthread_kill.c:44
at pthread_kill.c:78
(fmt=0x7739d9f9bb68 "%s%s%s:%u: %s%sAssertion `%s' failed.\n%n", assertion=assertion@entry=0x5a76573f9222 "root", file=file@entry=0x5a76573f9606 "../labwc/src/view.c", line=line@entry=2013, function=function@entry=0x5a7657400320 <__PRETTY_FUNCTION__.14> "view_move_to_front") at assert.c:94
(assertion=assertion@entry=0x5a76573f9222 "root", file=file@entry=0x5a76573f9606 "../labwc/src/view.c", line=line@entry=2013, function=function@entry=0x5a7657400320 <__PRETTY_FUNCTION__.14> "view_move_to_front") at assert.c:103
at ../labwc/src/view.c:2013
at ../labwc/src/view-impl-common.c:30
at ../labwc/src/xwayland.c:677
at ../wayland-1.22.0/src/wayland-server.c:2241
(signal=signal@entry=0x5a7659025160, data=data@entry=0x5a7659024e90)
at ../wayland-1.22.0/src/wayland-server.c:2241
at ../subprojects/wlroots/types/wlr_compositor.c:493
(cif=cif@entry=0x7ffc74d32530, fn=<optimized out>, rvalue=<optimized out>, avalue=<optimized out>, closure=closure@entry=0x0) at ../src/x86/ffi64.c:673
(cif=cif@entry=0x7ffc74d32530, fn=<optimized out>, rvalue=rvalue@entry=0x0, avalue=avalue@entry=0x7ffc74d32600) at ../src/x86/ffi64.c:710
(closure=closure@entry=0x5a7658f5adc0, target=<optimized out>,
target@entry=0x5a7659025240, opcode=opcode@entry=6, data=<optimized out>,
data@entry=0x5a7658609820, flags=2) at ../wayland-1.22.0/src/connection.c:1025
(fd=<optimized out>, mask=<optimized out>, data=<optimized out>)
at ../wayland-1.22.0/src/wayland-server.c:438
(loop=0x5a7657816e60, timeout=timeout@entry=-1)
at ../wayland-1.22.0/src/event-loop.c:1027
at ../wayland-1.22.0/src/wayland-server.c:1493
at ../labwc/src/main.c:179
```
Reported-by: @kode54
2024-03-17 21:06:46 +00:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The case of root->data == NULL is unlikely, but has been reported
|
|
|
|
|
* when starting XWayland games (for example 'Fall Guys'). It is
|
|
|
|
|
* believed to be caused by setting override-redirect on the root
|
|
|
|
|
* wlr_xwayland_surface making it not be associated with a view anymore.
|
|
|
|
|
*/
|
|
|
|
|
return (root && root->data) ? (struct view *)root->data : view;
|
2023-07-19 17:19:37 +01:00
|
|
|
}
|
|
|
|
|
|
2023-08-02 20:57:39 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-16 19:24:26 +01:00
|
|
|
static void
|
2023-02-06 20:01:18 +00:00
|
|
|
xwayland_view_set_activated(struct view *view, bool activated)
|
2021-10-16 19:24:26 +01:00
|
|
|
{
|
2022-11-25 13:41:12 -05:00
|
|
|
struct wlr_xwayland_surface *xwayland_surface =
|
|
|
|
|
xwayland_surface_from_view(view);
|
2021-10-16 19:24:26 +01:00
|
|
|
|
2022-11-25 13:41:12 -05:00
|
|
|
if (activated && xwayland_surface->minimized) {
|
|
|
|
|
wlr_xwayland_surface_set_minimized(xwayland_surface, false);
|
2021-10-16 19:24:26 +01:00
|
|
|
}
|
|
|
|
|
|
2022-11-25 13:41:12 -05:00
|
|
|
wlr_xwayland_surface_activate(xwayland_surface, activated);
|
2021-10-16 19:24:26 +01:00
|
|
|
}
|
|
|
|
|
|
2021-08-23 22:05:30 +01:00
|
|
|
static void
|
2023-02-06 20:01:18 +00:00
|
|
|
xwayland_view_set_fullscreen(struct view *view, bool fullscreen)
|
2021-08-23 22:05:30 +01:00
|
|
|
{
|
2022-11-25 13:41:12 -05:00
|
|
|
wlr_xwayland_surface_set_fullscreen(xwayland_surface_from_view(view),
|
|
|
|
|
fullscreen);
|
2021-08-23 22:05:30 +01:00
|
|
|
}
|
|
|
|
|
|
2024-04-22 17:43:46 +02:00
|
|
|
static pid_t
|
|
|
|
|
xwayland_view_get_pid(struct view *view)
|
|
|
|
|
{
|
|
|
|
|
assert(view);
|
|
|
|
|
|
|
|
|
|
struct wlr_xwayland_surface *xwayland_surface =
|
|
|
|
|
xwayland_surface_from_view(view);
|
|
|
|
|
if (!xwayland_surface) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
return xwayland_surface->pid;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-04 23:10:45 +02:00
|
|
|
static void
|
|
|
|
|
xwayland_view_shade(struct view *view, bool shaded)
|
|
|
|
|
{
|
|
|
|
|
assert(view);
|
|
|
|
|
|
|
|
|
|
/* Ensure that clicks on some xwayland surface don't end up on the shaded one */
|
|
|
|
|
if (shaded) {
|
|
|
|
|
wlr_xwayland_surface_restack(xwayland_surface_from_view(view),
|
|
|
|
|
NULL, XCB_STACK_MODE_BELOW);
|
|
|
|
|
} else {
|
|
|
|
|
xwayland_adjust_stacking_order(view->server);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-06 20:01:18 +00:00
|
|
|
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,
|
2023-06-25 11:37:56 +01:00
|
|
|
.minimize = xwayland_view_minimize,
|
2023-10-21 01:29:19 -04:00
|
|
|
.move_to_front = xwayland_view_move_to_front,
|
|
|
|
|
.move_to_back = xwayland_view_move_to_back,
|
2024-05-04 23:10:45 +02:00
|
|
|
.shade = xwayland_view_shade,
|
2023-08-02 20:57:39 +01:00
|
|
|
.get_root = xwayland_view_get_root,
|
|
|
|
|
.append_children = xwayland_view_append_children,
|
2023-09-22 01:22:19 -04:00
|
|
|
.get_size_hints = xwayland_view_get_size_hints,
|
2023-09-23 11:51:47 -04:00
|
|
|
.wants_focus = xwayland_view_wants_focus,
|
2023-11-27 17:11:29 -05:00
|
|
|
.has_strut_partial = xwayland_view_has_strut_partial,
|
2024-04-19 20:15:49 +02:00
|
|
|
.contains_window_type = xwayland_view_contains_window_type,
|
2024-04-22 17:43:46 +02:00
|
|
|
.get_pid = xwayland_view_get_pid,
|
2020-09-02 20:20:52 +01:00
|
|
|
};
|
|
|
|
|
|
2023-04-19 18:10:07 -04:00
|
|
|
void
|
|
|
|
|
xwayland_view_create(struct server *server,
|
|
|
|
|
struct wlr_xwayland_surface *xsurface, bool mapped)
|
2019-12-27 21:22:45 +00:00
|
|
|
{
|
2022-11-22 20:13:06 +00:00
|
|
|
struct xwayland_view *xwayland_view = znew(*xwayland_view);
|
|
|
|
|
struct view *view = &xwayland_view->base;
|
|
|
|
|
|
2019-12-26 21:37:31 +00:00
|
|
|
view->server = server;
|
|
|
|
|
view->type = LAB_XWAYLAND_VIEW;
|
2023-02-06 20:01:18 +00:00
|
|
|
view->impl = &xwayland_view_impl;
|
2022-11-11 15:54:26 -05:00
|
|
|
|
|
|
|
|
/*
|
2023-04-17 17:02:11 +01:00
|
|
|
* 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.
|
2022-11-11 15:54:26 -05:00
|
|
|
*/
|
2022-11-25 13:41:12 -05:00
|
|
|
xwayland_view->xwayland_surface = xsurface;
|
2022-11-11 15:54:26 -05:00
|
|
|
xsurface->data = view;
|
2019-12-26 21:37:31 +00:00
|
|
|
|
2022-06-15 02:02:50 +02:00
|
|
|
view->workspace = server->workspace_current;
|
|
|
|
|
view->scene_tree = wlr_scene_tree_create(view->workspace->tree);
|
2023-04-17 17:02:11 +01:00
|
|
|
node_descriptor_create(&view->scene_tree->node, LAB_NODE_DESC_VIEW, view);
|
2021-10-20 16:02:39 +00:00
|
|
|
|
2023-10-16 02:01:35 -04:00
|
|
|
CONNECT_SIGNAL(xsurface, view, destroy);
|
|
|
|
|
CONNECT_SIGNAL(xsurface, view, request_minimize);
|
|
|
|
|
CONNECT_SIGNAL(xsurface, view, request_maximize);
|
|
|
|
|
CONNECT_SIGNAL(xsurface, view, request_fullscreen);
|
|
|
|
|
CONNECT_SIGNAL(xsurface, view, request_move);
|
|
|
|
|
CONNECT_SIGNAL(xsurface, view, request_resize);
|
|
|
|
|
CONNECT_SIGNAL(xsurface, view, set_title);
|
2020-09-07 19:34:11 +01:00
|
|
|
|
2022-11-22 20:13:06 +00:00
|
|
|
/* Events specific to XWayland views */
|
2023-06-15 02:35:43 -07:00
|
|
|
CONNECT_SIGNAL(xsurface, xwayland_view, associate);
|
|
|
|
|
CONNECT_SIGNAL(xsurface, xwayland_view, dissociate);
|
2023-10-16 02:01:35 -04:00
|
|
|
CONNECT_SIGNAL(xsurface, xwayland_view, request_activate);
|
|
|
|
|
CONNECT_SIGNAL(xsurface, xwayland_view, request_configure);
|
|
|
|
|
CONNECT_SIGNAL(xsurface, xwayland_view, set_class);
|
|
|
|
|
CONNECT_SIGNAL(xsurface, xwayland_view, set_decorations);
|
|
|
|
|
CONNECT_SIGNAL(xsurface, xwayland_view, set_override_redirect);
|
2022-11-22 05:11:50 -05:00
|
|
|
CONNECT_SIGNAL(xsurface, xwayland_view, set_strut_partial);
|
2024-01-24 18:06:57 +01:00
|
|
|
CONNECT_SIGNAL(xsurface, xwayland_view, set_window_type);
|
2024-02-12 19:52:36 -05:00
|
|
|
CONNECT_SIGNAL(xsurface, xwayland_view, map_request);
|
2022-05-03 19:44:31 +01:00
|
|
|
|
2020-09-07 19:34:11 +01:00
|
|
|
wl_list_insert(&view->server->views, &view->link);
|
2023-04-19 18:10:07 -04:00
|
|
|
|
2024-01-23 18:22:56 -05:00
|
|
|
if (xsurface->surface) {
|
|
|
|
|
handle_associate(&xwayland_view->associate, NULL);
|
|
|
|
|
}
|
2023-04-19 18:10:07 -04:00
|
|
|
if (mapped) {
|
|
|
|
|
xwayland_view_map(view);
|
|
|
|
|
}
|
2023-04-17 17:02:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2023-04-19 18:10:07 -04:00
|
|
|
} else {
|
|
|
|
|
xwayland_view_create(server, xsurface, /* mapped */ false);
|
2023-04-17 17:02:11 +01:00
|
|
|
}
|
2019-12-26 21:37:31 +00:00
|
|
|
}
|
2023-01-07 17:50:33 -05:00
|
|
|
|
|
|
|
|
static void
|
2024-01-24 18:06:57 +01:00
|
|
|
sync_atoms(xcb_connection_t *xcb_conn)
|
|
|
|
|
{
|
|
|
|
|
assert(xcb_conn);
|
|
|
|
|
|
|
|
|
|
wlr_log(WLR_DEBUG, "Syncing X11 atoms");
|
2024-04-19 20:15:49 +02:00
|
|
|
xcb_intern_atom_cookie_t cookies[WINDOW_TYPE_LEN];
|
2024-01-24 18:06:57 +01:00
|
|
|
|
|
|
|
|
/* First request everything and then loop over the results to reduce latency */
|
2024-04-19 20:15:49 +02:00
|
|
|
for (size_t i = 0; i < WINDOW_TYPE_LEN; i++) {
|
2024-01-24 18:06:57 +01:00
|
|
|
cookies[i] = xcb_intern_atom(xcb_conn, 0,
|
|
|
|
|
strlen(atom_names[i]), atom_names[i]);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-19 20:15:49 +02:00
|
|
|
for (size_t i = 0; i < WINDOW_TYPE_LEN; i++) {
|
2024-01-24 18:06:57 +01:00
|
|
|
xcb_generic_error_t *err = NULL;
|
|
|
|
|
xcb_intern_atom_reply_t *reply =
|
|
|
|
|
xcb_intern_atom_reply(xcb_conn, cookies[i], &err);
|
|
|
|
|
if (reply) {
|
|
|
|
|
atoms[i] = reply->atom;
|
|
|
|
|
wlr_log(WLR_DEBUG, "Got X11 atom for %s: %u",
|
|
|
|
|
atom_names[i], reply->atom);
|
|
|
|
|
}
|
|
|
|
|
if (err) {
|
|
|
|
|
wlr_log(WLR_INFO, "Failed to get X11 atom for %s",
|
|
|
|
|
atom_names[i]);
|
|
|
|
|
}
|
|
|
|
|
free(reply);
|
|
|
|
|
free(err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
handle_server_ready(struct wl_listener *listener, void *data)
|
|
|
|
|
{
|
2024-07-20 04:40:11 -04:00
|
|
|
/* Fire an Xwayland startup script if one (or many) can be found */
|
|
|
|
|
session_run_script("xinitrc");
|
|
|
|
|
|
2024-01-24 18:06:57 +01:00
|
|
|
xcb_connection_t *xcb_conn = xcb_connect(NULL, NULL);
|
|
|
|
|
if (xcb_connection_has_error(xcb_conn)) {
|
|
|
|
|
wlr_log(WLR_ERROR, "Failed to create xcb connection");
|
|
|
|
|
|
|
|
|
|
/* Just clear all existing atoms */
|
2024-04-19 20:15:49 +02:00
|
|
|
for (size_t i = 0; i < WINDOW_TYPE_LEN; i++) {
|
2024-01-24 18:06:57 +01:00
|
|
|
atoms[i] = XCB_ATOM_NONE;
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wlr_log(WLR_DEBUG, "Connected to xwayland");
|
|
|
|
|
sync_atoms(xcb_conn);
|
|
|
|
|
wlr_log(WLR_DEBUG, "Disconnecting from xwayland");
|
|
|
|
|
xcb_disconnect(xcb_conn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
handle_xwm_ready(struct wl_listener *listener, void *data)
|
2023-01-07 17:50:33 -05:00
|
|
|
{
|
|
|
|
|
struct server *server =
|
2024-01-24 18:06:57 +01:00
|
|
|
wl_container_of(listener, server, xwayland_xwm_ready);
|
2023-01-07 17:50:33 -05:00
|
|
|
wlr_xwayland_set_seat(server->xwayland, server->seat.seat);
|
2023-10-24 13:49:15 -04:00
|
|
|
xwayland_update_workarea(server);
|
2023-01-07 17:50:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
xwayland_server_init(struct server *server, struct wlr_compositor *compositor)
|
|
|
|
|
{
|
|
|
|
|
server->xwayland =
|
2024-07-03 13:43:04 -04:00
|
|
|
wlr_xwayland_create(server->wl_display,
|
|
|
|
|
compositor, /* lazy */ !rc.xwayland_persistence);
|
2023-01-07 17:50:33 -05:00
|
|
|
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,
|
2023-01-31 11:43:45 +01:00
|
|
|
&server->xwayland_new_surface);
|
2023-01-07 17:50:33 -05:00
|
|
|
|
2024-01-24 18:06:57 +01:00
|
|
|
server->xwayland_server_ready.notify = handle_server_ready;
|
|
|
|
|
wl_signal_add(&server->xwayland->server->events.ready,
|
|
|
|
|
&server->xwayland_server_ready);
|
|
|
|
|
|
|
|
|
|
server->xwayland_xwm_ready.notify = handle_xwm_ready;
|
2023-01-07 17:50:33 -05:00
|
|
|
wl_signal_add(&server->xwayland->events.ready,
|
2024-01-24 18:06:57 +01:00
|
|
|
&server->xwayland_xwm_ready);
|
2023-01-07 17:50:33 -05:00
|
|
|
|
|
|
|
|
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;
|
2023-01-31 11:43:45 +01:00
|
|
|
xcursor = wlr_xcursor_manager_get_xcursor(
|
|
|
|
|
server->seat.xcursor_manager, XCURSOR_DEFAULT, 1);
|
2023-01-07 17:50:33 -05:00
|
|
|
if (xcursor) {
|
|
|
|
|
struct wlr_xcursor_image *image = xcursor->images[0];
|
|
|
|
|
wlr_xwayland_set_cursor(server->xwayland, image->buffer,
|
2023-01-31 11:43:45 +01:00
|
|
|
image->width * 4, image->width,
|
|
|
|
|
image->height, image->hotspot_x,
|
|
|
|
|
image->hotspot_y);
|
2023-01-07 17:50:33 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-21 03:09:22 +01:00
|
|
|
/*
|
|
|
|
|
* Until we expose the workspaces to xwayland we need a way to
|
|
|
|
|
* ensure that xwayland views on the current workspace are always
|
|
|
|
|
* stacked above xwayland views on other workspaces.
|
|
|
|
|
*
|
|
|
|
|
* If we fail to do so, issues arise in scenarios where we change
|
|
|
|
|
* the mouse focus but do not change the (xwayland) stacking order.
|
|
|
|
|
*
|
|
|
|
|
* Reproducer:
|
|
|
|
|
* - open at least two xwayland windows which allow scrolling
|
|
|
|
|
* (some X11 terminal with 'man man' for example)
|
|
|
|
|
* - switch to another workspace, open another xwayland
|
|
|
|
|
* window which allows scrolling and maximize it
|
|
|
|
|
* - switch back to the previous workspace with the two windows
|
|
|
|
|
* - move the mouse to the xwayland window that does *not* have focus
|
|
|
|
|
* - start scrolling
|
|
|
|
|
* - all scroll events should end up on the maximized window on the other workspace
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
xwayland_adjust_stacking_order(struct server *server)
|
|
|
|
|
{
|
|
|
|
|
struct view **view;
|
|
|
|
|
struct wl_array views;
|
|
|
|
|
|
|
|
|
|
wl_array_init(&views);
|
|
|
|
|
view_array_append(server, &views, LAB_VIEW_CRITERIA_ALWAYS_ON_TOP);
|
|
|
|
|
view_array_append(server, &views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE
|
|
|
|
|
| LAB_VIEW_CRITERIA_NO_ALWAYS_ON_TOP);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* view_array_append() provides top-most windows
|
|
|
|
|
* first so we simply reverse the iteration here
|
|
|
|
|
*/
|
|
|
|
|
wl_array_for_each_reverse(view, &views) {
|
|
|
|
|
view_move_to_front(*view);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wl_array_release(&views);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-10 00:01:54 +02:00
|
|
|
void
|
|
|
|
|
xwayland_reset_cursor(struct server *server)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* As xwayland caches the pixel data when not yet started up
|
|
|
|
|
* due to the delayed lazy startup approach, we do have to
|
|
|
|
|
* re-set the xwayland cursor image. Otherwise the first X11
|
|
|
|
|
* client connected will cause the xwayland server to use
|
|
|
|
|
* the cached (and potentially destroyed) pixel data.
|
|
|
|
|
*
|
|
|
|
|
* Calling this function after reloading the cursor theme
|
|
|
|
|
* ensures that the cached pixel data keeps being valid.
|
|
|
|
|
*
|
|
|
|
|
* To reproduce:
|
|
|
|
|
* - Compile with b_sanitize=address,undefined
|
|
|
|
|
* - Start labwc (nothing in autostart that could create
|
|
|
|
|
* a X11 connection, e.g. no GTK or X11 application)
|
|
|
|
|
* - Reconfigure
|
|
|
|
|
* - Start some X11 client
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (!server->xwayland) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(
|
|
|
|
|
server->seat.xcursor_manager, XCURSOR_DEFAULT, 1);
|
|
|
|
|
|
|
|
|
|
if (xcursor && !server->xwayland->xwm) {
|
|
|
|
|
/* Prevents setting the cursor on an active xwayland server */
|
|
|
|
|
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);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (server->xwayland->cursor) {
|
|
|
|
|
/*
|
|
|
|
|
* The previous configured theme has set the
|
|
|
|
|
* default cursor or the xwayland server is
|
|
|
|
|
* currently running but still has a cached
|
|
|
|
|
* xcursor set that will be used on the next
|
|
|
|
|
* xwayland destroy -> lazy startup cycle.
|
|
|
|
|
*/
|
|
|
|
|
zfree(server->xwayland->cursor);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-07 17:50:33 -05:00
|
|
|
void
|
|
|
|
|
xwayland_server_finish(struct server *server)
|
|
|
|
|
{
|
2023-07-17 03:37:21 -04:00
|
|
|
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
|
|
|
|
|
*/
|
2023-01-07 17:50:33 -05:00
|
|
|
server->xwayland = NULL;
|
2023-07-17 03:37:21 -04:00
|
|
|
wlr_xwayland_destroy(xwayland);
|
2023-01-07 17:50:33 -05:00
|
|
|
}
|
2023-10-24 13:49:15 -04:00
|
|
|
|
2022-11-22 05:11:50 -05:00
|
|
|
static bool
|
|
|
|
|
intervals_overlap(int start_a, int end_a, int start_b, int end_b)
|
|
|
|
|
{
|
|
|
|
|
/* check for empty intervals */
|
|
|
|
|
if (end_a <= start_a || end_b <= start_b) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return start_a < start_b ?
|
|
|
|
|
start_b < end_a : /* B starts within A */
|
|
|
|
|
start_a < end_b; /* A starts within B */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Subtract the area of an XWayland view (e.g. panel) from the usable
|
|
|
|
|
* area of the output based on _NET_WM_STRUT_PARTIAL property.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
xwayland_adjust_usable_area(struct view *view, struct wlr_output_layout *layout,
|
|
|
|
|
struct wlr_output *output, struct wlr_box *usable)
|
|
|
|
|
{
|
|
|
|
|
assert(view);
|
|
|
|
|
assert(layout);
|
|
|
|
|
assert(output);
|
|
|
|
|
assert(usable);
|
|
|
|
|
|
|
|
|
|
if (view->type != LAB_XWAYLAND_VIEW) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
xcb_ewmh_wm_strut_partial_t *strut =
|
|
|
|
|
xwayland_surface_from_view(view)->strut_partial;
|
|
|
|
|
if (!strut) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* these are layout coordinates */
|
|
|
|
|
struct wlr_box lb = { 0 };
|
|
|
|
|
wlr_output_layout_get_box(layout, NULL, &lb);
|
|
|
|
|
struct wlr_box ob = { 0 };
|
|
|
|
|
wlr_output_layout_get_box(layout, output, &ob);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* strut->right/bottom are offsets from the lower right corner
|
|
|
|
|
* of the X11 screen, which should generally correspond with the
|
|
|
|
|
* lower right corner of the output layout
|
|
|
|
|
*/
|
|
|
|
|
double strut_left = strut->left;
|
|
|
|
|
double strut_right = (lb.x + lb.width) - strut->right;
|
|
|
|
|
double strut_top = strut->top;
|
|
|
|
|
double strut_bottom = (lb.y + lb.height) - strut->bottom;
|
|
|
|
|
|
|
|
|
|
/* convert layout to output coordinates */
|
|
|
|
|
wlr_output_layout_output_coords(layout, output,
|
|
|
|
|
&strut_left, &strut_top);
|
|
|
|
|
wlr_output_layout_output_coords(layout, output,
|
|
|
|
|
&strut_right, &strut_bottom);
|
|
|
|
|
|
|
|
|
|
/* deal with right/bottom rather than width/height */
|
|
|
|
|
int usable_right = usable->x + usable->width;
|
|
|
|
|
int usable_bottom = usable->y + usable->height;
|
|
|
|
|
|
|
|
|
|
/* here we mix output and layout coordinates; be careful */
|
|
|
|
|
if (strut_left > usable->x && strut_left < usable_right
|
|
|
|
|
&& intervals_overlap(ob.y, ob.y + ob.height,
|
|
|
|
|
strut->left_start_y, strut->left_end_y + 1)) {
|
|
|
|
|
usable->x = strut_left;
|
|
|
|
|
}
|
|
|
|
|
if (strut_right > usable->x && strut_right < usable_right
|
|
|
|
|
&& intervals_overlap(ob.y, ob.y + ob.height,
|
|
|
|
|
strut->right_start_y, strut->right_end_y + 1)) {
|
|
|
|
|
usable_right = strut_right;
|
|
|
|
|
}
|
|
|
|
|
if (strut_top > usable->y && strut_top < usable_bottom
|
|
|
|
|
&& intervals_overlap(ob.x, ob.x + ob.width,
|
|
|
|
|
strut->top_start_x, strut->top_end_x + 1)) {
|
|
|
|
|
usable->y = strut_top;
|
|
|
|
|
}
|
|
|
|
|
if (strut_bottom > usable->y && strut_bottom < usable_bottom
|
|
|
|
|
&& intervals_overlap(ob.x, ob.x + ob.width,
|
|
|
|
|
strut->bottom_start_x, strut->bottom_end_x + 1)) {
|
|
|
|
|
usable_bottom = strut_bottom;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
usable->width = usable_right - usable->x;
|
|
|
|
|
usable->height = usable_bottom - usable->y;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-24 13:49:15 -04:00
|
|
|
void
|
|
|
|
|
xwayland_update_workarea(struct server *server)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Do nothing if called during destroy or before xwayland is ready.
|
|
|
|
|
* This function will be called again from the ready signal handler.
|
|
|
|
|
*/
|
|
|
|
|
if (!server->xwayland || !server->xwayland->xwm) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct wlr_box lb;
|
|
|
|
|
wlr_output_layout_get_box(server->output_layout, NULL, &lb);
|
|
|
|
|
|
|
|
|
|
/* Compute outer edges of layout (excluding negative regions) */
|
|
|
|
|
int layout_left = MAX(0, lb.x);
|
|
|
|
|
int layout_right = MAX(0, lb.x + lb.width);
|
|
|
|
|
int layout_top = MAX(0, lb.y);
|
|
|
|
|
int layout_bottom = MAX(0, lb.y + lb.height);
|
|
|
|
|
|
|
|
|
|
/* Workarea is initially the entire layout */
|
|
|
|
|
int workarea_left = layout_left;
|
|
|
|
|
int workarea_right = layout_right;
|
|
|
|
|
int workarea_top = layout_top;
|
|
|
|
|
int workarea_bottom = layout_bottom;
|
|
|
|
|
|
|
|
|
|
struct output *output;
|
|
|
|
|
wl_list_for_each(output, &server->outputs, link) {
|
|
|
|
|
if (!output_is_usable(output)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct wlr_box ob;
|
|
|
|
|
wlr_output_layout_get_box(server->output_layout,
|
|
|
|
|
output->wlr_output, &ob);
|
|
|
|
|
|
|
|
|
|
/* Compute edges of output */
|
|
|
|
|
int output_left = ob.x;
|
|
|
|
|
int output_right = ob.x + ob.width;
|
|
|
|
|
int output_top = ob.y;
|
|
|
|
|
int output_bottom = ob.y + ob.height;
|
|
|
|
|
|
|
|
|
|
/* Compute edges of usable area */
|
|
|
|
|
int usable_left = output_left + output->usable_area.x;
|
|
|
|
|
int usable_right = usable_left + output->usable_area.width;
|
|
|
|
|
int usable_top = output_top + output->usable_area.y;
|
|
|
|
|
int usable_bottom = usable_top + output->usable_area.height;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Only adjust workarea edges for output edges that are
|
|
|
|
|
* aligned with outer edges of layout
|
|
|
|
|
*/
|
|
|
|
|
if (output_left == layout_left) {
|
|
|
|
|
workarea_left = MAX(workarea_left, usable_left);
|
|
|
|
|
}
|
|
|
|
|
if (output_right == layout_right) {
|
|
|
|
|
workarea_right = MIN(workarea_right, usable_right);
|
|
|
|
|
}
|
|
|
|
|
if (output_top == layout_top) {
|
|
|
|
|
workarea_top = MAX(workarea_top, usable_top);
|
|
|
|
|
}
|
|
|
|
|
if (output_bottom == layout_bottom) {
|
|
|
|
|
workarea_bottom = MIN(workarea_bottom, usable_bottom);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Set _NET_WORKAREA property. We don't report virtual desktops
|
|
|
|
|
* to XWayland, so we set only one workarea.
|
|
|
|
|
*/
|
|
|
|
|
struct wlr_box workarea = {
|
|
|
|
|
.x = workarea_left,
|
|
|
|
|
.y = workarea_top,
|
|
|
|
|
.width = workarea_right - workarea_left,
|
|
|
|
|
.height = workarea_bottom - workarea_top,
|
|
|
|
|
};
|
|
|
|
|
wlr_xwayland_set_workareas(server->xwayland, &workarea, 1);
|
|
|
|
|
}
|