]> git.sur5r.net Git - i3/i3/blob - i3-migrate-config-to-v4
Merge branch 'master' into next
[i3/i3] / i3-migrate-config-to-v4
1 #!/usr/bin/env perl
2 # vim:ts=4:sw=4:expandtab
3 #
4 # script to migrate an old config file (i3 < 4.0) to the new format (>= 4.0)
5 # this script only uses modules which come with perl 5.10
6 #
7 # reads an i3 v3 config from stdin and spits out a v4 config on stdout
8 # exit codes:
9 #     0 = the input was a v3 config
10 #     1 = the input was already a v4 config
11 #         (the config is printed to stdout nevertheless)
12 #
13 # © 2011 Michael Stapelberg and contributors, see LICENSE
14
15 use strict;
16 use warnings;
17 use Getopt::Long;
18 use v5.10;
19
20 # is this a version 3 config file? disables auto-detection
21 my $v3 = 0;
22 my $result = GetOptions('v3' => \$v3);
23
24 # reads stdin
25 sub slurp {
26     local $/;
27     <>;
28 }
29
30 my @unchanged = qw(
31     font
32     set
33     mode
34     exec
35     assign
36     floating_modifier
37     focus_follows_mouse
38     ipc-socket
39     ipc_socket
40     client.focused
41     client.focused_inactive
42     client.unfocused
43     client.urgent
44     client.background
45 );
46
47 my %workspace_names;
48 my $workspace_bar = 1;
49
50 my $input = slurp();
51 my @lines = split /\n/, $input;
52
53 # remove whitespaces in the beginning of lines
54 @lines = map { s/^[ \t]*//g; $_ } @lines;
55
56 # Try to auto-detect if this is a v3 config file.
57 sub need_to_convert {
58     # If the user passed --v3, we need to convert in any case
59     return 1 if $v3;
60
61     for my $line (@lines) {
62         # only v4 configfiles can use bindcode or workspace_layout
63         return 0 if $line =~ /^bindcode/ ||
64                     $line =~ /^workspace_layout/ ||
65                     $line =~ /^force_focus_wrapping/;
66
67         # have a look at bindings
68         next unless $line =~ /^bind/;
69
70         my ($statement, $key, $command) = ($line =~ /([a-zA-Z_-]+)\s+([^\s]+)\s+(.*)/);
71         return 0 if $command =~ /^layout/ ||
72                     $command =~ /^floating/ ||
73                     $command =~ /^workspace/ ||
74                     $command =~ /^focus (left|right|up|down)/ ||
75                     $command =~ /^border (normal|1pixel|none)/;
76     }
77
78     return 1;
79 }
80
81 if (!need_to_convert()) {
82     # If this is already a v4 config file, we will spit out the lines
83     # and exit with return code 1
84     print $input;
85     exit 1;
86 }
87
88 # first pass: get workspace names
89 for my $line (@lines) {
90     next if $line =~ /^#/ || $line =~ /^$/;
91
92     my ($statement, $parameters) = ($line =~ /([a-zA-Z._-]+)(.*)/);
93
94     # skip everything but workspace lines
95     next unless defined($statement) and $statement eq 'workspace';
96
97     my ($number, $params) = ($parameters =~ /\s+([0-9]+)\s+(.+)/);
98
99     # save workspace name (unless the line is actually a workspace assignment)
100     $workspace_names{$number} = $params unless $params =~ /^output/;
101 }
102
103 for my $line (@lines) {
104     # directly use comments and empty lines
105     if ($line =~ /^#/ || $line =~ /^$/) {
106         print "$line\n";
107         next;
108     }
109
110     my ($statement, $parameters) = ($line =~ /([a-zA-Z._-]+)(.*)/);
111
112     # directly use lines which have not changed between 3.x and 4.x
113     if (!defined($statement) || (lc $statement ~~ @unchanged)) {
114         print "$line\n";
115         next;
116     }
117
118     # new_container changed only the statement name to workspace_layout
119     if ($statement eq 'new_container') {
120         # TODO: new_container stack-limit
121         print "workspace_layout$parameters\n";
122         next;
123     }
124
125     # workspace_bar is gone, you should use i3bar now
126     if ($statement eq 'workspace_bar') {
127         $workspace_bar = ($parameters =~ /\s+(yes|true|on|enable|active)/);
128         print "# XXX: REMOVED workspace_bar line. There is no internal workspace bar in v4.\n";
129         next;
130     }
131
132     # new_window changed the parameters from bb to none etc.
133     if ($statement eq 'new_window') {
134         if ($parameters =~ /bb/) {
135             print "new_window none\n";
136         } elsif ($parameters =~ /bp/) {
137             print "new_window 1pixel\n";
138         } elsif ($parameters =~ /bn/) {
139             print "new_window normal\n";
140         } else {
141             print "# XXX: Invalid parameter for new_window, not touching line:\n";
142             print "$line\n";
143         }
144         next;
145     }
146
147     # bar colors are obsolete, need to be configured in i3bar
148     if ($statement =~ /^bar\./) {
149         print "# XXX: REMOVED $statement, configure i3bar instead.\n";
150         print "# Old line: $line\n";
151         next;
152     }
153
154     # one form of this is still ok (workspace assignments), the other (named workspaces) isn’t
155     if ($statement eq 'workspace') {
156         my ($number, $params) = ($parameters =~ /\s+([0-9]+)\s+(.+)/);
157         if ($params =~ /^output/) {
158             print "$line\n";
159             next;
160         } else {
161             print "# XXX: workspace name will end up in the corresponding bindings.\n";
162             next;
163         }
164     }
165
166     if ($statement eq 'bind' || $statement eq 'bindsym') {
167         convert_command($line);
168         next;
169     }
170
171     print "# XXX: migration script could not handle line: $line\n";
172 }
173
174 #
175 # Converts a command (after bind/bindsym)
176 #
177 sub convert_command {
178     my ($line) = @_;
179
180     my @unchanged_cmds = qw(
181         exec
182         mark
183         kill
184         restart
185         reload
186         exit
187         stack-limit
188     );
189
190     my ($statement, $key, $command) = ($line =~ /([a-zA-Z_-]+)\s+([^\s]+)\s+(.*)/);
191
192     # turn 'bind' to 'bindcode'
193     $statement = 'bindcode' if $statement eq 'bind';
194
195     # check if it’s one of the unchanged commands
196     my ($cmd) = ($command =~ /([a-zA-Z_-]+)/);
197     if ($cmd ~~ @unchanged_cmds) {
198         print "$statement $key $command\n";
199         return;
200     }
201
202     # simple replacements
203     my @replace = (
204         qr/^s/ => 'layout stacking',
205         qr/^d/ => 'layout default',
206         qr/^T/ => 'layout tabbed',
207         qr/^f($|[^go])/ => 'fullscreen',
208         qr/^fg/ => 'fullscreen global',
209         qr/^t/ => 'floating toggle',
210         qr/^h/ => 'focus left',
211         qr/^j($|[^u])/ => 'focus down',
212         qr/^k/ => 'focus up',
213         qr/^l/ => 'focus right',
214         qr/^mh/ => 'move left',
215         qr/^mj/ => 'move down',
216         qr/^mk/ => 'move up',
217         qr/^ml/ => 'move right',
218         qr/^bn/ => 'border normal',
219         qr/^bp/ => 'border 1pixel',
220         qr/^bb/ => 'border none',
221         qr/^bt/ => 'border toggle',
222         qr/^pw/ => 'workspace prev',
223         qr/^nw/ => 'workspace next',
224     );
225
226     my $replaced = 0;
227     for (my $c = 0; $c < @replace; $c += 2) {
228         if ($command =~ $replace[$c]) {
229             $command = $replace[$c+1];
230             $replaced = 1;
231             last;
232         }
233     }
234
235     # goto command is now obsolete due to criteria + focus command
236     if ($command =~ /^goto/) {
237         my ($mark) = ($command =~ /^goto\s+(.*)/);
238         print qq|$statement $key [con_mark="$mark"] focus\n|;
239         return;
240     }
241
242     # the jump command is also obsolete due to criteria + focus
243     if ($command =~ /^jump/) {
244         my ($params) = ($command =~ /^jump\s+(.*)/);
245         if ($params =~ /^"/) {
246             # jump ["]window class[/window title]["]
247             ($params) = ($params =~ /^"([^"]+)"/);
248
249             # check if a window title was specified
250             if ($params =~ m,/,) {
251                 my ($class, $title) = ($params =~ m,([^/]+)/(.+),);
252                 print qq|$statement $key [class="$class" title="$title"] focus\n|;
253             } else {
254                 print qq|$statement $key [class="$params"] focus\n|;
255             }
256             return;
257         } else {
258             # jump <workspace> [ column row ]
259             print "# XXX: jump workspace is obsolete in 4.x: $line\n";
260             return;
261         }
262     }
263
264     if (!$replaced && $command =~ /^focus/) {
265         my ($what) = ($command =~ /^focus\s+(.*)/);
266         $what =~ s/\s//g;
267         if ($what eq 'ft') {
268             $what = 'mode_toggle';
269         } elsif ($what eq 'floating' || $what eq 'tiling') {
270             # those are unchanged
271         } else {
272             print "# XXX: focus <number> is obsolete in 4.x: $line\n";
273             return;
274         }
275         print qq|$statement $key focus $what\n|;
276         return;
277     }
278
279     if ($command =~ /^mode/) {
280         my ($parameters) = ($command =~ /^mode\s+(.*)/);
281         print qq|$statement $key mode "$parameters"\n|;
282         return;
283     }
284
285     # the parameters of the resize command have changed
286     if ($command =~ /^resize/) {
287         # OLD: resize <left|right|top|bottom> [+|-]<pixels>\n")
288         # NEW: resize <grow|shrink> <direction> [<px> px] [or <ppt> ppt]
289         my ($direction, $sign, $px) = ($command =~ /^resize\s+(left|right|top|bottom)\s+([+-])([0-9]+)/);
290         my $cmd = 'resize ';
291         $cmd .= ($sign eq '+' ? 'grow' : 'shrink') . ' ';
292         if ($direction eq 'top') {
293             $direction = 'up';
294         } elsif ($direction eq 'bottom') {
295             $direction = 'down';
296         }
297         $cmd .= "$direction ";
298         $cmd .= "$px px";
299         print qq|$statement $key $cmd\n|;
300         return;
301     }
302
303     # switch workspace
304     if ($command =~ /^[0-9]+/) {
305         my ($number) = ($command =~ /^([0-9]+)/);
306         if (exists $workspace_names{$number}) {
307             print qq|$statement $key workspace $workspace_names{$number}\n|;
308             return;
309         } else {
310             print qq|$statement $key workspace $number\n|;
311             return;
312         }
313     }
314
315     # move to workspace
316     if ($command =~ /^m[0-9]+/) {
317         my ($number) = ($command =~ /^m([0-9]+)/);
318         if (exists $workspace_names{$number}) {
319             print qq|$statement $key move container to workspace $workspace_names{$number}\n|;
320             return;
321         } else {
322             print qq|$statement $key move container to workspace $number\n|;
323             return;
324         }
325     }
326
327     # With Container-commands are now obsolete due to different abstraction
328     if ($command =~ /^wc/) {
329         $command =~ s/^wc//g;
330         my $wc_replaced = 0;
331         for (my $c = 0; $c < @replace; $c += 2) {
332             if ($command =~ $replace[$c]) {
333                 $command = $replace[$c+1];
334                 $wc_replaced = 1;
335                 last;
336             }
337         }
338         if (!$wc_replaced) {
339             print "# XXX: migration script could not handle command: $line\n";
340         } else {
341             # NOTE: This is not 100% accurate, as it only works for one level
342             # of nested containers. As this is a common use case, we use 'focus
343             # parent; $command' nevertheless. For advanced use cases, the user
344             # has to modify his config.
345             print "$statement $key focus parent; $command\n";
346         }
347         return;
348     }
349
350     if ($replaced) {
351         print "$statement $key $command\n";
352     } else {
353         print "# XXX: migration script could not handle command: $line\n";
354     }
355 }
356
357
358 # add an i3bar invocation automatically if no 'workspace_bar no' was found
359 if ($workspace_bar) {
360     print "\n";
361     print "# XXX: Automatically added a bar configuration\n";
362     print "bar {\n";
363     print "    status_command i3status\n";
364     print "}\n";
365 }