i3 - improved tiling WM


Allow to validate the config file without X.

Patch status: merged

Patch by aszlig

Long description:

We're going to call parse_configuration() very early if -C is given on
the command line. Instead of the previous "only_check_config", which has
been a global variable, we now simply pass use_nagbar as false if we're
just validating.

This causes the whole parsing to run without X and of course without
starting nagbar and displaying the errors to standard out/error instead.

The return code of parse_configuration() is now a boolean which
represents whether an error occured during parsing and the programs exit
code is returned accordingly.

Although the config parser still has a lot of side-effects, we now can
parse without the need to have an XCB connection. A nicer implementation
would be to just set the new font and load it just after we're done
parsing, but to ensure we don't break functionality we just load a dummy
FONT_TYPE_NONE if XCB isn't available. The main reason for going this
route is that it's a bit difficult to test fonts in a distribution
agnostic way without bundling fonts with i3 (or Xdummy to be more
exact).

Signed-off-by: aszlig <aszlig@redmoonstudios.org>

To apply this patch, use:
curl http://cr.i3wm.org/patch/636/raw.patch | git am

b/include/config.h

42
@@ -315,6 +315,20 @@ struct Barconfig {
43
 };
44
 
45
 /**
46
+ * Finds the configuration file to use (either the one specified by
47
+ * override_configpath), the user’s one or the system default) and calls
48
+ * parse_file().
49
+ *
50
+ * If you specify override_configpath, only this path is used to look for a
51
+ * configuration file.
52
+ *
53
+ * If use_nagbar is false, don't try to start i3-nagbar but log the errors to
54
+ * stdout/stderr instead.
55
+ *
56
+ */
57
+bool parse_configuration(const char *override_configpath, bool use_nagbar);
58
+
59
+/**
60
  * Reads the configuration from ~/.i3/config or /etc/i3/config if not found.
61
  *
62
  * If you specify override_configpath, only this path is used to look for a

b/include/config_parser.h

67
@@ -33,7 +33,10 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context);
68
 
69
 /**
70
  * Parses the given file by first replacing the variables, then calling
71
- * parse_config and possibly launching i3-nagbar.
72
+ * parse_config and launching i3-nagbar if use_nagbar is true.
73
+ *
74
+ * The return value is a boolean indicating whether there were errors during
75
+ * parsing.
76
  *
77
  */
78
-void parse_file(const char *f);
79
+bool parse_file(const char *f, bool use_nagbar);

b/libi3/font.c

