IPC interface (interprocess communication)
==========================================
Michael Stapelberg <michael@i3wm.org>
-February 2014
+October 2014
This document describes how to interface with i3 from a separate process. This
is useful for example to remote-control i3 (to write test cases for example) or
barconfig_update (4)::
Sent when the hidden_state or mode field in the barconfig of any bar
instance was updated and when the config is reloaded.
+binding (5)::
+ Sent when a configured command binding is triggered with the keyboard or
+ mouse
*Example:*
--------------------------------------------------------------------
barconfig of the specified bar_id that were updated in i3. This event is the
same as a +GET_BAR_CONFIG+ reply for the bar with the given id.
+=== binding event
+
+This event consists of a single serialized map reporting on the details of a
+binding that ran a command because of user input. The +change (sring)+ field
+indicates what sort of binding event was triggered (right now it will always be
++"run"+ but may be expanded in the future).
+
+The +binding (object)+ field contains details about the binding that was run:
+
+command (string)::
+ The i3 command that is configured to run for this binding.
+mods (array of strings)::
+ The modifier keys that were configured with this binding.
+input_code (integer)::
+ If the binding was configured with +bindcode+, this will be the key code
+ that was given for the binding. If the binding is a mouse binding, it will be
+ the number of the mouse button that was pressed. Otherwise it will be 0.
+symbol (string)::
+ If this is a keyboard binding that was configured with +bindsym+, this
+ field will contain the given symbol.
+input_type (string)::
+ This will be +"keyboard"+ or +"mouse"+ depending on whether or not this was
+ a keyboard or a mouse binding.
+
+*Example:*
+---------------------------
+{
+ "change": "run",
+ "binding": {
+ "command": "nop",
+ "mods": [
+ "shift",
+ "ctrl"
+ ],
+ "input_code": 0,
+ "symbol": "t",
+ "input_type": "keyboard"
+ }
+}
+---------------------------
+
== See also (existing libraries)
[[libraries]]
/** Bar config update will be triggered to update the bar config */
#define I3_IPC_EVENT_BARCONFIG_UPDATE (I3_IPC_EVENT_MASK | 4)
+
+/** The binding event will be triggered when bindings run */
+#define I3_IPC_EVENT_BINDING (I3_IPC_EVENT_MASK | 5)
* For the barconfig update events, we send the serialized barconfig.
*/
void ipc_send_barconfig_update_event(Barconfig *barconfig);
+
+/**
+ * For the binding events, we send the serialized binding struct.
+ */
+void ipc_send_binding_event(const char *event_type, Binding *bind);
free(pageraction);
}
- /* TODO: emit event for running a binding */
+ ipc_send_binding_event("run", bind);
return result;
}
y(map_close);
}
+static void dump_binding(yajl_gen gen, Binding *bind) {
+ y(map_open);
+ ystr("input_code");
+ y(integer, bind->keycode);
+
+ ystr("input_type");
+ ystr((const char*)(bind->input_type == B_KEYBOARD ? "keyboard" : "mouse"));
+
+ ystr("symbol");
+ ystr(bind->symbol);
+
+ ystr("command");
+ ystr(bind->command);
+
+ ystr("mods");
+ y(array_open);
+ for (int i = 0; i < 8; i++) {
+ if (bind->mods & (1 << i)) {
+ switch (1 << i) {
+ case XCB_MOD_MASK_SHIFT:
+ ystr("shift");
+ break;
+ case XCB_MOD_MASK_LOCK:
+ ystr("lock");
+ break;
+ case XCB_MOD_MASK_CONTROL:
+ ystr("ctrl");
+ break;
+ case XCB_MOD_MASK_1:
+ ystr("Mod1");
+ break;
+ case XCB_MOD_MASK_2:
+ ystr("Mod2");
+ break;
+ case XCB_MOD_MASK_3:
+ ystr("Mod3");
+ break;
+ case XCB_MOD_MASK_4:
+ ystr("Mod4");
+ break;
+ case XCB_MOD_MASK_5:
+ ystr("Mod5");
+ break;
+ }
+ }
+ }
+ y(array_close);
+
+ y(map_close);
+}
+
void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
y(map_open);
ystr("id");
y(free);
setlocale(LC_NUMERIC, "");
}
+
+/*
+ * For the binding events, we send the serialized binding struct.
+ */
+void ipc_send_binding_event(const char *event_type, Binding *bind) {
+ DLOG("Issue IPC binding %s event (sym = %s, code = %d)\n", event_type, bind->symbol, bind->keycode);
+
+ setlocale(LC_NUMERIC, "C");
+
+ yajl_gen gen = ygenalloc();
+
+ y(map_open);
+
+ ystr("change");
+ ystr(event_type);
+
+ ystr("binding");
+ dump_binding(gen, bind);
+
+ y(map_close);
+
+ const unsigned char *payload;
+ ylength length;
+ y(get_buf, &payload, &length);
+
+ ipc_send_event("binding", I3_IPC_EVENT_BINDING, (const char *)payload);
+
+ y(free);
+ setlocale(LC_NUMERIC, "");
+}
--- /dev/null
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Please read the following documents before working on tests:
+# • http://build.i3wm.org/docs/testsuite.html
+# (or docs/testsuite)
+#
+# • http://build.i3wm.org/docs/lib-i3test.html
+# (alternatively: perldoc ./testcases/lib/i3test.pm)
+#
+# • http://build.i3wm.org/docs/ipc.html
+# (or docs/ipc)
+#
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
+# (unless you are already familiar with Perl)
+#
+# Test that the binding event works properly
+# Ticket: #1210
+use i3test i3_autostart => 0;
+
+my $keysym = 't';
+my $command = 'nop';
+my @mods = ('Shift', 'Ctrl');
+my $binding_symbol = join("+", @mods) . "+$keysym";
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bindsym $binding_symbol $command
+EOT
+
+SKIP: {
+ qx(which xdotool 2> /dev/null);
+
+ skip 'xdotool is required to test the binding event. `[apt-get install|pacman -S] xdotool`', 1 if $?;
+
+ 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;
+
+ diag "Event:\n", Dumper($e);
+
+ ok($e,
+ 'the binding event should emit when user input triggers an i3 binding event');
+
+ is($e->{change}, 'run',
+ 'the `change` field should indicate this binding has run');
+
+ ok($e->{binding},
+ 'the `binding` field should be a hash that contains information about the binding');
+
+ is($e->{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 $_");
+ }
+
+ is($e->{binding}->{command}, $command,
+ 'the `command` field should contain the command the binding ran');
+
+ is($e->{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);
+
+}
+done_testing;