i3 - improved tiling WM


adding window event for urgency

Patch status: needinfo

Patch by Anders Aagaard

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

b/include/con.h

21
@@ -338,7 +338,7 @@ void con_update_parents_urgency(Con *con);
22
  * Set urgency flag to the container, all the parent containers and the workspace.
23
  *
24
  */
25
-void con_set_urgency(Con *con, bool urgent);
26
+void con_set_urgency(Con *con, bool urgent, const char *reason);
27
 
28
 /**
29
  * Create a string representing the subtree under con.

b/include/data.h

34
@@ -617,3 +617,8 @@ struct Con {
35
     /* Depth of the container window */
36
     uint16_t depth;
37
 };
38
+
39
+struct WindowEvent {
40
+    const char *key;
41
+    const char *value;
42
+};
43
\ No newline at end of file

b/include/ipc.h

48
@@ -90,10 +90,9 @@ void dump_node(yajl_gen gen, Con *con, bool inplace_restart);
49
 void ipc_send_workspace_focus_event(Con *current, Con *old);
50
 
51
 /**
52
- * For the window events we send, along the usual "change" field,
53
- * also the window container, in "container".
54
+ * For the window events we send, along with extra properties.
55
  */
56
-void ipc_send_window_event(const char *property, Con *con);
57
+void ipc_send_window_event(const struct WindowEvent properties[], Con *con);
58
 
59
 /**
60
  * For the barconfig update events, we send the serialized barconfig.

b/src/con.c

65
@@ -621,7 +621,8 @@ void con_toggle_fullscreen(Con *con, int fullscreen_mode) {
66
     DLOG("mode now: %d\n", con->fullscreen_mode);
67
 
68
     /* Send an ipc window "fullscreen_mode" event */
69
-    ipc_send_window_event("fullscreen_mode", con);
70
+    const struct WindowEvent const windowEvent[] = {{"change", "fullscreen_mode"}, {NULL, NULL}};
71
+    ipc_send_window_event(windowEvent, con);
72
 
73
     /* update _NET_WM_STATE if this container has a window */
74
     /* TODO: when a window is assigned to a container which is already
75
@@ -1572,7 +1573,7 @@ void con_update_parents_urgency(Con *con) {
76
  * Set urgency flag to the container, all the parent containers and the workspace.
77
  *
78
  */
