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