]> git.sur5r.net Git - i3/i3/commitdiff
Merge pull request #2980 from orestisf1993/issue-1160-pr
authorIngo Bürk <admin@airblader.de>
Sun, 5 Nov 2017 16:42:31 +0000 (17:42 +0100)
committerGitHub <noreply@github.com>
Sun, 5 Nov 2017 16:42:31 +0000 (17:42 +0100)
Use con_descend_focused for workspaces in _tree_next

55 files changed:
AnyEvent-I3/lib/AnyEvent/I3.pm
contrib/dump-asy.pl
contrib/per-workspace-layout.pl
docs/debugging
docs/ipc
docs/testsuite
docs/userguide
etc/config
i3-msg/main.c
i3bar/include/xcb_atoms.def
i3bar/src/xcb.c
include/all.h
include/commands.h
include/i3.h
include/i3/ipc.h
include/ipc.h
parser-specs/commands.spec
src/commands.c
src/con.c
src/config_parser.c
src/floating.c
src/handlers.c
src/ipc.c
src/main.c
src/restore_layout.c
src/util.c
testcases/complete-run.pl.in
testcases/lib/i3test.pm.in
testcases/lib/i3test/XTEST.pm
testcases/t/115-ipc-workspaces.t
testcases/t/167-workspace_layout.t
testcases/t/199-ipc-mode-event.t
testcases/t/205-ipc-windows.t
testcases/t/219-ipc-window-focus.t
testcases/t/220-ipc-window-title.t
testcases/t/225-ipc-window-fullscreen.t
testcases/t/227-ipc-workspace-empty.t
testcases/t/231-ipc-floating-event.t
testcases/t/238-ipc-binding-event.t
testcases/t/252-floating-size.t
testcases/t/257-keypress-group1-fallback.t
testcases/t/258-keypress-release.t
testcases/t/263-config-reload-reverts-bind-mode.t
testcases/t/265-ipc-mark.t
testcases/t/268-ipc-config.t
testcases/t/275-ipc-window-close.t
testcases/t/276-ipc-window-move.t
testcases/t/277-ipc-window-urgent.t
testcases/t/286-root-window-mouse-binding.t
testcases/t/289-ipc-shutdown-event.t
testcases/t/290-keypress-numlock.t
testcases/t/514-ipc-workspace-multi-monitor.t
testcases/t/517-regress-move-direction-ipc.t
testcases/t/525-i3bar-mouse-bindings.t
travis/run-tests.sh

index 08b1c0a7165bc1290800b8b2bd9a3e361077adc5..198c41c9a5eef1164a128150ec26496783134e69 100644 (file)
@@ -99,11 +99,12 @@ use constant TYPE_GET_BAR_CONFIG => 6;
 use constant TYPE_GET_VERSION => 7;
 use constant TYPE_GET_BINDING_MODES => 8;
 use constant TYPE_GET_CONFIG => 9;
+use constant TYPE_SEND_TICK => 10;
 
 our %EXPORT_TAGS = ( 'all' => [
     qw(i3 TYPE_RUN_COMMAND TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS
        TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION
-       TYPE_GET_BINDING_MODES TYPE_GET_CONFIG)
+       TYPE_GET_BINDING_MODES TYPE_GET_CONFIG TYPE_SEND_TICK)
 ] );
 
 our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
@@ -120,6 +121,7 @@ my %events = (
     barconfig_update => ($event_mask | 4),
     binding => ($event_mask | 5),
     shutdown => ($event_mask | 6),
+    tick => ($event_mask | 7),
     _error => 0xFFFFFFFF,
 );
 
@@ -519,6 +521,18 @@ sub get_config {
     $self->message(TYPE_GET_CONFIG);
 }
 
+=head2 send_tick
+
+Sends a tick event. Requires i3 >= 4.15
+
+=cut
+sub send_tick {
+    my ($self, $payload) = @_;
+
+    $self->_ensure_connection;
+
+    $self->message(TYPE_SEND_TICK, $payload);
+}
 
 =head2 command($content)
 
index 478a896a5c4b403c36962a6ad675c670f9351608..4fcf9b77b904f5a4daba5cdc15a50573b2a3aa70 100755 (executable)
@@ -39,7 +39,7 @@ sub dump_node {
     $na =~ s/~/\\textasciitilde{}/g;
     my $type = 'leaf';
     if (!defined($n->{window})) {
-        $type = $n->{orientation} . '-split';
+        $type = $n->{layout};
     }
     my $name = qq|``$na'' ($type)|;
 
index 48590456af74561868a9225e8b3f368b4fcba7fc..4a2b4b9e2745e01edec43c87d6c3b2b4a21428be 100644 (file)
@@ -14,6 +14,7 @@ use warnings;
 use AnyEvent;
 use AnyEvent::I3;
 use v5.10;
+use utf8;
 
 my %layouts = (
     '4' => 'tabbed',
index 07bc13a04a0f51bcc5b804212de99c9d5e475ec3..dd26f98d924b3783a79a09fb9b0e64ce15a25d94 100644 (file)
@@ -153,7 +153,7 @@ When sending bug reports, please attach the *whole* log file. Even if you think
 you found the section which clearly highlights the problem, additional
 information might be necessary to completely diagnose the problem.
 
-When debugging with us in IRC, be prepared to use a so called nopaste service
+When debugging with us in IRC, be prepared to use a so-called nopaste service
 such as https://pastebin.com because pasting large amounts of text in IRC
 sometimes leads to incomplete lines (servers have line length limitations) or
 flood kicks.
index 997a3055fb1c22b5393e4409d8fc18e450f08372..8b767adebffa127bab432c2c9607b57ce491567b 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -64,6 +64,7 @@ to do that).
 | 7 | +GET_VERSION+ | <<_version_reply,VERSION>> | Gets the i3 version.
 | 8 | +GET_BINDING_MODES+ | <<_binding_modes_reply,BINDING_MODES>> | Gets the names of all currently configured binding modes.
 | 9 | +GET_CONFIG+ | <<_config_reply,CONFIG>> | Returns the last loaded i3 config.
+| 10 | +SEND_TICK+ | <<_tick_reply,TICK>> | Sends a tick event with the specified payload.
 |======================================================
 
 So, a typical message could look like this:
@@ -126,6 +127,8 @@ BINDING_MODES (8)::
         Reply to the GET_BINDING_MODES message.
 GET_CONFIG (9)::
        Reply to the GET_CONFIG message.
+TICK (10)::
+       Reply to the SEND_TICK message.
 
 [[_command_reply]]
 === COMMAND reply
@@ -637,6 +640,19 @@ which is a string containing the config file as loaded by i3 most recently.
 { "config": "font pango:monospace 8\nbindsym Mod4+q exit\n" }
 -------------------
 
+[[_tick_reply]]
+=== TICK reply
+
+The reply is a map containing the "success" member. After the reply was
+received, the tick event has been written to all IPC connections which subscribe
+to tick events. UNIX sockets are usually buffered, but you can be certain that
+once you receive the tick event you just triggered, you must have received all
+events generated prior to the +SEND_TICK+ message (happened-before relation).
+
+*Example:*
+-------------------
+{ "success": true }
+-------------------
 
 == Events
 
@@ -694,6 +710,10 @@ binding (5)::
        mouse
 shutdown (6)::
        Sent when the ipc shuts down because of a restart or exit by user command
+tick (7)::
+       Sent when the ipc client subscribes to the tick event (with +"first":
+       true+) or when any ipc client sends a SEND_TICK message (with +"first":
+       false+).
 
 *Example:*
 --------------------------------------------------------------------
@@ -866,6 +886,27 @@ because of a user action such as a +restart+ or +exit+ command. The +change
 }
 ---------------------------
 
+=== tick event
+
+This event is triggered by a subscription to tick events or by a +SEND_TICK+
+message.
+
+*Example (upon subscription):*
+--------------------------------------------------------------------------------
+{
+ "first": true,
+ "payload": ""
+}
+--------------------------------------------------------------------------------
+
+*Example (upon +SEND_TICK+ with a payload of +arbitrary string+):*
+--------------------------------------------------------------------------------
+{
+ "first": false,
+ "payload": "arbitrary string"
+}
+--------------------------------------------------------------------------------
+
 == See also (existing libraries)
 
 [[libraries]]
@@ -881,6 +922,7 @@ C++::
        * https://github.com/drmgc/i3ipcpp
 Go::
        * https://github.com/mdirkse/i3ipc-go
+       * https://github.com/i3/go-i3
 JavaScript::
        * https://github.com/acrisci/i3ipc-gjs
 Lua::
@@ -958,3 +1000,6 @@ detect the byte order i3 is using:
      payload. Then, receive the pending +COMMAND+ message reply in big endian.
 
 5. From here on out, send/receive all messages using the detected byte order.
+
+Find an example implementation of this technique in
+https://github.com/i3/go-i3/blob/master/byteorder.go
index bf85cb1fbe34411cffa0c19b71d79dd4b6ea6193..b535e7c141ca994b9f453771097868e9150c8104 100644 (file)
@@ -113,10 +113,8 @@ containing the appropriate i3 logfile for each testcase. The latest folder can
 always be found under the symlink +latest/+. Unless told differently, it will
 run the tests on a separate X server instance (using Xephyr).
 
-Xephyr will open a window where you can inspect the running test. You can run
-the tests without an X session with Xvfb, such as with +xvfb-run
-./complete-run+. This will also speed up the tests significantly especially on
-machines without a powerful video card.
+Xephyr will open a window where you can inspect the running test. By default,
+tests are run under Xvfb.
 
 .Example invocation of +complete-run.pl+
 ---------------------------------------
index 5225c39149539342158c0774b93f54922419bc1d..4fec6cdaf1ad742a357946c329c951f048f96935 100644 (file)
@@ -11,7 +11,7 @@ mailing list.
 == Default keybindings
 
 For the "too long; didn’t read" people, here is an overview of the default
-keybindings (click to see the full size image):
+keybindings (click to see the full-size image):
 
 *Keys to use with $mod (Alt):*
 
@@ -35,7 +35,8 @@ above, just decline i3-config-wizard’s offer and base your config on
 
 Throughout this guide, the keyword +$mod+ will be used to refer to the
 configured modifier. This is the Alt key (+Mod1+) by default, with the Windows
-key (+Mod4+) being a popular alternative.
+key (+Mod4+) being a popular alternative that largely prevents conflicts with
+application-defined shortcuts.
 
 === Opening terminals and moving around
 
@@ -196,7 +197,7 @@ out to be complicated to use (snapping), understand and implement.
 
 === The tree consists of Containers
 
-The building blocks of our tree are so called +Containers+. A +Container+ can
+The building blocks of our tree are so-called +Containers+. A +Container+ can
 host a window (meaning an X11 window, one that you can actually see and use,
 like a browser). Alternatively, it could contain one or more +Containers+. A
 simple example is the workspace: When you start i3 with a single monitor, a
