commands_parser.c: Make re-entrant (#6523)

Refactor to work with various for_window matching related issues

Depends on https://github.com/i3/i3/pull/6514
Related to https://github.com/i3/i3/issues/3588#issuecomment-544186208
Related to https://github.com/i3/i3/issues/4346
Related to https://github.com/i3/i3/issues/6037
Reverts commit 1cc2548027 / #6509
This commit is contained in:
Orestis Floros
2025-11-07 08:18:36 +01:00
committed by GitHub
parent e035d0c658
commit d674090f96
6 changed files with 139 additions and 134 deletions

View File

@@ -12,6 +12,18 @@
#include <config.h> #include <config.h>
#include <yajl/yajl_gen.h> #include <yajl/yajl_gen.h>
#include "parser_util.h"
#include "queue.h"
/**
* Helper data structure for an operation window (window on which the operation
* will be performed). Used to build the TAILQ owindows.
*
*/
typedef struct owindow {
Con *con;
TAILQ_ENTRY(owindow) owindows;
} owindow;
/** /**
* Holds an intermediate representation of the result of a call to any command. * Holds an intermediate representation of the result of a call to any command.
@@ -19,6 +31,9 @@
* internally use this struct when calling cmd_floating and cmd_border. * internally use this struct when calling cmd_floating and cmd_border.
*/ */
struct CommandResultIR { struct CommandResultIR {
/* The parser context this command is executing in. */
struct cmd_parser_ctx *ctx;
/* The JSON generator to append a reply to (may be NULL). */ /* The JSON generator to append a reply to (may be NULL). */
yajl_gen json_gen; yajl_gen json_gen;
@@ -35,6 +50,28 @@ struct CommandResultIR {
bool needs_tree_render; bool needs_tree_render;
}; };
/* Define the owindows head structure here so it's complete */
TAILQ_HEAD(owindows_head, owindow);
/**
* Context structure for the command parser, making it re-entrant.
*/
struct cmd_parser_ctx {
int state;
Match current_match;
/* The (small) stack where identified literals are stored during the parsing
* of a single command (like $workspace). */
struct stack stack;
/* List of operation windows (windows on which operations will be performed).
* Used to build the TAILQ owindows. */
struct owindows_head owindows;
struct CommandResultIR subcommand_output;
struct CommandResultIR command_output;
};
typedef struct CommandResult CommandResult; typedef struct CommandResult CommandResult;
/** /**

View File

@@ -61,15 +61,15 @@
HANDLE_INVALID_MATCH; \ HANDLE_INVALID_MATCH; \
\ \
if (match_is_empty(current_match)) { \ if (match_is_empty(current_match)) { \
while (!TAILQ_EMPTY(&owindows)) { \ while (!TAILQ_EMPTY(&OWINDOWS)) { \
owindow *ow = TAILQ_FIRST(&owindows); \ owindow *ow = TAILQ_FIRST(&OWINDOWS); \
TAILQ_REMOVE(&owindows, ow, owindows); \ TAILQ_REMOVE(&OWINDOWS, ow, owindows); \
free(ow); \ free(ow); \
} \ } \
owindow *ow = smalloc(sizeof(owindow)); \ owindow *ow = smalloc(sizeof(owindow)); \
ow->con = focused; \ ow->con = focused; \
TAILQ_INIT(&owindows); \ TAILQ_INIT(&OWINDOWS); \
TAILQ_INSERT_TAIL(&owindows, ow, owindows); \ TAILQ_INSERT_TAIL(&OWINDOWS, ow, owindows); \
} \ } \
} while (0) } while (0)
@@ -124,19 +124,8 @@ static Con *maybe_auto_back_and_forth_workspace(Con *workspace) {
* Criteria functions. * Criteria functions.
******************************************************************************/ ******************************************************************************/
/* /* Macro to access owindows from the command output context */
* Helper data structure for an operation window (window on which the operation #define OWINDOWS (cmd_output->ctx->owindows)
* will be performed). Used to build the TAILQ owindows.
*
*/
typedef struct owindow {
Con *con;
TAILQ_ENTRY(owindow) owindows;
} owindow;
typedef TAILQ_HEAD(owindows_head, owindow) owindows_head;
static owindows_head owindows;
/* /*
* Initializes the specified 'Match' data structure and the initial state of * Initializes the specified 'Match' data structure and the initial state of
@@ -158,12 +147,12 @@ void cmd_criteria_match_windows(I3_CMD) {
DLOG("match specification finished, matching...\n"); DLOG("match specification finished, matching...\n");
/* Clear old queue */ /* Clear old queue */
while (!TAILQ_EMPTY(&owindows)) { while (!TAILQ_EMPTY(&OWINDOWS)) {
owindow *ow = TAILQ_FIRST(&owindows); owindow *ow = TAILQ_FIRST(&OWINDOWS);
TAILQ_REMOVE(&owindows, ow, owindows); TAILQ_REMOVE(&OWINDOWS, ow, owindows);
free(ow); free(ow);
} }
TAILQ_INIT(&owindows); TAILQ_INIT(&OWINDOWS);
/* Go through all cons and find matches */ /* Go through all cons and find matches */
Con *con; Con *con;
@@ -220,7 +209,7 @@ void cmd_criteria_match_windows(I3_CMD) {
DLOG("matching: %p / %s\n", con, con->name); DLOG("matching: %p / %s\n", con, con->name);
owindow *ow = smalloc(sizeof(owindow)); owindow *ow = smalloc(sizeof(owindow));
ow->con = con; ow->con = con;
TAILQ_INSERT_TAIL(&owindows, ow, owindows); TAILQ_INSERT_TAIL(&OWINDOWS, ow, owindows);
} }
} }
} }
@@ -234,9 +223,9 @@ void cmd_criteria_add(I3_CMD, const char *ctype, const char *cvalue) {
match_parse_property(current_match, ctype, cvalue); match_parse_property(current_match, ctype, cvalue);
} }
static void move_matches_to_workspace(Con *ws) { static void move_matches_to_workspace(struct owindows_head *owindows, Con *ws) {
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, owindows, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name); DLOG("matching: %p / %s\n", current->con, current->con->name);
con_move_to_workspace(current->con, ws, true, false, false); con_move_to_workspace(current->con, ws, true, false, false);
} }
@@ -245,17 +234,17 @@ static void move_matches_to_workspace(Con *ws) {
#define CHECK_MOVE_CON_TO_WORKSPACE \ #define CHECK_MOVE_CON_TO_WORKSPACE \
do { \ do { \
HANDLE_EMPTY_MATCH; \ HANDLE_EMPTY_MATCH; \
if (TAILQ_EMPTY(&owindows)) { \ if (TAILQ_EMPTY(&OWINDOWS)) { \
yerror("Nothing to move: specified criteria don't match any window"); \ yerror("Nothing to move: specified criteria don't match any window"); \
return; \ return; \
} else { \ } else { \
bool found = false; \ bool found = false; \
owindow *current = TAILQ_FIRST(&owindows); \ owindow *current = TAILQ_FIRST(&OWINDOWS); \
while (current) { \ while (current) { \
owindow *next = TAILQ_NEXT(current, owindows); \ owindow *next = TAILQ_NEXT(current, owindows); \
\ \
if (current->con->type == CT_WORKSPACE && !con_has_children(current->con)) { \ if (current->con->type == CT_WORKSPACE && !con_has_children(current->con)) { \
TAILQ_REMOVE(&owindows, current, owindows); \ TAILQ_REMOVE(&OWINDOWS, current, owindows); \
} else { \ } else { \
found = true; \ found = true; \
} \ } \
@@ -296,7 +285,7 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which) {
return; return;
} }
move_matches_to_workspace(ws); move_matches_to_workspace(&OWINDOWS, ws);
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply // XXX: default reply for now, make this a better reply
@@ -316,7 +305,7 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
move_matches_to_workspace(ws); move_matches_to_workspace(&OWINDOWS, ws);
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply // XXX: default reply for now, make this a better reply
@@ -343,7 +332,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *no_aut
ws = maybe_auto_back_and_forth_workspace(ws); ws = maybe_auto_back_and_forth_workspace(ws);
} }
move_matches_to_workspace(ws); move_matches_to_workspace(&OWINDOWS, ws);
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply // XXX: default reply for now, make this a better reply
@@ -375,7 +364,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_
ws = maybe_auto_back_and_forth_workspace(ws); ws = maybe_auto_back_and_forth_workspace(ws);
} }
move_matches_to_workspace(ws); move_matches_to_workspace(&OWINDOWS, ws);
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
// XXX: default reply for now, make this a better reply // XXX: default reply for now, make this a better reply
@@ -564,7 +553,7 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px,
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
/* Don't handle dock windows (issue #1201) */ /* Don't handle dock windows (issue #1201) */
if (current->con->window && current->con->window->dock) { if (current->con->window && current->con->window->dock) {
DLOG("This is a dock window. Not resizing (con = %p)\n)", current->con); DLOG("This is a dock window. Not resizing (con = %p)\n)", current->con);
@@ -644,7 +633,7 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c
owindow *current; owindow *current;
bool success = true; bool success = true;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
Con *floating_con; Con *floating_con;
if ((floating_con = con_inside_floating(current->con))) { if ((floating_con = con_inside_floating(current->con))) {
Con *output = con_get_output(floating_con); Con *output = con_get_output(floating_con);
@@ -712,7 +701,7 @@ void cmd_border(I3_CMD, const char *border_style_str, long border_width) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name); DLOG("matching: %p / %s\n", current->con, current->con->name);
border_style_t border_style; border_style_t border_style;
@@ -947,14 +936,14 @@ void cmd_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_
void cmd_mark(I3_CMD, const char *mark, const char *mode, const char *toggle) { void cmd_mark(I3_CMD, const char *mark, const char *mode, const char *toggle) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
owindow *current = TAILQ_FIRST(&owindows); owindow *current = TAILQ_FIRST(&OWINDOWS);
if (current == NULL) { if (current == NULL) {
yerror("Given criteria don't match a window"); yerror("Given criteria don't match a window");
return; return;
} }
/* Marks must be unique, i.e., no two windows must have the same mark. */ /* Marks must be unique, i.e., no two windows must have the same mark. */
if (current != TAILQ_LAST(&owindows, owindows_head)) { if (current != TAILQ_LAST(&OWINDOWS, owindows_head)) {
yerror("A mark must not be put onto more than one window"); yerror("A mark must not be put onto more than one window");
return; return;
} }
@@ -982,7 +971,7 @@ void cmd_unmark(I3_CMD, const char *mark) {
con_unmark(NULL, mark); con_unmark(NULL, mark);
} else { } else {
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
con_unmark(current->con, mark); con_unmark(current->con, mark);
} }
} }
@@ -1103,7 +1092,7 @@ void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) {
bool success = false; bool success = false;
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
Con *ws = con_get_workspace(current->con); Con *ws = con_get_workspace(current->con);
if (con_is_internal(ws)) { if (con_is_internal(ws)) {
continue; continue;
@@ -1141,7 +1130,7 @@ void cmd_move_con_to_mark(I3_CMD, const char *mark) {
bool result = true; bool result = true;
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
DLOG("moving matched window %p / %s to mark \"%s\"\n", current->con, current->con->name, mark); DLOG("moving matched window %p / %s to mark \"%s\"\n", current->con, current->con->name, mark);
result &= con_move_to_mark(current->con, mark); result &= con_move_to_mark(current->con, mark);
} }
@@ -1161,7 +1150,7 @@ void cmd_floating(I3_CMD, const char *floating_mode) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name); DLOG("matching: %p / %s\n", current->con, current->con->name);
if (strcmp(floating_mode, "toggle") == 0) { if (strcmp(floating_mode, "toggle") == 0) {
DLOG("should toggle mode\n"); DLOG("should toggle mode\n");
@@ -1174,6 +1163,7 @@ void cmd_floating(I3_CMD, const char *floating_mode) {
floating_disable(current->con); floating_disable(current->con);
} }
} }
run_assignments(current->con->window);
} }
cmd_output->needs_tree_render = true; cmd_output->needs_tree_render = true;
@@ -1190,7 +1180,7 @@ void cmd_split(I3_CMD, const char *direction) {
owindow *current; owindow *current;
LOG("splitting in direction %c\n", direction[0]); LOG("splitting in direction %c\n", direction[0]);
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
if (con_is_docked(current->con)) { if (con_is_docked(current->con)) {
ELOG("Cannot split a docked container, skipping.\n"); ELOG("Cannot split a docked container, skipping.\n");
continue; continue;
@@ -1244,7 +1234,7 @@ void cmd_kill(I3_CMD, const char *kill_mode_str) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
con_close(current->con, kill_mode); con_close(current->con, kill_mode);
} }
@@ -1264,7 +1254,7 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command) {
int count = 0; int count = 0;
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
count++; count++;
} }
@@ -1274,7 +1264,7 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command) {
count); count);
} }
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id); DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id);
start_application(command, no_startup_id); start_application(command, no_startup_id);
} }
@@ -1286,7 +1276,7 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command) {
do { \ do { \
int count = 0; \ int count = 0; \
owindow *current; \ owindow *current; \
TAILQ_FOREACH (current, &owindows, owindows) { \ TAILQ_FOREACH (current, &OWINDOWS, owindows) { \
count++; \ count++; \
} \ } \
\ \
@@ -1318,7 +1308,7 @@ void cmd_focus_direction(I3_CMD, const char *direction_str) {
} }
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
Con *ws = con_get_workspace(current->con); Con *ws = con_get_workspace(current->con);
if (!ws || con_is_internal(ws)) { if (!ws || con_is_internal(ws)) {
continue; continue;
@@ -1345,7 +1335,7 @@ void cmd_focus_sibling(I3_CMD, const char *direction_str) {
const position_t direction = (STARTS_WITH(direction_str, "prev")) ? BEFORE : AFTER; const position_t direction = (STARTS_WITH(direction_str, "prev")) ? BEFORE : AFTER;
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
Con *ws = con_get_workspace(current->con); Con *ws = con_get_workspace(current->con);
if (!ws || con_is_internal(ws)) { if (!ws || con_is_internal(ws)) {
continue; continue;
@@ -1451,7 +1441,7 @@ void cmd_focus(I3_CMD, bool focus_workspace) {
yerror("You have to specify which window/container should be focused"); yerror("You have to specify which window/container should be focused");
return; return;
} else if (TAILQ_EMPTY(&owindows)) { } else if (TAILQ_EMPTY(&OWINDOWS)) {
yerror("No window matches given criteria"); yerror("No window matches given criteria");
return; return;
} }
@@ -1460,7 +1450,7 @@ void cmd_focus(I3_CMD, bool focus_workspace) {
Con *__i3_scratch = workspace_get("__i3_scratch"); Con *__i3_scratch = workspace_get("__i3_scratch");
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
Con *ws = con_get_workspace(current->con); Con *ws = con_get_workspace(current->con);
/* If no workspace could be found, this was a dock window. /* If no workspace could be found, this was a dock window.
* Just skip it, you cannot focus dock windows. */ * Just skip it, you cannot focus dock windows. */
@@ -1504,7 +1494,7 @@ void cmd_fullscreen(I3_CMD, const char *action, const char *fullscreen_mode) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name); DLOG("matching: %p / %s\n", current->con, current->con->name);
if (strcmp(action, "toggle") == 0) { if (strcmp(action, "toggle") == 0) {
con_toggle_fullscreen(current->con, mode); con_toggle_fullscreen(current->con, mode);
@@ -1529,7 +1519,7 @@ void cmd_sticky(I3_CMD, const char *action) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
if (current->con->window == NULL) { if (current->con->window == NULL) {
ELOG("only containers holding a window can be made sticky, skipping con = %p\n", current->con); ELOG("only containers holding a window can be made sticky, skipping con = %p\n", current->con);
continue; continue;
@@ -1572,7 +1562,7 @@ void cmd_move_direction(I3_CMD, const char *direction_str, long amount, const ch
const bool is_ppt = mode && strcmp(mode, "ppt") == 0; const bool is_ppt = mode && strcmp(mode, "ppt") == 0;
DLOG("moving in direction %s, %ld %s\n", direction_str, amount, mode); DLOG("moving in direction %s, %ld %s\n", direction_str, amount, mode);
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
if (con_is_floating(current->con)) { if (con_is_floating(current->con)) {
DLOG("floating move with %ld %s\n", amount, mode); DLOG("floating move with %ld %s\n", amount, mode);
Rect newrect = current->con->parent->rect; Rect newrect = current->con->parent->rect;
@@ -1620,7 +1610,7 @@ void cmd_layout(I3_CMD, const char *layout_str) {
DLOG("changing layout to %s (%d)\n", layout_str, layout); DLOG("changing layout to %s (%d)\n", layout_str, layout);
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
if (con_is_docked(current->con)) { if (con_is_docked(current->con)) {
ELOG("cannot change layout of a docked container, skipping it.\n"); ELOG("cannot change layout of a docked container, skipping it.\n");
continue; continue;
@@ -1652,7 +1642,7 @@ void cmd_layout_toggle(I3_CMD, const char *toggle_mode) {
if (match_is_empty(current_match)) { if (match_is_empty(current_match)) {
con_toggle_layout(focused, toggle_mode); con_toggle_layout(focused, toggle_mode);
} else { } else {
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name); DLOG("matching: %p / %s\n", current->con, current->con->name);
con_toggle_layout(current->con, toggle_mode); con_toggle_layout(current->con, toggle_mode);
} }
@@ -1774,7 +1764,7 @@ void cmd_focus_output(I3_CMD, const char *name) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
if (TAILQ_EMPTY(&owindows)) { if (TAILQ_EMPTY(&OWINDOWS)) {
ysuccess(true); ysuccess(true);
return; return;
} }
@@ -1785,7 +1775,7 @@ void cmd_focus_output(I3_CMD, const char *name) {
* there is no match, fall back to the focused one. */ * there is no match, fall back to the focused one. */
owindow *current; owindow *current;
Con *con = focused; Con *con = focused;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
if (!con_is_internal(con_get_workspace(current->con))) { if (!con_is_internal(con_get_workspace(current->con))) {
con = current->con; con = current->con;
break; break;
@@ -1828,7 +1818,7 @@ void cmd_move_window_to_position(I3_CMD, long x, const char *mode_x, long y, con
owindow *current; owindow *current;
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
if (!con_is_floating(current->con)) { if (!con_is_floating(current->con)) {
ELOG("Cannot change position. The window/container is not floating\n"); ELOG("Cannot change position. The window/container is not floating\n");
@@ -1867,7 +1857,7 @@ void cmd_move_window_to_center(I3_CMD, const char *method) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
Con *floating_con = con_inside_floating(current->con); Con *floating_con = con_inside_floating(current->con);
if (floating_con == NULL) { if (floating_con == NULL) {
ELOG("con %p / %s is not floating, cannot move it to the center.\n", ELOG("con %p / %s is not floating, cannot move it to the center.\n",
@@ -1911,7 +1901,7 @@ void cmd_move_window_to_mouse(I3_CMD) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
Con *floating_con = con_inside_floating(current->con); Con *floating_con = con_inside_floating(current->con);
if (floating_con == NULL) { if (floating_con == NULL) {
DLOG("con %p / %s is not floating, cannot move it to the mouse position.\n", DLOG("con %p / %s is not floating, cannot move it to the mouse position.\n",
@@ -1937,7 +1927,7 @@ void cmd_move_scratchpad(I3_CMD) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name); DLOG("matching: %p / %s\n", current->con, current->con->name);
scratchpad_move(current->con); scratchpad_move(current->con);
} }
@@ -1959,7 +1949,7 @@ void cmd_scratchpad_show(I3_CMD) {
if (match_is_empty(current_match)) { if (match_is_empty(current_match)) {
result = scratchpad_show(NULL); result = scratchpad_show(NULL);
} else { } else {
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name); DLOG("matching: %p / %s\n", current->con, current->con->name);
result |= scratchpad_show(current->con); result |= scratchpad_show(current->con);
} }
@@ -1977,7 +1967,7 @@ void cmd_scratchpad_show(I3_CMD) {
void cmd_swap(I3_CMD, const char *mode, const char *arg) { void cmd_swap(I3_CMD, const char *mode, const char *arg) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
owindow *match = TAILQ_FIRST(&owindows); owindow *match = TAILQ_FIRST(&OWINDOWS);
if (match == NULL) { if (match == NULL) {
yerror("No match found for swapping."); yerror("No match found for swapping.");
return; return;
@@ -2016,7 +2006,7 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) {
return; return;
} }
if (match != TAILQ_LAST(&owindows, owindows_head)) { if (match != TAILQ_LAST(&OWINDOWS, owindows_head)) {
LOG("More than one container matched the swap command, only using the first one."); LOG("More than one container matched the swap command, only using the first one.");
} }
@@ -2037,7 +2027,7 @@ void cmd_title_format(I3_CMD, const char *format) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
DLOG("setting title_format for %p / %s\n", current->con, current->con->name); DLOG("setting title_format for %p / %s\n", current->con, current->con->name);
FREE(current->con->title_format); FREE(current->con->title_format);
@@ -2088,7 +2078,7 @@ void cmd_title_window_icon(I3_CMD, const char *enable, int padding) {
HANDLE_EMPTY_MATCH; HANDLE_EMPTY_MATCH;
owindow *current; owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) { TAILQ_FOREACH (current, &OWINDOWS, owindows) {
if (is_toggle) { if (is_toggle) {
const int current_padding = current->con->window_icon_padding; const int current_padding = current->con->window_icon_padding;
if (padding > 0) { if (padding > 0) {

View File

@@ -26,9 +26,9 @@
#include "all.h" #include "all.h"
#include "parser_util.h" #include "parser_util.h"
// Macros to make the YAJL API a bit easier to use. /* Macros to make the YAJL API a bit easier to use. */
#define y(x, ...) (command_output.json_gen != NULL ? yajl_gen_##x(command_output.json_gen, ##__VA_ARGS__) : 0) #define y(x, ...) (cmd_ctx.command_output.json_gen != NULL ? yajl_gen_##x(cmd_ctx.command_output.json_gen, ##__VA_ARGS__) : 0)
#define ystr(str) (command_output.json_gen != NULL ? yajl_gen_string(command_output.json_gen, (unsigned char *)str, strlen(str)) : 0) #define ystr(str) (cmd_ctx.command_output.json_gen != NULL ? yajl_gen_string(cmd_ctx.command_output.json_gen, (unsigned char *)str, strlen(str)) : 0)
/******************************************************************************* /*******************************************************************************
* The data structures used for parsing. Essentially the current state and a * The data structures used for parsing. Essentially the current state and a
@@ -61,37 +61,28 @@ typedef struct tokenptr {
* The parser itself. * The parser itself.
******************************************************************************/ ******************************************************************************/
static cmdp_state state;
static Match current_match;
/*******************************************************************************
* The (small) stack where identified literals are stored during the parsing
* of a single command (like $workspace).
******************************************************************************/
static struct stack stack;
static struct CommandResultIR subcommand_output;
static struct CommandResultIR command_output;
#include "GENERATED_command_call.h" #include "GENERATED_command_call.h"
static void next_state(const cmdp_token *token) { static void next_state(const cmdp_token *token, struct cmd_parser_ctx *cmd_ctx) {
if (token->next_state == __CALL) { if (token->next_state == __CALL) {
subcommand_output.json_gen = command_output.json_gen; cmd_ctx->subcommand_output.ctx = cmd_ctx;
subcommand_output.client = command_output.client; cmd_ctx->subcommand_output.json_gen = cmd_ctx->command_output.json_gen;
subcommand_output.needs_tree_render = false; cmd_ctx->subcommand_output.client = cmd_ctx->command_output.client;
GENERATED_call(&current_match, &stack, token->extra.call_identifier, &subcommand_output); cmd_ctx->subcommand_output.needs_tree_render = false;
state = subcommand_output.next_state; GENERATED_call(&cmd_ctx->current_match, &cmd_ctx->stack, token->extra.call_identifier, &cmd_ctx->subcommand_output);
cmd_ctx->state = cmd_ctx->subcommand_output.next_state;
/* If any subcommand requires a tree_render(), we need to make the /* If any subcommand requires a tree_render(), we need to make the
* whole parser result request a tree_render(). */ * whole parser result request a tree_render(). */
if (subcommand_output.needs_tree_render) { if (cmd_ctx->subcommand_output.needs_tree_render) {
command_output.needs_tree_render = true; cmd_ctx->command_output.needs_tree_render = true;
} }
parser_clear_stack(&stack); parser_clear_stack(&cmd_ctx->stack);
return; return;
} }
state = token->next_state; cmd_ctx->state = token->next_state;
if (state == INITIAL) { if (cmd_ctx->state == INITIAL) {
parser_clear_stack(&stack); parser_clear_stack(&cmd_ctx->stack);
} }
} }
@@ -166,16 +157,19 @@ char *parse_string(const char **walk, const bool as_word) {
*/ */
CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client) { CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client) {
DLOG("COMMAND: *%.4000s*\n", input); DLOG("COMMAND: *%.4000s*\n", input);
state = INITIAL; struct cmd_parser_ctx cmd_ctx = {0};
cmd_ctx.state = INITIAL;
CommandResult *result = scalloc(1, sizeof(CommandResult)); CommandResult *result = scalloc(1, sizeof(CommandResult));
command_output.client = client; cmd_ctx.command_output.ctx = &cmd_ctx;
cmd_ctx.command_output.client = client;
/* A YAJL JSON generator used for formatting replies. */ /* A YAJL JSON generator used for formatting replies. */
command_output.json_gen = gen; cmd_ctx.command_output.json_gen = gen;
y(array_open); y(array_open);
command_output.needs_tree_render = false; cmd_ctx.command_output.needs_tree_render = false;
const char *walk = input; const char *walk = input;
const size_t len = strlen(input); const size_t len = strlen(input);
@@ -185,7 +179,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
// TODO: make this testable // TODO: make this testable
#ifndef TEST_PARSER #ifndef TEST_PARSER
cmd_criteria_init(&current_match, &subcommand_output); cmd_criteria_init(&cmd_ctx.current_match, &cmd_ctx.subcommand_output);
#endif #endif
/* The "<=" operator is intentional: We also handle the terminating 0-byte /* The "<=" operator is intentional: We also handle the terminating 0-byte
@@ -198,7 +192,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
walk++; walk++;
} }
const cmdp_token_ptr *ptr = &(tokens[state]); const cmdp_token_ptr *ptr = &(tokens[cmd_ctx.state]);
token_handled = false; token_handled = false;
for (c = 0; c < ptr->n; c++) { for (c = 0; c < ptr->n; c++) {
token = &(ptr->array[c]); token = &(ptr->array[c]);
@@ -207,10 +201,10 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
if (token->name[0] == '\'') { if (token->name[0] == '\'') {
if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) { if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
if (token->identifier != NULL) { if (token->identifier != NULL) {
parser_push_string(&stack, token->identifier, token->name + 1); parser_push_string(&cmd_ctx.stack, token->identifier, token->name + 1);
} }
walk += strlen(token->name) - 1; walk += strlen(token->name) - 1;
next_state(token); next_state(token, &cmd_ctx);
token_handled = true; token_handled = true;
break; break;
} }
@@ -233,12 +227,12 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
} }
if (token->identifier != NULL) { if (token->identifier != NULL) {
parser_push_long(&stack, token->identifier, num); parser_push_long(&cmd_ctx.stack, token->identifier, num);
} }
/* Set walk to the first non-number character */ /* Set walk to the first non-number character */
walk = end; walk = end;
next_state(token); next_state(token, &cmd_ctx);
token_handled = true; token_handled = true;
break; break;
} }
@@ -248,7 +242,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
char *str = parse_string(&walk, (token->name[0] != 's')); char *str = parse_string(&walk, (token->name[0] != 's'));
if (str != NULL) { if (str != NULL) {
if (token->identifier) { if (token->identifier) {
parser_push_string(&stack, token->identifier, str); parser_push_string(&cmd_ctx.stack, token->identifier, str);
} }
free(str); free(str);
/* If we are at the end of a quoted string, skip the ending /* If we are at the end of a quoted string, skip the ending
@@ -256,7 +250,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
if (*walk == '"') { if (*walk == '"') {
walk++; walk++;
} }
next_state(token); next_state(token, &cmd_ctx);
token_handled = true; token_handled = true;
break; break;
} }
@@ -264,7 +258,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
if (strcmp(token->name, "end") == 0) { if (strcmp(token->name, "end") == 0) {
if (*walk == '\0' || *walk == ',' || *walk == ';') { if (*walk == '\0' || *walk == ',' || *walk == ';') {
next_state(token); next_state(token, &cmd_ctx);
token_handled = true; token_handled = true;
/* To make sure we start with an appropriate matching /* To make sure we start with an appropriate matching
* datastructure for commands which do *not* specify any * datastructure for commands which do *not* specify any
@@ -273,7 +267,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
// TODO: make this testable // TODO: make this testable
#ifndef TEST_PARSER #ifndef TEST_PARSER
if (*walk == '\0' || *walk == ';') { if (*walk == '\0' || *walk == ';') {
cmd_criteria_init(&current_match, &subcommand_output); cmd_criteria_init(&cmd_ctx.current_match, &cmd_ctx.subcommand_output);
} }
#endif #endif
walk++; walk++;
@@ -356,14 +350,20 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
y(map_close); y(map_close);
free(position); free(position);
parser_clear_stack(&stack); parser_clear_stack(&cmd_ctx.stack);
break; break;
} }
} }
y(array_close); y(array_close);
result->needs_tree_render = command_output.needs_tree_render; result->needs_tree_render = cmd_ctx.command_output.needs_tree_render;
/* Clean up owindows entries */
while (!TAILQ_EMPTY(&cmd_ctx.owindows)) {
struct owindow *ow = TAILQ_FIRST(&cmd_ctx.owindows);
TAILQ_REMOVE(&cmd_ctx.owindows, ow, owindows);
free(ow);
}
return result; return result;
} }

View File

@@ -895,6 +895,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
con->sticky = true; con->sticky = true;
ewmh_update_sticky(con->window->id, true); ewmh_update_sticky(con->window->id, true);
output_push_sticky_windows(focused); output_push_sticky_windows(focused);
run_assignments(con->window);
} }
} else { } else {
Con *ws = ewmh_get_workspace_by_index(index); Con *ws = ewmh_get_workspace_by_index(index);
@@ -1304,25 +1305,6 @@ static bool handle_strut_partial_change(Con *con, xcb_get_property_reply_t *prop
return true; return true;
} }
/*
* Handles the _I3_FLOATING_WINDOW property to properly run assignments for
* floating window changes.
*
* This is needed to correctly run the assignments after changes in floating
* windows which are triggered by user commands (floating enable | disable). In
* that case, we can't call run_assignments because it will modify the parser
* state when it needs to parse the user-specified action, breaking the parser
* state for the original command.
*
*/
static bool handle_i3_floating(Con *con, xcb_get_property_reply_t *prop) {
DLOG("floating change for con %p\n", con);
remanage_window(con);
return true;
}
static bool handle_windowicon_change(Con *con, xcb_get_property_reply_t *prop) { static bool handle_windowicon_change(Con *con, xcb_get_property_reply_t *prop) {
window_update_icon(con->window, prop); window_update_icon(con->window, prop);
@@ -1352,7 +1334,6 @@ static struct property_handler_t property_handlers[] = {
{0, 128, handle_class_change}, {0, 128, handle_class_change},
{0, UINT_MAX, handle_strut_partial_change}, {0, UINT_MAX, handle_strut_partial_change},
{0, UINT_MAX, handle_window_type}, {0, UINT_MAX, handle_window_type},
{0, UINT_MAX, handle_i3_floating},
{0, 128, handle_machine_change}, {0, 128, handle_machine_change},
{0, 5 * sizeof(uint64_t), handle_motif_hints_change}, {0, 5 * sizeof(uint64_t), handle_motif_hints_change},
{0, UINT_MAX, handle_windowicon_change}}; {0, UINT_MAX, handle_windowicon_change}};
@@ -1376,10 +1357,9 @@ void property_handlers_init(void) {
property_handlers[7].atom = XCB_ATOM_WM_CLASS; property_handlers[7].atom = XCB_ATOM_WM_CLASS;
property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL; property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL;
property_handlers[9].atom = A__NET_WM_WINDOW_TYPE; property_handlers[9].atom = A__NET_WM_WINDOW_TYPE;
property_handlers[10].atom = A_I3_FLOATING_WINDOW; property_handlers[10].atom = XCB_ATOM_WM_CLIENT_MACHINE;
property_handlers[11].atom = XCB_ATOM_WM_CLIENT_MACHINE; property_handlers[11].atom = A__MOTIF_WM_HINTS;
property_handlers[12].atom = A__MOTIF_WM_HINTS; property_handlers[12].atom = A__NET_WM_ICON;
property_handlers[13].atom = A__NET_WM_ICON;
} }
static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) { static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {

View File

@@ -293,6 +293,7 @@ void tree_move(Con *con, direction_t direction) {
if (con_is_floating(con)) { if (con_is_floating(con)) {
/* this is a floating con, we just disable floating */ /* this is a floating con, we just disable floating */
floating_disable(con); floating_disable(con);
run_assignments(con->window);
return; return;
} }
if (con_inside_floating(con)) { if (con_inside_floating(con)) {

View File

@@ -56,7 +56,6 @@ is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'floating', 'floating_auto' ], "mark
cmd '[id=' . $A->{id} . '] floating enable'; cmd '[id=' . $A->{id} . '] floating enable';
cmd '[id=' . $B->{id} . '] floating disable'; cmd '[id=' . $B->{id} . '] floating disable';
sync_with_i3; # Assignments run based on the _I3_FLOATING_WINDOW property. No sync is race-y.
@nodes = @{get_ws($tmp)->{nodes}}; @nodes = @{get_ws($tmp)->{nodes}};
cmp_ok(@nodes, '==', 1, 'one tiling container on this workspace'); cmp_ok(@nodes, '==', 1, 'one tiling container on this workspace');
@@ -76,7 +75,6 @@ is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'tiling_auto', 'floating', 'floating
# Use 'mark' to clear old marks # Use 'mark' to clear old marks
cmd '[id=' . $A->{id} . '] mark A, floating disable'; cmd '[id=' . $A->{id} . '] mark A, floating disable';
cmd '[id=' . $B->{id} . '] mark B, floating enable'; cmd '[id=' . $B->{id} . '] mark B, floating enable';
sync_with_i3;
@nodes = @{get_ws($tmp)->{nodes}}; @nodes = @{get_ws($tmp)->{nodes}};
cmp_ok(@nodes, '==', 1, 'one tiling container on this workspace'); cmp_ok(@nodes, '==', 1, 'one tiling container on this workspace');
@@ -117,7 +115,6 @@ is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'floating' ], "mark set for 'floatin
cmd '[tiling_from="auto" con_mark="tiling"] mark --add tiling_auto'; cmd '[tiling_from="auto" con_mark="tiling"] mark --add tiling_auto';
cmd '[floating_from="auto" con_mark="floating"] mark --add floating_auto'; cmd '[floating_from="auto" con_mark="floating"] mark --add floating_auto';
sync_with_i3;
@nodes = @{get_ws($tmp)->{nodes}}; @nodes = @{get_ws($tmp)->{nodes}};
cmp_ok(@nodes, '==', 1, 'one tiling container on this workspace'); cmp_ok(@nodes, '==', 1, 'one tiling container on this workspace');