]> git.sur5r.net Git - i3/i3/blobdiff - src/cfgparse.y
Merge branch 'master' into next
[i3/i3] / src / cfgparse.y
index 86e9ea98ea58371f85dc3e6a00bc5f1b62f7ab76..dbe227ddffd1b2bcce745d1c04cd8a2efc878e74 100644 (file)
 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;
 
 typedef struct yy_buffer_state *YY_BUFFER_STATE;
 extern int yylex(struct context *context);
@@ -98,7 +103,9 @@ static int detect_version(char *buf) {
                 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 borderless", strlen("border borderless")) == 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;
             }
@@ -240,6 +247,10 @@ static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
     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.
@@ -251,6 +262,7 @@ static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
         kill(configerror_pid, SIGKILL);
     }
 }
+#endif
 
 /*
  * Starts an i3-nagbar process which alerts the user that his configuration
@@ -260,7 +272,10 @@ static void nagbar_cleanup(EV_P_ ev_cleanup *watcher, int revent) {
  *
  */
 static void start_configerror_nagbar(const char *config_path) {
-    fprintf(stderr, "Would start i3-nagscreen now\n");
+    if (only_check_config)
+        return;
+
+    fprintf(stderr, "Starting i3-nagbar due to configuration errors\n");
     configerror_pid = fork();
     if (configerror_pid == -1) {
         warn("Could not fork()");
@@ -271,19 +286,21 @@ static void start_configerror_nagbar(const char *config_path) {
     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);
+        sasprintf(&editaction, "i3-sensible-terminal -e sh -c \"i3-sensible-editor \\\"%s\\\" && i3-msg reload\"", config_path);
+        sasprintf(&pageraction, "i3-sensible-terminal -e i3-sensible-pager \"%s\"", errorfilename);
         char *argv[] = {
             NULL, /* will be replaced by the executable path */
+            "-t",
+            (context->has_errors ? "error" : "warning"),
             "-m",
-            "You have an error in your i3 config file!",
+            (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),
-            "show errors",
+            (context->has_errors ? "show errors" : "show warnings"),
             pageraction,
             NULL
         };
@@ -296,11 +313,16 @@ static void start_configerror_nagbar(const char *config_path) {
     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
 }
 
 /*
@@ -376,6 +398,31 @@ static void check_for_duplicate_bindings(struct context *context) {
     }
 }
 
+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;
@@ -423,12 +470,14 @@ void parse_file(const char *f) {
 
             /* get key/value for this variable */
             char *v_key = value, *v_value;
-            if ((v_value = strstr(value, " ")) == NULL &&
-                (v_value = strstr(value, "\t")) == NULL) {
+            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';
 
             struct Variable *new = scalloc(sizeof(struct Variable));
@@ -453,7 +502,8 @@ void parse_file(const char *f) {
         int extra = (strlen(current->value) - strlen(current->key));
         char *next;
         for (next = bufcopy;
-             (next = strcasestr(bufcopy + (next - bufcopy), current->key)) != NULL;
+             next < (bufcopy + stbuf.st_size) &&
+             (next = strcasestr(next, current->key)) != NULL;
              next += strlen(current->key)) {
             *next = '_';
             extra_bytes += extra;
@@ -502,15 +552,15 @@ void parse_file(const char *f) {
         /* We need to convert this v3 configuration */
         char *converted = migrate_config(new, stbuf.st_size);
         if (converted != NULL) {
-            printf("\n");
-            printf("****************************************************************\n");
-            printf("NOTE: Automatically converted configuration file from v3 to v4.\n");
-            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 %s.O > %s\n", f, f);
-            printf("****************************************************************\n");
-            printf("\n");
+            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 {
@@ -536,13 +586,41 @@ void parse_file(const char *f) {
 
     check_for_duplicate_bindings(context);
 
-    if (context->has_errors) {
+    /* 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);
     }
 
     yylex_destroy();
     FREE(context->line_copy);
     free(context);
+    FREE(font_pattern);
     free(new);
     free(buf);
 
@@ -573,7 +651,7 @@ void parse_file(const char *f) {
 %token  <string>        WORD                        "<word>"
 %token  <string>        STR                         "<string>"
 %token  <string>        STR_NG                      "<string (non-greedy)>"
-%token  <string>        HEX                         "<hex>"
+%token  <string>        HEXCOLOR                    "#<hex>"
 %token  <string>        OUTPUT                      "<RandR output>"
 %token                  TOKBINDCODE
 %token                  TOKTERMINAL
@@ -597,6 +675,7 @@ void parse_file(const char *f) {
 %token  <color>         TOKCOLOR
 %token                  TOKARROW                    "→"
 %token                  TOKMODE                     "mode"
+%token                  TOK_BAR                     "bar"
 %token                  TOK_ORIENTATION             "default_orientation"
 %token                  TOK_HORIZ                   "horizontal"
 %token                  TOK_VERT                    "vertical"
@@ -609,6 +688,8 @@ void parse_file(const char *f) {
 %token                  TOK_1PIXEL                  "1pixel"
 %token                  TOKFOCUSFOLLOWSMOUSE        "focus_follows_mouse"
 %token                  TOK_FORCE_FOCUS_WRAPPING    "force_focus_wrapping"
+%token                  TOK_FORCE_XINERAMA          "force_xinerama"
+%token                  TOK_WORKSPACE_AUTO_BAF      "workspace_auto_back_and_forth"
 %token                  TOKWORKSPACEBAR             "workspace_bar"
 %token                  TOK_DEFAULT                 "default"
 %token                  TOK_STACKING                "stacking"
@@ -619,9 +700,33 @@ void parse_file(const char *f) {
 %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_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_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"
@@ -639,8 +744,12 @@ void parse_file(const char *f) {
 %type   <number>        colorpixel
 %type   <number>        bool
 %type   <number>        popup_setting
+%type   <number>        bar_position_position
+%type   <number>        bar_mode_mode
+%type   <number>        optional_no_startup_id
 %type   <string>        command
 %type   <string>        word_or_number
+%type   <string>        qstring_or_number
 %type   <string>        optional_workspace_name
 %type   <string>        workspace_name
 %type   <string>        window_class
@@ -656,6 +765,7 @@ line:
     bindline
     | for_window
     | mode
+    | bar
     | floating_modifier
     | orientation
     | workspace_layout
@@ -663,6 +773,8 @@ line:
     | new_float
     | focus_follows_mouse
     | force_focus_wrapping
+    | force_xinerama
+    | workspace_back_and_forth
     | workspace_bar
     | workspace
     | assign
@@ -782,6 +894,12 @@ criterion:
         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);
@@ -826,13 +944,16 @@ criterion:
     }
     ;
 
-
+qstring_or_number:
+    QUOTEDSTRING
+    | NUMBER { sasprintf(&$$, "%d", $1); }
+    ;
 
 word_or_number:
     WORD
     | NUMBER
     {
-        asprintf(&$$, "%d", $1);
+        sasprintf(&$$, "%d", $1);
     }
     ;
 
@@ -878,6 +999,221 @@ modeline:
     }
     ;
 
+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, &current_bar, sizeof(Barconfig));
+        TAILQ_INSERT_TAIL(&barconfigs, bar_config, configs);
+
+        memset(&current_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_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_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
+    {
+        DLOG("focused_ws = %s and %s\n", $2, $3);
+        current_bar.colors.focused_workspace_text = $2;
+        current_bar.colors.focused_workspace_bg = $3;
+    }
+    ;
+
+bar_color_active_workspace:
+    TOK_BAR_COLOR_ACTIVE_WORKSPACE HEXCOLOR HEXCOLOR
+    {
+        DLOG("active_ws = %s and %s\n", $2, $3);
+        current_bar.colors.active_workspace_text = $2;
+        current_bar.colors.active_workspace_bg = $3;
+    }
+    ;
+
+bar_color_inactive_workspace:
+    TOK_BAR_COLOR_INACTIVE_WORKSPACE HEXCOLOR HEXCOLOR
+    {
+        DLOG("inactive_ws = %s and %s\n", $2, $3);
+        current_bar.colors.inactive_workspace_text = $2;
+        current_bar.colors.inactive_workspace_bg = $3;
+    }
+    ;
+
+bar_color_urgent_workspace:
+    TOK_BAR_COLOR_URGENT_WORKSPACE HEXCOLOR HEXCOLOR
+    {
+        DLOG("urgent_ws = %s and %s\n", $2, $3);
+        current_bar.colors.urgent_workspace_text = $2;
+        current_bar.colors.urgent_workspace_bg = $3;
+    }
+    ;
+
 floating_modifier:
     TOKFLOATING_MODIFIER binding_modifiers
     {
@@ -1005,6 +1341,22 @@ force_focus_wrapping:
     }
     ;
 
+force_xinerama:
+    TOK_FORCE_XINERAMA bool
+    {
+        DLOG("force xinerama = %d\n", $2);
+        config.force_xinerama = $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;
+    }
+    ;
+
 workspace_bar:
     TOKWORKSPACEBAR bool
     {
@@ -1014,40 +1366,39 @@ workspace_bar:
     ;
 
 workspace:
-    TOKWORKSPACE NUMBER TOKOUTPUT OUTPUT optional_workspace_name
+    TOKWORKSPACE qstring_or_number TOKOUTPUT OUTPUT optional_workspace_name
     {
-        int ws_num = $2;
-        if (ws_num < 1) {
-            DLOG("Invalid workspace assignment, workspace number %d out of range\n", ws_num);
-        } else {
-            char *ws_name = NULL;
-            if ($5 == NULL) {
-                asprintf(&ws_name, "%d", ws_num);
-            } else {
-                ws_name = $5;
-            }
+        char *ws_name = $2;
+
+        if ($5 != NULL) {
+            ELOG("The old (v3) syntax workspace <number> output <output> <name> is deprecated.\n");
+            ELOG("Please use the new syntax: workspace \"<workspace>\" output <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("Should assign 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;
+        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;
-                TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
+                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
     {
@@ -1086,7 +1437,7 @@ assign:
         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_errors = true;
+        context->has_warnings = true;
         printf("assignment of %s to *%s*\n", $2, $3);
         char *workspace = $3;
         char *criteria = $2;
@@ -1099,20 +1450,14 @@ assign:
         if ((separator = strchr(criteria, '/')) != NULL) {
             *(separator++) = '\0';
             char *pattern;
-            if (asprintf(&pattern, "(?i)%s", separator) == -1) {
-                ELOG("asprintf failed\n");
-                break;
-            }
+            sasprintf(&pattern, "(?i)%s", separator);
             match->title = regex_new(pattern);
             free(pattern);
             printf("  title = %s\n", separator);
         }
         if (*criteria != '\0') {
             char *pattern;
-            if (asprintf(&pattern, "(?i)%s", criteria) == -1) {
-                ELOG("asprintf failed\n");
-                break;
-            }
+            sasprintf(&pattern, "(?i)%s", criteria);
             match->class = regex_new(pattern);
             free(pattern);
             printf("  class = %s\n", criteria);
@@ -1148,6 +1493,10 @@ assign:
     }
     | TOKASSIGN match STR
     {
+        if (match_is_empty(&current_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;
@@ -1177,23 +1526,30 @@ restart_state:
     ;
 
 exec:
-    TOKEXEC STR
+    TOKEXEC optional_no_startup_id STR
     {
         struct Autostart *new = smalloc(sizeof(struct Autostart));
-        new->command = $2;
+        new->command = $3;
+        new->no_startup_id = $2;
         TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
     }
     ;
 
 exec_always:
-    TOKEXEC_ALWAYS STR
+    TOKEXEC_ALWAYS optional_no_startup_id STR
     {
         struct Autostart *new = smalloc(sizeof(struct Autostart));
-        new->command = $2;
+        new->command = $3;
+        new->no_startup_id = $2;
         TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always);
     }
     ;
 
+optional_no_startup_id:
+    /* empty */ { $$ = false; }
+    | TOK_NO_STARTUP_ID  { $$ = true; }
+    ;
+
 terminal:
     TOKTERMINAL STR
     {
@@ -1206,8 +1562,10 @@ font:
     TOKFONT STR
     {
         config.font = load_font($2, true);
+        set_font(&config.font);
         printf("font %s\n", $2);
-        free($2);
+        FREE(font_pattern);
+        font_pattern = $2;
     }
     ;
 
@@ -1231,14 +1589,10 @@ color:
     ;
 
 colorpixel:
-    '#' HEX
+    HEXCOLOR
     {
-        char *hex;
-        if (asprintf(&hex, "#%s", $2) == -1)
-            die("asprintf()");
-        free($2);
-        $$ = get_colorpixel(hex);
-        free(hex);
+        $$ = get_colorpixel($1);
+        free($1);
     }
     ;