@@ -509,7 +510,7 @@ mode "$mode_launcher" {
 === The floating modifier
 
 To move floating windows with your mouse, you can either grab their titlebar
-or configure the so called floating modifier which you can then press and
+or configure the so-called floating modifier which you can then press and
 click anywhere in the window itself to move it. The most common setup is to
 use the same key you use for managing windows (Mod1 for example). Then
 you can press Mod1, click into a window using your left mouse button, and drag
@@ -2301,7 +2302,7 @@ If you want to resize containers/windows using your keyboard, you can use the
 *Syntax*:
 -------------------------------------------------------
 resize grow|shrink <direction> [<px> px [or <ppt> ppt]]
-resize set <width> [px] <height> [px]
+resize set <width> [px | ppt] <height> [px | ppt]
 -------------------------------------------------------
 
 Direction can either be one of +up+, +down+, +left+ or +right+. Or you can be
index 483694c1c78d6d5c26661a0990f7b492cfb39e92..65e36b808339625f603c51d2f9b3b69df2f97300 100644 (file)
@@ -22,7 +22,7 @@ font pango:monospace 8
 # The font above is very space-efficient, that is, it looks good, sharp and
 # clear in small sizes. However, its unicode glyph coverage is limited, the old
 # X core fonts rendering does not support right-to-left and this being a bitmap
-# font, it doesnt scale on retina/hidpi displays.
+# font, it doesn't scale on retina/hidpi displays.
 
 # use these keys for focus, movement, and resize directions when reaching for
 # the arrows is not convenient
index 8907a6f7adf80cf68729983d264748505040b732..a4f948500f16c0749b2548704c5d3289bc9e9fda 100644 (file)
@@ -207,9 +207,11 @@ int main(int argc, char *argv[]) {
                 message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION;
             } else if (strcasecmp(optarg, "get_config") == 0) {
                 message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG;
+            } else if (strcasecmp(optarg, "send_tick") == 0) {
+                message_type = I3_IPC_MESSAGE_TYPE_SEND_TICK;
             } else {
                 printf("Unknown message type\n");
-                printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config\n");
+                printf("Known types: run_command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config, send_tick\n");
                 exit(EXIT_FAILURE);
             }
         } else if (o == 'q') {
index 65a147c4718aeb44fcd57d5580cca4c55925dd1a..453dd0ceb3eb9f90dc6e09b10607ca557795a521 100644 (file)
@@ -9,4 +9,5 @@ ATOM_DO(_NET_SYSTEM_TRAY_OPCODE)
 ATOM_DO(_NET_SYSTEM_TRAY_COLORS)
 ATOM_DO(_XEMBED_INFO)
 ATOM_DO(_XEMBED)
+ATOM_DO(I3_SYNC)
 #undef ATOM_DO
index fed969df7823ed34266bd1bb3c10b9071f67f2ae..98f0dcbe02296fdb49ee5af8495d17940871022f 100644 (file)
@@ -83,7 +83,6 @@ int mod_pressed = 0;
 
 /* Event watchers, to interact with the user */
 ev_prepare *xcb_prep;
-ev_check *xcb_chk;
 ev_io *xcb_io;
 ev_io *xkb_io;
 
@@ -678,8 +677,26 @@ static void configure_trayclients(void) {
  *
  */
 static void handle_client_message(xcb_client_message_event_t *event) {
-    if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
-        event->format == 32) {
+    if (event->type == atoms[I3_SYNC]) {
+        xcb_window_t window = event->data.data32[0];
+        uint32_t rnd = event->data.data32[1];
+        DLOG("[i3 sync protocol] Forwarding random value %d, X11 window 0x%08x to i3\n", rnd, window);
+
+        void *reply = scalloc(32, 1);
+        xcb_client_message_event_t *ev = reply;
+
+        ev->response_type = XCB_CLIENT_MESSAGE;
+        ev->window = window;
+        ev->type = atoms[I3_SYNC];
+        ev->format = 32;
+        ev->data.data32[0] = window;
+        ev->data.data32[1] = rnd;
+
+        xcb_send_event(conn, false, xcb_root, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)ev);
+        xcb_flush(conn);
+        free(reply);
+    } else if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] &&
+               event->format == 32) {
         DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
         /* event->data.data32[0] is the timestamp */
         uint32_t op = event->data.data32[1];
@@ -1057,21 +1074,11 @@ static void handle_resize_request(xcb_resize_request_event_t *event) {
 }
 
 /*
- * This function is called immediately before the main loop locks. We flush xcb
- * then (and only then)
+ * This function is called immediately before the main loop locks. We check for
+ * events from X11, handle them, then flush our outgoing queue.
  *
  */
 void xcb_prep_cb(struct ev_loop *loop, ev_prepare *watcher, int revents) {
-    xcb_flush(xcb_connection);
-}
-
-/*
- * This function is called immediately after the main loop locks, so when one
- * of the watchers registered an event.
- * We check whether an X-Event arrived and handle it.
- *
- */
-void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
     xcb_generic_event_t *event;
 
     if (xcb_connection_has_error(xcb_connection)) {
@@ -1192,6 +1199,8 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
         }
         free(event);
     }
+
+    xcb_flush(xcb_connection);
 }
 
 /*
@@ -1249,21 +1258,12 @@ char *init_xcb_early() {
     /* The various watchers to communicate with xcb */
     xcb_io = smalloc(sizeof(ev_io));
     xcb_prep = smalloc(sizeof(ev_prepare));
-    xcb_chk = smalloc(sizeof(ev_check));
 
     ev_io_init(xcb_io, &xcb_io_cb, xcb_get_file_descriptor(xcb_connection), EV_READ);
     ev_prepare_init(xcb_prep, &xcb_prep_cb);
-    ev_check_init(xcb_chk, &xcb_chk_cb);
-
-    /* Within an event loop iteration, run the xcb_chk watcher last: other
-     * watchers might call xcb_flush(), which, unexpectedly, can also read
-     * events into the queue (see _xcb_conn_wait). Hence, we need to drain xcb’s
-     * queue last, otherwise we risk dead-locking. */
-    ev_set_priority(xcb_chk, EV_MINPRI);
 
     ev_io_start(main_loop, xcb_io);
     ev_prepare_start(main_loop, xcb_prep);
-    ev_check_start(main_loop, xcb_chk);
 
     /* Now we get the atoms and save them in a nice data structure */
     get_atoms();
@@ -1517,11 +1517,9 @@ void clean_xcb(void) {
     xcb_aux_sync(xcb_connection);
     xcb_disconnect(xcb_connection);
 
-    ev_check_stop(main_loop, xcb_chk);
     ev_prepare_stop(main_loop, xcb_prep);
     ev_io_stop(main_loop, xcb_io);
 
-    FREE(xcb_chk);
     FREE(xcb_prep);
     FREE(xcb_io);
 }
index c26835b90ff07e6de3742175fbd9052a7551388e..ecc875d08298f6b33b8b03340b84c8d1e33266a3 100644 (file)
@@ -28,6 +28,7 @@
 #include <errno.h>
 #include <err.h>
 #include <stdint.h>
+#include <inttypes.h>
 #include <math.h>
 #include <limits.h>
 
index 9780f788b5bb53b308609df0818017e420d77024..85d5fe78a9f27056e565c048d52e4edc806e9bbc 100644 (file)
@@ -66,7 +66,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_
  * Implementation of 'resize set <px> [px] <px> [px]'.
  *
  */
-void cmd_resize_set(I3_CMD, long cwidth, long cheight);
+void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, const char *mode_height);
 
 /**
  * Implementation of 'resize grow|shrink <direction> [<px> px] [or <ppt> ppt]'.
index 4d13d448f1395fdd97e13e82a477178e28ee4ea0..93a7e0a34cf6ca5983fe41cf7298b369bd32d9fe 100644 (file)
@@ -74,3 +74,4 @@ extern bool xcursor_supported, xkb_supported;
 extern xcb_window_t root;
 extern struct ev_loop *main_loop;
 extern bool only_check_config;
+extern bool force_xinerama;
index 993a2a2482d1de70ed67433ae2189948c6f53f35..9e0280c9363322b48ef7f25534bfde2b9ccd9c4f 100644 (file)
@@ -60,6 +60,9 @@ typedef struct i3_ipc_header {
 /** Request the raw last loaded i3 config. */
 #define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9
 
+/** Send a tick event to all subscribers. */
+#define I3_IPC_MESSAGE_TYPE_SEND_TICK 10
+
 /*
  * Messages from i3 to clients
  *
@@ -74,6 +77,7 @@ typedef struct i3_ipc_header {
 #define I3_IPC_REPLY_TYPE_VERSION 7
 #define I3_IPC_REPLY_TYPE_BINDING_MODES 8
 #define I3_IPC_REPLY_TYPE_CONFIG 9
+#define I3_IPC_REPLY_TYPE_TICK 10
 
 /*
  * Events from i3 to clients. Events have the first bit set high.
@@ -101,3 +105,6 @@ typedef struct i3_ipc_header {
 
 /** The shutdown event will be triggered when the ipc shuts down */
 #define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6)
+
+/** The tick event will be sent upon a tick IPC message */
+#define I3_IPC_EVENT_TICK (I3_IPC_EVENT_MASK | 7)
index 7ffbf7a831f9de3418f9d4ddad304beee5cbff22..c6ad35c770745038eb99dbd370966fd489bc262f 100644 (file)
@@ -31,6 +31,10 @@ typedef struct ipc_client {
     int num_events;
     char **events;
 
+    /* For clients which subscribe to the tick event: whether the first tick
+     * event has been sent by i3. */
+    bool first_tick_sent;
+
     TAILQ_ENTRY(ipc_client)
     clients;
 } ipc_client;
index a587332817e8b23bde3d48cd8ef434c43924b275..0289fa1ab6018a404cb2445f5fa57b61eaf283eb 100644 (file)
@@ -258,14 +258,16 @@ state RESIZE_SET:
       -> RESIZE_WIDTH
 
 state RESIZE_WIDTH:
-  'px'
+  mode_width = 'px', 'ppt'
       ->
   height = number
       -> RESIZE_HEIGHT
 
 state RESIZE_HEIGHT:
-  'px', end
-      -> call cmd_resize_set(&width, &height)
+  mode_height = 'px', 'ppt'
+      ->
+  end
+      -> call cmd_resize_set(&width, $mode_width, &height, $mode_height)
 
 # rename workspace <name> to <name>
 # rename workspace to <name>
index d7cdf2198f7d6170f4d5038cba7dc396a2897361..e68fcd802b1c15e7a20300bd6a5ca2af5a6f27d9 100644 (file)
@@ -676,13 +676,13 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
 }
 
 /*
- * Implementation of 'resize set <px> [px] <px> [px]'.
+ * Implementation of 'resize set <width> [px | ppt] <height> [px | ppt]'.
  *
  */
