i3/i3-migrate-config-to-v4

364 lines
11 KiB
Perl
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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) isnt
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 its 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";
}