2022-07-05 14:35:38 +03:00
|
|
|
/*
|
|
|
|
* Copyright 2022 Collabora, Ltd.
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
|
|
* a copy of this software and associated documentation files (the
|
|
|
|
* "Software"), to deal in the Software without restriction, including
|
|
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
|
|
* the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice (including the
|
|
|
|
* next paragraph) shall be included in all copies or substantial
|
|
|
|
* portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
|
|
|
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
* SOFTWARE.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <poll.h>
|
|
|
|
|
|
|
|
#include <time.h>
|
|
|
|
|
|
|
|
#include <wayland-client.h>
|
|
|
|
#include "test-config.h"
|
|
|
|
#include "shared/os-compatibility.h"
|
|
|
|
#include "shared/helpers.h"
|
|
|
|
#include "shared/xalloc.h"
|
|
|
|
#include "shared/xcb-xwayland.h"
|
|
|
|
#include <libweston/zalloc.h>
|
|
|
|
#include "xcb-client-helper.h"
|
|
|
|
|
2023-03-28 12:45:13 +03:00
|
|
|
#define DEBUG
|
2022-07-05 14:35:38 +03:00
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
#define printfd(fmt, args...) do { \
|
|
|
|
fprintf(stderr, fmt, ##args); \
|
|
|
|
} while (0)
|
|
|
|
#else
|
|
|
|
#define printfd(fmt, args...) {}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
struct event_response {
|
|
|
|
uint8_t response_type;
|
|
|
|
bool (*eventcb)(xcb_generic_event_t *e, struct window_x11 *win);
|
|
|
|
const char *name;
|
|
|
|
};
|
|
|
|
|
|
|
|
const char *to_event_name(uint8_t event);
|
|
|
|
|
|
|
|
static xcb_drawable_t
|
|
|
|
handle_event_to_wid(xcb_generic_event_t *ev)
|
|
|
|
{
|
|
|
|
xcb_drawable_t wid;
|
|
|
|
|
|
|
|
switch (EVENT_TYPE(ev)) {
|
|
|
|
case XCB_CREATE_NOTIFY: {
|
|
|
|
xcb_create_notify_event_t *ce = (xcb_create_notify_event_t *) ev;
|
|
|
|
wid = ce->window;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case XCB_DESTROY_NOTIFY: {
|
|
|
|
xcb_destroy_notify_event_t *de = (xcb_destroy_notify_event_t *) ev;
|
|
|
|
wid = de->window;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case XCB_MAP_NOTIFY: {
|
|
|
|
xcb_map_notify_event_t *mn = (xcb_map_notify_event_t *) ev;
|
|
|
|
wid = mn->window;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case XCB_UNMAP_NOTIFY: {
|
|
|
|
xcb_unmap_notify_event_t *un = (xcb_unmap_notify_event_t *) ev;
|
|
|
|
wid = un->window;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case XCB_PROPERTY_NOTIFY: {
|
|
|
|
xcb_property_notify_event_t *pn = (xcb_property_notify_event_t *) ev;
|
|
|
|
wid = pn->window;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case XCB_CONFIGURE_NOTIFY: {
|
|
|
|
xcb_configure_notify_event_t *cn = (xcb_configure_notify_event_t *) ev;
|
|
|
|
wid = cn->window;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case XCB_EXPOSE: {
|
|
|
|
xcb_expose_event_t *ep = (xcb_expose_event_t *) ev;
|
|
|
|
wid = ep->window;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case XCB_REPARENT_NOTIFY: {
|
|
|
|
xcb_reparent_notify_event_t *re = (xcb_reparent_notify_event_t *) ev;
|
|
|
|
wid = re->window;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
wid = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return wid;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
handle_event_remove_pending(struct window_state *wstate)
|
|
|
|
{
|
|
|
|
wl_list_remove(&wstate->link);
|
|
|
|
free(wstate);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* returns true if all events in the pending_list has been accounted for */
|
|
|
|
static bool
|
|
|
|
handle_event_check_pending(struct window_x11 *window, xcb_generic_event_t *ev)
|
|
|
|
{
|
|
|
|
|
|
|
|
struct window_state *wstate, *wstate_next;
|
|
|
|
struct wl_list *pending_events =
|
|
|
|
&window->tentative_state.pending_events_list;
|
|
|
|
xcb_drawable_t wid;
|
|
|
|
uint8_t event;
|
|
|
|
bool found = false;
|
|
|
|
|
|
|
|
event = EVENT_TYPE(ev);
|
|
|
|
wid = handle_event_to_wid(ev);
|
|
|
|
|
|
|
|
wl_list_for_each_safe(wstate, wstate_next, pending_events, link) {
|
|
|
|
if (wstate->event != event)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (wstate->wid == wid) {
|
|
|
|
handle_event_remove_pending(wstate);
|
|
|
|
found = true;
|
|
|
|
printfd("%s: removed event %d - %s\n", __func__,
|
|
|
|
event, to_event_name(event));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
printfd("%s(): event id %d, name %s not found\n", __func__,
|
|
|
|
event, to_event_name(event));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* still need to get events? -> wait one more round */
|
|
|
|
if (!wl_list_empty(pending_events)) {
|
|
|
|
printfd("%s(): still have %d events to handle!\n", __func__,
|
|
|
|
wl_list_length(pending_events));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** In case you need to wait for a notify event call use this function to do so
|
|
|
|
* and then call handle_events_x11() at the end, to wait for the events to be
|
|
|
|
* delivered/handled. Note that handle_events_x11() will wait forever if
|
|
|
|
* handle_event_set_pending() was called for an event which never arrives.
|
|
|
|
*
|
|
|
|
* You can call this function multiple times for the same wid, in case you
|
|
|
|
* expect multiple events to be delivered (i.e., a map notify event and a
|
|
|
|
* expose one). All functions in this XCB wrapper library calls this function,
|
|
|
|
* with the user only needing to call handle_events_x11() at the end. This
|
|
|
|
* function is only needed if the test itself requires to wait for additional
|
|
|
|
* events, or when expanding this library with other states changes
|
|
|
|
* (max/fullscreen).
|
|
|
|
*
|
|
|
|
* \param window the window_x11 in question
|
|
|
|
* \param event the event to wait for, like XCB_MAP_NOTIFY, XCB_EXPOSE, etc.
|
|
|
|
* \param pending_state the pending event to wait for, similar to the XCB ones
|
|
|
|
* but defined in enum w_state
|
|
|
|
* \param wid the window id, could be different than that of the window itself
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
handle_event_set_pending(struct window_x11 *window, uint8_t event,
|
|
|
|
enum w_state pending_state, xcb_drawable_t wid)
|
|
|
|
{
|
|
|
|
struct window_state *wstate = xzalloc(sizeof(*wstate));
|
|
|
|
|
|
|
|
wstate->event = event;
|
|
|
|
wstate->wid = wid;
|
|
|
|
wstate->pending_state = pending_state;
|
|
|
|
wl_list_insert(&window->tentative_state.pending_events_list,
|
|
|
|
&wstate->link);
|
|
|
|
|
|
|
|
printfd("%s: Added pending event id %d - name %s, wid %d\n",
|
|
|
|
__func__, event, to_event_name(event), wid);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
handle_map_notify(xcb_generic_event_t *e, struct window_x11 *window)
|
|
|
|
{
|
|
|
|
xcb_map_notify_event_t *ce = (xcb_map_notify_event_t *) e;
|
|
|
|
|
|
|
|
if (ce->window != window->win_id)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
window_state_set_flag(window, MAPPED);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
handle_unmap_notify(xcb_generic_event_t *e, struct window_x11 *window)
|
|
|
|
{
|
|
|
|
xcb_unmap_notify_event_t *ce = (xcb_unmap_notify_event_t*) e;
|
|
|
|
|
|
|
|
if (ce->window != window->win_id && ce->window != window->frame_id)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
assert(window_state_has_flag(window, MAPPED));
|
|
|
|
window_state_set_flag(window, UNMAPPED);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
handle_create_notify(xcb_generic_event_t *e, struct window_x11 *window)
|
|
|
|
{
|
|
|
|
xcb_create_notify_event_t *ce = (xcb_create_notify_event_t*) e;
|
|
|
|
|
|
|
|
if (ce->window != window->win_id)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
window_state_set_flag(window, CREATED);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
handle_destroy_notify(xcb_generic_event_t *e, struct window_x11 *window)
|
|
|
|
{
|
|
|
|
xcb_destroy_notify_event_t *dn = (xcb_destroy_notify_event_t*) e;
|
|
|
|
|
|
|
|
if (window->win_id != dn->window)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
assert(window_state_has_flag(window, CREATED));
|
|
|
|
window_state_set_flag(window, DESTROYED);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
handle_property_notify(xcb_generic_event_t *e, struct window_x11 *window)
|
|
|
|
{
|
|
|
|
xcb_property_notify_event_t *pn = (xcb_property_notify_event_t *) e;
|
|
|
|
struct atom_x11 *atoms = window->conn->atoms;
|
|
|
|
|
|
|
|
if (pn->window != window->win_id)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (pn->atom == atoms->net_wm_name) {
|
|
|
|
window_state_set_flag(window, PROPERTY_NAME);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
handle_expose(xcb_generic_event_t *e, struct window_x11 *window)
|
|
|
|
{
|
|
|
|
xcb_expose_event_t *ep = (xcb_expose_event_t *) e;
|
|
|
|
|
|
|
|
if (ep->window != window->win_id)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
window_state_set_flag(window, EXPOSE);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
handle_configure_notify(xcb_generic_event_t *e, struct window_x11 *window)
|
|
|
|
{
|
|
|
|
xcb_configure_notify_event_t *cn = (xcb_configure_notify_event_t*) e;
|
|
|
|
|
|
|
|
/* we're not interested into other's windows */
|
|
|
|
if (cn->window != window->win_id)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
handle_reparent_notify(xcb_generic_event_t *e, struct window_x11 *window)
|
|
|
|
{
|
|
|
|
xcb_reparent_notify_event_t *re = (xcb_reparent_notify_event_t *) e;
|
|
|
|
|
|
|
|
if (re->window == window->win_id && window->frame_id == 0) {
|
|
|
|
window->frame_id = re->parent;
|
|
|
|
window_state_set_flag(window, REPARENT);
|
|
|
|
printfd("Window reparent frame id %d\n", window->frame_id);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* the event handlers should return a boolean that denotes that fact
|
|
|
|
* they've been handled. One can customize that behaviour such that
|
|
|
|
* it forces handle_events_x11() to wait for additional events, in case
|
|
|
|
* that's needed. */
|
|
|
|
static const struct event_response events[] = {
|
|
|
|
{ XCB_CREATE_NOTIFY, handle_create_notify, "CREATE_NOTIFY" },
|
|
|
|
{ XCB_MAP_NOTIFY, handle_map_notify, "MAP_NOTIFY" },
|
|
|
|
{ XCB_UNMAP_NOTIFY, handle_unmap_notify, "UNMAP_NOTIFY" },
|
|
|
|
{ XCB_EXPOSE, handle_expose, "EXPOSE_NOTIFY" },
|
|
|
|
{ XCB_PROPERTY_NOTIFY, handle_property_notify, "PROPERTY_NOTIFY" },
|
|
|
|
{ XCB_CONFIGURE_NOTIFY, handle_configure_notify, "CONFIGURE_NOTIFY" },
|
|
|
|
{ XCB_DESTROY_NOTIFY, handle_destroy_notify, "DESTROY_NOTIFY" },
|
|
|
|
{ XCB_REPARENT_NOTIFY, handle_reparent_notify, "REPARENT_NOTIFY" },
|
|
|
|
};
|
|
|
|
|
|
|
|
const char *
|
|
|
|
to_event_name(uint8_t event)
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(events); i++)
|
|
|
|
if (events[i].response_type == event)
|
|
|
|
return events[i].name;
|
|
|
|
|
|
|
|
return "(unknown event)";
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tells the X server to display the window. Call handle_events_x11() after
|
|
|
|
* calling this function to wait for events to be delivered. Note there's no
|
|
|
|
* need to include additional events, they're already added.
|
|
|
|
*
|
|
|
|
* \sa handle_events_x11().
|
|
|
|
*
|
|
|
|
* \param window the window in question
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
window_x11_map(struct window_x11 *window)
|
|
|
|
{
|
|
|
|
handle_event_set_pending(window, XCB_MAP_NOTIFY, MAPPED, window->win_id);
|
|
|
|
handle_event_set_pending(window, XCB_EXPOSE, EXPOSE, window->win_id);
|
|
|
|
|
|
|
|
/* doing a synchronization for the frame wid helps with other potential
|
|
|
|
* states changes like max/fullscreen or if we try to map the window
|
|
|
|
* from the beginning with a max/fullscreen state rather than normal
|
|
|
|
* state (!max && !fullscreen)
|
|
|
|
*/
|
|
|
|
handle_event_set_pending(window, XCB_REPARENT_NOTIFY, REPARENT, window->win_id);
|
|
|
|
|
|
|
|
xcb_map_window(window->conn->connection, window->win_id);
|
|
|
|
xcb_flush(window->conn->connection);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \sa window_x11_map, handle_events_x11. Tells the X server to unmap the
|
|
|
|
* window. Call handle_events_x11() to wait for the events to be delivered.
|
|
|
|
*
|
|
|
|
* \param window the window in question
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
window_x11_unmap(struct window_x11 *window)
|
|
|
|
{
|
|
|
|
handle_event_set_pending(window, XCB_UNMAP_NOTIFY, UNMAPPED, window->win_id);
|
|
|
|
|
|
|
|
xcb_unmap_window(window->conn->connection, window->win_id);
|
|
|
|
xcb_flush(window->conn->connection);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
window_x11_set_cursor(struct window_x11 *window, const char *cursor_name)
|
|
|
|
{
|
|
|
|
assert(window);
|
|
|
|
assert(window->ctx == NULL);
|
|
|
|
|
|
|
|
if (xcb_cursor_context_new(window->conn->connection,
|
|
|
|
window->screen, &window->ctx) < 0) {
|
|
|
|
fprintf(stderr, "Error creating context!\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
window->cursor = xcb_cursor_load_cursor(window->ctx, cursor_name);
|
|
|
|
|
|
|
|
xcb_change_window_attributes(window->conn->connection,
|
|
|
|
window->root_win_id, XCB_CW_CURSOR,
|
|
|
|
&window->cursor);
|
|
|
|
xcb_flush(window->conn->connection);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
handle_event(xcb_generic_event_t *ev, struct window_x11 *window)
|
|
|
|
{
|
|
|
|
uint8_t event = EVENT_TYPE(ev);
|
|
|
|
bool events_handled = false;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_LENGTH(events); i++) {
|
|
|
|
if (event == events[i].response_type) {
|
|
|
|
bool ev_cb_handled = events[i].eventcb(ev, window);
|
|
|
|
if (!ev_cb_handled)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
events_handled =
|
|
|
|
handle_event_check_pending(window, ev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return events_handled;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Each operation on 'window_x11' requires calling handle_events_x11() to a) flush
|
|
|
|
* out the connection, b) poll for a xcb_generic_event_t and c) to call the
|
|
|
|
* appropriate event handler;
|
|
|
|
*
|
|
|
|
* This function should never block to allow a programmatic way of applying
|
|
|
|
* different operations/states to the window. If that happens, running it
|
|
|
|
* under meson test will cause a test fail (with a timeout).
|
|
|
|
*
|
|
|
|
* Before calling this function, one *shall* use handle_event_set_pending() to
|
|
|
|
* explicitly set which events to wait for. Not doing so will effectively
|
|
|
|
* deadlock the test, as it will wait for pending events never being set.
|
|
|
|
*
|
|
|
|
* Note that all state change functions, including map and unmap, would
|
|
|
|
* implicitly, if not otherwise stated, set the pending events to wait for.
|
|
|
|
*
|
|
|
|
* \sa handle_event_set_pending()
|
|
|
|
* \param window the X11 window in question
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
handle_events_x11(struct window_x11 *window)
|
|
|
|
{
|
|
|
|
bool running = true;
|
|
|
|
xcb_generic_event_t *ev;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
assert(window->handle_in_progress == false);
|
|
|
|
window->handle_in_progress = true;
|
|
|
|
|
|
|
|
do {
|
|
|
|
bool events_handled = false;
|
|
|
|
|
|
|
|
xcb_flush(window->conn->connection);
|
|
|
|
|
|
|
|
if (xcb_connection_has_error(window->conn->connection)) {
|
|
|
|
fprintf(stderr, "X11 connection got interrupted\n");
|
|
|
|
ret = -1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2024-04-11 18:06:56 +03:00
|
|
|
ev = xcb_wait_for_event(window->conn->connection);
|
2022-07-05 14:35:38 +03:00
|
|
|
if (!ev) {
|
|
|
|
fprintf(stderr, "Error, no event received, "
|
|
|
|
"although we requested for one!\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
events_handled = handle_event(ev, window);
|
|
|
|
|
|
|
|
/* signals that we've done processing all the pending events */
|
|
|
|
if (events_handled) {
|
|
|
|
running = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
free(ev);
|
|
|
|
} while (running);
|
|
|
|
|
|
|
|
window->handle_in_progress = false;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Might be useful in case you'd want to receive create notify for our own window. */
|
|
|
|
void
|
|
|
|
window_x11_notify_for_root_events(struct window_x11 *window)
|
|
|
|
{
|
|
|
|
int mask_values =
|
|
|
|
XCB_EVENT_MASK_STRUCTURE_NOTIFY |
|
|
|
|
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
|
|
|
|
|
|
|
|
xcb_change_window_attributes(window->conn->connection,
|
|
|
|
window->root_win_id,
|
|
|
|
XCB_CW_EVENT_MASK, &mask_values);
|
|
|
|
xcb_flush(window->conn->connection);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the x11 window a property name. Call handle_events_x11() to
|
|
|
|
* wait for the events to be delivered.
|
|
|
|
*
|
|
|
|
* \param window the window in question
|
|
|
|
* \param name the name, as string
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
window_x11_set_win_name(struct window_x11 *window, const char *name)
|
|
|
|
{
|
|
|
|
struct atom_x11 *atoms = window->conn->atoms;
|
|
|
|
|
|
|
|
handle_event_set_pending(window, XCB_PROPERTY_NOTIFY, PROPERTY_NAME, window->win_id);
|
|
|
|
|
|
|
|
xcb_change_property(window->conn->connection, XCB_PROP_MODE_REPLACE,
|
|
|
|
window->win_id, atoms->net_wm_name,
|
|
|
|
atoms->string, 8,
|
|
|
|
strlen(name), name);
|
|
|
|
xcb_flush(window->conn->connection);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Create a x11 connection.
|
|
|
|
*
|
|
|
|
* \sa window_get_connection() to retrieve it in the same tests to avoid
|
|
|
|
* creating a new connection
|
|
|
|
* \sa create_x11_window(), where you need to pass this connection_x11 object.
|
|
|
|
*
|
|
|
|
* \return a struct connection_x11 which defines a x11 connection.
|
|
|
|
*/
|
|
|
|
struct connection_x11 *
|
|
|
|
create_x11_connection(void)
|
|
|
|
{
|
|
|
|
struct connection_x11 *conn;
|
|
|
|
|
|
|
|
if (access(XSERVER_PATH, X_OK) != 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
conn = xzalloc(sizeof(*conn));
|
|
|
|
conn->connection = xcb_connect(NULL, NULL);
|
|
|
|
if (!conn->connection)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
conn->atoms = xzalloc(sizeof(struct atom_x11));
|
|
|
|
|
|
|
|
/* retrieve atoms */
|
|
|
|
x11_get_atoms(conn->connection, conn->atoms);
|
|
|
|
|
|
|
|
return conn;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Destroys a x11 connection. Use this at the end (of the test) to destroy the
|
|
|
|
* x11 connection.
|
|
|
|
*
|
|
|
|
* \param conn the x11 connection in question.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
destroy_x11_connection(struct connection_x11 *conn)
|
|
|
|
{
|
|
|
|
xcb_disconnect(conn->connection);
|
|
|
|
|
|
|
|
free(conn->atoms);
|
|
|
|
free(conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* creates a X window, based on the initial supplied values. All operations
|
|
|
|
* performed will work on this window_x11 object.
|
|
|
|
*
|
|
|
|
* The creation and destruction of the window_x11 object is handled implictly
|
|
|
|
* so there's no need wait for (additional) events, like it is required for
|
|
|
|
* all other change state operations.
|
|
|
|
*
|
|
|
|
* The window is not mapped/displayed so that needs to happen explictly, by
|
|
|
|
* calling window_x11_map() and then waiting for events using
|
|
|
|
* handle_events_x11().
|
|
|
|
*
|
|
|
|
* \param width initial size, width value
|
|
|
|
* \param height initial size, height value
|
|
|
|
* \param pos_x initial position, x value
|
|
|
|
* \param pos_y initial position, y value
|
|
|
|
* \param conn X11 connection
|
|
|
|
* \param bg_color a background color
|
|
|
|
* \param parent the window_x11 parent
|
|
|
|
* \return a pointer to window_x11, which gets destroyed with destroy_x11_window()
|
|
|
|
*/
|
|
|
|
struct window_x11 *
|
|
|
|
create_x11_window(int width, int height, int pos_x, int pos_y,
|
|
|
|
struct connection_x11 *conn, pixman_color_t bg_color,
|
|
|
|
struct window_x11 *parent)
|
|
|
|
{
|
|
|
|
uint32_t colorpixel = 0x0;
|
|
|
|
uint32_t values[2];
|
|
|
|
uint32_t mask = 0;
|
|
|
|
|
|
|
|
xcb_colormap_t colormap;
|
|
|
|
struct window_x11 *window;
|
|
|
|
xcb_window_t parent_win_id;
|
|
|
|
xcb_alloc_color_cookie_t cookie;
|
|
|
|
xcb_alloc_color_reply_t *reply;
|
|
|
|
xcb_void_cookie_t cookie_create;
|
|
|
|
xcb_generic_error_t *error_create;
|
|
|
|
const struct xcb_setup_t *xcb_setup;
|
|
|
|
|
|
|
|
assert(conn);
|
|
|
|
window = xzalloc(sizeof(*window));
|
|
|
|
|
|
|
|
window->conn = conn;
|
|
|
|
xcb_setup = xcb_get_setup(window->conn->connection);
|
|
|
|
window->screen = xcb_setup_roots_iterator(xcb_setup).data;
|
|
|
|
|
|
|
|
wl_list_init(&window->window_list);
|
|
|
|
wl_list_init(&window->tentative_state.pending_events_list);
|
|
|
|
|
|
|
|
window->root_win_id = window->screen->root;
|
|
|
|
window->parent = parent;
|
|
|
|
if (window->parent) {
|
|
|
|
parent_win_id = window->parent->win_id;
|
|
|
|
wl_list_insert(&parent->window_list, &window->window_link);
|
|
|
|
} else {
|
|
|
|
parent_win_id = window->root_win_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
colormap = window->screen->default_colormap;
|
|
|
|
cookie = xcb_alloc_color(window->conn->connection, colormap,
|
|
|
|
bg_color.red, bg_color.blue, bg_color.green);
|
|
|
|
reply = xcb_alloc_color_reply(window->conn->connection, cookie, NULL);
|
|
|
|
assert(reply);
|
|
|
|
|
|
|
|
colorpixel = reply->pixel;
|
|
|
|
free(reply);
|
|
|
|
|
|
|
|
window->background = xcb_generate_id(window->conn->connection);
|
|
|
|
mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES;
|
|
|
|
values[0] = colorpixel;
|
|
|
|
values[1] = 0;
|
|
|
|
window->bg_color = bg_color;
|
|
|
|
|
|
|
|
xcb_create_gc(window->conn->connection, window->background,
|
|
|
|
window->root_win_id, mask, values);
|
|
|
|
|
|
|
|
/* create the window */
|
|
|
|
window->win_id = xcb_generate_id(window->conn->connection);
|
|
|
|
mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
|
|
|
|
values[0] = colorpixel;
|
|
|
|
values[1] = XCB_EVENT_MASK_EXPOSURE |
|
|
|
|
XCB_EVENT_MASK_KEY_PRESS |
|
|
|
|
XCB_EVENT_MASK_VISIBILITY_CHANGE |
|
|
|
|
XCB_EVENT_MASK_STRUCTURE_NOTIFY |
|
|
|
|
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
|
|
|
|
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
|
|
|
|
XCB_EVENT_MASK_PROPERTY_CHANGE;
|
|
|
|
|
|
|
|
window->pos_x = pos_x;
|
|
|
|
window->pos_y = pos_y;
|
|
|
|
|
|
|
|
window->width = width;
|
|
|
|
window->height = height;
|
|
|
|
|
|
|
|
cookie_create = xcb_create_window_checked(window->conn->connection,
|
|
|
|
XCB_COPY_FROM_PARENT,
|
|
|
|
window->win_id, parent_win_id,
|
|
|
|
window->pos_x, window->pos_y,
|
|
|
|
window->width, window->height,
|
|
|
|
0,
|
|
|
|
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
|
|
|
window->screen->root_visual,
|
|
|
|
mask, values);
|
|
|
|
error_create = xcb_request_check(window->conn->connection, cookie_create);
|
|
|
|
assert(error_create == NULL);
|
|
|
|
|
|
|
|
window_state_set_flag(window, CREATED);
|
|
|
|
window_x11_set_cursor(window, "left_ptr");
|
|
|
|
|
|
|
|
return window;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
kill_window(struct window_x11 *window)
|
|
|
|
{
|
|
|
|
handle_event_set_pending(window, XCB_DESTROY_NOTIFY, DESTROYED, window->win_id);
|
|
|
|
|
|
|
|
xcb_destroy_window(window->conn->connection, window->win_id);
|
|
|
|
xcb_flush(window->conn->connection);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \sa create_x11_window(). The creation and destruction of the window_x11 is
|
|
|
|
* handled implicitly so there's no wait for (additional) events.
|
|
|
|
*
|
|
|
|
* This function would wait for destroy notify event and will disconnect from
|
|
|
|
* the server. No further operation can happen on the window_x11, except
|
|
|
|
* for destroying the x11 connection using destroy_x11_connection().
|
|
|
|
*
|
|
|
|
* \param window the window in question
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
destroy_x11_window(struct window_x11 *window)
|
|
|
|
{
|
|
|
|
struct window_state *wstate, *wstate_next;
|
|
|
|
|
|
|
|
xcb_free_cursor(window->conn->connection, window->cursor);
|
|
|
|
xcb_cursor_context_free(window->ctx);
|
|
|
|
xcb_flush(window->conn->connection);
|
|
|
|
|
|
|
|
kill_window(window);
|
|
|
|
handle_events_x11(window);
|
|
|
|
|
|
|
|
/* in case we're called before any events have been handled */
|
|
|
|
wl_list_for_each_safe(wstate, wstate_next,
|
|
|
|
&window->tentative_state.pending_events_list, link)
|
|
|
|
handle_event_remove_pending(wstate);
|
|
|
|
|
|
|
|
free(window);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the reply_t for an atom
|
|
|
|
*
|
|
|
|
* \param window the window in question
|
|
|
|
* \param win the handle for the window; could be different from the window itself!
|
|
|
|
* \param atom the atom in question
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
xcb_get_property_reply_t *
|
|
|
|
window_x11_dump_prop(struct window_x11 *window, xcb_drawable_t win, xcb_atom_t atom)
|
|
|
|
{
|
|
|
|
xcb_get_property_cookie_t prop_cookie;
|
|
|
|
xcb_get_property_reply_t *prop_reply;
|
|
|
|
|
|
|
|
prop_cookie = xcb_get_property(window->conn->connection, 0, win, atom,
|
|
|
|
XCB_GET_PROPERTY_TYPE_ANY, 0, 2048);
|
|
|
|
|
|
|
|
prop_reply = xcb_get_property_reply(window->conn->connection, prop_cookie, NULL);
|
|
|
|
|
|
|
|
/* callers needs to free it */
|
|
|
|
return prop_reply;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Retrieve the atoms
|
|
|
|
*
|
|
|
|
* \param win the window in question from which to retrieve the atoms
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
struct atom_x11 *
|
|
|
|
window_get_atoms(struct window_x11 *win)
|
|
|
|
{
|
|
|
|
return win->conn->atoms;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Retrive the connection_x11 from the window_x11
|
|
|
|
*
|
|
|
|
* \param win the window in question from which to retrieve the connection
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
struct xcb_connection_t *
|
|
|
|
window_get_connection(struct window_x11 *win)
|
|
|
|
{
|
|
|
|
return win->conn->connection;
|
|
|
|
}
|