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
|
• 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
|
||||||
|
38
docs/ipc
38
docs/ipc
@ -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 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:*
|
*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]]
|
||||||
|
@ -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,32 +266,38 @@ 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) {
|
||||||
yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
|
if (!raw_reply) {
|
||||||
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
|
yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
|
||||||
yajl_free(handle);
|
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
|
||||||
|
yajl_free(handle);
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case yajl_status_ok:
|
case yajl_status_ok:
|
||||||
break;
|
break;
|
||||||
case yajl_status_client_canceled:
|
case yajl_status_client_canceled:
|
||||||
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) {
|
||||||
yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
|
if (raw_reply) {
|
||||||
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
|
printf("%.*s\n", reply_length, reply);
|
||||||
yajl_free(handle);
|
} 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) {
|
switch (state) {
|
||||||
case yajl_status_ok:
|
case yajl_status_ok:
|
||||||
break;
|
break;
|
||||||
case yajl_status_client_canceled:
|
case yajl_status_client_canceled:
|
||||||
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 {
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
17
src/ipc.c
17
src/ipc.c
@ -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);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user