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/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/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/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"); 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/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/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/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, }; /* 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); +} 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';