File: i3-migrate-config-to-v4

package info (click to toggle)
i3-wm 4.16.1-1
  • links: PTS
  • area: main
  • in suites: buster
  • size: 6,296 kB
  • sloc: ansic: 29,563; perl: 15,671; sh: 4,475; makefile: 529
file content (363 lines) | stat: -rwxr-xr-x 10,860 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
#!/usr/bin/env perl
# vim:ts=4:sw=4:expandtab
#
# script to migrate an old config file (i3 < 4.0) to the new format (>= 4.0)
# this script only uses modules which come with perl 5.10
#
# reads an i3 v3 config from stdin and spits out a v4 config on stdout
# exit codes:
#     0 = the input was a v3 config
#     1 = the input was already a v4 config
#         (the config is printed to stdout nevertheless)
#
# © 2011 Michael Stapelberg and contributors, see LICENSE

use strict;
use warnings;
use Getopt::Long;
use v5.10;

# is this a version 3 config file? disables auto-detection
my $v3 = 0;
my $result = GetOptions('v3' => \$v3);

# reads stdin
sub slurp {
    local $/;
    <>;
}

my @unchanged = qw(
    font
    set
    mode
    exec
    assign
    floating_modifier
    focus_follows_mouse
    ipc-socket
    ipc_socket
    client.focused
    client.focused_inactive
    client.unfocused
    client.urgent
    client.background
);

my %workspace_names;
my $workspace_bar = 1;

my $input = slurp();
my @lines = split /\n/, $input;

