i3
commands.c
Go to the documentation of this file.
1 #undef I3__FILE__
2 #define I3__FILE__ "commands.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  * commands.c: all command functions (see commands_parser.c)
10  *
11  */
12 #include <float.h>
13 #include <stdarg.h>
14 
15 #include "all.h"
16 
17 // Macros to make the YAJL API a bit easier to use.
18 #define y(x, ...) yajl_gen_ ## x (cmd_output->json_gen, ##__VA_ARGS__)
19 #define ystr(str) yajl_gen_string(cmd_output->json_gen, (unsigned char*)str, strlen(str))
20 #define ysuccess(success) do { \
21  y(map_open); \
22  ystr("success"); \
23  y(bool, success); \
24  y(map_close); \
25 } while (0)
26 
32 #define HANDLE_EMPTY_MATCH do { \
33  if (match_is_empty(current_match)) { \
34  owindow *ow = smalloc(sizeof(owindow)); \
35  ow->con = focused; \
36  TAILQ_INIT(&owindows); \
37  TAILQ_INSERT_TAIL(&owindows, ow, owindows); \
38  } \
39 } while (0)
40 
41 static owindows_head owindows;
42 
43 /*
44  * Returns true if a is definitely greater than b (using the given epsilon)
45  *
46  */
47 static bool definitelyGreaterThan(float a, float b, float epsilon) {
48  return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
49 }
50 
51 /*
52  * Returns an 'output' corresponding to one of left/right/down/up or a specific
53  * output name.
54  *
55  */
56 static Output *get_output_from_string(Output *current_output, const char *output_str) {
57  Output *output;
58 
59  if (strcasecmp(output_str, "left") == 0) {
60  output = get_output_next(D_LEFT, current_output);
61  if (!output)
62  output = get_output_most(D_RIGHT, current_output);
63  } else if (strcasecmp(output_str, "right") == 0) {
64  output = get_output_next(D_RIGHT, current_output);
65  if (!output)
66  output = get_output_most(D_LEFT, current_output);
67  } else if (strcasecmp(output_str, "up") == 0) {
68  output = get_output_next(D_UP, current_output);
69  if (!output)
70  output = get_output_most(D_DOWN, current_output);
71  } else if (strcasecmp(output_str, "down") == 0) {
72  output = get_output_next(D_DOWN, current_output);
73  if (!output)
74  output = get_output_most(D_UP, current_output);
75  } else output = get_output_by_name(output_str);
76 
77  return output;
78 }
79 
80 /*
81  * Checks whether we switched to a new workspace and returns false in that case,
82  * signaling that further workspace switching should be done by the calling function
83  * If not, calls workspace_back_and_forth() if workspace_auto_back_and_forth is set
84  * and return true, signaling that no further workspace switching should occur in the calling function.
85  *
86  */
87 static bool maybe_back_and_forth(struct CommandResult *cmd_output, char *name) {
89 
90  /* If we switched to a different workspace, do nothing */
91  if (strcmp(ws->name, name) != 0)
92  return false;
93 
94  DLOG("This workspace is already focused.\n");
97  cmd_output->needs_tree_render = true;
98  }
99  return true;
100 }
101 
102 // This code is commented out because we might recycle it for popping up error
103 // messages on parser errors.
104 #if 0
105 static pid_t migration_pid = -1;
106 
107 /*
108  * Handler which will be called when we get a SIGCHLD for the nagbar, meaning
109  * it exited (or could not be started, depending on the exit code).
110  *
111  */
112 static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
113  ev_child_stop(EV_A_ watcher);
114  if (!WIFEXITED(watcher->rstatus)) {
115  fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
116  return;
117  }
118 
119  int exitcode = WEXITSTATUS(watcher->rstatus);
120  printf("i3-nagbar process exited with status %d\n", exitcode);
121  if (exitcode == 2) {
122  fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
123  }
124 
125  migration_pid = -1;
126 }
127 
128 /* We need ev >= 4 for the following code. Since it is not *that* important (it
129  * only makes sure that there are no i3-nagbar instances left behind) we still
130  * support old systems with libev 3. */
131 #if EV_VERSION_MAJOR >= 4
132 /*
133  * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal
134  * SIGKILL (9) to make sure there are no left-over i3-nagbar processes.
135  *
136  */
137 static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
138  if (migration_pid != -1) {
139  LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", migration_pid);
140  kill(migration_pid, SIGKILL);
141  }
142 }
143 #endif
144 
145 void cmd_MIGRATION_start_nagbar(void) {
146  if (migration_pid != -1) {
147  fprintf(stderr, "i3-nagbar already running.\n");
148  return;
149  }
150  fprintf(stderr, "Starting i3-nagbar, command parsing differs from expected output.\n");
151  ELOG("Please report this on IRC or in the bugtracker. Make sure to include the full debug level logfile:\n");
152  ELOG("i3-dump-log | gzip -9c > /tmp/i3.log.gz\n");
153  ELOG("FYI: Your i3 version is " I3_VERSION "\n");
154  migration_pid = fork();
155  if (migration_pid == -1) {
156  warn("Could not fork()");
157  return;
158  }
159 
160  /* child */
161  if (migration_pid == 0) {
162  char *pageraction;
163  sasprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename);
164  char *argv[] = {
165  NULL, /* will be replaced by the executable path */
166  "-t",
167  "error",
168  "-m",
169  "You found a parsing error. Please, please, please, report it!",
170  "-b",
171  "show errors",
172  pageraction,
173  NULL
174  };
175  exec_i3_utility("i3-nagbar", argv);
176  }
177 
178  /* parent */
179  /* install a child watcher */
180  ev_child *child = smalloc(sizeof(ev_child));
181  ev_child_init(child, &nagbar_exited, migration_pid, 0);
182  ev_child_start(main_loop, child);
183 
184 /* We need ev >= 4 for the following code. Since it is not *that* important (it
185  * only makes sure that there are no i3-nagbar instances left behind) we still
186  * support old systems with libev 3. */
187 #if EV_VERSION_MAJOR >= 4
188  /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is
189  * still running) */
190  ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup));
191  ev_cleanup_init(cleanup, nagbar_cleanup);
192  ev_cleanup_start(main_loop, cleanup);
193 #endif
194 }
195 
196 #endif
197 
198 /*******************************************************************************
199  * Criteria functions.
200  ******************************************************************************/
201 
202 /*
203  * Initializes the specified 'Match' data structure and the initial state of
204  * commands.c for matching target windows of a command.
205  *
206  */
208  Con *con;
209  owindow *ow;
210 
211  DLOG("Initializing criteria, current_match = %p\n", current_match);
213  while (!TAILQ_EMPTY(&owindows)) {
214  ow = TAILQ_FIRST(&owindows);
216  free(ow);
217  }
219  /* copy all_cons */
221  ow = smalloc(sizeof(owindow));
222  ow->con = con;
224  }
225 }
226 
227 /*
228  * A match specification just finished (the closing square bracket was found),
229  * so we filter the list of owindows.
230  *
231  */
233  owindow *next, *current;
234 
235  DLOG("match specification finished, matching...\n");
236  /* copy the old list head to iterate through it and start with a fresh
237  * list which will contain only matching windows */
238  struct owindows_head old = owindows;
240  for (next = TAILQ_FIRST(&old); next != TAILQ_END(&old);) {
241  /* make a copy of the next pointer and advance the pointer to the
242  * next element as we are going to invalidate the element’s
243  * next/prev pointers by calling TAILQ_INSERT_TAIL later */
244  current = next;
245  next = TAILQ_NEXT(next, owindows);
246 
247  DLOG("checking if con %p / %s matches\n", current->con, current->con->name);
248  if (current_match->con_id != NULL) {
249  if (current_match->con_id == current->con) {
250  DLOG("matches container!\n");
251  TAILQ_INSERT_TAIL(&owindows, current, owindows);
252  }
253  } else if (current_match->mark != NULL && current->con->mark != NULL &&
254  regex_matches(current_match->mark, current->con->mark)) {
255  DLOG("match by mark\n");
256  TAILQ_INSERT_TAIL(&owindows, current, owindows);
257  } else {
258  if (current->con->window == NULL)
259  continue;
260  if (match_matches_window(current_match, current->con->window)) {
261  DLOG("matches window!\n");
262  TAILQ_INSERT_TAIL(&owindows, current, owindows);
263  } else {
264  DLOG("doesnt match\n");
265  free(current);
266  }
267  }
268  }
269 
270  TAILQ_FOREACH(current, &owindows, owindows) {
271  DLOG("matching: %p / %s\n", current->con, current->con->name);
272  }
273 }
274 
275 /*
276  * Interprets a ctype=cvalue pair and adds it to the current match
277  * specification.
278  *
279  */
280 void cmd_criteria_add(I3_CMD, char *ctype, char *cvalue) {
281  DLOG("ctype=*%s*, cvalue=*%s*\n", ctype, cvalue);
282 
283  if (strcmp(ctype, "class") == 0) {
284  current_match->class = regex_new(cvalue);
285  return;
286  }
287 
288  if (strcmp(ctype, "instance") == 0) {
289  current_match->instance = regex_new(cvalue);
290  return;
291  }
292 
293  if (strcmp(ctype, "window_role") == 0) {
294  current_match->role = regex_new(cvalue);
295  return;
296  }
297 
298  if (strcmp(ctype, "con_id") == 0) {
299  char *end;
300  long parsed = strtol(cvalue, &end, 10);
301  if (parsed == LONG_MIN ||
302  parsed == LONG_MAX ||
303  parsed < 0 ||
304  (end && *end != '\0')) {
305  ELOG("Could not parse con id \"%s\"\n", cvalue);
306  } else {
307  current_match->con_id = (Con*)parsed;
308  printf("id as int = %p\n", current_match->con_id);
309  }
310  return;
311  }
312 
313  if (strcmp(ctype, "id") == 0) {
314  char *end;
315  long parsed = strtol(cvalue, &end, 10);
316  if (parsed == LONG_MIN ||
317  parsed == LONG_MAX ||
318  parsed < 0 ||
319  (end && *end != '\0')) {
320  ELOG("Could not parse window id \"%s\"\n", cvalue);
321  } else {
322  current_match->id = parsed;
323  printf("window id as int = %d\n", current_match->id);
324  }
325  return;
326  }
327 
328  if (strcmp(ctype, "con_mark") == 0) {
329  current_match->mark = regex_new(cvalue);
330  return;
331  }
332 
333  if (strcmp(ctype, "title") == 0) {
334  current_match->title = regex_new(cvalue);
335  return;
336  }
337 
338  if (strcmp(ctype, "urgent") == 0) {
339  if (strcasecmp(cvalue, "latest") == 0 ||
340  strcasecmp(cvalue, "newest") == 0 ||
341  strcasecmp(cvalue, "recent") == 0 ||
342  strcasecmp(cvalue, "last") == 0) {
343  current_match->urgent = U_LATEST;
344  } else if (strcasecmp(cvalue, "oldest") == 0 ||
345  strcasecmp(cvalue, "first") == 0) {
346  current_match->urgent = U_OLDEST;
347  }
348  return;
349  }
350 
351  ELOG("Unknown criterion: %s\n", ctype);
352 }
353 
354 /*
355  * Implementation of 'move [window|container] [to] workspace
356  * next|prev|next_on_output|prev_on_output|current'.
357  *
358  */
359 void cmd_move_con_to_workspace(I3_CMD, char *which) {
360  owindow *current;
361 
362  DLOG("which=%s\n", which);
363 
364  /* We have nothing to move:
365  * when criteria was specified but didn't match any window or
366  * when criteria wasn't specified and we don't have any window focused. */
368  (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
369  ysuccess(false);
370  return;
371  }
372 
374 
375  /* get the workspace */
376  Con *ws;
377  if (strcmp(which, "next") == 0)
378  ws = workspace_next();
379  else if (strcmp(which, "prev") == 0)
380  ws = workspace_prev();
381  else if (strcmp(which, "next_on_output") == 0)
383  else if (strcmp(which, "prev_on_output") == 0)
385  else if (strcmp(which, "current") == 0)
387  else {
388  ELOG("BUG: called with which=%s\n", which);
389  ysuccess(false);
390  return;
391  }
392 
393  TAILQ_FOREACH(current, &owindows, owindows) {
394  DLOG("matching: %p / %s\n", current->con, current->con->name);
395  con_move_to_workspace(current->con, ws, true, false);
396  }
397 
398  cmd_output->needs_tree_render = true;
399  // XXX: default reply for now, make this a better reply
400  ysuccess(true);
401 }
402 
403 /*
404  * Implementation of 'move [window|container] [to] workspace <name>'.
405  *
406  */
408  if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) {
409  LOG("You cannot switch to the i3 internal workspaces.\n");
410  ysuccess(false);
411  return;
412  }
413 
414  owindow *current;
415 
416  /* We have nothing to move:
417  * when criteria was specified but didn't match any window or
418  * when criteria wasn't specified and we don't have any window focused. */
420  ELOG("No windows match your criteria, cannot move.\n");
421  ysuccess(false);
422  return;
423  }
424 
425  if (match_is_empty(current_match) && focused->type == CT_WORKSPACE) {
426  ELOG("No window to move, you have focused a workspace.\n");
427  ysuccess(false);
428  return;
429  }
430 
431  LOG("should move window to workspace %s\n", name);
432  /* get the workspace */
433  Con *ws = workspace_get(name, NULL);
434 
436 
437  TAILQ_FOREACH(current, &owindows, owindows) {
438  DLOG("matching: %p / %s\n", current->con, current->con->name);
439  con_move_to_workspace(current->con, ws, true, false);
440  }
441 
442  cmd_output->needs_tree_render = true;
443  // XXX: default reply for now, make this a better reply
444  ysuccess(true);
445 }
446 
447 /*
448  * Implementation of 'move [window|container] [to] workspace number <number>'.
449  *
450  */
452  owindow *current;
453 
454  /* We have nothing to move:
455  * when criteria was specified but didn't match any window or
456  * when criteria wasn't specified and we don't have any window focused. */
458  (match_is_empty(current_match) && focused->type == CT_WORKSPACE)) {
459  ysuccess(false);
460  return;
461  }
462 
463  LOG("should move window to workspace %s\n", which);
464  /* get the workspace */
465  Con *output, *workspace = NULL;
466 
467  char *endptr = NULL;
468  long parsed_num = strtol(which, &endptr, 10);
469  if (parsed_num == LONG_MIN ||
470  parsed_num == LONG_MAX ||
471  parsed_num < 0 ||
472  *endptr != '\0') {
473  LOG("Could not parse \"%s\" as a number.\n", which);
474  y(map_open);
475  ystr("success");
476  y(bool, false);
477  ystr("error");
478  // TODO: better error message
479  ystr("Could not parse number");
480  y(map_close);
481  return;
482  }
483 
484  TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
485  GREP_FIRST(workspace, output_get_content(output),
486  child->num == parsed_num);
487 
488  if (!workspace) {
489  workspace = workspace_get(which, NULL);
490  }
491 
493 
494  TAILQ_FOREACH(current, &owindows, owindows) {
495  DLOG("matching: %p / %s\n", current->con, current->con->name);
496  con_move_to_workspace(current->con, workspace, true, false);
497  }
498 
499  cmd_output->needs_tree_render = true;
500  // XXX: default reply for now, make this a better reply
501  ysuccess(true);
502 }
503 
504 static void cmd_resize_floating(I3_CMD, char *way, char *direction, Con *floating_con, int px) {
505  LOG("floating resize\n");
506  if (strcmp(direction, "up") == 0) {
507  floating_con->rect.y -= px;
508  floating_con->rect.height += px;
509  } else if (strcmp(direction, "down") == 0 || strcmp(direction, "height") == 0) {
510  floating_con->rect.height += px;
511  } else if (strcmp(direction, "left") == 0) {
512  floating_con->rect.x -= px;
513  floating_con->rect.width += px;
514  } else {
515  floating_con->rect.width += px;
516  }
517 }
518 
519 static bool cmd_resize_tiling_direction(I3_CMD, char *way, char *direction, int ppt) {
520  LOG("tiling resize\n");
521  /* get the appropriate current container (skip stacked/tabbed cons) */
522  Con *current = focused;
523  Con *other = NULL;
524  double percentage = 0;
525  while (current->parent->layout == L_STACKED ||
526  current->parent->layout == L_TABBED)
527  current = current->parent;
528 
529  /* Then further go up until we find one with the matching orientation. */
530  orientation_t search_orientation =
531  (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0 ? HORIZ : VERT);
532 
533  do {
534  if (con_orientation(current->parent) != search_orientation) {
535  current = current->parent;
536  continue;
537  }
538 
539  /* get the default percentage */
540  int children = con_num_children(current->parent);
541  LOG("ins. %d children\n", children);
542  percentage = 1.0 / children;
543  LOG("default percentage = %f\n", percentage);
544 
545  orientation_t orientation = con_orientation(current->parent);
546 
547  if ((orientation == HORIZ &&
548  (strcmp(direction, "up") == 0 || strcmp(direction, "down") == 0)) ||
549  (orientation == VERT &&
550  (strcmp(direction, "left") == 0 || strcmp(direction, "right") == 0))) {
551  LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
552  (orientation == HORIZ ? "horizontal" : "vertical"));
553  ysuccess(false);
554  return false;
555  }
556 
557  if (strcmp(direction, "up") == 0 || strcmp(direction, "left") == 0) {
558  other = TAILQ_PREV(current, nodes_head, nodes);
559  } else {
560  other = TAILQ_NEXT(current, nodes);
561  }
562  if (other == TAILQ_END(workspaces)) {
563  LOG("No other container in this direction found, trying to look further up in the tree...\n");
564  current = current->parent;
565  continue;
566  }
567  break;
568  } while (current->type != CT_WORKSPACE &&
569  current->type != CT_FLOATING_CON);
570 
571  if (other == NULL) {
572  LOG("No other container in this direction found, trying to look further up in the tree...\n");
573  ysuccess(false);
574  return false;
575  }
576 
577  LOG("other->percent = %f\n", other->percent);
578  LOG("current->percent before = %f\n", current->percent);
579  if (current->percent == 0.0)
580  current->percent = percentage;
581  if (other->percent == 0.0)
582  other->percent = percentage;
583  double new_current_percent = current->percent + ((double)ppt / 100.0);
584  double new_other_percent = other->percent - ((double)ppt / 100.0);
585  LOG("new_current_percent = %f\n", new_current_percent);
586  LOG("new_other_percent = %f\n", new_other_percent);
587  /* Ensure that the new percentages are positive and greater than
588  * 0.05 to have a reasonable minimum size. */
589  if (definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON) &&
590  definitelyGreaterThan(new_other_percent, 0.05, DBL_EPSILON)) {
591  current->percent += ((double)ppt / 100.0);
592  other->percent -= ((double)ppt / 100.0);
593  LOG("current->percent after = %f\n", current->percent);
594  LOG("other->percent after = %f\n", other->percent);
595  } else {
596  LOG("Not resizing, already at minimum size\n");
597  }
598 
599  return true;
600 }
601 
602 static bool cmd_resize_tiling_width_height(I3_CMD, char *way, char *direction, int ppt) {
603  LOG("width/height resize\n");
604  /* get the appropriate current container (skip stacked/tabbed cons) */
605  Con *current = focused;
606  while (current->parent->layout == L_STACKED ||
607  current->parent->layout == L_TABBED)
608  current = current->parent;
609 
610  /* Then further go up until we find one with the matching orientation. */
611  orientation_t search_orientation =
612  (strcmp(direction, "width") == 0 ? HORIZ : VERT);
613 
614  while (current->type != CT_WORKSPACE &&
615  current->type != CT_FLOATING_CON &&
616  con_orientation(current->parent) != search_orientation)
617  current = current->parent;
618 
619  /* get the default percentage */
620  int children = con_num_children(current->parent);
621  LOG("ins. %d children\n", children);
622  double percentage = 1.0 / children;
623  LOG("default percentage = %f\n", percentage);
624 
625  orientation_t orientation = con_orientation(current->parent);
626 
627  if ((orientation == HORIZ &&
628  strcmp(direction, "height") == 0) ||
629  (orientation == VERT &&
630  strcmp(direction, "width") == 0)) {
631  LOG("You cannot resize in that direction. Your focus is in a %s split container currently.\n",
632  (orientation == HORIZ ? "horizontal" : "vertical"));
633  ysuccess(false);
634  return false;
635  }
636 
637  if (children == 1) {
638  LOG("This is the only container, cannot resize.\n");
639  ysuccess(false);
640  return false;
641  }
642 
643  /* Ensure all the other children have a percentage set. */
644  Con *child;
645  TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
646  LOG("child->percent = %f (child %p)\n", child->percent, child);
647  if (child->percent == 0.0)
648  child->percent = percentage;
649  }
650 
651  double new_current_percent = current->percent + ((double)ppt / 100.0);
652  double subtract_percent = ((double)ppt / 100.0) / (children - 1);
653  LOG("new_current_percent = %f\n", new_current_percent);
654  LOG("subtract_percent = %f\n", subtract_percent);
655  /* Ensure that the new percentages are positive and greater than
656  * 0.05 to have a reasonable minimum size. */
657  TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
658  if (child == current)
659  continue;
660  if (!definitelyGreaterThan(child->percent - subtract_percent, 0.05, DBL_EPSILON)) {
661  LOG("Not resizing, already at minimum size (child %p would end up with a size of %.f\n", child, child->percent - subtract_percent);
662  ysuccess(false);
663  return false;
664  }
665  }
666  if (!definitelyGreaterThan(new_current_percent, 0.05, DBL_EPSILON)) {
667  LOG("Not resizing, already at minimum size\n");
668  ysuccess(false);
669  return false;
670  }
671 
672  current->percent += ((double)ppt / 100.0);
673  LOG("current->percent after = %f\n", current->percent);
674 
675  TAILQ_FOREACH(child, &(current->parent->nodes_head), nodes) {
676  if (child == current)
677  continue;
678  child->percent -= subtract_percent;
679  LOG("child->percent after (%p) = %f\n", child, child->percent);
680  }
681 
682  return true;
683 }
684 
685 /*
686  * Implementation of 'resize grow|shrink <direction> [<px> px] [or <ppt> ppt]'.
687  *
688  */
689 void cmd_resize(I3_CMD, char *way, char *direction, char *resize_px, char *resize_ppt) {
690  /* resize <grow|shrink> <direction> [<px> px] [or <ppt> ppt] */
691  DLOG("resizing in way %s, direction %s, px %s or ppt %s\n", way, direction, resize_px, resize_ppt);
692  // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking
693  int px = atoi(resize_px);
694  int ppt = atoi(resize_ppt);
695  if (strcmp(way, "shrink") == 0) {
696  px *= -1;
697  ppt *= -1;
698  }
699 
700  Con *floating_con;
701  if ((floating_con = con_inside_floating(focused))) {
702  cmd_resize_floating(current_match, cmd_output, way, direction, floating_con, px);
703  } else {
704  if (strcmp(direction, "width") == 0 ||
705  strcmp(direction, "height") == 0) {
706  if (!cmd_resize_tiling_width_height(current_match, cmd_output, way, direction, ppt))
707  return;
708  } else {
709  if (!cmd_resize_tiling_direction(current_match, cmd_output, way, direction, ppt))
710  return;
711  }
712  }
713 
714  cmd_output->needs_tree_render = true;
715  // XXX: default reply for now, make this a better reply
716  ysuccess(true);
717 }
718 
719 /*
720  * Implementation of 'border normal|none|1pixel|toggle'.
721  *
722  */
723 void cmd_border(I3_CMD, char *border_style_str) {
724  DLOG("border style should be changed to %s\n", border_style_str);
725  owindow *current;
726 
728 
729  TAILQ_FOREACH(current, &owindows, owindows) {
730  DLOG("matching: %p / %s\n", current->con, current->con->name);
731  int border_style = current->con->border_style;
732  if (strcmp(border_style_str, "toggle") == 0) {
733  border_style++;
734  border_style %= 3;
735  } else {
736  if (strcmp(border_style_str, "normal") == 0)
737  border_style = BS_NORMAL;
738  else if (strcmp(border_style_str, "none") == 0)
739  border_style = BS_NONE;
740  else if (strcmp(border_style_str, "1pixel") == 0)
741  border_style = BS_1PIXEL;
742  else {
743  ELOG("BUG: called with border_style=%s\n", border_style_str);
744  ysuccess(false);
745  return;
746  }
747  }
748  con_set_border_style(current->con, border_style);
749  }
750 
751  cmd_output->needs_tree_render = true;
752  // XXX: default reply for now, make this a better reply
753  ysuccess(true);
754 }
755 
756 /*
757  * Implementation of 'nop <comment>'.
758  *
759  */
760 void cmd_nop(I3_CMD, char *comment) {
761  LOG("-------------------------------------------------\n");
762  LOG(" NOP: %s\n", comment);
763  LOG("-------------------------------------------------\n");
764 }
765 
766 /*
767  * Implementation of 'append_layout <path>'.
768  *
769  */
770 void cmd_append_layout(I3_CMD, char *path) {
771  LOG("Appending layout \"%s\"\n", path);
772  tree_append_json(path);
773 
774  cmd_output->needs_tree_render = true;
775  // XXX: default reply for now, make this a better reply
776  ysuccess(true);
777 }
778 
779 /*
780  * Implementation of 'workspace next|prev|next_on_output|prev_on_output'.
781  *
782  */
783 void cmd_workspace(I3_CMD, char *which) {
784  Con *ws;
785 
786  DLOG("which=%s\n", which);
787 
788  if (strcmp(which, "next") == 0)
789  ws = workspace_next();
790  else if (strcmp(which, "prev") == 0)
791  ws = workspace_prev();
792  else if (strcmp(which, "next_on_output") == 0)
794  else if (strcmp(which, "prev_on_output") == 0)
796  else {
797  ELOG("BUG: called with which=%s\n", which);
798  ysuccess(false);
799  return;
800  }
801 
802  workspace_show(ws);
803 
804  cmd_output->needs_tree_render = true;
805  // XXX: default reply for now, make this a better reply
806  ysuccess(true);
807 }
808 
809 /*
810  * Implementation of 'workspace number <number>'
811  *
812  */
813 void cmd_workspace_number(I3_CMD, char *which) {
814  Con *output, *workspace = NULL;
815 
816  char *endptr = NULL;
817  long parsed_num = strtol(which, &endptr, 10);
818  if (parsed_num == LONG_MIN ||
819  parsed_num == LONG_MAX ||
820  parsed_num < 0 ||
821  *endptr != '\0') {
822  LOG("Could not parse \"%s\" as a number.\n", which);
823  y(map_open);
824  ystr("success");
825  y(bool, false);
826  ystr("error");
827  // TODO: better error message
828  ystr("Could not parse number");
829  y(map_close);
830 
831  return;
832  }
833 
834  TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
835  GREP_FIRST(workspace, output_get_content(output),
836  child->num == parsed_num);
837 
838  if (!workspace) {
839  LOG("There is no workspace with number %ld, creating a new one.\n", parsed_num);
840  ysuccess(true);
841  /* terminate the which string after the endposition of the number */
842  *endptr = '\0';
843  workspace_show_by_name(which);
844  cmd_output->needs_tree_render = true;
845  return;
846  }
847  if (maybe_back_and_forth(cmd_output, workspace->name))
848  return;
849  workspace_show(workspace);
850 
851  cmd_output->needs_tree_render = true;
852  // XXX: default reply for now, make this a better reply
853  ysuccess(true);
854 }
855 
856 /*
857  * Implementation of 'workspace back_and_forth'.
858  *
859  */
862 
863  cmd_output->needs_tree_render = true;
864  // XXX: default reply for now, make this a better reply
865  ysuccess(true);
866 }
867 
868 /*
869  * Implementation of 'workspace <name>'
870  *
871  */
872 void cmd_workspace_name(I3_CMD, char *name) {
873  if (strncasecmp(name, "__i3_", strlen("__i3_")) == 0) {
874  LOG("You cannot switch to the i3 internal workspaces.\n");
875  ysuccess(false);
876  return;
877  }
878 
879  DLOG("should switch to workspace %s\n", name);
880  if (maybe_back_and_forth(cmd_output, name))
881  return;
883 
884  cmd_output->needs_tree_render = true;
885  // XXX: default reply for now, make this a better reply
886  ysuccess(true);
887 }
888 
889 /*
890  * Implementation of 'mark <mark>'
891  *
892  */
893 void cmd_mark(I3_CMD, char *mark) {
894  DLOG("Clearing all windows which have that mark first\n");
895 
896  Con *con;
898  if (con->mark && strcmp(con->mark, mark) == 0)
899  FREE(con->mark);
900  }
901 
902  DLOG("marking window with str %s\n", mark);
903  owindow *current;
904 
906 
907  TAILQ_FOREACH(current, &owindows, owindows) {
908  DLOG("matching: %p / %s\n", current->con, current->con->name);
909  current->con->mark = sstrdup(mark);
910  }
911 
912  cmd_output->needs_tree_render = true;
913  // XXX: default reply for now, make this a better reply
914  ysuccess(true);
915 }
916 
917 /*
918  * Implementation of 'mode <string>'.
919  *
920  */
921 void cmd_mode(I3_CMD, char *mode) {
922  DLOG("mode=%s\n", mode);
923  switch_mode(mode);
924 
925  // XXX: default reply for now, make this a better reply
926  ysuccess(true);
927 }
928 
929 /*
930  * Implementation of 'move [window|container] [to] output <str>'.
931  *
932  */
933 void cmd_move_con_to_output(I3_CMD, char *name) {
934  owindow *current;
935 
936  DLOG("should move window to output %s\n", name);
937 
939 
940  /* get the output */
941  Output *current_output = NULL;
942  Output *output;
943 
944  // TODO: fix the handling of criteria
945  TAILQ_FOREACH(current, &owindows, owindows)
946  current_output = get_output_containing(current->con->rect.x, current->con->rect.y);
947 
948  assert(current_output != NULL);
949 
950  // TODO: clean this up with commands.spec as soon as we switched away from the lex/yacc command parser
951  if (strcasecmp(name, "up") == 0)
952  output = get_output_next(D_UP, current_output);
953  else if (strcasecmp(name, "down") == 0)
954  output = get_output_next(D_DOWN, current_output);
955  else if (strcasecmp(name, "left") == 0)
956  output = get_output_next(D_LEFT, current_output);
957  else if (strcasecmp(name, "right") == 0)
958  output = get_output_next(D_RIGHT, current_output);
959  else
960  output = get_output_by_name(name);
961 
962  if (!output) {
963  LOG("No such output found.\n");
964  ysuccess(false);
965  return;
966  }
967 
968  /* get visible workspace on output */
969  Con *ws = NULL;
971  if (!ws) {
972  ysuccess(false);
973  return;
974  }
975 
976  TAILQ_FOREACH(current, &owindows, owindows) {
977  DLOG("matching: %p / %s\n", current->con, current->con->name);
978  con_move_to_workspace(current->con, ws, true, false);
979  }
980 
981  cmd_output->needs_tree_render = true;
982  // XXX: default reply for now, make this a better reply
983  ysuccess(true);
984 }
985 
986 /*
987  * Implementation of 'floating enable|disable|toggle'
988  *
989  */
990 void cmd_floating(I3_CMD, char *floating_mode) {
991  owindow *current;
992 
993  DLOG("floating_mode=%s\n", floating_mode);
994 
996 
997  TAILQ_FOREACH(current, &owindows, owindows) {
998  DLOG("matching: %p / %s\n", current->con, current->con->name);
999  if (strcmp(floating_mode, "toggle") == 0) {
1000  DLOG("should toggle mode\n");
1001  toggle_floating_mode(current->con, false);
1002  } else {
1003  DLOG("should switch mode to %s\n", floating_mode);
1004  if (strcmp(floating_mode, "enable") == 0) {
1005  floating_enable(current->con, false);
1006  } else {
1007  floating_disable(current->con, false);
1008  }
1009  }
1010  }
1011 
1012  cmd_output->needs_tree_render = true;
1013  // XXX: default reply for now, make this a better reply
1014  ysuccess(true);
1015 }
1016 
1017 /*
1018  * Implementation of 'move workspace to [output] <str>'.
1019  *
1020  */
1022  DLOG("should move workspace to output %s\n", name);
1023 
1025 
1026  owindow *current;
1027  TAILQ_FOREACH(current, &owindows, owindows) {
1028  Output *current_output = get_output_containing(current->con->rect.x,
1029  current->con->rect.y);
1030  if (!current_output) {
1031  ELOG("Cannot get current output. This is a bug in i3.\n");
1032  ysuccess(false);
1033  return;
1034  }
1035  Output *output = get_output_from_string(current_output, name);
1036  if (!output) {
1037  ELOG("Could not get output from string \"%s\"\n", name);
1038  ysuccess(false);
1039  return;
1040  }
1041 
1042  Con *content = output_get_content(output->con);
1043  LOG("got output %p with content %p\n", output, content);
1044 
1045  Con *ws = con_get_workspace(current->con);
1046  LOG("should move workspace %p / %s\n", ws, ws->name);
1047 
1048  if (con_num_children(ws->parent) == 1) {
1049  LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name);
1050 
1051  /* check if we can find a workspace assigned to this output */
1052  bool used_assignment = false;
1053  struct Workspace_Assignment *assignment;
1055  if (strcmp(assignment->output, current_output->name) != 0)
1056  continue;
1057 
1058  /* check if this workspace is already attached to the tree */
1059  Con *workspace = NULL, *out;
1060  TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
1061  GREP_FIRST(workspace, output_get_content(out),
1062  !strcasecmp(child->name, assignment->name));
1063  if (workspace != NULL)
1064  continue;
1065 
1066  /* so create the workspace referenced to by this assignment */
1067  LOG("Creating workspace from assignment %s.\n", assignment->name);
1068  workspace_get(assignment->name, NULL);
1069  used_assignment = true;
1070  break;
1071  }
1072 
1073  /* if we couldn't create the workspace using an assignment, create
1074  * it on the output */
1075  if (!used_assignment)
1076  create_workspace_on_output(current_output, ws->parent);
1077 
1078  /* notify the IPC listeners */
1079  ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
1080  }
1081 
1082  /* detach from the old output and attach to the new output */
1083  bool workspace_was_visible = workspace_is_visible(ws);
1084  Con *old_content = ws->parent;
1085  con_detach(ws);
1086  if (workspace_was_visible) {
1087  /* The workspace which we just detached was visible, so focus
1088  * the next one in the focus-stack. */
1089  Con *focus_ws = TAILQ_FIRST(&(old_content->focus_head));
1090  LOG("workspace was visible, focusing %p / %s now\n", focus_ws, focus_ws->name);
1091  workspace_show(focus_ws);
1092  }
1093  con_attach(ws, content, false);
1094 
1095  /* fix the coordinates of the floating containers */
1096  Con *floating_con;
1097  TAILQ_FOREACH(floating_con, &(ws->floating_head), floating_windows)
1098  floating_fix_coordinates(floating_con, &(old_content->rect), &(content->rect));
1099 
1100  ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"move\"}");
1101  if (workspace_was_visible) {
1102  /* Focus the moved workspace on the destination output. */
1103  workspace_show(ws);
1104  }
1105  }
1106 
1107  cmd_output->needs_tree_render = true;
1108  // XXX: default reply for now, make this a better reply
1109  ysuccess(true);
1110 }
1111 
1112 /*
1113  * Implementation of 'split v|h|vertical|horizontal'.
1114  *
1115  */
1116 void cmd_split(I3_CMD, char *direction) {
1117  owindow *current;
1118  /* TODO: use matches */
1119  LOG("splitting in direction %c\n", direction[0]);
1121  tree_split(focused, (direction[0] == 'v' ? VERT : HORIZ));
1122  else {
1123  TAILQ_FOREACH(current, &owindows, owindows) {
1124  DLOG("matching: %p / %s\n", current->con, current->con->name);
1125  tree_split(current->con, (direction[0] == 'v' ? VERT : HORIZ));
1126  }
1127  }
1128 
1129  cmd_output->needs_tree_render = true;
1130  // XXX: default reply for now, make this a better reply
1131  ysuccess(true);
1132 }
1133 
1134 /*
1135  * Implementaiton of 'kill [window|client]'.
1136  *
1137  */
1138 void cmd_kill(I3_CMD, char *kill_mode_str) {
1139  if (kill_mode_str == NULL)
1140  kill_mode_str = "window";
1141  owindow *current;
1142 
1143  DLOG("kill_mode=%s\n", kill_mode_str);
1144 
1145  int kill_mode;
1146  if (strcmp(kill_mode_str, "window") == 0)
1147  kill_mode = KILL_WINDOW;
1148  else if (strcmp(kill_mode_str, "client") == 0)
1149  kill_mode = KILL_CLIENT;
1150  else {
1151  ELOG("BUG: called with kill_mode=%s\n", kill_mode_str);
1152  ysuccess(false);
1153  return;
1154  }
1155 
1156  /* check if the match is empty, not if the result is empty */
1158  tree_close_con(kill_mode);
1159  else {
1160  TAILQ_FOREACH(current, &owindows, owindows) {
1161  DLOG("matching: %p / %s\n", current->con, current->con->name);
1162  tree_close(current->con, kill_mode, false, false);
1163  }
1164  }
1165 
1166  cmd_output->needs_tree_render = true;
1167  // XXX: default reply for now, make this a better reply
1168  ysuccess(true);
1169 }
1170 
1171 /*
1172  * Implementation of 'exec [--no-startup-id] <command>'.
1173  *
1174  */
1175 void cmd_exec(I3_CMD, char *nosn, char *command) {
1176  bool no_startup_id = (nosn != NULL);
1177 
1178  DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id);
1179  start_application(command, no_startup_id);
1180 
1181  // XXX: default reply for now, make this a better reply
1182  ysuccess(true);
1183 }
1184 
1185 /*
1186  * Implementation of 'focus left|right|up|down'.
1187  *
1188  */
1189 void cmd_focus_direction(I3_CMD, char *direction) {
1190  if (focused &&
1191  focused->type != CT_WORKSPACE &&
1192  focused->fullscreen_mode != CF_NONE) {
1193  LOG("Cannot change focus while in fullscreen mode.\n");
1194  ysuccess(false);
1195  return;
1196  }
1197 
1198  DLOG("direction = *%s*\n", direction);
1199 
1200  if (strcmp(direction, "left") == 0)
1201  tree_next('p', HORIZ);
1202  else if (strcmp(direction, "right") == 0)
1203  tree_next('n', HORIZ);
1204  else if (strcmp(direction, "up") == 0)
1205  tree_next('p', VERT);
1206  else if (strcmp(direction, "down") == 0)
1207  tree_next('n', VERT);
1208  else {
1209  ELOG("Invalid focus direction (%s)\n", direction);
1210  ysuccess(false);
1211  return;
1212  }
1213 
1214  cmd_output->needs_tree_render = true;
1215  // XXX: default reply for now, make this a better reply
1216  ysuccess(true);
1217 }
1218 
1219 /*
1220  * Implementation of 'focus tiling|floating|mode_toggle'.
1221  *
1222  */
1223 void cmd_focus_window_mode(I3_CMD, char *window_mode) {
1224  if (focused &&
1225  focused->type != CT_WORKSPACE &&
1226  focused->fullscreen_mode != CF_NONE) {
1227  LOG("Cannot change focus while in fullscreen mode.\n");
1228  ysuccess(false);
1229  return;
1230  }
1231 
1232  DLOG("window_mode = %s\n", window_mode);
1233 
1234  Con *ws = con_get_workspace(focused);
1235  Con *current;
1236  if (ws != NULL) {
1237  if (strcmp(window_mode, "mode_toggle") == 0) {
1238  current = TAILQ_FIRST(&(ws->focus_head));
1239  if (current != NULL && current->type == CT_FLOATING_CON)
1240  window_mode = "tiling";
1241  else window_mode = "floating";
1242  }
1243  TAILQ_FOREACH(current, &(ws->focus_head), focused) {
1244  if ((strcmp(window_mode, "floating") == 0 && current->type != CT_FLOATING_CON) ||
1245  (strcmp(window_mode, "tiling") == 0 && current->type == CT_FLOATING_CON))
1246  continue;
1247 
1248  con_focus(con_descend_focused(current));
1249  break;
1250  }
1251  }
1252 
1253  cmd_output->needs_tree_render = true;
1254  // XXX: default reply for now, make this a better reply
1255  ysuccess(true);
1256 }
1257 
1258 /*
1259  * Implementation of 'focus parent|child'.
1260  *
1261  */
1262 void cmd_focus_level(I3_CMD, char *level) {
1263  DLOG("level = %s\n", level);
1264  bool success = false;
1265 
1266  /* Focusing the parent can only be allowed if the newly
1267  * focused container won't escape the fullscreen container. */
1268  if (strcmp(level, "parent") == 0) {
1269  if (focused && focused->parent) {
1271  success = level_up();
1272  else
1273  ELOG("'focus parent': Currently in fullscreen, not going up\n");
1274  }
1275  }
1276 
1277  /* Focusing a child should always be allowed. */
1278  else success = level_down();
1279 
1280  cmd_output->needs_tree_render = success;
1281  // XXX: default reply for now, make this a better reply
1282  ysuccess(success);
1283 }
1284 
1285 /*
1286  * Implementation of 'focus'.
1287  *
1288  */
1290  DLOG("current_match = %p\n", current_match);
1291 
1293  ELOG("You have to specify which window/container should be focused.\n");
1294  ELOG("Example: [class=\"urxvt\" title=\"irssi\"] focus\n");
1295 
1296  y(map_open);
1297  ystr("success");
1298  y(bool, false);
1299  ystr("error");
1300  ystr("You have to specify which window/container should be focused");
1301  y(map_close);
1302 
1303  return;
1304  }
1305 
1306  int count = 0;
1307  owindow *current;
1308  TAILQ_FOREACH(current, &owindows, owindows) {
1309  Con *ws = con_get_workspace(current->con);
1310  /* If no workspace could be found, this was a dock window.
1311  * Just skip it, you cannot focus dock windows. */
1312  if (!ws)
1313  continue;
1314 
1315  /* Check the fullscreen focus constraints. */
1316  if (!con_fullscreen_permits_focusing(current->con)) {
1317  LOG("Cannot change focus while in fullscreen mode (fullscreen rules).\n");
1318  ysuccess(false);
1319  return;
1320  }
1321 
1322  /* If the container is not on the current workspace,
1323  * workspace_show() will switch to a different workspace and (if
1324  * enabled) trigger a mouse pointer warp to the currently focused
1325  * container (!) on the target workspace.
1326  *
1327  * Therefore, before calling workspace_show(), we make sure that
1328  * 'current' will be focused on the workspace. However, we cannot
1329  * just con_focus(current) because then the pointer will not be
1330  * warped at all (the code thinks we are already there).
1331  *
1332  * So we focus 'current' to make it the currently focused window of
1333  * the target workspace, then revert focus. */
1334  Con *currently_focused = focused;
1335  con_focus(current->con);
1336  con_focus(currently_focused);
1337 
1338  /* Now switch to the workspace, then focus */
1339  workspace_show(ws);
1340  LOG("focusing %p / %s\n", current->con, current->con->name);
1341  con_focus(current->con);
1342  count++;
1343  }
1344 
1345  if (count > 1)
1346  LOG("WARNING: Your criteria for the focus command matches %d containers, "
1347  "while only exactly one container can be focused at a time.\n", count);
1348 
1349  cmd_output->needs_tree_render = true;
1350  // XXX: default reply for now, make this a better reply
1351  ysuccess(true);
1352 }
1353 
1354 /*
1355  * Implementation of 'fullscreen [global]'.
1356  *
1357  */
1358 void cmd_fullscreen(I3_CMD, char *fullscreen_mode) {
1359  if (fullscreen_mode == NULL)
1360  fullscreen_mode = "output";
1361  DLOG("toggling fullscreen, mode = %s\n", fullscreen_mode);
1362  owindow *current;
1363 
1365 
1366  TAILQ_FOREACH(current, &owindows, owindows) {
1367  printf("matching: %p / %s\n", current->con, current->con->name);
1368  con_toggle_fullscreen(current->con, (strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT));
1369  }
1370 
1371  cmd_output->needs_tree_render = true;
1372  // XXX: default reply for now, make this a better reply
1373  ysuccess(true);
1374 }
1375 
1376 /*
1377  * Implementation of 'move <direction> [<pixels> [px]]'.
1378  *
1379  */
1380 void cmd_move_direction(I3_CMD, char *direction, char *move_px) {
1381  // TODO: We could either handle this in the parser itself as a separate token (and make the stack typed) or we need a better way to convert a string to a number with error checking
1382  int px = atoi(move_px);
1383 
1384  /* TODO: make 'move' work with criteria. */
1385  DLOG("moving in direction %s, px %s\n", direction, move_px);
1386  if (con_is_floating(focused)) {
1387  DLOG("floating move with %d pixels\n", px);
1388  Rect newrect = focused->parent->rect;
1389  if (strcmp(direction, "left") == 0) {
1390  newrect.x -= px;
1391  } else if (strcmp(direction, "right") == 0) {
1392  newrect.x += px;
1393  } else if (strcmp(direction, "up") == 0) {
1394  newrect.y -= px;
1395  } else if (strcmp(direction, "down") == 0) {
1396  newrect.y += px;
1397  }
1398  floating_reposition(focused->parent, newrect);
1399  } else {
1400  tree_move((strcmp(direction, "right") == 0 ? D_RIGHT :
1401  (strcmp(direction, "left") == 0 ? D_LEFT :
1402  (strcmp(direction, "up") == 0 ? D_UP :
1403  D_DOWN))));
1404  cmd_output->needs_tree_render = true;
1405  }
1406 
1407  // XXX: default reply for now, make this a better reply
1408  ysuccess(true);
1409 }
1410 
1411 /*
1412  * Implementation of 'layout default|stacked|stacking|tabbed|splitv|splith'.
1413  *
1414  */
1415 void cmd_layout(I3_CMD, char *layout_str) {
1416  if (strcmp(layout_str, "stacking") == 0)
1417  layout_str = "stacked";
1418  owindow *current;
1419  int layout;
1420  /* default is a special case which will be handled in con_set_layout(). */
1421  if (strcmp(layout_str, "default") == 0)
1422  layout = L_DEFAULT;
1423  else if (strcmp(layout_str, "stacked") == 0)
1424  layout = L_STACKED;
1425  else if (strcmp(layout_str, "tabbed") == 0)
1426  layout = L_TABBED;
1427  else if (strcmp(layout_str, "splitv") == 0)
1428  layout = L_SPLITV;
1429  else if (strcmp(layout_str, "splith") == 0)
1430  layout = L_SPLITH;
1431  else {
1432  ELOG("Unknown layout \"%s\", this is a mismatch between code and parser spec.\n", layout_str);
1433  return;
1434  }
1435 
1436  DLOG("changing layout to %s (%d)\n", layout_str, layout);
1437 
1438  /* check if the match is empty, not if the result is empty */
1440  con_set_layout(focused, layout);
1441  else {
1442  TAILQ_FOREACH(current, &owindows, owindows) {
1443  DLOG("matching: %p / %s\n", current->con, current->con->name);
1444  con_set_layout(current->con, layout);
1445  }
1446  }
1447 
1448  cmd_output->needs_tree_render = true;
1449  // XXX: default reply for now, make this a better reply
1450  ysuccess(true);
1451 }
1452 
1453 /*
1454  * Implementation of 'layout toggle [all|split]'.
1455  *
1456  */
1457 void cmd_layout_toggle(I3_CMD, char *toggle_mode) {
1458  owindow *current;
1459 
1460  if (toggle_mode == NULL)
1461  toggle_mode = "default";
1462 
1463  DLOG("toggling layout (mode = %s)\n", toggle_mode);
1464 
1465  /* check if the match is empty, not if the result is empty */
1467  con_toggle_layout(focused, toggle_mode);
1468  else {
1469  TAILQ_FOREACH(current, &owindows, owindows) {
1470  DLOG("matching: %p / %s\n", current->con, current->con->name);
1471  con_toggle_layout(current->con, toggle_mode);
1472  }
1473  }
1474 
1475  cmd_output->needs_tree_render = true;
1476  // XXX: default reply for now, make this a better reply
1477  ysuccess(true);
1478 }
1479 
1480 /*
1481  * Implementaiton of 'exit'.
1482  *
1483  */
1485  LOG("Exiting due to user command.\n");
1486  xcb_disconnect(conn);
1487  exit(0);
1488 
1489  /* unreached */
1490 }
1491 
1492 /*
1493  * Implementaiton of 'reload'.
1494  *
1495  */
1497  LOG("reloading\n");
1498  kill_configerror_nagbar(false);
1499  kill_commanderror_nagbar(false);
1500  load_configuration(conn, NULL, true);
1501  x_set_i3_atoms();
1502  /* Send an IPC event just in case the ws names have changed */
1503  ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
1504 
1505  // XXX: default reply for now, make this a better reply
1506  ysuccess(true);
1507 }
1508 
1509 /*
1510  * Implementaiton of 'restart'.
1511  *
1512  */
1514  LOG("restarting i3\n");
1515  i3_restart(false);
1516 
1517  // XXX: default reply for now, make this a better reply
1518  ysuccess(true);
1519 }
1520 
1521 /*
1522  * Implementaiton of 'open'.
1523  *
1524  */
1526  LOG("opening new container\n");
1527  Con *con = tree_open_con(NULL, NULL);
1528  con->layout = L_SPLITH;
1529  con_focus(con);
1530 
1531  y(map_open);
1532  ystr("success");
1533  y(bool, true);
1534  ystr("id");
1535  y(integer, (long int)con);
1536  y(map_close);
1537 
1538  cmd_output->needs_tree_render = true;
1539 }
1540 
1541 /*
1542  * Implementation of 'focus output <output>'.
1543  *
1544  */
1546  owindow *current;
1547 
1548  DLOG("name = %s\n", name);
1549 
1551 
1552  /* get the output */
1553  Output *current_output = NULL;
1554  Output *output;
1555 
1556  TAILQ_FOREACH(current, &owindows, owindows)
1557  current_output = get_output_containing(current->con->rect.x, current->con->rect.y);
1558  assert(current_output != NULL);
1559 
1560  output = get_output_from_string(current_output, name);
1561 
1562  if (!output) {
1563  LOG("No such output found.\n");
1564  ysuccess(false);
1565  return;
1566  }
1567 
1568  /* get visible workspace on output */
1569  Con *ws = NULL;
1570  GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
1571  if (!ws) {
1572  ysuccess(false);
1573  return;
1574  }
1575 
1576  workspace_show(ws);
1577 
1578  cmd_output->needs_tree_render = true;
1579  // XXX: default reply for now, make this a better reply
1580  ysuccess(true);
1581 }
1582 
1583 /*
1584  * Implementation of 'move [window|container] [to] [absolute] position <px> [px] <px> [px]
1585  *
1586  */
1587 void cmd_move_window_to_position(I3_CMD, char *method, char *cx, char *cy) {
1588 
1589  int x = atoi(cx);
1590  int y = atoi(cy);
1591 
1592  if (!con_is_floating(focused)) {
1593  ELOG("Cannot change position. The window/container is not floating\n");
1594  y(map_open);
1595  ystr("success");
1596  y(bool, false);
1597  ystr("error");
1598  ystr("Cannot change position. The window/container is not floating.");
1599  y(map_close);
1600  return;
1601  }
1602 
1603  if (strcmp(method, "absolute") == 0) {
1604  focused->parent->rect.x = x;
1605  focused->parent->rect.y = y;
1606 
1607  DLOG("moving to absolute position %d %d\n", x, y);
1609  cmd_output->needs_tree_render = true;
1610  }
1611 
1612  if (strcmp(method, "position") == 0) {
1613  Rect newrect = focused->parent->rect;
1614 
1615  DLOG("moving to position %d %d\n", x, y);
1616  newrect.x = x;
1617  newrect.y = y;
1618 
1619  floating_reposition(focused->parent, newrect);
1620  }
1621 
1622  // XXX: default reply for now, make this a better reply
1623  ysuccess(true);
1624 }
1625 
1626 /*
1627  * Implementation of 'move [window|container] [to] [absolute] position center
1628  *
1629  */
1630 void cmd_move_window_to_center(I3_CMD, char *method) {
1631 
1632  if (!con_is_floating(focused)) {
1633  ELOG("Cannot change position. The window/container is not floating\n");
1634  y(map_open);
1635  ystr("success");
1636  y(bool, false);
1637  ystr("error");
1638  ystr("Cannot change position. The window/container is not floating.");
1639  y(map_close);
1640  }
1641 
1642  if (strcmp(method, "absolute") == 0) {
1643  Rect *rect = &focused->parent->rect;
1644 
1645  DLOG("moving to absolute center\n");
1646  rect->x = croot->rect.width/2 - rect->width/2;
1647  rect->y = croot->rect.height/2 - rect->height/2;
1648 
1650  cmd_output->needs_tree_render = true;
1651  }
1652 
1653  if (strcmp(method, "position") == 0) {
1654  Rect *wsrect = &con_get_workspace(focused)->rect;
1655  Rect newrect = focused->parent->rect;
1656 
1657  DLOG("moving to center\n");
1658  newrect.x = wsrect->width/2 - newrect.width/2;
1659  newrect.y = wsrect->height/2 - newrect.height/2;
1660 
1661  floating_reposition(focused->parent, newrect);
1662  }
1663 
1664  // XXX: default reply for now, make this a better reply
1665  ysuccess(true);
1666 }
1667 
1668 /*
1669  * Implementation of 'move scratchpad'.
1670  *
1671  */
1673  DLOG("should move window to scratchpad\n");
1674  owindow *current;
1675 
1677 
1678  TAILQ_FOREACH(current, &owindows, owindows) {
1679  DLOG("matching: %p / %s\n", current->con, current->con->name);
1680  scratchpad_move(current->con);
1681  }
1682 
1683  cmd_output->needs_tree_render = true;
1684  // XXX: default reply for now, make this a better reply
1685  ysuccess(true);
1686 }
1687 
1688 /*
1689  * Implementation of 'scratchpad show'.
1690  *
1691  */
1693  DLOG("should show scratchpad window\n");
1694  owindow *current;
1695 
1697  scratchpad_show(NULL);
1698  } else {
1699  TAILQ_FOREACH(current, &owindows, owindows) {
1700  DLOG("matching: %p / %s\n", current->con, current->con->name);
1701  scratchpad_show(current->con);
1702  }
1703  }
1704 
1705  cmd_output->needs_tree_render = true;
1706  // XXX: default reply for now, make this a better reply
1707  ysuccess(true);
1708 }
1709 
1710 /*
1711  * Implementation of 'rename workspace <name> to <name>'
1712  *
1713  */
1714 void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
1715  LOG("Renaming workspace \"%s\" to \"%s\"\n", old_name, new_name);
1716 
1717  Con *output, *workspace = NULL;
1718  TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
1719  GREP_FIRST(workspace, output_get_content(output),
1720  !strcasecmp(child->name, old_name));
1721 
1722  if (!workspace) {
1723  // TODO: we should include the old workspace name here and use yajl for
1724  // generating the reply.
1725  y(map_open);
1726  ystr("success");
1727  y(bool, false);
1728  ystr("error");
1729  // TODO: better error message
1730  ystr("Old workspace not found");
1731  y(map_close);
1732  return;
1733  }
1734 
1735  Con *check_dest = NULL;
1736  TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
1737  GREP_FIRST(check_dest, output_get_content(output),
1738  !strcasecmp(child->name, new_name));
1739 
1740  if (check_dest != NULL) {
1741  // TODO: we should include the new workspace name here and use yajl for
1742  // generating the reply.
1743  y(map_open);
1744  ystr("success");
1745  y(bool, false);
1746  ystr("error");
1747  // TODO: better error message
1748  ystr("New workspace already exists");
1749  y(map_close);
1750  return;
1751  }
1752 
1753  /* Change the name and try to parse it as a number. */
1754  FREE(workspace->name);
1755  workspace->name = sstrdup(new_name);
1756  char *endptr = NULL;
1757  long parsed_num = strtol(new_name, &endptr, 10);
1758  if (parsed_num == LONG_MIN ||
1759  parsed_num == LONG_MAX ||
1760  parsed_num < 0 ||
1761  endptr == new_name)
1762  workspace->num = -1;
1763  else workspace->num = parsed_num;
1764  LOG("num = %d\n", workspace->num);
1765 
1766  /* By re-attaching, the sort order will be correct afterwards. */
1767  Con *previously_focused = focused;
1768  Con *parent = workspace->parent;
1769  con_detach(workspace);
1770  con_attach(workspace, parent, false);
1771  /* Restore the previous focus since con_attach messes with the focus. */
1772  con_focus(previously_focused);
1773 
1774  cmd_output->needs_tree_render = true;
1775  ysuccess(true);
1776 
1777  ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"rename\"}");
1778 }