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

Commit

Permalink
GET_CONFIG: add raw/variable-processed contents of all config files (…
Browse files Browse the repository at this point in the history
…#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
  • Loading branch information
stapelberg committed Sep 22, 2021
1 parent d3ff9af commit 535da94
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 41 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES-next
Expand Up @@ -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
Expand Down
38 changes: 35 additions & 3 deletions docs/ipc
Expand Up @@ -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]]
Expand Down
54 changes: 32 additions & 22 deletions i3-msg/main.c
Expand Up @@ -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'},
Expand All @@ -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') {
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion include/config_parser.h
Expand Up @@ -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);
2 changes: 2 additions & 0 deletions include/configuration.h
Expand Up @@ -77,6 +77,8 @@ struct Variable {
*/
struct IncludedFile {
char *path;
char *raw_contents;
char *variable_replaced_contents;

TAILQ_ENTRY(IncludedFile) files;
};
Expand Down
6 changes: 5 additions & 1 deletion man/i3-msg.man
Expand Up @@ -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

Expand All @@ -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.

Expand Down
6 changes: 3 additions & 3 deletions src/config.c
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand All @@ -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));
Expand Down
4 changes: 3 additions & 1 deletion src/config_directives.c
Expand Up @@ -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;

Expand All @@ -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;

Expand Down
14 changes: 7 additions & 7 deletions src/config_parser.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
17 changes: 16 additions & 1 deletion src/ipc.c
Expand Up @@ -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);

Expand Down
27 changes: 25 additions & 2 deletions testcases/t/313-include.t
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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);


Expand Down

0 comments on commit 535da94

Please sign in to comment.