Skip to content
This repository has been archived by the owner on Apr 18, 2023. It is now read-only.

Commit

Permalink
Merge remote-tracking branch 'vanilla/next' into gaps-next
Browse files Browse the repository at this point in the history
  • Loading branch information
Airblader committed Jun 2, 2021
2 parents 8281f8b + eaa5e63 commit 112cc1f
Show file tree
Hide file tree
Showing 16 changed files with 919 additions and 274 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES-next
Expand Up @@ -35,6 +35,7 @@ option is enabled and only then sets a screenshot as background.
• i3bar: use first bar config by default
• i3-dump-log -f now uses UNIX sockets instead of pthreads. The UNIX socket approach
should be more reliable and also more portable.
• Implement the include config directive
• Allow for_window to match against WM_CLIENT_MACHINE
• Add %machine placeholder (WM_CLIENT_MACHINE) to title_format
• Allow multiple output names in 'move container|workspace to output'
Expand Down
84 changes: 84 additions & 0 deletions docs/userguide
Expand Up @@ -319,6 +319,90 @@ include the following line in your config file:
# i3 config file (v4)
---------------------

[[include]]
=== Include directive

Since i3 v4.20, it is possible to include other configuration files from your i3
configuration.

*Syntax*:
-----------------
include <pattern>
-----------------

i3 expands `pattern` using shell-like word expansion, specifically using the
https://manpages.debian.org/wordexp.3[`wordexp(3)` C standard library function].

*Examples*:
--------------------------------------------------------------------------------
# Tilde expands to the user’s home directory:
include ~/.config/i3/assignments.conf

# Environment variables are expanded:
include $HOME/.config/i3/assignments.conf

