blob: bcba6c4406cf841b65077fa0050d0b6154f92ee7 [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sommelier.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <gbm.h>
#include <libgen.h>
#include <linux/virtwl.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#include <wayland-client.h>
#include <xcb/composite.h>
#include <xcb/xfixes.h>
#include "aura-shell-client-protocol.h"
#include "drm-server-protocol.h"
#include "keyboard-extension-unstable-v1-client-protocol.h"
#include "linux-dmabuf-unstable-v1-client-protocol.h"
#include "relative-pointer-unstable-v1-client-protocol.h"
#include "text-input-unstable-v1-client-protocol.h"
#include "viewporter-client-protocol.h"
#include "xdg-shell-unstable-v6-client-protocol.h"
// Check that required macro definitions exist.
#ifndef XWAYLAND_PATH
#error XWAYLAND_PATH must be defined
#endif
#ifndef XWAYLAND_GL_DRIVER_PATH
#error XWAYLAND_GL_DRIVER_PATH must be defined
#endif
#ifndef XWAYLAND_SHM_DRIVER
#error XWAYLAND_SHM_DRIVER must be defined
#endif
#ifndef SHM_DRIVER
#error SHM_DRIVER must be defined
#endif
#ifndef VIRTWL_DEVICE
#error VIRTWL_DEVICE must be defined
#endif
#ifndef PEER_CMD_PREFIX
#error PEER_CMD_PREFIX must be defined
#endif
#ifndef FRAME_COLOR
#error FRAME_COLOR must be defined
#endif
#ifndef DARK_FRAME_COLOR
#error DARK_FRAME_COLOR must be defined
#endif
struct sl_data_source {
struct sl_context* ctx;
struct wl_data_source *internal;
};
enum {
PROPERTY_WM_NAME,
PROPERTY_WM_CLASS,
PROPERTY_WM_TRANSIENT_FOR,
PROPERTY_WM_NORMAL_HINTS,
PROPERTY_WM_CLIENT_LEADER,
PROPERTY_MOTIF_WM_HINTS,
PROPERTY_NET_STARTUP_ID,
PROPERTY_GTK_THEME_VARIANT,
};
#define US_POSITION (1L << 0)
#define US_SIZE (1L << 1)
#define P_POSITION (1L << 2)
#define P_SIZE (1L << 3)
#define P_MIN_SIZE (1L << 4)
#define P_MAX_SIZE (1L << 5)
#define P_RESIZE_INC (1L << 6)
#define P_ASPECT (1L << 7)
#define P_BASE_SIZE (1L << 8)
#define P_WIN_GRAVITY (1L << 9)
struct sl_wm_size_hints {
uint32_t flags;
int32_t x, y;
int32_t width, height;
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 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_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)
struct sl_mwm_hints {
uint32_t flags;
uint32_t functions;
uint32_t decorations;
int32_t input_mode;
uint32_t status;
};
#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
#define NET_WM_STATE_REMOVE 0
#define NET_WM_STATE_ADD 1
#define NET_WM_STATE_TOGGLE 2
#define WM_STATE_WITHDRAWN 0
#define WM_STATE_NORMAL 1
#define WM_STATE_ICONIC 3
#define SEND_EVENT_MASK 0x80
#define MIN_SCALE 0.1
#define MAX_SCALE 10.0
#define MIN_DPI 72
#define MAX_DPI 9600
#define XCURSOR_SIZE_BASE 24
#ifndef UNIX_PATH_MAX
#define UNIX_PATH_MAX 108
#endif
#define LOCK_SUFFIX ".lock"
#define LOCK_SUFFIXLEN 5
#define APPLICATION_ID_FORMAT_PREFIX "org.chromium.termina"
#define XID_APPLICATION_ID_FORMAT APPLICATION_ID_FORMAT_PREFIX ".xid.%d"
#define WM_CLIENT_LEADER_APPLICATION_ID_FORMAT \
APPLICATION_ID_FORMAT_PREFIX ".wmclientleader.%d"
#define WM_CLASS_APPLICATION_ID_FORMAT \
APPLICATION_ID_FORMAT_PREFIX ".wmclass.%s"
#define MIN_AURA_SHELL_VERSION 6
// Performs an asprintf operation and checks the result for validity and calls
// abort() if there's a failure. Returns a newly allocated string rather than
// taking a double pointer argument like asprintf.
__attribute__((__format__(__printf__, 1, 0))) static char* sl_xasprintf(
const char* fmt, ...) {
char* str;
va_list args;
va_start(args, fmt);
int rv = vasprintf(&str, fmt, args);
assert(rv >= 0);
UNUSED(rv);
va_end(args);
return str;
}
struct sl_mmap* sl_mmap_create(int fd,
size_t size,
size_t bpp,
size_t num_planes,
size_t offset0,
size_t stride0,
size_t offset1,
size_t stride1,
size_t y_ss0,
size_t y_ss1) {
struct sl_mmap* map;
map = malloc(sizeof(*map));
map->refcount = 1;
map->fd = fd;
map->size = size;
map->num_planes = num_planes;
map->bpp = bpp;
map->offset[0] = offset0;
map->stride[0] = stride0;
map->offset[1] = offset1;
map->stride[1] = stride1;
map->y_ss[0] = y_ss0;
map->y_ss[1] = y_ss1;
map->begin_write = NULL;
map->end_write = NULL;
map->buffer_resource = NULL;
map->addr =
mmap(NULL, size + offset0, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
assert(map->addr != MAP_FAILED);
return map;
}
struct sl_mmap* sl_mmap_ref(struct sl_mmap* map) {
map->refcount++;
return map;
}
void sl_mmap_unref(struct sl_mmap* map) {
if (map->refcount-- == 1) {
munmap(map->addr, map->size + map->offset[0]);
close(map->fd);
free(map);
}
}
struct sl_sync_point* sl_sync_point_create(int fd) {
struct sl_sync_point* sync_point;
sync_point = malloc(sizeof(*sync_point));
sync_point->fd = fd;
sync_point->sync = NULL;
return sync_point;
}
void sl_sync_point_destroy(struct sl_sync_point* sync_point)
{
close(sync_point->fd);
free(sync_point);
}
static void sl_internal_xdg_shell_ping(void* data,
struct zxdg_shell_v6* xdg_shell,
uint32_t serial) {
zxdg_shell_v6_pong(xdg_shell, serial);
}
static const struct zxdg_shell_v6_listener sl_internal_xdg_shell_listener = {
sl_internal_xdg_shell_ping};
static void sl_send_configure_notify(struct sl_window* window) {
xcb_configure_notify_event_t event = {
.response_type = XCB_CONFIGURE_NOTIFY,
.event = window->id,
.window = window->id,
.above_sibling = XCB_WINDOW_NONE,
.x = window->x,
.y = window->y,
.width = window->width,
.height = window->height,
.border_width = window->border_width,
.override_redirect = 0,
.pad0 = 0,
.pad1 = 0,
};
xcb_send_event(window->ctx->connection, 0, window->id,
XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&event);
}
static void sl_adjust_window_size_for_screen_size(struct sl_window* window) {
struct sl_context* ctx = window->ctx;
// Clamp size to screen.
window->width = MIN(window->width, ctx->screen->width_in_pixels);
window->height = MIN(window->height, ctx->screen->height_in_pixels);
}
static void sl_adjust_window_position_for_screen_size(
struct sl_window* window) {
struct sl_context* ctx = window->ctx;
// Center horizontally/vertically.
window->x = ctx->screen->width_in_pixels / 2 - window->width / 2;
window->y = ctx->screen->height_in_pixels / 2 - window->height / 2;
}
static void sl_configure_window(struct sl_window* window) {
assert(!window->pending_config.serial);
if (window->next_config.mask) {
int values[5];
int x = window->x;
int y = window->y;
int i = 0;
xcb_configure_window(window->ctx->connection, window->frame_id,
window->next_config.mask, window->next_config.values);
if (window->next_config.mask & XCB_CONFIG_WINDOW_X)
x = window->next_config.values[i++];
if (window->next_config.mask & XCB_CONFIG_WINDOW_Y)
y = window->next_config.values[i++];
if (window->next_config.mask & XCB_CONFIG_WINDOW_WIDTH)
window->width = window->next_config.values[i++];
if (window->next_config.mask & XCB_CONFIG_WINDOW_HEIGHT)
window->height = window->next_config.values[i++];
if (window->next_config.mask & XCB_CONFIG_WINDOW_BORDER_WIDTH)
window->border_width = window->next_config.values[i++];
// Set x/y to origin in case window gravity is not northwest as expected.
assert(window->managed);
values[0] = 0;
values[1] = 0;
values[2] = window->width;
values[3] = window->height;
values[4] = window->border_width;
xcb_configure_window(
window->ctx->connection, window->id,
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH,
values);
if (x != window->x || y != window->y) {
window->x = x;
window->y = y;
sl_send_configure_notify(window);
}
}
if (window->managed) {
xcb_change_property(window->ctx->connection, XCB_PROP_MODE_REPLACE,
window->id, window->ctx->atoms[ATOM_NET_WM_STATE].value,
XCB_ATOM_ATOM, 32, window->next_config.states_length,
window->next_config.states);
}
window->pending_config = window->next_config;
window->next_config.serial = 0;
window->next_config.mask = 0;
window->next_config.states_length = 0;
}
static void sl_set_input_focus(struct sl_context* ctx,
struct sl_window* window) {
if (window) {
xcb_client_message_event_t event = {
.response_type = XCB_CLIENT_MESSAGE,
.format = 32,
.window = window->id,
.type = ctx->atoms[ATOM_WM_PROTOCOLS].value,
.data.data32 =
{
ctx->atoms[ATOM_WM_TAKE_FOCUS].value, XCB_CURRENT_TIME,
},
};
if (!window->managed)
return;
xcb_send_event(ctx->connection, 0, window->id,
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char*)&event);
xcb_set_input_focus(ctx->connection, XCB_INPUT_FOCUS_NONE, window->id,
XCB_CURRENT_TIME);
} else {
xcb_set_input_focus(ctx->connection, XCB_INPUT_FOCUS_NONE, XCB_NONE,
XCB_CURRENT_TIME);
}
}
void sl_restack_windows(struct sl_context* ctx, uint32_t focus_resource_id) {
struct sl_window* sibling;
uint32_t values[1];
wl_list_for_each(sibling, &ctx->windows, link) {
if (!sibling->managed)
continue;
// Move focus window to the top and all other windows to the bottom.
values[0] = sibling->host_surface_id == focus_resource_id
? XCB_STACK_MODE_ABOVE
: XCB_STACK_MODE_BELOW;
xcb_configure_window(ctx->connection, sibling->frame_id,
XCB_CONFIG_WINDOW_STACK_MODE, values);
}
}
void sl_roundtrip(struct sl_context* ctx) {
free(xcb_get_input_focus_reply(ctx->connection,
xcb_get_input_focus(ctx->connection), NULL));
}
int sl_process_pending_configure_acks(struct sl_window* window,
struct sl_host_surface* host_surface) {
if (!window->pending_config.serial)
return 0;
if (window->managed && host_surface) {
int width = window->width + window->border_width * 2;
int height = window->height + window->border_width * 2;
// Early out if we expect contents to match window size at some point in
// the future.
if (width != host_surface->contents_width ||
height != host_surface->contents_height) {
return 0;
}
}
if (window->xdg_surface) {
zxdg_surface_v6_ack_configure(window->xdg_surface,
window->pending_config.serial);
}
window->pending_config.serial = 0;
if (window->next_config.serial)
sl_configure_window(window);
return 1;
}
static void sl_internal_xdg_surface_configure(
void* data, struct zxdg_surface_v6* xdg_surface, uint32_t serial) {
struct sl_window* window = zxdg_surface_v6_get_user_data(xdg_surface);
window->next_config.serial = serial;
if (!window->pending_config.serial) {
struct wl_resource *host_resource;
struct sl_host_surface* host_surface = NULL;
host_resource =
wl_client_get_object(window->ctx->client, window->host_surface_id);
if (host_resource)
host_surface = wl_resource_get_user_data(host_resource);
sl_configure_window(window);
if (sl_process_pending_configure_acks(window, host_surface)) {
if (host_surface)
wl_surface_commit(host_surface->proxy);
}
}
}
static const struct zxdg_surface_v6_listener sl_internal_xdg_surface_listener =
{sl_internal_xdg_surface_configure};
static void sl_internal_xdg_toplevel_configure(
void* data,
struct zxdg_toplevel_v6* xdg_toplevel,
int32_t width,
int32_t height,
struct wl_array* states) {
struct sl_window* window = zxdg_toplevel_v6_get_user_data(xdg_toplevel);
int activated = 0;
uint32_t *state;
int i = 0;
if (!window->managed)
return;
if (width && height) {
int32_t width_in_pixels = width * window->ctx->scale;
int32_t height_in_pixels = height * window->ctx->scale;
int i = 0;
window->next_config.mask = XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT |
XCB_CONFIG_WINDOW_BORDER_WIDTH;
if (!(window->size_flags & (US_POSITION | P_POSITION))) {
window->next_config.mask |= XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y;
window->next_config.values[i++] =
window->ctx->screen->width_in_pixels / 2 - width_in_pixels / 2;
window->next_config.values[i++] =
window->ctx->screen->height_in_pixels / 2 - height_in_pixels / 2;
}
window->next_config.values[i++] = width_in_pixels;
window->next_config.values[i++] = height_in_pixels;
window->next_config.values[i++] = 0;
}
window->allow_resize = 1;
wl_array_for_each(state, states) {
if (*state == ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN) {
window->allow_resize = 0;
window->next_config.states[i++] =
window->ctx->atoms[ATOM_NET_WM_STATE_FULLSCREEN].value;
}
if (*state == ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED) {
window->allow_resize = 0;
window->next_config.states[i++] =
window->ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value;
window->next_config.states[i++] =
window->ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value;
}
if (*state == ZXDG_TOPLEVEL_V6_STATE_ACTIVATED)
activated = 1;
if (*state == ZXDG_TOPLEVEL_V6_STATE_RESIZING)
window->allow_resize = 0;
}
if (activated != window->activated) {
if (activated != (window->ctx->host_focus_window == window)) {
window->ctx->host_focus_window = activated ? window : NULL;
window->ctx->needs_set_input_focus = 1;
}
window->activated = activated;
}
window->next_config.states_length = i;
}
static void sl_internal_xdg_toplevel_close(
void* data, struct zxdg_toplevel_v6* xdg_toplevel) {
struct sl_window* window = zxdg_toplevel_v6_get_user_data(xdg_toplevel);
xcb_client_message_event_t event = {
.response_type = XCB_CLIENT_MESSAGE,
.format = 32,
.window = window->id,
.type = window->ctx->atoms[ATOM_WM_PROTOCOLS].value,
.data.data32 =
{
window->ctx->atoms[ATOM_WM_DELETE_WINDOW].value, XCB_CURRENT_TIME,
},
};
xcb_send_event(window->ctx->connection, 0, window->id,
XCB_EVENT_MASK_NO_EVENT, (const char*)&event);
}
static const struct zxdg_toplevel_v6_listener
sl_internal_xdg_toplevel_listener = {sl_internal_xdg_toplevel_configure,
sl_internal_xdg_toplevel_close};
static void sl_internal_xdg_popup_configure(void* data,
struct zxdg_popup_v6* xdg_popup,
int32_t x,
int32_t y,
int32_t width,
int32_t height) {}
static void sl_internal_xdg_popup_done(void* data,
struct zxdg_popup_v6* zxdg_popup_v6) {}
static const struct zxdg_popup_v6_listener sl_internal_xdg_popup_listener = {
sl_internal_xdg_popup_configure, sl_internal_xdg_popup_done};
static void sl_window_set_wm_state(struct sl_window* window, int state) {
struct sl_context* ctx = window->ctx;
uint32_t values[2];
values[0] = state;
values[1] = XCB_WINDOW_NONE;
xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, window->id,
ctx->atoms[ATOM_WM_STATE].value,
ctx->atoms[ATOM_WM_STATE].value, 32, 2, values);
}
void sl_window_update(struct sl_window* window) {
struct wl_resource *host_resource = NULL;
struct sl_host_surface* host_surface;
struct sl_context* ctx = window->ctx;
struct sl_window* parent = NULL;
if (window->host_surface_id) {
host_resource = wl_client_get_object(ctx->client, window->host_surface_id);
if (host_resource && window->unpaired) {
wl_list_remove(&window->link);
wl_list_insert(&ctx->windows, &window->link);
window->unpaired = 0;
}
} else if (!window->unpaired) {
wl_list_remove(&window->link);
wl_list_insert(&ctx->unpaired_windows, &window->link);
window->unpaired = 1;
}
if (!host_resource) {
if (window->aura_surface) {
zaura_surface_destroy(window->aura_surface);
window->aura_surface = NULL;
}
if (window->xdg_toplevel) {
zxdg_toplevel_v6_destroy(window->xdg_toplevel);
window->xdg_toplevel = NULL;
}
if (window->xdg_popup) {
zxdg_popup_v6_destroy(window->xdg_popup);
window->xdg_popup = NULL;
}
if (window->xdg_surface) {
zxdg_surface_v6_destroy(window->xdg_surface);
window->xdg_surface = NULL;
}
window->realized = 0;
return;
}
host_surface = wl_resource_get_user_data(host_resource);
assert(host_surface);
assert(!host_surface->has_role);
assert(ctx->xdg_shell);
assert(ctx->xdg_shell->internal);
if (window->managed) {
if (window->transient_for != XCB_WINDOW_NONE) {
struct sl_window* sibling;
wl_list_for_each(sibling, &ctx->windows, link) {
if (sibling->id == window->transient_for) {
if (sibling->xdg_toplevel)
parent = sibling;
break;
}
}
}
}
// If we have a transient parent, but could not find it in the list of
// realized windows, then pick the window that had the last event for the
// parent. We update this again when we gain focus, so if we picked the wrong
// one it can get corrected at that point (but it's also possible the parent
// will never be realized, which is why selecting one here is important).
if (!window->managed ||
(!parent && window->transient_for != XCB_WINDOW_NONE)) {
struct sl_window* sibling;
uint32_t parent_last_event_serial = 0;
wl_list_for_each(sibling, &ctx->windows, link) {
struct wl_resource *sibling_host_resource;
struct sl_host_surface* sibling_host_surface;
if (!sibling->realized)
continue;
sibling_host_resource =
wl_client_get_object(ctx->client, sibling->host_surface_id);
if (!sibling_host_resource)
continue;
// Any parent will do but prefer last event window.
sibling_host_surface = wl_resource_get_user_data(sibling_host_resource);
if (parent_last_event_serial > sibling_host_surface->last_event_serial)
continue;
// Do not use ourselves as the parent.
if (sibling->host_surface_id == window->host_surface_id)
continue;
parent = sibling;
parent_last_event_serial = sibling_host_surface->last_event_serial;
}
}
if (!window->depth) {
xcb_get_geometry_reply_t* geometry_reply = xcb_get_geometry_reply(
ctx->connection, xcb_get_geometry(ctx->connection, window->id), NULL);
if (geometry_reply) {
window->depth = geometry_reply->depth;
free(geometry_reply);
}
}
if (!window->xdg_surface) {
window->xdg_surface = zxdg_shell_v6_get_xdg_surface(
ctx->xdg_shell->internal, host_surface->proxy);
zxdg_surface_v6_set_user_data(window->xdg_surface, window);
zxdg_surface_v6_add_listener(window->xdg_surface,
&sl_internal_xdg_surface_listener, window);
}
if (ctx->aura_shell) {
uint32_t frame_color;
if (!window->aura_surface) {
window->aura_surface = zaura_shell_get_aura_surface(
ctx->aura_shell->internal, host_surface->proxy);
}
zaura_surface_set_frame(window->aura_surface,
window->decorated
? ZAURA_SURFACE_FRAME_TYPE_NORMAL
: window->depth == 32
? ZAURA_SURFACE_FRAME_TYPE_NONE
: ZAURA_SURFACE_FRAME_TYPE_SHADOW);
frame_color = window->dark_frame ? ctx->dark_frame_color : ctx->frame_color;
zaura_surface_set_frame_colors(window->aura_surface, frame_color,
frame_color);
zaura_surface_set_startup_id(window->aura_surface, window->startup_id);
if (ctx->application_id) {
zaura_surface_set_application_id(window->aura_surface,
ctx->application_id);
} else {
// Don't set application id for X11 override redirect. This prevents
// aura shell from thinking that these are regular application windows
// that should appear in application lists.
if (!ctx->xwayland || window->managed) {
char* application_id_str;
if (window->clazz) {
application_id_str =
sl_xasprintf(WM_CLASS_APPLICATION_ID_FORMAT, window->clazz);
} else if (window->client_leader != XCB_WINDOW_NONE) {
application_id_str = sl_xasprintf(
WM_CLIENT_LEADER_APPLICATION_ID_FORMAT, window->client_leader);
} else {
application_id_str =
sl_xasprintf(XID_APPLICATION_ID_FORMAT, window->id);
}
zaura_surface_set_application_id(window->aura_surface,
application_id_str);
free(application_id_str);
}
}
}
// Always use top-level surface for X11 windows as we can't control when the
// window is closed.
if (ctx->xwayland || !parent) {
if (!window->xdg_toplevel) {
window->xdg_toplevel = zxdg_surface_v6_get_toplevel(window->xdg_surface);
zxdg_toplevel_v6_set_user_data(window->xdg_toplevel, window);
zxdg_toplevel_v6_add_listener(window->xdg_toplevel,
&sl_internal_xdg_toplevel_listener, window);
}
if (parent)
zxdg_toplevel_v6_set_parent(window->xdg_toplevel, parent->xdg_toplevel);
if (window->name)
zxdg_toplevel_v6_set_title(window->xdg_toplevel, window->name);
if (window->size_flags & P_MIN_SIZE) {
zxdg_toplevel_v6_set_min_size(window->xdg_toplevel,
window->min_width / ctx->scale,
window->min_height / ctx->scale);
}
if (window->size_flags & P_MAX_SIZE) {
zxdg_toplevel_v6_set_max_size(window->xdg_toplevel,
window->max_width / ctx->scale,
window->max_height / ctx->scale);
}
} else if (!window->xdg_popup) {
struct zxdg_positioner_v6 *positioner;
positioner = zxdg_shell_v6_create_positioner(ctx->xdg_shell->internal);
assert(positioner);
zxdg_positioner_v6_set_anchor(positioner,
ZXDG_POSITIONER_V6_ANCHOR_TOP |
ZXDG_POSITIONER_V6_ANCHOR_LEFT);
zxdg_positioner_v6_set_gravity(positioner,
ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
ZXDG_POSITIONER_V6_GRAVITY_RIGHT);
zxdg_positioner_v6_set_anchor_rect(
positioner, (window->x - parent->x) / ctx->scale,
(window->y - parent->y) / ctx->scale, 1, 1);
window->xdg_popup = zxdg_surface_v6_get_popup(
window->xdg_surface, parent->xdg_surface, positioner);
zxdg_popup_v6_set_user_data(window->xdg_popup, window);
zxdg_popup_v6_add_listener(window->xdg_popup,
&sl_internal_xdg_popup_listener, window);
zxdg_positioner_v6_destroy(positioner);
}
if ((window->size_flags & (US_POSITION | P_POSITION)) && parent &&
ctx->aura_shell) {
zaura_surface_set_parent(window->aura_surface, parent->aura_surface,
(window->x - parent->x) / ctx->scale,
(window->y - parent->y) / ctx->scale);
}
wl_surface_commit(host_surface->proxy);
if (host_surface->contents_width && host_surface->contents_height)
window->realized = 1;
}
static void sl_host_buffer_destroy(struct wl_client* client,
struct wl_resource* resource) {
wl_resource_destroy(resource);
}
static const struct wl_buffer_interface sl_buffer_implementation = {
sl_host_buffer_destroy};
static void sl_buffer_release(void* data, struct wl_buffer* buffer) {
struct sl_host_buffer* host = wl_buffer_get_user_data(buffer);
wl_buffer_send_release(host->resource);
}
static const struct wl_buffer_listener sl_buffer_listener = {sl_buffer_release};
static void sl_destroy_host_buffer(struct wl_resource* resource) {
struct sl_host_buffer* host = wl_resource_get_user_data(resource);
if (host->proxy)
wl_buffer_destroy(host->proxy);
if (host->shm_mmap) {
host->shm_mmap->buffer_resource = NULL;
sl_mmap_unref(host->shm_mmap);
}
if (host->sync_point) {
sl_sync_point_destroy(host->sync_point);
}
wl_resource_set_user_data(resource, NULL);
free(host);
}
struct sl_host_buffer* sl_create_host_buffer(struct wl_client* client,
uint32_t id,
struct wl_buffer* proxy,
int32_t width,
int32_t height) {
struct sl_host_buffer* host_buffer;
host_buffer = malloc(sizeof(*host_buffer));
assert(host_buffer);
host_buffer->width = width;
host_buffer->height = height;
host_buffer->resource =
wl_resource_create(client, &wl_buffer_interface, 1, id);
wl_resource_set_implementation(host_buffer->resource,
&sl_buffer_implementation, host_buffer,
sl_destroy_host_buffer);
host_buffer->shm_mmap = NULL;
host_buffer->shm_format = 0;
host_buffer->proxy = proxy;
if (host_buffer->proxy) {
wl_buffer_set_user_data(host_buffer->proxy, host_buffer);
wl_buffer_add_listener(host_buffer->proxy, &sl_buffer_listener,
host_buffer);
}
host_buffer->sync_point = NULL;
return host_buffer;
}
static void sl_internal_data_offer_destroy(struct sl_data_offer* host) {
wl_data_offer_destroy(host->internal);
free(host);
}
static void sl_set_selection(struct sl_context* ctx,
struct sl_data_offer* data_offer) {
if (ctx->selection_data_offer) {
sl_internal_data_offer_destroy(ctx->selection_data_offer);
ctx->selection_data_offer = NULL;
}
if (ctx->clipboard_manager) {
if (!data_offer) {
if (ctx->selection_owner == ctx->selection_window)
xcb_set_selection_owner(ctx->connection, XCB_ATOM_NONE,
ctx->atoms[ATOM_CLIPBOARD].value,
ctx->selection_timestamp);
return;
}
xcb_set_selection_owner(ctx->connection, ctx->selection_window,
ctx->atoms[ATOM_CLIPBOARD].value, XCB_CURRENT_TIME);
}
ctx->selection_data_offer = data_offer;
}
static const char* sl_utf8_mime_type = "text/plain;charset=utf-8";
static void sl_internal_data_offer_offer(void* data,
struct wl_data_offer* data_offer,
const char* type) {
struct sl_data_offer* host = data;
if (strcmp(type, sl_utf8_mime_type) == 0)
host->utf8_text = 1;
}
static void sl_internal_data_offer_source_actions(
void* data, struct wl_data_offer* data_offer, uint32_t source_actions) {}
static void sl_internal_data_offer_action(void* data,
struct wl_data_offer* data_offer,
uint32_t dnd_action) {}
static const struct wl_data_offer_listener sl_internal_data_offer_listener = {
sl_internal_data_offer_offer, sl_internal_data_offer_source_actions,
sl_internal_data_offer_action};
static void sl_internal_data_device_data_offer(
void* data,
struct wl_data_device* data_device,
struct wl_data_offer* data_offer) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_data_offer* host_data_offer;
host_data_offer = malloc(sizeof(*host_data_offer));
assert(host_data_offer);
host_data_offer->ctx = ctx;
host_data_offer->internal = data_offer;
host_data_offer->utf8_text = 0;
wl_data_offer_add_listener(host_data_offer->internal,
&sl_internal_data_offer_listener, host_data_offer);
}
static void sl_internal_data_device_enter(void* data,
struct wl_data_device* data_device,
uint32_t serial,
struct wl_surface* surface,
wl_fixed_t x,
wl_fixed_t y,
struct wl_data_offer* data_offer) {}
static void sl_internal_data_device_leave(void* data,
struct wl_data_device* data_device) {}
static void sl_internal_data_device_motion(void* data,
struct wl_data_device* data_device,
uint32_t time,
wl_fixed_t x,
wl_fixed_t y) {}
static void sl_internal_data_device_drop(void* data,
struct wl_data_device* data_device) {}
static void sl_internal_data_device_selection(
void* data,
struct wl_data_device* data_device,
struct wl_data_offer* data_offer) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_data_offer* host_data_offer =
data_offer ? wl_data_offer_get_user_data(data_offer) : NULL;
sl_set_selection(ctx, host_data_offer);
}
static const struct wl_data_device_listener sl_internal_data_device_listener = {
sl_internal_data_device_data_offer, sl_internal_data_device_enter,
sl_internal_data_device_leave, sl_internal_data_device_motion,
sl_internal_data_device_drop, sl_internal_data_device_selection};
void sl_host_seat_added(struct sl_host_seat* host) {
struct sl_context* ctx = host->seat->ctx;
if (ctx->default_seat)
return;
ctx->default_seat = host;
// Get data device for selections.
if (ctx->data_device_manager && ctx->data_device_manager->internal) {
ctx->selection_data_device = wl_data_device_manager_get_data_device(
ctx->data_device_manager->internal, host->proxy);
wl_data_device_add_listener(ctx->selection_data_device,
&sl_internal_data_device_listener, ctx);
}
}
void sl_host_seat_removed(struct sl_host_seat* host) {
if (host->seat->ctx->default_seat == host)
host->seat->ctx->default_seat = NULL;
}
struct sl_global* sl_global_create(struct sl_context* ctx,
const struct wl_interface* interface,
int version,
void* data,
wl_global_bind_func_t bind) {
struct sl_host_registry* registry;
struct sl_global* global;
assert(version > 0);
assert(version <= interface->version);
global = malloc(sizeof *global);
assert(global);
global->ctx = ctx;
global->name = ctx->next_global_id++;
global->interface = interface;
global->version = version;
global->data = data;
global->bind = bind;
wl_list_insert(ctx->globals.prev, &global->link);
wl_list_for_each(registry, &ctx->registries, link) {
wl_resource_post_event(registry->resource, WL_REGISTRY_GLOBAL, global->name,
global->interface->name, global->version);
}
return global;
}
static void sl_global_destroy(struct sl_global* global) {
struct sl_host_registry* registry;
wl_list_for_each(registry, &global->ctx->registries, link)
wl_resource_post_event(registry->resource, WL_REGISTRY_GLOBAL_REMOVE,
global->name);
wl_list_remove(&global->link);
free(global);
}
static void sl_registry_handler(void* data,
struct wl_registry* registry,
uint32_t id,
const char* interface,
uint32_t version) {
struct sl_context* ctx = (struct sl_context*)data;
if (strcmp(interface, "wl_compositor") == 0) {
struct sl_compositor* compositor = malloc(sizeof(struct sl_compositor));
assert(compositor);
compositor->ctx = ctx;
compositor->id = id;
assert(version >= 3);
compositor->version = 3;
compositor->internal = wl_registry_bind(
registry, id, &wl_compositor_interface, compositor->version);
assert(!ctx->compositor);
ctx->compositor = compositor;
compositor->host_global = sl_compositor_global_create(ctx);
} else if (strcmp(interface, "wl_subcompositor") == 0) {
struct sl_subcompositor* subcompositor =
malloc(sizeof(struct sl_subcompositor));
assert(subcompositor);
subcompositor->ctx = ctx;
subcompositor->id = id;
assert(!ctx->subcompositor);
ctx->subcompositor = subcompositor;
subcompositor->host_global = sl_subcompositor_global_create(ctx);
} else if (strcmp(interface, "wl_shm") == 0) {
struct sl_shm* shm = malloc(sizeof(struct sl_shm));
assert(shm);
shm->ctx = ctx;
shm->id = id;
shm->internal = wl_registry_bind(registry, id, &wl_shm_interface, 1);
assert(!ctx->shm);
ctx->shm = shm;
shm->host_global = sl_shm_global_create(ctx);
} else if (strcmp(interface, "wl_shell") == 0) {
struct sl_shell* shell = malloc(sizeof(struct sl_shell));
assert(shell);
shell->ctx = ctx;
shell->id = id;
assert(!ctx->shell);
ctx->shell = shell;
shell->host_global = sl_shell_global_create(ctx);
} else if (strcmp(interface, "wl_output") == 0) {
struct sl_output* output = malloc(sizeof(struct sl_output));
assert(output);
output->ctx = ctx;
output->id = id;
output->version = MIN(3, version);
output->host_global = sl_output_global_create(output);
wl_list_insert(&ctx->outputs, &output->link);
} else if (strcmp(interface, "wl_seat") == 0) {
struct sl_seat* seat = malloc(sizeof(struct sl_seat));
assert(seat);
seat->ctx = ctx;
seat->id = id;
seat->version = MIN(5, version);
seat->last_serial = 0;
seat->host_global = sl_seat_global_create(seat);
wl_list_insert(&ctx->seats, &seat->link);
} else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) {
struct sl_relative_pointer_manager* relative_pointer =
malloc(sizeof(struct sl_relative_pointer_manager));
assert(relative_pointer);
relative_pointer->ctx = ctx;
relative_pointer->id = id;
relative_pointer->internal = wl_registry_bind(
registry, id, &zwp_relative_pointer_manager_v1_interface, 1);
assert(!ctx->relative_pointer_manager);
ctx->relative_pointer_manager = relative_pointer;
relative_pointer->host_global =
sl_relative_pointer_manager_global_create(ctx);
} else if (strcmp(interface, "wl_data_device_manager") == 0) {
struct sl_data_device_manager* data_device_manager =
malloc(sizeof(struct sl_data_device_manager));
assert(data_device_manager);
data_device_manager->ctx = ctx;
data_device_manager->id = id;
data_device_manager->version = MIN(3, version);
data_device_manager->internal = NULL;
data_device_manager->host_global = NULL;
assert(!ctx->data_device_manager);
ctx->data_device_manager = data_device_manager;
if (ctx->xwayland) {
data_device_manager->internal =
wl_registry_bind(registry, id, &wl_data_device_manager_interface,
data_device_manager->version);
} else {
data_device_manager->host_global =
sl_data_device_manager_global_create(ctx);
}
} else if (strcmp(interface, "zxdg_shell_v6") == 0) {
struct sl_xdg_shell* xdg_shell = malloc(sizeof(struct sl_xdg_shell));
assert(xdg_shell);
xdg_shell->ctx = ctx;
xdg_shell->id = id;
xdg_shell->internal = NULL;
xdg_shell->host_global = NULL;
assert(!ctx->xdg_shell);
ctx->xdg_shell = xdg_shell;
if (ctx->xwayland) {
xdg_shell->internal =
wl_registry_bind(registry, id, &zxdg_shell_v6_interface, 1);
zxdg_shell_v6_add_listener(xdg_shell->internal,
&sl_internal_xdg_shell_listener, NULL);
} else {
xdg_shell->host_global = sl_xdg_shell_global_create(ctx);
}
} else if (strcmp(interface, "zaura_shell") == 0) {
if (version >= MIN_AURA_SHELL_VERSION) {
struct sl_aura_shell* aura_shell = malloc(sizeof(struct sl_aura_shell));
assert(aura_shell);
aura_shell->ctx = ctx;
aura_shell->id = id;
aura_shell->version = MIN(MIN_AURA_SHELL_VERSION, version);
aura_shell->host_gtk_shell_global = NULL;
aura_shell->internal = wl_registry_bind(
registry, id, &zaura_shell_interface, aura_shell->version);
assert(!ctx->aura_shell);
ctx->aura_shell = aura_shell;
aura_shell->host_gtk_shell_global = sl_gtk_shell_global_create(ctx);
}
} else if (strcmp(interface, "wp_viewporter") == 0) {
struct sl_viewporter* viewporter = malloc(sizeof(struct sl_viewporter));
assert(viewporter);
viewporter->ctx = ctx;
viewporter->id = id;
viewporter->host_viewporter_global = NULL;
viewporter->internal =
wl_registry_bind(registry, id, &wp_viewporter_interface, 1);
assert(!ctx->viewporter);
ctx->viewporter = viewporter;
viewporter->host_viewporter_global = sl_viewporter_global_create(ctx);
// Allow non-integer scale.
ctx->scale = MIN(MAX_SCALE, MAX(MIN_SCALE, ctx->desired_scale));
} else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) {
struct sl_linux_dmabuf* linux_dmabuf =
malloc(sizeof(struct sl_linux_dmabuf));
assert(linux_dmabuf);
linux_dmabuf->ctx = ctx;
linux_dmabuf->id = id;
linux_dmabuf->version = MIN(2, version);
linux_dmabuf->internal = wl_registry_bind(
registry, id, &zwp_linux_dmabuf_v1_interface, linux_dmabuf->version);
assert(!ctx->linux_dmabuf);
ctx->linux_dmabuf = linux_dmabuf;
linux_dmabuf->host_drm_global = sl_drm_global_create(ctx);
} else if (strcmp(interface, "zcr_keyboard_extension_v1") == 0) {
struct sl_keyboard_extension* keyboard_extension =
malloc(sizeof(struct sl_keyboard_extension));
assert(keyboard_extension);
keyboard_extension->ctx = ctx;
keyboard_extension->id = id;
keyboard_extension->internal =
wl_registry_bind(registry, id, &zcr_keyboard_extension_v1_interface, 1);
assert(!ctx->keyboard_extension);
ctx->keyboard_extension = keyboard_extension;
} else if (strcmp(interface, "zwp_text_input_manager_v1") == 0) {
struct sl_text_input_manager* text_input_manager =
malloc(sizeof(struct sl_text_input_manager));
assert(text_input_manager);
text_input_manager->ctx = ctx;
text_input_manager->id = id;
text_input_manager->internal =
wl_registry_bind(registry, id, &zwp_text_input_manager_v1_interface, 1);
text_input_manager->host_global = sl_text_input_manager_global_create(ctx);
assert(!ctx->text_input_manager);
ctx->text_input_manager = text_input_manager;
}
}
static void sl_registry_remover(void* data,
struct wl_registry* registry,
uint32_t id) {
struct sl_context* ctx = (struct sl_context*)data;
struct sl_output* output;
struct sl_seat* seat;
if (ctx->compositor && ctx->compositor->id == id) {
sl_global_destroy(ctx->compositor->host_global);
wl_compositor_destroy(ctx->compositor->internal);
free(ctx->compositor);
ctx->compositor = NULL;
return;
}
if (ctx->subcompositor && ctx->subcompositor->id == id) {
sl_global_destroy(ctx->subcompositor->host_global);
wl_shm_destroy(ctx->shm->internal);
free(ctx->subcompositor);
ctx->subcompositor = NULL;
return;
}
if (ctx->shm && ctx->shm->id == id) {
sl_global_destroy(ctx->shm->host_global);
free(ctx->shm);
ctx->shm = NULL;
return;
}
if (ctx->shell && ctx->shell->id == id) {
sl_global_destroy(ctx->shell->host_global);
free(ctx->shell);
ctx->shell = NULL;
return;
}
if (ctx->data_device_manager && ctx->data_device_manager->id == id) {
if (ctx->data_device_manager->host_global)
sl_global_destroy(ctx->data_device_manager->host_global);
if (ctx->data_device_manager->internal)
wl_data_device_manager_destroy(ctx->data_device_manager->internal);
free(ctx->data_device_manager);
ctx->data_device_manager = NULL;
return;
}
if (ctx->xdg_shell && ctx->xdg_shell->id == id) {
if (ctx->xdg_shell->host_global)
sl_global_destroy(ctx->xdg_shell->host_global);
if (ctx->xdg_shell->internal)
zxdg_shell_v6_destroy(ctx->xdg_shell->internal);
free(ctx->xdg_shell);
ctx->xdg_shell = NULL;
return;
}
if (ctx->aura_shell && ctx->aura_shell->id == id) {
if (ctx->aura_shell->host_gtk_shell_global)
sl_global_destroy(ctx->aura_shell->host_gtk_shell_global);
zaura_shell_destroy(ctx->aura_shell->internal);
free(ctx->aura_shell);
ctx->aura_shell = NULL;
return;
}
if (ctx->viewporter && ctx->viewporter->id == id) {
if (ctx->viewporter->host_viewporter_global)
sl_global_destroy(ctx->viewporter->host_viewporter_global);
wp_viewporter_destroy(ctx->viewporter->internal);
free(ctx->viewporter);
ctx->viewporter = NULL;
return;
}
if (ctx->linux_dmabuf && ctx->linux_dmabuf->id == id) {
if (ctx->linux_dmabuf->host_drm_global)
sl_global_destroy(ctx->linux_dmabuf->host_drm_global);
zwp_linux_dmabuf_v1_destroy(ctx->linux_dmabuf->internal);
free(ctx->linux_dmabuf);
ctx->linux_dmabuf = NULL;
return;
}
if (ctx->keyboard_extension && ctx->keyboard_extension->id == id) {
zcr_keyboard_extension_v1_destroy(ctx->keyboard_extension->internal);
free(ctx->keyboard_extension);
ctx->keyboard_extension = NULL;
return;
}
if (ctx->text_input_manager && ctx->text_input_manager->id == id) {
sl_global_destroy(ctx->text_input_manager->host_global);
free(ctx->text_input_manager);
ctx->text_input_manager = NULL;
return;
}
if (ctx->relative_pointer_manager &&
ctx->relative_pointer_manager->id == id) {
sl_global_destroy(ctx->relative_pointer_manager->host_global);
free(ctx->relative_pointer_manager);
ctx->relative_pointer_manager = NULL;
return;
}
wl_list_for_each(output, &ctx->outputs, link) {
if (output->id == id) {
sl_global_destroy(output->host_global);
wl_list_remove(&output->link);
free(output);
return;
}
}
wl_list_for_each(seat, &ctx->seats, link) {
if (seat->id == id) {
sl_global_destroy(seat->host_global);
wl_list_remove(&seat->link);
free(seat);
return;
}
}
// Not reached.
assert(0);
}
static const struct wl_registry_listener sl_registry_listener = {
sl_registry_handler, sl_registry_remover};
static int sl_handle_event(int fd, uint32_t mask, void* data) {
struct sl_context* ctx = (struct sl_context*)data;
int count = 0;
if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) {
wl_client_flush(ctx->client);
exit(EXIT_SUCCESS);
}
if (mask & WL_EVENT_READABLE)
count = wl_display_dispatch(ctx->display);
if (mask & WL_EVENT_WRITABLE)
wl_display_flush(ctx->display);
if (mask == 0) {
count = wl_display_dispatch_pending(ctx->display);
wl_display_flush(ctx->display);
}
return count;
}
static void sl_create_window(struct sl_context* ctx,
xcb_window_t id,
int x,
int y,
int width,
int height,
int border_width) {
struct sl_window* window = malloc(sizeof(struct sl_window));
uint32_t values[1];
assert(window);
window->ctx = ctx;
window->id = id;
window->frame_id = XCB_WINDOW_NONE;
window->host_surface_id = 0;
window->unpaired = 1;
window->x = x;
window->y = y;
window->width = width;
window->height = height;
window->border_width = border_width;
window->depth = 0;
window->managed = 0;
window->realized = 0;
window->activated = 0;
window->allow_resize = 1;
window->transient_for = XCB_WINDOW_NONE;
window->client_leader = XCB_WINDOW_NONE;
window->decorated = 0;
window->name = NULL;
window->clazz = NULL;
window->startup_id = NULL;
window->dark_frame = 0;
window->size_flags = P_POSITION;
window->min_width = 0;
window->min_height = 0;
window->max_width = 0;
window->max_height = 0;
window->xdg_surface = NULL;
window->xdg_toplevel = NULL;
window->xdg_popup = NULL;
window->aura_surface = NULL;
window->next_config.serial = 0;
window->next_config.mask = 0;
window->next_config.states_length = 0;
window->pending_config.serial = 0;
window->pending_config.mask = 0;
window->pending_config.states_length = 0;
wl_list_insert(&ctx->unpaired_windows, &window->link);
values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_FOCUS_CHANGE;
xcb_change_window_attributes(ctx->connection, window->id, XCB_CW_EVENT_MASK,
values);
}
static void sl_destroy_window(struct sl_window* window) {
if (window->frame_id != XCB_WINDOW_NONE)
xcb_destroy_window(window->ctx->connection, window->frame_id);
if (window->ctx->host_focus_window == window) {
window->ctx->host_focus_window = NULL;
window->ctx->needs_set_input_focus = 1;
}
if (window->xdg_popup)
zxdg_popup_v6_destroy(window->xdg_popup);
if (window->xdg_toplevel)
zxdg_toplevel_v6_destroy(window->xdg_toplevel);
if (window->xdg_surface)
zxdg_surface_v6_destroy(window->xdg_surface);
if (window->aura_surface)
zaura_surface_destroy(window->aura_surface);
if (window->name)
free(window->name);
if (window->clazz)
free(window->clazz);
if (window->startup_id)
free(window->startup_id);
wl_list_remove(&window->link);
free(window);
}
static int sl_is_window(struct sl_window* window, xcb_window_t id) {
if (window->id == id)
return 1;
if (window->frame_id != XCB_WINDOW_NONE) {
if (window->frame_id == id)
return 1;
}
return 0;
}
static struct sl_window* sl_lookup_window(struct sl_context* ctx,
xcb_window_t id) {
struct sl_window* window;
wl_list_for_each(window, &ctx->windows, link) {
if (sl_is_window(window, id))
return window;
}
wl_list_for_each(window, &ctx->unpaired_windows, link) {
if (sl_is_window(window, id))
return window;
}
return NULL;
}
static int sl_is_our_window(struct sl_context* ctx, xcb_window_t id) {
const xcb_setup_t* setup = xcb_get_setup(ctx->connection);
return (id & ~setup->resource_id_mask) == setup->resource_id_base;
}
static void sl_handle_create_notify(struct sl_context* ctx,
xcb_create_notify_event_t* event) {
if (sl_is_our_window(ctx, event->window))
return;
sl_create_window(ctx, event->window, event->x, event->y, event->width,
event->height, event->border_width);
}
static void sl_handle_destroy_notify(struct sl_context* ctx,
xcb_destroy_notify_event_t* event) {
struct sl_window* window;
if (sl_is_our_window(ctx, event->window))
return;
window = sl_lookup_window(ctx, event->window);
if (!window)
return;
sl_destroy_window(window);
}
static void sl_handle_reparent_notify(struct sl_context* ctx,
xcb_reparent_notify_event_t* event) {
struct sl_window* window;
if (event->parent == ctx->screen->root) {
int width = 1;
int height = 1;
int border_width = 0;
// Return early if window is already tracked. This happens when we
// reparent an unampped window back to the root window.
window = sl_lookup_window(ctx, event->window);
if (window)
return;
xcb_get_geometry_reply_t* geometry_reply = xcb_get_geometry_reply(
ctx->connection, xcb_get_geometry(ctx->connection, event->window),
NULL);
if (geometry_reply) {
width = geometry_reply->width;
height = geometry_reply->height;
border_width = geometry_reply->border_width;
free(geometry_reply);
}
sl_create_window(ctx, event->window, event->x, event->y, width, height,
border_width);
return;
}
if (sl_is_our_window(ctx, event->parent))
return;
window = sl_lookup_window(ctx, event->window);
if (!window)
return;
sl_destroy_window(window);
}
static void sl_handle_map_request(struct sl_context* ctx,
xcb_map_request_event_t* event) {
struct sl_window* window = sl_lookup_window(ctx, event->window);
struct {
int type;
xcb_atom_t atom;
} properties[] = {
{PROPERTY_WM_NAME, XCB_ATOM_WM_NAME},
{PROPERTY_WM_CLASS, XCB_ATOM_WM_CLASS},
{PROPERTY_WM_TRANSIENT_FOR, XCB_ATOM_WM_TRANSIENT_FOR},
{PROPERTY_WM_NORMAL_HINTS, XCB_ATOM_WM_NORMAL_HINTS},
{PROPERTY_WM_CLIENT_LEADER, ctx->atoms[ATOM_WM_CLIENT_LEADER].value},
{PROPERTY_MOTIF_WM_HINTS, ctx->atoms[ATOM_MOTIF_WM_HINTS].value},
{PROPERTY_NET_STARTUP_ID, ctx->atoms[ATOM_NET_STARTUP_ID].value},
{PROPERTY_GTK_THEME_VARIANT, ctx->atoms[ATOM_GTK_THEME_VARIANT].value},
};
xcb_get_geometry_cookie_t geometry_cookie;
xcb_get_property_cookie_t property_cookies[ARRAY_SIZE(properties)];
struct sl_wm_size_hints size_hints = {0};
struct sl_mwm_hints mwm_hints = {0};
uint32_t values[5];
int i;
if (!window)
return;
assert(!sl_is_our_window(ctx, event->window));
window->managed = 1;
if (window->frame_id == XCB_WINDOW_NONE)
geometry_cookie = xcb_get_geometry(ctx->connection, window->id);
for (i = 0; i < ARRAY_SIZE(properties); ++i) {
property_cookies[i] =
xcb_get_property(ctx->connection, 0, window->id, properties[i].atom,
XCB_ATOM_ANY, 0, 2048);
}
if (window->frame_id == XCB_WINDOW_NONE) {
xcb_get_geometry_reply_t* geometry_reply =
xcb_get_geometry_reply(ctx->connection, geometry_cookie, NULL);
if (geometry_reply) {
window->x = geometry_reply->x;
window->y = geometry_reply->y;
window->width = geometry_reply->width;
window->height = geometry_reply->height;
window->depth = geometry_reply->depth;
free(geometry_reply);
}
}
free(window->name);
window->name = NULL;
free(window->clazz);
window->clazz = NULL;
free(window->startup_id);
window->startup_id = NULL;
window->transient_for = XCB_WINDOW_NONE;
window->client_leader = XCB_WINDOW_NONE;
window->decorated = 1;
window->size_flags = 0;
window->dark_frame = 0;
for (i = 0; i < ARRAY_SIZE(properties); ++i) {
xcb_get_property_reply_t* reply =
xcb_get_property_reply(ctx->connection, property_cookies[i], NULL);
if (!reply)
continue;
if (reply->type == XCB_ATOM_NONE) {
free(reply);
continue;
}
switch (properties[i].type) {
case PROPERTY_WM_NAME:
window->name = strndup(xcb_get_property_value(reply),
xcb_get_property_value_length(reply));
break;
case PROPERTY_WM_CLASS: {
// WM_CLASS property contains two consecutive null-terminated strings.
// These specify the Instance and Class names. If a global app ID is
// not set then use Class name for app ID.
const char *value = xcb_get_property_value(reply);
int value_length = xcb_get_property_value_length(reply);
int instance_length = strnlen(value, value_length);
if (value_length > instance_length) {
window->clazz = strndup(value + instance_length + 1,
value_length - instance_length - 1);
}
} break;
case PROPERTY_WM_TRANSIENT_FOR:
if (xcb_get_property_value_length(reply) >= 4)
window->transient_for = *((uint32_t *)xcb_get_property_value(reply));
break;
case PROPERTY_WM_NORMAL_HINTS:
if (xcb_get_property_value_length(reply) >= sizeof(size_hints))
memcpy(&size_hints, xcb_get_property_value(reply), sizeof(size_hints));
break;
case PROPERTY_WM_CLIENT_LEADER:
if (xcb_get_property_value_length(reply) >= 4)
window->client_leader = *((uint32_t *)xcb_get_property_value(reply));
break;
case PROPERTY_MOTIF_WM_HINTS:
if (xcb_get_property_value_length(reply) >= sizeof(mwm_hints))
memcpy(&mwm_hints, xcb_get_property_value(reply), sizeof(mwm_hints));
break;
case PROPERTY_NET_STARTUP_ID:
window->startup_id = strndup(xcb_get_property_value(reply),
xcb_get_property_value_length(reply));
break;
case PROPERTY_GTK_THEME_VARIANT:
if (xcb_get_property_value_length(reply) >= 4)
window->dark_frame = !strcmp(xcb_get_property_value(reply), "dark");
break;
default:
break;
}
free(reply);
}
if (mwm_hints.flags & MWM_HINTS_DECORATIONS) {
if (mwm_hints.decorations & MWM_DECOR_ALL)
window->decorated = ~mwm_hints.decorations & MWM_DECOR_TITLE;
else
window->decorated = mwm_hints.decorations & MWM_DECOR_TITLE;
}
// Allow user/program controlled position for transients.
if (window->transient_for)
window->size_flags |= size_hints.flags & (US_POSITION | P_POSITION);
// If startup ID is not set, then try the client leader window.
if (!window->startup_id && window->client_leader) {
xcb_get_property_reply_t* reply = xcb_get_property_reply(
ctx->connection,
xcb_get_property(ctx->connection, 0, window->client_leader,
ctx->atoms[ATOM_NET_STARTUP_ID].value, XCB_ATOM_ANY, 0,
2048),
NULL);
if (reply) {
if (reply->type != XCB_ATOM_NONE) {
window->startup_id = strndup(xcb_get_property_value(reply),
xcb_get_property_value_length(reply));
}
free(reply);
}
}
window->size_flags |= size_hints.flags & (P_MIN_SIZE | P_MAX_SIZE);
if (window->size_flags & P_MIN_SIZE) {
window->min_width = size_hints.min_width;
window->min_height = size_hints.min_height;
}
if (window->size_flags & P_MAX_SIZE) {
window->max_width = size_hints.max_width;
window->max_height = size_hints.max_height;
}
window->border_width = 0;
sl_adjust_window_size_for_screen_size(window);
if (!(window->size_flags & (US_POSITION | P_POSITION)))
sl_adjust_window_position_for_screen_size(window);
values[0] = window->width;
values[1] = window->height;
values[2] = 0;
xcb_configure_window(ctx->connection, window->id,
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
XCB_CONFIG_WINDOW_BORDER_WIDTH,
values);
// This needs to match the frame extents of the X11 frame window used
// for reparenting or applications tend to be confused. The actual window
// frame size used by the host compositor can be different.
values[0] = 0;
values[1] = 0;
values[2] = 0;
values[3] = 0;
xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE, window->id,
ctx->atoms[ATOM_NET_FRAME_EXTENTS].value,
XCB_ATOM_CARDINAL, 32, 4, values);
// Remove weird gravities.
values[0] = XCB_GRAVITY_NORTH_WEST;
xcb_change_window_attributes(ctx->connection, window->id, XCB_CW_WIN_GRAVITY,
values);
if (window->frame_id == XCB_WINDOW_NONE) {
int depth = window->depth ? window->depth : ctx->screen->root_depth;
values[0] = ctx->screen->black_pixel;
values[1] = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
values[2] = ctx->colormaps[depth];
window->frame_id = xcb_generate_id(ctx->connection);
xcb_create_window(
ctx->connection, depth, window->frame_id, ctx->screen->root, window->x,
window->y, window->width, window->height, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, ctx->visual_ids[depth],
XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP, values);
values[0] = XCB_STACK_MODE_BELOW;
xcb_configure_window(ctx->connection, window->frame_id,
XCB_CONFIG_WINDOW_STACK_MODE, values);
xcb_reparent_window(ctx->connection, window->id, window->frame_id, 0, 0);
} else {
values[0] = window->x;
values[1] = window->y;
values[2] = window->width;
values[3] = window->height;
values[4] = XCB_STACK_MODE_BELOW;
xcb_configure_window(
ctx->connection, window->frame_id,
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_STACK_MODE,
values);
}
sl_window_set_wm_state(window, WM_STATE_NORMAL);
sl_send_configure_notify(window);
xcb_map_window(ctx->connection, window->id);
xcb_map_window(ctx->connection, window->frame_id);
}
static void sl_handle_map_notify(struct sl_context* ctx,
xcb_map_notify_event_t* event) {}
static void sl_handle_unmap_notify(struct sl_context* ctx,
xcb_unmap_notify_event_t* event) {
struct sl_window* window;
if (sl_is_our_window(ctx, event->window))
return;
if (event->response_type & SEND_EVENT_MASK)
return;
window = sl_lookup_window(ctx, event->window);
if (!window)
return;
if (ctx->host_focus_window == window) {
ctx->host_focus_window = NULL;
ctx->needs_set_input_focus = 1;
}
if (window->host_surface_id) {
window->host_surface_id = 0;
sl_window_update(window);
}
sl_window_set_wm_state(window, WM_STATE_WITHDRAWN);
// Reparent window and destroy frame if it exists.
if (window->frame_id != XCB_WINDOW_NONE) {
xcb_reparent_window(ctx->connection, window->id, ctx->screen->root,
window->x, window->y);
xcb_destroy_window(ctx->connection, window->frame_id);
window->frame_id = XCB_WINDOW_NONE;
}
// Reset properties to unmanaged state in case the window transitions to
// an override-redirect window.
window->managed = 0;
window->decorated = 0;
window->size_flags = P_POSITION;
}
static void sl_handle_configure_request(struct sl_context* ctx,
xcb_configure_request_event_t* event) {
struct sl_window* window = sl_lookup_window(ctx, event->window);
int width = window->width;
int height = window->height;
uint32_t values[7];
assert(!sl_is_our_window(ctx, event->window));
if (!window->managed) {
int i = 0;
if (event->value_mask & XCB_CONFIG_WINDOW_X)
values[i++] = event->x;
if (event->value_mask & XCB_CONFIG_WINDOW_Y)
values[i++] = event->y;
if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH)
values[i++] = event->width;
if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)
values[i++] = event->height;
if (event->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH)
values[i++] = event->border_width;
if (event->value_mask & XCB_CONFIG_WINDOW_SIBLING)
values[i++] = event->sibling;
if (event->value_mask & XCB_CONFIG_WINDOW_STACK_MODE)
values[i++] = event->stack_mode;
xcb_configure_window(ctx->connection, window->id, event->value_mask,
values);
return;
}
// Ack configure events as satisfying request removes the guarantee
// that matching contents will arrive.
if (window->xdg_toplevel) {
if (window->pending_config.serial) {
zxdg_surface_v6_ack_configure(window->xdg_surface,
window->pending_config.serial);
window->pending_config.serial = 0;
window->pending_config.mask = 0;
window->pending_config.states_length = 0;
}
if (window->next_config.serial) {
zxdg_surface_v6_ack_configure(window->xdg_surface,
window->next_config.serial);
window->next_config.serial = 0;
window->next_config.mask = 0;
window->next_config.states_length = 0;
}
}
if (event->value_mask & XCB_CONFIG_WINDOW_X)
window->x = event->x;
if (event->value_mask & XCB_CONFIG_WINDOW_Y)
window->y = event->y;
if (window->allow_resize) {
if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH)
window->width = event->width;
if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)
window->height = event->height;
}
sl_adjust_window_size_for_screen_size(window);
if (window->size_flags & (US_POSITION | P_POSITION))
sl_window_update(window);
else
sl_adjust_window_position_for_screen_size(window);
values[0] = window->x;
values[1] = window->y;
values[2] = window->width;
values[3] = window->height;
values[4] = 0;
xcb_configure_window(ctx->connection, window->frame_id,
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
values);
// We need to send a synthetic configure notify if:
// - Not changing the size, location, border width.
// - Moving the window without resizing it or changing its border width.
if (width != window->width || height != window->height ||
window->border_width) {
xcb_configure_window(ctx->connection, window->id,
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
XCB_CONFIG_WINDOW_BORDER_WIDTH,
&values[2]);
window->border_width = 0;
} else {
sl_send_configure_notify(window);
}
}
static void sl_handle_configure_notify(struct sl_context* ctx,
xcb_configure_notify_event_t* event) {
struct sl_window* window;
if (sl_is_our_window(ctx, event->window))
return;
if (event->window == ctx->screen->root) {
xcb_get_geometry_reply_t* geometry_reply = xcb_get_geometry_reply(
ctx->connection, xcb_get_geometry(ctx->connection, event->window),
NULL);
int width = ctx->screen->width_in_pixels;
int height = ctx->screen->height_in_pixels;
if (geometry_reply) {
width = geometry_reply->width;
height = geometry_reply->height;
free(geometry_reply);
}
if (width == ctx->screen->width_in_pixels ||
height == ctx->screen->height_in_pixels) {
return;
}
ctx->screen->width_in_pixels = width;
ctx->screen->height_in_pixels = height;
// Re-center managed windows.
wl_list_for_each(window, &ctx->windows, link) {
int x, y;
if (window->size_flags & (US_POSITION | P_POSITION))
continue;
x = window->x;
y = window->y;
sl_adjust_window_position_for_screen_size(window);
if (window->x != x || window->y != y) {
uint32_t values[2];
values[0] = window->x;
values[1] = window->y;
xcb_configure_window(ctx->connection, window->frame_id,
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values);
sl_send_configure_notify(window);
}
}
return;
}
window = sl_lookup_window(ctx, event->window);
if (!window)
return;
if (window->managed)
return;
window->width = event->width;
window->height = event->height;
window->border_width = event->border_width;
if (event->x != window->x || event->y != window->y) {
window->x = event->x;
window->y = event->y;
sl_window_update(window);
}
}
static uint32_t sl_resize_edge(int net_wm_moveresize_size) {
switch (net_wm_moveresize_size) {
case NET_WM_MOVERESIZE_SIZE_TOPLEFT:
return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT;
case NET_WM_MOVERESIZE_SIZE_TOP:
return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP;
case NET_WM_MOVERESIZE_SIZE_TOPRIGHT:
return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_RIGHT;
case NET_WM_MOVERESIZE_SIZE_RIGHT:
return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_RIGHT;
case NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT:
return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_RIGHT;
case NET_WM_MOVERESIZE_SIZE_BOTTOM:
return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM;
case NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT:
return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_LEFT;
case NET_WM_MOVERESIZE_SIZE_LEFT:
return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_LEFT;
default:
return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_NONE;
}
}
static void sl_handle_client_message(struct sl_context* ctx,
xcb_client_message_event_t* event) {
if (event->type == ctx->atoms[ATOM_WL_SURFACE_ID].value) {
struct sl_window *window, *unpaired_window = NULL;
wl_list_for_each(window, &ctx->unpaired_windows, link) {
if (sl_is_window(window, event->window)) {
unpaired_window = window;
break;
}
}
if (unpaired_window) {
unpaired_window->host_surface_id = event->data.data32[0];
sl_window_update(unpaired_window);
}
} else if (event->type == ctx->atoms[ATOM_NET_WM_MOVERESIZE].value) {
struct sl_window* window = sl_lookup_window(ctx, event->window);
if (window && window->xdg_toplevel) {
struct sl_host_seat* seat = window->ctx->default_seat;
if (!seat)
return;
if (event->data.data32[2] == NET_WM_MOVERESIZE_MOVE) {
zxdg_toplevel_v6_move(window->xdg_toplevel, seat->proxy,
seat->seat->last_serial);
} else {
uint32_t edge = sl_resize_edge(event->data.data32[2]);
if (edge == ZXDG_TOPLEVEL_V6_RESIZE_EDGE_NONE)
return;
zxdg_toplevel_v6_resize(window->xdg_toplevel, seat->proxy,
seat->seat->last_serial, edge);
}
}
} else if (event->type == ctx->atoms[ATOM_NET_WM_STATE].value) {
struct sl_window* window = sl_lookup_window(ctx, event->window);
if (window && window->xdg_toplevel) {
int changed[ATOM_LAST + 1];
uint32_t action = event->data.data32[0];
int i;
for (i = 0; i < ARRAY_SIZE(ctx->atoms); ++i) {
changed[i] = event->data.data32[1] == ctx->atoms[i].value ||
event->data.data32[2] == ctx->atoms[i].value;
}
if (changed[ATOM_NET_WM_STATE_FULLSCREEN]) {
if (action == NET_WM_STATE_ADD)
zxdg_toplevel_v6_set_fullscreen(window->xdg_toplevel, NULL);
else if (action == NET_WM_STATE_REMOVE)
zxdg_toplevel_v6_unset_fullscreen(window->xdg_toplevel);
}
if (changed[ATOM_NET_WM_STATE_MAXIMIZED_VERT] &&
changed[ATOM_NET_WM_STATE_MAXIMIZED_HORZ]) {
if (action == NET_WM_STATE_ADD)
zxdg_toplevel_v6_set_maximized(window->xdg_toplevel);
else if (action == NET_WM_STATE_REMOVE)
zxdg_toplevel_v6_unset_maximized(window->xdg_toplevel);
}
}
} else if (event->type == ctx->atoms[ATOM_WM_CHANGE_STATE].value &&
event->data.data32[0] == WM_STATE_ICONIC) {
struct sl_window* window = sl_lookup_window(ctx, event->window);
if (window && window->xdg_toplevel) {
zxdg_toplevel_v6_set_minimized(window->xdg_toplevel);
}
}
}
static void sl_handle_focus_in(struct sl_context* ctx,
xcb_focus_in_event_t* event) {
struct sl_window* window = sl_lookup_window(ctx, event->event);
if (window && window->transient_for != XCB_WINDOW_NONE) {
// Set our parent now as it might not have been set properly when the
// window was realized.
struct sl_window* parent = sl_lookup_window(ctx, window->transient_for);
if (parent && parent->xdg_toplevel && window->xdg_toplevel)
zxdg_toplevel_v6_set_parent(window->xdg_toplevel, parent->xdg_toplevel);
}
}
static void sl_handle_focus_out(struct sl_context* ctx,
xcb_focus_out_event_t* event) {}
static int sl_handle_selection_fd_writable(int fd, uint32_t mask, void* data) {
struct sl_context* ctx = data;
uint8_t *value;
int bytes, bytes_left;
value = xcb_get_property_value(ctx->selection_property_reply);
bytes_left = xcb_get_property_value_length(ctx->selection_property_reply) -
ctx->selection_property_offset;
bytes = write(fd, value + ctx->selection_property_offset, bytes_left);
if (bytes == -1) {
fprintf(stderr, "write error to target fd: %m\n");
close(fd);
} else if (bytes == bytes_left) {
if (ctx->selection_incremental_transfer) {
xcb_delete_property(ctx->connection, ctx->selection_window,
ctx->atoms[ATOM_WL_SELECTION].value);
} else {
close(fd);
}
} else {
ctx->selection_property_offset += bytes;
return 1;
}
free(ctx->selection_property_reply);
ctx->selection_property_reply = NULL;
if (ctx->selection_send_event_source) {
wl_event_source_remove(ctx->selection_send_event_source);
ctx->selection_send_event_source = NULL;
}
return 1;
}
static void sl_write_selection_property(struct sl_context* ctx,
xcb_get_property_reply_t* reply) {
ctx->selection_property_offset = 0;
ctx->selection_property_reply = reply;
sl_handle_selection_fd_writable(ctx->selection_data_source_send_fd,
WL_EVENT_WRITABLE, ctx);
if (!ctx->selection_property_reply)
return;
assert(!ctx->selection_send_event_source);
ctx->selection_send_event_source = wl_event_loop_add_fd(
wl_display_get_event_loop(ctx->host_display),
ctx->selection_data_source_send_fd, WL_EVENT_WRITABLE,
sl_handle_selection_fd_writable, ctx);
}
static void sl_send_selection_notify(struct sl_context* ctx,
xcb_atom_t property) {
xcb_selection_notify_event_t event = {
.response_type = XCB_SELECTION_NOTIFY,
.sequence = 0,
.time = ctx->selection_request.time,
.requestor = ctx->selection_request.requestor,
.selection = ctx->selection_request.selection,
.target = ctx->selection_request.target,
.property = property,
.pad0 = 0};
xcb_send_event(ctx->connection, 0, ctx->selection_request.requestor,
XCB_EVENT_MASK_NO_EVENT, (char*)&event);
}
static void sl_send_selection_data(struct sl_context* ctx) {
assert(!ctx->selection_data_ack_pending);
xcb_change_property(
ctx->connection, XCB_PROP_MODE_REPLACE, ctx->selection_request.requestor,
ctx->selection_request.property, ctx->atoms[ATOM_UTF8_STRING].value, 8,
ctx->selection_data.size, ctx->selection_data.data);
ctx->selection_data_ack_pending = 1;
ctx->selection_data.size = 0;
}
static const uint32_t sl_incr_chunk_size = 64 * 1024;
static int sl_handle_selection_fd_readable(int fd, uint32_t mask, void* data) {
struct sl_context* ctx = data;
int bytes, offset, bytes_left;
void *p;
offset = ctx->selection_data.size;
if (ctx->selection_data.size < sl_incr_chunk_size)
p = wl_array_add(&ctx->selection_data, sl_incr_chunk_size);
else
p = (char*)ctx->selection_data.data + ctx->selection_data.size;
bytes_left = ctx->selection_data.alloc - offset;
bytes = read(fd, p, bytes_left);
if (bytes == -1) {
fprintf(stderr, "read error from data source: %m\n");
sl_send_selection_notify(ctx, XCB_ATOM_NONE);
ctx->selection_data_offer_receive_fd = -1;
close(fd);
} else {
ctx->selection_data.size = offset + bytes;
if (ctx->selection_data.size >= sl_incr_chunk_size) {
if (!ctx->selection_incremental_transfer) {
ctx->selection_incremental_transfer = 1;
xcb_change_property(
ctx->connection, XCB_PROP_MODE_REPLACE,
ctx->selection_request.requestor, ctx->selection_request.property,
ctx->atoms[ATOM_INCR].value, 32, 1, &sl_incr_chunk_size);
ctx->selection_data_ack_pending = 1;
sl_send_selection_notify(ctx, ctx->selection_request.property);
} else if (!ctx->selection_data_ack_pending) {
sl_send_selection_data(ctx);
}
} else if (bytes == 0) {
if (!ctx->selection_data_ack_pending)
sl_send_selection_data(ctx);
if (!ctx->selection_incremental_transfer) {
sl_send_selection_notify(ctx, ctx->selection_request.property);
ctx->selection_request.requestor = XCB_NONE;
wl_array_release(&ctx->selection_data);
}
xcb_flush(ctx->connection);
ctx->selection_data_offer_receive_fd = -1;
close(fd);
} else {
ctx->selection_data.size = offset + bytes;
return 1;
}
}
wl_event_source_remove(ctx->selection_event_source);
ctx->selection_event_source = NULL;
return 1;
}
static void sl_handle_property_notify(struct sl_context* ctx,
xcb_property_notify_event_t* event) {
if (event->atom == XCB_ATOM_WM_NAME) {
struct sl_window* window = sl_lookup_window(ctx, event->window);
if (!window)
return;
if (window->name) {
free(window->name);
window->name = NULL;
}
if (event->state != XCB_PROPERTY_DELETE) {
xcb_get_property_reply_t* reply = xcb_get_property_reply(
ctx->connection,
xcb_get_property(ctx->connection, 0, window->id, XCB_ATOM_WM_NAME,
XCB_ATOM_ANY, 0, 2048),
NULL);
if (reply) {
window->name = strndup(xcb_get_property_value(reply),
xcb_get_property_value_length(reply));
free(reply);
}
}
if (!window->xdg_toplevel)
return;
if (window->name) {
zxdg_toplevel_v6_set_title(window->xdg_toplevel, window->name);
} else {
zxdg_toplevel_v6_set_title(window->xdg_toplevel, "");
}
} else if (event->atom == XCB_ATOM_WM_NORMAL_HINTS) {
struct sl_window* window = sl_lookup_window(ctx, event->window);
if (!window)
return;
window->size_flags &= ~(P_MIN_SIZE | P_MAX_SIZE);
if (event->state != XCB_PROPERTY_DELETE) {
struct sl_wm_size_hints size_hints = {0};
xcb_get_property_reply_t* reply = xcb_get_property_reply(
ctx->connection,
xcb_get_property(ctx->connection, 0, window->id,
XCB_ATOM_WM_NORMAL_HINTS, XCB_ATOM_ANY, 0,
sizeof(size_hints)),
NULL);
if (reply) {
memcpy(&size_hints, xcb_get_property_value(reply), sizeof(size_hints));
free(reply);
}
window->size_flags |= size_hints.flags & (P_MIN_SIZE | P_MAX_SIZE);
if (window->size_flags & P_MIN_SIZE) {
window->min_width = size_hints.min_width;
window->min_height = size_hints.min_height;
}
if (window->size_flags & P_MAX_SIZE) {
window->max_width = size_hints.max_width;
window->max_height = size_hints.max_height;
}
}
if (!window->xdg_toplevel)
return;
if (window->size_flags & P_MIN_SIZE) {
zxdg_toplevel_v6_set_min_size(window->xdg_toplevel,
window->min_width / ctx->scale,
window->min_height / ctx->scale);
} else {
zxdg_toplevel_v6_set_min_size(window->xdg_toplevel, 0, 0);
}
if (window->size_flags & P_MAX_SIZE) {
zxdg_toplevel_v6_set_max_size(window->xdg_toplevel,
window->max_width / ctx->scale,
window->max_height / ctx->scale);
} else {
zxdg_toplevel_v6_set_max_size(window->xdg_toplevel, 0, 0);
}
} else if (event->atom == ctx->atoms[ATOM_MOTIF_WM_HINTS].value) {
struct sl_window* window = sl_lookup_window(ctx, event->window);
if (!window)
return;
// Managed windows are decorated by default.
window->decorated = window->managed;
if (event->state != XCB_PROPERTY_DELETE) {
struct sl_mwm_hints mwm_hints = {0};
xcb_get_property_reply_t* reply = xcb_get_property_reply(
ctx->connection,
xcb_get_property(ctx->connection, 0, window->id,
ctx->atoms[ATOM_MOTIF_WM_HINTS].value, XCB_ATOM_ANY,
0, sizeof(mwm_hints)),
NULL);
if (reply) {
if (mwm_hints.flags & MWM_HINTS_DECORATIONS) {
if (mwm_hints.decorations & MWM_DECOR_ALL)
window->decorated = ~mwm_hints.decorations & MWM_DECOR_TITLE;
else
window->decorated = mwm_hints.decorations & MWM_DECOR_TITLE;
}
}
}
if (!window->aura_surface)
return;
zaura_surface_set_frame(window->aura_surface,
window->decorated
? ZAURA_SURFACE_FRAME_TYPE_NORMAL
: window->depth == 32
? ZAURA_SURFACE_FRAME_TYPE_NONE
: ZAURA_SURFACE_FRAME_TYPE_SHADOW);
} else if (event->atom == ctx->atoms[ATOM_GTK_THEME_VARIANT].value) {
struct sl_window* window;
uint32_t frame_color;
window = sl_lookup_window(ctx, event->window);
if (!window)
return;
window->dark_frame = 0;
if (event->state != XCB_PROPERTY_DELETE) {
xcb_get_property_reply_t* reply = xcb_get_property_reply(
ctx->connection,
xcb_get_property(ctx->connection, 0, window->id,
ctx->atoms[ATOM_GTK_THEME_VARIANT].value,
XCB_ATOM_ANY, 0, 2048),
NULL);
if (reply) {
if (xcb_get_property_value_length(reply) >= 4)
window->dark_frame = !strcmp(xcb_get_property_value(reply), "dark");
free(reply);
}
}
if (!window->aura_surface)
return;
frame_color = window->dark_frame ? ctx->dark_frame_color : ctx->frame_color;
zaura_surface_set_frame_colors(window->aura_surface, frame_color,
frame_color);
} else if (event->atom == ctx->atoms[ATOM_WL_SELECTION].value) {
if (event->window == ctx->selection_window &&
event->state == XCB_PROPERTY_NEW_VALUE &&
ctx->selection_incremental_transfer) {
xcb_get_property_reply_t* reply = xcb_get_property_reply(
ctx->connection,
xcb_get_property(ctx->connection, 0, ctx->selection_window,
ctx->atoms[ATOM_WL_SELECTION].value,
XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff),
NULL);
if (!reply)
return;
if (xcb_get_property_value_length(reply) > 0) {
sl_write_selection_property(ctx, reply);
} else {
assert(!ctx->selection_send_event_source);
close(ctx->selection_data_source_send_fd);
free(reply);
}
}
} else if (event->atom == ctx->selection_request.property) {
if (event->window == ctx->selection_request.requestor &&
event->state == XCB_PROPERTY_DELETE &&
ctx->selection_incremental_transfer) {
int data_size = ctx->selection_data.size;
ctx->selection_data_ack_pending = 0;
// Handle the case when there's more data to be received.
if (ctx->selection_data_offer_receive_fd >= 0) {
// Avoid sending empty data until transfer is complete.
if (data_size)
sl_send_selection_data(ctx);
if (!ctx->selection_event_source) {
ctx->selection_event_source = wl_event_loop_add_fd(
wl_display_get_event_loop(ctx->host_display),
ctx->selection_data_offer_receive_fd, WL_EVENT_READABLE,
sl_handle_selection_fd_readable, ctx);
}
return;
}
sl_send_selection_data(ctx);
// Release data if transfer is complete.
if (!data_size) {
ctx->selection_request.requestor = XCB_NONE;
wl_array_release(&ctx->selection_data);
}
}
}
}
static void sl_internal_data_source_target(void* data,
struct wl_data_source* data_source,
const char* mime_type) {}
static void sl_internal_data_source_send(void* data,
struct wl_data_source* data_source,
const char* mime_type,
int32_t fd) {
struct sl_data_source* host = data;
struct sl_context* ctx = host->ctx;
if (strcmp(mime_type, sl_utf8_mime_type) == 0) {
int flags;
int rv;
xcb_convert_selection(
ctx->connection, ctx->selection_window,
ctx->atoms[ATOM_CLIPBOARD].value, ctx->atoms[ATOM_UTF8_STRING].value,
ctx->atoms[ATOM_WL_SELECTION].value, XCB_CURRENT_TIME);
flags = fcntl(fd, F_GETFL, 0);
rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
assert(!rv);
UNUSED(rv);
ctx->selection_data_source_send_fd = fd;
} else {
close(fd);
}
}
static void sl_internal_data_source_cancelled(
void* data, struct wl_data_source* data_source) {
struct sl_data_source* host = data;
if (host->ctx->selection_data_source == host)
host->ctx->selection_data_source = NULL;
wl_data_source_destroy(data_source);
}
static const struct wl_data_source_listener sl_internal_data_source_listener = {
sl_internal_data_source_target, sl_internal_data_source_send,
sl_internal_data_source_cancelled};
static void sl_get_selection_targets(struct sl_context* ctx) {
struct sl_data_source* data_source = NULL;
xcb_get_property_reply_t *reply;
xcb_atom_t *value;
uint32_t i;
reply = xcb_get_property_reply(
ctx->connection,
xcb_get_property(ctx->connection, 1, ctx->selection_window,
ctx->atoms[ATOM_WL_SELECTION].value,
XCB_GET_PROPERTY_TYPE_ANY, 0, 4096),
NULL);
if (!reply)
return;
if (reply->type != XCB_ATOM_ATOM) {
free(reply);
return;
}
if (ctx->data_device_manager) {
data_source = malloc(sizeof(*data_source));
assert(data_source);
data_source->ctx = ctx;
data_source->internal = wl_data_device_manager_create_data_source(
ctx->data_device_manager->internal);
wl_data_source_add_listener(data_source->internal,
&sl_internal_data_source_listener, data_source);
value = xcb_get_property_value(reply);
for (i = 0; i < reply->value_len; i++) {
if (value[i] == ctx->atoms[ATOM_UTF8_STRING].value)
wl_data_source_offer(data_source->internal, sl_utf8_mime_type);
}
if (ctx->selection_data_device && ctx->default_seat) {
wl_data_device_set_selection(ctx->selection_data_device,
data_source->internal,
ctx->default_seat->seat->last_serial);
}
if (ctx->selection_data_source) {
wl_data_source_destroy(ctx->selection_data_source->internal);
free(ctx->selection_data_source);
}
ctx->selection_data_source = data_source;
}
free(reply);
}
static void sl_get_selection_data(struct sl_context* ctx) {
xcb_get_property_reply_t* reply = xcb_get_property_reply(
ctx->connection,
xcb_get_property(ctx->connection, 1, ctx->selection_window,
ctx->atoms[ATOM_WL_SELECTION].value,
XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff),
NULL);
if (!reply)
return;
if (reply->type == ctx->atoms[ATOM_INCR].value) {
ctx->selection_incremental_transfer = 1;
free(reply);
} else {
ctx->selection_incremental_transfer = 0;
sl_write_selection_property(ctx, reply);
}
}
static void sl_handle_selection_notify(struct sl_context* ctx,
xcb_selection_notify_event_t* event) {
if (event->property == XCB_ATOM_NONE)
return;
if (event->target == ctx->atoms[ATOM_TARGETS].value)
sl_get_selection_targets(ctx);
else
sl_get_selection_data(ctx);
}
static void sl_send_targets(struct sl_context* ctx) {
xcb_atom_t targets[] = {
ctx->atoms[ATOM_TIMESTAMP].value, ctx->atoms[ATOM_TARGETS].value,
ctx->atoms[ATOM_UTF8_STRING].value, ctx->atoms[ATOM_TEXT].value,
};
xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE,
ctx->selection_request.requestor,
ctx->selection_request.property, XCB_ATOM_ATOM, 32,
ARRAY_SIZE(targets), targets);
sl_send_selection_notify(ctx, ctx->selection_request.property);
}
static void sl_send_timestamp(struct sl_context* ctx) {
xcb_change_property(ctx->connection, XCB_PROP_MODE_REPLACE,
ctx->selection_request.requestor,
ctx->selection_request.property, XCB_ATOM_INTEGER, 32, 1,
&ctx->selection_timestamp);