-void cmd_resize_set(I3_CMD, long cwidth, long cheight) {
-    DLOG("resizing to %ldx%ld px\n", cwidth, cheight);
+void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, const char *mode_height) {
+    DLOG("resizing to %ld %s x %ld %s\n", cwidth, mode_width, cheight, mode_height);
     if (cwidth <= 0 || cheight <= 0) {
-        ELOG("Resize failed: dimensions cannot be negative (was %ldx%ld)\n", cwidth, cheight);
+        ELOG("Resize failed: dimensions cannot be negative (was %ld %s x %ld %s)\n", cwidth, mode_width, cheight, mode_height);
         return;
     }
 
@@ -692,6 +692,13 @@ void cmd_resize_set(I3_CMD, long cwidth, long cheight) {
     TAILQ_FOREACH(current, &owindows, owindows) {
         Con *floating_con;
         if ((floating_con = con_inside_floating(current->con))) {
+            Con *output = con_get_output(floating_con);
+            if (mode_width && strcmp(mode_width, "ppt") == 0) {
+                cwidth = output->rect.width * ((double)cwidth / 100.0);
+            }
+            if (mode_height && strcmp(mode_height, "ppt") == 0) {
+                cheight = output->rect.height * ((double)cheight / 100.0);
+            }
             floating_resize(floating_con, cwidth, cheight);
         } else {
             ELOG("Resize failed: %p not a floating container\n", current->con);
index b6b0da2c6f48318b283e3a94479f172797317065..1de91d00861c3e11736f6b2682914c70b5c83407 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -1756,7 +1756,7 @@ void con_set_layout(Con *con, layout_t layout) {
             con->workspace_layout = ws_layout;
             DLOG("Setting layout to %d\n", layout);
             con->layout = layout;
-        } else if (layout == L_STACKED || layout == L_TABBED) {
+        } else if (layout == L_STACKED || layout == L_TABBED || layout == L_SPLITV || layout == L_SPLITH) {
             DLOG("Creating new split container\n");
             /* 1: create a new split container */
             Con *new = con_new(NULL, NULL);
@@ -1848,6 +1848,10 @@ void con_toggle_layout(Con *con, const char *toggle_mode) {
                  * change to the opposite split layout. */
                 if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) {
                     layout = parent->last_split_layout;
+                    /* In case last_split_layout was not initialized… */
+                    if (layout == L_DEFAULT) {
+                        layout = L_SPLITH;
+                    }
                 } else {
                     layout = (parent->layout == L_SPLITH) ? L_SPLITV : L_SPLITH;
                 }
index 58a5552ceb27d77d7f267004cf457a636c902fe1..4e66c91109f3f8f37d0dfddbeacc2da6545cb23c 100644 (file)
@@ -764,6 +764,7 @@ static char *migrate_config(char *input, off_t size) {
     wait(&status);
     if (!WIFEXITED(status)) {
         fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
+        FREE(converted);
         return NULL;
     }
 
@@ -778,6 +779,7 @@ static char *migrate_config(char *input, off_t size) {
             fprintf(stderr, "# i3 config file (v4)\n");
             /* TODO: nag the user with a message to include a hint for i3 in their config file */
         }
+        FREE(converted);
         return NULL;
     }
 
@@ -900,7 +902,9 @@ bool parse_file(const char *f, bool use_nagbar) {
 
     FREE(current_config);
     current_config = scalloc(stbuf.st_size + 1, 1);
-    fread(current_config, 1, stbuf.st_size, fstr);
+    if ((ssize_t)fread(current_config, 1, stbuf.st_size, fstr) != stbuf.st_size) {
+        die("Could not fread: %s\n", strerror(errno));
+    }
     rewind(fstr);
 
     bool invalid_sets = false;
index 5f46dcf9755f9fb8850db07492954620c3850954..14988818418c972e70a9d1181cf10d990c886256 100644 (file)
@@ -667,7 +667,7 @@ void floating_resize_window(Con *con, const bool proportional,
 
 /* Custom data structure used to track dragging-related events. */
 struct drag_x11_cb {
-    ev_check check;
+    ev_prepare prepare;
 
     /* Whether this modal event loop should be exited and with which result. */
     drag_result_t result;
@@ -686,7 +686,7 @@ struct drag_x11_cb {
     const void *extra;
 };
 
-static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) {
+static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) {
     struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data;
     xcb_motion_notify_event_t *last_motion_notify = NULL;
     xcb_generic_event_t *event;
@@ -765,6 +765,8 @@ static void xcb_drag_check_cb(EV_P_ ev_check *w, int revents) {
             dragloop->extra);
     }
     free(last_motion_notify);
+
+    xcb_flush(conn);
 }
 
 /*
@@ -831,18 +833,18 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_
         .callback = callback,
         .extra = extra,
     };
-    ev_check *check = &loop.check;
+    ev_prepare *prepare = &loop.prepare;
     if (con)
         loop.old_rect = con->rect;
-    ev_check_init(check, xcb_drag_check_cb);
-    check->data = &loop;
+    ev_prepare_init(prepare, xcb_drag_prepare_cb);
+    prepare->data = &loop;
     main_set_x11_cb(false);
-    ev_check_start(main_loop, check);
+    ev_prepare_start(main_loop, prepare);
 
     while (loop.result == DRAGGING)
         ev_run(main_loop, EVRUN_ONCE);
 
-    ev_check_stop(main_loop, check);
+    ev_prepare_stop(main_loop, prepare);
     main_set_x11_cb(true);
 
     xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
index 3140e4057674ca7596719ece8df9f5fbba70b19a..0f81afae19a095233e4c568e0b8dff7667edeb8b 100644 (file)
@@ -774,6 +774,8 @@ static void handle_client_message(xcb_client_message_event_t *event) {
                 scratchpad_show(con);
             } else {
                 workspace_show(ws);
+                /* Re-set focus, even if unchanged from i3’s perspective. */
+                focused_id = XCB_NONE;
                 con_focus(con);
             }
         } else {
@@ -1262,6 +1264,9 @@ static void handle_configure_notify(xcb_configure_notify_event_t *event) {
     }
     DLOG("ConfigureNotify for root window 0x%08x\n", event->event);
 
+    if (force_xinerama) {
+        return;
+    }
     randr_query_outputs();
 }
 
index 759665fe91b76341e74128807ce830c39e0ef63e..99b9e0ec65814fd7c4fb52f5ef724f5c2344c3c3 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -1046,8 +1046,9 @@ static int add_subscription(void *extra, const unsigned char *s,
     memcpy(client->events[event], s, len);
 
     DLOG("client is now subscribed to:\n");
-    for (int i = 0; i < client->num_events; i++)
+    for (int i = 0; i < client->num_events; i++) {
         DLOG("event %s\n", client->events[i]);
+    }
     DLOG("(done)\n");
 
     return 1;
@@ -1099,6 +1100,25 @@ IPC_HANDLER(subscribe) {
     yajl_free(p);
     const char *reply = "{\"success\":true}";
     ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply);
+
+    if (client->first_tick_sent) {
+        return;
+    }
+
+    bool is_tick = false;
+    for (int i = 0; i < client->num_events; i++) {
+        if (strcmp(client->events[i], "tick") == 0) {
+            is_tick = true;
+            break;
+        }
+    }
+    if (!is_tick) {
+        return;
+    }
+
+    client->first_tick_sent = true;
+    const char *payload = "{\"first\":true,\"payload\":\"\"}";
+    ipc_send_message(client->fd, strlen(payload), I3_IPC_EVENT_TICK, (const uint8_t *)payload);
 }
 
 /*
@@ -1122,9 +1142,35 @@ IPC_HANDLER(get_config) {
     y(free);
 }
 
+/*
+ * Sends the tick event from the message payload to subscribers. Establishes a
+ * synchronization point in event-related tests.
+ */
+IPC_HANDLER(send_tick) {
+    yajl_gen gen = ygenalloc();
+
+    y(map_open);
+
+    ystr("payload");
+    yajl_gen_string(gen, (unsigned char *)message, message_size);
+
+    y(map_close);
+
+    const unsigned char *payload;
+    ylength length;
+    y(get_buf, &payload, &length);
+
+    ipc_send_event("tick", I3_IPC_EVENT_TICK, (const char *)payload);
+    y(free);
+
+    const char *reply = "{\"success\":true}";
+    ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_TICK, (const uint8_t *)reply);
+    DLOG("Sent tick event\n");
+}
+
 /* The index of each callback function corresponds to the numeric
  * value of the message type (see include/i3/ipc.h) */
-handler_t handlers[10] = {
+handler_t handlers[11] = {
     handle_run_command,
     handle_get_workspaces,
     handle_subscribe,
@@ -1135,6 +1181,7 @@ handler_t handlers[10] = {
     handle_get_version,
     handle_get_binding_modes,
     handle_get_config,
+    handle_send_tick,
 };
 
 /*
index 0d1457fdc1e18ca443fe652292f7343c9948b91f..d87b9a29b0bcb75febe7e60b91b52bb220b50e97 100644 (file)
@@ -35,9 +35,9 @@ struct rlimit original_rlimit_core;
 /** The number of file descriptors passed via socket activation. */
 int listen_fds;
 
-/* We keep the xcb_check watcher around to be able to enable and disable it
+/* We keep the xcb_prepare watcher around to be able to enable and disable it
  * temporarily for drag_pointer(). */
-static struct ev_check *xcb_check;
+static struct ev_prepare *xcb_prepare;
 
 extern Con *focused;
 
@@ -92,29 +92,26 @@ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignment
 bool xcursor_supported = true;
 bool xkb_supported = true;
 
+bool force_xinerama = false;
+
 /*
- * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
+ * This callback is only a dummy, see xcb_prepare_cb.
  * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
  *
  */
 static void xcb_got_event(EV_P_ struct ev_io *w, int revents) {
-    /* empty, because xcb_prepare_cb and xcb_check_cb are used */
+    /* empty, because xcb_prepare_cb are used */
 }
 
 /*
- * Flush before blocking (and waiting for new events)
+ * Called just before the event loop sleeps. Ensures xcb’s incoming and outgoing
+ * queues are empty so that any activity will trigger another event loop
+ * iteration, and hence another xcb_prepare_cb invocation.
  *
  */
 static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
-    xcb_flush(conn);
-}
-
-/*
- * Instead of polling the X connection socket we leave this to
- * xcb_poll_for_event() which knows better than we can ever know.
- *
- */
-static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
+    /* Process all queued (and possibly new) events before the event loop
+       sleeps. */
     xcb_generic_event_t *event;
 
     while ((event = xcb_poll_for_event(conn)) != NULL) {
@@ -137,6 +134,9 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
 
         free(event);
     }
+
+    /* Flush all queued events to X11. */
+    xcb_flush(conn);
 }
 
 /*
@@ -148,12 +148,12 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) {
 void main_set_x11_cb(bool enable) {
     DLOG("Setting main X11 callback to enabled=%d\n", enable);
     if (enable) {
-        ev_check_start(main_loop, xcb_check);
+        ev_prepare_start(main_loop, xcb_prepare);
         /* Trigger the watcher explicitly to handle all remaining X11 events.
          * drag_pointer()’s event handler exits in the middle of the loop. */
-        ev_feed_event(main_loop, xcb_check, 0);
+        ev_feed_event(main_loop, xcb_prepare, 0);
     } else {
-        ev_check_stop(main_loop, xcb_check);
+        ev_prepare_stop(main_loop, xcb_prepare);
     }
 }
 
@@ -197,7 +197,6 @@ int main(int argc, char *argv[]) {
     bool autostart = true;
     char *layout_path = NULL;
     bool delete_layout_path = false;
-    bool force_xinerama = false;
     bool disable_randr15 = false;
     char *fake_outputs = NULL;
     bool disable_signalhandler = false;
@@ -550,6 +549,10 @@ int main(int argc, char *argv[]) {
             config.ipc_socket_path = sstrdup(config.ipc_socket_path);
     }
 
+    if (config.force_xinerama) {
+        force_xinerama = true;
+    }
+
     xcb_void_cookie_t cookie;
     cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ROOT_EVENT_MASK});
     xcb_generic_error_t *error = xcb_request_check(conn, cookie);
@@ -668,7 +671,7 @@ int main(int argc, char *argv[]) {
         fake_outputs_init(fake_outputs);
         FREE(fake_outputs);
         config.fake_outputs = NULL;
-    } else if (force_xinerama || config.force_xinerama) {
+    } else if (force_xinerama) {
         /* Force Xinerama (for drivers which don't support RandR yet, esp. the
          * nVidia binary graphics driver), when specified either in the config
          * file or on command-line */
@@ -776,15 +779,11 @@ int main(int argc, char *argv[]) {
     ewmh_update_desktop_viewport();
 
     struct ev_io *xcb_watcher = scalloc(1, sizeof(struct ev_io));
-    xcb_check = scalloc(1, sizeof(struct ev_check));
-    struct ev_prepare *xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
+    xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
 
     ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ);
     ev_io_start(main_loop, xcb_watcher);
 
-    ev_check_init(xcb_check, xcb_check_cb);
-    ev_check_start(main_loop, xcb_check);
-
     ev_prepare_init(xcb_prepare, xcb_prepare_cb);
     ev_prepare_start(main_loop, xcb_prepare);
 
index bf16c864c04be4a28d757071cff9edc3db4f5afb..b99a50c165ebbd922379f996527c8a57e00e3dc5 100644 (file)
@@ -39,7 +39,6 @@ static TAILQ_HEAD(state_head, placeholder_state) state_head =
 static xcb_connection_t *restore_conn;
 
 static struct ev_io *xcb_watcher;
-static struct ev_check *xcb_check;
 static struct ev_prepare *xcb_prepare;
 
 static void restore_handle_event(int type, xcb_generic_event_t *event);
@@ -49,10 +48,6 @@ static void restore_xcb_got_event(EV_P_ struct ev_io *w, int revents) {
 }
 
 static void restore_xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) {
-    xcb_flush(restore_conn);
-}
-
-static void restore_xcb_check_cb(EV_P_ ev_check *w, int revents) {
     xcb_generic_event_t *event;
 
     if (xcb_connection_has_error(restore_conn)) {
@@ -77,6 +72,8 @@ static void restore_xcb_check_cb(EV_P_ ev_check *w, int revents) {
 
         free(event);
     }
+
+    xcb_flush(restore_conn);
 }
 
 /*
@@ -91,7 +88,6 @@ void restore_connect(void) {
         /* This is not the initial connect, but a reconnect, most likely
          * because our X11 connection was killed (e.g. by a user with xkill. */
         ev_io_stop(main_loop, xcb_watcher);
-        ev_check_stop(main_loop, xcb_check);
         ev_prepare_stop(main_loop, xcb_prepare);
 
         placeholder_state *state;
@@ -107,7 +103,6 @@ void restore_connect(void) {
          */
         xcb_disconnect(restore_conn);
         free(xcb_watcher);
-        free(xcb_check);
         free(xcb_prepare);
     }
 
@@ -124,15 +119,11 @@ void restore_connect(void) {
     }
 
     xcb_watcher = scalloc(1, sizeof(struct ev_io));
-    xcb_check = scalloc(1, sizeof(struct ev_check));
     xcb_prepare = scalloc(1, sizeof(struct ev_prepare));
 
     ev_io_init(xcb_watcher, restore_xcb_got_event, xcb_get_file_descriptor(restore_conn), EV_READ);
     ev_io_start(main_loop, xcb_watcher);
 
-    ev_check_init(xcb_check, restore_xcb_check_cb);
-    ev_check_start(main_loop, xcb_check);
-
     ev_prepare_init(xcb_prepare, restore_xcb_prepare_cb);
     ev_prepare_start(main_loop, xcb_prepare);
 }
