From: Michael Stapelberg Date: Mon, 24 Dec 2012 14:46:57 +0000 (+0100) Subject: remove old cfgparse.{l,y} X-Git-Tag: 4.5~88 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=b304e6ad3496f36671befd8b9dbb9338427cf1fc;p=i3%2Fi3 remove old cfgparse.{l,y} --- diff --git a/src/cfgparse.l b/src/cfgparse.l deleted file mode 100644 index 6eef8a5a..00000000 --- a/src/cfgparse.l +++ /dev/null @@ -1,301 +0,0 @@ -/* - * vim:ts=4:sw=4:expandtab - * - */ -%option nounput -%option noinput -%option noyy_top_state -%option stack - -%{ -#include -#include -#include -#include - -#include "log.h" -#include "data.h" -#include "config.h" -#include "util.h" -#include "libi3.h" - -#include "cfgparse.tab.h" - -int yycolumn = 1; - -#define YY_DECL int yylex (struct context *context) - -#define YY_USER_ACTION { \ - context->first_column = yycolumn; \ - context->last_column = yycolumn+yyleng-1; \ - yycolumn += yyleng; \ -} - -/* macro to first eat whitespace, then expect a string */ -#define WS_STRING do { \ - yy_push_state(WANT_STRING); \ - yy_push_state(EAT_WHITESPACE); \ -} while (0) - -#define BAR_TRIPLE_COLOR do { \ - yy_push_state(BAR_COLOR); \ - yy_push_state(BAR_COLOR); \ - yy_push_state(BAR_COLOR); \ -} while (0) - -%} - -EOL (\r?\n) - -%s WANT_STRING -%s WANT_QSTRING -%s BINDSYM_COND -%s ASSIGN_COND -%s ASSIGN_TARGET_COND -%s COLOR_COND -%s OUTPUT_COND -%s FOR_WINDOW_COND -%s EAT_WHITESPACE -%s BORDER_WIDTH - -%x BUFFER_LINE -%x BAR -%x BAR_MODE -%x BAR_MODIFIER -%x BAR_POSITION -%x BAR_COLORS -%x BAR_COLOR - -%x EXEC -%x OPTRELEASE - -%% - - { - /* This is called when a new line is lexed. We only want the - * first line to match to go into state BUFFER_LINE */ - if (context->line_number == 0) { - context->line_number = 1; - BEGIN(INITIAL); - yy_push_state(BUFFER_LINE); - } - } - -^[^\r\n]*/{EOL}? { - /* save whole line */ - context->line_copy = sstrdup(yytext); - - yyless(0); - yy_pop_state(); - yy_set_bol(true); - yycolumn = 1; -} - - /* This part of the lexer handles the bar {} blocks */ -[ \t]+ { /* ignore whitespace */ ; } -"{" { return '{'; } -"}" { yy_pop_state(); return '}'; } -^[ \t]*#[^\n]* { return TOKCOMMENT; } -output { WS_STRING; return TOK_BAR_OUTPUT; } -tray_output { WS_STRING; return TOK_BAR_TRAY_OUTPUT; } -socket_path { WS_STRING; return TOK_BAR_SOCKET_PATH; } -mode { yy_push_state(BAR_MODE); return TOK_BAR_MODE; } -hide { yy_pop_state(); return TOK_BAR_HIDE; } -dock { yy_pop_state(); return TOK_BAR_DOCK; } -modifier { yy_push_state(BAR_MODIFIER); return TOK_BAR_MODIFIER; } -control { yy_pop_state(); return TOK_BAR_CONTROL; } -ctrl { yy_pop_state(); return TOK_BAR_CONTROL; } -shift { yy_pop_state(); return TOK_BAR_SHIFT; } -Mod1 { yy_pop_state(); return TOK_BAR_MOD1; } -Mod2 { yy_pop_state(); return TOK_BAR_MOD2; } -Mod3 { yy_pop_state(); return TOK_BAR_MOD3; } -Mod4 { yy_pop_state(); return TOK_BAR_MOD4; } -Mod5 { yy_pop_state(); return TOK_BAR_MOD5; } -position { yy_push_state(BAR_POSITION); return TOK_BAR_POSITION; } -bottom { yy_pop_state(); return TOK_BAR_BOTTOM; } -top { yy_pop_state(); return TOK_BAR_TOP; } -status_command { WS_STRING; return TOK_BAR_STATUS_COMMAND; } -i3bar_command { WS_STRING; return TOK_BAR_I3BAR_COMMAND; } -font { WS_STRING; return TOK_BAR_FONT; } -workspace_buttons { return TOK_BAR_WORKSPACE_BUTTONS; } -verbose { return TOK_BAR_VERBOSE; } -colors { yy_push_state(BAR_COLORS); return TOK_BAR_COLORS; } -"{" { return '{'; } -"}" { yy_pop_state(); return '}'; } -^[ \t]*#[^\n]* { return TOKCOMMENT; } -background { yy_push_state(BAR_COLOR); return TOK_BAR_COLOR_BACKGROUND; } -statusline { yy_push_state(BAR_COLOR); return TOK_BAR_COLOR_STATUSLINE; } -focused_workspace { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_FOCUSED_WORKSPACE; } -active_workspace { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_ACTIVE_WORKSPACE; } -inactive_workspace { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_INACTIVE_WORKSPACE; } -urgent_workspace { BAR_TRIPLE_COLOR; return TOK_BAR_COLOR_URGENT_WORKSPACE; } -#[0-9a-fA-F]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; } -{EOL} { - yy_pop_state(); - FREE(context->line_copy); - context->line_number++; - yy_push_state(BUFFER_LINE); - } -[a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; } - - - -"]" { yy_pop_state(); return ']'; } -"[" { - /* this is the case for the new assign syntax - * that uses criteria */ - yy_pop_state(); - yy_push_state(FOR_WINDOW_COND); - /* afterwards we will be in ASSIGN_TARGET_COND */ - return '['; - } -[ \t]* { yy_pop_state(); } -{EOL} { yy_pop_state(); } -{EOL} { yy_pop_state(); } -\"[^\"]+\" { - yy_pop_state(); - /* strip quotes */ - char *copy = sstrdup(yytext+1); - copy[strlen(copy)-1] = '\0'; - yylval.string = copy; - return STR; - } -[^\n]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return STR; } -[a-zA-Z0-9\/_-]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return OUTPUT; } -^[ \t]*#[^\n]* { return TOKCOMMENT; } -#[0-9a-fA-F]+ { yy_pop_state(); yylval.string = sstrdup(yytext); return HEXCOLOR; } -{EOL} { - yy_pop_state(); - FREE(context->line_copy); - context->line_number++; - yy_push_state(BUFFER_LINE); - } -[ \t]*→[ \t]* { BEGIN(WANT_STRING); } -[ \t]+ { BEGIN(WANT_STRING); } -[^\n][0-9]+ { printf("Border width set to: %s\n", yytext); yylval.number = atoi(yytext); return NUMBER;} ---no-startup-id { printf("no startup id\n"); yy_pop_state(); return TOK_NO_STARTUP_ID; } -. { printf("anything else: *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); } ---release { printf("--release\n"); yy_pop_state(); return TOK_RELEASE; } -. { printf("anything else (optrelease): *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); } -[0-9-]+ { yylval.number = atoi(yytext); return NUMBER; } -bar { yy_push_state(BAR); return TOK_BAR; } -mode { return TOKMODE; } -bind { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; } -bindcode { yy_push_state(WANT_STRING); yy_push_state(EAT_WHITESPACE); yy_push_state(EAT_WHITESPACE); yy_push_state(OPTRELEASE); yy_push_state(EAT_WHITESPACE); return TOKBINDCODE; } -bindsym { yy_push_state(BINDSYM_COND); yy_push_state(EAT_WHITESPACE); yy_push_state(OPTRELEASE); yy_push_state(EAT_WHITESPACE); return TOKBINDSYM; } -floating_maximum_size { return TOKFLOATING_MAXIMUM_SIZE; } -floating_minimum_size { return TOKFLOATING_MINIMUM_SIZE; } -floating_modifier { return TOKFLOATING_MODIFIER; } -workspace { return TOKWORKSPACE; } -output { yy_push_state(OUTPUT_COND); yy_push_state(EAT_WHITESPACE); return TOKOUTPUT; } -terminal { WS_STRING; return TOKTERMINAL; } -font { WS_STRING; return TOKFONT; } -assign { yy_push_state(ASSIGN_TARGET_COND); yy_push_state(ASSIGN_COND); return TOKASSIGN; } -set[^\n]* { return TOKCOMMENT; } -ipc-socket { WS_STRING; return TOKIPCSOCKET; } -ipc_socket { WS_STRING; return TOKIPCSOCKET; } -restart_state { WS_STRING; return TOKRESTARTSTATE; } -default_orientation { return TOK_ORIENTATION; } -horizontal { return TOK_HORIZ; } -vertical { return TOK_VERT; } -auto { return TOK_AUTO; } -workspace_layout { return TOK_WORKSPACE_LAYOUT; } -new_window { return TOKNEWWINDOW; } -new_float { return TOKNEWFLOAT; } -normal { yy_push_state(BORDER_WIDTH); return TOK_NORMAL; } -none { return TOK_NONE; } -1pixel { return TOK_1PIXEL; } -pixel { yy_push_state(BORDER_WIDTH); return TOK_PIXEL; } -hide_edge_borders { return TOK_HIDE_EDGE_BORDERS; } -both { return TOK_BOTH; } -focus_follows_mouse { return TOKFOCUSFOLLOWSMOUSE; } -force_focus_wrapping { return TOK_FORCE_FOCUS_WRAPPING; } -force_xinerama { return TOK_FORCE_XINERAMA; } -force-xinerama { return TOK_FORCE_XINERAMA; } -fake_outputs { WS_STRING; return TOK_FAKE_OUTPUTS; } -fake-outputs { WS_STRING; return TOK_FAKE_OUTPUTS; } -workspace_auto_back_and_forth { return TOK_WORKSPACE_AUTO_BAF; } -force_display_urgency_hint { return TOK_WORKSPACE_URGENCY_TIMER; } -ms { return TOK_TIME_MS; } -workspace_bar { return TOKWORKSPACEBAR; } -popup_during_fullscreen { return TOK_POPUP_DURING_FULLSCREEN; } -ignore { return TOK_IGNORE; } -leave_fullscreen { return TOK_LEAVE_FULLSCREEN; } -for_window { - /* Example: for_window [class="urxvt"] border none - * - * First, we wait for the ']' that finishes a match (FOR_WINDOW_COND) - * Then, we require a whitespace (EAT_WHITESPACE) - * And the rest of the line is parsed as a string - */ - yy_push_state(WANT_STRING); - yy_push_state(EAT_WHITESPACE); - yy_push_state(FOR_WINDOW_COND); - return TOK_FOR_WINDOW; - } -default { /* yylval.number = MODE_DEFAULT; */return TOK_DEFAULT; } -stacking { /* yylval.number = MODE_STACK; */return TOK_STACKING; } -stacked { return TOK_STACKING; } -tabbed { /* yylval.number = MODE_TABBED; */return TOK_TABBED; } -stack-limit { return TOKSTACKLIMIT; } -cols { /* yylval.number = STACK_LIMIT_COLS; */return TOKSTACKLIMIT; } -rows { /* yylval.number = STACK_LIMIT_ROWS; */return TOKSTACKLIMIT; } -exec { WS_STRING; yy_push_state(EXEC); yy_push_state(EAT_WHITESPACE); return TOKEXEC; } -exec_always { WS_STRING; yy_push_state(EXEC); yy_push_state(EAT_WHITESPACE); return TOKEXEC_ALWAYS; } -client.background { yy_push_state(COLOR_COND); yylval.single_color = &config.client.background; return TOKSINGLECOLOR; } -client.focused { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused; return TOKCOLOR; } -client.focused_inactive { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.focused_inactive; return TOKCOLOR; } -client.unfocused { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.unfocused; return TOKCOLOR; } -client.urgent { yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yy_push_state(COLOR_COND); yylval.color = &config.client.urgent; return TOKCOLOR; } -bar.focused { yy_push_state(COLOR_COND); yylval.color = &config.bar.focused; return TOKCOLOR; } -bar.unfocused { yy_push_state(COLOR_COND); yylval.color = &config.bar.unfocused; return TOKCOLOR; } -bar.urgent { yy_push_state(COLOR_COND); yylval.color = &config.bar.urgent; return TOKCOLOR; } -Mod1 { yylval.number = BIND_MOD1; return MODIFIER; } -Mod2 { yylval.number = BIND_MOD2; return MODIFIER; } -Mod3 { yylval.number = BIND_MOD3; return MODIFIER; } -Mod4 { yylval.number = BIND_MOD4; return MODIFIER; } -Mod5 { yylval.number = BIND_MOD5; return MODIFIER; } -Mode_switch { yylval.number = BIND_MODE_SWITCH; return MODIFIER; } -control { return TOKCONTROL; } -ctrl { return TOKCONTROL; } -shift { return TOKSHIFT; } - -class { yy_push_state(WANT_QSTRING); return TOK_CLASS; } -instance { yy_push_state(WANT_QSTRING); return TOK_INSTANCE; } -window_role { yy_push_state(WANT_QSTRING); return TOK_WINDOW_ROLE; } -id { yy_push_state(WANT_QSTRING); return TOK_ID; } -con_id { yy_push_state(WANT_QSTRING); return TOK_CON_ID; } -con_mark { yy_push_state(WANT_QSTRING); return TOK_MARK; } -title { yy_push_state(WANT_QSTRING); return TOK_TITLE; } -urgent { yy_push_state(WANT_QSTRING); return TOK_URGENT; } - -<*>{EOL} { - FREE(context->line_copy); - context->line_number++; - yy_push_state(BUFFER_LINE); - } -[ \t]+ { yy_pop_state(); yy_push_state(WANT_STRING); } -[ \t]+ { yy_pop_state(); yy_push_state(WANT_STRING); } -[ \t]+ { /* ignore whitespace */ ; } -\"[^\"]+\" { - /* if ASSIGN_COND then */ - if (yy_start_stack_ptr > 0) - yy_pop_state(); - /* yylval will be the string, but without quotes */ - char *copy = sstrdup(yytext+1); - copy[strlen(copy)-1] = '\0'; - yylval.string = copy; - return QUOTEDSTRING; - } -[^ \t\"\[]+ { BEGIN(ASSIGN_TARGET_COND); yylval.string = sstrdup(yytext); return STR_NG; } -[a-zA-Z0-9_]+ { yylval.string = sstrdup(yytext); return WORD; } -[a-zA-Z]+ { yylval.string = sstrdup(yytext); return WORD; } -. { return (int)yytext[0]; } - -<> { - while (yy_start_stack_ptr > 0) - yy_pop_state(); - yyterminate(); -} - -%% diff --git a/src/cfgparse.y b/src/cfgparse.y deleted file mode 100644 index 2a22aae4..00000000 --- a/src/cfgparse.y +++ /dev/null @@ -1,1875 +0,0 @@ -%{ -/* - * vim:ts=4:sw=4:expandtab - * - */ -#undef I3__FILE__ -#define I3__FILE__ "cfgparse.y" -#include -#include -#include -#include -#include - -#include "all.h" - -bool force_old_config_parser = false; - -static pid_t configerror_pid = -1; - -static Match current_match; -static Barconfig current_bar; -/* The pattern which was specified by the user, for example -misc-fixed-*. We - * store this in a separate variable because in the i3 config struct we just - * store the i3Font. */ -static char *font_pattern; -/* The path to the temporary script files used by i3-nagbar. We need to keep - * them around to delete the files in the i3-nagbar SIGCHLD handler. */ -static char *edit_script_path, *pager_script_path; - -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 *); - -static struct bindings_head *current_bindings; -static struct context *context; - -/* We don’t need yydebug for now, as we got decent error messages using - * yyerror(). Should you ever want to extend the parser, it might be handy - * to just comment it in again, so it stays here. */ -//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); - char buffer[context->last_column+1]; - buffer[context->last_column] = '\0'; - for (int c = 1; c <= context->last_column; c++) - buffer[c-1] = (c >= context->first_column ? '^' : ' '); - ELOG("CONFIG: %s\n", buffer); - ELOG("\n"); -} - -int yywrap(void) { - return 1; -} - -/* - * Goes through each line of buf (separated by \n) and checks for statements / - * commands which only occur in i3 v4 configuration files. If it finds any, it - * returns version 4, otherwise it returns version 3. - * - */ -static int detect_version(char *buf) { - char *walk = buf; - char *line = buf; - while (*walk != '\0') { - if (*walk != '\n') { - walk++; - continue; - } - - /* 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); - return 4; - } - - /* if this is a bind statement, we can check the command */ - if (strncasecmp(line, "bind", strlen("bind")) == 0) { - char *bind = strchr(line, ' '); - if (bind == NULL) - goto next; - while ((*bind == ' ' || *bind == '\t') && *bind != '\0') - bind++; - if (*bind == '\0') - goto next; - if ((bind = strchr(bind, ' ')) == NULL) - goto next; - while ((*bind == ' ' || *bind == '\t') && *bind != '\0') - bind++; - if (*bind == '\0') - goto next; - if (strncasecmp(bind, "layout", strlen("layout")) == 0 || - strncasecmp(bind, "floating", strlen("floating")) == 0 || - strncasecmp(bind, "workspace", strlen("workspace")) == 0 || - strncasecmp(bind, "focus left", strlen("focus left")) == 0 || - strncasecmp(bind, "focus right", strlen("focus right")) == 0 || - strncasecmp(bind, "focus up", strlen("focus up")) == 0 || - strncasecmp(bind, "focus down", strlen("focus down")) == 0 || - strncasecmp(bind, "border normal", strlen("border normal")) == 0 || - strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 || - strncasecmp(bind, "border pixel", strlen("border pixel")) == 0 || - strncasecmp(bind, "border borderless", strlen("border borderless")) == 0 || - strncasecmp(bind, "--no-startup-id", strlen("--no-startup-id")) == 0 || - strncasecmp(bind, "bar", strlen("bar")) == 0) { - printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line); - return 4; - } - } - -next: - /* advance to the next line */ - walk++; - line = walk; - } - - return 3; -} - -/* - * 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 - * example the script could not be found in $PATH or the i3 executable’s - * directory). - * - */ -static char *migrate_config(char *input, off_t size) { - int writepipe[2]; - int readpipe[2]; - - if (pipe(writepipe) != 0 || - pipe(readpipe) != 0) { - warn("migrate_config: Could not create pipes"); - return NULL; - } - - pid_t pid = fork(); - if (pid == -1) { - warn("Could not fork()"); - return NULL; - } - - /* child */ - if (pid == 0) { - /* close writing end of writepipe, connect reading side to stdin */ - close(writepipe[1]); - dup2(writepipe[0], 0); - - /* close reading end of readpipe, connect writing side to stdout */ - close(readpipe[0]); - dup2(readpipe[1], 1); - - static char *argv[] = { - NULL, /* will be replaced by the executable path */ - NULL - }; - exec_i3_utility("i3-migrate-config-to-v4", argv); - } - - /* parent */ - - /* close reading end of the writepipe (connected to the script’s stdin) */ - close(writepipe[0]); - - /* write the whole config file to the pipe, the script will read everything - * immediately */ - int written = 0; - int ret; - while (written < size) { - if ((ret = write(writepipe[1], input + written, size - written)) < 0) { - warn("Could not write to pipe"); - return NULL; - } - written += ret; - } - close(writepipe[1]); - - /* close writing end of the readpipe (connected to the script’s stdout) */ - close(readpipe[1]); - - /* read the script’s output */ - int conv_size = 65535; - char *converted = malloc(conv_size); - int read_bytes = 0; - do { - if (read_bytes == conv_size) { - conv_size += 65535; - converted = realloc(converted, conv_size); - } - ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes); - if (ret == -1) { - warn("Cannot read from pipe"); - FREE(converted); - return NULL; - } - read_bytes += ret; - } while (ret > 0); - - /* get the returncode */ - int status; - wait(&status); - if (!WIFEXITED(status)) { - fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n"); - return NULL; - } - - int returncode = WEXITSTATUS(status); - if (returncode != 0) { - fprintf(stderr, "Migration process exit code was != 0\n"); - if (returncode == 2) { - fprintf(stderr, "could not start the migration script\n"); - /* TODO: script was not found. tell the user to fix his system or create a v4 config */ - } else if (returncode == 1) { - fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n"); - fprintf(stderr, "# i3 config file (v4)\n"); - /* TODO: nag the user with a message to include a hint for i3 in his config file */ - } - return NULL; - } - - 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 (unlink(edit_script_path) != 0) - warn("Could not delete temporary i3-nagbar script %s", edit_script_path); - if (unlink(pager_script_path) != 0) - warn("Could not delete temporary i3-nagbar script %s", pager_script_path); - - 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; -} - -/* We need ev >= 4 for the following code. Since it is not *that* important (it - * only makes sure that there are no i3-nagbar instances left behind) we still - * support old systems with libev 3. */ -#if EV_VERSION_MAJOR >= 4 -/* - * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal - * SIGKILL (9) to make sure there are no left-over i3-nagbar processes. - * - */ -static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) { - if (configerror_pid != -1) { - LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", configerror_pid); - kill(configerror_pid, SIGKILL); - } -} -#endif - -/* - * Writes the given command as a shell script to path. - * Returns true unless something went wrong. - * - */ -static bool write_nagbar_script(const char *path, const char *command) { - int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR); - if (fd == -1) { - warn("Could not create temporary script to store the nagbar command"); - return false; - } - write(fd, "#!/bin/sh\n", strlen("#!/bin/sh\n")); - write(fd, command, strlen(command)); - close(fd); - return true; -} - -/* - * 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) { - if (only_check_config) - return; - - fprintf(stderr, "Starting i3-nagbar due to configuration errors\n"); - - /* We need to create a custom script containing our actual command - * since not every terminal emulator which is contained in - * i3-sensible-terminal supports -e with multiple arguments (and not - * all of them support -e with one quoted argument either). - * - * NB: The paths need to be unique, that is, don’t assume users close - * their nagbars at any point in time (and they still need to work). - * */ - edit_script_path = get_process_filename("nagbar-cfgerror-edit"); - pager_script_path = get_process_filename("nagbar-cfgerror-pager"); - - configerror_pid = fork(); - if (configerror_pid == -1) { - warn("Could not fork()"); - return; - } - - /* child */ - if (configerror_pid == 0) { - char *edit_command, *pager_command; - sasprintf(&edit_command, "i3-sensible-editor \"%s\" && i3-msg reload\n", config_path); - sasprintf(&pager_command, "i3-sensible-pager \"%s\"\n", errorfilename); - if (!write_nagbar_script(edit_script_path, edit_command) || - !write_nagbar_script(pager_script_path, pager_command)) - return; - - char *editaction, - *pageraction; - sasprintf(&editaction, "i3-sensible-terminal -e \"%s\"", edit_script_path); - sasprintf(&pageraction, "i3-sensible-terminal -e \"%s\"", pager_script_path); - char *argv[] = { - NULL, /* will be replaced by the executable path */ - "-t", - (context->has_errors ? "error" : "warning"), - "-m", - (context->has_errors ? - "You have an error in your i3 config file!" : - "Your config is outdated. Please fix the warnings to make sure everything works."), - "-b", - "edit config", - editaction, - (errorfilename ? "-b" : NULL), - (context->has_errors ? "show errors" : "show warnings"), - 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); - -/* We need ev >= 4 for the following code. Since it is not *that* important (it - * only makes sure that there are no i3-nagbar instances left behind) we still - * support old systems with libev 3. */ -#if EV_VERSION_MAJOR >= 4 - /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is - * still running) */ - ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup)); - ev_cleanup_init(cleanup, nagbar_cleanup); - ev_cleanup_start(main_loop, cleanup); -#endif -} - -/* - * 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 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 || - bind->release != current->release) - 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); - } - } - } -} - -static void migrate_i3bar_exec(struct Autostart *exec) { - ELOG("**********************************************************************\n"); - ELOG("IGNORING exec command: %s\n", exec->command); - ELOG("It contains \"i3bar\". Since i3 v4.1, i3bar will be automatically started\n"); - ELOG("for each 'bar' configuration block in your i3 config. Please remove the exec\n"); - ELOG("line and add the following to your i3 config:\n"); - ELOG("\n"); - ELOG(" bar {\n"); - ELOG(" status_command i3status\n"); - ELOG(" }\n"); - ELOG("**********************************************************************\n"); - - /* Generate a dummy bar configuration */ - Barconfig *bar_config = scalloc(sizeof(Barconfig)); - /* The hard-coded ID is not a problem. It does not conflict with the - * auto-generated bar IDs and having multiple hard-coded IDs is irrelevant - * – they all just contain status_command = i3status */ - bar_config->id = sstrdup("migrate-bar"); - bar_config->status_command = sstrdup("i3status"); - TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs); - - /* Trigger an i3-nagbar */ - context->has_warnings = true; -} - -void parse_file(const char *f) { - SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables); - int fd, ret, read_bytes = 0; - struct stat stbuf; - char *buf; - FILE *fstr; - char buffer[1026], key[512], value[512]; - - if ((fd = open(f, O_RDONLY)) == -1) - die("Could not open configuration file: %s\n", strerror(errno)); - - if (fstat(fd, &stbuf) == -1) - die("Could not fstat file: %s\n", strerror(errno)); - - buf = scalloc((stbuf.st_size + 1) * sizeof(char)); - while (read_bytes < stbuf.st_size) { - if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0) - die("Could not read(): %s\n", strerror(errno)); - read_bytes += ret; - } - - if (lseek(fd, 0, SEEK_SET) == (off_t)-1) - die("Could not lseek: %s\n", strerror(errno)); - - if ((fstr = fdopen(fd, "r")) == NULL) - die("Could not fdopen: %s\n", strerror(errno)); - - while (!feof(fstr)) { - if (fgets(buffer, 1024, fstr) == NULL) { - if (feof(fstr)) - break; - die("Could not read configuration file\n"); - } - - /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */ - if (sscanf(buffer, "%s %[^\n]", key, value) < 1 || - key[0] == '#' || strlen(key) < 3) - continue; - - if (strcasecmp(key, "set") == 0) { - if (value[0] != '$') { - ELOG("Malformed variable assignment, name has to start with $\n"); - continue; - } - - /* get key/value for this variable */ - char *v_key = value, *v_value; - if (strstr(value, " ") == NULL && strstr(value, "\t") == NULL) { - ELOG("Malformed variable assignment, need a value\n"); - continue; - } - - if (!(v_value = strstr(value, " "))) - v_value = strstr(value, "\t"); - - *(v_value++) = '\0'; - while (*v_value == '\t' || *v_value == ' ') - v_value++; - - struct Variable *new = scalloc(sizeof(struct Variable)); - new->key = sstrdup(v_key); - new->value = sstrdup(v_value); - SLIST_INSERT_HEAD(&variables, new, variables); - DLOG("Got new variable %s = %s\n", v_key, v_value); - continue; - } - } - fclose(fstr); - - /* For every custom variable, see how often it occurs in the file and - * how much extra bytes it requires when replaced. */ - struct Variable *current, *nearest; - int extra_bytes = 0; - /* We need to copy the buffer because we need to invalidate the - * variables (otherwise we will count them twice, which is bad when - * 'extra' is negative) */ - char *bufcopy = sstrdup(buf); - SLIST_FOREACH(current, &variables, variables) { - int extra = (strlen(current->value) - strlen(current->key)); - char *next; - for (next = bufcopy; - next < (bufcopy + stbuf.st_size) && - (next = strcasestr(next, current->key)) != NULL; - next += strlen(current->key)) { - *next = '_'; - extra_bytes += extra; - } - } - FREE(bufcopy); - - /* Then, allocate a new buffer and copy the file over to the new one, - * but replace occurences of our variables */ - char *walk = buf, *destwalk; - char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char)); - destwalk = new; - while (walk < (buf + stbuf.st_size)) { - /* Find the next variable */ - SLIST_FOREACH(current, &variables, variables) - current->next_match = strcasestr(walk, current->key); - nearest = NULL; - int distance = stbuf.st_size; - SLIST_FOREACH(current, &variables, variables) { - if (current->next_match == NULL) - continue; - if ((current->next_match - walk) < distance) { - distance = (current->next_match - walk); - nearest = current; - } - } - if (nearest == NULL) { - /* If there are no more variables, we just copy the rest */ - strncpy(destwalk, walk, (buf + stbuf.st_size) - walk); - destwalk += (buf + stbuf.st_size) - walk; - *destwalk = '\0'; - break; - } else { - /* Copy until the next variable, then copy its value */ - strncpy(destwalk, walk, distance); - strncpy(destwalk + distance, nearest->value, strlen(nearest->value)); - walk += distance + strlen(nearest->key); - destwalk += distance + strlen(nearest->value); - } - } - - /* analyze the string to find out whether this is an old config file (3.x) - * or a new config file (4.x). If it’s old, we run the converter script. */ - int version = detect_version(buf); - if (version == 3) { - /* We need to convert this v3 configuration */ - char *converted = migrate_config(new, stbuf.st_size); - if (converted != NULL) { - ELOG("\n"); - ELOG("****************************************************************\n"); - ELOG("NOTE: Automatically converted configuration file from v3 to v4.\n"); - ELOG("\n"); - ELOG("Please convert your config file to v4. You can use this command:\n"); - ELOG(" mv %s %s.O\n", f, f); - ELOG(" i3-migrate-config-to-v4 %s.O > %s\n", f, f); - ELOG("****************************************************************\n"); - ELOG("\n"); - free(new); - new = converted; - } else { - printf("\n"); - printf("**********************************************************************\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"); - } - } - - - context = scalloc(sizeof(struct context)); - context->filename = f; - - if (force_old_config_parser) { - /* now lex/parse it */ - yy_scan_string(new); - if (yyparse() != 0) { - fprintf(stderr, "Could not parse configfile\n"); - exit(1); - } - } else { - struct ConfigResult *config_output = parse_config(new, context); - yajl_gen_free(config_output->json_gen); - } - - check_for_duplicate_bindings(context); - - /* XXX: The following code will be removed in i3 v4.3 (three releases from - * now, as of 2011-10-22) */ - /* Check for any exec or exec_always lines starting i3bar. We remove these - * and add a bar block instead. Additionally, a i3-nagbar warning (not an - * error) will be displayed so that users update their config file. */ - struct Autostart *exec, *next; - for (exec = TAILQ_FIRST(&autostarts); exec; ) { - next = TAILQ_NEXT(exec, autostarts); - if (strstr(exec->command, "i3bar") != NULL) { - migrate_i3bar_exec(exec); - TAILQ_REMOVE(&autostarts, exec, autostarts); - } - exec = next; - } - - for (exec = TAILQ_FIRST(&autostarts_always); exec; ) { - next = TAILQ_NEXT(exec, autostarts_always); - if (strstr(exec->command, "i3bar") != NULL) { - migrate_i3bar_exec(exec); - TAILQ_REMOVE(&autostarts_always, exec, autostarts_always); - } - exec = next; - } - - if (context->has_errors || context->has_warnings) { - ELOG("FYI: You are using i3 version " I3_VERSION "\n"); - if (version == 3) - ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n"); - start_configerror_nagbar(f); - } - - if (force_old_config_parser) - yylex_destroy(); - FREE(context->line_copy); - free(context); - FREE(font_pattern); - free(new); - free(buf); - - while (!SLIST_EMPTY(&variables)) { - current = SLIST_FIRST(&variables); - FREE(current->key); - FREE(current->value); - SLIST_REMOVE_HEAD(&variables, variables); - FREE(current); - } -} - -%} - -%error-verbose -%lex-param { struct context *context } - -%union { - int number; - char *string; - uint32_t *single_color; - struct Colortriple *color; - Match *match; - struct Binding *binding; -} - -%token NUMBER "" -%token WORD "" -%token STR "" -%token STR_NG "" -%token HEXCOLOR "#" -%token OUTPUT "" -%token TOKBINDCODE -%token TOKTERMINAL -%token TOKCOMMENT "" -%token TOKFONT "font" -%token TOKBINDSYM "bindsym" -%token MODIFIER "" -%token TOKCONTROL "control" -%token TOKSHIFT "shift" -%token TOKFLOATING_MODIFIER "floating_modifier" -%token TOKFLOATING_MAXIMUM_SIZE "floating_maximum_size" -%token TOKFLOATING_MINIMUM_SIZE "floating_minimum_size" -%token QUOTEDSTRING "" -%token TOKWORKSPACE "workspace" -%token TOKOUTPUT "output" -%token TOKASSIGN "assign" -%token TOKSET -%token TOKIPCSOCKET "ipc_socket" -%token TOKRESTARTSTATE "restart_state" -%token TOKEXEC "exec" -%token TOKEXEC_ALWAYS "exec_always" -%token TOKSINGLECOLOR -%token TOKCOLOR -%token TOKARROW "→" -%token TOKMODE "mode" -%token TOK_TIME_MS "ms" -%token TOK_BAR "bar" -%token TOK_ORIENTATION "default_orientation" -%token TOK_HORIZ "horizontal" -%token TOK_VERT "vertical" -%token TOK_AUTO "auto" -%token TOK_WORKSPACE_LAYOUT "workspace_layout" -%token TOKNEWWINDOW "new_window" -%token TOKNEWFLOAT "new_float" -%token TOK_NORMAL "normal" -%token TOK_NONE "none" -%token TOK_PIXEL "pixel" -%token TOK_1PIXEL "1pixel" -%token TOK_HIDE_EDGE_BORDERS "hide_edge_borders" -%token TOK_BOTH "both" -%token TOKFOCUSFOLLOWSMOUSE "focus_follows_mouse" -%token TOK_FORCE_FOCUS_WRAPPING "force_focus_wrapping" -%token TOK_FORCE_XINERAMA "force_xinerama" -%token TOK_FAKE_OUTPUTS "fake_outputs" -%token TOK_WORKSPACE_AUTO_BAF "workspace_auto_back_and_forth" -%token TOK_WORKSPACE_URGENCY_TIMER "force_display_urgency_hint" -%token TOKWORKSPACEBAR "workspace_bar" -%token TOK_DEFAULT "default" -%token TOK_STACKING "stacking" -%token TOK_TABBED "tabbed" -%token TOKSTACKLIMIT "stack-limit" -%token TOK_POPUP_DURING_FULLSCREEN "popup_during_fullscreen" -%token TOK_IGNORE "ignore" -%token TOK_LEAVE_FULLSCREEN "leave_fullscreen" -%token TOK_FOR_WINDOW "for_window" - -%token TOK_BAR_OUTPUT "output (bar)" -%token TOK_BAR_TRAY_OUTPUT "tray_output" -%token TOK_BAR_SOCKET_PATH "socket_path" -%token TOK_BAR_MODE "mode (bar)" -%token TOK_BAR_HIDE "hide" -%token TOK_BAR_DOCK "dock" -%token TOK_BAR_MODIFIER "modifier (bar)" -%token TOK_BAR_CONTROL "shift (bar)" -%token TOK_BAR_SHIFT "control (bar)" -%token TOK_BAR_MOD1 "Mod1" -%token TOK_BAR_MOD2 "Mod2" -%token TOK_BAR_MOD3 "Mod3" -%token TOK_BAR_MOD4 "Mod4" -%token TOK_BAR_MOD5 "Mod5" -%token TOK_BAR_POSITION "position" -%token TOK_BAR_BOTTOM "bottom" -%token TOK_BAR_TOP "top" -%token TOK_BAR_STATUS_COMMAND "status_command" -%token TOK_BAR_I3BAR_COMMAND "i3bar_command" -%token TOK_BAR_FONT "font (bar)" -%token TOK_BAR_WORKSPACE_BUTTONS "workspace_buttons" -%token TOK_BAR_VERBOSE "verbose" -%token TOK_BAR_COLORS "colors" -%token TOK_BAR_COLOR_BACKGROUND "background" -%token TOK_BAR_COLOR_STATUSLINE "statusline" -%token TOK_BAR_COLOR_FOCUSED_WORKSPACE "focused_workspace" -%token TOK_BAR_COLOR_ACTIVE_WORKSPACE "active_workspace" -%token TOK_BAR_COLOR_INACTIVE_WORKSPACE "inactive_workspace" -%token TOK_BAR_COLOR_URGENT_WORKSPACE "urgent_workspace" -%token TOK_NO_STARTUP_ID "--no-startup-id" -%token TOK_RELEASE "--release" - -%token TOK_MARK "mark" -%token TOK_CLASS "class" -%token TOK_INSTANCE "instance" -%token TOK_WINDOW_ROLE "window_role" -%token TOK_ID "id" -%token TOK_CON_ID "con_id" -%token TOK_TITLE "title" -%token TOK_URGENT "urgent" - -%type binding -%type bindcode -%type bindsym -%type binding_modifiers -%type binding_modifier -%type direction -%type layout_mode -%type border_style -%type new_window -%type hide_edge_borders -%type edge_hiding_mode -%type new_float -%type colorpixel -%type bool -%type popup_setting -%type bar_position_position -%type bar_mode_mode -%type bar_modifier_modifier -%type optional_no_startup_id -%type optional_border_width -%type optional_release -%type command -%type word_or_number -%type duration -%type qstring_or_number -%type optional_workspace_name -%type workspace_name -%type window_class - -%% - -lines: /* empty */ - | lines error - | lines line - ; - -line: - bindline - | for_window - | mode - | bar - | floating_maximum_size - | floating_minimum_size - | floating_modifier - | orientation - | workspace_layout - | new_window - | new_float - | hide_edge_borders - | focus_follows_mouse - | force_focus_wrapping - | force_xinerama - | fake_outputs - | force_display_urgency_hint - | workspace_back_and_forth - | workspace_bar - | workspace - | assign - | ipcsocket - | restart_state - | exec - | exec_always - | single_color - | color - | terminal - | font - | comment - | popup_during_fullscreen - ; - -comment: - TOKCOMMENT - ; - -command: - STR - ; - -bindline: - binding - { - TAILQ_INSERT_TAIL(bindings, $1, bindings); - } - ; - -binding: - TOKBINDCODE bindcode { $$ = $2; } - | TOKBINDSYM bindsym { $$ = $2; } - ; - -bindcode: - optional_release binding_modifiers NUMBER command - { - DLOG("bindcode: release = %d, mod = %d, key = %d, command = %s\n", $1, $2, $3, $4); - Binding *new = scalloc(sizeof(Binding)); - - new->release = $1; - new->keycode = $3; - new->mods = $2; - new->command = $4; - - $$ = new; - } - ; - -bindsym: - optional_release binding_modifiers word_or_number command - { - DLOG("bindsym: release = %d, mod = %d, key = %s, command = %s\n", $1, $2, $3, $4); - Binding *new = scalloc(sizeof(Binding)); - - new->release = $1; - new->symbol = $3; - new->mods = $2; - new->command = $4; - - $$ = new; - } - ; - -optional_release: - /* empty */ { $$ = B_UPON_KEYPRESS; } - | TOK_RELEASE { $$ = B_UPON_KEYRELEASE; } - ; - -for_window: - TOK_FOR_WINDOW match command - { - if (match_is_empty(¤t_match)) { - ELOG("Match is empty, ignoring this for_window statement\n"); - break; - } - printf("\t should execute command %s for the criteria mentioned above\n", $3); - Assignment *assignment = scalloc(sizeof(Assignment)); - assignment->type = A_COMMAND; - assignment->match = current_match; - assignment->dest.command = $3; - TAILQ_INSERT_TAIL(&assignments, assignment, assignments); - } - ; - -match: - | matchstart criteria matchend - { - printf("match parsed\n"); - } - ; - -matchstart: - '[' - { - printf("start\n"); - match_init(¤t_match); - } - ; - -matchend: - ']' - { - printf("match specification finished\n"); - } - ; - -criteria: - criteria criterion - | criterion - ; - -criterion: - TOK_CLASS '=' STR - { - printf("criteria: class = %s\n", $3); - current_match.class = regex_new($3); - free($3); - } - | TOK_INSTANCE '=' STR - { - printf("criteria: instance = %s\n", $3); - current_match.instance = regex_new($3); - free($3); - } - | TOK_WINDOW_ROLE '=' STR - { - printf("criteria: window_role = %s\n", $3); - current_match.role = regex_new($3); - free($3); - } - | TOK_CON_ID '=' STR - { - printf("criteria: id = %s\n", $3); - char *end; - long parsed = strtol($3, &end, 10); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { - ELOG("Could not parse con id \"%s\"\n", $3); - } else { - current_match.con_id = (Con*)parsed; - printf("id as int = %p\n", current_match.con_id); - } - } - | TOK_ID '=' STR - { - printf("criteria: window id = %s\n", $3); - char *end; - long parsed = strtol($3, &end, 10); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { - ELOG("Could not parse window id \"%s\"\n", $3); - } else { - current_match.id = parsed; - printf("window id as int = %d\n", current_match.id); - } - } - | TOK_MARK '=' STR - { - printf("criteria: mark = %s\n", $3); - current_match.mark = regex_new($3); - free($3); - } - | TOK_TITLE '=' STR - { - printf("criteria: title = %s\n", $3); - current_match.title = regex_new($3); - free($3); - } - | TOK_URGENT '=' STR - { - printf("criteria: urgent = %s\n", $3); - if (strcasecmp($3, "latest") == 0 || - strcasecmp($3, "newest") == 0 || - strcasecmp($3, "recent") == 0 || - strcasecmp($3, "last") == 0) { - current_match.urgent = U_LATEST; - } else if (strcasecmp($3, "oldest") == 0 || - strcasecmp($3, "first") == 0) { - current_match.urgent = U_OLDEST; - } - free($3); - } - ; - -qstring_or_number: - QUOTEDSTRING - | NUMBER { sasprintf(&$$, "%d", $1); } - ; - -word_or_number: - WORD - | NUMBER - { - sasprintf(&$$, "%d", $1); - } - ; - -duration: - NUMBER { sasprintf(&$$, "%d", $1); } - | NUMBER TOK_TIME_MS { sasprintf(&$$, "%d", $1); } - ; - -mode: - TOKMODE QUOTEDSTRING '{' modelines '}' - { - if (strcasecmp($2, "default") == 0) { - printf("You cannot use the name \"default\" for your mode\n"); - exit(1); - } - printf("\t now in mode %s\n", $2); - printf("\t current bindings = %p\n", current_bindings); - Binding *binding; - TAILQ_FOREACH(binding, current_bindings, bindings) { - printf("got binding on mods %d, keycode %d, symbol %s, command %s\n", - binding->mods, binding->keycode, binding->symbol, binding->command); - } - - struct Mode *mode = scalloc(sizeof(struct Mode)); - mode->name = $2; - mode->bindings = current_bindings; - current_bindings = NULL; - SLIST_INSERT_HEAD(&modes, mode, modes); - } - ; - - -modelines: - /* empty */ - | modelines modeline - ; - -modeline: - comment - | binding - { - if (current_bindings == NULL) { - current_bindings = scalloc(sizeof(struct bindings_head)); - TAILQ_INIT(current_bindings); - } - - TAILQ_INSERT_TAIL(current_bindings, $1, bindings); - } - ; - -bar: - TOK_BAR '{' barlines '}' - { - printf("\t new bar configuration finished, saving.\n"); - /* Generate a unique ID for this bar */ - current_bar.id = sstrdup("bar-XXXXXX"); - /* This works similar to mktemp in that it replaces the last six X with - * random letters, but without the restriction that the given buffer - * has to contain a valid path name. */ - char *x = current_bar.id + strlen("bar-"); - while (*x != '\0') { - *(x++) = (rand() % 26) + 'a'; - } - - /* If no font was explicitly set, we use the i3 font as default */ - if (!current_bar.font && font_pattern) - current_bar.font = sstrdup(font_pattern); - - /* Copy the current (static) structure into a dynamically allocated - * one, then cleanup our static one. */ - Barconfig *bar_config = scalloc(sizeof(Barconfig)); - memcpy(bar_config, ¤t_bar, sizeof(Barconfig)); - TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs); - - memset(¤t_bar, '\0', sizeof(Barconfig)); - } - ; - -barlines: - /* empty */ - | barlines barline - ; - -barline: - comment - | bar_status_command - | bar_i3bar_command - | bar_output - | bar_tray_output - | bar_position - | bar_mode - | bar_modifier - | bar_font - | bar_workspace_buttons - | bar_verbose - | bar_socket_path - | bar_colors - | bar_color_background - | bar_color_statusline - | bar_color_focused_workspace - | bar_color_active_workspace - | bar_color_inactive_workspace - | bar_color_urgent_workspace - ; - -bar_status_command: - TOK_BAR_STATUS_COMMAND STR - { - DLOG("should add status command %s\n", $2); - FREE(current_bar.status_command); - current_bar.status_command = $2; - } - ; - -bar_i3bar_command: - TOK_BAR_I3BAR_COMMAND STR - { - DLOG("should add i3bar_command %s\n", $2); - FREE(current_bar.i3bar_command); - current_bar.i3bar_command = $2; - } - ; - -bar_output: - TOK_BAR_OUTPUT STR - { - DLOG("bar output %s\n", $2); - int new_outputs = current_bar.num_outputs + 1; - current_bar.outputs = srealloc(current_bar.outputs, sizeof(char*) * new_outputs); - current_bar.outputs[current_bar.num_outputs] = $2; - current_bar.num_outputs = new_outputs; - } - ; - -bar_tray_output: - TOK_BAR_TRAY_OUTPUT STR - { - DLOG("tray %s\n", $2); - FREE(current_bar.tray_output); - current_bar.tray_output = $2; - } - ; - -bar_position: - TOK_BAR_POSITION bar_position_position - { - DLOG("position %d\n", $2); - current_bar.position = $2; - } - ; - -bar_position_position: - TOK_BAR_TOP { $$ = P_TOP; } - | TOK_BAR_BOTTOM { $$ = P_BOTTOM; } - ; - -bar_mode: - TOK_BAR_MODE bar_mode_mode - { - DLOG("mode %d\n", $2); - current_bar.mode = $2; - } - ; - -bar_mode_mode: - TOK_BAR_HIDE { $$ = M_HIDE; } - | TOK_BAR_DOCK { $$ = M_DOCK; } - ; - -bar_modifier: - TOK_BAR_MODIFIER bar_modifier_modifier - { - DLOG("modifier %d\n", $2); - current_bar.modifier = $2; - }; - -bar_modifier_modifier: - TOK_BAR_CONTROL { $$ = M_CONTROL; } - | TOK_BAR_SHIFT { $$ = M_SHIFT; } - | TOK_BAR_MOD1 { $$ = M_MOD1; } - | TOK_BAR_MOD2 { $$ = M_MOD2; } - | TOK_BAR_MOD3 { $$ = M_MOD3; } - | TOK_BAR_MOD4 { $$ = M_MOD4; } - | TOK_BAR_MOD5 { $$ = M_MOD5; } - ; - -bar_font: - TOK_BAR_FONT STR - { - DLOG("font %s\n", $2); - FREE(current_bar.font); - current_bar.font = $2; - } - ; - -bar_workspace_buttons: - TOK_BAR_WORKSPACE_BUTTONS bool - { - DLOG("workspace_buttons = %d\n", $2); - /* We store this inverted to make the default setting right when - * initializing the struct with zero. */ - current_bar.hide_workspace_buttons = !($2); - } - ; - -bar_verbose: - TOK_BAR_VERBOSE bool - { - DLOG("verbose = %d\n", $2); - current_bar.verbose = $2; - } - ; - -bar_socket_path: - TOK_BAR_SOCKET_PATH STR - { - DLOG("socket_path = %s\n", $2); - FREE(current_bar.socket_path); - current_bar.socket_path = $2; - } - ; - -bar_colors: - TOK_BAR_COLORS '{' barlines '}' - { - /* At the moment, the TOK_BAR_COLORS token is only to make the config - * friendlier for humans. We might change this in the future if it gets - * more complex. */ - } - ; - -bar_color_background: - TOK_BAR_COLOR_BACKGROUND HEXCOLOR - { - DLOG("background = %s\n", $2); - current_bar.colors.background = $2; - } - ; - -bar_color_statusline: - TOK_BAR_COLOR_STATUSLINE HEXCOLOR - { - DLOG("statusline = %s\n", $2); - current_bar.colors.statusline = $2; - } - ; - -bar_color_focused_workspace: - TOK_BAR_COLOR_FOCUSED_WORKSPACE HEXCOLOR HEXCOLOR - { - /* Old syntax: text / background */ - DLOG("focused_ws = %s, %s (old)\n", $2, $3); - current_bar.colors.focused_workspace_bg = $3; - current_bar.colors.focused_workspace_text = $2; - } - | TOK_BAR_COLOR_FOCUSED_WORKSPACE HEXCOLOR HEXCOLOR HEXCOLOR - { - /* New syntax: border / background / text */ - DLOG("focused_ws = %s, %s and %s\n", $2, $3, $4); - current_bar.colors.focused_workspace_border = $2; - current_bar.colors.focused_workspace_bg = $3; - current_bar.colors.focused_workspace_text = $4; - } - ; - -bar_color_active_workspace: - TOK_BAR_COLOR_ACTIVE_WORKSPACE HEXCOLOR HEXCOLOR - { - /* Old syntax: text / background */ - DLOG("active_ws = %s, %s (old)\n", $2, $3); - current_bar.colors.active_workspace_bg = $3; - current_bar.colors.active_workspace_text = $2; - } - | TOK_BAR_COLOR_ACTIVE_WORKSPACE HEXCOLOR HEXCOLOR HEXCOLOR - { - /* New syntax: border / background / text */ - DLOG("active_ws = %s, %s and %s\n", $2, $3, $4); - current_bar.colors.active_workspace_border = $2; - current_bar.colors.active_workspace_bg = $3; - current_bar.colors.active_workspace_text = $4; - } - ; - -bar_color_inactive_workspace: - TOK_BAR_COLOR_INACTIVE_WORKSPACE HEXCOLOR HEXCOLOR - { - /* Old syntax: text / background */ - DLOG("inactive_ws = %s, %s (old)\n", $2, $3); - current_bar.colors.inactive_workspace_bg = $3; - current_bar.colors.inactive_workspace_text = $2; - } - | TOK_BAR_COLOR_INACTIVE_WORKSPACE HEXCOLOR HEXCOLOR HEXCOLOR - { - DLOG("inactive_ws = %s, %s and %s\n", $2, $3, $4); - current_bar.colors.inactive_workspace_border = $2; - current_bar.colors.inactive_workspace_bg = $3; - current_bar.colors.inactive_workspace_text = $4; - } - ; - -bar_color_urgent_workspace: - TOK_BAR_COLOR_URGENT_WORKSPACE HEXCOLOR HEXCOLOR - { - /* Old syntax: text / background */ - DLOG("urgent_ws = %s, %s (old)\n", $2, $3); - current_bar.colors.urgent_workspace_bg = $3; - current_bar.colors.urgent_workspace_text = $2; - } - | TOK_BAR_COLOR_URGENT_WORKSPACE HEXCOLOR HEXCOLOR HEXCOLOR - { - DLOG("urgent_ws = %s, %s and %s\n", $2, $3, $4); - current_bar.colors.urgent_workspace_border = $2; - current_bar.colors.urgent_workspace_bg = $3; - current_bar.colors.urgent_workspace_text = $4; - } - ; - -floating_maximum_size: - TOKFLOATING_MAXIMUM_SIZE NUMBER WORD NUMBER - { - printf("floating_maximum_width = %d\n", $2); - printf("floating_maximum_height = %d\n", $4); - config.floating_maximum_width = $2; - config.floating_maximum_height = $4; - } - ; - -floating_minimum_size: - TOKFLOATING_MINIMUM_SIZE NUMBER WORD NUMBER - { - printf("floating_minimum_width = %d\n", $2); - printf("floating_minimum_height = %d\n", $4); - config.floating_minimum_width = $2; - config.floating_minimum_height = $4; - } - ; - -floating_modifier: - TOKFLOATING_MODIFIER binding_modifiers - { - DLOG("floating modifier = %d\n", $2); - config.floating_modifier = $2; - } - ; - -orientation: - TOK_ORIENTATION direction - { - DLOG("New containers should start with split direction %d\n", $2); - config.default_orientation = $2; - } - ; - -direction: - TOK_HORIZ { $$ = HORIZ; } - | TOK_VERT { $$ = VERT; } - | TOK_AUTO { $$ = NO_ORIENTATION; } - ; - -workspace_layout: - TOK_WORKSPACE_LAYOUT layout_mode - { - DLOG("new containers will be in mode %d\n", $2); - config.default_layout = $2; - -#if 0 - /* We also need to change the layout of the already existing - * workspaces here. Workspaces may exist at this point because - * of the other directives which are modifying workspaces - * (setting the preferred screen or name). While the workspace - * objects are already created, they have never been used. - * Thus, the user very likely awaits the default container mode - * to trigger in this case, regardless of where it is inside - * his configuration file. */ - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->table == NULL) - continue; - switch_layout_mode(global_conn, - ws->table[0][0], - config.container_mode); - } -#endif - } - | TOK_WORKSPACE_LAYOUT TOKSTACKLIMIT TOKSTACKLIMIT NUMBER - { - DLOG("stack-limit %d with val %d\n", $3, $4); - config.container_stack_limit = $3; - config.container_stack_limit_value = $4; - -#if 0 - /* See the comment above */ - Workspace *ws; - TAILQ_FOREACH(ws, workspaces, workspaces) { - if (ws->table == NULL) - continue; - Container *con = ws->table[0][0]; - con->stack_limit = config.container_stack_limit; - con->stack_limit_value = config.container_stack_limit_value; - } -#endif - } - ; - -layout_mode: - TOK_DEFAULT { $$ = L_DEFAULT; } - | TOK_STACKING { $$ = L_STACKED; } - | TOK_TABBED { $$ = L_TABBED; } - ; - -new_window: - TOKNEWWINDOW border_style - { - DLOG("new windows should start with border style %d\n", $2); - config.default_border = $2; - } - ; - -new_float: - TOKNEWFLOAT border_style - { - DLOG("new floating windows should start with border style %d\n", $2); - config.default_floating_border = $2; - } - ; - -border_style: - TOK_NORMAL optional_border_width - { - /* FIXME: the whole border_style thing actually screws up when new_float is used because it overwrites earlier values :-/ */ - config.default_border_width = $2; - $$ = BS_NORMAL; - } - | TOK_1PIXEL - { - config.default_border_width = 1; - $$ = BS_PIXEL; - } - | TOK_NONE - { - config.default_border_width = 0; - $$ = BS_NONE; - } - | TOK_PIXEL optional_border_width - { - config.default_border_width = $2; - $$ = BS_PIXEL; - } - ; - -bool: - NUMBER - { - $$ = ($1 == 1); - } - | WORD - { - DLOG("checking word \"%s\"\n", $1); - $$ = (strcasecmp($1, "yes") == 0 || - strcasecmp($1, "true") == 0 || - strcasecmp($1, "on") == 0 || - strcasecmp($1, "enable") == 0 || - strcasecmp($1, "active") == 0); - } - ; - -hide_edge_borders: - TOK_HIDE_EDGE_BORDERS edge_hiding_mode - { - DLOG("hide edge borders = %d\n", $2); - config.hide_edge_borders = $2; - } - ; - -edge_hiding_mode: - TOK_NONE { $$ = ADJ_NONE; } - | TOK_VERT { $$ = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE; } - | TOK_HORIZ { $$ = ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE; } - | TOK_BOTH { $$ = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE | ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE; } - | bool { $$ = ($1 ? ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE : ADJ_NONE); } - ; - -focus_follows_mouse: - TOKFOCUSFOLLOWSMOUSE bool - { - DLOG("focus follows mouse = %d\n", $2); - config.disable_focus_follows_mouse = !($2); - } - ; - -force_focus_wrapping: - TOK_FORCE_FOCUS_WRAPPING bool - { - DLOG("force focus wrapping = %d\n", $2); - config.force_focus_wrapping = $2; - } - ; - -force_xinerama: - TOK_FORCE_XINERAMA bool - { - DLOG("force xinerama = %d\n", $2); - config.force_xinerama = $2; - } - ; - -fake_outputs: - TOK_FAKE_OUTPUTS STR - { - DLOG("fake outputs = %s\n", $2); - config.fake_outputs = $2; - } - ; - -workspace_back_and_forth: - TOK_WORKSPACE_AUTO_BAF bool - { - DLOG("automatic workspace back-and-forth = %d\n", $2); - config.workspace_auto_back_and_forth = $2; - } - ; - -force_display_urgency_hint: - TOK_WORKSPACE_URGENCY_TIMER duration - { - DLOG("workspace urgency_timer = %f\n", atoi($2) / 1000.0); - config.workspace_urgency_timer = atoi($2) / 1000.0; - } - ; - -workspace_bar: - TOKWORKSPACEBAR bool - { - DLOG("workspace bar = %d\n", $2); - config.disable_workspace_bar = !($2); - } - ; - -workspace: - TOKWORKSPACE qstring_or_number TOKOUTPUT OUTPUT optional_workspace_name - { - char *ws_name = $2; - - if ($5 != NULL) { - ELOG("The old (v3) syntax workspace output is deprecated.\n"); - ELOG("Please use the new syntax: workspace \"\" output \n"); - ELOG("In your case, the following should work:\n"); - ELOG(" workspace \"%s\" output %s\n", $5, $4); - ws_name = $5; - context->has_warnings = true; - } - - DLOG("Assigning workspace \"%s\" to output \"%s\"\n", ws_name, $4); - /* Check for earlier assignments of the same workspace so that we - * don’t have assignments of a single workspace to different - * outputs */ - struct Workspace_Assignment *assignment; - bool duplicate = false; - TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) { - if (strcasecmp(assignment->name, ws_name) == 0) { - ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n", - ws_name); - assignment->output = $4; - duplicate = true; - } - } - if (!duplicate) { - assignment = scalloc(sizeof(struct Workspace_Assignment)); - assignment->name = ws_name; - assignment->output = $4; - TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments); - } - } - | TOKWORKSPACE NUMBER workspace_name - { - int ws_num = $2; - if (ws_num < 1) { - DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num); - } else { - DLOG("workspace name to: %s\n", $3); -#if 0 - if ($3 != NULL) { - workspace_set_name(workspace_get(ws_num - 1), $3); - free($3); - } -#endif - } - } - ; - -optional_workspace_name: - /* empty */ { $$ = NULL; } - | workspace_name { $$ = $1; } - ; - -workspace_name: - QUOTEDSTRING { $$ = $1; } - | STR { $$ = $1; } - | WORD { $$ = $1; } - ; - -assign: - TOKASSIGN window_class STR - { - /* This is the old, deprecated form of assignments. It’s provided for - * compatibility in version (4.1, 4.2, 4.3) and will be removed - * afterwards. It triggers an i3-nagbar warning starting from 4.1. */ - ELOG("You are using the old assign syntax (without criteria). " - "Please see the User's Guide for the new syntax and fix " - "your config file.\n"); - context->has_warnings = true; - printf("assignment of %s to *%s*\n", $2, $3); - char *workspace = $3; - char *criteria = $2; - - Assignment *assignment = scalloc(sizeof(Assignment)); - Match *match = &(assignment->match); - match_init(match); - - char *separator = NULL; - if ((separator = strchr(criteria, '/')) != NULL) { - *(separator++) = '\0'; - char *pattern; - sasprintf(&pattern, "(?i)%s", separator); - match->title = regex_new(pattern); - free(pattern); - printf(" title = %s\n", separator); - } - if (*criteria != '\0') { - char *pattern; - sasprintf(&pattern, "(?i)%s", criteria); - match->class = regex_new(pattern); - free(pattern); - printf(" class = %s\n", criteria); - } - free(criteria); - - /* Compatibility with older versions: If the assignment target starts - * with ~, we create the equivalent of: - * - * for_window [class="foo"] floating enable - */ - if (*workspace == '~') { - workspace++; - if (*workspace == '\0') { - /* This assignment was *only* for floating */ - assignment->type = A_COMMAND; - assignment->dest.command = sstrdup("floating enable"); - TAILQ_INSERT_TAIL(&assignments, assignment, assignments); - break; - } else { - /* Create a new assignment and continue afterwards */ - Assignment *floating = scalloc(sizeof(Assignment)); - match_copy(&(floating->match), match); - floating->type = A_COMMAND; - floating->dest.command = sstrdup("floating enable"); - TAILQ_INSERT_TAIL(&assignments, floating, assignments); - } - } - - assignment->type = A_TO_WORKSPACE; - assignment->dest.workspace = workspace; - TAILQ_INSERT_TAIL(&assignments, assignment, assignments); - } - | TOKASSIGN match STR - { - if (match_is_empty(¤t_match)) { - ELOG("Match is empty, ignoring this assignment\n"); - break; - } - printf("new assignment, using above criteria, to workspace %s\n", $3); - Assignment *assignment = scalloc(sizeof(Assignment)); - assignment->match = current_match; - assignment->type = A_TO_WORKSPACE; - assignment->dest.workspace = $3; - TAILQ_INSERT_TAIL(&assignments, assignment, assignments); - } - ; - -window_class: - QUOTEDSTRING - | STR_NG - ; - -ipcsocket: - TOKIPCSOCKET STR - { - config.ipc_socket_path = $2; - } - ; - -restart_state: - TOKRESTARTSTATE STR - { - config.restart_state_path = $2; - } - ; - -exec: - TOKEXEC optional_no_startup_id STR - { - struct Autostart *new = smalloc(sizeof(struct Autostart)); - new->command = $3; - new->no_startup_id = $2; - TAILQ_INSERT_TAIL(&autostarts, new, autostarts); - } - ; - -exec_always: - TOKEXEC_ALWAYS optional_no_startup_id STR - { - struct Autostart *new = smalloc(sizeof(struct Autostart)); - new->command = $3; - new->no_startup_id = $2; - TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always); - } - ; - -optional_border_width: - /* empty */ { $$ = 2; } // 2 pixels is the default value for any type of border - | NUMBER { $$ = $1; } - ; - -optional_no_startup_id: - /* empty */ { $$ = false; } - | TOK_NO_STARTUP_ID { $$ = true; } - ; - -terminal: - TOKTERMINAL STR - { - ELOG("The terminal option is DEPRECATED and has no effect. " - "Please remove it from your configuration file.\n"); - } - ; - -font: - TOKFONT STR - { - config.font = load_font($2, true); - set_font(&config.font); - printf("font %s\n", $2); - FREE(font_pattern); - font_pattern = $2; - } - ; - -single_color: - TOKSINGLECOLOR colorpixel - { - uint32_t *dest = $1; - *dest = $2; - } - ; - -color: - TOKCOLOR colorpixel colorpixel colorpixel - { - struct Colortriple *dest = $1; - - dest->border = $2; - dest->background = $3; - dest->text = $4; - } - | TOKCOLOR colorpixel colorpixel colorpixel colorpixel - { - struct Colortriple *dest = $1; - - dest->border = $2; - dest->background = $3; - dest->text = $4; - dest->indicator = $5; - } - ; - -colorpixel: - HEXCOLOR - { - $$ = get_colorpixel($1); - free($1); - } - ; - - -binding_modifiers: - /* NULL */ { $$ = 0; } - | binding_modifier - | binding_modifiers '+' binding_modifier { $$ = $1 | $3; } - | binding_modifiers '+' { $$ = $1; } - ; - -binding_modifier: - MODIFIER { $$ = $1; } - | TOKCONTROL { $$ = BIND_CONTROL; } - | TOKSHIFT { $$ = BIND_SHIFT; } - ; - -popup_during_fullscreen: - TOK_POPUP_DURING_FULLSCREEN popup_setting - { - DLOG("popup_during_fullscreen setting: %d\n", $2); - config.popup_during_fullscreen = $2; - } - ; - -popup_setting: - TOK_IGNORE { $$ = PDF_IGNORE; } - | TOK_LEAVE_FULLSCREEN { $$ = PDF_LEAVE_FULLSCREEN; } - ; diff --git a/src/config_parser.c b/src/config_parser.c index 80d436b1..0e04bf1c 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -31,6 +31,10 @@ #include #include #include +#include +#include +#include +#include #include "all.h" @@ -38,6 +42,14 @@ #define y(x, ...) yajl_gen_ ## x (command_output.json_gen, ##__VA_ARGS__) #define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char*)str, strlen(str)) +#ifndef TEST_PARSER +static pid_t configerror_pid = -1; +/* The path to the temporary script files used by i3-nagbar. We need to keep + * them around to delete the files in the i3-nagbar SIGCHLD handler. */ +static char *edit_script_path, *pager_script_path; +static struct context *context; +#endif + /******************************************************************************* * The data structures used for parsing. Essentially the current state and a * list of tokens for that state. @@ -649,4 +661,579 @@ int main(int argc, char *argv[]) { context.filename = ""; parse_config(argv[1], &context); } + +#else + +/* + * Writes the given command as a shell script to path. + * Returns true unless something went wrong. + * + */ +static bool write_nagbar_script(const char *path, const char *command) { + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IXUSR); + if (fd == -1) { + warn("Could not create temporary script to store the nagbar command"); + return false; + } + write(fd, "#!/bin/sh\n", strlen("#!/bin/sh\n")); + write(fd, command, strlen(command)); + close(fd); + return true; +} + +/* + * 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 (unlink(edit_script_path) != 0) + warn("Could not delete temporary i3-nagbar script %s", edit_script_path); + if (unlink(pager_script_path) != 0) + warn("Could not delete temporary i3-nagbar script %s", pager_script_path); + + 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; +} + +/* We need ev >= 4 for the following code. Since it is not *that* important (it + * only makes sure that there are no i3-nagbar instances left behind) we still + * support old systems with libev 3. */ +#if EV_VERSION_MAJOR >= 4 +/* + * Cleanup handler. Will be called when i3 exits. Kills i3-nagbar with signal + * SIGKILL (9) to make sure there are no left-over i3-nagbar processes. + * + */ +static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) { + if (configerror_pid != -1) { + LOG("Sending SIGKILL (9) to i3-nagbar with PID %d\n", configerror_pid); + kill(configerror_pid, SIGKILL); + } +} +#endif + +/* + * 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 process. Therefore we + * waitpid() here. */ + waitpid(configerror_pid, NULL, 0); +} + +/* + * Goes through each line of buf (separated by \n) and checks for statements / + * commands which only occur in i3 v4 configuration files. If it finds any, it + * returns version 4, otherwise it returns version 3. + * + */ +static int detect_version(char *buf) { + char *walk = buf; + char *line = buf; + while (*walk != '\0') { + if (*walk != '\n') { + walk++; + continue; + } + + /* 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); + return 4; + } + + /* if this is a bind statement, we can check the command */ + if (strncasecmp(line, "bind", strlen("bind")) == 0) { + char *bind = strchr(line, ' '); + if (bind == NULL) + goto next; + while ((*bind == ' ' || *bind == '\t') && *bind != '\0') + bind++; + if (*bind == '\0') + goto next; + if ((bind = strchr(bind, ' ')) == NULL) + goto next; + while ((*bind == ' ' || *bind == '\t') && *bind != '\0') + bind++; + if (*bind == '\0') + goto next; + if (strncasecmp(bind, "layout", strlen("layout")) == 0 || + strncasecmp(bind, "floating", strlen("floating")) == 0 || + strncasecmp(bind, "workspace", strlen("workspace")) == 0 || + strncasecmp(bind, "focus left", strlen("focus left")) == 0 || + strncasecmp(bind, "focus right", strlen("focus right")) == 0 || + strncasecmp(bind, "focus up", strlen("focus up")) == 0 || + strncasecmp(bind, "focus down", strlen("focus down")) == 0 || + strncasecmp(bind, "border normal", strlen("border normal")) == 0 || + strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 || + strncasecmp(bind, "border pixel", strlen("border pixel")) == 0 || + strncasecmp(bind, "border borderless", strlen("border borderless")) == 0 || + strncasecmp(bind, "--no-startup-id", strlen("--no-startup-id")) == 0 || + strncasecmp(bind, "bar", strlen("bar")) == 0) { + printf("deciding for version 4 due to this line: %.*s\n", (int)(walk-line), line); + return 4; + } + } + +next: + /* advance to the next line */ + walk++; + line = walk; + } + + return 3; +} + +/* + * 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 + * example the script could not be found in $PATH or the i3 executable’s + * directory). + * + */ +static char *migrate_config(char *input, off_t size) { + int writepipe[2]; + int readpipe[2]; + + if (pipe(writepipe) != 0 || + pipe(readpipe) != 0) { + warn("migrate_config: Could not create pipes"); + return NULL; + } + + pid_t pid = fork(); + if (pid == -1) { + warn("Could not fork()"); + return NULL; + } + + /* child */ + if (pid == 0) { + /* close writing end of writepipe, connect reading side to stdin */ + close(writepipe[1]); + dup2(writepipe[0], 0); + + /* close reading end of readpipe, connect writing side to stdout */ + close(readpipe[0]); + dup2(readpipe[1], 1); + + static char *argv[] = { + NULL, /* will be replaced by the executable path */ + NULL + }; + exec_i3_utility("i3-migrate-config-to-v4", argv); + } + + /* parent */ + + /* close reading end of the writepipe (connected to the script’s stdin) */ + close(writepipe[0]); + + /* write the whole config file to the pipe, the script will read everything + * immediately */ + int written = 0; + int ret; + while (written < size) { + if ((ret = write(writepipe[1], input + written, size - written)) < 0) { + warn("Could not write to pipe"); + return NULL; + } + written += ret; + } + close(writepipe[1]); + + /* close writing end of the readpipe (connected to the script’s stdout) */ + close(readpipe[1]); + + /* read the script’s output */ + int conv_size = 65535; + char *converted = malloc(conv_size); + int read_bytes = 0; + do { + if (read_bytes == conv_size) { + conv_size += 65535; + converted = realloc(converted, conv_size); + } + ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes); + if (ret == -1) { + warn("Cannot read from pipe"); + FREE(converted); + return NULL; + } + read_bytes += ret; + } while (ret > 0); + + /* get the returncode */ + int status; + wait(&status); + if (!WIFEXITED(status)) { + fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n"); + return NULL; + } + + int returncode = WEXITSTATUS(status); + if (returncode != 0) { + fprintf(stderr, "Migration process exit code was != 0\n"); + if (returncode == 2) { + fprintf(stderr, "could not start the migration script\n"); + /* TODO: script was not found. tell the user to fix his system or create a v4 config */ + } else if (returncode == 1) { + fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n"); + fprintf(stderr, "# i3 config file (v4)\n"); + /* TODO: nag the user with a message to include a hint for i3 in his config file */ + } + return NULL; + } + + return converted; +} + +/* + * 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 || + bind->release != current->release) + 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); + } + } + } +} + +/* + * 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) { + if (only_check_config) + return; + + fprintf(stderr, "Starting i3-nagbar due to configuration errors\n"); + + /* We need to create a custom script containing our actual command + * since not every terminal emulator which is contained in + * i3-sensible-terminal supports -e with multiple arguments (and not + * all of them support -e with one quoted argument either). + * + * NB: The paths need to be unique, that is, don’t assume users close + * their nagbars at any point in time (and they still need to work). + * */ + edit_script_path = get_process_filename("nagbar-cfgerror-edit"); + pager_script_path = get_process_filename("nagbar-cfgerror-pager"); + + configerror_pid = fork(); + if (configerror_pid == -1) { + warn("Could not fork()"); + return; + } + + /* child */ + if (configerror_pid == 0) { + char *edit_command, *pager_command; + sasprintf(&edit_command, "i3-sensible-editor \"%s\" && i3-msg reload\n", config_path); + sasprintf(&pager_command, "i3-sensible-pager \"%s\"\n", errorfilename); + if (!write_nagbar_script(edit_script_path, edit_command) || + !write_nagbar_script(pager_script_path, pager_command)) + return; + + char *editaction, + *pageraction; + sasprintf(&editaction, "i3-sensible-terminal -e \"%s\"", edit_script_path); + sasprintf(&pageraction, "i3-sensible-terminal -e \"%s\"", pager_script_path); + char *argv[] = { + NULL, /* will be replaced by the executable path */ + "-t", + (context->has_errors ? "error" : "warning"), + "-m", + (context->has_errors ? + "You have an error in your i3 config file!" : + "Your config is outdated. Please fix the warnings to make sure everything works."), + "-b", + "edit config", + editaction, + (errorfilename ? "-b" : NULL), + (context->has_errors ? "show errors" : "show warnings"), + 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); + +/* We need ev >= 4 for the following code. Since it is not *that* important (it + * only makes sure that there are no i3-nagbar instances left behind) we still + * support old systems with libev 3. */ +#if EV_VERSION_MAJOR >= 4 + /* install a cleanup watcher (will be called when i3 exits and i3-nagbar is + * still running) */ + ev_cleanup *cleanup = smalloc(sizeof(ev_cleanup)); + ev_cleanup_init(cleanup, nagbar_cleanup); + ev_cleanup_start(main_loop, cleanup); +#endif +} + + + +void parse_file(const char *f) { + SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables); + int fd, ret, read_bytes = 0; + struct stat stbuf; + char *buf; + FILE *fstr; + char buffer[1026], key[512], value[512]; + + if ((fd = open(f, O_RDONLY)) == -1) + die("Could not open configuration file: %s\n", strerror(errno)); + + if (fstat(fd, &stbuf) == -1) + die("Could not fstat file: %s\n", strerror(errno)); + + buf = scalloc((stbuf.st_size + 1) * sizeof(char)); + while (read_bytes < stbuf.st_size) { + if ((ret = read(fd, buf + read_bytes, (stbuf.st_size - read_bytes))) < 0) + die("Could not read(): %s\n", strerror(errno)); + read_bytes += ret; + } + + if (lseek(fd, 0, SEEK_SET) == (off_t)-1) + die("Could not lseek: %s\n", strerror(errno)); + + if ((fstr = fdopen(fd, "r")) == NULL) + die("Could not fdopen: %s\n", strerror(errno)); + + while (!feof(fstr)) { + if (fgets(buffer, 1024, fstr) == NULL) { + if (feof(fstr)) + break; + die("Could not read configuration file\n"); + } + + /* sscanf implicitly strips whitespace. Also, we skip comments and empty lines. */ + if (sscanf(buffer, "%s %[^\n]", key, value) < 1 || + key[0] == '#' || strlen(key) < 3) + continue; + + if (strcasecmp(key, "set") == 0) { + if (value[0] != '$') { + ELOG("Malformed variable assignment, name has to start with $\n"); + continue; + } + + /* get key/value for this variable */ + char *v_key = value, *v_value; + if (strstr(value, " ") == NULL && strstr(value, "\t") == NULL) { + ELOG("Malformed variable assignment, need a value\n"); + continue; + } + + if (!(v_value = strstr(value, " "))) + v_value = strstr(value, "\t"); + + *(v_value++) = '\0'; + while (*v_value == '\t' || *v_value == ' ') + v_value++; + + struct Variable *new = scalloc(sizeof(struct Variable)); + new->key = sstrdup(v_key); + new->value = sstrdup(v_value); + SLIST_INSERT_HEAD(&variables, new, variables); + DLOG("Got new variable %s = %s\n", v_key, v_value); + continue; + } + } + fclose(fstr); + + /* For every custom variable, see how often it occurs in the file and + * how much extra bytes it requires when replaced. */ + struct Variable *current, *nearest; + int extra_bytes = 0; + /* We need to copy the buffer because we need to invalidate the + * variables (otherwise we will count them twice, which is bad when + * 'extra' is negative) */ + char *bufcopy = sstrdup(buf); + SLIST_FOREACH(current, &variables, variables) { + int extra = (strlen(current->value) - strlen(current->key)); + char *next; + for (next = bufcopy; + next < (bufcopy + stbuf.st_size) && + (next = strcasestr(next, current->key)) != NULL; + next += strlen(current->key)) { + *next = '_'; + extra_bytes += extra; + } + } + FREE(bufcopy); + + /* Then, allocate a new buffer and copy the file over to the new one, + * but replace occurences of our variables */ + char *walk = buf, *destwalk; + char *new = smalloc((stbuf.st_size + extra_bytes + 1) * sizeof(char)); + destwalk = new; + while (walk < (buf + stbuf.st_size)) { + /* Find the next variable */ + SLIST_FOREACH(current, &variables, variables) + current->next_match = strcasestr(walk, current->key); + nearest = NULL; + int distance = stbuf.st_size; + SLIST_FOREACH(current, &variables, variables) { + if (current->next_match == NULL) + continue; + if ((current->next_match - walk) < distance) { + distance = (current->next_match - walk); + nearest = current; + } + } + if (nearest == NULL) { + /* If there are no more variables, we just copy the rest */ + strncpy(destwalk, walk, (buf + stbuf.st_size) - walk); + destwalk += (buf + stbuf.st_size) - walk; + *destwalk = '\0'; + break; + } else { + /* Copy until the next variable, then copy its value */ + strncpy(destwalk, walk, distance); + strncpy(destwalk + distance, nearest->value, strlen(nearest->value)); + walk += distance + strlen(nearest->key); + destwalk += distance + strlen(nearest->value); + } + } + + /* analyze the string to find out whether this is an old config file (3.x) + * or a new config file (4.x). If it’s old, we run the converter script. */ + int version = detect_version(buf); + if (version == 3) { + /* We need to convert this v3 configuration */ + char *converted = migrate_config(new, stbuf.st_size); + if (converted != NULL) { + ELOG("\n"); + ELOG("****************************************************************\n"); + ELOG("NOTE: Automatically converted configuration file from v3 to v4.\n"); + ELOG("\n"); + ELOG("Please convert your config file to v4. You can use this command:\n"); + ELOG(" mv %s %s.O\n", f, f); + ELOG(" i3-migrate-config-to-v4 %s.O > %s\n", f, f); + ELOG("****************************************************************\n"); + ELOG("\n"); + free(new); + new = converted; + } else { + printf("\n"); + printf("**********************************************************************\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"); + } + } + + + context = scalloc(sizeof(struct context)); + context->filename = f; + + struct ConfigResult *config_output = parse_config(new, context); + yajl_gen_free(config_output->json_gen); + + check_for_duplicate_bindings(context); + + if (context->has_errors || context->has_warnings) { + ELOG("FYI: You are using i3 version " I3_VERSION "\n"); + if (version == 3) + ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n"); + start_configerror_nagbar(f); + } + + FREE(context->line_copy); + free(context); + free(new); + free(buf); + + while (!SLIST_EMPTY(&variables)) { + current = SLIST_FIRST(&variables); + FREE(current->key); + FREE(current->value); + SLIST_REMOVE_HEAD(&variables, variables); + FREE(current); + } +} + #endif diff --git a/src/i3.mk b/src/i3.mk index dedf4e27..38099fb5 100644 --- a/src/i3.mk +++ b/src/i3.mk @@ -2,7 +2,6 @@ ALL_TARGETS += i3 INSTALL_TARGETS += install-i3 CLEAN_TARGETS += clean-i3 -i3_SOURCES_GENERATED = src/cfgparse.tab.c src/cfgparse.yy.c i3_SOURCES := $(filter-out $(i3_SOURCES_GENERATED),$(wildcard src/*.c)) i3_HEADERS_CMDPARSER := $(wildcard include/GENERATED_*.h) i3_HEADERS := $(filter-out $(i3_HEADERS_CMDPARSER),$(wildcard include/*.h)) @@ -37,14 +36,6 @@ src/%.o: src/%.c $(i3_HEADERS_DEP) echo "[i3] CC $<" $(CC) $(I3_CPPFLAGS) $(XCB_CPPFLAGS) $(CPPFLAGS) $(i3_CFLAGS) $(I3_CFLAGS) $(CFLAGS) $(PCH_FLAGS) -c -o $@ ${canonical_path}/$< -src/cfgparse.yy.c: src/cfgparse.l src/cfgparse.tab.o $(i3_HEADERS_DEP) - echo "[i3] LEX $<" - $(FLEX) -i -o $@ ${canonical_path}/$< - -src/cfgparse.tab.c: src/cfgparse.y $(i3_HEADERS_DEP) - echo "[i3] YACC $<" - $(BISON) --debug --verbose -b $(basename $< .y) -d ${canonical_path}/$< - # This target compiles the command parser twice: # Once with -DTEST_PARSER, creating a stand-alone executable used for tests, # and once as an object file for i3. @@ -96,4 +87,4 @@ install-i3: i3 clean-i3: echo "[i3] Clean" - rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3-config-parser.stamp i3 src/*.gcno src/cfgparse.{output,dot,tab.h,y.o} src/cmdparse.* + rm -f $(i3_OBJECTS) $(i3_SOURCES_GENERATED) $(i3_HEADERS_CMDPARSER) include/loglevels.h loglevels.tmp include/all.h.pch i3-command-parser.stamp i3-config-parser.stamp i3 src/*.gcno src/cfgparse.* src/cmdparse.* diff --git a/src/main.c b/src/main.c index cb6767fb..1369f4bb 100644 --- a/src/main.c +++ b/src/main.c @@ -374,8 +374,7 @@ int main(int argc, char *argv[]) { fake_outputs = sstrdup(optarg); break; } else if (strcmp(long_options[option_index].name, "force-old-config-parser-v4.4-only") == 0) { - LOG("FORCING OLD CONFIG PARSER!\n"); - force_old_config_parser = true; + ELOG("You are passing --force-old-config-parser-v4.4-only, but that flag was removed by now.\n"); break; } /* fall-through */