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