]> git.sur5r.net Git - i3/i3/blob - src/commands.c
b91c71a47c76389c193e5ad7ab5ed0f16d71a63e
[i3/i3] / src / commands.c
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
6  *
7  * commands.c: all command functions (see commands_parser.c)
8  *
9  */
10 #include "all.h"
11
12 #include <stdint.h>
13 #include <float.h>
14 #include <stdarg.h>
15
16 #ifdef I3_ASAN_ENABLED
17 #include <sanitizer/lsan_interface.h>
18 #endif
19
20 #include "shmlog.h"
21
22 // Macros to make the YAJL API a bit easier to use.
23 #define y(x, ...) (cmd_output->json_gen != NULL ? yajl_gen_##x(cmd_output->json_gen, ##__VA_ARGS__) : 0)
24 #define ystr(str) (cmd_output->json_gen != NULL ? yajl_gen_string(cmd_output->json_gen, (unsigned char *)str, strlen(str)) : 0)
25 #define ysuccess(success)                   \
26     do {                                    \
27         if (cmd_output->json_gen != NULL) { \
28             y(map_open);                    \
29             ystr("success");                \
30             y(bool, success);               \
31             y(map_close);                   \
32         }                                   \
33     } while (0)
34 #define yerror(format, ...)                             \
35     do {                                                \
36         if (cmd_output->json_gen != NULL) {             \
37             char *message;                              \
38             sasprintf(&message, format, ##__VA_ARGS__); \
39             y(map_open);                                \
40             ystr("success");                            \
41             y(bool, false);                             \
42             ystr("error");                              \
43             ystr(message);                              \
44             y(map_close);                               \
45             free(message);                              \
46         }                                               \
47     } while (0)
48
49 /** If an error occurred during parsing of the criteria, we want to exit instead
50  * of relying on fallback behavior. See #2091. */
51 #define HANDLE_INVALID_MATCH                                   \
52     do {                                                       \
53         if (current_match->error != NULL) {                    \
54             yerror("Invalid match: %s", current_match->error); \
55             return;                                            \
56         }                                                      \
57     } while (0)
58
59 /** When the command did not include match criteria (!), we use the currently
60  * focused container. Do not confuse this case with a command which included
61  * criteria but which did not match any windows. This macro has to be called in
62  * every command.
63  */
64 #define HANDLE_EMPTY_MATCH                              \
65     do {                                                \
66         HANDLE_INVALID_MATCH;                           \
67                                                         \
68         if (match_is_empty(current_match)) {            \
69             while (!TAILQ_EMPTY(&owindows)) {           \
70                 owindow *ow = TAILQ_FIRST(&owindows);   \
71                 TAILQ_REMOVE(&owindows, ow, owindows);  \
72                 free(ow);                               \
73             }                                           \
74             owindow *ow = smalloc(sizeof(owindow));     \
75             ow->con = focused;                          \
76             TAILQ_INIT(&owindows);                      \
77             TAILQ_INSERT_TAIL(&owindows, ow, owindows); \
78         }                                               \
79     } while (0)
80
81 /*
82  * Returns true if a is definitely greater than b (using the given epsilon)
83  *
84  */
85 static bool definitelyGreaterThan(float a, float b, float epsilon) {
86     return (a - b) > ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
87 }
88
89 /*
90  * Checks whether we switched to a new workspace and returns false in that case,
91  * signaling that further workspace switching should be done by the calling function
92  * If not, calls workspace_back_and_forth() if workspace_auto_back_and_forth is set
93  * and return true, signaling that no further workspace switching should occur in the calling function.
94  *
95  */
96 static bool maybe_back_and_forth(struct CommandResultIR *cmd_output, const char *name) {
97     Con *ws = con_get_workspace(focused);
98
99     /* If we switched to a different workspace, do nothing */
100     if (strcmp(ws->name, name) != 0)
101         return false;
102
103     DLOG("This workspace is already focused.\n");
104     if (config.workspace_auto_back_and_forth) {
105         workspace_back_and_forth();
106         cmd_output->needs_tree_render = true;
107     }
108     return true;
109 }
110
111 /*
112  * Return the passed workspace unless it is the current one and auto back and
113  * forth is enabled, in which case the back_and_forth workspace is returned.
114  */
115 static Con *maybe_auto_back_and_forth_workspace(Con *workspace) {
116     Con *current, *baf;
117
118     if (!config.workspace_auto_back_and_forth)
119         return workspace;
120
121     current = con_get_workspace(focused);
122
123     if (current == workspace) {
124         baf = workspace_back_and_forth_get();
125         if (baf != NULL) {
126             DLOG("Substituting workspace with back_and_forth, as it is focused.\n");
127             return baf;
128         }
129     }
130
131     return workspace;
132 }
133
134 /*******************************************************************************
135  * Criteria functions.
136  ******************************************************************************/
137
138 /*
139  * Helper data structure for an operation window (window on which the operation
140  * will be performed). Used to build the TAILQ owindows.
141  *
142  */
143 typedef struct owindow {
144     Con *con;
145     TAILQ_ENTRY(owindow) owindows;
146 } owindow;
147
148 typedef TAILQ_HEAD(owindows_head, owindow) owindows_head;
149
150 static owindows_head owindows;
151
152 /*
153  * Initializes the specified 'Match' data structure and the initial state of
154  * commands.c for matching target windows of a command.
155  *
156  */
157 void cmd_criteria_init(I3_CMD) {
158     Con *con;
159     owindow *ow;
160
161     DLOG("Initializing criteria, current_match = %p\n", current_match);
162     match_free(current_match);
163     match_init(current_match);
164     while (!TAILQ_EMPTY(&owindows)) {
165         ow = TAILQ_FIRST(&owindows);
166         TAILQ_REMOVE(&owindows, ow, owindows);
167         free(ow);
168     }
169     TAILQ_INIT(&owindows);
170     /* copy all_cons */
171     TAILQ_FOREACH(con, &all_cons, all_cons) {
172         ow = smalloc(sizeof(owindow));
173         ow->con = con;
174         TAILQ_INSERT_TAIL(&owindows, ow, owindows);
175     }
176 }
177
178 /*
179  * A match specification just finished (the closing square bracket was found),
180  * so we filter the list of owindows.
181  *
182  */
183 void cmd_criteria_match_windows(I3_CMD) {
184     owindow *next, *current;
185
186     DLOG("match specification finished, matching...\n");
187     /* copy the old list head to iterate through it and start with a fresh
188      * list which will contain only matching windows */
189     struct owindows_head old = owindows;
190     TAILQ_INIT(&owindows);
191     for (next = TAILQ_FIRST(&old); next != TAILQ_END(&old);) {
192         /* make a copy of the next pointer and advance the pointer to the
193          * next element as we are going to invalidate the element’s
194          * next/prev pointers by calling TAILQ_INSERT_TAIL later */
195         current = next;
196         next = TAILQ_NEXT(next, owindows);
197
198         DLOG("checking if con %p / %s matches\n", current->con, current->con->name);
199
200         /* We use this flag to prevent matching on window-less containers if
201          * only window-specific criteria were specified. */
202         bool accept_match = false;
203
204         if (current_match->con_id != NULL) {
205             accept_match = true;
206
207             if (current_match->con_id == current->con) {
208                 DLOG("con_id matched.\n");
209             } else {
210                 DLOG("con_id does not match.\n");
211                 FREE(current);
212                 continue;
213             }
214         }
215
216         if (current_match->mark != NULL && !TAILQ_EMPTY(&(current->con->marks_head))) {
217             accept_match = true;
218             bool matched_by_mark = false;
219
220             mark_t *mark;
221             TAILQ_FOREACH(mark, &(current->con->marks_head), marks) {
222                 if (!regex_matches(current_match->mark, mark->name))
223                     continue;
224
225                 DLOG("match by mark\n");
226                 matched_by_mark = true;
227                 break;
228             }
229
230             if (!matched_by_mark) {
231                 DLOG("mark does not match.\n");
232                 FREE(current);
233                 continue;
234             }
235         }
236
237         if (current->con->window != NULL) {
238             if (match_matches_window(current_match, current->con->window)) {
239                 DLOG("matches window!\n");
240                 accept_match = true;
241             } else {
242                 DLOG("doesn't match\n");
243                 FREE(current);
244                 continue;
245             }
246         }
247
248         if (accept_match) {
249             TAILQ_INSERT_TAIL(&owindows, current, owindows);
250         } else {
251             FREE(current);
252             continue;
253         }
254     }
255
256     TAILQ_FOREACH(current, &owindows, owindows) {
257         DLOG("matching: %p / %s\n", current->con, current->con->name);
258     }
259 }
260
261 /*
262  * Interprets a ctype=cvalue pair and adds it to the current match
263  * specification.
264  *
265  */
266 void cmd_criteria_add(I3_CMD, const char *ctype, const char *cvalue) {
267     match_parse_property(current_match, ctype, cvalue);
268 }
269
270 /*
271  * Implementation of 'move [window|container] [to] workspace
272  * next|prev|next_on_output|prev_on_output|current'.
273  *
274  */
275 void cmd_move_con_to_workspace(I3_CMD, const char *which) {
276     owindow *current;
277
278     DLOG("which=%s\n", which);
279
280     /* We have nothing to move:
281      *  when criteria was specified but didn't match any window or
282      *  when criteria wasn't specified and we don't have any window focused. */
283     if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
284         (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
285          !con_has_children(focused))) {
286         ysuccess(false);
287         return;
288     }
289
290     HANDLE_EMPTY_MATCH;
291
292     /* get the workspace */
293     Con *ws;
294     if (strcmp(which, "next") == 0)
295         ws = workspace_next();
296     else if (strcmp(which, "prev") == 0)
297         ws = workspace_prev();
298     else if (strcmp(which, "next_on_output") == 0)
299         ws = workspace_next_on_output();
300     else if (strcmp(which, "prev_on_output") == 0)
301         ws = workspace_prev_on_output();
302     else if (strcmp(which, "current") == 0)
303         ws = con_get_workspace(focused);
304     else {
305         ELOG("BUG: called with which=%s\n", which);
306         ysuccess(false);
307         return;
308     }
309
310     TAILQ_FOREACH(current, &owindows, owindows) {
311         DLOG("matching: %p / %s\n", current->con, current->con->name);
312         con_move_to_workspace(current->con, ws, true, false, false);
313     }
314
315     cmd_output->needs_tree_render = true;
316     // XXX: default reply for now, make this a better reply
317     ysuccess(true);
318 }
319
320 /**
321  * Implementation of 'move [window|container] [to] workspace back_and_forth'.
322  *
323  */
324 void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
325     owindow *current;
326     Con *ws;
327
328     ws = workspace_back_and_forth_get();
329
330     if (ws == NULL) {
331         yerror("No workspace was previously active.");
332         return;
333     }
334
335     HANDLE_EMPTY_MATCH;
336
337     TAILQ_FOREACH(current, &owindows, owindows) {
338         DLOG("matching: %p / %s\n", current->con, current->con->name);
339         con_move_to_workspace(current->con, ws, true, false, false);
340     }
341
342     cmd_output->needs_tree_render = true;
343     // XXX: default reply for now, make this a better reply
344     ysuccess(true);
345 }
346
347 /*
348  * Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace <name>'.
349  *
350  */
351 void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_forth) {
352     if (strncasecmp(name, "__", strlen("__")) == 0) {
353         LOG("You cannot move containers to i3-internal workspaces (\"%s\").\n", name);
354         ysuccess(false);
355         return;
356     }
357
358     const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
359     owindow *current;
360
361     /* We have nothing to move:
362      *  when criteria was specified but didn't match any window or
363      *  when criteria wasn't specified and we don't have any window focused. */
364     if (!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) {
365         ELOG("No windows match your criteria, cannot move.\n");
366         ysuccess(false);
367         return;
368     } else if (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
369                !con_has_children(focused)) {
370         ysuccess(false);
371         return;
372     }
373
374     LOG("should move window to workspace %s\n", name);
375     /* get the workspace */
376     Con *ws = workspace_get(name, NULL);
377
378     if (!no_auto_back_and_forth)
379         ws = maybe_auto_back_and_forth_workspace(ws);
380
381     HANDLE_EMPTY_MATCH;
382
383     TAILQ_FOREACH(current, &owindows, owindows) {
384         DLOG("matching: %p / %s\n", current->con, current->con->name);
385         con_move_to_workspace(current->con, ws, true, false, false);
386     }
387
388     cmd_output->needs_tree_render = true;
389     // XXX: default reply for now, make this a better reply
390     ysuccess(true);
391 }
392
393 /*
394  * Implementation of 'move [--no-auto-back-and-forth] [window|container] [to] workspace number <name>'.
395  *
396  */
397 void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_and_forth) {
398     const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
399     owindow *current;
400
401     /* We have nothing to move:
402      *  when criteria was specified but didn't match any window or
403      *  when criteria wasn't specified and we don't have any window focused. */
404     if ((!match_is_empty(current_match) && TAILQ_EMPTY(&owindows)) ||
405         (match_is_empty(current_match) && focused->type == CT_WORKSPACE &&
406          !con_has_children(focused))) {
407         ysuccess(false);
408         return;
409     }
410
411     LOG("should move window to workspace %s\n", which);
412     /* get the workspace */
413     Con *output, *workspace = NULL;
414
415     long parsed_num = ws_name_to_number(which);
416
417     if (parsed_num == -1) {
418         LOG("Could not parse initial part of \"%s\" as a number.\n", which);
419         yerror("Could not parse number \"%s\"", which);
420         return;
421     }
422
423     TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
424     GREP_FIRST(workspace, output_get_content(output),
425                child->num == parsed_num);
426
427     if (!workspace) {
428         workspace = workspace_get(which, NULL);
429     }
430
431     if (!no_auto_back_and_forth)
432         workspace = maybe_auto_back_and_forth_workspace(workspace);
433
434     HANDLE_EMPTY_MATCH;
435
436     TAILQ_FOREACH(current, &owindows, owindows) {
437         DLOG("matching: %p / %s\n", current->con, current->con->name);
438         con_move_to_workspace(current->con, workspace, true, false, false);
439     }
440
441     cmd_output->needs_tree_render = true;
442     // XXX: default reply for now, make this a better reply
443     ysuccess(true);
444 }
445
446 static void cmd_resize_floating(I3_CMD, const char *way, const char *direction, Con *floating_con, int px) {
447     LOG("floating resize\n");
448     Rect old_rect = floating_con->rect;
449     Con *focused_con = con_descend_focused(floating_con);
450
451     /* ensure that resize will take place even if pixel increment is smaller than
452      * height increment or width increment.
453      * fixes #1011 */
454     const i3Window *window = focused_con->window;
455     if (window != NULL) {
456         if (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0 ||
457             strcmp(direction, "height") == 0) {
458             if (px < 0)
459                 px = (-px < window->height_increment) ? -window->height_increment : px;
460             else
461                 px = (px < window->height_increment) ? window->height_increment : px;
462         } else if (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0) {
463             if (px < 0)
464                 px = (-px < window->width_increment) ? -window->width_increment : px;
465             else
466                 px = (px < window->width_increment) ? window->width_increment : px;
467         }
468     }
469
470     if (strcmp(direction, "up") == 0) {
471         floating_con->rect.height += px;
472     } else if (strcmp(direction, "down") == 0 || strcmp(direction, "height") == 0) {
473         floating_con->rect.height += px;
474     } else if (strcmp(direction, "left") == 0) {
475         floating_con->rect.width += px;
476     } else {
477         floating_con->rect.width += px;
478     }
479
480     floating_check_size(floating_con);
481
482     /* Did we actually resize anything or did the size constraints prevent us?
483      * If we could not resize, exit now to not move the window. */
484     if (memcmp(&old_rect, &(floating_con->rect), sizeof(Rect)) == 0)
485         return;
486
487     if (strcmp(direction, "up") == 0) {
488         floating_con->rect.y -= (floating_con->rect.height - old_rect.height);
489     } else if (strcmp(direction, "left") == 0) {
490         floating_con->rect.x -= (floating_con->rect.width - old_rect.width);
491     }
492
493     /* If this is a scratchpad window, don't auto center it from now on. */
494     if (floating_con->scratchpad_state == SCRATCHPAD_FRESH)
495         floating_con->scratchpad_state = SCRATCHPAD_CHANGED;
496 }
497
498 static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *way, const char *direction, int ppt) {
499     LOG("tiling resize\n");
500     Con *second = NULL;
501     Con *first = current;
502     direction_t search_direction;
503     if (!strcmp(direction, "left"))
504         search_direction = D_LEFT;
505     else if (!strcmp(direction, "right"))
506         search_direction = D_RIGHT;
507     else if (!strcmp(direction, "up"))
508         search_direction = D_UP;
509     else
510         search_direction = D_DOWN;
511
512     bool res = resize_find_tiling_participants(&first, &second, search_direction);
513     if (!res) {
514         LOG("No second container in this direction found.\n");
515         ysuccess(false);
516         return false;
517     }
518
519     /* get the default percentage */
520     int children = con_num_children(first->parent);
521     LOG("ins. %d children\n", children);
522     double percentage = 1.0 / children;
523     LOG("default percentage = %f\n", percentage);
524
525     /* resize */
526     LOG("second->percent = %f\n", second->percent);
527     LOG("first->percent before = %f\n", first->percent);
528     if (first->percent == 0.0)
529         first->percent = percentage;
530     if (second->percent == 0.0)
531         second->percent = percentage;
532     double new_first_percent = first->percent + ((double)ppt / 100.0);
533     double new_second_percent = second->percent - ((double)ppt / 100.0);
534     LOG("new_first_percent = %f\n", new_first_percent);
535     LOG("new_second_percent = %f\n", new_second_percent);
536     /* Ensure that the new percentages are positive and greater than
537      * 0.05 to have a reasonable minimum size. */
538     if (definitelyGreaterThan(new_first_percent, 0.05, DBL_EPSILON) &&
539         definitelyGreaterThan(new_second_percent, 0.05, DBL_EPSILON)) {
540         first->percent += ((double)ppt / 100.0);
541         second->percent -= ((double)ppt / 100.0);
542         LOG("first->percent after = %f\n", first->percent);
543         LOG("second->percent after = %f\n", second->percent);
544     } else {
545         LOG("Not resizing, already at minimum size\n");
546     }
547
548     return true;
549 }
550
551 static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *way, const char *direction, int ppt) {
552     LOG("width/height resize\n");
553     /* get the appropriate current container (skip stacked/tabbed cons) */
554     while (current->parent->layout == L_STACKED ||
555            current->parent->layout == L_TABBED)
556         current = current->parent;
557
558     /* Then further go up until we find one with the matching orientation. */
559     orientation_t search_orientation =
560         (strcmp(direction, "width") == 0 ? HORIZ : VERT);
561
562     while (current->type != CT_WORKSPACE &&
563            current->type != CT_FLOATING_CON &&
564            (con_orientation(current->parent) != search_orientation || con_num_children(current->parent) == 1))
565         current = current->parent;
566
567     /* get the default percentage */
568     int children = con_num_children(current->parent);
569     LOG("ins. %d children\n", children);
570     double percentage = 1.0 / children;
571     LOG("default percentage = %f\n", percentage);
572
573     orientation_t orientation = con_orientation(current->parent);
574
575     if ((orientation == HORIZ &&
576          strcmp(direction, "height") == 0) ||
577         (orientation == VERT &&
578          strcmp(direction, "width") == 0)) {
579         LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
580             (orientation == HORIZ ? "horizontal" : "vertical"));
581         ysuccess(false);
582         return false;
583     }
584
585     if (children == 1) {
586         LOG("This is the only container, cannot resize.\n");
587         ysuccess(false);
588         return false;
589     }
590
591     /* Ensure all the other children have a percentage set. */
592     Con *child;
593     TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
594         LOG("child->percent = %f (child %p)\n", child->percent, child);
595         if (child->percent == 0.0)
596             child->percent = percentage;
597     }
598
599     double new_current_percent = current->percent + ((double)ppt / 100.0);
600     double subtract_percent = ((double)ppt / 100.0) / (children - 1);
601     LOG("new_current_percent = %f\n", new_current_percent);
602     LOG("subtract_percent = %f\n", subtract_percent);
603     /* Ensure that the new percentages are positive and greater than
604      * 0.05 to have a reasonable minimum size. */
605     TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
606         if (child == current)
607             continue;
608         if (!definitelyGreaterThan(child->percent - subtract_percent, 0.05, DBL_EPSILON)) {
609             LOG("Not resizing, already at minimum size (child %p would end up with a size of %.f\n", child, child->percent - subtract_percent);
610             ysuccess(false);
611             return false;
612         }
613     }
614     if (!definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON)) {
615         LOG("Not resizing, already at minimum size\n");
616         ysuccess(false);
617         return false;
618     }
619
620     current->percent += ((double)ppt / 100.0);
621     LOG("current->percent after = %f\n", current->percent);
622
623     TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
624         if (child == current)
625             continue;
626         child->percent -= subtract_percent;
627         LOG("child->percent after (%p) = %f\n", child, child->percent);
628     }
629
630     return true;
631 }
632
633 /*
634  * Implementation of 'resize grow|shrink <direction> [<px> px] [or <ppt> ppt]'.
635  *
636  */
637 void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px, long resize_ppt) {
638     DLOG("resizing in way %s, direction %s, px %ld or ppt %ld\n", way, direction, resize_px, resize_ppt);
639     if (strcmp(way, "shrink") == 0) {
640         resize_px *= -1;
641         resize_ppt *= -1;
642     }
643
644     HANDLE_EMPTY_MATCH;
645
646     owindow *current;
647     TAILQ_FOREACH(current, &owindows, owindows) {
648         /* Don't handle dock windows (issue #1201) */
649         if (current->con->window && current->con->window->dock) {
650             DLOG("This is a dock window. Not resizing (con = %p)\n)", current->con);
651             continue;
652         }
653
654         Con *floating_con;
655         if ((floating_con = con_inside_floating(current->con))) {
656             cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, resize_px);
657         } else {
658             if (strcmp(direction, "width") == 0 ||
659                 strcmp(direction, "height") == 0) {
660                 if (!cmd_resize_tiling_width_height(current_match, cmd_output,
661                                                     current->con, way, direction, resize_ppt))
662                     return;
663             } else {
664                 if (!cmd_resize_tiling_direction(current_match, cmd_output,
665                                                  current->con, way, direction, resize_ppt))
666                     return;
667             }
668         }
669     }
670
671     cmd_output->needs_tree_render = true;
672     // XXX: default reply for now, make this a better reply
673     ysuccess(true);
674 }
675
676 /*
677  * Implementation of 'resize set <px> [px] <px> [px]'.
678  *
679  */
680 void cmd_resize_set(I3_CMD, long cwidth, long cheight) {
681     DLOG("resizing to %ldx%ld px\n", cwidth, cheight);
682     if (cwidth <= 0 || cheight <= 0) {
683         ELOG("Resize failed: dimensions cannot be negative (was %ldx%ld)\n", cwidth, cheight);
684         return;
685     }
686
687     HANDLE_EMPTY_MATCH;
688
689     owindow *current;
690     TAILQ_FOREACH(current, &owindows, owindows) {
691         Con *floating_con;
692         if ((floating_con = con_inside_floating(current->con))) {
693             floating_resize(floating_con, cwidth, cheight);
694         } else {
695             ELOG("Resize failed: %p not a floating container\n", current->con);
696         }
697     }
698
699     cmd_output->needs_tree_render = true;
700     // XXX: default reply for now, make this a better reply
701     ysuccess(true);
702 }
703
704 /*
705  * Implementation of 'border normal|pixel [<n>]', 'border none|1pixel|toggle'.
706  *
707  */
708 void cmd_border(I3_CMD, const char *border_style_str, long border_width) {
709     DLOG("border style should be changed to %s with border width %ld\n", border_style_str, border_width);
710     owindow *current;
711
712     HANDLE_EMPTY_MATCH;
713
714     TAILQ_FOREACH(current, &owindows, owindows) {
715         DLOG("matching: %p / %s\n", current->con, current->con->name);
716         int border_style = current->con->border_style;
717         int con_border_width = border_width;
718
719         if (strcmp(border_style_str, "toggle") == 0) {
720             border_style++;
721             border_style %= 3;
722             if (border_style == BS_NORMAL)
723                 con_border_width = 2;
724             else if (border_style == BS_NONE)
725                 con_border_width = 0;
726             else if (border_style == BS_PIXEL)
727                 con_border_width = 1;
728         } else {
729             if (strcmp(border_style_str, "normal") == 0) {
730                 border_style = BS_NORMAL;
731             } else if (strcmp(border_style_str, "pixel") == 0) {
732                 border_style = BS_PIXEL;
733             } else if (strcmp(border_style_str, "1pixel") == 0) {
734                 border_style = BS_PIXEL;
735                 con_border_width = 1;
736             } else if (strcmp(border_style_str, "none") == 0) {
737                 border_style = BS_NONE;
738             } else {
739                 ELOG("BUG: called with border_style=%s\n", border_style_str);
740                 ysuccess(false);
741                 return;
742             }
743         }
744
745         con_set_border_style(current->con, border_style, logical_px(con_border_width));
746     }
747
748     cmd_output->needs_tree_render = true;
749     // XXX: default reply for now, make this a better reply
750     ysuccess(true);
751 }
752
753 /*
754  * Implementation of 'nop <comment>'.
755  *
756  */
757 void cmd_nop(I3_CMD, const char *comment) {
758     LOG("-------------------------------------------------\n");
759     LOG("  NOP: %s\n", comment);
760     LOG("-------------------------------------------------\n");
761 }
762
763 /*
764  * Implementation of 'append_layout <path>'.
765  *
766  */
767 void cmd_append_layout(I3_CMD, const char *cpath) {
768     char *path = sstrdup(cpath);
769     LOG("Appending layout \"%s\"\n", path);
770
771     /* Make sure we allow paths like '~/.i3/layout.json' */
772     path = resolve_tilde(path);
773
774     json_content_t content = json_determine_content(path);
775     LOG("JSON content = %d\n", content);
776     if (content == JSON_CONTENT_UNKNOWN) {
777         ELOG("Could not determine the contents of \"%s\", not loading.\n", path);
778         yerror("Could not determine the contents of \"%s\".", path);
779         free(path);
780         return;
781     }
782
783     Con *parent = focused;
784     if (content == JSON_CONTENT_WORKSPACE) {
785         parent = output_get_content(con_get_output(parent));
786     } else {
787         /* We need to append the layout to a split container, since a leaf
788          * container must not have any children (by definition).
789          * Note that we explicitly check for workspaces, since they are okay for
790          * this purpose, but con_accepts_window() returns false for workspaces. */
791         while (parent->type != CT_WORKSPACE && !con_accepts_window(parent))
792             parent = parent->parent;
793     }
794     DLOG("Appending to parent=%p instead of focused=%p\n", parent, focused);
795     char *errormsg = NULL;
796     tree_append_json(parent, path, &errormsg);
797     if (errormsg != NULL) {
798         yerror(errormsg);
799         free(errormsg);
800         /* Note that we continue executing since tree_append_json() has
801          * side-effects — user-provided layouts can be partly valid, partly
802          * invalid, leading to half of the placeholder containers being
803          * created. */
804     } else {
805         ysuccess(true);
806     }
807
808     // XXX: This is a bit of a kludge. Theoretically, render_con(parent,
809     // false); should be enough, but when sending 'workspace 4; append_layout
810     // /tmp/foo.json', the needs_tree_render == true of the workspace command
811     // is not executed yet and will be batched with append_layout’s
812     // needs_tree_render after the parser finished. We should check if that is
813     // necessary at all.
814     render_con(croot, false);
815
816     restore_open_placeholder_windows(parent);
817
818     if (content == JSON_CONTENT_WORKSPACE)
819         ipc_send_workspace_event("restored", parent, NULL);
820
821     free(path);
822     cmd_output->needs_tree_render = true;
823 }
824
825 /*
826  * Implementation of 'workspace next|prev|next_on_output|prev_on_output'.
827  *
828  */
829 void cmd_workspace(I3_CMD, const char *which) {
830     Con *ws;
831
832     DLOG("which=%s\n", which);
833
834     if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
835         LOG("Cannot switch workspace while in global fullscreen\n");
836         ysuccess(false);
837         return;
838     }
839
840     if (strcmp(which, "next") == 0)
841         ws = workspace_next();
842     else if (strcmp(which, "prev") == 0)
843         ws = workspace_prev();
844     else if (strcmp(which, "next_on_output") == 0)
845         ws = workspace_next_on_output();
846     else if (strcmp(which, "prev_on_output") == 0)
847         ws = workspace_prev_on_output();
848     else {
849         ELOG("BUG: called with which=%s\n", which);
850         ysuccess(false);
851         return;
852     }
853
854     workspace_show(ws);
855
856     cmd_output->needs_tree_render = true;
857     // XXX: default reply for now, make this a better reply
858     ysuccess(true);
859 }
860
861 /*
862  * Implementation of 'workspace [--no-auto-back-and-forth] number <name>'
863  *
864  */
865 void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_and_forth) {
866     const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
867     Con *output, *workspace = NULL;
868
869     if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
870         LOG("Cannot switch workspace while in global fullscreen\n");
871         ysuccess(false);
872         return;
873     }
874
875     long parsed_num = ws_name_to_number(which);
876
877     if (parsed_num == -1) {
878         LOG("Could not parse initial part of \"%s\" as a number.\n", which);
879         yerror("Could not parse number \"%s\"", which);
880         return;
881     }
882
883     TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
884     GREP_FIRST(workspace, output_get_content(output),
885                child->num == parsed_num);
886
887     if (!workspace) {
888         LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num);
889         ysuccess(true);
890         workspace_show_by_name(which);
891         cmd_output->needs_tree_render = true;
892         return;
893     }
894     if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, workspace->name))
895         return;
896     workspace_show(workspace);
897
898     cmd_output->needs_tree_render = true;
899     // XXX: default reply for now, make this a better reply
900     ysuccess(true);
901 }
902
903 /*
904  * Implementation of 'workspace back_and_forth'.
905  *
906  */
907 void cmd_workspace_back_and_forth(I3_CMD) {
908     if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
909         LOG("Cannot switch workspace while in global fullscreen\n");
910         ysuccess(false);
911         return;
912     }
913
914     workspace_back_and_forth();
915
916     cmd_output->needs_tree_render = true;
917     // XXX: default reply for now, make this a better reply
918     ysuccess(true);
919 }
920
921 /*
922  * Implementation of 'workspace [--no-auto-back-and-forth] <name>'
923  *
924  */
925 void cmd_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_forth) {
926     const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL);
927
928     if (strncasecmp(name, "__", strlen("__")) == 0) {
929         LOG("You cannot switch to the i3-internal workspaces (\"%s\").\n", name);
930         ysuccess(false);
931         return;
932     }
933
934     if (con_get_fullscreen_con(croot, CF_GLOBAL)) {
935         LOG("Cannot switch workspace while in global fullscreen\n");
936         ysuccess(false);
937         return;
938     }
939
940     DLOG("should switch to workspace %s\n", name);
941     if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, name))
942         return;
943     workspace_show_by_name(name);
944
945     cmd_output->needs_tree_render = true;
946     // XXX: default reply for now, make this a better reply
947     ysuccess(true);
948 }
949
950 /*
951  * Implementation of 'mark [--add|--replace] [--toggle] <mark>'
952  *
953  */
954 void cmd_mark(I3_CMD, const char *mark, const char *mode, const char *toggle) {
955     HANDLE_EMPTY_MATCH;
956
957     owindow *current = TAILQ_FIRST(&owindows);
958     if (current == NULL) {
959         ysuccess(false);
960         return;
961     }
962
963     /* Marks must be unique, i.e., no two windows must have the same mark. */
964     if (current != TAILQ_LAST(&owindows, owindows_head)) {
965         yerror("A mark must not be put onto more than one window");
966         return;
967     }
968
969     DLOG("matching: %p / %s\n", current->con, current->con->name);
970
971     mark_mode_t mark_mode = (mode == NULL || strcmp(mode, "--replace") == 0) ? MM_REPLACE : MM_ADD;
972     if (toggle != NULL) {
973         con_mark_toggle(current->con, mark, mark_mode);
974     } else {
975         con_mark(current->con, mark, mark_mode);
976     }
977
978     cmd_output->needs_tree_render = true;
979     // XXX: default reply for now, make this a better reply
980     ysuccess(true);
981 }
982
983 /*
984  * Implementation of 'unmark [mark]'
985  *
986  */
987 void cmd_unmark(I3_CMD, const char *mark) {
988     if (match_is_empty(current_match)) {
989         con_unmark(NULL, mark);
990     } else {
991         owindow *current;
992         TAILQ_FOREACH(current, &owindows, owindows) {
993             con_unmark(current->con, mark);
994         }
995     }
996
997     cmd_output->needs_tree_render = true;
998     // XXX: default reply for now, make this a better reply
999     ysuccess(true);
1000 }
1001
1002 /*
1003  * Implementation of 'mode <string>'.
1004  *
1005  */
1006 void cmd_mode(I3_CMD, const char *mode) {
1007     DLOG("mode=%s\n", mode);
1008     switch_mode(mode);
1009
1010     // XXX: default reply for now, make this a better reply
1011     ysuccess(true);
1012 }
1013
1014 /*
1015  * Implementation of 'move [window|container] [to] output <str>'.
1016  *
1017  */
1018 void cmd_move_con_to_output(I3_CMD, const char *name) {
1019     DLOG("Should move window to output \"%s\".\n", name);
1020     HANDLE_EMPTY_MATCH;
1021
1022     owindow *current;
1023     bool had_error = false;
1024     TAILQ_FOREACH(current, &owindows, owindows) {
1025         DLOG("matching: %p / %s\n", current->con, current->con->name);
1026
1027         Output *current_output = get_output_for_con(current->con);
1028         assert(current_output != NULL);
1029
1030         Output *output = get_output_from_string(current_output, name);
1031         if (output == NULL) {
1032             ELOG("Could not find output \"%s\", skipping.\n", name);
1033             had_error = true;
1034             continue;
1035         }
1036
1037         Con *ws = NULL;
1038         GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
1039         if (ws == NULL) {
1040             ELOG("Could not find a visible workspace on output %p.\n", output);
1041             had_error = true;
1042             continue;
1043         }
1044
1045         con_move_to_workspace(current->con, ws, true, false, false);
1046     }
1047
1048     cmd_output->needs_tree_render = true;
1049     ysuccess(!had_error);
1050 }
1051
1052 /*
1053  * Implementation of 'move [container|window] [to] mark <str>'.
1054  *
1055  */
1056 void cmd_move_con_to_mark(I3_CMD, const char *mark) {
1057     DLOG("moving window to mark \"%s\"\n", mark);
1058
1059     HANDLE_EMPTY_MATCH;
1060
1061     bool result = true;
1062     owindow *current;
1063     TAILQ_FOREACH(current, &owindows, owindows) {
1064         DLOG("moving matched window %p / %s to mark \"%s\"\n", current->con, current->con->name, mark);
1065         result &= con_move_to_mark(current->con, mark);
1066     }
1067
1068     cmd_output->needs_tree_render = true;
1069     ysuccess(result);
1070 }
1071
1072 /*
1073  * Implementation of 'floating enable|disable|toggle'
1074  *
1075  */
1076 void cmd_floating(I3_CMD, const char *floating_mode) {
1077     owindow *current;
1078
1079     DLOG("floating_mode=%s\n", floating_mode);
1080
1081     HANDLE_EMPTY_MATCH;
1082
1083     TAILQ_FOREACH(current, &owindows, owindows) {
1084         DLOG("matching: %p / %s\n", current->con, current->con->name);
1085         if (strcmp(floating_mode, "toggle") == 0) {
1086             DLOG("should toggle mode\n");
1087             toggle_floating_mode(current->con, false);
1088         } else {
1089             DLOG("should switch mode to %s\n", floating_mode);
1090             if (strcmp(floating_mode, "enable") == 0) {
1091                 floating_enable(current->con, false);
1092             } else {
1093                 floating_disable(current->con, false);
1094             }
1095         }
1096     }
1097
1098     cmd_output->needs_tree_render = true;
1099     // XXX: default reply for now, make this a better reply
1100     ysuccess(true);
1101 }
1102
1103 /*
1104  * Implementation of 'move workspace to [output] <str>'.
1105  *
1106  */
1107 void cmd_move_workspace_to_output(I3_CMD, const char *name) {
1108     DLOG("should move workspace to output %s\n", name);
1109
1110     HANDLE_EMPTY_MATCH;
1111
1112     owindow *current;
1113     TAILQ_FOREACH(current, &owindows, owindows) {
1114         Con *ws = con_get_workspace(current->con);
1115         bool success = workspace_move_to_output(ws, name);
1116         if (!success) {
1117             ELOG("Failed to move workspace to output.\n");
1118             ysuccess(false);
1119             return;
1120         }
1121     }
1122
1123     cmd_output->needs_tree_render = true;
1124     // XXX: default reply for now, make this a better reply
1125     ysuccess(true);
1126 }
1127
1128 /*
1129  * Implementation of 'split v|h|t|vertical|horizontal|toggle'.
1130  *
1131  */
1132 void cmd_split(I3_CMD, const char *direction) {
1133     HANDLE_EMPTY_MATCH;
1134
1135     owindow *current;
1136     LOG("splitting in direction %c\n", direction[0]);
1137     TAILQ_FOREACH(current, &owindows, owindows) {
1138         if (con_is_docked(current->con)) {
1139             ELOG("Cannot split a docked container, skipping.\n");
1140             continue;
1141         }
1142
1143         DLOG("matching: %p / %s\n", current->con, current->con->name);
1144         if (direction[0] == 't') {
1145             layout_t current_layout;
1146             if (current->con->type == CT_WORKSPACE) {
1147                 current_layout = current->con->layout;
1148             } else {
1149                 current_layout = current->con->parent->layout;
1150             }
1151             /* toggling split orientation */
1152             if (current_layout == L_SPLITH) {
1153                 tree_split(current->con, VERT);
1154             } else {
1155                 tree_split(current->con, HORIZ);
1156             }
1157         } else {
1158             tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ));
1159         }
1160     }
1161
1162     cmd_output->needs_tree_render = true;
1163     // XXX: default reply for now, make this a better reply
1164     ysuccess(true);
1165 }
1166
1167 /*
1168  * Implementation of 'kill [window|client]'.
1169  *
1170  */
1171 void cmd_kill(I3_CMD, const char *kill_mode_str) {
1172     if (kill_mode_str == NULL)
1173         kill_mode_str = "window";
1174
1175     DLOG("kill_mode=%s\n", kill_mode_str);
1176
1177     int kill_mode;
1178     if (strcmp(kill_mode_str, "window") == 0)
1179         kill_mode = KILL_WINDOW;
1180     else if (strcmp(kill_mode_str, "client") == 0)
1181         kill_mode = KILL_CLIENT;
1182     else {
1183         ELOG("BUG: called with kill_mode=%s\n", kill_mode_str);
1184         ysuccess(false);
1185         return;
1186     }
1187
1188     HANDLE_EMPTY_MATCH;
1189
1190     owindow *current;
1191     TAILQ_FOREACH(current, &owindows, owindows) {
1192         con_close(current->con, kill_mode);
1193     }
1194
1195     cmd_output->needs_tree_render = true;
1196     // XXX: default reply for now, make this a better reply
1197     ysuccess(true);
1198 }
1199
1200 /*
1201  * Implementation of 'exec [--no-startup-id] <command>'.
1202  *
1203  */
1204 void cmd_exec(I3_CMD, const char *nosn, const char *command) {
1205     bool no_startup_id = (nosn != NULL);
1206
1207     DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id);
1208     start_application(command, no_startup_id);
1209
1210     // XXX: default reply for now, make this a better reply
1211     ysuccess(true);
1212 }
1213
1214 /*
1215  * Implementation of 'focus left|right|up|down'.
1216  *
1217  */
1218 void cmd_focus_direction(I3_CMD, const char *direction) {
1219     DLOG("direction = *%s*\n", direction);
1220
1221     if (strcmp(direction, "left") == 0)
1222         tree_next('p', HORIZ);
1223     else if (strcmp(direction, "right") == 0)
1224         tree_next('n', HORIZ);
1225     else if (strcmp(direction, "up") == 0)
1226         tree_next('p', VERT);
1227     else if (strcmp(direction, "down") == 0)
1228         tree_next('n', VERT);
1229     else {
1230         ELOG("Invalid focus direction (%s)\n", direction);
1231         ysuccess(false);
1232         return;
1233     }
1234
1235     cmd_output->needs_tree_render = true;
1236     // XXX: default reply for now, make this a better reply
1237     ysuccess(true);
1238 }
1239
1240 /*
1241  * Implementation of 'focus tiling|floating|mode_toggle'.
1242  *
1243  */
1244 void cmd_focus_window_mode(I3_CMD, const char *window_mode) {
1245     DLOG("window_mode = %s\n", window_mode);
1246
1247     Con *ws = con_get_workspace(focused);
1248     if (ws != NULL) {
1249         if (strcmp(window_mode, "mode_toggle") == 0) {
1250             if (con_inside_floating(focused))
1251                 window_mode = "tiling";
1252             else
1253                 window_mode = "floating";
1254         }
1255         Con *current;
1256         TAILQ_FOREACH(current, &(ws->focus_head), focused) {
1257             if ((strcmp(window_mode, "floating") == 0 && current->type != CT_FLOATING_CON) ||
1258                 (strcmp(window_mode, "tiling") == 0 && current->type == CT_FLOATING_CON))
1259                 continue;
1260
1261             con_focus(con_descend_focused(current));
1262             break;
1263         }
1264     }
1265
1266     cmd_output->needs_tree_render = true;
1267     // XXX: default reply for now, make this a better reply
1268     ysuccess(true);
1269 }
1270
1271 /*
1272  * Implementation of 'focus parent|child'.
1273  *
1274  */
1275 void cmd_focus_level(I3_CMD, const char *level) {
1276     DLOG("level = %s\n", level);
1277     bool success = false;
1278
1279     /* Focusing the parent can only be allowed if the newly
1280      * focused container won't escape the fullscreen container. */
1281     if (strcmp(level, "parent") == 0) {
1282         if (focused && focused->parent) {
1283             if (con_fullscreen_permits_focusing(focused->parent))
1284                 success = level_up();
1285             else
1286                 ELOG("'focus parent': Currently in fullscreen, not going up\n");
1287         }
1288     }
1289
1290     /* Focusing a child should always be allowed. */
1291     else
1292         success = level_down();
1293
1294     cmd_output->needs_tree_render = success;
1295     // XXX: default reply for now, make this a better reply
1296     ysuccess(success);
1297 }
1298
1299 /*
1300  * Implementation of 'focus'.
1301  *
1302  */
1303 void cmd_focus(I3_CMD) {
1304     DLOG("current_match = %p\n", current_match);
1305
1306     if (match_is_empty(current_match)) {
1307         ELOG("You have to specify which window/container should be focused.\n");
1308         ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
1309
1310         yerror("You have to specify which window/container should be focused");
1311
1312         return;
1313     }
1314
1315     Con *__i3_scratch = workspace_get("__i3_scratch", NULL);
1316     int count = 0;
1317     owindow *current;
1318     TAILQ_FOREACH(current, &owindows, owindows) {
1319         Con *ws = con_get_workspace(current->con);
1320         /* If no workspace could be found, this was a dock window.
1321          * Just skip it, you cannot focus dock windows. */
1322         if (!ws)
1323             continue;
1324
1325         /* Check the fullscreen focus constraints. */
1326         if (!con_fullscreen_permits_focusing(current->con)) {
1327             LOG("Cannot change focus while in fullscreen mode (fullscreen rules).\n");
1328             ysuccess(false);
1329             return;
1330         }
1331
1332         /* In case this is a scratchpad window, call scratchpad_show(). */
1333         if (ws == __i3_scratch) {
1334             scratchpad_show(current->con);
1335             count++;
1336             /* While for the normal focus case we can change focus multiple
1337              * times and only a single window ends up focused, we could show
1338              * multiple scratchpad windows. So, rather break here. */
1339             break;
1340         }
1341
1342         /* If the container is not on the current workspace,
1343          * workspace_show() will switch to a different workspace and (if
1344          * enabled) trigger a mouse pointer warp to the currently focused
1345          * container (!) on the target workspace.
1346          *
1347          * Therefore, before calling workspace_show(), we make sure that
1348          * 'current' will be focused on the workspace. However, we cannot
1349          * just con_focus(current) because then the pointer will not be
1350          * warped at all (the code thinks we are already there).
1351          *
1352          * So we focus 'current' to make it the currently focused window of
1353          * the target workspace, then revert focus. */
1354         Con *currently_focused = focused;
1355         con_focus(current->con);
1356         con_focus(currently_focused);
1357
1358         /* Now switch to the workspace, then focus */
1359         workspace_show(ws);
1360         LOG("focusing %p / %s\n", current->con, current->con->name);
1361         con_focus(current->con);
1362         count++;
1363     }
1364
1365     if (count > 1)
1366         LOG("WARNING: Your criteria for the focus command matches %d containers, "
1367             "while only exactly one container can be focused at a time.\n",
1368             count);
1369
1370     cmd_output->needs_tree_render = true;
1371     ysuccess(count > 0);
1372 }
1373
1374 /*
1375  * Implementation of 'fullscreen enable|toggle [global]' and
1376  *                   'fullscreen disable'
1377  *
1378  */
1379 void cmd_fullscreen(I3_CMD, const char *action, const char *fullscreen_mode) {
1380     fullscreen_mode_t mode = strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT;
1381     DLOG("%s fullscreen, mode = %s\n", action, fullscreen_mode);
1382     owindow *current;
1383
1384     HANDLE_EMPTY_MATCH;
1385
1386     TAILQ_FOREACH(current, &owindows, owindows) {
1387         DLOG("matching: %p / %s\n", current->con, current->con->name);
1388         if (strcmp(action, "toggle") == 0) {
1389             con_toggle_fullscreen(current->con, mode);
1390         } else if (strcmp(action, "enable") == 0) {
1391             con_enable_fullscreen(current->con, mode);
1392         } else if (strcmp(action, "disable") == 0) {
1393             con_disable_fullscreen(current->con);
1394         }
1395     }
1396
1397     cmd_output->needs_tree_render = true;
1398     // XXX: default reply for now, make this a better reply
1399     ysuccess(true);
1400 }
1401
1402 /*
1403  * Implementation of 'sticky enable|disable|toggle'.
1404  *
1405  */
1406 void cmd_sticky(I3_CMD, const char *action) {
1407     DLOG("%s sticky on window\n", action);
1408     HANDLE_EMPTY_MATCH;
1409
1410     owindow *current;
1411     TAILQ_FOREACH(current, &owindows, owindows) {
1412         if (current->con->window == NULL) {
1413             ELOG("only containers holding a window can be made sticky, skipping con = %p\n", current->con);
1414             continue;
1415         }
1416         DLOG("setting sticky for container = %p / %s\n", current->con, current->con->name);
1417
1418         bool sticky = false;
1419         if (strcmp(action, "enable") == 0)
1420             sticky = true;
1421         else if (strcmp(action, "disable") == 0)
1422             sticky = false;
1423         else if (strcmp(action, "toggle") == 0)
1424             sticky = !current->con->sticky;
1425
1426         current->con->sticky = sticky;
1427         ewmh_update_sticky(current->con->window->id, sticky);
1428     }
1429
1430     /* A window we made sticky might not be on a visible workspace right now, so we need to make
1431      * sure it gets pushed to the front now. */
1432     output_push_sticky_windows(focused);
1433
1434     ewmh_update_wm_desktop();
1435
1436     cmd_output->needs_tree_render = true;
1437     ysuccess(true);
1438 }
1439
1440 /*
1441  * Implementation of 'move <direction> [<pixels> [px]]'.
1442  *
1443  */
1444 void cmd_move_direction(I3_CMD, const char *direction, long move_px) {
1445     owindow *current;
1446     HANDLE_EMPTY_MATCH;
1447
1448     Con *initially_focused = focused;
1449
1450     TAILQ_FOREACH(current, &owindows, owindows) {
1451         DLOG("moving in direction %s, px %ld\n", direction, move_px);
1452         if (con_is_floating(current->con)) {
1453             DLOG("floating move with %ld pixels\n", move_px);
1454             Rect newrect = current->con->parent->rect;
1455             if (strcmp(direction, "left") == 0) {
1456                 newrect.x -= move_px;
1457             } else if (strcmp(direction, "right") == 0) {
1458                 newrect.x += move_px;
1459             } else if (strcmp(direction, "up") == 0) {
1460                 newrect.y -= move_px;
1461             } else if (strcmp(direction, "down") == 0) {
1462                 newrect.y += move_px;
1463             }
1464             floating_reposition(current->con->parent, newrect);
1465         } else {
1466             tree_move(current->con, (strcmp(direction, "right") == 0 ? D_RIGHT : (strcmp(direction, "left") == 0 ? D_LEFT : (strcmp(direction, "up") == 0 ? D_UP : D_DOWN))));
1467             cmd_output->needs_tree_render = true;
1468         }
1469     }
1470
1471     /* the move command should not disturb focus */
1472     if (focused != initially_focused)
1473         con_focus(initially_focused);
1474
1475     // XXX: default reply for now, make this a better reply
1476     ysuccess(true);
1477 }
1478
1479 /*
1480  * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'.
1481  *
1482  */
1483 void cmd_layout(I3_CMD, const char *layout_str) {
1484     HANDLE_EMPTY_MATCH;
1485
1486     if (strcmp(layout_str, "stacking") == 0)
1487         layout_str = "stacked";
1488     layout_t layout;
1489     /* default is a special case which will be handled in con_set_layout(). */
1490     if (strcmp(layout_str, "default") == 0)
1491         layout = L_DEFAULT;
1492     else if (strcmp(layout_str, "stacked") == 0)
1493         layout = L_STACKED;
1494     else if (strcmp(layout_str, "tabbed") == 0)
1495         layout = L_TABBED;
1496     else if (strcmp(layout_str, "splitv") == 0)
1497         layout = L_SPLITV;
1498     else if (strcmp(layout_str, "splith") == 0)
1499         layout = L_SPLITH;
1500     else {
1501         ELOG("Unknown layout \"%s\", this is a mismatch between code and parser spec.\n", layout_str);
1502         return;
1503     }
1504
1505     DLOG("changing layout to %s (%d)\n", layout_str, layout);
1506
1507     owindow *current;
1508     TAILQ_FOREACH(current, &owindows, owindows) {
1509         if (con_is_docked(current->con)) {
1510             ELOG("cannot change layout of a docked container, skipping it.\n");
1511             continue;
1512         }
1513
1514         DLOG("matching: %p / %s\n", current->con, current->con->name);
1515         con_set_layout(current->con, layout);
1516     }
1517
1518     cmd_output->needs_tree_render = true;
1519     // XXX: default reply for now, make this a better reply
1520     ysuccess(true);
1521 }
1522
1523 /*
1524  * Implementation of 'layout toggle [all|split]'.
1525  *
1526  */
1527 void cmd_layout_toggle(I3_CMD, const char *toggle_mode) {
1528     owindow *current;
1529
1530     if (toggle_mode == NULL)
1531         toggle_mode = "default";
1532
1533     DLOG("toggling layout (mode = %s)\n", toggle_mode);
1534
1535     /* check if the match is empty, not if the result is empty */
1536     if (match_is_empty(current_match))
1537         con_toggle_layout(focused, toggle_mode);
1538     else {
1539         TAILQ_FOREACH(current, &owindows, owindows) {
1540             DLOG("matching: %p / %s\n", current->con, current->con->name);
1541             con_toggle_layout(current->con, toggle_mode);
1542         }
1543     }
1544
1545     cmd_output->needs_tree_render = true;
1546     // XXX: default reply for now, make this a better reply
1547     ysuccess(true);
1548 }
1549
1550 /*
1551  * Implementation of 'exit'.
1552  *
1553  */
1554 void cmd_exit(I3_CMD) {
1555     LOG("Exiting due to user command.\n");
1556 #ifdef I3_ASAN_ENABLED
1557     __lsan_do_leak_check();
1558 #endif
1559     ipc_shutdown();
1560     unlink(config.ipc_socket_path);
1561     xcb_disconnect(conn);
1562     exit(0);
1563
1564     /* unreached */
1565 }
1566
1567 /*
1568  * Implementation of 'reload'.
1569  *
1570  */
1571 void cmd_reload(I3_CMD) {
1572     LOG("reloading\n");
1573     kill_nagbar(&config_error_nagbar_pid, false);
1574     kill_nagbar(&command_error_nagbar_pid, false);
1575     load_configuration(conn, NULL, true);
1576     x_set_i3_atoms();
1577     /* Send an IPC event just in case the ws names have changed */
1578     ipc_send_workspace_event("reload", NULL, NULL);
1579     /* Send an update event for the barconfig just in case it has changed */
1580     update_barconfig();
1581
1582     // XXX: default reply for now, make this a better reply
1583     ysuccess(true);
1584 }
1585
1586 /*
1587  * Implementation of 'restart'.
1588  *
1589  */
1590 void cmd_restart(I3_CMD) {
1591     LOG("restarting i3\n");
1592     ipc_shutdown();
1593     unlink(config.ipc_socket_path);
1594     /* We need to call this manually since atexit handlers don’t get called
1595      * when exec()ing */
1596     purge_zerobyte_logfile();
1597     i3_restart(false);
1598
1599     // XXX: default reply for now, make this a better reply
1600     ysuccess(true);
1601 }
1602
1603 /*
1604  * Implementation of 'open'.
1605  *
1606  */
1607 void cmd_open(I3_CMD) {
1608     LOG("opening new container\n");
1609     Con *con = tree_open_con(NULL, NULL);
1610     con->layout = L_SPLITH;
1611     con_focus(con);
1612
1613     y(map_open);
1614     ystr("success");
1615     y(bool, true);
1616     ystr("id");
1617     y(integer, (uintptr_t)con);
1618     y(map_close);
1619
1620     cmd_output->needs_tree_render = true;
1621 }
1622
1623 /*
1624  * Implementation of 'focus output <output>'.
1625  *
1626  */
1627 void cmd_focus_output(I3_CMD, const char *name) {
1628     owindow *current;
1629
1630     DLOG("name = %s\n", name);
1631
1632     HANDLE_EMPTY_MATCH;
1633
1634     /* get the output */
1635     Output *current_output = NULL;
1636     Output *output;
1637
1638     TAILQ_FOREACH(current, &owindows, owindows)
1639     current_output = get_output_for_con(current->con);
1640     assert(current_output != NULL);
1641
1642     output = get_output_from_string(current_output, name);
1643
1644     if (!output) {
1645         LOG("No such output found.\n");
1646         ysuccess(false);
1647         return;
1648     }
1649
1650     /* get visible workspace on output */
1651     Con *ws = NULL;
1652     GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
1653     if (!ws) {
1654         ysuccess(false);
1655         return;
1656     }
1657
1658     workspace_show(ws);
1659
1660     cmd_output->needs_tree_render = true;
1661     // XXX: default reply for now, make this a better reply
1662     ysuccess(true);
1663 }
1664
1665 /*
1666  * Implementation of 'move [window|container] [to] [absolute] position <px> [px] <px> [px]
1667  *
1668  */
1669 void cmd_move_window_to_position(I3_CMD, const char *method, long x, long y) {
1670     bool has_error = false;
1671
1672     owindow *current;
1673     HANDLE_EMPTY_MATCH;
1674
1675     TAILQ_FOREACH(current, &owindows, owindows) {
1676         if (!con_is_floating(current->con)) {
1677             ELOG("Cannot change position. The window/container is not floating\n");
1678
1679             if (!has_error) {
1680                 yerror("Cannot change position of a window/container because it is not floating.");
1681                 has_error = true;
1682             }
1683
1684             continue;
1685         }
1686
1687         if (strcmp(method, "absolute") == 0) {
1688             current->con->parent->rect.x = x;
1689             current->con->parent->rect.y = y;
1690
1691             DLOG("moving to absolute position %ld %ld\n", x, y);
1692             floating_maybe_reassign_ws(current->con->parent);
1693             cmd_output->needs_tree_render = true;
1694         }
1695
1696         if (strcmp(method, "position") == 0) {
1697             Rect newrect = current->con->parent->rect;
1698
1699             DLOG("moving to position %ld %ld\n", x, y);
1700             newrect.x = x;
1701             newrect.y = y;
1702
1703             floating_reposition(current->con->parent, newrect);
1704         }
1705     }
1706
1707     // XXX: default reply for now, make this a better reply
1708     if (!has_error)
1709         ysuccess(true);
1710 }
1711
1712 /*
1713  * Implementation of 'move [window|container] [to] [absolute] position center
1714  *
1715  */
1716 void cmd_move_window_to_center(I3_CMD, const char *method) {
1717     bool has_error = false;
1718     HANDLE_EMPTY_MATCH;
1719
1720     owindow *current;
1721     TAILQ_FOREACH(current, &owindows, owindows) {
1722         Con *floating_con = con_inside_floating(current->con);
1723         if (floating_con == NULL) {
1724             ELOG("con %p / %s is not floating, cannot move it to the center.\n",
1725                  current->con, current->con->name);
1726
1727             if (!has_error) {
1728                 yerror("Cannot change position of a window/container because it is not floating.");
1729                 has_error = true;
1730             }
1731
1732             continue;
1733         }
1734
1735         if (strcmp(method, "absolute") == 0) {
1736             DLOG("moving to absolute center\n");
1737             floating_center(floating_con, croot->rect);
1738
1739             floating_maybe_reassign_ws(floating_con);
1740             cmd_output->needs_tree_render = true;
1741         }
1742
1743         if (strcmp(method, "position") == 0) {
1744             DLOG("moving to center\n");
1745             floating_center(floating_con, con_get_workspace(floating_con)->rect);
1746
1747             cmd_output->needs_tree_render = true;
1748         }
1749     }
1750
1751     // XXX: default reply for now, make this a better reply
1752     if (!has_error)
1753         ysuccess(true);
1754 }
1755
1756 /*
1757  * Implementation of 'move [window|container] [to] position mouse'
1758  *
1759  */
1760 void cmd_move_window_to_mouse(I3_CMD) {
1761     HANDLE_EMPTY_MATCH;
1762
1763     owindow *current;
1764     TAILQ_FOREACH(current, &owindows, owindows) {
1765         Con *floating_con = con_inside_floating(current->con);
1766         if (floating_con == NULL) {
1767             DLOG("con %p / %s is not floating, cannot move it to the mouse position.\n",
1768                  current->con, current->con->name);
1769             continue;
1770         }
1771
1772         DLOG("moving floating container %p / %s to cursor position\n", floating_con, floating_con->name);
1773         floating_move_to_pointer(floating_con);
1774     }
1775
1776     cmd_output->needs_tree_render = true;
1777     ysuccess(true);
1778 }
1779
1780 /*
1781  * Implementation of 'move scratchpad'.
1782  *
1783  */
1784 void cmd_move_scratchpad(I3_CMD) {
1785     DLOG("should move window to scratchpad\n");
1786     owindow *current;
1787
1788     HANDLE_EMPTY_MATCH;
1789
1790     TAILQ_FOREACH(current, &owindows, owindows) {
1791         DLOG("matching: %p / %s\n", current->con, current->con->name);
1792         scratchpad_move(current->con);
1793     }
1794
1795     cmd_output->needs_tree_render = true;
1796     // XXX: default reply for now, make this a better reply
1797     ysuccess(true);
1798 }
1799
1800 /*
1801  * Implementation of 'scratchpad show'.
1802  *
1803  */
1804 void cmd_scratchpad_show(I3_CMD) {
1805     DLOG("should show scratchpad window\n");
1806     owindow *current;
1807
1808     if (match_is_empty(current_match)) {
1809         scratchpad_show(NULL);
1810     } else {
1811         TAILQ_FOREACH(current, &owindows, owindows) {
1812             DLOG("matching: %p / %s\n", current->con, current->con->name);
1813             scratchpad_show(current->con);
1814         }
1815     }
1816
1817     cmd_output->needs_tree_render = true;
1818     // XXX: default reply for now, make this a better reply
1819     ysuccess(true);
1820 }
1821
1822 /*
1823  * Implementation of 'title_format <format>'
1824  *
1825  */
1826 void cmd_title_format(I3_CMD, const char *format) {
1827     DLOG("setting title_format to \"%s\"\n", format);
1828     HANDLE_EMPTY_MATCH;
1829
1830     owindow *current;
1831     TAILQ_FOREACH(current, &owindows, owindows) {
1832         DLOG("setting title_format for %p / %s\n", current->con, current->con->name);
1833         FREE(current->con->title_format);
1834
1835         /* If we only display the title without anything else, we can skip the parsing step,
1836          * so we remove the title format altogether. */
1837         if (strcasecmp(format, "%title") != 0) {
1838             current->con->title_format = sstrdup(format);
1839
1840             if (current->con->window != NULL) {
1841                 i3String *formatted_title = con_parse_title_format(current->con);
1842                 ewmh_update_visible_name(current->con->window->id, i3string_as_utf8(formatted_title));
1843                 I3STRING_FREE(formatted_title);
1844             }
1845         } else {
1846             if (current->con->window != NULL) {
1847                 /* We can remove _NET_WM_VISIBLE_NAME since we don't display a custom title. */
1848                 ewmh_update_visible_name(current->con->window->id, NULL);
1849             }
1850         }
1851
1852         if (current->con->window != NULL) {
1853             /* Make sure the window title is redrawn immediately. */
1854             current->con->window->name_x_changed = true;
1855         } else {
1856             /* For windowless containers we also need to force the redrawing. */
1857             FREE(current->con->deco_render_params);
1858         }
1859     }
1860
1861     cmd_output->needs_tree_render = true;
1862     ysuccess(true);
1863 }
1864
1865 /*
1866  * Implementation of 'rename workspace [<name>] to <name>'
1867  *
1868  */
1869 void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) {
1870     if (strncasecmp(new_name, "__", strlen("__")) == 0) {
1871         LOG("Cannot rename workspace to \"%s\": names starting with __ are i3-internal.\n", new_name);
1872         ysuccess(false);
1873         return;
1874     }
1875     if (old_name) {
1876         LOG("Renaming workspace \"%s\" to \"%s\"\n", old_name, new_name);
1877     } else {
1878         LOG("Renaming current workspace to \"%s\"\n", new_name);
1879     }
1880
1881     Con *output, *workspace = NULL;
1882     if (old_name) {
1883         TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
1884         GREP_FIRST(workspace, output_get_content(output),
1885                    !strcasecmp(child->name, old_name));
1886     } else {
1887         workspace = con_get_workspace(focused);
1888         old_name = workspace->name;
1889     }
1890
1891     if (!workspace) {
1892         yerror("Old workspace \"%s\" not found", old_name);
1893         return;
1894     }
1895
1896     Con *check_dest = NULL;
1897     TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
1898     GREP_FIRST(check_dest, output_get_content(output),
1899                !strcasecmp(child->name, new_name));
1900
1901     /* If check_dest == workspace, the user might be changing the case of the
1902      * workspace, or it might just be a no-op. */
1903     if (check_dest != NULL && check_dest != workspace) {
1904         yerror("New workspace \"%s\" already exists", new_name);
1905         return;
1906     }
1907
1908     /* Change the name and try to parse it as a number. */
1909     /* old_name might refer to workspace->name, so copy it before free()ing */
1910     char *old_name_copy = sstrdup(old_name);
1911     FREE(workspace->name);
1912     workspace->name = sstrdup(new_name);
1913
1914     workspace->num = ws_name_to_number(new_name);
1915     LOG("num = %d\n", workspace->num);
1916
1917     /* By re-attaching, the sort order will be correct afterwards. */
1918     Con *previously_focused = focused;
1919     Con *parent = workspace->parent;
1920     con_detach(workspace);
1921     con_attach(workspace, parent, false);
1922
1923     /* Move the workspace to the correct output if it has an assignment */
1924     struct Workspace_Assignment *assignment = NULL;
1925     TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
1926         if (assignment->output == NULL)
1927             continue;
1928         if (strcmp(assignment->name, workspace->name) != 0 && (!name_is_digits(assignment->name) || ws_name_to_number(assignment->name) != workspace->num)) {
1929             continue;
1930         }
1931
1932         workspace_move_to_output(workspace, assignment->output);
1933
1934         if (previously_focused)
1935             workspace_show(con_get_workspace(previously_focused));
1936
1937         break;
1938     }
1939
1940     /* Restore the previous focus since con_attach messes with the focus. */
1941     con_focus(previously_focused);
1942
1943     cmd_output->needs_tree_render = true;
1944     ysuccess(true);
1945
1946     ipc_send_workspace_event("rename", workspace, NULL);
1947     ewmh_update_desktop_names();
1948     ewmh_update_desktop_viewport();
1949     ewmh_update_current_desktop();
1950
1951     startup_sequence_rename_workspace(old_name_copy, new_name);
1952     free(old_name_copy);
1953 }
1954
1955 /*
1956  * Implementation of 'bar mode dock|hide|invisible|toggle [<bar_id>]'
1957  *
1958  */
1959 bool cmd_bar_mode(const char *bar_mode, const char *bar_id) {
1960     int mode = M_DOCK;
1961     bool toggle = false;
1962     if (strcmp(bar_mode, "dock") == 0)
1963         mode = M_DOCK;
1964     else if (strcmp(bar_mode, "hide") == 0)
1965         mode = M_HIDE;
1966     else if (strcmp(bar_mode, "invisible") == 0)
1967         mode = M_INVISIBLE;
1968     else if (strcmp(bar_mode, "toggle") == 0)
1969         toggle = true;
1970     else {
1971         ELOG("Unknown bar mode \"%s\", this is a mismatch between code and parser spec.\n", bar_mode);
1972         return false;
1973     }
1974
1975     bool changed_sth = false;
1976     Barconfig *current = NULL;
1977     TAILQ_FOREACH(current, &barconfigs, configs) {
1978         if (bar_id && strcmp(current->id, bar_id) != 0)
1979             continue;
1980
1981         if (toggle)
1982             mode = (current->mode + 1) % 2;
1983
1984         DLOG("Changing bar mode of bar_id '%s' to '%s (%d)'\n", current->id, bar_mode, mode);
1985         current->mode = mode;
1986         changed_sth = true;
1987
1988         if (bar_id)
1989             break;
1990     }
1991
1992     if (bar_id && !changed_sth) {
1993         DLOG("Changing bar mode of bar_id %s failed, bar_id not found.\n", bar_id);
1994         return false;
1995     }
1996
1997     return true;
1998 }
1999
2000 /*
2001  * Implementation of 'bar hidden_state hide|show|toggle [<bar_id>]'
2002  *
2003  */
2004 bool cmd_bar_hidden_state(const char *bar_hidden_state, const char *bar_id) {
2005     int hidden_state = S_SHOW;
2006     bool toggle = false;
2007     if (strcmp(bar_hidden_state, "hide") == 0)
2008         hidden_state = S_HIDE;
2009     else if (strcmp(bar_hidden_state, "show") == 0)
2010         hidden_state = S_SHOW;
2011     else if (strcmp(bar_hidden_state, "toggle") == 0)
2012         toggle = true;
2013     else {
2014         ELOG("Unknown bar state \"%s\", this is a mismatch between code and parser spec.\n", bar_hidden_state);
2015         return false;
2016     }
2017
2018     bool changed_sth = false;
2019     Barconfig *current = NULL;
2020     TAILQ_FOREACH(current, &barconfigs, configs) {
2021         if (bar_id && strcmp(current->id, bar_id) != 0)
2022             continue;
2023
2024         if (toggle)
2025             hidden_state = (current->hidden_state + 1) % 2;
2026
2027         DLOG("Changing bar hidden_state of bar_id '%s' to '%s (%d)'\n", current->id, bar_hidden_state, hidden_state);
2028         current->hidden_state = hidden_state;
2029         changed_sth = true;
2030
2031         if (bar_id)
2032             break;
2033     }
2034
2035     if (bar_id && !changed_sth) {
2036         DLOG("Changing bar hidden_state of bar_id %s failed, bar_id not found.\n", bar_id);
2037         return false;
2038     }
2039
2040     return true;
2041 }
2042
2043 /*
2044  * Implementation of 'bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>]'
2045  *
2046  */
2047 void cmd_bar(I3_CMD, const char *bar_type, const char *bar_value, const char *bar_id) {
2048     bool ret;
2049     if (strcmp(bar_type, "mode") == 0)
2050         ret = cmd_bar_mode(bar_value, bar_id);
2051     else if (strcmp(bar_type, "hidden_state") == 0)
2052         ret = cmd_bar_hidden_state(bar_value, bar_id);
2053     else {
2054         ELOG("Unknown bar option type \"%s\", this is a mismatch between code and parser spec.\n", bar_type);
2055         ret = false;
2056     }
2057
2058     ysuccess(ret);
2059     if (!ret)
2060         return;
2061
2062     update_barconfig();
2063 }
2064
2065 /*
2066  * Implementation of 'shmlog <size>|toggle|on|off'
2067  *
2068  */
2069 void cmd_shmlog(I3_CMD, const char *argument) {
2070     if (!strcmp(argument, "toggle"))
2071         /* Toggle shm log, if size is not 0. If it is 0, set it to default. */
2072         shmlog_size = shmlog_size ? -shmlog_size : default_shmlog_size;
2073     else if (!strcmp(argument, "on"))
2074         shmlog_size = default_shmlog_size;
2075     else if (!strcmp(argument, "off"))
2076         shmlog_size = 0;
2077     else {
2078         /* If shm logging now, restart logging with the new size. */
2079         if (shmlog_size > 0) {
2080             shmlog_size = 0;
2081             LOG("Restarting shm logging...\n");
2082             init_logging();
2083         }
2084         shmlog_size = atoi(argument);
2085         /* Make a weakly attempt at ensuring the argument is valid. */
2086         if (shmlog_size <= 0)
2087             shmlog_size = default_shmlog_size;
2088     }
2089     LOG("%s shm logging\n", shmlog_size > 0 ? "Enabling" : "Disabling");
2090     init_logging();
2091     update_shmlog_atom();
2092     // XXX: default reply for now, make this a better reply
2093     ysuccess(true);
2094 }
2095
2096 /*
2097  * Implementation of 'debuglog toggle|on|off'
2098  *
2099  */
2100 void cmd_debuglog(I3_CMD, const char *argument) {
2101     bool logging = get_debug_logging();
2102     if (!strcmp(argument, "toggle")) {
2103         LOG("%s debug logging\n", logging ? "Disabling" : "Enabling");
2104         set_debug_logging(!logging);
2105     } else if (!strcmp(argument, "on") && !logging) {
2106         LOG("Enabling debug logging\n");
2107         set_debug_logging(true);
2108     } else if (!strcmp(argument, "off") && logging) {
2109         LOG("Disabling debug logging\n");
2110         set_debug_logging(false);
2111     }
2112     // XXX: default reply for now, make this a better reply
2113     ysuccess(true);
2114 }