/* * Copyright © 2013 Ran Benita * * 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 "x11-priv.h" XKB_EXPORT int xkb_x11_setup_xkb_extension(xcb_connection_t *conn, uint16_t major_xkb_version, uint16_t minor_xkb_version, enum xkb_x11_setup_xkb_extension_flags flags, uint16_t *major_xkb_version_out, uint16_t *minor_xkb_version_out, uint8_t *base_event_out, uint8_t *base_error_out) { uint8_t base_event, base_error; uint16_t server_major, server_minor; if (flags & ~(XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS)) { /* log_err_func(ctx, "unrecognized flags: %#x\n", flags); */ return 0; } { const xcb_query_extension_reply_t *reply = xcb_get_extension_data(conn, &xcb_xkb_id); if (!reply) { /* log_err_func(ctx, "failed to query for XKB extension\n"); */ return 0; } if (!reply->present) { /* log_err_func(ctx, "failed to start using XKB extension: not available in server\n"); */ return 0; } base_event = reply->first_event; base_error = reply->first_error; } { xcb_generic_error_t *error = NULL; xcb_xkb_use_extension_cookie_t cookie = xcb_xkb_use_extension(conn, major_xkb_version, minor_xkb_version); xcb_xkb_use_extension_reply_t *reply = xcb_xkb_use_extension_reply(conn, cookie, &error); if (!reply) { /* log_err_func(ctx, */ /* "failed to start using XKB extension: error code %d\n", */ /* error ? error->error_code : -1); */ free(error); return 0; } if (!reply->supported) { /* log_err_func(ctx, */ /* "failed to start using XKB extension: server doesn't support version %d.%d\n", */ /* major_xkb_version, minor_xkb_version); */ free(reply); return 0; } server_major = reply->serverMajor; server_minor = reply->serverMinor; free(reply); } /* * The XkbUseExtension() in libX11 has a *bunch* of legacy stuff, but * it doesn't seem like any of it is useful to us. */ if (major_xkb_version_out) *major_xkb_version_out = server_major; if (minor_xkb_version_out) *minor_xkb_version_out = server_minor; if (base_event_out) *base_event_out = base_event; if (base_error_out) *base_error_out = base_error; return 1; } XKB_EXPORT int32_t xkb_x11_get_core_keyboard_device_id(xcb_connection_t *conn) { int32_t device_id; xcb_xkb_get_device_info_cookie_t cookie = xcb_xkb_get_device_info(conn, XCB_XKB_ID_USE_CORE_KBD, 0, 0, 0, 0, 0, 0); xcb_xkb_get_device_info_reply_t *reply = xcb_xkb_get_device_info_reply(conn, cookie, NULL); if (!reply) return -1; device_id = reply->deviceID; free(reply); return device_id; } struct x11_atom_cache { /* * Invalidate the cache based on the XCB connection. * X11 atoms are actually not per connection or client, but per X server * session. But better be safe just in case we survive an X server restart. */ xcb_connection_t *conn; struct { xcb_atom_t from; xkb_atom_t to; } cache[256]; size_t len; }; static struct x11_atom_cache * get_cache(struct xkb_context *ctx, xcb_connection_t *conn) { if (!ctx->x11_atom_cache) { ctx->x11_atom_cache = calloc(1, sizeof(struct x11_atom_cache)); } /* Can be NULL in case the malloc failed. */ struct x11_atom_cache *cache = ctx->x11_atom_cache; if (cache && cache->conn != conn) { cache->conn = conn; cache->len = 0; } return cache; } void x11_atom_interner_init(struct x11_atom_interner *interner, struct xkb_context *ctx, xcb_connection_t *conn) { interner->had_error = false; interner->ctx = ctx; interner->conn = conn; interner->num_pending = 0; interner->num_copies = 0; interner->num_escaped = 0; } void x11_atom_interner_adopt_atom(struct x11_atom_interner *interner, const xcb_atom_t atom, xkb_atom_t *out) { *out = 0; /* Can be NULL in case the malloc failed. */ struct x11_atom_cache *cache = get_cache(interner->ctx, interner->conn); retry: /* Already in the cache? */ if (cache) { for (size_t c = 0; c < cache->len; c++) { if (cache->cache[c].from == atom) { *out = cache->cache[c].to; return; } } } /* Already pending? */ for (size_t i = 0; i < interner->num_pending; i++) { if (interner->pending[i].from == atom) { if (interner->num_copies == ARRAY_SIZE(interner->copies)) { x11_atom_interner_round_trip(interner); goto retry; } size_t idx = interner->num_copies++; interner->copies[idx].from = atom; interner->copies[idx].out = out; return; } } /* We have to send a GetAtomName request */ if (interner->num_pending == ARRAY_SIZE(interner->pending)) { x11_atom_interner_round_trip(interner); assert(interner->num_pending < ARRAY_SIZE(interner->pending)); } size_t idx = interner->num_pending++; interner->pending[idx].from = atom; interner->pending[idx].out = out; interner->pending[idx].cookie = xcb_get_atom_name(interner->conn, atom); } void x11_atom_interner_adopt_atoms(struct x11_atom_interner *interner, const xcb_atom_t *from, xkb_atom_t *to, size_t count) { for (size_t i = 0; i < count; i++) { x11_atom_interner_adopt_atom(interner, from[i], &to[i]); } } void x11_atom_interner_round_trip(struct x11_atom_interner *interner) { struct xkb_context *ctx = interner->ctx; xcb_connection_t *conn = interner->conn; /* Can be NULL in case the malloc failed. */ struct x11_atom_cache *cache = get_cache(ctx, conn); for (size_t i = 0; i < interner->num_pending; i++) { xcb_get_atom_name_reply_t *reply; reply = xcb_get_atom_name_reply(conn, interner->pending[i].cookie, NULL); if (!reply) { interner->had_error = true; continue; } xcb_atom_t x11_atom = interner->pending[i].from; xkb_atom_t atom = xkb_atom_intern(ctx, xcb_get_atom_name_name(reply), xcb_get_atom_name_name_length(reply)); free(reply); if (cache && cache->len < ARRAY_SIZE(cache->cache)) { size_t idx = cache->len++; cache->cache[idx].from = x11_atom; cache->cache[idx].to = atom; } *interner->pending[i].out = atom; for (size_t j = 0; j < interner->num_copies; j++) { if (interner->copies[j].from == x11_atom) *interner->copies[j].out = atom; } } for (size_t i = 0; i < interner->num_escaped; i++) { xcb_get_atom_name_reply_t *reply; int length; char *name; char **out = interner->escaped[i].out; reply = xcb_get_atom_name_reply(conn, interner->escaped[i].cookie, NULL); *interner->escaped[i].out = NULL; if (!reply) { interner->had_error = true; } else { length = xcb_get_atom_name_name_length(reply); name = xcb_get_atom_name_name(reply); *out = strndup(name, length); free(reply); if (*out == NULL) { interner->had_error = true; } else { XkbEscapeMapName(*out); } } } interner->num_pending = 0; interner->num_copies = 0; interner->num_escaped = 0; } void x11_atom_interner_get_escaped_atom_name(struct x11_atom_interner *interner, xcb_atom_t atom, char **out) { if (atom == 0) { *out = NULL; return; } size_t idx = interner->num_escaped++; /* There can only be a fixed number of calls to this function "in-flight", * thus we assert this number. Increase the array size if this assert fails. */ assert(idx < ARRAY_SIZE(interner->escaped)); interner->escaped[idx].out = out; interner->escaped[idx].cookie = xcb_get_atom_name(interner->conn, atom); }