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