84
@@ -167,6 +167,12 @@ i3Font load_font(const char *pattern, const bool fallback) {
85
     i3Font font;
86
     font.type = FONT_TYPE_NONE;
87
 
88
+    /* No XCB connction, return early because we're just validating the
89
+     * configuration file. */
90
+    if (conn == NULL) {
91
+        return font;
92
+    }
93
+
94
 #if PANGO_SUPPORT
95
     /* Try to load a pango font if specified */
96
     if (strlen(pattern) > strlen("pango:") && !strncmp(pattern, "pango:", strlen("pango:"))) {

b/src/config.c

101
@@ -114,12 +114,19 @@ static char *get_config_path(const char *override_configpath) {
102
  * parse_file().
103
  *
104
  */
105
-static void parse_configuration(const char *override_configpath) {
106
+bool parse_configuration(const char *override_configpath, bool use_nagbar) {
107
     char *path = get_config_path(override_configpath);
108
     LOG("Parsing configfile %s\n", path);
109
     FREE(current_configpath);
110
     current_configpath = path;
111
-    parse_file(path);
112
+
113
+    /* initialize default bindings if we're just validating the config file */
114
+    if (!use_nagbar && bindings == NULL) {
115
+        bindings = scalloc(sizeof(struct bindings_head));
116
+        TAILQ_INIT(bindings);
117
+    }
118
+
119
+    return parse_file(path, use_nagbar);
120
 }
121
 
122
 /*
123
@@ -262,7 +269,7 @@ void load_configuration(xcb_connection_t *conn, const char *override_configpath,
124
     if (config.workspace_urgency_timer == 0)
125
         config.workspace_urgency_timer = 0.5;
126
 
127
-    parse_configuration(override_configpath);
128
+    parse_configuration(override_configpath, true);
129
 
130
     if (reload) {
131
         translate_keysyms();

b/src/config_parser.c

136
@@ -840,7 +840,7 @@ static char *migrate_config(char *input, off_t size) {
137
  * parse_config and possibly launching i3-nagbar.
138
  *
139
  */
140
-void parse_file(const char *f) {
141
+bool parse_file(const char *f, bool use_nagbar) {
142
     SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
143
     int fd, ret, read_bytes = 0;
144
     struct stat stbuf;
145
@@ -1000,7 +1000,7 @@ void parse_file(const char *f) {
146
 
147
     check_for_duplicate_bindings(context);
148
 
149
-    if (context->has_errors || context->has_warnings) {
150
+    if (use_nagbar && (context->has_errors || context->has_warnings)) {
151
         ELOG("FYI: You are using i3 version " I3_VERSION "\n");
152
         if (version == 3)
153
             ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
154
@@ -1030,6 +1030,8 @@ void parse_file(const char *f) {
155
         free(pageraction);
156
     }
157
 
158
+    bool has_errors = context->has_errors;
159
+
160
     FREE(context->line_copy);
161
     free(context);
162
     free(new);
163
@@ -1042,6 +1044,8 @@ void parse_file(const char *f) {
164
         SLIST_REMOVE_HEAD(&variables, variables);
165
         FREE(current);
166
     }
167
+
168
+    return !has_errors;
169
 }
170
 
171
 #endif

b/src/main.c

176
@@ -96,11 +96,6 @@ struct ws_assignments_head ws_assignments = TAILQ_HEAD_INITIALIZER(ws_assignment
177
 bool xcursor_supported = true;
178
 bool xkb_supported = true;
179
 
180
-/* This will be set to true when -C is used so that functions can behave
181
- * slightly differently. We don’t want i3-nagbar to be started when validating
182
- * the config, for example. */
183
-bool only_check_config = false;
184
-
185
 /*
186
  * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb.
187
  * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop
188
@@ -276,6 +271,7 @@ int main(int argc, char *argv[]) {
189
     bool force_xinerama = false;
190
     char *fake_outputs = NULL;
191
     bool disable_signalhandler = false;
192
+    bool only_check_config = false;
193
     static struct option long_options[] = {
194
         {"no-autostart", no_argument, 0, 'a'},
195
         {"config", required_argument, 0, 'c'},
196
@@ -441,10 +437,14 @@ int main(int argc, char *argv[]) {
197
         }
198
     }
199
 
200
+    if (only_check_config) {
201
+        exit(parse_configuration(override_configpath, false) ? 0 : 1);
202
+    }
203
+
204
     /* If the user passes more arguments, we act like i3-msg would: Just send
205
      * the arguments as an IPC message to i3. This allows for nice semantic
206
      * commands such as 'i3 border none'. */
207
-    if (!only_check_config && optind < argc) {
208
+    if (optind < argc) {
209
         /* We enable verbose mode so that the user knows what’s going on.
210
          * This should make it easier to find mistakes when the user passes
211
          * arguments by mistake. */
212
@@ -567,10 +567,6 @@ int main(int argc, char *argv[]) {
213
     xcb_query_pointer_cookie_t pointercookie = xcb_query_pointer(conn, root);
214
 
215
     load_configuration(conn, override_configpath, false);
216
-    if (only_check_config) {
217
-        LOG("Done checking configuration file. Exiting.\n");
218
-        exit(0);
219
-    }
220
 
221
     if (config.ipc_socket_path == NULL) {
222
         /* Fall back to a file name in /tmp/ based on the PID */

b/testcases/t/235-check-config-no-x.t

228
@@ -0,0 +1,60 @@
229
+#!perl
230
+# vim:ts=4:sw=4:expandtab
231
+#
232
+# Please read the following documents before working on tests:
233
+# • http://build.i3wm.org/docs/testsuite.html
234
+#   (or docs/testsuite)
235
+#
236
+# • http://build.i3wm.org/docs/lib-i3test.html
237
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
238
+#
239
+# • http://build.i3wm.org/docs/ipc.html
240
+#   (or docs/ipc)
241
+#
242
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
243
+#   (unless you are already familiar with Perl)
244
+#
245
+# Check whether the -C option works without a display and doesn't
246
+# accidentally start the nagbar.
247
+#
248
+use i3test i3_autostart => 0;
249
+use File::Temp qw(tempfile);
250
+
251
+sub check_config {
252
+    my ($config) = @_;
253
+    my ($fh, $tmpfile) = tempfile(UNLINK => 1);
254
+    print $fh $config;
255
+    my $output = qx(DISPLAY= ../i3 -C -c $tmpfile 2>&1);
256
+    my $retval = $?;
257
+    $fh->flush;
258
+    close($fh);
259
+    return ($retval >> 8, $output);
260
+}
261
+
262
+################################################################################
263
+# 1: test with a bogus configuration file
264
+################################################################################
265
+
266
+my $cfg = <<EOT;
267
+# i3 config file (v4)
268
+i_am_an_unknown_config option
269
+EOT
270
+
271
+my ($ret, $out) = check_config($cfg);
272
+is($ret, 1, "exit code == 1");
273
+like($out, qr/ERROR: *CONFIG: *[Ee]xpected.*tokens/, 'bogus config file');
274
+
275
+################################################################################
276
+# 2: test with a valid configuration file
277
+################################################################################
278
+
279
+my $cfg = <<EOT;
280
+# i3 config file (v4)
281
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
282
+EOT
283
+
284
+my ($ret, $out) = check_config($cfg);
285
+is($ret, 0, "exit code == 0");
286
+is($out, "", 'valid config file');
287
+
288
+done_testing;