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 struct x11_atom_cache {
128     /*
129      * Invalidate the cache based on the XCB connection.
130      * X11 atoms are actually not per connection or client, but per X server
131      * session. But better be safe just in case we survive an X server restart.
132      */
133     xcb_connection_t *conn;
134     struct {
135         xcb_atom_t from;
136         xkb_atom_t to;
137     } cache[256];
138     size_t len;
139 };
140 
141 static struct x11_atom_cache *
get_cache(struct xkb_context * ctx,xcb_connection_t * conn)142 get_cache(struct xkb_context *ctx, xcb_connection_t *conn)
143 {
144     if (!ctx->x11_atom_cache) {
145         ctx->x11_atom_cache = calloc(1, sizeof(struct x11_atom_cache));
146     }
147     /* Can be NULL in case the malloc failed. */
148     struct x11_atom_cache *cache = ctx->x11_atom_cache;
149     if (cache && cache->conn != conn) {
150         cache->conn = conn;
151         cache->len = 0;
152     }
153     return cache;
154 }
155 
156 void
x11_atom_interner_init(struct x11_atom_interner * interner,struct xkb_context * ctx,xcb_connection_t * conn)157 x11_atom_interner_init(struct x11_atom_interner *interner,
158                        struct xkb_context *ctx, xcb_connection_t *conn)
159 {
160     interner->had_error = false;
161     interner->ctx = ctx;
162     interner->conn = conn;
163     interner->num_pending = 0;
164     interner->num_copies = 0;
165     interner->num_escaped = 0;
166 }
167 
168 void
x11_atom_interner_adopt_atom(struct x11_atom_interner * interner,const xcb_atom_t atom,xkb_atom_t * out)169 x11_atom_interner_adopt_atom(struct x11_atom_interner *interner,
170                              const xcb_atom_t atom, xkb_atom_t *out)
171 {
172     *out = XKB_ATOM_NONE;
173 
174     if (atom == XCB_ATOM_NONE)
175         return;
176 
177     /* Can be NULL in case the malloc failed. */
178     struct x11_atom_cache *cache = get_cache(interner->ctx, interner->conn);
179 
180 retry:
181 
182     /* Already in the cache? */
183     if (cache) {
184         for (size_t c = 0; c < cache->len; c++) {
185             if (cache->cache[c].from == atom) {
186                 *out = cache->cache[c].to;
187                 return;
188             }
189         }
190     }
191 
192     /* Already pending? */
193     for (size_t i = 0; i < interner->num_pending; i++) {
194         if (interner->pending[i].from == atom) {
195             if (interner->num_copies == ARRAY_SIZE(interner->copies)) {
196                 x11_atom_interner_round_trip(interner);
197                 goto retry;
198             }
199 
200             size_t idx = interner->num_copies++;
201             interner->copies[idx].from = atom;
202             interner->copies[idx].out = out;
203             return;
204         }
205     }
206 
207     /* We have to send a GetAtomName request */
208     if (interner->num_pending == ARRAY_SIZE(interner->pending)) {
209         x11_atom_interner_round_trip(interner);
210         assert(interner->num_pending < ARRAY_SIZE(interner->pending));
211     }
212     size_t idx = interner->num_pending++;
213     interner->pending[idx].from = atom;
214     interner->pending[idx].out = out;
215     interner->pending[idx].cookie = xcb_get_atom_name(interner->conn, atom);
216 }
217 
218 void
x11_atom_interner_round_trip(struct x11_atom_interner * interner)219 x11_atom_interner_round_trip(struct x11_atom_interner *interner) {
220     struct xkb_context *ctx = interner->ctx;
221     xcb_connection_t *conn = interner->conn;
222 
223     /* Can be NULL in case the malloc failed. */
224     struct x11_atom_cache *cache = get_cache(ctx, conn);
225 
226     for (size_t i = 0; i < interner->num_pending; i++) {
227         xcb_get_atom_name_reply_t *reply;
228 
229         reply = xcb_get_atom_name_reply(conn, interner->pending[i].cookie, NULL);
230         if (!reply) {
231             interner->had_error = true;
232             continue;
233         }
234         xcb_atom_t x11_atom = interner->pending[i].from;
235         xkb_atom_t atom = xkb_atom_intern(ctx,
236                                           xcb_get_atom_name_name(reply),
237                                           xcb_get_atom_name_name_length(reply));
238         free(reply);
239 
240         if (cache && cache->len < ARRAY_SIZE(cache->cache)) {
241             size_t idx = cache->len++;
242             cache->cache[idx].from = x11_atom;
243             cache->cache[idx].to = atom;
244         }
245 
246         *interner->pending[i].out = atom;
247 
248         for (size_t j = 0; j < interner->num_copies; j++) {
249             if (interner->copies[j].from == x11_atom)
250                 *interner->copies[j].out = atom;
251         }
252     }
253 
254     for (size_t i = 0; i < interner->num_escaped; i++) {
255         xcb_get_atom_name_reply_t *reply;
256         int length;
257         char *name;
258         char **out = interner->escaped[i].out;
259 
260         reply = xcb_get_atom_name_reply(conn, interner->escaped[i].cookie, NULL);
261         *interner->escaped[i].out = NULL;
262         if (!reply) {
263             interner->had_error = true;
264         } else {
265             length = xcb_get_atom_name_name_length(reply);
266             name = xcb_get_atom_name_name(reply);
267 
268             *out = strndup(name, length);
269             free(reply);
270             if (*out == NULL) {
271                 interner->had_error = true;
272             } else {
273                 XkbEscapeMapName(*out);
274             }
275         }
276     }
277 
278     interner->num_pending = 0;
279     interner->num_copies = 0;
280     interner->num_escaped = 0;
281 }
282 
283 void
x11_atom_interner_get_escaped_atom_name(struct x11_atom_interner * interner,xcb_atom_t atom,char ** out)284 x11_atom_interner_get_escaped_atom_name(struct x11_atom_interner *interner,
285                                         xcb_atom_t atom, char **out)
286 {
287     if (atom == 0) {
288         *out = NULL;
289         return;
290     }
291     size_t idx = interner->num_escaped++;
292     /* There can only be a fixed number of calls to this function "in-flight",
293      * thus we assert this number. Increase the array size if this assert fails.
294      */
295     assert(idx < ARRAY_SIZE(interner->escaped));
296     interner->escaped[idx].out = out;
297     interner->escaped[idx].cookie = xcb_get_atom_name(interner->conn, atom);
298 }
299