From af30a1b67c22aa54dad4e1a0ee3aacb537c4ab92 Mon Sep 17 00:00:00 2001 From: "S. Christoffer Eliesen" Date: Thu, 22 Oct 2015 14:14:13 +0200 Subject: [PATCH] ipc,commands,config: Replace cmd_status enum with cmd_results struct. In i3 the ipc reply will contain a human readable error message, and this patch replicates that behaviour. However, that error message is also useful for logging, which this patch takes advantage of. E.g. instead of logging errors directly in commands.c/checkargs, it is fed back to the caller which eventually ends up logging everything with maximum context available (config.c/read_config). So instead of logging e.g. "Error on line 'exit'" it will now log: "Error on line 'exit': Can't execute from config." --- include/commands.h | 20 +- sway/commands.c | 506 ++++++++++++++++++++++++++------------------- sway/config.c | 6 +- sway/handlers.c | 15 +- sway/ipc.c | 8 +- 5 files changed, 333 insertions(+), 222 deletions(-) diff --git a/include/commands.h b/include/commands.h index 1b4cd9ca..1e0a1452 100644 --- a/include/commands.h +++ b/include/commands.h @@ -1,22 +1,34 @@ #ifndef _SWAY_COMMANDS_H #define _SWAY_COMMANDS_H #include +#include #include "config.h" enum cmd_status { CMD_SUCCESS, - CMD_FAILURE, - CMD_INVALID, + CMD_FAILURE, // was or at least could be executed + CMD_INVALID, // unknown or parse error CMD_DEFER, // Config Blocks CMD_BLOCK_END, CMD_BLOCK_MODE, }; -enum cmd_status handle_command(char *command); +struct cmd_results { + enum cmd_status status; + + const char *input; + char *error; +}; + +struct cmd_results *handle_command(char *command); // Handles commands during config -enum cmd_status config_command(char *command); +struct cmd_results *config_command(char *command); + +struct cmd_results *cmd_results_new(enum cmd_status status, const char* input, const char *error, ...); +void free_cmd_results(struct cmd_results *results); +const char *cmd_results_to_json(struct cmd_results *results); void remove_view_from_scratchpad(); diff --git a/sway/commands.c b/sway/commands.c index a9c20e51..4b11886e 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -21,7 +21,7 @@ #include "sway.h" #include "resize.h" -typedef enum cmd_status sway_cmd(int argc, char **argv); +typedef struct cmd_results *sway_cmd(int argc, char **argv); struct cmd_handler { char *command; @@ -81,41 +81,43 @@ enum expected_args { EXPECTED_EQUAL_TO }; -static bool checkarg(int argc, const char *name, enum expected_args type, int val) { +// Returns error object, or NULL if check succeeds. +static struct cmd_results *checkarg(int argc, const char *name, enum expected_args type, int val) { + struct cmd_results *error = NULL; switch (type) { case EXPECTED_MORE_THAN: if (argc > val) { - return true; + return NULL; } - sway_log(L_ERROR, "Invalid %s command." + error = cmd_results_new(CMD_INVALID, name, "Invalid %s command " "(expected more than %d argument%s, got %d)", name, val, (char*[2]){"s", ""}[argc==1], argc); break; case EXPECTED_AT_LEAST: if (argc >= val) { - return true; + return NULL; } - sway_log(L_ERROR, "Invalid %s command." + error = cmd_results_new(CMD_INVALID, name, "Invalid %s command " "(expected at least %d argument%s, got %d)", name, val, (char*[2]){"s", ""}[argc==1], argc); break; case EXPECTED_LESS_THAN: if (argc < val) { - return true; + return NULL; }; - sway_log(L_ERROR, "Invalid %s command." + error = cmd_results_new(CMD_INVALID, name, "Invalid %s command " "(expected less than %d argument%s, got %d)", name, val, (char*[2]){"s", ""}[argc==1], argc); break; case EXPECTED_EQUAL_TO: if (argc == val) { - return true; + return NULL; }; - sway_log(L_ERROR, "Invalid %s command." + error = cmd_results_new(CMD_INVALID, name, "Invalid %s command " "(expected %d arguments, got %d)", name, val, argc); break; } - return false; + return error; } static int bindsym_sort(const void *_lbind, const void *_rbind) { @@ -131,11 +133,13 @@ static int bindsym_sort(const void *_lbind, const void *_rbind) { return (rbind->keys->length + rmod) - (lbind->keys->length + lmod); } -static enum cmd_status cmd_bindsym(int argc, char **argv) { - if (!checkarg(argc, "bindsym", EXPECTED_MORE_THAN, 1) - || !config->reading) { - return CMD_FAILURE; - }; +static struct cmd_results *cmd_bindsym(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "bindsym", EXPECTED_MORE_THAN, 1))) { + return error; + } else if (!config->reading) { + return cmd_results_new(CMD_FAILURE, "bindsym", "Can only be used in config file."); + } struct sway_binding *binding = malloc(sizeof(struct sway_binding)); binding->keys = create_list(); @@ -159,12 +163,12 @@ static enum cmd_status cmd_bindsym(int argc, char **argv) { // Check for xkb key xkb_keysym_t sym = xkb_keysym_from_name(split->items[i], XKB_KEYSYM_CASE_INSENSITIVE); if (!sym) { - sway_log(L_ERROR, "bindsym - unknown key %s", (char *)split->items[i]); + error = cmd_results_new(CMD_INVALID, "bindsym", "Unknown key '%s'", (char *)split->items[i]); list_free(binding->keys); free(binding->command); free(binding); list_free(split); - return CMD_FAILURE; + return error; } xkb_keysym_t *key = malloc(sizeof(xkb_keysym_t)); *key = sym; @@ -178,21 +182,21 @@ static enum cmd_status cmd_bindsym(int argc, char **argv) { list_sort(mode->bindings, bindsym_sort); sway_log(L_DEBUG, "bindsym - Bound %s to command %s", argv[0], binding->command); - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_exec_always(int argc, char **argv) { - if (!config->active) return CMD_DEFER; - if (!checkarg(argc, "exec_always", EXPECTED_MORE_THAN, 0)) { - return CMD_FAILURE; +static struct cmd_results *cmd_exec_always(int argc, char **argv) { + struct cmd_results *error = NULL; + if (!config->active) return cmd_results_new(CMD_DEFER, NULL, NULL); + if ((error = checkarg(argc, "exec_always", EXPECTED_MORE_THAN, 0))) { + return error; } char *tmp = NULL; if (strcmp((char*)*argv, "--no-startup-id") == 0) { sway_log(L_INFO, "exec switch '--no-startup-id' not supported, ignored."); - - if (!checkarg(argc - 1, "exec_always", EXPECTED_MORE_THAN, 0)) { - return CMD_FAILURE; + if ((error = checkarg(argc - 1, "exec_always", EXPECTED_MORE_THAN, 0))) { + return error; } tmp = join_args(argv + 1, argc - 1); @@ -218,22 +222,20 @@ static enum cmd_status cmd_exec_always(int argc, char **argv) { // Close child process _exit(0); } else if (pid < 0) { - sway_log(L_ERROR, "exec command failed, sway could not fork"); - return CMD_FAILURE; + return cmd_results_new(CMD_FAILURE, "exec_always", "Command failed (sway could not fork)."); } // cleanup child process wait(0); - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_exec(int argc, char **argv) { - if (!config->active) return CMD_DEFER; - +static struct cmd_results *cmd_exec(int argc, char **argv) { + if (!config->active) return cmd_results_new(CMD_DEFER, "exec", NULL); if (config->reloading) { char *args = join_args(argv, argc); sway_log(L_DEBUG, "Ignoring 'exec %s' due to reload", args); free(args); - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } return cmd_exec_always(argc, argv); } @@ -244,28 +246,30 @@ static void kill_views(swayc_t *container, void *data) { } } -static enum cmd_status cmd_exit(int argc, char **argv) { - if (config->reading) return CMD_INVALID; - if (!checkarg(argc, "exit", EXPECTED_EQUAL_TO, 0)) { - return CMD_FAILURE; +static struct cmd_results *cmd_exit(int argc, char **argv) { + struct cmd_results *error = NULL; + if (config->reading) return cmd_results_new(CMD_FAILURE, "exit", "Can't be used in config file."); + if ((error = checkarg(argc, "exit", EXPECTED_EQUAL_TO, 0))) { + return error; } // Close all views container_map(&root_container, kill_views, NULL); sway_terminate(); - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_floating(int argc, char **argv) { - if (config->reading) return CMD_INVALID; - if (!checkarg(argc, "floating", EXPECTED_EQUAL_TO, 1)) { - return CMD_FAILURE; +static struct cmd_results *cmd_floating(int argc, char **argv) { + struct cmd_results *error = NULL; + if (config->reading) return cmd_results_new(CMD_FAILURE, "floating", "Can't be used in config file."); + if ((error = checkarg(argc, "floating", EXPECTED_EQUAL_TO, 1))) { + return error; } if (strcasecmp(argv[0], "toggle") == 0) { swayc_t *view = get_focused_container(&root_container); // Prevent running floating commands on things like workspaces if (view->type != C_VIEW) { - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } // Change from nonfloating to floating if (!view->is_floating) { @@ -314,13 +318,14 @@ static enum cmd_status cmd_floating(int argc, char **argv) { } set_focused_container(view); } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_floating_mod(int argc, char **argv) { - if (!config->reading) return CMD_INVALID; - if (!checkarg(argc, "floating_modifier", EXPECTED_EQUAL_TO, 1)) { - return CMD_FAILURE; +static struct cmd_results *cmd_floating_mod(int argc, char **argv) { + struct cmd_results *error = NULL; + if (!config->reading) return cmd_results_new(CMD_FAILURE, "floating_modifier", "Can only be used in config file."); + if ((error = checkarg(argc, "floating_modifier", EXPECTED_EQUAL_TO, 1))) { + return error; } int i, j; list_t *split = split_string(argv[0], "+"); @@ -336,19 +341,20 @@ static enum cmd_status cmd_floating_mod(int argc, char **argv) { } free_flat_list(split); if (!config->floating_mod) { - sway_log(L_ERROR, "bindsym - unknown keys %s", argv[0]); - return CMD_FAILURE; + error = cmd_results_new(CMD_INVALID, "floating_modifier", "Unknown keys %s", argv[0]); + return error; } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_focus(int argc, char **argv) { - if (config->reading) return CMD_INVALID; +static struct cmd_results *cmd_focus(int argc, char **argv) { + if (config->reading) return cmd_results_new(CMD_FAILURE, "focus", "Can't be used in config file."); + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "focus", EXPECTED_EQUAL_TO, 1))) { + return error; + } static int floating_toggled_index = 0; static int tiled_toggled_index = 0; - if (!checkarg(argc, "focus", EXPECTED_EQUAL_TO, 1)) { - return CMD_FAILURE; - } if (strcasecmp(argv[0], "left") == 0) { move_focus(MOVE_LEFT); } else if (strcasecmp(argv[0], "right") == 0) { @@ -397,26 +403,28 @@ static enum cmd_status cmd_focus(int argc, char **argv) { } } } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_focus_follows_mouse(int argc, char **argv) { - if (!config->reading) return CMD_INVALID; - if (!checkarg(argc, "focus_follows_mouse", EXPECTED_EQUAL_TO, 1)) { - return CMD_FAILURE; +static struct cmd_results *cmd_focus_follows_mouse(int argc, char **argv) { + struct cmd_results *error = NULL; + if (!config->reading) return cmd_results_new(CMD_FAILURE, "focus_follows_mouse", "Can only be used in config file."); + if ((error = checkarg(argc, "focus_follows_mouse", EXPECTED_EQUAL_TO, 1))) { + return error; } config->focus_follows_mouse = !strcasecmp(argv[0], "yes"); - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_seamless_mouse(int argc, char **argv) { - if (!checkarg(argc, "seamless_mouse", EXPECTED_EQUAL_TO, 1)) { - return CMD_FAILURE; +static struct cmd_results *cmd_seamless_mouse(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "seamless_mouse", EXPECTED_EQUAL_TO, 1))) { + return error; } config->seamless_mouse = (strcasecmp(argv[0], "on") == 0 || strcasecmp(argv[0], "yes") == 0); - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } static void hide_view_in_scratchpad(swayc_t *sp_view) { @@ -435,15 +443,17 @@ static void hide_view_in_scratchpad(swayc_t *sp_view) { set_focused_container(container_under_pointer()); } -static enum cmd_status cmd_mode(int argc, char **argv) { - if (!checkarg(argc, "mode", EXPECTED_AT_LEAST, 1)) { - return CMD_FAILURE; +static struct cmd_results *cmd_mode(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "mode", EXPECTED_AT_LEAST, 1))) { + return error; } char *mode_name = join_args(argv, argc); int mode_len = strlen(mode_name); bool mode_make = mode_name[mode_len-1] == '{'; if (mode_make) { - if (!config->reading) return CMD_INVALID; + if (!config->reading) + return cmd_results_new(CMD_FAILURE, "mode", "Can only be used in config file."); // Trim trailing spaces do { mode_name[--mode_len] = 0; @@ -467,9 +477,9 @@ static enum cmd_status cmd_mode(int argc, char **argv) { list_add(config->modes, mode); } if (!mode) { - sway_log(L_ERROR, "Unknown mode `%s'", mode_name); + error = cmd_results_new(CMD_INVALID, "mode", "Unknown mode `%s'", mode_name); free(mode_name); - return CMD_FAILURE; + return error; } if ((config->reading && mode_make) || (!config->reading && !mode_make)) { sway_log(L_DEBUG, "Switching to mode `%s'",mode->name); @@ -477,15 +487,15 @@ static enum cmd_status cmd_mode(int argc, char **argv) { free(mode_name); // Set current mode config->current_mode = mode; - return mode_make ? CMD_BLOCK_MODE : CMD_SUCCESS; + return cmd_results_new(mode_make ? CMD_BLOCK_MODE : CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_move(int argc, char **argv) { - if (config->reading) return CMD_FAILURE; - if (!checkarg(argc, "move", EXPECTED_AT_LEAST, 1)) { - return CMD_FAILURE; +static struct cmd_results *cmd_move(int argc, char **argv) { + struct cmd_results *error = NULL; + if (config->reading) return cmd_results_new(CMD_FAILURE, "move", "Can't be used in config file."); + if ((error = checkarg(argc, "move", EXPECTED_AT_LEAST, 1))) { + return error; } - swayc_t *view = get_focused_container(&root_container); if (strcasecmp(argv[0], "left") == 0) { @@ -498,14 +508,14 @@ static enum cmd_status cmd_move(int argc, char **argv) { move_container(view, MOVE_DOWN); } else if (strcasecmp(argv[0], "container") == 0 || strcasecmp(argv[0], "window") == 0) { // "move container to workspace x" - if (!checkarg(argc, "move container/window", EXPECTED_EQUAL_TO, 4) - || strcasecmp(argv[1], "to") != 0 - || strcasecmp(argv[2], "workspace") != 0) { - return CMD_FAILURE; + if ((error = checkarg(argc, "move container/window", EXPECTED_EQUAL_TO, 4))) { + return error; + } else if ( strcasecmp(argv[1], "to") != 0 || strcasecmp(argv[2], "workspace") != 0) { + return cmd_results_new(CMD_INVALID, "move", "Expected 'move %s to workspace '", argv[0]); } if (view->type != C_CONTAINER && view->type != C_VIEW) { - return CMD_FAILURE; + return cmd_results_new(CMD_FAILURE, "move", "Can only move containers and views."); } const char *ws_name = argv[3]; @@ -521,7 +531,7 @@ static enum cmd_status cmd_move(int argc, char **argv) { move_container_to(view, get_focused_container(ws)); } else if (strcasecmp(argv[0], "scratchpad") == 0) { if (view->type != C_CONTAINER && view->type != C_VIEW) { - return CMD_FAILURE; + return cmd_results_new(CMD_FAILURE, "move", "Can only move containers and views."); } swayc_t *view = get_focused_container(&root_container); int i; @@ -529,7 +539,7 @@ static enum cmd_status cmd_move(int argc, char **argv) { if (scratchpad->items[i] == view) { hide_view_in_scratchpad(view); sp_view = NULL; - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } } list_add(scratchpad, view); @@ -546,17 +556,18 @@ static enum cmd_status cmd_move(int argc, char **argv) { } set_focused_container(focused); } else { - return CMD_FAILURE; + return cmd_results_new(CMD_INVALID, "move", + "Expected 'move ' or 'move to workspace '"); } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_orientation(int argc, char **argv) { - if (!config->reading) return CMD_FAILURE; - if (!checkarg(argc, "orientation", EXPECTED_EQUAL_TO, 1)) { - return CMD_FAILURE; +static struct cmd_results *cmd_orientation(int argc, char **argv) { + struct cmd_results *error = NULL; + if (!config->reading) return cmd_results_new(CMD_FAILURE, "orientation", "Can only be used in config file."); + if ((error = checkarg(argc, "orientation", EXPECTED_EQUAL_TO, 1))) { + return error; } - if (strcasecmp(argv[0], "horizontal") == 0) { config->default_orientation = L_HORIZ; } else if (strcasecmp(argv[0], "vertical") == 0) { @@ -564,16 +575,16 @@ static enum cmd_status cmd_orientation(int argc, char **argv) { } else if (strcasecmp(argv[0], "auto") == 0) { // Do nothing } else { - return CMD_FAILURE; + return cmd_results_new(CMD_INVALID, "orientation", "Expected 'orientation '"); } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_output(int argc, char **argv) { - if (!checkarg(argc, "output", EXPECTED_AT_LEAST, 1)) { - return CMD_FAILURE; +static struct cmd_results *cmd_output(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "output", EXPECTED_AT_LEAST, 1))) { + return error; } - struct output_config *output = calloc(1, sizeof(struct output_config)); output->x = output->y = output->width = output->height = -1; output->name = strdup(argv[0]); @@ -644,12 +655,13 @@ static enum cmd_status cmd_output(int argc, char **argv) { } } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_gaps(int argc, char **argv) { - if (!checkarg(argc, "gaps", EXPECTED_AT_LEAST, 1)) { - return CMD_FAILURE; +static struct cmd_results *cmd_gaps(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "gaps", EXPECTED_AT_LEAST, 1))) { + return error; } const char *amount_str = argv[0]; // gaps amount @@ -657,7 +669,7 @@ static enum cmd_status cmd_gaps(int argc, char **argv) { int amount = (int)strtol(amount_str, NULL, 10); if (errno == ERANGE || amount == 0) { errno = 0; - return CMD_FAILURE; + return cmd_results_new(CMD_INVALID, "gaps", "Number is out out of range."); } if (config->gaps_inner == 0) { config->gaps_inner = amount; @@ -665,14 +677,14 @@ static enum cmd_status cmd_gaps(int argc, char **argv) { if (config->gaps_outer == 0) { config->gaps_outer = amount; } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } // gaps inner|outer n else if (argc >= 2 && isdigit((amount_str = argv[1])[0])) { int amount = (int)strtol(amount_str, NULL, 10); if (errno == ERANGE || amount == 0) { errno = 0; - return CMD_FAILURE; + return cmd_results_new(CMD_INVALID, "gaps", "Number is out out of range."); } const char *target_str = argv[0]; if (strcasecmp(target_str, "inner") == 0) { @@ -680,11 +692,11 @@ static enum cmd_status cmd_gaps(int argc, char **argv) { } else if (strcasecmp(target_str, "outer") == 0) { config->gaps_outer = amount; } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } // gaps inner|outer current|all set|plus|minus n if (argc < 4 || config->reading) { - return CMD_FAILURE; + return cmd_results_new(CMD_INVALID, "gaps", "Expected 'gaps '"); } // gaps inner|outer ... const char *inout_str = argv[0]; @@ -694,7 +706,7 @@ static enum cmd_status cmd_gaps(int argc, char **argv) { } else if (strcasecmp(inout_str, "outer") == 0) { inout = OUTER; } else { - return CMD_FAILURE; + return cmd_results_new(CMD_INVALID, "gaps", "Expected 'gaps '"); } // gaps ... current|all ... @@ -712,7 +724,7 @@ static enum cmd_status cmd_gaps(int argc, char **argv) { target = WORKSPACE; } } else { - return CMD_FAILURE; + return cmd_results_new(CMD_INVALID, "gaps", "Expected 'gaps '"); } // gaps ... n @@ -720,7 +732,7 @@ static enum cmd_status cmd_gaps(int argc, char **argv) { int amount = (int)strtol(amount_str, NULL, 10); if (errno == ERANGE || amount == 0) { errno = 0; - return CMD_FAILURE; + return cmd_results_new(CMD_INVALID, "gaps", "Number is out out of range."); } // gaps ... set|plus|minus ... @@ -734,18 +746,18 @@ static enum cmd_status cmd_gaps(int argc, char **argv) { method = ADD; amount *= -1; } else { - return CMD_FAILURE; + return cmd_results_new(CMD_INVALID, "gaps", "Expected 'gaps '"); } if (target == CURRENT) { swayc_t *cont; if (inout == OUTER) { if ((cont = swayc_active_workspace()) == NULL) { - return CMD_FAILURE; + return cmd_results_new(CMD_FAILURE, "gaps", "There's no active workspace."); } } else { if ((cont = get_focused_view(&root_container))->type != C_VIEW) { - return CMD_FAILURE; + return cmd_results_new(CMD_FAILURE, "gaps", "Currently focused item is not a view."); } } cont->gaps = swayc_gap(cont); @@ -775,7 +787,7 @@ static enum cmd_status cmd_gaps(int argc, char **argv) { swayc_t *top; if (target == WORKSPACE) { if ((top = swayc_active_workspace()) == NULL) { - return CMD_FAILURE; + return cmd_results_new(CMD_FAILURE, "gaps", "There's currently no active workspace."); } } else { top = &root_container; @@ -786,22 +798,24 @@ static enum cmd_status cmd_gaps(int argc, char **argv) { arrange_windows(top, -1, -1); } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_kill(int argc, char **argv) { - if (config->reading || !config->active) { - return CMD_FAILURE; - } +static struct cmd_results *cmd_kill(int argc, char **argv) { + if (config->reading) return cmd_results_new(CMD_FAILURE, "kill", "Can't be used in config file."); + if (!config->active) return cmd_results_new(CMD_FAILURE, "kill", "Can only be used when sway is running."); + swayc_t *view = get_focused_container(&root_container); wlc_view_close(view->handle); - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_layout(int argc, char **argv) { - if (!checkarg(argc, "layout", EXPECTED_MORE_THAN, 0) - || config->reading || !config->active) { - return CMD_FAILURE; +static struct cmd_results *cmd_layout(int argc, char **argv) { + struct cmd_results *error = NULL; + if (config->reading) return cmd_results_new(CMD_FAILURE, "layout", "Can't be used in config file."); + if (!config->active) return cmd_results_new(CMD_FAILURE, "layout", "Can only be used when sway is running."); + if ((error = checkarg(argc, "layout", EXPECTED_MORE_THAN, 0))) { + return error; } swayc_t *parent = get_focused_container(&root_container); while (parent->type == C_VIEW) { @@ -821,33 +835,38 @@ static enum cmd_status cmd_layout(int argc, char **argv) { } arrange_windows(parent, parent->width, parent->height); - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_reload(int argc, char **argv) { - if (!checkarg(argc, "reload", EXPECTED_EQUAL_TO, 0) - || config->reading - || !load_config(NULL)) { - return CMD_FAILURE; +static struct cmd_results *cmd_reload(int argc, char **argv) { + struct cmd_results *error = NULL; + if (config->reading) return cmd_results_new(CMD_FAILURE, "reload", "Can't be used in config file."); + if ((error = checkarg(argc, "reload", EXPECTED_EQUAL_TO, 0))) { + return error; } + if (!load_config(NULL)) return cmd_results_new(CMD_FAILURE, "reload", "Error(s) reloading config."); + arrange_windows(&root_container, -1, -1); - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_resize(int argc, char **argv) { - if (!checkarg(argc, "resize", EXPECTED_AT_LEAST, 3) - || config->reading || !config->active) { - return CMD_FAILURE; +static struct cmd_results *cmd_resize(int argc, char **argv) { + struct cmd_results *error = NULL; + if (config->reading) return cmd_results_new(CMD_FAILURE, "resize", "Can't be used in config file."); + if (!config->active) return cmd_results_new(CMD_FAILURE, "resize", "Can only be used when sway is running."); + if ((error = checkarg(argc, "resize", EXPECTED_AT_LEAST, 3))) { + return error; } char *end; int amount = (int)strtol(argv[2], &end, 10); if (errno == ERANGE || amount == 0) { errno = 0; - return CMD_FAILURE; + return cmd_results_new(CMD_INVALID, "resize", "Number is out of range."); } if (strcmp(argv[0], "shrink") != 0 && strcmp(argv[0], "grow") != 0) { - return CMD_FAILURE; + return cmd_results_new(CMD_INVALID, "resize", + "Expected 'resize '"); } if (strcmp(argv[0], "shrink") == 0) { @@ -859,9 +878,10 @@ static enum cmd_status cmd_resize(int argc, char **argv) { } else if (strcmp(argv[1], "height") == 0) { resize_tiled(amount, false); } else { - return CMD_FAILURE; + return cmd_results_new(CMD_INVALID, "resize", + "Expected 'resize '"); } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } static swayc_t *fetch_view_from_scratchpad() { @@ -909,10 +929,12 @@ void remove_view_from_scratchpad(swayc_t *view) { } } -static enum cmd_status cmd_scratchpad(int argc, char **argv) { - if (!checkarg(argc, "scratchpad", EXPECTED_EQUAL_TO, 1) - || config->reading || !config->active) { - return CMD_FAILURE; +static struct cmd_results *cmd_scratchpad(int argc, char **argv) { + struct cmd_results *error = NULL; + if (config->reading) return cmd_results_new(CMD_FAILURE, "scratchpad", "Can't be used in config file."); + if (!config->active) return cmd_results_new(CMD_FAILURE, "scratchpad", "Can only be used when sway is running."); + if ((error = checkarg(argc, "scratchpad", EXPECTED_EQUAL_TO, 1))) { + return error; } if (strcasecmp(argv[0], "show") == 0 && scratchpad->length > 0) { @@ -931,9 +953,9 @@ static enum cmd_status cmd_scratchpad(int argc, char **argv) { sp_view = NULL; } } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } - return CMD_FAILURE; + return cmd_results_new(CMD_FAILURE, "scratchpad", "Expected 'scratchpad show' when scratchpad is not empty."); } // sort in order of longest->shortest @@ -943,11 +965,13 @@ static int compare_set(const void *_l, const void *_r) { return strlen((*r)->name) - strlen((*l)->name); } -static enum cmd_status cmd_set(int argc, char **argv) { - if (!checkarg(argc, "set", EXPECTED_AT_LEAST, 2) - || !config->reading) { - return CMD_FAILURE; +static struct cmd_results *cmd_set(int argc, char **argv) { + struct cmd_results *error = NULL; + if (!config->reading) return cmd_results_new(CMD_FAILURE, "set", "Can only be used in config file."); + if ((error = checkarg(argc, "set", EXPECTED_AT_LEAST, 2))) { + return error; } + struct sway_variable *var = NULL; // Find old variable if it exists int i; @@ -967,21 +991,23 @@ static enum cmd_status cmd_set(int argc, char **argv) { list_sort(config->symbols, compare_set); } var->value = join_args(argv + 1, argc - 1); - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status _do_split(int argc, char **argv, int layout) { +static struct cmd_results *_do_split(int argc, char **argv, int layout) { char *name = layout == L_VERT ? "splitv" : layout == L_HORIZ ? "splith" : "split"; - if (!checkarg(argc, name, EXPECTED_EQUAL_TO, 0) - || config->reading || !config->active) { - return CMD_FAILURE; + struct cmd_results *error = NULL; + if (config->reading) return cmd_results_new(CMD_FAILURE, name, "Can't be used in config file."); + if (!config->active) return cmd_results_new(CMD_FAILURE, name, "Can only be used when sway is running."); + if ((error = checkarg(argc, name, EXPECTED_EQUAL_TO, 0))) { + return error; } swayc_t *focused = get_focused_container(&root_container); // Case of floating window, dont split if (focused->is_floating) { - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } /* Case that focus is on an workspace with 0/1 children.change its layout */ if (focused->type == C_WORKSPACE && focused->children->length <= 1) { @@ -999,56 +1025,61 @@ static enum cmd_status _do_split(int argc, char **argv, int layout) { set_focused_container(focused); arrange_windows(parent, -1, -1); } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_split(int argc, char **argv) { - if (!checkarg(argc, "split", EXPECTED_EQUAL_TO, 1) - || config->reading || !config->active) { - return CMD_FAILURE; +static struct cmd_results *cmd_split(int argc, char **argv) { + struct cmd_results *error = NULL; + if (config->reading) return cmd_results_new(CMD_FAILURE, "split", "Can't be used in config file."); + if (!config->active) return cmd_results_new(CMD_FAILURE, "split", "Can only be used when sway is running."); + if ((error = checkarg(argc, "split", EXPECTED_EQUAL_TO, 1))) { + return error; } - if (strcasecmp(argv[0], "v") == 0 || strcasecmp(argv[0], "vertical") == 0) { _do_split(argc - 1, argv + 1, L_VERT); } else if (strcasecmp(argv[0], "h") == 0 || strcasecmp(argv[0], "horizontal") == 0) { _do_split(argc - 1, argv + 1, L_HORIZ); } else { - sway_log(L_ERROR, "Invalid split command (expected either horiziontal or vertical)."); - return CMD_FAILURE; + error = cmd_results_new(CMD_FAILURE, "split", + "Invalid split command (expected either horiziontal or vertical)."); + return error; } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_splitv(int argc, char **argv) { +static struct cmd_results *cmd_splitv(int argc, char **argv) { return _do_split(argc, argv, L_VERT); } -static enum cmd_status cmd_splith(int argc, char **argv) { +static struct cmd_results *cmd_splith(int argc, char **argv) { return _do_split(argc, argv, L_HORIZ); } -static enum cmd_status cmd_log_colors(int argc, char **argv) { - if (!checkarg(argc, "log_colors", EXPECTED_EQUAL_TO, 1) - || !config->reading) { - return CMD_FAILURE; +static struct cmd_results *cmd_log_colors(int argc, char **argv) { + struct cmd_results *error = NULL; + if (!config->reading) return cmd_results_new(CMD_FAILURE, "log_colors", "Can only be used in config file."); + if ((error = checkarg(argc, "log_colors", EXPECTED_EQUAL_TO, 1))) { + return error; } if (strcasecmp(argv[0], "no") == 0) { sway_log_colors(0); } else if (strcasecmp(argv[0], "yes") == 0) { sway_log_colors(1); } else { - sway_log(L_ERROR, "Invalid log_colors command (expected `yes` or `no`, got '%s')", argv[0]); - return CMD_FAILURE; + error = cmd_results_new(CMD_FAILURE, "log_colors", + "Invalid log_colors command (expected `yes` or `no`, got '%s')", argv[0]); + return error; } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_fullscreen(int argc, char **argv) { - if (!checkarg(argc, "fullscreen", EXPECTED_AT_LEAST, 0) - || config->reading || !config->active) { - return CMD_FAILURE; +static struct cmd_results *cmd_fullscreen(int argc, char **argv) { + struct cmd_results *error = NULL; + if (config->reading) return cmd_results_new(CMD_FAILURE, "fullscreen", "Can't be used in config file."); + if (!config->active) return cmd_results_new(CMD_FAILURE, "fullscreen", "Can only be used when sway is running."); + if ((error = checkarg(argc, "fullscreen", EXPECTED_AT_LEAST, 0))) { + return error; } - swayc_t *container = get_focused_view(&root_container); bool current = swayc_is_fullscreen(container); wlc_view_set_state(container->handle, WLC_BIT_FULLSCREEN, !current); @@ -1060,17 +1091,17 @@ static enum cmd_status cmd_fullscreen(int argc, char **argv) { // Only resize container when going into fullscreen arrange_windows(container, -1, -1); - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_workspace(int argc, char **argv) { - if (!checkarg(argc, "workspace", EXPECTED_AT_LEAST, 1)) { - return CMD_FAILURE; +static struct cmd_results *cmd_workspace(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "workspace", EXPECTED_AT_LEAST, 1))) { + return error; } - if (argc == 1) { if (config->reading || !config->active) { - return CMD_DEFER; + return cmd_results_new(CMD_DEFER, "workspace", NULL); } // Handle workspace next/prev swayc_t *ws = NULL; @@ -1096,8 +1127,8 @@ static enum cmd_status cmd_workspace(int argc, char **argv) { workspace_switch(ws); } else { if (strcasecmp(argv[1], "output") == 0) { - if (!checkarg(argc, "workspace", EXPECTED_EQUAL_TO, 3)) { - return CMD_FAILURE; + if ((error = checkarg(argc, "workspace", EXPECTED_EQUAL_TO, 3))) { + return error; } struct workspace_output *wso = calloc(1, sizeof(struct workspace_output)); sway_log(L_DEBUG, "Assigning workspace %s to output %s", argv[0], argv[2]); @@ -1109,21 +1140,22 @@ static enum cmd_status cmd_workspace(int argc, char **argv) { } } } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } -static enum cmd_status cmd_ws_auto_back_and_forth(int argc, char **argv) { - if (!checkarg(argc, "workspace_auto_back_and_forth", EXPECTED_EQUAL_TO, 1)) { - return CMD_FAILURE; +static struct cmd_results *cmd_ws_auto_back_and_forth(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "workspace_auto_back_and_forth", EXPECTED_EQUAL_TO, 1))) { + return error; } if (strcasecmp(argv[0], "yes") == 0) { config->auto_back_and_forth = true; } else if (strcasecmp(argv[0], "no") == 0) { config->auto_back_and_forth = false; } else { - return CMD_FAILURE; + return cmd_results_new(CMD_INVALID, "workspace_auto_back_and_forth", "Expected 'workspace_auto_back_and_forth '"); } - return CMD_SUCCESS; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } /* Keep alphabetized */ @@ -1171,8 +1203,11 @@ static struct cmd_handler *find_handler(char *line) { return res; } -enum cmd_status handle_command(char *_exec) { - enum cmd_status status = CMD_SUCCESS; +struct cmd_results *handle_command(char *_exec) { + // Even though this function will process multiple commands we will only + // return the last error, if any (for now). (Since we have access to an + // error string we could e.g. concatonate all errors there.) + struct cmd_results *results = NULL; char *exec = strdup(_exec); char *head = exec; char *cmdlist; @@ -1188,8 +1223,7 @@ enum cmd_status handle_command(char *_exec) { ++head; // TODO handle criteria } else { - sway_log(L_ERROR, "Unmatched ["); - status = CMD_INVALID; + results = cmd_results_new(CMD_INVALID, NULL, "Unmatched ["); } // Skip leading whitespace head += strspn(head, whitespace); @@ -1205,7 +1239,6 @@ enum cmd_status handle_command(char *_exec) { sway_log(L_INFO, "Ignoring empty command."); continue; } - sway_log(L_INFO, "Handling command '%s'", cmd); //TODO better handling of argv int argc; @@ -1214,37 +1247,54 @@ enum cmd_status handle_command(char *_exec) { strip_quotes(argv[1]); } struct cmd_handler *handler = find_handler(argv[0]); - enum cmd_status res = CMD_INVALID; - if (!handler - || (res = handler->handle(argc-1, argv+1)) != CMD_SUCCESS) { - sway_log(L_ERROR, "Command '%s' failed", cmd); + if (!handler) { + if (results) { + free_cmd_results(results); + } + results = cmd_results_new(CMD_INVALID, strdup(cmd), "Unknown/invalid command"); free_argv(argc, argv); - status = res; + goto cleanup; + } + struct cmd_results *res = handler->handle(argc-1, argv+1); + if (res->status != CMD_SUCCESS) { + free_argv(argc, argv); + if (results) { + free_cmd_results(results); + } + results = res; goto cleanup; } free_argv(argc, argv); + free_cmd_results(res); } while(cmdlist); } while(head); cleanup: free(exec); - return status; + if (!results) { + results = cmd_results_new(CMD_SUCCESS, NULL, NULL); + } + return results; } -enum cmd_status config_command(char *exec) { - enum cmd_status status = CMD_SUCCESS; +struct cmd_results *config_command(char *exec) { + struct cmd_results *results = NULL; int argc; char **argv = split_args(exec, &argc); - if (!argc) goto cleanup; + if (!argc) { + results = cmd_results_new(CMD_SUCCESS, NULL, NULL); + goto cleanup; + } sway_log(L_INFO, "handling config command '%s'", exec); // Endblock if (**argv == '}') { - status = CMD_BLOCK_END; + results = cmd_results_new(CMD_BLOCK_END, NULL, NULL); goto cleanup; } struct cmd_handler *handler = find_handler(argv[0]); if (!handler) { - status = CMD_INVALID; + char *input = argv[0] ? strdup(argv[0]) : "(empty)"; + results = cmd_results_new(CMD_INVALID, input, "Unknown/invalid command"); goto cleanup; } int i; @@ -1257,8 +1307,44 @@ enum cmd_status config_command(char *exec) { if (argc>1 && (*argv[1] == '\"' || *argv[1] == '\'')) { strip_quotes(argv[1]); } - status = handler->handle(argc-1, argv+1); + results = handler->handle(argc-1, argv+1); cleanup: free_argv(argc, argv); - return status; + return results; +} + +struct cmd_results *cmd_results_new(enum cmd_status status, const char* input, const char *format, ...) { + struct cmd_results *results = malloc(sizeof(struct cmd_results)); + results->status = status; + results->input = input; // input is the command name + if (format) { + char *error = malloc(256); + va_list args; + va_start(args, format); + vsnprintf(error, 256, format, args); + va_end(args); + results->error = error; + } else { + results->error = NULL; + } + return results; +} + +void free_cmd_results(struct cmd_results *results) { + if (results->error) + free(results->error); + free(results); +} + +const char *cmd_results_to_json(struct cmd_results *results) { + json_object *root = json_object_new_object(); + json_object_object_add(root, "success", json_object_new_boolean(results->status == CMD_SUCCESS)); + if (results->input) + json_object_object_add(root, "input", json_object_new_string(results->input)); + if (results->error) + json_object_object_add(root, "error", json_object_new_string(results->error)); + + const char *json = json_object_to_json_string(root); + free(root); + return json; } diff --git a/sway/config.c b/sway/config.c index 67f8284c..7e0b22f9 100644 --- a/sway/config.c +++ b/sway/config.c @@ -234,10 +234,11 @@ bool read_config(FILE *file, bool is_active) { while (!feof(file)) { line = read_line(file); line = strip_comments(line); - switch(config_command(line)) { + struct cmd_results *res = config_command(line); + switch(res->status) { case CMD_FAILURE: case CMD_INVALID: - sway_log(L_ERROR, "Error on line '%s'", line); + sway_log(L_ERROR, "Error on line '%s': %s", line, res->error); success = false; break; @@ -270,6 +271,7 @@ bool read_config(FILE *file, bool is_active) { default:; } free(line); + free(res); } if (is_active) { diff --git a/sway/handlers.c b/sway/handlers.c index 6120e663..24105130 100644 --- a/sway/handlers.c +++ b/sway/handlers.c @@ -341,7 +341,11 @@ static bool handle_key(wlc_handle view, uint32_t time, const struct wlc_modifier } if (match) { if (state == WLC_KEY_STATE_PRESSED) { - handle_command(binding->command); + struct cmd_results *res = handle_command(binding->command); + if (res->status != CMD_SUCCESS) { + sway_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error); + } + free_cmd_results(res); return EVENT_HANDLED; } else if (state == WLC_KEY_STATE_RELEASED) { // TODO: --released @@ -544,8 +548,13 @@ static void handle_wlc_ready(void) { // Execute commands until there are none left config->active = true; while (config->cmd_queue->length) { - handle_command(config->cmd_queue->items[0]); - free(config->cmd_queue->items[0]); + char *line = config->cmd_queue->items[0]; + struct cmd_results *res = handle_command(line); + if (res->status != CMD_SUCCESS) { + sway_log(L_ERROR, "Error on line '%s': %s", line, res->error); + } + free_cmd_results(res); + free(line); list_del(config->cmd_queue, 0); } } diff --git a/sway/ipc.c b/sway/ipc.c index 1521e5cd..1134f1a2 100644 --- a/sway/ipc.c +++ b/sway/ipc.c @@ -222,10 +222,12 @@ void ipc_client_handle_command(struct ipc_client *client) { case IPC_COMMAND: { buf[client->payload_length] = '\0'; - bool success = (handle_command(buf) == CMD_SUCCESS); - char reply[64]; - int length = snprintf(reply, sizeof(reply), "{\"success\":%s}", success ? "true" : "false"); + struct cmd_results *results = handle_command(buf); + const char *json = cmd_results_to_json(results); + char reply[256]; + int length = snprintf(reply, sizeof(reply), "%s", json); ipc_send_reply(client, reply, (uint32_t) length); + free_cmd_results(results); break; } case IPC_GET_WORKSPACES: