44fc1be913
The cairo surface used for the icon must be completely given to the frame as soon as said frame has been created. To prevent both the window and the frame from sharing ownership of the icon, we set window->icon_surface back to NULL right after creating or changing the frame, only keeping it there when no frame has been created yet. Fixes https://lists.freedesktop.org/archives/wayland-devel/2018-January/036655.html Reported-by: Derek Foreman <derekf@osg.samsung.com> Tested-by: Derek Foreman <derekf@osg.samsung.com> Signed-off-by: Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2937 lines
82 KiB
C
2937 lines
82 KiB
C
/*
|
||
* Copyright © 2011 Intel Corporation
|
||
*
|
||
* Permission is hereby granted, free of charge, to any person obtaining
|
||
* a copy of this software and associated documentation files (the
|
||
* "Software"), to deal in the Software without restriction, including
|
||
* without limitation the rights to use, copy, modify, merge, publish,
|
||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||
* permit persons to whom the Software is furnished to do so, subject to
|
||
* the following conditions:
|
||
*
|
||
* The above copyright notice and this permission notice (including the
|
||
* next paragraph) shall be included in all copies or substantial
|
||
* portions of the Software.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
* SOFTWARE.
|
||
*/
|
||
|
||
#include "config.h"
|
||
|
||
#include <stdlib.h>
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <sys/socket.h>
|
||
#include <sys/un.h>
|
||
#include <fcntl.h>
|
||
#include <errno.h>
|
||
#include <unistd.h>
|
||
#include <signal.h>
|
||
#include <limits.h>
|
||
#include <assert.h>
|
||
#include <X11/Xcursor/Xcursor.h>
|
||
#include <linux/input.h>
|
||
|
||
#include "compositor.h"
|
||
#include "xwayland.h"
|
||
#include "xwayland-internal-interface.h"
|
||
|
||
#include "cairo-util.h"
|
||
#include "hash.h"
|
||
#include "shared/helpers.h"
|
||
|
||
struct wm_size_hints {
|
||
uint32_t flags;
|
||
int32_t x, y;
|
||
int32_t width, height; /* should set so old wm's don't mess up */
|
||
int32_t min_width, min_height;
|
||
int32_t max_width, max_height;
|
||
int32_t width_inc, height_inc;
|
||
struct {
|
||
int32_t x;
|
||
int32_t y;
|
||
} min_aspect, max_aspect;
|
||
int32_t base_width, base_height;
|
||
int32_t win_gravity;
|
||
};
|
||
|
||
#define USPosition (1L << 0)
|
||
#define USSize (1L << 1)
|
||
#define PPosition (1L << 2)
|
||
#define PSize (1L << 3)
|
||
#define PMinSize (1L << 4)
|
||
#define PMaxSize (1L << 5)
|
||
#define PResizeInc (1L << 6)
|
||
#define PAspect (1L << 7)
|
||
#define PBaseSize (1L << 8)
|
||
#define PWinGravity (1L << 9)
|
||
|
||
struct motif_wm_hints {
|
||
uint32_t flags;
|
||
uint32_t functions;
|
||
uint32_t decorations;
|
||
int32_t input_mode;
|
||
uint32_t status;
|
||
};
|
||
|
||
#define MWM_HINTS_FUNCTIONS (1L << 0)
|
||
#define MWM_HINTS_DECORATIONS (1L << 1)
|
||
#define MWM_HINTS_INPUT_MODE (1L << 2)
|
||
#define MWM_HINTS_STATUS (1L << 3)
|
||
|
||
#define MWM_FUNC_ALL (1L << 0)
|
||
#define MWM_FUNC_RESIZE (1L << 1)
|
||
#define MWM_FUNC_MOVE (1L << 2)
|
||
#define MWM_FUNC_MINIMIZE (1L << 3)
|
||
#define MWM_FUNC_MAXIMIZE (1L << 4)
|
||
#define MWM_FUNC_CLOSE (1L << 5)
|
||
|
||
#define MWM_DECOR_ALL (1L << 0)
|
||
#define MWM_DECOR_BORDER (1L << 1)
|
||
#define MWM_DECOR_RESIZEH (1L << 2)
|
||
#define MWM_DECOR_TITLE (1L << 3)
|
||
#define MWM_DECOR_MENU (1L << 4)
|
||
#define MWM_DECOR_MINIMIZE (1L << 5)
|
||
#define MWM_DECOR_MAXIMIZE (1L << 6)
|
||
|
||
#define MWM_DECOR_EVERYTHING \
|
||
(MWM_DECOR_BORDER | MWM_DECOR_RESIZEH | MWM_DECOR_TITLE | \
|
||
MWM_DECOR_MENU | MWM_DECOR_MINIMIZE | MWM_DECOR_MAXIMIZE)
|
||
|
||
#define MWM_INPUT_MODELESS 0
|
||
#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1
|
||
#define MWM_INPUT_SYSTEM_MODAL 2
|
||
#define MWM_INPUT_FULL_APPLICATION_MODAL 3
|
||
#define MWM_INPUT_APPLICATION_MODAL MWM_INPUT_PRIMARY_APPLICATION_MODAL
|
||
|
||
#define MWM_TEAROFF_WINDOW (1L<<0)
|
||
|
||
#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0
|
||
#define _NET_WM_MOVERESIZE_SIZE_TOP 1
|
||
#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2
|
||
#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3
|
||
#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4
|
||
#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5
|
||
#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6
|
||
#define _NET_WM_MOVERESIZE_SIZE_LEFT 7
|
||
#define _NET_WM_MOVERESIZE_MOVE 8 /* movement only */
|
||
#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 /* size via keyboard */
|
||
#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */
|
||
#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */
|
||
|
||
struct weston_output_weak_ref {
|
||
struct weston_output *output;
|
||
struct wl_listener destroy_listener;
|
||
};
|
||
|
||
struct weston_wm_window {
|
||
struct weston_wm *wm;
|
||
xcb_window_t id;
|
||
xcb_window_t frame_id;
|
||
struct frame *frame;
|
||
cairo_surface_t *cairo_surface;
|
||
int icon;
|
||
cairo_surface_t *icon_surface; /* A temporary slot, to be passed to frame on creation */
|
||
uint32_t surface_id;
|
||
struct weston_surface *surface;
|
||
struct weston_desktop_xwayland_surface *shsurf;
|
||
struct wl_listener surface_destroy_listener;
|
||
struct wl_event_source *repaint_source;
|
||
struct wl_event_source *configure_source;
|
||
int properties_dirty;
|
||
int pid;
|
||
char *machine;
|
||
char *class;
|
||
char *name;
|
||
struct weston_wm_window *transient_for;
|
||
uint32_t protocols;
|
||
xcb_atom_t type;
|
||
int width, height;
|
||
int x;
|
||
int y;
|
||
bool pos_dirty;
|
||
int map_request_x;
|
||
int map_request_y;
|
||
struct weston_output_weak_ref legacy_fullscreen_output;
|
||
int saved_width, saved_height;
|
||
int decorate;
|
||
uint32_t last_button_time;
|
||
int did_double;
|
||
int override_redirect;
|
||
int fullscreen;
|
||
int has_alpha;
|
||
int delete_window;
|
||
int maximized_vert;
|
||
int maximized_horz;
|
||
struct wm_size_hints size_hints;
|
||
struct motif_wm_hints motif_hints;
|
||
struct wl_list link;
|
||
};
|
||
|
||
static struct weston_wm_window *
|
||
get_wm_window(struct weston_surface *surface);
|
||
|
||
static void
|
||
weston_wm_set_net_active_window(struct weston_wm *wm, xcb_window_t window);
|
||
|
||
static void
|
||
weston_wm_window_schedule_repaint(struct weston_wm_window *window);
|
||
|
||
static int
|
||
legacy_fullscreen(struct weston_wm *wm,
|
||
struct weston_wm_window *window,
|
||
struct weston_output **output_ret);
|
||
|
||
static void
|
||
xserver_map_shell_surface(struct weston_wm_window *window,
|
||
struct weston_surface *surface);
|
||
|
||
static int __attribute__ ((format (printf, 1, 2)))
|
||
wm_log(const char *fmt, ...)
|
||
{
|
||
#ifdef WM_DEBUG
|
||
int l;
|
||
va_list argp;
|
||
|
||
va_start(argp, fmt);
|
||
l = weston_vlog(fmt, argp);
|
||
va_end(argp);
|
||
|
||
return l;
|
||
#else
|
||
return 0;
|
||
#endif
|
||
}
|
||
|
||
static int __attribute__ ((format (printf, 1, 2)))
|
||
wm_log_continue(const char *fmt, ...)
|
||
{
|
||
#ifdef WM_DEBUG
|
||
int l;
|
||
va_list argp;
|
||
|
||
va_start(argp, fmt);
|
||
l = weston_vlog_continue(fmt, argp);
|
||
va_end(argp);
|
||
|
||
return l;
|
||
#else
|
||
return 0;
|
||
#endif
|
||
}
|
||
|
||
static void
|
||
weston_output_weak_ref_init(struct weston_output_weak_ref *ref)
|
||
{
|
||
ref->output = NULL;
|
||
}
|
||
|
||
static void
|
||
weston_output_weak_ref_clear(struct weston_output_weak_ref *ref)
|
||
{
|
||
if (!ref->output)
|
||
return;
|
||
|
||
wl_list_remove(&ref->destroy_listener.link);
|
||
ref->output = NULL;
|
||
}
|
||
|
||
static void
|
||
weston_output_weak_ref_handle_destroy(struct wl_listener *listener, void *data)
|
||
{
|
||
struct weston_output_weak_ref *ref;
|
||
|
||
ref = wl_container_of(listener, ref, destroy_listener);
|
||
assert(ref->output == data);
|
||
|
||
weston_output_weak_ref_clear(ref);
|
||
}
|
||
|
||
static void
|
||
weston_output_weak_ref_set(struct weston_output_weak_ref *ref,
|
||
struct weston_output *output)
|
||
{
|
||
weston_output_weak_ref_clear(ref);
|
||
|
||
if (!output)
|
||
return;
|
||
|
||
ref->destroy_listener.notify = weston_output_weak_ref_handle_destroy;
|
||
wl_signal_add(&output->destroy_signal, &ref->destroy_listener);
|
||
ref->output = output;
|
||
}
|
||
|
||
static bool __attribute__ ((warn_unused_result))
|
||
wm_lookup_window(struct weston_wm *wm, xcb_window_t hash,
|
||
struct weston_wm_window **window)
|
||
{
|
||
*window = hash_table_lookup(wm->window_hash, hash);
|
||
if (*window)
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
const char *
|
||
get_atom_name(xcb_connection_t *c, xcb_atom_t atom)
|
||
{
|
||
xcb_get_atom_name_cookie_t cookie;
|
||
xcb_get_atom_name_reply_t *reply;
|
||
xcb_generic_error_t *e;
|
||
static char buffer[64];
|
||
|
||
if (atom == XCB_ATOM_NONE)
|
||
return "None";
|
||
|
||
cookie = xcb_get_atom_name (c, atom);
|
||
reply = xcb_get_atom_name_reply (c, cookie, &e);
|
||
|
||
if (reply) {
|
||
snprintf(buffer, sizeof buffer, "%.*s",
|
||
xcb_get_atom_name_name_length (reply),
|
||
xcb_get_atom_name_name (reply));
|
||
} else {
|
||
snprintf(buffer, sizeof buffer, "(atom %u)", atom);
|
||
}
|
||
|
||
free(reply);
|
||
|
||
return buffer;
|
||
}
|
||
|
||
static xcb_cursor_t
|
||
xcb_cursor_image_load_cursor(struct weston_wm *wm, const XcursorImage *img)
|
||
{
|
||
xcb_connection_t *c = wm->conn;
|
||
xcb_screen_iterator_t s = xcb_setup_roots_iterator(xcb_get_setup(c));
|
||
xcb_screen_t *screen = s.data;
|
||
xcb_gcontext_t gc;
|
||
xcb_pixmap_t pix;
|
||
xcb_render_picture_t pic;
|
||
xcb_cursor_t cursor;
|
||
int stride = img->width * 4;
|
||
|
||
pix = xcb_generate_id(c);
|
||
xcb_create_pixmap(c, 32, pix, screen->root, img->width, img->height);
|
||
|
||
pic = xcb_generate_id(c);
|
||
xcb_render_create_picture(c, pic, pix, wm->format_rgba.id, 0, 0);
|
||
|
||
gc = xcb_generate_id(c);
|
||
xcb_create_gc(c, gc, pix, 0, 0);
|
||
|
||
xcb_put_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc,
|
||
img->width, img->height, 0, 0, 0, 32,
|
||
stride * img->height, (uint8_t *) img->pixels);
|
||
xcb_free_gc(c, gc);
|
||
|
||
cursor = xcb_generate_id(c);
|
||
xcb_render_create_cursor(c, cursor, pic, img->xhot, img->yhot);
|
||
|
||
xcb_render_free_picture(c, pic);
|
||
xcb_free_pixmap(c, pix);
|
||
|
||
return cursor;
|
||
}
|
||
|
||
static xcb_cursor_t
|
||
xcb_cursor_images_load_cursor(struct weston_wm *wm, const XcursorImages *images)
|
||
{
|
||
/* TODO: treat animated cursors as well */
|
||
if (images->nimage != 1)
|
||
return -1;
|
||
|
||
return xcb_cursor_image_load_cursor(wm, images->images[0]);
|
||
}
|
||
|
||
static xcb_cursor_t
|
||
xcb_cursor_library_load_cursor(struct weston_wm *wm, const char *file)
|
||
{
|
||
xcb_cursor_t cursor;
|
||
XcursorImages *images;
|
||
char *v = NULL;
|
||
int size = 0;
|
||
|
||
if (!file)
|
||
return 0;
|
||
|
||
v = getenv ("XCURSOR_SIZE");
|
||
if (v)
|
||
size = atoi(v);
|
||
|
||
if (!size)
|
||
size = 32;
|
||
|
||
images = XcursorLibraryLoadImages (file, NULL, size);
|
||
if (!images)
|
||
return -1;
|
||
|
||
cursor = xcb_cursor_images_load_cursor (wm, images);
|
||
XcursorImagesDestroy (images);
|
||
|
||
return cursor;
|
||
}
|
||
|
||
void
|
||
dump_property(struct weston_wm *wm,
|
||
xcb_atom_t property, xcb_get_property_reply_t *reply)
|
||
{
|
||
int32_t *incr_value;
|
||
const char *text_value, *name;
|
||
xcb_atom_t *atom_value;
|
||
int width, len;
|
||
uint32_t i;
|
||
|
||
width = wm_log_continue("%s: ", get_atom_name(wm->conn, property));
|
||
if (reply == NULL) {
|
||
wm_log_continue("(no reply)\n");
|
||
return;
|
||
}
|
||
|
||
width += wm_log_continue("%s/%d, length %d (value_len %d): ",
|
||
get_atom_name(wm->conn, reply->type),
|
||
reply->format,
|
||
xcb_get_property_value_length(reply),
|
||
reply->value_len);
|
||
|
||
if (reply->type == wm->atom.incr) {
|
||
incr_value = xcb_get_property_value(reply);
|
||
wm_log_continue("%d\n", *incr_value);
|
||
} else if (reply->type == wm->atom.utf8_string ||
|
||
reply->type == wm->atom.string) {
|
||
text_value = xcb_get_property_value(reply);
|
||
if (reply->value_len > 40)
|
||
len = 40;
|
||
else
|
||
len = reply->value_len;
|
||
wm_log_continue("\"%.*s\"\n", len, text_value);
|
||
} else if (reply->type == XCB_ATOM_ATOM) {
|
||
atom_value = xcb_get_property_value(reply);
|
||
for (i = 0; i < reply->value_len; i++) {
|
||
name = get_atom_name(wm->conn, atom_value[i]);
|
||
if (width + strlen(name) + 2 > 78) {
|
||
wm_log_continue("\n ");
|
||
width = 4;
|
||
} else if (i > 0) {
|
||
width += wm_log_continue(", ");
|
||
}
|
||
|
||
width += wm_log_continue("%s", name);
|
||
}
|
||
wm_log_continue("\n");
|
||
} else {
|
||
wm_log_continue("huh?\n");
|
||
}
|
||
}
|
||
|
||
static void
|
||
read_and_dump_property(struct weston_wm *wm,
|
||
xcb_window_t window, xcb_atom_t property)
|
||
{
|
||
xcb_get_property_reply_t *reply;
|
||
xcb_get_property_cookie_t cookie;
|
||
|
||
cookie = xcb_get_property(wm->conn, 0, window,
|
||
property, XCB_ATOM_ANY, 0, 2048);
|
||
reply = xcb_get_property_reply(wm->conn, cookie, NULL);
|
||
|
||
dump_property(wm, property, reply);
|
||
|
||
free(reply);
|
||
}
|
||
|
||
/* We reuse some predefined, but otherwise useles atoms
|
||
* as local type placeholders that never touch the X11 server,
|
||
* to make weston_wm_window_read_properties() less exceptional.
|
||
*/
|
||
#define TYPE_WM_PROTOCOLS XCB_ATOM_CUT_BUFFER0
|
||
#define TYPE_MOTIF_WM_HINTS XCB_ATOM_CUT_BUFFER1
|
||
#define TYPE_NET_WM_STATE XCB_ATOM_CUT_BUFFER2
|
||
#define TYPE_WM_NORMAL_HINTS XCB_ATOM_CUT_BUFFER3
|
||
|
||
static void
|
||
weston_wm_window_read_properties(struct weston_wm_window *window)
|
||
{
|
||
struct weston_wm *wm = window->wm;
|
||
|
||
#define F(field) (&window->field)
|
||
const struct {
|
||
xcb_atom_t atom;
|
||
xcb_atom_t type;
|
||
void *ptr;
|
||
} props[] = {
|
||
{ XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, F(class) },
|
||
{ XCB_ATOM_WM_NAME, XCB_ATOM_STRING, F(name) },
|
||
{ XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, F(transient_for) },
|
||
{ wm->atom.wm_protocols, TYPE_WM_PROTOCOLS, NULL },
|
||
{ wm->atom.wm_normal_hints, TYPE_WM_NORMAL_HINTS, NULL },
|
||
{ wm->atom.net_wm_state, TYPE_NET_WM_STATE, NULL },
|
||
{ wm->atom.net_wm_window_type, XCB_ATOM_ATOM, F(type) },
|
||
{ wm->atom.net_wm_name, XCB_ATOM_STRING, F(name) },
|
||
{ wm->atom.net_wm_icon, XCB_ATOM_CARDINAL, F(icon) },
|
||
{ wm->atom.net_wm_pid, XCB_ATOM_CARDINAL, F(pid) },
|
||
{ wm->atom.motif_wm_hints, TYPE_MOTIF_WM_HINTS, NULL },
|
||
{ wm->atom.wm_client_machine, XCB_ATOM_WM_CLIENT_MACHINE, F(machine) },
|
||
};
|
||
#undef F
|
||
|
||
xcb_get_property_cookie_t cookie[ARRAY_LENGTH(props)];
|
||
xcb_get_property_reply_t *reply;
|
||
void *p;
|
||
uint32_t *xid;
|
||
xcb_atom_t *atom;
|
||
uint32_t i;
|
||
char name[1024];
|
||
|
||
if (!window->properties_dirty)
|
||
return;
|
||
window->properties_dirty = 0;
|
||
|
||
for (i = 0; i < ARRAY_LENGTH(props); i++)
|
||
cookie[i] = xcb_get_property(wm->conn,
|
||
0, /* delete */
|
||
window->id,
|
||
props[i].atom,
|
||
XCB_ATOM_ANY, 0, 2048);
|
||
|
||
window->decorate = window->override_redirect ? 0 : MWM_DECOR_EVERYTHING;
|
||
window->size_hints.flags = 0;
|
||
window->motif_hints.flags = 0;
|
||
window->delete_window = 0;
|
||
|
||
for (i = 0; i < ARRAY_LENGTH(props); i++) {
|
||
reply = xcb_get_property_reply(wm->conn, cookie[i], NULL);
|
||
if (!reply)
|
||
/* Bad window, typically */
|
||
continue;
|
||
if (reply->type == XCB_ATOM_NONE) {
|
||
/* No such property */
|
||
free(reply);
|
||
continue;
|
||
}
|
||
|
||
p = props[i].ptr;
|
||
|
||
switch (props[i].type) {
|
||
case XCB_ATOM_WM_CLIENT_MACHINE:
|
||
case XCB_ATOM_STRING:
|
||
/* FIXME: We're using this for both string and
|
||
utf8_string */
|
||
if (*(char **) p)
|
||
free(*(char **) p);
|
||
|
||
*(char **) p =
|
||
strndup(xcb_get_property_value(reply),
|
||
xcb_get_property_value_length(reply));
|
||
break;
|
||
case XCB_ATOM_WINDOW:
|
||
xid = xcb_get_property_value(reply);
|
||
if (!wm_lookup_window(wm, *xid, p))
|
||
weston_log("XCB_ATOM_WINDOW contains window"
|
||
" id not found in hash table.\n");
|
||
break;
|
||
case XCB_ATOM_CARDINAL:
|
||
case XCB_ATOM_ATOM:
|
||
atom = xcb_get_property_value(reply);
|
||
*(xcb_atom_t *) p = *atom;
|
||
break;
|
||
case TYPE_WM_PROTOCOLS:
|
||
atom = xcb_get_property_value(reply);
|
||
for (i = 0; i < reply->value_len; i++)
|
||
if (atom[i] == wm->atom.wm_delete_window) {
|
||
window->delete_window = 1;
|
||
break;
|
||
}
|
||
break;
|
||
case TYPE_WM_NORMAL_HINTS:
|
||
memcpy(&window->size_hints,
|
||
xcb_get_property_value(reply),
|
||
sizeof window->size_hints);
|
||
break;
|
||
case TYPE_NET_WM_STATE:
|
||
window->fullscreen = 0;
|
||
atom = xcb_get_property_value(reply);
|
||
for (i = 0; i < reply->value_len; i++) {
|
||
if (atom[i] == wm->atom.net_wm_state_fullscreen)
|
||
window->fullscreen = 1;
|
||
if (atom[i] == wm->atom.net_wm_state_maximized_vert)
|
||
window->maximized_vert = 1;
|
||
if (atom[i] == wm->atom.net_wm_state_maximized_horz)
|
||
window->maximized_horz = 1;
|
||
}
|
||
break;
|
||
case TYPE_MOTIF_WM_HINTS:
|
||
memcpy(&window->motif_hints,
|
||
xcb_get_property_value(reply),
|
||
sizeof window->motif_hints);
|
||
if (window->motif_hints.flags & MWM_HINTS_DECORATIONS) {
|
||
if (window->motif_hints.decorations & MWM_DECOR_ALL)
|
||
/* MWM_DECOR_ALL means all except the other values listed. */
|
||
window->decorate =
|
||
MWM_DECOR_EVERYTHING & (~window->motif_hints.decorations);
|
||
else
|
||
window->decorate =
|
||
window->motif_hints.decorations;
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
free(reply);
|
||
}
|
||
|
||
if (window->pid > 0) {
|
||
gethostname(name, sizeof(name));
|
||
for (i = 0; i < sizeof(name); i++) {
|
||
if (name[i] == '\0')
|
||
break;
|
||
}
|
||
if (i == sizeof(name))
|
||
name[0] = '\0'; /* ignore stupid hostnames */
|
||
|
||
/* this is only one heuristic to guess the PID of a client is
|
||
* valid, assuming it's compliant with icccm and ewmh.
|
||
* Non-compliants and remote applications of course fail. */
|
||
if (!window->machine || strcmp(window->machine, name))
|
||
window->pid = 0;
|
||
}
|
||
}
|
||
|
||
#undef TYPE_WM_PROTOCOLS
|
||
#undef TYPE_MOTIF_WM_HINTS
|
||
#undef TYPE_NET_WM_STATE
|
||
#undef TYPE_WM_NORMAL_HINTS
|
||
|
||
static void
|
||
weston_wm_window_get_frame_size(struct weston_wm_window *window,
|
||
int *width, int *height)
|
||
{
|
||
struct theme *t = window->wm->theme;
|
||
|
||
if (window->fullscreen) {
|
||
*width = window->width;
|
||
*height = window->height;
|
||
} else if (window->decorate && window->frame) {
|
||
*width = frame_width(window->frame);
|
||
*height = frame_height(window->frame);
|
||
} else {
|
||
*width = window->width + t->margin * 2;
|
||
*height = window->height + t->margin * 2;
|
||
}
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_get_child_position(struct weston_wm_window *window,
|
||
int *x, int *y)
|
||
{
|
||
struct theme *t = window->wm->theme;
|
||
|
||
if (window->fullscreen) {
|
||
*x = 0;
|
||
*y = 0;
|
||
} else if (window->decorate && window->frame) {
|
||
frame_interior(window->frame, x, y, NULL, NULL);
|
||
} else {
|
||
*x = t->margin;
|
||
*y = t->margin;
|
||
}
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_get_input_rect(struct weston_wm_window *window,
|
||
int32_t *x, int32_t *y,
|
||
int32_t *width, int32_t *height)
|
||
{
|
||
if (!window->decorate) {
|
||
weston_wm_window_get_child_position(window, x, y);
|
||
*width = window->width;
|
||
*height = window->height;
|
||
} else {
|
||
frame_input_rect(window->frame, x, y, width, height);
|
||
}
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_send_configure_notify(struct weston_wm_window *window)
|
||
{
|
||
xcb_configure_notify_event_t configure_notify;
|
||
struct weston_wm *wm = window->wm;
|
||
int x, y;
|
||
|
||
weston_wm_window_get_child_position(window, &x, &y);
|
||
configure_notify.response_type = XCB_CONFIGURE_NOTIFY;
|
||
configure_notify.pad0 = 0;
|
||
configure_notify.event = window->id;
|
||
configure_notify.window = window->id;
|
||
configure_notify.above_sibling = XCB_WINDOW_NONE;
|
||
configure_notify.x = x;
|
||
configure_notify.y = y;
|
||
configure_notify.width = window->width;
|
||
configure_notify.height = window->height;
|
||
configure_notify.border_width = 0;
|
||
configure_notify.override_redirect = 0;
|
||
configure_notify.pad1 = 0;
|
||
|
||
xcb_send_event(wm->conn, 0, window->id,
|
||
XCB_EVENT_MASK_STRUCTURE_NOTIFY,
|
||
(char *) &configure_notify);
|
||
}
|
||
|
||
static void
|
||
weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *event)
|
||
{
|
||
xcb_configure_request_event_t *configure_request =
|
||
(xcb_configure_request_event_t *) event;
|
||
struct weston_wm_window *window;
|
||
uint32_t mask, values[16];
|
||
int x, y, width, height, i = 0;
|
||
|
||
wm_log("XCB_CONFIGURE_REQUEST (window %d) %d,%d @ %dx%d\n",
|
||
configure_request->window,
|
||
configure_request->x, configure_request->y,
|
||
configure_request->width, configure_request->height);
|
||
|
||
if (!wm_lookup_window(wm, configure_request->window, &window))
|
||
return;
|
||
|
||
if (window->fullscreen) {
|
||
weston_wm_window_send_configure_notify(window);
|
||
return;
|
||
}
|
||
|
||
if (configure_request->value_mask & XCB_CONFIG_WINDOW_WIDTH)
|
||
window->width = configure_request->width;
|
||
if (configure_request->value_mask & XCB_CONFIG_WINDOW_HEIGHT)
|
||
window->height = configure_request->height;
|
||
|
||
if (window->frame)
|
||
frame_resize_inside(window->frame, window->width, window->height);
|
||
|
||
weston_wm_window_get_child_position(window, &x, &y);
|
||
values[i++] = x;
|
||
values[i++] = y;
|
||
values[i++] = window->width;
|
||
values[i++] = window->height;
|
||
values[i++] = 0;
|
||
mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
|
||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
|
||
XCB_CONFIG_WINDOW_BORDER_WIDTH;
|
||
if (configure_request->value_mask & XCB_CONFIG_WINDOW_SIBLING) {
|
||
values[i++] = configure_request->sibling;
|
||
mask |= XCB_CONFIG_WINDOW_SIBLING;
|
||
}
|
||
if (configure_request->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) {
|
||
values[i++] = configure_request->stack_mode;
|
||
mask |= XCB_CONFIG_WINDOW_STACK_MODE;
|
||
}
|
||
|
||
xcb_configure_window(wm->conn, window->id, mask, values);
|
||
|
||
weston_wm_window_get_frame_size(window, &width, &height);
|
||
values[0] = width;
|
||
values[1] = height;
|
||
mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
|
||
xcb_configure_window(wm->conn, window->frame_id, mask, values);
|
||
|
||
weston_wm_window_schedule_repaint(window);
|
||
}
|
||
|
||
static int
|
||
our_resource(struct weston_wm *wm, uint32_t id)
|
||
{
|
||
const xcb_setup_t *setup;
|
||
|
||
setup = xcb_get_setup(wm->conn);
|
||
|
||
return (id & ~setup->resource_id_mask) == setup->resource_id_base;
|
||
}
|
||
|
||
static void
|
||
weston_wm_handle_configure_notify(struct weston_wm *wm, xcb_generic_event_t *event)
|
||
{
|
||
xcb_configure_notify_event_t *configure_notify =
|
||
(xcb_configure_notify_event_t *) event;
|
||
const struct weston_desktop_xwayland_interface *xwayland_api =
|
||
wm->server->compositor->xwayland_interface;
|
||
struct weston_wm_window *window;
|
||
|
||
wm_log("XCB_CONFIGURE_NOTIFY (window %d) %d,%d @ %dx%d%s\n",
|
||
configure_notify->window,
|
||
configure_notify->x, configure_notify->y,
|
||
configure_notify->width, configure_notify->height,
|
||
configure_notify->override_redirect ? ", override" : "");
|
||
|
||
if (!wm_lookup_window(wm, configure_notify->window, &window))
|
||
return;
|
||
|
||
window->x = configure_notify->x;
|
||
window->y = configure_notify->y;
|
||
window->pos_dirty = false;
|
||
|
||
if (window->override_redirect) {
|
||
window->width = configure_notify->width;
|
||
window->height = configure_notify->height;
|
||
if (window->frame)
|
||
frame_resize_inside(window->frame,
|
||
window->width, window->height);
|
||
|
||
/* We should check if shsurf has been created because sometimes
|
||
* there are races
|
||
* (configure_notify is sent before xserver_map_surface) */
|
||
if (window->shsurf)
|
||
xwayland_api->set_xwayland(window->shsurf,
|
||
window->x, window->y);
|
||
}
|
||
}
|
||
|
||
static void
|
||
weston_wm_kill_client(struct wl_listener *listener, void *data)
|
||
{
|
||
struct weston_surface *surface = data;
|
||
struct weston_wm_window *window = get_wm_window(surface);
|
||
if (!window)
|
||
return;
|
||
|
||
if (window->pid > 0)
|
||
kill(window->pid, SIGKILL);
|
||
}
|
||
|
||
static void
|
||
weston_wm_create_surface(struct wl_listener *listener, void *data)
|
||
{
|
||
struct weston_surface *surface = data;
|
||
struct weston_wm *wm =
|
||
container_of(listener,
|
||
struct weston_wm, create_surface_listener);
|
||
struct weston_wm_window *window;
|
||
|
||
if (wl_resource_get_client(surface->resource) != wm->server->client)
|
||
return;
|
||
|
||
wm_log("XWM: create weston_surface %p\n", surface);
|
||
|
||
wl_list_for_each(window, &wm->unpaired_window_list, link)
|
||
if (window->surface_id ==
|
||
wl_resource_get_id(surface->resource)) {
|
||
xserver_map_shell_surface(window, surface);
|
||
window->surface_id = 0;
|
||
wl_list_remove(&window->link);
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void
|
||
weston_wm_send_focus_window(struct weston_wm *wm,
|
||
struct weston_wm_window *window)
|
||
{
|
||
xcb_client_message_event_t client_message;
|
||
|
||
if (window) {
|
||
uint32_t values[1];
|
||
|
||
if (window->override_redirect)
|
||
return;
|
||
|
||
client_message.response_type = XCB_CLIENT_MESSAGE;
|
||
client_message.format = 32;
|
||
client_message.window = window->id;
|
||
client_message.type = wm->atom.wm_protocols;
|
||
client_message.data.data32[0] = wm->atom.wm_take_focus;
|
||
client_message.data.data32[1] = XCB_TIME_CURRENT_TIME;
|
||
|
||
xcb_send_event(wm->conn, 0, window->id,
|
||
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
|
||
(char *) &client_message);
|
||
|
||
xcb_set_input_focus (wm->conn, XCB_INPUT_FOCUS_POINTER_ROOT,
|
||
window->id, XCB_TIME_CURRENT_TIME);
|
||
|
||
values[0] = XCB_STACK_MODE_ABOVE;
|
||
xcb_configure_window (wm->conn, window->frame_id,
|
||
XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||
} else {
|
||
xcb_set_input_focus (wm->conn,
|
||
XCB_INPUT_FOCUS_POINTER_ROOT,
|
||
XCB_NONE,
|
||
XCB_TIME_CURRENT_TIME);
|
||
}
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_activate(struct wl_listener *listener, void *data)
|
||
{
|
||
struct weston_surface_activation_data *activation_data = data;
|
||
struct weston_surface *surface = activation_data->surface;
|
||
struct weston_wm_window *window = NULL;
|
||
struct weston_wm *wm =
|
||
container_of(listener, struct weston_wm, activate_listener);
|
||
|
||
if (surface) {
|
||
window = get_wm_window(surface);
|
||
}
|
||
|
||
if (window) {
|
||
weston_wm_set_net_active_window(wm, window->id);
|
||
} else {
|
||
weston_wm_set_net_active_window(wm, XCB_WINDOW_NONE);
|
||
}
|
||
|
||
weston_wm_send_focus_window(wm, window);
|
||
|
||
if (wm->focus_window) {
|
||
if (wm->focus_window->frame)
|
||
frame_unset_flag(wm->focus_window->frame, FRAME_FLAG_ACTIVE);
|
||
weston_wm_window_schedule_repaint(wm->focus_window);
|
||
}
|
||
wm->focus_window = window;
|
||
if (wm->focus_window) {
|
||
if (wm->focus_window->frame)
|
||
frame_set_flag(wm->focus_window->frame, FRAME_FLAG_ACTIVE);
|
||
weston_wm_window_schedule_repaint(wm->focus_window);
|
||
}
|
||
|
||
xcb_flush(wm->conn);
|
||
|
||
}
|
||
|
||
/** Control Xwayland wl_surface.commit behaviour
|
||
*
|
||
* This function sets the "_XWAYLAND_ALLOW_COMMITS" property of the frame window
|
||
* (not the content window!) to \p allow.
|
||
*
|
||
* If the property is set to \c true, Xwayland will commit whenever it likes.
|
||
* If the property is set to \c false, Xwayland will not commit.
|
||
* If the property is not set at all, Xwayland assumes it is \c true.
|
||
*
|
||
* \param window The XWM window to control.
|
||
* \param allow Whether Xwayland is allowed to wl_surface.commit for the window.
|
||
*/
|
||
static void
|
||
weston_wm_window_set_allow_commits(struct weston_wm_window *window, bool allow)
|
||
{
|
||
struct weston_wm *wm = window->wm;
|
||
uint32_t property[1];
|
||
|
||
assert(window->frame_id != XCB_WINDOW_NONE);
|
||
|
||
property[0] = allow ? 1 : 0;
|
||
|
||
xcb_change_property(wm->conn,
|
||
XCB_PROP_MODE_REPLACE,
|
||
window->frame_id,
|
||
wm->atom.allow_commits,
|
||
XCB_ATOM_CARDINAL,
|
||
32, /* format */
|
||
1, property);
|
||
}
|
||
|
||
#define ICCCM_WITHDRAWN_STATE 0
|
||
#define ICCCM_NORMAL_STATE 1
|
||
#define ICCCM_ICONIC_STATE 3
|
||
|
||
static void
|
||
weston_wm_window_set_wm_state(struct weston_wm_window *window, int32_t state)
|
||
{
|
||
struct weston_wm *wm = window->wm;
|
||
uint32_t property[2];
|
||
|
||
property[0] = state;
|
||
property[1] = XCB_WINDOW_NONE;
|
||
|
||
xcb_change_property(wm->conn,
|
||
XCB_PROP_MODE_REPLACE,
|
||
window->id,
|
||
wm->atom.wm_state,
|
||
wm->atom.wm_state,
|
||
32, /* format */
|
||
2, property);
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_set_net_wm_state(struct weston_wm_window *window)
|
||
{
|
||
struct weston_wm *wm = window->wm;
|
||
uint32_t property[3];
|
||
int i;
|
||
|
||
i = 0;
|
||
if (window->fullscreen)
|
||
property[i++] = wm->atom.net_wm_state_fullscreen;
|
||
if (window->maximized_vert)
|
||
property[i++] = wm->atom.net_wm_state_maximized_vert;
|
||
if (window->maximized_horz)
|
||
property[i++] = wm->atom.net_wm_state_maximized_horz;
|
||
|
||
xcb_change_property(wm->conn,
|
||
XCB_PROP_MODE_REPLACE,
|
||
window->id,
|
||
wm->atom.net_wm_state,
|
||
XCB_ATOM_ATOM,
|
||
32, /* format */
|
||
i, property);
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_create_frame(struct weston_wm_window *window)
|
||
{
|
||
struct weston_wm *wm = window->wm;
|
||
uint32_t values[3];
|
||
xcb_rectangle_t rect;
|
||
int x, y, width, height;
|
||
int buttons = FRAME_BUTTON_CLOSE;
|
||
|
||
if (window->decorate & MWM_DECOR_MAXIMIZE)
|
||
buttons |= FRAME_BUTTON_MAXIMIZE;
|
||
|
||
window->frame = frame_create(window->wm->theme,
|
||
window->width, window->height,
|
||
buttons, window->name,
|
||
window->icon_surface);
|
||
window->icon_surface = NULL;
|
||
frame_resize_inside(window->frame, window->width, window->height);
|
||
|
||
weston_wm_window_get_frame_size(window, &width, &height);
|
||
weston_wm_window_get_child_position(window, &x, &y);
|
||
|
||
values[0] = wm->screen->black_pixel;
|
||
values[1] =
|
||
XCB_EVENT_MASK_KEY_PRESS |
|
||
XCB_EVENT_MASK_KEY_RELEASE |
|
||
XCB_EVENT_MASK_BUTTON_PRESS |
|
||
XCB_EVENT_MASK_BUTTON_RELEASE |
|
||
XCB_EVENT_MASK_POINTER_MOTION |
|
||
XCB_EVENT_MASK_ENTER_WINDOW |
|
||
XCB_EVENT_MASK_LEAVE_WINDOW |
|
||
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
|
||
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
|
||
values[2] = wm->colormap;
|
||
|
||
window->frame_id = xcb_generate_id(wm->conn);
|
||
xcb_create_window(wm->conn,
|
||
32,
|
||
window->frame_id,
|
||
wm->screen->root,
|
||
0, 0,
|
||
width, height,
|
||
0,
|
||
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||
wm->visual_id,
|
||
XCB_CW_BORDER_PIXEL |
|
||
XCB_CW_EVENT_MASK |
|
||
XCB_CW_COLORMAP, values);
|
||
|
||
xcb_reparent_window(wm->conn, window->id, window->frame_id, x, y);
|
||
|
||
values[0] = 0;
|
||
xcb_configure_window(wm->conn, window->id,
|
||
XCB_CONFIG_WINDOW_BORDER_WIDTH, values);
|
||
|
||
window->cairo_surface =
|
||
cairo_xcb_surface_create_with_xrender_format(wm->conn,
|
||
wm->screen,
|
||
window->frame_id,
|
||
&wm->format_rgba,
|
||
width, height);
|
||
|
||
weston_wm_window_get_input_rect(window, &x, &y, &width, &height);
|
||
rect.x = x;
|
||
rect.y = y;
|
||
rect.width = width;
|
||
rect.height = height;
|
||
|
||
/* The window frame was created with position and size which include
|
||
* an offset for margins and shadow. Set the input region to ignore
|
||
* shadow. */
|
||
xcb_shape_rectangles(wm->conn,
|
||
XCB_SHAPE_SO_SET,
|
||
XCB_SHAPE_SK_INPUT,
|
||
0,
|
||
window->frame_id,
|
||
0,
|
||
0,
|
||
1,
|
||
&rect);
|
||
|
||
hash_table_insert(wm->window_hash, window->frame_id, window);
|
||
}
|
||
|
||
/*
|
||
* Sets the _NET_WM_DESKTOP property for the window to 'desktop'.
|
||
* Passing a <0 desktop value deletes the property.
|
||
*/
|
||
static void
|
||
weston_wm_window_set_virtual_desktop(struct weston_wm_window *window,
|
||
int desktop)
|
||
{
|
||
if (desktop >= 0) {
|
||
xcb_change_property(window->wm->conn,
|
||
XCB_PROP_MODE_REPLACE,
|
||
window->id,
|
||
window->wm->atom.net_wm_desktop,
|
||
XCB_ATOM_CARDINAL,
|
||
32, /* format */
|
||
1, &desktop);
|
||
} else {
|
||
xcb_delete_property(window->wm->conn,
|
||
window->id,
|
||
window->wm->atom.net_wm_desktop);
|
||
}
|
||
}
|
||
|
||
static void
|
||
weston_wm_handle_map_request(struct weston_wm *wm, xcb_generic_event_t *event)
|
||
{
|
||
xcb_map_request_event_t *map_request =
|
||
(xcb_map_request_event_t *) event;
|
||
struct weston_wm_window *window;
|
||
struct weston_output *output;
|
||
|
||
if (our_resource(wm, map_request->window)) {
|
||
wm_log("XCB_MAP_REQUEST (window %d, ours)\n",
|
||
map_request->window);
|
||
return;
|
||
}
|
||
|
||
if (!wm_lookup_window(wm, map_request->window, &window))
|
||
return;
|
||
|
||
weston_wm_window_read_properties(window);
|
||
|
||
/* For a new Window, MapRequest happens before the Window is realized
|
||
* in Xwayland. We do the real xcb_map_window() here as a response to
|
||
* MapRequest. The Window will get realized (wl_surface created in
|
||
* Wayland and WL_SURFACE_ID sent in X11) when it has been mapped for
|
||
* real.
|
||
*
|
||
* MapRequest only happens for (X11) unmapped Windows. On UnmapNotify,
|
||
* we reset shsurf to NULL, so even if X11 connection races far ahead
|
||
* of the Wayland connection and the X11 client is repeatedly mapping
|
||
* and unmapping, we will never have shsurf set on MapRequest.
|
||
*/
|
||
assert(!window->shsurf);
|
||
|
||
window->map_request_x = window->x;
|
||
window->map_request_y = window->y;
|
||
|
||
if (window->frame_id == XCB_WINDOW_NONE)
|
||
weston_wm_window_create_frame(window); /* sets frame_id */
|
||
assert(window->frame_id != XCB_WINDOW_NONE);
|
||
|
||
wm_log("XCB_MAP_REQUEST (window %d, %p, frame %d, %dx%d @ %d,%d)\n",
|
||
window->id, window, window->frame_id,
|
||
window->width, window->height,
|
||
window->map_request_x, window->map_request_y);
|
||
|
||
weston_wm_window_set_allow_commits(window, false);
|
||
weston_wm_window_set_wm_state(window, ICCCM_NORMAL_STATE);
|
||
weston_wm_window_set_net_wm_state(window);
|
||
weston_wm_window_set_virtual_desktop(window, 0);
|
||
|
||
if (legacy_fullscreen(wm, window, &output)) {
|
||
window->fullscreen = 1;
|
||
weston_output_weak_ref_set(&window->legacy_fullscreen_output,
|
||
output);
|
||
}
|
||
|
||
xcb_map_window(wm->conn, map_request->window);
|
||
xcb_map_window(wm->conn, window->frame_id);
|
||
|
||
/* Mapped in the X server, we can draw immediately.
|
||
* Cannot set pending state though, no weston_surface until
|
||
* xserver_map_shell_surface() time. */
|
||
weston_wm_window_schedule_repaint(window);
|
||
}
|
||
|
||
static void
|
||
weston_wm_handle_map_notify(struct weston_wm *wm, xcb_generic_event_t *event)
|
||
{
|
||
xcb_map_notify_event_t *map_notify = (xcb_map_notify_event_t *) event;
|
||
|
||
if (our_resource(wm, map_notify->window)) {
|
||
wm_log("XCB_MAP_NOTIFY (window %d, ours)\n",
|
||
map_notify->window);
|
||
return;
|
||
}
|
||
|
||
wm_log("XCB_MAP_NOTIFY (window %d%s)\n", map_notify->window,
|
||
map_notify->override_redirect ? ", override" : "");
|
||
}
|
||
|
||
static void
|
||
weston_wm_handle_unmap_notify(struct weston_wm *wm, xcb_generic_event_t *event)
|
||
{
|
||
xcb_unmap_notify_event_t *unmap_notify =
|
||
(xcb_unmap_notify_event_t *) event;
|
||
struct weston_wm_window *window;
|
||
|
||
wm_log("XCB_UNMAP_NOTIFY (window %d, event %d%s)\n",
|
||
unmap_notify->window,
|
||
unmap_notify->event,
|
||
our_resource(wm, unmap_notify->window) ? ", ours" : "");
|
||
|
||
if (our_resource(wm, unmap_notify->window))
|
||
return;
|
||
|
||
if (unmap_notify->response_type & SEND_EVENT_MASK)
|
||
/* We just ignore the ICCCM 4.1.4 synthetic unmap notify
|
||
* as it may come in after we've destroyed the window. */
|
||
return;
|
||
|
||
if (!wm_lookup_window(wm, unmap_notify->window, &window))
|
||
return;
|
||
|
||
if (window->surface_id) {
|
||
/* Make sure we're not on the unpaired surface list or we
|
||
* could be assigned a surface during surface creation that
|
||
* was mapped before this unmap request.
|
||
*/
|
||
wl_list_remove(&window->link);
|
||
window->surface_id = 0;
|
||
}
|
||
if (wm->focus_window == window)
|
||
wm->focus_window = NULL;
|
||
if (window->surface)
|
||
wl_list_remove(&window->surface_destroy_listener.link);
|
||
window->surface = NULL;
|
||
window->shsurf = NULL;
|
||
|
||
weston_wm_window_set_wm_state(window, ICCCM_WITHDRAWN_STATE);
|
||
weston_wm_window_set_virtual_desktop(window, -1);
|
||
|
||
xcb_unmap_window(wm->conn, window->frame_id);
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_draw_decoration(struct weston_wm_window *window)
|
||
{
|
||
cairo_t *cr;
|
||
int width, height;
|
||
|
||
wm_log("XWM: draw decoration, win %d\n", window->id);
|
||
|
||
weston_wm_window_get_frame_size(window, &width, &height);
|
||
|
||
cairo_xcb_surface_set_size(window->cairo_surface, width, height);
|
||
cr = cairo_create(window->cairo_surface);
|
||
|
||
if (window->fullscreen) {
|
||
/* nothing */
|
||
} else if (window->decorate) {
|
||
frame_set_title(window->frame, window->name);
|
||
frame_repaint(window->frame, cr);
|
||
} else {
|
||
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
|
||
cairo_set_source_rgba(cr, 0, 0, 0, 0);
|
||
cairo_paint(cr);
|
||
|
||
render_shadow(cr, window->wm->theme->shadow,
|
||
2, 2, width + 8, height + 8, 64, 64);
|
||
}
|
||
|
||
cairo_destroy(cr);
|
||
cairo_surface_flush(window->cairo_surface);
|
||
xcb_flush(window->wm->conn);
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_set_pending_state(struct weston_wm_window *window)
|
||
{
|
||
int x, y, width, height;
|
||
int32_t input_x, input_y, input_w, input_h;
|
||
const struct weston_desktop_xwayland_interface *xwayland_interface =
|
||
window->wm->server->compositor->xwayland_interface;
|
||
|
||
if (!window->surface)
|
||
return;
|
||
|
||
weston_wm_window_get_frame_size(window, &width, &height);
|
||
weston_wm_window_get_child_position(window, &x, &y);
|
||
|
||
pixman_region32_fini(&window->surface->pending.opaque);
|
||
if (window->has_alpha) {
|
||
pixman_region32_init(&window->surface->pending.opaque);
|
||
} else {
|
||
/* We leave an extra pixel around the X window area to
|
||
* make sure we don't sample from the undefined alpha
|
||
* channel when filtering. */
|
||
pixman_region32_init_rect(&window->surface->pending.opaque,
|
||
x - 1, y - 1,
|
||
window->width + 2,
|
||
window->height + 2);
|
||
}
|
||
|
||
if (window->decorate && !window->fullscreen) {
|
||
frame_input_rect(window->frame, &input_x, &input_y,
|
||
&input_w, &input_h);
|
||
} else {
|
||
input_x = x;
|
||
input_y = y;
|
||
input_w = width;
|
||
input_h = height;
|
||
}
|
||
|
||
wm_log("XWM: win %d geometry: %d,%d %dx%d\n",
|
||
window->id, input_x, input_y, input_w, input_h);
|
||
|
||
pixman_region32_fini(&window->surface->pending.input);
|
||
pixman_region32_init_rect(&window->surface->pending.input,
|
||
input_x, input_y, input_w, input_h);
|
||
|
||
xwayland_interface->set_window_geometry(window->shsurf,
|
||
input_x, input_y,
|
||
input_w, input_h);
|
||
if (window->name)
|
||
xwayland_interface->set_title(window->shsurf, window->name);
|
||
if (window->pid > 0)
|
||
xwayland_interface->set_pid(window->shsurf, window->pid);
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_do_repaint(void *data)
|
||
{
|
||
struct weston_wm_window *window = data;
|
||
|
||
window->repaint_source = NULL;
|
||
|
||
weston_wm_window_read_properties(window);
|
||
|
||
weston_wm_window_draw_decoration(window);
|
||
weston_wm_window_set_pending_state(window);
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_set_pending_state_OR(struct weston_wm_window *window)
|
||
{
|
||
int width, height;
|
||
|
||
/* for override-redirect windows */
|
||
assert(window->frame_id == XCB_WINDOW_NONE);
|
||
|
||
if (!window->surface)
|
||
return;
|
||
|
||
weston_wm_window_get_frame_size(window, &width, &height);
|
||
pixman_region32_fini(&window->surface->pending.opaque);
|
||
if (window->has_alpha) {
|
||
pixman_region32_init(&window->surface->pending.opaque);
|
||
} else {
|
||
pixman_region32_init_rect(&window->surface->pending.opaque, 0, 0,
|
||
width, height);
|
||
}
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_schedule_repaint(struct weston_wm_window *window)
|
||
{
|
||
struct weston_wm *wm = window->wm;
|
||
|
||
if (window->frame_id == XCB_WINDOW_NONE) {
|
||
/* Override-redirect windows go through here, but we
|
||
* cannot assert(window->override_redirect); because
|
||
* we do not deal with changing OR flag yet.
|
||
* XXX: handle OR flag changes in message handlers
|
||
*/
|
||
weston_wm_window_set_pending_state_OR(window);
|
||
return;
|
||
}
|
||
|
||
if (window->repaint_source)
|
||
return;
|
||
|
||
wm_log("XWM: schedule repaint, win %d\n", window->id);
|
||
|
||
window->repaint_source =
|
||
wl_event_loop_add_idle(wm->server->loop,
|
||
weston_wm_window_do_repaint, window);
|
||
}
|
||
|
||
static void
|
||
weston_wm_handle_icon(struct weston_wm *wm, struct weston_wm_window *window)
|
||
{
|
||
xcb_get_property_reply_t *reply;
|
||
xcb_get_property_cookie_t cookie;
|
||
uint32_t length;
|
||
uint32_t *data, width, height;
|
||
cairo_surface_t *new_surface;
|
||
|
||
/* TODO: icons don’t have any specified order, we should pick the
|
||
* closest one to the target dimension instead of the first one. */
|
||
|
||
cookie = xcb_get_property(wm->conn, 0, window->id,
|
||
wm->atom.net_wm_icon, XCB_ATOM_ANY, 0,
|
||
UINT32_MAX);
|
||
reply = xcb_get_property_reply(wm->conn, cookie, NULL);
|
||
length = xcb_get_property_value_length(reply);
|
||
|
||
/* This is in 32-bit words, not in bytes. */
|
||
if (length < 2)
|
||
return;
|
||
|
||
data = xcb_get_property_value(reply);
|
||
width = *data++;
|
||
height = *data++;
|
||
|
||
/* Some checks against malformed input. */
|
||
if (width == 0 || height == 0 || length < 2 + width * height)
|
||
return;
|
||
|
||
new_surface =
|
||
cairo_image_surface_create_for_data((unsigned char *)data,
|
||
CAIRO_FORMAT_ARGB32,
|
||
width, height, width * 4);
|
||
|
||
/* Bail out in case anything wrong happened during surface creation. */
|
||
if (cairo_surface_status(new_surface) != CAIRO_STATUS_SUCCESS) {
|
||
cairo_surface_destroy(new_surface);
|
||
return;
|
||
}
|
||
|
||
if (window->frame)
|
||
frame_set_icon(window->frame, new_surface);
|
||
else /* We don’t have a frame yet */
|
||
window->icon_surface = new_surface;
|
||
}
|
||
|
||
static void
|
||
weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *event)
|
||
{
|
||
xcb_property_notify_event_t *property_notify =
|
||
(xcb_property_notify_event_t *) event;
|
||
struct weston_wm_window *window;
|
||
|
||
if (!wm_lookup_window(wm, property_notify->window, &window))
|
||
return;
|
||
|
||
window->properties_dirty = 1;
|
||
|
||
wm_log("XCB_PROPERTY_NOTIFY: window %d, ", property_notify->window);
|
||
if (property_notify->state == XCB_PROPERTY_DELETE)
|
||
wm_log_continue("deleted %s\n",
|
||
get_atom_name(wm->conn, property_notify->atom));
|
||
else
|
||
read_and_dump_property(wm, property_notify->window,
|
||
property_notify->atom);
|
||
|
||
if (property_notify->atom == wm->atom.net_wm_icon) {
|
||
if (property_notify->state != XCB_PROPERTY_DELETE) {
|
||
weston_wm_handle_icon(wm, window);
|
||
} else {
|
||
if (window->frame)
|
||
frame_set_icon(window->frame, NULL);
|
||
if (window->icon_surface)
|
||
cairo_surface_destroy(window->icon_surface);
|
||
window->icon_surface = NULL;
|
||
}
|
||
weston_wm_window_schedule_repaint(window);
|
||
}
|
||
|
||
if (property_notify->atom == wm->atom.net_wm_name ||
|
||
property_notify->atom == XCB_ATOM_WM_NAME)
|
||
weston_wm_window_schedule_repaint(window);
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_create(struct weston_wm *wm,
|
||
xcb_window_t id, int width, int height, int x, int y, int override)
|
||
{
|
||
struct weston_wm_window *window;
|
||
uint32_t values[1];
|
||
xcb_get_geometry_cookie_t geometry_cookie;
|
||
xcb_get_geometry_reply_t *geometry_reply;
|
||
|
||
window = zalloc(sizeof *window);
|
||
if (window == NULL) {
|
||
wm_log("failed to allocate window\n");
|
||
return;
|
||
}
|
||
|
||
geometry_cookie = xcb_get_geometry(wm->conn, id);
|
||
|
||
values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
|
||
XCB_EVENT_MASK_FOCUS_CHANGE;
|
||
xcb_change_window_attributes(wm->conn, id, XCB_CW_EVENT_MASK, values);
|
||
|
||
window->wm = wm;
|
||
window->id = id;
|
||
window->properties_dirty = 1;
|
||
window->override_redirect = override;
|
||
window->width = width;
|
||
window->height = height;
|
||
window->x = x;
|
||
window->y = y;
|
||
window->pos_dirty = false;
|
||
window->map_request_x = INT_MIN; /* out of range for valid positions */
|
||
window->map_request_y = INT_MIN; /* out of range for valid positions */
|
||
weston_output_weak_ref_init(&window->legacy_fullscreen_output);
|
||
|
||
geometry_reply = xcb_get_geometry_reply(wm->conn, geometry_cookie, NULL);
|
||
/* technically we should use XRender and check the visual format's
|
||
alpha_mask, but checking depth is simpler and works in all known cases */
|
||
if (geometry_reply != NULL)
|
||
window->has_alpha = geometry_reply->depth == 32;
|
||
free(geometry_reply);
|
||
|
||
hash_table_insert(wm->window_hash, id, window);
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_destroy(struct weston_wm_window *window)
|
||
{
|
||
struct weston_wm *wm = window->wm;
|
||
|
||
weston_output_weak_ref_clear(&window->legacy_fullscreen_output);
|
||
|
||
if (window->repaint_source)
|
||
wl_event_source_remove(window->repaint_source);
|
||
if (window->cairo_surface)
|
||
cairo_surface_destroy(window->cairo_surface);
|
||
|
||
if (window->frame_id) {
|
||
xcb_reparent_window(wm->conn, window->id, wm->wm_window, 0, 0);
|
||
xcb_destroy_window(wm->conn, window->frame_id);
|
||
weston_wm_window_set_wm_state(window, ICCCM_WITHDRAWN_STATE);
|
||
weston_wm_window_set_virtual_desktop(window, -1);
|
||
hash_table_remove(wm->window_hash, window->frame_id);
|
||
window->frame_id = XCB_WINDOW_NONE;
|
||
}
|
||
|
||
if (window->surface_id)
|
||
wl_list_remove(&window->link);
|
||
|
||
if (window->surface)
|
||
wl_list_remove(&window->surface_destroy_listener.link);
|
||
|
||
hash_table_remove(window->wm->window_hash, window->id);
|
||
free(window);
|
||
}
|
||
|
||
static void
|
||
weston_wm_handle_create_notify(struct weston_wm *wm, xcb_generic_event_t *event)
|
||
{
|
||
xcb_create_notify_event_t *create_notify =
|
||
(xcb_create_notify_event_t *) event;
|
||
|
||
wm_log("XCB_CREATE_NOTIFY (window %d, at (%d, %d), width %d, height %d%s%s)\n",
|
||
create_notify->window,
|
||
create_notify->x, create_notify->y,
|
||
create_notify->width, create_notify->height,
|
||
create_notify->override_redirect ? ", override" : "",
|
||
our_resource(wm, create_notify->window) ? ", ours" : "");
|
||
|
||
if (our_resource(wm, create_notify->window))
|
||
return;
|
||
|
||
weston_wm_window_create(wm, create_notify->window,
|
||
create_notify->width, create_notify->height,
|
||
create_notify->x, create_notify->y,
|
||
create_notify->override_redirect);
|
||
}
|
||
|
||
static void
|
||
weston_wm_handle_destroy_notify(struct weston_wm *wm, xcb_generic_event_t *event)
|
||
{
|
||
xcb_destroy_notify_event_t *destroy_notify =
|
||
(xcb_destroy_notify_event_t *) event;
|
||
struct weston_wm_window *window;
|
||
|
||
wm_log("XCB_DESTROY_NOTIFY, win %d, event %d%s\n",
|
||
destroy_notify->window,
|
||
destroy_notify->event,
|
||
our_resource(wm, destroy_notify->window) ? ", ours" : "");
|
||
|
||
if (our_resource(wm, destroy_notify->window))
|
||
return;
|
||
|
||
if (!wm_lookup_window(wm, destroy_notify->window, &window))
|
||
return;
|
||
|
||
weston_wm_window_destroy(window);
|
||
}
|
||
|
||
static void
|
||
weston_wm_handle_reparent_notify(struct weston_wm *wm, xcb_generic_event_t *event)
|
||
{
|
||
xcb_reparent_notify_event_t *reparent_notify =
|
||
(xcb_reparent_notify_event_t *) event;
|
||
struct weston_wm_window *window;
|
||
|
||
wm_log("XCB_REPARENT_NOTIFY (window %d, parent %d, event %d%s)\n",
|
||
reparent_notify->window,
|
||
reparent_notify->parent,
|
||
reparent_notify->event,
|
||
reparent_notify->override_redirect ? ", override" : "");
|
||
|
||
if (reparent_notify->parent == wm->screen->root) {
|
||
weston_wm_window_create(wm, reparent_notify->window, 10, 10,
|
||
reparent_notify->x, reparent_notify->y,
|
||
reparent_notify->override_redirect);
|
||
} else if (!our_resource(wm, reparent_notify->parent)) {
|
||
if (!wm_lookup_window(wm, reparent_notify->window, &window))
|
||
return;
|
||
weston_wm_window_destroy(window);
|
||
}
|
||
}
|
||
|
||
struct weston_seat *
|
||
weston_wm_pick_seat(struct weston_wm *wm)
|
||
{
|
||
struct wl_list *seats = wm->server->compositor->seat_list.next;
|
||
if (wl_list_empty(seats))
|
||
return NULL;
|
||
return container_of(seats, struct weston_seat, link);
|
||
}
|
||
|
||
static struct weston_seat *
|
||
weston_wm_pick_seat_for_window(struct weston_wm_window *window)
|
||
{
|
||
struct weston_wm *wm = window->wm;
|
||
struct weston_seat *seat, *s;
|
||
|
||
seat = NULL;
|
||
wl_list_for_each(s, &wm->server->compositor->seat_list, link) {
|
||
struct weston_pointer *pointer = weston_seat_get_pointer(s);
|
||
struct weston_pointer *old_pointer =
|
||
weston_seat_get_pointer(seat);
|
||
|
||
if (pointer && pointer->focus &&
|
||
pointer->focus->surface == window->surface &&
|
||
pointer->button_count > 0 &&
|
||
(!seat ||
|
||
pointer->grab_serial -
|
||
old_pointer->grab_serial < (1 << 30)))
|
||
seat = s;
|
||
}
|
||
|
||
return seat;
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_handle_moveresize(struct weston_wm_window *window,
|
||
xcb_client_message_event_t *client_message)
|
||
{
|
||
static const int map[] = {
|
||
THEME_LOCATION_RESIZING_TOP_LEFT,
|
||
THEME_LOCATION_RESIZING_TOP,
|
||
THEME_LOCATION_RESIZING_TOP_RIGHT,
|
||
THEME_LOCATION_RESIZING_RIGHT,
|
||
THEME_LOCATION_RESIZING_BOTTOM_RIGHT,
|
||
THEME_LOCATION_RESIZING_BOTTOM,
|
||
THEME_LOCATION_RESIZING_BOTTOM_LEFT,
|
||
THEME_LOCATION_RESIZING_LEFT
|
||
};
|
||
|
||
struct weston_wm *wm = window->wm;
|
||
struct weston_seat *seat = weston_wm_pick_seat_for_window(window);
|
||
struct weston_pointer *pointer = weston_seat_get_pointer(seat);
|
||
int detail;
|
||
const struct weston_desktop_xwayland_interface *xwayland_interface =
|
||
wm->server->compositor->xwayland_interface;
|
||
|
||
if (!pointer || pointer->button_count != 1
|
||
|| !pointer->focus
|
||
|| pointer->focus->surface != window->surface)
|
||
return;
|
||
|
||
detail = client_message->data.data32[2];
|
||
switch (detail) {
|
||
case _NET_WM_MOVERESIZE_MOVE:
|
||
xwayland_interface->move(window->shsurf, pointer);
|
||
break;
|
||
case _NET_WM_MOVERESIZE_SIZE_TOPLEFT:
|
||
case _NET_WM_MOVERESIZE_SIZE_TOP:
|
||
case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT:
|
||
case _NET_WM_MOVERESIZE_SIZE_RIGHT:
|
||
case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT:
|
||
case _NET_WM_MOVERESIZE_SIZE_BOTTOM:
|
||
case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT:
|
||
case _NET_WM_MOVERESIZE_SIZE_LEFT:
|
||
xwayland_interface->resize(window->shsurf, pointer, map[detail]);
|
||
break;
|
||
case _NET_WM_MOVERESIZE_CANCEL:
|
||
break;
|
||
}
|
||
}
|
||
|
||
#define _NET_WM_STATE_REMOVE 0
|
||
#define _NET_WM_STATE_ADD 1
|
||
#define _NET_WM_STATE_TOGGLE 2
|
||
|
||
static int
|
||
update_state(int action, int *state)
|
||
{
|
||
int new_state, changed;
|
||
|
||
switch (action) {
|
||
case _NET_WM_STATE_REMOVE:
|
||
new_state = 0;
|
||
break;
|
||
case _NET_WM_STATE_ADD:
|
||
new_state = 1;
|
||
break;
|
||
case _NET_WM_STATE_TOGGLE:
|
||
new_state = !*state;
|
||
break;
|
||
default:
|
||
return 0;
|
||
}
|
||
|
||
changed = (*state != new_state);
|
||
*state = new_state;
|
||
|
||
return changed;
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_configure(void *data);
|
||
|
||
static void
|
||
weston_wm_window_set_toplevel(struct weston_wm_window *window)
|
||
{
|
||
const struct weston_desktop_xwayland_interface *xwayland_interface =
|
||
window->wm->server->compositor->xwayland_interface;
|
||
|
||
xwayland_interface->set_toplevel(window->shsurf);
|
||
window->width = window->saved_width;
|
||
window->height = window->saved_height;
|
||
if (window->frame)
|
||
frame_resize_inside(window->frame,
|
||
window->width,
|
||
window->height);
|
||
weston_wm_window_configure(window);
|
||
}
|
||
|
||
static inline bool
|
||
weston_wm_window_is_maximized(struct weston_wm_window *window)
|
||
{
|
||
return window->maximized_horz && window->maximized_vert;
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_handle_state(struct weston_wm_window *window,
|
||
xcb_client_message_event_t *client_message)
|
||
{
|
||
struct weston_wm *wm = window->wm;
|
||
const struct weston_desktop_xwayland_interface *xwayland_interface =
|
||
wm->server->compositor->xwayland_interface;
|
||
uint32_t action, property1, property2;
|
||
int maximized = weston_wm_window_is_maximized(window);
|
||
|
||
action = client_message->data.data32[0];
|
||
property1 = client_message->data.data32[1];
|
||
property2 = client_message->data.data32[2];
|
||
|
||
if ((property1 == wm->atom.net_wm_state_fullscreen ||
|
||
property2 == wm->atom.net_wm_state_fullscreen) &&
|
||
update_state(action, &window->fullscreen)) {
|
||
weston_wm_window_set_net_wm_state(window);
|
||
if (window->fullscreen) {
|
||
window->saved_width = window->width;
|
||
window->saved_height = window->height;
|
||
|
||
if (window->shsurf)
|
||
xwayland_interface->set_fullscreen(window->shsurf,
|
||
NULL);
|
||
} else {
|
||
if (window->shsurf)
|
||
weston_wm_window_set_toplevel(window);
|
||
}
|
||
} else {
|
||
if ((property1 == wm->atom.net_wm_state_maximized_vert ||
|
||
property2 == wm->atom.net_wm_state_maximized_vert) &&
|
||
update_state(action, &window->maximized_vert))
|
||
weston_wm_window_set_net_wm_state(window);
|
||
if ((property1 == wm->atom.net_wm_state_maximized_horz ||
|
||
property2 == wm->atom.net_wm_state_maximized_horz) &&
|
||
update_state(action, &window->maximized_horz))
|
||
weston_wm_window_set_net_wm_state(window);
|
||
|
||
if (maximized != weston_wm_window_is_maximized(window)) {
|
||
if (weston_wm_window_is_maximized(window)) {
|
||
window->saved_width = window->width;
|
||
window->saved_height = window->height;
|
||
|
||
if (window->shsurf)
|
||
xwayland_interface->set_maximized(window->shsurf);
|
||
} else if (window->shsurf) {
|
||
weston_wm_window_set_toplevel(window);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static void
|
||
surface_destroy(struct wl_listener *listener, void *data)
|
||
{
|
||
struct weston_wm_window *window =
|
||
container_of(listener,
|
||
struct weston_wm_window, surface_destroy_listener);
|
||
|
||
wm_log("surface for xid %d destroyed\n", window->id);
|
||
|
||
/* This should have been freed by the shell.
|
||
* Don't try to use it later. */
|
||
window->shsurf = NULL;
|
||
window->surface = NULL;
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_handle_surface_id(struct weston_wm_window *window,
|
||
xcb_client_message_event_t *client_message)
|
||
{
|
||
struct weston_wm *wm = window->wm;
|
||
struct wl_resource *resource;
|
||
|
||
if (window->surface_id != 0) {
|
||
wm_log("already have surface id for window %d\n", window->id);
|
||
return;
|
||
}
|
||
|
||
/* Xwayland will send the wayland requests to create the
|
||
* wl_surface before sending this client message. Even so, we
|
||
* can end up handling the X event before the wayland requests
|
||
* and thus when we try to look up the surface ID, the surface
|
||
* hasn't been created yet. In that case put the window on
|
||
* the unpaired window list and continue when the surface gets
|
||
* created. */
|
||
uint32_t id = client_message->data.data32[0];
|
||
resource = wl_client_get_object(wm->server->client, id);
|
||
if (resource) {
|
||
window->surface_id = 0;
|
||
xserver_map_shell_surface(window,
|
||
wl_resource_get_user_data(resource));
|
||
}
|
||
else {
|
||
window->surface_id = id;
|
||
wl_list_insert(&wm->unpaired_window_list, &window->link);
|
||
}
|
||
}
|
||
|
||
static void
|
||
weston_wm_handle_client_message(struct weston_wm *wm,
|
||
xcb_generic_event_t *event)
|
||
{
|
||
xcb_client_message_event_t *client_message =
|
||
(xcb_client_message_event_t *) event;
|
||
struct weston_wm_window *window;
|
||
|
||
wm_log("XCB_CLIENT_MESSAGE (%s %d %d %d %d %d win %d)\n",
|
||
get_atom_name(wm->conn, client_message->type),
|
||
client_message->data.data32[0],
|
||
client_message->data.data32[1],
|
||
client_message->data.data32[2],
|
||
client_message->data.data32[3],
|
||
client_message->data.data32[4],
|
||
client_message->window);
|
||
|
||
/* The window may get created and destroyed before we actually
|
||
* handle the message. If it doesn't exist, bail.
|
||
*/
|
||
if (!wm_lookup_window(wm, client_message->window, &window))
|
||
return;
|
||
|
||
if (client_message->type == wm->atom.net_wm_moveresize)
|
||
weston_wm_window_handle_moveresize(window, client_message);
|
||
else if (client_message->type == wm->atom.net_wm_state)
|
||
weston_wm_window_handle_state(window, client_message);
|
||
else if (client_message->type == wm->atom.wl_surface_id)
|
||
weston_wm_window_handle_surface_id(window, client_message);
|
||
}
|
||
|
||
enum cursor_type {
|
||
XWM_CURSOR_TOP,
|
||
XWM_CURSOR_BOTTOM,
|
||
XWM_CURSOR_LEFT,
|
||
XWM_CURSOR_RIGHT,
|
||
XWM_CURSOR_TOP_LEFT,
|
||
XWM_CURSOR_TOP_RIGHT,
|
||
XWM_CURSOR_BOTTOM_LEFT,
|
||
XWM_CURSOR_BOTTOM_RIGHT,
|
||
XWM_CURSOR_LEFT_PTR,
|
||
};
|
||
|
||
/*
|
||
* The following correspondences between file names and cursors was copied
|
||
* from: https://bugs.kde.org/attachment.cgi?id=67313
|
||
*/
|
||
|
||
static const char *bottom_left_corners[] = {
|
||
"bottom_left_corner",
|
||
"sw-resize",
|
||
"size_bdiag"
|
||
};
|
||
|
||
static const char *bottom_right_corners[] = {
|
||
"bottom_right_corner",
|
||
"se-resize",
|
||
"size_fdiag"
|
||
};
|
||
|
||
static const char *bottom_sides[] = {
|
||
"bottom_side",
|
||
"s-resize",
|
||
"size_ver"
|
||
};
|
||
|
||
static const char *left_ptrs[] = {
|
||
"left_ptr",
|
||
"default",
|
||
"top_left_arrow",
|
||
"left-arrow"
|
||
};
|
||
|
||
static const char *left_sides[] = {
|
||
"left_side",
|
||
"w-resize",
|
||
"size_hor"
|
||
};
|
||
|
||
static const char *right_sides[] = {
|
||
"right_side",
|
||
"e-resize",
|
||
"size_hor"
|
||
};
|
||
|
||
static const char *top_left_corners[] = {
|
||
"top_left_corner",
|
||
"nw-resize",
|
||
"size_fdiag"
|
||
};
|
||
|
||
static const char *top_right_corners[] = {
|
||
"top_right_corner",
|
||
"ne-resize",
|
||
"size_bdiag"
|
||
};
|
||
|
||
static const char *top_sides[] = {
|
||
"top_side",
|
||
"n-resize",
|
||
"size_ver"
|
||
};
|
||
|
||
struct cursor_alternatives {
|
||
const char **names;
|
||
size_t count;
|
||
};
|
||
|
||
static const struct cursor_alternatives cursors[] = {
|
||
{top_sides, ARRAY_LENGTH(top_sides)},
|
||
{bottom_sides, ARRAY_LENGTH(bottom_sides)},
|
||
{left_sides, ARRAY_LENGTH(left_sides)},
|
||
{right_sides, ARRAY_LENGTH(right_sides)},
|
||
{top_left_corners, ARRAY_LENGTH(top_left_corners)},
|
||
{top_right_corners, ARRAY_LENGTH(top_right_corners)},
|
||
{bottom_left_corners, ARRAY_LENGTH(bottom_left_corners)},
|
||
{bottom_right_corners, ARRAY_LENGTH(bottom_right_corners)},
|
||
{left_ptrs, ARRAY_LENGTH(left_ptrs)},
|
||
};
|
||
|
||
static void
|
||
weston_wm_create_cursors(struct weston_wm *wm)
|
||
{
|
||
const char *name;
|
||
int i, count = ARRAY_LENGTH(cursors);
|
||
size_t j;
|
||
|
||
wm->cursors = malloc(count * sizeof(xcb_cursor_t));
|
||
for (i = 0; i < count; i++) {
|
||
for (j = 0; j < cursors[i].count; j++) {
|
||
name = cursors[i].names[j];
|
||
wm->cursors[i] =
|
||
xcb_cursor_library_load_cursor(wm, name);
|
||
if (wm->cursors[i] != (xcb_cursor_t)-1)
|
||
break;
|
||
}
|
||
}
|
||
|
||
wm->last_cursor = -1;
|
||
}
|
||
|
||
static void
|
||
weston_wm_destroy_cursors(struct weston_wm *wm)
|
||
{
|
||
uint8_t i;
|
||
|
||
for (i = 0; i < ARRAY_LENGTH(cursors); i++)
|
||
xcb_free_cursor(wm->conn, wm->cursors[i]);
|
||
|
||
free(wm->cursors);
|
||
}
|
||
|
||
static int
|
||
get_cursor_for_location(enum theme_location location)
|
||
{
|
||
switch (location) {
|
||
case THEME_LOCATION_RESIZING_TOP:
|
||
return XWM_CURSOR_TOP;
|
||
case THEME_LOCATION_RESIZING_BOTTOM:
|
||
return XWM_CURSOR_BOTTOM;
|
||
case THEME_LOCATION_RESIZING_LEFT:
|
||
return XWM_CURSOR_LEFT;
|
||
case THEME_LOCATION_RESIZING_RIGHT:
|
||
return XWM_CURSOR_RIGHT;
|
||
case THEME_LOCATION_RESIZING_TOP_LEFT:
|
||
return XWM_CURSOR_TOP_LEFT;
|
||
case THEME_LOCATION_RESIZING_TOP_RIGHT:
|
||
return XWM_CURSOR_TOP_RIGHT;
|
||
case THEME_LOCATION_RESIZING_BOTTOM_LEFT:
|
||
return XWM_CURSOR_BOTTOM_LEFT;
|
||
case THEME_LOCATION_RESIZING_BOTTOM_RIGHT:
|
||
return XWM_CURSOR_BOTTOM_RIGHT;
|
||
case THEME_LOCATION_EXTERIOR:
|
||
case THEME_LOCATION_TITLEBAR:
|
||
default:
|
||
return XWM_CURSOR_LEFT_PTR;
|
||
}
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_set_cursor(struct weston_wm *wm, xcb_window_t window_id,
|
||
int cursor)
|
||
{
|
||
uint32_t cursor_value_list;
|
||
|
||
if (wm->last_cursor == cursor)
|
||
return;
|
||
|
||
wm->last_cursor = cursor;
|
||
|
||
cursor_value_list = wm->cursors[cursor];
|
||
xcb_change_window_attributes (wm->conn, window_id,
|
||
XCB_CW_CURSOR, &cursor_value_list);
|
||
xcb_flush(wm->conn);
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_close(struct weston_wm_window *window, xcb_timestamp_t time)
|
||
{
|
||
xcb_client_message_event_t client_message;
|
||
|
||
if (window->delete_window) {
|
||
client_message.response_type = XCB_CLIENT_MESSAGE;
|
||
client_message.format = 32;
|
||
client_message.window = window->id;
|
||
client_message.type = window->wm->atom.wm_protocols;
|
||
client_message.data.data32[0] =
|
||
window->wm->atom.wm_delete_window;
|
||
client_message.data.data32[1] = time;
|
||
|
||
xcb_send_event(window->wm->conn, 0, window->id,
|
||
XCB_EVENT_MASK_NO_EVENT,
|
||
(char *) &client_message);
|
||
} else {
|
||
xcb_kill_client(window->wm->conn, window->id);
|
||
}
|
||
}
|
||
|
||
#define DOUBLE_CLICK_PERIOD 250
|
||
static void
|
||
weston_wm_handle_button(struct weston_wm *wm, xcb_generic_event_t *event)
|
||
{
|
||
xcb_button_press_event_t *button = (xcb_button_press_event_t *) event;
|
||
const struct weston_desktop_xwayland_interface *xwayland_interface =
|
||
wm->server->compositor->xwayland_interface;
|
||
struct weston_seat *seat;
|
||
struct weston_pointer *pointer;
|
||
struct weston_wm_window *window;
|
||
enum theme_location location;
|
||
enum wl_pointer_button_state button_state;
|
||
uint32_t button_id;
|
||
uint32_t double_click = 0;
|
||
|
||
wm_log("XCB_BUTTON_%s (detail %d)\n",
|
||
button->response_type == XCB_BUTTON_PRESS ?
|
||
"PRESS" : "RELEASE", button->detail);
|
||
|
||
if (!wm_lookup_window(wm, button->event, &window) ||
|
||
!window->decorate)
|
||
return;
|
||
|
||
if (button->detail != 1 && button->detail != 2)
|
||
return;
|
||
|
||
seat = weston_wm_pick_seat_for_window(window);
|
||
pointer = weston_seat_get_pointer(seat);
|
||
|
||
button_state = button->response_type == XCB_BUTTON_PRESS ?
|
||
WL_POINTER_BUTTON_STATE_PRESSED :
|
||
WL_POINTER_BUTTON_STATE_RELEASED;
|
||
button_id = button->detail == 1 ? BTN_LEFT : BTN_RIGHT;
|
||
|
||
if (button_state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
||
if (button->time - window->last_button_time <= DOUBLE_CLICK_PERIOD) {
|
||
double_click = 1;
|
||
window->did_double = 1;
|
||
} else
|
||
window->did_double = 0;
|
||
|
||
window->last_button_time = button->time;
|
||
} else if (window->did_double == 1) {
|
||
double_click = 1;
|
||
window->did_double = 0;
|
||
}
|
||
|
||
/* Make sure we're looking at the right location. The frame
|
||
* could have received a motion event from a pointer from a
|
||
* different wl_seat, but under X it looks like our core
|
||
* pointer moved. Move the frame pointer to the button press
|
||
* location before deciding what to do. */
|
||
location = frame_pointer_motion(window->frame, NULL,
|
||
button->event_x, button->event_y);
|
||
if (double_click)
|
||
location = frame_double_click(window->frame, NULL,
|
||
button_id, button_state);
|
||
else
|
||
location = frame_pointer_button(window->frame, NULL,
|
||
button_id, button_state);
|
||
|
||
if (frame_status(window->frame) & FRAME_STATUS_REPAINT)
|
||
weston_wm_window_schedule_repaint(window);
|
||
|
||
if (frame_status(window->frame) & FRAME_STATUS_MOVE) {
|
||
if (pointer)
|
||
xwayland_interface->move(window->shsurf, pointer);
|
||
frame_status_clear(window->frame, FRAME_STATUS_MOVE);
|
||
}
|
||
|
||
if (frame_status(window->frame) & FRAME_STATUS_RESIZE) {
|
||
if (pointer)
|
||
xwayland_interface->resize(window->shsurf, pointer, location);
|
||
frame_status_clear(window->frame, FRAME_STATUS_RESIZE);
|
||
}
|
||
|
||
if (frame_status(window->frame) & FRAME_STATUS_CLOSE) {
|
||
weston_wm_window_close(window, button->time);
|
||
frame_status_clear(window->frame, FRAME_STATUS_CLOSE);
|
||
}
|
||
|
||
if (frame_status(window->frame) & FRAME_STATUS_MAXIMIZE) {
|
||
window->maximized_horz = !window->maximized_horz;
|
||
window->maximized_vert = !window->maximized_vert;
|
||
if (weston_wm_window_is_maximized(window)) {
|
||
window->saved_width = window->width;
|
||
window->saved_height = window->height;
|
||
xwayland_interface->set_maximized(window->shsurf);
|
||
} else {
|
||
weston_wm_window_set_toplevel(window);
|
||
}
|
||
frame_status_clear(window->frame, FRAME_STATUS_MAXIMIZE);
|
||
}
|
||
}
|
||
|
||
static void
|
||
weston_wm_handle_motion(struct weston_wm *wm, xcb_generic_event_t *event)
|
||
{
|
||
xcb_motion_notify_event_t *motion = (xcb_motion_notify_event_t *) event;
|
||
struct weston_wm_window *window;
|
||
enum theme_location location;
|
||
int cursor;
|
||
|
||
if (!wm_lookup_window(wm, motion->event, &window) ||
|
||
!window->decorate)
|
||
return;
|
||
|
||
location = frame_pointer_motion(window->frame, NULL,
|
||
motion->event_x, motion->event_y);
|
||
if (frame_status(window->frame) & FRAME_STATUS_REPAINT)
|
||
weston_wm_window_schedule_repaint(window);
|
||
|
||
cursor = get_cursor_for_location(location);
|
||
weston_wm_window_set_cursor(wm, window->frame_id, cursor);
|
||
}
|
||
|
||
static void
|
||
weston_wm_handle_enter(struct weston_wm *wm, xcb_generic_event_t *event)
|
||
{
|
||
xcb_enter_notify_event_t *enter = (xcb_enter_notify_event_t *) event;
|
||
struct weston_wm_window *window;
|
||
enum theme_location location;
|
||
int cursor;
|
||
|
||
if (!wm_lookup_window(wm, enter->event, &window) ||
|
||
!window->decorate)
|
||
return;
|
||
|
||
location = frame_pointer_enter(window->frame, NULL,
|
||
enter->event_x, enter->event_y);
|
||
if (frame_status(window->frame) & FRAME_STATUS_REPAINT)
|
||
weston_wm_window_schedule_repaint(window);
|
||
|
||
cursor = get_cursor_for_location(location);
|
||
weston_wm_window_set_cursor(wm, window->frame_id, cursor);
|
||
}
|
||
|
||
static void
|
||
weston_wm_handle_leave(struct weston_wm *wm, xcb_generic_event_t *event)
|
||
{
|
||
xcb_leave_notify_event_t *leave = (xcb_leave_notify_event_t *) event;
|
||
struct weston_wm_window *window;
|
||
|
||
if (!wm_lookup_window(wm, leave->event, &window) ||
|
||
!window->decorate)
|
||
return;
|
||
|
||
frame_pointer_leave(window->frame, NULL);
|
||
if (frame_status(window->frame) & FRAME_STATUS_REPAINT)
|
||
weston_wm_window_schedule_repaint(window);
|
||
|
||
weston_wm_window_set_cursor(wm, window->frame_id, XWM_CURSOR_LEFT_PTR);
|
||
}
|
||
|
||
static void
|
||
weston_wm_handle_focus_in(struct weston_wm *wm, xcb_generic_event_t *event)
|
||
{
|
||
xcb_focus_in_event_t *focus = (xcb_focus_in_event_t *) event;
|
||
|
||
/* Do not interfere with grabs */
|
||
if (focus->mode == XCB_NOTIFY_MODE_GRAB ||
|
||
focus->mode == XCB_NOTIFY_MODE_UNGRAB)
|
||
return;
|
||
|
||
/* Do not let X clients change the focus behind the compositor's
|
||
* back. Reset the focus to the old one if it changed. */
|
||
if (!wm->focus_window || focus->event != wm->focus_window->id)
|
||
weston_wm_send_focus_window(wm, wm->focus_window);
|
||
}
|
||
|
||
static int
|
||
weston_wm_handle_event(int fd, uint32_t mask, void *data)
|
||
{
|
||
struct weston_wm *wm = data;
|
||
xcb_generic_event_t *event;
|
||
int count = 0;
|
||
|
||
while (event = xcb_poll_for_event(wm->conn), event != NULL) {
|
||
if (weston_wm_handle_selection_event(wm, event)) {
|
||
free(event);
|
||
count++;
|
||
continue;
|
||
}
|
||
|
||
if (weston_wm_handle_dnd_event(wm, event)) {
|
||
free(event);
|
||
count++;
|
||
continue;
|
||
}
|
||
|
||
switch (EVENT_TYPE(event)) {
|
||
case XCB_BUTTON_PRESS:
|
||
case XCB_BUTTON_RELEASE:
|
||
weston_wm_handle_button(wm, event);
|
||
break;
|
||
case XCB_ENTER_NOTIFY:
|
||
weston_wm_handle_enter(wm, event);
|
||
break;
|
||
case XCB_LEAVE_NOTIFY:
|
||
weston_wm_handle_leave(wm, event);
|
||
break;
|
||
case XCB_MOTION_NOTIFY:
|
||
weston_wm_handle_motion(wm, event);
|
||
break;
|
||
case XCB_CREATE_NOTIFY:
|
||
weston_wm_handle_create_notify(wm, event);
|
||
break;
|
||
case XCB_MAP_REQUEST:
|
||
weston_wm_handle_map_request(wm, event);
|
||
break;
|
||
case XCB_MAP_NOTIFY:
|
||
weston_wm_handle_map_notify(wm, event);
|
||
break;
|
||
case XCB_UNMAP_NOTIFY:
|
||
weston_wm_handle_unmap_notify(wm, event);
|
||
break;
|
||
case XCB_REPARENT_NOTIFY:
|
||
weston_wm_handle_reparent_notify(wm, event);
|
||
break;
|
||
case XCB_CONFIGURE_REQUEST:
|
||
weston_wm_handle_configure_request(wm, event);
|
||
break;
|
||
case XCB_CONFIGURE_NOTIFY:
|
||
weston_wm_handle_configure_notify(wm, event);
|
||
break;
|
||
case XCB_DESTROY_NOTIFY:
|
||
weston_wm_handle_destroy_notify(wm, event);
|
||
break;
|
||
case XCB_MAPPING_NOTIFY:
|
||
wm_log("XCB_MAPPING_NOTIFY\n");
|
||
break;
|
||
case XCB_PROPERTY_NOTIFY:
|
||
weston_wm_handle_property_notify(wm, event);
|
||
break;
|
||
case XCB_CLIENT_MESSAGE:
|
||
weston_wm_handle_client_message(wm, event);
|
||
break;
|
||
case XCB_FOCUS_IN:
|
||
weston_wm_handle_focus_in(wm, event);
|
||
break;
|
||
}
|
||
|
||
free(event);
|
||
count++;
|
||
}
|
||
|
||
if (count != 0)
|
||
xcb_flush(wm->conn);
|
||
|
||
return count;
|
||
}
|
||
|
||
static void
|
||
weston_wm_set_net_active_window(struct weston_wm *wm, xcb_window_t window) {
|
||
xcb_change_property(wm->conn, XCB_PROP_MODE_REPLACE,
|
||
wm->screen->root, wm->atom.net_active_window,
|
||
wm->atom.window, 32, 1, &window);
|
||
}
|
||
|
||
static void
|
||
weston_wm_get_visual_and_colormap(struct weston_wm *wm)
|
||
{
|
||
xcb_depth_iterator_t d_iter;
|
||
xcb_visualtype_iterator_t vt_iter;
|
||
xcb_visualtype_t *visualtype;
|
||
|
||
d_iter = xcb_screen_allowed_depths_iterator(wm->screen);
|
||
visualtype = NULL;
|
||
while (d_iter.rem > 0) {
|
||
if (d_iter.data->depth == 32) {
|
||
vt_iter = xcb_depth_visuals_iterator(d_iter.data);
|
||
visualtype = vt_iter.data;
|
||
break;
|
||
}
|
||
|
||
xcb_depth_next(&d_iter);
|
||
}
|
||
|
||
if (visualtype == NULL) {
|
||
weston_log("no 32 bit visualtype\n");
|
||
return;
|
||
}
|
||
|
||
wm->visual_id = visualtype->visual_id;
|
||
wm->colormap = xcb_generate_id(wm->conn);
|
||
xcb_create_colormap(wm->conn, XCB_COLORMAP_ALLOC_NONE,
|
||
wm->colormap, wm->screen->root, wm->visual_id);
|
||
}
|
||
|
||
static void
|
||
weston_wm_get_resources(struct weston_wm *wm)
|
||
{
|
||
|
||
#define F(field) offsetof(struct weston_wm, field)
|
||
|
||
static const struct { const char *name; int offset; } atoms[] = {
|
||
{ "WM_PROTOCOLS", F(atom.wm_protocols) },
|
||
{ "WM_NORMAL_HINTS", F(atom.wm_normal_hints) },
|
||
{ "WM_TAKE_FOCUS", F(atom.wm_take_focus) },
|
||
{ "WM_DELETE_WINDOW", F(atom.wm_delete_window) },
|
||
{ "WM_STATE", F(atom.wm_state) },
|
||
{ "WM_S0", F(atom.wm_s0) },
|
||
{ "WM_CLIENT_MACHINE", F(atom.wm_client_machine) },
|
||
{ "_NET_WM_CM_S0", F(atom.net_wm_cm_s0) },
|
||
{ "_NET_WM_NAME", F(atom.net_wm_name) },
|
||
{ "_NET_WM_PID", F(atom.net_wm_pid) },
|
||
{ "_NET_WM_ICON", F(atom.net_wm_icon) },
|
||
{ "_NET_WM_STATE", F(atom.net_wm_state) },
|
||
{ "_NET_WM_STATE_MAXIMIZED_VERT", F(atom.net_wm_state_maximized_vert) },
|
||
{ "_NET_WM_STATE_MAXIMIZED_HORZ", F(atom.net_wm_state_maximized_horz) },
|
||
{ "_NET_WM_STATE_FULLSCREEN", F(atom.net_wm_state_fullscreen) },
|
||
{ "_NET_WM_USER_TIME", F(atom.net_wm_user_time) },
|
||
{ "_NET_WM_ICON_NAME", F(atom.net_wm_icon_name) },
|
||
{ "_NET_WM_DESKTOP", F(atom.net_wm_desktop) },
|
||
{ "_NET_WM_WINDOW_TYPE", F(atom.net_wm_window_type) },
|
||
|
||
{ "_NET_WM_WINDOW_TYPE_DESKTOP", F(atom.net_wm_window_type_desktop) },
|
||
{ "_NET_WM_WINDOW_TYPE_DOCK", F(atom.net_wm_window_type_dock) },
|
||
{ "_NET_WM_WINDOW_TYPE_TOOLBAR", F(atom.net_wm_window_type_toolbar) },
|
||
{ "_NET_WM_WINDOW_TYPE_MENU", F(atom.net_wm_window_type_menu) },
|
||
{ "_NET_WM_WINDOW_TYPE_UTILITY", F(atom.net_wm_window_type_utility) },
|
||
{ "_NET_WM_WINDOW_TYPE_SPLASH", F(atom.net_wm_window_type_splash) },
|
||
{ "_NET_WM_WINDOW_TYPE_DIALOG", F(atom.net_wm_window_type_dialog) },
|
||
{ "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", F(atom.net_wm_window_type_dropdown) },
|
||
{ "_NET_WM_WINDOW_TYPE_POPUP_MENU", F(atom.net_wm_window_type_popup) },
|
||
{ "_NET_WM_WINDOW_TYPE_TOOLTIP", F(atom.net_wm_window_type_tooltip) },
|
||
{ "_NET_WM_WINDOW_TYPE_NOTIFICATION", F(atom.net_wm_window_type_notification) },
|
||
{ "_NET_WM_WINDOW_TYPE_COMBO", F(atom.net_wm_window_type_combo) },
|
||
{ "_NET_WM_WINDOW_TYPE_DND", F(atom.net_wm_window_type_dnd) },
|
||
{ "_NET_WM_WINDOW_TYPE_NORMAL", F(atom.net_wm_window_type_normal) },
|
||
|
||
{ "_NET_WM_MOVERESIZE", F(atom.net_wm_moveresize) },
|
||
{ "_NET_SUPPORTING_WM_CHECK",
|
||
F(atom.net_supporting_wm_check) },
|
||
{ "_NET_SUPPORTED", F(atom.net_supported) },
|
||
{ "_NET_ACTIVE_WINDOW", F(atom.net_active_window) },
|
||
{ "_MOTIF_WM_HINTS", F(atom.motif_wm_hints) },
|
||
{ "CLIPBOARD", F(atom.clipboard) },
|
||
{ "CLIPBOARD_MANAGER", F(atom.clipboard_manager) },
|
||
{ "TARGETS", F(atom.targets) },
|
||
{ "UTF8_STRING", F(atom.utf8_string) },
|
||
{ "_WL_SELECTION", F(atom.wl_selection) },
|
||
{ "INCR", F(atom.incr) },
|
||
{ "TIMESTAMP", F(atom.timestamp) },
|
||
{ "MULTIPLE", F(atom.multiple) },
|
||
{ "UTF8_STRING" , F(atom.utf8_string) },
|
||
{ "COMPOUND_TEXT", F(atom.compound_text) },
|
||
{ "TEXT", F(atom.text) },
|
||
{ "STRING", F(atom.string) },
|
||
{ "WINDOW", F(atom.window) },
|
||
{ "text/plain;charset=utf-8", F(atom.text_plain_utf8) },
|
||
{ "text/plain", F(atom.text_plain) },
|
||
{ "XdndSelection", F(atom.xdnd_selection) },
|
||
{ "XdndAware", F(atom.xdnd_aware) },
|
||
{ "XdndEnter", F(atom.xdnd_enter) },
|
||
{ "XdndLeave", F(atom.xdnd_leave) },
|
||
{ "XdndDrop", F(atom.xdnd_drop) },
|
||
{ "XdndStatus", F(atom.xdnd_status) },
|
||
{ "XdndFinished", F(atom.xdnd_finished) },
|
||
{ "XdndTypeList", F(atom.xdnd_type_list) },
|
||
{ "XdndActionCopy", F(atom.xdnd_action_copy) },
|
||
{ "_XWAYLAND_ALLOW_COMMITS", F(atom.allow_commits) },
|
||
{ "WL_SURFACE_ID", F(atom.wl_surface_id) }
|
||
};
|
||
#undef F
|
||
|
||
xcb_xfixes_query_version_cookie_t xfixes_cookie;
|
||
xcb_xfixes_query_version_reply_t *xfixes_reply;
|
||
xcb_intern_atom_cookie_t cookies[ARRAY_LENGTH(atoms)];
|
||
xcb_intern_atom_reply_t *reply;
|
||
xcb_render_query_pict_formats_reply_t *formats_reply;
|
||
xcb_render_query_pict_formats_cookie_t formats_cookie;
|
||
xcb_render_pictforminfo_t *formats;
|
||
uint32_t i;
|
||
|
||
xcb_prefetch_extension_data (wm->conn, &xcb_xfixes_id);
|
||
xcb_prefetch_extension_data (wm->conn, &xcb_composite_id);
|
||
|
||
formats_cookie = xcb_render_query_pict_formats(wm->conn);
|
||
|
||
for (i = 0; i < ARRAY_LENGTH(atoms); i++)
|
||
cookies[i] = xcb_intern_atom (wm->conn, 0,
|
||
strlen(atoms[i].name),
|
||
atoms[i].name);
|
||
|
||
for (i = 0; i < ARRAY_LENGTH(atoms); i++) {
|
||
reply = xcb_intern_atom_reply (wm->conn, cookies[i], NULL);
|
||
*(xcb_atom_t *) ((char *) wm + atoms[i].offset) = reply->atom;
|
||
free(reply);
|
||
}
|
||
|
||
wm->xfixes = xcb_get_extension_data(wm->conn, &xcb_xfixes_id);
|
||
if (!wm->xfixes || !wm->xfixes->present)
|
||
weston_log("xfixes not available\n");
|
||
|
||
xfixes_cookie = xcb_xfixes_query_version(wm->conn,
|
||
XCB_XFIXES_MAJOR_VERSION,
|
||
XCB_XFIXES_MINOR_VERSION);
|
||
xfixes_reply = xcb_xfixes_query_version_reply(wm->conn,
|
||
xfixes_cookie, NULL);
|
||
|
||
weston_log("xfixes version: %d.%d\n",
|
||
xfixes_reply->major_version, xfixes_reply->minor_version);
|
||
|
||
free(xfixes_reply);
|
||
|
||
formats_reply = xcb_render_query_pict_formats_reply(wm->conn,
|
||
formats_cookie, 0);
|
||
if (formats_reply == NULL)
|
||
return;
|
||
|
||
formats = xcb_render_query_pict_formats_formats(formats_reply);
|
||
for (i = 0; i < formats_reply->num_formats; i++) {
|
||
if (formats[i].direct.red_mask != 0xff &&
|
||
formats[i].direct.red_shift != 16)
|
||
continue;
|
||
if (formats[i].type == XCB_RENDER_PICT_TYPE_DIRECT &&
|
||
formats[i].depth == 24)
|
||
wm->format_rgb = formats[i];
|
||
if (formats[i].type == XCB_RENDER_PICT_TYPE_DIRECT &&
|
||
formats[i].depth == 32 &&
|
||
formats[i].direct.alpha_mask == 0xff &&
|
||
formats[i].direct.alpha_shift == 24)
|
||
wm->format_rgba = formats[i];
|
||
}
|
||
|
||
free(formats_reply);
|
||
}
|
||
|
||
static void
|
||
weston_wm_create_wm_window(struct weston_wm *wm)
|
||
{
|
||
static const char name[] = "Weston WM";
|
||
|
||
wm->wm_window = xcb_generate_id(wm->conn);
|
||
xcb_create_window(wm->conn,
|
||
XCB_COPY_FROM_PARENT,
|
||
wm->wm_window,
|
||
wm->screen->root,
|
||
0, 0,
|
||
10, 10,
|
||
0,
|
||
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||
wm->screen->root_visual,
|
||
0, NULL);
|
||
|
||
xcb_change_property(wm->conn,
|
||
XCB_PROP_MODE_REPLACE,
|
||
wm->wm_window,
|
||
wm->atom.net_supporting_wm_check,
|
||
XCB_ATOM_WINDOW,
|
||
32, /* format */
|
||
1, &wm->wm_window);
|
||
|
||
xcb_change_property(wm->conn,
|
||
XCB_PROP_MODE_REPLACE,
|
||
wm->wm_window,
|
||
wm->atom.net_wm_name,
|
||
wm->atom.utf8_string,
|
||
8, /* format */
|
||
strlen(name), name);
|
||
|
||
xcb_change_property(wm->conn,
|
||
XCB_PROP_MODE_REPLACE,
|
||
wm->screen->root,
|
||
wm->atom.net_supporting_wm_check,
|
||
XCB_ATOM_WINDOW,
|
||
32, /* format */
|
||
1, &wm->wm_window);
|
||
|
||
/* Claim the WM_S0 selection even though we don't support
|
||
* the --replace functionality. */
|
||
xcb_set_selection_owner(wm->conn,
|
||
wm->wm_window,
|
||
wm->atom.wm_s0,
|
||
XCB_TIME_CURRENT_TIME);
|
||
|
||
xcb_set_selection_owner(wm->conn,
|
||
wm->wm_window,
|
||
wm->atom.net_wm_cm_s0,
|
||
XCB_TIME_CURRENT_TIME);
|
||
}
|
||
|
||
struct weston_wm *
|
||
weston_wm_create(struct weston_xserver *wxs, int fd)
|
||
{
|
||
struct weston_wm *wm;
|
||
struct wl_event_loop *loop;
|
||
xcb_screen_iterator_t s;
|
||
uint32_t values[1];
|
||
xcb_atom_t supported[6];
|
||
|
||
wm = zalloc(sizeof *wm);
|
||
if (wm == NULL)
|
||
return NULL;
|
||
|
||
wm->server = wxs;
|
||
wm->window_hash = hash_table_create();
|
||
if (wm->window_hash == NULL) {
|
||
free(wm);
|
||
return NULL;
|
||
}
|
||
|
||
/* xcb_connect_to_fd takes ownership of the fd. */
|
||
wm->conn = xcb_connect_to_fd(fd, NULL);
|
||
if (xcb_connection_has_error(wm->conn)) {
|
||
weston_log("xcb_connect_to_fd failed\n");
|
||
close(fd);
|
||
hash_table_destroy(wm->window_hash);
|
||
free(wm);
|
||
return NULL;
|
||
}
|
||
|
||
s = xcb_setup_roots_iterator(xcb_get_setup(wm->conn));
|
||
wm->screen = s.data;
|
||
|
||
loop = wl_display_get_event_loop(wxs->wl_display);
|
||
wm->source =
|
||
wl_event_loop_add_fd(loop, fd,
|
||
WL_EVENT_READABLE,
|
||
weston_wm_handle_event, wm);
|
||
wl_event_source_check(wm->source);
|
||
|
||
weston_wm_get_resources(wm);
|
||
weston_wm_get_visual_and_colormap(wm);
|
||
|
||
values[0] =
|
||
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
|
||
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
|
||
XCB_EVENT_MASK_PROPERTY_CHANGE;
|
||
xcb_change_window_attributes(wm->conn, wm->screen->root,
|
||
XCB_CW_EVENT_MASK, values);
|
||
|
||
xcb_composite_redirect_subwindows(wm->conn, wm->screen->root,
|
||
XCB_COMPOSITE_REDIRECT_MANUAL);
|
||
|
||
wm->theme = theme_create();
|
||
|
||
supported[0] = wm->atom.net_wm_moveresize;
|
||
supported[1] = wm->atom.net_wm_state;
|
||
supported[2] = wm->atom.net_wm_state_fullscreen;
|
||
supported[3] = wm->atom.net_wm_state_maximized_vert;
|
||
supported[4] = wm->atom.net_wm_state_maximized_horz;
|
||
supported[5] = wm->atom.net_active_window;
|
||
xcb_change_property(wm->conn,
|
||
XCB_PROP_MODE_REPLACE,
|
||
wm->screen->root,
|
||
wm->atom.net_supported,
|
||
XCB_ATOM_ATOM,
|
||
32, /* format */
|
||
ARRAY_LENGTH(supported), supported);
|
||
|
||
weston_wm_set_net_active_window(wm, XCB_WINDOW_NONE);
|
||
|
||
weston_wm_selection_init(wm);
|
||
|
||
weston_wm_dnd_init(wm);
|
||
|
||
xcb_flush(wm->conn);
|
||
|
||
wm->create_surface_listener.notify = weston_wm_create_surface;
|
||
wl_signal_add(&wxs->compositor->create_surface_signal,
|
||
&wm->create_surface_listener);
|
||
wm->activate_listener.notify = weston_wm_window_activate;
|
||
wl_signal_add(&wxs->compositor->activate_signal,
|
||
&wm->activate_listener);
|
||
wm->kill_listener.notify = weston_wm_kill_client;
|
||
wl_signal_add(&wxs->compositor->kill_signal,
|
||
&wm->kill_listener);
|
||
wl_list_init(&wm->unpaired_window_list);
|
||
|
||
weston_wm_create_cursors(wm);
|
||
weston_wm_window_set_cursor(wm, wm->screen->root, XWM_CURSOR_LEFT_PTR);
|
||
|
||
/* Create wm window and take WM_S0 selection last, which
|
||
* signals to Xwayland that we're done with setup. */
|
||
weston_wm_create_wm_window(wm);
|
||
|
||
weston_log("created wm, root %d\n", wm->screen->root);
|
||
|
||
return wm;
|
||
}
|
||
|
||
void
|
||
weston_wm_destroy(struct weston_wm *wm)
|
||
{
|
||
/* FIXME: Free windows in hash. */
|
||
hash_table_destroy(wm->window_hash);
|
||
weston_wm_destroy_cursors(wm);
|
||
xcb_disconnect(wm->conn);
|
||
wl_event_source_remove(wm->source);
|
||
wl_list_remove(&wm->selection_listener.link);
|
||
wl_list_remove(&wm->activate_listener.link);
|
||
wl_list_remove(&wm->kill_listener.link);
|
||
wl_list_remove(&wm->create_surface_listener.link);
|
||
|
||
free(wm);
|
||
}
|
||
|
||
static struct weston_wm_window *
|
||
get_wm_window(struct weston_surface *surface)
|
||
{
|
||
struct wl_listener *listener;
|
||
|
||
listener = wl_signal_get(&surface->destroy_signal, surface_destroy);
|
||
if (listener)
|
||
return container_of(listener, struct weston_wm_window,
|
||
surface_destroy_listener);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
static bool
|
||
is_wm_window(struct weston_surface *surface)
|
||
{
|
||
return get_wm_window(surface) != NULL;
|
||
}
|
||
|
||
static void
|
||
weston_wm_window_configure(void *data)
|
||
{
|
||
struct weston_wm_window *window = data;
|
||
struct weston_wm *wm = window->wm;
|
||
uint32_t values[4];
|
||
int x, y, width, height;
|
||
|
||
weston_wm_window_get_child_position(window, &x, &y);
|
||
values[0] = x;
|
||
values[1] = y;
|
||
values[2] = window->width;
|
||
values[3] = window->height;
|
||
xcb_configure_window(wm->conn,
|
||
window->id,
|
||
XCB_CONFIG_WINDOW_X |
|
||
XCB_CONFIG_WINDOW_Y |
|
||
XCB_CONFIG_WINDOW_WIDTH |
|
||
XCB_CONFIG_WINDOW_HEIGHT,
|
||
values);
|
||
|
||
weston_wm_window_get_frame_size(window, &width, &height);
|
||
values[0] = width;
|
||
values[1] = height;
|
||
xcb_configure_window(wm->conn,
|
||
window->frame_id,
|
||
XCB_CONFIG_WINDOW_WIDTH |
|
||
XCB_CONFIG_WINDOW_HEIGHT,
|
||
values);
|
||
|
||
window->configure_source = NULL;
|
||
|
||
weston_wm_window_schedule_repaint(window);
|
||
}
|
||
|
||
static void
|
||
send_configure(struct weston_surface *surface, int32_t width, int32_t height)
|
||
{
|
||
struct weston_wm_window *window = get_wm_window(surface);
|
||
struct weston_wm *wm = window->wm;
|
||
struct theme *t = window->wm->theme;
|
||
int new_width, new_height;
|
||
int vborder, hborder;
|
||
|
||
if (window->decorate && !window->fullscreen) {
|
||
hborder = 2 * t->width;
|
||
vborder = t->titlebar_height + t->width;
|
||
} else {
|
||
hborder = 0;
|
||
vborder = 0;
|
||
}
|
||
|
||
if (width > hborder)
|
||
new_width = width - hborder;
|
||
else
|
||
new_width = 1;
|
||
|
||
if (height > vborder)
|
||
new_height = height - vborder;
|
||
else
|
||
new_height = 1;
|
||
|
||
if (window->width == new_width && window->height == new_height)
|
||
return;
|
||
|
||
window->width = new_width;
|
||
window->height = new_height;
|
||
|
||
if (window->frame)
|
||
frame_resize_inside(window->frame, window->width, window->height);
|
||
|
||
if (window->configure_source)
|
||
return;
|
||
|
||
window->configure_source =
|
||
wl_event_loop_add_idle(wm->server->loop,
|
||
weston_wm_window_configure, window);
|
||
}
|
||
|
||
static void
|
||
send_position(struct weston_surface *surface, int32_t x, int32_t y)
|
||
{
|
||
struct weston_wm_window *window = get_wm_window(surface);
|
||
struct weston_wm *wm;
|
||
uint32_t mask, values[2];
|
||
|
||
if (!window || !window->wm)
|
||
return;
|
||
|
||
wm = window->wm;
|
||
/* We use pos_dirty to tell whether a configure message is in flight.
|
||
* This is needed in case we send two configure events in a very
|
||
* short time, since window->x/y is set in after a roundtrip, hence
|
||
* we cannot just check if the current x and y are different. */
|
||
if (window->x != x || window->y != y || window->pos_dirty) {
|
||
window->pos_dirty = true;
|
||
values[0] = x;
|
||
values[1] = y;
|
||
mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y;
|
||
|
||
xcb_configure_window(wm->conn, window->frame_id, mask, values);
|
||
xcb_flush(wm->conn);
|
||
}
|
||
}
|
||
|
||
static const struct weston_xwayland_client_interface shell_client = {
|
||
send_configure,
|
||
};
|
||
|
||
static int
|
||
legacy_fullscreen(struct weston_wm *wm,
|
||
struct weston_wm_window *window,
|
||
struct weston_output **output_ret)
|
||
{
|
||
struct weston_compositor *compositor = wm->server->compositor;
|
||
struct weston_output *output;
|
||
uint32_t minmax = PMinSize | PMaxSize;
|
||
int matching_size;
|
||
|
||
/* Heuristics for detecting legacy fullscreen windows... */
|
||
|
||
wl_list_for_each(output, &compositor->output_list, link) {
|
||
if (output->x == window->x &&
|
||
output->y == window->y &&
|
||
output->width == window->width &&
|
||
output->height == window->height &&
|
||
window->override_redirect) {
|
||
*output_ret = output;
|
||
return 1;
|
||
}
|
||
|
||
matching_size = 0;
|
||
if ((window->size_hints.flags & (USSize |PSize)) &&
|
||
window->size_hints.width == output->width &&
|
||
window->size_hints.height == output->height)
|
||
matching_size = 1;
|
||
if ((window->size_hints.flags & minmax) == minmax &&
|
||
window->size_hints.min_width == output->width &&
|
||
window->size_hints.min_height == output->height &&
|
||
window->size_hints.max_width == output->width &&
|
||
window->size_hints.max_height == output->height)
|
||
matching_size = 1;
|
||
|
||
if (matching_size && !window->decorate &&
|
||
(window->size_hints.flags & (USPosition | PPosition)) &&
|
||
window->size_hints.x == output->x &&
|
||
window->size_hints.y == output->y) {
|
||
*output_ret = output;
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static bool
|
||
weston_wm_window_is_positioned(struct weston_wm_window *window)
|
||
{
|
||
if (window->map_request_x == INT_MIN ||
|
||
window->map_request_y == INT_MIN)
|
||
weston_log("XWM warning: win %d did not see map request\n",
|
||
window->id);
|
||
|
||
return window->map_request_x != 0 || window->map_request_y != 0;
|
||
}
|
||
|
||
static bool
|
||
weston_wm_window_type_inactive(struct weston_wm_window *window)
|
||
{
|
||
struct weston_wm *wm = window->wm;
|
||
|
||
return window->type == wm->atom.net_wm_window_type_tooltip ||
|
||
window->type == wm->atom.net_wm_window_type_dropdown ||
|
||
window->type == wm->atom.net_wm_window_type_dnd ||
|
||
window->type == wm->atom.net_wm_window_type_combo ||
|
||
window->type == wm->atom.net_wm_window_type_popup ||
|
||
window->type == wm->atom.net_wm_window_type_utility;
|
||
}
|
||
|
||
static void
|
||
xserver_map_shell_surface(struct weston_wm_window *window,
|
||
struct weston_surface *surface)
|
||
{
|
||
struct weston_wm *wm = window->wm;
|
||
struct weston_desktop_xwayland *xwayland =
|
||
wm->server->compositor->xwayland;
|
||
const struct weston_desktop_xwayland_interface *xwayland_interface =
|
||
wm->server->compositor->xwayland_interface;
|
||
struct weston_wm_window *parent;
|
||
|
||
/* This should be necessary only for override-redirected windows,
|
||
* because otherwise MapRequest handler would have already updated
|
||
* the properties. However, if X11 clients set properties after
|
||
* sending MapWindow, here we can still process them. The decorations
|
||
* have already been drawn once with the old property values, so if the
|
||
* app changes something affecting decor after MapWindow, we glitch.
|
||
* We only hit xserver_map_shell_surface() once per MapWindow and
|
||
* wl_surface, so better ensure we get the window type right.
|
||
*/
|
||
weston_wm_window_read_properties(window);
|
||
|
||
/* A weston_wm_window may have many different surfaces assigned
|
||
* throughout its life, so we must make sure to remove the listener
|
||
* from the old surface signal list. */
|
||
if (window->surface)
|
||
wl_list_remove(&window->surface_destroy_listener.link);
|
||
|
||
window->surface = surface;
|
||
window->surface_destroy_listener.notify = surface_destroy;
|
||
wl_signal_add(&window->surface->destroy_signal,
|
||
&window->surface_destroy_listener);
|
||
|
||
if (!xwayland_interface)
|
||
return;
|
||
|
||
if (window->surface->committed) {
|
||
weston_log("warning, unexpected in %s: "
|
||
"surface's configure hook is already set.\n",
|
||
__func__);
|
||
return;
|
||
}
|
||
|
||
window->shsurf =
|
||
xwayland_interface->create_surface(xwayland,
|
||
window->surface,
|
||
&shell_client);
|
||
|
||
wm_log("XWM: map shell surface, win %d, weston_surface %p, xwayland surface %p\n",
|
||
window->id, window->surface, window->shsurf);
|
||
|
||
if (window->name)
|
||
xwayland_interface->set_title(window->shsurf, window->name);
|
||
if (window->pid > 0)
|
||
xwayland_interface->set_pid(window->shsurf, window->pid);
|
||
|
||
if (window->fullscreen) {
|
||
window->saved_width = window->width;
|
||
window->saved_height = window->height;
|
||
xwayland_interface->set_fullscreen(window->shsurf,
|
||
window->legacy_fullscreen_output.output);
|
||
return;
|
||
} else if (window->override_redirect) {
|
||
xwayland_interface->set_xwayland(window->shsurf,
|
||
window->x, window->y);
|
||
} else if (window->transient_for && window->transient_for->surface) {
|
||
parent = window->transient_for;
|
||
if (weston_wm_window_type_inactive(window)) {
|
||
xwayland_interface->set_transient(window->shsurf,
|
||
parent->surface,
|
||
window->x - parent->x,
|
||
window->y - parent->y);
|
||
} else {
|
||
xwayland_interface->set_toplevel(window->shsurf);
|
||
xwayland_interface->set_parent(window->shsurf,
|
||
parent->surface);
|
||
}
|
||
} else if (weston_wm_window_is_maximized(window)) {
|
||
xwayland_interface->set_maximized(window->shsurf);
|
||
} else {
|
||
if (weston_wm_window_type_inactive(window)) {
|
||
xwayland_interface->set_xwayland(window->shsurf,
|
||
window->x,
|
||
window->y);
|
||
} else if (weston_wm_window_is_positioned(window)) {
|
||
xwayland_interface->set_toplevel_with_position(window->shsurf,
|
||
window->map_request_x,
|
||
window->map_request_y);
|
||
} else {
|
||
xwayland_interface->set_toplevel(window->shsurf);
|
||
}
|
||
}
|
||
|
||
if (window->frame_id == XCB_WINDOW_NONE) {
|
||
weston_wm_window_set_pending_state_OR(window);
|
||
} else {
|
||
weston_wm_window_set_pending_state(window);
|
||
weston_wm_window_set_allow_commits(window, true);
|
||
xcb_flush(wm->conn);
|
||
}
|
||
}
|
||
|
||
const struct weston_xwayland_surface_api surface_api = {
|
||
is_wm_window,
|
||
send_position,
|
||
};
|