index ba0969c729674aabe3dd04a0ce9614803514f4f7..dc3444f7b6aa950be8b4696896ac55b1cc4033fc 100644 (file)
@@ -500,7 +500,7 @@ ssize_t slurp(const char *path, char **buf) {
     size_t n = fread(*buf, 1, stbuf.st_size, f);
     fclose(f);
     if ((ssize_t)n != stbuf.st_size) {
-        ELOG("File \"%s\" could not be read entirely: got %zd, want %zd\n", path, n, stbuf.st_size);
+        ELOG("File \"%s\" could not be read entirely: got %zd, want %" PRIi64 "\n", path, n, (int64_t)stbuf.st_size);
         free(*buf);
         *buf = NULL;
         return -1;
index ddd6ccadf992596d1b02672028fe239c4c42fd3b..b7b398720e283a150dfd47fdd4ab82192e2267e6 100755 (executable)
@@ -38,6 +38,8 @@ binmode STDERR, ':utf8';
 # subshell or situations like that.
 AnyEvent::Util::close_all_fds_except(0, 1, 2);
 
+our @CLEANUP;
+
 # convenience wrapper to write to the log file
 my $log;
 sub Log { say $log "@_" }
@@ -55,6 +57,7 @@ my %options = (
     xtrace => 0,
     coverage => 0,
     restart => 0,
+    xvfb => 1,
 );
 my $keep_xserver_output = 0;
 
@@ -64,6 +67,7 @@ my $result = GetOptions(
     "valgrind" => \$options{valgrind},
     "strace" => \$options{strace},
     "xtrace" => \$options{xtrace},
+    "xvfb" => \$options{xvfb},
     "display=s" => \@displays,
     "parallel=i" => \$parallel,
     "help|?" => \$help,
@@ -112,6 +116,44 @@ $ENV{PATH} = join(':',
 qx(Xephyr -help 2>&1);
 die "Xephyr was not found in your path. Please install Xephyr (xserver-xephyr on Debian)." if $?;
 
+qx(xvfb-run --help 2>&1);
+if ($? && $options{xvfb}) {
+    say "xvfb-run not found, not running tests under xvfb. Install the xvfb package to speed up tests";
+    $options{xvfb} = 0;
+}
+
+if ($options{xvfb}) {
+    for (my $n = 99; $n < 120; $n++) {
+       my $path = File::Temp::tmpnam($ENV{TMPDIR} // "/tmp", "i3-testsXXXXXX");
+       if (!defined(POSIX::mkfifo($path, 0600))) {
+           die "mkfifo: $!";
+       }
+       my $pid = fork // die "fork: $!";
+       if ($pid == 0) {
+           # Child
+
+           # Xvfb checks whether the parent ignores USR1 and sends USR1 to the
+           # parent when ready, so that the wait call will be interrupted.  We
+           # can’t implement this in Perl, as Perl’s waitpid transparently
+           # handles -EINTR.
+           exec('/bin/sh', '-c', qq|trap "exit" INT; trap : USR1; (trap '' USR1; exec Xvfb :$n -screen 0 640x480x8 -nolisten tcp) & PID=\$!; wait; if ! kill -0 \$PID 2>/dev/null; then echo 1:\$PID > $path; else echo 0:\$PID > $path; wait \$PID; fi|);
+           die "exec: $!";
+       }
+       chomp(my $kill = slurp($path));
+       unlink($path);
+       my ($code, $xvfbpid) = ($kill =~ m,^([0-1]):(.*)$,);
+       next unless $code eq '0';
+
+       $ENV{DISPLAY} = ":$n";
+       say "Running tests under Xvfb display $ENV{DISPLAY}";
+
+       push(@CLEANUP, sub {
+           kill(15, $xvfbpid);
+       });
+       last;
+    }
+}
+
 @displays = split(/,/, join(',', @displays));
 @displays = map { s/ //g; $_ } @displays;
 
@@ -379,7 +421,7 @@ sub take_job {
 
 sub cleanup {
     my $exitcode = $?;
-    $_->() for our @CLEANUP;
+    $_->() for @CLEANUP;
     exit $exitcode;
 }
 
index a484c91a441fe318cbecfe5f3286e2c9e094ba76..ed239241c84dadba0412c3609701d6cf957c5505 100644 (file)
@@ -47,6 +47,8 @@ our @EXPORT = qw(
     wait_for_unmap
     $x
     kill_all_windows
+    events_for
+    listen_for_binding
 );
 
 =head1 NAME
@@ -131,6 +133,22 @@ sub import {
     my ($class, %args) = @_;
     my $pkg = caller;
 
+    $x ||= i3test::X11->new;
+    # set the pointer to a predictable position in case a previous test has
+    # disturbed it
+    $x->warp_pointer(
+       0, # src_window (None)
+       $x->get_root_window(), # dst_window (None)
+       0, # src_x
+       0, # src_y
+       0, # src_width
+       0, # src_height
+       0, # dst_x
+       0); # dst_y
+    # Synchronize with X11 to ensure the pointer has been warped before i3
+    # starts up.
+    $x->get_input_focus_reply($x->get_input_focus()->{sequence});
+
     $i3_autostart = delete($args{i3_autostart}) // 1;
     my $i3_config = delete($args{i3_config}) // '-default';
 
@@ -153,10 +171,6 @@ __
     strict->import;
     warnings->import;
 
-    $x ||= i3test::X11->new;
-    # set the pointer to a predictable position in case a previous test has
-    # disturbed it
-    $x->root->warp_pointer(0, 0);
     $cv->recv if $i3_autostart;
 
     @_ = ($class);
@@ -179,29 +193,11 @@ received, etc.
 sub wait_for_event {
     my ($timeout, $cb) = @_;
 
-    my $cv = AE::cv;
-
     $x->flush;
 
-    # unfortunately, there is no constant for this
-    my $ae_read = 0;
-
-    my $guard = AE::io $x->get_file_descriptor, $ae_read, sub {
-        while (defined(my $event = $x->poll_for_event)) {
-            if ($cb->($event)) {
-                $cv->send(1);
-                last;
-            }
-        }
-    };
-
-    # Trigger timeout after $timeout seconds (can be fractional)
-    my $t = AE::timer $timeout, 0, sub { warn "timeout ($timeout secs)"; $cv->send(0) };
-
-    my $result = $cv->recv;
-    undef $t;
-    undef $guard;
-    return $result;
+    while (defined(my $event = $x->wait_for_event)) {
+       return 1 if $cb->($event);
+    }
 }
 
 =head2 wait_for_map($window)
@@ -348,6 +344,12 @@ sub open_window {
 
     $window->map;
     wait_for_map($window);
+
+    # MapWindow is sent before i3 even starts rendering: the window is placed at
+    # temporary off-screen coordinates first, and x_push_changes() sends further
+    # X11 requests to set focus etc. Hence, we sync with i3 before continuing.
+    sync_with_i3();
+
     return $window;
 }
 
@@ -686,6 +688,7 @@ sub sync_with_i3 {
         $_sync_window = open_window(
             rect => [ -15, -15, 10, 10 ],
             override_redirect => 1,
+            dont_map => 1,
         );
     }
 
@@ -900,6 +903,86 @@ sub kill_all_windows {
     cmd '[title=".*"] kill';
 }
 
+=head2 events_for($subscribecb, [ $rettype ], [ $eventcbs ])
+
+Helper function which returns an array containing all events of type $rettype
+which were generated by i3 while $subscribecb was running.
+
+Set $eventcbs to subscribe to multiple event types and/or perform your own event
+aggregation.
+
+=cut
+sub events_for {
+    my ($subscribecb, $rettype, $eventcbs) = @_;
+
+    my @events;
+    $eventcbs //= {};
+    if (defined($rettype)) {
+       $eventcbs->{$rettype} = sub { push @events, shift };
+    }
+    my $subscribed = AnyEvent->condvar;
+    my $flushed = AnyEvent->condvar;
+    $eventcbs->{tick} = sub {
+       my ($event) = @_;
+       if ($event->{first}) {
+           $subscribed->send($event);
+       } else {
+           $flushed->send($event);
+       }
+    };
+    my $i3 = i3(get_socket_path(0));
+    $i3->connect->recv;
+    $i3->subscribe($eventcbs)->recv;
+    $subscribed->recv;
+    # Subscription established, run the callback.
+    $subscribecb->();
+    # Now generate a tick event, which we know we’ll receive (and at which point
+    # all other events have been received).
+    my $nonce = int(rand(255)) + 1;
+    $i3->send_tick($nonce);
+
+    my $tick = $flushed->recv;
+    $tester->is_eq($tick->{payload}, $nonce, 'tick nonce received');
+    return @events;
+}
+
+=head2 listen_for_binding($cb)
+
+Helper function to evaluate whether sending KeyPress/KeyRelease events via XTEST
+triggers an i3 key binding or not. Expects key bindings to be configured in the
+form “bindsym <binding> nop <binding>”, 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
+          xtest_sync_with_i3;
+      },
+      ),
+     'Mod4+Return',
+     'triggered the "Mod4+Return" keybinding');
+
+=cut
+
+sub listen_for_binding {
+    my ($cb) = @_;
+    my $triggered = AnyEvent->condvar;
+    my @events = events_for(
+       $cb,
+       'binding');
+
+    $tester->is_eq(scalar @events, 1, 'Received precisely one event');
+    $tester->is_eq($events[0]->{change}, 'run', 'change is "run"');
+    # We look at the command (which is “nop <binding>”) because that is easier
+    # than re-assembling the string representation of $event->{binding}.
+    my $command = $events[0]->{binding}->{command};
+    $command =~ s/^nop //g;
+    return $command;
+}
+
 =head1 AUTHOR
 
 Michael Stapelberg <michael@i3wm.org>
index 1ca964b154813d6897f7c43c3b40ab58c187cbb0..4c464c5e3943f6f5db064766dbef891af44a68cb 100644 (file)
@@ -14,13 +14,13 @@ use ExtUtils::PkgConfig;
 use Exporter ();
 our @EXPORT = qw(
     inlinec_connect
+    xtest_sync_with
+    xtest_sync_with_i3
     set_xkb_group
     xtest_key_press
     xtest_key_release
     xtest_button_press
     xtest_button_release
-    listen_for_binding
-    start_binding_capture
     binding_events
 );
 
@@ -38,7 +38,7 @@ i3test::XTEST - Inline::C wrappers for xcb-xtest and xcb-xkb
 # ineffective.
 my %sn_config;
 BEGIN {
-    %sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest');
+    %sn_config = ExtUtils::PkgConfig->find('xcb-xkb xcb-xtest xcb-util');
 }
 
 use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags};
@@ -53,8 +53,12 @@ use Inline C => <<'END_OF_C_CODE';
 #include <xcb/xcb.h>
 #include <xcb/xkb.h>
 #include <xcb/xtest.h>
+#include <xcb/xcb_aux.h>
 
 static xcb_connection_t *conn = NULL;
+static xcb_window_t sync_window;
+static xcb_window_t root_window;
+static xcb_atom_t i3_sync_atom;
 
 bool inlinec_connect() {
     int screen;
@@ -89,9 +93,94 @@ bool inlinec_connect() {
     }
     free(usereply);
 
+    xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, xcb_intern_atom(conn, 0, strlen("I3_SYNC"), "I3_SYNC"), NULL);
+    i3_sync_atom = reply->atom;
+    free(reply);
+
+    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
+    root_window = root_screen->root;
+    sync_window = xcb_generate_id(conn);
+    xcb_create_window(conn,
+                      XCB_COPY_FROM_PARENT,           // depth
+                      sync_window,                    // window
+                      root_window,                    // parent
+                      -15,                            // x
+                      -15,                            // y
+                      1,                              // width
+                      1,                              // height
+                      0,                              // border_width
+                      XCB_WINDOW_CLASS_INPUT_OUTPUT,  // class
+                      XCB_COPY_FROM_PARENT,           // visual
+                      XCB_CW_OVERRIDE_REDIRECT,       // value_mask
+                      (uint32_t[]){
+                          1,  // override_redirect
+                      });     // value_list
+
     return true;
 }
 
+void xtest_sync_with(int window) {
+    xcb_client_message_event_t ev;
+    memset(&ev, '\0', sizeof(xcb_client_message_event_t));
+
+    const int nonce = rand() % 255;
+
+    ev.response_type = XCB_CLIENT_MESSAGE;
+    ev.window = sync_window;
+    ev.type = i3_sync_atom;
+    ev.format = 32;
+    ev.data.data32[0] = sync_window;
+    ev.data.data32[1] = nonce;
+
+    xcb_send_event(conn, false, (xcb_window_t)window, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char *)&ev);
+    xcb_flush(conn);
+
+    xcb_generic_event_t *event = NULL;
+    while (1) {
+        free(event);
+        if ((event = xcb_wait_for_event(conn)) == NULL) {
+            break;
+        }
+        if (event->response_type == 0) {
+            fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
+            continue;
+        }
+
+        /* Strip off the highest bit (set if the event is generated) */
+        const int type = (event->response_type & 0x7F);
+        switch (type) {
+            case XCB_CLIENT_MESSAGE: {
+                xcb_client_message_event_t *ev = (xcb_client_message_event_t *)event;
+                {
+                    const uint32_t got = ev->data.data32[0];
+                    const uint32_t want = sync_window;
+                    if (got != want) {
+                        fprintf(stderr, "Ignoring ClientMessage: unknown window: got %d, want %d\n", got, want);
+                        continue;
+                    }
+                }
+                {
+                    const uint32_t got = ev->data.data32[1];
+                    const uint32_t want = nonce;
+                    if (got != want) {
+                        fprintf(stderr, "Ignoring ClientMessage: unknown nonce: got %d, want %d\n", got, want);
+                        continue;
+                    }
+                }
+                return;
+            }
+            default:
+                fprintf(stderr, "Unexpected X11 event of type %d received (XCB_CLIENT_MESSAGE = %d)\n", type, XCB_CLIENT_MESSAGE);
+                break;
+        }
+    }
+    free(event);
+}
+
+void xtest_sync_with_i3() {
+    xtest_sync_with((int)root_window);
+}
+
 // 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) {
@@ -170,86 +259,6 @@ sub import {
 
 =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 <binding> nop <binding>”, 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 <binding>”) 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
@@ -283,6 +292,15 @@ Sends a ButtonRelease event via XTEST, with the specified C<$button>.
 
 Returns false when there was an X11 error, true otherwise.
 
+=head2 xtest_sync_with($window)
+
+Ensures the specified window has processed all X11 events which were triggered
+by this module, provided the window response to the i3 sync protocol.
+
+=head2 xtest_sync_with_i3()
+
+Ensures i3 has processed all X11 events which were triggered by this module.
+
 =head1 AUTHOR
 
 Michael Stapelberg <michael@i3wm.org>
index 34ce078130c34e0e99bd98558d6e7c6f1370ba1d..b0c4354ec10fe7385e41ed5a95d7dd06b3e01051 100644 (file)
 
 use i3test;
 
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-################################
-# Workspaces requests and events
-################################
-
 my $old_ws = get_ws(focused_ws());
 
-# Events
-
 # We are switching to an empty workpspace from an empty workspace, so we expect
 # to receive "init", "focus", and "empty".
-my $init = AnyEvent->condvar;
-my $focus = AnyEvent->condvar;
-my $empty = AnyEvent->condvar;
-$i3->subscribe({
-    workspace => sub {
-        my ($event) = @_;
-        if ($event->{change} eq 'init') {
-            $init->send($event);
-        } elsif ($event->{change} eq 'focus') {
-            $focus->send($event);
-        } elsif ($event->{change} eq 'empty') {
-            $empty->send($event);
-        }
-    }
-})->recv;
-
-cmd 'workspace 2';
-
-my $t;
-$t = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $init->send(0);
-        $focus->send(0);
-        $empty->send(0);
-    }
-);
-
-my $init_event = $init->recv;
-my $focus_event = $focus->recv;
-my $empty_event = $empty->recv;
+my @events = events_for(
+    sub { cmd 'workspace 2' },
+    'workspace');
 
 my $current_ws = get_ws(focused_ws());
 
