#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
-#include <limits.h>
-#include <libgen.h>
#include "all.h"
+static pid_t configerror_pid = -1;
+
static Match current_match;
typedef struct yy_buffer_state *YY_BUFFER_STATE;
extern int yylex(struct context *context);
extern int yyparse(void);
+extern int yylex_destroy(void);
extern FILE *yyin;
YY_BUFFER_STATE yy_scan_string(const char *);
//int yydebug = 1;
void yyerror(const char *error_message) {
+ context->has_errors = true;
+
ELOG("\n");
ELOG("CONFIG: %s\n", error_message);
ELOG("CONFIG: in file \"%s\", line %d:\n",
context->filename, context->line_number);
ELOG("CONFIG: %s\n", context->line_copy);
- ELOG("CONFIG: ");
+ char buffer[context->last_column+1];
+ buffer[context->last_column] = '\0';
for (int c = 1; c <= context->last_column; c++)
- if (c >= context->first_column)
- printf("^");
- else printf(" ");
- printf("\n");
+ buffer[c-1] = (c >= context->first_column ? '^' : ' ');
+ ELOG("CONFIG: %s\n", buffer);
ELOG("\n");
}
/* check for some v4-only statements */
if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 ||
+ strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 ||
strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 ||
strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) {
printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line);
}
/*
- * Calls i3-migrate-config-to-v4.pl to migrate a configuration file (input
+ * Calls i3-migrate-config-to-v4 to migrate a configuration file (input
* buffer).
*
* Returns the converted config file or NULL if there was an error (for
close(readpipe[0]);
dup2(readpipe[1], 1);
- /* start the migration script, search PATH first */
- char *migratepath = "i3-migrate-config-to-v4.pl";
- execlp(migratepath, migratepath, NULL);
-
- /* if the script is not in path, maybe the user installed to a strange
- * location and runs the i3 binary with an absolute path. We use
- * argv[0]’s dirname */
- char *pathbuf = strdup(start_argv[0]);
- char *dir = dirname(pathbuf);
- asprintf(&migratepath, "%s/%s", dir, "i3-migrate-config-to-v4.pl");
- execlp(migratepath, migratepath, NULL);
-
-#if defined(__linux__)
- /* on linux, we have one more fall-back: dirname(/proc/self/exe) */
- char buffer[BUFSIZ];
- if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) {
- warn("could not read /proc/self/exe");
- exit(1);
- }
- dir = dirname(buffer);
- asprintf(&migratepath, "%s/%s", dir, "i3-migrate-config-to-v4.pl");
- execlp(migratepath, migratepath, NULL);
-#endif
-
- warn("Could not start i3-migrate-config-to-v4.pl");
- exit(2);
+ static char *argv[] = {
+ NULL, /* will be replaced by the executable path */
+ NULL
+ };
+ exec_i3_utility("i3-migrate-config-to-v4", argv);
}
/* parent */
return converted;
}
+/*
+ * Handler which will be called when we get a SIGCHLD for the nagbar, meaning
+ * it exited (or could not be started, depending on the exit code).
+ *
+ */
+static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
+ ev_child_stop(EV_A_ watcher);
+ if (!WIFEXITED(watcher->rstatus)) {
+ fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
+ return;
+ }
+
+ int exitcode = WEXITSTATUS(watcher->rstatus);
+ printf("i3-nagbar process exited with status %d\n", exitcode);
+ if (exitcode == 2) {
+ fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
+ }
+
+ configerror_pid = -1;
+}
+
+/*
+ * Starts an i3-nagbar process which alerts the user that his configuration
+ * file contains one or more errors. Also offers two buttons: One to launch an
+ * $EDITOR on the config file and another one to launch a $PAGER on the error
+ * logfile.
+ *
+ */
+static void start_configerror_nagbar(const char *config_path) {
+ fprintf(stderr, "Would start i3-nagscreen now\n");
+ configerror_pid = fork();
+ if (configerror_pid == -1) {
+ warn("Could not fork()");
+ return;
+ }
+
+ /* child */
+ if (configerror_pid == 0) {
+ char *editaction,
+ *pageraction;
+ if (asprintf(&editaction, TERM_EMU " -e sh -c \"${EDITOR:-vi} \"%s\" && i3-msg reload\"", config_path) == -1)
+ exit(1);
+ if (asprintf(&pageraction, TERM_EMU " -e sh -c \"${PAGER:-less} \"%s\"\"", errorfilename) == -1)
+ exit(1);
+ char *argv[] = {
+ NULL, /* will be replaced by the executable path */
+ "-m",
+ "You have an error in your i3 config file!",
+ "-b",
+ "edit config",
+ editaction,
+ (errorfilename ? "-b" : NULL),
+ "show errors",
+ pageraction,
+ NULL
+ };
+ exec_i3_utility("i3-nagbar", argv);
+ }
+
+ /* parent */
+ /* install a child watcher */
+ ev_child *child = smalloc(sizeof(ev_child));
+ ev_child_init(child, &nagbar_exited, configerror_pid, 0);
+ ev_child_start(main_loop, child);
+}
+
+/*
+ * Kills the configerror i3-nagbar process, if any.
+ *
+ * Called when reloading/restarting.
+ *
+ * If wait_for_it is set (restarting), this function will waitpid(), otherwise,
+ * ev is assumed to handle it (reloading).
+ *
+ */
+void kill_configerror_nagbar(bool wait_for_it) {
+ if (configerror_pid == -1)
+ return;
+
+ if (kill(configerror_pid, SIGTERM) == -1)
+ warn("kill(configerror_nagbar) failed");
+
+ if (!wait_for_it)
+ return;
+
+ /* When restarting, we don’t enter the ev main loop anymore and after the
+ * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD
+ * for us and we would end up with a <defunct> process. Therefore we
+ * waitpid() here. */
+ waitpid(configerror_pid, NULL, 0);
+}
+
+/*
+ * Checks for duplicate key bindings (the same keycode or keysym is configured
+ * more than once). If a duplicate binding is found, a message is printed to
+ * stderr and the has_errors variable is set to true, which will start
+ * i3-nagbar.
+ *
+ */
+static void check_for_duplicate_bindings(struct context *context) {
+ Binding *bind, *current;
+ TAILQ_FOREACH(current, bindings, bindings) {
+ TAILQ_FOREACH(bind, bindings, bindings) {
+ /* Abort when we reach the current keybinding, only check the
+ * bindings before */
+ if (bind == current)
+ break;
+
+ /* Check if one is using keysym while the other is using bindsym.
+ * If so, skip. */
+ /* XXX: It should be checked at a later place (when translating the
+ * keysym to keycodes) if there are any duplicates */
+ if ((bind->symbol == NULL && current->symbol != NULL) ||
+ (bind->symbol != NULL && current->symbol == NULL))
+ continue;
+
+ /* If bind is NULL, current has to be NULL, too (see above).
+ * If the keycodes differ, it can't be a duplicate. */
+ if (bind->symbol != NULL &&
+ strcasecmp(bind->symbol, current->symbol) != 0)
+ continue;
+
+ /* Check if the keycodes or modifiers are different. If so, they
+ * can't be duplicate */
+ if (bind->keycode != current->keycode ||
+ bind->mods != current->mods)
+ continue;
+ context->has_errors = true;
+ if (current->keycode != 0) {
+ ELOG("Duplicate keybinding in config file:\n modmask %d with keycode %d, command \"%s\"\n",
+ current->mods, current->keycode, current->command);
+ } else {
+ ELOG("Duplicate keybinding in config file:\n modmask %d with keysym %s, command \"%s\"\n",
+ current->mods, current->symbol, current->command);
+ }
+ }
+ }
+}
+
void parse_file(const char *f) {
SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
int fd, ret, read_bytes = 0;
printf("\n");
printf("Please convert your config file to v4. You can use this command:\n");
printf(" mv %s %s.O\n", f, f);
- printf(" i3-migrate-config-to-v4.pl %s.O > %s\n", f, f);
+ printf(" i3-migrate-config-to-v4 %s.O > %s\n", f, f);
printf("****************************************************************\n");
printf("\n");
free(new);
} else {
printf("\n");
printf("**********************************************************************\n");
- printf("ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4.pl\n");
+ printf("ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4\n");
printf("was not correctly installed on your system?\n");
printf("**********************************************************************\n");
printf("\n");
exit(1);
}
+ check_for_duplicate_bindings(context);
+
+ if (context->has_errors) {
+ start_configerror_nagbar(f);
+ }
+
+ yylex_destroy();
FREE(context->line_copy);
free(context);
free(new);
%token TOKIPCSOCKET "ipc_socket"
%token TOKRESTARTSTATE "restart_state"
%token TOKEXEC "exec"
+%token TOKEXEC_ALWAYS "exec_always"
%token <single_color> TOKSINGLECOLOR
%token <color> TOKCOLOR
%token TOKARROW "→"
%token TOK_MARK "mark"
%token TOK_CLASS "class"
+%token TOK_INSTANCE "instance"
%token TOK_ID "id"
%token TOK_CON_ID "con_id"
%token TOK_TITLE "title"
| ipcsocket
| restart_state
| exec
+ | exec_always
| single_color
| color
| terminal
;
criteria:
+ criteria criterion
+ | criterion
+ ;
+
+criterion:
TOK_CLASS '=' STR
{
printf("criteria: class = %s\n", $3);
current_match.class = $3;
}
+ | TOK_INSTANCE '=' STR
+ {
+ printf("criteria: instance = %s\n", $3);
+ current_match.instance = $3;
+ }
| TOK_CON_ID '=' STR
{
printf("criteria: id = %s\n", $3);
/* Compatibility with older versions: If the assignment target starts
* with ~, we create the equivalent of:
*
- * for_window [class="foo"] mode floating
+ * for_window [class="foo"] floating enable
*/
if (*workspace == '~') {
workspace++;
}
;
+exec_always:
+ TOKEXEC_ALWAYS STR
+ {
+ struct Autostart *new = smalloc(sizeof(struct Autostart));
+ new->command = $2;
+ TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always);
+ }
+ ;
+
terminal:
TOKTERMINAL STR
{
{
config.font = load_font($2, true);
printf("font %s\n", $2);
+ free($2);
}
;
char *hex;
if (asprintf(&hex, "#%s", $2) == -1)
die("asprintf()");
+ free($2);
$$ = get_colorpixel(hex);
free(hex);
}