i3
load_layout.c
Go to the documentation of this file.
1 #undef I3__FILE__
2 #define I3__FILE__ "load_layout.c"
3 /*
4  * vim:ts=4:sw=4:expandtab
5  *
6  * i3 - an improved dynamic tiling window manager
7  * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
8  *
9  * load_layout.c: Restore (parts of) the layout, for example after an inplace
10  * restart.
11  *
12  */
13 #include "all.h"
14 
15 #include <yajl/yajl_common.h>
16 #include <yajl/yajl_gen.h>
17 #include <yajl/yajl_parse.h>
18 #include <yajl/yajl_version.h>
19 
20 /* TODO: refactor the whole parsing thing */
21 
22 static char *last_key;
23 static Con *json_node;
24 static Con *to_focus;
25 static bool parsing_swallows;
26 static bool parsing_rect;
27 static bool parsing_window_rect;
28 static bool parsing_geometry;
29 static bool parsing_focus;
31 
32 /* This list is used for reordering the focus stack after parsing the 'focus'
33  * array. */
34 struct focus_mapping {
35  int old_id;
36  TAILQ_ENTRY(focus_mapping) focus_mappings;
37 };
38 
39 static TAILQ_HEAD(focus_mappings_head, focus_mapping) focus_mappings =
40  TAILQ_HEAD_INITIALIZER(focus_mappings);
41 
42 static int json_start_map(void *ctx) {
43  LOG("start of map, last_key = %s\n", last_key);
44  if (parsing_swallows) {
45  LOG("creating new swallow\n");
46  current_swallow = smalloc(sizeof(Match));
47  match_init(current_swallow);
48  TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches);
49  } else {
51  if (last_key && strcasecmp(last_key, "floating_nodes") == 0) {
52  DLOG("New floating_node\n");
53  Con *ws = con_get_workspace(json_node);
54  json_node = con_new_skeleton(NULL, NULL);
55  json_node->parent = ws;
56  DLOG("Parent is workspace = %p\n", ws);
57  } else {
58  Con *parent = json_node;
59  json_node = con_new_skeleton(NULL, NULL);
60  json_node->parent = parent;
61  }
62  }
63  }
64  return 1;
65 }
66 
67 static int json_end_map(void *ctx) {
68  LOG("end of map\n");
70  /* Set a few default values to simplify manually crafted layout files. */
71  if (json_node->layout == L_DEFAULT) {
72  DLOG("Setting layout = L_SPLITH\n");
73  json_node->layout = L_SPLITH;
74  }
75 
76  /* Sanity check: swallow criteria don’t make any sense on a split
77  * container. */
78  if (con_is_split(json_node) > 0 && !TAILQ_EMPTY(&(json_node->swallow_head))) {
79  DLOG("sanity check: removing swallows specification from split container\n");
80  while (!TAILQ_EMPTY(&(json_node->swallow_head))) {
81  Match *match = TAILQ_FIRST(&(json_node->swallow_head));
82  TAILQ_REMOVE(&(json_node->swallow_head), match, matches);
83  match_free(match);
84  }
85  }
86 
87  LOG("attaching\n");
88  con_attach(json_node, json_node->parent, true);
89  LOG("Creating window\n");
90  x_con_init(json_node, json_node->depth);
91  json_node = json_node->parent;
92  }
93  if (parsing_rect)
94  parsing_rect = false;
96  parsing_window_rect = false;
97  if (parsing_geometry)
98  parsing_geometry = false;
99  return 1;
100 }
101 
102 static int json_end_array(void *ctx) {
103  LOG("end of array\n");
104  if (!parsing_swallows && !parsing_focus) {
105  con_fix_percent(json_node);
106  }
107  if (parsing_swallows) {
108  parsing_swallows = false;
109  }
110  if (parsing_focus) {
111  /* Clear the list of focus mappings */
112  struct focus_mapping *mapping;
113  TAILQ_FOREACH_REVERSE (mapping, &focus_mappings, focus_mappings_head, focus_mappings) {
114  LOG("focus (reverse) %d\n", mapping->old_id);
115  Con *con;
116  TAILQ_FOREACH (con, &(json_node->focus_head), focused) {
117  if (con->old_id != mapping->old_id)
118  continue;
119  LOG("got it! %p\n", con);
120  /* Move this entry to the top of the focus list. */
121  TAILQ_REMOVE(&(json_node->focus_head), con, focused);
122  TAILQ_INSERT_HEAD(&(json_node->focus_head), con, focused);
123  break;
124  }
125  }
126  while (!TAILQ_EMPTY(&focus_mappings)) {
127  mapping = TAILQ_FIRST(&focus_mappings);
128  TAILQ_REMOVE(&focus_mappings, mapping, focus_mappings);
129  free(mapping);
130  }
131  parsing_focus = false;
132  }
133  return 1;
134 }
135 
136 static int json_key(void *ctx, const unsigned char *val, size_t len) {
137  LOG("key: %.*s\n", (int)len, val);
138  FREE(last_key);
139  last_key = scalloc((len + 1) * sizeof(char));
140  memcpy(last_key, val, len);
141  if (strcasecmp(last_key, "swallows") == 0)
142  parsing_swallows = true;
143 
144  if (strcasecmp(last_key, "rect") == 0)
145  parsing_rect = true;
146 
147  if (strcasecmp(last_key, "window_rect") == 0)
148  parsing_window_rect = true;
149 
150  if (strcasecmp(last_key, "geometry") == 0)
151  parsing_geometry = true;
152 
153  if (strcasecmp(last_key, "focus") == 0)
154  parsing_focus = true;
155 
156  return 1;
157 }
158 
159 static int json_string(void *ctx, const unsigned char *val, size_t len) {
160  LOG("string: %.*s for key %s\n", (int)len, val, last_key);
161  if (parsing_swallows) {
162  char *sval;
163  sasprintf(&sval, "%.*s", len, val);
164  if (strcasecmp(last_key, "class") == 0) {
165  current_swallow->class = regex_new(sval);
166  } else if (strcasecmp(last_key, "instance") == 0) {
167  current_swallow->instance = regex_new(sval);
168  } else if (strcasecmp(last_key, "window_role") == 0) {
169  current_swallow->window_role = regex_new(sval);
170  } else if (strcasecmp(last_key, "title") == 0) {
171  current_swallow->title = regex_new(sval);
172  } else {
173  ELOG("swallow key %s unknown\n", last_key);
174  }
175  free(sval);
176  } else {
177  if (strcasecmp(last_key, "name") == 0) {
178  json_node->name = scalloc((len + 1) * sizeof(char));
179  memcpy(json_node->name, val, len);
180  } else if (strcasecmp(last_key, "sticky_group") == 0) {
181  json_node->sticky_group = scalloc((len + 1) * sizeof(char));
182  memcpy(json_node->sticky_group, val, len);
183  LOG("sticky_group of this container is %s\n", json_node->sticky_group);
184  } else if (strcasecmp(last_key, "orientation") == 0) {
185  /* Upgrade path from older versions of i3 (doing an inplace restart
186  * to a newer version):
187  * "orientation" is dumped before "layout". Therefore, we store
188  * whether the orientation was horizontal or vertical in the
189  * last_split_layout. When we then encounter layout == "default",
190  * we will use the last_split_layout as layout instead. */
191  char *buf = NULL;
192  sasprintf(&buf, "%.*s", (int)len, val);
193  if (strcasecmp(buf, "none") == 0 ||
194  strcasecmp(buf, "horizontal") == 0)
195  json_node->last_split_layout = L_SPLITH;
196  else if (strcasecmp(buf, "vertical") == 0)
197  json_node->last_split_layout = L_SPLITV;
198  else
199  LOG("Unhandled orientation: %s\n", buf);
200  free(buf);
201  } else if (strcasecmp(last_key, "border") == 0) {
202  char *buf = NULL;
203  sasprintf(&buf, "%.*s", (int)len, val);
204  if (strcasecmp(buf, "none") == 0)
205  json_node->border_style = BS_NONE;
206  else if (strcasecmp(buf, "1pixel") == 0) {
207  json_node->border_style = BS_PIXEL;
208  json_node->current_border_width = 1;
209  } else if (strcasecmp(buf, "pixel") == 0)
210  json_node->border_style = BS_PIXEL;
211  else if (strcasecmp(buf, "normal") == 0)
212  json_node->border_style = BS_NORMAL;
213  else
214  LOG("Unhandled \"border\": %s\n", buf);
215  free(buf);
216  } else if (strcasecmp(last_key, "type") == 0) {
217  char *buf = NULL;
218  sasprintf(&buf, "%.*s", (int)len, val);
219  if (strcasecmp(buf, "root") == 0)
220  json_node->type = CT_ROOT;
221  else if (strcasecmp(buf, "output") == 0)
222  json_node->type = CT_OUTPUT;
223  else if (strcasecmp(buf, "con") == 0)
224  json_node->type = CT_CON;
225  else if (strcasecmp(buf, "floating_con") == 0)
226  json_node->type = CT_FLOATING_CON;
227  else if (strcasecmp(buf, "workspace") == 0)
228  json_node->type = CT_WORKSPACE;
229  else if (strcasecmp(buf, "dockarea") == 0)
230  json_node->type = CT_DOCKAREA;
231  else
232  LOG("Unhandled \"type\": %s\n", buf);
233  free(buf);
234  } else if (strcasecmp(last_key, "layout") == 0) {
235  char *buf = NULL;
236  sasprintf(&buf, "%.*s", (int)len, val);
237  if (strcasecmp(buf, "default") == 0)
238  /* This set above when we read "orientation". */
239  json_node->layout = json_node->last_split_layout;
240  else if (strcasecmp(buf, "stacked") == 0)
241  json_node->layout = L_STACKED;
242  else if (strcasecmp(buf, "tabbed") == 0)
243  json_node->layout = L_TABBED;
244  else if (strcasecmp(buf, "dockarea") == 0)
245  json_node->layout = L_DOCKAREA;
246  else if (strcasecmp(buf, "output") == 0)
247  json_node->layout = L_OUTPUT;
248  else if (strcasecmp(buf, "splith") == 0)
249  json_node->layout = L_SPLITH;
250  else if (strcasecmp(buf, "splitv") == 0)
251  json_node->layout = L_SPLITV;
252  else
253  LOG("Unhandled \"layout\": %s\n", buf);
254  free(buf);
255  } else if (strcasecmp(last_key, "workspace_layout") == 0) {
256  char *buf = NULL;
257  sasprintf(&buf, "%.*s", (int)len, val);
258  if (strcasecmp(buf, "default") == 0)
259  json_node->workspace_layout = L_DEFAULT;
260  else if (strcasecmp(buf, "stacked") == 0)
261  json_node->workspace_layout = L_STACKED;
262  else if (strcasecmp(buf, "tabbed") == 0)
263  json_node->workspace_layout = L_TABBED;
264  else
265  LOG("Unhandled \"workspace_layout\": %s\n", buf);
266  free(buf);
267  } else if (strcasecmp(last_key, "last_split_layout") == 0) {
268  char *buf = NULL;
269  sasprintf(&buf, "%.*s", (int)len, val);
270  if (strcasecmp(buf, "splith") == 0)
271  json_node->last_split_layout = L_SPLITH;
272  else if (strcasecmp(buf, "splitv") == 0)
273  json_node->last_split_layout = L_SPLITV;
274  else
275  LOG("Unhandled \"last_splitlayout\": %s\n", buf);
276  free(buf);
277  } else if (strcasecmp(last_key, "mark") == 0) {
278  char *buf = NULL;
279  sasprintf(&buf, "%.*s", (int)len, val);
280  json_node->mark = buf;
281  } else if (strcasecmp(last_key, "floating") == 0) {
282  char *buf = NULL;
283  sasprintf(&buf, "%.*s", (int)len, val);
284  if (strcasecmp(buf, "auto_off") == 0)
285  json_node->floating = FLOATING_AUTO_OFF;
286  else if (strcasecmp(buf, "auto_on") == 0)
287  json_node->floating = FLOATING_AUTO_ON;
288  else if (strcasecmp(buf, "user_off") == 0)
289  json_node->floating = FLOATING_USER_OFF;
290  else if (strcasecmp(buf, "user_on") == 0)
291  json_node->floating = FLOATING_USER_ON;
292  free(buf);
293  } else if (strcasecmp(last_key, "scratchpad_state") == 0) {
294  char *buf = NULL;
295  sasprintf(&buf, "%.*s", (int)len, val);
296  if (strcasecmp(buf, "none") == 0)
297  json_node->scratchpad_state = SCRATCHPAD_NONE;
298  else if (strcasecmp(buf, "fresh") == 0)
299  json_node->scratchpad_state = SCRATCHPAD_FRESH;
300  else if (strcasecmp(buf, "changed") == 0)
301  json_node->scratchpad_state = SCRATCHPAD_CHANGED;
302  free(buf);
303  }
304  }
305  return 1;
306 }
307 
308 static int json_int(void *ctx, long long val) {
309  LOG("int %lld for key %s\n", val, last_key);
310  /* For backwards compatibility with i3 < 4.8 */
311  if (strcasecmp(last_key, "type") == 0)
312  json_node->type = val;
313 
314  if (strcasecmp(last_key, "fullscreen_mode") == 0)
315  json_node->fullscreen_mode = val;
316 
317  if (strcasecmp(last_key, "num") == 0)
318  json_node->num = val;
319 
320  if (strcasecmp(last_key, "current_border_width") == 0)
321  json_node->current_border_width = val;
322 
323  if (strcasecmp(last_key, "depth") == 0)
324  json_node->depth = val;
325 
326  if (!parsing_swallows && strcasecmp(last_key, "id") == 0)
327  json_node->old_id = val;
328 
329  if (parsing_focus) {
330  struct focus_mapping *focus_mapping = scalloc(sizeof(struct focus_mapping));
331  focus_mapping->old_id = val;
332  TAILQ_INSERT_TAIL(&focus_mappings, focus_mapping, focus_mappings);
333  }
334 
336  Rect *r;
337  if (parsing_rect)
338  r = &(json_node->rect);
339  else if (parsing_window_rect)
340  r = &(json_node->window_rect);
341  else
342  r = &(json_node->geometry);
343  if (strcasecmp(last_key, "x") == 0)
344  r->x = val;
345  else if (strcasecmp(last_key, "y") == 0)
346  r->y = val;
347  else if (strcasecmp(last_key, "width") == 0)
348  r->width = val;
349  else if (strcasecmp(last_key, "height") == 0)
350  r->height = val;
351  else
352  ELOG("WARNING: unknown key %s in rect\n", last_key);
353  DLOG("rect now: (%d, %d, %d, %d)\n",
354  r->x, r->y, r->width, r->height);
355  }
356  if (parsing_swallows) {
357  if (strcasecmp(last_key, "id") == 0) {
358  current_swallow->id = val;
359  }
360  if (strcasecmp(last_key, "dock") == 0) {
361  current_swallow->dock = val;
362  }
363  if (strcasecmp(last_key, "insert_where") == 0) {
364  current_swallow->insert_where = val;
365  }
366  }
367 
368  return 1;
369 }
370 
371 static int json_bool(void *ctx, int val) {
372  LOG("bool %d for key %s\n", val, last_key);
373  if (strcasecmp(last_key, "focused") == 0 && val) {
374  to_focus = json_node;
375  }
376 
377  if (parsing_swallows) {
378  if (strcasecmp(last_key, "restart_mode") == 0)
379  current_swallow->restart_mode = val;
380  }
381 
382  return 1;
383 }
384 
385 static int json_double(void *ctx, double val) {
386  LOG("double %f for key %s\n", val, last_key);
387  if (strcasecmp(last_key, "percent") == 0) {
388  json_node->percent = val;
389  }
390  return 1;
391 }
392 
393 void tree_append_json(Con *con, const char *filename, char **errormsg) {
394  FILE *f;
395  if ((f = fopen(filename, "r")) == NULL) {
396  LOG("Cannot open file \"%s\"\n", filename);
397  return;
398  }
399  struct stat stbuf;
400  if (fstat(fileno(f), &stbuf) != 0) {
401  LOG("Cannot fstat() the file\n");
402  fclose(f);
403  return;
404  }
405  char *buf = smalloc(stbuf.st_size);
406  int n = fread(buf, 1, stbuf.st_size, f);
407  if (n != stbuf.st_size) {
408  LOG("File \"%s\" could not be read entirely, not loading.\n", filename);
409  fclose(f);
410  return;
411  }
412  LOG("read %d bytes\n", n);
413  yajl_gen g;
414  yajl_handle hand;
415  static yajl_callbacks callbacks = {
416  .yajl_boolean = json_bool,
417  .yajl_integer = json_int,
418  .yajl_double = json_double,
419  .yajl_string = json_string,
420  .yajl_start_map = json_start_map,
421  .yajl_map_key = json_key,
422  .yajl_end_map = json_end_map,
423  .yajl_end_array = json_end_array,
424  };
425  g = yajl_gen_alloc(NULL);
426  hand = yajl_alloc(&callbacks, NULL, (void *)g);
427  /* Allowing comments allows for more user-friendly layout files. */
428  yajl_config(hand, yajl_allow_comments, true);
429  /* Allow multiple values, i.e. multiple nodes to attach */
430  yajl_config(hand, yajl_allow_multiple_values, true);
431  yajl_status stat;
432  json_node = con;
433  to_focus = NULL;
434  parsing_swallows = false;
435  parsing_rect = false;
436  parsing_window_rect = false;
437  parsing_geometry = false;
438  parsing_focus = false;
439  setlocale(LC_NUMERIC, "C");
440  stat = yajl_parse(hand, (const unsigned char *)buf, n);
441  if (stat != yajl_status_ok) {
442  unsigned char *str = yajl_get_error(hand, 1, (const unsigned char *)buf, n);
443  ELOG("JSON parsing error: %s\n", str);
444  if (errormsg != NULL)
445  *errormsg = sstrdup((const char *)str);
446  yajl_free_error(hand, str);
447  }
448 
449  /* In case not all containers were restored, we need to fix the
450  * percentages, otherwise i3 will crash immediately when rendering the
451  * next time. */
452  con_fix_percent(con);
453 
454  setlocale(LC_NUMERIC, "");
455  yajl_complete_parse(hand);
456 
457  fclose(f);
458  if (to_focus)
459  con_focus(to_focus);
460 }
enum Con::@20 scratchpad_state
Definition: data.h:87
static Con * json_node
Definition: load_layout.c:23
struct Con * parent
Definition: data.h:512
char * sstrdup(const char *str)
Safe-wrapper around strdup which exits if malloc returns NULL (meaning that there is no more memory a...
Definition: data.h:56
uint32_t y
Definition: data.h:124
#define TAILQ_REMOVE(head, elm, field)
Definition: queue.h:386
void con_attach(Con *con, Con *parent, bool ignore_focus)
Attaches the given container to the given parent.
Definition: con.c:103
double percent
Definition: data.h:530
char * sticky_group
Definition: data.h:525
Con * con_get_workspace(Con *con)
Gets the workspace container this node is on.
Definition: con.c:317
Stores a rectangle, for example the size of a window, the child window etc.
Definition: data.h:122
#define LOG(fmt,...)
Definition: libi3.h:76
static char * last_key
Definition: load_layout.c:22
Definition: data.h:56
struct Match * current_swallow
Definition: load_layout.c:30
struct Rect window_rect
Definition: data.h:515
struct Rect rect
Definition: data.h:514
char * mark
Definition: data.h:528
static int json_bool(void *ctx, int val)
Definition: load_layout.c:371
int current_border_width
Definition: data.h:541
struct regex * window_role
Definition: data.h:396
#define TAILQ_FOREACH_REVERSE(var, head, headname, field)
Definition: queue.h:339
#define TAILQ_FIRST(head)
Definition: queue.h:323
static Con * to_focus
Definition: load_layout.c:24
static int json_double(void *ctx, double val)
Definition: load_layout.c:385
enum Con::@19 floating
floating? (= not in tiling layout) This cannot be simply a bool because we want to keep track of whet...
void tree_append_json(Con *con, const char *filename, char **errormsg)
Definition: load_layout.c:393
border_style_t border_style
Definition: data.h:579
static bool parsing_rect
Definition: load_layout.c:26
static bool parsing_focus
Definition: load_layout.c:29
enum Con::@18 type
Definition: data.h:85
#define DLOG(fmt,...)
Definition: libi3.h:86
struct regex * regex_new(const char *pattern)
Creates a new &#39;regex&#39; struct containing the given pattern and a PCRE compiled regular expression...
Definition: regex.c:24
struct regex * instance
Definition: data.h:394
int sasprintf(char **strp, const char *fmt,...)
Safe-wrapper around asprintf which exits if it returns -1 (meaning that there is no more memory avail...
Definition: data.h:86
int old_id
Definition: data.h:615
xcb_window_t id
Definition: data.h:409
static int json_key(void *ctx, const unsigned char *val, size_t len)
Definition: load_layout.c:136
uint16_t depth
Definition: data.h:618
static int json_end_array(void *ctx)
Definition: load_layout.c:102
uint32_t height
Definition: data.h:126
#define TAILQ_INSERT_HEAD(head, elm, field)
Definition: queue.h:352
Con * focused
Definition: tree.c:15
struct Rect geometry
the geometry this window requested when getting mapped
Definition: data.h:518
int num
the workspace number, if this Con is of type CT_WORKSPACE and the workspace is not a named workspace ...
Definition: data.h:510
char * name
Definition: data.h:520
fullscreen_mode_t fullscreen_mode
Definition: data.h:563
Definition: data.h:91
void con_focus(Con *con)
Sets input focus to the given container.
Definition: con.c:213
#define TAILQ_EMPTY(head)
Definition: queue.h:331
static int json_int(void *ctx, long long val)
Definition: load_layout.c:308
bool con_is_split(Con *con)
Definition: con.c:265
enum Match::@13 dock
#define TAILQ_ENTRY(type)
Definition: queue.h:314
#define ELOG(fmt,...)
Definition: libi3.h:81
A &quot;match&quot; is a data structure which acts like a mask or expression to match certain windows or not...
Definition: data.h:390
static xcb_cursor_context_t * ctx
Definition: xcursor.c:19
layout_t layout
Definition: data.h:578
A &#39;Con&#39; represents everything from the X11 root window down to a single X11 window.
Definition: data.h:479
uint32_t x
Definition: data.h:123
void * scalloc(size_t size)
Safe-wrapper around calloc which exits if malloc returns NULL (meaning that there is no more memory a...
Definition: data.h:89
layout_t last_split_layout
Definition: data.h:578
struct regex * title
Definition: data.h:391
layout_t workspace_layout
Definition: data.h:578
enum Match::@15 insert_where
#define TAILQ_INSERT_TAIL(head, elm, field)
Definition: queue.h:362
#define TAILQ_HEAD_INITIALIZER(head)
Definition: queue.h:311
bool restart_mode
Definition: data.h:429
static bool parsing_swallows
Definition: load_layout.c:25
void * smalloc(size_t size)
Safe-wrapper around malloc which exits if malloc returns NULL (meaning that there is no more memory a...
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:334
void con_fix_percent(Con *con)
Updates the percent attribute of the children of the given container.
Definition: con.c:542
static bool parsing_window_rect
Definition: load_layout.c:27
static bool parsing_geometry
Definition: load_layout.c:28
static int json_end_map(void *ctx)
Definition: load_layout.c:67
#define FREE(pointer)
Definition: util.h:46
static int json_string(void *ctx, const unsigned char *val, size_t len)
Definition: load_layout.c:159
Definition: data.h:56
#define TAILQ_HEAD(name, type)
Definition: queue.h:305
void match_init(Match *match)
Definition: match.c:28
Con * con_new_skeleton(Con *parent, i3Window *window)
Create a new container (and attach it to the given parent, if not NULL).
Definition: con.c:49
struct regex * class
Definition: data.h:393
uint32_t width
Definition: data.h:125
Definition: data.h:90
void match_free(Match *match)
Frees the given match.
Definition: match.c:191
void x_con_init(Con *con, uint16_t depth)
Initializes the X11 part for the given container.
Definition: x.c:94