# remove whitespaces in the beginning of lines
@lines = map { s/^[ \t]*//g; $_ } @lines;

# Try to auto-detect if this is a v3 config file.
sub need_to_convert {
    # If the user passed --v3, we need to convert in any case
    return 1 if $v3;

    for my $line (@lines) {
        # only v4 configfiles can use bindcode or workspace_layout
        return 0 if $line =~ /^bindcode/ ||
                    $line =~ /^workspace_layout/ ||
                    $line =~ /^force_focus_wrapping/;

        # have a look at bindings
        next unless $line =~ /^bind/;

        my ($statement, $key, $command) = ($line =~ /([a-zA-Z_-]+)\s+([^\s]+)\s+(.*)/);
        return 0 if $command =~ /^layout/ ||
                    $command =~ /^floating/ ||
                    $command =~ /^workspace/ ||
                    $command =~ /^focus (left|right|up|down)/ ||
                    $command =~ /^border (normal|1pixel|none)/;
    }

    return 1;
}

if (!need_to_convert()) {
    # If this is already a v4 config file, we will spit out the lines
    # and exit with return code 1
    print $input;
    exit 1;
}

# first pass: get workspace names
for my $line (@lines) {
    next if $line =~ /^#/ || $line =~ /^$/;

    my ($statement, $parameters) = ($line =~ /([a-zA-Z._-]+)(.*)/);

    # skip everything but workspace lines
    next unless defined($statement) and $statement eq 'workspace';

    my ($number, $params) = ($parameters =~ /\s+([0-9]+)\s+(.+)/);

    # save workspace name (unless the line is actually a workspace assignment)
    $workspace_names{$number} = $params unless $params =~ /^output/;
}

for my $line (@lines) {
    # directly use comments and empty lines
    if ($line =~ /^#/ || $line =~ /^$/) {
        print "$line\n";
        next;
    }

    my ($statement, $parameters) = ($line =~ /([a-zA-Z._-]+)(.*)/);

    # directly use lines which have not changed between 3.x and 4.x
    if (!defined($statement) || (lc $statement ~~ @unchanged)) {
        print "$line\n";
        next;
    }

    # new_container changed only the statement name to workspace_layout
    if ($statement eq 'new_container') {
        print "workspace_layout$parameters\n";
        next;
    }

    # workspace_bar is gone, you should use i3bar now
    if ($statement eq 'workspace_bar') {
        $workspace_bar = ($parameters =~ /\s+(yes|true|on|enable|active)/);
        print "# XXX: REMOVED workspace_bar line. There is no internal workspace bar in v4.\n";
        next;
    }

    # new_window changed the parameters from bb to none etc.
    if ($statement eq 'new_window') {
        if ($parameters =~ /bb/) {
            print "new_window none\n";
        } elsif ($parameters =~ /bp/) {
            print "new_window 1pixel\n";
        } elsif ($parameters =~ /bn/) {
            print "new_window normal\n";
        } else {
            print "# XXX: Invalid parameter for new_window, not touching line:\n";
            print "$line\n";
        }
        next;
    }

    # bar colors are obsolete, need to be configured in i3bar
    if ($statement =~ /^bar\./) {
        print "# XXX: REMOVED $statement, configure i3bar instead.\n";
        print "# Old line: $line\n";
        next;
    }

    # one form of this is still ok (workspace assignments), the other (named workspaces) isn’t
    if ($statement eq 'workspace') {
        my ($number, $params) = ($parameters =~ /\s+([0-9]+)\s+(.+)/);
        if ($params =~ /^output/) {
            print "$line\n";
            next;
        } else {
            print "# XXX: workspace name will end up in the corresponding bindings.\n";
            next;
        }
    }

    if ($statement eq 'bind' || $statement eq 'bindsym') {
        convert_command($line);
        next;
    }

    print "# XXX: migration script could not handle line: $line\n";
}

#
# Converts a command (after bind/bindsym)
#
sub convert_command {
    my ($line) = @_;

    my @unchanged_cmds = qw(
        exec
        mark
        kill
        restart
        reload
        exit
    );

    my ($statement, $key, $command) = ($line =~ /([a-zA-Z_-]+)\s+([^\s]+)\s+(.*)/);

    # turn 'bind' to 'bindcode'
    $statement = 'bindcode' if $statement eq 'bind';

    # check if it’s one of the unchanged commands
    my ($cmd) = ($command =~ /([a-zA-Z_-]+)/);
    if ($cmd ~~ @unchanged_cmds) {
        print "$statement $key $command\n";
        return;
    }

    # simple replacements
    my @replace = (
        qr/^s/ => 'layout stacking',
        qr/^d/ => 'layout toggle split',
        qr/^T/ => 'layout tabbed',
        qr/^f($|[^go])/ => 'fullscreen',
        qr/^fg/ => 'fullscreen global',
        qr/^t/ => 'floating toggle',
        qr/^h/ => 'focus left',
        qr/^j($|[^u])/ => 'focus down',
        qr/^k/ => 'focus up',
        qr/^l/ => 'focus right',
        qr/^mh/ => 'move left',
        qr/^mj/ => 'move down',
        qr/^mk/ => 'move up',
        qr/^ml/ => 'move right',
        qr/^bn/ => 'border normal',
        qr/^bp/ => 'border 1pixel',
        qr/^bb/ => 'border none',
        qr/^bt/ => 'border toggle',
        qr/^pw/ => 'workspace prev',
        qr/^nw/ => 'workspace next',
    );

    my $replaced = 0;
    for (my $c = 0; $c < @replace; $c += 2) {
        if ($command =~ $replace[$c]) {
            $command = $replace[$c+1];
            $replaced = 1;
            last;
        }
    }

    # goto command is now obsolete due to criteria + focus command
    if ($command =~ /^goto/) {
        my ($mark) = ($command =~ /^goto\s+(.*)/);
        print qq|$statement $key [con_mark="$mark"] focus\n|;
        return;
    }

    # the jump command is also obsolete due to criteria + focus
    if ($command =~ /^jump/) {
        my ($params) = ($command =~ /^jump\s+(.*)/);
        if ($params =~ /^"/) {
            # jump ["]window class[/window title]["]
            ($params) = ($params =~ /^"([^"]+)"/);

            # check if a window title was specified
            if ($params =~ m,/,) {
                my ($class, $title) = ($params =~ m,([^/]+)/(.+),);
                print qq|$statement $key [class="$class" title="$title"] focus\n|;
            } else {
                print qq|$statement $key [class="$params"] focus\n|;
            }
            return;
        } else {
            # jump <workspace> [ column row ]
            print "# XXX: jump workspace is obsolete in 4.x: $line\n";
            return;
        }
    }

    if (!$replaced && $command =~ /^focus/) {
        my ($what) = ($command =~ /^focus\s+(.*)/);
        $what =~ s/\s//g;
        if ($what eq 'ft') {
            $what = 'mode_toggle';
        } elsif ($what eq 'floating' || $what eq 'tiling') {
            # those are unchanged
        } else {
            print "# XXX: focus <number> is obsolete in 4.x: $line\n";
            return;
        }
        print qq|$statement $key focus $what\n|;
        return;
    }

    if ($command =~ /^mode/) {
        my ($parameters) = ($command =~ /^mode\s+(.*)/);
        print qq|$statement $key mode "$parameters"\n|;
        return;
    }

    # the parameters of the resize command have changed
    if ($command =~ /^resize/) {
        # OLD: resize <left|right|top|bottom> [+|-]<pixels>\n")
        # NEW: resize <grow|shrink> <direction> [<px> px] [or <ppt> ppt]
        my ($direction, $sign, $px) = ($command =~ /^resize\s+(left|right|top|bottom)\s+([+-])([0-9]+)/);
        my $cmd = 'resize ';
        $cmd .= ($sign eq '+' ? 'grow' : 'shrink') . ' ';
        if ($direction eq 'top') {
            $direction = 'up';
        } elsif ($direction eq 'bottom') {
            $direction = 'down';
        }
        $cmd .= "$direction ";
        $cmd .= "$px px";
        print qq|$statement $key $cmd\n|;
        return;
    }

    # switch workspace
    if ($command =~ /^[0-9]+/) {
        my ($number) = ($command =~ /^([0-9]+)/);
        if (exists $workspace_names{$number}) {
            print qq|$statement $key workspace $workspace_names{$number}\n|;
            return;
        } else {
            print qq|$statement $key workspace $number\n|;
            return;
        }
    }

    # move to workspace
    if ($command =~ /^m[0-9]+/) {
        my ($number) = ($command =~ /^m([0-9]+)/);
        if (exists $workspace_names{$number}) {
            print qq|$statement $key move container to workspace $workspace_names{$number}\n|;
            return;
        } else {
            print qq|$statement $key move container to workspace $number\n|;
            return;
        }
    }

    # With Container-commands are now obsolete due to different abstraction
    if ($command =~ /^wc/) {
        $command =~ s/^wc//g;
        my $wc_replaced = 0;
        for (my $c = 0; $c < @replace; $c += 2) {
            if ($command =~ $replace[$c]) {
                $command = $replace[$c+1];
                $wc_replaced = 1;
                last;
            }
        }
        if (!$wc_replaced) {
            print "# XXX: migration script could not handle command: $line\n";
        } else {
            # NOTE: This is not 100% accurate, as it only works for one level
            # of nested containers. As this is a common use case, we use 'focus
            # parent; $command' nevertheless. For advanced use cases, the user
            # has to modify their config.
            print "$statement $key focus parent; $command\n";
        }
        return;
    }

    if ($replaced) {
        print "$statement $key $command\n";
    } else {
        print "# XXX: migration script could not handle command: $line\n";
    }
}


# add an i3bar invocation automatically if no 'workspace_bar no' was found
if ($workspace_bar) {
    print "\n";
    print "# XXX: Automatically added a bar configuration\n";
    print "bar {\n";
    print "    status_command i3status\n";
    print "}\n";
}