Introduce the GET_CONFIG IPC request

This introduces memory usage by one copy of the config file, which is an
acceptable trade-off for being able to easily revert data loss.
The default config is 6KB, user configs will be in the same ballpark.

fixes #2856
This commit is contained in:
Michael Stapelberg 2017-08-19 17:29:03 +02:00
parent 6bb9c9e708
commit a6d8ed9b1a
8 changed files with 173 additions and 13 deletions

View File

@ -95,10 +95,13 @@ use constant TYPE_GET_TREE => 4;
use constant TYPE_GET_MARKS => 5;
use constant TYPE_GET_BAR_CONFIG => 6;
use constant TYPE_GET_VERSION => 7;
use constant TYPE_GET_BINDING_MODES => 8;
use constant TYPE_GET_CONFIG => 9;
our %EXPORT_TAGS = ( 'all' => [
qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS
TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION)
TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION
TYPE_GET_BINDING_MODES TYPE_GET_CONFIG)
] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
@ -501,6 +504,20 @@ sub get_version {
return $cv;
}
=head2 get_config
Gets the raw last read config from i3. Requires i3 >= 4.14
=cut
sub get_config {
my ($self) = @_;
$self->_ensure_connection;
$self->message(TYPE_GET_CONFIG);
}
=head2 command($content)
Makes i3 execute the given command

View File

