diff --git a/docs/ipc b/docs/ipc index ce38a546..c95bcd57 100644 --- a/docs/ipc +++ b/docs/ipc @@ -1,7 +1,7 @@ IPC interface (interprocess communication) ========================================== Michael Stapelberg -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 @@ -638,6 +638,9 @@ window (3):: 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:* -------------------------------------------------------------------- @@ -749,6 +752,47 @@ This event consists of a single serialized map reporting on options from the 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]] diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 9562a209..f1b50dec 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -100,3 +100,6 @@ typedef struct i3_ipc_header { /** 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) diff --git a/include/ipc.h b/include/ipc.h index e9358ee5..77dcad21 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -105,3 +105,8 @@ void ipc_send_window_event(const char *property, Con *con); * 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); diff --git a/src/bindings.c b/src/bindings.c index cd91a398..b75c6357 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -423,7 +423,7 @@ CommandResult *run_binding(Binding *bind, Con *con) { free(pageraction); } - /* TODO: emit event for running a binding */ + ipc_send_binding_event("run", bind); return result; } diff --git a/src/ipc.c b/src/ipc.c index 6dab654c..e93fe567 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -151,6 +151,57 @@ static void dump_rect(yajl_gen gen, const char *name, Rect r) { 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"); @@ -1146,3 +1197,33 @@ void ipc_send_barconfig_update_event(Barconfig *barconfig) { 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, ""); +} diff --git a/testcases/t/238-ipc-binding-event.t b/testcases/t/238-ipc-binding-event.t new file mode 100644 index 00000000..a968bd1a --- /dev/null +++ b/testcases/t/238-ipc-binding-event.t @@ -0,0 +1,86 @@ +#!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 = < /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;