2018-04-29 15:46:29 +02:00
|
|
|
#define _POSIX_C_SOURCE 200809L
|
2018-11-10 23:58:14 +01:00
|
|
|
|
2018-03-31 04:09:06 +02:00
|
|
|
#include <assert.h>
|
2018-03-31 03:40:43 +02:00
|
|
|
#include <stdlib.h>
|
2018-04-29 15:46:29 +02:00
|
|
|
#include <string.h>
|
2021-04-19 20:52:31 +02:00
|
|
|
#include <fcntl.h>
|
2018-11-10 23:58:14 +01:00
|
|
|
|
2020-11-18 19:21:03 +01:00
|
|
|
#include <drm_fourcc.h>
|
|
|
|
#include <xcb/dri3.h>
|
|
|
|
#include <xcb/present.h>
|
2021-01-21 02:06:28 +01:00
|
|
|
#include <xcb/render.h>
|
2021-04-19 20:52:31 +02:00
|
|
|
#include <xcb/shm.h>
|
2018-11-11 02:21:49 +01:00
|
|
|
#include <xcb/xcb.h>
|
|
|
|
#include <xcb/xinput.h>
|
|
|
|
|
2018-03-31 03:40:43 +02:00
|
|
|
#include <wlr/interfaces/wlr_output.h>
|
2018-04-29 15:46:29 +02:00
|
|
|
#include <wlr/interfaces/wlr_pointer.h>
|
2019-07-15 23:36:21 +02:00
|
|
|
#include <wlr/interfaces/wlr_touch.h>
|
2021-06-08 09:15:55 +02:00
|
|
|
#include <wlr/render/wlr_renderer.h>
|
2021-01-21 02:06:28 +01:00
|
|
|
#include <wlr/types/wlr_matrix.h>
|
2018-03-31 03:40:43 +02:00
|
|
|
#include <wlr/util/log.h>
|
2018-11-10 23:58:14 +01:00
|
|
|
|
2018-03-31 03:40:43 +02:00
|
|
|
#include "backend/x11.h"
|
2020-12-13 12:54:05 +01:00
|
|
|
#include "util/time.h"
|
2018-03-31 03:40:43 +02:00
|
|
|
|
2021-06-18 12:15:08 +02:00
|
|
|
static const uint32_t SUPPORTED_OUTPUT_STATE =
|
|
|
|
WLR_OUTPUT_STATE_BACKEND_OPTIONAL |
|
|
|
|
WLR_OUTPUT_STATE_BUFFER |
|
|
|
|
WLR_OUTPUT_STATE_MODE;
|
|
|
|
|
2018-09-17 22:16:07 +02:00
|
|
|
static void parse_xcb_setup(struct wlr_output *output,
|
2018-11-10 23:48:45 +01:00
|
|
|
xcb_connection_t *xcb) {
|
|
|
|
const xcb_setup_t *xcb_setup = xcb_get_setup(xcb);
|
2018-03-31 03:40:43 +02:00
|
|
|
|
2022-05-23 10:32:26 +02:00
|
|
|
output->make = calloc(1, xcb_setup_vendor_length(xcb_setup) + 1);
|
|
|
|
if (output->make == NULL) {
|
|
|
|
wlr_log_errno(WLR_ERROR, "Allocation failed");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
memcpy(output->make, xcb_setup_vendor(xcb_setup),
|
|
|
|
xcb_setup_vendor_length(xcb_setup));
|
|
|
|
|
|
|
|
char model[64];
|
|
|
|
snprintf(model, sizeof(model), "%"PRIu16".%"PRIu16,
|
|
|
|
xcb_setup->protocol_major_version,
|
|
|
|
xcb_setup->protocol_minor_version);
|
2022-05-26 17:48:37 +02:00
|
|
|
output->model = strdup(model);
|
2018-03-31 03:40:43 +02:00
|
|
|
}
|
|
|
|
|
2018-09-17 22:16:07 +02:00
|
|
|
static struct wlr_x11_output *get_x11_output_from_output(
|
|
|
|
struct wlr_output *wlr_output) {
|
|
|
|
assert(wlr_output_is_x11(wlr_output));
|
|
|
|
return (struct wlr_x11_output *)wlr_output;
|
|
|
|
}
|
|
|
|
|
2018-04-04 04:54:01 +02:00
|
|
|
static bool output_set_custom_mode(struct wlr_output *wlr_output,
|
|
|
|
int32_t width, int32_t height, int32_t refresh) {
|
2018-09-17 22:16:07 +02:00
|
|
|
struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);
|
2018-04-04 04:54:01 +02:00
|
|
|
struct wlr_x11_backend *x11 = output->x11;
|
|
|
|
|
2018-03-31 03:40:43 +02:00
|
|
|
const uint32_t values[] = { width, height };
|
2018-09-17 22:16:07 +02:00
|
|
|
xcb_void_cookie_t cookie = xcb_configure_window_checked(
|
2018-11-10 23:48:45 +01:00
|
|
|
x11->xcb, output->win,
|
2018-03-31 03:40:43 +02:00
|
|
|
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
|
2018-07-11 17:29:41 +02:00
|
|
|
|
|
|
|
xcb_generic_error_t *error;
|
2018-11-10 23:48:45 +01:00
|
|
|
if ((error = xcb_request_check(x11->xcb, cookie))) {
|
2018-09-17 22:16:07 +02:00
|
|
|
wlr_log(WLR_ERROR, "Could not set window size to %dx%d\n",
|
|
|
|
width, height);
|
2018-07-11 17:29:41 +02:00
|
|
|
free(error);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-03-31 03:40:43 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-12-12 13:15:07 +01:00
|
|
|
static void destroy_x11_buffer(struct wlr_x11_buffer *buffer);
|
|
|
|
|
2018-03-31 03:40:43 +02:00
|
|
|
static void output_destroy(struct wlr_output *wlr_output) {
|
2018-09-17 22:16:07 +02:00
|
|
|
struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);
|
2018-03-31 03:40:43 +02:00
|
|
|
struct wlr_x11_backend *x11 = output->x11;
|
|
|
|
|
2021-01-21 12:24:52 +01:00
|
|
|
pixman_region32_fini(&output->exposed);
|
|
|
|
|
2022-03-02 19:57:28 +01:00
|
|
|
wlr_pointer_finish(&output->pointer);
|
2022-03-02 22:07:40 +01:00
|
|
|
wlr_touch_finish(&output->touch);
|
2018-04-29 15:46:29 +02:00
|
|
|
|
2020-12-12 13:15:07 +01:00
|
|
|
struct wlr_x11_buffer *buffer, *buffer_tmp;
|
|
|
|
wl_list_for_each_safe(buffer, buffer_tmp, &output->buffers, link) {
|
|
|
|
destroy_x11_buffer(buffer);
|
|
|
|
}
|
|
|
|
|
2018-03-31 03:40:43 +02:00
|
|
|
wl_list_remove(&output->link);
|
2020-12-19 15:54:53 +01:00
|
|
|
|
2021-01-21 02:06:28 +01:00
|
|
|
if (output->cursor.pic != XCB_NONE) {
|
|
|
|
xcb_render_free_picture(x11->xcb, output->cursor.pic);
|
|
|
|
}
|
|
|
|
|
2020-12-28 12:27:27 +01:00
|
|
|
// A zero event mask deletes the event context
|
|
|
|
xcb_present_select_input(x11->xcb, output->present_event_id, output->win, 0);
|
2018-11-10 23:48:45 +01:00
|
|
|
xcb_destroy_window(x11->xcb, output->win);
|
|
|
|
xcb_flush(x11->xcb);
|
2018-03-31 03:40:43 +02:00
|
|
|
free(output);
|
|
|
|
}
|
|
|
|
|
2022-05-24 19:16:50 +02:00
|
|
|
static bool output_test(struct wlr_output *wlr_output,
|
|
|
|
const struct wlr_output_state *state) {
|
|
|
|
uint32_t unsupported = state->committed & ~SUPPORTED_OUTPUT_STATE;
|
2021-06-18 12:15:08 +02:00
|
|
|
if (unsupported != 0) {
|
|
|
|
wlr_log(WLR_DEBUG, "Unsupported output state fields: 0x%"PRIx32,
|
|
|
|
unsupported);
|
2020-04-02 12:41:19 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-05-24 19:16:50 +02:00
|
|
|
if (state->committed & WLR_OUTPUT_STATE_MODE) {
|
|
|
|
assert(state->mode_type == WLR_OUTPUT_STATE_MODE_CUSTOM);
|
2020-04-02 12:41:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-12-12 13:12:06 +01:00
|
|
|
static void destroy_x11_buffer(struct wlr_x11_buffer *buffer) {
|
|
|
|
if (!buffer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
wl_list_remove(&buffer->buffer_destroy.link);
|
|
|
|
wl_list_remove(&buffer->link);
|
|
|
|
xcb_free_pixmap(buffer->x11->xcb, buffer->pixmap);
|
|
|
|
free(buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void buffer_handle_buffer_destroy(struct wl_listener *listener,
|
|
|
|
void *data) {
|
|
|
|
struct wlr_x11_buffer *buffer =
|
|
|
|
wl_container_of(listener, buffer, buffer_destroy);
|
|
|
|
destroy_x11_buffer(buffer);
|
|
|
|
}
|
|
|
|
|
2021-04-19 20:52:31 +02:00
|
|
|
static xcb_pixmap_t import_dmabuf(struct wlr_x11_output *output,
|
|
|
|
struct wlr_dmabuf_attributes *dmabuf) {
|
2020-11-18 19:21:03 +01:00
|
|
|
struct wlr_x11_backend *x11 = output->x11;
|
|
|
|
|
2021-04-19 20:52:31 +02:00
|
|
|
if (dmabuf->format != x11->x11_format->drm) {
|
2020-11-18 19:21:03 +01:00
|
|
|
// The pixmap's depth must match the window's depth, otherwise Present
|
|
|
|
// will throw a Match error
|
2021-04-19 20:52:31 +02:00
|
|
|
return XCB_PIXMAP_NONE;
|
2020-11-18 19:21:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// xcb closes the FDs after sending them, so we need to dup them here
|
|
|
|
struct wlr_dmabuf_attributes dup_attrs = {0};
|
2021-04-19 20:52:31 +02:00
|
|
|
if (!wlr_dmabuf_attributes_copy(&dup_attrs, dmabuf)) {
|
|
|
|
return XCB_PIXMAP_NONE;
|
2020-11-18 19:21:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const struct wlr_x11_format *x11_fmt = x11->x11_format;
|
|
|
|
xcb_pixmap_t pixmap = xcb_generate_id(x11->xcb);
|
2021-01-17 10:19:51 +01:00
|
|
|
|
|
|
|
if (x11->dri3_major_version > 1 || x11->dri3_minor_version >= 2) {
|
2021-04-19 20:52:31 +02:00
|
|
|
if (dmabuf->n_planes > 4) {
|
2021-01-17 10:21:18 +01:00
|
|
|
wlr_dmabuf_attributes_finish(&dup_attrs);
|
2021-04-19 20:52:31 +02:00
|
|
|
return XCB_PIXMAP_NONE;
|
2021-01-17 10:21:18 +01:00
|
|
|
}
|
2021-01-17 10:19:51 +01:00
|
|
|
xcb_dri3_pixmap_from_buffers(x11->xcb, pixmap, output->win,
|
2021-04-19 20:52:31 +02:00
|
|
|
dmabuf->n_planes, dmabuf->width, dmabuf->height, dmabuf->stride[0],
|
|
|
|
dmabuf->offset[0], dmabuf->stride[1], dmabuf->offset[1],
|
|
|
|
dmabuf->stride[2], dmabuf->offset[2], dmabuf->stride[3],
|
|
|
|
dmabuf->offset[3], x11_fmt->depth, x11_fmt->bpp, dmabuf->modifier,
|
|
|
|
dup_attrs.fd);
|
2021-01-17 10:19:51 +01:00
|
|
|
} else {
|
|
|
|
// PixmapFromBuffers requires DRI3 1.2
|
2021-04-19 20:52:31 +02:00
|
|
|
if (dmabuf->n_planes != 1
|
|
|
|
|| dmabuf->modifier != DRM_FORMAT_MOD_INVALID) {
|
2021-01-17 10:19:51 +01:00
|
|
|
wlr_dmabuf_attributes_finish(&dup_attrs);
|
2021-04-19 20:52:31 +02:00
|
|
|
return XCB_PIXMAP_NONE;
|
2021-01-17 10:19:51 +01:00
|
|
|
}
|
|
|
|
xcb_dri3_pixmap_from_buffer(x11->xcb, pixmap, output->win,
|
2021-04-19 20:52:31 +02:00
|
|
|
dmabuf->height * dmabuf->stride[0], dmabuf->width, dmabuf->height,
|
|
|
|
dmabuf->stride[0], x11_fmt->depth, x11_fmt->bpp, dup_attrs.fd[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return pixmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
static xcb_pixmap_t import_shm(struct wlr_x11_output *output,
|
|
|
|
struct wlr_shm_attributes *shm) {
|
|
|
|
struct wlr_x11_backend *x11 = output->x11;
|
|
|
|
|
|
|
|
if (shm->format != x11->x11_format->drm) {
|
|
|
|
// The pixmap's depth must match the window's depth, otherwise Present
|
|
|
|
// will throw a Match error
|
|
|
|
return XCB_PIXMAP_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// xcb closes the FD after sending it
|
|
|
|
int fd = fcntl(shm->fd, F_DUPFD_CLOEXEC, 0);
|
|
|
|
if (fd < 0) {
|
|
|
|
wlr_log_errno(WLR_ERROR, "fcntl(F_DUPFD_CLOEXEC) failed");
|
|
|
|
return XCB_PIXMAP_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
xcb_shm_seg_t seg = xcb_generate_id(x11->xcb);
|
|
|
|
xcb_shm_attach_fd(x11->xcb, seg, fd, false);
|
|
|
|
|
|
|
|
xcb_pixmap_t pixmap = xcb_generate_id(x11->xcb);
|
|
|
|
xcb_shm_create_pixmap(x11->xcb, pixmap, output->win, shm->width,
|
|
|
|
shm->height, x11->x11_format->depth, seg, shm->offset);
|
|
|
|
|
|
|
|
xcb_shm_detach(x11->xcb, seg);
|
|
|
|
|
|
|
|
return pixmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct wlr_x11_buffer *create_x11_buffer(struct wlr_x11_output *output,
|
|
|
|
struct wlr_buffer *wlr_buffer) {
|
|
|
|
struct wlr_x11_backend *x11 = output->x11;
|
|
|
|
xcb_pixmap_t pixmap = XCB_PIXMAP_NONE;
|
|
|
|
|
|
|
|
struct wlr_dmabuf_attributes dmabuf_attrs;
|
|
|
|
struct wlr_shm_attributes shm_attrs;
|
|
|
|
if (wlr_buffer_get_dmabuf(wlr_buffer, &dmabuf_attrs)) {
|
|
|
|
pixmap = import_dmabuf(output, &dmabuf_attrs);
|
|
|
|
} else if (wlr_buffer_get_shm(wlr_buffer, &shm_attrs)) {
|
|
|
|
pixmap = import_shm(output, &shm_attrs);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pixmap == XCB_PIXMAP_NONE) {
|
|
|
|
return NULL;
|
2021-01-17 10:19:51 +01:00
|
|
|
}
|
2020-11-18 19:21:03 +01:00
|
|
|
|
|
|
|
struct wlr_x11_buffer *buffer = calloc(1, sizeof(struct wlr_x11_buffer));
|
|
|
|
if (!buffer) {
|
|
|
|
xcb_free_pixmap(x11->xcb, pixmap);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
buffer->buffer = wlr_buffer_lock(wlr_buffer);
|
|
|
|
buffer->pixmap = pixmap;
|
|
|
|
buffer->x11 = x11;
|
|
|
|
wl_list_insert(&output->buffers, &buffer->link);
|
2020-12-12 13:12:06 +01:00
|
|
|
|
|
|
|
buffer->buffer_destroy.notify = buffer_handle_buffer_destroy;
|
|
|
|
wl_signal_add(&wlr_buffer->events.destroy, &buffer->buffer_destroy);
|
|
|
|
|
2020-11-18 19:21:03 +01:00
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
2020-12-12 13:12:06 +01:00
|
|
|
static struct wlr_x11_buffer *get_or_create_x11_buffer(
|
|
|
|
struct wlr_x11_output *output, struct wlr_buffer *wlr_buffer) {
|
|
|
|
struct wlr_x11_buffer *buffer;
|
|
|
|
wl_list_for_each(buffer, &output->buffers, link) {
|
|
|
|
if (buffer->buffer == wlr_buffer) {
|
|
|
|
wlr_buffer_lock(buffer->buffer);
|
|
|
|
return buffer;
|
|
|
|
}
|
2020-11-18 19:21:03 +01:00
|
|
|
}
|
2020-12-12 13:12:06 +01:00
|
|
|
|
|
|
|
return create_x11_buffer(output, wlr_buffer);
|
2020-11-18 19:21:03 +01:00
|
|
|
}
|
|
|
|
|
2022-05-24 19:16:50 +02:00
|
|
|
static bool output_commit_buffer(struct wlr_x11_output *output,
|
|
|
|
const struct wlr_output_state *state) {
|
2020-11-18 19:21:03 +01:00
|
|
|
struct wlr_x11_backend *x11 = output->x11;
|
2021-01-21 11:15:06 +01:00
|
|
|
|
2022-05-24 19:16:50 +02:00
|
|
|
struct wlr_buffer *buffer = state->buffer;
|
2020-11-18 19:21:03 +01:00
|
|
|
struct wlr_x11_buffer *x11_buffer =
|
2021-01-21 11:15:06 +01:00
|
|
|
get_or_create_x11_buffer(output, buffer);
|
2020-11-18 19:21:03 +01:00
|
|
|
if (!x11_buffer) {
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
xcb_xfixes_region_t region = XCB_NONE;
|
2022-05-24 19:16:50 +02:00
|
|
|
if (state->committed & WLR_OUTPUT_STATE_DAMAGE) {
|
|
|
|
pixman_region32_union(&output->exposed, &output->exposed,
|
|
|
|
(pixman_region32_t *) &state->damage);
|
2020-11-18 19:21:03 +01:00
|
|
|
|
|
|
|
int rects_len = 0;
|
2021-01-21 12:24:52 +01:00
|
|
|
pixman_box32_t *rects = pixman_region32_rectangles(&output->exposed, &rects_len);
|
2020-11-18 19:21:03 +01:00
|
|
|
|
|
|
|
xcb_rectangle_t *xcb_rects = calloc(rects_len, sizeof(xcb_rectangle_t));
|
|
|
|
if (!xcb_rects) {
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < rects_len; i++) {
|
|
|
|
pixman_box32_t *box = &rects[i];
|
|
|
|
xcb_rects[i] = (struct xcb_rectangle_t){
|
|
|
|
.x = box->x1,
|
|
|
|
.y = box->y1,
|
|
|
|
.width = box->x2 - box->x1,
|
|
|
|
.height = box->y2 - box->y1,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-01-18 22:31:09 +01:00
|
|
|
region = xcb_generate_id(x11->xcb);
|
2020-11-18 19:21:03 +01:00
|
|
|
xcb_xfixes_create_region(x11->xcb, region, rects_len, xcb_rects);
|
|
|
|
|
|
|
|
free(xcb_rects);
|
|
|
|
}
|
|
|
|
|
2021-01-21 12:24:52 +01:00
|
|
|
pixman_region32_clear(&output->exposed);
|
|
|
|
|
2020-11-18 19:21:03 +01:00
|
|
|
uint32_t serial = output->wlr_output.commit_seq;
|
|
|
|
uint32_t options = 0;
|
2020-12-13 14:12:02 +01:00
|
|
|
uint64_t target_msc = output->last_msc ? output->last_msc + 1 : 0;
|
2020-11-18 19:21:03 +01:00
|
|
|
xcb_present_pixmap(x11->xcb, output->win, x11_buffer->pixmap, serial,
|
2020-12-13 14:12:02 +01:00
|
|
|
0, region, 0, 0, XCB_NONE, XCB_NONE, XCB_NONE, options, target_msc,
|
|
|
|
0, 0, 0, NULL);
|
2020-11-18 19:21:03 +01:00
|
|
|
|
|
|
|
if (region != XCB_NONE) {
|
|
|
|
xcb_xfixes_destroy_region(x11->xcb, region);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
error:
|
|
|
|
destroy_x11_buffer(x11_buffer);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-05-24 19:16:50 +02:00
|
|
|
static bool output_commit(struct wlr_output *wlr_output,
|
|
|
|
const struct wlr_output_state *state) {
|
2019-04-23 18:26:21 +02:00
|
|
|
struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);
|
2018-03-31 03:40:43 +02:00
|
|
|
struct wlr_x11_backend *x11 = output->x11;
|
|
|
|
|
2022-05-24 19:16:50 +02:00
|
|
|
if (!output_test(wlr_output, state)) {
|
2019-08-16 18:41:56 +02:00
|
|
|
return false;
|
2019-07-18 20:38:12 +02:00
|
|
|
}
|
|
|
|
|
2022-05-24 19:16:50 +02:00
|
|
|
if (state->committed & WLR_OUTPUT_STATE_MODE) {
|
2019-08-16 18:41:56 +02:00
|
|
|
if (!output_set_custom_mode(wlr_output,
|
2022-05-24 19:16:50 +02:00
|
|
|
state->custom_mode.width,
|
|
|
|
state->custom_mode.height,
|
|
|
|
state->custom_mode.refresh)) {
|
2019-08-16 18:41:56 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-24 19:16:50 +02:00
|
|
|
if (state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED &&
|
2020-01-10 16:02:10 +01:00
|
|
|
x11->atoms.variable_refresh != XCB_ATOM_NONE) {
|
2022-05-24 19:16:50 +02:00
|
|
|
if (state->adaptive_sync_enabled) {
|
2020-01-10 16:02:10 +01:00
|
|
|
uint32_t enabled = 1;
|
|
|
|
xcb_change_property(x11->xcb, XCB_PROP_MODE_REPLACE, output->win,
|
|
|
|
x11->atoms.variable_refresh, XCB_ATOM_CARDINAL, 32, 1,
|
|
|
|
&enabled);
|
|
|
|
wlr_output->adaptive_sync_status = WLR_OUTPUT_ADAPTIVE_SYNC_UNKNOWN;
|
|
|
|
} else {
|
|
|
|
xcb_delete_property(x11->xcb, output->win,
|
|
|
|
x11->atoms.variable_refresh);
|
|
|
|
wlr_output->adaptive_sync_status = WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-24 19:16:50 +02:00
|
|
|
if (state->committed & WLR_OUTPUT_STATE_BUFFER) {
|
|
|
|
if (!output_commit_buffer(output, state)) {
|
2019-08-16 18:41:56 +02:00
|
|
|
return false;
|
|
|
|
}
|
2018-10-01 11:07:58 +02:00
|
|
|
}
|
|
|
|
|
2020-11-18 19:21:03 +01:00
|
|
|
xcb_flush(x11->xcb);
|
|
|
|
|
2018-10-01 11:07:58 +02:00
|
|
|
return true;
|
2018-03-31 03:40:43 +02:00
|
|
|
}
|
|
|
|
|
2021-01-21 02:06:28 +01:00
|
|
|
static void update_x11_output_cursor(struct wlr_x11_output *output,
|
|
|
|
int32_t hotspot_x, int32_t hotspot_y) {
|
|
|
|
struct wlr_x11_backend *x11 = output->x11;
|
|
|
|
|
|
|
|
xcb_cursor_t cursor = x11->transparent_cursor;
|
|
|
|
|
|
|
|
if (output->cursor.pic != XCB_NONE) {
|
|
|
|
cursor = xcb_generate_id(x11->xcb);
|
|
|
|
xcb_render_create_cursor(x11->xcb, cursor, output->cursor.pic,
|
|
|
|
hotspot_x, hotspot_y);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t values[] = {cursor};
|
|
|
|
xcb_change_window_attributes(x11->xcb, output->win,
|
|
|
|
XCB_CW_CURSOR, values);
|
|
|
|
xcb_flush(x11->xcb);
|
|
|
|
|
|
|
|
if (cursor != x11->transparent_cursor) {
|
|
|
|
xcb_free_cursor(x11->xcb, cursor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool output_cursor_to_picture(struct wlr_x11_output *output,
|
2020-12-04 16:41:16 +01:00
|
|
|
struct wlr_buffer *buffer) {
|
2021-01-21 02:06:28 +01:00
|
|
|
struct wlr_x11_backend *x11 = output->x11;
|
2021-11-15 18:53:03 +01:00
|
|
|
struct wlr_renderer *renderer = output->wlr_output.renderer;
|
2021-01-21 02:06:28 +01:00
|
|
|
|
|
|
|
if (output->cursor.pic != XCB_NONE) {
|
|
|
|
xcb_render_free_picture(x11->xcb, output->cursor.pic);
|
|
|
|
}
|
|
|
|
output->cursor.pic = XCB_NONE;
|
|
|
|
|
2020-12-04 16:41:16 +01:00
|
|
|
if (buffer == NULL) {
|
2021-01-21 02:06:28 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-12-04 16:41:16 +01:00
|
|
|
int depth = 32;
|
|
|
|
int stride = buffer->width * 4;
|
2021-01-21 02:06:28 +01:00
|
|
|
|
2021-06-08 09:15:55 +02:00
|
|
|
uint8_t *data = malloc(buffer->height * stride);
|
|
|
|
if (data == NULL) {
|
2021-01-21 02:06:28 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-06-08 09:15:55 +02:00
|
|
|
if (!wlr_renderer_begin_with_buffer(renderer, buffer)) {
|
|
|
|
free(data);
|
2021-01-21 02:06:28 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool result = wlr_renderer_read_pixels(
|
2021-04-29 09:33:54 +02:00
|
|
|
renderer, DRM_FORMAT_ARGB8888, NULL,
|
2020-12-04 16:41:16 +01:00
|
|
|
stride, buffer->width, buffer->height, 0, 0, 0, 0,
|
2021-01-21 02:06:28 +01:00
|
|
|
data);
|
|
|
|
|
2021-06-08 09:15:55 +02:00
|
|
|
wlr_renderer_end(renderer);
|
2021-01-21 02:06:28 +01:00
|
|
|
|
|
|
|
if (!result) {
|
|
|
|
free(data);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
xcb_pixmap_t pix = xcb_generate_id(x11->xcb);
|
|
|
|
xcb_create_pixmap(x11->xcb, depth, pix, output->win,
|
2020-12-04 16:41:16 +01:00
|
|
|
buffer->width, buffer->height);
|
2021-01-21 02:06:28 +01:00
|
|
|
|
|
|
|
output->cursor.pic = xcb_generate_id(x11->xcb);
|
|
|
|
xcb_render_create_picture(x11->xcb, output->cursor.pic,
|
|
|
|
pix, x11->argb32, 0, 0);
|
|
|
|
|
|
|
|
xcb_gcontext_t gc = xcb_generate_id(x11->xcb);
|
|
|
|
xcb_create_gc(x11->xcb, gc, pix, 0, NULL);
|
|
|
|
|
|
|
|
xcb_put_image(x11->xcb, XCB_IMAGE_FORMAT_Z_PIXMAP,
|
2020-12-04 16:41:16 +01:00
|
|
|
pix, gc, buffer->width, buffer->height, 0, 0, 0, depth,
|
|
|
|
stride * buffer->height * sizeof(uint8_t), data);
|
2021-01-21 02:06:28 +01:00
|
|
|
free(data);
|
|
|
|
xcb_free_gc(x11->xcb, gc);
|
|
|
|
xcb_free_pixmap(x11->xcb, pix);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool output_set_cursor(struct wlr_output *wlr_output,
|
2020-12-04 16:41:16 +01:00
|
|
|
struct wlr_buffer *buffer, int32_t hotspot_x, int32_t hotspot_y) {
|
2021-01-21 02:06:28 +01:00
|
|
|
struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);
|
|
|
|
struct wlr_x11_backend *x11 = output->x11;
|
|
|
|
|
|
|
|
if (x11->argb32 == XCB_NONE) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-12-04 16:41:16 +01:00
|
|
|
if (buffer != NULL) {
|
2021-02-06 22:15:46 +01:00
|
|
|
if (hotspot_x < 0) {
|
|
|
|
hotspot_x = 0;
|
|
|
|
}
|
2020-12-04 16:41:16 +01:00
|
|
|
if (hotspot_x > buffer->width) {
|
|
|
|
hotspot_x = buffer->width;
|
2021-02-06 22:15:46 +01:00
|
|
|
}
|
|
|
|
if (hotspot_y < 0) {
|
|
|
|
hotspot_y = 0;
|
|
|
|
}
|
2020-12-04 16:41:16 +01:00
|
|
|
if (hotspot_y > buffer->height) {
|
|
|
|
hotspot_y = buffer->height;
|
2021-01-21 02:06:28 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-04 16:41:16 +01:00
|
|
|
bool success = output_cursor_to_picture(output, buffer);
|
2021-01-21 02:06:28 +01:00
|
|
|
|
2020-12-04 16:41:16 +01:00
|
|
|
update_x11_output_cursor(output, hotspot_x, hotspot_y);
|
2021-01-21 02:06:28 +01:00
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool output_move_cursor(struct wlr_output *_output, int x, int y) {
|
|
|
|
// TODO: only return true if x == current x and y == current y
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-12-19 15:30:25 +01:00
|
|
|
static const struct wlr_drm_format_set *output_get_primary_formats(
|
|
|
|
struct wlr_output *wlr_output, uint32_t buffer_caps) {
|
|
|
|
struct wlr_x11_output *output = get_x11_output_from_output(wlr_output);
|
|
|
|
struct wlr_x11_backend *x11 = output->x11;
|
|
|
|
|
|
|
|
if (x11->have_dri3 && (buffer_caps & WLR_BUFFER_CAP_DMABUF)) {
|
|
|
|
return &output->x11->primary_dri3_formats;
|
|
|
|
} else if (x11->have_shm && (buffer_caps & WLR_BUFFER_CAP_SHM)) {
|
|
|
|
return &output->x11->primary_shm_formats;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-03-31 03:40:43 +02:00
|
|
|
static const struct wlr_output_impl output_impl = {
|
|
|
|
.destroy = output_destroy,
|
2020-04-02 12:41:19 +02:00
|
|
|
.test = output_test,
|
2019-04-23 18:26:21 +02:00
|
|
|
.commit = output_commit,
|
2021-01-21 02:06:28 +01:00
|
|
|
.set_cursor = output_set_cursor,
|
|
|
|
.move_cursor = output_move_cursor,
|
2020-12-19 15:30:25 +01:00
|
|
|
.get_primary_formats = output_get_primary_formats,
|
2018-03-31 03:40:43 +02:00
|
|
|
};
|
|
|
|
|
2018-03-31 04:09:06 +02:00
|
|
|
struct wlr_output *wlr_x11_output_create(struct wlr_backend *backend) {
|
2018-09-17 22:16:07 +02:00
|
|
|
struct wlr_x11_backend *x11 = get_x11_backend_from_backend(backend);
|
2018-03-31 04:09:06 +02:00
|
|
|
|
|
|
|
if (!x11->started) {
|
|
|
|
++x11->requested_outputs;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-03-31 03:40:43 +02:00
|
|
|
struct wlr_x11_output *output = calloc(1, sizeof(struct wlr_x11_output));
|
|
|
|
if (output == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
output->x11 = x11;
|
2020-11-18 19:21:03 +01:00
|
|
|
wl_list_init(&output->buffers);
|
2021-01-21 12:24:52 +01:00
|
|
|
pixman_region32_init(&output->exposed);
|
2018-03-31 03:40:43 +02:00
|
|
|
|
|
|
|
struct wlr_output *wlr_output = &output->wlr_output;
|
|
|
|
wlr_output_init(wlr_output, &x11->backend, &output_impl, x11->wl_display);
|
|
|
|
|
2021-01-20 02:38:22 +01:00
|
|
|
wlr_output_update_custom_mode(wlr_output, 1024, 768, 0);
|
2019-11-19 05:37:43 +01:00
|
|
|
|
2021-12-09 15:43:19 +01:00
|
|
|
char name[64];
|
2021-12-09 16:39:09 +01:00
|
|
|
snprintf(name, sizeof(name), "X11-%zu", ++x11->last_output_num);
|
2021-12-09 15:43:19 +01:00
|
|
|
wlr_output_set_name(wlr_output, name);
|
|
|
|
|
2018-11-10 23:48:45 +01:00
|
|
|
parse_xcb_setup(wlr_output, x11->xcb);
|
2018-03-31 03:40:43 +02:00
|
|
|
|
2019-12-26 16:09:05 +01:00
|
|
|
char description[128];
|
|
|
|
snprintf(description, sizeof(description),
|
2021-12-09 16:39:09 +01:00
|
|
|
"X11 output %zu", x11->last_output_num);
|
2019-12-26 16:09:05 +01:00
|
|
|
wlr_output_set_description(wlr_output, description);
|
|
|
|
|
2020-11-18 19:21:03 +01:00
|
|
|
// The X11 protocol requires us to set a colormap and border pixel if the
|
|
|
|
// depth doesn't match the root window's
|
2021-01-18 00:05:38 +01:00
|
|
|
uint32_t mask = XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK |
|
|
|
|
XCB_CW_COLORMAP | XCB_CW_CURSOR;
|
2019-11-19 05:37:43 +01:00
|
|
|
uint32_t values[] = {
|
2020-11-18 19:21:03 +01:00
|
|
|
0,
|
|
|
|
XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY,
|
|
|
|
x11->colormap,
|
2021-01-21 02:06:28 +01:00
|
|
|
x11->transparent_cursor,
|
2019-11-19 05:37:43 +01:00
|
|
|
};
|
2018-11-10 23:48:45 +01:00
|
|
|
output->win = xcb_generate_id(x11->xcb);
|
2020-11-18 19:21:03 +01:00
|
|
|
xcb_create_window(x11->xcb, x11->depth->depth, output->win,
|
|
|
|
x11->screen->root, 0, 0, wlr_output->width, wlr_output->height, 0,
|
|
|
|
XCB_WINDOW_CLASS_INPUT_OUTPUT, x11->visualid, mask, values);
|
2018-03-31 03:40:43 +02:00
|
|
|
|
2018-11-11 02:21:49 +01:00
|
|
|
struct {
|
|
|
|
xcb_input_event_mask_t head;
|
|
|
|
xcb_input_xi_event_mask_t mask;
|
|
|
|
} xinput_mask = {
|
|
|
|
.head = { .deviceid = XCB_INPUT_DEVICE_ALL_MASTER, .mask_len = 1 },
|
|
|
|
.mask = XCB_INPUT_XI_EVENT_MASK_KEY_PRESS |
|
|
|
|
XCB_INPUT_XI_EVENT_MASK_KEY_RELEASE |
|
|
|
|
XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS |
|
|
|
|
XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE |
|
2018-11-11 02:51:22 +01:00
|
|
|
XCB_INPUT_XI_EVENT_MASK_MOTION |
|
2019-07-15 23:36:21 +02:00
|
|
|
XCB_INPUT_XI_EVENT_MASK_TOUCH_BEGIN |
|
|
|
|
XCB_INPUT_XI_EVENT_MASK_TOUCH_END |
|
2020-12-13 12:21:21 +01:00
|
|
|
XCB_INPUT_XI_EVENT_MASK_TOUCH_UPDATE,
|
2018-11-11 02:21:49 +01:00
|
|
|
};
|
|
|
|
xcb_input_xi_select_events(x11->xcb, output->win, 1, &xinput_mask.head);
|
|
|
|
|
2020-12-13 12:54:05 +01:00
|
|
|
uint32_t present_mask = XCB_PRESENT_EVENT_MASK_IDLE_NOTIFY |
|
|
|
|
XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY;
|
2020-12-28 12:27:27 +01:00
|
|
|
output->present_event_id = xcb_generate_id(x11->xcb);
|
|
|
|
xcb_present_select_input(x11->xcb, output->present_event_id, output->win,
|
2020-11-18 19:21:03 +01:00
|
|
|
present_mask);
|
2018-03-31 03:40:43 +02:00
|
|
|
|
2018-11-10 23:48:45 +01:00
|
|
|
xcb_change_property(x11->xcb, XCB_PROP_MODE_REPLACE, output->win,
|
2018-03-31 03:40:43 +02:00
|
|
|
x11->atoms.wm_protocols, XCB_ATOM_ATOM, 32, 1,
|
|
|
|
&x11->atoms.wm_delete_window);
|
|
|
|
|
2019-01-24 13:48:26 +01:00
|
|
|
wlr_x11_output_set_title(wlr_output, NULL);
|
2018-03-31 03:40:43 +02:00
|
|
|
|
2019-11-19 05:37:43 +01:00
|
|
|
xcb_map_window(x11->xcb, output->win);
|
|
|
|
xcb_flush(x11->xcb);
|
|
|
|
|
2018-04-29 15:46:29 +02:00
|
|
|
wl_list_insert(&x11->outputs, &output->link);
|
|
|
|
|
2019-11-19 05:37:43 +01:00
|
|
|
wlr_output_update_enabled(wlr_output, true);
|
|
|
|
|
2022-02-08 21:32:40 +01:00
|
|
|
wlr_pointer_init(&output->pointer, &x11_pointer_impl, "x11-pointer");
|
2022-03-08 22:45:03 +01:00
|
|
|
output->pointer.output_name = strdup(wlr_output->name);
|
2018-04-29 15:46:29 +02:00
|
|
|
|
2022-02-08 21:32:40 +01:00
|
|
|
wlr_touch_init(&output->touch, &x11_touch_impl, "x11-touch");
|
2022-03-08 22:45:03 +01:00
|
|
|
output->touch.output_name = strdup(wlr_output->name);
|
2019-07-15 23:36:21 +02:00
|
|
|
wl_list_init(&output->touchpoints);
|
|
|
|
|
2022-08-18 13:16:16 +02:00
|
|
|
wl_signal_emit_mutable(&x11->backend.events.new_output, wlr_output);
|
|
|
|
wl_signal_emit_mutable(&x11->backend.events.new_input, &output->pointer.base);
|
|
|
|
wl_signal_emit_mutable(&x11->backend.events.new_input, &output->touch.base);
|
2018-03-31 03:40:43 +02:00
|
|
|
|
2020-12-13 14:12:02 +01:00
|
|
|
// Start the rendering loop by requesting the compositor to render a frame
|
|
|
|
wlr_output_schedule_frame(wlr_output);
|
|
|
|
|
2018-03-31 04:09:06 +02:00
|
|
|
return wlr_output;
|
2018-03-31 03:40:43 +02:00
|
|
|
}
|
|
|
|
|
2019-11-19 05:37:43 +01:00
|
|
|
void handle_x11_configure_notify(struct wlr_x11_output *output,
|
|
|
|
xcb_configure_notify_event_t *ev) {
|
|
|
|
// ignore events that set an invalid size:
|
2020-11-18 19:21:03 +01:00
|
|
|
if (ev->width == 0 || ev->height == 0) {
|
2019-11-19 05:37:43 +01:00
|
|
|
wlr_log(WLR_DEBUG,
|
|
|
|
"Ignoring X11 configure event for height=%d, width=%d",
|
|
|
|
ev->width, ev->height);
|
2020-11-18 19:21:03 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wlr_output_update_custom_mode(&output->wlr_output, ev->width,
|
2020-12-13 14:12:02 +01:00
|
|
|
ev->height, 0);
|
2020-11-18 19:21:03 +01:00
|
|
|
|
|
|
|
// Move the pointer to its new location
|
|
|
|
update_x11_pointer_position(output, output->x11->time);
|
2018-03-31 03:40:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool wlr_output_is_x11(struct wlr_output *wlr_output) {
|
|
|
|
return wlr_output->impl == &output_impl;
|
|
|
|
}
|
2019-01-20 16:21:04 +01:00
|
|
|
|
|
|
|
void wlr_x11_output_set_title(struct wlr_output *output, const char *title) {
|
|
|
|
struct wlr_x11_output *x11_output = get_x11_output_from_output(output);
|
|
|
|
|
2019-01-24 13:48:26 +01:00
|
|
|
char wl_title[32];
|
|
|
|
if (title == NULL) {
|
|
|
|
if (snprintf(wl_title, sizeof(wl_title), "wlroots - %s", output->name) <= 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
title = wl_title;
|
|
|
|
}
|
|
|
|
|
2019-01-20 16:21:04 +01:00
|
|
|
xcb_change_property(x11_output->x11->xcb, XCB_PROP_MODE_REPLACE, x11_output->win,
|
|
|
|
x11_output->x11->atoms.net_wm_name, x11_output->x11->atoms.utf8_string, 8,
|
|
|
|
strlen(title), title);
|
|
|
|
}
|
2020-11-18 19:21:03 +01:00
|
|
|
|
|
|
|
static struct wlr_x11_buffer *get_x11_buffer(struct wlr_x11_output *output,
|
|
|
|
xcb_pixmap_t pixmap) {
|
|
|
|
struct wlr_x11_buffer *buffer;
|
|
|
|
wl_list_for_each(buffer, &output->buffers, link) {
|
|
|
|
if (buffer->pixmap == pixmap) {
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void handle_x11_present_event(struct wlr_x11_backend *x11,
|
|
|
|
xcb_ge_generic_event_t *event) {
|
2020-12-13 12:54:05 +01:00
|
|
|
struct wlr_x11_output *output;
|
|
|
|
|
2020-11-18 19:21:03 +01:00
|
|
|
switch (event->event_type) {
|
|
|
|
case XCB_PRESENT_EVENT_IDLE_NOTIFY:;
|
|
|
|
xcb_present_idle_notify_event_t *idle_notify =
|
|
|
|
(xcb_present_idle_notify_event_t *)event;
|
|
|
|
|
2020-12-13 12:54:05 +01:00
|
|
|
output = get_x11_output_from_window_id(x11, idle_notify->window);
|
2020-11-18 19:21:03 +01:00
|
|
|
if (!output) {
|
|
|
|
wlr_log(WLR_DEBUG, "Got PresentIdleNotify event for unknown window");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct wlr_x11_buffer *buffer =
|
|
|
|
get_x11_buffer(output, idle_notify->pixmap);
|
|
|
|
if (!buffer) {
|
|
|
|
wlr_log(WLR_DEBUG, "Got PresentIdleNotify event for unknown buffer");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-12 13:12:06 +01:00
|
|
|
wlr_buffer_unlock(buffer->buffer); // may destroy buffer
|
2020-11-18 19:21:03 +01:00
|
|
|
break;
|
2020-12-13 12:54:05 +01:00
|
|
|
case XCB_PRESENT_COMPLETE_NOTIFY:;
|
|
|
|
xcb_present_complete_notify_event_t *complete_notify =
|
|
|
|
(xcb_present_complete_notify_event_t *)event;
|
|
|
|
|
|
|
|
output = get_x11_output_from_window_id(x11, complete_notify->window);
|
|
|
|
if (!output) {
|
|
|
|
wlr_log(WLR_DEBUG, "Got PresentCompleteNotify event for unknown window");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-13 14:12:02 +01:00
|
|
|
output->last_msc = complete_notify->msc;
|
|
|
|
|
2020-12-13 12:54:05 +01:00
|
|
|
struct timespec t;
|
|
|
|
timespec_from_nsec(&t, complete_notify->ust * 1000);
|
|
|
|
|
|
|
|
uint32_t flags = 0;
|
|
|
|
if (complete_notify->mode == XCB_PRESENT_COMPLETE_MODE_FLIP) {
|
|
|
|
flags |= WLR_OUTPUT_PRESENT_ZERO_COPY;
|
|
|
|
}
|
|
|
|
|
2021-10-14 21:40:12 +02:00
|
|
|
bool presented = complete_notify->mode != XCB_PRESENT_COMPLETE_MODE_SKIP;
|
2020-12-13 12:54:05 +01:00
|
|
|
struct wlr_output_event_present present_event = {
|
|
|
|
.output = &output->wlr_output,
|
|
|
|
.commit_seq = complete_notify->serial,
|
2021-10-14 21:40:12 +02:00
|
|
|
.presented = presented,
|
2020-12-13 12:54:05 +01:00
|
|
|
.when = &t,
|
|
|
|
.seq = complete_notify->msc,
|
|
|
|
.flags = flags,
|
|
|
|
};
|
|
|
|
wlr_output_send_present(&output->wlr_output, &present_event);
|
2020-12-13 14:12:02 +01:00
|
|
|
|
|
|
|
wlr_output_send_frame(&output->wlr_output);
|
2020-12-13 12:54:05 +01:00
|
|
|
break;
|
2020-11-18 19:21:03 +01:00
|
|
|
default:
|
|
|
|
wlr_log(WLR_DEBUG, "Unhandled Present event %"PRIu16, event->event_type);
|
|
|
|
}
|
|
|
|
}
|