]> git.sur5r.net Git - i3/i3/blob - testcases/lib/i3test/XTEST.pm
92adde42f0683af3d653132f2ae9a98af3330f71
[i3/i3] / testcases / lib / i3test / XTEST.pm
1 package i3test::XTEST;
2 # vim:ts=4:sw=4:expandtab
3
4 use strict;
5 use warnings;
6 use v5.10;
7
8 use i3test i3_autostart => 0;
9 use AnyEvent::I3;
10 use ExtUtils::PkgConfig;
11
12 use Exporter ();
13 our @EXPORT = qw(
14     inlinec_connect
15     set_xkb_group
16     xtest_key_press
17     xtest_key_release
18     xtest_button_press
19     xtest_button_release
20     listen_for_binding
21     start_binding_capture
22     binding_events
23 );
24
25 =encoding utf-8
26
27 =head1 NAME
28
29 i3test::XTEST - Inline::C wrappers for xcb-xtest and xcb-xkb
30
31 =cut
32
33 # We need to use libxcb-xkb because xdotool cannot trigger ISO_Next_Group
34 # anymore: it contains code to set the XKB group to 1 and then restore the
35 # previous group, effectively rendering any keys that switch groups
36 # ineffective.
37 my %sn_config;
38 BEGIN {
39     %sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest');
40 }
41
42 use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags};
43 use Inline C => <<'END_OF_C_CODE';
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <unistd.h>
48 #include <stdbool.h>
49 #include <stdint.h>
50
51 #include <xcb/xcb.h>
52 #include <xcb/xkb.h>
53 #include <xcb/xtest.h>
54
55 static xcb_connection_t *conn = NULL;
56
57 bool inlinec_connect() {
58     int screen;
59
60     if ((conn = xcb_connect(NULL, &screen)) == NULL ||
61         xcb_connection_has_error(conn)) {
62         if (conn != NULL) {
63             xcb_disconnect(conn);
64         }
65         fprintf(stderr, "Could not connect to X11\n");
66         return false;
67     }
68
69     if (!xcb_get_extension_data(conn, &xcb_xkb_id)->present) {
70         fprintf(stderr, "XKB not present\n");
71         return false;
72     }
73
74     if (!xcb_get_extension_data(conn, &xcb_test_id)->present) {
75         fprintf(stderr, "XTEST not present\n");
76         return false;
77     }
78
79     xcb_generic_error_t *err = NULL;
80     xcb_xkb_use_extension_reply_t *usereply;
81     usereply = xcb_xkb_use_extension_reply(
82         conn, xcb_xkb_use_extension(conn, XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION), &err);
83     if (err != NULL || usereply == NULL) {
84         fprintf(stderr, "xcb_xkb_use_extension() failed\n");
85         free(err);
86         return false;
87     }
88     free(usereply);
89
90     return true;
91 }
92
93 // NOTE: while |group| should be a uint8_t, Inline::C will not define the
94 // function unless we use an int.
95 bool set_xkb_group(int group) {
96     xcb_generic_error_t *err = NULL;
97     // Needs libxcb ≥ 1.11 so that we have the following bug fix:
98     // http://cgit.freedesktop.org/xcb/proto/commit/src/xkb.xml?id=8d7ee5b6ba4cf343f7df70372a3e1f85b82aeed7
99     xcb_void_cookie_t cookie = xcb_xkb_latch_lock_state_checked(
100         conn,
101         XCB_XKB_ID_USE_CORE_KBD, /* deviceSpec */
102         0,                       /* affectModLocks */
103         0,                       /* modLocks */
104         1,                       /* lockGroup */
105         group,                   /* groupLock */
106         0,                       /* affectModLatches */
107         0,                       /* latchGroup */
108         0);                      /* groupLatch */
109     if ((err = xcb_request_check(conn, cookie)) != NULL) {
110         fprintf(stderr, "X error code %d\n", err->error_code);
111         free(err);
112         return false;
113     }
114     return true;
115 }
116
117 bool xtest_input(int type, int detail, int x, int y) {
118     xcb_generic_error_t *err;
119     xcb_void_cookie_t cookie;
120
121     cookie = xcb_test_fake_input_checked(
122         conn,
123         type,             /* type */
124         detail,           /* detail */
125         XCB_CURRENT_TIME, /* time */
126         XCB_NONE,         /* root */
127         x,                /* rootX */
128         y,                /* rootY */
129         XCB_NONE);        /* deviceid */
130     if ((err = xcb_request_check(conn, cookie)) != NULL) {
131         fprintf(stderr, "X error code %d\n", err->error_code);
132         free(err);
133         return false;
134     }
135
136     return true;
137 }
138
139 bool xtest_key(int type, int detail) {
140     return xtest_input(type, detail, 0, 0);
141 }
142
143 bool xtest_key_press(int detail) {
144     return xtest_key(XCB_KEY_PRESS, detail);
145 }
146
147 bool xtest_key_release(int detail) {
148     return xtest_key(XCB_KEY_RELEASE, detail);
149 }
150
151 bool xtest_button_press(int button, int x, int y) {
152     return xtest_input(XCB_BUTTON_PRESS, button, x, y);
153 }
154
155 bool xtest_button_release(int button, int x, int y) {
156     return xtest_input(XCB_BUTTON_RELEASE, button, x, y);
157 }
158
159 END_OF_C_CODE
160
161 sub import {
162     my ($class, %args) = @_;
163     ok(inlinec_connect(), 'Connect to X11, verify XKB and XTEST are present (via Inline::C)');
164     goto \&Exporter::import;
165 }
166
167 =head1 EXPORT
168
169 =cut
170
171 my $i3;
172 our @binding_events;
173
174 =head2 start_binding_capture()
175
176 Captures all binding events sent by i3 in the C<@binding_events> symbol, so
177 that you can verify the correct number of binding events was generated.
178
179   my $pid = launch_with_config($config);
180   start_binding_capture;
181   # …
182   sync_with_i3;
183   is(scalar @i3test::XTEST::binding_events, 2, 'Received exactly 2 binding events');
184
185 =cut
186
187 sub start_binding_capture {
188     # Store a copy of each binding event so that we can count the expected
189     # events in test cases.
190     $i3 = i3(get_socket_path());
191     $i3->connect()->recv;
192     $i3->subscribe({
193         binding => sub {
194             my ($event) = @_;
195             @binding_events = (@binding_events, $event);
196         },
197     })->recv;
198 }
199
200 =head2 listen_for_binding($cb)
201
202 Helper function to evaluate whether sending KeyPress/KeyRelease events via
203 XTEST triggers an i3 key binding or not (with a timeout of 0.5s). Expects key
204 bindings to be configured in the form “bindsym <binding> nop <binding>”, e.g.
205 “bindsym Mod4+Return nop Mod4+Return”.
206
207   is(listen_for_binding(
208       sub {
209           xtest_key_press(133); # Super_L
210           xtest_key_press(36); # Return
211           xtest_key_release(36); # Return
212           xtest_key_release(133); # Super_L
213       },
214       ),
215      'Mod4+Return',
216      'triggered the "Mod4+Return" keybinding');
217
218 =cut
219
220 sub listen_for_binding {
221     my ($cb) = @_;
222     my $triggered = AnyEvent->condvar;
223     my $i3 = i3(get_socket_path());
224     $i3->connect()->recv;
225     $i3->subscribe({
226         binding => sub {
227             my ($event) = @_;
228             return unless $event->{change} eq 'run';
229             # We look at the command (which is “nop <binding>”) because that is
230             # easier than re-assembling the string representation of
231             # $event->{binding}.
232             $triggered->send($event->{binding}->{command});
233         },
234     })->recv;
235
236     my $t;
237     $t = AnyEvent->timer(
238         after => 0.5,
239         cb => sub {
240             $triggered->send('timeout');
241         }
242     );
243
244     $cb->();
245
246     my $recv = $triggered->recv;
247     $recv =~ s/^nop //g;
248     return $recv;
249 }
250
251 =head2 set_xkb_group($group)
252
253 Changes the current XKB group from the default of 1 to C<$group>, which must be
254 one of 1, 2, 3, 4.
255
256 Returns false when there was an X11 error changing the group, true otherwise.
257
258 =head2 xtest_key_press($detail)
259
260 Sends a KeyPress event via XTEST, with the specified C<$detail>, i.e. key code.
261 Use C<xev(1)> to find key codes.
262
263 Returns false when there was an X11 error, true otherwise.
264
265 =head2 xtest_key_release($detail)
266
267 Sends a KeyRelease event via XTEST, with the specified C<$detail>, i.e. key code.
268 Use C<xev(1)> to find key codes.
269
270 Returns false when there was an X11 error, true otherwise.
271
272 =head2 xtest_button_press($button, $x, $y)
273
274 Sends a ButtonPress event via XTEST, with the specified C<$button>.
275
276 Returns false when there was an X11 error, true otherwise.
277
278 =head2 xtest_button_release($button, $x, $y)
279
280 Sends a ButtonRelease event via XTEST, with the specified C<$button>.
281
282 Returns false when there was an X11 error, true otherwise.
283
284 =head1 AUTHOR
285
286 Michael Stapelberg <michael@i3wm.org>
287
288 =cut
289
290 1