-ok($init_event, 'workspace "init" event received');
-is($init_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the initted workspace con');
+is(scalar @events, 3, 'Received 3 events');
+is($events[0]->{change}, 'init', 'First event has change = init');
+is($events[0]->{current}->{id}, $current_ws->{id}, 'the "current" property contains the initted workspace con');
 
-ok($focus_event, 'workspace "focus" event received');
-is($focus_event->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con');
-is($focus_event->{old}->{id}, $old_ws->{id}, 'the "old" property should contain the workspace con that was focused last');
+is($events[1]->{change}, 'focus', 'Second event has change = focus');
+is($events[1]->{current}->{id}, $current_ws->{id}, 'the "current" property should contain the focused workspace con');
+is($events[1]->{old}->{id}, $old_ws->{id}, 'the "old" property should contain the workspace con that was focused last');
 
-ok($empty_event, 'workspace "empty" event received');
-is($empty_event->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con');
+is($events[2]->{change}, 'empty', 'Third event has change = empty');
+is($events[2]->{current}->{id}, $old_ws->{id}, 'the "current" property should contain the emptied workspace con');
 
 done_testing;
index d983eb8565de3d77b5ba9ffba1544e145122aaaa..597d545eb8c903d806643503bb12d58e11120725 100644 (file)
@@ -375,7 +375,74 @@ ok(@content == 2, 'two containers opened');
 isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed');
 isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed');
 
+exit_gracefully($pid);
+
+#####################################################################
+# 16: Check that the command 'layout toggle split' works regardless
+# of what layout we're using.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+workspace_layout default
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+my @layouts = ('splith', 'splitv', 'tabbed', 'stacked');
+my $first_layout;
+
+foreach $first_layout (@layouts) {
+    cmd 'layout ' . $first_layout;
+    $first = open_window;
+    $second = open_window;
+    cmd 'layout toggle split';
+    @content = @{get_ws_content($tmp)};
+    if ($first_layout eq 'splith') {
+        is($content[0]->{layout}, 'splitv', 'layout toggles to splitv');
+    } else {
+        is($content[0]->{layout}, 'splith', 'layout toggles to splith');
+    }
+
+    cmd '[id="' . $first->id . '"] kill';
+    cmd '[id="' . $second->id . '"] kill';
+    sync_with_i3;
+}
 
 exit_gracefully($pid);
 
+#####################################################################
+# 17: Check about setting a new layout.
+#####################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+workspace_layout default
+EOT
+
+$pid = launch_with_config($config);
+
+$tmp = fresh_workspace;
+
+my $second_layout;
+
+foreach $first_layout (@layouts) {
+    foreach $second_layout (@layouts) {
+        cmd 'layout ' . $first_layout;
+        $first = open_window;
+        $second = open_window;
+        cmd 'layout ' . $second_layout;
+        @content = @{get_ws_content($tmp)};
+        is($content[0]->{layout}, $second_layout, 'layout changes to ' . $second_layout);
+
+        cmd '[id="' . $first->id . '"] kill';
+        cmd '[id="' . $second->id . '"] kill';
+        sync_with_i3;
+    }
+}
+
 done_testing;
index 959ff6c474281260ab8d0e2b59133bc04abc21f3..0e4f89600c47b39686609e6456e2bd652bd48a18 100644 (file)
@@ -28,24 +28,11 @@ mode "with spaces" {
 }
 EOT
 
-my $i3 = i3(get_socket_path(0));
-$i3->connect->recv;
+my @events = events_for(
+    sub { cmd 'mode "m1"' },
+    'mode');
 
-my $cv = AnyEvent->condvar;
-
-$i3->subscribe({
-    mode => sub {
-        my ($event) = @_;
-        $cv->send($event->{change} eq 'm1');
-    }
-})->recv;
-
-cmd 'mode "m1"';
-
-# Timeout after 0.5s
-my $t;
-$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); });
-
-ok($cv->recv, 'Mode event received');
+my @changes = map { $_->{change} } @events;
+is_deeply(\@changes, [ 'm1' ], 'Mode event received');
 
 done_testing;
index ca7db1533493416550d1f984f626f8cc796fc406..bafd155f6afeecf055bd0efc5579ad667c8ee174 100644 (file)
 
 use i3test;
 
-SKIP: {
-
-    skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
-
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-################################
-# Window event
-################################
-
-# Events
-
 my $new = AnyEvent->condvar;
 my $focus = AnyEvent->condvar;
-$i3->subscribe({
-    window => sub {
-        my ($event) = @_;
-        if ($event->{change} eq 'new') {
-            $new->send($event);
-        } elsif ($event->{change} eq 'focus') {
-            $focus->send($event);
-        }
-    }
-})->recv;
-
-open_window;
-
-my $t;
-$t = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $new->send(0);
-        $focus->send(0);
-    }
-);
 
-is($new->recv->{container}->{focused}, 0, 'Window "new" event received');
-is($focus->recv->{container}->{focused}, 1, 'Window "focus" event received');
+my @events = events_for(
+    sub { open_window },
+    'window');
 
-}
+is(scalar @events, 2, 'Received 2 events');
+is($events[0]->{container}->{focused}, 0, 'Window "new" event received');
+is($events[1]->{container}->{focused}, 1, 'Window "focus" event received');
 
 done_testing;
index ae7781877cffac9e3e7a26c5d2d0a7a123bd76be..b1c8ba1836148b377a706da4848143d040a2b951 100644 (file)
 
 use i3test;
 
-SKIP: {
-
-    skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
-
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
 ################################
 # Window focus event
 ################################
@@ -33,62 +26,29 @@ my $win0 = open_window;
 my $win1 = open_window;
 my $win2 = open_window;
 
-my $focus = AnyEvent->condvar;
-
-$i3->subscribe({
-    window => sub {
-        my ($event) = @_;
-        $focus->send($event);
-    }
-})->recv;
-
-my $t;
-$t = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $focus->send(0);
-    }
-);
-
 # ensure the rightmost window contains input focus
-$i3->command('[id="' . $win2->id . '"] focus')->recv;
+cmd '[id="' . $win2->id . '"] focus';
 is($x->input_focus, $win2->id, "Window 2 focused");
 
-cmd 'focus left';
-my $event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($focus->recv->{container}->{name}, 'Window 1', 'Window 1 focused');
-
-$focus = AnyEvent->condvar;
-cmd 'focus left';
-$event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($event->{container}->{name}, 'Window 0', 'Window 0 focused');
-
-$focus = AnyEvent->condvar;
-cmd 'focus right';
-$event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($event->{container}->{name}, 'Window 1', 'Window 1 focused');
+sub focus_subtest {
+    my ($cmd, $name) = @_;
 
-$focus = AnyEvent->condvar;
-cmd 'focus right';
-$event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($event->{container}->{name}, 'Window 2', 'Window 2 focused');
+    my $focus = AnyEvent->condvar;
 
-$focus = AnyEvent->condvar;
-cmd 'focus right';
-$event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($event->{container}->{name}, 'Window 0', 'Window 0 focused');
-
-$focus = AnyEvent->condvar;
-cmd 'focus left';
-$event = $focus->recv;
-is($event->{change}, 'focus', 'Focus event received');
-is($event->{container}->{name}, 'Window 2', 'Window 2 focused');
+    my @events = events_for(
+       sub { cmd $cmd },
+       'window');
 
+    is(scalar @events, 1, 'Received 1 event');
+    is($events[0]->{change}, 'focus', 'Focus event received');
+    is($events[0]->{container}->{name}, $name, "$name focused");
 }
 
+subtest 'focus left (1)', \&focus_subtest, 'focus left', $win1->name;
+subtest 'focus left (2)', \&focus_subtest, 'focus left', $win0->name;
+subtest 'focus right (1)', \&focus_subtest, 'focus right', $win1->name;
+subtest 'focus right (2)', \&focus_subtest, 'focus right', $win2->name;
+subtest 'focus right (3)', \&focus_subtest, 'focus right', $win0->name;
+subtest 'focus left', \&focus_subtest, 'focus left', $win2->name;
+
 done_testing;
index c751350a43f9660150323eaed2077f0dc040cabe..b5d14e237864622f64dea8fa8aec4643d2d26db5 100644 (file)
 
 use i3test;
 
-SKIP: {
-
-    skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
-
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-################################
-# Window title event
-################################
-
 my $window = open_window(name => 'Window 0');
 
-my $title = AnyEvent->condvar;
-
-$i3->subscribe({
-    window => sub {
-        my ($event) = @_;
-        $title->send($event);
-    }
-})->recv;
-
-$window->name('New Window Title');
-
-my $t;
-$t = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $title->send(0);
-    }
-);
-
-my $event = $title->recv;
-is($event->{change}, 'title', 'Window title change event received');
-is($event->{container}->{name}, 'New Window Title', 'Window title changed');
+my @events = events_for(
+    sub {
+       $window->name('New Window Title');
+       sync_with_i3;
+    },
+    'window');
 
-}
+is(scalar @events, 1, 'Received 1 event');
+is($events[0]->{change}, 'title', 'Window title change event received');
+is($events[0]->{container}->{name}, 'New Window Title', 'Window title changed');
 
 done_testing;
index aeabe9539f44cbb5a3f9040a1e7a49d1fd34372a..bc150546906e94e625e0c3a6fb8d920aa2480399 100644 (file)
 # Bug still in: 4.7.2-135-g7deb23c
 use i3test;
 
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
+open_window;
 