@ -119,6 +119,43 @@ static yajl_callbacks reply_callbacks = {
.yajl_end_map = reply_end_map_cb,
};
/*******************************************************************************
* Config reply callbacks
*******************************************************************************/
static char *config_last_key = NULL;
static int config_string_cb(void *params, const unsigned char *val, size_t len) {
char *str = scalloc(len + 1, 1);
strncpy(str, (const char *)val, len);
if (strcmp(config_last_key, "config") == 0) {
fprintf(stdout, "%s", str);
}
free(str);
return 1;
}
static int config_start_map_cb(void *params) {
return 1;
}
static int config_end_map_cb(void *params) {
return 1;
}
static int config_map_key_cb(void *params, const unsigned char *keyVal, size_t keyLen) {
config_last_key = scalloc(keyLen + 1, 1);
strncpy(config_last_key, (const char *)keyVal, keyLen);
return 1;
}
static yajl_callbacks config_callbacks = {
.yajl_string = config_string_cb,
.yajl_start_map = config_start_map_cb,
.yajl_map_key = config_map_key_cb,
.yajl_end_map = config_end_map_cb,
};
int main(int argc, char *argv[]) {
#if defined(__OpenBSD__)
if (pledge("stdio rpath unix", NULL) == -1)
@ -150,25 +187,27 @@ int main(int argc, char *argv[]) {
free(socket_path);
socket_path = sstrdup(optarg);
} else if (o == 't') {
if (strcasecmp(optarg, "command") == 0)
if (strcasecmp(optarg, "command") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
else if (strcasecmp(optarg, "get_workspaces") == 0)
} else if (strcasecmp(optarg, "get_workspaces") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES;
else if (strcasecmp(optarg, "get_outputs") == 0)
} else if (strcasecmp(optarg, "get_outputs") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS;
else if (strcasecmp(optarg, "get_tree") == 0)
} else if (strcasecmp(optarg, "get_tree") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_TREE;
else if (strcasecmp(optarg, "get_marks") == 0)
} else if (strcasecmp(optarg, "get_marks") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS;
else if (strcasecmp(optarg, "get_bar_config") == 0)
} else if (strcasecmp(optarg, "get_bar_config") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG;
else if (strcasecmp(optarg, "get_binding_modes") == 0)
} else if (strcasecmp(optarg, "get_binding_modes") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_BINDING_MODES;
else if (strcasecmp(optarg, "get_version") == 0)
} else if (strcasecmp(optarg, "get_version") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION;
else {
} else if (strcasecmp(optarg, "get_config") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG;
} else {
printf("Unknown message type\n");
printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version\n");
printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config\n");
exit(EXIT_FAILURE);
}
} else if (o == 'q') {
@ -241,7 +280,7 @@ int main(int argc, char *argv[]) {
errx(EXIT_FAILURE, "IPC: Received reply of type %d but expected %d", reply_type, message_type);
/* 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_MESSAGE_TYPE_COMMAND) {
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);
@ -256,8 +295,24 @@ int main(int argc, char *argv[]) {
/* NB: We still fall-through and print the reply, because even if one
* command failed, that doesnt mean that all commands failed. */
} 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.");
}
goto exit;
}
printf("%.*s\n", reply_length, reply);
exit:
free(reply);
close(sockfd);

View File

@ -21,6 +21,7 @@
typedef struct Config Config;
typedef struct Barconfig Barconfig;
extern char *current_configpath;
extern char *current_config;
extern Config config;
extern SLIST_HEAD(modes_head, Mode) modes;
extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;

View File

@ -54,6 +54,9 @@ typedef struct i3_ipc_header {
/** Request a list of configured binding modes. */
#define I3_IPC_MESSAGE_TYPE_GET_BINDING_MODES 8
/** Request the raw last loaded i3 config. */
#define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9
/*
* Messages from i3 to clients
*
@ -67,6 +70,7 @@ typedef struct i3_ipc_header {
#define I3_IPC_REPLY_TYPE_BAR_CONFIG 6
#define I3_IPC_REPLY_TYPE_VERSION 7
#define I3_IPC_REPLY_TYPE_BINDING_MODES 8
#define I3_IPC_REPLY_TYPE_CONFIG 9
/*
* Events from i3 to clients. Events have the first bit set high.

View File

@ -13,6 +13,7 @@
#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);

View File

@ -898,6 +898,11 @@ bool parse_file(const char *f, bool use_nagbar) {
if ((fstr = fdopen(fd, "r")) == NULL)
die("Could not fdopen: %s\n", strerror(errno));
FREE(current_config);
current_config = scalloc(stbuf.st_size + 1, 1);
fread(current_config, 1, stbuf.st_size, fstr);
rewind(fstr);
while (!feof(fstr)) {
if (!continuation)
continuation = buffer;

View File

@ -1087,9 +1087,30 @@ IPC_HANDLER(subscribe) {
ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply);
}
/*
* Returns the raw last loaded i3 configuration file contents.
*/
IPC_HANDLER(get_config) {
yajl_gen gen = ygenalloc();
y(map_open);
ystr("config");
ystr(current_config);
y(map_close);
const unsigned char *payload;
ylength length;
y(get_buf, &payload, &length);
ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_CONFIG, payload);
y(free);
}
/* The index of each callback function corresponds to the numeric
* value of the message type (see include/i3/ipc.h) */
handler_t handlers[9] = {
handler_t handlers[10] = {
handle_command,
handle_get_workspaces,
handle_subscribe,
@ -1099,6 +1120,7 @@ handler_t handlers[9] = {
handle_get_bar_config,
handle_get_version,
handle_get_binding_modes,
handle_get_config,
};
/*

View File

@ -0,0 +1,55 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • http://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • http://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • http://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Verifies that the config file is returned raw via the IPC interface.
# Ticket: #2856
# Bug still in: 4.13-133-ge4da07e7
use i3test i3_autostart => 0;
use File::Temp qw(tempdir);
my $tmpdir = tempdir(CLEANUP => 1);
my $socketpath = $tmpdir . "/config.sock";
ok(! -e $socketpath, "$socketpath does not exist yet");
my $config = <<'EOT';
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
nop foo \
continued
set $var normal title
for_window [title="$vartest"] border none
EOT
$config .= "ipc-socket $socketpath";
my $pid = launch_with_config($config, dont_add_socket_path => 1, dont_create_temp_dir => 1);
get_socket_path(0);
my $i3 = i3(get_socket_path());
$i3->connect->recv;
my $cv = AE::cv;
my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
my $last_config = $i3->get_config()->recv;
chomp($last_config->{config});
is($last_config->{config}, $config,
'received config is not equal to written config');
exit_gracefully($pid);
done_testing;