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:
parent
6bb9c9e708
commit
a6d8ed9b1a
|
@ -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
|
||||
|
|
|
@ -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 doesn’t 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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
24
src/ipc.c
24
src/ipc.c
|
@ -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,
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -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;
|
Loading…
Reference in New Issue