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
This commit is contained in:
Michael Stapelberg 2021-09-22 08:54:37 +02:00 committed by GitHub
parent d3ff9afbb5
commit 535da94536
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 128 additions and 39 deletions

View File

@ -30,6 +30,7 @@ option is enabled and only then sets a screenshot as background.
• default config: use dex for XDG autostart • default config: use dex for XDG autostart
• docs/ipc: document scratchpad_state • 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: position on focused monitor by default
• i3-nagbar: add option to position on primary monitor • i3-nagbar: add option to position on primary monitor
• alternate focusing tab/stack children-parent containers by clicking on their titlebars • alternate focusing tab/stack children-parent containers by clicking on their titlebars

View File

@ -793,12 +793,44 @@ No payload.
*Reply:* *Reply:*
The config reply is a map which currently only contains the "config" member, The config reply is a map which contains the following fields:
which is a string containing the config file as loaded by i3 most recently.
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 entrys +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:* *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]] [[_tick_reply]]

View File

@ -156,6 +156,7 @@ int main(int argc, char *argv[]) {
char *payload = NULL; char *payload = NULL;
bool quiet = false; bool quiet = false;
bool monitor = false; bool monitor = false;
bool raw_reply = false;
static struct option long_options[] = { static struct option long_options[] = {
{"socket", required_argument, 0, 's'}, {"socket", required_argument, 0, 's'},
@ -164,9 +165,10 @@ int main(int argc, char *argv[]) {
{"quiet", no_argument, 0, 'q'}, {"quiet", no_argument, 0, 'q'},
{"monitor", no_argument, 0, 'm'}, {"monitor", no_argument, 0, 'm'},
{"help", no_argument, 0, 'h'}, {"help", no_argument, 0, 'h'},
{"raw", no_argument, 0, 'r'},
{0, 0, 0, 0}}; {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) { while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
if (o == 's') { if (o == 's') {
@ -217,6 +219,8 @@ int main(int argc, char *argv[]) {
return 0; return 0;
} else if (o == '?') { } else if (o == '?') {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} else if (o == 'r') {
raw_reply = true;
} }
} }
@ -262,6 +266,7 @@ int main(int argc, char *argv[]) {
/* For the reply of commands, have a look if that command was successful. /* For the reply of commands, have a look if that command was successful.
* If not, nicely format the error message. */ * If not, nicely format the error message. */
if (reply_type == I3_IPC_REPLY_TYPE_COMMAND) { if (reply_type == I3_IPC_REPLY_TYPE_COMMAND) {
if (!raw_reply) {
yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL); yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length); yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
yajl_free(handle); yajl_free(handle);
@ -273,11 +278,15 @@ int main(int argc, char *argv[]) {
case yajl_status_error: case yajl_status_error:
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply."); errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
} }
}
if (!quiet) { if (!quiet || raw_reply) {
printf("%.*s\n", reply_length, reply); printf("%.*s\n", reply_length, reply);
} }
} else if (reply_type == I3_IPC_REPLY_TYPE_CONFIG) { } else if (reply_type == I3_IPC_REPLY_TYPE_CONFIG) {
if (raw_reply) {
printf("%.*s\n", reply_length, reply);
} else {
yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL); yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length); yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
yajl_free(handle); yajl_free(handle);
@ -289,6 +298,7 @@ int main(int argc, char *argv[]) {
case yajl_status_error: case yajl_status_error:
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply."); errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
} }
}
} else if (reply_type == I3_IPC_REPLY_TYPE_SUBSCRIBE) { } else if (reply_type == I3_IPC_REPLY_TYPE_SUBSCRIBE) {
do { do {
free(reply); free(reply);

View File

@ -103,4 +103,4 @@ typedef enum {
* parsing. * 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);

View File

@ -77,6 +77,8 @@ struct Variable {
*/ */
struct IncludedFile { struct IncludedFile {
char *path; char *path;
char *raw_contents;
char *variable_replaced_contents;
TAILQ_ENTRY(IncludedFile) files; TAILQ_ENTRY(IncludedFile) files;
}; };

View File

@ -9,7 +9,7 @@ i3-msg - send messages to i3 window manager
== SYNOPSIS == SYNOPSIS
i3-msg [-q] [-v] [-h] [-s socket] [-t type] [message] i3-msg [-q] [-v] [-h] [-s socket] [-t type] [-r] [message]
== OPTIONS == 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". wait indefinitely for all of them. Can only be used with "-t subscribe".
See the "subscribe" IPC message type below for details. 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*:: *message*::
Send ipc message, see below. Send ipc message, see below.

View File

@ -16,7 +16,6 @@
#include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon.h>
char *current_configpath = NULL; char *current_configpath = NULL;
char *current_config = NULL;
Config config; Config config;
struct modes_head modes; struct modes_head modes;
struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs); 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)) { while (!TAILQ_EMPTY(&included_files)) {
file = TAILQ_FIRST(&included_files); file = TAILQ_FIRST(&included_files);
FREE(file->path); FREE(file->path);
FREE(file->raw_contents);
FREE(file->variable_replaced_contents);
TAILQ_REMOVE(&included_files, file, files); TAILQ_REMOVE(&included_files, file, files);
FREE(file); FREE(file);
} }
@ -256,8 +257,7 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
.stack = &stack, .stack = &stack,
}; };
SLIST_INIT(&(ctx.variables)); SLIST_INIT(&(ctx.variables));
FREE(current_config); const int result = parse_file(&ctx, resolved_path, file);
const int result = parse_file(&ctx, resolved_path);
free_variables(&ctx); free_variables(&ctx);
if (result == -1) { if (result == -1) {
die("Could not open configuration file: %s\n", strerror(errno)); die("Could not open configuration file: %s\n", strerror(errno));

View File

@ -63,7 +63,7 @@ CFGFUN(include, const char *pattern) {
.stack = &stack, .stack = &stack,
.variables = result->ctx->variables, .variables = result->ctx->variables,
}; };
switch (parse_file(&ctx, resolved_path)) { switch (parse_file(&ctx, resolved_path, file)) {
case PARSE_FILE_SUCCESS: case PARSE_FILE_SUCCESS:
break; break;
@ -75,6 +75,8 @@ CFGFUN(include, const char *pattern) {
result->has_errors = true; result->has_errors = true;
TAILQ_REMOVE(&included_files, file, files); TAILQ_REMOVE(&included_files, file, files);
FREE(file->path); FREE(file->path);
FREE(file->raw_contents);
FREE(file->variable_replaced_contents);
FREE(file); FREE(file);
break; break;

View File

@ -857,7 +857,7 @@ void free_variables(struct parser_ctx *ctx) {
* parse_config and possibly launching i3-nagbar. * 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; int fd;
struct stat stbuf; struct stat stbuf;
char *buf; char *buf;
@ -891,13 +891,11 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f) {
return PARSE_FILE_FAILED; return PARSE_FILE_FAILED;
} }
if (current_config == NULL) { included_file->raw_contents = scalloc(stbuf.st_size + 1, 1);
current_config = scalloc(stbuf.st_size + 1, 1); if ((ssize_t)fread(included_file->raw_contents, 1, stbuf.st_size, fstr) != stbuf.st_size) {
if ((ssize_t)fread(current_config, 1, stbuf.st_size, fstr) != stbuf.st_size) {
return PARSE_FILE_FAILED; return PARSE_FILE_FAILED;
} }
rewind(fstr); rewind(fstr);
}
bool invalid_sets = false; 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)); struct context *context = scalloc(1, sizeof(struct context));
context->filename = f; context->filename = f;
parse_config(ctx, new, context); parse_config(ctx, new, context);

View File

@ -1236,7 +1236,22 @@ IPC_HANDLER(get_config) {
y(map_open); y(map_open);
ystr("config"); 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); y(map_close);

View File

@ -56,10 +56,11 @@ is(launch_get_border($config), 'normal', 'normal border');
##################################################################### #####################################################################
my ($fh, $filename) = tempfile(UNLINK => 1); my ($fh, $filename) = tempfile(UNLINK => 1);
print $fh <<'EOT'; my $varconfig = <<'EOT';
set $vartest special title set $vartest special title
for_window [title="$vartest"] border none for_window [title="$vartest"] border none
EOT EOT
print $fh $varconfig;
$fh->flush; $fh->flush;
$config = <<EOT; $config = <<EOT;
@ -316,11 +317,21 @@ my $tmpdir = tempdir(CLEANUP => 1);
my $socketpath = $tmpdir . "/config.sock"; my $socketpath = $tmpdir . "/config.sock";
ok(! -e $socketpath, "$socketpath does not exist yet"); 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; $config = <<EOT;
# i3 config file (v4) # i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 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 ipc-socket $socketpath
EOT 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'); 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); exit_gracefully($pid);