# Wildcards are expanded:
include ~/.config/i3/config.d/*.conf

# Command substitution:
include ~/.config/i3/`hostname`.conf

# i3 loads each path only once, so including the i3 config will not result
# in an endless loop, but in an error:
include ~/.config/i3/config

# i3 changes the working directory while parsing a config file
# so that relative paths are interpreted relative to the directory
# of the config file that contains the path:
include assignments.conf
--------------------------------------------------------------------------------

If a specified file cannot be read, for example because of a lack of file
permissions, or because of a dangling symlink, i3 will report an error and
continue processing your remaining configuration.

To list all loaded configuration files, run `i3 --moreversion`:

--------------------------------------------------------------------------------
% i3 --moreversion
Binary i3 version: 4.19.2-87-gfcae64f7+ © 2009 Michael Stapelberg and contributors
Running i3 version: 4.19.2-87-gfcae64f7+ (pid 963940)
Loaded i3 config:
/tmp/i3.cfg (main) (last modified: 2021-05-13T16:42:31 CEST, 463 seconds ago)
/tmp/included.cfg (included) (last modified: 2021-05-13T16:42:43 CEST, 451 seconds ago)
/tmp/another.cfg (included) (last modified: 2021-05-13T16:42:46 CEST, 448 seconds ago)
--------------------------------------------------------------------------------

Variables are shared between all config files, but beware of the following limitation:

* You can define a variable and use it within an included file.
* You cannot use (in the parent file) a variable that was defined within an included file.

This is a technical limitation: variable expansion happens in a separate stage
before parsing include directives.

Conceptually, included files can only add to the configuration, not undo the
effects of already-processed configuration. For example, you can only add new
key bindings, not overwrite or remove existing key bindings. This means:

* The `include` directive is suitable for organizing large configurations into
separate files, possibly selecting files based on conditionals.

* The `include` directive is not suitable for expressing “use the default
configuration with the following changes”. For that case, we still recommend
copying and modifying the default config.

[NOTE]
====
Implementation-wise, i3 does not currently construct one big configuration from
all `include` directives. Instead, i3’s config file parser interprets all
configuration directives in its `parse_file()` function. When processing an
`include` configuration directive, the parser recursively calls `parse_file()`.

This means the evaluation order of files forms a tree, or one could say i3 uses
depth-first traversal.
====

=== Comments

It is possible and recommended to use comments in your configuration file to
Expand Down
10 changes: 5 additions & 5 deletions generate-command-parser.pl
Expand Up @@ -133,7 +133,7 @@ sub slurp {
open(my $callfh, '>', "GENERATED_${prefix}_call.h");
my $resultname = uc(substr($prefix, 0, 1)) . substr($prefix, 1) . 'ResultIR';
say $callfh '#pragma once';
say $callfh "static void GENERATED_call(const int call_identifier, struct $resultname *result) {";
say $callfh "static void GENERATED_call(Match *current_match, struct stack *stack, const int call_identifier, struct $resultname *result) {";
say $callfh ' switch (call_identifier) {';
my $call_id = 0;
for my $state (@keys) {
Expand All @@ -150,8 +150,8 @@ sub slurp {
# calls to get_string(). Also replaces state names (like FOR_WINDOW)
# with their ID (useful for cfg_criteria_init(FOR_WINDOW) e.g.).
$cmd =~ s/$_/$statenum{$_}/g for @keys;
$cmd =~ s/\$([a-z_]+)/get_string("$1")/g;
$cmd =~ s/\&([a-z_]+)/get_long("$1")/g;
$cmd =~ s/\$([a-z_]+)/get_string(stack, "$1")/g;
$cmd =~ s/\&([a-z_]+)/get_long(stack, "$1")/g;
# For debugging/testing, we print the call using printf() and thus need
# to generate a format string. The format uses %d for <number>s,
# literal numbers or state IDs and %s for NULL, <string>s and literal
Expand All @@ -175,9 +175,9 @@ sub slurp {
say $callfh '#ifndef TEST_PARSER';
my $real_cmd = $cmd;
if ($real_cmd =~ /\(\)/) {
$real_cmd =~ s/\(/(&current_match, result/;
$real_cmd =~ s/\(/(current_match, result/;
} else {
$real_cmd =~ s/\(/(&current_match, result, /;
$real_cmd =~ s/\(/(current_match, result, /;
}
say $callfh " $real_cmd;";
say $callfh '#else';
Expand Down
1 change: 1 addition & 0 deletions include/config_directives.h
Expand Up @@ -39,6 +39,7 @@ CFGFUN(criteria_init, int _state);
CFGFUN(criteria_add, const char *ctype, const char *cvalue);
CFGFUN(criteria_pop_state);

CFGFUN(include, const char *pattern);
CFGFUN(font, const char *font);
CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command);
CFGFUN(for_window, const char *command);
Expand Down
66 changes: 61 additions & 5 deletions include/config_parser.h
Expand Up @@ -16,29 +16,85 @@
SLIST_HEAD(variables_head, Variable);
extern pid_t config_error_nagbar_pid;

struct stack_entry {
/* Just a pointer, not dynamically allocated. */
const char *identifier;
enum {
STACK_STR = 0,
STACK_LONG = 1,
} type;
union {
char *str;
long num;
} val;
};

struct stack {
struct stack_entry stack[10];
};

struct parser_ctx {
bool use_nagbar;
bool assume_v4;

int state;
Match current_match;

/* A list which contains the states that lead to the current state, e.g.
* INITIAL, WORKSPACE_LAYOUT.
* When jumping back to INITIAL, statelist_idx will simply be set to 1
* (likewise for other states, e.g. MODE or BAR).
* This list is used to process the nearest error token. */
int statelist[10];
/* NB: statelist_idx points to where the next entry will be inserted */
int statelist_idx;

/*******************************************************************************
* The (small) stack where identified literals are stored during the parsing
* of a single config directive (like $workspace).
******************************************************************************/
struct stack *stack;

struct variables_head variables;

bool has_errors;
};

/**
* An intermediate reprsentation of the result of a parse_config call.
* Currently unused, but the JSON output will be useful in the future when we
* implement a config parsing IPC command.
*
*/
struct ConfigResultIR {
/* The JSON generator to append a reply to. */
yajl_gen json_gen;
struct parser_ctx *ctx;

/* The next state to transition to. Passed to the function so that we can
* determine the next state as a result of a function call, like
* cfg_criteria_pop_state() does. */
int next_state;
};

struct ConfigResultIR *parse_config(const char *input, struct context *context);
/* Whether any error happened while processing this config directive. */
bool has_errors;
};

/**
* launch nagbar to indicate errors in the configuration file.
*/
void start_config_error_nagbar(const char *configpath, bool has_errors);

/**
* Releases the memory of all variables in ctx.
*
*/
void free_variables(struct parser_ctx *ctx);

