]> git.sur5r.net Git - i3/i3/blobdiff - src/cfgparse.y
Merge branch 'tree' into next
[i3/i3] / src / cfgparse.y
index a5e0f0baca70f3860a78e388a3f4d31fbe974e56..ecfa665859c51520d8bbe557633192e561a03266 100644 (file)
@@ -5,14 +5,21 @@
  */
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <limits.h>
 
 #include "all.h"
 
+static pid_t configerror_pid = -1;
+
+static Match current_match;
+
 typedef struct yy_buffer_state *YY_BUFFER_STATE;
 extern int yylex(struct context *context);
 extern int yyparse(void);
+extern int yylex_destroy(void);
 extern FILE *yyin;
 YY_BUFFER_STATE yy_scan_string(const char *);
 
@@ -25,17 +32,18 @@ static struct context *context;
 //int yydebug = 1;
 
 void yyerror(const char *error_message) {
+    context->has_errors = true;
+
     ELOG("\n");
     ELOG("CONFIG: %s\n", error_message);
     ELOG("CONFIG: in file \"%s\", line %d:\n",
         context->filename, context->line_number);
     ELOG("CONFIG:   %s\n", context->line_copy);
-    ELOG("CONFIG:   ");
+    char buffer[context->last_column+1];
+    buffer[context->last_column] = '\0';
     for (int c = 1; c <= context->last_column; c++)
-        if (c >= context->first_column)
-            printf("^");
-        else printf(" ");
-    printf("\n");
+        buffer[c-1] = (c >= context->first_column ? '^' : ' ');
+    ELOG("CONFIG:   %s\n", buffer);
     ELOG("\n");
 }
 
@@ -43,6 +51,266 @@ int yywrap() {
     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 borderless", strlen("border borderless")) == 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.pl 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.pl", 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");
+            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 (!WIFEXITED(watcher->rstatus)) {
+        fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
+        return;
+    }
+
+    int exitcode = WEXITSTATUS(watcher->rstatus);
+    printf("i3-nagbar process exited with status %d\n", exitcode);
+    if (exitcode == 2) {
+        fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
+    }
+
+    configerror_pid = -1;
+}
+
+/*
+ * Starts an i3-nagbar process which alerts the user that his configuration
+ * file contains one or more errors. Also offers two buttons: One to launch an
+ * $EDITOR on the config file and another one to launch a $PAGER on the error
+ * logfile.
+ *
+ */
+static void start_configerror_nagbar(const char *config_path) {
+    fprintf(stderr, "Would start i3-nagscreen now\n");
+    configerror_pid = fork();
+    if (configerror_pid == -1) {
+        warn("Could not fork()");
+        return;
+    }
+
+    /* child */
+    if (configerror_pid == 0) {
+        char *editaction,
+             *pageraction;
+        if (asprintf(&editaction, TERM_EMU " -e sh -c \"${EDITOR:-vi} \"%s\" && i3-msg reload\"", config_path) == -1)
+            exit(1);
+        if (asprintf(&pageraction, TERM_EMU " -e sh -c \"${PAGER:-less} \"%s\"\"", errorfilename) == -1)
+            exit(1);
+        char *argv[] = {
+            NULL, /* will be replaced by the executable path */
+            "-m",
+            "You have an error in your i3 config file!",
+            "-b",
+            "edit config",
+            editaction,
+            (errorfilename ? "-b" : NULL),
+            "show errors",
+            pageraction,
+            NULL
+        };
+        exec_i3_utility("i3-nagbar", argv);
+    }
+
+    /* parent */
+    /* install a child watcher */
+    ev_child *child = smalloc(sizeof(ev_child));
+    ev_child_init(child, &nagbar_exited, configerror_pid, 0);
+    ev_child_start(main_loop, child);
+}
+
+/*
+ * Kills the configerror i3-nagbar process, if any.
+ *
+ * Called when reloading/restarting.
+ *
+ * If wait_for_it is set (restarting), this function will waitpid(), otherwise,
+ * ev is assumed to handle it (reloading).
+ *
+ */
+void kill_configerror_nagbar(bool wait_for_it) {
+    if (configerror_pid == -1)
+        return;
+
+    if (kill(configerror_pid, SIGTERM) == -1)
+        warn("kill(configerror_nagbar) failed");
+
+    if (!wait_for_it)
+        return;
+
+    /* When restarting, we don’t enter the ev main loop anymore and after the
+     * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD
+     * for us and we would end up with a <defunct> process. Therefore we
+     * waitpid() here. */
+    waitpid(configerror_pid, NULL, 0);
+}
+
 void parse_file(const char *f) {
     SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
     int fd, ret, read_bytes = 0;
@@ -157,6 +425,35 @@ void parse_file(const char *f) {
         }
     }
 
