From 6a7f9370dbff7c9147622c4a3f364b98125a08b2 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 16 Nov 2015 09:25:53 +0100 Subject: [PATCH] Add i3test::XTEST, add 2 test cases for key bindings --- .travis.yml | 2 +- testcases/lib/i3test/XTEST.pm | 258 +++++++++++++++++++++ testcases/t/257-keypress-group1-fallback.t | 96 ++++++++ testcases/t/258-keypress-release.t | 69 ++++++ 4 files changed, 424 insertions(+), 1 deletion(-) create mode 100644 testcases/lib/i3test/XTEST.pm create mode 100644 testcases/t/257-keypress-group1-fallback.t create mode 100644 testcases/t/258-keypress-release.t diff --git a/.travis.yml b/.travis.yml index dd1fb156..c4d221ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,7 +44,7 @@ before_install: install: - sudo mk-build-deps --install --remove --tool 'apt-get --no-install-recommends' debian/control # Install as many dependencies as possible via apt because cpanm is not very reliable/easy to debug. - - sudo apt-get install --no-install-recommends libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl-modules libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libtest-use-ok-perl libipc-run-perl + - sudo apt-get install --no-install-recommends libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl-modules libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libtest-use-ok-perl libipc-run-perl libxcb-xtest0-dev - sudo /bin/sh -c 'cpanm -n -v X11::XCB || true' - sudo /bin/sh -c 'cpanm -n -v AnyEvent::I3 || true' script: diff --git a/testcases/lib/i3test/XTEST.pm b/testcases/lib/i3test/XTEST.pm new file mode 100644 index 00000000..065c8a35 --- /dev/null +++ b/testcases/lib/i3test/XTEST.pm @@ -0,0 +1,258 @@ +package i3test::XTEST; +# vim:ts=4:sw=4:expandtab + +use strict; +use warnings; +use v5.10; + +use i3test i3_autostart => 0; +use AnyEvent::I3; +use ExtUtils::PkgConfig; + +use Exporter (); +our @EXPORT = qw( + inlinec_connect + set_xkb_group + xtest_key_press + xtest_key_release + listen_for_binding + start_binding_capture + binding_events +); + +=encoding utf-8 + +=head1 NAME + +i3test::XTEST - Inline::C wrappers for xcb-xtest and xcb-xkb + +=cut + +# We need to use libxcb-xkb because xdotool cannot trigger ISO_Next_Group +# anymore: it contains code to set the XKB group to 1 and then restore the +# previous group, effectively rendering any keys that switch groups +# ineffective. +my %sn_config; +BEGIN { + %sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest'); +} + +use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags}; +use Inline C => <<'END_OF_C_CODE'; +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static xcb_connection_t *conn = NULL; + +bool inlinec_connect() { + int screen; + + if ((conn = xcb_connect(NULL, &screen)) == NULL || + xcb_connection_has_error(conn)) { + fprintf(stderr, "Could not connect to X11\n"); + return false; + } + + if (!xcb_get_extension_data(conn, &xcb_xkb_id)->present) { + fprintf(stderr, "XKB not present\n"); + return false; + } + + if (!xcb_get_extension_data(conn, &xcb_test_id)->present) { + fprintf(stderr, "XTEST not present\n"); + return false; + } + + xcb_generic_error_t *err = NULL; + xcb_xkb_use_extension_reply_t *usereply; + usereply = xcb_xkb_use_extension_reply( + conn, xcb_xkb_use_extension(conn, XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION), &err); + if (err != NULL || usereply == NULL) { + fprintf(stderr, "xcb_xkb_use_extension() failed\n"); + return false; + } + free(usereply); + + return true; +} + +// NOTE: while |group| should be a uint8_t, Inline::C will not define the +// function unless we use an int. +bool set_xkb_group(int group) { + xcb_generic_error_t *err = NULL; + // Needs libxcb ≥ 1.11 so that we have the following bug fix: + // http://cgit.freedesktop.org/xcb/proto/commit/src/xkb.xml?id=8d7ee5b6ba4cf343f7df70372a3e1f85b82aeed7 + xcb_void_cookie_t cookie = xcb_xkb_latch_lock_state_checked( + conn, + XCB_XKB_ID_USE_CORE_KBD, /* deviceSpec */ + 0, /* affectModLocks */ + 0, /* modLocks */ + 1, /* lockGroup */ + group, /* groupLock */ + 0, /* affectModLatches */ + 0, /* latchGroup */ + 0); /* groupLatch */ + if ((err = xcb_request_check(conn, cookie)) != NULL) { + fprintf(stderr, "X error code %d\n", err->error_code); + return false; + } + return true; +} + +bool xtest_key(int type, int detail) { + xcb_generic_error_t *err; + xcb_void_cookie_t cookie; + + cookie = xcb_test_fake_input_checked( + conn, + type, /* type */ + detail, /* detail */ + XCB_CURRENT_TIME, /* time */ + XCB_NONE, /* root */ + 0, /* rootX */ + 0, /* rootY */ + XCB_NONE); /* deviceid */ + if ((err = xcb_request_check(conn, cookie)) != NULL) { + fprintf(stderr, "X error code %d\n", err->error_code); + return false; + } + + return true; +} + +bool xtest_key_press(int detail) { + return xtest_key(XCB_KEY_PRESS, detail); +} + +bool xtest_key_release(int detail) { + return xtest_key(XCB_KEY_RELEASE, detail); +} + +END_OF_C_CODE + +sub import { + my ($class, %args) = @_; + ok(inlinec_connect(), 'Connect to X11, verify XKB and XTEST are present (via Inline::C)'); + goto \&Exporter::import; +} + +=head1 EXPORT + +=cut + +my $i3; +our @binding_events; + +=head2 start_binding_capture() + +Captures all binding events sent by i3 in the C<@binding_events> symbol, so +that you can verify the correct number of binding events was generated. + + my $pid = launch_with_config($config); + start_binding_capture; + # … + sync_with_i3; + is(scalar @i3test::XTEST::binding_events, 2, 'Received exactly 2 binding events'); + +=cut + +sub start_binding_capture { + # Store a copy of each binding event so that we can count the expected + # events in test cases. + $i3 = i3(get_socket_path()); + $i3->connect()->recv; + $i3->subscribe({ + binding => sub { + my ($event) = @_; + @binding_events = (@binding_events, $event); + }, + })->recv; +} + +=head2 listen_for_binding($cb) + +Helper function to evaluate whether sending KeyPress/KeyRelease events via +XTEST triggers an i3 key binding or not (with a timeout of 0.5s). Expects key +bindings to be configured in the form “bindsym nop ”, e.g. +“bindsym Mod4+Return nop Mod4+Return”. + + is(listen_for_binding( + sub { + xtest_key_press(133); # Super_L + xtest_key_press(36); # Return + xtest_key_release(36); # Return + xtest_key_release(133); # Super_L + }, + ), + 'Mod4+Return', + 'triggered the "Mod4+Return" keybinding'); + +=cut + +sub listen_for_binding { + my ($cb) = @_; + my $triggered = AnyEvent->condvar; + my $i3 = i3(get_socket_path()); + $i3->connect()->recv; + $i3->subscribe({ + binding => sub { + my ($event) = @_; + return unless $event->{change} eq 'run'; + # We look at the command (which is “nop ”) because that is + # easier than re-assembling the string representation of + # $event->{binding}. + $triggered->send($event->{binding}->{command}); + }, + })->recv; + + my $t; + $t = AnyEvent->timer( + after => 0.5, + cb => sub { + $triggered->send('timeout'); + } + ); + + $cb->(); + + my $recv = $triggered->recv; + $recv =~ s/^nop //g; + return $recv; +} + +=head2 set_xkb_group($group) + +Changes the current XKB group from the default of 1 to C<$group>, which must be +one of 1, 2, 3, 4. + +Returns false when there was an X11 error changing the group, true otherwise. + +=head2 xtest_key_press($detail) + +Sends a KeyPress event via XTEST, with the specified C<$detail>, i.e. key code. +Use C to find key codes. + +Returns false when there was an X11 error changing the group, true otherwise. + +=head2 xtest_key_release($detail) + +Sends a KeyRelease event via XTEST, with the specified C<$detail>, i.e. key code. +Use C to find key codes. + +Returns false when there was an X11 error changing the group, true otherwise. + +=head1 AUTHOR + +Michael Stapelberg + +=cut + +1 diff --git a/testcases/t/257-keypress-group1-fallback.t b/testcases/t/257-keypress-group1-fallback.t new file mode 100644 index 00000000..212dfd15 --- /dev/null +++ b/testcases/t/257-keypress-group1-fallback.t @@ -0,0 +1,96 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies that when using multiple keyboard layouts at the same time, bindings +# without a specified XKB group will work in all XKB groups. +# Ticket: #2062 +# Bug still in: 4.11-103-gc8d51b4 +# Bug introduced with commit 0e5180cae9e9295678e3f053042b559e82cb8c98 +use i3test i3_autostart => 0; +use i3test::XTEST; +use ExtUtils::PkgConfig; + +SKIP: { + skip "libxcb-xkb too old (need >= 1.11)", 1 unless + ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11'); + skip "setxkbmap not found", 1 if + system(q|setxkbmap -print >/dev/null|) != 0; + +my $config = < 0; +use i3test::XTEST; +use ExtUtils::PkgConfig; + +SKIP: { + skip "libxcb-xkb too old (need >= 1.11)", 1 unless + ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11'); + +my $config = <