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:
parent
d3ff9afbb5
commit
535da94536
@ -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
|
||||
|
38
docs/ipc
38
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]]
|
||||
|
@ -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,6 +266,7 @@ 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) {
|
||||
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);
|
||||
@ -273,11 +278,15 @@ int main(int argc, char *argv[]) {
|
||||
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) {
|
||||
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);
|
||||
@ -289,6 +298,7 @@ int main(int argc, char *argv[]) {
|
||||
case yajl_status_error:
|
||||
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
|
||||
}
|
||||
}
|
||||
} else if (reply_type == I3_IPC_REPLY_TYPE_SUBSCRIBE) {
|
||||
do {
|
||||
free(reply);
|
||||
|
@ -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);
|
||||
|
@ -77,6 +77,8 @@ struct Variable {
|
||||
*/
|
||||
struct IncludedFile {
|
||||
char *path;
|
||||
char *raw_contents;
|
||||
char *variable_replaced_contents;
|
||||
|
||||
TAILQ_ENTRY(IncludedFile) files;
|
||||
};
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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) {
|
||||
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);
|
||||
|
17
src/ipc.c
17
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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user