79
-void con_set_urgency(Con *con, bool urgent) {
80
+void con_set_urgency(Con *con, bool urgent, const char *reason) {
81
     if (focused == con) {
82
         DLOG("Ignoring urgency flag for current client\n");
83
         con->window->urgent.tv_sec = 0;
84
@@ -1596,6 +1597,8 @@ void con_set_urgency(Con *con, bool urgent) {
85
     }
86
 
87
     con_update_parents_urgency(con);
88
+    const struct WindowEvent const windowEvent[] = {{"change", "urgency"}, {"reason", reason}, {NULL, NULL}};
89
+    ipc_send_window_event(windowEvent, con);
90
 
91
     if (con->urgent == urgent)
92
         LOG("Urgency flag changed to %d\n", con->urgent);

b/src/handlers.c

97
@@ -555,8 +555,10 @@ static bool handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t
98
 
99
     x_push_changes(croot);
100
 
101
-    if (window_name_changed(con->window, old_name))
102
-        ipc_send_window_event("title", con);
103
+    if (window_name_changed(con->window, old_name)) {
104
+        const struct WindowEvent const windowEvent[] = {{"change", "title"}, {NULL, NULL}};
105
+        ipc_send_window_event(windowEvent, con);
106
+    }
107
 
108
     FREE(old_name);
109
 
110
@@ -580,8 +582,10 @@ static bool handle_windowname_change_legacy(void *data, xcb_connection_t *conn,
111
 
112
     x_push_changes(croot);
113
 
114
-    if (window_name_changed(con->window, old_name))
115
-        ipc_send_window_event("title", con);
116
+    if (window_name_changed(con->window, old_name)) {
117
+        const struct WindowEvent const windowEvent[] = {{"change", "title"}, {NULL, NULL}};
118
+        ipc_send_window_event(windowEvent, con);
119
+    }
120
 
121
     FREE(old_name);
122
 
123
@@ -684,12 +688,16 @@ static void handle_client_message(xcb_client_message_event_t *event) {
124
             }
125
         } else if (event->data.data32[1] == A__NET_WM_STATE_DEMANDS_ATTENTION) {
126
             /* Check if the urgent flag must be set or not */
127
-            if (event->data.data32[0] == _NET_WM_STATE_ADD)
128
-                con_set_urgency(con, true);
129
-            else if (event->data.data32[0] == _NET_WM_STATE_REMOVE)
130
-                con_set_urgency(con, false);
131
-            else if (event->data.data32[0] == _NET_WM_STATE_TOGGLE)
132
-                con_set_urgency(con, !con->urgent);
133
+            if (event->data.data32[0] == _NET_WM_STATE_ADD) {
134
+                const char *reason = "WM_STATE_ADD";
135
+                con_set_urgency(con, true, reason);
136
+            } else if (event->data.data32[0] == _NET_WM_STATE_REMOVE) {
137
+                const char *reason = "WM_STATE_REMOVED";
138
+                con_set_urgency(con, false, reason);
139
+            } else if (event->data.data32[0] == _NET_WM_STATE_TOGGLE) {
140
+                const char *reason = "WM_STATE_TOGGLE";
141
+                con_set_urgency(con, !con->urgent, reason);
142
+            }
143
         }
144
 
145
         tree_render();
146
@@ -733,7 +741,8 @@ static void handle_client_message(xcb_client_message_event_t *event) {
147
                 con_focus(con);
148
             } else {
149
                 DLOG("Request to focus con on a hidden workspace. Setting urgent con = %p\n", con);
150
-                con_set_urgency(con, true);
151
+                const char *reason = "FOCUS_BLOCKED";
152
+                con_set_urgency(con, true, reason);
153
             }
154
         }
155
 
156
@@ -929,7 +938,8 @@ static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_
157
     if (reply == NULL)
158
         reply = xcb_get_property_reply(conn, xcb_icccm_get_wm_hints(conn, window), NULL);
159
     window_update_hints(con->window, reply, &urgency_hint);
160
-    con_set_urgency(con, urgency_hint);
161
+    const char *reason = "WM_HINTS";
162
+    con_set_urgency(con, urgency_hint, reason);
163
     tree_render();
164
 
165
     return true;

b/src/ipc.c

170
@@ -1080,18 +1080,30 @@ void ipc_send_workspace_focus_event(Con *current, Con *old) {
171
  * For the window events we send, along the usual "change" field,
172
  * also the window container, in "container".
173
  */
174
-void ipc_send_window_event(const char *property, Con *con) {
175
-    DLOG("Issue IPC window %s event (con = %p, window = 0x%08x)\n",
176
-            property, con, (con->window ? con->window->id : XCB_WINDOW_NONE));
177
+/*void ipc_send_window_event(const char *property, Con *con) {
178
+    const struct WindowEvent const crap[] = {{"change", property}, {NULL, NULL}};
179
+    ipc_send_window_eventx(crap, con);
180
+}*/
181
+
182
+
183
+void ipc_send_window_event(const struct WindowEvent properties[], Con *con) {
184
+    DLOG("Issue IPC window event (con = %p, window = 0x%08x)\n",
185
+            con, (con->window ? con->window->id : XCB_WINDOW_NONE));
186
 
187
     setlocale(LC_NUMERIC, "C");
188
     yajl_gen gen = ygenalloc();
189
 
190
     y(map_open);
191
 
192
-    ystr("change");
193
-    ystr(property);
194
-
195
+    int i = 0;
196
+    while (properties[i].key != NULL) {
197
+        if (properties[i].key != NULL && properties[i].value != NULL) {
198
+            ystr(properties[i].key);
199
+            ystr(properties[i].value);
200
+        }
201
+        i++;
202
+    }
203
+    
204
     ystr("container");
205
     dump_node(gen, con, false);
206
 
207
@@ -1106,6 +1118,7 @@ void ipc_send_window_event(const char *property, Con *con) {
208
     setlocale(LC_NUMERIC, "");
209
 }
210
 
211
+
212
 /**
213
  * For the barconfig update events, we send the serialized barconfig.
214
  */

b/src/manage.c

219
@@ -484,7 +484,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
220
     render_con(croot, false);
221
 
222
     /* Send an event about window creation */
223
-    ipc_send_window_event("new", nc);
224
+    const struct WindowEvent const windowEvent[] = {{"change", "new"}, {NULL, NULL}};
225
+    ipc_send_window_event(windowEvent, nc);
226
 
227
     /* Defer setting focus after the 'new' event has been sent to ensure the
228
      * proper window event sequence. */
229
@@ -497,7 +498,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
230
      * known to do that), so check for that and handle the hint accordingly.
231
      * This code needs to be in this part of manage_window() because the window
232
      * needs to be on the final workspace first. */
233
-    con_set_urgency(nc, urgency_hint);
234
+    con_set_urgency(nc, urgency_hint, NULL);
235
 
236
 geom_out:
237
     free(geom);

b/src/x.c

242
@@ -1015,8 +1015,10 @@ void x_push_changes(Con *con) {
243
 
244
                 ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE));
245
 
246
-                if (to_focus != last_focused && is_con_attached(focused))
247
-                   ipc_send_window_event("focus", focused);
248
+                if (to_focus != last_focused && is_con_attached(focused)) {
249
+                   const struct WindowEvent const windowEvent[] = {{"change", "focus"}, {NULL, NULL}};
250
+                   ipc_send_window_event(windowEvent, focused);
251
+                }
252
             } else {
253
                 DLOG("Updating focus (focused: %p / %s) to X11 window 0x%08x\n", focused, focused->name, to_focus);
254
                 /* We remove XCB_EVENT_MASK_FOCUS_CHANGE from the event mask to get
255
@@ -1034,8 +1036,10 @@ void x_push_changes(Con *con) {
256
 
257
                 ewmh_update_active_window((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE));
258
 
259
-                if (to_focus != XCB_NONE && to_focus != last_focused && focused->window != NULL && is_con_attached(focused))
260
-                   ipc_send_window_event("focus", focused);
261
+                if (to_focus != XCB_NONE && to_focus != last_focused && focused->window != NULL && is_con_attached(focused)) {
262
+                   const struct WindowEvent const windowEvent[] = {{"change", "focus"}, {NULL, NULL}};
263
+                   ipc_send_window_event(windowEvent, focused);
264
+                }
265
             }
266
 
267
             focused_id = last_focused = to_focus;

b/testcases/t/205-ipc-windows.t

272
@@ -31,6 +31,7 @@ $i3->connect()->recv;
273
 
274
 my $new = AnyEvent->condvar;
275
 my $focus = AnyEvent->condvar;
276
+my $urgency = AnyEvent->condvar;
277
 $i3->subscribe({
278
     window => sub {
279
         my ($event) = @_;
280
@@ -38,11 +39,16 @@ $i3->subscribe({
281
             $new->send($event);
282
         } elsif ($event->{change} eq 'focus') {
283
             $focus->send($event);
284
+        } elsif ($event->{change} eq 'urgency') {
285
+            $urgency->send($event);
286
         }
287
     }
288
 })->recv;
289
 
290
-open_window;
291
+my $first = open_window;
292
+my $second = open_window;
293
+$first->delete_hint('urgency');
294
+$first->add_hint('urgency');
295
 
296
 my $t;
297
 $t = AnyEvent->timer(
298
@@ -50,11 +56,13 @@ $t = AnyEvent->timer(
299
     cb => sub {
300
         $new->send(0);
301
         $focus->send(0);
302
+        $urgency->send(0);
303
     }
304
 );
305
 
306
 is($new->recv->{container}->{focused}, 0, 'Window "new" event received');
307
 is($focus->recv->{container}->{focused}, 1, 'Window "focus" event received');
308
+is($urgency->recv->{reason}, "WM_HINTS", 'Window "urgency" event received');
309
 
310
 }
311