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} } );
$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
src/sd-daemon.c \
src/sighandler.c \
src/startup.c \
+ src/sync.c \
src/tree.c \
src/util.c \
src/version.c \
| 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:
{ "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]]
/* 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 */
};
/*
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");
#include "fake_outputs.h"
#include "display_version.h"
#include "restore_layout.h"
+#include "sync.h"
#include "main.h"
/** 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
*
#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.
--- /dev/null
+/*
+ * 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 <xcb/xcb.h>
+
+void sync_respond(xcb_window_t window, uint32_t rnd);
} 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
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,
handle_get_binding_modes,
handle_get_config,
handle_send_tick,
+ handle_sync,
};
/*
--- /dev/null
+/*
+ * 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);
+}
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';
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';
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';
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';
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';
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';
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';
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';
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';