i3 - improved tiling WM


Add support for the _NET_CLIENT_LIST root window property.

Patch status: needinfo

Patch by Steve Jones

Long description:

This sets the the _NET_CLIENT_LIST property in x_push_changes when the
client list changes. Changes to the client list are tracked by the
client_list_changed flag which is updated by x_con_init and x_con_kill.
The client list is maintained in the order of connecting by the TAILQ
with head client_head.

Adds the _NET_CLIENT_LIST Atom to the _NET_SUPPORTED root property to
claim support of this feature.

fixes #1099

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

b/include/atoms.xmacro

26
@@ -12,6 +12,7 @@ xmacro(_NET_WM_WINDOW_TYPE_TOOLBAR)
27
 xmacro(_NET_WM_WINDOW_TYPE_SPLASH)
28
 xmacro(_NET_WM_DESKTOP)
29
 xmacro(_NET_WM_STRUT_PARTIAL)
30
+xmacro(_NET_CLIENT_LIST)
31
 xmacro(_NET_CLIENT_LIST_STACKING)
32
 xmacro(_NET_CURRENT_DESKTOP)
33
 xmacro(_NET_ACTIVE_WINDOW)

b/include/ewmh.h

38
@@ -28,6 +28,11 @@ void ewmh_update_current_desktop(void);
39
 void ewmh_update_active_window(xcb_window_t window);
40
 
41
 /**
42
+ * Updates the _NET_CLIENT_LIST hint. Used for window listers.
43
+ */
44
+void ewmh_update_client_list(xcb_window_t *list, int num_windows);
45
+
46
+/**
47
  * Updates the _NET_CLIENT_LIST_STACKING hint. Necessary to move tabs in
48
  * Chromium correctly.
49
  *

b/src/ewmh.c

54
@@ -69,6 +69,22 @@ void ewmh_update_workarea(void) {
55
 }
56
 
57
 /*
58
+ * Updates the _NET_CLIENT_LIST hint.
59
+ *
60
+ */
61
+void ewmh_update_client_list(xcb_window_t *list, int num_windows) {
62
+    xcb_change_property(
63
+        conn,
64
+        XCB_PROP_MODE_REPLACE,
65
+        root,
66
+        A__NET_CLIENT_LIST,
67
+        XCB_ATOM_WINDOW,
68
+        32,
69
+        num_windows,
70
+        list);
71
+}
72
+
73
+/*
74
  * Updates the _NET_CLIENT_LIST_STACKING hint.
75
  *
76
  */
77
@@ -119,5 +135,5 @@ void ewmh_setup_hints(void) {
78
     /* I’m not entirely sure if we need to keep _NET_WM_NAME on root. */
79
     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3");
80
 
81
-    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 18, supported_atoms);
82
+    xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 19, supported_atoms);
83
 }

b/src/x.c

88
@@ -15,11 +15,6 @@
89
 /* Stores the X11 window ID of the currently focused window */
90
 xcb_window_t focused_id = XCB_NONE;
91
 
92
-/* The bottom-to-top window stack of all windows which are managed by i3.
93
- * Used for x_get_window_stack(). */
94
-static xcb_window_t *btt_stack;
95
-static int btt_stack_num;
96
-
97
 /* Stores coordinates to warp mouse pointer to if set */
98
 static Rect *warp_to;
99
 
100
@@ -55,6 +50,7 @@ typedef struct con_state {
101
 
102
     CIRCLEQ_ENTRY(con_state) state;
103
     CIRCLEQ_ENTRY(con_state) old_state;
104
+    TAILQ_ENTRY(con_state) initial_mapping_order;
105
 } con_state;
106
 
107
 CIRCLEQ_HEAD(state_head, con_state) state_head =
108
@@ -63,6 +59,9 @@ CIRCLEQ_HEAD(state_head, con_state) state_head =
109
 CIRCLEQ_HEAD(old_state_head, con_state) old_state_head =
110
     CIRCLEQ_HEAD_INITIALIZER(old_state_head);
111
 
