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.

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

b/include/atoms.xmacro

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

b/include/ewmh.h

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

b/src/ewmh.c

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

b/src/x.c

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