From 725ee3ce6269947577deb7a67a0e1d9762c21600 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 30 Mar 2018 21:05:32 +0200 Subject: [PATCH 1/4] move i3 sync code into sync_respond (for following commits) --- Makefile.am | 1 + include/all.h | 1 + include/sync.h | 14 ++++++++++++++ src/handlers.c | 16 +--------------- src/sync.c | 28 ++++++++++++++++++++++++++++ 5 files changed, 45 insertions(+), 15 deletions(-) create mode 100644 include/sync.h create mode 100644 src/sync.c diff --git a/Makefile.am b/Makefile.am index 184b0734..557c65a5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -562,6 +562,7 @@ i3_SOURCES = \ src/sd-daemon.c \ src/sighandler.c \ src/startup.c \ + src/sync.c \ src/tree.c \ src/util.c \ src/version.c \ diff --git a/include/all.h b/include/all.h index ecc875d0..e93b066b 100644 --- a/include/all.h +++ b/include/all.h @@ -82,4 +82,5 @@ #include "fake_outputs.h" #include "display_version.h" #include "restore_layout.h" +#include "sync.h" #include "main.h" diff --git a/include/sync.h b/include/sync.h new file mode 100644 index 00000000..e726f99e --- /dev/null +++ b/include/sync.h @@ -0,0 +1,14 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) + * + * sync.c: i3 sync protocol: https://i3wm.org/docs/testsuite.html#i3_sync + * + */ +#pragma once + +#include + +void sync_respond(xcb_window_t window, uint32_t rnd); diff --git a/src/handlers.c b/src/handlers.c index 50fd8566..d5023b9d 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -800,21 +800,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { } else if (event->type == A_I3_SYNC) { xcb_window_t window = event->data.data32[0]; uint32_t rnd = event->data.data32[1]; - DLOG("[i3 sync protocol] Sending random value %d back to X11 window 0x%08x\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 = A_I3_SYNC; - ev->format = 32; - ev->data.data32[0] = window; - ev->data.data32[1] = rnd; - - xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)ev); - xcb_flush(conn); - free(reply); + sync_respond(window, rnd); } else if (event->type == A__NET_REQUEST_FRAME_EXTENTS) { /* * A client can request an estimate for the frame size which the window diff --git a/src/sync.c b/src/sync.c new file mode 100644 index 00000000..dafbc355 --- /dev/null +++ b/src/sync.c @@ -0,0 +1,28 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) + * + * sync.c: i3 sync protocol: https://i3wm.org/docs/testsuite.html#i3_sync + * + */ +#include "all.h" + +void sync_respond(xcb_window_t window, uint32_t rnd) { + DLOG("[i3 sync protocol] Sending random value %d back to X11 window 0x%08x\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 = A_I3_SYNC; + ev->format = 32; + ev->data.data32[0] = window; + ev->data.data32[1] = rnd; + + xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)ev); + xcb_flush(conn); + free(reply); +} From eca8fae2de2a2f7d6a8953d5ce6e0a76c6dc1264 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 30 Mar 2018 21:06:18 +0200 Subject: [PATCH 2/4] introduce the sync IPC command Sending the sync command via IPC ensures pending IPC messages are handled by i3 before the sync response is read. This is rarely useful for direct IPC connections to i3, but becomes useful when synchronizing with i3bar, which might have pending IPC messages in response to button clicks. --- AnyEvent-I3/lib/AnyEvent/I3.pm | 16 ++++++++- docs/ipc | 13 +++++++ include/i3/ipc.h | 4 +++ src/ipc.c | 62 +++++++++++++++++++++++++++++++++- 4 files changed, 93 insertions(+), 2 deletions(-) diff --git a/AnyEvent-I3/lib/AnyEvent/I3.pm b/AnyEvent-I3/lib/AnyEvent/I3.pm index 198c41c9..ae9e5bea 100644 --- a/AnyEvent-I3/lib/AnyEvent/I3.pm +++ b/AnyEvent-I3/lib/AnyEvent/I3.pm @@ -100,11 +100,12 @@ 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; +use constant TYPE_SYNC => 11; 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_SEND_TICK) + TYPE_GET_BINDING_MODES TYPE_GET_CONFIG TYPE_SEND_TICK TYPE_SYNC) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); @@ -534,6 +535,19 @@ sub send_tick { $self->message(TYPE_SEND_TICK, $payload); } +=head2 sync + +Sends an i3 sync event. Requires i3 >= 4.16 + +=cut +sub sync { + my ($self, $payload) = @_; + + $self->_ensure_connection; + + $self->message(TYPE_SYNC, $payload); +} + =head2 command($content) Makes i3 execute the given command diff --git a/docs/ipc b/docs/ipc index 8b767ade..f011e773 100644 --- a/docs/ipc +++ b/docs/ipc @@ -65,6 +65,7 @@ to do that). | 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. +| 11 | +SYNC+ | <<_sync_reply,SYNC>> | Sends an i3 sync event with the specified random value to the specified window. |====================================================== So, a typical message could look like this: @@ -654,6 +655,18 @@ events generated prior to the +SEND_TICK+ message (happened-before relation). { "success": true } ------------------- +[[_sync_reply]] +=== SYNC reply + +The reply is a map containing the "success" member. After the reply was +received, the https://i3wm.org/docs/testsuite.html#i3_sync[i3 sync message] was +responded to. + +*Example:* +------------------- +{ "success": true } +------------------- + == Events [[events]] diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 9e0280c9..0c57f7fd 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -63,6 +63,9 @@ typedef struct i3_ipc_header { /** Send a tick event to all subscribers. */ #define I3_IPC_MESSAGE_TYPE_SEND_TICK 10 +/** Trigger an i3 sync protocol message via IPC. */ +#define I3_IPC_MESSAGE_TYPE_SYNC 11 + /* * Messages from i3 to clients * @@ -78,6 +81,7 @@ typedef struct i3_ipc_header { #define I3_IPC_REPLY_TYPE_BINDING_MODES 8 #define I3_IPC_REPLY_TYPE_CONFIG 9 #define I3_IPC_REPLY_TYPE_TICK 10 +#define I3_IPC_REPLY_TYPE_SYNC 11 /* * Events from i3 to clients. Events have the first bit set high. diff --git a/src/ipc.c b/src/ipc.c index 6b6383ec..d422217e 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -1173,9 +1173,68 @@ IPC_HANDLER(send_tick) { DLOG("Sent tick event\n"); } +struct sync_state { + char *last_key; + uint32_t rnd; + xcb_window_t window; +}; + +static int _sync_json_key(void *extra, const unsigned char *val, size_t len) { + struct sync_state *state = extra; + FREE(state->last_key); + state->last_key = scalloc(len + 1, 1); + memcpy(state->last_key, val, len); + return 1; +} + +static int _sync_json_int(void *extra, long long val) { + struct sync_state *state = extra; + if (strcasecmp(state->last_key, "rnd") == 0) { + state->rnd = val; + } else if (strcasecmp(state->last_key, "window") == 0) { + state->window = (xcb_window_t)val; + } + return 1; +} + +IPC_HANDLER(sync) { + yajl_handle p; + yajl_status stat; + + /* Setup the JSON parser */ + static yajl_callbacks callbacks = { + .yajl_map_key = _sync_json_key, + .yajl_integer = _sync_json_int, + }; + + struct sync_state state; + memset(&state, '\0', sizeof(struct sync_state)); + p = yalloc(&callbacks, (void *)&state); + stat = yajl_parse(p, (const unsigned char *)message, message_size); + FREE(state.last_key); + if (stat != yajl_status_ok) { + unsigned char *err; + err = yajl_get_error(p, true, (const unsigned char *)message, + message_size); + ELOG("YAJL parse error: %s\n", err); + yajl_free_error(p, err); + + const char *reply = "{\"success\":false}"; + ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SYNC, (const uint8_t *)reply); + yajl_free(p); + return; + } + yajl_free(p); + + DLOG("received IPC sync request (rnd = %d, window = 0x%08x)\n", state.rnd, state.window); + sync_respond(state.window, state.rnd); + const char *reply = "{\"success\":true}"; + ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SYNC, (const uint8_t *)reply); +} + /* The index of each callback function corresponds to the numeric * value of the message type (see include/i3/ipc.h) */ -handler_t handlers[11] = { +handler_t handlers[12] = { handle_run_command, handle_get_workspaces, handle_subscribe, @@ -1187,6 +1246,7 @@ handler_t handlers[11] = { handle_get_binding_modes, handle_get_config, handle_send_tick, + handle_sync, }; /* From 145ac532aaa09259ff71a8ab2a75c3bf4004a80c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 30 Mar 2018 21:07:48 +0200 Subject: [PATCH 3/4] i3bar: forward the sync request via IPC, not X11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i3bar’s X11 output is not what our testcases are testing — the state manipulations which i3bar triggers via IPC messages to i3 are what we are interested in. --- i3bar/src/ipc.c | 19 ++++++++++++------- i3bar/src/xcb.c | 21 ++++++--------------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index cc3563ec..7a657338 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -114,13 +114,18 @@ void got_bar_config(char *reply) { /* Data structure to easily call the reply handlers later */ handler_t reply_handlers[] = { - &got_command_reply, - &got_workspace_reply, - &got_subscribe_reply, - &got_output_reply, - NULL, - NULL, - &got_bar_config, + &got_command_reply, /* I3_IPC_REPLY_TYPE_COMMAND */ + &got_workspace_reply, /* I3_IPC_REPLY_TYPE_WORKSPACES */ + &got_subscribe_reply, /* I3_IPC_REPLY_TYPE_SUBSCRIBE */ + &got_output_reply, /* I3_IPC_REPLY_TYPE_OUTPUTS */ + NULL, /* I3_IPC_REPLY_TYPE_TREE */ + NULL, /* I3_IPC_REPLY_TYPE_MARKS */ + &got_bar_config, /* I3_IPC_REPLY_TYPE_BAR_CONFIG */ + NULL, /* I3_IPC_REPLY_TYPE_VERSION */ + NULL, /* I3_IPC_REPLY_TYPE_BINDING_MODES */ + NULL, /* I3_IPC_REPLY_TYPE_CONFIG */ + NULL, /* I3_IPC_REPLY_TYPE_TICK */ + NULL, /* I3_IPC_REPLY_TYPE_SYNC */ }; /* diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 542c86c3..8843edbd 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -694,21 +694,12 @@ static void handle_client_message(xcb_client_message_event_t *event) { 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); + /* Forward the request to i3 via the IPC interface so that all pending + * IPC messages are guaranteed to be handled. */ + char *payload = NULL; + sasprintf(&payload, "{\"rnd\":%d, \"window\":%d}", rnd, window); + i3_send_msg(I3_IPC_MESSAGE_TYPE_SYNC, payload); + free(payload); } else if (event->type == atoms[_NET_SYSTEM_TRAY_OPCODE] && event->format == 32) { DLOG("_NET_SYSTEM_TRAY_OPCODE received\n"); From 874151bb09ae7e2b06b4e96c2396972d21a82a07 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 30 Mar 2018 21:08:35 +0200 Subject: [PATCH 4/4] t/525-i3bar-mouse-bindings.t: sync with i3 _and_ i3bar See the comment in the code for rationale. --- testcases/t/525-i3bar-mouse-bindings.t | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/testcases/t/525-i3bar-mouse-bindings.t b/testcases/t/525-i3bar-mouse-bindings.t index 87552785..3593ea0b 100644 --- a/testcases/t/525-i3bar-mouse-bindings.t +++ b/testcases/t/525-i3bar-mouse-bindings.t @@ -100,11 +100,19 @@ sub focus_subtest { is_deeply(\@focus, $want, $msg); } +sub sync { + # Ensure XTEST events were sent to i3, which grabs and hence needs to + # forward any events to i3bar: + xtest_sync_with_i3; + # Ensure any pending i3bar IPC messages were handled by i3: + xtest_sync_with($i3bar_window); +} + 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); + sync; }, [ $left->{id} ], 'button 1 moves focus left'; @@ -113,7 +121,7 @@ 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); + sync; }, [ $right->{id} ], 'button 2 moves focus right'; @@ -122,7 +130,7 @@ 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); + sync; }, [ $left->{id} ], 'button 3 moves focus left'; @@ -131,7 +139,7 @@ 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); + sync; }, [ $right->{id} ], 'button 4 moves focus right'; @@ -140,7 +148,7 @@ 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); + sync; }, [ $left->{id} ], 'button 5 moves focus left'; @@ -152,7 +160,7 @@ my $old_focus = get_focused($ws); subtest 'button 6 does not move focus while pressed', \&focus_subtest, sub { xtest_button_press(6, 3, 3); - xtest_sync_with($i3bar_window); + sync; }, [], 'button 6 does not move focus while pressed'; @@ -161,7 +169,7 @@ is(get_focused($ws), $old_focus, 'focus unchanged'); subtest 'button 6 release moves focus right', \&focus_subtest, sub { xtest_button_release(6, 3, 3); - xtest_sync_with($i3bar_window); + sync; }, [ $right->{id} ], 'button 6 release moves focus right'; @@ -171,7 +179,7 @@ subtest 'button 6 release moves focus right', \&focus_subtest, subtest 'button 7 press moves focus left', \&focus_subtest, sub { xtest_button_press(7, 3, 3); - xtest_sync_with($i3bar_window); + sync; }, [ $left->{id} ], 'button 7 press moves focus left'; @@ -179,7 +187,7 @@ subtest 'button 7 press moves focus left', \&focus_subtest, subtest 'button 7 release moves focus right', \&focus_subtest, sub { xtest_button_release(7, 3, 3); - xtest_sync_with($i3bar_window); + sync; }, [ $right->{id} ], 'button 7 release moves focus right';