1 /*
2 * Copyright © 2013 Ran Benita
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 */
23
24 #include "config.h"
25
26 #include "x11-priv.h"
27
28 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)29 xkb_x11_setup_xkb_extension(xcb_connection_t *conn,
30 uint16_t major_xkb_version,
31 uint16_t minor_xkb_version,
32 enum xkb_x11_setup_xkb_extension_flags flags,
33 uint16_t *major_xkb_version_out,
34 uint16_t *minor_xkb_version_out,
35 uint8_t *base_event_out,
36 uint8_t *base_error_out)
37 {
38 uint8_t base_event, base_error;
39 uint16_t server_major, server_minor;
40
41 if (flags & ~(XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS)) {
42 /* log_err_func(ctx, "unrecognized flags: %#x\n", flags); */
43 return 0;
44 }
45
46 {
47 const xcb_query_extension_reply_t *reply =
48 xcb_get_extension_data(conn, &xcb_xkb_id);
49 if (!reply) {
50 /* log_err_func(ctx, "failed to query for XKB extension\n"); */
51 return 0;
52 }
53
54 if (!reply->present) {
55 /* log_err_func(ctx, "failed to start using XKB extension: not available in server\n"); */
56 return 0;
57 }
58
59 base_event = reply->first_event;
60 base_error = reply->first_error;
61 }
62
63 {
64 xcb_generic_error_t *error = NULL;
65 xcb_xkb_use_extension_cookie_t cookie =
66 xcb_xkb_use_extension(conn, major_xkb_version, minor_xkb_version);
67 xcb_xkb_use_extension_reply_t *reply =
68 xcb_xkb_use_extension_reply(conn, cookie, &error);
69
70 if (!reply) {
71 /* log_err_func(ctx, */
72 /* "failed to start using XKB extension: error code %d\n", */
73 /* error ? error->error_code : -1); */
74 free(error);
75 return 0;
76 }
77
78 if (!reply->supported) {
79 /* log_err_func(ctx, */
80 /* "failed to start using XKB extension: server doesn't support version %d.%d\n", */
81 /* major_xkb_version, minor_xkb_version); */
82 free(reply);
83 return 0;
84 }
85
86 server_major = reply->serverMajor;
87 server_minor = reply->serverMinor;
88
89 free(reply);
90 }
91
92 /*
93 * The XkbUseExtension() in libX11 has a *bunch* of legacy stuff, but
94 * it doesn't seem like any of it is useful to us.
95 */
96
97 if (major_xkb_version_out)
98 *major_xkb_version_out = server_major;
99 if (minor_xkb_version_out)
100 *minor_xkb_version_out = server_minor;
101 if (base_event_out)
102 *base_event_out = base_event;
103 if (base_error_out)
104 *base_error_out = base_error;
105
106 return 1;
107 }
108
109 XKB_EXPORT int32_t
xkb_x11_get_core_keyboard_device_id(xcb_connection_t * conn)110 xkb_x11_get_core_keyboard_device_id(xcb_connection_t *conn)
111 {
112 int32_t device_id;
113 xcb_xkb_get_device_info_cookie_t cookie =
114 xcb_xkb_get_device_info(conn, XCB_XKB_ID_USE_CORE_KBD,
115 0, 0, 0, 0, 0, 0);
116 xcb_xkb_get_device_info_reply_t *reply =
117 xcb_xkb_get_device_info_reply(conn, cookie, NULL);
118
119 if (!reply)
120 return -1;
121
122 device_id = reply->deviceID;
123 free(reply);
124 return device_id;
125 }
126
127 bool
get_atom_name(xcb_connection_t * conn,xcb_atom_t atom,char ** out)128 get_atom_name(xcb_connection_t *conn, xcb_atom_t atom, char **out)
129 {
130 xcb_get_atom_name_cookie_t cookie;
131 xcb_get_atom_name_reply_t *reply;
132 int length;
133 char *name;
134
135 if (atom == 0) {
136 *out = NULL;
137 return true;
138 }
139
140 cookie = xcb_get_atom_name(conn, atom);
141 reply = xcb_get_atom_name_reply(conn, cookie, NULL);
142 if (!reply)
143 return false;
144
145 length = xcb_get_atom_name_name_length(reply);
146 name = xcb_get_atom_name_name(reply);
147
148 *out = strndup(name, length);
149 if (!*out) {
150 free(reply);
151 return false;
152 }
153
154 free(reply);
155 return true;
156 }
157
158 struct x11_atom_cache {
159 /*
160 * Invalidate the cache based on the XCB connection.
161 * X11 atoms are actually not per connection or client, but per X server
162 * session. But better be safe just in case we survive an X server restart.
163 */
164 xcb_connection_t *conn;
165 struct {
166 xcb_atom_t from;
167 xkb_atom_t to;
168 } cache[256];
169 size_t len;
170 };
171
172 bool
adopt_atoms(struct xkb_context * ctx,xcb_connection_t * conn,const xcb_atom_t * from,xkb_atom_t * to,const size_t count)173 adopt_atoms(struct xkb_context *ctx, xcb_connection_t *conn,
174 const xcb_atom_t *from, xkb_atom_t *to, const size_t count)
175 {
176 enum { SIZE = 128 };
177 xcb_get_atom_name_cookie_t cookies[SIZE];
178 const size_t num_batches = ROUNDUP(count, SIZE) / SIZE;
179
180 if (!ctx->x11_atom_cache) {
181 ctx->x11_atom_cache = calloc(1, sizeof(struct x11_atom_cache));
182 }
183 /* Can be NULL in case the malloc failed. */
184 struct x11_atom_cache *cache = ctx->x11_atom_cache;
185 if (cache && cache->conn != conn) {
186 cache->conn = conn;
187 cache->len = 0;
188 }
189
190 memset(to, 0, count * sizeof(*to));
191
192 /* Send and collect the atoms in batches of reasonable SIZE. */
193 for (size_t batch = 0; batch < num_batches; batch++) {
194 const size_t start = batch * SIZE;
195 const size_t stop = MIN((batch + 1) * SIZE, count);
196
197 /* Send. */
198 for (size_t i = start; i < stop; i++) {
199 bool cache_hit = false;
200 if (cache) {
201 for (size_t c = 0; c < cache->len; c++) {
202 if (cache->cache[c].from == from[i]) {
203 to[i] = cache->cache[c].to;
204 cache_hit = true;
205 break;
206 }
207 }
208 }
209 if (!cache_hit && from[i] != XCB_ATOM_NONE)
210 cookies[i % SIZE] = xcb_get_atom_name(conn, from[i]);
211 }
212
213 /* Collect. */
214 for (size_t i = start; i < stop; i++) {
215 xcb_get_atom_name_reply_t *reply;
216
217 if (from[i] == XCB_ATOM_NONE)
218 continue;
219
220 /* Was filled from cache. */
221 if (to[i] != 0)
222 continue;
223
224 reply = xcb_get_atom_name_reply(conn, cookies[i % SIZE], NULL);
225 if (!reply)
226 goto err_discard;
227
228 to[i] = xkb_atom_intern(ctx,
229 xcb_get_atom_name_name(reply),
230 xcb_get_atom_name_name_length(reply));
231 free(reply);
232
233 if (to[i] == XKB_ATOM_NONE)
234 goto err_discard;
235
236 if (cache && cache->len < ARRAY_SIZE(cache->cache)) {
237 size_t idx = cache->len++;
238 cache->cache[idx].from = from[i];
239 cache->cache[idx].to = to[i];
240 }
241
242 continue;
243
244 /*
245 * If we don't discard the uncollected replies, they just
246 * sit in the XCB queue waiting forever. Sad.
247 */
248 err_discard:
249 for (size_t j = i + 1; j < stop; j++)
250 if (from[j] != XCB_ATOM_NONE)
251 xcb_discard_reply(conn, cookies[j % SIZE].sequence);
252 return false;
253 }
254 }
255
256 return true;
257 }
258
259 bool
adopt_atom(struct xkb_context * ctx,xcb_connection_t * conn,xcb_atom_t atom,xkb_atom_t * out)260 adopt_atom(struct xkb_context *ctx, xcb_connection_t *conn, xcb_atom_t atom,
261 xkb_atom_t *out)
262 {
263 return adopt_atoms(ctx, conn, &atom, out, 1);
264 }
265