i3 - improved tiling WM


Implement the ipc 'binding' event

Patch status: needinfo

Patch by Tony Crisci

Long description:

The binding event will be triggered when a binding is run as a result of
some a user action. The binding event has the following properties:

change: (str) Currently this will only be "run" but may be expanded in
the future. Included for consistency with other events.

binding: (map) the serialized binding

The "binding" member will have these properties:

input_type: (str) either "keyboard" or "mouse"

input_code: (int) the xcb keycode of the keyboard binding if it was
provided or the mouse button if it is a mouse binding.

symbol: (str) the string representation of the input code

command: (str) the bound command

mods: (list of str) a list of the modifiers that were pressed as string
symbols

fixes #1210

To apply this patch, use:
curl http://cr.i3wm.org/patch/590/raw.patch | git am

b/include/i3/ipc.h

41
@@ -100,3 +100,6 @@ typedef struct i3_ipc_header {
42
 
43
 /** Bar config update will be triggered to update the bar config */
44
 #define I3_IPC_EVENT_BARCONFIG_UPDATE           (I3_IPC_EVENT_MASK | 4)
45
+
46
+/** The binding event will be triggered when bindings run */
47
+#define I3_IPC_EVENT_BINDING                    (I3_IPC_EVENT_MASK | 5)

b/include/ipc.h

52
@@ -99,3 +99,8 @@ void ipc_send_window_event(const char *property, Con *con);
53
  * For the barconfig update events, we send the serialized barconfig.
54
  */
55
 void ipc_send_barconfig_update_event(Barconfig *barconfig);
56
+
57
+/**
58
+ * For the binding events, we send the serialized binding struct.
59
+ */
60
+void ipc_send_binding_event(const char *event_type, Binding *bind);

b/src/bindings.c

65
@@ -415,6 +415,7 @@ CommandResult *run_binding(Binding *bind) {
66
     }
67
 
68
     /* TODO: emit event for running a binding */
69
+    ipc_send_binding_event("run", bind);
70
 
71
     return result;
72
 }

b/src/ipc.c

77
@@ -151,6 +151,57 @@ static void dump_rect(yajl_gen gen, const char *name, Rect r) {
78
     y(map_close);
79
 }
80
 
81
+static void dump_binding(yajl_gen gen, Binding *bind) {
82
+    y(map_open);
83
+    ystr("input_code");
84
+    y(integer, bind->keycode);
85
+
86
+    ystr("input_type");
87
+    ystr((const char*)(bind->input_type == B_KEYBOARD ? "keyboard" : "mouse"));
88
+
89
+    ystr("symbol");
90
+    ystr(bind->symbol);
91
+
92
+    ystr("command");
93
+    ystr(bind->command);
94
+
95
+    ystr("mods");
96
+    y(array_open);
97
+    for (int i = 0; i < 8; i++) {
98
+        if (bind->mods & (1 << i)) {
99
+            switch (1 << i) {
100
+                case XCB_MOD_MASK_SHIFT:
101
+                    ystr("shift");
102
+                    break;
103
+                case XCB_MOD_MASK_LOCK:
104
+                    ystr("lock");
105
+                    break;
106
+                case XCB_MOD_MASK_CONTROL:
107
+                    ystr("ctrl");
108
+                    break;
109
+                case XCB_MOD_MASK_1:
110
+                    ystr("Mod1");
111
+                    break;
112
+                case XCB_MOD_MASK_2:
113
+                    ystr("Mod2");
114
+                    break;
115
+                case XCB_MOD_MASK_3:
116
+                    ystr("Mod3");
117
+                    break;
118
+                case XCB_MOD_MASK_4:
119
+                    ystr("Mod4");
120
+                    break;
121
+                case XCB_MOD_MASK_5:
122
+                    ystr("Mod5");
123
+                    break;
124
+            }
125
+        }
126
+    }
127
+    y(array_close);
128
+
129
+    y(map_close);
130
+}
131
+
132
 void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
133
     y(map_open);
134
     ystr("id");
135
@@ -1132,3 +1183,33 @@ void ipc_send_barconfig_update_event(Barconfig *barconfig) {
136
     y(free);
137
     setlocale(LC_NUMERIC, "");
138
 }