112
+TAILQ_HEAD(initial_mapping_head, con_state) initial_mapping_head =
113
+    TAILQ_HEAD_INITIALIZER(initial_mapping_head);
114
+
115
 /*
116
  * Returns the container state for the given frame. This function always
117
  * returns a container state (otherwise, there is a bug in the code and the
118
@@ -146,8 +145,10 @@ void x_con_init(Con *con, uint16_t depth) {
119
     state->id = con->frame;
120
     state->mapped = false;
121
     state->initial = true;
122
+    DLOG("Adding window 0x%08x to lists\n", state->id);
123
     CIRCLEQ_INSERT_HEAD(&state_head, state, state);
124
     CIRCLEQ_INSERT_HEAD(&old_state_head, state, old_state);
125
+    TAILQ_INSERT_TAIL(&initial_mapping_head, state, initial_mapping_order);
126
     DLOG("adding new state for window id 0x%08x\n", state->id);
127
 }
128
 
129
@@ -228,6 +229,7 @@ void x_con_kill(Con *con) {
130
     state = state_for_frame(con->frame);
131
     CIRCLEQ_REMOVE(&state_head, state, state);
132
     CIRCLEQ_REMOVE(&old_state_head, state, old_state);
133
+    TAILQ_REMOVE(&initial_mapping_head, state, initial_mapping_order);
134
     FREE(state->name);
135
     free(state);
136
 
137
@@ -879,6 +881,7 @@ void x_push_changes(Con *con) {
138
     bool order_changed = false;
139
     bool stacking_changed = false;
140
 
141
+
142
     /* count first, necessary to (re)allocate memory for the bottom-to-top
143
      * stack afterwards */
144
     int cnt = 0;
145
@@ -886,12 +889,22 @@ void x_push_changes(Con *con) {
146
         if (state->con && state->con->window)
147
             cnt++;
148
 
149
-    if (cnt != btt_stack_num) {
150
-        btt_stack = srealloc(btt_stack, sizeof(xcb_window_t) * cnt);
151
-        btt_stack_num = cnt;
152
+    /* The bottom-to-top window stack of all windows which are managed by i3.
153
+     * Used for x_get_window_stack(). */
154
+    static xcb_window_t *client_list_windows = NULL;
155
+    static int client_list_count = 0;
156
+
157
+    bool client_list_changed = false;
158
+
159
+    if (cnt != client_list_count) {
160
+        client_list_windows = srealloc(client_list_windows, sizeof(xcb_window_t) * cnt);
161
+        client_list_count = cnt;
162
+        client_list_changed = true;
163
     }
164
 
165
-    xcb_window_t *walk = btt_stack;
166
+    DLOG("Getting window stacking order\n");
167
+
168
+    xcb_window_t *walk = client_list_windows;
169
 
170
     /* X11 correctly represents the stack if we push it from bottom to top */
171
     CIRCLEQ_FOREACH_REVERSE(state, &state_head, state) {
172
@@ -919,7 +932,27 @@ void x_push_changes(Con *con) {
173
     /* If we re-stacked something (or a new window appeared), we need to update
174
      * the _NET_CLIENT_LIST_STACKING hint */
175
     if (stacking_changed)
176
-        ewmh_update_client_list_stacking(btt_stack, btt_stack_num);
177
+        ewmh_update_client_list_stacking(client_list_windows, client_list_count);
178
+
179
+    /* If the client list changed since the last call update the
180
+     * _NET_CLIENT_LIST property. */
181
+    if (client_list_changed) {
182
+        DLOG("Client list changed (%i clients)\n", cnt);
183
+
184
+        if (cnt) {
185
+            walk = client_list_windows;
186
+
187
+            TAILQ_FOREACH(state, &initial_mapping_head, initial_mapping_order) {
188
+                assert((client_list_windows - walk) < cnt);
189
+                if (state->con && state->con->window)
190
+                    *walk++ = state->con->window->id;
191
+            }
192
+
193
+            ewmh_update_client_list(client_list_windows, client_list_count);
194
+        } else {
195
+            ewmh_update_client_list(NULL, 0);
196
+        }
197
+    }
198
 
199
     DLOG("PUSHING CHANGES\n");
200
     x_push_node(con);