summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Stapelberg <stapelberg@users.noreply.github.com>2021-09-22 08:54:37 +0200
committerGitHub <noreply@github.com>2021-09-22 08:54:37 +0200
commit535da94536a005fb60e29f7bf902e49390b9cc10 (patch)
tree16575170932c91aaf71a23805520f54515788f00
parentd3ff9afbb5e700161b597799d6067f0d72ac725b (diff)
downloadi3-535da94536a005fb60e29f7bf902e49390b9cc10.tar.gz
i3-535da94536a005fb60e29f7bf902e49390b9cc10.zip
GET_CONFIG: add raw/variable-processed contents of all config files (#4528)
We do this by adding to included_files as i3 processes the configs. This should allow for easy debugging, without having to change how i3 processes config files. related to #4192
-rw-r--r--RELEASE-NOTES-next1
-rw-r--r--docs/ipc38
-rw-r--r--i3-msg/main.c54
-rw-r--r--include/config_parser.h2
-rw-r--r--include/configuration.h2
-rw-r--r--man/i3-msg.man6
-rw-r--r--src/config.c6
-rw-r--r--src/config_directives.c4
-rw-r--r--src/config_parser.c14
-rw-r--r--src/ipc.c17
-rw-r--r--testcases/t/313-include.t27
11 files changed, 130 insertions, 41 deletions
diff --git a/RELEASE-NOTES-next b/RELEASE-NOTES-next
index 9b7f41ea..f9c1a4f1 100644
--- a/RELEASE-NOTES-next
+++ b/RELEASE-NOTES-next
@@ -30,6 +30,7 @@ option is enabled and only then sets a screenshot as background.
• default config: use dex for XDG autostart
• docs/ipc: document scratchpad_state
+ • ipc: the GET_CONFIG request now returns all included files and their details
• i3-nagbar: position on focused monitor by default
• i3-nagbar: add option to position on primary monitor
• alternate focusing tab/stack children-parent containers by clicking on their titlebars
diff --git a/docs/ipc b/docs/ipc
index a5c94459..02c6f9a9 100644
--- a/docs/ipc
+++ b/docs/ipc
@@ -793,12 +793,44 @@ No payload.
*Reply:*
-The config reply is a map which currently only contains the "config" member,
-which is a string containing the config file as loaded by i3 most recently.
+The config reply is a map which contains the following fields:
+
+config (string)::
+ The top-level config file contents that i3 has loaded most recently.
+ This field is kept for backwards compatibility. See +included_configs+
+ instead.
+included_configs (array of maps)::
+ i3 adds one entry to this array for each config file it loads, in
+ order. The first entry’s +raw_contents+ are identical to the +config+
+ field.
+
+Each +included_configs+ entry contains the following fields
+
+path (string)::
+ Absolute path name to the config file that i3 loaded.
+raw_contents (string)::
+ The raw contents of the file as i3 read them.
+variable_replaced_contents (string)::
+ The contents of the file after i3 replaced all variables. This is useful
+ for debugging variable replacement.
*Example:*
-------------------
-{ "config": "font pango:monospace 8\nbindsym Mod4+q exit\n" }
+{
+ "config": "include font.cfg\n",
+ "included_configs": [
+ {
+ "path": "/home/michael/configfiles/i3/config",
+ "raw_contents": "include font.cfg\n",
+ "variable_replaced_contents": "include font.cfg\n"
+ },
+ {
+ "path": "/home/michael/configfiles/i3/font.cfg",
+ "raw_contents": "set $font pango:monospace 8\nfont $font",
+ "variable_replaced_contents": "set pango:monospace 8 pango:monospace 8\nfont pango:monospace 8\n"
+ }
+ ],
+}
-------------------
[[_tick_reply]]
diff --git a/i3-msg/main.c b/i3-msg/main.c
index c1c8bb81..239ac46f 100644
--- a/i3-msg/main.c
+++ b/i3-msg/main.c
@@ -156,6 +156,7 @@ int main(int argc, char *argv[]) {
char *payload = NULL;
bool quiet = false;
bool monitor = false;
+ bool raw_reply = false;
static struct option long_options[] = {
{"socket", required_argument, 0, 's'},
@@ -164,9 +165,10 @@ int main(int argc, char *argv[]) {
{"quiet", no_argument, 0, 'q'},
{"monitor", no_argument, 0, 'm'},
{"help", no_argument, 0, 'h'},
+ {"raw", no_argument, 0, 'r'},
{0, 0, 0, 0}};
- char *options_string = "s:t:vhqm";
+ char *options_string = "s:t:vhqmr";
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
if (o == 's') {
@@ -217,6 +219,8 @@ int main(int argc, char *argv[]) {
return 0;
} else if (o == '?') {
exit(EXIT_FAILURE);
+ } else if (o == 'r') {
+ raw_reply = true;
}
}
@@ -262,32 +266,38 @@ int main(int argc, char *argv[]) {
/* For the reply of commands, have a look if that command was successful.
* If not, nicely format the error message. */
if (reply_type == I3_IPC_REPLY_TYPE_COMMAND) {
- yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
- yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
- yajl_free(handle);
-
- switch (state) {
- case yajl_status_ok:
- break;
- case yajl_status_client_canceled:
- case yajl_status_error:
- errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
+ if (!raw_reply) {
+ yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
+ yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
+ yajl_free(handle);
+
+ switch (state) {
+ case yajl_status_ok:
+ break;
+ case yajl_status_client_canceled:
+ case yajl_status_error:
+ errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
+ }
}
- if (!quiet) {
+ if (!quiet || raw_reply) {
printf("%.*s\n", reply_length, reply);
}
} else if (reply_type == I3_IPC_REPLY_TYPE_CONFIG) {
- yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
- yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
- yajl_free(handle);
-
- switch (state) {
- case yajl_status_ok:
- break;
- case yajl_status_client_canceled:
- case yajl_status_error:
- errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
+ if (raw_reply) {
+ printf("%.*s\n", reply_length, reply);
+ } else {
+ yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
+ yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
+ yajl_free(handle);
+
+ switch (state) {
+ case yajl_status_ok:
+ break;
+ case yajl_status_client_canceled:
+ case yajl_status_error:
+ errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
+ }
}
} else if (reply_type == I3_IPC_REPLY_TYPE_SUBSCRIBE) {
do {
diff --git a/include/config_parser.h b/include/config_parser.h
index 7cdb5a19..00d01e45 100644
--- a/include/config_parser.h
+++ b/include/config_parser.h
@@ -103,4 +103,4 @@ typedef enum {
* parsing.
*
*/
-parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f);
+parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFile *included_file);
diff --git a/include/configuration.h b/include/configuration.h
index 1e41893a..be072cf8 100644
--- a/include/configuration.h
+++ b/include/configuration.h
@@ -77,6 +77,8 @@ struct Variable {
*/
struct IncludedFile {
char *path;
+ char *raw_contents;
+ char *variable_replaced_contents;
TAILQ_ENTRY(IncludedFile) files;
};
diff --git a/man/i3-msg.man b/man/i3-msg.man
index ce9b476d..97013fa5 100644
--- a/man/i3-msg.man
+++ b/man/i3-msg.man
@@ -9,7 +9,7 @@ i3-msg - send messages to i3 window manager
== SYNOPSIS
-i3-msg [-q] [-v] [-h] [-s socket] [-t type] [message]
+i3-msg [-q] [-v] [-h] [-s socket] [-t type] [-r] [message]
== OPTIONS
@@ -36,6 +36,10 @@ Instead of exiting right after receiving the first subscribed event,
wait indefinitely for all of them. Can only be used with "-t subscribe".
See the "subscribe" IPC message type below for details.
+*-q, --raw*::
+Display the raw JSON reply instead of pretty-printing errors (for commands) or
+displaying the top-level config file contents (for GET_CONFIG).
+
*message*::
Send ipc message, see below.
diff --git a/src/config.c b/src/config.c
index 7f7e0257..c590f3c5 100644
--- a/src/config.c
+++ b/src/config.c
@@ -16,7 +16,6 @@
#include <xkbcommon/xkbcommon.h>
char *current_configpath = NULL;
-char *current_config = NULL;
Config config;
struct modes_head modes;
struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs);
@@ -234,6 +233,8 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
while (!TAILQ_EMPTY(&included_files)) {
file = TAILQ_FIRST(&included_files);
FREE(file->path);
+ FREE(file->raw_contents);
+ FREE(file->variable_replaced_contents);
TAILQ_REMOVE(&included_files, file, files);
FREE(file);
}
@@ -256,8 +257,7 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
.stack = &stack,
};
SLIST_INIT(&(ctx.variables));
- FREE(current_config);
- const int result = parse_file(&ctx, resolved_path);
+ const int result = parse_file(&ctx, resolved_path, file);
free_variables(&ctx);
if (result == -1) {
die("Could not open configuration file: %s\n", strerror(errno));
diff --git a/src/config_directives.c b/src/config_directives.c
index af365181..3bb6e793 100644
--- a/src/config_directives.c
+++ b/src/config_directives.c
@@ -63,7 +63,7 @@ CFGFUN(include, const char *pattern) {
.stack = &stack,
.variables = result->ctx->variables,
};
- switch (parse_file(&ctx, resolved_path)) {
+ switch (parse_file(&ctx, resolved_path, file)) {
case PARSE_FILE_SUCCESS:
break;
@@ -75,6 +75,8 @@ CFGFUN(include, const char *pattern) {
result->has_errors = true;
TAILQ_REMOVE(&included_files, file, files);
FREE(file->path);
+ FREE(file->raw_contents);
+ FREE(file->variable_replaced_contents);
FREE(file);
break;
diff --git a/src/config_parser.c b/src/config_parser.c
index ff1132f6..860ea86f 100644
--- a/src/config_parser.c
+++ b/src/config_parser.c
@@ -857,7 +857,7 @@ void free_variables(struct parser_ctx *ctx) {
* parse_config and possibly launching i3-nagbar.
*
*/
-parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f) {
+parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFile *included_file) {
int fd;
struct stat stbuf;
char *buf;
@@ -891,13 +891,11 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f) {
return PARSE_FILE_FAILED;
}
- if (current_config == NULL) {
- current_config = scalloc(stbuf.st_size + 1, 1);
- if ((ssize_t)fread(current_config, 1, stbuf.st_size, fstr) != stbuf.st_size) {
- return PARSE_FILE_FAILED;
- }
- rewind(fstr);
+ included_file->raw_contents = scalloc(stbuf.st_size + 1, 1);
+ if ((ssize_t)fread(included_file->raw_contents, 1, stbuf.st_size, fstr) != stbuf.st_size) {
+ return PARSE_FILE_FAILED;
}
+ rewind(fstr);
bool invalid_sets = false;
@@ -1084,6 +1082,8 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f) {
}
}
+ included_file->variable_replaced_contents = sstrdup(new);
+
struct context *context = scalloc(1, sizeof(struct context));
context->filename = f;
parse_config(ctx, new, context);
diff --git a/src/ipc.c b/src/ipc.c
index b50648e3..084d171e 100644
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -1236,7 +1236,22 @@ IPC_HANDLER(get_config) {
y(map_open);
ystr("config");
- ystr(current_config);
+ IncludedFile *file = TAILQ_FIRST(&included_files);
+ ystr(file->raw_contents);
+
+ ystr("included_configs");
+ y(array_open);
+ TAILQ_FOREACH (file, &included_files, files) {
+ y(map_open);
+ ystr("path");
+ ystr(file->path);
+ ystr("raw_contents");
+ ystr(file->raw_contents);
+ ystr("variable_replaced_contents");
+ ystr(file->variable_replaced_contents);
+ y(map_close);
+ }
+ y(array_close);
y(map_close);
diff --git a/testcases/t/313-include.t b/testcases/t/313-include.t
index 3a511e51..9b8b0fab 100644
--- a/testcases/t/313-include.t
+++ b/testcases/t/313-include.t
@@ -56,10 +56,11 @@ is(launch_get_border($config), 'normal', 'normal border');
#####################################################################
my ($fh, $filename) = tempfile(UNLINK => 1);
-print $fh <<'EOT';
+my $varconfig = <<'EOT';
set $vartest special title
for_window [title="$vartest"] border none
EOT
+print $fh $varconfig;
$fh->flush;
$config = <<EOT;
@@ -316,11 +317,21 @@ my $tmpdir = tempdir(CLEANUP => 1);
my $socketpath = $tmpdir . "/config.sock";
ok(! -e $socketpath, "$socketpath does not exist yet");
+my ($indirectfh3, $indirectfilename3) = tempfile(UNLINK => 1);
+my $indirectconfig = <<EOT;
+for_window [title="\$vartest"] border none
+include $relative
+EOT
+print $indirectfh3 $indirectconfig;
+$indirectfh3->flush;
+
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
-include $indirectfilename2
+set \$vartest special title
+
+include $indirectfilename3
ipc-socket $socketpath
EOT
@@ -332,6 +343,18 @@ my $config_reply = $i3->get_config()->recv;
is($config_reply->{config}, $config, 'GET_CONFIG returns the top-level config file');
+my $included = $config_reply->{included_configs};
+is(scalar @{$included}, 3, 'included_configs contains all 3 files');
+is($included->[0]->{raw_contents}, $config, 'included_configs->[0]->{raw_contents} contains top-level config');
+is($included->[1]->{raw_contents}, $indirectconfig, 'included_configs->[1]->{raw_contents} contains indirect config');
+is($included->[2]->{raw_contents}, $varconfig, 'included_configs->[2]->{raw_contents} contains variable config');
+
+my $indirect_replaced_config = <<EOT;
+for_window [title="special title"] border none
+include $relative
+EOT
+is($included->[1]->{variable_replaced_contents}, $indirect_replaced_config, 'included_configs->[1]->{variable_replaced_contents} contains config with variables replaced');
+
exit_gracefully($pid);