2 #define I3__FILE__ "config_parser.c"
34 #include <sys/types.h>
42 #define y(x, ...) yajl_gen_##x(command_output.json_gen, ##__VA_ARGS__)
43 #define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char *)str, strlen(str))
60 typedef struct token {
104 for (
int c = 0; c < 10; c++) {
105 if (
stack[c].identifier != NULL &&
106 strcmp(
stack[c].identifier, identifier) != 0)
108 if (
stack[c].identifier == NULL) {
125 fprintf(stderr,
"BUG: config_parser stack full. This means either a bug "
126 "in the code, or a new command which contains more than "
127 "10 identified tokens.\n");
132 for (
int c = 0; c < 10; c++) {
133 if (
stack[c].identifier != NULL)
145 fprintf(stderr,
"BUG: config_parser stack full. This means either a bug "
146 "in the code, or a new command which contains more than "
147 "10 identified tokens.\n");
152 for (
int c = 0; c < 10; c++) {
153 if (
stack[c].identifier == NULL)
155 if (strcmp(identifier,
stack[c].identifier) == 0)
162 for (
int c = 0; c < 10; c++) {
163 if (
stack[c].identifier == NULL)
165 if (strcmp(identifier,
stack[c].identifier) == 0)
172 for (
int c = 0; c < 10; c++) {
223 statelist_idx = i + 1;
228 statelist[statelist_idx++] = _next_state;
237 while (*walk !=
'\n' && *walk !=
'\r' && walk >= beginning) {
252 char *end = strchr(result,
'\n');
261 const char *dumpwalk = input;
263 while (*dumpwalk !=
'\0') {
264 char *next_nl = strchr(dumpwalk,
'\n');
265 if (next_nl != NULL) {
266 DLOG(
"CONFIG(line %3d): %.*s\n", linecnt, (
int)(next_nl - dumpwalk), dumpwalk);
267 dumpwalk = next_nl + 1;
269 DLOG(
"CONFIG(line %3d): %s\n", linecnt, dumpwalk);
282 const char *walk = input;
283 const size_t len = strlen(input);
296 while ((
size_t)(walk - input) <= len) {
299 while ((*walk ==
' ' || *walk ==
'\t') && *walk !=
'\0')
305 token_handled =
false;
306 for (c = 0; c < ptr->
n; c++) {
307 token = &(ptr->
array[c]);
310 if (token->
name[0] ==
'\'') {
311 if (strncasecmp(walk, token->
name + 1, strlen(token->
name) - 1) == 0) {
314 walk += strlen(token->
name) - 1;
316 token_handled =
true;
322 if (strcmp(token->
name,
"number") == 0) {
326 long int num = strtol(walk, &end, 10);
327 if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) ||
328 (errno != 0 && num == 0))
341 token_handled =
true;
345 if (strcmp(token->
name,
"string") == 0 ||
346 strcmp(token->
name,
"word") == 0) {
347 const char *beginning = walk;
352 while (*walk !=
'\0' && (*walk !=
'"' || *(walk - 1) ==
'\\'))
355 if (token->
name[0] ==
's') {
356 while (*walk !=
'\0' && *walk !=
'\r' && *walk !=
'\n')
362 while (*walk !=
' ' && *walk !=
'\t' &&
363 *walk !=
']' && *walk !=
',' &&
364 *walk !=
';' && *walk !=
'\r' &&
365 *walk !=
'\n' && *walk !=
'\0')
369 if (walk != beginning) {
370 char *str =
scalloc(walk - beginning + 1, 1);
373 for (inpos = 0, outpos = 0;
374 inpos < (walk - beginning);
379 if (beginning[inpos] ==
'\\' && beginning[inpos + 1] ==
'"')
381 str[outpos] = beginning[inpos];
391 token_handled =
true;
396 if (strcmp(token->
name,
"line") == 0) {
397 while (*walk !=
'\0' && *walk !=
'\n' && *walk !=
'\r')
400 token_handled =
true;
406 if (strcmp(token->
name,
"end") == 0) {
408 if (*walk ==
'\0' || *walk ==
'\n' || *walk ==
'\r') {
410 token_handled =
true;
426 if (!token_handled) {
430 for (c = 0; c < ptr->
n; c++)
431 tokenlen += strlen(ptr->
array[c].
name) + strlen(
"'', ");
437 char *possible_tokens =
smalloc(tokenlen + 1);
438 char *tokenwalk = possible_tokens;
439 for (c = 0; c < ptr->
n; c++) {
440 token = &(ptr->
array[c]);
441 if (token->
name[0] ==
'\'') {
445 strcpy(tokenwalk, token->
name + 1);
446 tokenwalk += strlen(token->
name + 1);
451 if (strcmp(token->
name,
"error") == 0)
456 strcpy(tokenwalk, token->
name);
457 tokenwalk += strlen(token->
name);
460 if (c < (ptr->
n - 1)) {
466 sasprintf(&errormessage,
"Expected one of these tokens: %s",
468 free(possible_tokens);
475 char *position =
scalloc(strlen(error_line) + 1, 1);
476 const char *copywalk;
477 for (copywalk = error_line;
478 *copywalk !=
'\n' && *copywalk !=
'\r' && *copywalk !=
'\0';
480 position[(copywalk - error_line)] = (copywalk >= walk ?
'^' : (*copywalk ==
'\t' ?
'\t' :
' '));
481 position[(copywalk - error_line)] =
'\0';
483 ELOG(
"CONFIG: %s\n", errormessage);
489 const char *context_p1_start =
start_of_line(error_line - 2, input);
490 char *context_p1_line =
single_line(context_p1_start);
492 const char *context_p2_start =
start_of_line(context_p1_start - 2, input);
493 char *context_p2_line =
single_line(context_p2_start);
494 ELOG(
"CONFIG: Line %3d: %s\n", linecnt - 2, context_p2_line);
495 free(context_p2_line);
497 ELOG(
"CONFIG: Line %3d: %s\n", linecnt - 1, context_p1_line);
498 free(context_p1_line);
500 ELOG(
"CONFIG: Line %3d: %s\n", linecnt, error_copy);
501 ELOG(
"CONFIG: %s\n", position);
504 for (
int i = 0; i < 2; i++) {
505 char *error_line_end = strchr(error_line,
'\n');
506 if (error_line_end != NULL && *(error_line_end + 1) !=
'\0') {
507 error_line = error_line_end + 1;
509 ELOG(
"CONFIG: Line %3d: %s\n", linecnt + i + 1, error_copy);
529 ystr(
"errorposition");
534 while ((
size_t)(walk - input) <= len && *walk !=
'\n')
544 bool error_token_found =
false;
545 for (
int i =
statelist_idx - 1; (i >= 0) && !error_token_found; i--) {
547 for (
int j = 0; j < errptr->
n; j++) {
548 if (strcmp(errptr->
array[j].
name,
"error") != 0)
551 error_token_found =
true;
556 assert(error_token_found);
582 fprintf(stdout,
"# ");
583 vfprintf(stdout, fmt, args);
591 vfprintf(stderr, fmt, args);
597 void cfg_criteria_init(
I3_CFG,
int _state) {
601 void cfg_criteria_add(
I3_CFG,
const char *ctype,
const char *cvalue) {
604 void cfg_criteria_pop_state(
I3_CFG) {
608 int main(
int argc,
char *argv[]) {
610 fprintf(stderr,
"Syntax: %s <command>\n", argv[0]);
613 struct context context;
629 while (*walk !=
'\0') {
636 if (strncasecmp(line,
"bindcode", strlen(
"bindcode")) == 0 ||
637 strncasecmp(line,
"force_focus_wrapping", strlen(
"force_focus_wrapping")) == 0 ||
638 strncasecmp(line,
"# i3 config file (v4)", strlen(
"# i3 config file (v4)")) == 0 ||
639 strncasecmp(line,
"workspace_layout", strlen(
"workspace_layout")) == 0) {
640 LOG(
"deciding for version 4 due to this line: %.*s\n", (
int)(walk - line), line);
645 if (strncasecmp(line,
"bind", strlen(
"bind")) == 0) {
646 char *bind = strchr(line,
' ');
649 while ((*bind ==
' ' || *bind ==
'\t') && *bind !=
'\0')
653 if ((bind = strchr(bind,
' ')) == NULL)
655 while ((*bind ==
' ' || *bind ==
'\t') && *bind !=
'\0')
659 if (strncasecmp(bind,
"layout", strlen(
"layout")) == 0 ||
660 strncasecmp(bind,
"floating", strlen(
"floating")) == 0 ||
661 strncasecmp(bind,
"workspace", strlen(
"workspace")) == 0 ||
662 strncasecmp(bind,
"focus left", strlen(
"focus left")) == 0 ||
663 strncasecmp(bind,
"focus right", strlen(
"focus right")) == 0 ||
664 strncasecmp(bind,
"focus up", strlen(
"focus up")) == 0 ||
665 strncasecmp(bind,
"focus down", strlen(
"focus down")) == 0 ||
666 strncasecmp(bind,
"border normal", strlen(
"border normal")) == 0 ||
667 strncasecmp(bind,
"border 1pixel", strlen(
"border 1pixel")) == 0 ||
668 strncasecmp(bind,
"border pixel", strlen(
"border pixel")) == 0 ||
669 strncasecmp(bind,
"border borderless", strlen(
"border borderless")) == 0 ||
670 strncasecmp(bind,
"--no-startup-id", strlen(
"--no-startup-id")) == 0 ||
671 strncasecmp(bind,
"bar", strlen(
"bar")) == 0) {
672 LOG(
"deciding for version 4 due to this line: %.*s\n", (
int)(walk - line), line);
699 if (pipe(writepipe) != 0 ||
700 pipe(readpipe) != 0) {
701 warn(
"migrate_config: Could not create pipes");
707 warn(
"Could not fork()");
715 dup2(writepipe[0], 0);
719 dup2(readpipe[1], 1);
721 static char *argv[] = {
734 if (
writeall(writepipe[1], input, size) == -1) {
735 warn(
"Could not write to pipe");
744 int conv_size = 65535;
745 char *converted =
smalloc(conv_size);
746 int read_bytes = 0, ret;
748 if (read_bytes == conv_size) {
750 converted =
srealloc(converted, conv_size);
752 ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes);
754 warn(
"Cannot read from pipe");
764 if (!WIFEXITED(status)) {
765 fprintf(stderr,
"Child did not terminate normally, using old config file (will lead to broken behaviour)\n");
769 int returncode = WEXITSTATUS(status);
770 if (returncode != 0) {
771 fprintf(stderr,
"Migration process exit code was != 0\n");
772 if (returncode == 2) {
773 fprintf(stderr,
"could not start the migration script\n");
775 }
else if (returncode == 1) {
776 fprintf(stderr,
"This already was a v4 config. Please add the following line to your config file:\n");
777 fprintf(stderr,
"# i3 config file (v4)\n");
790 char *editaction, *pageraction;
791 sasprintf(&editaction,
"i3-sensible-editor \"%s\" && i3-msg reload\n", configpath);
798 (has_errors ?
"error" :
"warning"),
800 (has_errors ?
"You have an error in your i3 config file!" :
"Your config is outdated. Please fix the warnings to make sure everything works."),
805 (has_errors ?
"show errors" :
"show warnings"),
825 char buffer[4096], key[512], value[512], *continuation = NULL;
827 if ((fd = open(f, O_RDONLY)) == -1)
828 die(
"Could not open configuration file: %s\n", strerror(errno));
830 if (fstat(fd, &stbuf) == -1)
831 die(
"Could not fstat file: %s\n", strerror(errno));
833 buf =
scalloc(stbuf.st_size + 1, 1);
835 if ((fstr = fdopen(fd,
"r")) == NULL)
836 die(
"Could not fdopen: %s\n", strerror(errno));
838 while (!feof(fstr)) {
840 continuation = buffer;
841 if (fgets(continuation,
sizeof(buffer) - (continuation - buffer), fstr) == NULL) {
844 die(
"Could not read configuration file\n");
846 if (buffer[strlen(buffer) - 1] !=
'\n' && !feof(fstr)) {
847 ELOG(
"Your line continuation is too long, it exceeds %zd bytes\n",
sizeof(buffer));
849 continuation = strstr(buffer,
"\\\n");
854 strncpy(buf + strlen(buf), buffer, strlen(buffer) + 1);
857 if (sscanf(buffer,
"%511s %511[^\n]", key, value) < 1 ||
858 key[0] ==
'#' || strlen(key) < 3)
861 if (strcasecmp(key,
"set") == 0) {
862 if (value[0] !=
'$') {
863 ELOG(
"Malformed variable assignment, name has to start with $\n");
868 char *v_key = value, *v_value;
869 if (strstr(value,
" ") == NULL && strstr(value,
"\t") == NULL) {
870 ELOG(
"Malformed variable assignment, need a value\n");
874 if (!(v_value = strstr(value,
" ")))
875 v_value = strstr(value,
"\t");
878 while (*v_value ==
'\t' || *v_value ==
' ')
885 DLOG(
"Got new variable %s = %s\n", v_key, v_value);
900 int extra = (strlen(current->
value) - strlen(current->
key));
903 next < (bufcopy + stbuf.st_size) &&
904 (next = strcasestr(next, current->
key)) != NULL;
905 next += strlen(current->
key)) {
907 extra_bytes += extra;
914 char *walk = buf, *destwalk;
915 char *
new =
smalloc(stbuf.st_size + extra_bytes + 1);
917 while (walk < (buf + stbuf.st_size)) {
922 int distance = stbuf.st_size;
926 if ((current->
next_match - walk) < distance) {
931 if (nearest == NULL) {
933 strncpy(destwalk, walk, (buf + stbuf.st_size) - walk);
934 destwalk += (buf + stbuf.st_size) - walk;
939 strncpy(destwalk, walk, distance);
940 strncpy(destwalk + distance, nearest->
value, strlen(nearest->
value));
941 walk += distance + strlen(nearest->
key);
942 destwalk += distance + strlen(nearest->
value);
952 if (converted != NULL) {
954 ELOG(
"****************************************************************\n");
955 ELOG(
"NOTE: Automatically converted configuration file from v3 to v4.\n");
957 ELOG(
"Please convert your config file to v4. You can use this command:\n");
958 ELOG(
" mv %s %s.O\n", f, f);
959 ELOG(
" i3-migrate-config-to-v4 %s.O > %s\n", f, f);
960 ELOG(
"****************************************************************\n");
966 LOG(
"**********************************************************************\n");
967 LOG(
"ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4\n");
968 LOG(
"was not correctly installed on your system?\n");
969 LOG(
"**********************************************************************\n");
974 context =
scalloc(1,
sizeof(
struct context));
978 yajl_gen_free(config_output->
json_gen);
987 ELOG(
"Please convert your configfile first, then fix any remaining errors (see above).\n");
#define SLIST_HEAD(name, type)
static cmdp_token_ptr tokens[59]
#define I3_CFG
The beginning of the prototype for every cfg_ function.
int main(int argc, char *argv[])
void * smalloc(size_t size)
Safe-wrapper around malloc which exits if malloc returns NULL (meaning that there is no more memory a...
static void push_string(const char *identifier, const char *str)
void start_config_error_nagbar(const char *configpath, bool has_errors)
Launch nagbar to indicate errors in the configuration file.
enum stack_entry::@1 type
void reorder_bindings(void)
Reorders bindings by event_state_mask descendingly so that get_binding() correctly matches more speci...
Holds a user-assigned variable for parsing the configuration file.
void * scalloc(size_t num, size_t size)
Safe-wrapper around calloc which exits if malloc returns NULL (meaning that there is no more memory a...
static long get_long(const char *identifier)
void debuglog(char *fmt,...)
static struct context * context
pid_t config_error_nagbar_pid
static struct ConfigResultIR command_output
static const char * get_string(const char *identifier)
void start_nagbar(pid_t *nagbar_pid, char *argv[])
Starts an i3-nagbar instance with the given parameters.
void check_for_duplicate_bindings(struct context *context)
Checks for duplicate key bindings (the same keycode or keysym is configured more than once)...
struct tokenptr cmdp_token_ptr
#define SLIST_FOREACH(var, head, field)
ssize_t writeall(int fd, const void *buf, size_t count)
Wrapper around correct write which returns -1 (meaning that write failed) or count (meaning that all ...
struct ConfigResultIR * parse_config(const char *input, struct context *context)
static char * migrate_config(char *input, off_t size)
static const char * start_of_line(const char *walk, const char *beginning)
static struct ConfigResultIR subcommand_output
void * srealloc(void *ptr, size_t size)
Safe-wrapper around realloc which exits if realloc returns NULL (meaning that there is no more memory...
static void push_long(const char *identifier, long num)
static void clear_stack(void)
char * pattern
The pattern/name used to load the font.
#define SLIST_INSERT_HEAD(head, elm, field)
int sasprintf(char **strp, const char *fmt,...)
Safe-wrapper around asprintf which exits if it returns -1 (meaning that there is no more memory avail...
static char * single_line(const char *start)
union stack_entry::@2 val
char * sstrdup(const char *str)
Safe-wrapper around strdup which exits if malloc returns NULL (meaning that there is no more memory a...
static void GENERATED_call(const int call_identifier, struct CommandResultIR *result)
bool parse_file(const char *f, bool use_nagbar)
Parses the given file by first replacing the variables, then calling parse_config and launching i3-na...
static int detect_version(char *buf)
#define SLIST_REMOVE_HEAD(head, field)
static struct stack_entry stack[10]
#define SLIST_EMPTY(head)
A "match" is a data structure which acts like a mask or expression to match certain windows or not...
static int criteria_next_state
#define SLIST_FIRST(head)
static void next_state(const cmdp_token *token)
void extract_workspace_names_from_bindings(void)
Extracts workspace names from keybindings (e.g.
void exec_i3_utility(char *name, char *argv[])
exec()s an i3 utility, for example the config file migration script or i3-nagbar. ...
const char * i3_version
Git commit identifier, from version.c.
static Match current_match
static cmdp_state statelist[10]
#define SLIST_HEAD_INITIALIZER(head)
Used during the config file lexing/parsing to keep the state of the lexer in order to provide useful ...
void errorlog(char *fmt,...)