139
+
140
+/*
141
+ * For the binding events, we send the serialized binding struct.
142
+ */
143
+void ipc_send_binding_event(const char *event_type, Binding *bind) {
144
+    DLOG("Issue IPC binding %s event (sym = %s, code = %d)\n", event_type, bind->symbol, bind->keycode);
145
+
146
+    setlocale(LC_NUMERIC, "C");
147
+
148
+    yajl_gen gen = ygenalloc();
149
+
150
+    y(map_open);
151
+
152
+    ystr("change");
153
+    ystr(event_type);
154
+
155
+    ystr("binding");
156
+    dump_binding(gen, bind);
157
+
158
+    y(map_close);
159
+
160
+    const unsigned char *payload;
161
+    ylength length;
162
+    y(get_buf, &payload, &length);
163
+
164
+    ipc_send_event("binding", I3_IPC_EVENT_BINDING, (const char *)payload);
165
+
166
+    y(free);
167
+    setlocale(LC_NUMERIC, "");
168
+}

b/testcases/t/231-ipc-binding-event.t

174
@@ -0,0 +1,86 @@
175
+#!perl
176
+# vim:ts=4:sw=4:expandtab
177
+#
178
+# Please read the following documents before working on tests:
179
+# • http://build.i3wm.org/docs/testsuite.html
180
+#   (or docs/testsuite)
181
+#
182
+# • http://build.i3wm.org/docs/lib-i3test.html
183
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
184
+#
185
+# • http://build.i3wm.org/docs/ipc.html
186
+#   (or docs/ipc)
187
+#
188
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
189
+#   (unless you are already familiar with Perl)
190
+#
191
+# Test that the binding event works properly
192
+# Ticket: #1210
193
+# Bug still in: 4.7.2-148-g54a6404
194
+use i3test i3_autostart => 0;
195
+
196
+my $keysym = 't';
197
+my $command = 'exec nop';
198
+my @mods = ('Shift', 'Ctrl');
199
+my $binding_symbol = "$mods[0]+$mods[1]+$keysym";
200
+
201
+my $config = <<EOT;
202
+# i3 config file (v4)
203
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
204
+
205
+bindsym $binding_symbol $command
206
+EOT
207
+
208
+SKIP: {
209
+    qx(which xdotool 2> /dev/null);
210
+
211
+    skip 'xdotool is required to test the binding event. `[apt-get install|pacman -S] xdotool`', 1 if $?;
212
+
213
+    my $pid = launch_with_config($config);
214
+
215
+    my $i3 = i3(get_socket_path());
216
+    $i3->connect->recv;
217
+
218
+    my $cv = AE::cv;
219
+    my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
220
+
221
+    $i3->subscribe({
222
+            binding => sub {
223
+                $cv->send(shift);
224
+            }
225
+        })->recv;
226
+
227
+    qx(xdotool key $binding_symbol);
228
+
229
+    my $e = $cv->recv;
230
+
231
+    does_i3_live;
232
+
233
+    diag "Event:\n", Dumper($e);
234
+
235
+    ok($e,
236
+        'the binding event should emit when user input triggers an i3 binding event');
237
+
238
+    is($e->{change}, 'run',
239
+        'the `change` field should indicate this binding has run');
240
+
241
+    ok($e->{binding},
242
+        'the `binding` field should be a hash that contains information about the binding');
243
+
244
+    is($e->{binding}->{input_type}, 'keyboard',
245
+        'the input_type field should be the input type of the binding (keyboard or mouse)');
246
+
247
+    note 'the `mods` field should contain the symbols for the modifiers of the binding';
248
+    ok(grep(/$mods[0]/i, @{$e->{binding}->{mods}}), '`mods` contains first mod');
249
+    ok(grep(/$mods[1]/i, @{$e->{binding}->{mods}}), '`mods` contains second mod');
250
+
251
+    is($e->{binding}->{command}, $command,
252
+        'the `command` field should contain the command the binding ran');
253
+
254
+    is($e->{binding}->{input_code}, 0,
255
+        'the input_code should be the specified code if the key was bound with bindcode, and otherwise zero');
256
+
257
+    exit_gracefully($pid);
258
+
259
+}
260
+done_testing;