typedef enum {
PARSE_FILE_FAILED = -1,
PARSE_FILE_SUCCESS = 0,
PARSE_FILE_CONFIG_ERRORS = 1,
} parse_file_result_t;

/**
* Parses the given file by first replacing the variables, then calling
* parse_config and launching i3-nagbar if use_nagbar is true.
Expand All @@ -47,4 +103,4 @@ void start_config_error_nagbar(const char *configpath, bool has_errors);
* parsing.
*
*/
bool parse_file(const char *f, bool use_nagbar);
parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f);
12 changes: 12 additions & 0 deletions include/configuration.h
Expand Up @@ -15,13 +15,15 @@
#include "queue.h"
#include "i3.h"

typedef struct IncludedFile IncludedFile;
typedef struct Config Config;
typedef struct Barconfig Barconfig;
extern char *current_configpath;
extern char *current_config;
extern Config config;
extern SLIST_HEAD(modes_head, Mode) modes;
extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;
extern TAILQ_HEAD(includedfiles_head, IncludedFile) included_files;

/**
* Used during the config file lexing/parsing to keep the state of the lexer
Expand Down Expand Up @@ -69,6 +71,16 @@ struct Variable {
SLIST_ENTRY(Variable) variables;
};

/**
* List entry struct for an included file.
*
*/
struct IncludedFile {
char *path;

TAILQ_ENTRY(IncludedFile) files;
};

/**
* The configuration file can contain multiple sets of bindings. Apart from the
* default set (name == "default"), you can specify other sets and change the
Expand Down
8 changes: 8 additions & 0 deletions parser-specs/config.spec
Expand Up @@ -20,6 +20,7 @@ state INITIAL:
'set ' -> IGNORE_LINE
'set ' -> IGNORE_LINE
'set_from_resource' -> IGNORE_LINE
'include' -> INCLUDE
bindtype = 'bindsym', 'bindcode', 'bind' -> BINDING
'bar' -> BARBRACE
'font' -> FONT
Expand Down Expand Up @@ -90,6 +91,11 @@ state SMART_GAPS:
enabled = 'inverse_outer'
-> call cfg_smart_gaps($enabled)

# include <pattern>
state INCLUDE:
pattern = string
-> call cfg_include($pattern)

# floating_minimum_size <width> x <height>
state FLOATING_MINIMUM_SIZE_WIDTH:
width = number
Expand Down Expand Up @@ -424,6 +430,8 @@ state BINDCOMMAND:
->
command = string
-> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)
end
-> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)

################################################################################
# Mode configuration
Expand Down
60 changes: 38 additions & 22 deletions src/bindings.c
Expand Up @@ -717,6 +717,40 @@ void reorder_bindings(void) {
}
}

/*
* Returns true if a is a key binding for the same key as b.
*
*/
static bool binding_same_key(Binding *a, Binding *b) {
/* Check if the input types are different */
if (a->input_type != b->input_type) {
return false;
}

/* Check if one is using keysym while the other is using bindsym. */
if ((a->symbol == NULL && b->symbol != NULL) ||
(a->symbol != NULL && b->symbol == NULL)) {
return false;
}

/* If a is NULL, b has to be NULL, too (see previous conditional).
* If the keycodes differ, it can't be a duplicate. */
if (a->symbol != NULL &&
strcasecmp(a->symbol, b->symbol) != 0) {
return false;
}

/* Check if the keycodes or modifiers are different. If so, they
* can't be duplicate */
if (a->keycode != b->keycode ||
a->event_state_mask != b->event_state_mask ||
a->release != b->release) {
return false;
}

return true;
}

/*
* 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
Expand All @@ -730,31 +764,13 @@ void check_for_duplicate_bindings(struct context *context) {
TAILQ_FOREACH (bind, bindings, bindings) {
/* Abort when we reach the current keybinding, only check the
* bindings before */
if (bind == current)
if (bind == current) {
break;
}

/* Check if the input types are different */
if (bind->input_type != current->input_type)
continue;

/* Check if one is using keysym while the other is using bindsym.
* If so, skip. */
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->event_state_mask != current->event_state_mask ||
bind->release != current->release)
if (!binding_same_key(bind, current)) {
continue;
}

context->has_errors = true;
if (current->keycode != 0) {
Expand Down

0 comments on commit 112cc1f

Please sign in to comment.