]> git.sur5r.net Git - i3/i3/commitdiff
Introduce the GET_CONFIG IPC request
authorMichael Stapelberg <michael@stapelberg.de>
Sat, 19 Aug 2017 15:29:03 +0000 (17:29 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Sat, 19 Aug 2017 17:08:51 +0000 (19:08 +0200)
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

AnyEvent-I3/lib/AnyEvent/I3.pm
i3-msg/main.c
include/configuration.h
include/i3/ipc.h
src/config.c
src/config_parser.c
src/ipc.c
testcases/t/268-ipc-config.t [new file with mode: 0644]

index 875f379018dd3dffad7c2d985d6517971f8cce63..5ce3c016ca0ff4ddb1d45acb470eaf4a22ddc39c 100644 (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
index 4bc3c14987300866e994f241c8916f0ad324b295..1a1727894b407283b7db50ff6fba8cc70dce8688 100644 (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 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);
index 66628eebf15c5317eca677021ad98b0f28e3af07..4f6e5ce833be7e80ac36422efc8537e4e8ba68d5 100644 (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;
index 249cc32e3d6e1e85c977db546c63b00dcce51b47..e3891454fdc561bca4847fbe96a010a8b0f4a33d 100644 (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.
index d4441d5dfec8da61c64f1f6e92ee60ab20ef861b..7e08b5208702ef64d06af2e047e302f3b0714aee 100644 (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);
index 60b27815d84549bad0a76c328b797d555ff1b688..e72923d6658905eee9521d7518fc896f66a923ae 100644 (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;
index bb20b340cb8323990b6048faa7773c5d161f26dd..18d6075d09901a54b2414c26b60d9da43d3baa96 100644 (file)
--- a/src/ipc.c
+++ b/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,
 };
 
 /*
diff --git a/testcases/t/268-ipc-config.t b/testcases/t/268-ipc-config.t
new file mode 100644 (file)
index 0000000..bb578e1
--- /dev/null
@@ -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;