x11: don't iterate on empty batches
[platform/upstream/libxkbcommon.git] / src / x11 / util.c
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 "x11-priv.h"
25
26 XKB_EXPORT int
27 xkb_x11_setup_xkb_extension(xcb_connection_t *conn,
28                             uint16_t major_xkb_version,
29                             uint16_t minor_xkb_version,
30                             enum xkb_x11_setup_xkb_extension_flags flags,
31                             uint16_t *major_xkb_version_out,
32                             uint16_t *minor_xkb_version_out,
33                             uint8_t *base_event_out,
34                             uint8_t *base_error_out)
35 {
36     uint8_t base_event, base_error;
37     uint16_t server_major, server_minor;
38
39     if (flags & ~(XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS)) {
40         /* log_err_func(ctx, "unrecognized flags: %#x\n", flags); */
41         return 0;
42     }
43
44     {
45         const xcb_query_extension_reply_t *reply =
46             xcb_get_extension_data(conn, &xcb_xkb_id);
47         if (!reply) {
48             /* log_err_func(ctx, "failed to query for XKB extension\n"); */
49             return 0;
50         }
51
52         if (!reply->present) {
53             /* log_err_func(ctx, "failed to start using XKB extension: not available in server\n"); */
54             return 0;
55         }
56
57         base_event = reply->first_event;
58         base_error = reply->first_error;
59     }
60
61     {
62         xcb_generic_error_t *error = NULL;
63         xcb_xkb_use_extension_cookie_t cookie =
64             xcb_xkb_use_extension(conn, major_xkb_version, minor_xkb_version);
65         xcb_xkb_use_extension_reply_t *reply =
66             xcb_xkb_use_extension_reply(conn, cookie, &error);
67
68         if (!reply) {
69             /* log_err_func(ctx, */
70             /*              "failed to start using XKB extension: error code %d\n", */
71             /*              error ? error->error_code : -1); */
72             free(error);
73             return 0;
74         }
75
76         if (!reply->supported) {
77             /* log_err_func(ctx, */
78             /*              "failed to start using XKB extension: server doesn't support version %d.%d\n", */
79             /*              major_xkb_version, minor_xkb_version); */
80             free(reply);
81             return 0;
82         }
83
84         server_major = reply->serverMajor;
85         server_minor = reply->serverMinor;
86
87         free(reply);
88     }
89
90     /*
91     * The XkbUseExtension() in libX11 has a *bunch* of legacy stuff, but
92     * it doesn't seem like any of it is useful to us.
93     */
94
95     if (major_xkb_version_out)
96         *major_xkb_version_out = server_major;
97     if (minor_xkb_version_out)
98         *minor_xkb_version_out = server_minor;
99     if (base_event_out)
100         *base_event_out = base_event;
101     if (base_error_out)
102         *base_error_out = base_error;
103
104     return 1;
105 }
106
107 XKB_EXPORT int32_t
108 xkb_x11_get_core_keyboard_device_id(xcb_connection_t *conn)
109 {
110     int32_t device_id;
111     xcb_xkb_get_device_info_cookie_t cookie =
112         xcb_xkb_get_device_info(conn, XCB_XKB_ID_USE_CORE_KBD,
113                                 0, 0, 0, 0, 0, 0);
114     xcb_xkb_get_device_info_reply_t *reply =
115         xcb_xkb_get_device_info_reply(conn, cookie, NULL);
116
117     if (!reply)
118         return -1;
119
120     device_id = reply->deviceID;
121     free(reply);
122     return device_id;
123 }
124
125 bool
126 get_atom_name(xcb_connection_t *conn, xcb_atom_t atom, char **out)
127 {
128     xcb_get_atom_name_cookie_t cookie;
129     xcb_get_atom_name_reply_t *reply;
130     int length;
131     char *name;
132
133     if (atom == 0) {
134         *out = NULL;
135         return true;
136     }
137
138     cookie = xcb_get_atom_name(conn, atom);
139     reply = xcb_get_atom_name_reply(conn, cookie, NULL);
140     if (!reply)
141         return false;
142
143     length = xcb_get_atom_name_name_length(reply);
144     name = xcb_get_atom_name_name(reply);
145
146     *out = strndup(name, length);
147     if (!*out) {
148         free(reply);
149         return false;
150     }
151
152     free(reply);
153     return true;
154 }
155
156 bool
157 adopt_atoms(struct xkb_context *ctx, xcb_connection_t *conn,
158             const xcb_atom_t *from, xkb_atom_t *to, const size_t count)
159 {
160     enum { SIZE = 128 };
161     xcb_get_atom_name_cookie_t cookies[SIZE];
162     const size_t num_batches = ROUNDUP(count, SIZE) / SIZE;
163
164     /* Send and collect the atoms in batches of reasonable SIZE. */
165     for (size_t batch = 0; batch < num_batches; batch++) {
166         const size_t start = batch * SIZE;
167         const size_t stop = min((batch + 1) * SIZE, count);
168
169         /* Send. */
170         for (size_t i = start; i < stop; i++)
171             if (from[i] != XCB_ATOM_NONE)
172                 cookies[i % SIZE] = xcb_get_atom_name(conn, from[i]);
173
174         /* Collect. */
175         for (size_t i = start; i < stop; i++) {
176             xcb_get_atom_name_reply_t *reply;
177
178             if (from[i] == XCB_ATOM_NONE) {
179                 to[i] = XKB_ATOM_NONE;
180                 continue;
181             }
182
183             reply = xcb_get_atom_name_reply(conn, cookies[i % SIZE], NULL);
184             if (!reply)
185                 goto err_discard;
186
187             to[i] = xkb_atom_intern(ctx,
188                                     xcb_get_atom_name_name(reply),
189                                     xcb_get_atom_name_name_length(reply));
190             free(reply);
191
192             if (to[i] == XKB_ATOM_NONE)
193                 goto err_discard;
194
195             continue;
196
197             /*
198              * If we don't discard the uncollected replies, they just
199              * sit in the XCB queue waiting forever. Sad.
200              */
201 err_discard:
202             for (size_t j = i + 1; j < stop; j++)
203                 if (from[j] != XCB_ATOM_NONE)
204                     xcb_discard_reply(conn, cookies[j % SIZE].sequence);
205             return false;
206         }
207     }
208
209     return true;
210 }
211
212 bool
213 adopt_atom(struct xkb_context *ctx, xcb_connection_t *conn, xcb_atom_t atom,
214            xkb_atom_t *out)
215 {
216     return adopt_atoms(ctx, conn, &atom, out, 1);
217 }