-my $cv;
-my $t;
+sub fullscreen_subtest {
+    my ($want) = @_;
+    my @events = events_for(
+       sub { cmd 'fullscreen' },
+       'window');
 
-sub reset_test {
-    $cv = AE::cv;
-    $t = AE::timer(0.5, 0, sub { $cv->send(0); });
+    is(scalar @events, 1, 'Received 1 event');
+    is($events[0]->{container}->{fullscreen_mode}, $want, "fullscreen_mode now $want");
 }
 
-reset_test;
-
-$i3->subscribe({
-        window => sub {
-            my ($e) = @_;
-            if ($e->{change} eq 'fullscreen_mode') {
-                $cv->send($e->{container});
-            }
-        },
-    })->recv;
-
-my $window = open_window;
-
-cmd 'fullscreen';
-my $con = $cv->recv;
-
-ok($con, 'got fullscreen window event (on)');
-is($con->{fullscreen_mode}, 1, 'window is fullscreen');
-
-reset_test;
-cmd 'fullscreen';
-$con = $cv->recv;
-
-ok($con, 'got fullscreen window event (off)');
-is($con->{fullscreen_mode}, 0, 'window is not fullscreen');
+subtest 'fullscreen on', \&fullscreen_subtest, 1;
+subtest 'fullscreen off', \&fullscreen_subtest, 0;
 
 done_testing;
index fe8e03c02b01f9cb37c335fd2097a0e29ac8547c..b1f517ef5e4eb67aed9cf44044b02c04a31686db 100644 (file)
 #
 use i3test;
 
-SKIP: {
-
-    skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
-
 ################################################################################
-# check that the workspace empty event is send upon workspace switch when the
+# check that the workspace empty event is sent upon workspace switch when the
 # old workspace is empty
 ################################################################################
 subtest 'Workspace empty event upon switch', sub {
@@ -35,26 +31,17 @@ subtest 'Workspace empty event upon switch', sub {
     cmd '[id="' . $w1->id . '"] kill';
 
     my $cond = AnyEvent->condvar;
-    my $client = i3(get_socket_path(0));
-    $client->connect()->recv;
-    $client->subscribe({
-        workspace => sub {
-            my ($event) = @_;
-            $cond->send($event);
-        }
-    })->recv;
-
-    cmd "workspace $ws2";
-
-    sync_with_i3;
+    my @events = events_for(
+       sub { cmd "workspace $ws2" },
+       'workspace');
 
-    my $event = $cond->recv;
-    is($event->{change}, 'empty', '"Empty" event received upon workspace switch');
-    is($event->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
+    is(scalar @events, 2, 'Received 2 event');
+    is($events[1]->{change}, 'empty', '"Empty" event received upon workspace switch');
+    is($events[1]->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
 };
 
 ################################################################################
-# check that no workspace empty event is send upon workspace switch if the
+# check that no workspace empty event is sent upon workspace switch if the
 # workspace is not empty
 ################################################################################
 subtest 'No workspace empty event', sub {
@@ -63,36 +50,16 @@ subtest 'No workspace empty event', sub {
     my $ws1 = fresh_workspace;
     my $w1 = open_window();
 
-    my @events;
-    my $cond = AnyEvent->condvar;
-    my $client = i3(get_socket_path(0));
-    $client->connect()->recv;
-    $client->subscribe({
-        workspace => sub {
-            my ($event) = @_;
-            push @events, $event;
-        }
-    })->recv;
-
-    # Wait for the workspace event on a new connection. Events will be delivered
-    # to older connections earlier, so by the time it arrives here, it should be
-    # in @events already.
-    my $ws_event_block_conn = i3(get_socket_path(0));
-    $ws_event_block_conn->connect()->recv;
-    $ws_event_block_conn->subscribe({ workspace => sub { $cond->send(1) }});
-
-    cmd "workspace $ws2";
+    my @events = events_for(
+       sub { cmd "workspace $ws2" },
+       'workspace');
 
-    sync_with_i3;
-
-    my @expected_events = grep { $_->{change} eq 'focus' } @events;
-    my @empty_events = grep { $_->{change} eq 'empty' } @events;
-    is(@expected_events, 1, '"Focus" event received');
-    is(@empty_events, 0, 'No "empty" events received');
+    is(scalar @events, 1, 'Received 1 event');
+    is($events[0]->{change}, 'focus', 'Event change is "focus"');
 };
 
 ################################################################################
-# check that workspace empty event is send when the last window has been closed
+# check that workspace empty event is sent when the last window has been closed
 # on invisible workspace
 ################################################################################
 subtest 'Workspace empty event upon window close', sub {
@@ -101,25 +68,16 @@ subtest 'Workspace empty event upon window close', sub {
     my $ws2 = fresh_workspace;
     my $w2 = open_window();
 
-    my $cond = AnyEvent->condvar;
-    my $client = i3(get_socket_path(0));
-    $client->connect()->recv;
-    $client->subscribe({
-        workspace => sub {
-            my ($event) = @_;
-            $cond->send($event);
-        }
-    })->recv;
-
-    cmd '[id="' . $w1->id . '"] kill';
-
-    sync_with_i3;
+    my @events = events_for(
+       sub {
+           $w1->unmap;
+           sync_with_i3;
+       },
+       'workspace');
 
-    my $event = $cond->recv;
-    is($event->{change}, 'empty', '"Empty" event received upon window close');
-    is($event->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
+    is(scalar @events, 1, 'Received 1 event');
+    is($events[0]->{change}, 'empty', '"Empty" event received upon window close');
+    is($events[0]->{current}->{name}, $ws1, '"current" property should be set to the workspace con');
 };
 
-}
-
 done_testing;
index e38a187609797a1853fca76f5a590aeca083fc38..96c94a497bd7b1b1a00f0b440ff7acc8a229a192 100644 (file)
 # Bug still in: 4.8-7-gf4a8253
 use i3test;
 
-my $i3 = i3(get_socket_path());
-$i3->connect->recv;
+sub floating_subtest {
+    my ($win, $cmd, $want) = @_;
 
-my $cv = AnyEvent->condvar;
+    my @events = events_for(
+       sub { cmd $cmd },
+       'window');
 
-$i3->subscribe({
-        window => sub {
-            my ($event) = @_;
-            $cv->send($event) if $event->{change} eq 'floating';
-        }
-    })->recv;
-
-my $t;
-$t = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $cv->send(0);
-    }
-);
+    my @floating = grep { $_->{change} eq 'floating' } @events;
+    is(scalar @floating, 1, 'Received 1 floating event');
+    is($floating[0]->{container}->{window}, $win->{id}, "window id matches");
+    is($floating[0]->{container}->{floating}, $want, "floating is $want");
+}
 
 my $win = open_window();
 
-cmd '[id="' . $win->{id} . '"] floating enable';
-my $e = $cv->recv;
-
-isnt($e, 0, 'floating a container should send an ipc window event');
-is($e->{container}->{window}, $win->{id}, 'the event should contain information about the window');
-is($e->{container}->{floating}, 'user_on', 'the container should be floating');
-
-$cv = AnyEvent->condvar;
-cmd '[id="' . $win->{id} . '"] floating disable';
-$e = $cv->recv;
-
-isnt($e, 0, 'disabling floating on a container should send an ipc window event');
-is($e->{container}->{window}, $win->{id}, 'the event should contain information about the window');
-is($e->{container}->{floating}, 'user_off', 'the container should not be floating');
+subtest 'floating enable', \&floating_subtest, $win, '[id="' . $win->{id} . '"] floating enable', 'user_on';
+subtest 'floating disable', \&floating_subtest, $win, '[id="' . $win->{id} . '"] floating disable', 'user_off';
 
 done_testing;
index af3f4d2fa10bd25f6a13e2bdf028f5f96d2226eb..bec95a2376e29fcb2cd8952c718482262c8c614e 100644 (file)
@@ -35,51 +35,38 @@ SKIP: {
 
     skip 'xdotool is required to test the binding event. `[apt-get install|pacman -S] xdotool`', 1 if $?;
 
-    skip "AnyEvent::I3 too old (need >= 0.16)", 1 if $AnyEvent::I3::VERSION < 0.16;
-
     my $pid = launch_with_config($config);
 
-    my $i3 = i3(get_socket_path());
-    $i3->connect->recv;
-
-    my $cv = AE::cv;
-    my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
-
-    $i3->subscribe({
-            binding => sub {
-                $cv->send(shift);
-            }
-        })->recv;
-
-    qx(xdotool key $binding_symbol);
-
-    my $e = $cv->recv;
-
-    does_i3_live;
+    my $cv = AnyEvent->condvar;
 
-    diag "Event:\n", Dumper($e);
+    my @events = events_for(
+       sub {
+           # TODO: this is still flaky: we need to synchronize every X11
+           # connection with i3. Move to XTEST and synchronize that connection.
+           qx(xdotool key $binding_symbol);
+       },
+       'binding');
 
-    ok($e,
-        'the binding event should emit when user input triggers an i3 binding event');
+    is(scalar @events, 1, 'Received 1 event');
 
-    is($e->{change}, 'run',
+    is($events[0]->{change}, 'run',
         'the `change` field should indicate this binding has run');
 
-    ok($e->{binding},
+    ok($events[0]->{binding},
         'the `binding` field should be a hash that contains information about the binding');
 
-    is($e->{binding}->{input_type}, 'keyboard',
+    is($events[0]->{binding}->{input_type}, 'keyboard',
         'the input_type field should be the input type of the binding (keyboard or mouse)');
 
     note 'the `mods` field should contain the symbols for the modifiers of the binding';
     foreach (@mods) {
-        ok(grep(/$_/i, @{$e->{binding}->{mods}}), "`mods` contains the modifier $_");
+        ok(grep(/$_/i, @{$events[0]->{binding}->{mods}}), "`mods` contains the modifier $_");
     }
 
-    is($e->{binding}->{command}, $command,
+    is($events[0]->{binding}->{command}, $command,
         'the `command` field should contain the command the binding ran');
 
-    is($e->{binding}->{input_code}, 0,
+    is($events[0]->{binding}->{input_code}, 0,
         'the input_code should be the specified code if the key was bound with bindcode, and otherwise zero');
 
     exit_gracefully($pid);
index 8d8d412036105fd17a18ebb9aaf72bae8e176733..ac0c48d03d81a8ba31209197df47dd9c601426c0 100644 (file)
 # Test behavior of "resize <width> <height>" command.
 # Ticket: #1727
 # Bug still in: 4.10.2-1-gc0dbc5d
-use i3test;
+use i3test i3_config => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1333x999+0+0
+workspace ws output fake-0
+EOT
 
 ################################################################################
 # Check that setting floating windows size works
@@ -42,4 +48,54 @@ cmp_ok($content[0]->{rect}->{height}, '!=', $oldrect->{width}, 'height changed')
 cmp_ok($content[0]->{rect}->{width}, '==', 100, 'width changed to 100 px');
 cmp_ok($content[0]->{rect}->{height}, '==', 250, 'height changed to 250 px');
 
+################################################################################
+# Same but with ppt instead of px
+################################################################################
+
+kill_all_windows;
+$tmp = 'ws';
+cmd "workspace $tmp";
+open_floating_window;
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+is(@content, 1, 'one floating node on this ws');
+
+$oldrect = $content[0]->{rect};
+
+cmd 'resize set 33 ppt 20 ppt';
+my $expected_width = int(0.33 * 1333);
+my $expected_height = int(0.2 * 999);
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched');
+cmp_ok($content[0]->{rect}->{width}, '!=', $oldrect->{width}, 'width changed');
+cmp_ok($content[0]->{rect}->{height}, '!=', $oldrect->{width}, 'height changed');
+cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $expected_width px");
+cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px");
+
+################################################################################
+# Mix ppt and px in a single resize set command
+################################################################################
+
+cmd 'resize set 44 ppt 111 px';
+my $expected_width = int(0.44 * 1333);
+my $expected_height = 111;
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched');
+cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $expected_width px");
+cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px");
+
+cmd 'resize set 222 px 100 ppt';
+my $expected_width = 222;
+my $expected_height = 999;
+
+@content = @{get_ws($tmp)->{floating_nodes}};
+cmp_ok($content[0]->{rect}->{x}, '==', $oldrect->{x}, 'x untouched');
+cmp_ok($content[0]->{rect}->{y}, '==', $oldrect->{y}, 'y untouched');
+cmp_ok($content[0]->{rect}->{width}, '==', $expected_width, "width changed to $expected_width px");
+cmp_ok($content[0]->{rect}->{height}, '==', $expected_height, "height changed to $expected_height px");
+
 done_testing;
index f9166fadbcbfeddff82ead75870a17773d0ff81c..bc08aa2f2118997b2117e564bc633372126db65d 100644 (file)
@@ -36,14 +36,13 @@ SKIP: {
     skip "setxkbmap not found", 1 if
         system(q|setxkbmap -print >/dev/null|) != 0;
 
-start_binding_capture;
-
 system(q|setxkbmap us,ru -option grp:alt_shift_toggle|);
 
 is(listen_for_binding(
     sub {
         xtest_key_press(107);
         xtest_key_release(107);
+        xtest_sync_with_i3;
     },
     ),
    'Print',
@@ -55,6 +54,7 @@ is(listen_for_binding(
         xtest_key_press(36); # Return
         xtest_key_release(36); # Return
         xtest_key_release(133); # Super_L
+        xtest_sync_with_i3;
     },
     ),
    'Mod4+Return',
@@ -67,6 +67,7 @@ is(listen_for_binding(
     sub {
         xtest_key_press(107);
         xtest_key_release(107);
+        xtest_sync_with_i3;
     },
     ),
    'Print',
@@ -78,14 +79,12 @@ is(listen_for_binding(
         xtest_key_press(36); # Return
         xtest_key_release(36); # Return
         xtest_key_release(133); # Super_L
+        xtest_sync_with_i3;
     },
     ),
    'Mod4+Return',
    'triggered the "Mod4+Return" keybinding');
 
-sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events');
-
 # Disable the grp:alt_shift_toggle option, as we use Alt+Shift in other testcases.
 system(q|setxkbmap us -option|);
 
index 72bfc86281e2831355ebca4609713d3b8a4690ac..8bca0d869996216c7aa4293e82703259841408bc 100644 (file)
@@ -37,12 +37,11 @@ SKIP: {
     skip "libxcb-xkb too old (need >= 1.11)", 1 unless
         ExtUtils::PkgConfig->atleast_version('xcb-xkb', '1.11');
 
-start_binding_capture;
-
 is(listen_for_binding(
     sub {
         xtest_key_press(107); # Print
         xtest_key_release(107); # Print
+        xtest_sync_with_i3;
     },
     ),
     'Print',
@@ -54,6 +53,7 @@ is(listen_for_binding(
         xtest_key_press(107); # Print
         xtest_key_release(107); # Print
         xtest_key_release(37); # Control_L
+        xtest_sync_with_i3;
     },
     ),
     'Control+Print',
@@ -65,6 +65,7 @@ is(listen_for_binding(
         xtest_key_press(56); # b
         xtest_key_release(56); # b
         xtest_key_release(64); # Alt_L
+        xtest_sync_with_i3;
     },
     ),
     'Mod1+b',
@@ -78,14 +79,12 @@ is(listen_for_binding(
         xtest_key_release(56); # b
         xtest_key_release(50); # Shift_L
         xtest_key_release(64); # Alt_L
+        xtest_sync_with_i3;
     },
     ),
     'Mod1+Shift+b release',
     'triggered the "Mod1+Shift+b" release keybinding');
 
-sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 4, 'Received exactly 4 binding events');
-
 }
 
 done_testing;
index ba95897c2974ffaf8e392ce40f79b0d6af2a34cd..1416b6b9e19bedf3d0e0b46feefa6c837d92413b 100644 (file)
@@ -28,23 +28,11 @@ EOT
 
 cmd 'mode othermode';
 
-my $i3 = i3(get_socket_path(0));
-$i3->connect->recv;
+my @events = events_for(
+    sub { cmd 'reload' },
+    'mode');
 
-my $cv = AnyEvent->condvar;
-$i3->subscribe({
-    mode => sub {
-        my ($event) = @_;
-        $cv->send($event->{change} eq 'default');
-    }
-})->recv;
-
-cmd 'reload';
-
-# Timeout after 0.5s
-my $t;
-$t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0); });
-
-ok($cv->recv, 'Mode event received');
+is(scalar @events, 1, 'Received 1 event');
+is($events[0]->{change}, 'default', 'change is "default"');
 
 done_testing;
index 06d8d83d15cad1bca951e84c76f5a409658acfaf..a101944e793e83233e3addc456afa9927101038e 100644 (file)
 # Ticket: #2501
 use i3test;
 
-my ($i3, $timer, $event, $mark);
+sub mark_subtest {
+    my ($cmd) = @_;
 
-$i3 = i3(get_socket_path());
-$i3->connect()->recv;
+    my @events = events_for(
+       sub { cmd $cmd },
+       'window');
 
-$i3->subscribe({
-    window => sub {
-        my ($event) = @_;
-        return unless defined $mark;
-        return unless $event->{change} eq 'mark';
-
-        $mark->send($event);
-    }
-})->recv;
-
-$timer = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $mark->send(0);
-    }
-);
+    my @mark = grep { $_->{change} eq 'mark' } @events;
+    is(scalar @mark, 1, 'Received 1 window::mark event');
+}
 
 ###############################################################################
 # Marking a container triggers a 'mark' event.
@@ -46,11 +35,7 @@ $timer = AnyEvent->timer(
 fresh_workspace;
 open_window;
 
-$mark = AnyEvent->condvar;
-cmd 'mark x';
-
-$event = $mark->recv;
-ok($event, 'window::mark event has been received');
+subtest 'mark', \&mark_subtest, 'mark x';
 
 ###############################################################################
 # Unmarking a container triggers a 'mark' event.
@@ -59,11 +44,7 @@ fresh_workspace;
 open_window;
 cmd 'mark x';
 
-$mark = AnyEvent->condvar;
-cmd 'unmark x';
-
-$event = $mark->recv;
-ok($event, 'window::mark event has been received');
+subtest 'unmark', \&mark_subtest, 'unmark x';
 
 ###############################################################################
 
index b21ff1b2ae003781b9763c818ed4ea1b7d0c5d5e..9ac749b63143950b372984f7462b40ac06c41e7d 100644 (file)
@@ -42,8 +42,8 @@ get_socket_path(0);
 my $i3 = i3(get_socket_path());
 $i3->connect->recv;
 
-my $cv = AE::cv;
-my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
+my $cv = AnyEvent->condvar;
+my $timer = AnyEvent->timer(after => 0.5, interval => 0, cb => sub { $cv->send(0); });
 
 my $last_config = $i3->get_config()->recv;
 chomp($last_config->{config});
index bf9d4f8bba6cfca833b930dcbbbe3e7b05fbb780..ccaf4440ea8a0d9dbe8b64543984e28d53c13751 100644 (file)
 # Bug still in: 4.8-7-gf4a8253
 use i3test;
 
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-my $cv;
-my $t;
-
-sub reset_test {
-    $cv = AE::cv;
-    $t = AE::timer(0.5, 0, sub { $cv->send(0); });
-}
-
-reset_test;
-
-$i3->subscribe({
-        window => sub {
-            my ($e) = @_;
-            if ($e->{change} eq 'close') {
-                $cv->send($e->{container});
-            }
-        },
-    })->recv;
-
 my $window = open_window;
 
-cmd 'kill';
-my $con = $cv->recv;
+my @events = events_for(
+    sub {
+       $window->unmap;
+       sync_with_i3;
+    },
+    'window');
 
-ok($con, 'closing a window should send the window::close event');
-is($con->{window}, $window->{id}, 'the event should contain information about the window');
+my @close = grep { $_->{change} eq 'close' } @events;
+is(scalar @close, 1, 'Received 1 window::close event');
+is($close[0]->{container}->{window}, $window->{id}, 'the event should contain information about the window');
 
 done_testing;
index a3c825348a203246b736441600ecf3b48395da48..f3606b4e8106b1c32d34a463cca45bb2d29d5d24 100644 (file)
 # Bug still in: 4.8-7-gf4a8253
 use i3test;
 
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-my $cv;
-my $t;
-
-sub reset_test {
-    $cv = AE::cv;
-    $t = AE::timer(0.5, 0, sub { $cv->send(0); });
-}
-
-reset_test;
-
-$i3->subscribe({
-        window => sub {
-            my ($e) = @_;
-            if ($e->{change} eq 'move') {
-                $cv->send($e->{container});
-            }
-        },
-    })->recv;
-
 my $dummy_window = open_window;
 my $window = open_window;
 
-cmd 'move right';
-my $con = $cv->recv;
-
-ok($con, 'moving a window should emit the window::move event');
-is($con->{window}, $window->{id}, 'the event should contain info about the window');
+sub move_subtest {
+    my ($cmd) = @_;
+    my $cv = AnyEvent->condvar;
+    my @events = events_for(
+       sub { cmd $cmd },
+       'window');
 
-reset_test;
-
-cmd 'move to workspace ws_new';
-$con = $cv->recv;
+    my @move = grep { $_->{change} eq 'move' } @events;
+    is(scalar @move, 1, 'Received 1 window::move event');
+    is($move[0]->{container}->{window}, $window->{id}, 'window id matches');
+}
 
-ok($con, 'moving a window to a different workspace should emit the window::move event');
-is($con->{window}, $window->{id}, 'the event should contain info about the window');
+subtest 'move right', \&move_subtest, 'move right';
+subtest 'move to workspace', \&move_subtest, 'move to workspace ws_new';
 
 done_testing;
index 2af29dac9de05494c71cef31d606ec535240ea6a..4eea2cdcac1ef27eed5a40a0cc25ec5bca6fbd7f 100644 (file)
 #
 use i3test;
 
-my $config = <<EOT;
-# i3 config file (v4)
-font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-
-force_display_urgency_hint 0ms
-EOT
-
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-my $cv;
-$i3->subscribe({
-    window => sub {
-        my ($event) = @_;
-        $cv->send($event) if $event->{change} eq 'urgent';
-    }
-})->recv;
-
-my $t;
-$t = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $cv->send(0);
-    }
-);
-
-$cv = AnyEvent->condvar;
 fresh_workspace;
 my $win = open_window;
 my $dummy_win = open_window;
 
-$win->add_hint('urgency');
-my $event = $cv->recv;
-
-isnt($event, 0, 'an urgent con should emit the window::urgent event');
-is($event->{container}->{window}, $win->{id}, 'the event should contain information about the window');
-is($event->{container}->{urgent}, 1, 'the container should be urgent');
-
-$cv = AnyEvent->condvar;
-$win->delete_hint('urgency');
-$event = $cv->recv;
-
-isnt($event, 0, 'an urgent con should emit the window::urgent event');
-is($event->{container}->{window}, $win->{id}, 'the event should contain information about the window');
-is($event->{container}->{urgent}, 0, 'the container should not be urgent');
+sub urgency_subtest {
+    my ($subscribecb, $win, $want) = @_;
+
+    my @events = events_for(
+       $subscribecb,
+       'window');
+
+    my @urgent = grep { $_->{change} eq 'urgent' } @events;
+    is(scalar @urgent, 1, 'Received 1 window::urgent event');
+    is($urgent[0]->{container}->{window}, $win->{id}, "window id matches");
+    is($urgent[0]->{container}->{urgent}, $want, "urgent is $want");
+}
+
+subtest "urgency set", \&urgency_subtest,
+    sub {
+       $win->add_hint('urgency');
+       sync_with_i3;
+    },
+    $win,
+    1;
+
+subtest "urgency unset", \&urgency_subtest,
+    sub {
+       $win->delete_hint('urgency');
+       sync_with_i3;
+    },
+    $win,
+    0;
 
 done_testing;
index 6c1b5d6ace7b5f561df1c66672ee8c5e55f0ac3f..0da373b74e3168129d95b6971dcf60200d5011b7 100644 (file)
@@ -30,7 +30,7 @@ fresh_workspace;
 
 xtest_button_press(4, 50, 50);
 xtest_button_release(4, 50, 50);
-sync_with_i3;
+xtest_sync_with_i3;
 
 is(focused_ws(), 'special', 'the binding was triggered');
 
index 0cf347aa88b923c210f0c551308ec05e67c3edbb..606474e24e2abb27a77648f06962d545d5ae6b54 100644 (file)
 # Bug still in: 4.12-46-g2123888
 use i3test;
 
-SKIP: {
-    skip "AnyEvent::I3 too old (need >= 0.17)", 1 if $AnyEvent::I3::VERSION < 0.17;
+# We cannot use events_for in this test as we cannot send events after
+# issuing the restart/shutdown command.
 
 my $i3 = i3(get_socket_path());
 $i3->connect->recv;
 
-my $cv = AE::cv;
-my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
+my $cv = AnyEvent->condvar;
+my $timer = AnyEvent->timer(after => 0.5, interval => 0, cb => sub { $cv->send(0); });
 
 $i3->subscribe({
         shutdown => sub {
@@ -50,8 +50,8 @@ is($e->{change}, 'restart', 'the `change` field should tell the reason for the s
 $i3 = i3(get_socket_path());
 $i3->connect->recv;
 
-$cv = AE::cv;
-$timer = AE::timer 0.5, 0, sub { $cv->send(0); };
+$cv = AnyEvent->condvar;
+$timer = AnyEvent->timer(after => 0.5, interval => 0, cb => sub { $cv->send(0); });
 
 $i3->subscribe({
         shutdown => sub {
@@ -66,6 +66,5 @@ $e = $cv->recv;
 diag "Event:\n", Dumper($e);
 ok($e, 'the shutdown event should emit when the ipc is exited by command');
 is($e->{change}, 'exit', 'the `change` field should tell the reason for the shutdown');
-}
 
 done_testing;
index 5137c35fc8e55d0e3ed1fb1f45e8c282b1d4176f..94a5747d0ffa9e5f4033ce71b0fab2a8c551b60b 100644 (file)
@@ -51,12 +51,11 @@ EOT
 
 my $pid = launch_with_config($config);
 
-start_binding_capture;
-
 is(listen_for_binding(
     sub {
         xtest_key_press(87); # KP_End
         xtest_key_release(87); # KP_End
+        xtest_sync_with_i3;
     },
     ),
    'KP_End',
@@ -70,6 +69,7 @@ is(listen_for_binding(
         xtest_key_release(87); # KP_1
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'KP_1',
@@ -81,6 +81,7 @@ is(listen_for_binding(
         xtest_key_press(38); # a
         xtest_key_release(38); # a
         xtest_key_release(133); # Super_L
+        xtest_sync_with_i3;
     },
     ),
    'a',
@@ -96,6 +97,7 @@ is(listen_for_binding(
         xtest_key_release(133); # Super_L
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'a',
@@ -105,6 +107,7 @@ is(listen_for_binding(
     sub {
         xtest_key_press(9); # Escape
         xtest_key_release(9); # Escape
+        xtest_sync_with_i3;
     },
     ),
    'Escape',
@@ -118,6 +121,7 @@ is(listen_for_binding(
         xtest_key_release(9); # Escape
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'Escape',
@@ -129,6 +133,7 @@ is(listen_for_binding(
         xtest_key_press(9); # Escape
         xtest_key_release(9); # Escape
         xtest_key_release(50); # Shift_L
+        xtest_sync_with_i3;
     },
     ),
    'Shift+Escape',
@@ -144,6 +149,7 @@ is(listen_for_binding(
         xtest_key_release(50); # Shift_L
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'Shift+Escape',
@@ -157,6 +163,7 @@ is(listen_for_binding(
         xtest_key_release(24); # q
         xtest_key_release(64); # Alt_L
         xtest_key_release(50); # Shift_L
+        xtest_sync_with_i3;
     },
     ),
    'Mod1+Shift+q',
@@ -174,6 +181,7 @@ is(listen_for_binding(
         xtest_key_release(50); # Shift_L
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'Mod1+Shift+q',
@@ -183,6 +191,7 @@ is(listen_for_binding(
     sub {
         xtest_key_press(39); # s
         xtest_key_release(39); # s
+        xtest_sync_with_i3;
     },
     ),
    's',
@@ -196,14 +205,12 @@ is(listen_for_binding(
         xtest_key_release(39); # s
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    's',
    'triggered the "s" keybinding with Num_Lock');
 
-sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 12, 'Received exactly 12 binding events');
-
 exit_gracefully($pid);
 
 ################################################################################
@@ -222,12 +229,11 @@ EOT
 
 $pid = launch_with_config($config);
 
-start_binding_capture;
-
 is(listen_for_binding(
     sub {
         xtest_key_press(133); # Super_L
         xtest_key_release(133); # Super_L
+        xtest_sync_with_i3;
     },
     ),
    'Super_L',
@@ -241,6 +247,7 @@ is(listen_for_binding(
         xtest_key_release(133); # Super_L
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'Super_L',
@@ -252,6 +259,7 @@ is(listen_for_binding(
         xtest_key_press(36); # Return
         xtest_key_release(36); # Return
         xtest_key_release(133); # Super_L
+        xtest_sync_with_i3;
     },
     ),
    'Return',
@@ -267,14 +275,12 @@ is(listen_for_binding(
         xtest_key_release(133); # Super_L
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'Return',
    'triggered the "Return" keybinding with Num_Lock');
 
-sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 16, 'Received exactly 16 binding events');
-
 exit_gracefully($pid);
 
 ################################################################################
@@ -291,12 +297,11 @@ EOT
 
 $pid = launch_with_config($config);
 
-start_binding_capture;
-
 is(listen_for_binding(
     sub {
         xtest_key_press(87); # KP_End
         xtest_key_release(87); # KP_End
+        xtest_sync_with_i3;
     },
     ),
    'KP_End',
@@ -306,12 +311,13 @@ is(listen_for_binding(
     sub {
         xtest_key_press(88); # KP_Down
         xtest_key_release(88); # KP_Down
+        xtest_sync_with_i3;
     },
     ),
    'KP_Down',
    'triggered the "KP_Down" keybinding');
 
-is(listen_for_binding(
+my @unexpected = events_for(
     sub {
         xtest_key_press(77); # enable Num_Lock
         xtest_key_release(77); # enable Num_Lock
@@ -319,12 +325,12 @@ is(listen_for_binding(
         xtest_key_release(87); # KP_1
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
-    ),
-   'timeout',
-   'Did not trigger the KP_End keybinding with KP_1');
+    'binding');
+is(scalar @unexpected, 0, 'Did not trigger the KP_End keybinding with KP_1');
 
-is(listen_for_binding(
+my @unexpected2 = events_for(
     sub {
         xtest_key_press(77); # enable Num_Lock
         xtest_key_release(77); # enable Num_Lock
@@ -332,15 +338,13 @@ is(listen_for_binding(
         xtest_key_release(88); # KP_2
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
-    ),
-   'timeout',
-   'Did not trigger the KP_Down keybinding with KP_2');
+    'binding');
 
-# TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2.
+is(scalar @unexpected2, 0, 'Did not trigger the KP_Down keybinding with KP_2');
 
-sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 18, 'Received exactly 18 binding events');
+# TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2.
 
 exit_gracefully($pid);
 
@@ -359,8 +363,6 @@ $pid = launch_with_config($config);
 
 my $win = open_window;
 
-start_binding_capture;
-
 is(listen_for_binding(
     sub {
         xtest_key_press(77); # enable Num_Lock
@@ -369,6 +371,7 @@ is(listen_for_binding(
         xtest_button_release(4, 50, 50);
         xtest_key_press(77); # disable Num_Lock
         xtest_key_release(77); # disable Num_Lock
+        xtest_sync_with_i3;
     },
     ),
    'button4',
@@ -376,8 +379,9 @@ is(listen_for_binding(
 
 is(listen_for_binding(
     sub {
-       xtest_button_press(4, 50, 50);
-       xtest_button_release(4, 50, 50);
+        xtest_button_press(4, 50, 50);
+        xtest_button_release(4, 50, 50);
+        xtest_sync_with_i3;
     },
     ),
    'button4',
index e3753bec681ffffa3ac15ac2814d0c1b76e38d3c..ac918fe379d74c160fed7c672156f47edf5af34f 100644 (file)
@@ -13,7 +13,7 @@
 #
 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
 #   (unless you are already familiar with Perl)
-# 
+#
 # Ticket: #990
 # Bug still in: 4.5.1-23-g82b5978
 
@@ -23,46 +23,17 @@ font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 fake-outputs 1024x768+0+0,1024x768+1024+0
 EOT
 
-my $i3 = i3(get_socket_path());
-
-$i3->connect()->recv;
-
-################################
-# Workspaces requests and events
-################################
-
 my $old_ws = get_ws(focused_ws);
 
-# Events
-
-# We are switching to an empty workpspace on the output to the right from an empty workspace on the output on the left, so we expect
-# to receive "init", "focus", and "empty".
 my $focus = AnyEvent->condvar;
-$i3->subscribe({
-    workspace => sub {
-        my ($event) = @_;
-        if ($event->{change} eq 'focus') {
-            $focus->send($event);
-        }
-    }
-})->recv;
-
-my $t;
-$t = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $focus->send(0);
-    }
-);
-
-cmd 'focus output right';
-
-my $event = $focus->recv;
+my @events = events_for(
+    sub { cmd 'focus output right' },
+    'workspace');
 
 my $current_ws = get_ws(focused_ws);
 
-ok($event, 'Workspace "focus" event received');
-is($event->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace');
-is($event->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace');
+is(scalar @events, 1, 'Received 1 event');
+is($events[0]->{current}->{id}, $current_ws->{id}, 'Event gave correct current workspace');
+is($events[0]->{old}->{id}, $old_ws->{id}, 'Event gave correct old workspace');
 
 done_testing;
index 217d898ca2f2dbdb202368920dcbf8ee03a847f6..2f7f2b27f897b5168a4fa839071f9d7614bf2430 100644 (file)
@@ -27,51 +27,29 @@ workspace ws-left output fake-0
 workspace ws-right output fake-1
 EOT
 
-my $i3 = i3(get_socket_path());
-$i3->connect()->recv;
-
-# subscribe to the 'focus' ipc event
-my $focus = AnyEvent->condvar;
-$i3->subscribe({
-    workspace => sub {
-        my ($event) = @_;
-        if ($event->{change} eq 'focus') {
-            $focus->send($event);
-        }
-    }
-})->recv;
-
-# give up after 0.5 seconds
-my $timer = AnyEvent->timer(
-    after => 0.5,
-    cb => sub {
-        $focus->send(0);
-    }
-);
-
 # open two windows on the left output
 cmd 'workspace ws-left';
 open_window;
 open_window;
 
-# move a window over to the right output
-cmd 'move right';
-my $event = $focus->recv;
+sub focus_subtest {
+    my ($cmd, $want) = @_;
 
-ok($event, 'moving from workspace with two windows triggered focus ipc event');
-is($event->{current}->{name}, 'ws-right', 'focus event gave the right workspace');
-is(@{$event->{current}->{nodes}}, 1, 'focus event gave the right number of windows on the workspace');
+    my @events = events_for(
+       sub { cmd $cmd },
+       'workspace');
 
-# reset and try again
-$focus = AnyEvent->condvar;
-cmd 'workspace ws-left';
-$focus->recv;
+    my @focus = grep { $_->{change} eq 'focus' } @events;
+    is(scalar @focus, 1, 'Received 1 workspace::focus event');
+    is($focus[0]->{current}->{name}, 'ws-right', 'focus event gave the right workspace');
+    is(@{$focus[0]->{current}->{nodes}}, $want, 'focus event gave the right number of windows on the workspace');
+}
+
+# move a window over to the right output
+subtest 'move right (1)', \&focus_subtest, 'move right', 1;
 
-$focus = AnyEvent->condvar;
-cmd 'move right';
-$event = $focus->recv;
-ok($event, 'moving from workspace with one window triggered focus ipc event');
-is($event->{current}->{name}, 'ws-right', 'focus event gave the right workspace');
-is(@{$event->{current}->{nodes}}, 2, 'focus event gave the right number of windows on the workspace');
+# move another window
+cmd 'workspace ws-left';
+subtest 'move right (2)', \&focus_subtest, 'move right', 2;
 
 done_testing;
index ff34328c6c35ee2ecbc727b07dd6aecfbc08d5f0..57786deaf2f8bc6e727498f61268ce450ee4e88d 100644 (file)
@@ -34,17 +34,12 @@ bar {
 EOT
 use i3test::XTEST;
 
-my ($cv, $timer);
-sub reset_test {
-    $cv = AE::cv;
-    $timer = AE::timer(1, 0, sub { $cv->send(0); });
-}
-
 my $i3 = i3(get_socket_path());
 $i3->connect()->recv;
 my $ws = fresh_workspace;
 
-reset_test;
+my $cv = AnyEvent->condvar;
+my $timer = AnyEvent->timer(after => 1, interval => 0, cb => sub { $cv->send(0) });
 $i3->subscribe({
         window => sub {
             my ($event) = @_;
@@ -60,15 +55,13 @@ $i3->subscribe({
         },
     })->recv;
 
-my $con;
-
 sub i3bar_present {
     my ($nodes) = @_;
 
     for my $node (@{$nodes}) {
        my $props = $node->{window_properties};
        if (defined($props) && $props->{class} eq 'i3bar') {
-           return 1;
+           return $node->{window};
        }
     }
 
@@ -80,53 +73,73 @@ sub i3bar_present {
     return i3bar_present(\@children);
 }
 
-if (i3bar_present($i3->get_tree->recv->{nodes})) {
+my $i3bar_window = i3bar_present($i3->get_tree->recv->{nodes});
+if ($i3bar_window) {
     ok(1, 'i3bar present');
 } else {
-    $con = $cv->recv;
+    my $con = $cv->recv;
     ok($con, 'i3bar appeared');
+    $i3bar_window = $con->{window};
 }
 
+diag('i3bar window = ' . $i3bar_window);
+
 my $left = open_window;
 my $right = open_window;
 sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $right->{id}, 'focus is initially on the right container');
-reset_test;
 
-xtest_button_press(1, 3, 3);
-xtest_button_release(1, 3, 3);
-sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $left->{id}, 'button 1 moves focus left');
-reset_test;
-
-xtest_button_press(2, 3, 3);
-xtest_button_release(2, 3, 3);
-sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $right->{id}, 'button 2 moves focus right');
-reset_test;
-
-xtest_button_press(3, 3, 3);
-xtest_button_release(3, 3, 3);
-sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $left->{id}, 'button 3 moves focus left');
-reset_test;
-
-xtest_button_press(4, 3, 3);
-xtest_button_release(4, 3, 3);
-sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $right->{id}, 'button 4 moves focus right');
-reset_test;
+sub focus_subtest {
+    my ($subscribecb, $want, $msg) = @_;
+    my @events = events_for(
+       $subscribecb,
+       'window');
+    my @focus = map { $_->{container}->{window} } grep { $_->{change} eq 'focus' } @events;
+    is_deeply(\@focus, $want, $msg);
+}
 
-xtest_button_press(5, 3, 3);
-xtest_button_release(5, 3, 3);
-sync_with_i3;
-$con = $cv->recv;
-is($con->{window}, $left->{id}, 'button 5 moves focus left');
-reset_test;
+subtest 'button 1 moves focus left', \&focus_subtest,
+    sub {
+       xtest_button_press(1, 3, 3);
+       xtest_button_release(1, 3, 3);
+       xtest_sync_with($i3bar_window);
+    },
+    [ $left->{id} ],
+    'button 1 moves focus left';
+
+subtest 'button 2 moves focus right', \&focus_subtest,
+    sub {
+       xtest_button_press(2, 3, 3);
+       xtest_button_release(2, 3, 3);
+       xtest_sync_with($i3bar_window);
+    },
+    [ $right->{id} ],
+    'button 2 moves focus right';
+
+subtest 'button 3 moves focus left', \&focus_subtest,
+    sub {
+       xtest_button_press(3, 3, 3);
+       xtest_button_release(3, 3, 3);
+       xtest_sync_with($i3bar_window);
+    },
+    [ $left->{id} ],
+    'button 3 moves focus left';
+
+subtest 'button 4 moves focus right', \&focus_subtest,
+    sub {
+       xtest_button_press(4, 3, 3);
+       xtest_button_release(4, 3, 3);
+       xtest_sync_with($i3bar_window);
+    },
+    [ $right->{id} ],
+    'button 4 moves focus right';
+
+subtest 'button 5 moves focus left', \&focus_subtest,
+    sub {
+       xtest_button_press(5, 3, 3);
+       xtest_button_release(5, 3, 3);
+       xtest_sync_with($i3bar_window);
+    },
+    [ $left->{id} ],
+    'button 5 moves focus left';
 
 done_testing;
index 44df81d2ffbdf257fdb0c428999290c56c711db5..eac2ea8a37715f340f27e17969419ff55bceb4b0 100755 (executable)
@@ -26,7 +26,7 @@ fi
 
 # Try running the tests in parallel so that the common case (tests pass) is
 # quick, but fall back to running them in sequence to make debugging easier.
-if ! xvfb-run make check
+if ! make check
 then
-       xvfb-run ./testcases/complete-run.pl --parallel=1 || (cat latest/complete-run.log; false)
+       ./testcases/complete-run.pl --parallel=1 || (cat latest/complete-run.log; false)
 fi