+    /* 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) {
+            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.pl %s.O > %s\n", f, f);
+            printf("****************************************************************\n");
+            printf("\n");
+            free(new);
+            new = converted;
+        } else {
+            printf("\n");
+            printf("**********************************************************************\n");
+            printf("ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4.pl\n");
+            printf("was not correctly installed on your system?\n");
+            printf("**********************************************************************\n");
+            printf("\n");
+        }
+    }
+
+    /* now lex/parse it */
     yy_scan_string(new);
 
     context = scalloc(sizeof(struct context));
@@ -167,6 +464,11 @@ void parse_file(const char *f) {
         exit(1);
     }
 
+    if (context->has_errors) {
+        start_configerror_nagbar(f);
+    }
+
+    yylex_destroy();
     FREE(context->line_copy);
     free(context);
     free(new);
@@ -183,7 +485,6 @@ void parse_file(const char *f) {
 
 %}
 
-%expect 1
 %error-verbose
 %lex-param { struct context *context }
 
@@ -210,7 +511,6 @@ void parse_file(const char *f) {
 %token  <number>        MODIFIER                    "<modifier>"
 %token                  TOKCONTROL                  "control"
 %token                  TOKSHIFT                    "shift"
-%token                  WHITESPACE                  "<whitespace>"
 %token                  TOKFLOATING_MODIFIER        "floating_modifier"
 %token  <string>        QUOTEDSTRING                "<quoted string>"
 %token                  TOKWORKSPACE                "workspace"
@@ -220,6 +520,7 @@ void parse_file(const char *f) {
 %token                  TOKIPCSOCKET                "ipc_socket"
 %token                  TOKRESTARTSTATE             "restart_state"
 %token                  TOKEXEC                     "exec"
+%token                  TOKEXEC_ALWAYS              "exec_always"
 %token  <single_color>  TOKSINGLECOLOR
 %token  <color>         TOKCOLOR
 %token                  TOKARROW                    "→"
@@ -234,6 +535,7 @@ void parse_file(const char *f) {
 %token                  TOK_NONE                    "none"
 %token                  TOK_1PIXEL                  "1pixel"
 %token                  TOKFOCUSFOLLOWSMOUSE        "focus_follows_mouse"
+%token                  TOK_FORCE_FOCUS_WRAPPING    "force_focus_wrapping"
 %token                  TOKWORKSPACEBAR             "workspace_bar"
 %token                  TOK_DEFAULT                 "default"
 %token                  TOK_STACKING                "stacking"
@@ -242,6 +544,13 @@ void parse_file(const char *f) {
 %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_MARK            "mark"
+%token              TOK_CLASS           "class"
+%token              TOK_ID              "id"
+%token              TOK_CON_ID          "con_id"
+%token              TOK_TITLE           "title"
 
 %type   <binding>       binding
 %type   <binding>       bindcode
@@ -260,30 +569,31 @@ void parse_file(const char *f) {
 %type   <string>        optional_workspace_name
 %type   <string>        workspace_name
 %type   <string>        window_class
-%type   <match>         assign_target
 
 %%
 
 lines: /* empty */
-    | lines WHITESPACE line
     | lines error
     | lines line
     ;
 
 line:
     bindline
+    | for_window
     | mode
     | floating_modifier
     | orientation
     | workspace_layout
     | new_window
     | focus_follows_mouse
+    | force_focus_wrapping
     | workspace_bar
     | workspace
     | assign
     | ipcsocket
     | restart_state
     | exec
+    | exec_always
     | single_color
     | color
     | terminal
@@ -308,38 +618,122 @@ bindline:
     ;
 
 binding:
-    TOKBINDCODE WHITESPACE bindcode         { $$ = $3; }
-    | TOKBINDSYM WHITESPACE bindsym         { $$ = $3; }
+    TOKBINDCODE bindcode         { $$ = $2; }
+    | TOKBINDSYM bindsym         { $$ = $2; }
     ;
 
 bindcode:
-    binding_modifiers NUMBER WHITESPACE command
+    binding_modifiers NUMBER command
     {
-        printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $4);
+        printf("\tFound keycode binding mod%d with key %d and command %s\n", $1, $2, $3);
         Binding *new = scalloc(sizeof(Binding));
 
         new->keycode = $2;
         new->mods = $1;
-        new->command = $4;
+        new->command = $3;
 
         $$ = new;
     }
     ;
 
 bindsym:
-    binding_modifiers word_or_number WHITESPACE command
+    binding_modifiers word_or_number command
     {
-        printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $4);
+        printf("\tFound keysym binding mod%d with key %s and command %s\n", $1, $2, $3);
         Binding *new = scalloc(sizeof(Binding));
 
         new->symbol = $2;
         new->mods = $1;
-        new->command = $4;
+        new->command = $3;
 
         $$ = new;
     }
     ;
 
+for_window:
+    TOK_FOR_WINDOW match command
+    {
+        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(&current_match);
+    }
+    ;
+
+matchend:
+    ']'
+    {
+        printf("match specification finished\n");
+    }
+    ;
+
+criteria:
+    TOK_CLASS '=' STR
+    {
+        printf("criteria: class = %s\n", $3);
+        current_match.class = $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 = $3;
+    }
+    | TOK_TITLE '=' STR
+    {
+        printf("criteria: title = %s\n", $3);
+        current_match.title = $3;
+    }
+    ;
+
+
+
 word_or_number:
     WORD
     | NUMBER
@@ -349,13 +743,13 @@ word_or_number:
     ;
 
 mode:
-    TOKMODE WHITESPACE QUOTEDSTRING WHITESPACE '{' modelines '}'
+    TOKMODE QUOTEDSTRING '{' modelines '}'
     {
-        if (strcasecmp($3, "default") == 0) {
+        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", $3);
+        printf("\t now in mode %s\n", $2);
         printf("\t current bindings = %p\n", current_bindings);
         Binding *binding;
         TAILQ_FOREACH(binding, current_bindings, bindings) {
@@ -364,7 +758,7 @@ mode:
         }
 
         struct Mode *mode = scalloc(sizeof(struct Mode));
-        mode->name = $3;
+        mode->name = $2;
         mode->bindings = current_bindings;
         current_bindings = NULL;
         SLIST_INSERT_HEAD(&modes, mode, modes);
@@ -378,8 +772,7 @@ modelines:
     ;
 
 modeline:
-    WHITESPACE
-    | comment
+    comment
     | binding
     {
         if (current_bindings == NULL) {
@@ -392,18 +785,18 @@ modeline:
     ;
 
 floating_modifier:
-    TOKFLOATING_MODIFIER WHITESPACE binding_modifiers
+    TOKFLOATING_MODIFIER binding_modifiers
     {
-        DLOG("floating modifier = %d\n", $3);
-        config.floating_modifier = $3;
+        DLOG("floating modifier = %d\n", $2);
+        config.floating_modifier = $2;
     }
     ;
 
 orientation:
-    TOK_ORIENTATION WHITESPACE direction
+    TOK_ORIENTATION direction
     {
-        DLOG("New containers should start with split direction %d\n", $3);
-        config.default_orientation = $3;
+        DLOG("New containers should start with split direction %d\n", $2);
+        config.default_orientation = $2;
     }
     ;
 
@@ -414,10 +807,10 @@ direction:
     ;
 
 workspace_layout:
-    TOK_WORKSPACE_LAYOUT WHITESPACE layout_mode
+    TOK_WORKSPACE_LAYOUT layout_mode
     {
-        DLOG("new containers will be in mode %d\n", $3);
-        config.default_layout = $3;
+        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
@@ -438,11 +831,11 @@ workspace_layout:
         }
 #endif
     }
-    | TOK_WORKSPACE_LAYOUT WHITESPACE TOKSTACKLIMIT WHITESPACE TOKSTACKLIMIT WHITESPACE NUMBER
+    | TOK_WORKSPACE_LAYOUT TOKSTACKLIMIT TOKSTACKLIMIT NUMBER
     {
-        DLOG("stack-limit %d with val %d\n", $5, $7);
-        config.container_stack_limit = $5;
-        config.container_stack_limit_value = $7;
+        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 */
@@ -465,10 +858,10 @@ layout_mode:
     ;
 
 new_window:
-    TOKNEWWINDOW WHITESPACE border_style
+    TOKNEWWINDOW border_style
     {
-        DLOG("new windows should start with border style %d\n", $3);
-        config.default_border = $3;
+        DLOG("new windows should start with border style %d\n", $2);
+        config.default_border = $2;
     }
     ;
 
@@ -495,53 +888,61 @@ bool:
     ;
 
 focus_follows_mouse:
-    TOKFOCUSFOLLOWSMOUSE WHITESPACE bool
+    TOKFOCUSFOLLOWSMOUSE bool
+    {
+        DLOG("focus follows mouse = %d\n", $2);
+        config.disable_focus_follows_mouse = !($2);
+    }
+    ;
+
+force_focus_wrapping:
+    TOK_FORCE_FOCUS_WRAPPING bool
     {
-        DLOG("focus follows mouse = %d\n", $3);
-        config.disable_focus_follows_mouse = !($3);
+        DLOG("force focus wrapping = %d\n", $2);
+        config.force_focus_wrapping = $2;
     }
     ;
 
 workspace_bar:
-    TOKWORKSPACEBAR WHITESPACE bool
+    TOKWORKSPACEBAR bool
     {
-        DLOG("workspace bar = %d\n", $3);
-        config.disable_workspace_bar = !($3);
+        DLOG("workspace bar = %d\n", $2);
+        config.disable_workspace_bar = !($2);
     }
     ;
 
 workspace:
-    TOKWORKSPACE WHITESPACE NUMBER WHITESPACE TOKOUTPUT WHITESPACE OUTPUT optional_workspace_name
+    TOKWORKSPACE NUMBER TOKOUTPUT OUTPUT optional_workspace_name
     {
-        int ws_num = $3;
+        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 ($8 == NULL) {
+            if ($5 == NULL) {
                 asprintf(&ws_name, "%d", ws_num);
             } else {
-                ws_name = $8;
+                ws_name = $5;
             }
 
-            DLOG("Should assign workspace %s to output %s\n", ws_name, $7);
+            DLOG("Should assign workspace %s to output %s\n", ws_name, $4);
             struct Workspace_Assignment *assignment = scalloc(sizeof(struct Workspace_Assignment));
             assignment->name = ws_name;
-            assignment->output = $7;
+            assignment->output = $4;
             TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
         }
     }
-    | TOKWORKSPACE WHITESPACE NUMBER WHITESPACE workspace_name
+    | TOKWORKSPACE NUMBER workspace_name
     {
-        int ws_num = $3;
+        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", $5);
+            DLOG("workspace name to: %s\n", $3);
 #if 0
-            if ($<string>5 != NULL) {
-                    workspace_set_name(workspace_get(ws_num - 1), $<string>5);
-                    free($<string>5);
+            if ($<string>3 != NULL) {
+                    workspace_set_name(workspace_get(ws_num - 1), $<string>3);
+                    free($<string>3);
             }
 #endif
         }
@@ -549,8 +950,8 @@ workspace:
     ;
 
 optional_workspace_name:
-    /* empty */                     { $$ = NULL; }
-    | WHITESPACE workspace_name     { $$ = $2; }
+    /* empty */          { $$ = NULL; }
+    | workspace_name     { $$ = $1; }
     ;
 
 workspace_name:
@@ -560,57 +961,54 @@ workspace_name:
     ;
 
 assign:
-    TOKASSIGN WHITESPACE window_class WHITESPACE optional_arrow assign_target
+    TOKASSIGN window_class STR
     {
-        printf("assignment of %s\n", $3);
+        printf("assignment of %s to *%s*\n", $2, $3);
+        char *workspace = $3;
+        char *criteria = $2;
 
-        struct Match *match = $6;
+        Assignment *assignment = scalloc(sizeof(Assignment));
+        Match *match = &(assignment->match);
+        match_init(match);
 
         char *separator = NULL;
-        if ((separator = strchr($3, '/')) != NULL) {
+        if ((separator = strchr(criteria, '/')) != NULL) {
             *(separator++) = '\0';
             match->title = sstrdup(separator);
         }
-        if (*$3 != '\0')
-            match->class = sstrdup($3);
-        free($3);
+        if (*criteria != '\0')
+            match->class = sstrdup(criteria);
+        free(criteria);
 
         printf("  class = %s\n", match->class);
         printf("  title = %s\n", match->title);
-        if (match->insert_where == M_ASSIGN_WS)
-            printf("  to ws %s\n", match->target_ws);
-        TAILQ_INSERT_TAIL(&assignments, match, assignments);
-    }
-    ;
 
-assign_target:
-    NUMBER
-    {
-        /* TODO: named workspaces */
-        Match *match = smalloc(sizeof(Match));
-        match_init(match);
-        match->insert_where = M_ASSIGN_WS;
-        asprintf(&(match->target_ws), "%d", $1);
-        $$ = match;
-    }
-    | '~'
-    {
-        /* TODO: compatiblity */
-#if 0
-        struct Assignment *new = scalloc(sizeof(struct Assignment));
-        new->floating = ASSIGN_FLOATING_ONLY;
-        $<assignment>$ = new;
-#endif
-    }
-    | '~' NUMBER
-    {
-        /* TODO: compatiblity */
-#if 0
-        struct Assignment *new = scalloc(sizeof(struct Assignment));
-        new->workspace = $<number>2;
-        new->floating = ASSIGN_FLOATING;
-        $<assignment>$ = new;
-#endif
+        /* Compatibility with older versions: If the assignment target starts
+         * with ~, we create the equivalent of:
+         *
+         * for_window [class="foo"] mode floating
+         */
+        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);
     }
     ;
 
@@ -619,36 +1017,40 @@ window_class:
     | STR_NG
     ;
 
-optional_arrow:
-    /* NULL */
-    | TOKARROW WHITESPACE
-    ;
-
 ipcsocket:
-    TOKIPCSOCKET WHITESPACE STR
+    TOKIPCSOCKET STR
     {
-        config.ipc_socket_path = $3;
+        config.ipc_socket_path = $2;
     }
     ;
 
 restart_state:
-    TOKRESTARTSTATE WHITESPACE STR
+    TOKRESTARTSTATE STR
     {
-        config.restart_state_path = $3;
+        config.restart_state_path = $2;
     }
     ;
 
 exec:
-    TOKEXEC WHITESPACE STR
+    TOKEXEC STR
     {
         struct Autostart *new = smalloc(sizeof(struct Autostart));
-        new->command = $3;
+        new->command = $2;
         TAILQ_INSERT_TAIL(&autostarts, new, autostarts);
     }
     ;
 
+exec_always:
+    TOKEXEC_ALWAYS STR
+    {
+        struct Autostart *new = smalloc(sizeof(struct Autostart));
+        new->command = $2;
+        TAILQ_INSERT_TAIL(&autostarts_always, new, autostarts_always);
+    }
+    ;
+
 terminal:
-    TOKTERMINAL WHITESPACE STR
+    TOKTERMINAL STR
     {
         ELOG("The terminal option is DEPRECATED and has no effect. "
             "Please remove it from your configuration file.\n");
@@ -656,29 +1058,30 @@ terminal:
     ;
 
 font:
-    TOKFONT WHITESPACE STR
+    TOKFONT STR
     {
-        config.font = load_font($3, true);
-        printf("font %s\n", $3);
+        config.font = load_font($2, true);
+        printf("font %s\n", $2);
+        free($2);
     }
     ;
 
 single_color:
-    TOKSINGLECOLOR WHITESPACE colorpixel
+    TOKSINGLECOLOR colorpixel
     {
         uint32_t *dest = $1;
-        *dest = $3;
+        *dest = $2;
     }
     ;
 
 color:
-    TOKCOLOR WHITESPACE colorpixel WHITESPACE colorpixel WHITESPACE colorpixel
+    TOKCOLOR colorpixel colorpixel colorpixel
     {
         struct Colortriple *dest = $1;
 
-        dest->border = $3;
-        dest->background = $5;
-        dest->text = $7;
+        dest->border = $2;
+        dest->background = $3;
+        dest->text = $4;
     }
     ;
 
@@ -708,10 +1111,10 @@ binding_modifier:
     ;
 
 popup_during_fullscreen:
-    TOK_POPUP_DURING_FULLSCREEN WHITESPACE popup_setting
+    TOK_POPUP_DURING_FULLSCREEN popup_setting
     {
-        DLOG("popup_during_fullscreen setting: %d\n", $3);
-        config.popup_during_fullscreen = $3;
+        DLOG("popup_during_fullscreen setting: %d\n", $2);
+        config.popup_during_fullscreen = $2;
     }
     ;