Merge pull request #2861 from stapelberg/ipcconfig

Introduce the GET_CONFIG IPC request
This commit is contained in:
Michael Stapelberg 2017-08-19 19:23:02 +02:00 committed by GitHub
commit e6682f862b
10 changed files with 180 additions and 16 deletions

View File

@ -1,5 +1,9 @@
Revision history for AnyEvent-I3 Revision history for AnyEvent-I3
0.18 2017-08-19
* support the GET_CONFIG command
0.17 2017-04-09 0.17 2017-04-09
* support the shutdown event * support the shutdown event

View File

@ -16,11 +16,11 @@ AnyEvent::I3 - communicate with the i3 window manager
=cut =cut
our $VERSION = '0.17'; our $VERSION = '0.18';
=head1 VERSION =head1 VERSION
Version 0.17 Version 0.18
=head1 SYNOPSIS =head1 SYNOPSIS
@ -95,10 +95,13 @@ use constant TYPE_GET_TREE => 4;
use constant TYPE_GET_MARKS => 5; use constant TYPE_GET_MARKS => 5;
use constant TYPE_GET_BAR_CONFIG => 6; use constant TYPE_GET_BAR_CONFIG => 6;
use constant TYPE_GET_VERSION => 7; use constant TYPE_GET_VERSION => 7;
use constant TYPE_GET_BINDING_MODES => 8;
use constant TYPE_GET_CONFIG => 9;
our %EXPORT_TAGS = ( 'all' => [ our %EXPORT_TAGS = ( 'all' => [
qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS 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} } ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
@ -501,6 +504,20 @@ sub get_version {
return $cv; 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) =head2 command($content)
Makes i3 execute the given command Makes i3 execute the given command

View File

@ -580,7 +580,7 @@ i3-config-parser.stamp: parser/$(dirstamp) generate-command-parser.pl parser-spe
# AnyEvent-I3 build process # AnyEvent-I3 build process
################################################################################ ################################################################################
anyevent-i3.stamp: generate-command-parser.pl parser-specs/config.spec anyevent-i3.stamp: AnyEvent-I3/lib/AnyEvent/I3.pm
$(AM_V_BUILD) (cd $(top_srcdir)/AnyEvent-I3 && perl Makefile.PL && make) $(AM_V_BUILD) (cd $(top_srcdir)/AnyEvent-I3 && perl Makefile.PL && make)
$(AM_V_at) touch $@ $(AM_V_at) touch $@

View File

@ -119,6 +119,43 @@ static yajl_callbacks reply_callbacks = {
.yajl_end_map = reply_end_map_cb, .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[]) { int main(int argc, char *argv[]) {
#if defined(__OpenBSD__) #if defined(__OpenBSD__)
if (pledge("stdio rpath unix", NULL) == -1) if (pledge("stdio rpath unix", NULL) == -1)
@ -150,25 +187,27 @@ int main(int argc, char *argv[]) {
free(socket_path); free(socket_path);
socket_path = sstrdup(optarg); socket_path = sstrdup(optarg);
} else if (o == 't') { } else if (o == 't') {
if (strcasecmp(optarg, "command") == 0) if (strcasecmp(optarg, "command") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_COMMAND; 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; 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; 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; 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; 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; 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; 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; 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("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); exit(EXIT_FAILURE);
} }
} else if (o == 'q') { } 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); 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. /* 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_MESSAGE_TYPE_COMMAND) { if (reply_type == I3_IPC_REPLY_TYPE_COMMAND) {
yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL); yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length); yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
yajl_free(handle); 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 /* NB: We still fall-through and print the reply, because even if one
* command failed, that doesnt mean that all commands failed. */ * 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); printf("%.*s\n", reply_length, reply);
exit:
free(reply); free(reply);
close(sockfd); close(sockfd);

View File

@ -21,6 +21,7 @@
typedef struct Config Config; typedef struct Config Config;
typedef struct Barconfig Barconfig; typedef struct Barconfig Barconfig;
extern char *current_configpath; extern char *current_configpath;
extern char *current_config;
extern Config config; extern Config config;
extern SLIST_HEAD(modes_head, Mode) modes; extern SLIST_HEAD(modes_head, Mode) modes;
extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs; 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. */ /** Request a list of configured binding modes. */
#define I3_IPC_MESSAGE_TYPE_GET_BINDING_MODES 8 #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 * 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_BAR_CONFIG 6
#define I3_IPC_REPLY_TYPE_VERSION 7 #define I3_IPC_REPLY_TYPE_VERSION 7
#define I3_IPC_REPLY_TYPE_BINDING_MODES 8 #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. * Events from i3 to clients. Events have the first bit set high.

View File

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

View File

@ -898,6 +898,11 @@ bool parse_file(const char *f, bool use_nagbar) {
if ((fstr = fdopen(fd, "r")) == NULL) if ((fstr = fdopen(fd, "r")) == NULL)
die("Could not fdopen: %s\n", strerror(errno)); 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)) { while (!feof(fstr)) {
if (!continuation) if (!continuation)
continuation = buffer; 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); 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 /* The index of each callback function corresponds to the numeric
* value of the message type (see include/i3/ipc.h) */ * value of the message type (see include/i3/ipc.h) */
handler_t handlers[9] = { handler_t handlers[10] = {
handle_command, handle_command,
handle_get_workspaces, handle_get_workspaces,
handle_subscribe, handle_subscribe,
@ -1099,6 +1120,7 @@ handler_t handlers[9] = {
handle_get_bar_config, handle_get_bar_config,
handle_get_version, handle_get_version,
handle_get_binding_modes, 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;