]> git.sur5r.net Git - i3/i3/commitdiff
Merge branch 'next' into master
authorMichael Stapelberg <michael@stapelberg.de>
Mon, 4 Sep 2017 05:53:39 +0000 (07:53 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Mon, 4 Sep 2017 05:53:39 +0000 (07:53 +0200)
130 files changed:
.clang-format
.github/CONTRIBUTING.md
.github/GOVERNANCE.md [new file with mode: 0644]
.travis.yml
AnyEvent-I3/Changes [new file with mode: 0644]
AnyEvent-I3/MANIFEST [new file with mode: 0644]
AnyEvent-I3/MANIFEST.SKIP [new file with mode: 0644]
AnyEvent-I3/Makefile.PL [new file with mode: 0644]
AnyEvent-I3/README [new file with mode: 0644]
AnyEvent-I3/lib/AnyEvent/I3.pm [new file with mode: 0644]
AnyEvent-I3/t/00-load.t [new file with mode: 0644]
AnyEvent-I3/t/01-workspaces.t [new file with mode: 0644]
AnyEvent-I3/t/02-sugar.t [new file with mode: 0644]
AnyEvent-I3/t/boilerplate.t [new file with mode: 0644]
AnyEvent-I3/t/manifest.t [new file with mode: 0644]
AnyEvent-I3/t/pod-coverage.t [new file with mode: 0644]
AnyEvent-I3/t/pod.t [new file with mode: 0644]
I3_VERSION
Makefile.am
RELEASE-NOTES-4.13 [deleted file]
RELEASE-NOTES-4.14 [new file with mode: 0644]
configure.ac
debian/changelog
docs/hacking-howto
docs/ipc
docs/layout-saving
docs/testsuite
docs/userguide
generate-command-parser.pl
i3-config-wizard/main.c
i3-dmenu-desktop
i3-input/keysym2ucs.c
i3-input/main.c
i3-msg/main.c
i3-nagbar/main.c
i3-sensible-editor
i3-sensible-terminal
i3bar/include/common.h
i3bar/include/configuration.h
i3bar/include/outputs.h
i3bar/include/trayclients.h
i3bar/include/workspaces.h
i3bar/src/child.c
i3bar/src/config.c
i3bar/src/ipc.c
i3bar/src/main.c
i3bar/src/outputs.c
i3bar/src/xcb.c
include/atoms_NET_SUPPORTED.xmacro
include/bindings.h
include/commands.h
include/con.h
include/config_directives.h
include/configuration.h
include/data.h
include/debug.h [deleted file]
include/i3/ipc.h
include/ipc.h
include/libi3.h
include/queue.h
include/randr.h
include/sighandler.h
include/util.h
include/xcb.h
libi3/dpi.c
libi3/draw_util.c
libi3/font.c
libi3/get_config_path.c
m4/ax_extend_srcdir.m4
man/i3-sensible-editor.man
man/i3-sensible-terminal.man
man/i3.man
parser-specs/commands.spec
parser-specs/config.spec
release.sh
src/bindings.c
src/click.c
src/commands.c
src/con.c
src/config.c
src/config_directives.c
src/config_parser.c
src/debug.c [deleted file]
src/display_version.c
src/floating.c
src/handlers.c
src/ipc.c
src/load_layout.c
src/log.c
src/main.c
src/manage.c
src/match.c
src/output.c
src/randr.c
src/regex.c
src/restore_layout.c
src/sighandler.c
src/tree.c
src/util.c
src/x.c
src/xcb.c
testcases/Makefile.PL
testcases/complete-run.pl.in
testcases/inject_randr1.5.c [new file with mode: 0644]
testcases/lib/SocketActivation.pm
testcases/lib/i3test.pm.in
testcases/lib/i3test/XTEST.pm
testcases/t/000-load-deps.t
testcases/t/117-workspace.t
testcases/t/141-resize.t
testcases/t/167-workspace_layout.t
testcases/t/171-config-migrate.t
testcases/t/187-commands-parser.t
testcases/t/192-layout.t
testcases/t/201-config-parser.t
testcases/t/221-floating-type-hints.t
testcases/t/264-ipc-shutdown-event.t [new file with mode: 0644]
testcases/t/264-keypress-numlock.t
testcases/t/265-swap.t [new file with mode: 0644]
testcases/t/266-net-moveresize-window.t [new file with mode: 0644]
testcases/t/267-regress-mark-restart.t [new file with mode: 0644]
testcases/t/268-ipc-config.t [new file with mode: 0644]
testcases/t/269-focus-stack-above.t [new file with mode: 0644]
testcases/t/533-randr15.t [new file with mode: 0644]
testcases/t/534-dont-warp.t [new file with mode: 0644]
travis/check-formatting.sh
travis/travis-base-386.Dockerfile
travis/travis-base-ubuntu-386.Dockerfile
travis/travis-base-ubuntu.Dockerfile
travis/travis-base.Dockerfile

index 1d84013239de1922edcb58688c0800226db9b46b..6e49d83563639c382d7102d15157ee310c73bead 100644 (file)
@@ -8,3 +8,4 @@ IndentWidth: 4
 PointerBindsToType: false
 ColumnLimit: 0
 SpaceBeforeParens: ControlStatements
+SortIncludes: false
index 474355ad90f7a98c5325ce584f0ce871bf4c719b..c19ac81eb29e898e84b85a7b03061fae8b20548d 100644 (file)
@@ -14,6 +14,9 @@ Note that bug reports and feature requests for related projects should be filed
    having access to the source code is too time-consuming. Additionally,
    experience has shown that often, the software in question is responsible for
    the issue. Please raise an issue with the software in question, not i3.
+5. Please note that i3 does not support compositors (e.g. compton). If you
+   encountered the issue you are about to report while using a compositor,
+   please try reproducing it without a compositor.
 
 ## Pull requests
 
diff --git a/.github/GOVERNANCE.md b/.github/GOVERNANCE.md
new file mode 100644 (file)
index 0000000..44e1334
--- /dev/null
@@ -0,0 +1,30 @@
+# i3 project governance
+
+## Overview
+
+The i3 project uses a governance model commonly described as Benevolent
+Dictator For Life (BDFL). This document outlines our understanding of what this
+means.
+
+## Roles
+
+* user: anyone who interacts with the i3 project
+* core contributor: a handful of people who have contributed significantly to
+  the project by any means (issue triage, support, documentation, code, etc.).
+  Core contributors are recognizable via GitHub’s “Member” badge.
+* BDFL: a single individual who makes decisions when consensus cannot be
+  reached. i3’s current BDFL is [@stapelberg](https://github.com/stapelberg).
+
+## Decision making process
+
+In general, we try to reach consensus in discussions. In case consensus cannot
+be reached, the BDFL makes a decision.
+
+For feature requests and code contributions specifically, the values with which
+we consider them can be found on the bottom of https://i3wm.org/. These values
+are not set in stone and are to be treated as guiding principles, not absolute
+rules that must be followed in every case.
+
+## Contribution process
+
+Please see [CONTRIBUTING](CONTRIBUTING.md).
index f97443355feb96b78683a5d9580db6fe158e21bc..63f69ac860438bb508f214bbde48f0cae1a77ac2 100644 (file)
@@ -1,4 +1,4 @@
-sudo: required
+sudo: false
 dist: trusty
 services:
   - docker
diff --git a/AnyEvent-I3/Changes b/AnyEvent-I3/Changes
new file mode 100644 (file)
index 0000000..d763437
--- /dev/null
@@ -0,0 +1,73 @@
+Revision history for AnyEvent-I3
+
+0.18    2017-08-19
+
+   * support the GET_CONFIG command
+
+0.17    2017-04-09
+
+   * support the shutdown event
+   * use lib '.' for Perl 5.25.11+
+
+0.16    2014-10-03
+
+   * support the barconfig_update and binding event
+
+0.15    2013-02-18
+
+    * support the window event
+
+0.14    2012-09-22
+
+    * support the mode event
+
+0.13    2012-08-05
+
+    * support the GET_VERSION request with a fall-back to i3 --version
+
+0.12    2012-07-11
+
+    * taint mode fix: remove relative directories from $ENV{PATH}
+
+0.11    2012-07-10
+
+    * taint mode fix for FreeBSD
+
+0.10    2012-07-09
+
+    * Use i3 --get-socketpath by default for determining the socket path
+    * Bugfix: Also delete callbacks which are triggered due to an error
+
+0.09    2011-10-12
+
+    * Implement GET_BAR_CONFIG request
+
+0.08    2011-09-26
+
+    * Implement GET_MARKS request
+    * The synopsis mentioned ->workspaces, but it’s ->get_workspaces
+
+0.07    2010-11-21
+
+    * Implement GET_TREE request
+
+0.06    2010-06-16
+
+    * Add check to Makefile to abort in a Windows environment (neither i3 nor
+      unix sockets available)
+
+0.05    2010-06-09
+
+    * use getpwuid() to resolve ~ in socket paths instead of glob()
+
+0.04    2010-03-27
+
+    * use new default ipc-socket path, glob() path, bump version
+
+0.03    2010-03-26
+
+    * fix MANIFEST
+
+0.02    2010-03-23
+
+    * first upload to CPAN
diff --git a/AnyEvent-I3/MANIFEST b/AnyEvent-I3/MANIFEST
new file mode 100644 (file)
index 0000000..34c8a8f
--- /dev/null
@@ -0,0 +1,22 @@
+Changes
+inc/Module/Install.pm
+inc/Module/Install/Base.pm
+inc/Module/Install/Can.pm
+inc/Module/Install/Fetch.pm
+inc/Module/Install/Makefile.pm
+inc/Module/Install/Metadata.pm
+inc/Module/Install/Win32.pm
+inc/Module/Install/WriteAll.pm
+lib/AnyEvent/I3.pm
+Makefile.PL
+MANIFEST
+MANIFEST.SKIP
+META.yml
+README
+t/00-load.t
+t/01-workspaces.t
+t/02-sugar.t
+t/boilerplate.t
+t/manifest.t
+t/pod-coverage.t
+t/pod.t
diff --git a/AnyEvent-I3/MANIFEST.SKIP b/AnyEvent-I3/MANIFEST.SKIP
new file mode 100644 (file)
index 0000000..01bee91
--- /dev/null
@@ -0,0 +1,11 @@
+^\.git/
+\.bak$
+blib/
+^Makefile$
+^Makefile.old$
+Build
+Build.bat
+^pm_to_blib
+\.tar\.gz$
+^pod2htm(.*).tmp$
+^AnyEvent-I3-
diff --git a/AnyEvent-I3/Makefile.PL b/AnyEvent-I3/Makefile.PL
new file mode 100644 (file)
index 0000000..5d2ab32
--- /dev/null
@@ -0,0 +1,17 @@
+use lib '.';
+use inc::Module::Install;
+
+name     'AnyEvent-I3';
+all_from 'lib/AnyEvent/I3.pm';
+author   'Michael Stapelberg';
+
+requires 'AnyEvent';
+requires 'AnyEvent::Handle';
+requires 'AnyEvent::Socket';
+requires 'JSON::XS';
+
+if ($^O eq 'MSWin32') {
+    die "AnyEvent::I3 cannot be used on win32 (unix sockets are missing)";
+}
+
+WriteAll;
diff --git a/AnyEvent-I3/README b/AnyEvent-I3/README
new file mode 100644 (file)
index 0000000..4658ba1
--- /dev/null
@@ -0,0 +1,40 @@
+AnyEvent-I3
+
+This module connects to the i3 window manager using the UNIX socket based
+IPC interface it provides (if enabled in the configuration file). You can
+then subscribe to events or send messages and receive their replies.
+
+INSTALLATION
+
+To install this module, run the following commands:
+
+       perl Makefile.PL
+       make
+       make test
+       make install
+
+SUPPORT AND DOCUMENTATION
+
+After installing, you can find documentation for this module with the
+perldoc command.
+
+    perldoc AnyEvent::I3
+
+You can also look for information at:
+
+    RT, CPAN's request tracker
+        http://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-I3
+
+    The i3 window manager website
+        http://i3.zekjur.net/
+
+
+LICENSE AND COPYRIGHT
+
+Copyright (C) 2010 Michael Stapelberg
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of either: the GNU General Public License as published
+by the Free Software Foundation; or the Artistic License.
+
+See http://dev.perl.org/licenses/ for more information.
diff --git a/AnyEvent-I3/lib/AnyEvent/I3.pm b/AnyEvent-I3/lib/AnyEvent/I3.pm
new file mode 100644 (file)
index 0000000..75845cc
--- /dev/null
@@ -0,0 +1,586 @@
+package AnyEvent::I3;
+# vim:ts=4:sw=4:expandtab
+
+use strict;
+use warnings;
+use JSON::XS;
+use AnyEvent::Handle;
+use AnyEvent::Socket;
+use AnyEvent;
+use Encode;
+use Scalar::Util qw(tainted);
+
+=head1 NAME
+
+AnyEvent::I3 - communicate with the i3 window manager
+
+=cut
+
+our $VERSION = '0.18';
+
+=head1 VERSION
+
+Version 0.18
+
+=head1 SYNOPSIS
+
+This module connects to the i3 window manager using the UNIX socket based
+IPC interface it provides (if enabled in the configuration file). You can
+then subscribe to events or send messages and receive their replies.
+
+    use AnyEvent::I3 qw(:all);
+
+    my $i3 = i3();
+
+    $i3->connect->recv or die "Error connecting";
+    say "Connected to i3";
+
+    my $workspaces = $i3->message(TYPE_GET_WORKSPACES)->recv;
+    say "Currently, you use " . @{$workspaces} . " workspaces";
+
+...or, using the sugar methods:
+
+    use AnyEvent::I3;
+
+    my $workspaces = i3->get_workspaces->recv;
+    say "Currently, you use " . @{$workspaces} . " workspaces";
+
+A somewhat more involved example which dumps the i3 layout tree whenever there
+is a workspace event:
+
+    use Data::Dumper;
+    use AnyEvent;
+    use AnyEvent::I3;
+
+    my $i3 = i3();
+
+    $i3->connect->recv or die "Error connecting to i3";
+
+    $i3->subscribe({
+        workspace => sub {
+            $i3->get_tree->cb(sub {
+                my ($tree) = @_;
+                say "tree: " . Dumper($tree);
+            });
+        }
+    })->recv->{success} or die "Error subscribing to events";
+
+    AE::cv->recv
+
+=head1 EXPORT
+
+=head2 $i3 = i3([ $path ]);
+
+Creates a new C<AnyEvent::I3> object and returns it.
+
+C<path> is an optional path of the UNIX socket to connect to. It is strongly
+advised to NOT specify this unless you're absolutely sure you need it.
+C<AnyEvent::I3> will automatically figure it out by querying the running i3
+instance on the current DISPLAY which is almost always what you want.
+
+=head1 SUBROUTINES/METHODS
+
+=cut
+
+use Exporter qw(import);
+use base 'Exporter';
+
+our @EXPORT = qw(i3);
+
+use constant TYPE_COMMAND => 0;
+use constant TYPE_GET_WORKSPACES => 1;
+use constant TYPE_SUBSCRIBE => 2;
+use constant TYPE_GET_OUTPUTS => 3;
+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_BINDING_MODES TYPE_GET_CONFIG)
+] );
+
+our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
+
+my $magic = "i3-ipc";
+
+# TODO: auto-generate this from the header file? (i3/ipc.h)
+my $event_mask = (1 << 31);
+my %events = (
+    workspace => ($event_mask | 0),
+    output => ($event_mask | 1),
+    mode => ($event_mask | 2),
+    window => ($event_mask | 3),
+    barconfig_update => ($event_mask | 4),
+    binding => ($event_mask | 5),
+    shutdown => ($event_mask | 6),
+    _error => 0xFFFFFFFF,
+);
+
+sub i3 {
+    AnyEvent::I3->new(@_)
+}
+
+# Calls i3, even when running in taint mode.
+sub _call_i3 {
+    my ($args) = @_;
+
+    my $path_tainted = tainted($ENV{PATH});
+    # This effectively circumvents taint mode checking for $ENV{PATH}. We
+    # do this because users might specify PATH explicitly to call i3 in a
+    # custom location (think ~/.bin/).
+    (local $ENV{PATH}) = ($ENV{PATH} =~ /(.*)/);
+
+    # In taint mode, we also need to remove all relative directories from
+    # PATH (like . or ../bin). We only do this in taint mode and warn the
+    # user, since this might break a real-world use case for some people.
+    if ($path_tainted) {
+        my @dirs = split /:/, $ENV{PATH};
+        my @filtered = grep !/^\./, @dirs;
+        if (scalar @dirs != scalar @filtered) {
+            $ENV{PATH} = join ':', @filtered;
+            warn qq|Removed relative directories from PATH because you | .
+                 qq|are running Perl with taint mode enabled. Remove -T | .
+                 qq|to be able to use relative directories in PATH. | .
+                 qq|New PATH is "$ENV{PATH}"|;
+        }
+    }
+    # Otherwise the qx() operator wont work:
+    delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
+    chomp(my $result = qx(i3 $args));
+    # Circumventing taint mode again: the socket can be anywhere on the
+    # system and that’s okay.
+    if ($result =~ /^([^\0]+)$/) {
+        return $1;
+    }
+
+    warn "Calling i3 $args failed. Is DISPLAY set and is i3 in your PATH?";
+    return undef;
+}
+
+=head2 $i3 = AnyEvent::I3->new([ $path ])
+
+Creates a new C<AnyEvent::I3> object and returns it.
+
+C<path> is an optional path of the UNIX socket to connect to. It is strongly
+advised to NOT specify this unless you're absolutely sure you need it.
+C<AnyEvent::I3> will automatically figure it out by querying the running i3
+instance on the current DISPLAY which is almost always what you want.
+
+=cut
+sub new {
+    my ($class, $path) = @_;
+
+    $path = _call_i3('--get-socketpath') unless $path;
+
+    # This is the old default path (v3.*). This fallback line can be removed in
+    # a year from now. -- Michael, 2012-07-09
+    $path ||= '~/.i3/ipc.sock';
+
+    # Check if we need to resolve ~
+    if ($path =~ /~/) {
+        # We use getpwuid() instead of $ENV{HOME} because the latter is tainted
+        # and thus produces warnings when running tests with perl -T
+        my $home = (getpwuid($<))[7];
+        die "Could not get home directory" unless $home and -d $home;
+        $path =~ s/~/$home/g;
+    }
+
+    bless { path => $path } => $class;
+}
+
+=head2 $i3->connect
+
+Establishes the connection to i3. Returns an C<AnyEvent::CondVar> which will
+be triggered with a boolean (true if the connection was established) as soon as
+the connection has been established.
+
+    if ($i3->connect->recv) {
+        say "Connected to i3";
+    }
+
+=cut
+sub connect {
+    my ($self) = @_;
+    my $cv = AnyEvent->condvar;
+
+    tcp_connect "unix/", $self->{path}, sub {
+        my ($fh) = @_;
+
+        return $cv->send(0) unless $fh;
+
+        $self->{ipchdl} = AnyEvent::Handle->new(
+            fh => $fh,
+            on_read => sub { my ($hdl) = @_; $self->_data_available($hdl) },
+            on_error => sub {
+                my ($hdl, $fatal, $msg) = @_;
+                delete $self->{ipchdl};
+                $hdl->destroy;
+
+                my $cb = $self->{callbacks};
+
+                # Trigger all one-time callbacks with undef
+                for my $type (keys %{$cb}) {
+                    next if ($type & $event_mask) == $event_mask;
+                    $cb->{$type}->();
+                    delete $cb->{$type};
+                }
+
+                # Trigger _error callback, if set
+                my $type = $events{_error};
+                return unless defined($cb->{$type});
+                $cb->{$type}->($msg);
+            }
+        );
+
+        $cv->send(1)
+    };
+
+    $cv
+}
+
+sub _data_available {
+    my ($self, $hdl) = @_;
+
+    $hdl->unshift_read(
+        chunk => length($magic) + 4 + 4,
+        sub {
+            my $header = $_[1];
+            # Unpack message length and read the payload
+            my ($len, $type) = unpack("LL", substr($header, length($magic)));
+            $hdl->unshift_read(
+                chunk => $len,
+                sub { $self->_handle_i3_message($type, $_[1]) }
+            );
+        }
+    );
+}
+
+sub _handle_i3_message {
+    my ($self, $type, $payload) = @_;
+
+    return unless defined($self->{callbacks}->{$type});
+
+    my $cb = $self->{callbacks}->{$type};
+    $cb->(decode_json $payload);
+
+    return if ($type & $event_mask) == $event_mask;
+
+    # If this was a one-time callback, we delete it
+    # (when connection is lost, all one-time callbacks get triggered)
+    delete $self->{callbacks}->{$type};
+}
+
+=head2 $i3->subscribe(\%callbacks)
+
+Subscribes to the given event types. This function awaits a hashref with the
+key being the name of the event and the value being a callback.
+
+    my %callbacks = (
+        workspace => sub { say "Workspaces changed" }
+    );
+
+    if ($i3->subscribe(\%callbacks)->recv->{success}) {
+        say "Successfully subscribed";
+    }
+
+The special callback with name C<_error> is called when the connection to i3
+is killed (because of a crash, exit or restart of i3 most likely). You can
+use it to print an appropriate message and exit cleanly or to try to reconnect.
+
+    my %callbacks = (
+        _error => sub {
+            my ($msg) = @_;
+            say "I am sorry. I am so sorry: $msg";
+            exit 1;
+        }
+    );
+
+    $i3->subscribe(\%callbacks)->recv;
+
+=cut
+sub subscribe {
+    my ($self, $callbacks) = @_;
+
+    # Register callbacks for each message type
+    for my $key (keys %{$callbacks}) {
+        my $type = $events{$key};
+        $self->{callbacks}->{$type} = $callbacks->{$key};
+    }
+
+    $self->message(TYPE_SUBSCRIBE, [ keys %{$callbacks} ])
+}
+
+=head2 $i3->message($type, $content)
+
+Sends a message of the specified C<type> to i3, possibly containing the data
+structure C<content> (or C<content>, encoded as utf8, if C<content> is a
+scalar), if specified.
+
+    my $reply = $i3->message(TYPE_COMMAND, "reload")->recv;
+    if ($reply->{success}) {
+        say "Configuration successfully reloaded";
+    }
+
+=cut
+sub message {
+    my ($self, $type, $content) = @_;
+
+    die "No message type specified" unless defined($type);
+
+    die "No connection to i3" unless defined($self->{ipchdl});
+
+    my $payload = "";
+    if ($content) {
+        if (not ref($content)) {
+            # Convert from Perl’s internal encoding to UTF8 octets
+            $payload = encode_utf8($content);
+        } else {
+            $payload = encode_json $content;
+        }
+    }
+    my $message = $magic . pack("LL", length($payload), $type) . $payload;
+    $self->{ipchdl}->push_write($message);
+
+    my $cv = AnyEvent->condvar;
+
+    # We don’t preserve the old callback as it makes no sense to
+    # have a callback on message reply types (only on events)
+    $self->{callbacks}->{$type} =
+        sub {
+            my ($reply) = @_;
+            $cv->send($reply);
+            undef $self->{callbacks}->{$type};
+        };
+
+    $cv
+}
+
+=head1 SUGAR METHODS
+
+These methods intend to make your scripts as beautiful as possible. All of
+them automatically establish a connection to i3 blockingly (if it does not
+already exist).
+
+=cut
+
+sub _ensure_connection {
+    my ($self) = @_;
+
+    return if defined($self->{ipchdl});
+
+    $self->connect->recv or die "Unable to connect to i3 (socket path " . $self->{path} . ")";
+}
+
+=head2 get_workspaces
+
+Gets the current workspaces from i3.
+
+    my $ws = i3->get_workspaces->recv;
+    say Dumper($ws);
+
+=cut
+sub get_workspaces {
+    my ($self) = @_;
+
+    $self->_ensure_connection;
+
+    $self->message(TYPE_GET_WORKSPACES)
+}
+
+=head2 get_outputs
+
+Gets the current outputs from i3.
+
+    my $outs = i3->get_outputs->recv;
+    say Dumper($outs);
+
+=cut
+sub get_outputs {
+    my ($self) = @_;
+
+    $self->_ensure_connection;
+
+    $self->message(TYPE_GET_OUTPUTS)
+}
+
+=head2 get_tree
+
+Gets the layout tree from i3 (>= v4.0).
+
+    my $tree = i3->get_tree->recv;
+    say Dumper($tree);
+
+=cut
+sub get_tree {
+    my ($self) = @_;
+
+    $self->_ensure_connection;
+
+    $self->message(TYPE_GET_TREE)
+}
+
+=head2 get_marks
+
+Gets all the window identifier marks from i3 (>= v4.1).
+
+    my $marks = i3->get_marks->recv;
+    say Dumper($marks);
+
+=cut
+sub get_marks {
+    my ($self) = @_;
+
+    $self->_ensure_connection;
+
+    $self->message(TYPE_GET_MARKS)
+}
+
+=head2 get_bar_config
+
+Gets the bar configuration for the specific bar id from i3 (>= v4.1).
+
+    my $config = i3->get_bar_config($id)->recv;
+    say Dumper($config);
+
+=cut
+sub get_bar_config {
+    my ($self, $id) = @_;
+
+    $self->_ensure_connection;
+
+    $self->message(TYPE_GET_BAR_CONFIG, $id)
+}
+
+=head2 get_version
+
+Gets the i3 version via IPC, with a fall-back that parses the output of i3
+--version (for i3 < v4.3).
+
+    my $version = i3->get_version()->recv;
+    say "major: " . $version->{major} . ", minor = " . $version->{minor};
+
+=cut
+sub get_version {
+    my ($self) = @_;
+
+    $self->_ensure_connection;
+
+    my $cv = AnyEvent->condvar;
+
+    my $version_cv = $self->message(TYPE_GET_VERSION);
+    my $timeout;
+    $timeout = AnyEvent->timer(
+        after => 1,
+        cb => sub {
+            warn "Falling back to i3 --version since the running i3 doesn’t support GET_VERSION yet.";
+            my $version = _call_i3('--version');
+            $version =~ s/^i3 version //;
+            my $patch = 0;
+            my ($major, $minor) = ($version =~ /^([0-9]+)\.([0-9]+)/);
+            if ($version =~ /^[0-9]+\.[0-9]+\.([0-9]+)/) {
+                $patch = $1;
+            }
+            # Strip everything from the © sign on.
+            $version =~ s/ ©.*$//g;
+            $cv->send({
+                major => int($major),
+                minor => int($minor),
+                patch => int($patch),
+                human_readable => $version,
+            });
+            undef $timeout;
+        },
+    );
+    $version_cv->cb(sub {
+        undef $timeout;
+        $cv->send($version_cv->recv);
+    });
+
+    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
+
+    my $reply = i3->command("reload")->recv;
+    die "command failed" unless $reply->{success};
+
+=cut
+sub command {
+    my ($self, $content) = @_;
+
+    $self->_ensure_connection;
+
+    $self->message(TYPE_COMMAND, $content)
+}
+
+=head1 AUTHOR
+
+Michael Stapelberg, C<< <michael at i3wm.org> >>
+
+=head1 BUGS
+
+Please report any bugs or feature requests to C<bug-anyevent-i3 at
+rt.cpan.org>, or through the web interface at
+L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=AnyEvent-I3>.  I will be
+notified, and then you'll automatically be notified of progress on your bug as
+I make changes.
+
+=head1 SUPPORT
+
+You can find documentation for this module with the perldoc command.
+
+    perldoc AnyEvent::I3
+
+You can also look for information at:
+
+=over 2
+
+=item * RT: CPAN's request tracker
+
+L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-I3>
+
+=item * The i3 window manager website
+
+L<http://i3wm.org>
+
+=back
+
+
+=head1 ACKNOWLEDGEMENTS
+
+
+=head1 LICENSE AND COPYRIGHT
+
+Copyright 2010-2012 Michael Stapelberg.
+
+This program is free software; you can redistribute it and/or modify it
+under the terms of either: the GNU General Public License as published
+by the Free Software Foundation; or the Artistic License.
+
+See http://dev.perl.org/licenses/ for more information.
+
+
+=cut
+
+1; # End of AnyEvent::I3
diff --git a/AnyEvent-I3/t/00-load.t b/AnyEvent-I3/t/00-load.t
new file mode 100644 (file)
index 0000000..4bf6151
--- /dev/null
@@ -0,0 +1,10 @@
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+    use_ok( 'AnyEvent::I3' ) || print "Bail out!
+";
+}
+
+diag( "Testing AnyEvent::I3 $AnyEvent::I3::VERSION, Perl $], $^X" );
diff --git a/AnyEvent-I3/t/01-workspaces.t b/AnyEvent-I3/t/01-workspaces.t
new file mode 100644 (file)
index 0000000..f3206d8
--- /dev/null
@@ -0,0 +1,29 @@
+#!perl -T
+# vim:ts=4:sw=4:expandtab
+
+use Test::More tests => 3;
+use AnyEvent::I3;
+use AnyEvent;
+
+my $i3 = i3();
+my $cv = AnyEvent->condvar;
+
+# Try to connect to i3
+$i3->connect->cb(sub { my ($v) = @_; $cv->send($v->recv) });
+
+# But cancel if we are not connected after 0.5 seconds
+my $t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0) });
+my $connected = $cv->recv;
+
+SKIP: {
+    skip 'No connection to i3', 3 unless $connected;
+
+    my $workspaces = $i3->message(1)->recv;
+    isa_ok($workspaces, 'ARRAY');
+
+    ok(@{$workspaces} > 0, 'More than zero workspaces found');
+
+    ok(defined(@{$workspaces}[0]->{num}), 'JSON deserialized');
+}
+
+diag( "Testing AnyEvent::I3 $AnyEvent::I3::VERSION, Perl $], $^X" );
diff --git a/AnyEvent-I3/t/02-sugar.t b/AnyEvent-I3/t/02-sugar.t
new file mode 100644 (file)
index 0000000..a3e2cc7
--- /dev/null
@@ -0,0 +1,29 @@
+#!perl -T
+# vim:ts=4:sw=4:expandtab
+
+use Test::More tests => 3;
+use AnyEvent::I3;
+use AnyEvent;
+
+my $i3 = i3();
+my $cv = AnyEvent->condvar;
+
+# Try to connect to i3
+$i3->connect->cb(sub { my ($v) = @_; $cv->send($v->recv) });
+
+# But cancel if we are not connected after 0.5 seconds
+my $t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0) });
+my $connected = $cv->recv;
+
+SKIP: {
+    skip 'No connection to i3', 3 unless $connected;
+
+    my $workspaces = i3->get_workspaces->recv;
+    isa_ok($workspaces, 'ARRAY');
+
+    ok(@{$workspaces} > 0, 'More than zero workspaces found');
+
+    ok(defined(@{$workspaces}[0]->{num}), 'JSON deserialized');
+}
+
+diag( "Testing AnyEvent::I3 $AnyEvent::I3::VERSION, Perl $], $^X" );
diff --git a/AnyEvent-I3/t/boilerplate.t b/AnyEvent-I3/t/boilerplate.t
new file mode 100644 (file)
index 0000000..effb65b
--- /dev/null
@@ -0,0 +1,55 @@
+#!perl -T
+
+use strict;
+use warnings;
+use Test::More tests => 3;
+
+sub not_in_file_ok {
+    my ($filename, %regex) = @_;
+    open( my $fh, '<', $filename )
+        or die "couldn't open $filename for reading: $!";
+
+    my %violated;
+
+    while (my $line = <$fh>) {
+        while (my ($desc, $regex) = each %regex) {
+            if ($line =~ $regex) {
+                push @{$violated{$desc}||=[]}, $.;
+            }
+        }
+    }
+
+    if (%violated) {
+        fail("$filename contains boilerplate text");
+        diag "$_ appears on lines @{$violated{$_}}" for keys %violated;
+    } else {
+        pass("$filename contains no boilerplate text");
+    }
+}
+
+sub module_boilerplate_ok {
+    my ($module) = @_;
+    not_in_file_ok($module =>
+        'the great new $MODULENAME'   => qr/ - The great new /,
+        'boilerplate description'     => qr/Quick summary of what the module/,
+        'stub function definition'    => qr/function[12]/,
+    );
+}
+
+TODO: {
+  local $TODO = "Need to replace the boilerplate text";
+
+  not_in_file_ok(README =>
+    "The README is used..."       => qr/The README is used/,
+    "'version information here'"  => qr/to provide version information/,
+  );
+
+  not_in_file_ok(Changes =>
+    "placeholder date/time"       => qr(Date/time)
+  );
+
+  module_boilerplate_ok('lib/AnyEvent/I3.pm');
+
+
+}
+
diff --git a/AnyEvent-I3/t/manifest.t b/AnyEvent-I3/t/manifest.t
new file mode 100644 (file)
index 0000000..45eb83f
--- /dev/null
@@ -0,0 +1,13 @@
+#!perl -T
+
+use strict;
+use warnings;
+use Test::More;
+
+unless ( $ENV{RELEASE_TESTING} ) {
+    plan( skip_all => "Author tests not required for installation" );
+}
+
+eval "use Test::CheckManifest 0.9";
+plan skip_all => "Test::CheckManifest 0.9 required" if $@;
+ok_manifest();
diff --git a/AnyEvent-I3/t/pod-coverage.t b/AnyEvent-I3/t/pod-coverage.t
new file mode 100644 (file)
index 0000000..fc40a57
--- /dev/null
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+use Test::More;
+
+# Ensure a recent version of Test::Pod::Coverage
+my $min_tpc = 1.08;
+eval "use Test::Pod::Coverage $min_tpc";
+plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage"
+    if $@;
+
+# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version,
+# but older versions don't recognize some common documentation styles
+my $min_pc = 0.18;
+eval "use Pod::Coverage $min_pc";
+plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage"
+    if $@;
+
+all_pod_coverage_ok();
diff --git a/AnyEvent-I3/t/pod.t b/AnyEvent-I3/t/pod.t
new file mode 100644 (file)
index 0000000..ee8b18a
--- /dev/null
@@ -0,0 +1,12 @@
+#!perl -T
+
+use strict;
+use warnings;
+use Test::More;
+
+# Ensure a recent version of Test::Pod
+my $min_tp = 1.22;
+eval "use Test::Pod $min_tp";
+plan skip_all => "Test::Pod $min_tp required for testing POD" if $@;
+
+all_pod_files_ok();
index d4cfa42ae4df38b6478e1666c72fb451bd333ea7..af1432e3d06e5aaf78f98be22c9fab9f2329855e 100644 (file)
@@ -1 +1 @@
-4.13-non-git
+4.14-non-git
index c90e26c71ed2d83d3c205b6094a2d8662f57f847..3ea300e5a71f0b1efa752e95aed59455d68ebef9 100644 (file)
@@ -45,11 +45,17 @@ dist_xsessions_DATA = \
 
 noinst_LIBRARIES = libi3.a
 
-check_PROGRAMS = test.commands_parser test.config_parser
+check_PROGRAMS = \
+       test.commands_parser \
+       test.config_parser \
+       test.inject_randr15
 
 check_SCRIPTS = \
        testcases/complete-run.pl
 
+check_DATA = \
+       anyevent-i3.stamp
+
 clean-check:
        rm -rf testsuite-* latest i3-cfg-for-* _Inline
 clean-local: clean-check
@@ -94,7 +100,7 @@ EXTRA_DIST = \
        I3_VERSION \
        LICENSE \
        PACKAGE-MAINTAINER \
-       RELEASE-NOTES-4.13 \
+       RELEASE-NOTES-4.14 \
        generate-command-parser.pl \
        parser-specs/commands.spec \
        parser-specs/config.spec \
@@ -212,6 +218,7 @@ asciidoc_MANS =
 endif
 
 AM_CPPFLAGS = \
+       -DSYSCONFDIR="\"$(sysconfdir)\"" \
        -I$(top_builddir)/parser \
        -I$(top_srcdir)/include \
        @AX_EXTEND_SRCDIR_CPPFLAGS@
@@ -401,6 +408,19 @@ i3_config_wizard_i3_config_wizard_SOURCES = \
        i3-config-wizard/main.c \
        i3-config-wizard/xcb.h
 
+test_inject_randr15_CPPFLAGS = \
+       $(AM_CPPFLAGS)
+
+test_inject_randr15_CFLAGS = \
+       $(AM_CFLAGS) \
+       $(i3_CFLAGS)
+
+test_inject_randr15_SOURCES = \
+       testcases/inject_randr1.5.c
+
+test_inject_randr15_LDADD = \
+       $(i3_LDADD)
+
 test_commands_parser_CPPFLAGS = \
        $(AM_CPPFLAGS) \
        -DTEST_PARSER
@@ -457,7 +477,6 @@ i3_SOURCES = \
        include/config_parser.h \
        include/con.h \
        include/data.h \
-       include/debug.h \
        include/display_version.h \
        include/ewmh.h \
        include/fake_outputs.h \
@@ -502,7 +521,6 @@ i3_SOURCES = \
        src/config.c \
        src/config_directives.c \
        src/config_parser.c \
-       src/debug.c \
        src/display_version.c \
        src/ewmh.c \
        src/fake_outputs.c \
@@ -558,6 +576,15 @@ i3-config-parser.stamp: parser/$(dirstamp) generate-command-parser.pl parser-spe
        $(AM_V_at) mv GENERATED_config_* $(top_builddir)/parser
        $(AM_V_at) touch $@
 
+################################################################################
+# AnyEvent-I3 build process
+################################################################################
+
+anyevent-i3.stamp: AnyEvent-I3/lib/AnyEvent/I3.pm
+       $(AM_V_BUILD) (cd $(top_srcdir)/AnyEvent-I3 && perl Makefile.PL && make)
+       $(AM_V_at) touch $@
+
 CLEANFILES = \
        i3-command-parser.stamp \
-       i3-config-parser.stamp
+       i3-config-parser.stamp \
+       anyevent-i3.stamp
diff --git a/RELEASE-NOTES-4.13 b/RELEASE-NOTES-4.13
deleted file mode 100644 (file)
index 0e85456..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-
- ┌────────────────────────────┐
- │ Release notes for i3 v4.13 │
- └────────────────────────────┘
-
-This is i3 v4.13. This version is considered stable. All users of i3 are
-strongly encouraged to upgrade.
-
-For users, there are two changes to be aware of:
-
-1. The X server DPI is read from the Xft.dpi X resource (if available).
-   Previously, i3 used to directly look at the X server’s DPI (based on screen
-   resolution and physical size). Looking at Xft.dpi is more consistent with
-   other software, more likely to be correct (because it’s user-specified and
-   not read from possibly broken hardware information) and allows users to
-   override the value.
-
-2. It is now possible to set config file variables from X resources using the
-   “set_from_resource” directive. This allows users to have a single source of
-   truth for e.g. theming X11 applications (specify “*color0: #121212” and have
-   it apply to URxvt and your i3 config).
-
-For packagers, there are three changes that likely require action:
-
-1. cairo/pango are now required dependencies, as announced in the i3 v4.12
-   release notes.
-
-2. The aforementioned “set_from_resource” feature requires the new dependency
-   libxcb-util-xrm.
-
-3. i3 now uses the GNU build system (autotools). Please see
-   https://github.com/i3/i3/commit/4a52a7e9fb6fb2e1f0256b2e086cfa313f411cd8 for
-   a lot more details about the rationale and what this means for your package.
-   Bottomline, things should get simpler for you, though :).
-
- ┌────────────────────────────┐
- │ Changes in i3 v4.13        │
- └────────────────────────────┘
-
-  • build: wire up version handling for non-release tarballs (as opposed to git
-    checkouts)
-  • build: switch to the GNU build system
-  • i3bar: disable pango markup for plain-text input
-  • man/i3-msg: point out default ipc message type
-  • config: introduce support for specifying variables from X resources
-  • config: ensure variables match on longest-length, eliminating problems
-    where one variable was a prefix of another
-  • config: do not count '\' in comment lines as line continuation
-  • ipc: introduce a new GET_BINDING_MODES command
-  • ipc: implement new window::mark event
-  • ipc: add “output” to IPC events referencing a container
-  • make fullscreen windows open on the output which is indicated by their
-    geometry (fixes LibreOffice Impress multi-monitor presentations)
-  • focus newly managed windows only if they don’t use the globally active
-    input mode (fixes issues with RubyMine)
-  • remove title indentation in nested containers (rationale was unclear,
-    nobody spoke up when we asked about the feature on i3-discuss)
-  • use the last known timestamp when calling xcb_set_input_focus (might fix
-    rare race conditions in focus handling)
-  • introduce the “smart” option for hide_edge_borders, which will hide borders
-    when there is precisely one window on the workspace
-  • handle _MOTIF_WM_HINTS changes (_MOTIF_WM_HINTS were previously only
-    considered when managing a new window)
-  • don’t change border style if BS_NORMAL is requested in _MOTIF_WM_HINTS
-  • only add numlock fallback for keybindings where necessary (allows users to
-    correctly bind keys on the numpad)
-  • do not match docks in config and command criteria
-  • get DPI from the Xft.dpi resource instead of directly looking at the screen
-    resolution/size
-  • handle _NET_ACTIVE_WINDOW for scratchpad windows (for pagers)
-  • set _NET_WM_DESKTOP to sticky for scratchpad windows
-  • add new criteria “tiling” and “floating”
-  • implement special output name “current” for commands
-  • handle ResizeRequests for tray clients (fixes VLC tray icon)
-
- ┌────────────────────────────┐
- │ Bugfixes                   │
- └────────────────────────────┘
-
-  • i3bar: fix crash when the I3SOCK environment variable is present
-  • i3-dmenu-desktop: do not die on failed open
-  • i3-input: properly position in non-standard cases (fixes an issue where
-    i3-input would launch off-screen)
-  • i3-save-tree: rename “mark” to “marks” to reflect our recent change to
-    allow multiple marks
-  • mouse bindings: only grab the mouse buttons that need to be grabbed
-  • no_focus: correctly count the number of windows (makes no_focus work with
-    tabbed/stacked workspace layouts).
-  • properly close disabled outputs restored during a restart (this fixes state
-    handling when RandR changes happen during i3 restarts)
-  • don’t trigger bindings on window border clicks unless --border was
-    specified for the binding
-  • traverse numbered workspaces in correct order
-  • fix transition from named to numbered workspaces in “workspace next|prev”
-  • avoid setting urgency hint on content containers and above (fixes crashes)
-  • don’t trigger unrelated key bindings for --release bindings
-  • fix colormap handling for containers (fixes taking screenshots using xwd)
-  • check output crossing on ENTER_NOTIFY to dockarea (fixes pointer jumping)
-  • fix a use-after-free bug (fixes “floating enable” on single split windows)
-
- ┌────────────────────────────┐
- │ Thanks!                    │
- └────────────────────────────┘
-
-Thanks for testing, bugfixes, discussions and everything I forgot go out to:
-
-  Benedikt Heine, Cedric Buissart, Chih-Chyuan Hwang, Denton Liu, eplanet, Eric
-  Engeström, EvilPudding, Ferdinand Bachmann, Hong, Ingo Bürk, Jakob Schnell,
-  Jakub Wilk, johannes karoff, Johannes Lange, joshrosso, Julien Lequertier,
-  Kacper Kowalik, Kenneth Lyons, Kyle Kneitinger, madroach, Michael Vetter,
-  Nathan Schulte, Øsse, Peder Stray, Tony Crisci, Trevor Merrifield, wentasah,
-  yshui, Zamarin Arthur
-
--- Michael Stapelberg, 2016-11-08
diff --git a/RELEASE-NOTES-4.14 b/RELEASE-NOTES-4.14
new file mode 100644 (file)
index 0000000..2ab058f
--- /dev/null
@@ -0,0 +1,94 @@
+
+ ┌────────────────────────────┐
+ │ Release notes for i3 v4.14 │
+ └────────────────────────────┘
+
+This is i3 v4.14. This version is considered stable. All users of i3 are
+strongly encouraged to upgrade.
+
+Aside from many bug and documentation fixes, the “swap” command is a notable
+addition of this release. As is almost tradition at this point, keybinding
+handling has seen some fixes as well. A noticeable change for users with such
+monitors is i3’s support for RandR 1.5, which transparently supports the TILE
+property of first-gen 4K monitors and current 5K or 8K monitors.
+
+ ┌────────────────────────────┐
+ │ Changes in i3 v4.14        │
+ └────────────────────────────┘
+
+  • build: link libiconv explicitly for systems which need it
+  • build: move AnyEvent-I3 into the i3 repository
+  • docs/hacking-howto: add compilation instructions
+  • docs/ipc: add missing cases to the workspace event
+  • docs/ipc: document the “primary” field of the OUTPUTS reply
+  • docs/ipc: replace Go IPC library with a maintained one
+  • docs/ipc: add link to the ocaml-i3ipc library
+  • docs/ipc: fix invalid trailing commas in JSON examples
+  • docs/layout-saving: add section about troubleshooting window titles
+  • docs/testsuite: update for the move to autotools
+  • docs/userguide: clarify the move command syntax
+  • docs/userguide: correct “Esc” to “Escape”
+  • docs/userguide: clarify focus_follows_mouse behavior
+  • docs/userguide: expand on combining “workspace number” with a name
+  • docs/userguide: mention the magic v4 config marker
+  • man/i3.man: correct configuration lookup order
+  • i3bar, i3-config-wizard, i3-nagbar: use the Xft.dpi setting (see 4.13 notes)
+  • i3bar: restart bar status command on reload if it changed
+  • i3bar: treat left/right scrolling like up/down scrolling
+  • i3bar: accept “primary” in the “output” configuration directive
+  • i3-input: do not set input focus, grabbing the keyboard suffices
+  • i3-msg: return an exit code when missing the -t argument
+  • i3-sensible-editor: correct “mc-edit” to “mcedit”
+  • i3-sensible-terminal: add lilyterm, tilix, terminix, konsole
+  • respect SYSCONFDIR when looking for the default xdg directory
+  • use RandR 1.5 to query screens, supporting the TILE property commonly used
+    by multi-stream transport (MST) monitors, such as first-gen 4K monitors, or
+    current 5K and 8K monitors
+  • respect minimum size hints for floating windows
+  • support the _NET_MOVERESIZE_WINDOW client message (for e.g. wmctrl)
+  • validate binding modes are not defined more than once
+  • only react to the last ExposeEvent in a series of events
+  • add the shutdown IPC event (upon “restart” or “exit”)
+  • treat left/right scrolling like up/down scrolling (on window titles)
+  • make the “layout toggle” command optionally take a sequence of layouts
+  • introduce --exclude-titlebar flag for mouse bindings
+  • introduce the “swap” command
+  • support the primary output in the “focus” and “move” commands
+  • compare keybinding modifiers for equality, not subset
+  • introduce the GET_CONFIG ipc request (i3-msg -t get_config)
+  • start i3-nagbar when encountering invalid set statements
+  • focus windows upon ConfigureWindow requests with stack-mode=Above
+
+ ┌────────────────────────────┐
+ │ Bugfixes                   │
+ └────────────────────────────┘
+
+  • i3bar: correct the color codes used for statusline errors
+  • i3bar: avoid freeze after VisibilityNotify
+  • i3-dmenu-desktop: fix quoted command names
+  • i3-dmenu-desktop: avoid adding items multiple times
+  • fix various X11 resource leaks, memory leaks and memory errors
+  • fix IPC success reply for the workspace command
+  • report errors during logfile creation
+  • fix the signal handler being blank
+  • display marks and the title even if the title is empty (for title_format)
+  • fix changing workspace layout from stacked/tabbed for empty workspaces
+  • add numlock fallback to “bindcode” where necessary
+  • fix a crash on restart when using marks
+  • fix renaming workspaces when the new name starts with “to”
+  • respect dont_warp flag when moving containers
+
+ ┌────────────────────────────┐
+ │ Thanks!                    │
+ └────────────────────────────┘
+
+Thanks for testing, bugfixes, discussions and everything I forgot go out to:
+
+  akash akya, Armaël Guéneau, Baptiste Daroussin, Chih-Chyuan Hwang, cresh,
+  David Jimenez Sequero, Franz König, fred777, Ingo Bürk, Jakub Wilk,
+  Jens-Wolfhard Schicke-Uffmann, Johannes Lange, lasers, lebenlechzer,
+  loungecube, Maarten Dirkse, Manuel Mendez, Max Fisher, Mihai Coman, Nathan
+  Schulte, s3rb31, Sebastian Larsson, Stefan Hagen, Tobias Hänel, Tony Crisci,
+  Trevor Merrifield, Zbyněk Moravec
+
+-- Michael Stapelberg, 2017-09-04
index 1a91b3b1d35202e1bdf10e72b9036505c44e16a1..7d274e36b209e4fe7dc677c2f6707a075f30d595 100644 (file)
@@ -2,7 +2,7 @@
 # Run autoreconf -fi to generate a configure script from this file.
 
 AC_PREREQ([2.69])
-AC_INIT([i3], [4.13], [https://github.com/i3/i3/issues])
+AC_INIT([i3], [4.14], [https://github.com/i3/i3/issues])
 # For AX_EXTEND_SRCDIR
 AX_ENABLE_BUILDDIR
 AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2])
@@ -15,6 +15,12 @@ AC_CONFIG_SRCDIR([libi3/ipc_recv_message.c])
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_MACRO_DIR([m4])
 
+dnl Verify macros defined in m4/ such as AX_SANITIZERS are not present in the
+dnl output, i.e. are replaced as expected. This line results in a better error
+dnl message when using aclocal < 1.13 (which does not understand
+dnl AC_CONFIG_MACRO_DIR) without passing the -I m4 parameter.
+m4_pattern_forbid([AX_SANITIZERS])
+
 # Verify we are using GNU make because we use '%'-style pattern rules in
 # Makefile.am, which are a GNU make extension. Pull requests to replace
 # '%'-style pattern rules with a more portable alternative are welcome.
@@ -25,8 +31,8 @@ AX_EXTEND_SRCDIR
 
 AS_IF([test -d ${srcdir}/.git],
       [
-        VERSION="$(git describe --tags --abbrev=0)"
-        I3_VERSION="$(git describe --tags --always) ($(git log --pretty=format:%cd --date=short -n1), branch \\\"$(git describe --tags --always --all | sed s:heads/::)\\\")"
+        VERSION="$(git -C ${srcdir} describe --tags --abbrev=0)"
+        I3_VERSION="$(git -C ${srcdir} describe --tags --always) ($(git -C ${srcdir} log --pretty=format:%cd --date=short -n1), branch \\\"$(git -C ${srcdir} describe --tags --always --all | sed s:heads/::)\\\")"
         # Mirrors what libi3/is_debug_build.c does:
         is_release=$(test $(echo "${I3_VERSION}" | cut -d '(' -f 1 | wc -m) -lt 10 && echo yes || echo no)
       ],
@@ -53,8 +59,6 @@ AX_CHECK_ENABLE_DEBUG([yes], , [UNUSED_NDEBUG], [$is_release])
 
 AC_PROG_CC_C99
 
-AC_DEFINE_UNQUOTED(SYSCONFDIR, "`eval echo $sysconfdir`", [Location of system configuration files])
-
 # For strnlen() and vasprintf().
 AC_USE_SYSTEM_EXTENSIONS
 
@@ -79,6 +83,8 @@ AC_SEARCH_LIBS([ev_run], [ev], , [AC_MSG_FAILURE([cannot find the required ev_ru
 
 AC_SEARCH_LIBS([shm_open], [rt])
 
+AC_SEARCH_LIBS([iconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])])
+
 AX_PTHREAD
 
 dnl Each prefix corresponds to a source tarball which users might have
@@ -146,8 +152,9 @@ else
        print_BUILD_MANS=no
 fi
 
-git_dir=`git rev-parse --git-dir 2>/dev/null`
-if test -n "$git_dir"; then
+in_git_worktree=`git rev-parse --is-inside-work-tree 2>/dev/null`
+if [[ "$in_git_worktree" = "true" ]]; then
+       git_dir=`git rev-parse --git-dir 2>/dev/null`
        srcdir=`dirname "$git_dir"`
        exclude_dir=`pwd | sed "s,^$srcdir,,g"`
        if ! grep -q "^$exclude_dir" "$git_dir/info/exclude"; then
index bb08c7fd4e89044bd693be9fe43ad3c3fc41f6c2..9333c6655594c456553ddbde5143c637e825091d 100644 (file)
@@ -1,3 +1,9 @@
+i3-wm (4.13.1-1) unstable; urgency=medium
+
+  * New upstream release.
+
+ -- Michael Stapelberg <stapelberg@debian.org>  Tue, 08 Nov 2016 21:31:13 +0100
+
 i3-wm (4.13-1) unstable; urgency=medium
 
   * New upstream release.
index 74a690e7c1ef65d323d5a5f0b03338b3c7b60c79..52436da67418adf62578d1637e5752a09bf96f25 100644 (file)
@@ -119,9 +119,6 @@ src/config.c::
 Contains all functions handling the configuration file (calling the parser
 src/config_parser.c) with the correct path, switching key bindings mode).
 
-src/debug.c::
-Contains debugging functions to print unhandled X events.
-
 src/ewmh.c::
 Functions to get/set certain EWMH properties easily.
 
@@ -993,6 +990,47 @@ New features are only found in the “next” branch. Therefore, if you are work
 on a new feature, use the “next” branch. If you are working on a bugfix, use the
 “next” branch, too, but make sure your code also works on “master”.
 
+=== How to build?
+
+You can build i3 like you build any other software package which uses autotools.
+Here’s a memory refresher:
+
+    $ autoreconf -fi
+    $ mkdir -p build && cd build
+    $ ../configure
+    $ make -j8
+
+(The autoreconf -fi step is unnecessary if you are building from a release tarball,
+ but shouldn’t hurt either.)
+
+==== Build system features
+
+* We use the AX_ENABLE_BUILDDIR macro to enforce builds happening in a separate
+  directory. This is a prerequisite for the AX_EXTEND_SRCDIR macro and building
+  in a separate directory is common practice anyway. In case this causes any
+  trouble when packaging i3 for your distribution, please open an issue.
+
+* “make check” runs the i3 testsuite. See docs/testsuite for details.
+
+* “make distcheck” (runs testsuite on “make dist” result, tiny bit quicker
+  feedback cycle than waiting for the travis build to catch the issue).
+
+* “make uninstall” (occasionally requested by users who compile from source)
+
+* “make” will build manpages/docs by default if the tools are installed.
+  Conversely, manpages/docs are not tried to be built for users who don’t want
+  to install all these dependencies to get started hacking on i3.
+
+* non-release builds will enable address sanitizer by default. Use the
+  --disable-sanitizers configure option to turn off all sanitizers, and see
+  --help for available sanitizers.
+
+* Support for pre-compiled headers (PCH) has been dropped for now in the
+  interest of simplicity. If you need support for PCH, please open an issue.
+
+* Coverage reports are now generated using “make check-code-coverage”, which
+  requires specifying --enable-code-coverage when calling configure.
+
 == Thought experiments
 
 In this section, we collect thought experiments, so that we don’t forget our
index fda289a0a21c2776d30c29a7c4207c7b6484b3bb..6572357704a92064a0b33cb7c830f22f10623e5d 100644 (file)
--- a/docs/ipc
+++ b/docs/ipc
@@ -232,6 +232,8 @@ name (string)::
        The name of this output (as seen in +xrandr(1)+). Encoded in UTF-8.
 active (boolean)::
        Whether this output is currently active (has a valid mode).
+primary (boolean)::
+       Whether this output is currently the primary output.
 current_workspace (string)::
        The name of the current workspace that is visible on this output. +null+ if
        the output is not active.
@@ -262,7 +264,7 @@ rect (map)::
    "y": 0,
    "width": 1280,
    "height": 1024
-  },
+  }
  }
 ]
 -------------------
@@ -390,7 +392,7 @@ JSON dump:
         "y": 0,
         "width": 1280,
         "height": 0
-       },
+       }
       },
 
       {
@@ -671,6 +673,8 @@ barconfig_update (4)::
 binding (5)::
        Sent when a configured command binding is triggered with the keyboard or
        mouse
+shutdown (6)::
+       Sent when the ipc shuts down because of a restart or exit by user command
 
 *Example:*
 --------------------------------------------------------------------
@@ -694,9 +698,9 @@ if ($is_event) {
 
 This event consists of a single serialized map containing a property
 +change (string)+ which indicates the type of the change ("focus", "init",
-"empty", "urgent"). A +current (object)+ property will be present with the
-affected workspace whenever the type of event affects a workspace (otherwise,
-it will be +null).
+"empty", "urgent", "reload", "rename", "restored", "move"). A
++current (object)+ property will be present with the affected workspace
+whenever the type of event affects a workspace (otherwise, it will be +null).
 
 When the change is "focus", an +old (object)+ property will be present with the
 previous workspace.  When the first switch occurs (when i3 focuses the
@@ -791,7 +795,7 @@ same as a +GET_BAR_CONFIG+ reply for the bar with the given id.
 === binding event
 
 This event consists of a single serialized map reporting on the details of a
-binding that ran a command because of user input. The +change (sring)+ field
+binding that ran a command because of user input. The +change (string)+ field
 indicates what sort of binding event was triggered (right now it will always be
 +"run"+ but may be expanded in the future).
 
@@ -829,6 +833,20 @@ input_type (string)::
 }
 ---------------------------
 
+=== shutdown event
+
+This event is triggered when the connection to the ipc is about to shutdown
+because of a user action such as a +restart+ or +exit+ command. The +change
+(string)+ field indicates why the ipc is shutting down. It can be either
++"restart"+ or +"exit"+.
+
+*Example:*
+---------------------------
+{
+ "change": "restart"
+}
+---------------------------
+
 == See also (existing libraries)
 
 [[libraries]]
@@ -843,7 +861,7 @@ C::
 C++::
        * https://github.com/drmgc/i3ipcpp
 Go::
-       * https://github.com/proxypoke/i3ipc
+       * https://github.com/mdirkse/i3ipc-go
 JavaScript::
        * https://github.com/acrisci/i3ipc-gjs
 Lua::
@@ -859,3 +877,5 @@ Ruby::
        * https://github.com/badboy/i3-ipc (not maintained)
 Rust::
        * https://github.com/tmerr/i3ipc-rs
+OCaml::
+       * https://github.com/Armael/ocaml-i3ipc
index 5897036e5143f310e2b665e68db6174f7318198b..6ca08fa239dbe7bd38ac2221e987c153cf3162c0 100644 (file)
@@ -259,3 +259,27 @@ container:
     ]
 }
 --------------------------------------------------------------------------------
+
+=== Placeholders using window title matches don't swallow the window
+
+If you use the +title+ attribute to match a window and find that it doesn't
+work or only works sometimes, the reason might be that the application sets the
+title only after making the window visible. This will be especially true for
+programs running inside terminal emulators, e.g., +urxvt -e irssi+ when
+matching on +title: "irssi"+.
+
+One way to deal with this is to not rely on the title, but instead use, e.g.,
+the +instance+ attribute and running the program to set this window instance to
+that value:
+
+--------------------------------------------------------------------------------
+# Run irssi via
+# urxvt -name "irssi-container" -e irssi
+
+"swallows": [
+    {
+        "class": "URxvt",
+        "instance": "irssi-container"
+    }
+]
+--------------------------------------------------------------------------------
index 71c6a427862fb4b4a46c32f093923a25e21c2c63..795be0424fa477cf50d1e97b484881f37058d3c9 100644 (file)
@@ -82,6 +82,8 @@ The tests additionally require +Xephyr(1)+ to run a nested X server. Install
 $ cd ~/i3/testcases
 $ sudo apt-get install cpanminus
 $ sudo cpanm .
+$ cd ~/i3/AnyEvent-I3
+$ sudo cpanm .
 --------------------------------------------------------------------------------
 
 If you don’t want to use cpanminus for some reason, the same works with cpan:
@@ -90,6 +92,8 @@ If you don’t want to use cpanminus for some reason, the same works with cpan:
 --------------------------------------------------------------------------------
 $ cd ~/i3/testcases
 $ sudo cpan .
+$ cd ~/i3/AnyEvent-I3
+$ sudo cpan .
 --------------------------------------------------------------------------------
 
 In case you don’t have root permissions, you can also install into your home
@@ -112,9 +116,20 @@ the tests without an X session with Xvfb, such as with +xvfb-run
 ./complete-run+. This will also speed up the tests significantly especially on
 machines without a powerful video card.
 
-.Example invocation of complete-run.pl+
+.Example invocation of +complete-run.pl+
 ---------------------------------------
-$ cd ~/i3/testcases
+$ cd ~/i3
+
+$ autoreconf -fi
+
+$ mkdir -p build && cd build
+
+$ ../configure
+
+$ make -j8
+# output omitted because it is very long
+
+$ cd testcases
 
 $ ./complete-run.pl
 # output omitted because it is very long
@@ -160,6 +175,41 @@ $ ./complete-run.pl --parallel=1 --keep-xserver-output
 This will show the output of Xephyr, which is the X server implementation we
 use for testing.
 
+===== make command: +make check+
+Make check runs the i3 testsuite.
+You can still use ./testcases/complete-run.pl to get the interactive progress output.
+
+.Example invocation of +make check+
+---------------------------------------
+$ cd ~/i3
+
+$ autoreconf -fi
+
+$ mkdir -p build && cd build
+
+$ ../configure
+
+$ make -j8
+# output omitted because it is very long
+
+$ make check
+# output omitted because it is very long
+PASS: testcases/complete-run.pl
+============================================================================
+Testsuite summary for i3 4.13
+============================================================================
+# TOTAL: 1
+# PASS:  1
+# SKIP:  0
+# XFAIL: 0
+# FAIL:  0
+# XPASS: 0
+# ERROR: 0
+============================================================================
+
+$ less test-suite.log
+----------------------------------------
+
 ==== Coverage testing
 
 Coverage testing is possible with +lcov+, the front-end for GCC's coverage
index acdc0a587e1834854fb6aebf532388e8cfde8d92..0d5de3b9056c86eff0e483124825d27192588e6e 100644 (file)
@@ -297,6 +297,15 @@ keyboard layout. To start the wizard, use the command +i3-config-wizard+.
 Please note that you must not have +~/.i3/config+, otherwise the wizard will
 exit.
 
+Since i3 4.0, a new configuration format is used. i3 will try to automatically
+detect the format version of a config file based on a few different keywords,
+but if you want to make sure that your config is read with the new format,
+include the following line in your config file:
+
+---------------------
+# i3 config file (v4)
+---------------------
+
 === Comments
 
 It is possible and recommended to use comments in your configuration file to
@@ -412,9 +421,9 @@ button in the scope of the clicked container (see <<command_criteria>>). You
 can configure mouse bindings in a similar way to key bindings.
 
 *Syntax*:
--------------------------------------------------------------------------------
-bindsym [--release] [--border] [--whole-window] [<Modifiers>+]button<n> command
--------------------------------------------------------------------------------
+----------------------------------------------------------------------------------------------------
+bindsym [--release] [--border] [--whole-window] [--exclude-titlebar] [<Modifiers>+]button<n> command
+----------------------------------------------------------------------------------------------------
 
 By default, the binding will only run when you click on the titlebar of the
 window. If the +--release+ flag is given, it will run when the mouse button
@@ -424,6 +433,9 @@ If the +--whole-window+ flag is given, the binding will also run when any part
 of the window is clicked, with the exception of the border. To have a bind run
 when the border is clicked, specify the +--border+ flag.
 
+If the +--exclude-titlebar+ flag is given, the titlebar will not be considered
+for the keybinding.
+
 *Examples*:
 --------------------------------
 # The middle button over a titlebar kills the window
@@ -479,7 +491,7 @@ mode <name>
 
 *Example*:
 ------------------------------------------------------------------------
-# Press $mod+o followed by either f, t, Esc or Return to launch firefox,
+# Press $mod+o followed by either f, t, Escape or Return to launch firefox,
 # thunderbird or return to the default mode, respectively.
 set $mode_launcher Launch: [f]irefox [t]hunderbird
 bindsym $mod+o mode "$mode_launcher"
@@ -488,7 +500,7 @@ mode "$mode_launcher" {
     bindsym f exec firefox
     bindsym t exec thunderbird
 
-    bindsym Esc mode "default"
+    bindsym Escape mode "default"
     bindsym Return mode "default"
 }
 ------------------------------------------------------------------------
@@ -946,12 +958,12 @@ the next section.
 
 === Focus follows mouse
 
-By default, window focus follows your mouse movements. However, if you have a
-setup where your mouse usually is in your way (like a touchpad on your laptop
-which you do not want to disable completely), you might want to disable 'focus
-follows mouse' and control focus only by using your keyboard.  The mouse will
-still be useful inside the currently active window (for example to click on
-links in your browser window).
+By default, window focus follows your mouse movements as the mouse crosses
+window borders. However, if you have a setup where your mouse usually is in your
+way (like a touchpad on your laptop which you do not want to disable
+completely), you might want to disable 'focus follows mouse' and control focus
+only by using your keyboard.  The mouse will still be useful inside the
+currently active window (for example to click on links in your browser window).
 
 *Syntax*:
 --------------------------
@@ -1127,9 +1139,9 @@ none::
 [[show_marks]]
 === Drawing marks on window decoration
 
-If activated, marks on windows are drawn in their window decoration. However,
-any mark starting with an underscore in its name (+_+) will not be drawn even if
-this option is activated.
+If activated, marks (see <<vim_like_marks>>) on windows are drawn in their window
+decoration. However, any mark starting with an underscore in its name (+_+) will
+not be drawn even if this option is activated.
 
 The default for this option is +yes+.
 
@@ -1378,7 +1390,7 @@ directive multiple times.
 
 *Syntax*:
 ---------------
-output <output>
+output primary|<output>
 ---------------
 
 *Example*:
@@ -1400,7 +1412,19 @@ bar {
         statusline #ffffff
     }
 }
+
+# show bar on the primary monitor and on HDMI2
+bar {
+    output primary
+    output HDMI2
+    status_command i3status
+}
+
 -------------------------------
+Note that you might not have a primary output configured yet. To do so, run:
+-------------------------
+xrandr --output <output> --primary
+-------------------------
 
 === Tray output
 
@@ -1802,7 +1826,8 @@ The +toggle+ option will toggle the orientation of the split container if it
 contains a single window. Otherwise it makes the current window a split
 container with opposite orientation compared to the parent container.
 Use +layout toggle split+ to change the layout of any split container from
-splitv to splith or vice-versa.
+splitv to splith or vice-versa. You can also define a custom sequence of layouts
+to cycle through with +layout toggle+, see <<manipulating_layout>>.
 
 *Syntax*:
 --------------------------------
@@ -1822,6 +1847,11 @@ Use +layout toggle split+, +layout stacking+, +layout tabbed+, +layout splitv+
 or +layout splith+ to change the current container layout to splith/splitv,
 stacking, tabbed layout, splitv or splith, respectively.
 
+Specify up to four layouts after +layout toggle+ to cycle through them. Every
+time the command is executed, the layout specified after the currently active
+one will be applied. If the currently active layout is not in the list, the
+first layout in the list will be activated.
+
 To make the current window (!) fullscreen, use +fullscreen enable+ (or
 +fullscreen enable global+ for the global mode), to leave either fullscreen
 mode use +fullscreen disable+, and to toggle between these two states use
@@ -1834,6 +1864,7 @@ enable+ respectively +floating disable+ (or +floating toggle+):
 --------------------------------------------
 layout default|tabbed|stacking|splitv|splith
 layout toggle [split|all]
+layout toggle [split|tabbed|stacking|splitv|splith] [split|tabbed|stacking|splitv|splith]…
 --------------------------------------------
 
 *Examples*:
@@ -1848,6 +1879,15 @@ bindsym $mod+x layout toggle
 # Toggle between stacking/tabbed/splith/splitv:
 bindsym $mod+x layout toggle all
 
+# Toggle between stacking/tabbed/splith:
+bindsym $mod+x layout toggle stacking tabbed splith
+
+# Toggle between splitv/tabbed
+bindsym $mod+x layout toggle splitv tabbed
+
+# Toggle between last split layout/tabbed/stacking
+bindsym $mod+x layout toggle split tabbed stacking
+
 # Toggle fullscreen
 bindsym $mod+f fullscreen toggle
 
@@ -1882,7 +1922,7 @@ output::
 ----------------------------------------------
 focus left|right|down|up
 focus parent|child|floating|tiling|mode_toggle
-focus output left|right|up|down|<output>
+focus output left|right|up|down|primary|<output>
 ----------------------------------------------
 
 *Examples*:
@@ -1904,8 +1944,17 @@ bindsym $mod+x focus output right
 
 # Focus the big output
 bindsym $mod+x focus output HDMI-2
+
+# Focus the primary output
+bindsym $mod+x focus output primary
 -------------------------------------------------
 
+-------------------------------
+Note that you might not have a primary output configured yet. To do so, run:
+-------------------------
+xrandr --output <output> --primary
+-------------------------
+
 === Moving containers
 
 Use the +move+ command to move a container.
@@ -1921,7 +1970,8 @@ move <left|right|down|up> [<px> px]
 # Moves the container either to a specific location
 # or to the center of the screen. If 'absolute' is
 # used, it is moved to the center of all outputs.
-move [absolute] position [[<px> px] [<px> px]|center]
+move [absolute] position <pos_x> [px] <pos_y> [px]
+move [absolute] position center
 
 # Moves the container to the current position of the
 # mouse cursor. Only affects floating containers.
@@ -1947,6 +1997,39 @@ bindsym $mod+c move absolute position center
 bindsym $mod+m move position mouse
 -------------------------------------------------------
 
+=== Swapping containers
+
+Two containers can be swapped (i.e., move to each other's position) by using
+the +swap+ command. They will assume the position and geometry of the container
+they are swapped with.
+
+The first container to participate in the swapping can be selected through the
+normal command criteria process with the focused window being the usual
+fallback if no criteria are specified. The second container can be selected
+using one of the following methods:
+
++id+:: The X11 window ID of a client window.
++con_id+:: The i3 container ID of a container.
++mark+:: A container with the specified mark, see <<vim_like_marks>>.
+
+Note that swapping does not work with all containers. Most notably, swapping
+floating containers or containers that have a parent-child relationship to one
+another does not work.
+
+*Syntax*:
+----------------------------------------
+swap container with id|con_id|mark <arg>
+----------------------------------------
+
+*Examples*:
+-----------------------------------------------------------------
+# Swaps the focused container with the container marked »swapee«.
+swap container with mark swapee
+
+# Swaps container marked »A« and »B«
+[con_mark="^A$"] swap container with mark B
+-----------------------------------------------------------------
+
 === Sticky floating windows
 
 If you want a window to stick to the glass, i.e., have it stay on screen even
@@ -2089,6 +2172,23 @@ i3-msg 'rename workspace to "2: mail"'
 bindsym $mod+r exec i3-input -F 'rename workspace to "%s"' -P 'New name: '
 --------------------------------------------------------------------------
 
+If you want to rename workspaces on demand while keeping the navigation stable,
+you can use a setup like this:
+
+*Example*:
+-------------------------
+bindsym $mod+1 workspace number "1: www"
+bindsym $mod+2 workspace number "2: mail"
+...
+-------------------------
+
+If a workspace does not exist, the command +workspace number "1: mail"+ will
+create workspace "1: mail".
+
+If a workspace with number 1 does already exist, the command will switch to this
+workspace and ignore the text part. So even when the workspace has been renamed
+to "1: web", the above command will still switch to it.
+
 === Moving workspaces to a different screen
 
 See <<move_to_outputs>> for how to move a container/workspace to a different
@@ -2104,8 +2204,8 @@ To move a container to another RandR output (addressed by names like +LVDS1+ or
 
 *Syntax*:
 ------------------------------------------------------------
-move container to output left|right|down|up|current|<output>
-move workspace to output left|right|down|up|current|<output>
+move container to output left|right|down|up|current|primary|<output>
+move workspace to output left|right|down|up|current|primary|<output>
 ------------------------------------------------------------
 
 *Examples*:
@@ -2116,8 +2216,17 @@ bindsym $mod+x move workspace to output right
 
 # Put this window on the presentation output.
 bindsym $mod+x move container to output VGA1
+
+# Put this window on the primary output.
+bindsym $mod+x move container to output primary
 --------------------------------------------------------
 
+-------------------------------
+Note that you might not have a primary output configured yet. To do so, run:
+-------------------------
+xrandr --output <output> --primary
+-------------------------
+
 === Moving containers/windows to marks
 
 To move a container to another container with a specific mark (see <<vim_like_marks>>),
index 6208945dc87558f0ee88cd5cfdbfcd9a9bd68e69..a7687c7bc480ab91d9e63c0cc13c2c94bec434ae 100755 (executable)
@@ -65,7 +65,7 @@ for my $line (@raw_lines) {
 my $current_state;
 
 for my $line (@lines) {
-    if (my ($state) = ($line =~ /^state ([A-Z_]+):$/)) {
+    if (my ($state) = ($line =~ /^state ([A-Z0-9_]+):$/)) {
         #say "got a new state: $state";
         $current_state = $state;
     } else {
@@ -155,12 +155,20 @@ for my $state (@keys) {
         # to generate a format string. The format uses %d for <number>s,
         # literal numbers or state IDs and %s for NULL, <string>s and literal
         # strings.
+
+        # remove the function name temporarily, so that the following
+        # replacements only apply to the arguments.
+        my ($funcname) = ($fmt =~ /^(.+)\(/);
+        $fmt =~ s/^$funcname//;
+
         $fmt =~ s/$_/%d/g for @keys;
         $fmt =~ s/\$([a-z_]+)/%s/g;
         $fmt =~ s/\&([a-z_]+)/%ld/g;
         $fmt =~ s/"([a-z0-9_]+)"/%s/g;
         $fmt =~ s/(?:-?|\b)[0-9]+\b/%d/g;
 
+        $fmt = $funcname . $fmt;
+
         say $callfh "         case $call_id:";
         say $callfh "             result->next_state = $next_state;";
         say $callfh '#ifndef TEST_PARSER';
index 2bb43270e32d02d637a026b80ea613a12ec41353..dd58fd124547df7dcee0ce40785784eb1e43be4d 100644 (file)
 #include "xcb.h"
 #include "libi3.h"
 
+#define TEXT_PADDING logical_px(4)
+#define WIN_POS_X logical_px(490)
+#define WIN_POS_Y logical_px(297)
+#define WIN_WIDTH logical_px(300)
+#define WIN_HEIGHT (15 * font.height + TEXT_PADDING)
+
+#define col_x(col) \
+    (((col)-1) * char_width + TEXT_PADDING)
 #define row_y(row) \
-    (((row)-1) * font.height + logical_px(4))
-#define window_height() \
-    (row_y(15) + font.height)
+    (((row)-1) * font.height + TEXT_PADDING)
 
 enum { STEP_WELCOME,
        STEP_GENERATE } current_step = STEP_WELCOME;
@@ -90,8 +96,7 @@ static i3Font bold_font;
 static int char_width;
 static char *socket_path;
 static xcb_window_t win;
-static xcb_pixmap_t pixmap;
-static xcb_gcontext_t pixmap_gc;
+static surface_t surface;
 static xcb_key_symbols_t *symbols;
 xcb_window_t root;
 static struct xkb_keymap *xkb_keymap;
@@ -463,82 +468,73 @@ void errorlog(char *fmt, ...) {
 void debuglog(char *fmt, ...) {
 }
 
+static void txt(int col, int row, char *text, color_t fg, color_t bg) {
+    int x = col_x(col);
+    int y = row_y(row);
+    i3String *string = i3string_from_utf8(text);
+    draw_util_text(string, &surface, fg, bg, x, y, WIN_WIDTH - x - TEXT_PADDING);
+    i3string_free(string);
+}
+
 /*
  * Handles expose events, that is, draws the window contents.
  *
  */
 static int handle_expose() {
-    /* re-draw the background */
-    xcb_rectangle_t border = {0, 0, logical_px(300), window_height()};
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")});
-    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
+    const color_t black = draw_util_hex_to_color("#000000");
+    const color_t white = draw_util_hex_to_color("#FFFFFF");
+    const color_t green = draw_util_hex_to_color("#00FF00");
+    const color_t red = draw_util_hex_to_color("#FF0000");
 
-    set_font(&font);
+    /* draw background */
+    draw_util_clear_surface(&surface, black);
 
-#define txt(x, row, text)                    \
-    draw_text_ascii(text, pixmap, pixmap_gc, \
-                    x, row_y(row), logical_px(500) - x * 2)
+    set_font(&font);
 
     if (current_step == STEP_WELCOME) {
-        /* restore font color */
-        set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000"));
-
-        txt(logical_px(10), 2, "You have not configured i3 yet.");
-        txt(logical_px(10), 3, "Do you want me to generate a config at");
+        txt(2, 2, "You have not configured i3 yet.", white, black);
+        txt(2, 3, "Do you want me to generate a config at", white, black);
 
         char *msg;
         sasprintf(&msg, "%s?", config_path);
-        txt(logical_px(10), 4, msg);
+        txt(2, 4, msg, white, black);
         free(msg);
 
-        txt(logical_px(85), 6, "Yes, generate the config");
-        txt(logical_px(85), 8, "No, I will use the defaults");
+        txt(13, 6, "Yes, generate the config", white, black);
+        txt(13, 8, "No, I will use the defaults", white, black);
 
-        /* green */
-        set_font_colors(pixmap_gc, draw_util_hex_to_color("#00FF00"), draw_util_hex_to_color("#000000"));
-        txt(logical_px(25), 6, "<Enter>");
+        txt(4, 6, "<Enter>", green, black);
 
-        /* red */
-        set_font_colors(pixmap_gc, draw_util_hex_to_color("#FF0000"), draw_util_hex_to_color("#000000"));
-        txt(logical_px(31), 8, "<ESC>");
+        txt(5, 8, "<ESC>", red, black);
     }
 
     if (current_step == STEP_GENERATE) {
-        set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000"));
-
-        txt(logical_px(10), 2, "Please choose either:");
-        txt(logical_px(85), 4, "Win as default modifier");
-        txt(logical_px(85), 5, "Alt as default modifier");
-        txt(logical_px(10), 7, "Afterwards, press");
-        txt(logical_px(85), 9, "to write the config");
-        txt(logical_px(85), 10, "to abort");
+        txt(2, 2, "Please choose either:", white, black);
+        txt(13, 4, "Win as default modifier", white, black);
+        txt(13, 5, "Alt as default modifier", white, black);
+        txt(2, 7, "Afterwards, press", white, black);
+        txt(13, 9, "to write the config", white, black);
+        txt(13, 10, "to abort", white, black);
 
         /* the not-selected modifier */
         if (modifier == MOD_Mod4)
-            txt(logical_px(31), 5, "<Alt>");
+            txt(5, 5, "<Alt>", white, black);
         else
-            txt(logical_px(31), 4, "<Win>");
+            txt(5, 4, "<Win>", white, black);
 
         /* the selected modifier */
         set_font(&bold_font);
-        set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000"));
         if (modifier == MOD_Mod4)
-            txt(logical_px(10), 4, "-> <Win>");
+            txt(2, 4, "-> <Win>", white, black);
         else
-            txt(logical_px(10), 5, "-> <Alt>");
+            txt(2, 5, "-> <Alt>", white, black);
 
-        /* green */
         set_font(&font);
-        set_font_colors(pixmap_gc, draw_util_hex_to_color("#00FF00"), draw_util_hex_to_color("#000000"));
-        txt(logical_px(25), 9, "<Enter>");
+        txt(4, 9, "<Enter>", green, black);
 
-        /* red */
-        set_font_colors(pixmap_gc, draw_util_hex_to_color("#FF0000"), draw_util_hex_to_color("#000000"));
-        txt(logical_px(31), 10, "<ESC>");
+        txt(5, 10, "<ESC>", red, black);
     }
 
-    /* Copy the contents of the pixmap to the real window */
-    xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, logical_px(500), logical_px(500));
     xcb_flush(conn);
 
     return 1;
@@ -625,8 +621,7 @@ static void handle_button_press(xcb_button_press_event_t *event) {
     if (current_step != STEP_GENERATE)
         return;
 
-    if (event->event_x < logical_px(32) ||
-        event->event_x > (logical_px(32) + char_width * 5))
+    if (event->event_x < col_x(5) || event->event_x > col_x(10))
         return;
 
     if (event->event_y >= row_y(4) && event->event_y <= (row_y(4) + font.height)) {
@@ -854,6 +849,7 @@ int main(int argc, char *argv[]) {
 
     xcb_numlock_mask = get_mod_mask_for(XCB_NUM_LOCK, symbols, modmap_reply);
 
+    init_dpi();
     font = load_font(pattern, true);
     bold_font = load_font(patternbold, true);
 
@@ -866,10 +862,10 @@ int main(int argc, char *argv[]) {
     xcb_create_window(
         conn,
         XCB_COPY_FROM_PARENT,
-        win,                                                                /* the window id */
-        root,                                                               /* parent == root */
-        logical_px(490), logical_px(297), logical_px(300), window_height(), /* dimensions */
-        0,                                                                  /* X11 border = 0, we draw our own */
+        win,                                         /* the window id */
+        root,                                        /* parent == root */
+        WIN_POS_X, WIN_POS_Y, WIN_WIDTH, WIN_HEIGHT, /* dimensions */
+        0,                                           /* X11 border = 0, we draw our own */
         XCB_WINDOW_CLASS_INPUT_OUTPUT,
         XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
         XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
@@ -914,11 +910,8 @@ int main(int argc, char *argv[]) {
                         strlen("i3: first configuration"),
                         "i3: first configuration");
 
-    /* Create pixmap */
-    pixmap = xcb_generate_id(conn);
-    pixmap_gc = xcb_generate_id(conn);
-    xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, logical_px(500), logical_px(500));
-    xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
+    /* Initialize drawable surface */
+    draw_util_surface_init(conn, &surface, win, get_visualtype(root_screen), WIN_WIDTH, WIN_HEIGHT);
 
     /* Grab the keyboard to get all input */
     xcb_flush(conn);
@@ -965,12 +958,18 @@ int main(int argc, char *argv[]) {
                 break;
 
             case XCB_EXPOSE:
-                handle_expose();
+                if (((xcb_expose_event_t *)event)->count == 0) {
+                    handle_expose();
+                }
+
                 break;
         }
 
         free(event);
     }
 
+    /* Dismiss drawable surface */
+    draw_util_surface_free(conn, &surface);
+
     return 0;
 }
index 3b81cb20ce008097b9f3dbd412f1fd08a6ea2c87..aee5dc2f4e32eeb4b82099dc0633e1a911a6e0e4 100755 (executable)
@@ -279,16 +279,42 @@ for my $app (keys %apps) {
         }
 
         $choices{$name} = $app;
+        next;
     }
 
     if ((scalar grep { $_ eq 'command' } @entry_types) > 0) {
-        my ($command) = split(' ', $apps{$app}->{Exec});
+        my $command = $apps{$app}->{Exec};
+
+        # Handle escape sequences (should be done for all string values, but does
+        # matter here).
+        my %escapes = (
+            '\\s' => ' ',
+            '\\n' => '\n',
+            '\\t' => '\t',
+            '\\r' => '\r',
+            '\\\\' => '\\',
+        );
+        $command =~ s/(\\[sntr\\])/$escapes{$1}/go;
+
+        # Extract executable
+        if ($command =~ m/^\s*([^\s\"]+)(?:\s|$)/) {
+            # No quotes
+            $command = $1;
+        } elsif ($command =~ m/^\s*\"([^\"\\]*(?:\\.[^\"\\]*)*)\"(?:\s|$)/) {
+            # Quoted, remove quotes and fix escaped characters
+            $command = $1;
+            $command =~ s/\\([\"\`\$\\])/$1/g;
+        } else {
+            # Invalid quotes, fallback to whitespace
+            ($command) = split(' ', $command);
+        }
 
         # Don’t add “geany” if “Geany” is already present.
         my @keys = map { lc } keys %choices;
-        next if (scalar grep { $_ eq lc(basename($command)) } @keys) > 0;
-
-        $choices{basename($command)} = $app;
+        if (!(scalar grep { $_ eq lc(basename($command)) } @keys) > 0) {
+            $choices{basename($command)} = $app;
+        }
+        next;
     }
 
     if ((scalar grep { $_ eq 'filename' } @entry_types) > 0) {
index b8e45a145ccfc2422658754dc791ef5824525307..52bdc04444aa1c1d538e311f9cdbb182e1a2bd7a 100644 (file)
@@ -38,780 +38,780 @@ struct codepair {
     unsigned short keysym;
     unsigned short ucs;
 } keysymtab[] = {
-      {0x01a1, 0x0104}, /*                     Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */
-      {0x01a2, 0x02d8}, /*                       breve ˘ BREVE */
-      {0x01a3, 0x0141}, /*                     Lstroke Ł LATIN CAPITAL LETTER L WITH STROKE */
-      {0x01a5, 0x013d}, /*                      Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */
-      {0x01a6, 0x015a}, /*                      Sacute Ś LATIN CAPITAL LETTER S WITH ACUTE */
-      {0x01a9, 0x0160}, /*                      Scaron Š LATIN CAPITAL LETTER S WITH CARON */
-      {0x01aa, 0x015e}, /*                    Scedilla Ş LATIN CAPITAL LETTER S WITH CEDILLA */
-      {0x01ab, 0x0164}, /*                      Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */
-      {0x01ac, 0x0179}, /*                      Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */
-      {0x01ae, 0x017d}, /*                      Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */
-      {0x01af, 0x017b}, /*                   Zabovedot Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE */
-      {0x01b1, 0x0105}, /*                     aogonek ą LATIN SMALL LETTER A WITH OGONEK */
-      {0x01b2, 0x02db}, /*                      ogonek ˛ OGONEK */
-      {0x01b3, 0x0142}, /*                     lstroke ł LATIN SMALL LETTER L WITH STROKE */
-      {0x01b5, 0x013e}, /*                      lcaron ľ LATIN SMALL LETTER L WITH CARON */
-      {0x01b6, 0x015b}, /*                      sacute ś LATIN SMALL LETTER S WITH ACUTE */
-      {0x01b7, 0x02c7}, /*                       caron ˇ CARON */
-      {0x01b9, 0x0161}, /*                      scaron š LATIN SMALL LETTER S WITH CARON */
-      {0x01ba, 0x015f}, /*                    scedilla ş LATIN SMALL LETTER S WITH CEDILLA */
-      {0x01bb, 0x0165}, /*                      tcaron ť LATIN SMALL LETTER T WITH CARON */
-      {0x01bc, 0x017a}, /*                      zacute ź LATIN SMALL LETTER Z WITH ACUTE */
-      {0x01bd, 0x02dd}, /*                 doubleacute ˝ DOUBLE ACUTE ACCENT */
-      {0x01be, 0x017e}, /*                      zcaron ž LATIN SMALL LETTER Z WITH CARON */
-      {0x01bf, 0x017c}, /*                   zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */
-      {0x01c0, 0x0154}, /*                      Racute Ŕ LATIN CAPITAL LETTER R WITH ACUTE */
-      {0x01c3, 0x0102}, /*                      Abreve Ă LATIN CAPITAL LETTER A WITH BREVE */
-      {0x01c5, 0x0139}, /*                      Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */
-      {0x01c6, 0x0106}, /*                      Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */
-      {0x01c8, 0x010c}, /*                      Ccaron Č LATIN CAPITAL LETTER C WITH CARON */
-      {0x01ca, 0x0118}, /*                     Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */
-      {0x01cc, 0x011a}, /*                      Ecaron Ě LATIN CAPITAL LETTER E WITH CARON */
-      {0x01cf, 0x010e}, /*                      Dcaron Ď LATIN CAPITAL LETTER D WITH CARON */
-      {0x01d0, 0x0110}, /*                     Dstroke Đ LATIN CAPITAL LETTER D WITH STROKE */
-      {0x01d1, 0x0143}, /*                      Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */
-      {0x01d2, 0x0147}, /*                      Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */
-      {0x01d5, 0x0150}, /*                Odoubleacute Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */
-      {0x01d8, 0x0158}, /*                      Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */
-      {0x01d9, 0x016e}, /*                       Uring Ů LATIN CAPITAL LETTER U WITH RING ABOVE */
-      {0x01db, 0x0170}, /*                Udoubleacute Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */
-      {0x01de, 0x0162}, /*                    Tcedilla Ţ LATIN CAPITAL LETTER T WITH CEDILLA */
-      {0x01e0, 0x0155}, /*                      racute ŕ LATIN SMALL LETTER R WITH ACUTE */
-      {0x01e3, 0x0103}, /*                      abreve ă LATIN SMALL LETTER A WITH BREVE */
-      {0x01e5, 0x013a}, /*                      lacute ĺ LATIN SMALL LETTER L WITH ACUTE */
-      {0x01e6, 0x0107}, /*                      cacute ć LATIN SMALL LETTER C WITH ACUTE */
-      {0x01e8, 0x010d}, /*                      ccaron č LATIN SMALL LETTER C WITH CARON */
-      {0x01ea, 0x0119}, /*                     eogonek ę LATIN SMALL LETTER E WITH OGONEK */
-      {0x01ec, 0x011b}, /*                      ecaron ě LATIN SMALL LETTER E WITH CARON */
-      {0x01ef, 0x010f}, /*                      dcaron ď LATIN SMALL LETTER D WITH CARON */
-      {0x01f0, 0x0111}, /*                     dstroke đ LATIN SMALL LETTER D WITH STROKE */
-      {0x01f1, 0x0144}, /*                      nacute ń LATIN SMALL LETTER N WITH ACUTE */
-      {0x01f2, 0x0148}, /*                      ncaron ň LATIN SMALL LETTER N WITH CARON */
-      {0x01f5, 0x0151}, /*                odoubleacute ő LATIN SMALL LETTER O WITH DOUBLE ACUTE */
-      {0x01f8, 0x0159}, /*                      rcaron ř LATIN SMALL LETTER R WITH CARON */
-      {0x01f9, 0x016f}, /*                       uring ů LATIN SMALL LETTER U WITH RING ABOVE */
-      {0x01fb, 0x0171}, /*                udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */
-      {0x01fe, 0x0163}, /*                    tcedilla ţ LATIN SMALL LETTER T WITH CEDILLA */
-      {0x01ff, 0x02d9}, /*                    abovedot ˙ DOT ABOVE */
-      {0x02a1, 0x0126}, /*                     Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */
-      {0x02a6, 0x0124}, /*                 Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */
-      {0x02a9, 0x0130}, /*                   Iabovedot İ LATIN CAPITAL LETTER I WITH DOT ABOVE */
-      {0x02ab, 0x011e}, /*                      Gbreve Ğ LATIN CAPITAL LETTER G WITH BREVE */
-      {0x02ac, 0x0134}, /*                 Jcircumflex Ĵ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */
-      {0x02b1, 0x0127}, /*                     hstroke ħ LATIN SMALL LETTER H WITH STROKE */
-      {0x02b6, 0x0125}, /*                 hcircumflex ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX */
-      {0x02b9, 0x0131}, /*                    idotless ı LATIN SMALL LETTER DOTLESS I */
-      {0x02bb, 0x011f}, /*                      gbreve ğ LATIN SMALL LETTER G WITH BREVE */
-      {0x02bc, 0x0135}, /*                 jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */
-      {0x02c5, 0x010a}, /*                   Cabovedot Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE */
-      {0x02c6, 0x0108}, /*                 Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */
-      {0x02d5, 0x0120}, /*                   Gabovedot Ġ LATIN CAPITAL LETTER G WITH DOT ABOVE */
-      {0x02d8, 0x011c}, /*                 Gcircumflex Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */
-      {0x02dd, 0x016c}, /*                      Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */
-      {0x02de, 0x015c}, /*                 Scircumflex Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */
-      {0x02e5, 0x010b}, /*                   cabovedot ċ LATIN SMALL LETTER C WITH DOT ABOVE */
-      {0x02e6, 0x0109}, /*                 ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */
-      {0x02f5, 0x0121}, /*                   gabovedot ġ LATIN SMALL LETTER G WITH DOT ABOVE */
-      {0x02f8, 0x011d}, /*                 gcircumflex ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX */
-      {0x02fd, 0x016d}, /*                      ubreve ŭ LATIN SMALL LETTER U WITH BREVE */
-      {0x02fe, 0x015d}, /*                 scircumflex ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX */
-      {0x03a2, 0x0138}, /*                         kra ĸ LATIN SMALL LETTER KRA */
-      {0x03a3, 0x0156}, /*                    Rcedilla Ŗ LATIN CAPITAL LETTER R WITH CEDILLA */
-      {0x03a5, 0x0128}, /*                      Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */
-      {0x03a6, 0x013b}, /*                    Lcedilla Ļ LATIN CAPITAL LETTER L WITH CEDILLA */
-      {0x03aa, 0x0112}, /*                     Emacron Ē LATIN CAPITAL LETTER E WITH MACRON */
-      {0x03ab, 0x0122}, /*                    Gcedilla Ģ LATIN CAPITAL LETTER G WITH CEDILLA */
-      {0x03ac, 0x0166}, /*                      Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */
-      {0x03b3, 0x0157}, /*                    rcedilla ŗ LATIN SMALL LETTER R WITH CEDILLA */
-      {0x03b5, 0x0129}, /*                      itilde ĩ LATIN SMALL LETTER I WITH TILDE */
-      {0x03b6, 0x013c}, /*                    lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */
-      {0x03ba, 0x0113}, /*                     emacron ē LATIN SMALL LETTER E WITH MACRON */
-      {0x03bb, 0x0123}, /*                    gcedilla ģ LATIN SMALL LETTER G WITH CEDILLA */
-      {0x03bc, 0x0167}, /*                      tslash ŧ LATIN SMALL LETTER T WITH STROKE */
-      {0x03bd, 0x014a}, /*                         ENG Ŋ LATIN CAPITAL LETTER ENG */
-      {0x03bf, 0x014b}, /*                         eng ŋ LATIN SMALL LETTER ENG */
-      {0x03c0, 0x0100}, /*                     Amacron Ā LATIN CAPITAL LETTER A WITH MACRON */
-      {0x03c7, 0x012e}, /*                     Iogonek Į LATIN CAPITAL LETTER I WITH OGONEK */
-      {0x03cc, 0x0116}, /*                   Eabovedot Ė LATIN CAPITAL LETTER E WITH DOT ABOVE */
-      {0x03cf, 0x012a}, /*                     Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */
-      {0x03d1, 0x0145}, /*                    Ncedilla Ņ LATIN CAPITAL LETTER N WITH CEDILLA */
-      {0x03d2, 0x014c}, /*                     Omacron Ō LATIN CAPITAL LETTER O WITH MACRON */
-      {0x03d3, 0x0136}, /*                    Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */
-      {0x03d9, 0x0172}, /*                     Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */
-      {0x03dd, 0x0168}, /*                      Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */
-      {0x03de, 0x016a}, /*                     Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */
-      {0x03e0, 0x0101}, /*                     amacron ā LATIN SMALL LETTER A WITH MACRON */
-      {0x03e7, 0x012f}, /*                     iogonek į LATIN SMALL LETTER I WITH OGONEK */
-      {0x03ec, 0x0117}, /*                   eabovedot ė LATIN SMALL LETTER E WITH DOT ABOVE */
-      {0x03ef, 0x012b}, /*                     imacron ī LATIN SMALL LETTER I WITH MACRON */
-      {0x03f1, 0x0146}, /*                    ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */
-      {0x03f2, 0x014d}, /*                     omacron ō LATIN SMALL LETTER O WITH MACRON */
-      {0x03f3, 0x0137}, /*                    kcedilla ķ LATIN SMALL LETTER K WITH CEDILLA */
-      {0x03f9, 0x0173}, /*                     uogonek ų LATIN SMALL LETTER U WITH OGONEK */
-      {0x03fd, 0x0169}, /*                      utilde ũ LATIN SMALL LETTER U WITH TILDE */
-      {0x03fe, 0x016b}, /*                     umacron ū LATIN SMALL LETTER U WITH MACRON */
-      {0x047e, 0x203e}, /*                    overline ‾ OVERLINE */
-      {0x04a1, 0x3002}, /*               kana_fullstop 。 IDEOGRAPHIC FULL STOP */
-      {0x04a2, 0x300c}, /*         kana_openingbracket 「 LEFT CORNER BRACKET */
-      {0x04a3, 0x300d}, /*         kana_closingbracket 」 RIGHT CORNER BRACKET */
-      {0x04a4, 0x3001}, /*                  kana_comma 、 IDEOGRAPHIC COMMA */
-      {0x04a5, 0x30fb}, /*            kana_conjunctive ・ KATAKANA MIDDLE DOT */
-      {0x04a6, 0x30f2}, /*                     kana_WO ヲ KATAKANA LETTER WO */
-      {0x04a7, 0x30a1}, /*                      kana_a ァ KATAKANA LETTER SMALL A */
-      {0x04a8, 0x30a3}, /*                      kana_i ィ KATAKANA LETTER SMALL I */
-      {0x04a9, 0x30a5}, /*                      kana_u ゥ KATAKANA LETTER SMALL U */
-      {0x04aa, 0x30a7}, /*                      kana_e ェ KATAKANA LETTER SMALL E */
-      {0x04ab, 0x30a9}, /*                      kana_o ォ KATAKANA LETTER SMALL O */
-      {0x04ac, 0x30e3}, /*                     kana_ya ャ KATAKANA LETTER SMALL YA */
-      {0x04ad, 0x30e5}, /*                     kana_yu ュ KATAKANA LETTER SMALL YU */
-      {0x04ae, 0x30e7}, /*                     kana_yo ョ KATAKANA LETTER SMALL YO */
-      {0x04af, 0x30c3}, /*                    kana_tsu ッ KATAKANA LETTER SMALL TU */
-      {0x04b0, 0x30fc}, /*              prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */
-      {0x04b1, 0x30a2}, /*                      kana_A ア KATAKANA LETTER A */
-      {0x04b2, 0x30a4}, /*                      kana_I イ KATAKANA LETTER I */
-      {0x04b3, 0x30a6}, /*                      kana_U ウ KATAKANA LETTER U */
-      {0x04b4, 0x30a8}, /*                      kana_E エ KATAKANA LETTER E */
-      {0x04b5, 0x30aa}, /*                      kana_O オ KATAKANA LETTER O */
-      {0x04b6, 0x30ab}, /*                     kana_KA カ KATAKANA LETTER KA */
-      {0x04b7, 0x30ad}, /*                     kana_KI キ KATAKANA LETTER KI */
-      {0x04b8, 0x30af}, /*                     kana_KU ク KATAKANA LETTER KU */
-      {0x04b9, 0x30b1}, /*                     kana_KE ケ KATAKANA LETTER KE */
-      {0x04ba, 0x30b3}, /*                     kana_KO コ KATAKANA LETTER KO */
-      {0x04bb, 0x30b5}, /*                     kana_SA サ KATAKANA LETTER SA */
-      {0x04bc, 0x30b7}, /*                    kana_SHI シ KATAKANA LETTER SI */
-      {0x04bd, 0x30b9}, /*                     kana_SU ス KATAKANA LETTER SU */
-      {0x04be, 0x30bb}, /*                     kana_SE セ KATAKANA LETTER SE */
-      {0x04bf, 0x30bd}, /*                     kana_SO ソ KATAKANA LETTER SO */
-      {0x04c0, 0x30bf}, /*                     kana_TA タ KATAKANA LETTER TA */
-      {0x04c1, 0x30c1}, /*                    kana_CHI チ KATAKANA LETTER TI */
-      {0x04c2, 0x30c4}, /*                    kana_TSU ツ KATAKANA LETTER TU */
-      {0x04c3, 0x30c6}, /*                     kana_TE テ KATAKANA LETTER TE */
-      {0x04c4, 0x30c8}, /*                     kana_TO ト KATAKANA LETTER TO */
-      {0x04c5, 0x30ca}, /*                     kana_NA ナ KATAKANA LETTER NA */
-      {0x04c6, 0x30cb}, /*                     kana_NI ニ KATAKANA LETTER NI */
-      {0x04c7, 0x30cc}, /*                     kana_NU ヌ KATAKANA LETTER NU */
-      {0x04c8, 0x30cd}, /*                     kana_NE ネ KATAKANA LETTER NE */
-      {0x04c9, 0x30ce}, /*                     kana_NO ノ KATAKANA LETTER NO */
-      {0x04ca, 0x30cf}, /*                     kana_HA ハ KATAKANA LETTER HA */
-      {0x04cb, 0x30d2}, /*                     kana_HI ヒ KATAKANA LETTER HI */
-      {0x04cc, 0x30d5}, /*                     kana_FU フ KATAKANA LETTER HU */
-      {0x04cd, 0x30d8}, /*                     kana_HE ヘ KATAKANA LETTER HE */
-      {0x04ce, 0x30db}, /*                     kana_HO ホ KATAKANA LETTER HO */
-      {0x04cf, 0x30de}, /*                     kana_MA マ KATAKANA LETTER MA */
-      {0x04d0, 0x30df}, /*                     kana_MI ミ KATAKANA LETTER MI */
-      {0x04d1, 0x30e0}, /*                     kana_MU ム KATAKANA LETTER MU */
-      {0x04d2, 0x30e1}, /*                     kana_ME メ KATAKANA LETTER ME */
-      {0x04d3, 0x30e2}, /*                     kana_MO モ KATAKANA LETTER MO */
-      {0x04d4, 0x30e4}, /*                     kana_YA ヤ KATAKANA LETTER YA */
-      {0x04d5, 0x30e6}, /*                     kana_YU ユ KATAKANA LETTER YU */
-      {0x04d6, 0x30e8}, /*                     kana_YO ヨ KATAKANA LETTER YO */
-      {0x04d7, 0x30e9}, /*                     kana_RA ラ KATAKANA LETTER RA */
-      {0x04d8, 0x30ea}, /*                     kana_RI リ KATAKANA LETTER RI */
-      {0x04d9, 0x30eb}, /*                     kana_RU ル KATAKANA LETTER RU */
-      {0x04da, 0x30ec}, /*                     kana_RE レ KATAKANA LETTER RE */
-      {0x04db, 0x30ed}, /*                     kana_RO ロ KATAKANA LETTER RO */
-      {0x04dc, 0x30ef}, /*                     kana_WA ワ KATAKANA LETTER WA */
-      {0x04dd, 0x30f3}, /*                      kana_N ン KATAKANA LETTER N */
-      {0x04de, 0x309b}, /*                 voicedsound ゛ KATAKANA-HIRAGANA VOICED SOUND MARK */
-      {0x04df, 0x309c}, /*             semivoicedsound ゜ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */
-      {0x05ac, 0x060c}, /*                Arabic_comma ، ARABIC COMMA */
-      {0x05bb, 0x061b}, /*            Arabic_semicolon ؛ ARABIC SEMICOLON */
-      {0x05bf, 0x061f}, /*        Arabic_question_mark ؟ ARABIC QUESTION MARK */
-      {0x05c1, 0x0621}, /*                Arabic_hamza ء ARABIC LETTER HAMZA */
-      {0x05c2, 0x0622}, /*          Arabic_maddaonalef آ ARABIC LETTER ALEF WITH MADDA ABOVE */
-      {0x05c3, 0x0623}, /*          Arabic_hamzaonalef أ ARABIC LETTER ALEF WITH HAMZA ABOVE */
-      {0x05c4, 0x0624}, /*           Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */
-      {0x05c5, 0x0625}, /*       Arabic_hamzaunderalef إ ARABIC LETTER ALEF WITH HAMZA BELOW */
-      {0x05c6, 0x0626}, /*           Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */
-      {0x05c7, 0x0627}, /*                 Arabic_alef ا ARABIC LETTER ALEF */
-      {0x05c8, 0x0628}, /*                  Arabic_beh ب ARABIC LETTER BEH */
-      {0x05c9, 0x0629}, /*           Arabic_tehmarbuta ة ARABIC LETTER TEH MARBUTA */
-      {0x05ca, 0x062a}, /*                  Arabic_teh ت ARABIC LETTER TEH */
-      {0x05cb, 0x062b}, /*                 Arabic_theh ث ARABIC LETTER THEH */
-      {0x05cc, 0x062c}, /*                 Arabic_jeem ج ARABIC LETTER JEEM */
-      {0x05cd, 0x062d}, /*                  Arabic_hah ح ARABIC LETTER HAH */
-      {0x05ce, 0x062e}, /*                 Arabic_khah خ ARABIC LETTER KHAH */
-      {0x05cf, 0x062f}, /*                  Arabic_dal د ARABIC LETTER DAL */
-      {0x05d0, 0x0630}, /*                 Arabic_thal ذ ARABIC LETTER THAL */
-      {0x05d1, 0x0631}, /*                   Arabic_ra ر ARABIC LETTER REH */
-      {0x05d2, 0x0632}, /*                 Arabic_zain ز ARABIC LETTER ZAIN */
-      {0x05d3, 0x0633}, /*                 Arabic_seen س ARABIC LETTER SEEN */
-      {0x05d4, 0x0634}, /*                Arabic_sheen ش ARABIC LETTER SHEEN */
-      {0x05d5, 0x0635}, /*                  Arabic_sad ص ARABIC LETTER SAD */
-      {0x05d6, 0x0636}, /*                  Arabic_dad ض ARABIC LETTER DAD */
-      {0x05d7, 0x0637}, /*                  Arabic_tah ط ARABIC LETTER TAH */
-      {0x05d8, 0x0638}, /*                  Arabic_zah ظ ARABIC LETTER ZAH */
-      {0x05d9, 0x0639}, /*                  Arabic_ain ع ARABIC LETTER AIN */
-      {0x05da, 0x063a}, /*                Arabic_ghain غ ARABIC LETTER GHAIN */
-      {0x05e0, 0x0640}, /*              Arabic_tatweel ـ ARABIC TATWEEL */
-      {0x05e1, 0x0641}, /*                  Arabic_feh ف ARABIC LETTER FEH */
-      {0x05e2, 0x0642}, /*                  Arabic_qaf ق ARABIC LETTER QAF */
-      {0x05e3, 0x0643}, /*                  Arabic_kaf ك ARABIC LETTER KAF */
-      {0x05e4, 0x0644}, /*                  Arabic_lam ل ARABIC LETTER LAM */
-      {0x05e5, 0x0645}, /*                 Arabic_meem م ARABIC LETTER MEEM */
-      {0x05e6, 0x0646}, /*                 Arabic_noon ن ARABIC LETTER NOON */
-      {0x05e7, 0x0647}, /*                   Arabic_ha ه ARABIC LETTER HEH */
-      {0x05e8, 0x0648}, /*                  Arabic_waw و ARABIC LETTER WAW */
-      {0x05e9, 0x0649}, /*          Arabic_alefmaksura ى ARABIC LETTER ALEF MAKSURA */
-      {0x05ea, 0x064a}, /*                  Arabic_yeh ي ARABIC LETTER YEH */
-      {0x05eb, 0x064b}, /*             Arabic_fathatan ً ARABIC FATHATAN */
-      {0x05ec, 0x064c}, /*             Arabic_dammatan ٌ ARABIC DAMMATAN */
-      {0x05ed, 0x064d}, /*             Arabic_kasratan ٍ ARABIC KASRATAN */
-      {0x05ee, 0x064e}, /*                Arabic_fatha َ ARABIC FATHA */
-      {0x05ef, 0x064f}, /*                Arabic_damma ُ ARABIC DAMMA */
-      {0x05f0, 0x0650}, /*                Arabic_kasra ِ ARABIC KASRA */
-      {0x05f1, 0x0651}, /*               Arabic_shadda ّ ARABIC SHADDA */
-      {0x05f2, 0x0652}, /*                Arabic_sukun ْ ARABIC SUKUN */
-      {0x06a1, 0x0452}, /*                 Serbian_dje ђ CYRILLIC SMALL LETTER DJE */
-      {0x06a2, 0x0453}, /*               Macedonia_gje ѓ CYRILLIC SMALL LETTER GJE */
-      {0x06a3, 0x0451}, /*                 Cyrillic_io ё CYRILLIC SMALL LETTER IO */
-      {0x06a4, 0x0454}, /*                Ukrainian_ie є CYRILLIC SMALL LETTER UKRAINIAN IE */
-      {0x06a5, 0x0455}, /*               Macedonia_dse ѕ CYRILLIC SMALL LETTER DZE */
-      {0x06a6, 0x0456}, /*                 Ukrainian_i і CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */
-      {0x06a7, 0x0457}, /*                Ukrainian_yi ї CYRILLIC SMALL LETTER YI */
-      {0x06a8, 0x0458}, /*                 Cyrillic_je ј CYRILLIC SMALL LETTER JE */
-      {0x06a9, 0x0459}, /*                Cyrillic_lje љ CYRILLIC SMALL LETTER LJE */
-      {0x06aa, 0x045a}, /*                Cyrillic_nje њ CYRILLIC SMALL LETTER NJE */
-      {0x06ab, 0x045b}, /*                Serbian_tshe ћ CYRILLIC SMALL LETTER TSHE */
-      {0x06ac, 0x045c}, /*               Macedonia_kje ќ CYRILLIC SMALL LETTER KJE */
-                        /*  0x06ad                 Ukrainian_ghe_with_upturn ? ??? */
-      {0x06ae, 0x045e}, /*         Byelorussian_shortu ў CYRILLIC SMALL LETTER SHORT U */
-      {0x06af, 0x045f}, /*               Cyrillic_dzhe џ CYRILLIC SMALL LETTER DZHE */
-      {0x06b0, 0x2116}, /*                  numerosign № NUMERO SIGN */
-      {0x06b1, 0x0402}, /*                 Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */
-      {0x06b2, 0x0403}, /*               Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */
-      {0x06b3, 0x0401}, /*                 Cyrillic_IO Ё CYRILLIC CAPITAL LETTER IO */
-      {0x06b4, 0x0404}, /*                Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */
-      {0x06b5, 0x0405}, /*               Macedonia_DSE Ѕ CYRILLIC CAPITAL LETTER DZE */
-      {0x06b6, 0x0406}, /*                 Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */
-      {0x06b7, 0x0407}, /*                Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */
-      {0x06b8, 0x0408}, /*                 Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */
-      {0x06b9, 0x0409}, /*                Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */
-      {0x06ba, 0x040a}, /*                Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */
-      {0x06bb, 0x040b}, /*                Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */
-      {0x06bc, 0x040c}, /*               Macedonia_KJE Ќ CYRILLIC CAPITAL LETTER KJE */
-                        /*  0x06bd                 Ukrainian_GHE_WITH_UPTURN ? ??? */
-      {0x06be, 0x040e}, /*         Byelorussian_SHORTU Ў CYRILLIC CAPITAL LETTER SHORT U */
-      {0x06bf, 0x040f}, /*               Cyrillic_DZHE Џ CYRILLIC CAPITAL LETTER DZHE */
-      {0x06c0, 0x044e}, /*                 Cyrillic_yu ю CYRILLIC SMALL LETTER YU */
-      {0x06c1, 0x0430}, /*                  Cyrillic_a а CYRILLIC SMALL LETTER A */
-      {0x06c2, 0x0431}, /*                 Cyrillic_be б CYRILLIC SMALL LETTER BE */
-      {0x06c3, 0x0446}, /*                Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */
-      {0x06c4, 0x0434}, /*                 Cyrillic_de д CYRILLIC SMALL LETTER DE */
-      {0x06c5, 0x0435}, /*                 Cyrillic_ie е CYRILLIC SMALL LETTER IE */
-      {0x06c6, 0x0444}, /*                 Cyrillic_ef ф CYRILLIC SMALL LETTER EF */
-      {0x06c7, 0x0433}, /*                Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */
-      {0x06c8, 0x0445}, /*                 Cyrillic_ha х CYRILLIC SMALL LETTER HA */
-      {0x06c9, 0x0438}, /*                  Cyrillic_i и CYRILLIC SMALL LETTER I */
-      {0x06ca, 0x0439}, /*             Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */
-      {0x06cb, 0x043a}, /*                 Cyrillic_ka к CYRILLIC SMALL LETTER KA */
-      {0x06cc, 0x043b}, /*                 Cyrillic_el л CYRILLIC SMALL LETTER EL */
-      {0x06cd, 0x043c}, /*                 Cyrillic_em м CYRILLIC SMALL LETTER EM */
-      {0x06ce, 0x043d}, /*                 Cyrillic_en н CYRILLIC SMALL LETTER EN */
-      {0x06cf, 0x043e}, /*                  Cyrillic_o о CYRILLIC SMALL LETTER O */
-      {0x06d0, 0x043f}, /*                 Cyrillic_pe п CYRILLIC SMALL LETTER PE */
-      {0x06d1, 0x044f}, /*                 Cyrillic_ya я CYRILLIC SMALL LETTER YA */
-      {0x06d2, 0x0440}, /*                 Cyrillic_er р CYRILLIC SMALL LETTER ER */
-      {0x06d3, 0x0441}, /*                 Cyrillic_es с CYRILLIC SMALL LETTER ES */
-      {0x06d4, 0x0442}, /*                 Cyrillic_te т CYRILLIC SMALL LETTER TE */
-      {0x06d5, 0x0443}, /*                  Cyrillic_u у CYRILLIC SMALL LETTER U */
-      {0x06d6, 0x0436}, /*                Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */
-      {0x06d7, 0x0432}, /*                 Cyrillic_ve в CYRILLIC SMALL LETTER VE */
-      {0x06d8, 0x044c}, /*           Cyrillic_softsign ь CYRILLIC SMALL LETTER SOFT SIGN */
-      {0x06d9, 0x044b}, /*               Cyrillic_yeru ы CYRILLIC SMALL LETTER YERU */
-      {0x06da, 0x0437}, /*                 Cyrillic_ze з CYRILLIC SMALL LETTER ZE */
-      {0x06db, 0x0448}, /*                Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */
-      {0x06dc, 0x044d}, /*                  Cyrillic_e э CYRILLIC SMALL LETTER E */
-      {0x06dd, 0x0449}, /*              Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */
-      {0x06de, 0x0447}, /*                Cyrillic_che ч CYRILLIC SMALL LETTER CHE */
-      {0x06df, 0x044a}, /*           Cyrillic_hardsign ъ CYRILLIC SMALL LETTER HARD SIGN */
-      {0x06e0, 0x042e}, /*                 Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */
-      {0x06e1, 0x0410}, /*                  Cyrillic_A А CYRILLIC CAPITAL LETTER A */
-      {0x06e2, 0x0411}, /*                 Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */
-      {0x06e3, 0x0426}, /*                Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */
-      {0x06e4, 0x0414}, /*                 Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */
-      {0x06e5, 0x0415}, /*                 Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */
-      {0x06e6, 0x0424}, /*                 Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */
-      {0x06e7, 0x0413}, /*                Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */
-      {0x06e8, 0x0425}, /*                 Cyrillic_HA Х CYRILLIC CAPITAL LETTER HA */
-      {0x06e9, 0x0418}, /*                  Cyrillic_I И CYRILLIC CAPITAL LETTER I */
-      {0x06ea, 0x0419}, /*             Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */
-      {0x06eb, 0x041a}, /*                 Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */
-      {0x06ec, 0x041b}, /*                 Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */
-      {0x06ed, 0x041c}, /*                 Cyrillic_EM М CYRILLIC CAPITAL LETTER EM */
-      {0x06ee, 0x041d}, /*                 Cyrillic_EN Н CYRILLIC CAPITAL LETTER EN */
-      {0x06ef, 0x041e}, /*                  Cyrillic_O О CYRILLIC CAPITAL LETTER O */
-      {0x06f0, 0x041f}, /*                 Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */
-      {0x06f1, 0x042f}, /*                 Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */
-      {0x06f2, 0x0420}, /*                 Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */
-      {0x06f3, 0x0421}, /*                 Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */
-      {0x06f4, 0x0422}, /*                 Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */
-      {0x06f5, 0x0423}, /*                  Cyrillic_U У CYRILLIC CAPITAL LETTER U */
-      {0x06f6, 0x0416}, /*                Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */
-      {0x06f7, 0x0412}, /*                 Cyrillic_VE В CYRILLIC CAPITAL LETTER VE */
-      {0x06f8, 0x042c}, /*           Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */
-      {0x06f9, 0x042b}, /*               Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */
-      {0x06fa, 0x0417}, /*                 Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */
-      {0x06fb, 0x0428}, /*                Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */
-      {0x06fc, 0x042d}, /*                  Cyrillic_E Э CYRILLIC CAPITAL LETTER E */
-      {0x06fd, 0x0429}, /*              Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */
-      {0x06fe, 0x0427}, /*                Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */
-      {0x06ff, 0x042a}, /*           Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */
-      {0x07a1, 0x0386}, /*           Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */
-      {0x07a2, 0x0388}, /*         Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */
-      {0x07a3, 0x0389}, /*             Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */
-      {0x07a4, 0x038a}, /*            Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */
-      {0x07a5, 0x03aa}, /*         Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */
-      {0x07a7, 0x038c}, /*         Greek_OMICRONaccent Ό GREEK CAPITAL LETTER OMICRON WITH TONOS */
-      {0x07a8, 0x038e}, /*         Greek_UPSILONaccent Ύ GREEK CAPITAL LETTER UPSILON WITH TONOS */
-      {0x07a9, 0x03ab}, /*       Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */
-      {0x07ab, 0x038f}, /*           Greek_OMEGAaccent Ώ GREEK CAPITAL LETTER OMEGA WITH TONOS */
-      {0x07ae, 0x0385}, /*        Greek_accentdieresis ΅ GREEK DIALYTIKA TONOS */
-      {0x07af, 0x2015}, /*              Greek_horizbar ― HORIZONTAL BAR */
-      {0x07b1, 0x03ac}, /*           Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */
-      {0x07b2, 0x03ad}, /*         Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */
-      {0x07b3, 0x03ae}, /*             Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */
-      {0x07b4, 0x03af}, /*            Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */
-      {0x07b5, 0x03ca}, /*          Greek_iotadieresis ϊ GREEK SMALL LETTER IOTA WITH DIALYTIKA */
-      {0x07b6, 0x0390}, /*    Greek_iotaaccentdieresis ΐ GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */
-      {0x07b7, 0x03cc}, /*         Greek_omicronaccent ό GREEK SMALL LETTER OMICRON WITH TONOS */
-      {0x07b8, 0x03cd}, /*         Greek_upsilonaccent ύ GREEK SMALL LETTER UPSILON WITH TONOS */
-      {0x07b9, 0x03cb}, /*       Greek_upsilondieresis ϋ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */
-      {0x07ba, 0x03b0}, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */
-      {0x07bb, 0x03ce}, /*           Greek_omegaaccent ώ GREEK SMALL LETTER OMEGA WITH TONOS */
-      {0x07c1, 0x0391}, /*                 Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */
-      {0x07c2, 0x0392}, /*                  Greek_BETA Β GREEK CAPITAL LETTER BETA */
-      {0x07c3, 0x0393}, /*                 Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */
-      {0x07c4, 0x0394}, /*                 Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */
-      {0x07c5, 0x0395}, /*               Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */
-      {0x07c6, 0x0396}, /*                  Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */
-      {0x07c7, 0x0397}, /*                   Greek_ETA Η GREEK CAPITAL LETTER ETA */
-      {0x07c8, 0x0398}, /*                 Greek_THETA Θ GREEK CAPITAL LETTER THETA */
-      {0x07c9, 0x0399}, /*                  Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */
-      {0x07ca, 0x039a}, /*                 Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */
-      {0x07cb, 0x039b}, /*                Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMDA */
-      {0x07cc, 0x039c}, /*                    Greek_MU Μ GREEK CAPITAL LETTER MU */
-      {0x07cd, 0x039d}, /*                    Greek_NU Ν GREEK CAPITAL LETTER NU */
-      {0x07ce, 0x039e}, /*                    Greek_XI Ξ GREEK CAPITAL LETTER XI */
-      {0x07cf, 0x039f}, /*               Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */
-      {0x07d0, 0x03a0}, /*                    Greek_PI Π GREEK CAPITAL LETTER PI */
-      {0x07d1, 0x03a1}, /*                   Greek_RHO Ρ GREEK CAPITAL LETTER RHO */
-      {0x07d2, 0x03a3}, /*                 Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */
-      {0x07d4, 0x03a4}, /*                   Greek_TAU Τ GREEK CAPITAL LETTER TAU */
-      {0x07d5, 0x03a5}, /*               Greek_UPSILON Υ GREEK CAPITAL LETTER UPSILON */
-      {0x07d6, 0x03a6}, /*                   Greek_PHI Φ GREEK CAPITAL LETTER PHI */
-      {0x07d7, 0x03a7}, /*                   Greek_CHI Χ GREEK CAPITAL LETTER CHI */
-      {0x07d8, 0x03a8}, /*                   Greek_PSI Ψ GREEK CAPITAL LETTER PSI */
-      {0x07d9, 0x03a9}, /*                 Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */
-      {0x07e1, 0x03b1}, /*                 Greek_alpha α GREEK SMALL LETTER ALPHA */
-      {0x07e2, 0x03b2}, /*                  Greek_beta β GREEK SMALL LETTER BETA */
-      {0x07e3, 0x03b3}, /*                 Greek_gamma γ GREEK SMALL LETTER GAMMA */
-      {0x07e4, 0x03b4}, /*                 Greek_delta δ GREEK SMALL LETTER DELTA */
-      {0x07e5, 0x03b5}, /*               Greek_epsilon ε GREEK SMALL LETTER EPSILON */
-      {0x07e6, 0x03b6}, /*                  Greek_zeta ζ GREEK SMALL LETTER ZETA */
-      {0x07e7, 0x03b7}, /*                   Greek_eta η GREEK SMALL LETTER ETA */
-      {0x07e8, 0x03b8}, /*                 Greek_theta θ GREEK SMALL LETTER THETA */
-      {0x07e9, 0x03b9}, /*                  Greek_iota ι GREEK SMALL LETTER IOTA */
-      {0x07ea, 0x03ba}, /*                 Greek_kappa κ GREEK SMALL LETTER KAPPA */
-      {0x07eb, 0x03bb}, /*                Greek_lambda λ GREEK SMALL LETTER LAMDA */
-      {0x07ec, 0x03bc}, /*                    Greek_mu μ GREEK SMALL LETTER MU */
-      {0x07ed, 0x03bd}, /*                    Greek_nu ν GREEK SMALL LETTER NU */
-      {0x07ee, 0x03be}, /*                    Greek_xi ξ GREEK SMALL LETTER XI */
-      {0x07ef, 0x03bf}, /*               Greek_omicron ο GREEK SMALL LETTER OMICRON */
-      {0x07f0, 0x03c0}, /*                    Greek_pi π GREEK SMALL LETTER PI */
-      {0x07f1, 0x03c1}, /*                   Greek_rho ρ GREEK SMALL LETTER RHO */
-      {0x07f2, 0x03c3}, /*                 Greek_sigma σ GREEK SMALL LETTER SIGMA */
-      {0x07f3, 0x03c2}, /*       Greek_finalsmallsigma ς GREEK SMALL LETTER FINAL SIGMA */
-      {0x07f4, 0x03c4}, /*                   Greek_tau τ GREEK SMALL LETTER TAU */
-      {0x07f5, 0x03c5}, /*               Greek_upsilon υ GREEK SMALL LETTER UPSILON */
-      {0x07f6, 0x03c6}, /*                   Greek_phi φ GREEK SMALL LETTER PHI */
-      {0x07f7, 0x03c7}, /*                   Greek_chi χ GREEK SMALL LETTER CHI */
-      {0x07f8, 0x03c8}, /*                   Greek_psi ψ GREEK SMALL LETTER PSI */
-      {0x07f9, 0x03c9}, /*                 Greek_omega ω GREEK SMALL LETTER OMEGA */
-                        /*  0x08a1                               leftradical ? ??? */
-                        /*  0x08a2                            topleftradical ? ??? */
-                        /*  0x08a3                            horizconnector ? ??? */
-      {0x08a4, 0x2320}, /*                 topintegral ⌠ TOP HALF INTEGRAL */
-      {0x08a5, 0x2321}, /*                 botintegral ⌡ BOTTOM HALF INTEGRAL */
-      {0x08a6, 0x2502}, /*               vertconnector │ BOX DRAWINGS LIGHT VERTICAL */
-                        /*  0x08a7                          topleftsqbracket ? ??? */
-                        /*  0x08a8                          botleftsqbracket ? ??? */
-                        /*  0x08a9                         toprightsqbracket ? ??? */
-                        /*  0x08aa                         botrightsqbracket ? ??? */
-                        /*  0x08ab                             topleftparens ? ??? */
-                        /*  0x08ac                             botleftparens ? ??? */
-                        /*  0x08ad                            toprightparens ? ??? */
-                        /*  0x08ae                            botrightparens ? ??? */
-                        /*  0x08af                      leftmiddlecurlybrace ? ??? */
-                        /*  0x08b0                     rightmiddlecurlybrace ? ??? */
-                        /*  0x08b1                          topleftsummation ? ??? */
-                        /*  0x08b2                          botleftsummation ? ??? */
-                        /*  0x08b3                 topvertsummationconnector ? ??? */
-                        /*  0x08b4                 botvertsummationconnector ? ??? */
-                        /*  0x08b5                         toprightsummation ? ??? */
-                        /*  0x08b6                         botrightsummation ? ??? */
-                        /*  0x08b7                      rightmiddlesummation ? ??? */
-      {0x08bc, 0x2264}, /*               lessthanequal ≤ LESS-THAN OR EQUAL TO */
-      {0x08bd, 0x2260}, /*                    notequal ≠ NOT EQUAL TO */
-      {0x08be, 0x2265}, /*            greaterthanequal ≥ GREATER-THAN OR EQUAL TO */
-      {0x08bf, 0x222b}, /*                    integral ∫ INTEGRAL */
-      {0x08c0, 0x2234}, /*                   therefore ∴ THEREFORE */
-      {0x08c1, 0x221d}, /*                   variation ∝ PROPORTIONAL TO */
-      {0x08c2, 0x221e}, /*                    infinity ∞ INFINITY */
-      {0x08c5, 0x2207}, /*                       nabla ∇ NABLA */
-      {0x08c8, 0x2245}, /*                 approximate ≅ APPROXIMATELY EQUAL TO */
-                        /*  0x08c9                              similarequal ? ??? */
-      {0x08cd, 0x21d4}, /*                    ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */
-      {0x08ce, 0x21d2}, /*                     implies ⇒ RIGHTWARDS DOUBLE ARROW */
-      {0x08cf, 0x2261}, /*                   identical ≡ IDENTICAL TO */
-      {0x08d6, 0x221a}, /*                     radical √ SQUARE ROOT */
-      {0x08da, 0x2282}, /*                  includedin ⊂ SUBSET OF */
-      {0x08db, 0x2283}, /*                    includes ⊃ SUPERSET OF */
-      {0x08dc, 0x2229}, /*                intersection ∩ INTERSECTION */
-      {0x08dd, 0x222a}, /*                       union ∪ UNION */
-      {0x08de, 0x2227}, /*                  logicaland ∧ LOGICAL AND */
-      {0x08df, 0x2228}, /*                   logicalor ∨ LOGICAL OR */
-      {0x08ef, 0x2202}, /*           partialderivative ∂ PARTIAL DIFFERENTIAL */
-      {0x08f6, 0x0192}, /*                    function ƒ LATIN SMALL LETTER F WITH HOOK */
-      {0x08fb, 0x2190}, /*                   leftarrow ← LEFTWARDS ARROW */
-      {0x08fc, 0x2191}, /*                     uparrow ↑ UPWARDS ARROW */
-      {0x08fd, 0x2192}, /*                  rightarrow → RIGHTWARDS ARROW */
-      {0x08fe, 0x2193}, /*                   downarrow ↓ DOWNWARDS ARROW */
-      {0x09df, 0x2422}, /*                       blank ␢ BLANK SYMBOL */
-      {0x09e0, 0x25c6}, /*                soliddiamond ◆ BLACK DIAMOND */
-      {0x09e1, 0x2592}, /*                checkerboard ▒ MEDIUM SHADE */
-      {0x09e2, 0x2409}, /*                          ht ␉ SYMBOL FOR HORIZONTAL TABULATION */
-      {0x09e3, 0x240c}, /*                          ff ␌ SYMBOL FOR FORM FEED */
-      {0x09e4, 0x240d}, /*                          cr ␍ SYMBOL FOR CARRIAGE RETURN */
-      {0x09e5, 0x240a}, /*                          lf ␊ SYMBOL FOR LINE FEED */
-      {0x09e8, 0x2424}, /*                          nl ␤ SYMBOL FOR NEWLINE */
-      {0x09e9, 0x240b}, /*                          vt ␋ SYMBOL FOR VERTICAL TABULATION */
-      {0x09ea, 0x2518}, /*              lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */
-      {0x09eb, 0x2510}, /*               uprightcorner ┐ BOX DRAWINGS LIGHT DOWN AND LEFT */
-      {0x09ec, 0x250c}, /*                upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */
-      {0x09ed, 0x2514}, /*               lowleftcorner └ BOX DRAWINGS LIGHT UP AND RIGHT */
-      {0x09ee, 0x253c}, /*               crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
-                        /*  0x09ef                            horizlinescan1 ? ??? */
-                        /*  0x09f0                            horizlinescan3 ? ??? */
-      {0x09f1, 0x2500}, /*              horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */
-                        /*  0x09f2                            horizlinescan7 ? ??? */
-                        /*  0x09f3                            horizlinescan9 ? ??? */
-      {0x09f4, 0x251c}, /*                       leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
-      {0x09f5, 0x2524}, /*                      rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */
-      {0x09f6, 0x2534}, /*                        bott ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL */
-      {0x09f7, 0x252c}, /*                        topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
-      {0x09f8, 0x2502}, /*                     vertbar │ BOX DRAWINGS LIGHT VERTICAL */
-      {0x0aa1, 0x2003}, /*                     emspace   EM SPACE */
-      {0x0aa2, 0x2002}, /*                     enspace   EN SPACE */
-      {0x0aa3, 0x2004}, /*                    em3space   THREE-PER-EM SPACE */
-      {0x0aa4, 0x2005}, /*                    em4space   FOUR-PER-EM SPACE */
-      {0x0aa5, 0x2007}, /*                  digitspace   FIGURE SPACE */
-      {0x0aa6, 0x2008}, /*                  punctspace   PUNCTUATION SPACE */
-      {0x0aa7, 0x2009}, /*                   thinspace   THIN SPACE */
-      {0x0aa8, 0x200a}, /*                   hairspace   HAIR SPACE */
-      {0x0aa9, 0x2014}, /*                      emdash — EM DASH */
-      {0x0aaa, 0x2013}, /*                      endash – EN DASH */
-                        /*  0x0aac                               signifblank ? ??? */
-      {0x0aae, 0x2026}, /*                    ellipsis … HORIZONTAL ELLIPSIS */
-                        /*  0x0aaf                           doubbaselinedot ? ??? */
-      {0x0ab0, 0x2153}, /*                    onethird ⅓ VULGAR FRACTION ONE THIRD */
-      {0x0ab1, 0x2154}, /*                   twothirds ⅔ VULGAR FRACTION TWO THIRDS */
-      {0x0ab2, 0x2155}, /*                    onefifth ⅕ VULGAR FRACTION ONE FIFTH */
-      {0x0ab3, 0x2156}, /*                   twofifths ⅖ VULGAR FRACTION TWO FIFTHS */
-      {0x0ab4, 0x2157}, /*                 threefifths ⅗ VULGAR FRACTION THREE FIFTHS */
-      {0x0ab5, 0x2158}, /*                  fourfifths ⅘ VULGAR FRACTION FOUR FIFTHS */
-      {0x0ab6, 0x2159}, /*                    onesixth ⅙ VULGAR FRACTION ONE SIXTH */
-      {0x0ab7, 0x215a}, /*                  fivesixths ⅚ VULGAR FRACTION FIVE SIXTHS */
-      {0x0ab8, 0x2105}, /*                      careof ℅ CARE OF */
-      {0x0abb, 0x2012}, /*                     figdash ‒ FIGURE DASH */
-      {0x0abc, 0x2329}, /*            leftanglebracket 〈 LEFT-POINTING ANGLE BRACKET */
-      {0x0abd, 0x002e}, /*                decimalpoint . FULL STOP */
-      {0x0abe, 0x232a}, /*           rightanglebracket 〉 RIGHT-POINTING ANGLE BRACKET */
-                        /*  0x0abf                                    marker ? ??? */
-      {0x0ac3, 0x215b}, /*                   oneeighth ⅛ VULGAR FRACTION ONE EIGHTH */
-      {0x0ac4, 0x215c}, /*                threeeighths ⅜ VULGAR FRACTION THREE EIGHTHS */
-      {0x0ac5, 0x215d}, /*                 fiveeighths ⅝ VULGAR FRACTION FIVE EIGHTHS */
-      {0x0ac6, 0x215e}, /*                seveneighths ⅞ VULGAR FRACTION SEVEN EIGHTHS */
-      {0x0ac9, 0x2122}, /*                   trademark ™ TRADE MARK SIGN */
-      {0x0aca, 0x2613}, /*               signaturemark ☓ SALTIRE */
-                        /*  0x0acb                         trademarkincircle ? ??? */
-      {0x0acc, 0x25c1}, /*            leftopentriangle ◁ WHITE LEFT-POINTING TRIANGLE */
-      {0x0acd, 0x25b7}, /*           rightopentriangle ▷ WHITE RIGHT-POINTING TRIANGLE */
-      {0x0ace, 0x25cb}, /*                emopencircle ○ WHITE CIRCLE */
-      {0x0acf, 0x25a1}, /*             emopenrectangle □ WHITE SQUARE */
-      {0x0ad0, 0x2018}, /*         leftsinglequotemark ‘ LEFT SINGLE QUOTATION MARK */
-      {0x0ad1, 0x2019}, /*        rightsinglequotemark ’ RIGHT SINGLE QUOTATION MARK */
-      {0x0ad2, 0x201c}, /*         leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */
-      {0x0ad3, 0x201d}, /*        rightdoublequotemark ” RIGHT DOUBLE QUOTATION MARK */
-      {0x0ad4, 0x211e}, /*                prescription ℞ PRESCRIPTION TAKE */
-      {0x0ad6, 0x2032}, /*                     minutes ′ PRIME */
-      {0x0ad7, 0x2033}, /*                     seconds ″ DOUBLE PRIME */
-      {0x0ad9, 0x271d}, /*                  latincross ✝ LATIN CROSS */
-                        /*  0x0ada                                  hexagram ? ??? */
-      {0x0adb, 0x25ac}, /*            filledrectbullet ▬ BLACK RECTANGLE */
-      {0x0adc, 0x25c0}, /*         filledlefttribullet ◀ BLACK LEFT-POINTING TRIANGLE */
-      {0x0add, 0x25b6}, /*        filledrighttribullet ▶ BLACK RIGHT-POINTING TRIANGLE */
-      {0x0ade, 0x25cf}, /*              emfilledcircle ● BLACK CIRCLE */
-      {0x0adf, 0x25a0}, /*                emfilledrect ■ BLACK SQUARE */
-      {0x0ae0, 0x25e6}, /*            enopencircbullet ◦ WHITE BULLET */
-      {0x0ae1, 0x25ab}, /*          enopensquarebullet ▫ WHITE SMALL SQUARE */
-      {0x0ae2, 0x25ad}, /*              openrectbullet ▭ WHITE RECTANGLE */
-      {0x0ae3, 0x25b3}, /*             opentribulletup △ WHITE UP-POINTING TRIANGLE */
-      {0x0ae4, 0x25bd}, /*           opentribulletdown ▽ WHITE DOWN-POINTING TRIANGLE */
-      {0x0ae5, 0x2606}, /*                    openstar ☆ WHITE STAR */
-      {0x0ae6, 0x2022}, /*          enfilledcircbullet • BULLET */
-      {0x0ae7, 0x25aa}, /*            enfilledsqbullet ▪ BLACK SMALL SQUARE */
-      {0x0ae8, 0x25b2}, /*           filledtribulletup ▲ BLACK UP-POINTING TRIANGLE */
-      {0x0ae9, 0x25bc}, /*         filledtribulletdown ▼ BLACK DOWN-POINTING TRIANGLE */
-      {0x0aea, 0x261c}, /*                 leftpointer ☜ WHITE LEFT POINTING INDEX */
-      {0x0aeb, 0x261e}, /*                rightpointer ☞ WHITE RIGHT POINTING INDEX */
-      {0x0aec, 0x2663}, /*                        club ♣ BLACK CLUB SUIT */
-      {0x0aed, 0x2666}, /*                     diamond ♦ BLACK DIAMOND SUIT */
-      {0x0aee, 0x2665}, /*                       heart ♥ BLACK HEART SUIT */
-      {0x0af0, 0x2720}, /*                maltesecross ✠ MALTESE CROSS */
-      {0x0af1, 0x2020}, /*                      dagger † DAGGER */
-      {0x0af2, 0x2021}, /*                doubledagger ‡ DOUBLE DAGGER */
-      {0x0af3, 0x2713}, /*                   checkmark ✓ CHECK MARK */
-      {0x0af4, 0x2717}, /*                 ballotcross ✗ BALLOT X */
-      {0x0af5, 0x266f}, /*                musicalsharp ♯ MUSIC SHARP SIGN */
-      {0x0af6, 0x266d}, /*                 musicalflat ♭ MUSIC FLAT SIGN */
-      {0x0af7, 0x2642}, /*                  malesymbol ♂ MALE SIGN */
-      {0x0af8, 0x2640}, /*                femalesymbol ♀ FEMALE SIGN */
-      {0x0af9, 0x260e}, /*                   telephone ☎ BLACK TELEPHONE */
-      {0x0afa, 0x2315}, /*           telephonerecorder ⌕ TELEPHONE RECORDER */
-      {0x0afb, 0x2117}, /*         phonographcopyright ℗ SOUND RECORDING COPYRIGHT */
-      {0x0afc, 0x2038}, /*                       caret ‸ CARET */
-      {0x0afd, 0x201a}, /*          singlelowquotemark ‚ SINGLE LOW-9 QUOTATION MARK */
-      {0x0afe, 0x201e}, /*          doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */
-                        /*  0x0aff                                    cursor ? ??? */
-      {0x0ba3, 0x003c}, /*                   leftcaret < LESS-THAN SIGN */
-      {0x0ba6, 0x003e}, /*                  rightcaret > GREATER-THAN SIGN */
-      {0x0ba8, 0x2228}, /*                   downcaret ∨ LOGICAL OR */
-      {0x0ba9, 0x2227}, /*                     upcaret ∧ LOGICAL AND */
-      {0x0bc0, 0x00af}, /*                     overbar ¯ MACRON */
-      {0x0bc2, 0x22a4}, /*                    downtack ⊤ DOWN TACK */
-      {0x0bc3, 0x2229}, /*                      upshoe ∩ INTERSECTION */
-      {0x0bc4, 0x230a}, /*                   downstile ⌊ LEFT FLOOR */
-      {0x0bc6, 0x005f}, /*                    underbar _ LOW LINE */
-      {0x0bca, 0x2218}, /*                         jot ∘ RING OPERATOR */
-      {0x0bcc, 0x2395}, /*                        quad ⎕ APL FUNCTIONAL SYMBOL QUAD */
-      {0x0bce, 0x22a5}, /*                      uptack ⊥ UP TACK */
-      {0x0bcf, 0x25cb}, /*                      circle ○ WHITE CIRCLE */
-      {0x0bd3, 0x2308}, /*                     upstile ⌈ LEFT CEILING */
-      {0x0bd6, 0x222a}, /*                    downshoe ∪ UNION */
-      {0x0bd8, 0x2283}, /*                   rightshoe ⊃ SUPERSET OF */
-      {0x0bda, 0x2282}, /*                    leftshoe ⊂ SUBSET OF */
-      {0x0bdc, 0x22a3}, /*                    lefttack ⊣ LEFT TACK */
-      {0x0bfc, 0x22a2}, /*                   righttack ⊢ RIGHT TACK */
-      {0x0cdf, 0x2017}, /*        hebrew_doublelowline ‗ DOUBLE LOW LINE */
-      {0x0ce0, 0x05d0}, /*                hebrew_aleph א HEBREW LETTER ALEF */
-      {0x0ce1, 0x05d1}, /*                  hebrew_bet ב HEBREW LETTER BET */
-      {0x0ce2, 0x05d2}, /*                hebrew_gimel ג HEBREW LETTER GIMEL */
-      {0x0ce3, 0x05d3}, /*                hebrew_dalet ד HEBREW LETTER DALET */
-      {0x0ce4, 0x05d4}, /*                   hebrew_he ה HEBREW LETTER HE */
-      {0x0ce5, 0x05d5}, /*                  hebrew_waw ו HEBREW LETTER VAV */
-      {0x0ce6, 0x05d6}, /*                 hebrew_zain ז HEBREW LETTER ZAYIN */
-      {0x0ce7, 0x05d7}, /*                 hebrew_chet ח HEBREW LETTER HET */
-      {0x0ce8, 0x05d8}, /*                  hebrew_tet ט HEBREW LETTER TET */
-      {0x0ce9, 0x05d9}, /*                  hebrew_yod י HEBREW LETTER YOD */
-      {0x0cea, 0x05da}, /*            hebrew_finalkaph ך HEBREW LETTER FINAL KAF */
-      {0x0ceb, 0x05db}, /*                 hebrew_kaph כ HEBREW LETTER KAF */
-      {0x0cec, 0x05dc}, /*                hebrew_lamed ל HEBREW LETTER LAMED */
-      {0x0ced, 0x05dd}, /*             hebrew_finalmem ם HEBREW LETTER FINAL MEM */
-      {0x0cee, 0x05de}, /*                  hebrew_mem מ HEBREW LETTER MEM */
-      {0x0cef, 0x05df}, /*             hebrew_finalnun ן HEBREW LETTER FINAL NUN */
-      {0x0cf0, 0x05e0}, /*                  hebrew_nun נ HEBREW LETTER NUN */
-      {0x0cf1, 0x05e1}, /*               hebrew_samech ס HEBREW LETTER SAMEKH */
-      {0x0cf2, 0x05e2}, /*                 hebrew_ayin ע HEBREW LETTER AYIN */
-      {0x0cf3, 0x05e3}, /*              hebrew_finalpe ף HEBREW LETTER FINAL PE */
-      {0x0cf4, 0x05e4}, /*                   hebrew_pe פ HEBREW LETTER PE */
-      {0x0cf5, 0x05e5}, /*            hebrew_finalzade ץ HEBREW LETTER FINAL TSADI */
-      {0x0cf6, 0x05e6}, /*                 hebrew_zade צ HEBREW LETTER TSADI */
-      {0x0cf7, 0x05e7}, /*                 hebrew_qoph ק HEBREW LETTER QOF */
-      {0x0cf8, 0x05e8}, /*                 hebrew_resh ר HEBREW LETTER RESH */
-      {0x0cf9, 0x05e9}, /*                 hebrew_shin ש HEBREW LETTER SHIN */
-      {0x0cfa, 0x05ea}, /*                  hebrew_taw ת HEBREW LETTER TAV */
-      {0x0da1, 0x0e01}, /*                  Thai_kokai ก THAI CHARACTER KO KAI */
-      {0x0da2, 0x0e02}, /*                Thai_khokhai ข THAI CHARACTER KHO KHAI */
-      {0x0da3, 0x0e03}, /*               Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */
-      {0x0da4, 0x0e04}, /*               Thai_khokhwai ค THAI CHARACTER KHO KHWAI */
-      {0x0da5, 0x0e05}, /*                Thai_khokhon ฅ THAI CHARACTER KHO KHON */
-      {0x0da6, 0x0e06}, /*             Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */
-      {0x0da7, 0x0e07}, /*                 Thai_ngongu ง THAI CHARACTER NGO NGU */
-      {0x0da8, 0x0e08}, /*                Thai_chochan จ THAI CHARACTER CHO CHAN */
-      {0x0da9, 0x0e09}, /*               Thai_choching ฉ THAI CHARACTER CHO CHING */
-      {0x0daa, 0x0e0a}, /*               Thai_chochang ช THAI CHARACTER CHO CHANG */
-      {0x0dab, 0x0e0b}, /*                   Thai_soso ซ THAI CHARACTER SO SO */
-      {0x0dac, 0x0e0c}, /*                Thai_chochoe ฌ THAI CHARACTER CHO CHOE */
-      {0x0dad, 0x0e0d}, /*                 Thai_yoying ญ THAI CHARACTER YO YING */
-      {0x0dae, 0x0e0e}, /*                Thai_dochada ฎ THAI CHARACTER DO CHADA */
-      {0x0daf, 0x0e0f}, /*                Thai_topatak ฏ THAI CHARACTER TO PATAK */
-      {0x0db0, 0x0e10}, /*                Thai_thothan ฐ THAI CHARACTER THO THAN */
-      {0x0db1, 0x0e11}, /*          Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */
-      {0x0db2, 0x0e12}, /*             Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */
-      {0x0db3, 0x0e13}, /*                  Thai_nonen ณ THAI CHARACTER NO NEN */
-      {0x0db4, 0x0e14}, /*                  Thai_dodek ด THAI CHARACTER DO DEK */
-      {0x0db5, 0x0e15}, /*                  Thai_totao ต THAI CHARACTER TO TAO */
-      {0x0db6, 0x0e16}, /*               Thai_thothung ถ THAI CHARACTER THO THUNG */
-      {0x0db7, 0x0e17}, /*              Thai_thothahan ท THAI CHARACTER THO THAHAN */
-      {0x0db8, 0x0e18}, /*               Thai_thothong ธ THAI CHARACTER THO THONG */
-      {0x0db9, 0x0e19}, /*                   Thai_nonu น THAI CHARACTER NO NU */
-      {0x0dba, 0x0e1a}, /*               Thai_bobaimai บ THAI CHARACTER BO BAIMAI */
-      {0x0dbb, 0x0e1b}, /*                  Thai_popla ป THAI CHARACTER PO PLA */
-      {0x0dbc, 0x0e1c}, /*               Thai_phophung ผ THAI CHARACTER PHO PHUNG */
-      {0x0dbd, 0x0e1d}, /*                   Thai_fofa ฝ THAI CHARACTER FO FA */
-      {0x0dbe, 0x0e1e}, /*                Thai_phophan พ THAI CHARACTER PHO PHAN */
-      {0x0dbf, 0x0e1f}, /*                  Thai_fofan ฟ THAI CHARACTER FO FAN */
-      {0x0dc0, 0x0e20}, /*             Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */
-      {0x0dc1, 0x0e21}, /*                   Thai_moma ม THAI CHARACTER MO MA */
-      {0x0dc2, 0x0e22}, /*                  Thai_yoyak ย THAI CHARACTER YO YAK */
-      {0x0dc3, 0x0e23}, /*                  Thai_rorua ร THAI CHARACTER RO RUA */
-      {0x0dc4, 0x0e24}, /*                     Thai_ru ฤ THAI CHARACTER RU */
-      {0x0dc5, 0x0e25}, /*                 Thai_loling ล THAI CHARACTER LO LING */
-      {0x0dc6, 0x0e26}, /*                     Thai_lu ฦ THAI CHARACTER LU */
-      {0x0dc7, 0x0e27}, /*                 Thai_wowaen ว THAI CHARACTER WO WAEN */
-      {0x0dc8, 0x0e28}, /*                 Thai_sosala ศ THAI CHARACTER SO SALA */
-      {0x0dc9, 0x0e29}, /*                 Thai_sorusi ษ THAI CHARACTER SO RUSI */
-      {0x0dca, 0x0e2a}, /*                  Thai_sosua ส THAI CHARACTER SO SUA */
-      {0x0dcb, 0x0e2b}, /*                  Thai_hohip ห THAI CHARACTER HO HIP */
-      {0x0dcc, 0x0e2c}, /*                Thai_lochula ฬ THAI CHARACTER LO CHULA */
-      {0x0dcd, 0x0e2d}, /*                   Thai_oang อ THAI CHARACTER O ANG */
-      {0x0dce, 0x0e2e}, /*               Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */
-      {0x0dcf, 0x0e2f}, /*              Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */
-      {0x0dd0, 0x0e30}, /*                  Thai_saraa ะ THAI CHARACTER SARA A */
-      {0x0dd1, 0x0e31}, /*             Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */
-      {0x0dd2, 0x0e32}, /*                 Thai_saraaa า THAI CHARACTER SARA AA */
-      {0x0dd3, 0x0e33}, /*                 Thai_saraam ำ THAI CHARACTER SARA AM */
-      {0x0dd4, 0x0e34}, /*                  Thai_sarai ิ THAI CHARACTER SARA I */
-      {0x0dd5, 0x0e35}, /*                 Thai_saraii ี THAI CHARACTER SARA II */
-      {0x0dd6, 0x0e36}, /*                 Thai_saraue ึ THAI CHARACTER SARA UE */
-      {0x0dd7, 0x0e37}, /*                Thai_sarauee ื THAI CHARACTER SARA UEE */
-      {0x0dd8, 0x0e38}, /*                  Thai_sarau ุ THAI CHARACTER SARA U */
-      {0x0dd9, 0x0e39}, /*                 Thai_sarauu ู THAI CHARACTER SARA UU */
-      {0x0dda, 0x0e3a}, /*                Thai_phinthu ฺ THAI CHARACTER PHINTHU */
-      {0x0dde, 0x0e3e}, /*      Thai_maihanakat_maitho ฾ ??? */
-      {0x0ddf, 0x0e3f}, /*                   Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */
-      {0x0de0, 0x0e40}, /*                  Thai_sarae เ THAI CHARACTER SARA E */
-      {0x0de1, 0x0e41}, /*                 Thai_saraae แ THAI CHARACTER SARA AE */
-      {0x0de2, 0x0e42}, /*                  Thai_sarao โ THAI CHARACTER SARA O */
-      {0x0de3, 0x0e43}, /*          Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */
-      {0x0de4, 0x0e44}, /*         Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */
-      {0x0de5, 0x0e45}, /*            Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */
-      {0x0de6, 0x0e46}, /*               Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */
-      {0x0de7, 0x0e47}, /*              Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */
-      {0x0de8, 0x0e48}, /*                  Thai_maiek ่ THAI CHARACTER MAI EK */
-      {0x0de9, 0x0e49}, /*                 Thai_maitho ้ THAI CHARACTER MAI THO */
-      {0x0dea, 0x0e4a}, /*                 Thai_maitri ๊ THAI CHARACTER MAI TRI */
-      {0x0deb, 0x0e4b}, /*            Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */
-      {0x0dec, 0x0e4c}, /*            Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */
-      {0x0ded, 0x0e4d}, /*               Thai_nikhahit ํ THAI CHARACTER NIKHAHIT */
-      {0x0df0, 0x0e50}, /*                 Thai_leksun ๐ THAI DIGIT ZERO */
-      {0x0df1, 0x0e51}, /*                Thai_leknung ๑ THAI DIGIT ONE */
-      {0x0df2, 0x0e52}, /*                Thai_leksong ๒ THAI DIGIT TWO */
-      {0x0df3, 0x0e53}, /*                 Thai_leksam ๓ THAI DIGIT THREE */
-      {0x0df4, 0x0e54}, /*                  Thai_leksi ๔ THAI DIGIT FOUR */
-      {0x0df5, 0x0e55}, /*                  Thai_lekha ๕ THAI DIGIT FIVE */
-      {0x0df6, 0x0e56}, /*                 Thai_lekhok ๖ THAI DIGIT SIX */
-      {0x0df7, 0x0e57}, /*                Thai_lekchet ๗ THAI DIGIT SEVEN */
-      {0x0df8, 0x0e58}, /*                Thai_lekpaet ๘ THAI DIGIT EIGHT */
-      {0x0df9, 0x0e59}, /*                 Thai_lekkao ๙ THAI DIGIT NINE */
-      {0x0ea1, 0x3131}, /*               Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */
-      {0x0ea2, 0x3132}, /*          Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */
-      {0x0ea3, 0x3133}, /*           Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */
-      {0x0ea4, 0x3134}, /*                Hangul_Nieun ㄴ HANGUL LETTER NIEUN */
-      {0x0ea5, 0x3135}, /*           Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */
-      {0x0ea6, 0x3136}, /*           Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */
-      {0x0ea7, 0x3137}, /*               Hangul_Dikeud ㄷ HANGUL LETTER TIKEUT */
-      {0x0ea8, 0x3138}, /*          Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */
-      {0x0ea9, 0x3139}, /*                Hangul_Rieul ㄹ HANGUL LETTER RIEUL */
-      {0x0eaa, 0x313a}, /*          Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */
-      {0x0eab, 0x313b}, /*           Hangul_RieulMieum ㄻ HANGUL LETTER RIEUL-MIEUM */
-      {0x0eac, 0x313c}, /*           Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */
-      {0x0ead, 0x313d}, /*            Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */
-      {0x0eae, 0x313e}, /*           Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */
-      {0x0eaf, 0x313f}, /*          Hangul_RieulPhieuf ㄿ HANGUL LETTER RIEUL-PHIEUPH */
-      {0x0eb0, 0x3140}, /*           Hangul_RieulHieuh ㅀ HANGUL LETTER RIEUL-HIEUH */
-      {0x0eb1, 0x3141}, /*                Hangul_Mieum ㅁ HANGUL LETTER MIEUM */
-      {0x0eb2, 0x3142}, /*                Hangul_Pieub ㅂ HANGUL LETTER PIEUP */
-      {0x0eb3, 0x3143}, /*           Hangul_SsangPieub ㅃ HANGUL LETTER SSANGPIEUP */
-      {0x0eb4, 0x3144}, /*            Hangul_PieubSios ㅄ HANGUL LETTER PIEUP-SIOS */
-      {0x0eb5, 0x3145}, /*                 Hangul_Sios ㅅ HANGUL LETTER SIOS */
-      {0x0eb6, 0x3146}, /*            Hangul_SsangSios ㅆ HANGUL LETTER SSANGSIOS */
-      {0x0eb7, 0x3147}, /*                Hangul_Ieung ㅇ HANGUL LETTER IEUNG */
-      {0x0eb8, 0x3148}, /*                Hangul_Jieuj ㅈ HANGUL LETTER CIEUC */
-      {0x0eb9, 0x3149}, /*           Hangul_SsangJieuj ㅉ HANGUL LETTER SSANGCIEUC */
-      {0x0eba, 0x314a}, /*                Hangul_Cieuc ㅊ HANGUL LETTER CHIEUCH */
-      {0x0ebb, 0x314b}, /*               Hangul_Khieuq ㅋ HANGUL LETTER KHIEUKH */
-      {0x0ebc, 0x314c}, /*                Hangul_Tieut ㅌ HANGUL LETTER THIEUTH */
-      {0x0ebd, 0x314d}, /*               Hangul_Phieuf ㅍ HANGUL LETTER PHIEUPH */
-      {0x0ebe, 0x314e}, /*                Hangul_Hieuh ㅎ HANGUL LETTER HIEUH */
-      {0x0ebf, 0x314f}, /*                    Hangul_A ㅏ HANGUL LETTER A */
-      {0x0ec0, 0x3150}, /*                   Hangul_AE ㅐ HANGUL LETTER AE */
-      {0x0ec1, 0x3151}, /*                   Hangul_YA ㅑ HANGUL LETTER YA */
-      {0x0ec2, 0x3152}, /*                  Hangul_YAE ㅒ HANGUL LETTER YAE */
-      {0x0ec3, 0x3153}, /*                   Hangul_EO ㅓ HANGUL LETTER EO */
-      {0x0ec4, 0x3154}, /*                    Hangul_E ㅔ HANGUL LETTER E */
-      {0x0ec5, 0x3155}, /*                  Hangul_YEO ㅕ HANGUL LETTER YEO */
-      {0x0ec6, 0x3156}, /*                   Hangul_YE ㅖ HANGUL LETTER YE */
-      {0x0ec7, 0x3157}, /*                    Hangul_O ㅗ HANGUL LETTER O */
-      {0x0ec8, 0x3158}, /*                   Hangul_WA ㅘ HANGUL LETTER WA */
-      {0x0ec9, 0x3159}, /*                  Hangul_WAE ㅙ HANGUL LETTER WAE */
-      {0x0eca, 0x315a}, /*                   Hangul_OE ㅚ HANGUL LETTER OE */
-      {0x0ecb, 0x315b}, /*                   Hangul_YO ㅛ HANGUL LETTER YO */
-      {0x0ecc, 0x315c}, /*                    Hangul_U ㅜ HANGUL LETTER U */
-      {0x0ecd, 0x315d}, /*                  Hangul_WEO ㅝ HANGUL LETTER WEO */
-      {0x0ece, 0x315e}, /*                   Hangul_WE ㅞ HANGUL LETTER WE */
-      {0x0ecf, 0x315f}, /*                   Hangul_WI ㅟ HANGUL LETTER WI */
-      {0x0ed0, 0x3160}, /*                   Hangul_YU ㅠ HANGUL LETTER YU */
-      {0x0ed1, 0x3161}, /*                   Hangul_EU ㅡ HANGUL LETTER EU */
-      {0x0ed2, 0x3162}, /*                   Hangul_YI ㅢ HANGUL LETTER YI */
-      {0x0ed3, 0x3163}, /*                    Hangul_I ㅣ HANGUL LETTER I */
-      {0x0ed4, 0x11a8}, /*             Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */
-      {0x0ed5, 0x11a9}, /*        Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */
-      {0x0ed6, 0x11aa}, /*         Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */
-      {0x0ed7, 0x11ab}, /*              Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */
-      {0x0ed8, 0x11ac}, /*         Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */
-      {0x0ed9, 0x11ad}, /*         Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */
-      {0x0eda, 0x11ae}, /*             Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */
-      {0x0edb, 0x11af}, /*              Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */
-      {0x0edc, 0x11b0}, /*        Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */
-      {0x0edd, 0x11b1}, /*         Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */
-      {0x0ede, 0x11b2}, /*         Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */
-      {0x0edf, 0x11b3}, /*          Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */
-      {0x0ee0, 0x11b4}, /*         Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */
-      {0x0ee1, 0x11b5}, /*        Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */
-      {0x0ee2, 0x11b6}, /*         Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */
-      {0x0ee3, 0x11b7}, /*              Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */
-      {0x0ee4, 0x11b8}, /*              Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */
-      {0x0ee5, 0x11b9}, /*          Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */
-      {0x0ee6, 0x11ba}, /*               Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */
-      {0x0ee7, 0x11bb}, /*          Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */
-      {0x0ee8, 0x11bc}, /*              Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */
-      {0x0ee9, 0x11bd}, /*              Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */
-      {0x0eea, 0x11be}, /*              Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */
-      {0x0eeb, 0x11bf}, /*             Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */
-      {0x0eec, 0x11c0}, /*              Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */
-      {0x0eed, 0x11c1}, /*             Hangul_J_Phieuf ᇁ HANGUL JONGSEONG PHIEUPH */
-      {0x0eee, 0x11c2}, /*              Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */
-      {0x0eef, 0x316d}, /*     Hangul_RieulYeorinHieuh ㅭ HANGUL LETTER RIEUL-YEORINHIEUH */
-      {0x0ef0, 0x3171}, /*    Hangul_SunkyeongeumMieum ㅱ HANGUL LETTER KAPYEOUNMIEUM */
-      {0x0ef1, 0x3178}, /*    Hangul_SunkyeongeumPieub ㅸ HANGUL LETTER KAPYEOUNPIEUP */
-      {0x0ef2, 0x317f}, /*              Hangul_PanSios ㅿ HANGUL LETTER PANSIOS */
-                        /*  0x0ef3                  Hangul_KkogjiDalrinIeung ? ??? */
-      {0x0ef4, 0x3184}, /*   Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */
-      {0x0ef5, 0x3186}, /*          Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */
-      {0x0ef6, 0x318d}, /*                Hangul_AraeA ㆍ HANGUL LETTER ARAEA */
-      {0x0ef7, 0x318e}, /*               Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */
-      {0x0ef8, 0x11eb}, /*            Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */
-                        /*  0x0ef9                Hangul_J_KkogjiDalrinIeung ? ??? */
-      {0x0efa, 0x11f9}, /*        Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */
-      {0x0eff, 0x20a9}, /*                  Korean_Won ₩ WON SIGN */
-      {0x13bc, 0x0152}, /*                          OE Œ LATIN CAPITAL LIGATURE OE */
-      {0x13bd, 0x0153}, /*                          oe œ LATIN SMALL LIGATURE OE */
-      {0x13be, 0x0178}, /*                  Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */
-      {0x20ac, 0x20ac}, /*                    EuroSign € EURO SIGN */
+    {0x01a1, 0x0104}, /*                     Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */
+    {0x01a2, 0x02d8}, /*                       breve ˘ BREVE */
+    {0x01a3, 0x0141}, /*                     Lstroke Ł LATIN CAPITAL LETTER L WITH STROKE */
+    {0x01a5, 0x013d}, /*                      Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */
+    {0x01a6, 0x015a}, /*                      Sacute Ś LATIN CAPITAL LETTER S WITH ACUTE */
+    {0x01a9, 0x0160}, /*                      Scaron Š LATIN CAPITAL LETTER S WITH CARON */
+    {0x01aa, 0x015e}, /*                    Scedilla Ş LATIN CAPITAL LETTER S WITH CEDILLA */
+    {0x01ab, 0x0164}, /*                      Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */
+    {0x01ac, 0x0179}, /*                      Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */
+    {0x01ae, 0x017d}, /*                      Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */
+    {0x01af, 0x017b}, /*                   Zabovedot Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE */
+    {0x01b1, 0x0105}, /*                     aogonek ą LATIN SMALL LETTER A WITH OGONEK */
+    {0x01b2, 0x02db}, /*                      ogonek ˛ OGONEK */
+    {0x01b3, 0x0142}, /*                     lstroke ł LATIN SMALL LETTER L WITH STROKE */
+    {0x01b5, 0x013e}, /*                      lcaron ľ LATIN SMALL LETTER L WITH CARON */
+    {0x01b6, 0x015b}, /*                      sacute ś LATIN SMALL LETTER S WITH ACUTE */
+    {0x01b7, 0x02c7}, /*                       caron ˇ CARON */
+    {0x01b9, 0x0161}, /*                      scaron š LATIN SMALL LETTER S WITH CARON */
+    {0x01ba, 0x015f}, /*                    scedilla ş LATIN SMALL LETTER S WITH CEDILLA */
+    {0x01bb, 0x0165}, /*                      tcaron ť LATIN SMALL LETTER T WITH CARON */
+    {0x01bc, 0x017a}, /*                      zacute ź LATIN SMALL LETTER Z WITH ACUTE */
+    {0x01bd, 0x02dd}, /*                 doubleacute ˝ DOUBLE ACUTE ACCENT */
+    {0x01be, 0x017e}, /*                      zcaron ž LATIN SMALL LETTER Z WITH CARON */
+    {0x01bf, 0x017c}, /*                   zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */
+    {0x01c0, 0x0154}, /*                      Racute Ŕ LATIN CAPITAL LETTER R WITH ACUTE */
+    {0x01c3, 0x0102}, /*                      Abreve Ă LATIN CAPITAL LETTER A WITH BREVE */
+    {0x01c5, 0x0139}, /*                      Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */
+    {0x01c6, 0x0106}, /*                      Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */
+    {0x01c8, 0x010c}, /*                      Ccaron Č LATIN CAPITAL LETTER C WITH CARON */
+    {0x01ca, 0x0118}, /*                     Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */
+    {0x01cc, 0x011a}, /*                      Ecaron Ě LATIN CAPITAL LETTER E WITH CARON */
+    {0x01cf, 0x010e}, /*                      Dcaron Ď LATIN CAPITAL LETTER D WITH CARON */
+    {0x01d0, 0x0110}, /*                     Dstroke Đ LATIN CAPITAL LETTER D WITH STROKE */
+    {0x01d1, 0x0143}, /*                      Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */
+    {0x01d2, 0x0147}, /*                      Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */
+    {0x01d5, 0x0150}, /*                Odoubleacute Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */
+    {0x01d8, 0x0158}, /*                      Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */
+    {0x01d9, 0x016e}, /*                       Uring Ů LATIN CAPITAL LETTER U WITH RING ABOVE */
+    {0x01db, 0x0170}, /*                Udoubleacute Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */
+    {0x01de, 0x0162}, /*                    Tcedilla Ţ LATIN CAPITAL LETTER T WITH CEDILLA */
+    {0x01e0, 0x0155}, /*                      racute ŕ LATIN SMALL LETTER R WITH ACUTE */
+    {0x01e3, 0x0103}, /*                      abreve ă LATIN SMALL LETTER A WITH BREVE */
+    {0x01e5, 0x013a}, /*                      lacute ĺ LATIN SMALL LETTER L WITH ACUTE */
+    {0x01e6, 0x0107}, /*                      cacute ć LATIN SMALL LETTER C WITH ACUTE */
+    {0x01e8, 0x010d}, /*                      ccaron č LATIN SMALL LETTER C WITH CARON */
+    {0x01ea, 0x0119}, /*                     eogonek ę LATIN SMALL LETTER E WITH OGONEK */
+    {0x01ec, 0x011b}, /*                      ecaron ě LATIN SMALL LETTER E WITH CARON */
+    {0x01ef, 0x010f}, /*                      dcaron ď LATIN SMALL LETTER D WITH CARON */
+    {0x01f0, 0x0111}, /*                     dstroke đ LATIN SMALL LETTER D WITH STROKE */
+    {0x01f1, 0x0144}, /*                      nacute ń LATIN SMALL LETTER N WITH ACUTE */
+    {0x01f2, 0x0148}, /*                      ncaron ň LATIN SMALL LETTER N WITH CARON */
+    {0x01f5, 0x0151}, /*                odoubleacute ő LATIN SMALL LETTER O WITH DOUBLE ACUTE */
+    {0x01f8, 0x0159}, /*                      rcaron ř LATIN SMALL LETTER R WITH CARON */
+    {0x01f9, 0x016f}, /*                       uring ů LATIN SMALL LETTER U WITH RING ABOVE */
+    {0x01fb, 0x0171}, /*                udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */
+    {0x01fe, 0x0163}, /*                    tcedilla ţ LATIN SMALL LETTER T WITH CEDILLA */
+    {0x01ff, 0x02d9}, /*                    abovedot ˙ DOT ABOVE */
+    {0x02a1, 0x0126}, /*                     Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */
+    {0x02a6, 0x0124}, /*                 Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */
+    {0x02a9, 0x0130}, /*                   Iabovedot İ LATIN CAPITAL LETTER I WITH DOT ABOVE */
+    {0x02ab, 0x011e}, /*                      Gbreve Ğ LATIN CAPITAL LETTER G WITH BREVE */
+    {0x02ac, 0x0134}, /*                 Jcircumflex Ĵ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */
+    {0x02b1, 0x0127}, /*                     hstroke ħ LATIN SMALL LETTER H WITH STROKE */
+    {0x02b6, 0x0125}, /*                 hcircumflex ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX */
+    {0x02b9, 0x0131}, /*                    idotless ı LATIN SMALL LETTER DOTLESS I */
+    {0x02bb, 0x011f}, /*                      gbreve ğ LATIN SMALL LETTER G WITH BREVE */
+    {0x02bc, 0x0135}, /*                 jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */
+    {0x02c5, 0x010a}, /*                   Cabovedot Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE */
+    {0x02c6, 0x0108}, /*                 Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */
+    {0x02d5, 0x0120}, /*                   Gabovedot Ġ LATIN CAPITAL LETTER G WITH DOT ABOVE */
+    {0x02d8, 0x011c}, /*                 Gcircumflex Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */
+    {0x02dd, 0x016c}, /*                      Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */
+    {0x02de, 0x015c}, /*                 Scircumflex Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */
+    {0x02e5, 0x010b}, /*                   cabovedot ċ LATIN SMALL LETTER C WITH DOT ABOVE */
+    {0x02e6, 0x0109}, /*                 ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */
+    {0x02f5, 0x0121}, /*                   gabovedot ġ LATIN SMALL LETTER G WITH DOT ABOVE */
+    {0x02f8, 0x011d}, /*                 gcircumflex ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX */
+    {0x02fd, 0x016d}, /*                      ubreve ŭ LATIN SMALL LETTER U WITH BREVE */
+    {0x02fe, 0x015d}, /*                 scircumflex ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX */
+    {0x03a2, 0x0138}, /*                         kra ĸ LATIN SMALL LETTER KRA */
+    {0x03a3, 0x0156}, /*                    Rcedilla Ŗ LATIN CAPITAL LETTER R WITH CEDILLA */
+    {0x03a5, 0x0128}, /*                      Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */
+    {0x03a6, 0x013b}, /*                    Lcedilla Ļ LATIN CAPITAL LETTER L WITH CEDILLA */
+    {0x03aa, 0x0112}, /*                     Emacron Ē LATIN CAPITAL LETTER E WITH MACRON */
+    {0x03ab, 0x0122}, /*                    Gcedilla Ģ LATIN CAPITAL LETTER G WITH CEDILLA */
+    {0x03ac, 0x0166}, /*                      Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */
+    {0x03b3, 0x0157}, /*                    rcedilla ŗ LATIN SMALL LETTER R WITH CEDILLA */
+    {0x03b5, 0x0129}, /*                      itilde ĩ LATIN SMALL LETTER I WITH TILDE */
+    {0x03b6, 0x013c}, /*                    lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */
+    {0x03ba, 0x0113}, /*                     emacron ē LATIN SMALL LETTER E WITH MACRON */
+    {0x03bb, 0x0123}, /*                    gcedilla ģ LATIN SMALL LETTER G WITH CEDILLA */
+    {0x03bc, 0x0167}, /*                      tslash ŧ LATIN SMALL LETTER T WITH STROKE */
+    {0x03bd, 0x014a}, /*                         ENG Ŋ LATIN CAPITAL LETTER ENG */
+    {0x03bf, 0x014b}, /*                         eng ŋ LATIN SMALL LETTER ENG */
+    {0x03c0, 0x0100}, /*                     Amacron Ā LATIN CAPITAL LETTER A WITH MACRON */
+    {0x03c7, 0x012e}, /*                     Iogonek Į LATIN CAPITAL LETTER I WITH OGONEK */
+    {0x03cc, 0x0116}, /*                   Eabovedot Ė LATIN CAPITAL LETTER E WITH DOT ABOVE */
+    {0x03cf, 0x012a}, /*                     Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */
+    {0x03d1, 0x0145}, /*                    Ncedilla Ņ LATIN CAPITAL LETTER N WITH CEDILLA */
+    {0x03d2, 0x014c}, /*                     Omacron Ō LATIN CAPITAL LETTER O WITH MACRON */
+    {0x03d3, 0x0136}, /*                    Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */
+    {0x03d9, 0x0172}, /*                     Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */
+    {0x03dd, 0x0168}, /*                      Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */
+    {0x03de, 0x016a}, /*                     Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */
+    {0x03e0, 0x0101}, /*                     amacron ā LATIN SMALL LETTER A WITH MACRON */
+    {0x03e7, 0x012f}, /*                     iogonek į LATIN SMALL LETTER I WITH OGONEK */
+    {0x03ec, 0x0117}, /*                   eabovedot ė LATIN SMALL LETTER E WITH DOT ABOVE */
+    {0x03ef, 0x012b}, /*                     imacron ī LATIN SMALL LETTER I WITH MACRON */
+    {0x03f1, 0x0146}, /*                    ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */
+    {0x03f2, 0x014d}, /*                     omacron ō LATIN SMALL LETTER O WITH MACRON */
+    {0x03f3, 0x0137}, /*                    kcedilla ķ LATIN SMALL LETTER K WITH CEDILLA */
+    {0x03f9, 0x0173}, /*                     uogonek ų LATIN SMALL LETTER U WITH OGONEK */
+    {0x03fd, 0x0169}, /*                      utilde ũ LATIN SMALL LETTER U WITH TILDE */
+    {0x03fe, 0x016b}, /*                     umacron ū LATIN SMALL LETTER U WITH MACRON */
+    {0x047e, 0x203e}, /*                    overline ‾ OVERLINE */
+    {0x04a1, 0x3002}, /*               kana_fullstop 。 IDEOGRAPHIC FULL STOP */
+    {0x04a2, 0x300c}, /*         kana_openingbracket 「 LEFT CORNER BRACKET */
+    {0x04a3, 0x300d}, /*         kana_closingbracket 」 RIGHT CORNER BRACKET */
+    {0x04a4, 0x3001}, /*                  kana_comma 、 IDEOGRAPHIC COMMA */
+    {0x04a5, 0x30fb}, /*            kana_conjunctive ・ KATAKANA MIDDLE DOT */
+    {0x04a6, 0x30f2}, /*                     kana_WO ヲ KATAKANA LETTER WO */
+    {0x04a7, 0x30a1}, /*                      kana_a ァ KATAKANA LETTER SMALL A */
+    {0x04a8, 0x30a3}, /*                      kana_i ィ KATAKANA LETTER SMALL I */
+    {0x04a9, 0x30a5}, /*                      kana_u ゥ KATAKANA LETTER SMALL U */
+    {0x04aa, 0x30a7}, /*                      kana_e ェ KATAKANA LETTER SMALL E */
+    {0x04ab, 0x30a9}, /*                      kana_o ォ KATAKANA LETTER SMALL O */
+    {0x04ac, 0x30e3}, /*                     kana_ya ャ KATAKANA LETTER SMALL YA */
+    {0x04ad, 0x30e5}, /*                     kana_yu ュ KATAKANA LETTER SMALL YU */
+    {0x04ae, 0x30e7}, /*                     kana_yo ョ KATAKANA LETTER SMALL YO */
+    {0x04af, 0x30c3}, /*                    kana_tsu ッ KATAKANA LETTER SMALL TU */
+    {0x04b0, 0x30fc}, /*              prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */
+    {0x04b1, 0x30a2}, /*                      kana_A ア KATAKANA LETTER A */
+    {0x04b2, 0x30a4}, /*                      kana_I イ KATAKANA LETTER I */
+    {0x04b3, 0x30a6}, /*                      kana_U ウ KATAKANA LETTER U */
+    {0x04b4, 0x30a8}, /*                      kana_E エ KATAKANA LETTER E */
+    {0x04b5, 0x30aa}, /*                      kana_O オ KATAKANA LETTER O */
+    {0x04b6, 0x30ab}, /*                     kana_KA カ KATAKANA LETTER KA */
+    {0x04b7, 0x30ad}, /*                     kana_KI キ KATAKANA LETTER KI */
+    {0x04b8, 0x30af}, /*                     kana_KU ク KATAKANA LETTER KU */
+    {0x04b9, 0x30b1}, /*                     kana_KE ケ KATAKANA LETTER KE */
+    {0x04ba, 0x30b3}, /*                     kana_KO コ KATAKANA LETTER KO */
+    {0x04bb, 0x30b5}, /*                     kana_SA サ KATAKANA LETTER SA */
+    {0x04bc, 0x30b7}, /*                    kana_SHI シ KATAKANA LETTER SI */
+    {0x04bd, 0x30b9}, /*                     kana_SU ス KATAKANA LETTER SU */
+    {0x04be, 0x30bb}, /*                     kana_SE セ KATAKANA LETTER SE */
+    {0x04bf, 0x30bd}, /*                     kana_SO ソ KATAKANA LETTER SO */
+    {0x04c0, 0x30bf}, /*                     kana_TA タ KATAKANA LETTER TA */
+    {0x04c1, 0x30c1}, /*                    kana_CHI チ KATAKANA LETTER TI */
+    {0x04c2, 0x30c4}, /*                    kana_TSU ツ KATAKANA LETTER TU */
+    {0x04c3, 0x30c6}, /*                     kana_TE テ KATAKANA LETTER TE */
+    {0x04c4, 0x30c8}, /*                     kana_TO ト KATAKANA LETTER TO */
+    {0x04c5, 0x30ca}, /*                     kana_NA ナ KATAKANA LETTER NA */
+    {0x04c6, 0x30cb}, /*                     kana_NI ニ KATAKANA LETTER NI */
+    {0x04c7, 0x30cc}, /*                     kana_NU ヌ KATAKANA LETTER NU */
+    {0x04c8, 0x30cd}, /*                     kana_NE ネ KATAKANA LETTER NE */
+    {0x04c9, 0x30ce}, /*                     kana_NO ノ KATAKANA LETTER NO */
+    {0x04ca, 0x30cf}, /*                     kana_HA ハ KATAKANA LETTER HA */
+    {0x04cb, 0x30d2}, /*                     kana_HI ヒ KATAKANA LETTER HI */
+    {0x04cc, 0x30d5}, /*                     kana_FU フ KATAKANA LETTER HU */
+    {0x04cd, 0x30d8}, /*                     kana_HE ヘ KATAKANA LETTER HE */
+    {0x04ce, 0x30db}, /*                     kana_HO ホ KATAKANA LETTER HO */
+    {0x04cf, 0x30de}, /*                     kana_MA マ KATAKANA LETTER MA */
+    {0x04d0, 0x30df}, /*                     kana_MI ミ KATAKANA LETTER MI */
+    {0x04d1, 0x30e0}, /*                     kana_MU ム KATAKANA LETTER MU */
+    {0x04d2, 0x30e1}, /*                     kana_ME メ KATAKANA LETTER ME */
+    {0x04d3, 0x30e2}, /*                     kana_MO モ KATAKANA LETTER MO */
+    {0x04d4, 0x30e4}, /*                     kana_YA ヤ KATAKANA LETTER YA */
+    {0x04d5, 0x30e6}, /*                     kana_YU ユ KATAKANA LETTER YU */
+    {0x04d6, 0x30e8}, /*                     kana_YO ヨ KATAKANA LETTER YO */
+    {0x04d7, 0x30e9}, /*                     kana_RA ラ KATAKANA LETTER RA */
+    {0x04d8, 0x30ea}, /*                     kana_RI リ KATAKANA LETTER RI */
+    {0x04d9, 0x30eb}, /*                     kana_RU ル KATAKANA LETTER RU */
+    {0x04da, 0x30ec}, /*                     kana_RE レ KATAKANA LETTER RE */
+    {0x04db, 0x30ed}, /*                     kana_RO ロ KATAKANA LETTER RO */
+    {0x04dc, 0x30ef}, /*                     kana_WA ワ KATAKANA LETTER WA */
+    {0x04dd, 0x30f3}, /*                      kana_N ン KATAKANA LETTER N */
+    {0x04de, 0x309b}, /*                 voicedsound ゛ KATAKANA-HIRAGANA VOICED SOUND MARK */
+    {0x04df, 0x309c}, /*             semivoicedsound ゜ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */
+    {0x05ac, 0x060c}, /*                Arabic_comma ، ARABIC COMMA */
+    {0x05bb, 0x061b}, /*            Arabic_semicolon ؛ ARABIC SEMICOLON */
+    {0x05bf, 0x061f}, /*        Arabic_question_mark ؟ ARABIC QUESTION MARK */
+    {0x05c1, 0x0621}, /*                Arabic_hamza ء ARABIC LETTER HAMZA */
+    {0x05c2, 0x0622}, /*          Arabic_maddaonalef آ ARABIC LETTER ALEF WITH MADDA ABOVE */
+    {0x05c3, 0x0623}, /*          Arabic_hamzaonalef أ ARABIC LETTER ALEF WITH HAMZA ABOVE */
+    {0x05c4, 0x0624}, /*           Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */
+    {0x05c5, 0x0625}, /*       Arabic_hamzaunderalef إ ARABIC LETTER ALEF WITH HAMZA BELOW */
+    {0x05c6, 0x0626}, /*           Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */
+    {0x05c7, 0x0627}, /*                 Arabic_alef ا ARABIC LETTER ALEF */
+    {0x05c8, 0x0628}, /*                  Arabic_beh ب ARABIC LETTER BEH */
+    {0x05c9, 0x0629}, /*           Arabic_tehmarbuta ة ARABIC LETTER TEH MARBUTA */
+    {0x05ca, 0x062a}, /*                  Arabic_teh ت ARABIC LETTER TEH */
+    {0x05cb, 0x062b}, /*                 Arabic_theh ث ARABIC LETTER THEH */
+    {0x05cc, 0x062c}, /*                 Arabic_jeem ج ARABIC LETTER JEEM */
+    {0x05cd, 0x062d}, /*                  Arabic_hah ح ARABIC LETTER HAH */
+    {0x05ce, 0x062e}, /*                 Arabic_khah خ ARABIC LETTER KHAH */
+    {0x05cf, 0x062f}, /*                  Arabic_dal د ARABIC LETTER DAL */
+    {0x05d0, 0x0630}, /*                 Arabic_thal ذ ARABIC LETTER THAL */
+    {0x05d1, 0x0631}, /*                   Arabic_ra ر ARABIC LETTER REH */
+    {0x05d2, 0x0632}, /*                 Arabic_zain ز ARABIC LETTER ZAIN */
+    {0x05d3, 0x0633}, /*                 Arabic_seen س ARABIC LETTER SEEN */
+    {0x05d4, 0x0634}, /*                Arabic_sheen ش ARABIC LETTER SHEEN */
+    {0x05d5, 0x0635}, /*                  Arabic_sad ص ARABIC LETTER SAD */
+    {0x05d6, 0x0636}, /*                  Arabic_dad ض ARABIC LETTER DAD */
+    {0x05d7, 0x0637}, /*                  Arabic_tah ط ARABIC LETTER TAH */
+    {0x05d8, 0x0638}, /*                  Arabic_zah ظ ARABIC LETTER ZAH */
+    {0x05d9, 0x0639}, /*                  Arabic_ain ع ARABIC LETTER AIN */
+    {0x05da, 0x063a}, /*                Arabic_ghain غ ARABIC LETTER GHAIN */
+    {0x05e0, 0x0640}, /*              Arabic_tatweel ـ ARABIC TATWEEL */
+    {0x05e1, 0x0641}, /*                  Arabic_feh ف ARABIC LETTER FEH */
+    {0x05e2, 0x0642}, /*                  Arabic_qaf ق ARABIC LETTER QAF */
+    {0x05e3, 0x0643}, /*                  Arabic_kaf ك ARABIC LETTER KAF */
+    {0x05e4, 0x0644}, /*                  Arabic_lam ل ARABIC LETTER LAM */
+    {0x05e5, 0x0645}, /*                 Arabic_meem م ARABIC LETTER MEEM */
+    {0x05e6, 0x0646}, /*                 Arabic_noon ن ARABIC LETTER NOON */
+    {0x05e7, 0x0647}, /*                   Arabic_ha ه ARABIC LETTER HEH */
+    {0x05e8, 0x0648}, /*                  Arabic_waw و ARABIC LETTER WAW */
+    {0x05e9, 0x0649}, /*          Arabic_alefmaksura ى ARABIC LETTER ALEF MAKSURA */
+    {0x05ea, 0x064a}, /*                  Arabic_yeh ي ARABIC LETTER YEH */
+    {0x05eb, 0x064b}, /*             Arabic_fathatan ً ARABIC FATHATAN */
+    {0x05ec, 0x064c}, /*             Arabic_dammatan ٌ ARABIC DAMMATAN */
+    {0x05ed, 0x064d}, /*             Arabic_kasratan ٍ ARABIC KASRATAN */
+    {0x05ee, 0x064e}, /*                Arabic_fatha َ ARABIC FATHA */
+    {0x05ef, 0x064f}, /*                Arabic_damma ُ ARABIC DAMMA */
+    {0x05f0, 0x0650}, /*                Arabic_kasra ِ ARABIC KASRA */
+    {0x05f1, 0x0651}, /*               Arabic_shadda ّ ARABIC SHADDA */
+    {0x05f2, 0x0652}, /*                Arabic_sukun ْ ARABIC SUKUN */
+    {0x06a1, 0x0452}, /*                 Serbian_dje ђ CYRILLIC SMALL LETTER DJE */
+    {0x06a2, 0x0453}, /*               Macedonia_gje ѓ CYRILLIC SMALL LETTER GJE */
+    {0x06a3, 0x0451}, /*                 Cyrillic_io ё CYRILLIC SMALL LETTER IO */
+    {0x06a4, 0x0454}, /*                Ukrainian_ie є CYRILLIC SMALL LETTER UKRAINIAN IE */
+    {0x06a5, 0x0455}, /*               Macedonia_dse ѕ CYRILLIC SMALL LETTER DZE */
+    {0x06a6, 0x0456}, /*                 Ukrainian_i і CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */
+    {0x06a7, 0x0457}, /*                Ukrainian_yi ї CYRILLIC SMALL LETTER YI */
+    {0x06a8, 0x0458}, /*                 Cyrillic_je ј CYRILLIC SMALL LETTER JE */
+    {0x06a9, 0x0459}, /*                Cyrillic_lje љ CYRILLIC SMALL LETTER LJE */
+    {0x06aa, 0x045a}, /*                Cyrillic_nje њ CYRILLIC SMALL LETTER NJE */
+    {0x06ab, 0x045b}, /*                Serbian_tshe ћ CYRILLIC SMALL LETTER TSHE */
+    {0x06ac, 0x045c}, /*               Macedonia_kje ќ CYRILLIC SMALL LETTER KJE */
+                      /*  0x06ad                 Ukrainian_ghe_with_upturn ? ??? */
+    {0x06ae, 0x045e}, /*         Byelorussian_shortu ў CYRILLIC SMALL LETTER SHORT U */
+    {0x06af, 0x045f}, /*               Cyrillic_dzhe џ CYRILLIC SMALL LETTER DZHE */
+    {0x06b0, 0x2116}, /*                  numerosign № NUMERO SIGN */
+    {0x06b1, 0x0402}, /*                 Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */
+    {0x06b2, 0x0403}, /*               Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */
+    {0x06b3, 0x0401}, /*                 Cyrillic_IO Ё CYRILLIC CAPITAL LETTER IO */
+    {0x06b4, 0x0404}, /*                Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */
+    {0x06b5, 0x0405}, /*               Macedonia_DSE Ѕ CYRILLIC CAPITAL LETTER DZE */
+    {0x06b6, 0x0406}, /*                 Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */
+    {0x06b7, 0x0407}, /*                Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */
+    {0x06b8, 0x0408}, /*                 Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */
+    {0x06b9, 0x0409}, /*                Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */
+    {0x06ba, 0x040a}, /*                Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */
+    {0x06bb, 0x040b}, /*                Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */
+    {0x06bc, 0x040c}, /*               Macedonia_KJE Ќ CYRILLIC CAPITAL LETTER KJE */
+                      /*  0x06bd                 Ukrainian_GHE_WITH_UPTURN ? ??? */
+    {0x06be, 0x040e}, /*         Byelorussian_SHORTU Ў CYRILLIC CAPITAL LETTER SHORT U */
+    {0x06bf, 0x040f}, /*               Cyrillic_DZHE Џ CYRILLIC CAPITAL LETTER DZHE */
+    {0x06c0, 0x044e}, /*                 Cyrillic_yu ю CYRILLIC SMALL LETTER YU */
+    {0x06c1, 0x0430}, /*                  Cyrillic_a а CYRILLIC SMALL LETTER A */
+    {0x06c2, 0x0431}, /*                 Cyrillic_be б CYRILLIC SMALL LETTER BE */
+    {0x06c3, 0x0446}, /*                Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */
+    {0x06c4, 0x0434}, /*                 Cyrillic_de д CYRILLIC SMALL LETTER DE */
+    {0x06c5, 0x0435}, /*                 Cyrillic_ie е CYRILLIC SMALL LETTER IE */
+    {0x06c6, 0x0444}, /*                 Cyrillic_ef ф CYRILLIC SMALL LETTER EF */
+    {0x06c7, 0x0433}, /*                Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */
+    {0x06c8, 0x0445}, /*                 Cyrillic_ha х CYRILLIC SMALL LETTER HA */
+    {0x06c9, 0x0438}, /*                  Cyrillic_i и CYRILLIC SMALL LETTER I */
+    {0x06ca, 0x0439}, /*             Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */
+    {0x06cb, 0x043a}, /*                 Cyrillic_ka к CYRILLIC SMALL LETTER KA */
+    {0x06cc, 0x043b}, /*                 Cyrillic_el л CYRILLIC SMALL LETTER EL */
+    {0x06cd, 0x043c}, /*                 Cyrillic_em м CYRILLIC SMALL LETTER EM */
+    {0x06ce, 0x043d}, /*                 Cyrillic_en н CYRILLIC SMALL LETTER EN */
+    {0x06cf, 0x043e}, /*                  Cyrillic_o о CYRILLIC SMALL LETTER O */
+    {0x06d0, 0x043f}, /*                 Cyrillic_pe п CYRILLIC SMALL LETTER PE */
+    {0x06d1, 0x044f}, /*                 Cyrillic_ya я CYRILLIC SMALL LETTER YA */
+    {0x06d2, 0x0440}, /*                 Cyrillic_er р CYRILLIC SMALL LETTER ER */
+    {0x06d3, 0x0441}, /*                 Cyrillic_es с CYRILLIC SMALL LETTER ES */
+    {0x06d4, 0x0442}, /*                 Cyrillic_te т CYRILLIC SMALL LETTER TE */
+    {0x06d5, 0x0443}, /*                  Cyrillic_u у CYRILLIC SMALL LETTER U */
+    {0x06d6, 0x0436}, /*                Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */
+    {0x06d7, 0x0432}, /*                 Cyrillic_ve в CYRILLIC SMALL LETTER VE */
+    {0x06d8, 0x044c}, /*           Cyrillic_softsign ь CYRILLIC SMALL LETTER SOFT SIGN */
+    {0x06d9, 0x044b}, /*               Cyrillic_yeru ы CYRILLIC SMALL LETTER YERU */
+    {0x06da, 0x0437}, /*                 Cyrillic_ze з CYRILLIC SMALL LETTER ZE */
+    {0x06db, 0x0448}, /*                Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */
+    {0x06dc, 0x044d}, /*                  Cyrillic_e э CYRILLIC SMALL LETTER E */
+    {0x06dd, 0x0449}, /*              Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */
+    {0x06de, 0x0447}, /*                Cyrillic_che ч CYRILLIC SMALL LETTER CHE */
+    {0x06df, 0x044a}, /*           Cyrillic_hardsign ъ CYRILLIC SMALL LETTER HARD SIGN */
+    {0x06e0, 0x042e}, /*                 Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */
+    {0x06e1, 0x0410}, /*                  Cyrillic_A А CYRILLIC CAPITAL LETTER A */
+    {0x06e2, 0x0411}, /*                 Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */
+    {0x06e3, 0x0426}, /*                Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */
+    {0x06e4, 0x0414}, /*                 Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */
+    {0x06e5, 0x0415}, /*                 Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */
+    {0x06e6, 0x0424}, /*                 Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */
+    {0x06e7, 0x0413}, /*                Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */
+    {0x06e8, 0x0425}, /*                 Cyrillic_HA Х CYRILLIC CAPITAL LETTER HA */
+    {0x06e9, 0x0418}, /*                  Cyrillic_I И CYRILLIC CAPITAL LETTER I */
+    {0x06ea, 0x0419}, /*             Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */
+    {0x06eb, 0x041a}, /*                 Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */
+    {0x06ec, 0x041b}, /*                 Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */
+    {0x06ed, 0x041c}, /*                 Cyrillic_EM М CYRILLIC CAPITAL LETTER EM */
+    {0x06ee, 0x041d}, /*                 Cyrillic_EN Н CYRILLIC CAPITAL LETTER EN */
+    {0x06ef, 0x041e}, /*                  Cyrillic_O О CYRILLIC CAPITAL LETTER O */
+    {0x06f0, 0x041f}, /*                 Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */
+    {0x06f1, 0x042f}, /*                 Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */
+    {0x06f2, 0x0420}, /*                 Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */
+    {0x06f3, 0x0421}, /*                 Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */
+    {0x06f4, 0x0422}, /*                 Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */
+    {0x06f5, 0x0423}, /*                  Cyrillic_U У CYRILLIC CAPITAL LETTER U */
+    {0x06f6, 0x0416}, /*                Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */
+    {0x06f7, 0x0412}, /*                 Cyrillic_VE В CYRILLIC CAPITAL LETTER VE */
+    {0x06f8, 0x042c}, /*           Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */
+    {0x06f9, 0x042b}, /*               Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */
+    {0x06fa, 0x0417}, /*                 Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */
+    {0x06fb, 0x0428}, /*                Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */
+    {0x06fc, 0x042d}, /*                  Cyrillic_E Э CYRILLIC CAPITAL LETTER E */
+    {0x06fd, 0x0429}, /*              Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */
+    {0x06fe, 0x0427}, /*                Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */
+    {0x06ff, 0x042a}, /*           Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */
+    {0x07a1, 0x0386}, /*           Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */
+    {0x07a2, 0x0388}, /*         Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */
+    {0x07a3, 0x0389}, /*             Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */
+    {0x07a4, 0x038a}, /*            Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */
+    {0x07a5, 0x03aa}, /*         Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */
+    {0x07a7, 0x038c}, /*         Greek_OMICRONaccent Ό GREEK CAPITAL LETTER OMICRON WITH TONOS */
+    {0x07a8, 0x038e}, /*         Greek_UPSILONaccent Ύ GREEK CAPITAL LETTER UPSILON WITH TONOS */
+    {0x07a9, 0x03ab}, /*       Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */
+    {0x07ab, 0x038f}, /*           Greek_OMEGAaccent Ώ GREEK CAPITAL LETTER OMEGA WITH TONOS */
+    {0x07ae, 0x0385}, /*        Greek_accentdieresis ΅ GREEK DIALYTIKA TONOS */
+    {0x07af, 0x2015}, /*              Greek_horizbar ― HORIZONTAL BAR */
+    {0x07b1, 0x03ac}, /*           Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */
+    {0x07b2, 0x03ad}, /*         Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */
+    {0x07b3, 0x03ae}, /*             Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */
+    {0x07b4, 0x03af}, /*            Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */
+    {0x07b5, 0x03ca}, /*          Greek_iotadieresis ϊ GREEK SMALL LETTER IOTA WITH DIALYTIKA */
+    {0x07b6, 0x0390}, /*    Greek_iotaaccentdieresis ΐ GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */
+    {0x07b7, 0x03cc}, /*         Greek_omicronaccent ό GREEK SMALL LETTER OMICRON WITH TONOS */
+    {0x07b8, 0x03cd}, /*         Greek_upsilonaccent ύ GREEK SMALL LETTER UPSILON WITH TONOS */
+    {0x07b9, 0x03cb}, /*       Greek_upsilondieresis ϋ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */
+    {0x07ba, 0x03b0}, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */
+    {0x07bb, 0x03ce}, /*           Greek_omegaaccent ώ GREEK SMALL LETTER OMEGA WITH TONOS */
+    {0x07c1, 0x0391}, /*                 Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */
+    {0x07c2, 0x0392}, /*                  Greek_BETA Β GREEK CAPITAL LETTER BETA */
+    {0x07c3, 0x0393}, /*                 Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */
+    {0x07c4, 0x0394}, /*                 Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */
+    {0x07c5, 0x0395}, /*               Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */
+    {0x07c6, 0x0396}, /*                  Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */
+    {0x07c7, 0x0397}, /*                   Greek_ETA Η GREEK CAPITAL LETTER ETA */
+    {0x07c8, 0x0398}, /*                 Greek_THETA Θ GREEK CAPITAL LETTER THETA */
+    {0x07c9, 0x0399}, /*                  Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */
+    {0x07ca, 0x039a}, /*                 Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */
+    {0x07cb, 0x039b}, /*                Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMDA */
+    {0x07cc, 0x039c}, /*                    Greek_MU Μ GREEK CAPITAL LETTER MU */
+    {0x07cd, 0x039d}, /*                    Greek_NU Ν GREEK CAPITAL LETTER NU */
+    {0x07ce, 0x039e}, /*                    Greek_XI Ξ GREEK CAPITAL LETTER XI */
+    {0x07cf, 0x039f}, /*               Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */
+    {0x07d0, 0x03a0}, /*                    Greek_PI Π GREEK CAPITAL LETTER PI */
+    {0x07d1, 0x03a1}, /*                   Greek_RHO Ρ GREEK CAPITAL LETTER RHO */
+    {0x07d2, 0x03a3}, /*                 Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */
+    {0x07d4, 0x03a4}, /*                   Greek_TAU Τ GREEK CAPITAL LETTER TAU */
+    {0x07d5, 0x03a5}, /*               Greek_UPSILON Υ GREEK CAPITAL LETTER UPSILON */
+    {0x07d6, 0x03a6}, /*                   Greek_PHI Φ GREEK CAPITAL LETTER PHI */
+    {0x07d7, 0x03a7}, /*                   Greek_CHI Χ GREEK CAPITAL LETTER CHI */
+    {0x07d8, 0x03a8}, /*                   Greek_PSI Ψ GREEK CAPITAL LETTER PSI */
+    {0x07d9, 0x03a9}, /*                 Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */
+    {0x07e1, 0x03b1}, /*                 Greek_alpha α GREEK SMALL LETTER ALPHA */
+    {0x07e2, 0x03b2}, /*                  Greek_beta β GREEK SMALL LETTER BETA */
+    {0x07e3, 0x03b3}, /*                 Greek_gamma γ GREEK SMALL LETTER GAMMA */
+    {0x07e4, 0x03b4}, /*                 Greek_delta δ GREEK SMALL LETTER DELTA */
+    {0x07e5, 0x03b5}, /*               Greek_epsilon ε GREEK SMALL LETTER EPSILON */
+    {0x07e6, 0x03b6}, /*                  Greek_zeta ζ GREEK SMALL LETTER ZETA */
+    {0x07e7, 0x03b7}, /*                   Greek_eta η GREEK SMALL LETTER ETA */
+    {0x07e8, 0x03b8}, /*                 Greek_theta θ GREEK SMALL LETTER THETA */
+    {0x07e9, 0x03b9}, /*                  Greek_iota ι GREEK SMALL LETTER IOTA */
+    {0x07ea, 0x03ba}, /*                 Greek_kappa κ GREEK SMALL LETTER KAPPA */
+    {0x07eb, 0x03bb}, /*                Greek_lambda λ GREEK SMALL LETTER LAMDA */
+    {0x07ec, 0x03bc}, /*                    Greek_mu μ GREEK SMALL LETTER MU */
+    {0x07ed, 0x03bd}, /*                    Greek_nu ν GREEK SMALL LETTER NU */
+    {0x07ee, 0x03be}, /*                    Greek_xi ξ GREEK SMALL LETTER XI */
+    {0x07ef, 0x03bf}, /*               Greek_omicron ο GREEK SMALL LETTER OMICRON */
+    {0x07f0, 0x03c0}, /*                    Greek_pi π GREEK SMALL LETTER PI */
+    {0x07f1, 0x03c1}, /*                   Greek_rho ρ GREEK SMALL LETTER RHO */
+    {0x07f2, 0x03c3}, /*                 Greek_sigma σ GREEK SMALL LETTER SIGMA */
+    {0x07f3, 0x03c2}, /*       Greek_finalsmallsigma ς GREEK SMALL LETTER FINAL SIGMA */
+    {0x07f4, 0x03c4}, /*                   Greek_tau τ GREEK SMALL LETTER TAU */
+    {0x07f5, 0x03c5}, /*               Greek_upsilon υ GREEK SMALL LETTER UPSILON */
+    {0x07f6, 0x03c6}, /*                   Greek_phi φ GREEK SMALL LETTER PHI */
+    {0x07f7, 0x03c7}, /*                   Greek_chi χ GREEK SMALL LETTER CHI */
+    {0x07f8, 0x03c8}, /*                   Greek_psi ψ GREEK SMALL LETTER PSI */
+    {0x07f9, 0x03c9}, /*                 Greek_omega ω GREEK SMALL LETTER OMEGA */
+                      /*  0x08a1                               leftradical ? ??? */
+                      /*  0x08a2                            topleftradical ? ??? */
+                      /*  0x08a3                            horizconnector ? ??? */
+    {0x08a4, 0x2320}, /*                 topintegral ⌠ TOP HALF INTEGRAL */
+    {0x08a5, 0x2321}, /*                 botintegral ⌡ BOTTOM HALF INTEGRAL */
+    {0x08a6, 0x2502}, /*               vertconnector │ BOX DRAWINGS LIGHT VERTICAL */
+                      /*  0x08a7                          topleftsqbracket ? ??? */
+                      /*  0x08a8                          botleftsqbracket ? ??? */
+                      /*  0x08a9                         toprightsqbracket ? ??? */
+                      /*  0x08aa                         botrightsqbracket ? ??? */
+                      /*  0x08ab                             topleftparens ? ??? */
+                      /*  0x08ac                             botleftparens ? ??? */
+                      /*  0x08ad                            toprightparens ? ??? */
+                      /*  0x08ae                            botrightparens ? ??? */
+                      /*  0x08af                      leftmiddlecurlybrace ? ??? */
+                      /*  0x08b0                     rightmiddlecurlybrace ? ??? */
+                      /*  0x08b1                          topleftsummation ? ??? */
+                      /*  0x08b2                          botleftsummation ? ??? */
+                      /*  0x08b3                 topvertsummationconnector ? ??? */
+                      /*  0x08b4                 botvertsummationconnector ? ??? */
+                      /*  0x08b5                         toprightsummation ? ??? */
+                      /*  0x08b6                         botrightsummation ? ??? */
+                      /*  0x08b7                      rightmiddlesummation ? ??? */
+    {0x08bc, 0x2264}, /*               lessthanequal ≤ LESS-THAN OR EQUAL TO */
+    {0x08bd, 0x2260}, /*                    notequal ≠ NOT EQUAL TO */
+    {0x08be, 0x2265}, /*            greaterthanequal ≥ GREATER-THAN OR EQUAL TO */
+    {0x08bf, 0x222b}, /*                    integral ∫ INTEGRAL */
+    {0x08c0, 0x2234}, /*                   therefore ∴ THEREFORE */
+    {0x08c1, 0x221d}, /*                   variation ∝ PROPORTIONAL TO */
+    {0x08c2, 0x221e}, /*                    infinity ∞ INFINITY */
+    {0x08c5, 0x2207}, /*                       nabla ∇ NABLA */
+    {0x08c8, 0x2245}, /*                 approximate ≅ APPROXIMATELY EQUAL TO */
+                      /*  0x08c9                              similarequal ? ??? */
+    {0x08cd, 0x21d4}, /*                    ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */
+    {0x08ce, 0x21d2}, /*                     implies ⇒ RIGHTWARDS DOUBLE ARROW */
+    {0x08cf, 0x2261}, /*                   identical ≡ IDENTICAL TO */
+    {0x08d6, 0x221a}, /*                     radical √ SQUARE ROOT */
+    {0x08da, 0x2282}, /*                  includedin ⊂ SUBSET OF */
+    {0x08db, 0x2283}, /*                    includes ⊃ SUPERSET OF */
+    {0x08dc, 0x2229}, /*                intersection ∩ INTERSECTION */
+    {0x08dd, 0x222a}, /*                       union ∪ UNION */
+    {0x08de, 0x2227}, /*                  logicaland ∧ LOGICAL AND */
+    {0x08df, 0x2228}, /*                   logicalor ∨ LOGICAL OR */
+    {0x08ef, 0x2202}, /*           partialderivative ∂ PARTIAL DIFFERENTIAL */
+    {0x08f6, 0x0192}, /*                    function ƒ LATIN SMALL LETTER F WITH HOOK */
+    {0x08fb, 0x2190}, /*                   leftarrow ← LEFTWARDS ARROW */
+    {0x08fc, 0x2191}, /*                     uparrow ↑ UPWARDS ARROW */
+    {0x08fd, 0x2192}, /*                  rightarrow → RIGHTWARDS ARROW */
+    {0x08fe, 0x2193}, /*                   downarrow ↓ DOWNWARDS ARROW */
+    {0x09df, 0x2422}, /*                       blank ␢ BLANK SYMBOL */
+    {0x09e0, 0x25c6}, /*                soliddiamond ◆ BLACK DIAMOND */
+    {0x09e1, 0x2592}, /*                checkerboard ▒ MEDIUM SHADE */
+    {0x09e2, 0x2409}, /*                          ht ␉ SYMBOL FOR HORIZONTAL TABULATION */
+    {0x09e3, 0x240c}, /*                          ff ␌ SYMBOL FOR FORM FEED */
+    {0x09e4, 0x240d}, /*                          cr ␍ SYMBOL FOR CARRIAGE RETURN */
+    {0x09e5, 0x240a}, /*                          lf ␊ SYMBOL FOR LINE FEED */
+    {0x09e8, 0x2424}, /*                          nl ␤ SYMBOL FOR NEWLINE */
+    {0x09e9, 0x240b}, /*                          vt ␋ SYMBOL FOR VERTICAL TABULATION */
+    {0x09ea, 0x2518}, /*              lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */
+    {0x09eb, 0x2510}, /*               uprightcorner ┐ BOX DRAWINGS LIGHT DOWN AND LEFT */
+    {0x09ec, 0x250c}, /*                upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */
+    {0x09ed, 0x2514}, /*               lowleftcorner └ BOX DRAWINGS LIGHT UP AND RIGHT */
+    {0x09ee, 0x253c}, /*               crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
+                      /*  0x09ef                            horizlinescan1 ? ??? */
+                      /*  0x09f0                            horizlinescan3 ? ??? */
+    {0x09f1, 0x2500}, /*              horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */
+                      /*  0x09f2                            horizlinescan7 ? ??? */
+                      /*  0x09f3                            horizlinescan9 ? ??? */
+    {0x09f4, 0x251c}, /*                       leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
+    {0x09f5, 0x2524}, /*                      rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */
+    {0x09f6, 0x2534}, /*                        bott ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL */
+    {0x09f7, 0x252c}, /*                        topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
+    {0x09f8, 0x2502}, /*                     vertbar │ BOX DRAWINGS LIGHT VERTICAL */
+    {0x0aa1, 0x2003}, /*                     emspace   EM SPACE */
+    {0x0aa2, 0x2002}, /*                     enspace   EN SPACE */
+    {0x0aa3, 0x2004}, /*                    em3space   THREE-PER-EM SPACE */
+    {0x0aa4, 0x2005}, /*                    em4space   FOUR-PER-EM SPACE */
+    {0x0aa5, 0x2007}, /*                  digitspace   FIGURE SPACE */
+    {0x0aa6, 0x2008}, /*                  punctspace   PUNCTUATION SPACE */
+    {0x0aa7, 0x2009}, /*                   thinspace   THIN SPACE */
+    {0x0aa8, 0x200a}, /*                   hairspace   HAIR SPACE */
+    {0x0aa9, 0x2014}, /*                      emdash — EM DASH */
+    {0x0aaa, 0x2013}, /*                      endash – EN DASH */
+                      /*  0x0aac                               signifblank ? ??? */
+    {0x0aae, 0x2026}, /*                    ellipsis … HORIZONTAL ELLIPSIS */
+                      /*  0x0aaf                           doubbaselinedot ? ??? */
+    {0x0ab0, 0x2153}, /*                    onethird ⅓ VULGAR FRACTION ONE THIRD */
+    {0x0ab1, 0x2154}, /*                   twothirds ⅔ VULGAR FRACTION TWO THIRDS */
+    {0x0ab2, 0x2155}, /*                    onefifth ⅕ VULGAR FRACTION ONE FIFTH */
+    {0x0ab3, 0x2156}, /*                   twofifths ⅖ VULGAR FRACTION TWO FIFTHS */
+    {0x0ab4, 0x2157}, /*                 threefifths ⅗ VULGAR FRACTION THREE FIFTHS */
+    {0x0ab5, 0x2158}, /*                  fourfifths ⅘ VULGAR FRACTION FOUR FIFTHS */
+    {0x0ab6, 0x2159}, /*                    onesixth ⅙ VULGAR FRACTION ONE SIXTH */
+    {0x0ab7, 0x215a}, /*                  fivesixths ⅚ VULGAR FRACTION FIVE SIXTHS */
+    {0x0ab8, 0x2105}, /*                      careof ℅ CARE OF */
+    {0x0abb, 0x2012}, /*                     figdash ‒ FIGURE DASH */
+    {0x0abc, 0x2329}, /*            leftanglebracket 〈 LEFT-POINTING ANGLE BRACKET */
+    {0x0abd, 0x002e}, /*                decimalpoint . FULL STOP */
+    {0x0abe, 0x232a}, /*           rightanglebracket 〉 RIGHT-POINTING ANGLE BRACKET */
+                      /*  0x0abf                                    marker ? ??? */
+    {0x0ac3, 0x215b}, /*                   oneeighth ⅛ VULGAR FRACTION ONE EIGHTH */
+    {0x0ac4, 0x215c}, /*                threeeighths ⅜ VULGAR FRACTION THREE EIGHTHS */
+    {0x0ac5, 0x215d}, /*                 fiveeighths ⅝ VULGAR FRACTION FIVE EIGHTHS */
+    {0x0ac6, 0x215e}, /*                seveneighths ⅞ VULGAR FRACTION SEVEN EIGHTHS */
+    {0x0ac9, 0x2122}, /*                   trademark ™ TRADE MARK SIGN */
+    {0x0aca, 0x2613}, /*               signaturemark ☓ SALTIRE */
+                      /*  0x0acb                         trademarkincircle ? ??? */
+    {0x0acc, 0x25c1}, /*            leftopentriangle ◁ WHITE LEFT-POINTING TRIANGLE */
+    {0x0acd, 0x25b7}, /*           rightopentriangle ▷ WHITE RIGHT-POINTING TRIANGLE */
+    {0x0ace, 0x25cb}, /*                emopencircle ○ WHITE CIRCLE */
+    {0x0acf, 0x25a1}, /*             emopenrectangle □ WHITE SQUARE */
+    {0x0ad0, 0x2018}, /*         leftsinglequotemark ‘ LEFT SINGLE QUOTATION MARK */
+    {0x0ad1, 0x2019}, /*        rightsinglequotemark ’ RIGHT SINGLE QUOTATION MARK */
+    {0x0ad2, 0x201c}, /*         leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */
+    {0x0ad3, 0x201d}, /*        rightdoublequotemark ” RIGHT DOUBLE QUOTATION MARK */
+    {0x0ad4, 0x211e}, /*                prescription ℞ PRESCRIPTION TAKE */
+    {0x0ad6, 0x2032}, /*                     minutes ′ PRIME */
+    {0x0ad7, 0x2033}, /*                     seconds ″ DOUBLE PRIME */
+    {0x0ad9, 0x271d}, /*                  latincross ✝ LATIN CROSS */
+                      /*  0x0ada                                  hexagram ? ??? */
+    {0x0adb, 0x25ac}, /*            filledrectbullet ▬ BLACK RECTANGLE */
+    {0x0adc, 0x25c0}, /*         filledlefttribullet ◀ BLACK LEFT-POINTING TRIANGLE */
+    {0x0add, 0x25b6}, /*        filledrighttribullet ▶ BLACK RIGHT-POINTING TRIANGLE */
+    {0x0ade, 0x25cf}, /*              emfilledcircle ● BLACK CIRCLE */
+    {0x0adf, 0x25a0}, /*                emfilledrect ■ BLACK SQUARE */
+    {0x0ae0, 0x25e6}, /*            enopencircbullet ◦ WHITE BULLET */
+    {0x0ae1, 0x25ab}, /*          enopensquarebullet ▫ WHITE SMALL SQUARE */
+    {0x0ae2, 0x25ad}, /*              openrectbullet ▭ WHITE RECTANGLE */
+    {0x0ae3, 0x25b3}, /*             opentribulletup △ WHITE UP-POINTING TRIANGLE */
+    {0x0ae4, 0x25bd}, /*           opentribulletdown ▽ WHITE DOWN-POINTING TRIANGLE */
+    {0x0ae5, 0x2606}, /*                    openstar ☆ WHITE STAR */
+    {0x0ae6, 0x2022}, /*          enfilledcircbullet • BULLET */
+    {0x0ae7, 0x25aa}, /*            enfilledsqbullet ▪ BLACK SMALL SQUARE */
+    {0x0ae8, 0x25b2}, /*           filledtribulletup ▲ BLACK UP-POINTING TRIANGLE */
+    {0x0ae9, 0x25bc}, /*         filledtribulletdown ▼ BLACK DOWN-POINTING TRIANGLE */
+    {0x0aea, 0x261c}, /*                 leftpointer ☜ WHITE LEFT POINTING INDEX */
+    {0x0aeb, 0x261e}, /*                rightpointer ☞ WHITE RIGHT POINTING INDEX */
+    {0x0aec, 0x2663}, /*                        club ♣ BLACK CLUB SUIT */
+    {0x0aed, 0x2666}, /*                     diamond ♦ BLACK DIAMOND SUIT */
+    {0x0aee, 0x2665}, /*                       heart ♥ BLACK HEART SUIT */
+    {0x0af0, 0x2720}, /*                maltesecross ✠ MALTESE CROSS */
+    {0x0af1, 0x2020}, /*                      dagger † DAGGER */
+    {0x0af2, 0x2021}, /*                doubledagger ‡ DOUBLE DAGGER */
+    {0x0af3, 0x2713}, /*                   checkmark ✓ CHECK MARK */
+    {0x0af4, 0x2717}, /*                 ballotcross ✗ BALLOT X */
+    {0x0af5, 0x266f}, /*                musicalsharp ♯ MUSIC SHARP SIGN */
+    {0x0af6, 0x266d}, /*                 musicalflat ♭ MUSIC FLAT SIGN */
+    {0x0af7, 0x2642}, /*                  malesymbol ♂ MALE SIGN */
+    {0x0af8, 0x2640}, /*                femalesymbol ♀ FEMALE SIGN */
+    {0x0af9, 0x260e}, /*                   telephone ☎ BLACK TELEPHONE */
+    {0x0afa, 0x2315}, /*           telephonerecorder ⌕ TELEPHONE RECORDER */
+    {0x0afb, 0x2117}, /*         phonographcopyright ℗ SOUND RECORDING COPYRIGHT */
+    {0x0afc, 0x2038}, /*                       caret ‸ CARET */
+    {0x0afd, 0x201a}, /*          singlelowquotemark ‚ SINGLE LOW-9 QUOTATION MARK */
+    {0x0afe, 0x201e}, /*          doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */
+                      /*  0x0aff                                    cursor ? ??? */
+    {0x0ba3, 0x003c}, /*                   leftcaret < LESS-THAN SIGN */
+    {0x0ba6, 0x003e}, /*                  rightcaret > GREATER-THAN SIGN */
+    {0x0ba8, 0x2228}, /*                   downcaret ∨ LOGICAL OR */
+    {0x0ba9, 0x2227}, /*                     upcaret ∧ LOGICAL AND */
+    {0x0bc0, 0x00af}, /*                     overbar ¯ MACRON */
+    {0x0bc2, 0x22a4}, /*                    downtack ⊤ DOWN TACK */
+    {0x0bc3, 0x2229}, /*                      upshoe ∩ INTERSECTION */
+    {0x0bc4, 0x230a}, /*                   downstile ⌊ LEFT FLOOR */
+    {0x0bc6, 0x005f}, /*                    underbar _ LOW LINE */
+    {0x0bca, 0x2218}, /*                         jot ∘ RING OPERATOR */
+    {0x0bcc, 0x2395}, /*                        quad ⎕ APL FUNCTIONAL SYMBOL QUAD */
+    {0x0bce, 0x22a5}, /*                      uptack ⊥ UP TACK */
+    {0x0bcf, 0x25cb}, /*                      circle ○ WHITE CIRCLE */
+    {0x0bd3, 0x2308}, /*                     upstile ⌈ LEFT CEILING */
+    {0x0bd6, 0x222a}, /*                    downshoe ∪ UNION */
+    {0x0bd8, 0x2283}, /*                   rightshoe ⊃ SUPERSET OF */
+    {0x0bda, 0x2282}, /*                    leftshoe ⊂ SUBSET OF */
+    {0x0bdc, 0x22a3}, /*                    lefttack ⊣ LEFT TACK */
+    {0x0bfc, 0x22a2}, /*                   righttack ⊢ RIGHT TACK */
+    {0x0cdf, 0x2017}, /*        hebrew_doublelowline ‗ DOUBLE LOW LINE */
+    {0x0ce0, 0x05d0}, /*                hebrew_aleph א HEBREW LETTER ALEF */
+    {0x0ce1, 0x05d1}, /*                  hebrew_bet ב HEBREW LETTER BET */
+    {0x0ce2, 0x05d2}, /*                hebrew_gimel ג HEBREW LETTER GIMEL */
+    {0x0ce3, 0x05d3}, /*                hebrew_dalet ד HEBREW LETTER DALET */
+    {0x0ce4, 0x05d4}, /*                   hebrew_he ה HEBREW LETTER HE */
+    {0x0ce5, 0x05d5}, /*                  hebrew_waw ו HEBREW LETTER VAV */
+    {0x0ce6, 0x05d6}, /*                 hebrew_zain ז HEBREW LETTER ZAYIN */
+    {0x0ce7, 0x05d7}, /*                 hebrew_chet ח HEBREW LETTER HET */
+    {0x0ce8, 0x05d8}, /*                  hebrew_tet ט HEBREW LETTER TET */
+    {0x0ce9, 0x05d9}, /*                  hebrew_yod י HEBREW LETTER YOD */
+    {0x0cea, 0x05da}, /*            hebrew_finalkaph ך HEBREW LETTER FINAL KAF */
+    {0x0ceb, 0x05db}, /*                 hebrew_kaph כ HEBREW LETTER KAF */
+    {0x0cec, 0x05dc}, /*                hebrew_lamed ל HEBREW LETTER LAMED */
+    {0x0ced, 0x05dd}, /*             hebrew_finalmem ם HEBREW LETTER FINAL MEM */
+    {0x0cee, 0x05de}, /*                  hebrew_mem מ HEBREW LETTER MEM */
+    {0x0cef, 0x05df}, /*             hebrew_finalnun ן HEBREW LETTER FINAL NUN */
+    {0x0cf0, 0x05e0}, /*                  hebrew_nun נ HEBREW LETTER NUN */
+    {0x0cf1, 0x05e1}, /*               hebrew_samech ס HEBREW LETTER SAMEKH */
+    {0x0cf2, 0x05e2}, /*                 hebrew_ayin ע HEBREW LETTER AYIN */
+    {0x0cf3, 0x05e3}, /*              hebrew_finalpe ף HEBREW LETTER FINAL PE */
+    {0x0cf4, 0x05e4}, /*                   hebrew_pe פ HEBREW LETTER PE */
+    {0x0cf5, 0x05e5}, /*            hebrew_finalzade ץ HEBREW LETTER FINAL TSADI */
+    {0x0cf6, 0x05e6}, /*                 hebrew_zade צ HEBREW LETTER TSADI */
+    {0x0cf7, 0x05e7}, /*                 hebrew_qoph ק HEBREW LETTER QOF */
+    {0x0cf8, 0x05e8}, /*                 hebrew_resh ר HEBREW LETTER RESH */
+    {0x0cf9, 0x05e9}, /*                 hebrew_shin ש HEBREW LETTER SHIN */
+    {0x0cfa, 0x05ea}, /*                  hebrew_taw ת HEBREW LETTER TAV */
+    {0x0da1, 0x0e01}, /*                  Thai_kokai ก THAI CHARACTER KO KAI */
+    {0x0da2, 0x0e02}, /*                Thai_khokhai ข THAI CHARACTER KHO KHAI */
+    {0x0da3, 0x0e03}, /*               Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */
+    {0x0da4, 0x0e04}, /*               Thai_khokhwai ค THAI CHARACTER KHO KHWAI */
+    {0x0da5, 0x0e05}, /*                Thai_khokhon ฅ THAI CHARACTER KHO KHON */
+    {0x0da6, 0x0e06}, /*             Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */
+    {0x0da7, 0x0e07}, /*                 Thai_ngongu ง THAI CHARACTER NGO NGU */
+    {0x0da8, 0x0e08}, /*                Thai_chochan จ THAI CHARACTER CHO CHAN */
+    {0x0da9, 0x0e09}, /*               Thai_choching ฉ THAI CHARACTER CHO CHING */
+    {0x0daa, 0x0e0a}, /*               Thai_chochang ช THAI CHARACTER CHO CHANG */
+    {0x0dab, 0x0e0b}, /*                   Thai_soso ซ THAI CHARACTER SO SO */
+    {0x0dac, 0x0e0c}, /*                Thai_chochoe ฌ THAI CHARACTER CHO CHOE */
+    {0x0dad, 0x0e0d}, /*                 Thai_yoying ญ THAI CHARACTER YO YING */
+    {0x0dae, 0x0e0e}, /*                Thai_dochada ฎ THAI CHARACTER DO CHADA */
+    {0x0daf, 0x0e0f}, /*                Thai_topatak ฏ THAI CHARACTER TO PATAK */
+    {0x0db0, 0x0e10}, /*                Thai_thothan ฐ THAI CHARACTER THO THAN */
+    {0x0db1, 0x0e11}, /*          Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */
+    {0x0db2, 0x0e12}, /*             Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */
+    {0x0db3, 0x0e13}, /*                  Thai_nonen ณ THAI CHARACTER NO NEN */
+    {0x0db4, 0x0e14}, /*                  Thai_dodek ด THAI CHARACTER DO DEK */
+    {0x0db5, 0x0e15}, /*                  Thai_totao ต THAI CHARACTER TO TAO */
+    {0x0db6, 0x0e16}, /*               Thai_thothung ถ THAI CHARACTER THO THUNG */
+    {0x0db7, 0x0e17}, /*              Thai_thothahan ท THAI CHARACTER THO THAHAN */
+    {0x0db8, 0x0e18}, /*               Thai_thothong ธ THAI CHARACTER THO THONG */
+    {0x0db9, 0x0e19}, /*                   Thai_nonu น THAI CHARACTER NO NU */
+    {0x0dba, 0x0e1a}, /*               Thai_bobaimai บ THAI CHARACTER BO BAIMAI */
+    {0x0dbb, 0x0e1b}, /*                  Thai_popla ป THAI CHARACTER PO PLA */
+    {0x0dbc, 0x0e1c}, /*               Thai_phophung ผ THAI CHARACTER PHO PHUNG */
+    {0x0dbd, 0x0e1d}, /*                   Thai_fofa ฝ THAI CHARACTER FO FA */
+    {0x0dbe, 0x0e1e}, /*                Thai_phophan พ THAI CHARACTER PHO PHAN */
+    {0x0dbf, 0x0e1f}, /*                  Thai_fofan ฟ THAI CHARACTER FO FAN */
+    {0x0dc0, 0x0e20}, /*             Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */
+    {0x0dc1, 0x0e21}, /*                   Thai_moma ม THAI CHARACTER MO MA */
+    {0x0dc2, 0x0e22}, /*                  Thai_yoyak ย THAI CHARACTER YO YAK */
+    {0x0dc3, 0x0e23}, /*                  Thai_rorua ร THAI CHARACTER RO RUA */
+    {0x0dc4, 0x0e24}, /*                     Thai_ru ฤ THAI CHARACTER RU */
+    {0x0dc5, 0x0e25}, /*                 Thai_loling ล THAI CHARACTER LO LING */
+    {0x0dc6, 0x0e26}, /*                     Thai_lu ฦ THAI CHARACTER LU */
+    {0x0dc7, 0x0e27}, /*                 Thai_wowaen ว THAI CHARACTER WO WAEN */
+    {0x0dc8, 0x0e28}, /*                 Thai_sosala ศ THAI CHARACTER SO SALA */
+    {0x0dc9, 0x0e29}, /*                 Thai_sorusi ษ THAI CHARACTER SO RUSI */
+    {0x0dca, 0x0e2a}, /*                  Thai_sosua ส THAI CHARACTER SO SUA */
+    {0x0dcb, 0x0e2b}, /*                  Thai_hohip ห THAI CHARACTER HO HIP */
+    {0x0dcc, 0x0e2c}, /*                Thai_lochula ฬ THAI CHARACTER LO CHULA */
+    {0x0dcd, 0x0e2d}, /*                   Thai_oang อ THAI CHARACTER O ANG */
+    {0x0dce, 0x0e2e}, /*               Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */
+    {0x0dcf, 0x0e2f}, /*              Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */
+    {0x0dd0, 0x0e30}, /*                  Thai_saraa ะ THAI CHARACTER SARA A */
+    {0x0dd1, 0x0e31}, /*             Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */
+    {0x0dd2, 0x0e32}, /*                 Thai_saraaa า THAI CHARACTER SARA AA */
+    {0x0dd3, 0x0e33}, /*                 Thai_saraam ำ THAI CHARACTER SARA AM */
+    {0x0dd4, 0x0e34}, /*                  Thai_sarai ิ THAI CHARACTER SARA I */
+    {0x0dd5, 0x0e35}, /*                 Thai_saraii ี THAI CHARACTER SARA II */
+    {0x0dd6, 0x0e36}, /*                 Thai_saraue ึ THAI CHARACTER SARA UE */
+    {0x0dd7, 0x0e37}, /*                Thai_sarauee ื THAI CHARACTER SARA UEE */
+    {0x0dd8, 0x0e38}, /*                  Thai_sarau ุ THAI CHARACTER SARA U */
+    {0x0dd9, 0x0e39}, /*                 Thai_sarauu ู THAI CHARACTER SARA UU */
+    {0x0dda, 0x0e3a}, /*                Thai_phinthu ฺ THAI CHARACTER PHINTHU */
+    {0x0dde, 0x0e3e}, /*      Thai_maihanakat_maitho ฾ ??? */
+    {0x0ddf, 0x0e3f}, /*                   Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */
+    {0x0de0, 0x0e40}, /*                  Thai_sarae เ THAI CHARACTER SARA E */
+    {0x0de1, 0x0e41}, /*                 Thai_saraae แ THAI CHARACTER SARA AE */
+    {0x0de2, 0x0e42}, /*                  Thai_sarao โ THAI CHARACTER SARA O */
+    {0x0de3, 0x0e43}, /*          Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */
+    {0x0de4, 0x0e44}, /*         Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */
+    {0x0de5, 0x0e45}, /*            Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */
+    {0x0de6, 0x0e46}, /*               Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */
+    {0x0de7, 0x0e47}, /*              Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */
+    {0x0de8, 0x0e48}, /*                  Thai_maiek ่ THAI CHARACTER MAI EK */
+    {0x0de9, 0x0e49}, /*                 Thai_maitho ้ THAI CHARACTER MAI THO */
+    {0x0dea, 0x0e4a}, /*                 Thai_maitri ๊ THAI CHARACTER MAI TRI */
+    {0x0deb, 0x0e4b}, /*            Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */
+    {0x0dec, 0x0e4c}, /*            Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */
+    {0x0ded, 0x0e4d}, /*               Thai_nikhahit ํ THAI CHARACTER NIKHAHIT */
+    {0x0df0, 0x0e50}, /*                 Thai_leksun ๐ THAI DIGIT ZERO */
+    {0x0df1, 0x0e51}, /*                Thai_leknung ๑ THAI DIGIT ONE */
+    {0x0df2, 0x0e52}, /*                Thai_leksong ๒ THAI DIGIT TWO */
+    {0x0df3, 0x0e53}, /*                 Thai_leksam ๓ THAI DIGIT THREE */
+    {0x0df4, 0x0e54}, /*                  Thai_leksi ๔ THAI DIGIT FOUR */
+    {0x0df5, 0x0e55}, /*                  Thai_lekha ๕ THAI DIGIT FIVE */
+    {0x0df6, 0x0e56}, /*                 Thai_lekhok ๖ THAI DIGIT SIX */
+    {0x0df7, 0x0e57}, /*                Thai_lekchet ๗ THAI DIGIT SEVEN */
+    {0x0df8, 0x0e58}, /*                Thai_lekpaet ๘ THAI DIGIT EIGHT */
+    {0x0df9, 0x0e59}, /*                 Thai_lekkao ๙ THAI DIGIT NINE */
+    {0x0ea1, 0x3131}, /*               Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */
+    {0x0ea2, 0x3132}, /*          Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */
+    {0x0ea3, 0x3133}, /*           Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */
+    {0x0ea4, 0x3134}, /*                Hangul_Nieun ㄴ HANGUL LETTER NIEUN */
+    {0x0ea5, 0x3135}, /*           Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */
+    {0x0ea6, 0x3136}, /*           Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */
+    {0x0ea7, 0x3137}, /*               Hangul_Dikeud ㄷ HANGUL LETTER TIKEUT */
+    {0x0ea8, 0x3138}, /*          Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */
+    {0x0ea9, 0x3139}, /*                Hangul_Rieul ㄹ HANGUL LETTER RIEUL */
+    {0x0eaa, 0x313a}, /*          Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */
+    {0x0eab, 0x313b}, /*           Hangul_RieulMieum ㄻ HANGUL LETTER RIEUL-MIEUM */
+    {0x0eac, 0x313c}, /*           Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */
+    {0x0ead, 0x313d}, /*            Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */
+    {0x0eae, 0x313e}, /*           Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */
+    {0x0eaf, 0x313f}, /*          Hangul_RieulPhieuf ㄿ HANGUL LETTER RIEUL-PHIEUPH */
+    {0x0eb0, 0x3140}, /*           Hangul_RieulHieuh ㅀ HANGUL LETTER RIEUL-HIEUH */
+    {0x0eb1, 0x3141}, /*                Hangul_Mieum ㅁ HANGUL LETTER MIEUM */
+    {0x0eb2, 0x3142}, /*                Hangul_Pieub ㅂ HANGUL LETTER PIEUP */
+    {0x0eb3, 0x3143}, /*           Hangul_SsangPieub ㅃ HANGUL LETTER SSANGPIEUP */
+    {0x0eb4, 0x3144}, /*            Hangul_PieubSios ㅄ HANGUL LETTER PIEUP-SIOS */
+    {0x0eb5, 0x3145}, /*                 Hangul_Sios ㅅ HANGUL LETTER SIOS */
+    {0x0eb6, 0x3146}, /*            Hangul_SsangSios ㅆ HANGUL LETTER SSANGSIOS */
+    {0x0eb7, 0x3147}, /*                Hangul_Ieung ㅇ HANGUL LETTER IEUNG */
+    {0x0eb8, 0x3148}, /*                Hangul_Jieuj ㅈ HANGUL LETTER CIEUC */
+    {0x0eb9, 0x3149}, /*           Hangul_SsangJieuj ㅉ HANGUL LETTER SSANGCIEUC */
+    {0x0eba, 0x314a}, /*                Hangul_Cieuc ㅊ HANGUL LETTER CHIEUCH */
+    {0x0ebb, 0x314b}, /*               Hangul_Khieuq ㅋ HANGUL LETTER KHIEUKH */
+    {0x0ebc, 0x314c}, /*                Hangul_Tieut ㅌ HANGUL LETTER THIEUTH */
+    {0x0ebd, 0x314d}, /*               Hangul_Phieuf ㅍ HANGUL LETTER PHIEUPH */
+    {0x0ebe, 0x314e}, /*                Hangul_Hieuh ㅎ HANGUL LETTER HIEUH */
+    {0x0ebf, 0x314f}, /*                    Hangul_A ㅏ HANGUL LETTER A */
+    {0x0ec0, 0x3150}, /*                   Hangul_AE ㅐ HANGUL LETTER AE */
+    {0x0ec1, 0x3151}, /*                   Hangul_YA ㅑ HANGUL LETTER YA */
+    {0x0ec2, 0x3152}, /*                  Hangul_YAE ㅒ HANGUL LETTER YAE */
+    {0x0ec3, 0x3153}, /*                   Hangul_EO ㅓ HANGUL LETTER EO */
+    {0x0ec4, 0x3154}, /*                    Hangul_E ㅔ HANGUL LETTER E */
+    {0x0ec5, 0x3155}, /*                  Hangul_YEO ㅕ HANGUL LETTER YEO */
+    {0x0ec6, 0x3156}, /*                   Hangul_YE ㅖ HANGUL LETTER YE */
+    {0x0ec7, 0x3157}, /*                    Hangul_O ㅗ HANGUL LETTER O */
+    {0x0ec8, 0x3158}, /*                   Hangul_WA ㅘ HANGUL LETTER WA */
+    {0x0ec9, 0x3159}, /*                  Hangul_WAE ㅙ HANGUL LETTER WAE */
+    {0x0eca, 0x315a}, /*                   Hangul_OE ㅚ HANGUL LETTER OE */
+    {0x0ecb, 0x315b}, /*                   Hangul_YO ㅛ HANGUL LETTER YO */
+    {0x0ecc, 0x315c}, /*                    Hangul_U ㅜ HANGUL LETTER U */
+    {0x0ecd, 0x315d}, /*                  Hangul_WEO ㅝ HANGUL LETTER WEO */
+    {0x0ece, 0x315e}, /*                   Hangul_WE ㅞ HANGUL LETTER WE */
+    {0x0ecf, 0x315f}, /*                   Hangul_WI ㅟ HANGUL LETTER WI */
+    {0x0ed0, 0x3160}, /*                   Hangul_YU ㅠ HANGUL LETTER YU */
+    {0x0ed1, 0x3161}, /*                   Hangul_EU ㅡ HANGUL LETTER EU */
+    {0x0ed2, 0x3162}, /*                   Hangul_YI ㅢ HANGUL LETTER YI */
+    {0x0ed3, 0x3163}, /*                    Hangul_I ㅣ HANGUL LETTER I */
+    {0x0ed4, 0x11a8}, /*             Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */
+    {0x0ed5, 0x11a9}, /*        Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */
+    {0x0ed6, 0x11aa}, /*         Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */
+    {0x0ed7, 0x11ab}, /*              Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */
+    {0x0ed8, 0x11ac}, /*         Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */
+    {0x0ed9, 0x11ad}, /*         Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */
+    {0x0eda, 0x11ae}, /*             Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */
+    {0x0edb, 0x11af}, /*              Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */
+    {0x0edc, 0x11b0}, /*        Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */
+    {0x0edd, 0x11b1}, /*         Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */
+    {0x0ede, 0x11b2}, /*         Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */
+    {0x0edf, 0x11b3}, /*          Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */
+    {0x0ee0, 0x11b4}, /*         Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */
+    {0x0ee1, 0x11b5}, /*        Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */
+    {0x0ee2, 0x11b6}, /*         Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */
+    {0x0ee3, 0x11b7}, /*              Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */
+    {0x0ee4, 0x11b8}, /*              Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */
+    {0x0ee5, 0x11b9}, /*          Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */
+    {0x0ee6, 0x11ba}, /*               Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */
+    {0x0ee7, 0x11bb}, /*          Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */
+    {0x0ee8, 0x11bc}, /*              Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */
+    {0x0ee9, 0x11bd}, /*              Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */
+    {0x0eea, 0x11be}, /*              Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */
+    {0x0eeb, 0x11bf}, /*             Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */
+    {0x0eec, 0x11c0}, /*              Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */
+    {0x0eed, 0x11c1}, /*             Hangul_J_Phieuf ᇁ HANGUL JONGSEONG PHIEUPH */
+    {0x0eee, 0x11c2}, /*              Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */
+    {0x0eef, 0x316d}, /*     Hangul_RieulYeorinHieuh ㅭ HANGUL LETTER RIEUL-YEORINHIEUH */
+    {0x0ef0, 0x3171}, /*    Hangul_SunkyeongeumMieum ㅱ HANGUL LETTER KAPYEOUNMIEUM */
+    {0x0ef1, 0x3178}, /*    Hangul_SunkyeongeumPieub ㅸ HANGUL LETTER KAPYEOUNPIEUP */
+    {0x0ef2, 0x317f}, /*              Hangul_PanSios ㅿ HANGUL LETTER PANSIOS */
+                      /*  0x0ef3                  Hangul_KkogjiDalrinIeung ? ??? */
+    {0x0ef4, 0x3184}, /*   Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */
+    {0x0ef5, 0x3186}, /*          Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */
+    {0x0ef6, 0x318d}, /*                Hangul_AraeA ㆍ HANGUL LETTER ARAEA */
+    {0x0ef7, 0x318e}, /*               Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */
+    {0x0ef8, 0x11eb}, /*            Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */
+                      /*  0x0ef9                Hangul_J_KkogjiDalrinIeung ? ??? */
+    {0x0efa, 0x11f9}, /*        Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */
+    {0x0eff, 0x20a9}, /*                  Korean_Won ₩ WON SIGN */
+    {0x13bc, 0x0152}, /*                          OE Œ LATIN CAPITAL LIGATURE OE */
+    {0x13bd, 0x0153}, /*                          oe œ LATIN SMALL LIGATURE OE */
+    {0x13be, 0x0178}, /*                  Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */
+    {0x20ac, 0x20ac}, /*                    EuroSign € EURO SIGN */
 };
 
 long keysym2ucs(xcb_keysym_t keysym) {
index 97d574a220a051efda053e2433042c176859a267..785a133fc10e7de57b307821b1c3302ee0c2069b 100644 (file)
 
 #include "i3-input.h"
 
+#define MAX_WIDTH logical_px(500)
+#define BORDER logical_px(2)
+#define PADDING logical_px(2)
+
 /* IPC format string. %s will be replaced with what the user entered, then
  * the command will be sent to i3 */
 static char *format;
@@ -42,8 +46,7 @@ static int sockfd;
 static xcb_key_symbols_t *symbols;
 static bool modeswitch_active = false;
 static xcb_window_t win;
-static xcb_pixmap_t pixmap;
-static xcb_gcontext_t pixmap_gc;
+static surface_t surface;
 static xcb_char2b_t glyphs_ucs[512];
 static char *glyphs_utf8[512];
 static int input_position;
@@ -54,7 +57,6 @@ static int limit;
 xcb_window_t root;
 xcb_connection_t *conn;
 xcb_screen_t *root_screen;
-static xcb_get_input_focus_cookie_t focus_cookie;
 
 /*
  * Having verboselog(), errorlog() and debuglog() is necessary when using libi3.
@@ -79,24 +81,6 @@ void errorlog(char *fmt, ...) {
 void debuglog(char *fmt, ...) {
 }
 
-/*
- * Restores the X11 input focus to wherever it was before.
- * This is necessary because i3-input’s window has override_redirect=1
- * (→ unmanaged by the window manager) and thus i3-input changes focus itself.
- * This function is called on exit().
- *
- */
-static void restore_input_focus(void) {
-    xcb_generic_error_t *error;
-    xcb_get_input_focus_reply_t *reply = xcb_get_input_focus_reply(conn, focus_cookie, &error);
-    if (error != NULL) {
-        fprintf(stderr, "[i3-input] ERROR: Could not restore input focus (X error %d)\n", error->error_code);
-        return;
-    }
-    xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, reply->focus, XCB_CURRENT_TIME);
-    xcb_flush(conn);
-}
-
 /*
  * Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for
  * rendering it (UCS-2) or sending it to i3 (UTF-8).
@@ -128,30 +112,30 @@ static uint8_t *concat_strings(char **glyphs, int max) {
 static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) {
     printf("expose!\n");
 
-    /* re-draw the background */
-    xcb_rectangle_t border = {0, 0, logical_px(500), font.height + logical_px(8)},
-                    inner = {logical_px(2), logical_px(2), logical_px(496), font.height + logical_px(8) - logical_px(4)};
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#FF0000")});
-    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")});
-    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
+    color_t border_color = draw_util_hex_to_color("#FF0000");
+    color_t fg_color = draw_util_hex_to_color("#FFFFFF");
+    color_t bg_color = draw_util_hex_to_color("#000000");
 
-    /* restore font color */
-    set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000"));
+    int text_offset = BORDER + PADDING;
+
+    /* draw border */
+    draw_util_rectangle(&surface, border_color, 0, 0, surface.width, surface.height);
+
+    /* draw background */
+    draw_util_rectangle(&surface, bg_color, BORDER, BORDER, surface.width - 2 * BORDER, surface.height - 2 * BORDER);
 
     /* draw the prompt … */
     if (prompt != NULL) {
-        draw_text(prompt, pixmap, pixmap_gc, NULL, logical_px(4), logical_px(4), logical_px(492));
+        draw_util_text(prompt, &surface, fg_color, bg_color, text_offset, text_offset, MAX_WIDTH - text_offset);
     }
+
     /* … and the text */
     if (input_position > 0) {
         i3String *input = i3string_from_ucs2(glyphs_ucs, input_position);
-        draw_text(input, pixmap, pixmap_gc, NULL, prompt_offset + logical_px(4), logical_px(4), logical_px(492));
+        draw_util_text(input, &surface, fg_color, bg_color, text_offset + prompt_offset, text_offset, MAX_WIDTH - text_offset);
         i3string_free(input);
     }
 
-    /* Copy the contents of the pixmap to the real window */
-    xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, logical_px(500), font.height + logical_px(8));
     xcb_flush(conn);
 
     return 1;
@@ -208,10 +192,6 @@ static void finish_input() {
     /* prefix the command if a prefix was specified on commandline */
     printf("command = %s\n", full);
 
-    restore_input_focus();
-
-    xcb_aux_sync(conn);
-
     ipc_send_message(sockfd, strlen(full), 0, (uint8_t *)full);
 
     free(full);
@@ -265,7 +245,6 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
         return 1;
     }
     if (sym == XK_Escape) {
-        restore_input_focus();
         exit(0);
     }
 
@@ -311,7 +290,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
 }
 
 static xcb_rectangle_t get_window_position(void) {
-    xcb_rectangle_t result = (xcb_rectangle_t){logical_px(50), logical_px(50), logical_px(500), font.height + logical_px(8)};
+    xcb_rectangle_t result = (xcb_rectangle_t){logical_px(50), logical_px(50), MAX_WIDTH, font.height + 2 * BORDER + 2 * PADDING};
 
     xcb_get_property_reply_t *supporting_wm_reply = NULL;
     xcb_get_input_focus_reply_t *input_focus = NULL;
@@ -467,14 +446,12 @@ int main(int argc, char *argv[]) {
 
     sockfd = ipc_connect(socket_path);
 
-    /* Request the current InputFocus to restore when i3-input exits. */
-    focus_cookie = xcb_get_input_focus(conn);
-
     root_screen = xcb_aux_get_screen(conn, screen);
     root = root_screen->root;
 
     symbols = xcb_key_symbols_alloc(conn);
 
+    init_dpi();
     font = load_font(pattern, true);
     set_font(&font);
 
@@ -503,15 +480,8 @@ int main(int argc, char *argv[]) {
     /* Map the window (make it visible) */
     xcb_map_window(conn, win);
 
-    /* Create pixmap */
-    pixmap = xcb_generate_id(conn);
-    pixmap_gc = xcb_generate_id(conn);
-    xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, logical_px(500), font.height + logical_px(8));
-    xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
-
-    /* Set input focus (we have override_redirect=1, so the wm will not do
-     * this for us) */
-    xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
+    /* Initialize the drawable surface */
+    draw_util_surface_init(conn, &surface, win, get_visualtype(root_screen), win_pos.width, win_pos.height);
 
     /* Grab the keyboard to get all input */
     xcb_flush(conn);
@@ -531,7 +501,6 @@ int main(int argc, char *argv[]) {
 
     if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
         fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
-        restore_input_focus();
         exit(-1);
     }
 
@@ -557,12 +526,16 @@ int main(int argc, char *argv[]) {
                 break;
 
             case XCB_EXPOSE:
-                handle_expose(NULL, conn, (xcb_expose_event_t *)event);
+                if (((xcb_expose_event_t *)event)->count == 0) {
+                    handle_expose(NULL, conn, (xcb_expose_event_t *)event);
+                }
+
                 break;
         }
 
         free(event);
     }
 
+    draw_util_surface_free(conn, &surface);
     return 0;
 }
index 02e156a16764952f6308403316e60c353ce35322..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') {
@@ -180,6 +219,8 @@ int main(int argc, char *argv[]) {
             printf("i3-msg " I3_VERSION "\n");
             printf("i3-msg [-s <socket>] [-t <type>] <message>\n");
             return 0;
+        } else if (o == '?') {
+            exit(EXIT_FAILURE);
         }
     }
 
@@ -239,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);
@@ -254,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 2e9e77d490fe3af6e36c9383fc91880d40874d27..7d38f73140d741c635aa0de25bd0e5809d94df75 100644 (file)
  * constant for that. */
 #define XCB_CURSOR_LEFT_PTR 68
 
+#define MSG_PADDING logical_px(8)
+#define BTN_PADDING logical_px(3)
+#define BTN_BORDER logical_px(3)
+#define BTN_GAP logical_px(20)
+#define CLOSE_BTN_GAP logical_px(15)
+#define BAR_BORDER logical_px(2)
+
 static char *argv0 = NULL;
 
 typedef struct {
@@ -48,11 +55,12 @@ typedef struct {
 } button_t;
 
 static xcb_window_t win;
-static xcb_pixmap_t pixmap;
-static xcb_gcontext_t pixmap_gc;
-static xcb_rectangle_t rect = {0, 0, 600, 20};
+static surface_t bar;
+
 static i3Font font;
 static i3String *prompt;
+
+static button_t btn_close;
 static button_t *buttons;
 static int buttoncnt;
 
@@ -138,7 +146,7 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
     printf("button released on x = %d, y = %d\n",
            event->event_x, event->event_y);
     /* If the user hits the close button, we exit(0) */
-    if (event->event_x >= (rect.width - logical_px(32)))
+    if (event->event_x >= btn_close.x && event->event_x < btn_close.x + btn_close.width)
         exit(0);
     button_t *button = get_button_at(event->event_x, event->event_y);
     if (!button)
@@ -190,108 +198,64 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
     /* TODO: unset flag, re-render */
 }
 
+/*
+ * Draws a button and returns its width
+ *
+ */
+static int button_draw(button_t *button, int position) {
+    int text_width = predict_text_width(button->label);
+    button->width = text_width + 2 * BTN_PADDING + 2 * BTN_BORDER;
+    button->x = position - button->width;
+
+    /* draw border */
+    draw_util_rectangle(&bar, color_border,
+                        position - button->width,
+                        MSG_PADDING - BTN_PADDING - BTN_BORDER,
+                        button->width,
+                        font.height + 2 * BTN_PADDING + 2 * BTN_BORDER);
+    /* draw background */
+    draw_util_rectangle(&bar, color_button_background,
+                        position - button->width + BTN_BORDER,
+                        MSG_PADDING - BTN_PADDING,
+                        text_width + 2 * BTN_PADDING,
+                        font.height + 2 * BTN_PADDING);
+    /* draw label */
+    draw_util_text(button->label, &bar, color_text, color_button_background,
+                   position - button->width + BTN_BORDER + BTN_PADDING,
+                   MSG_PADDING,
+                   200);
+    return button->width;
+}
+
 /*
  * Handles expose events (redraws of the window) and rendering in general. Will
  * be called from the code with event == NULL or from X with event != NULL.
  *
  */
 static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
-    /* re-draw the background */
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_background.colorpixel});
-    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect);
+    /* draw background */
+    draw_util_clear_surface(&bar, color_background);
+    /* draw message */
+    draw_util_text(prompt, &bar, color_text, color_background,
+                   MSG_PADDING, MSG_PADDING,
+                   bar.width - 2 * MSG_PADDING);
 
-    /* restore font color */
-    set_font_colors(pixmap_gc, color_text, color_background);
-    draw_text(prompt, pixmap, pixmap_gc, NULL,
-              logical_px(4) + logical_px(4),
-              logical_px(4) + logical_px(4),
-              rect.width - logical_px(4) - logical_px(4));
+    int position = bar.width - (MSG_PADDING - BTN_BORDER - BTN_PADDING);
 
     /* render close button */
-    const char *close_button_label = "X";
-    int line_width = logical_px(4);
-    /* set width to the width of the label */
-    int w = predict_text_width(i3string_from_utf8(close_button_label));
-    /* account for left/right padding, which seems to be set to 8px (total) below */
-    w += logical_px(8);
-    int y = rect.width;
-    uint32_t values[3];
-    values[0] = color_button_background.colorpixel;
-    values[1] = line_width;
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
-
-    xcb_rectangle_t close = {y - w - (2 * line_width), 0, w + (2 * line_width), rect.height};
-    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
-
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border.colorpixel});
-    xcb_point_t points[] = {
-        {y - w - (2 * line_width), line_width / 2},
-        {y - (line_width / 2), line_width / 2},
-        {y - (line_width / 2), (rect.height - (line_width / 2)) - logical_px(2)},
-        {y - w - (2 * line_width), (rect.height - (line_width / 2)) - logical_px(2)},
-        {y - w - (2 * line_width), line_width / 2}};
-    xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points);
-
-    values[0] = 1;
-    set_font_colors(pixmap_gc, color_text, color_button_background);
-    /* the x term here seems to set left/right padding */
-    draw_text_ascii(close_button_label, pixmap, pixmap_gc,
-                    y - w - line_width + w / 2 - logical_px(4),
-                    logical_px(4) + logical_px(3),
-                    rect.width - y + w + line_width - w / 2 + logical_px(4));
-    y -= w;
-
-    y -= logical_px(20);
+    position -= button_draw(&btn_close, position);
+    position -= CLOSE_BTN_GAP;
 
     /* render custom buttons */
-    line_width = 1;
-    for (int c = 0; c < buttoncnt; c++) {
-        /* set w to the width of the label */
-        w = predict_text_width(buttons[c].label);
-        /* account for left/right padding, which seems to be set to 12px (total) below */
-        w += logical_px(12);
-        y -= logical_px(30);
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_button_background.colorpixel});
-        close = (xcb_rectangle_t){y - w - (2 * line_width), logical_px(2), w + (2 * line_width), rect.height - logical_px(6)};
-        xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
-
-        xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border.colorpixel});
-        buttons[c].x = y - w - (2 * line_width);
-        buttons[c].width = w;
-        xcb_point_t points2[] = {
-            {y - w - (2 * line_width), (line_width / 2) + logical_px(2)},
-            {y - (line_width / 2), (line_width / 2) + logical_px(2)},
-            {y - (line_width / 2), (rect.height - logical_px(4) - (line_width / 2))},
-            {y - w - (2 * line_width), (rect.height - logical_px(4) - (line_width / 2))},
-            {y - w - (2 * line_width), (line_width / 2) + logical_px(2)}};
-        xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2);
-
-        values[0] = color_text.colorpixel;
-        values[1] = color_button_background.colorpixel;
-        set_font_colors(pixmap_gc, color_text, color_button_background);
-        /* the x term seems to set left/right padding */
-        draw_text(buttons[c].label, pixmap, pixmap_gc, NULL,
-                  y - w - line_width + logical_px(6),
-                  logical_px(4) + logical_px(3),
-                  rect.width - y + w + line_width - logical_px(6));
-
-        y -= w;
+    for (int i = 0; i < buttoncnt; i++) {
+        position -= BTN_GAP;
+        position -= button_draw(&buttons[i], position);
     }
 
     /* border line at the bottom */
-    line_width = logical_px(2);
-    values[0] = color_border_bottom.colorpixel;
-    values[1] = line_width;
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
-    xcb_point_t bottom[] = {
-        {0, rect.height - 0},
-        {rect.width, rect.height - 0}};
-    xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 2, bottom);
-
-    /* Copy the contents of the pixmap to the real window */
-    xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, rect.width, rect.height);
-    xcb_flush(conn);
+    draw_util_rectangle(&bar, color_border_bottom, 0, bar.height - BAR_BORDER, bar.width, BAR_BORDER);
 
+    xcb_flush(conn);
     return 1;
 }
 
@@ -301,7 +265,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
  */
 static xcb_rectangle_t get_window_position(void) {
     /* Default values if we cannot determine the primary output or its CRTC info. */
-    xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + logical_px(8) + logical_px(8)};
+    xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + 2 * MSG_PADDING + BAR_BORDER};
 
     xcb_randr_get_screen_resources_current_cookie_t rcookie = xcb_randr_get_screen_resources_current(conn, root);
     xcb_randr_get_output_primary_cookie_t pcookie = xcb_randr_get_output_primary(conn, root);
@@ -438,6 +402,8 @@ int main(int argc, char *argv[]) {
         }
     }
 
+    btn_close.label = i3string_from_utf8("X");
+
     int screens;
     if ((conn = xcb_connect(NULL, &screens)) == NULL ||
         xcb_connection_has_error(conn))
@@ -468,6 +434,7 @@ int main(int argc, char *argv[]) {
         color_border_bottom = draw_util_hex_to_color("#ab7100");
     }
 
+    init_dpi();
     font = load_font(pattern, true);
     set_font(&font);
 
@@ -574,11 +541,8 @@ int main(int argc, char *argv[]) {
                         12,
                         &strut_partial);
 
-    /* Create pixmap */
-    pixmap = xcb_generate_id(conn);
-    pixmap_gc = xcb_generate_id(conn);
-    xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + logical_px(8));
-    xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
+    /* Initialize the drawable bar */
+    draw_util_surface_init(conn, &bar, win, get_visualtype(root_screen), win_pos.width, win_pos.height);
 
     /* Grab the keyboard to get all input */
     xcb_flush(conn);
@@ -595,7 +559,10 @@ int main(int argc, char *argv[]) {
 
         switch (type) {
             case XCB_EXPOSE:
-                handle_expose(conn, (xcb_expose_event_t *)event);
+                if (((xcb_expose_event_t *)event)->count == 0) {
+                    handle_expose(conn, (xcb_expose_event_t *)event);
+                }
+
                 break;
 
             case XCB_BUTTON_PRESS:
@@ -608,18 +575,7 @@ int main(int argc, char *argv[]) {
 
             case XCB_CONFIGURE_NOTIFY: {
                 xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t *)event;
-                rect = (xcb_rectangle_t){
-                    configure_notify->x,
-                    configure_notify->y,
-                    configure_notify->width,
-                    configure_notify->height};
-
-                /* Recreate the pixmap / gc */
-                xcb_free_pixmap(conn, pixmap);
-                xcb_free_gc(conn, pixmap_gc);
-
-                xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, rect.width, rect.height);
-                xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
+                draw_util_surface_set_size(&bar, configure_notify->width, configure_notify->height);
                 break;
             }
         }
@@ -628,6 +584,7 @@ int main(int argc, char *argv[]) {
     }
 
     FREE(pattern);
+    draw_util_surface_free(conn, &bar);
 
     return 0;
 }
index b93893a17576626563336c03e9bf616cfc5c5ee5..ad3f6bddb433d3101636baefaaa402f6c9578d5f 100755 (executable)
@@ -9,7 +9,7 @@
 # mechanism to find the preferred editor
 
 # Hopefully one of these is installed (no flamewars about preference please!):
-for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mc-edit; do
+for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mcedit; do
     if command -v "$editor" > /dev/null 2>&1; then
         exec "$editor" "$@"
     fi
index 4ef1c75f613c2b95370b46563c049ca1a8433cef..f92ff224a8989d3fbef991257d0a9ccd8deb56b7 100755 (executable)
@@ -8,7 +8,7 @@
 # We welcome patches that add distribution-specific mechanisms to find the
 # preferred terminal emulator. On Debian, there is the x-terminal-emulator
 # symlink for example.
-for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal; do
+for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole; do
     if command -v "$terminal" > /dev/null 2>&1; then
         exec "$terminal" "$@"
     fi
index 0929e4082371ba91a029137d873e0365db1ca602..77be318235681341c41ad39453f967544219383d 100644 (file)
@@ -74,10 +74,12 @@ struct status_block {
     char *name;
     char *instance;
 
-    TAILQ_ENTRY(status_block) blocks;
+    TAILQ_ENTRY(status_block)
+    blocks;
 };
 
-TAILQ_HEAD(statusline_head, status_block) statusline_head;
+TAILQ_HEAD(statusline_head, status_block)
+statusline_head;
 
 #include "child.h"
 #include "ipc.h"
index c7c1f5e2fd9d758fa8c381717375b108054ef7e2..e77e891b9f3bdf03afd548839b9d6508162a3b5a 100644 (file)
@@ -28,18 +28,23 @@ typedef struct binding_t {
     int input_code;
     char *command;
 
-    TAILQ_ENTRY(binding_t) bindings;
+    TAILQ_ENTRY(binding_t)
+    bindings;
 } binding_t;
 
 typedef struct tray_output_t {
     char *output;
 
-    TAILQ_ENTRY(tray_output_t) tray_outputs;
+    TAILQ_ENTRY(tray_output_t)
+    tray_outputs;
 } tray_output_t;
 
 typedef struct config_t {
     int modifier;
-    TAILQ_HEAD(bindings_head, binding_t) bindings;
+
+    TAILQ_HEAD(bindings_head, binding_t)
+    bindings;
+
     position_t position;
     int verbose;
     struct xcb_color_strings_t colors;
@@ -50,7 +55,10 @@ typedef struct config_t {
     char *command;
     char *fontname;
     i3String *separator_symbol;
-    TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs;
+
+    TAILQ_HEAD(tray_outputs_head, tray_output_t)
+    tray_outputs;
+
     int tray_padding;
     int num_outputs;
     char **outputs;
index 3067581da0ab4dbd0db9b86d74fdac2c848707f0..de960270e500186dce9c032e1a548b5d486f1bd7 100644 (file)
@@ -67,5 +67,6 @@ struct i3_output {
     struct ws_head* workspaces;  /* The workspaces on this output */
     struct tc_head* trayclients; /* The tray clients on this output */
 
-    SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */
+    SLIST_ENTRY(i3_output)
+    slist; /* Pointer for the SLIST-Macro */
 };
index 694faa483a7ac5682812094503d888ff2362f055..db954bb1d5758e56869da9586758f41b17e9debb 100644 (file)
@@ -18,5 +18,6 @@ struct trayclient {
     bool mapped;      /* Whether this window is mapped */
     int xe_version;   /* The XEMBED version supported by the client */
 
-    TAILQ_ENTRY(trayclient) tailq; /* Pointer for the TAILQ-Macro */
+    TAILQ_ENTRY(trayclient)
+    tailq; /* Pointer for the TAILQ-Macro */
 };
index dde8b6e1689c718063c1cd28ddc9fa2208b89256..e1f9e8878a57a374bb77c4b60d1f7179e7128599 100644 (file)
@@ -40,5 +40,6 @@ struct i3_ws {
     rect rect;                /* The rect of the ws (not used (yet)) */
     struct i3_output *output; /* The current output of the ws */
 
-    TAILQ_ENTRY(i3_ws) tailq; /* Pointer for the TAILQ-Macro */
+    TAILQ_ENTRY(i3_ws)
+    tailq; /* Pointer for the TAILQ-Macro */
 };
index 60ab462a5a73fba00e62b11e93f79439c86f265b..814f041110d48db57c296a6cc0a47d59ce98954d 100644 (file)
@@ -112,13 +112,13 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha
     struct status_block *err_block = scalloc(1, sizeof(struct status_block));
     err_block->full_text = i3string_from_utf8("Error: ");
     err_block->name = sstrdup("error");
-    err_block->color = sstrdup("red");
+    err_block->color = sstrdup("#ff0000");
     err_block->no_separator = true;
 
     struct status_block *message_block = scalloc(1, sizeof(struct status_block));
     message_block->full_text = i3string_from_utf8(message);
     message_block->name = sstrdup("error_message");
-    message_block->color = sstrdup("red");
+    message_block->color = sstrdup("#ff0000");
     message_block->no_separator = true;
 
     TAILQ_INSERT_HEAD(&statusline_head, err_block, blocks);
index d065ff4ce88ee6effd0d00cbdacfa0cc0afbdb8f..cbe84d5073e55b714a2d564fd4d666fdfb872e1a 100644 (file)
@@ -185,6 +185,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
 
     if (!strcmp(cur_key, "status_command")) {
         DLOG("command = %.*s\n", len, val);
+        FREE(config.command);
         sasprintf(&config.command, "%.*s", len, val);
         return 1;
     }
index 4a090ad794f16fac5339ff03a4401936496753b1..c932aaf7d3b2536d864aa3dbedc6c47ff6824908 100644 (file)
@@ -63,16 +63,26 @@ void got_subscribe_reply(char *reply) {
  *
  */
 void got_output_reply(char *reply) {
+    DLOG("Clearing old output configuration...\n");
+    i3_output *o_walk;
+    SLIST_FOREACH(o_walk, outputs, slist) {
+        destroy_window(o_walk);
+    }
+    FREE_SLIST(outputs, i3_output);
+
     DLOG("Parsing outputs JSON...\n");
     parse_outputs_json(reply);
     DLOG("Reconfiguring windows...\n");
     reconfig_windows(false);
 
-    i3_output *o_walk;
     SLIST_FOREACH(o_walk, outputs, slist) {
         kick_tray_clients(o_walk);
     }
 
+    if (!config.disable_ws) {
+        i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
+    }
+
     draw_bars(false);
 }
 
@@ -103,7 +113,6 @@ void got_bar_config(char *reply) {
     init_colors(&(config.colors));
 
     start_child(config.command);
-    FREE(config.command);
 }
 
 /* Data structure to easily call the reply handlers later */
@@ -168,6 +177,7 @@ void got_bar_config_update(char *event) {
 
     /* update the configuration with the received settings */
     DLOG("Received bar config update \"%s\"\n", event);
+    char *old_command = sstrdup(config.command);
     bar_display_mode_t old_mode = config.hide_on_modifier;
     parse_config_json(event);
     if (old_mode != config.hide_on_modifier) {
@@ -178,6 +188,13 @@ void got_bar_config_update(char *event) {
     init_xcb_late(config.fontname);
     init_colors(&(config.colors));
 
+    /* restart status command process */
+    if (strcmp(old_command, config.command) != 0) {
+        kill_child();
+        start_child(config.command);
+    }
+    free(old_command);
+
     draw_bars(false);
 }
 
index be684fc5d1f32b935f994e876dd1a203718bdc37..910e952482f0d788b46747ac0b9bab396242f7c5 100644 (file)
@@ -149,6 +149,8 @@ int main(int argc, char **argv) {
         socket_path = expand_path(i3_default_sock_path);
     }
 
+    init_dpi();
+
     init_outputs();
     if (init_connection(socket_path)) {
         /* Request the bar configuration. When it arrives, we fill the config array. */
index 480c00be7dd90a1b9e500b3074e87888da64bcbc..bd056a700d231a49eadfcee3120bc156364a9d6b 100644 (file)
@@ -189,11 +189,12 @@ static int outputs_end_map_cb(void *params_) {
     if (config.num_outputs > 0) {
         bool handle_output = false;
         for (int c = 0; c < config.num_outputs; c++) {
-            if (strcasecmp(params->outputs_walk->name, config.outputs[c]) != 0)
-                continue;
-
-            handle_output = true;
-            break;
+            if (strcasecmp(params->outputs_walk->name, config.outputs[c]) == 0 ||
+                (strcasecmp(config.outputs[c], "primary") == 0 &&
+                 params->outputs_walk->primary)) {
+                handle_output = true;
+                break;
+            }
         }
         if (!handle_output) {
             DLOG("Ignoring output \"%s\", not configured to handle it.\n",
index 2715e44711cb327be871f07ec4653070a351b917..2ba446b1176be8596d34d2b3b7c6e19287dcd19a 100644 (file)
@@ -181,7 +181,7 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b
     uint32_t center_x = x - sep_offset;
     if (config.separator_symbol == NULL) {
         /* Draw a classic one pixel, vertical separator. */
-        draw_util_rectangle(xcb_connection, &output->statusline_buffer, sep_fg,
+        draw_util_rectangle(&output->statusline_buffer, sep_fg,
                             center_x,
                             logical_px(sep_voff_px),
                             logical_px(1),
@@ -250,7 +250,7 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_color
     struct status_block *block;
 
     color_t bar_color = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg);
-    draw_util_clear_surface(xcb_connection, &output->statusline_buffer, bar_color);
+    draw_util_clear_surface(&output->statusline_buffer, bar_color);
 
     /* Use unsigned integer wraparound to clip off the left side.
      * For example, if clip_left is 75, then x will start at the very large
@@ -301,13 +301,13 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_color
             }
 
             /* Draw the border. */
-            draw_util_rectangle(xcb_connection, &output->statusline_buffer, border_color,
+            draw_util_rectangle(&output->statusline_buffer, border_color,
                                 x, logical_px(1),
                                 full_render_width,
                                 bar_height - logical_px(2));
 
             /* Draw the background. */
-            draw_util_rectangle(xcb_connection, &output->statusline_buffer, bg_color,
+            draw_util_rectangle(&output->statusline_buffer, bg_color,
                                 x + border_width,
                                 logical_px(1) + border_width,
                                 full_render_width - 2 * border_width,
@@ -531,7 +531,8 @@ void handle_button(xcb_button_press_event_t *event) {
         return;
     }
     switch (event->detail) {
-        case 4:
+        case XCB_BUTTON_SCROLL_UP:
+        case XCB_BUTTON_SCROLL_LEFT:
             /* Mouse wheel up. We select the previous ws, if any.
              * If there is no more workspace, don’t even send the workspace
              * command, otherwise (with workspace auto_back_and_forth) we’d end
@@ -541,7 +542,8 @@ void handle_button(xcb_button_press_event_t *event) {
 
             cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq);
             break;
-        case 5:
+        case XCB_BUTTON_SCROLL_DOWN:
+        case XCB_BUTTON_SCROLL_RIGHT:
             /* Mouse wheel down. We select the next ws, if any.
              * If there is no more workspace, don’t even send the workspace
              * command, otherwise (with workspace auto_back_and_forth) we’d end
@@ -870,11 +872,13 @@ static void handle_destroy_notify(xcb_destroy_notify_event_t *event) {
         DLOG("checking output %s\n", walk->name);
         trayclient *trayclient;
         TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
-            if (trayclient->win != event->window)
+            if (trayclient->win != event->window) {
                 continue;
+            }
 
             DLOG("Removing tray client with window ID %08x\n", event->window);
             TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
+            FREE(trayclient);
 
             /* Trigger an update, we now have more space for the statusline */
             configure_trayclients();
@@ -1147,8 +1151,11 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
                 handle_visibility_notify((xcb_visibility_notify_event_t *)event);
                 break;
             case XCB_EXPOSE:
-                /* Expose-events happen, when the window needs to be redrawn */
-                redraw_bars();
+                if (((xcb_expose_event_t *)event)->count == 0) {
+                    /* Expose-events happen, when the window needs to be redrawn */
+                    redraw_bars();
+                }
+
                 break;
             case XCB_BUTTON_PRESS:
                 /* Button press events are mouse buttons clicked on one of our bars */
@@ -1177,6 +1184,7 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
             case XCB_CONFIGURE_REQUEST:
                 /* ConfigureRequest, sent by a tray child */
                 handle_configure_request((xcb_configure_request_event_t *)event);
+                break;
             case XCB_RESIZE_REQUEST:
                 /* ResizeRequest sent by a tray child using override_redirect. */
                 handle_resize_request((xcb_resize_request_event_t *)event);
@@ -1247,6 +1255,12 @@ char *init_xcb_early() {
     ev_prepare_init(xcb_prep, &xcb_prep_cb);
     ev_check_init(xcb_chk, &xcb_chk_cb);
 
+    /* Within an event loop iteration, run the xcb_chk watcher last: other
+     * watchers might call xcb_flush(), which, unexpectedly, can also read
+     * events into the queue (see _xcb_conn_wait). Hence, we need to drain xcb’s
+     * queue last, otherwise we risk dead-locking. */
+    ev_set_priority(xcb_chk, EV_MINPRI);
+
     ev_io_start(main_loop, xcb_io);
     ev_prepare_start(main_loop, xcb_prep);
     ev_check_start(main_loop, xcb_chk);
@@ -1558,6 +1572,7 @@ void kick_tray_clients(i3_output *output) {
         /* We remove the trayclient right here. We might receive an UnmapNotify
          * event afterwards, but better safe than sorry. */
         TAILQ_REMOVE(output->trayclients, trayclient, tailq);
+        FREE(trayclient);
     }
 
     /* Fake a DestroyNotify so that Qt re-adds tray icons.
@@ -1943,8 +1958,7 @@ void draw_bars(bool unhide) {
         bool use_focus_colors = output_has_focus(outputs_walk);
 
         /* First things first: clear the backbuffer */
-        draw_util_clear_surface(xcb_connection, &(outputs_walk->buffer),
-                                (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg));
+        draw_util_clear_surface(&(outputs_walk->buffer), (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg));
 
         if (!config.disable_ws) {
             i3_ws *ws_walk;
@@ -1974,14 +1988,14 @@ void draw_bars(bool unhide) {
                 }
 
                 /* Draw the border of the button. */
-                draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), border_color,
+                draw_util_rectangle(&(outputs_walk->buffer), border_color,
                                     workspace_width,
                                     logical_px(1),
                                     ws_walk->name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
                                     font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
 
                 /* Draw the inside of the button. */
-                draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), bg_color,
+                draw_util_rectangle(&(outputs_walk->buffer), bg_color,
                                     workspace_width + logical_px(1),
                                     2 * logical_px(1),
                                     ws_walk->name_width + 2 * logical_px(ws_hoff_px),
@@ -2004,13 +2018,13 @@ void draw_bars(bool unhide) {
             color_t fg_color = colors.binding_mode_fg;
             color_t bg_color = colors.binding_mode_bg;
 
-            draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), colors.binding_mode_border,
+            draw_util_rectangle(&(outputs_walk->buffer), colors.binding_mode_border,
                                 workspace_width,
                                 logical_px(1),
                                 binding.width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
                                 font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
 
-            draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), bg_color,
+            draw_util_rectangle(&(outputs_walk->buffer), bg_color,
                                 workspace_width + logical_px(1),
                                 2 * logical_px(1),
                                 binding.width + 2 * logical_px(ws_hoff_px),
@@ -2046,7 +2060,7 @@ void draw_bars(bool unhide) {
             int x_dest = outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width;
 
             draw_statusline(outputs_walk, clip_left, use_focus_colors, use_short_text);
-            draw_util_copy_surface(xcb_connection, &outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0,
+            draw_util_copy_surface(&outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0,
                                    x_dest, 0, visible_statusline_width, (int16_t)bar_height);
 
             outputs_walk->statusline_width = statusline_width;
@@ -2077,7 +2091,7 @@ void redraw_bars(void) {
             continue;
         }
 
-        draw_util_copy_surface(xcb_connection, &(outputs_walk->buffer), &(outputs_walk->bar), 0, 0,
+        draw_util_copy_surface(&(outputs_walk->buffer), &(outputs_walk->bar), 0, 0,
                                0, 0, outputs_walk->rect.w, outputs_walk->rect.h);
         xcb_flush(xcb_connection);
     }
index 1358e0f11d7e3bef6231140f6a9a97adfbefe152..a7b9676d9817e21dcd142c6f62fc9606cada2773 100644 (file)
@@ -31,3 +31,4 @@ xmacro(_NET_DESKTOP_NAMES)
 xmacro(_NET_DESKTOP_VIEWPORT)
 xmacro(_NET_ACTIVE_WINDOW)
 xmacro(_NET_CLOSE_WINDOW)
+xmacro(_NET_MOVERESIZE_WINDOW)
index 0fcc4df4c94f390d43648503c65fd27c95b1e303..df3c32a5a88c8edea7048a39fb7b8d40aefb1883 100644 (file)
@@ -27,7 +27,8 @@ extern const char *DEFAULT_BINDING_MODE;
  */
 Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code,
                            const char *release, const char *border, const char *whole_window,
-                           const char *command, const char *mode, bool pango_markup);
+                           const char *exclude_titlebar, const char *command, const char *mode,
+                           bool pango_markup);
 
 /**
  * Grab the bound keys (tell X to send us keypress events for those keycodes)
index a57b2925e1cee52dd451ff1927ed7dd6db578f28..9780f788b5bb53b308609df0818017e420d77024 100644 (file)
@@ -290,6 +290,12 @@ void cmd_move_scratchpad(I3_CMD);
  */
 void cmd_scratchpad_show(I3_CMD);
 
+/**
+ * Implementation of 'swap [container] [with] id|con_id|mark <arg>'.
+ *
+ */
+void cmd_swap(I3_CMD, const char *mode, const char *arg);
+
 /**
  * Implementation of 'title_format <format>'
  *
index 0c532207ba021d78427691e290fa376ba278ad90..0fa660a10ccd1cad7c55b5d77f3c39f731234036 100644 (file)
@@ -139,6 +139,12 @@ Con *con_inside_floating(Con *con);
  */
 bool con_inside_focused(Con *con);
 
+/**
+ * Checks if the container has the given parent as an actual parent.
+ *
+ */
+bool con_has_parent(Con *con, Con *parent);
+
 /**
  * Returns the container with the given client window ID or NULL if no such
  * container exists.
@@ -461,3 +467,9 @@ void con_force_split_parents_redraw(Con *con);
  *
  */
 i3String *con_parse_title_format(Con *con);
+
+/**
+ * Swaps the two containers.
+ *
+ */
+bool con_swap(Con *first, Con *second);
index 7f1bfe4a091e9613d5d21f0fa186da759107e87e..f35666f3612000c21c4837c592a5b22261bf6d33 100644 (file)
@@ -51,6 +51,7 @@ CFGFUN(focus_follows_mouse, const char *value);
 CFGFUN(mouse_warping, const char *value);
 CFGFUN(force_focus_wrapping, const char *value);
 CFGFUN(force_xinerama, const char *value);
+CFGFUN(disable_randr15, const char *value);
 CFGFUN(fake_outputs, const char *outputs);
 CFGFUN(force_display_urgency_hint, const long duration_ms);
 CFGFUN(focus_on_window_activation, const char *mode);
@@ -66,10 +67,10 @@ CFGFUN(color_single, const char *colorclass, const char *color);
 CFGFUN(floating_modifier, const char *modifiers);
 CFGFUN(new_window, const char *windowtype, const char *border, const long width);
 CFGFUN(workspace, const char *workspace, const char *output);
-CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command);
+CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command);
 
 CFGFUN(enter_mode, const char *pango_markup, const char *mode);
-CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command);
+CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command);
 
 CFGFUN(bar_font, const char *font);
 CFGFUN(bar_separator_symbol, const char *separator);
index b7cdc804459ca6195676a1ba460714ca23615920..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;
@@ -68,7 +69,8 @@ struct Variable {
     char *value;
     char *next_match;
 
-    SLIST_ENTRY(Variable) variables;
+    SLIST_ENTRY(Variable)
+    variables;
 };
 
 /**
@@ -82,7 +84,8 @@ struct Mode {
     bool pango_markup;
     struct bindings_head *bindings;
 
-    SLIST_ENTRY(Mode) modes;
+    SLIST_ENTRY(Mode)
+    modes;
 };
 
 /**
@@ -154,6 +157,9 @@ struct Config {
      * is fetched once and never updated. */
     bool force_xinerama;
 
+    /** Don’t use RandR 1.5 for querying outputs. */
+    bool disable_randr15;
+
     /** Overwrites output detection (for testing), see src/fake_outputs.c */
     char *fake_outputs;
 
@@ -253,7 +259,8 @@ struct Barconfig {
     /* List of outputs on which the tray is allowed to be shown, in order.
      * The special value "none" disables it (per default, it will be shown) and
      * the special value "primary" enabled it on the primary output. */
-    TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs;
+    TAILQ_HEAD(tray_outputs_head, tray_output_t)
+    tray_outputs;
 
     /* Padding around the tray icons. */
     int tray_padding;
@@ -284,7 +291,8 @@ struct Barconfig {
         M_MOD5 = 7
     } modifier;
 
-    TAILQ_HEAD(bar_bindings_head, Barbinding) bar_bindings;
+    TAILQ_HEAD(bar_bindings_head, Barbinding)
+    bar_bindings;
 
     /** Bar position (bottom by default). */
     enum { P_BOTTOM = 0,
@@ -351,7 +359,8 @@ struct Barconfig {
         char *binding_mode_text;
     } colors;
 
-    TAILQ_ENTRY(Barconfig) configs;
+    TAILQ_ENTRY(Barconfig)
+    configs;
 };
 
 /**
@@ -366,13 +375,15 @@ struct Barbinding {
     /** The command which is to be executed for this button. */
     char *command;
 
-    TAILQ_ENTRY(Barbinding) bindings;
+    TAILQ_ENTRY(Barbinding)
+    bindings;
 };
 
 struct tray_output_t {
     char *output;
 
-    TAILQ_ENTRY(tray_output_t) tray_outputs;
+    TAILQ_ENTRY(tray_output_t)
+    tray_outputs;
 };
 
 /**
index a729b21ed8ac00d97063df55fa39dad63e68ce54..69a79ade09faabf22bca2ddbbebef2243a65c14c 100644 (file)
@@ -199,7 +199,8 @@ struct Workspace_Assignment {
     char *name;
     char *output;
 
-    TAILQ_ENTRY(Workspace_Assignment) ws_assignments;
+    TAILQ_ENTRY(Workspace_Assignment)
+    ws_assignments;
 };
 
 struct Ignore_Event {
@@ -207,7 +208,8 @@ struct Ignore_Event {
     int response_type;
     time_t added;
 
-    SLIST_ENTRY(Ignore_Event) ignore_events;
+    SLIST_ENTRY(Ignore_Event)
+    ignore_events;
 };
 
 /**
@@ -226,7 +228,8 @@ struct Startup_Sequence {
      * completed) */
     time_t delete_at;
 
-    TAILQ_ENTRY(Startup_Sequence) sequences;
+    TAILQ_ENTRY(Startup_Sequence)
+    sequences;
 };
 
 /**
@@ -252,7 +255,9 @@ struct regex {
 struct Binding_Keycode {
     xcb_keycode_t keycode;
     i3_event_state_mask_t modifiers;
-    TAILQ_ENTRY(Binding_Keycode) keycodes;
+
+    TAILQ_ENTRY(Binding_Keycode)
+    keycodes;
 };
 
 /******************************************************************************
@@ -293,6 +298,10 @@ struct Binding {
      * title bar (default). */
     bool whole_window;
 
+    /** If this is true for a mouse binding, the binding should only be
+     * executed if the button press was not on the titlebar. */
+    bool exclude_titlebar;
+
     /** Keycode to bind */
     uint32_t keycode;
 
@@ -309,12 +318,14 @@ struct Binding {
     /** Only in use if symbol != NULL. Contains keycodes which generate the
      * specified symbol. Useful for unbinding and checking which binding was
      * used when a key press event comes in. */
-    TAILQ_HEAD(keycodes_head, Binding_Keycode) keycodes_head;
+    TAILQ_HEAD(keycodes_head, Binding_Keycode)
+    keycodes_head;
 
     /** Command, like in command mode */
     char *command;
 
-    TAILQ_ENTRY(Binding) bindings;
+    TAILQ_ENTRY(Binding)
+    bindings;
 };
 
 /**
@@ -330,8 +341,12 @@ struct Autostart {
     /** no_startup_id flag for start_application(). Determines whether a
      * startup notification context/ID should be created. */
     bool no_startup_id;
-    TAILQ_ENTRY(Autostart) autostarts;
-    TAILQ_ENTRY(Autostart) autostarts_always;
+
+    TAILQ_ENTRY(Autostart)
+    autostarts;
+
+    TAILQ_ENTRY(Autostart)
+    autostarts_always;
 };
 
 /**
@@ -364,7 +379,8 @@ struct xoutput {
     /** x, y, width, height */
     Rect rect;
 
-    TAILQ_ENTRY(xoutput) outputs;
+    TAILQ_ENTRY(xoutput)
+    outputs;
 };
 
 /**
@@ -438,6 +454,10 @@ struct Window {
     int width_increment;
     int height_increment;
 
+    /* Minimum size specified for the window. */
+    int min_width;
+    int min_height;
+
     /* aspect ratio from WM_NORMAL_HINTS (MPlayer uses this for example) */
     double aspect_ratio;
 };
@@ -493,7 +513,8 @@ struct Match {
            M_ASSIGN_WS,
            M_BELOW } insert_where;
 
-    TAILQ_ENTRY(Match) matches;
+    TAILQ_ENTRY(Match)
+    matches;
 
     /* Whether this match was generated when restarting i3 inplace.
      * Leads to not setting focus when managing a new window, because the old
@@ -537,7 +558,8 @@ struct Assignment {
         char *workspace;
     } dest;
 
-    TAILQ_ENTRY(Assignment) assignments;
+    TAILQ_ENTRY(Assignment)
+    assignments;
 };
 
 /** Fullscreen modes. Used by Con.fullscreen_mode. */
@@ -548,7 +570,8 @@ typedef enum { CF_NONE = 0,
 struct mark_t {
     char *name;
 
-    TAILQ_ENTRY(mark_t) marks;
+    TAILQ_ENTRY(mark_t)
+    marks;
 };
 
 /**
@@ -612,7 +635,8 @@ struct Con {
     char *sticky_group;
 
     /* user-definable marks to jump to this container later */
-    TAILQ_HEAD(marks_head, mark_t) marks_head;
+    TAILQ_HEAD(marks_head, mark_t)
+    marks_head;
     /* cached to decide whether a redraw is needed */
     bool mark_changed;
 
@@ -631,12 +655,17 @@ struct Con {
     struct deco_render_params *deco_render_params;
 
     /* Only workspace-containers can have floating clients */
-    TAILQ_HEAD(floating_head, Con) floating_head;
+    TAILQ_HEAD(floating_head, Con)
+    floating_head;
 
-    TAILQ_HEAD(nodes_head, Con) nodes_head;
-    TAILQ_HEAD(focus_head, Con) focus_head;
+    TAILQ_HEAD(nodes_head, Con)
+    nodes_head;
 
-    TAILQ_HEAD(swallow_head, Match) swallow_head;
+    TAILQ_HEAD(focus_head, Con)
+    focus_head;
+
+    TAILQ_HEAD(swallow_head, Match)
+    swallow_head;
 
     fullscreen_mode_t fullscreen_mode;
 
@@ -674,10 +703,17 @@ struct Con {
         FLOATING_USER_ON = 3
     } floating;
 
-    TAILQ_ENTRY(Con) nodes;
-    TAILQ_ENTRY(Con) focused;
-    TAILQ_ENTRY(Con) all_cons;
-    TAILQ_ENTRY(Con) floating_windows;
+    TAILQ_ENTRY(Con)
+    nodes;
+
+    TAILQ_ENTRY(Con)
+    focused;
+
+    TAILQ_ENTRY(Con)
+    all_cons;
+
+    TAILQ_ENTRY(Con)
+    floating_windows;
 
     /** callbacks */
     void (*on_remove_child)(Con *);
diff --git a/include/debug.h b/include/debug.h
deleted file mode 100644 (file)
index ab5f380..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
- *
- * debug.c: Debugging functions, especially FormatEvent, which prints unhandled
- *          events.  This code is from xcb-util.
- *
- */
-#pragma once
-
-#include <config.h>
-
-int handle_event(void *ignored, xcb_connection_t *c, xcb_generic_event_t *e);
index 98ac35b0eb8b6fba24250e04ba86378234d02c04..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.
@@ -91,3 +95,6 @@ typedef struct i3_ipc_header {
 
 /** The binding event will be triggered when bindings run */
 #define I3_IPC_EVENT_BINDING (I3_IPC_EVENT_MASK | 5)
+
+/** The shutdown event will be triggered when the ipc shuts down */
+#define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6)
index 5c528a6def35dddb244192eb319670fc867bf6f4..7ffbf7a831f9de3418f9d4ddad304beee5cbff22 100644 (file)
@@ -31,7 +31,8 @@ typedef struct ipc_client {
     int num_events;
     char **events;
 
-    TAILQ_ENTRY(ipc_client) clients;
+    TAILQ_ENTRY(ipc_client)
+    clients;
 } ipc_client;
 
 /*
@@ -76,11 +77,18 @@ int ipc_create_socket(const char *filename);
 void ipc_send_event(const char *event, uint32_t message_type, const char *payload);
 
 /**
- * Calls shutdown() on each socket and closes it. This function to be called
- * when exiting or restarting only!
+ * Calls to ipc_shutdown() should provide a reason for the shutdown.
+ */
+typedef enum {
+    SHUTDOWN_REASON_RESTART,
+    SHUTDOWN_REASON_EXIT
+} shutdown_reason_t;
+
+/**
+ * Calls shutdown() on each socket and closes it.
  *
  */
-void ipc_shutdown(void);
+void ipc_shutdown(shutdown_reason_t reason);
 
 void dump_node(yajl_gen gen, Con *con, bool inplace_restart);
 
index 11ca31273b828cbf38036e8358421040da64a1db..dbb29e1f79b587ba7395db436e3f8b8b48cfdcd6 100644 (file)
 
 #define DEFAULT_DIR_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)
 
+/** Mouse buttons */
+#define XCB_BUTTON_CLICK_LEFT XCB_BUTTON_INDEX_1
+#define XCB_BUTTON_CLICK_MIDDLE XCB_BUTTON_INDEX_2
+#define XCB_BUTTON_CLICK_RIGHT XCB_BUTTON_INDEX_3
+#define XCB_BUTTON_SCROLL_UP XCB_BUTTON_INDEX_4
+#define XCB_BUTTON_SCROLL_DOWN XCB_BUTTON_INDEX_5
+/* xcb doesn't define constants for these. */
+#define XCB_BUTTON_SCROLL_LEFT 6
+#define XCB_BUTTON_SCROLL_RIGHT 7
+
 /**
  * XCB connection and root screen
  *
@@ -473,6 +483,12 @@ char *get_exe_path(const char *argv0);
  */
 void init_dpi(void);
 
+/**
+ * This function returns the value of the DPI setting.
+ *
+ */
+long get_dpi_value(void);
+
 /**
  * Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI
  * screen) to a corresponding amount of physical pixels on a standard or retina
@@ -591,17 +607,17 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_
  * surface as well as restoring the cairo state.
  *
  */
-void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t color, double x, double y, double w, double h);
+void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h);
 
 /**
  * Clears a surface with the given color.
  *
  */
-void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t color);
+void draw_util_clear_surface(surface_t *surface, color_t color);
 
 /**
  * Copies a surface onto another surface.
  *
  */
-void draw_util_copy_surface(xcb_connection_t *conn, surface_t *src, surface_t *dest, double src_x, double src_y,
+void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y,
                             double dest_x, double dest_y, double width, double height);
index 9fb9ba5e7160f24619cec4946004e9b0bf9acbcc..9b4104490814bdd86f5dcf728002170d94462c50 100644 (file)
     }
 
 #define CIRCLEQ_HEAD_INITIALIZER(head) \
-    { CIRCLEQ_END(&head), CIRCLEQ_END(&head) }
+    {                                  \
+        CIRCLEQ_END(&head)             \
+        , CIRCLEQ_END(&head)           \
+    }
 
 #define CIRCLEQ_ENTRY(type)                           \
     struct {                                          \
index 55068316607e5e660b4b9e209280608f6a00ca5c..568b52f0ea2c77d76aae109475dbe3ab0a9d82ae 100644 (file)
@@ -29,7 +29,7 @@ typedef enum {
  * XRandR information to setup workspaces for each screen.
  *
  */
-void randr_init(int *event_base);
+void randr_init(int *event_base, const bool disable_randr15);
 
 /**
  * Initializes a CT_OUTPUT Con (searches existing ones from inplace restart
@@ -75,10 +75,11 @@ void randr_disable_output(Output *output);
 Output *get_first_output(void);
 
 /**
- * Returns the output with the given name if it is active (!) or NULL.
+ * Returns the output with the given name or NULL.
+ * If require_active is true, only active outputs are considered.
  *
  */
-Output *get_output_by_name(const char *name);
+Output *get_output_by_name(const char *name, const bool require_active);
 
 /**
  * Returns the active (!) output which contains the coordinates x, y or NULL
index 20ede4eb95ee90538ec7295f40e97a0ffa99ffc0..2cc20cd214720cd3ed6e201ffa73e19cee025650 100644 (file)
@@ -3,10 +3,6 @@
  *
  * i3 - an improved dynamic tiling window manager
  * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
- * © 2009 Jan-Erik Rediger
- *
- * sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers
- *               to restart inplace).
  *
  */
 #pragma once
@@ -14,7 +10,8 @@
 #include <config.h>
 
 /**
- * Setup signal handlers to safely handle SIGSEGV and SIGFPE
+ * Configured a signal handler to gracefully handle crashes and allow the user
+ * to generate a backtrace and rescue their session.
  *
  */
 void setup_signal_handler(void);
index e5ba3341336e0ce6531bd5139b60e9091903bdd1..6c5fc4c659eb1c5f7230a143ad314aa6dae2de64 100644 (file)
@@ -69,6 +69,14 @@ Rect rect_sub(Rect a, Rect b);
  */
 __attribute__((pure)) bool name_is_digits(const char *name);
 
+/**
+ * Set 'out' to the layout_t value for the given layout. The function
+ * returns true on success or false if the passed string is not a valid
+ * layout name.
+ *
+ */
+bool layout_from_name(const char *layout_str, layout_t *out);
+
 /**
  * Parses the workspace name as a number. Returns -1 if the workspace should be
  * interpreted as a "named workspace".
@@ -149,3 +157,10 @@ void start_nagbar(pid_t *nagbar_pid, char *argv[]);
  *
  */
 void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it);
+
+/**
+ * Converts a string into a long using strtol().
+ * This is a convenience wrapper checking the parsing result. It returns true
+ * if the number could be parsed.
+ */
+bool parse_long(const char *str, long *out, int base);
index 94f2945d2bc667deda3b3b36b3d7a9dba6238199..92be7b895511f5f16913fe178a58db878993af27 100644 (file)
@@ -69,22 +69,6 @@ extern unsigned int xcb_numlock_mask;
 xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t depth, xcb_visualid_t visual,
                            uint16_t window_class, enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values);
 
-/**
- * Draws a line from x,y to to_x,to_y using the given color
- *
- */
-void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable,
-                   xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x,
-                   uint32_t y, uint32_t to_x, uint32_t to_y);
-
-/**
- * Draws a rectangle from x,y with width,height using the given color
- *
- */
-void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable,
-                   xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x,
-                   uint32_t y, uint32_t width, uint32_t height);
-
 /**
  * Generates a configure_notify_event with absolute coordinates (relative to
  * the X root window, not to the client’s frame) for the given client.
@@ -98,12 +82,6 @@ void fake_absolute_configure_notify(Con *con);
  */
 void send_take_focus(xcb_window_t window, xcb_timestamp_t timestamp);
 
-/**
- * Raises the given window (typically client->frame) above all other windows
- *
- */
-void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window);
-
 /**
  * Configures the given window to have the size/position specified by given rect
  *
@@ -122,12 +100,6 @@ xcb_atom_t xcb_get_preferred_window_type(xcb_get_property_reply_t *reply);
  */
 bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom);
 
-/**
- * Moves the mouse pointer into the middle of rect.
- *
- */
-void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect);
-
 /**
  * Set the cursor of the root window to the given cursor id.
  * This function should only be used if xcursor_supported == false.
index a832a689c40600b089ffbfdc2f5dd7421bb34ef5..ce85cacc59fcc2d48c0b35877a334f323ab73c15 100644 (file)
@@ -53,6 +53,10 @@ void init_dpi(void) {
     DLOG("Found Xft.dpi = %ld.\n", dpi);
 
 init_dpi_end:
+    if (resource != NULL) {
+        free(resource);
+    }
+
     if (database != NULL) {
         xcb_xrm_database_free(database);
     }
@@ -64,6 +68,14 @@ init_dpi_end:
     }
 }
 
+/*
+ * This function returns the value of the DPI setting.
+ *
+ */
+long get_dpi_value(void) {
+    return dpi;
+}
+
 /*
  * Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI
  * screen) to a corresponding amount of physical pixels on a standard or retina
index e471405b1ae22b1b7ac1541e96fccbcbaf515075..6a2e93dcd8e3a413099e2e34b119fcfbf13faf93 100644 (file)
@@ -19,7 +19,7 @@
 xcb_visualtype_t *visual_type;
 
 /* Forward declarations */
-static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color);
+static void draw_util_set_source_color(surface_t *surface, color_t color);
 
 #define RETURN_UNLESS_SURFACE_INITIALIZED(surface)                               \
     do {                                                                         \
@@ -84,6 +84,11 @@ void draw_util_surface_set_size(surface_t *surface, int width, int height) {
  *
  */
 color_t draw_util_hex_to_color(const char *color) {
+    if (strlen(color) < 6 || color[0] != '#') {
+        ELOG("Could not parse color: %s\n", color);
+        return draw_util_hex_to_color("#A9A9A9");
+    }
+
     char alpha[2];
     if (strlen(color) == strlen("#rrggbbaa")) {
         alpha[0] = color[7];
@@ -110,7 +115,7 @@ color_t draw_util_hex_to_color(const char *color) {
  * Set the given color as the source color on the surface.
  *
  */
-static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color) {
+static void draw_util_set_source_color(surface_t *surface, color_t color) {
     RETURN_UNLESS_SURFACE_INITIALIZED(surface);
 
     cairo_set_source_rgba(surface->cr, color.red, color.green, color.blue, color.alpha);
@@ -141,7 +146,7 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_
  * surface as well as restoring the cairo state.
  *
  */
-void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t color, double x, double y, double w, double h) {
+void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h) {
     RETURN_UNLESS_SURFACE_INITIALIZED(surface);
 
     cairo_save(surface->cr);
@@ -150,7 +155,7 @@ void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t col
      * onto the surface rather than blending it. This is a bit more efficient and
      * allows better color control for the user when using opacity. */
     cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE);
-    draw_util_set_source_color(conn, surface, color);
+    draw_util_set_source_color(surface, color);
 
     cairo_rectangle(surface->cr, x, y, w, h);
     cairo_fill(surface->cr);
@@ -166,7 +171,7 @@ void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t col
  * Clears a surface with the given color.
  *
  */
-void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t color) {
+void draw_util_clear_surface(surface_t *surface, color_t color) {
     RETURN_UNLESS_SURFACE_INITIALIZED(surface);
 
     cairo_save(surface->cr);
@@ -175,7 +180,7 @@ void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t
      * onto the surface rather than blending it. This is a bit more efficient and
      * allows better color control for the user when using opacity. */
     cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE);
-    draw_util_set_source_color(conn, surface, color);
+    draw_util_set_source_color(surface, color);
 
     cairo_paint(surface->cr);
 
@@ -190,7 +195,7 @@ void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t
  * Copies a surface onto another surface.
  *
  */
-void draw_util_copy_surface(xcb_connection_t *conn, surface_t *src, surface_t *dest, double src_x, double src_y,
+void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y,
                             double dest_x, double dest_y, double width, double height) {
     RETURN_UNLESS_SURFACE_INITIALIZED(src);
     RETURN_UNLESS_SURFACE_INITIALIZED(dest);
index fa8484813b472c3b9a28c7473da9f229effb2bb4..81091ea74a31da11d392864c1158ed387b0da1cd 100644 (file)
@@ -24,24 +24,12 @@ static double pango_font_red;
 static double pango_font_green;
 static double pango_font_blue;
 
-/* Necessary to track whether the dpi changes and trigger a LOG() message,
- * which is more easily visible to users. */
-static double logged_dpi = 0.0;
-
 static PangoLayout *create_layout_with_dpi(cairo_t *cr) {
     PangoLayout *layout;
     PangoContext *context;
 
     context = pango_cairo_create_context(cr);
-    const double dpi = (double)root_screen->height_in_pixels * 25.4 /
-                       (double)root_screen->height_in_millimeters;
-    if (logged_dpi != dpi) {
-        logged_dpi = dpi;
-        LOG("X11 root window dictates %f DPI\n", dpi);
-    } else {
-        DLOG("X11 root window dictates %f DPI\n", dpi);
-    }
-    pango_cairo_context_set_resolution(context, dpi);
+    pango_cairo_context_set_resolution(context, get_dpi_value());
     layout = pango_layout_new(context);
     g_object_unref(context);
 
index c47e6dd8b56775df0039621079c96db43d4a14ad..efece5cde792f44949423897983445a033865dd1 100644 (file)
@@ -71,7 +71,7 @@ char *get_config_path(const char *override_configpath, bool use_system_paths) {
 
     /* 4: check for $XDG_CONFIG_DIRS/i3/config */
     if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL)
-        xdg_config_dirs = "/etc/xdg";
+        xdg_config_dirs = SYSCONFDIR "/xdg";
 
     char *buf = sstrdup(xdg_config_dirs);
     char *tok = strtok(buf, ":");
index a308d67df1d1b46c89258d4b3e7777c3fe8f53a4..40f37878544ebf9ea062fb2b39cbe42d1592c7ad 100644 (file)
@@ -74,7 +74,7 @@
 AC_DEFUN([AX_EXTEND_SRCDIR],
 [dnl
 AS_CASE([$srcdir],
-  [.|.*],
+  [.|.*|/*],
   [
     # pwd -P is specified in IEEE 1003.1 from 2004
     as_dir=`cd "$srcdir" && pwd -P`
index bfc5c5c664dcf9ffc32edbeac0edf7f4aa9ebfc1..effae6c06ebcbdc52a5220024a05de0391d4b7eb 100644 (file)
@@ -29,7 +29,7 @@ It tries to start one of the following (in that order):
 * mg
 * jed
 * gedit
-* mc-edit
+* mcedit
 
 Please don’t complain about the order: If the user has any preference, they will
 have $VISUAL or $EDITOR set.
index b830cd09387208745a65e37ed0bb8274c8ce1469..20a6810c59bb8ed9ec4a1ab717e85de16ad63b3e 100644 (file)
@@ -40,6 +40,10 @@ It tries to start one of the following (in that order):
 * terminology
 * st
 * qterminal
+* lilyterm
+* tilix
+* terminix
+* konsole
 
 Please don’t complain about the order: If the user has any preference, they will
 have $TERMINAL set or modified their i3 configuration file.
index 16302e08d9320a679c6f56f3a29e89b883532692..c1f7eca2a6fad9bb94ba1f395fbef44f3bf45872 100644 (file)
@@ -170,10 +170,10 @@ Exits i3.
 
 When starting, i3 looks for configuration files in the following order:
 
-1. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set)
-2. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set)
-3. ~/.i3/config
-4. /etc/i3/config
+1. ~/.i3/config
+2. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set)
+3. /etc/i3/config
+4. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set)
 
 You can specify a custom path using the -c option.
 
index d4b3dbc61b066c9d57807719d02f93e1f8d1b5c2..a587332817e8b23bde3d48cd8ef434c43924b275 100644 (file)
@@ -38,6 +38,7 @@ state INITIAL:
   'rename' -> RENAME
   'nop' -> NOP
   'scratchpad' -> SCRATCHPAD
+  'swap' -> SWAP
   'title_format' -> TITLE_FORMAT
   'mode' -> MODE
   'bar' -> BAR
@@ -110,7 +111,7 @@ state LAYOUT:
 state LAYOUT_TOGGLE:
   end
       -> call cmd_layout_toggle($toggle_mode)
-  toggle_mode = 'split', 'all'
+  toggle_mode = string
       -> call cmd_layout_toggle($toggle_mode)
 
 # append_layout <path>
@@ -273,24 +274,28 @@ state RENAME:
       -> RENAME_WORKSPACE
 
 state RENAME_WORKSPACE:
-  old_name = 'to'
+  'to'
       -> RENAME_WORKSPACE_LIKELY_TO
   old_name = word
       -> RENAME_WORKSPACE_TO
 
 state RENAME_WORKSPACE_LIKELY_TO:
-  'to'
-      -> RENAME_WORKSPACE_NEW_NAME
+  'to '
+      -> RENAME_WORKSPACE_LIKELY_TO_NEW_NAME
   new_name = word
       -> call cmd_rename_workspace(NULL, $new_name)
 
+state RENAME_WORKSPACE_LIKELY_TO_NEW_NAME:
+  new_name = string
+      -> call cmd_rename_workspace("to", $new_name)
+  end
+      -> call cmd_rename_workspace(NULL, "to")
+
 state RENAME_WORKSPACE_TO:
   'to'
-      -> RENAME_WORKSPACE_NEW_NAME
+      -> RENAME_WORKSPACE_TO_NEW_NAME
 
-state RENAME_WORKSPACE_NEW_NAME:
-  end
-      -> call cmd_rename_workspace(NULL, "to")
+state RENAME_WORKSPACE_TO_NEW_NAME:
   new_name = string
       -> call cmd_rename_workspace($old_name, $new_name)
 
@@ -406,6 +411,21 @@ state SCRATCHPAD:
   'show'
       -> call cmd_scratchpad_show()
 
+# swap [container] [with] id <window>
+# swap [container] [with] con_id <con_id>
+# swap [container] [with] mark <mark>
+state SWAP:
+  'container'
+      ->
+  'with'
+      ->
+  mode = 'id', 'con_id', 'mark'
+      -> SWAP_ARGUMENT
+
+state SWAP_ARGUMENT:
+  arg = string
+      -> call cmd_swap($mode, $arg)
+
 state TITLE_FORMAT:
   format = string
       -> call cmd_title_format($format)
index 9029681983791be93000e7566ec0c5c6763c3d03..4aa320bf63a1b822f8c66a3f1c2e8304058d7118 100644 (file)
@@ -17,7 +17,8 @@ state INITIAL:
   end ->
   error ->
   '#'                                      -> IGNORE_LINE
-  'set'                                    -> IGNORE_LINE
+  'set '                                   -> IGNORE_LINE
+  'set '                                  -> IGNORE_LINE
   'set_from_resource'                      -> IGNORE_LINE
   bindtype = 'bindsym', 'bindcode', 'bind' -> BINDING
   'bar'                                    -> BARBRACE
@@ -37,6 +38,7 @@ state INITIAL:
   'mouse_warping'                          -> MOUSE_WARPING
   'force_focus_wrapping'                   -> FORCE_FOCUS_WRAPPING
   'force_xinerama', 'force-xinerama'       -> FORCE_XINERAMA
+  'disable_randr15', 'disable-randr15'     -> DISABLE_RANDR15
   'workspace_auto_back_and_forth'          -> WORKSPACE_BACK_AND_FORTH
   'fake_outputs', 'fake-outputs'           -> FAKE_OUTPUTS
   'force_display_urgency_hint'             -> FORCE_DISPLAY_URGENCY_HINT
@@ -205,6 +207,11 @@ state FORCE_XINERAMA:
   value = word
       -> call cfg_force_xinerama($value)
 
+# disable_randr15
+state DISABLE_RANDR15:
+  value = word
+      -> call cfg_disable_randr15($value)
+
 # workspace_back_and_forth
 state WORKSPACE_BACK_AND_FORTH:
   value = word
@@ -315,6 +322,8 @@ state BINDING:
       ->
   whole_window = '--whole-window'
       ->
+  exclude_titlebar = '--exclude-titlebar'
+      ->
   modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod'
       ->
   '+'
@@ -329,8 +338,10 @@ state BINDCOMMAND:
       ->
   whole_window = '--whole-window'
       ->
+  exclude_titlebar = '--exclude-titlebar'
+      ->
   command = string
-      -> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $command)
+      -> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)
 
 ################################################################################
 # Mode configuration
@@ -370,6 +381,8 @@ state MODE_BINDING:
       ->
   whole_window = '--whole-window'
       ->
+  exclude_titlebar = '--exclude-titlebar'
+      ->
   modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod'
       ->
   '+'
@@ -384,8 +397,10 @@ state MODE_BINDCOMMAND:
       ->
   whole_window = '--whole-window'
       ->
+  exclude_titlebar = '--exclude-titlebar'
+      ->
   command = string
-      -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $command); MODE
+      -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command); MODE
 
 ################################################################################
 # Bar configuration (i3bar)
index 9101332c100761bc841e2367e2c8ec88138b40fc..8f537c0e25d32cccbbb7ab4e48b9b06f98ceefa5 100755 (executable)
@@ -1,8 +1,8 @@
 #!/bin/zsh
 # This script is used to prepare a new release of i3.
 
-export RELEASE_VERSION="4.12"
-export PREVIOUS_VERSION="4.11"
+export RELEASE_VERSION="4.13"
+export PREVIOUS_VERSION="4.12"
 export RELEASE_BRANCH="next"
 
 if [ ! -e "../i3.github.io" ]
@@ -232,7 +232,12 @@ echo ""
 echo "  cd ${TMPDIR}"
 echo "  sendmail -t < email.txt"
 echo ""
+echo "Update milestones on GitHub:"
+echo "  Set due date of ${RELEASE_VERSION} to $(date +'%Y-$m-%d') and close the milestone"
+echo "  Create milestone for the next version with unset due date"
+echo ""
 echo "Announce on:"
 echo "  twitter"
 echo "  google+"
 echo "  #i3 topic"
+echo "  reddit /r/i3wm"
index eec821b6a732d1bf245756c0d60ce9310f04196f..3e2bb96529f727daea1860efb3ea5b8446d10d23 100644 (file)
@@ -32,8 +32,9 @@ static struct Mode *mode_from_name(const char *name, bool pango_markup) {
 
     /* Try to find the mode in the list of modes and return it */
     SLIST_FOREACH(mode, &modes, modes) {
-        if (strcmp(mode->name, name) == 0)
+        if (strcmp(mode->name, name) == 0) {
             return mode;
+        }
     }
 
     /* If the mode was not found, create a new one */
@@ -55,12 +56,14 @@ static struct Mode *mode_from_name(const char *name, bool pango_markup) {
  */
 Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code,
                            const char *release, const char *border, const char *whole_window,
-                           const char *command, const char *modename, bool pango_markup) {
+                           const char *exclude_titlebar, const char *command, const char *modename,
+                           bool pango_markup) {
     Binding *new_binding = scalloc(1, sizeof(Binding));
     DLOG("Binding %p bindtype %s, modifiers %s, input code %s, release %s\n", new_binding, bindtype, modifiers, input_code, release);
     new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS);
     new_binding->border = (border != NULL);
     new_binding->whole_window = (whole_window != NULL);
+    new_binding->exclude_titlebar = (exclude_titlebar != NULL);
     if (strcmp(bindtype, "bindsym") == 0) {
         new_binding->input_type = (strncasecmp(input_code, "button", (sizeof("button") - 1)) == 0
                                        ? B_MOUSE
@@ -68,15 +71,15 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch
 
         new_binding->symbol = sstrdup(input_code);
     } else {
-        char *endptr;
-        long keycode = strtol(input_code, &endptr, 10);
-        new_binding->keycode = keycode;
-        new_binding->input_type = B_KEYBOARD;
-        if (keycode == LONG_MAX || keycode == LONG_MIN || keycode < 0 || *endptr != '\0' || endptr == input_code) {
+        long keycode;
+        if (!parse_long(input_code, &keycode, 10)) {
             ELOG("Could not parse \"%s\" as an input code, ignoring this binding.\n", input_code);
             FREE(new_binding);
             return NULL;
         }
+
+        new_binding->keycode = keycode;
+        new_binding->input_type = B_KEYBOARD;
     }
     new_binding->command = sstrdup(command);
     new_binding->event_state_mask = event_state_from_str(modifiers);
@@ -188,17 +191,6 @@ void regrab_all_buttons(xcb_connection_t *conn) {
     xcb_ungrab_server(conn);
 }
 
-static bool modifiers_match(const uint32_t modifiers_mask, const uint32_t modifiers_state) {
-    /* modifiers_mask is a special case: a value of 0 does not mean “match
-     * all”, but rather “match exactly when no modifiers are present”. */
-    if (modifiers_mask == 0) {
-        /* Verify no modifiers are pressed. A bitwise AND would lead to
-         * false positives, see issue #2002. */
-        return (modifiers_state == 0);
-    }
-    return ((modifiers_state & modifiers_mask) == modifiers_mask);
-}
-
 /*
  * Returns a pointer to the Binding with the specified modifiers and
  * keycode or NULL if no such binding exists.
@@ -221,8 +213,9 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas
     const uint32_t xkb_group_state = (state_filtered & 0xFFFF0000);
     const uint32_t modifiers_state = (state_filtered & 0x0000FFFF);
     TAILQ_FOREACH(bind, bindings, bindings) {
-        if (bind->input_type != input_type)
+        if (bind->input_type != input_type) {
             continue;
+        }
 
         const uint32_t xkb_group_mask = (bind->event_state_mask & 0xFFFF0000);
         const bool groups_match = ((xkb_group_state & xkb_group_mask) == xkb_group_mask);
@@ -240,7 +233,7 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas
             struct Binding_Keycode *binding_keycode;
             TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) {
                 const uint32_t modifiers_mask = (binding_keycode->modifiers & 0x0000FFFF);
-                const bool mods_match = modifiers_match(modifiers_mask, modifiers_state);
+                const bool mods_match = (modifiers_mask == modifiers_state);
                 DLOG("binding_keycode->modifiers = %d, modifiers_mask = %d, modifiers_state = %d, mods_match = %s\n",
                      binding_keycode->modifiers, modifiers_mask, modifiers_state, (mods_match ? "yes" : "no"));
                 if (binding_keycode->keycode == input_keycode && mods_match) {
@@ -248,23 +241,30 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas
                     break;
                 }
             }
-            if (!found_keycode)
+            if (!found_keycode) {
                 continue;
+            }
         } else {
-            const uint32_t modifiers_mask = (bind->event_state_mask & 0x0000FFFF);
-            const bool mods_match = modifiers_match(modifiers_mask, modifiers_state);
-            DLOG("binding mods_match = %s\n", (mods_match ? "yes" : "no"));
-            /* First compare the state_filtered (unless this is a
-             * B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease
-             * event) */
-            if (!mods_match &&
-                (bind->release != B_UPON_KEYRELEASE_IGNORE_MODS ||
-                 !is_release))
+            /* This case is easier: The user specified a keycode */
+            if (bind->keycode != input_code) {
                 continue;
+            }
 
-            /* This case is easier: The user specified a keycode */
-            if (bind->keycode != input_code)
+            bool found_keycode = false;
+            struct Binding_Keycode *binding_keycode;
+            TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) {
+                const uint32_t modifiers_mask = (binding_keycode->modifiers & 0x0000FFFF);
+                const bool mods_match = (modifiers_mask == modifiers_state);
+                DLOG("binding_keycode->modifiers = %d, modifiers_mask = %d, modifiers_state = %d, mods_match = %s\n",
+                     binding_keycode->modifiers, modifiers_mask, modifiers_state, (mods_match ? "yes" : "no"));
+                if (mods_match || (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS && is_release)) {
+                    found_keycode = true;
+                    break;
+                }
+            }
+            if (!found_keycode) {
                 continue;
+            }
         }
 
         /* If this binding is a release binding, it matches the key which the
@@ -283,8 +283,9 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas
 
         /* Check if the binding is for a press or a release event */
         if ((bind->release == B_UPON_KEYPRESS && is_release) ||
-            (bind->release >= B_UPON_KEYRELEASE && !is_release))
+            (bind->release >= B_UPON_KEYRELEASE && !is_release)) {
             continue;
+        }
 
         break;
     }
@@ -457,26 +458,23 @@ void translate_keysyms(void) {
     bool has_errors = false;
     Binding *bind;
     TAILQ_FOREACH(bind, bindings, bindings) {
-        if (bind->input_type == B_MOUSE) {
-            char *endptr;
-            long button = strtol(bind->symbol + (sizeof("button") - 1), &endptr, 10);
-            bind->keycode = button;
+#define ADD_TRANSLATED_KEY(code, mods)                                                     \
+    do {                                                                                   \
+        struct Binding_Keycode *binding_keycode = smalloc(sizeof(struct Binding_Keycode)); \
+        binding_keycode->modifiers = (mods);                                               \
+        binding_keycode->keycode = (code);                                                 \
+        TAILQ_INSERT_TAIL(&(bind->keycodes_head), binding_keycode, keycodes);              \
+    } while (0)
 
-            if (button == LONG_MAX || button == LONG_MIN || button < 0 || *endptr != '\0' || endptr == bind->symbol)
+        if (bind->input_type == B_MOUSE) {
+            long button;
+            if (!parse_long(bind->symbol + (sizeof("button") - 1), &button, 10)) {
                 ELOG("Could not translate string to button: \"%s\"\n", bind->symbol);
+            }
 
-            continue;
-        }
-
-        if (bind->keycode > 0)
-            continue;
-
-        /* We need to translate the symbol to a keycode */
-        const xkb_keysym_t keysym = xkb_keysym_from_name(bind->symbol, XKB_KEYSYM_NO_FLAGS);
-        if (keysym == XKB_KEY_NoSymbol) {
-            ELOG("Could not translate string to key symbol: \"%s\"\n",
-                 bind->symbol);
-            continue;
+            xcb_keycode_t key = button;
+            bind->keycode = key;
+            DLOG("Binding Mouse button, Keycode = %d\n", key);
         }
 
         xkb_layout_index_t group = XCB_XKB_GROUP_1;
@@ -530,6 +528,52 @@ void translate_keysyms(void) {
             0 /* xkb_layout_index_t latched_group, */,
             group /* xkb_layout_index_t locked_group, */);
 
+        if (bind->keycode > 0) {
+            /* We need to specify modifiers for the keycode binding (numlock
+             * fallback). */
+            while (!TAILQ_EMPTY(&(bind->keycodes_head))) {
+                struct Binding_Keycode *first = TAILQ_FIRST(&(bind->keycodes_head));
+                TAILQ_REMOVE(&(bind->keycodes_head), first, keycodes);
+                FREE(first);
+            }
+
+            ADD_TRANSLATED_KEY(bind->keycode, bind->event_state_mask);
+
+            /* Also bind the key with active CapsLock */
+            ADD_TRANSLATED_KEY(bind->keycode, bind->event_state_mask | XCB_MOD_MASK_LOCK);
+
+            /* If this binding is not explicitly for NumLock, check whether we need to
+             * add a fallback. */
+            if ((bind->event_state_mask & xcb_numlock_mask) != xcb_numlock_mask) {
+                /* Check whether the keycode results in the same keysym when NumLock is
+                 * active. If so, grab the key with NumLock as well, so that users don’t
+                 * need to duplicate every key binding with an additional Mod2 specified.
+                 */
+                xkb_keysym_t sym = xkb_state_key_get_one_sym(dummy_state, bind->keycode);
+                xkb_keysym_t sym_numlock = xkb_state_key_get_one_sym(dummy_state_numlock, bind->keycode);
+                if (sym == sym_numlock) {
+                    /* Also bind the key with active NumLock */
+                    ADD_TRANSLATED_KEY(bind->keycode, bind->event_state_mask | xcb_numlock_mask);
+
+                    /* Also bind the key with active NumLock+CapsLock */
+                    ADD_TRANSLATED_KEY(bind->keycode, bind->event_state_mask | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
+                } else {
+                    DLOG("Skipping automatic numlock fallback, key %d resolves to 0x%x with numlock\n",
+                         bind->keycode, sym_numlock);
+                }
+            }
+
+            continue;
+        }
+
+        /* We need to translate the symbol to a keycode */
+        const xkb_keysym_t keysym = xkb_keysym_from_name(bind->symbol, XKB_KEYSYM_NO_FLAGS);
+        if (keysym == XKB_KEY_NoSymbol) {
+            ELOG("Could not translate string to key symbol: \"%s\"\n",
+                 bind->symbol);
+            continue;
+        }
+
         struct resolve resolving = {
             .bind = bind,
             .keysym = keysym,
@@ -572,6 +616,8 @@ void translate_keysyms(void) {
         DLOG("state=0x%x, cfg=\"%s\", sym=0x%x → keycodes%s (%d)\n",
              bind->event_state_mask, bind->symbol, keysym, keycodes, num_keycodes);
         free(keycodes);
+
+#undef ADD_TRANSLATED_KEY
     }
 
     xkb_state_unref(dummy_state);
@@ -973,15 +1019,14 @@ int *bindings_get_buttons_to_grab(void) {
         if (bind->input_type != B_MOUSE || !bind->whole_window)
             continue;
 
-        char *endptr;
-        long button = strtol(bind->symbol + (sizeof("button") - 1), &endptr, 10);
-        if (button == LONG_MAX || button == LONG_MIN || button < 0 || *endptr != '\0' || endptr == bind->symbol) {
+        long button;
+        if (!parse_long(bind->symbol + (sizeof("button") - 1), &button, 10)) {
             ELOG("Could not parse button number, skipping this binding. Please report this bug in i3.\n");
             continue;
         }
 
         /* Avoid duplicates. */
-        for (int i = 0; i < num_max; i++) {
+        for (int i = 0; i < num; i++) {
             if (buffer[i] == button)
                 continue;
         }
index 913741b4fd96cdfd3484fd3d55f9c8c9a738297e..e5cdc8b2f39e82dbe880fb184337f3f8363380f2 100644 (file)
@@ -178,15 +178,15 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
     if (con->parent->type == CT_DOCKAREA)
         goto done;
 
-    const bool is_left_or_right_click = (event->detail == XCB_BUTTON_INDEX_1 ||
-                                         event->detail == XCB_BUTTON_INDEX_3);
+    const bool is_left_or_right_click = (event->detail == XCB_BUTTON_CLICK_LEFT ||
+                                         event->detail == XCB_BUTTON_CLICK_RIGHT);
 
     /* if the user has bound an action to this click, it should override the
      * default behavior. */
     if (dest == CLICK_DECORATION || dest == CLICK_INSIDE || dest == CLICK_BORDER) {
         Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event);
 
-        if (bind != NULL && (dest == CLICK_DECORATION ||
+        if (bind != NULL && ((dest == CLICK_DECORATION && !bind->exclude_titlebar) ||
                              (dest == CLICK_INSIDE && bind->whole_window) ||
                              (dest == CLICK_BORDER && bind->border))) {
             CommandResult *result = run_binding(bind, con);
@@ -228,8 +228,10 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
     /* 1: see if the user scrolled on the decoration of a stacked/tabbed con */
     if (in_stacked &&
         dest == CLICK_DECORATION &&
-        (event->detail == XCB_BUTTON_INDEX_4 ||
-         event->detail == XCB_BUTTON_INDEX_5)) {
+        (event->detail == XCB_BUTTON_SCROLL_UP ||
+         event->detail == XCB_BUTTON_SCROLL_DOWN ||
+         event->detail == XCB_BUTTON_SCROLL_LEFT ||
+         event->detail == XCB_BUTTON_SCROLL_RIGHT)) {
         DLOG("Scrolling on a window decoration\n");
         orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ);
         /* Focus the currently focused container on the same level that the
@@ -244,10 +246,12 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
          * #557), we first check if scrolling is possible at all. */
         bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
         bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL);
-        if (event->detail == XCB_BUTTON_INDEX_4 && scroll_prev_possible)
+        if ((event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) && scroll_prev_possible) {
             tree_next('p', orientation);
-        else if (event->detail == XCB_BUTTON_INDEX_5 && scroll_next_possible)
+        } else if ((event->detail == XCB_BUTTON_SCROLL_DOWN || event->detail == XCB_BUTTON_SCROLL_RIGHT) && scroll_next_possible) {
             tree_next('n', orientation);
+        }
+
         goto done;
     }
 
@@ -261,7 +265,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
         floating_raise_con(floatingcon);
 
         /* 4: floating_modifier plus left mouse button drags */
-        if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1) {
+        if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) {
             floating_drag_window(floatingcon, event);
             return 1;
         }
@@ -269,7 +273,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
         /*  5: resize (floating) if this was a (left or right) click on the
          * left/right/bottom border, or a right click on the decoration.
          * also try resizing (tiling) if it was a click on the top */
-        if (mod_pressed && event->detail == XCB_BUTTON_INDEX_3) {
+        if (mod_pressed && event->detail == XCB_BUTTON_CLICK_RIGHT) {
             DLOG("floating resize due to floatingmodifier\n");
             floating_resize_window(floatingcon, proportional, event);
             return 1;
@@ -283,7 +287,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
                 goto done;
         }
 
-        if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_INDEX_3) {
+        if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_CLICK_RIGHT) {
             DLOG("floating resize due to decoration right click\n");
             floating_resize_window(floatingcon, proportional, event);
             return 1;
@@ -298,7 +302,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
         /* 6: dragging, if this was a click on a decoration (which did not lead
          * to a resize) */
         if (!in_stacked && dest == CLICK_DECORATION &&
-            (event->detail == XCB_BUTTON_INDEX_1)) {
+            (event->detail == XCB_BUTTON_CLICK_LEFT)) {
             floating_drag_window(floatingcon, event);
             return 1;
         }
@@ -313,7 +317,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
     }
 
     /* 7: floating modifier pressed, initiate a resize */
-    if (dest == CLICK_INSIDE && mod_pressed && event->detail == XCB_BUTTON_INDEX_3) {
+    if (dest == CLICK_INSIDE && mod_pressed && event->detail == XCB_BUTTON_CLICK_RIGHT) {
         if (floating_mod_on_tiled_client(con, event))
             return 1;
     }
index b91c71a47c76389c193e5ad7ab5ed0f16d71a63e..393d70185e5a8e9e8d779c3ecee657d9fadba459 100644 (file)
@@ -142,7 +142,9 @@ static Con *maybe_auto_back_and_forth_workspace(Con *workspace) {
  */
 typedef struct owindow {
     Con *con;
-    TAILQ_ENTRY(owindow) owindows;
+
+    TAILQ_ENTRY(owindow)
+    owindows;
 } owindow;
 
 typedef TAILQ_HEAD(owindows_head, owindow) owindows_head;
@@ -891,8 +893,10 @@ void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_a
         cmd_output->needs_tree_render = true;
         return;
     }
-    if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, workspace->name))
+    if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, workspace->name)) {
+        ysuccess(true);
         return;
+    }
     workspace_show(workspace);
 
     cmd_output->needs_tree_render = true;
@@ -938,8 +942,10 @@ void cmd_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_
     }
 
     DLOG("should switch to workspace %s\n", name);
-    if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, name))
+    if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, name)) {
+        ysuccess(true);
         return;
+    }
     workspace_show_by_name(name);
 
     cmd_output->needs_tree_render = true;
@@ -1483,21 +1489,8 @@ void cmd_move_direction(I3_CMD, const char *direction, long move_px) {
 void cmd_layout(I3_CMD, const char *layout_str) {
     HANDLE_EMPTY_MATCH;
 
-    if (strcmp(layout_str, "stacking") == 0)
-        layout_str = "stacked";
     layout_t layout;
-    /* default is a special case which will be handled in con_set_layout(). */
-    if (strcmp(layout_str, "default") == 0)
-        layout = L_DEFAULT;
-    else if (strcmp(layout_str, "stacked") == 0)
-        layout = L_STACKED;
-    else if (strcmp(layout_str, "tabbed") == 0)
-        layout = L_TABBED;
-    else if (strcmp(layout_str, "splitv") == 0)
-        layout = L_SPLITV;
-    else if (strcmp(layout_str, "splith") == 0)
-        layout = L_SPLITH;
-    else {
+    if (!layout_from_name(layout_str, &layout)) {
         ELOG("Unknown layout \"%s\", this is a mismatch between code and parser spec.\n", layout_str);
         return;
     }
@@ -1556,7 +1549,7 @@ void cmd_exit(I3_CMD) {
 #ifdef I3_ASAN_ENABLED
     __lsan_do_leak_check();
 #endif
-    ipc_shutdown();
+    ipc_shutdown(SHUTDOWN_REASON_EXIT);
     unlink(config.ipc_socket_path);
     xcb_disconnect(conn);
     exit(0);
@@ -1589,7 +1582,7 @@ void cmd_reload(I3_CMD) {
  */
 void cmd_restart(I3_CMD) {
     LOG("restarting i3\n");
-    ipc_shutdown();
+    ipc_shutdown(SHUTDOWN_REASON_RESTART);
     unlink(config.ipc_socket_path);
     /* We need to call this manually since atexit handlers don’t get called
      * when exec()ing */
@@ -1819,6 +1812,65 @@ void cmd_scratchpad_show(I3_CMD) {
     ysuccess(true);
 }
 
+/*
+ * Implementation of 'swap [container] [with] id|con_id|mark <arg>'.
+ *
+ */
+void cmd_swap(I3_CMD, const char *mode, const char *arg) {
+    HANDLE_EMPTY_MATCH;
+
+    owindow *match = TAILQ_FIRST(&owindows);
+    if (match == NULL) {
+        DLOG("No match found for swapping.\n");
+        return;
+    }
+
+    Con *con;
+    if (strcmp(mode, "id") == 0) {
+        long target;
+        if (!parse_long(arg, &target, 0)) {
+            yerror("Failed to parse %s into a window id.\n", arg);
+            return;
+        }
+
+        con = con_by_window_id(target);
+    } else if (strcmp(mode, "con_id") == 0) {
+        long target;
+        if (!parse_long(arg, &target, 0)) {
+            yerror("Failed to parse %s into a container id.\n", arg);
+            return;
+        }
+
+        con = (Con *)target;
+    } else if (strcmp(mode, "mark") == 0) {
+        con = con_by_mark(arg);
+    } else {
+        yerror("Unhandled swap mode \"%s\". This is a bug.\n", mode);
+        return;
+    }
+
+    if (con == NULL) {
+        yerror("Could not find container for %s = %s\n", mode, arg);
+        return;
+    }
+
+    if (match == TAILQ_LAST(&owindows, owindows_head)) {
+        DLOG("More than one container matched the swap command, only using the first one.");
+    }
+
+    if (match->con == NULL) {
+        DLOG("Match %p has no container.\n", match);
+        ysuccess(false);
+        return;
+    }
+
+    DLOG("Swapping %p with %p.\n", match->con, con);
+    bool result = con_swap(match->con, con);
+
+    cmd_output->needs_tree_render = true;
+    ysuccess(result);
+}
+
 /*
  * Implementation of 'title_format <format>'
  *
index 362254151b01397fb66a1b06fb92ff65a9bf2da6..cf923ec83ed6d4b3654d0f2502ab892bfe6f65c0 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -22,9 +22,11 @@ static void con_on_remove_child(Con *con);
 void con_force_split_parents_redraw(Con *con) {
     Con *parent = con;
 
-    while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) {
-        if (!con_is_leaf(parent))
+    while (parent != NULL && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) {
+        if (!con_is_leaf(parent)) {
             FREE(parent->deco_render_params);
+        }
+
         parent = parent->parent;
     }
 }
@@ -145,7 +147,7 @@ static void _con_attach(Con *con, Con *parent, Con *previous, bool ignore_focus)
 
         /* Insert the container after the tiling container, if found.
          * When adding to a CT_OUTPUT, just append one after another. */
-        if (current && parent->type != CT_OUTPUT) {
+        if (current != NULL && parent->type != CT_OUTPUT) {
             DLOG("Inserting con = %p after con %p\n", con, current);
             TAILQ_INSERT_AFTER(nodes_head, current, con, nodes);
         } else
@@ -410,7 +412,8 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation) {
 struct bfs_entry {
     Con *con;
 
-    TAILQ_ENTRY(bfs_entry) entries;
+    TAILQ_ENTRY(bfs_entry)
+    entries;
 };
 
 /*
@@ -422,7 +425,9 @@ Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode) {
 
     /* TODO: is breadth-first-search really appropriate? (check as soon as
      * fullscreen levels and fullscreen for containers is implemented) */
-    TAILQ_HEAD(bfs_head, bfs_entry) bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head);
+    TAILQ_HEAD(bfs_head, bfs_entry)
+    bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head);
+
     struct bfs_entry *entry = smalloc(sizeof(struct bfs_entry));
     entry->con = con;
     TAILQ_INSERT_TAIL(&bfs_head, entry, entries);
@@ -522,6 +527,23 @@ bool con_inside_focused(Con *con) {
     return con_inside_focused(con->parent);
 }
 
+/*
+ * Checks if the container has the given parent as an actual parent.
+ *
+ */
+bool con_has_parent(Con *con, Con *parent) {
+    Con *current = con->parent;
+    if (current == NULL) {
+        return false;
+    }
+
+    if (current == parent) {
+        return true;
+    }
+
+    return con_has_parent(current, parent);
+}
+
 /*
  * Returns the container with the given client window ID or NULL if no such
  * container exists.
@@ -800,10 +822,11 @@ void con_fix_percent(Con *con) {
     if (children_with_percent != children) {
         TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
             if (child->percent <= 0.0) {
-                if (children_with_percent == 0)
+                if (children_with_percent == 0) {
                     total += (child->percent = 1.0);
-                else
+                } else {
                     total += (child->percent = total / children_with_percent);
+                }
             }
         }
     }
@@ -811,11 +834,13 @@ void con_fix_percent(Con *con) {
     // if we got a zero, just distribute the space equally, otherwise
     // distribute according to the proportions we got
     if (total == 0.0) {
-        TAILQ_FOREACH(child, &(con->nodes_head), nodes)
-        child->percent = 1.0 / children;
+        TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+            child->percent = 1.0 / children;
+        }
     } else if (total != 1.0) {
-        TAILQ_FOREACH(child, &(con->nodes_head), nodes)
-        child->percent /= total;
+        TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+            child->percent /= total;
+        }
     }
 }
 
@@ -941,7 +966,7 @@ void con_disable_fullscreen(Con *con) {
     con_set_fullscreen_mode(con, CF_NONE);
 }
 
-static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fix_coordinates, bool dont_warp, bool ignore_focus) {
+static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fix_coordinates, bool dont_warp, bool ignore_focus, bool fix_percentage) {
     Con *orig_target = target;
 
     /* Prevent moving if this would violate the fullscreen focus restrictions. */
@@ -1050,9 +1075,11 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
     _con_attach(con, target, behind_focused ? NULL : orig_target, !behind_focused);
 
     /* 5: fix the percentages */
-    con_fix_percent(parent);
-    con->percent = 0.0;
-    con_fix_percent(target);
+    if (fix_percentage) {
+        con_fix_percent(parent);
+        con->percent = 0.0;
+        con_fix_percent(target);
+    }
 
     /* 6: focus the con on the target workspace, but only within that
      * workspace, that is, don’t move focus away if the target workspace is
@@ -1078,8 +1105,13 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
     /* Descend focus stack in case focus_next is a workspace which can
      * occur if we move to the same workspace.  Also show current workspace
      * to ensure it is focused. */
-    if (!ignore_focus)
+    if (!ignore_focus) {
         workspace_show(current_ws);
+        if (dont_warp) {
+            DLOG("x_set_warp_to(NULL) because dont_warp is set\n");
+            x_set_warp_to(NULL);
+        }
+    }
 
     /* Set focus only if con was on current workspace before moving.
      * Otherwise we would give focus to some window on different workspace. */
@@ -1124,6 +1156,9 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
         con_set_urgency(con, true);
     }
 
+    /* Ensure the container will be redrawn. */
+    FREE(con->deco_render_params);
+
     CALL(parent, on_remove_child);
 
     ipc_send_window_event("move", con);
@@ -1163,12 +1198,12 @@ bool con_move_to_mark(Con *con, const char *mark) {
         target = TAILQ_FIRST(&(target->focus_head));
     }
 
-    if (con == target || con == target->parent) {
+    if (con == target || con_has_parent(target, con)) {
         DLOG("cannot move the container to or inside itself, aborting.\n");
         return false;
     }
 
-    return _con_move_to_con(con, target, false, true, false, false);
+    return _con_move_to_con(con, target, false, true, false, false, true);
 }
 
 /*
@@ -1201,7 +1236,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
     }
 
     Con *target = con_descend_focused(workspace);
-    _con_move_to_con(con, target, true, fix_coordinates, dont_warp, ignore_focus);
+    _con_move_to_con(con, target, true, fix_coordinates, dont_warp, ignore_focus, true);
 }
 
 /*
@@ -1308,15 +1343,16 @@ Con *con_next_focused(Con *con) {
     } else {
         /* try to focus the next container on the same level as this one or fall
          * back to its parent */
-        if (!(next = TAILQ_NEXT(con, focused)))
+        if (!(next = TAILQ_NEXT(con, focused))) {
             next = con->parent;
+        }
     }
 
     /* now go down the focus stack as far as
      * possible, excluding the current container */
-    while (!TAILQ_EMPTY(&(next->focus_head)) &&
-           TAILQ_FIRST(&(next->focus_head)) != con)
+    while (!TAILQ_EMPTY(&(next->focus_head)) && TAILQ_FIRST(&(next->focus_head)) != con) {
         next = TAILQ_FIRST(&(next->focus_head));
+    }
 
     return next;
 }
@@ -1636,12 +1672,14 @@ void con_set_layout(Con *con, layout_t layout) {
      * whole workspace into stacked/tabbed mode. To do this and still allow
      * intuitive operations (like level-up and then opening a new window), we
      * need to create a new split container. */
-    if (con->type == CT_WORKSPACE &&
-        (layout == L_STACKED || layout == L_TABBED)) {
+    if (con->type == CT_WORKSPACE) {
         if (con_num_children(con) == 0) {
-            DLOG("Setting workspace_layout to %d\n", layout);
-            con->workspace_layout = layout;
-        } else {
+            layout_t ws_layout = (layout == L_STACKED || layout == L_TABBED) ? layout : L_DEFAULT;
+            DLOG("Setting workspace_layout to %d\n", ws_layout);
+            con->workspace_layout = ws_layout;
+            DLOG("Setting layout to %d\n", layout);
+            con->layout = layout;
+        } else if (layout == L_STACKED || layout == L_TABBED) {
             DLOG("Creating new split container\n");
             /* 1: create a new split container */
             Con *new = con_new(NULL, NULL);
@@ -1716,28 +1754,64 @@ void con_toggle_layout(Con *con, const char *toggle_mode) {
         parent = con->parent;
     DLOG("con_toggle_layout(%p, %s), parent = %p\n", con, toggle_mode, parent);
 
-    if (strcmp(toggle_mode, "split") == 0) {
-        /* Toggle between splits. When the current layout is not a split
-         * layout, we just switch back to last_split_layout. Otherwise, we
-         * change to the opposite split layout. */
-        if (parent->layout != L_SPLITH && parent->layout != L_SPLITV)
-            con_set_layout(con, parent->last_split_layout);
-        else {
-            if (parent->layout == L_SPLITH)
-                con_set_layout(con, L_SPLITV);
-            else
-                con_set_layout(con, L_SPLITH);
+    const char delim[] = " ";
+
+    if (strcasecmp(toggle_mode, "split") == 0 || strstr(toggle_mode, delim)) {
+        /* L_DEFAULT is used as a placeholder value to distinguish if
+         * the first layout has already been saved. (it can never be L_DEFAULT) */
+        layout_t new_layout = L_DEFAULT;
+        bool current_layout_found = false;
+        char *tm_dup = sstrdup(toggle_mode);
+        char *cur_tok = strtok(tm_dup, delim);
+
+        for (layout_t layout; cur_tok != NULL; cur_tok = strtok(NULL, delim)) {
+            if (strcasecmp(cur_tok, "split") == 0) {
+                /* Toggle between splits. When the current layout is not a split
+                 * layout, we just switch back to last_split_layout. Otherwise, we
+                 * change to the opposite split layout. */
+                if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) {
+                    layout = parent->last_split_layout;
+                } else {
+                    layout = (parent->layout == L_SPLITH) ? L_SPLITV : L_SPLITH;
+                }
+            } else {
+                bool success = layout_from_name(cur_tok, &layout);
+                if (!success || layout == L_DEFAULT) {
+                    ELOG("The token '%s' was not recognized and has been skipped.\n", cur_tok);
+                    continue;
+                }
+            }
+
+            /* If none of the specified layouts match the current,
+             * fall back to the first layout in the list */
+            if (new_layout == L_DEFAULT) {
+                new_layout = layout;
+            }
+
+            /* We found the active layout in the last iteration, so
+             * now let's activate the current layout (next in list) */
+            if (current_layout_found) {
+                new_layout = layout;
+                free(tm_dup);
+                break;
+            }
+
+            if (parent->layout == layout) {
+                current_layout_found = true;
+            }
         }
-    } else {
+
+        con_set_layout(con, new_layout);
+    } else if (strcasecmp(toggle_mode, "all") == 0 || strcasecmp(toggle_mode, "default") == 0) {
         if (parent->layout == L_STACKED)
             con_set_layout(con, L_TABBED);
         else if (parent->layout == L_TABBED) {
-            if (strcmp(toggle_mode, "all") == 0)
+            if (strcasecmp(toggle_mode, "all") == 0)
                 con_set_layout(con, L_SPLITH);
             else
                 con_set_layout(con, parent->last_split_layout);
         } else if (parent->layout == L_SPLITH || parent->layout == L_SPLITV) {
-            if (strcmp(toggle_mode, "all") == 0) {
+            if (strcasecmp(toggle_mode, "all") == 0) {
                 /* When toggling through all modes, we toggle between
                  * splith/splitv, whereas normally we just directly jump to
                  * stacked. */
@@ -2120,3 +2194,169 @@ i3String *con_parse_title_format(Con *con) {
 
     return formatted;
 }
+
+/*
+ * Swaps the two containers.
+ *
+ */
+bool con_swap(Con *first, Con *second) {
+    assert(first != NULL);
+    assert(second != NULL);
+    DLOG("Swapping containers %p / %p\n", first, second);
+
+    if (first->type != CT_CON) {
+        ELOG("Only regular containers can be swapped, but found con = %p with type = %d.\n", first, first->type);
+        return false;
+    }
+
+    if (second->type != CT_CON) {
+        ELOG("Only regular containers can be swapped, but found con = %p with type = %d.\n", second, second->type);
+        return false;
+    }
+
+    if (con_is_floating(first) || con_is_floating(second)) {
+        ELOG("Floating windows cannot be swapped.\n");
+        return false;
+    }
+
+    if (first == second) {
+        DLOG("Swapping container %p with itself, nothing to do.\n", first);
+        return false;
+    }
+
+    if (con_has_parent(first, second) || con_has_parent(second, first)) {
+        ELOG("Cannot swap containers %p and %p because they are in a parent-child relationship.\n", first, second);
+        return false;
+    }
+
+    Con *old_focus = focused;
+
+    Con *first_ws = con_get_workspace(first);
+    Con *second_ws = con_get_workspace(second);
+    Con *current_ws = con_get_workspace(old_focus);
+    const bool focused_within_first = (first == old_focus || con_has_parent(old_focus, first));
+    const bool focused_within_second = (second == old_focus || con_has_parent(old_focus, second));
+
+    if (!con_fullscreen_permits_focusing(first_ws)) {
+        DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", first_ws->name);
+        return false;
+    }
+
+    if (!con_fullscreen_permits_focusing(second_ws)) {
+        DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", second_ws->name);
+        return false;
+    }
+
+    double first_percent = first->percent;
+    double second_percent = second->percent;
+
+    /* De- and reattaching the containers will insert them at the tail of the
+     * focus_heads. We will need to fix this. But we need to make sure first
+     * and second don't get in each other's way if they share the same parent,
+     * so we select the closest previous focus_head that isn't involved. */
+    Con *first_prev_focus_head = first;
+    while (first_prev_focus_head == first || first_prev_focus_head == second) {
+        first_prev_focus_head = TAILQ_PREV(first_prev_focus_head, focus_head, focused);
+    }
+
+    Con *second_prev_focus_head = second;
+    while (second_prev_focus_head == second || second_prev_focus_head == first) {
+        second_prev_focus_head = TAILQ_PREV(second_prev_focus_head, focus_head, focused);
+    }
+
+    /* We use a fake container to mark the spot of where the second container needs to go. */
+    Con *fake = con_new(NULL, NULL);
+    fake->layout = L_SPLITH;
+    _con_attach(fake, first->parent, first, true);
+
+    bool result = true;
+    /* Swap the containers. We set the ignore_focus flag here because after the
+     * container is attached, the focus order is not yet correct and would
+     * result in wrong windows being focused. */
+
+    /* Move first to second. */
+    result &= _con_move_to_con(first, second, false, false, false, true, false);
+
+    /* If we moved the container holding the focused window to another
+     * workspace we need to ensure the visible workspace has the focused
+     * container.
+     * We don't need to check this for the second container because we've only
+     * moved the first one at this point.*/
+    if (first_ws != second_ws && focused_within_first) {
+        con_focus(con_descend_focused(current_ws));
+    }
+
+    /* Move second to where first has been originally. */
+    result &= _con_move_to_con(second, fake, false, false, false, true, false);
+
+    /* If swapping the containers didn't work we don't need to mess with the focus. */
+    if (!result) {
+        goto swap_end;
+    }
+
+    /* Swapping will have inserted the containers at the tail of their parents'
+     * focus head. We fix this now by putting them in the position of the focus
+     * head the container they swapped with was in. */
+    TAILQ_REMOVE(&(first->parent->focus_head), first, focused);
+    TAILQ_REMOVE(&(second->parent->focus_head), second, focused);
+
+    if (second_prev_focus_head == NULL) {
+        TAILQ_INSERT_HEAD(&(first->parent->focus_head), first, focused);
+    } else {
+        TAILQ_INSERT_AFTER(&(first->parent->focus_head), second_prev_focus_head, first, focused);
+    }
+
+    if (first_prev_focus_head == NULL) {
+        TAILQ_INSERT_HEAD(&(second->parent->focus_head), second, focused);
+    } else {
+        TAILQ_INSERT_AFTER(&(second->parent->focus_head), first_prev_focus_head, second, focused);
+    }
+
+    /* If the focus was within any of the swapped containers, do the following:
+     * - If swapping took place within a workspace, ensure the previously
+     *   focused container stays focused.
+     * - Otherwise, focus the container that has been swapped in.
+     *
+     * To understand why fixing the focus_head previously wasn't enough,
+     * consider the scenario
+     *   H[ V[ A X ] V[ Y B ] ]
+     * with B being focused, but X being the focus_head within its parent. If
+     * we swap A and B now, fixing the focus_head would focus X, but since B
+     * was the focused container before it should stay focused.
+     */
+    if (focused_within_first) {
+        if (first_ws == second_ws) {
+            con_focus(old_focus);
+        } else {
+            con_focus(con_descend_focused(second));
+        }
+    } else if (focused_within_second) {
+        if (first_ws == second_ws) {
+            con_focus(old_focus);
+        } else {
+            con_focus(con_descend_focused(first));
+        }
+    }
+
+    /* We need to copy each other's percentages to ensure that the geometry
+     * doesn't change during the swap. This needs to happen _before_ we close
+     * the fake container as closing the tree will recalculate percentages. */
+    first->percent = second_percent;
+    second->percent = first_percent;
+    fake->percent = 0.0;
+
+swap_end:
+    /* We don't actually need this since percentages-wise we haven't changed
+     * anything, but we'll better be safe than sorry and just make sure as we'd
+     * otherwise crash i3. */
+    con_fix_percent(first->parent);
+    con_fix_percent(second->parent);
+
+    /* We can get rid of the fake container again now. */
+    con_close(fake, DONT_KILL_WINDOW);
+
+    con_force_split_parents_redraw(first);
+    con_force_split_parents_redraw(second);
+
+    return result;
+}
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 6b5464f19575985365d0e577f131f5664b9e64c4..7ca6e102785a674daa3741e5f884fd68ebc958ab 100644 (file)
@@ -106,8 +106,8 @@ CFGFUN(font, const char *font) {
     font_pattern = sstrdup(font);
 }
 
-CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) {
-    configure_binding(bindtype, modifiers, key, release, border, whole_window, command, DEFAULT_BINDING_MODE, false);
+CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
+    configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, DEFAULT_BINDING_MODE, false);
 }
 
 /*******************************************************************************
@@ -117,15 +117,23 @@ CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, co
 static char *current_mode;
 static bool current_mode_pango_markup;
 
-CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) {
-    configure_binding(bindtype, modifiers, key, release, border, whole_window, command, current_mode, current_mode_pango_markup);
+CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
+    configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, current_mode, current_mode_pango_markup);
 }
 
 CFGFUN(enter_mode, const char *pango_markup, const char *modename) {
     if (strcasecmp(modename, DEFAULT_BINDING_MODE) == 0) {
         ELOG("You cannot use the name %s for your mode\n", DEFAULT_BINDING_MODE);
-        exit(1);
+        return;
+    }
+
+    struct Mode *mode;
+    SLIST_FOREACH(mode, &modes, modes) {
+        if (strcmp(mode->name, modename) == 0) {
+            ELOG("The binding mode with name \"%s\" is defined at least twice.\n", modename);
+        }
     }
+
     DLOG("\t now in mode %s\n", modename);
     FREE(current_mode);
     current_mode = sstrdup(modename);
@@ -252,6 +260,10 @@ CFGFUN(force_xinerama, const char *value) {
     config.force_xinerama = eval_boolstr(value);
 }
 
+CFGFUN(disable_randr15, const char *value) {
+    config.disable_randr15 = eval_boolstr(value);
+}
+
 CFGFUN(force_focus_wrapping, const char *value) {
     config.force_focus_wrapping = eval_boolstr(value);
 }
index a82658698e943b37c9a635c148ca3f729f30f532..c88e9d1e3c2e7919b277f95b9eabf4e2d8885654 100644 (file)
@@ -235,7 +235,7 @@ static void next_state(const cmdp_token *token) {
  *
  */
 static const char *start_of_line(const char *walk, const char *beginning) {
-    while (*walk != '\n' && *walk != '\r' && walk >= beginning) {
+    while (walk >= beginning && *walk != '\n' && *walk != '\r') {
         walk--;
     }
 
@@ -898,6 +898,13 @@ 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);
+
+    bool invalid_sets = false;
+
     while (!feof(fstr)) {
         if (!continuation)
             continuation = buffer;
@@ -911,6 +918,7 @@ bool parse_file(const char *f, bool use_nagbar) {
         }
 
         /* sscanf implicitly strips whitespace. */
+        value[0] = '\0';
         const bool skip_line = (sscanf(buffer, "%511s %4095[^\n]", key, value) < 1 || strlen(key) < 3);
         const bool comment = (key[0] == '#');
         value[4095] = '\n';
@@ -931,26 +939,28 @@ bool parse_file(const char *f, bool use_nagbar) {
             continue;
         }
 
-        if (strcasecmp(key, "set") == 0) {
+        if (strcasecmp(key, "set") == 0 && *value != '\0') {
             char v_key[512];
-            char v_value[4096];
+            char v_value[4096] = {'\0'};
 
             if (sscanf(value, "%511s %4095[^\n]", v_key, v_value) < 1) {
                 ELOG("Failed to parse variable specification '%s', skipping it.\n", value);
+                invalid_sets = true;
                 continue;
             }
 
             if (v_key[0] != '$') {
                 ELOG("Malformed variable assignment, name has to start with $\n");
+                invalid_sets = true;
                 continue;
             }
 
             upsert_variable(&variables, v_key, v_value);
             continue;
         } else if (strcasecmp(key, "set_from_resource") == 0) {
-            char res_name[512];
+            char res_name[512] = {'\0'};
             char v_key[512];
-            char fallback[4096];
+            char fallback[4096] = {'\0'};
 
             /* Ensure that this string is terminated. For example, a user might
              * want a variable to be empty if the resource can't be found and
@@ -962,11 +972,13 @@ bool parse_file(const char *f, bool use_nagbar) {
 
             if (sscanf(value, "%511s %511s %4095[^\n]", v_key, res_name, fallback) < 1) {
                 ELOG("Failed to parse resource specification '%s', skipping it.\n", value);
+                invalid_sets = true;
                 continue;
             }
 
             if (v_key[0] != '$') {
                 ELOG("Malformed variable assignment, name has to start with $\n");
+                invalid_sets = true;
                 continue;
             }
 
@@ -1002,7 +1014,7 @@ bool parse_file(const char *f, bool use_nagbar) {
         char *next;
         for (next = bufcopy;
              next < (bufcopy + stbuf.st_size) &&
-                 (next = strcasestr(next, current->key)) != NULL;
+             (next = strcasestr(next, current->key)) != NULL;
              next += strlen(current->key)) {
             *next = '_';
             extra_bytes += extra;
@@ -1082,12 +1094,12 @@ bool parse_file(const char *f, bool use_nagbar) {
     check_for_duplicate_bindings(context);
     reorder_bindings();
 
-    if (use_nagbar && (context->has_errors || context->has_warnings)) {
+    if (use_nagbar && (context->has_errors || context->has_warnings || invalid_sets)) {
         ELOG("FYI: You are using i3 version %s\n", i3_version);
         if (version == 3)
             ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
 
-        start_config_error_nagbar(f, context->has_errors);
+        start_config_error_nagbar(f, context->has_errors || invalid_sets);
     }
 
     bool has_errors = context->has_errors;
diff --git a/src/debug.c b/src/debug.c
deleted file mode 100644 (file)
index ea4ca99..0000000
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * vim:ts=4:sw=4:expandtab
- *
- * i3 - an improved dynamic tiling window manager
- * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
- *
- * debug.c: Debugging functions, especially FormatEvent, which prints unhandled
- *          events.  This code is from xcb-util.
- *
- */
-#include <config.h>
-
-#include <stdio.h>
-#include <xcb/xcb.h>
-
-#include "log.h"
-
-static const char *labelError[] = {
-    "Success",
-    "BadRequest",
-    "BadValue",
-    "BadWindow",
-    "BadPixmap",
-    "BadAtom",
-    "BadCursor",
-    "BadFont",
-    "BadMatch",
-    "BadDrawable",
-    "BadAccess",
-    "BadAlloc",
-    "BadColor",
-    "BadGC",
-    "BadIDChoice",
-    "BadName",
-    "BadLength",
-    "BadImplementation",
-};
-
-static const char *labelRequest[] = {
-    "no request",
-    "CreateWindow",
-    "ChangeWindowAttributes",
-    "GetWindowAttributes",
-    "DestroyWindow",
-    "DestroySubwindows",
-    "ChangeSaveSet",
-    "ReparentWindow",
-    "MapWindow",
-    "MapSubwindows",
-    "UnmapWindow",
-    "UnmapSubwindows",
-    "ConfigureWindow",
-    "CirculateWindow",
-    "GetGeometry",
-    "QueryTree",
-    "InternAtom",
-    "GetAtomName",
-    "ChangeProperty",
-    "DeleteProperty",
-    "GetProperty",
-    "ListProperties",
-    "SetSelectionOwner",
-    "GetSelectionOwner",
-    "ConvertSelection",
-    "SendEvent",
-    "GrabPointer",
-    "UngrabPointer",
-    "GrabButton",
-    "UngrabButton",
-    "ChangeActivePointerGrab",
-    "GrabKeyboard",
-    "UngrabKeyboard",
-    "GrabKey",
-    "UngrabKey",
-    "AllowEvents",
-    "GrabServer",
-    "UngrabServer",
-    "QueryPointer",
-    "GetMotionEvents",
-    "TranslateCoords",
-    "WarpPointer",
-    "SetInputFocus",
-    "GetInputFocus",
-    "QueryKeymap",
-    "OpenFont",
-    "CloseFont",
-    "QueryFont",
-    "QueryTextExtents",
-    "ListFonts",
-    "ListFontsWithInfo",
-    "SetFontPath",
-    "GetFontPath",
-    "CreatePixmap",
-    "FreePixmap",
-    "CreateGC",
-    "ChangeGC",
-    "CopyGC",
-    "SetDashes",
-    "SetClipRectangles",
-    "FreeGC",
-    "ClearArea",
-    "CopyArea",
-    "CopyPlane",
-    "PolyPoint",
-    "PolyLine",
-    "PolySegment",
-    "PolyRectangle",
-    "PolyArc",
-    "FillPoly",
-    "PolyFillRectangle",
-    "PolyFillArc",
-    "PutImage",
-    "GetImage",
-    "PolyText",
-    "PolyText",
-    "ImageText",
-    "ImageText",
-    "CreateColormap",
-    "FreeColormap",
-    "CopyColormapAndFree",
-    "InstallColormap",
-    "UninstallColormap",
-    "ListInstalledColormaps",
-    "AllocColor",
-    "AllocNamedColor",
-    "AllocColorCells",
-    "AllocColorPlanes",
-    "FreeColors",
-    "StoreColors",
-    "StoreNamedColor",
-    "QueryColors",
-    "LookupColor",
-    "CreateCursor",
-    "CreateGlyphCursor",
-    "FreeCursor",
-    "RecolorCursor",
-    "QueryBestSize",
-    "QueryExtension",
-    "ListExtensions",
-    "ChangeKeyboardMapping",
-    "GetKeyboardMapping",
-    "ChangeKeyboardControl",
-    "GetKeyboardControl",
-    "Bell",
-    "ChangePointerControl",
-    "GetPointerControl",
-    "SetScreenSaver",
-    "GetScreenSaver",
-    "ChangeHosts",
-    "ListHosts",
-    "SetAccessControl",
-    "SetCloseDownMode",
-    "KillClient",
-    "RotateProperties",
-    "ForceScreenSaver",
-    "SetPointerMapping",
-    "GetPointerMapping",
-    "SetModifierMapping",
-    "GetModifierMapping",
-    "major 120",
-    "major 121",
-    "major 122",
-    "major 123",
-    "major 124",
-    "major 125",
-    "major 126",
-    "NoOperation",
-};
-
-static const char *labelEvent[] = {
-    "error",
-    "reply",
-    "KeyPress",
-    "KeyRelease",
-    "ButtonPress",
-    "ButtonRelease",
-    "MotionNotify",
-    "EnterNotify",
-    "LeaveNotify",
-    "FocusIn",
-    "FocusOut",
-    "KeymapNotify",
-    "Expose",
-    "GraphicsExpose",
-    "NoExpose",
-    "VisibilityNotify",
-    "CreateNotify",
-    "DestroyNotify",
-    "UnmapNotify",
-    "MapNotify",
-    "MapRequest",
-    "ReparentNotify",
-    "ConfigureNotify",
-    "ConfigureRequest",
-    "GravityNotify",
-    "ResizeRequest",
-    "CirculateNotify",
-    "CirculateRequest",
-    "PropertyNotify",
-    "SelectionClear",
-    "SelectionRequest",
-    "SelectionNotify",
-    "ColormapNotify",
-    "ClientMessage",
-    "MappingNotify",
-};
-
-static const char *labelSendEvent[] = {
-    "",
-    " (from SendEvent)",
-};
-
-int format_event(xcb_generic_event_t *e) {
-    uint8_t sendEvent;
-    uint16_t seqnum;
-
-    sendEvent = (e->response_type & 0x80) ? 1 : 0;
-    e->response_type &= ~0x80;
-    seqnum = *((uint16_t *)e + 1);
-
-    switch (e->response_type) {
-        case 0:
-            DLOG("Error %s on seqnum %d (%s).\n",
-                 labelError[*((uint8_t *)e + 1)],
-                 seqnum,
-                 labelRequest[*((uint8_t *)e + 10)]);
-            break;
-        default:
-            if (e->response_type > sizeof(labelEvent) / sizeof(char *))
-                break;
-            DLOG("Event %s following seqnum %d%s.\n",
-                 labelEvent[e->response_type],
-                 seqnum,
-                 labelSendEvent[sendEvent]);
-            break;
-        case XCB_KEYMAP_NOTIFY:
-            DLOG("Event %s%s.\n",
-                 labelEvent[e->response_type],
-                 labelSendEvent[sendEvent]);
-            break;
-    }
-
-    fflush(stdout);
-    return 1;
-}
index 0e650e817378174f39a7bd5def167b144a508769..764ee753177f1875a5de4fdd65827a19fadad985 100644 (file)
@@ -182,4 +182,7 @@ void display_running_version(void) {
 #endif
 
     yajl_free(handle);
+    free(reply);
+    free(pid_from_atom);
+    free(socket_path);
 }
index 0a8b6957c32635a5e0c7f7205d7db4584df28dc8..f29943393c208c60913449fedbfe04036ba5fb2d 100644 (file)
@@ -72,18 +72,29 @@ void floating_check_size(Con *floating_con) {
     Rect floating_sane_max_dimensions;
     Con *focused_con = con_descend_focused(floating_con);
 
-    /* obey size increments */
-    if (focused_con->window != NULL && (focused_con->window->height_increment || focused_con->window->width_increment)) {
-        Rect border_rect = con_border_style_rect(focused_con);
-
-        /* We have to do the opposite calculations that render_con() do
-         * to get the exact size we want. */
-        border_rect.width = -border_rect.width;
-        border_rect.width += 2 * focused_con->border_width;
-        border_rect.height = -border_rect.height;
-        border_rect.height += 2 * focused_con->border_width;
-        if (con_border_style(focused_con) == BS_NORMAL)
-            border_rect.height += render_deco_height();
+    Rect border_rect = con_border_style_rect(focused_con);
+    /* We have to do the opposite calculations that render_con() do
+     * to get the exact size we want. */
+    border_rect.width = -border_rect.width;
+    border_rect.width += 2 * focused_con->border_width;
+    border_rect.height = -border_rect.height;
+    border_rect.height += 2 * focused_con->border_width;
+    if (con_border_style(focused_con) == BS_NORMAL) {
+        border_rect.height += render_deco_height();
+    }
+
+    if (focused_con->window != NULL) {
+        if (focused_con->window->min_width) {
+            floating_con->rect.width -= border_rect.width;
+            floating_con->rect.width = max(floating_con->rect.width, focused_con->window->min_width);
+            floating_con->rect.width += border_rect.width;
+        }
+
+        if (focused_con->window->min_height) {
+            floating_con->rect.height -= border_rect.height;
+            floating_con->rect.height = max(floating_con->rect.height, focused_con->window->min_height);
+            floating_con->rect.height += border_rect.height;
+        }
 
         if (focused_con->window->height_increment &&
             floating_con->rect.height >= focused_con->window->base_height + border_rect.height) {
@@ -100,36 +111,50 @@ void floating_check_size(Con *floating_con) {
         }
     }
 
-    /* Unless user requests otherwise (-1), ensure width/height do not exceed
-     * configured maxima or, if unconfigured, limit to combined width of all
-     * outputs */
+    /* Unless user requests otherwise (-1), raise the width/height to
+     * reasonable minimum dimensions */
     if (config.floating_minimum_height != -1) {
-        if (config.floating_minimum_height == 0)
+        floating_con->rect.height -= border_rect.height;
+        if (config.floating_minimum_height == 0) {
             floating_con->rect.height = max(floating_con->rect.height, floating_sane_min_height);
-        else
+        } else {
             floating_con->rect.height = max(floating_con->rect.height, config.floating_minimum_height);
+        }
+        floating_con->rect.height += border_rect.height;
     }
+
     if (config.floating_minimum_width != -1) {
-        if (config.floating_minimum_width == 0)
+        floating_con->rect.width -= border_rect.width;
+        if (config.floating_minimum_width == 0) {
             floating_con->rect.width = max(floating_con->rect.width, floating_sane_min_width);
-        else
+        } else {
             floating_con->rect.width = max(floating_con->rect.width, config.floating_minimum_width);
+        }
+        floating_con->rect.width += border_rect.width;
     }
 
-    /* Unless user requests otherwise (-1), raise the width/height to
-     * reasonable minimum dimensions */
+    /* Unless user requests otherwise (-1), ensure width/height do not exceed
+     * configured maxima or, if unconfigured, limit to combined width of all
+     * outputs */
     floating_sane_max_dimensions = total_outputs_dimensions();
     if (config.floating_maximum_height != -1) {
-        if (config.floating_maximum_height == 0)
+        floating_con->rect.height -= border_rect.height;
+        if (config.floating_maximum_height == 0) {
             floating_con->rect.height = min(floating_con->rect.height, floating_sane_max_dimensions.height);
-        else
+        } else {
             floating_con->rect.height = min(floating_con->rect.height, config.floating_maximum_height);
+        }
+        floating_con->rect.height += border_rect.height;
     }
+
     if (config.floating_maximum_width != -1) {
-        if (config.floating_maximum_width == 0)
+        floating_con->rect.width -= border_rect.width;
+        if (config.floating_maximum_width == 0) {
             floating_con->rect.width = min(floating_con->rect.width, floating_sane_max_dimensions.width);
-        else
+        } else {
             floating_con->rect.width = min(floating_con->rect.width, config.floating_maximum_width);
+        }
+        floating_con->rect.width += border_rect.width;
     }
 }
 
@@ -208,7 +233,8 @@ void floating_enable(Con *con, bool automatic) {
         }
     }
 
-    floating_check_size(nc);
+    TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes);
+    TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused);
 
     /* 3: attach the child to the new parent container. We need to do this
      * because con_border_style_rect() needs to access con->parent. */
@@ -227,13 +253,16 @@ void floating_enable(Con *con, bool automatic) {
     nc->rect.width -= border_style_rect.width;
 
     /* Add some more pixels for the title bar */
-    if (con_border_style(con) == BS_NORMAL)
+    if (con_border_style(con) == BS_NORMAL) {
         nc->rect.height += deco_height;
+    }
 
     /* Honor the X11 border */
     nc->rect.height += con->border_width * 2;
     nc->rect.width += con->border_width * 2;
 
+    floating_check_size(nc);
+
     /* Some clients (like GIMP’s color picker window) get mapped
      * to (0, 0), so we push them to a reasonable position
      * (centered over their leader) */
@@ -280,9 +309,6 @@ void floating_enable(Con *con, bool automatic) {
 
     DLOG("Corrected y = %d (deco_height = %d)\n", nc->rect.y, deco_height);
 
-    TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes);
-    TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused);
-
     /* render the cons to get initial window_rect correct */
     render_con(nc, false);
     render_con(con, false);
index 7dfacef78f5b56a6c17e5481dbc81a5e6f117956..c273e1161285df78230f7c8c3551d61ea74c6505 100644 (file)
@@ -400,11 +400,52 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
                 DLOG("Dock client will not be moved, we only support moving it to another output.\n");
             }
         }
+        fake_absolute_configure_notify(con);
+        return;
     }
 
-    fake_absolute_configure_notify(con);
+    if (event->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) {
+        DLOG("window 0x%08x wants to be stacked %d\n", event->window, event->stack_mode);
 
-    return;
+        /* Emacs and IntelliJ Idea “request focus” by stacking their window
+             * above all others. */
+        if (event->stack_mode != XCB_STACK_MODE_ABOVE) {
+            DLOG("stack_mode != XCB_STACK_MODE_ABOVE, ignoring ConfigureRequest\n");
+            goto out;
+        }
+
+        if (fullscreen || !con_is_leaf(con)) {
+            DLOG("fullscreen or not a leaf, ignoring ConfigureRequest\n");
+            goto out;
+        }
+
+        Con *ws = con_get_workspace(con);
+        if (ws == NULL) {
+            DLOG("Window is not being managed, ignoring ConfigureRequest\n");
+            goto out;
+        }
+
+        if (strcmp(ws->name, "__i3_scratch") == 0) {
+            DLOG("This is a scratchpad container, ignoring ConfigureRequest\n");
+            goto out;
+        }
+
+        if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) {
+            DLOG("Focusing con = %p\n", con);
+            workspace_show(ws);
+            con_focus(con);
+            tree_render();
+        } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
+            DLOG("Marking con = %p urgent\n", con);
+            con_set_urgency(con, true);
+            tree_render();
+        } else {
+            DLOG("Ignoring request for con = %p.\n", con);
+        }
+    }
+
+out:
+    fake_absolute_configure_notify(con);
 }
 
 /*
@@ -614,12 +655,9 @@ static void handle_expose_event(xcb_expose_event_t *event) {
     }
 
     /* Since we render to our surface on every change anyways, expose events
-     * only tell us that the X server lost (parts of) the window contents. We
-     * can handle that by copying the appropriate part from our surface to the
-     * window. */
-    draw_util_copy_surface(conn, &(parent->frame_buffer), &(parent->frame),
-                           event->x, event->y, event->x, event->y,
-                           event->width, event->height);
+     * only tell us that the X server lost (parts of) the window contents. */
+    draw_util_copy_surface(&(parent->frame_buffer), &(parent->frame),
+                           0, 0, 0, 0, parent->rect.width, parent->rect.height);
     xcb_flush(conn);
     return;
 }
@@ -637,6 +675,11 @@ static void handle_expose_event(xcb_expose_event_t *event) {
 #define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */
 #define _NET_WM_MOVERESIZE_CANCEL 11        /* cancel operation */
 
+#define _NET_MOVERESIZE_WINDOW_X (1 << 8)
+#define _NET_MOVERESIZE_WINDOW_Y (1 << 9)
+#define _NET_MOVERESIZE_WINDOW_WIDTH (1 << 10)
+#define _NET_MOVERESIZE_WINDOW_HEIGHT (1 << 11)
+
 /*
  * Handle client messages (EWMH)
  *
@@ -897,6 +940,35 @@ static void handle_client_message(xcb_client_message_event_t *event) {
                 DLOG("_NET_WM_MOVERESIZE direction %d not implemented\n", direction);
                 break;
         }
+    } else if (event->type == A__NET_MOVERESIZE_WINDOW) {
+        DLOG("Received _NET_MOVE_RESIZE_WINDOW. Handling by faking a configure request.\n");
+
+        void *_generated_event = scalloc(32, 1);
+        xcb_configure_request_event_t *generated_event = _generated_event;
+
+        generated_event->window = event->window;
+        generated_event->response_type = XCB_CONFIGURE_REQUEST;
+
+        generated_event->value_mask = 0;
+        if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_X) {
+            generated_event->value_mask |= XCB_CONFIG_WINDOW_X;
+            generated_event->x = event->data.data32[1];
+        }
+        if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_Y) {
+            generated_event->value_mask |= XCB_CONFIG_WINDOW_Y;
+            generated_event->y = event->data.data32[2];
+        }
+        if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_WIDTH) {
+            generated_event->value_mask |= XCB_CONFIG_WINDOW_WIDTH;
+            generated_event->width = event->data.data32[3];
+        }
+        if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_HEIGHT) {
+            generated_event->value_mask |= XCB_CONFIG_WINDOW_HEIGHT;
+            generated_event->height = event->data.data32[4];
+        }
+
+        handle_configure_request(generated_event);
+        FREE(generated_event);
     } else {
         DLOG("Skipping client message for unhandled type %d\n", event->type);
     }
@@ -929,54 +1001,72 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
 
     xcb_size_hints_t size_hints;
 
-    //CLIENT_LOG(client);
-
     /* If the hints were already in this event, use them, if not, request them */
-    if (reply != NULL)
+    if (reply != NULL) {
         xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply);
-    else
+    } else {
         xcb_icccm_get_wm_normal_hints_reply(conn, xcb_icccm_get_wm_normal_hints_unchecked(conn, con->window->id), &size_hints, NULL);
+    }
+
+    int win_width = con->window_rect.width;
+    int win_height = con->window_rect.height;
 
     if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) {
-        // TODO: Minimum size is not yet implemented
         DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
+
+        con->window->min_width = size_hints.min_width;
+        con->window->min_height = size_hints.min_height;
+    }
+
+    if (con_is_floating(con)) {
+        win_width = MAX(win_width, con->window->min_width);
+        win_height = MAX(win_height, con->window->min_height);
     }
 
     bool changed = false;
     if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) {
-        if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF)
+        if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) {
             if (con->window->width_increment != size_hints.width_inc) {
                 con->window->width_increment = size_hints.width_inc;
                 changed = true;
             }
-        if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF)
+        }
+
+        if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF) {
             if (con->window->height_increment != size_hints.height_inc) {
                 con->window->height_increment = size_hints.height_inc;
                 changed = true;
             }
+        }
 
-        if (changed)
+        if (changed) {
             DLOG("resize increments changed\n");
+        }
     }
 
-    int base_width = 0, base_height = 0;
+    bool has_base_size = false;
+    int base_width = 0;
+    int base_height = 0;
 
-    /* base_width/height are the desired size of the window.
-       We check if either the program-specified size or the program-specified
-       min-size is available */
+    /* The base width / height is the desired size of the window. */
     if (size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) {
         base_width = size_hints.base_width;
         base_height = size_hints.base_height;
-    } else if (size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
-        /* TODO: is this right? icccm says not */
+        has_base_size = true;
+    }
+
+    /* If the window didn't specify a base size, the ICCCM tells us to fall
+     * back to the minimum size instead, if available. */
+    if (!has_base_size && size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
         base_width = size_hints.min_width;
         base_height = size_hints.min_height;
     }
 
-    if (base_width != con->window->base_width ||
-        base_height != con->window->base_height) {
+    // TODO XXX Should we only do this is the base size is > 0?
+    if (base_width != con->window->base_width || base_height != con->window->base_height) {
         con->window->base_width = base_width;
         con->window->base_height = base_height;
+
         DLOG("client's base_height changed to %d\n", base_height);
         DLOG("client's base_width changed to %d\n", base_width);
         changed = true;
@@ -989,9 +1079,13 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
         goto render_and_return;
     }
 
-    /* XXX: do we really use rect here, not window_rect? */
-    double width = con->rect.width - base_width;
-    double height = con->rect.height - base_height;
+    /* The ICCCM says to subtract the base size from the window size for aspect
+     * ratio calculations. However, unlike determining the base size itself we
+     * must not fall back to using the minimum size in this case according to
+     * the ICCCM. */
+    double width = win_width - base_width * has_base_size;
+    double height = win_height - base_height * has_base_size;
+
     /* Convert numerator/denominator to a double */
     double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
     double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den;
@@ -1000,8 +1094,9 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
     DLOG("width = %f, height = %f\n", width, height);
 
     /* Sanity checks, this is user-input, in a way */
-    if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0)
+    if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0) {
         goto render_and_return;
+    }
 
     /* Check if we need to set proportional_* variables using the correct ratio */
     double aspect_ratio = 0.0;
@@ -1009,8 +1104,9 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
         aspect_ratio = min_aspect;
     } else if ((width / height) > max_aspect) {
         aspect_ratio = max_aspect;
-    } else
+    } else {
         goto render_and_return;
+    }
 
     if (fabs(con->window->aspect_ratio - aspect_ratio) > DBL_EPSILON) {
         con->window->aspect_ratio = aspect_ratio;
@@ -1018,8 +1114,10 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
     }
 
 render_and_return:
-    if (changed)
+    if (changed) {
         tree_render();
+    }
+
     FREE(reply);
     return true;
 }
@@ -1150,6 +1248,21 @@ static void handle_focus_in(xcb_focus_in_event_t *event) {
     return;
 }
 
+/*
+ * Handles ConfigureNotify events for the root window, which are generated when
+ * the monitor configuration changed.
+ *
+ */
+static void handle_configure_notify(xcb_configure_notify_event_t *event) {
+    if (event->event != root) {
+        DLOG("ConfigureNotify for non-root window 0x%08x, ignoring\n", event->event);
+        return;
+    }
+    DLOG("ConfigureNotify for root window 0x%08x\n", event->event);
+
+    randr_query_outputs();
+}
+
 /*
  * Handles the WM_CLASS property for assignments and criteria selection.
  *
@@ -1436,7 +1549,10 @@ void handle_event(int type, xcb_generic_event_t *event) {
             break;
 
         case XCB_EXPOSE:
-            handle_expose_event((xcb_expose_event_t *)event);
+            if (((xcb_expose_event_t *)event)->count == 0) {
+                handle_expose_event((xcb_expose_event_t *)event);
+            }
+
             break;
 
         case XCB_MOTION_NOTIFY:
@@ -1476,6 +1592,10 @@ void handle_event(int type, xcb_generic_event_t *event) {
             break;
         }
 
+        case XCB_CONFIGURE_NOTIFY:
+            handle_configure_notify((xcb_configure_notify_event_t *)event);
+            break;
+
         default:
             //DLOG("Unhandled event of type %d\n", type);
             break;
index c0dfb1ecb7dd1e6eaa5e878693d64c4d11c18521..18d6075d09901a54b2414c26b60d9da43d3baa96 100644 (file)
--- a/src/ipc.c
+++ b/src/ipc.c
@@ -22,7 +22,8 @@
 
 char *current_socketpath = NULL;
 
-TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
+TAILQ_HEAD(ipc_client_head, ipc_client)
+all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
 
 /*
  * Puts the given socket file descriptor into non-blocking mode or dies if
@@ -61,11 +62,39 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa
 }
 
 /*
- * Calls shutdown() on each socket and closes it. This function to be called
+ * For shutdown events, we send the reason for the shutdown.
+ */
+static void ipc_send_shutdown_event(shutdown_reason_t reason) {
+    yajl_gen gen = ygenalloc();
+    y(map_open);
+
+    ystr("change");
+
+    if (reason == SHUTDOWN_REASON_RESTART) {
+        ystr("restart");
+    } else if (reason == SHUTDOWN_REASON_EXIT) {
+        ystr("exit");
+    }
+
+    y(map_close);
+
+    const unsigned char *payload;
+    ylength length;
+
+    y(get_buf, &payload, &length);
+    ipc_send_event("shutdown", I3_IPC_EVENT_SHUTDOWN, (const char *)payload);
+
+    y(free);
+}
+
+/*
+ * Calls shutdown() on each socket and closes it. This function is to be called
  * when exiting or restarting only!
  *
  */
-void ipc_shutdown(void) {
+void ipc_shutdown(shutdown_reason_t reason) {
+    ipc_send_shutdown_event(reason);
+
     ipc_client *current;
     while (!TAILQ_EMPTY(&all_clients)) {
         current = TAILQ_FIRST(&all_clients);
@@ -1058,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,
@@ -1070,6 +1120,7 @@ handler_t handlers[9] = {
     handle_get_bar_config,
     handle_get_version,
     handle_get_binding_modes,
+    handle_get_config,
 };
 
 /*
index 7004a85970b8752b1ecc8d918e7f4993a47c0c10..632c6ec76f737d25d3cf78771455e4b15b6fc942 100644 (file)
@@ -29,12 +29,16 @@ static bool parsing_focus;
 static bool parsing_marks;
 struct Match *current_swallow;
 static bool swallow_is_empty;
+static int num_marks;
+static char **marks;
 
 /* This list is used for reordering the focus stack after parsing the 'focus'
  * array. */
 struct focus_mapping {
     int old_id;
-    TAILQ_ENTRY(focus_mapping) focus_mappings;
+
+    TAILQ_ENTRY(focus_mapping)
+    focus_mappings;
 };
 
 static TAILQ_HEAD(focus_mappings_head, focus_mapping) focus_mappings =
@@ -146,6 +150,16 @@ static int json_end_map(void *ctx) {
             floating_check_size(json_node);
         }
 
+        if (num_marks > 0) {
+            for (int i = 0; i < num_marks; i++) {
+                con_mark(json_node, marks[i], MM_ADD);
+                free(marks[i]);
+            }
+
+            free(marks);
+            num_marks = 0;
+        }
+
         LOG("attaching\n");
         con_attach(json_node, json_node->parent, true);
         LOG("Creating window\n");
@@ -228,8 +242,10 @@ static int json_key(void *ctx, const unsigned char *val, size_t len) {
     if (strcasecmp(last_key, "focus") == 0)
         parsing_focus = true;
 
-    if (strcasecmp(last_key, "marks") == 0)
+    if (strcasecmp(last_key, "marks") == 0) {
+        num_marks = 0;
         parsing_marks = true;
+    }
 
     return 1;
 }
@@ -259,7 +275,8 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) {
         char *mark;
         sasprintf(&mark, "%.*s", (int)len, val);
 
-        con_mark(json_node, mark, MM_ADD);
+        marks = srealloc(marks, (++num_marks) * sizeof(char *));
+        marks[num_marks - 1] = sstrdup(mark);
     } else {
         if (strcasecmp(last_key, "name") == 0) {
             json_node->name = scalloc(len + 1, 1);
index 1c33649a52d176283999a324e0c1ee6a5ef165c0..916085f456a8d8c1a95dbfca8c13d3d5b038db12 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -88,8 +88,13 @@ void init_logging(void) {
             fprintf(stderr, "Could not initialize errorlog\n");
         else {
             errorfile = fopen(errorfilename, "w");
-            if (fcntl(fileno(errorfile), F_SETFD, FD_CLOEXEC)) {
-                fprintf(stderr, "Could not set close-on-exec flag\n");
+            if (!errorfile) {
+                fprintf(stderr, "Could not initialize errorlog on %s: %s\n",
+                        errorfilename, strerror(errno));
+            } else {
+                if (fcntl(fileno(errorfile), F_SETFD, FD_CLOEXEC)) {
+                    fprintf(stderr, "Could not set close-on-exec flag\n");
+                }
             }
         }
     }
index 4737175b09a18efa810ae0533560ef8133e1359e..212654466954649c47628b53ecb0dbc5cca5b524 100644 (file)
 #include <libgen.h>
 #include "shmlog.h"
 
+#ifdef I3_ASAN_ENABLED
+#include <sanitizer/lsan_interface.h>
+#endif
+
 #include "sd-daemon.h"
 
 /* The original value of RLIMIT_CORE when i3 was started. We need to restore
@@ -194,6 +198,7 @@ int main(int argc, char *argv[]) {
     char *layout_path = NULL;
     bool delete_layout_path = false;
     bool force_xinerama = false;
+    bool disable_randr15 = false;
     char *fake_outputs = NULL;
     bool disable_signalhandler = false;
     bool only_check_config = false;
@@ -209,6 +214,8 @@ int main(int argc, char *argv[]) {
         {"restart", required_argument, 0, 0},
         {"force-xinerama", no_argument, 0, 0},
         {"force_xinerama", no_argument, 0, 0},
+        {"disable-randr15", no_argument, 0, 0},
+        {"disable_randr15", no_argument, 0, 0},
         {"disable-signalhandler", no_argument, 0, 0},
         {"shmlog-size", required_argument, 0, 0},
         {"shmlog_size", required_argument, 0, 0},
@@ -289,6 +296,10 @@ int main(int argc, char *argv[]) {
                          "Please check if your driver really does not support RandR "
                          "and disable this option as soon as you can.\n");
                     break;
+                } else if (strcmp(long_options[option_index].name, "disable-randr15") == 0 ||
+                           strcmp(long_options[option_index].name, "disable_randr15") == 0) {
+                    disable_randr15 = true;
+                    break;
                 } else if (strcmp(long_options[option_index].name, "disable-signalhandler") == 0) {
                     disable_signalhandler = true;
                     break;
@@ -544,6 +555,9 @@ int main(int argc, char *argv[]) {
     xcb_generic_error_t *error = xcb_request_check(conn, cookie);
     if (error != NULL) {
         ELOG("Another window manager seems to be running (X error %d)\n", error->error_code);
+#ifdef I3_ASAN_ENABLED
+        __lsan_do_leak_check();
+#endif
         return 1;
     }
 
@@ -661,7 +675,7 @@ int main(int argc, char *argv[]) {
         xinerama_init();
     } else {
         DLOG("Checking for XRandR...\n");
-        randr_init(&randr_base);
+        randr_init(&randr_base, disable_randr15 || config.disable_randr15);
     }
 
     /* We need to force disabling outputs which have been loaded from the
index 81ee16fdcc7fc9f0ef77f00084659f7b1b080168..86a361c376a856a32d6a1d90c51178dbec163e12 100644 (file)
@@ -491,6 +491,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
         geom->height = wm_size_hints.height;
     }
 
+    if (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
+        DLOG("Window specifies minimum size %d x %d\n", wm_size_hints.min_width, wm_size_hints.min_height);
+        nc->window->min_width = wm_size_hints.min_width;
+        nc->window->min_height = wm_size_hints.min_height;
+    }
+
     /* Store the requested geometry. The width/height gets raised to at least
      * 75x50 when entering floating mode, which is the minimum size for a
      * window to be useful (smaller windows are usually overlays/toolbars/…
index 4d87c560e6425f525d19e3074ce9cf713ee4c042..b3136ab9169bc269d028ec44032310560dcb98b9 100644 (file)
@@ -167,7 +167,7 @@ bool match_matches_window(Match *match, i3Window *window) {
         /* if we find a window that is newer than this one, bail */
         TAILQ_FOREACH(con, &all_cons, all_cons) {
             if ((con->window != NULL) &&
-                _i3_timercmp(con->window->urgent, window->urgent, > )) {
+                _i3_timercmp(con->window->urgent, window->urgent, >)) {
                 return false;
             }
         }
@@ -183,7 +183,7 @@ bool match_matches_window(Match *match, i3Window *window) {
         TAILQ_FOREACH(con, &all_cons, all_cons) {
             if ((con->window != NULL) &&
                 (con->window->urgent.tv_sec != 0) &&
-                _i3_timercmp(con->window->urgent, window->urgent, < )) {
+                _i3_timercmp(con->window->urgent, window->urgent, <)) {
                 return false;
             }
         }
@@ -307,12 +307,8 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) {
             return;
         }
 
-        char *end;
-        long parsed = strtol(cvalue, &end, 0);
-        if (parsed == LONG_MIN ||
-            parsed == LONG_MAX ||
-            parsed < 0 ||
-            (end && *end != '\0')) {
+        long parsed;
+        if (!parse_long(cvalue, &parsed, 0)) {
             ELOG("Could not parse con id \"%s\"\n", cvalue);
             match->error = sstrdup("invalid con_id");
         } else {
@@ -323,12 +319,8 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) {
     }
 
     if (strcmp(ctype, "id") == 0) {
-        char *end;
-        long parsed = strtol(cvalue, &end, 0);
-        if (parsed == LONG_MIN ||
-            parsed == LONG_MAX ||
-            parsed < 0 ||
-            (end && *end != '\0')) {
+        long parsed;
+        if (!parse_long(cvalue, &parsed, 0)) {
             ELOG("Could not parse window id \"%s\"\n", cvalue);
             match->error = sstrdup("invalid id");
         } else {
index 0f2bd61726d3930194ac5a7edf4d420db3fde9db..e3c54a67274e31821be38e7ecd62663b4bca363e 100644 (file)
@@ -41,7 +41,7 @@ Output *get_output_from_string(Output *current_output, const char *output_str) {
         return get_output_next_wrap(D_DOWN, current_output);
     }
 
-    return get_output_by_name(output_str);
+    return get_output_by_name(output_str, true);
 }
 
 Output *get_output_for_con(Con *con) {
@@ -51,7 +51,7 @@ Output *get_output_for_con(Con *con) {
         return NULL;
     }
 
-    Output *output = get_output_by_name(output_con->name);
+    Output *output = get_output_by_name(output_con->name, true);
     if (output == NULL) {
         ELOG("Could not get output from name \"%s\".\n", output_con->name);
         return NULL;
index e5dcddfc38146763b1c8e21043d73e1b09bcdef1..48bffb4622d1c86bbc1bb129e57a28036fc74e92 100644 (file)
 #include <time.h>
 #include <xcb/randr.h>
 
-/* While a clean namespace is usually a pretty good thing, we really need
- * to use shorter names than the whole xcb_randr_* default names. */
-typedef xcb_randr_get_crtc_info_reply_t crtc_info;
-typedef xcb_randr_get_screen_resources_current_reply_t resources_reply;
-
 /* Pointer to the result of the query for primary output */
 xcb_randr_get_output_primary_reply_t *primary;
 
@@ -27,6 +22,7 @@ struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs);
 
 /* This is the output covering the root window */
 static Output *root_output;
+static bool has_randr_1_5 = false;
 
 /*
  * Get a specific output by its internal X11 id. Used by randr_query_outputs
@@ -44,15 +40,19 @@ static Output *get_output_by_id(xcb_randr_output_t id) {
 }
 
 /*
- * Returns the output with the given name if it is active (!) or NULL.
+ * Returns the output with the given name or NULL.
+ * If require_active is true, only active outputs are considered.
  *
  */
-Output *get_output_by_name(const char *name) {
+Output *get_output_by_name(const char *name, const bool require_active) {
     Output *output;
-    TAILQ_FOREACH(output, &outputs, outputs)
-    if (output->active &&
-        strcasecmp(output->name, name) == 0)
-        return output;
+    bool get_primary = (strcasecmp("primary", name) == 0);
+    TAILQ_FOREACH(output, &outputs, outputs) {
+        if ((output->primary && get_primary) ||
+            ((!require_active || output->active) && strcasecmp(output->name, name) == 0)) {
+            return output;
+        }
+    }
 
     return NULL;
 }
@@ -443,7 +443,7 @@ void init_ws_for_output(Output *output, Con *content) {
         if (visible && previous == NULL) {
             LOG("There is no workspace left on \"%s\", re-initializing\n",
                 workspace_out->name);
-            init_ws_for_output(get_output_by_name(workspace_out->name),
+            init_ws_for_output(get_output_by_name(workspace_out->name, true),
                                output_get_content(workspace_out));
             DLOG("Done re-initializing, continuing with \"%s\"\n", output->name);
         }
@@ -534,18 +534,112 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) {
 }
 
 /*
- * Gets called by randr_query_outputs() for each output. The function adds new
- * outputs to the list of outputs, checks if the mode of existing outputs has
- * been changed or if an existing output has been disabled. It will then change
- * either the "changed" or the "to_be_deleted" flag of the output, if
+ * randr_query_outputs_15 uses RandR ≥ 1.5 to update outputs.
+ *
+ */
+static bool randr_query_outputs_15(void) {
+#if XCB_RANDR_MINOR_VERSION < 5
+    return false;
+#else
+    /* RandR 1.5 available at compile-time, i.e. libxcb is new enough */
+    if (!has_randr_1_5) {
+        return false;
+    }
+    /* RandR 1.5 available at run-time (supported by the server and not
+     * disabled by the user) */
+    DLOG("Querying outputs using RandR 1.5\n");
+    xcb_generic_error_t *err;
+    xcb_randr_get_monitors_reply_t *monitors =
+        xcb_randr_get_monitors_reply(
+            conn, xcb_randr_get_monitors(conn, root, true), &err);
+    if (err != NULL) {
+        ELOG("Could not get RandR monitors: X11 error code %d\n", err->error_code);
+        free(err);
+        /* Fall back to RandR ≤ 1.4 */
+        return false;
+    }
+
+    /* Mark all outputs as to_be_disabled, since xcb_randr_get_monitors() will
+     * only return active outputs. */
+    Output *output;
+    TAILQ_FOREACH(output, &outputs, outputs) {
+        if (output != root_output) {
+            output->to_be_disabled = true;
+        }
+    }
+
+    DLOG("%d RandR monitors found (timestamp %d)\n",
+         xcb_randr_get_monitors_monitors_length(monitors),
+         monitors->timestamp);
+
+    xcb_randr_monitor_info_iterator_t iter;
+    for (iter = xcb_randr_get_monitors_monitors_iterator(monitors);
+         iter.rem;
+         xcb_randr_monitor_info_next(&iter)) {
+        const xcb_randr_monitor_info_t *monitor_info = iter.data;
+        xcb_get_atom_name_reply_t *atom_reply =
+            xcb_get_atom_name_reply(
+                conn, xcb_get_atom_name(conn, monitor_info->name), &err);
+        if (err != NULL) {
+            ELOG("Could not get RandR monitor name: X11 error code %d\n", err->error_code);
+            free(err);
+            continue;
+        }
+        char *name;
+        sasprintf(&name, "%.*s",
+                  xcb_get_atom_name_name_length(atom_reply),
+                  xcb_get_atom_name_name(atom_reply));
+        free(atom_reply);
+
+        Output *new = get_output_by_name(name, false);
+        if (new == NULL) {
+            new = scalloc(1, sizeof(Output));
+            new->name = sstrdup(name);
+            if (monitor_info->primary) {
+                TAILQ_INSERT_HEAD(&outputs, new, outputs);
+            } else {
+                TAILQ_INSERT_TAIL(&outputs, new, outputs);
+            }
+        }
+        /* We specified get_active == true in xcb_randr_get_monitors(), so we
+         * will only receive active outputs. */
+        new->active = true;
+        new->to_be_disabled = false;
+
+        new->primary = monitor_info->primary;
+
+        new->changed =
+            update_if_necessary(&(new->rect.x), monitor_info->x) |
+            update_if_necessary(&(new->rect.y), monitor_info->y) |
+            update_if_necessary(&(new->rect.width), monitor_info->width) |
+            update_if_necessary(&(new->rect.height), monitor_info->height);
+
+        DLOG("name %s, x %d, y %d, width %d px, height %d px, width %d mm, height %d mm, primary %d, automatic %d\n",
+             name,
+             monitor_info->x, monitor_info->y, monitor_info->width, monitor_info->height,
+             monitor_info->width_in_millimeters, monitor_info->height_in_millimeters,
+             monitor_info->primary, monitor_info->automatic);
+        free(name);
+    }
+    free(monitors);
+    return true;
+#endif
+}
+
+/*
+ * Gets called by randr_query_outputs_14() for each output. The function adds
+ * new outputs to the list of outputs, checks if the mode of existing outputs
+ * has been changed or if an existing output has been disabled. It will then
+ * change either the "changed" or the "to_be_deleted" flag of the output, if
  * appropriate.
  *
  */
 static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
                           xcb_randr_get_output_info_reply_t *output,
-                          xcb_timestamp_t cts, resources_reply *res) {
+                          xcb_timestamp_t cts,
+                          xcb_randr_get_screen_resources_current_reply_t *res) {
     /* each CRT controller has a position in which we are interested in */
-    crtc_info *crtc;
+    xcb_randr_get_crtc_info_reply_t *crtc;
 
     Output *new = get_output_by_id(id);
     bool existing = (new != NULL);
@@ -614,25 +708,16 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
 }
 
 /*
- * (Re-)queries the outputs via RandR and stores them in the list of outputs.
- *
- * If no outputs are found use the root window.
+ * randr_query_outputs_14 uses RandR ≤ 1.4 to update outputs.
  *
  */
-void randr_query_outputs(void) {
-    Output *output, *other;
-    xcb_randr_get_output_primary_cookie_t pcookie;
-    xcb_randr_get_screen_resources_current_cookie_t rcookie;
-
-    /* timestamp of the configuration so that we get consistent replies to all
-     * requests (if the configuration changes between our different calls) */
-    xcb_timestamp_t cts;
-
-    /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */
-    xcb_randr_output_t *randr_outputs;
+static void randr_query_outputs_14(void) {
+    DLOG("Querying outputs using RandR ≤ 1.4\n");
 
     /* Get screen resources (primary output, crtcs, outputs, modes) */
+    xcb_randr_get_screen_resources_current_cookie_t rcookie;
     rcookie = xcb_randr_get_screen_resources_current(conn, root);
+    xcb_randr_get_output_primary_cookie_t pcookie;
     pcookie = xcb_randr_get_output_primary(conn, root);
 
     if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL)
@@ -640,30 +725,52 @@ void randr_query_outputs(void) {
     else
         DLOG("primary output is %08x\n", primary->output);
 
-    resources_reply *res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL);
+    xcb_randr_get_screen_resources_current_reply_t *res =
+        xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL);
     if (res == NULL) {
         ELOG("Could not query screen resources.\n");
-    } else {
-        cts = res->config_timestamp;
+        return;
+    }
 
-        int len = xcb_randr_get_screen_resources_current_outputs_length(res);
-        randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
+    /* timestamp of the configuration so that we get consistent replies to all
+     * requests (if the configuration changes between our different calls) */
+    const xcb_timestamp_t cts = res->config_timestamp;
 
-        /* Request information for each output */
-        xcb_randr_get_output_info_cookie_t ocookie[len];
-        for (int i = 0; i < len; i++)
-            ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts);
+    const int len = xcb_randr_get_screen_resources_current_outputs_length(res);
 
-        /* Loop through all outputs available for this X11 screen */
-        for (int i = 0; i < len; i++) {
-            xcb_randr_get_output_info_reply_t *output;
+    /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */
+    xcb_randr_output_t *randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
 
-            if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL)
-                continue;
+    /* Request information for each output */
+    xcb_randr_get_output_info_cookie_t ocookie[len];
+    for (int i = 0; i < len; i++)
+        ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts);
 
-            handle_output(conn, randr_outputs[i], output, cts, res);
-            free(output);
-        }
+    /* Loop through all outputs available for this X11 screen */
+    for (int i = 0; i < len; i++) {
+        xcb_randr_get_output_info_reply_t *output;
+
+        if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL)
+            continue;
+
+        handle_output(conn, randr_outputs[i], output, cts, res);
+        free(output);
+    }
+
+    FREE(res);
+}
+
+/*
+ * (Re-)queries the outputs via RandR and stores them in the list of outputs.
+ *
+ * If no outputs are found use the root window.
+ *
+ */
+void randr_query_outputs(void) {
+    Output *output, *other;
+
+    if (!randr_query_outputs_15()) {
+        randr_query_outputs_14();
     }
 
     /* If there's no randr output, enable the output covering the root window. */
@@ -763,7 +870,6 @@ void randr_query_outputs(void) {
     /* render_layout flushes */
     tree_render();
 
-    FREE(res);
     FREE(primary);
 }
 
@@ -857,12 +963,18 @@ void randr_disable_output(Output *output) {
     output->changed = false;
 }
 
+static void fallback_to_root_output(void) {
+    root_output->active = true;
+    output_init_con(root_output);
+    init_ws_for_output(root_output, output_get_content(root_output->con));
+}
+
 /*
  * We have just established a connection to the X server and need the initial
  * XRandR information to setup workspaces for each screen.
  *
  */
-void randr_init(int *event_base) {
+void randr_init(int *event_base, const bool disable_randr15) {
     const xcb_query_extension_reply_t *extreply;
 
     root_output = create_root_output(conn);
@@ -871,13 +983,27 @@ void randr_init(int *event_base) {
     extreply = xcb_get_extension_data(conn, &xcb_randr_id);
     if (!extreply->present) {
         DLOG("RandR is not present, activating root output.\n");
-        root_output->active = true;
-        output_init_con(root_output);
-        init_ws_for_output(root_output, output_get_content(root_output->con));
+        fallback_to_root_output();
+        return;
+    }
 
+    xcb_generic_error_t *err;
+    xcb_randr_query_version_reply_t *randr_version =
+        xcb_randr_query_version_reply(
+            conn, xcb_randr_query_version(conn, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), &err);
+    if (err != NULL) {
+        free(err);
+        ELOG("Could not query RandR version: X11 error code %d\n", err->error_code);
+        fallback_to_root_output();
         return;
     }
 
+    has_randr_1_5 = (randr_version->major_version >= 1) &&
+                    (randr_version->minor_version >= 5) &&
+                    !disable_randr15;
+
+    free(randr_version);
+
     randr_query_outputs();
 
     if (event_base != NULL)
index 296b91dd10cf24b03966779c522a5627f3250036..8f039157f6f7547d57e66733aa4bffdb23f72345 100644 (file)
@@ -38,6 +38,7 @@ struct regex *regex_new(const char *pattern) {
         }
         ELOG("PCRE regular expression compilation failed at %d: %s\n",
              offset, error);
+        regex_free(re);
         return NULL;
     }
     re->extra = pcre_study(re->regex, 0, &error);
index 131196e9eb4f65d430c1cd571b6b3d1f4558638c..d567e9717139d51eb996abe15565341a4a82376e 100644 (file)
@@ -15,6 +15,8 @@
 #include <sanitizer/lsan_interface.h>
 #endif
 
+#define TEXT_PADDING logical_px(2)
+
 typedef struct placeholder_state {
     /** The X11 placeholder window. */
     xcb_window_t window;
@@ -24,12 +26,11 @@ typedef struct placeholder_state {
     /** Current size of the placeholder window (to detect size changes). */
     Rect rect;
 
-    /** The pixmap to render on (back buffer). */
-    xcb_pixmap_t pixmap;
-    /** The graphics context for “pixmap”. */
-    xcb_gcontext_t gc;
+    /** The drawable surface */
+    surface_t surface;
 
-    TAILQ_ENTRY(placeholder_state) state;
+    TAILQ_ENTRY(placeholder_state)
+    state;
 } placeholder_state;
 
 static TAILQ_HEAD(state_head, placeholder_state) state_head =
@@ -137,17 +138,15 @@ void restore_connect(void) {
 }
 
 static void update_placeholder_contents(placeholder_state *state) {
-    xcb_change_gc(restore_conn, state->gc, XCB_GC_FOREGROUND,
-                  (uint32_t[]){config.client.placeholder.background.colorpixel});
-    xcb_poly_fill_rectangle(restore_conn, state->pixmap, state->gc, 1,
-                            (xcb_rectangle_t[]){{0, 0, state->rect.width, state->rect.height}});
+    const color_t foreground = config.client.placeholder.text;
+    const color_t background = config.client.placeholder.background;
+
+    draw_util_clear_surface(&(state->surface), background);
 
     // TODO: make i3font functions per-connection, at least these two for now…?
     xcb_flush(restore_conn);
     xcb_aux_sync(restore_conn);
 
-    set_font_colors(state->gc, config.client.placeholder.text, config.client.placeholder.background);
-
     Match *swallows;
     int n = 0;
     TAILQ_FOREACH(swallows, &(state->con->swallow_head), matches) {
@@ -174,7 +173,10 @@ static void update_placeholder_contents(placeholder_state *state) {
         DLOG("con %p (placeholder 0x%08x) line %d: %s\n", state->con, state->window, n, serialized);
 
         i3String *str = i3string_from_utf8(serialized);
-        draw_text(str, state->pixmap, state->gc, NULL, 2, (n * (config.font.height + 2)) + 2, state->rect.width - 2);
+        draw_util_text(str, &(state->surface), foreground, background,
+                       TEXT_PADDING,
+                       (n * (config.font.height + TEXT_PADDING)) + TEXT_PADDING,
+                       state->rect.width - 2 * TEXT_PADDING);
         i3string_free(str);
         n++;
         free(serialized);
@@ -185,7 +187,7 @@ static void update_placeholder_contents(placeholder_state *state) {
     int text_width = predict_text_width(line);
     int x = (state->rect.width / 2) - (text_width / 2);
     int y = (state->rect.height / 2) - (config.font.height / 2);
-    draw_text(line, state->pixmap, state->gc, NULL, x, y, text_width);
+    draw_util_text(line, &(state->surface), foreground, background, x, y, text_width);
     i3string_free(line);
     xcb_flush(conn);
     xcb_aux_sync(conn);
@@ -227,11 +229,8 @@ static void open_placeholder_window(Con *con) {
         state->window = placeholder;
         state->con = con;
         state->rect = con->rect;
-        state->pixmap = xcb_generate_id(restore_conn);
-        xcb_create_pixmap(restore_conn, root_depth, state->pixmap,
-                          state->window, state->rect.width, state->rect.height);
-        state->gc = xcb_generate_id(restore_conn);
-        xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){0});
+
+        draw_util_surface_init(conn, &(state->surface), placeholder, get_visualtype(root_screen), state->rect.width, state->rect.height);
         update_placeholder_contents(state);
         TAILQ_INSERT_TAIL(&state_head, state, state);
 
@@ -285,8 +284,7 @@ bool restore_kill_placeholder(xcb_window_t placeholder) {
             continue;
 
         xcb_destroy_window(restore_conn, state->window);
-        xcb_free_pixmap(restore_conn, state->pixmap);
-        xcb_free_gc(restore_conn, state->gc);
+        draw_util_surface_free(restore_conn, &(state->surface));
         TAILQ_REMOVE(&state_head, state, state);
         free(state);
         DLOG("placeholder window 0x%08x destroyed.\n", placeholder);
@@ -305,14 +303,8 @@ static void expose_event(xcb_expose_event_t *event) {
 
         DLOG("refreshing window 0x%08x contents (con %p)\n", state->window, state->con);
 
-        /* Since we render to our pixmap on every change anyways, expose events
-         * only tell us that the X server lost (parts of) the window contents. We
-         * can handle that by copying the appropriate part from our pixmap to the
-         * window. */
-        xcb_copy_area(restore_conn, state->pixmap, state->window, state->gc,
-                      event->x, event->y, event->x, event->y,
-                      event->width, event->height);
-        xcb_flush(restore_conn);
+        update_placeholder_contents(state);
+
         return;
     }
 
@@ -337,19 +329,10 @@ static void configure_notify(xcb_configure_notify_event_t *event) {
         state->rect.width = event->width;
         state->rect.height = event->height;
 
-        xcb_free_pixmap(restore_conn, state->pixmap);
-        xcb_free_gc(restore_conn, state->gc);
-
-        state->pixmap = xcb_generate_id(restore_conn);
-        xcb_create_pixmap(restore_conn, root_depth, state->pixmap,
-                          state->window, state->rect.width, state->rect.height);
-        state->gc = xcb_generate_id(restore_conn);
-        xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){0});
+        draw_util_surface_set_size(&(state->surface), state->rect.width, state->rect.height);
 
         update_placeholder_contents(state);
-        xcb_copy_area(restore_conn, state->pixmap, state->window, state->gc,
-                      0, 0, 0, 0, state->rect.width, state->rect.height);
-        xcb_flush(restore_conn);
+
         return;
     }
 
@@ -359,7 +342,10 @@ static void configure_notify(xcb_configure_notify_event_t *event) {
 static void restore_handle_event(int type, xcb_generic_event_t *event) {
     switch (type) {
         case XCB_EXPOSE:
-            expose_event((xcb_expose_event_t *)event);
+            if (((xcb_expose_event_t *)event)->count == 0) {
+                expose_event((xcb_expose_event_t *)event);
+            }
+
             break;
         case XCB_CONFIGURE_NOTIFY:
             configure_notify((xcb_configure_notify_event_t *)event);
index 79f90d9a72a0f8aa8953c57710c8f8e0f65ff91e..b1e7d166f7631d371e775541a78a3a541832677d 100644 (file)
@@ -3,10 +3,6 @@
  *
  * i3 - an improved dynamic tiling window manager
  * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
- * © 2009 Jan-Erik Rediger
- *
- * sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers
- *               to restart inplace).
  *
  */
 #include "all.h"
 
 #include <X11/keysym.h>
 
-static void open_popups(void);
+typedef struct dialog_t {
+    xcb_window_t id;
+    xcb_colormap_t colormap;
+    Rect dims;
+    surface_t surface;
 
-static xcb_gcontext_t pixmap_gc;
-static xcb_pixmap_t pixmap;
-static int raised_signal;
+    TAILQ_ENTRY(dialog_t)
+    dialogs;
+} dialog_t;
 
-static char *crash_text[] = {
-    "i3 just crashed.",
-    "To debug this problem, either attach gdb now",
-    "or press",
-    "- 'b' to save a backtrace (needs GDB),",
-    "- 'r' to restart i3 in-place or",
-    "- 'f' to forget the current layout and restart"};
-static int crash_text_longest = 5;
-static int backtrace_string_index = 3;
+static TAILQ_HEAD(dialogs_head, dialog_t) dialogs = TAILQ_HEAD_INITIALIZER(dialogs);
+static int raised_signal;
 static int backtrace_done = 0;
 
+static int sighandler_backtrace(void);
+static void sighandler_setup(void);
+static void sighandler_create_dialogs(void);
+static void sighandler_destroy_dialogs(void);
+static void sighandler_handle_expose(void);
+static void sighandler_draw_dialog(dialog_t *dialog);
+static void sighandler_handle_key_press(xcb_key_press_event_t *event);
+
+static i3String *message_intro;
+static i3String *message_intro2;
+static i3String *message_option_backtrace;
+static i3String *message_option_restart;
+static i3String *message_option_forget;
+static int dialog_width;
+static int dialog_height;
+
+static int border_width = 2;
+static int margin = 4;
+
 /*
  * Attach gdb to pid_parent and dump a backtrace to i3-backtrace.$pid in the
  * tmpdir
  */
-static int backtrace(void) {
+static int sighandler_backtrace(void) {
     char *tmpdir = getenv("TMPDIR");
     if (tmpdir == NULL)
         tmpdir = "/tmp";
@@ -125,167 +137,169 @@ static int backtrace(void) {
     return 1;
 }
 
-/*
- * Draw the window containing the info text
- *
- */
-static int sig_draw_window(xcb_window_t win, int width, int height, int font_height, i3String **crash_text_i3strings) {
-    /* re-draw the background */
-    xcb_rectangle_t border = {0, 0, width, height},
-                    inner = {2, 2, width - 4, height - 4};
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#FF0000")});
-    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
-    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")});
-    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
-
-    /* restore font color */
-    set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000"));
-
-    char *bt_colour = "#FFFFFF";
-    if (backtrace_done < 0)
-        bt_colour = "#AA0000";
-    else if (backtrace_done > 0)
-        bt_colour = "#00AA00";
-
-    for (int i = 0; crash_text_i3strings[i] != NULL; ++i) {
-        /* fix the colour for the backtrace line when it finished */
-        if (i == backtrace_string_index)
-            set_font_colors(pixmap_gc, draw_util_hex_to_color(bt_colour), draw_util_hex_to_color("#000000"));
-
-        draw_text(crash_text_i3strings[i], pixmap, pixmap_gc, NULL,
-                  8, 5 + i * font_height, width - 16);
-
-        /* and reset the colour again for other lines */
-        if (i == backtrace_string_index)
-            set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000"));
-    }
+static void sighandler_setup(void) {
+    border_width = logical_px(border_width);
+    margin = logical_px(margin);
 
-    /* Copy the contents of the pixmap to the real window */
-    xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, width, height);
-    xcb_flush(conn);
+    int num_lines = 5;
+    message_intro = i3string_from_utf8("i3 has just crashed. Please report a bug for this.");
+    message_intro2 = i3string_from_utf8("To debug this problem, you can either attach gdb or choose from the following options:");
+    message_option_backtrace = i3string_from_utf8("- 'b' to save a backtrace (requires gdb)");
+    message_option_restart = i3string_from_utf8("- 'r' to restart i3 in-place");
+    message_option_forget = i3string_from_utf8("- 'f' to forget the previous layout and restart i3");
 
-    return 1;
+    int width_longest_message = predict_text_width(message_intro2);
+
+    dialog_width = width_longest_message + 2 * border_width + 2 * margin;
+    dialog_height = num_lines * config.font.height + 2 * border_width + 2 * margin;
 }
 
-/*
- * Handles keypresses of 'b', 'r' and 'f' to get a backtrace or restart i3
- *
- */
-static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
-    uint16_t state = event->state;
+static void sighandler_create_dialogs(void) {
+    Output *output;
+    TAILQ_FOREACH(output, &outputs, outputs) {
+        if (!output->active) {
+            continue;
+        }
 
-    /* Apparently, after activating numlock once, the numlock modifier
-     * stays turned on (use xev(1) to verify). So, to resolve useful
-     * keysyms, we remove the numlock flag from the event state */
-    state &= ~xcb_numlock_mask;
+        dialog_t *dialog = scalloc(1, sizeof(struct dialog_t));
+        TAILQ_INSERT_TAIL(&dialogs, dialog, dialogs);
 
-    xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, state);
+        xcb_visualid_t visual = get_visualid_by_depth(root_depth);
+        dialog->colormap = xcb_generate_id(conn);
+        xcb_create_colormap(conn, XCB_COLORMAP_ALLOC_NONE, dialog->colormap, root, visual);
 
-    if (sym == 'b') {
-        DLOG("User issued core-dump command.\n");
+        uint32_t mask = 0;
+        uint32_t values[4];
+        int i = 0;
 
-        /* fork and exec/attach GDB to the parent to get a backtrace in the
-         * tmpdir */
-        backtrace_done = backtrace();
+        /* Needs to be set in the case of a 32-bit root depth. */
+        mask |= XCB_CW_BACK_PIXEL;
+        values[i++] = root_screen->black_pixel;
 
-        /* re-open the windows to indicate that it's finished */
-        open_popups();
-    }
+        /* Needs to be set in the case of a 32-bit root depth. */
+        mask |= XCB_CW_BORDER_PIXEL;
+        values[i++] = root_screen->black_pixel;
 
-    if (sym == 'r')
-        i3_restart(false);
+        mask |= XCB_CW_OVERRIDE_REDIRECT;
+        values[i++] = 1;
 
-    if (sym == 'f')
-        i3_restart(true);
+        /* Needs to be set in the case of a 32-bit root depth. */
+        mask |= XCB_CW_COLORMAP;
+        values[i++] = dialog->colormap;
 
-    return 1;
-}
+        dialog->dims.x = output->rect.x + (output->rect.width / 2);
+        dialog->dims.y = output->rect.y + (output->rect.height / 2);
+        dialog->dims.width = dialog_width;
+        dialog->dims.height = dialog_height;
 
-/*
- * Opens the window we use for input/output and maps it
- *
- */
-static xcb_window_t open_input_window(xcb_connection_t *conn, Rect screen_rect, uint32_t width, uint32_t height) {
-    xcb_window_t win = xcb_generate_id(conn);
+        /* Make sure the dialog is centered. */
+        dialog->dims.x -= dialog->dims.width / 2;
+        dialog->dims.y -= dialog->dims.height / 2;
 
-    uint32_t mask = 0;
-    uint32_t values[2];
+        dialog->id = create_window(conn, dialog->dims, root_depth, visual,
+                                   XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER,
+                                   true, mask, values);
 
-    mask |= XCB_CW_BACK_PIXEL;
-    values[0] = 0;
+        draw_util_surface_init(conn, &(dialog->surface), dialog->id, get_visualtype_by_id(visual),
+                               dialog->dims.width, dialog->dims.height);
 
-    mask |= XCB_CW_OVERRIDE_REDIRECT;
-    values[1] = 1;
+        xcb_grab_keyboard(conn, false, dialog->id, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
 
-    /* center each popup on the specified screen */
-    uint32_t x = screen_rect.x + ((screen_rect.width / 2) - (width / 2)),
-             y = screen_rect.y + ((screen_rect.height / 2) - (height / 2));
+        /* Confine the pointer to the crash dialog. */
+        xcb_grab_pointer(conn, false, dialog->id, XCB_NONE, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, dialog->id,
+                         XCB_NONE, XCB_CURRENT_TIME);
+    }
 
-    xcb_create_window(conn,
-                      XCB_COPY_FROM_PARENT,
-                      win,                 /* the window id */
-                      root,                /* parent == root */
-                      x, y, width, height, /* dimensions */
-                      0,                   /* border = 0, we draw our own */
-                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
-                      XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
-                      mask,
-                      values);
+    sighandler_handle_expose();
+    xcb_flush(conn);
+}
+
+static void sighandler_destroy_dialogs(void) {
+    while (!TAILQ_EMPTY(&dialogs)) {
+        dialog_t *dialog = TAILQ_FIRST(&dialogs);
+
+        xcb_free_colormap(conn, dialog->colormap);
+        draw_util_surface_free(conn, &(dialog->surface));
+        xcb_destroy_window(conn, dialog->id);
+
+        TAILQ_REMOVE(&dialogs, dialog, dialogs);
+        free(dialog);
+    }
 
-    /* Map the window (= make it visible) */
-    xcb_map_window(conn, win);
+    xcb_flush(conn);
+}
+
+static void sighandler_handle_expose(void) {
+    dialog_t *current;
+    TAILQ_FOREACH(current, &dialogs, dialogs) {
+        sighandler_draw_dialog(current);
+    }
 
-    return win;
+    xcb_flush(conn);
 }
 
-static void open_popups() {
-    /* width and height of the popup window, so that the text fits in */
-    int crash_text_num = sizeof(crash_text) / sizeof(char *);
-    int height = 13 + (crash_text_num * config.font.height);
+static void sighandler_draw_dialog(dialog_t *dialog) {
+    const color_t black = draw_util_hex_to_color("#000000");
+    const color_t white = draw_util_hex_to_color("#FFFFFF");
+    const color_t red = draw_util_hex_to_color("#FF0000");
+
+    /* Start with a clean slate and draw a red border. */
+    draw_util_clear_surface(&(dialog->surface), red);
+    draw_util_rectangle(&(dialog->surface), black, border_width, border_width,
+                        dialog->dims.width - 2 * border_width, dialog->dims.height - 2 * border_width);
+
+    int y = border_width + margin;
+    const int x = border_width + margin;
+    const int max_width = dialog->dims.width - 2 * x;
+
+    draw_util_text(message_intro, &(dialog->surface), white, black, x, y, max_width);
+    y += config.font.height;
+
+    draw_util_text(message_intro2, &(dialog->surface), white, black, x, y, max_width);
+    y += config.font.height;
 
-    int crash_text_length = sizeof(crash_text) / sizeof(char *);
-    i3String **crash_text_i3strings = smalloc(sizeof(i3String *) * (crash_text_length + 1));
-    /* Pre-compute i3Strings for our text */
-    for (int i = 0; i < crash_text_length; ++i) {
-        crash_text_i3strings[i] = i3string_from_utf8(crash_text[i]);
+    char *bt_color = "#FFFFFF";
+    if (backtrace_done < 0) {
+        bt_color = "#AA0000";
+    } else if (backtrace_done > 0) {
+        bt_color = "#00AA00";
     }
-    crash_text_i3strings[crash_text_length] = NULL;
-    /* calculate width for longest text */
-    int font_width = predict_text_width(crash_text_i3strings[crash_text_longest]);
-    int width = font_width + 20;
-
-    /* Open a popup window on each virtual screen */
-    Output *screen;
-    xcb_window_t win;
-    TAILQ_FOREACH(screen, &outputs, outputs) {
-        if (!screen->active)
-            continue;
-        win = open_input_window(conn, screen->rect, width, height);
+    draw_util_text(message_option_backtrace, &(dialog->surface), draw_util_hex_to_color(bt_color), black, x, y, max_width);
+    y += config.font.height;
 
-        /* Create pixmap */
-        pixmap = xcb_generate_id(conn);
-        pixmap_gc = xcb_generate_id(conn);
-        xcb_create_pixmap(conn, root_depth, pixmap, win, width, height);
-        xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
+    draw_util_text(message_option_restart, &(dialog->surface), white, black, x, y, max_width);
+    y += config.font.height;
 
-        /* Grab the keyboard to get all input */
-        xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
+    draw_util_text(message_option_forget, &(dialog->surface), white, black, x, y, max_width);
+    y += config.font.height;
+}
 
-        /* Grab the cursor inside the popup */
-        xcb_grab_pointer(conn, false, win, XCB_NONE, XCB_GRAB_MODE_ASYNC,
-                         XCB_GRAB_MODE_ASYNC, win, XCB_NONE, XCB_CURRENT_TIME);
+static void sighandler_handle_key_press(xcb_key_press_event_t *event) {
+    uint16_t state = event->state;
 
-        sig_draw_window(win, width, height, config.font.height, crash_text_i3strings);
-        xcb_flush(conn);
+    /* Apparently, after activating numlock once, the numlock modifier
+     * stays turned on (use xev(1) to verify). So, to resolve useful
+     * keysyms, we remove the numlock flag from the event state */
+    state &= ~xcb_numlock_mask;
+
+    xcb_keysym_t sym = xcb_key_press_lookup_keysym(keysyms, event, state);
+
+    if (sym == 'b') {
+        DLOG("User issued core-dump command.\n");
+
+        /* fork and exec/attach GDB to the parent to get a backtrace in the
+         * tmpdir */
+        backtrace_done = sighandler_backtrace();
+        sighandler_handle_expose();
+    } else if (sym == 'r') {
+        sighandler_destroy_dialogs();
+        i3_restart(false);
+    } else if (sym == 'f') {
+        sighandler_destroy_dialogs();
+        i3_restart(true);
     }
 }
 
-/*
- * Handle signals
- * It creates a window asking the user to restart in-place
- * or exit to generate a core dump
- *
- */
 void handle_signal(int sig, siginfo_t *info, void *data) {
     DLOG("i3 crashed. SIG: %d\n", sig);
 
@@ -294,22 +308,33 @@ void handle_signal(int sig, siginfo_t *info, void *data) {
     sigaction(sig, &action, NULL);
     raised_signal = sig;
 
-    open_popups();
+    sighandler_setup();
+    sighandler_create_dialogs();
 
     xcb_generic_event_t *event;
     /* Yay, more own eventhandlers… */
     while ((event = xcb_wait_for_event(conn))) {
         /* Strip off the highest bit (set if the event is generated) */
         int type = (event->response_type & 0x7F);
-        if (type == XCB_KEY_PRESS) {
-            sig_handle_key_press(NULL, conn, (xcb_key_press_event_t *)event);
+        switch (type) {
+            case XCB_KEY_PRESS:
+                sighandler_handle_key_press((xcb_key_press_event_t *)event);
+                break;
+            case XCB_EXPOSE:
+                if (((xcb_expose_event_t *)event)->count == 0) {
+                    sighandler_handle_expose();
+                }
+
+                break;
         }
+
         free(event);
     }
 }
 
 /*
- * Setup signal handlers to safely handle SIGSEGV and SIGFPE
+ * Configured a signal handler to gracefully handle crashes and allow the user
+ * to generate a backtrace and rescue their session.
  *
  */
 void setup_signal_handler(void) {
index 6e128363913319decb436054d39526ec2b104b75..2d4647f8dfba08b7d6c97abe3fc1588119d905a0 100644 (file)
@@ -329,6 +329,12 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
         match_free(match);
         free(match);
     }
+    while (!TAILQ_EMPTY(&(con->marks_head))) {
+        mark_t *mark = TAILQ_FIRST(&(con->marks_head));
+        TAILQ_REMOVE(&(con->marks_head), mark, marks);
+        FREE(mark->name);
+        FREE(mark);
+    }
     free(con);
 
     /* in the case of floating windows, we already focused another container
@@ -377,7 +383,11 @@ void tree_split(Con *con, orientation_t orientation) {
 
     if (con->type == CT_WORKSPACE) {
         if (con_num_children(con) < 2) {
-            DLOG("Just changing orientation of workspace\n");
+            if (con_num_children(con) == 0) {
+                DLOG("Changing workspace_layout to L_DEFAULT\n");
+                con->workspace_layout = L_DEFAULT;
+            }
+            DLOG("Changing orientation of workspace\n");
             con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
             return;
         } else {
index cfe4c953db029c1333e4f9ed90396cb8d79c58a6..32c3c57e15d5bd2f206bab5a1984a00e19dff06a 100644 (file)
@@ -66,6 +66,34 @@ __attribute__((pure)) bool name_is_digits(const char *name) {
     return true;
 }
 
+/*
+ * Set 'out' to the layout_t value for the given layout. The function
+ * returns true on success or false if the passed string is not a valid
+ * layout name.
+ *
+ */
+bool layout_from_name(const char *layout_str, layout_t *out) {
+    if (strcmp(layout_str, "default") == 0) {
+        *out = L_DEFAULT;
+        return true;
+    } else if (strcasecmp(layout_str, "stacked") == 0 ||
+               strcasecmp(layout_str, "stacking") == 0) {
+        *out = L_STACKED;
+        return true;
+    } else if (strcasecmp(layout_str, "tabbed") == 0) {
+        *out = L_TABBED;
+        return true;
+    } else if (strcasecmp(layout_str, "splitv") == 0) {
+        *out = L_SPLITV;
+        return true;
+    } else if (strcasecmp(layout_str, "splith") == 0) {
+        *out = L_SPLITH;
+        return true;
+    }
+
+    return false;
+}
+
 /*
  * Parses the workspace name as a number. Returns -1 if the workspace should be
  * interpreted as a "named workspace".
@@ -259,7 +287,7 @@ void i3_restart(bool forget_layout) {
 
     restore_geometry();
 
-    ipc_shutdown();
+    ipc_shutdown(SHUTDOWN_REASON_RESTART);
 
     LOG("restarting \"%s\"...\n", start_argv[0]);
     /* make sure -a is in the argument list or add it */
@@ -430,3 +458,20 @@ void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it) {
      * waitpid() here. */
     waitpid(*nagbar_pid, NULL, 0);
 }
+
+/*
+ * Converts a string into a long using strtol().
+ * This is a convenience wrapper checking the parsing result. It returns true
+ * if the number could be parsed.
+ */
+bool parse_long(const char *str, long *out, int base) {
+    char *end;
+    long result = strtol(str, &end, base);
+    if (result == LONG_MIN || result == LONG_MAX || result < 0 || (end != NULL && *end != '\0')) {
+        *out = result;
+        return false;
+    }
+
+    *out = result;
+    return true;
+}
diff --git a/src/x.c b/src/x.c
index 8d7d3dd803098ca61ca021c43aeed3805f2b591a..ee5ed2ce794fa9ca397c99d6ec3295720d87cb99 100644 (file)
--- a/src/x.c
+++ b/src/x.c
@@ -58,18 +58,26 @@ typedef struct con_state {
 
     char *name;
 
-    CIRCLEQ_ENTRY(con_state) state;
-    CIRCLEQ_ENTRY(con_state) old_state;
-    TAILQ_ENTRY(con_state) initial_mapping_order;
+    CIRCLEQ_ENTRY(con_state)
+    state;
+
+    CIRCLEQ_ENTRY(con_state)
+    old_state;
+
+    TAILQ_ENTRY(con_state)
+    initial_mapping_order;
 } con_state;
 
-CIRCLEQ_HEAD(state_head, con_state) state_head =
+CIRCLEQ_HEAD(state_head, con_state)
+state_head =
     CIRCLEQ_HEAD_INITIALIZER(state_head);
 
-CIRCLEQ_HEAD(old_state_head, con_state) old_state_head =
+CIRCLEQ_HEAD(old_state_head, con_state)
+old_state_head =
     CIRCLEQ_HEAD_INITIALIZER(old_state_head);
 
-TAILQ_HEAD(initial_mapping_head, con_state) initial_mapping_head =
+TAILQ_HEAD(initial_mapping_head, con_state)
+initial_mapping_head =
     TAILQ_HEAD_INITIALIZER(initial_mapping_head);
 
 /*
@@ -322,10 +330,10 @@ static void x_draw_title_border(Con *con, struct deco_render_params *p) {
         deco_diff_r = 0;
     }
 
-    draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border,
+    draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
                         dr->x, dr->y, dr->width, 1);
 
-    draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border,
+    draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
                         dr->x + deco_diff_l, dr->y + dr->height - 1, dr->width - (deco_diff_l + deco_diff_r), 1);
 }
 
@@ -341,7 +349,7 @@ static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p
         /* We actually only redraw the far right two pixels as that is the
          * distance we keep from the edge (not the entire border width).
          * Redrawing the entire border would cause text to be cut off. */
-        draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->background,
+        draw_util_rectangle(&(con->parent->frame_buffer), p->color->background,
                             dr->x + dr->width - 2 * logical_px(1),
                             dr->y,
                             2 * logical_px(1),
@@ -352,11 +360,11 @@ static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p
      * be easily distinguished. */
     if (con->parent->layout == L_TABBED) {
         /* Left side */
-        draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border,
+        draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
                             dr->x, dr->y, 1, dr->height);
 
         /* Right side */
-        draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border,
+        draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
                             dr->x + dr->width - 1, dr->y, 1, dr->height);
     }
 
@@ -450,16 +458,16 @@ void x_draw_decoration(Con *con) {
     /* 2: draw the client.background, but only for the parts around the window_rect */
     if (con->window != NULL) {
         /* top area */
-        draw_util_rectangle(conn, &(con->frame_buffer), config.client.background,
+        draw_util_rectangle(&(con->frame_buffer), config.client.background,
                             0, 0, r->width, w->y);
         /* bottom area */
-        draw_util_rectangle(conn, &(con->frame_buffer), config.client.background,
+        draw_util_rectangle(&(con->frame_buffer), config.client.background,
                             0, w->y + w->height, r->width, r->height - (w->y + w->height));
         /* left area */
-        draw_util_rectangle(conn, &(con->frame_buffer), config.client.background,
+        draw_util_rectangle(&(con->frame_buffer), config.client.background,
                             0, 0, w->x, r->height);
         /* right area */
-        draw_util_rectangle(conn, &(con->frame_buffer), config.client.background,
+        draw_util_rectangle(&(con->frame_buffer), config.client.background,
                             w->x + w->width, 0, r->width - (w->x + w->width), r->height);
     }
 
@@ -476,21 +484,21 @@ void x_draw_decoration(Con *con) {
          * rectangle because some childs are not freely resizable and we want
          * their background color to "shine through". */
         if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) {
-            draw_util_rectangle(conn, &(con->frame_buffer), p->color->child_border, 0, 0, br.x, r->height);
+            draw_util_rectangle(&(con->frame_buffer), p->color->child_border, 0, 0, br.x, r->height);
         }
         if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) {
-            draw_util_rectangle(conn, &(con->frame_buffer),
+            draw_util_rectangle(&(con->frame_buffer),
                                 p->color->child_border, r->width + (br.width + br.x), 0,
                                 -(br.width + br.x), r->height);
         }
         if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) {
-            draw_util_rectangle(conn, &(con->frame_buffer),
+            draw_util_rectangle(&(con->frame_buffer),
                                 p->color->child_border, br.x, r->height + (br.height + br.y),
                                 r->width + br.width, -(br.height + br.y));
         }
         /* pixel border needs an additional line at the top */
         if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) {
-            draw_util_rectangle(conn, &(con->frame_buffer),
+            draw_util_rectangle(&(con->frame_buffer),
                                 p->color->child_border, br.x, 0, r->width + br.width, br.y);
         }
 
@@ -502,10 +510,10 @@ void x_draw_decoration(Con *con) {
             TAILQ_PREV(con, nodes_head, nodes) == NULL &&
             con->parent->type != CT_FLOATING_CON) {
             if (p->parent_layout == L_SPLITH) {
-                draw_util_rectangle(conn, &(con->frame_buffer), p->color->indicator,
+                draw_util_rectangle(&(con->frame_buffer), p->color->indicator,
                                     r->width + (br.width + br.x), br.y, -(br.width + br.x), r->height + br.height);
             } else if (p->parent_layout == L_SPLITV) {
-                draw_util_rectangle(conn, &(con->frame_buffer), p->color->indicator,
+                draw_util_rectangle(&(con->frame_buffer), p->color->indicator,
                                     br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y));
             }
         }
@@ -525,12 +533,12 @@ void x_draw_decoration(Con *con) {
      * garbage left on there. This is important to avoid tearing when using
      * transparency. */
     if (con == TAILQ_FIRST(&(con->parent->nodes_head))) {
-        draw_util_clear_surface(conn, &(con->parent->frame_buffer), COLOR_TRANSPARENT);
+        draw_util_clear_surface(&(con->parent->frame_buffer), COLOR_TRANSPARENT);
         FREE(con->parent->deco_render_params);
     }
 
     /* 4: paint the bar */
-    draw_util_rectangle(conn, &(parent->frame_buffer), p->color->background,
+    draw_util_rectangle(&(parent->frame_buffer), p->color->background,
                         con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height);
 
     /* 5: draw two unconnected horizontal lines in border color */
@@ -564,9 +572,6 @@ void x_draw_decoration(Con *con) {
         goto after_title;
     }
 
-    if (win->name == NULL)
-        goto copy_pixmaps;
-
     int mark_width = 0;
     if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) {
         char *formatted_mark = sstrdup("");
@@ -600,18 +605,24 @@ void x_draw_decoration(Con *con) {
     }
 
     i3String *title = con->title_format == NULL ? win->name : con_parse_title_format(con);
+    if (title == NULL) {
+        goto copy_pixmaps;
+    }
+
     draw_util_text(title, &(parent->frame_buffer),
                    p->color->text, p->color->background,
                    con->deco_rect.x + logical_px(2),
                    con->deco_rect.y + text_offset_y,
                    con->deco_rect.width - mark_width - 2 * logical_px(2));
-    if (con->title_format != NULL)
+
+    if (con->title_format != NULL) {
         I3STRING_FREE(title);
+    }
 
 after_title:
     x_draw_decoration_after_title(con, p);
 copy_pixmaps:
-    draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
+    draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
 }
 
 /*
@@ -634,7 +645,7 @@ void x_deco_recurse(Con *con) {
         x_deco_recurse(current);
 
         if (state->mapped) {
-            draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
+            draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
         }
     }
 
@@ -821,7 +832,7 @@ void x_push_node(Con *con) {
         xcb_flush(conn);
         xcb_set_window_rect(conn, con->frame.id, rect);
         if (con->frame_buffer.id != XCB_NONE) {
-            draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
+            draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
         }
         xcb_flush(conn);
 
@@ -873,7 +884,7 @@ void x_push_node(Con *con) {
 
         /* copy the pixmap contents to the frame window immediately after mapping */
         if (con->frame_buffer.id != XCB_NONE) {
-            draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
+            draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
         }
         xcb_flush(conn);
 
@@ -893,8 +904,9 @@ void x_push_node(Con *con) {
     /* Handle all children and floating windows of this node. We recurse
      * in focus order to display the focused client in a stack first when
      * switching workspaces (reduces flickering). */
-    TAILQ_FOREACH(current, &(con->focus_head), focused)
-    x_push_node(current);
+    TAILQ_FOREACH(current, &(con->focus_head), focused) {
+        x_push_node(current);
+    }
 }
 
 /*
index 900840a20a419b19832680d4d9a9c20195227065..bdfb08bc4d9dd994bc75a7050fa8b06019993eed 100644 (file)
--- a/src/xcb.c
+++ b/src/xcb.c
@@ -28,16 +28,21 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims,
         visual = XCB_COPY_FROM_PARENT;
     }
 
-    xcb_create_window(conn,
-                      depth,
-                      result,                                  /* the window id */
-                      root,                                    /* parent == root */
-                      dims.x, dims.y, dims.width, dims.height, /* dimensions */
-                      0,                                       /* border = 0, we draw our own */
-                      window_class,
-                      visual,
-                      mask,
-                      values);
+    xcb_void_cookie_t gc_cookie = xcb_create_window(conn,
+                                                    depth,
+                                                    result,                                  /* the window id */
+                                                    root,                                    /* parent == root */
+                                                    dims.x, dims.y, dims.width, dims.height, /* dimensions */
+                                                    0,                                       /* border = 0, we draw our own */
+                                                    window_class,
+                                                    visual,
+                                                    mask,
+                                                    values);
+
+    xcb_generic_error_t *error = xcb_request_check(conn, gc_cookie);
+    if (error != NULL) {
+        ELOG("Could not create window. Error code: %d.\n", error->error_code);
+    }
 
     /* Set the cursor */
     if (xcursor_supported) {
@@ -62,28 +67,6 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims,
     return result;
 }
 
-/*
- * Draws a line from x,y to to_x,to_y using the given color
- *
- */
-void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
-                   uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t to_x, uint32_t to_y) {
-    xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, (uint32_t[]){colorpixel});
-    xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2,
-                  (xcb_point_t[]){{x, y}, {to_x, to_y}});
-}
-
-/*
- * Draws a rectangle from x,y with width,height using the given color
- *
- */
-void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc,
-                   uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
-    xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, (uint32_t[]){colorpixel});
-    xcb_rectangle_t rect = {x, y, width, height};
-    xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
-}
-
 /*
  * Generates a configure_notify_event with absolute coordinates (relative to the X root
  * window, not to the client’s frame) for the given client.
@@ -127,15 +110,6 @@ void send_take_focus(xcb_window_t window, xcb_timestamp_t timestamp) {
     free(event);
 }
 
-/*
- * Raises the given window (typically client->frame) above all other windows
- *
- */
-void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) {
-    uint32_t values[] = {XCB_STACK_MODE_ABOVE};
-    xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values);
-}
-
 /*
  * Configures the given window to have the size/position specified by given rect
  *
@@ -201,18 +175,6 @@ bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom) {
     return false;
 }
 
-/**
- * Moves the mouse pointer into the middle of rect.
- *
- */
-void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect) {
-    int mid_x = rect->x + (rect->width / 2);
-    int mid_y = rect->y + (rect->height / 2);
-
-    LOG("warp pointer to: %d %d\n", mid_x, mid_y);
-    xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y);
-}
-
 /*
  * Set the cursor of the root window to the given cursor id.
  * This function should only be used if xcursor_supported == false.
index 3c2a26f97255c09161e13c03f48ee4658aaa8b0e..0b1f305592a14e806bb5ae4920a1404ed05761f5 100755 (executable)
@@ -8,7 +8,6 @@ WriteMakefile(
     MIN_PERL_VERSION => '5.010000', # 5.10.0
     PREREQ_PM => {
         'AnyEvent'     => 0,
-        'AnyEvent::I3' => '0.16',
         'X11::XCB'     => '0.12',
         'Inline'       => 0,
         'Inline::C'    => 0,
index d872bda11d870cd1b66596bcffeb089d9d4eecc6..ba192469e6867fa8ebda0c8b7c5a6b6733776a9e 100755 (executable)
@@ -18,7 +18,7 @@ use Time::HiRes qw(time);
 use IO::Handle;
 
 # these are shipped with the testsuite
-use lib qw(@abs_top_builddir@/testcases/lib @abs_top_srcdir@/testcases/lib);
+use lib qw(@abs_top_builddir@/testcases/lib @abs_top_srcdir@/testcases/lib @abs_top_srcdir@/AnyEvent-I3/blib/lib);
 use i3test::Util qw(slurp);
 use StartXServer;
 use StatusLine;
@@ -87,6 +87,17 @@ foreach my $binary (@binaries) {
     die "$binary is not an executable" unless -x $binary;
 }
 
+my @test_binaries = qw(
+                        @abs_top_builddir@/test.commands_parser
+                        @abs_top_builddir@/test.config_parser
+                        @abs_top_builddir@/test.inject_randr15
+                    );
+
+foreach my $binary (@test_binaries) {
+    die "$binary executable not found, did you run “make check”?" unless -e $binary;
+    die "$binary is not an executable" unless -x $binary;
+}
+
 $ENV{PATH} = join(':',
     '@abs_top_builddir@/i3-nagbar',
     '@abs_top_builddir@/i3-msg',
diff --git a/testcases/inject_randr1.5.c b/testcases/inject_randr1.5.c
new file mode 100644 (file)
index 0000000..5796ef0
--- /dev/null
@@ -0,0 +1,440 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ * inject_randr1.5.c: An X11 proxy which interprets RandR 1.5 GetMonitors
+ * requests and overwrites their reply with a custom reply.
+ *
+ * This tool can be refactored as necessary in order to perform the same
+ * purpose for other request types. The RandR 1.5 specific portions of the code
+ * have been marked as such to make such a refactoring easier.
+ *
+ */
+#include "all.h"
+
+#include <ev.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <libgen.h>
+
+static void uds_connection_cb(EV_P_ ev_io *w, int revents);
+static void read_client_setup_request_cb(EV_P_ ev_io *w, int revents);
+static void read_server_setup_reply_cb(EV_P_ ev_io *w, int revents);
+static void read_client_x11_packet_cb(EV_P_ ev_io *w, int revents);
+static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents);
+
+static char *sun_path = NULL;
+
+void cleanup_socket(void) {
+    if (sun_path != NULL) {
+        unlink(sun_path);
+        free(sun_path);
+        sun_path = NULL;
+    }
+}
+
+/* BEGIN RandR 1.5 specific */
+static void *injected_reply = NULL;
+static off_t injected_reply_len = 0;
+/* END RandR 1.5 specific */
+
+#define XCB_PAD(i) (-(i)&3)
+
+struct connstate {
+    /* clientw is a libev watcher for the connection which we accept()ed. */
+    ev_io *clientw;
+
+    /* serverw is a libev watcher for the connection to X11 which we initiated
+        * on behalf of the client. */
+    ev_io *serverw;
+
+    /* sequence is the client-side sequence number counter. In X11’s wire
+     * encoding, sequence counters are not included in requests, only in
+     * replies. */
+    int sequence;
+
+    /* BEGIN RandR 1.5 specific */
+    /* sequence number of the most recent GetExtension request for RANDR */
+    int getext_randr;
+    /* sequence number of the most recent RRGetMonitors request */
+    int getmonitors;
+
+    int randr_major_opcode;
+    /* END RandR 1.5 specific */
+};
+
+/*
+ * Returns 0 on EOF
+ * Returns -1 on error (with errno from read() untouched)
+ *
+ */
+static size_t readall_into(void *buffer, const size_t len, int fd) {
+    size_t read_bytes = 0;
+    while (read_bytes < len) {
+        ssize_t n = read(fd, buffer + read_bytes, len - read_bytes);
+        if (n <= 0) {
+            return n;
+        }
+        read_bytes += (size_t)n;
+    }
+    return read_bytes;
+}
+
+/*
+ * Exits the program with an error if the read failed.
+ *
+ */
+static void must_read(int n) {
+    if (n == -1) {
+        err(EXIT_FAILURE, "read()");
+    }
+    if (n == 0) {
+        errx(EXIT_FAILURE, "EOF");
+    }
+}
+
+/*
+ * Exits the program with an error if the write failed.
+ *
+ */
+static void must_write(int n) {
+    if (n == -1) {
+        err(EXIT_FAILURE, "write()");
+    }
+}
+
+static void uds_connection_cb(EV_P_ ev_io *w, int revents) {
+    struct sockaddr_un addr;
+    socklen_t addrlen = sizeof(addr);
+    const int clientfd = accept(w->fd, (struct sockaddr *)&addr, &addrlen);
+    if (clientfd == -1) {
+        if (errno == EINTR) {
+            return;
+        }
+        err(EXIT_FAILURE, "accept()");
+    }
+
+    struct connstate *connstate = scalloc(1, sizeof(struct connstate));
+
+    ev_io *clientw = scalloc(1, sizeof(ev_io));
+    connstate->clientw = clientw;
+    clientw->data = connstate;
+    ev_io_init(clientw, read_client_setup_request_cb, clientfd, EV_READ);
+    ev_io_start(EV_A_ clientw);
+}
+
+// https://www.x.org/releases/current/doc/xproto/x11protocol.html#Encoding::Connection_Setup
+static void read_client_setup_request_cb(EV_P_ ev_io *w, int revents) {
+    ev_io_stop(EV_A_ w);
+    struct connstate *connstate = (struct connstate *)w->data;
+
+    /* Read X11 setup request in its entirety. */
+    xcb_setup_request_t setup_request;
+    must_read(readall_into(&setup_request, sizeof(setup_request), w->fd));
+
+    /* Establish a connection to X11. */
+    int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+    if (fd == -1) {
+        err(EXIT_FAILURE, "socket()");
+    }
+
+    char *host;
+    int displayp;
+    if (xcb_parse_display(getenv("DISPLAY"), &host, &displayp, NULL) == 0) {
+        errx(EXIT_FAILURE, "Could not parse DISPLAY=%s", getenv("DISPLAY"));
+    }
+    free(host);
+
+    struct sockaddr_un addr;
+    memset(&addr, 0, sizeof(addr));
+    addr.sun_family = AF_LOCAL;
+    snprintf(addr.sun_path, sizeof(addr.sun_path), "/tmp/.X11-unix/X%d", displayp);
+    if (connect(fd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
+        err(EXIT_FAILURE, "connect(%s)", addr.sun_path);
+    }
+
+    /* Relay setup request. */
+    must_write(writeall(fd, &setup_request, sizeof(setup_request)));
+
+    if (setup_request.authorization_protocol_name_len > 0 ||
+        setup_request.authorization_protocol_data_len > 0) {
+        const size_t authlen = setup_request.authorization_protocol_name_len +
+                               XCB_PAD(setup_request.authorization_protocol_name_len) +
+                               setup_request.authorization_protocol_data_len +
+                               XCB_PAD(setup_request.authorization_protocol_data_len);
+        void *buf = smalloc(authlen);
+        must_read(readall_into(buf, authlen, w->fd));
+        must_write(writeall(fd, buf, authlen));
+        free(buf);
+    }
+
+    /* Wait for a response from the X11 server. */
+    ev_io *serverw = scalloc(1, sizeof(ev_io));
+    connstate->serverw = serverw;
+    serverw->data = connstate;
+    ev_io_init(serverw, read_server_setup_reply_cb, fd, EV_READ);
+    ev_io_start(EV_A_ serverw);
+}
+
+static void read_server_setup_reply_cb(EV_P_ ev_io *w, int revents) {
+    struct connstate *connstate = (struct connstate *)w->data;
+    xcb_setup_failed_t setup_failed;
+    must_read(readall_into(&setup_failed, sizeof(setup_failed), w->fd));
+
+    switch (setup_failed.status) {
+        case 0:
+            errx(EXIT_FAILURE, "error authenticating at the X11 server");
+
+        case 2:
+            errx(EXIT_FAILURE, "two-factor auth not implemented");
+
+        case 1:
+            must_write(writeall(connstate->clientw->fd, &setup_failed, sizeof(xcb_setup_failed_t)));
+            const size_t len = (setup_failed.length * 4);
+            void *buf = smalloc(len);
+            must_read(readall_into(buf, len, w->fd));
+            must_write(writeall(connstate->clientw->fd, buf, len));
+            free(buf);
+
+            ev_set_cb(connstate->clientw, read_client_x11_packet_cb);
+            ev_set_cb(connstate->serverw, read_server_x11_packet_cb);
+            ev_io_start(EV_A_ connstate->clientw);
+            break;
+
+        default:
+            errx(EXIT_FAILURE, "X11 protocol error: expected setup_failed.status in [0..2], got %d", setup_failed.status);
+    }
+}
+
+// https://www.x.org/releases/current/doc/xproto/x11protocol.html#request_format
+typedef struct {
+    uint8_t opcode;
+    uint8_t pad0;
+    uint16_t length;
+} generic_x11_request_t;
+
+// https://www.x.org/releases/current/doc/xproto/x11protocol.html#reply_format
+typedef struct {
+    uint8_t code; /* if 1, this is a reply. if 0, this is an error. else, an event */
+    uint8_t pad0;
+    uint16_t sequence;
+    uint32_t length;
+} generic_x11_reply_t;
+
+static void read_client_x11_packet_cb(EV_P_ ev_io *w, int revents) {
+    struct connstate *connstate = (struct connstate *)w->data;
+
+    void *request = smalloc(sizeof(generic_x11_request_t));
+    must_read(readall_into(request, sizeof(generic_x11_request_t), connstate->clientw->fd));
+    const size_t len = (((generic_x11_request_t *)request)->length * 4);
+    if (len > sizeof(generic_x11_request_t)) {
+        request = srealloc(request, len);
+        must_read(readall_into(request + sizeof(generic_x11_request_t),
+                               len - sizeof(generic_x11_request_t),
+                               connstate->clientw->fd));
+    }
+
+    // XXX: sequence counter wrapping is not implemented, but should not be
+    // necessary given that this tool is scoped for test cases.
+    connstate->sequence++;
+
+    /* BEGIN RandR 1.5 specific */
+    const uint8_t opcode = ((generic_x11_request_t *)request)->opcode;
+    if (opcode == XCB_QUERY_EXTENSION) {
+        xcb_query_extension_request_t *req = request;
+        const char *name = request + sizeof(xcb_query_extension_request_t);
+        if (req->name_len == strlen("RANDR") &&
+            strncmp(name, "RANDR", strlen("RANDR")) == 0) {
+            connstate->getext_randr = connstate->sequence;
+        }
+    } else if (opcode == connstate->randr_major_opcode) {
+        const uint8_t randr_opcode = ((generic_x11_request_t *)request)->pad0;
+        if (randr_opcode == XCB_RANDR_GET_MONITORS) {
+            connstate->getmonitors = connstate->sequence;
+        }
+    }
+    /* END RandR 1.5 specific */
+
+    must_write(writeall(connstate->serverw->fd, request, len));
+    free(request);
+}
+
+static void read_server_x11_packet_cb(EV_P_ ev_io *w, int revents) {
+    struct connstate *connstate = (struct connstate *)w->data;
+    // all packets from the server are at least 32 bytes in length
+    size_t len = 32;
+    void *packet = smalloc(len);
+    must_read(readall_into(packet, len, connstate->serverw->fd));
+    switch (((generic_x11_reply_t *)packet)->code) {
+        case 0:  // error
+            break;
+
+        case 1:  // reply
+            len += ((generic_x11_reply_t *)packet)->length * 4;
+            if (len > 32) {
+                packet = srealloc(packet, len);
+                must_read(readall_into(packet + 32, len - 32, connstate->serverw->fd));
+            }
+
+            /* BEGIN RandR 1.5 specific */
+            const uint16_t sequence = ((generic_x11_reply_t *)packet)->sequence;
+
+            if (sequence == connstate->getext_randr) {
+                xcb_query_extension_reply_t *reply = packet;
+                connstate->randr_major_opcode = reply->major_opcode;
+            }
+
+            if (sequence == connstate->getmonitors) {
+                printf("RRGetMonitors reply!\n");
+                if (injected_reply != NULL) {
+                    printf("injecting reply\n");
+                    ((generic_x11_reply_t *)injected_reply)->sequence = sequence;
+                    must_write(writeall(connstate->clientw->fd, injected_reply, injected_reply_len));
+                    free(packet);
+                    return;
+                }
+            }
+            /* END RandR 1.5 specific */
+
+            break;
+
+        default:  // event
+            break;
+    }
+    must_write(writeall(connstate->clientw->fd, packet, len));
+    free(packet);
+}
+
+static void child_cb(EV_P_ ev_child *w, int revents) {
+    ev_child_stop(EV_A_ w);
+    if (WIFEXITED(w->rstatus)) {
+        exit(WEXITSTATUS(w->rstatus));
+    } else {
+        exit(WTERMSIG(w->rstatus) + 128);
+    }
+}
+
+static void must_read_reply(const char *filename) {
+    FILE *f;
+    if ((f = fopen(filename, "r")) == NULL) {
+        err(EXIT_FAILURE, "fopen(%s)", filename);
+    }
+    struct stat stbuf;
+    if (fstat(fileno(f), &stbuf) != 0) {
+        err(EXIT_FAILURE, "fstat(%s)", filename);
+    }
+    /* BEGIN RandR 1.5 specific */
+    injected_reply_len = stbuf.st_size;
+    injected_reply = smalloc(stbuf.st_size);
+    int n = fread(injected_reply, 1, stbuf.st_size, f);
+    /* END RandR 1.5 specific */
+    if (n != stbuf.st_size) {
+        err(EXIT_FAILURE, "fread(%s)", filename);
+    }
+    fclose(f);
+}
+
+int main(int argc, char *argv[]) {
+    static struct option long_options[] = {
+        {"getmonitors_reply", required_argument, 0, 0},
+        {0, 0, 0, 0},
+    };
+    char *options_string = "";
+    int opt;
+    int option_index = 0;
+
+    while ((opt = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
+        switch (opt) {
+            case 0:
+                if (strcmp(long_options[option_index].name, "getmonitors_reply") == 0) {
+                    must_read_reply(optarg);
+                }
+                break;
+            default:
+                exit(EXIT_FAILURE);
+        }
+    }
+
+    if (optind >= argc) {
+        errx(EXIT_FAILURE, "syntax: %s [options] <command>\n", argv[0]);
+    }
+
+    int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+    if (fd == -1) {
+        err(EXIT_FAILURE, "socket(AF_UNIX)");
+    }
+
+    if (fcntl(fd, F_SETFD, FD_CLOEXEC)) {
+        warn("Could not set FD_CLOEXEC");
+    }
+
+    struct sockaddr_un addr;
+    memset(&addr, 0, sizeof(struct sockaddr_un));
+    addr.sun_family = AF_UNIX;
+    int i;
+    bool bound = false;
+    for (i = 0; i < 100; i++) {
+        /* XXX: The path to X11 sockets differs on some platforms (e.g. Trusted
+         * Solaris, HPUX), but since libxcb doesn’t provide a function to
+         * generate the path, we’ll just have to hard-code it for now. */
+        snprintf(addr.sun_path, sizeof(addr.sun_path), "/tmp/.X11-unix/X%d", i);
+
+        if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
+            warn("bind(%s)", addr.sun_path);
+        } else {
+            bound = true;
+            /* Let the user know bind() was successful, so that they know the
+             * error messages can be disregarded. */
+            fprintf(stderr, "Successfuly bound to %s\n", addr.sun_path);
+            sun_path = sstrdup(addr.sun_path);
+            break;
+        }
+    }
+
+    if (!bound) {
+        err(EXIT_FAILURE, "bind()");
+    }
+
+    atexit(cleanup_socket);
+
+    /* This program will be started for each testcase which requires it, so we
+     * expect precisely one connection. */
+    if (listen(fd, 1) == -1) {
+        err(EXIT_FAILURE, "listen()");
+    }
+
+    pid_t child = fork();
+    if (child == -1) {
+        err(EXIT_FAILURE, "fork()");
+    }
+    if (child == 0) {
+        char *display;
+        sasprintf(&display, ":%d", i);
+        setenv("DISPLAY", display, 1);
+        free(display);
+
+        char **child_args = argv + optind;
+        execvp(child_args[0], child_args);
+        err(EXIT_FAILURE, "exec()");
+    }
+
+    struct ev_loop *loop = ev_default_loop(0);
+
+    ev_child cw;
+    ev_child_init(&cw, child_cb, child, 0);
+    ev_child_start(loop, &cw);
+
+    ev_io watcher;
+    ev_io_init(&watcher, uds_connection_cb, fd, EV_READ);
+    ev_io_start(loop, &watcher);
+
+    ev_run(loop, 0);
+}
index 53dbb3b6ec5c7a64e7fdab5abe585c18d74c2e12..0f307eb3366abc04c230f068ee050fd9347d6a70 100644 (file)
@@ -88,7 +88,10 @@ sub activate_i3 {
         # the interactive signalhandler to make it crash immediately instead.
         # Also disable logging to SHM since we redirect the logs anyways.
         # Force Xinerama because we use Xdmx for multi-monitor tests.
-        my $i3cmd = q|i3 --shmlog-size=0 --disable-signalhandler --force-xinerama|;
+        my $i3cmd = q|i3 --shmlog-size=0 --disable-signalhandler|;
+        if (!defined($args{inject_randr15})) {
+            $i3cmd .= q| --force-xinerama|;
+        }
         if (!$args{validate_config}) {
             # We only set logging if i3 is actually started, but not if we only
             # validate the config file. This is to keep logging to a minimum as
@@ -139,6 +142,13 @@ sub activate_i3 {
                      'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"';
         }
 
+        if ($args{inject_randr15}) {
+            # See comment in $args{strace} branch.
+            $cmd = 'test.inject_randr15 --getmonitors_reply="' .
+                   $args{inject_randr15} . '" -- ' .
+                   'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"';
+        }
+
         # We need to use the shell due to using output redirections.
         exec '/bin/sh', '-c', $cmd;
 
index f9f6e821532ef07e8e25b658e6d469242aefa317..18bebb52146ed0dba8e2471d565c1a1118f9520f 100644 (file)
@@ -7,6 +7,7 @@ use Test::Builder;
 use X11::XCB::Rect;
 use X11::XCB::Window;
 use X11::XCB qw(:all);
+use lib qw(@abs_top_srcdir@/AnyEvent-I3/blib/lib);
 use AnyEvent::I3;
 use List::Util qw(first);
 use Time::HiRes qw(sleep);
@@ -861,6 +862,7 @@ sub launch_with_config {
         cv => $cv,
         dont_create_temp_dir => $args{dont_create_temp_dir},
         validate_config => $args{validate_config},
+        inject_randr15 => $args{inject_randr15},
     );
 
     # If we called i3 with -C, we wait for it to exit and then return as
index 92adde42f0683af3d653132f2ae9a98af3330f71..3937b70acd65c934f3ff46f616199b9170f7dc2d 100644 (file)
@@ -6,6 +6,7 @@ use warnings;
 use v5.10;
 
 use i3test i3_autostart => 0;
+use lib qw(@abs_top_srcdir@/AnyEvent-I3/blib/lib);
 use AnyEvent::I3;
 use ExtUtils::PkgConfig;
 
index ab93233a23654f368c96d21a2bed6acdc119277e..e0408338c2d1dd974d48ade772e1c2742c34cce0 100644 (file)
@@ -8,7 +8,6 @@ BEGIN {
         X11::XCB::Connection
         X11::XCB::Window
         AnyEvent
-        AnyEvent::I3
         IPC::Run
         ExtUtils::PkgConfig
         Inline
index 01d51cc0e3c2c648a0072baaafe35509c3daf5e3..40b2cbb692d0ba99cd80f535345d889231c8a870 100644 (file)
@@ -279,6 +279,12 @@ is(focused_ws(), 'bla', 'now on workspace bla');
 cmd 'rename workspace to to';
 ok(!workspace_exists('bla'), 'workspace bla does not exist anymore');
 is(focused_ws(), 'to', 'now on workspace to');
+cmd 'rename workspace to bla';
+ok(!workspace_exists('to'), 'workspace to does not exist anymore');
+is(focused_ws(), 'bla', 'now on workspace bla');
+cmd 'rename workspace to tosomething';
+ok(!workspace_exists('bla'), 'workspace bla does not exist anymore');
+is(focused_ws(), 'tosomething', 'now on workspace tosomething');
 
 # 6: already existing workspace
 my $result = cmd 'rename workspace qux to 11: bar';
index 44ad9bbf97c2dfddb49e4fc2e3929098140d9aaa..64e33bc3d74659c15533c9276c2579359208bd7d 100644 (file)
@@ -298,11 +298,11 @@ sub get_floating_rect {
 # focus is on the right window, so we resize the left one using criteria
 my $leftold = get_floating_rect($left->id);
 my $rightold = get_floating_rect($right->id);
-cmd '[id="' . $left->id . '"] resize shrink height 10px or 10ppt';
+cmd '[id="' . $left->id . '"] resize grow height 10px or 10ppt';
 
 my $leftnew = get_floating_rect($left->id);
 my $rightnew = get_floating_rect($right->id);
 is($rightnew->{height}, $rightold->{height}, 'height of right container unchanged');
-is($leftnew->{height}, $leftold->{height} - 10, 'height of left container changed');
+is($leftnew->{height}, $leftold->{height} + 10, 'height of left container changed');
 
 done_testing;
index 033a31f2665e025ea86188b275c8c832707bcd5a..1ed0999031fb9db2fa20980ad17f243ff84ba17c 100644 (file)
@@ -145,6 +145,237 @@ is($x->input_focus, $second->id, 'second window focused');
 ok(@content == 1, 'one con at workspace level');
 is($content[0]->{layout}, 'stacked', 'layout stacked');
 
+#####################################################################
+# 8: when the workspace is empty check that its layout can be changed
+# from stacked to horizontal split using the 'layout splith' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout stacked';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'layout splith';
+$first = open_window;
+$second = open_window;
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'stacked', 'layout not stacked');
+isnt($content[1]->{layout}, 'stacked', 'layout not stacked');
+
+#####################################################################
+# 9: when the workspace is empty check that its layout can be changed
+# from stacked to vertical split using the 'layout splitv' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout stacked';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'layout splitv';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'stacked', 'layout not stacked');
+isnt($content[1]->{layout}, 'stacked', 'layout not stacked');
+
+#####################################################################
+# 10: when the workspace is empty check that its layout can be changed
+# from tabbed to horizontal split using the 'layout splith' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout tabbed';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'tabbed', 'layout tabbed');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'layout splith';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed');
+isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed');
+
+#####################################################################
+# 11: when the workspace is empty check that its layout can be changed
+# from tabbed to vertical split using the 'layout splitv' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout tabbed';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'tabbed', 'layout tabbed');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'layout splitv';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed');
+isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed');
+
+#####################################################################
+# 12: when the workspace is empty check that its layout can be changed
+# from stacked to horizontal split using the 'split horizontal' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout stacked';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'split horizontal';
+$first = open_window;
+$second = open_window;
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'stacked', 'layout not stacked');
+isnt($content[1]->{layout}, 'stacked', 'layout not stacked');
+
+#####################################################################
+# 13: when the workspace is empty check that its layout can be changed
+# from stacked to vertical split using the 'split vertical' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout stacked';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'stacked', 'layout stacked');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'split vertical';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'stacked', 'layout not stacked');
+isnt($content[1]->{layout}, 'stacked', 'layout not stacked');
+
+#####################################################################
+# 14: when the workspace is empty check that its layout can be changed
+# from tabbed to horizontal split using the 'split horizontal' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout tabbed';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'tabbed', 'layout tabbed');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'split horizontal';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed');
+isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed');
+
+#####################################################################
+# 15: when the workspace is empty check that its layout can be changed
+# from tabbed to vertical split using the 'split vertical' command.
+#####################################################################
+
+$tmp = fresh_workspace;
+
+cmd 'layout tabbed';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+is($content[0]->{layout}, 'tabbed', 'layout tabbed');
+
+cmd '[id="' . $first->id . '"] kill';
+cmd '[id="' . $second->id . '"] kill';
+sync_with_i3;
+
+ok(@{get_ws_content($tmp)} == 0, 'workspace is empty');
+
+cmd 'split vertical';
+$first = open_window;
+$second = open_window;
+
+@content = @{get_ws_content($tmp)};
+ok(@content == 2, 'two containers opened');
+isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed');
+isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed');
+
+
 exit_gracefully($pid);
 
 done_testing;
index 5bd21128348f463277628786d89272229a02f152..d855e8185cebde349d788946f39777109376f48d 100644 (file)
@@ -153,7 +153,7 @@ ok(line_exists($output, qr|^bindsym Mod1\+s restart$|), 'restart unchanged');
 ok(line_exists($output, qr|^bindsym Mod1\+s reload$|), 'reload unchanged');
 ok(line_exists($output, qr|^bindsym Mod1\+s exit$|), 'exit unchanged');
 ok(line_exists($output, qr|^bindcode Mod1\+c exec /usr/bin/urxvt$|), 'bind changed to bindcode');
-ok(line_exists($output, qr|^mode "asdf" {$|), 'mode asdf unchanged');
+ok(line_exists($output, qr|^mode "asdf" \{$|), 'mode asdf unchanged');
 ok(line_exists($output, qr|^bindcode 36 mode \"default\"$|), 'mode default unchanged');
 ok(line_exists($output, qr|^}$|), 'closing mode bracket still there');
 
@@ -336,13 +336,13 @@ ok(line_exists($output, qr|^bindsym Mod1\+3 move container to workspace work|),
 #####################################################################
 
 $output = migrate_config('');
-ok(line_exists($output, qr|bar {|), 'i3bar added');
+ok(line_exists($output, qr|bar \{|), 'i3bar added');
 
 $output = migrate_config('workspace_bar enable');
-ok(line_exists($output, qr|bar {|), 'i3bar added');
+ok(line_exists($output, qr|bar \{|), 'i3bar added');
 
 $output = migrate_config('workspace_bar no');
-ok(!line_exists($output, qr|bar {|), 'no i3bar added');
+ok(!line_exists($output, qr|bar \{|), 'no i3bar added');
 
 #####################################################################
 # check whether the mode command gets quotes
index dc5b67ed127a73b08212d77dfc234d9f39eeace1..5d3a0ff5660ff776007694bd72309235ccc9de12 100644 (file)
@@ -169,6 +169,7 @@ is(parser_calls('unknown_literal'),
        rename
        nop
        scratchpad
+       swap
        title_format
        mode
        bar
index 6fd6eae8e89e7151cf3078894aa9dcec0b6e40f8..c54f27a2b2d5f02cb6ae86899e960dd4864625d4 100644 (file)
@@ -95,4 +95,72 @@ cmd 'layout toggle all';
 ($nodes, $focus) = get_ws_content($tmp);
 is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
 
+cmd 'layout toggle splith splitv';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+cmd 'layout toggle splith splitv';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle stacked splitv tabbed';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle stacking splitv tabbed';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle stacking splitv tabbed';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle splitv i stacking tabbed';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle stacked';
+($nodes, $focus) = get_ws_content($tmp);
+# this is correct if it does nothing
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle tabbed stacked';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+# obsoletes 'split' ;)
+cmd 'layout toggle splith splitv';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+# nonsense but works expectedly
+cmd 'layout toggle split split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle split split';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splith', 'layout now splith');
+
+# testing with arbitrary length and garbage
+cmd 'layout toggle stacking splith tabbed splitv stacking';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
+cmd 'layout toggle stacking splith garbage tabbed splitv stacking';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle stacking splith garbage tabbed splitv stacking';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'stacked', 'layout now stacked');
+
+cmd 'layout toggle splitv splith garbage splitv tabbed stacking splitv';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'splitv', 'layout now splitv');
+
+cmd 'layout toggle splitv garbage    tabbed';
+($nodes, $focus) = get_ws_content($tmp);
+is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed');
+
 done_testing;
index 6cd84b6f1dfc39d918048accb8ce06b65afa0d7e..fb3130d5135e344bba0d4cc1045539268e1ba193 100644 (file)
@@ -49,18 +49,22 @@ mode "meh" {
     bindsym --release --whole-window button3 nop
     bindsym --border button3 nop
     bindsym --release --border button3 nop
+    bindsym --exclude-titlebar button3 nop
+    bindsym --whole-window --border --exclude-titlebar button3 nop
 }
 EOT
 
 my $expected = <<'EOT';
 cfg_enter_mode((null), meh)
-cfg_mode_binding(bindsym, Mod1,Shift, x, (null), (null), (null), resize grow)
-cfg_mode_binding(bindcode, Mod1, 44, (null), (null), (null), resize shrink)
-cfg_mode_binding(bindsym, Mod1, x, --release, (null), (null), exec foo)
-cfg_mode_binding(bindsym, (null), button3, (null), (null), --whole-window, nop)
-cfg_mode_binding(bindsym, (null), button3, --release, (null), --whole-window, nop)
-cfg_mode_binding(bindsym, (null), button3, (null), --border, (null), nop)
-cfg_mode_binding(bindsym, (null), button3, --release, --border, (null), nop)
+cfg_mode_binding(bindsym, Mod1,Shift, x, (null), (null), (null), (null), resize grow)
+cfg_mode_binding(bindcode, Mod1, 44, (null), (null), (null), (null), resize shrink)
+cfg_mode_binding(bindsym, Mod1, x, --release, (null), (null), (null), exec foo)
+cfg_mode_binding(bindsym, (null), button3, (null), (null), --whole-window, (null), nop)
+cfg_mode_binding(bindsym, (null), button3, --release, (null), --whole-window, (null), nop)
+cfg_mode_binding(bindsym, (null), button3, (null), --border, (null), (null), nop)
+cfg_mode_binding(bindsym, (null), button3, --release, --border, (null), (null), nop)
+cfg_mode_binding(bindsym, (null), button3, (null), (null), (null), --exclude-titlebar, nop)
+cfg_mode_binding(bindsym, (null), button3, (null), --border, --whole-window, --exclude-titlebar, nop)
 EOT
 
 is(parser_calls($config),
@@ -442,8 +446,7 @@ hide_edge_border both
 client.focused          #4c7899 #285577 #ffffff #2e9ef4
 EOT
 
-my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '#', '" . join("', '", qw(
-        set
+my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '#', '" . join("', '", 'set ', 'set     ', qw(
         set_from_resource
         bindsym
         bindcode
@@ -467,6 +470,8 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
         force_focus_wrapping
         force_xinerama
         force-xinerama
+        disable_randr15
+        disable-randr15
         workspace_auto_back_and_forth
         fake_outputs
         fake-outputs
@@ -672,7 +677,7 @@ EOT
 
 $expected = <<'EOT';
 cfg_enter_mode((null), yo)
-cfg_mode_binding(bindsym, (null), x, (null), (null), (null), resize shrink left)
+cfg_mode_binding(bindsym, (null), x, (null), (null), (null), (null), resize shrink left)
 ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'bindsym', 'bindcode', 'bind', '}'
 ERROR: CONFIG: (in file <stdin>)
 ERROR: CONFIG: Line   1: mode "yo" {
index 01c73a75505a10eb81dd8dfc36376a0e9e48c096..ae0f05c3cdfd96142bd7bd198f17e541baf0d0ab 100644 (file)
@@ -62,10 +62,10 @@ sub open_with_fixed_size {
 
     my $flags = $XCB_ICCCM_SIZE_HINT_P_MIN_SIZE | $XCB_ICCCM_SIZE_HINT_P_MAX_SIZE;
 
-    my $min_width = 55;
-    my $max_width = 55;
-    my $min_height = 77;
-    my $max_height = 77;
+    my $min_width = 150;
+    my $max_width = 150;
+    my $min_height = 100;
+    my $max_height = 100;
 
     my $pad = 0x00;
 
@@ -81,7 +81,7 @@ sub open_with_fixed_size {
                 $atomname->id,
                 $atomtype->id,
                 32,
-                12,
+                13,
                 pack('C5N8', $flags, $pad, $pad, $pad, $pad, 0, 0, 0, $min_width, $min_height, $max_width, $max_height),
             );
         },
@@ -114,6 +114,8 @@ $window->unmap;
 
 $window = open_with_fixed_size;
 is(get_ws($ws)->{floating_nodes}[0]->{nodes}[0]->{window}, $window->id, 'Fixed size window opened floating');
+is(get_ws($ws)->{floating_nodes}[0]->{nodes}[0]->{window_rect}->{width}, 150, 'Fixed size window opened with minimum width');
+is(get_ws($ws)->{floating_nodes}[0]->{nodes}[0]->{window_rect}->{height}, 100, 'Fixed size window opened with minimum height');
 $window->unmap;
 
 done_testing;
diff --git a/testcases/t/264-ipc-shutdown-event.t b/testcases/t/264-ipc-shutdown-event.t
new file mode 100644 (file)
index 0000000..379b9bf
--- /dev/null
@@ -0,0 +1,71 @@
+#!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)
+#
+# Test the ipc shutdown event. This event is triggered when the connection to
+# the ipc is about to shutdown because of a user action such as with a
+# `restart` or `exit` command. The `change` field indicates why the ipc is
+# shutting down. It can be either "restart" or "exit".
+#
+# Ticket: #2318
+# Bug still in: 4.12-46-g2123888
+use i3test;
+
+SKIP: {
+    skip "AnyEvent::I3 too old (need >= 0.17)", 1 if $AnyEvent::I3::VERSION < 0.17;
+
+my $i3 = i3(get_socket_path());
+$i3->connect->recv;
+
+my $cv = AE::cv;
+my $timer = AE::timer 0.5, 0, sub { $cv->send(0); };
+
+$i3->subscribe({
+        shutdown => sub {
+            $cv->send(shift);
+        }
+    })->recv;
+
+cmd 'restart';
+
+my $e = $cv->recv;
+
+diag "Event:\n", Dumper($e);
+ok($e, 'the shutdown event should emit when the ipc is restarted by command');
+is($e->{change}, 'restart', 'the `change` field should tell the reason for the shutdown');
+
+# restarting kills the ipc client so we have to make a new one
+$i3 = i3(get_socket_path());
+$i3->connect->recv;
+
+$cv = AE::cv;
+$timer = AE::timer 0.5, 0, sub { $cv->send(0); };
+
+$i3->subscribe({
+        shutdown => sub {
+            $cv->send(shift);
+        }
+    })->recv;
+
+cmd 'exit';
+
+$e = $cv->recv;
+
+diag "Event:\n", Dumper($e);
+ok($e, 'the shutdown event should emit when the ipc is exited by command');
+is($e->{change}, 'exit', 'the `change` field should tell the reason for the shutdown');
+}
+
+done_testing;
index 45ec7e8897163e00a727f11fe21143ab6234a2d6..fcc39ead4f492cbc1693a5fe0513b9dbbde582fa 100644 (file)
@@ -44,6 +44,9 @@ bindsym Shift+Escape nop Shift+Escape
 
 # Binding which should work with numlock and without, see issue #2418.
 bindsym Mod1+Shift+q nop Mod1+Shift+q
+
+# Binding which should work with numlock and without, see issue #2559.
+bindcode 39 nop s
 EOT
 
 my $pid = launch_with_config($config);
@@ -159,7 +162,6 @@ is(listen_for_binding(
    'Mod1+Shift+q',
    'triggered the "Mod1+Shift+q" keybinding');
 
-
 is(listen_for_binding(
     sub {
         xtest_key_press(77); # enable Num_Lock
@@ -177,8 +179,101 @@ is(listen_for_binding(
    'Mod1+Shift+q',
    'triggered the "Mod1+Shift+q" keybinding');
 
+is(listen_for_binding(
+    sub {
+        xtest_key_press(39); # s
+        xtest_key_release(39); # s
+    },
+    ),
+   's',
+   'triggered the "s" keybinding without Num_Lock');
+
+is(listen_for_binding(
+    sub {
+        xtest_key_press(77); # enable Num_Lock
+        xtest_key_release(77); # enable Num_Lock
+        xtest_key_press(39); # s
+        xtest_key_release(39); # s
+        xtest_key_press(77); # disable Num_Lock
+        xtest_key_release(77); # disable Num_Lock
+    },
+    ),
+   's',
+   'triggered the "s" keybinding with Num_Lock');
+
 sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 10, 'Received exactly 10 binding events');
+is(scalar @i3test::XTEST::binding_events, 12, 'Received exactly 12 binding events');
+
+exit_gracefully($pid);
+
+################################################################################
+# Verify bindings for modifiers work
+################################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bindsym Mod4+Return nop Return
+
+# Binding which should work with numlock and without, see issue #2559.
+bindcode --release 133 nop Super_L
+EOT
+
+$pid = launch_with_config($config);
+
+start_binding_capture;
+
+is(listen_for_binding(
+    sub {
+        xtest_key_press(133); # Super_L
+        xtest_key_release(133); # Super_L
+    },
+    ),
+   'Super_L',
+   'triggered the "Super_L" keybinding without Num_Lock');
+
+is(listen_for_binding(
+    sub {
+        xtest_key_press(77); # enable Num_Lock
+        xtest_key_release(77); # enable Num_Lock
+        xtest_key_press(133); # Super_L
+        xtest_key_release(133); # Super_L
+        xtest_key_press(77); # disable Num_Lock
+        xtest_key_release(77); # disable Num_Lock
+    },
+    ),
+   'Super_L',
+   'triggered the "Super_L" keybinding with Num_Lock');
+
+is(listen_for_binding(
+    sub {
+        xtest_key_press(133); # Super_L
+        xtest_key_press(36); # Return
+        xtest_key_release(36); # Return
+        xtest_key_release(133); # Super_L
+    },
+    ),
+   'Return',
+   'triggered the "Return" keybinding without Num_Lock');
+
+is(listen_for_binding(
+    sub {
+        xtest_key_press(77); # enable Num_Lock
+        xtest_key_release(77); # enable Num_Lock
+        xtest_key_press(133); # Super_L
+        xtest_key_press(36); # Return
+        xtest_key_release(36); # Return
+        xtest_key_release(133); # Super_L
+        xtest_key_press(77); # disable Num_Lock
+        xtest_key_release(77); # disable Num_Lock
+    },
+    ),
+   'Return',
+   'triggered the "Return" keybinding with Num_Lock');
+
+sync_with_i3;
+is(scalar @i3test::XTEST::binding_events, 16, 'Received exactly 16 binding events');
 
 exit_gracefully($pid);
 
@@ -191,6 +286,7 @@ $config = <<EOT;
 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 
 bindsym KP_End nop KP_End
+bindcode 88 nop KP_Down
 EOT
 
 $pid = launch_with_config($config);
@@ -206,6 +302,15 @@ is(listen_for_binding(
    'KP_End',
    'triggered the "KP_End" keybinding');
 
+is(listen_for_binding(
+    sub {
+        xtest_key_press(88); # KP_Down
+        xtest_key_release(88); # KP_Down
+    },
+    ),
+   'KP_Down',
+   'triggered the "KP_Down" keybinding');
+
 is(listen_for_binding(
     sub {
         xtest_key_press(77); # enable Num_Lock
@@ -219,10 +324,64 @@ is(listen_for_binding(
    'timeout',
    'Did not trigger the KP_End keybinding with KP_1');
 
+is(listen_for_binding(
+    sub {
+        xtest_key_press(77); # enable Num_Lock
+        xtest_key_release(77); # enable Num_Lock
+        xtest_key_press(88); # KP_2
+        xtest_key_release(88); # KP_2
+        xtest_key_press(77); # disable Num_Lock
+        xtest_key_release(77); # disable Num_Lock
+    },
+    ),
+   'timeout',
+   'Did not trigger the KP_Down keybinding with KP_2');
+
 # TODO: This test does not verify that i3 does _NOT_ grab keycode 87 with Mod2.
 
 sync_with_i3;
-is(scalar @i3test::XTEST::binding_events, 11, 'Received exactly 11 binding events');
+is(scalar @i3test::XTEST::binding_events, 18, 'Received exactly 18 binding events');
+
+exit_gracefully($pid);
+
+################################################################################
+# Verify mouse bindings are unaffected by NumLock
+################################################################################
+
+$config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+bindsym --whole-window button4 nop button4
+EOT
+
+$pid = launch_with_config($config);
+
+my $win = open_window;
+
+start_binding_capture;
+
+is(listen_for_binding(
+    sub {
+        xtest_key_press(77); # enable Num_Lock
+        xtest_key_release(77); # enable Num_Lock
+        xtest_button_press(4, 50, 50);
+        xtest_button_release(4, 50, 50);
+        xtest_key_press(77); # disable Num_Lock
+        xtest_key_release(77); # disable Num_Lock
+    },
+    ),
+   'button4',
+   'triggered the button4 keybinding with NumLock');
+
+is(listen_for_binding(
+    sub {
+       xtest_button_press(4, 50, 50);
+       xtest_button_release(4, 50, 50);
+    },
+    ),
+   'button4',
+   'triggered the button4 keybinding without NumLock');
 
 exit_gracefully($pid);
 
diff --git a/testcases/t/265-swap.t b/testcases/t/265-swap.t
new file mode 100644 (file)
index 0000000..f86bba7
--- /dev/null
@@ -0,0 +1,412 @@
+#!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)
+#
+# Tests the swap command.
+# Ticket: #917
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+for_window[class="mark_A"] mark A
+for_window[class="mark_B"] mark B
+EOT
+
+my ($pid);
+my ($ws, $ws1, $ws2, $ws3);
+my ($nodes, $expected_focus, $A, $B, $F);
+my ($result);
+my @urgent;
+
+###############################################################################
+# Swap two containers next to each other.
+# Focus should stay on B because both windows are on the focused workspace.
+# The focused container is B.
+#
+# +---+---+    Layout: H1[ A B ]
+# | A | B |    Focus Stacks:
+# +---+---+        H1: B, A
+###############################################################################
+$pid = launch_with_config($config);
+$ws = fresh_workspace;
+
+$A = open_window(wm_class => 'mark_A');
+$B = open_window(wm_class => 'mark_B');
+$expected_focus = get_focused($ws);
+
+cmd '[con_mark=B] swap container with mark A';
+
+$nodes = get_ws_content($ws);
+is($nodes->[0]->{window}, $B->{id}, 'B is on the left');
+is($nodes->[1]->{window}, $A->{id}, 'A is on the right');
+is(get_focused($ws), $expected_focus, 'B is still focused');
+
+exit_gracefully($pid);
+
+###############################################################################
+# Swap two containers with different parents.
+# In this test, the focus head of the left v-split container is A.
+# The focused container is B.
+#
+# +---+---+    Layout: H1[ V1[ A Y ] V2[ X B ] ]
+# | A | X |    Focus Stacks:
+# +---+---+        H1: V2, V1
+# | Y | B |        V1: A, Y
+# +---+---+        V2: B, X
+###############################################################################
+$pid = launch_with_config($config);
+$ws = fresh_workspace;
+
+$A = open_window(wm_class => 'mark_A');
+$B = open_window(wm_class => 'mark_B');
+cmd 'split v';
+open_window;
+cmd 'move up, focus left';
+cmd 'split v';
+open_window;
+cmd 'focus up, focus right, focus down';
+$expected_focus = get_focused($ws);
+
+cmd '[con_mark=B] swap container with mark A';
+
+$nodes = get_ws_content($ws);
+is($nodes->[0]->{nodes}->[0]->{window}, $B->{id}, 'B is on the top left');
+is($nodes->[1]->{nodes}->[1]->{window}, $A->{id}, 'A is on the bottom right');
+is(get_focused($ws), $expected_focus, 'B is still focused');
+
+exit_gracefully($pid);
+
+###############################################################################
+# Swap two containers with different parents.
+# In this test, the focus head of the left v-split container is _not_ A.
+# The focused container is B.
+#
+# +---+---+    Layout: H1[ V1[ A Y ] V2[ X B ] ]
+# | A | X |    Focus Stacks:
+# +---+---+        H1: V2, V1
+# | Y | B |        V1: Y, A
+# +---+---+        V2: B, X
+###############################################################################
+$pid = launch_with_config($config);
+$ws = fresh_workspace;
+
+$A = open_window(wm_class => 'mark_A');
+$B = open_window(wm_class => 'mark_B');
+cmd 'split v';
+open_window;
+cmd 'move up, focus left';
+cmd 'split v';
+open_window;
+cmd 'focus right, focus down';
+$expected_focus = get_focused($ws);
+
+cmd '[con_mark=B] swap container with mark A';
+
+$nodes = get_ws_content($ws);
+is($nodes->[0]->{nodes}->[0]->{window}, $B->{id}, 'B is on the top left');
+is($nodes->[1]->{nodes}->[1]->{window}, $A->{id}, 'A is on the bottom right');
+is(get_focused($ws), $expected_focus, 'B is still focused');
+
+exit_gracefully($pid);
+
+###############################################################################
+# Swap two containers with one being on a different workspace.
+# The focused container is B.
+#
+# Layout: O1[ W1[ H1 ] W2[ H2 ] ]
+# Focus Stacks:
+#     O1: W2, W1
+#
+# +---+---+    Layout: H1[ A X ]
+# | A | X |    Focus Stacks:
+# +---+---+        H1: A, X
+#
+# +---+---+    Layout: H2[ Y, B ]
+# | Y | B |    Focus Stacks:
+# +---+---+        H2: B, Y
+###############################################################################
+$pid = launch_with_config($config);
+
+$ws1 = fresh_workspace;
+$A = open_window(wm_class => 'mark_A');
+$expected_focus = get_focused($ws1);
+open_window;
+cmd 'focus left';
+
+$ws2 = fresh_workspace;
+open_window;
+$B = open_window(wm_class => 'mark_B');
+
+cmd '[con_mark=B] swap container with mark A';
+
+$nodes = get_ws_content($ws1);
+is($nodes->[0]->{window}, $B->{id}, 'B is on ws1:left');
+
+$nodes = get_ws_content($ws2);
+is($nodes->[1]->{window}, $A->{id}, 'A is on ws1:right');
+is(get_focused($ws2), $expected_focus, 'A is focused');
+
+exit_gracefully($pid);
+
+###############################################################################
+# Swap two non-focused containers within the same workspace.
+#
+# +---+---+    Layout: H1[ V1[ A X ] V2[ F B ] ]
+# | A | F |    Focus Stacks:
+# +---+---+        H1: V2, V1
+# | X | B |        V1: A, X
+# +---+---+        V2: F, B
+###############################################################################
+$pid = launch_with_config($config);
+$ws = fresh_workspace;
+
+$A = open_window(wm_class => 'mark_A');
+$B = open_window(wm_class => 'mark_B');
+cmd 'split v';
+open_window;
+cmd 'move up, focus left';
+cmd 'split v';
+open_window;
+cmd 'focus up, focus right';
+$expected_focus = get_focused($ws);
+
+cmd '[con_mark=B] swap container with mark A';
+
+$nodes = get_ws_content($ws);
+is($nodes->[0]->{nodes}->[0]->{window}, $B->{id}, 'B is on the top left');
+is($nodes->[1]->{nodes}->[1]->{window}, $A->{id}, 'A is on the bottom right');
+is(get_focused($ws), $expected_focus, 'F is still focused');
+
+exit_gracefully($pid);
+
+###############################################################################
+# Swap two non-focused containers which are both on different workspaces.
+#
+# Layout: O1[ W1[ A ] W2[ B ] W3[ F ] ]
+# Focus Stacks:
+#     O1: W3, W2, W1
+#
+# +---+
+# | A |
+# +---+
+#
+# +---+
+# | B |
+# +---+
+#
+# +---+
+# | F |
+# +---+
+###############################################################################
+$pid = launch_with_config($config);
+
+$ws1 = fresh_workspace;
+$A = open_window(wm_class => 'mark_A');
+
+$ws2 = fresh_workspace;
+$B = open_window(wm_class => 'mark_B');
+
+$ws3 = fresh_workspace;
+open_window;
+$expected_focus = get_focused($ws3);
+
+cmd '[con_mark=B] swap container with mark A';
+
+$nodes = get_ws_content($ws1);
+is($nodes->[0]->{window}, $B->{id}, 'B is on the first workspace');
+
+$nodes = get_ws_content($ws2);
+is($nodes->[0]->{window}, $A->{id}, 'A is on the second workspace');
+
+is(get_focused($ws3), $expected_focus, 'F is still focused');
+
+exit_gracefully($pid);
+
+###############################################################################
+# Swap two non-focused containers with one being on a different workspace.
+#
+# Layout: O1[ W1[ A ] W2[ H2 ] ]
+# Focus Stacks:
+#     O1: W2, W1
+#
+# +---+
+# | A |
+# +---+
+#
+# +---+---+    Layout: H2[ B, F ]
+# | B | F |    Focus Stacks:
+# +---+---+        H2: F, B
+###############################################################################
+$pid = launch_with_config($config);
+
+$ws1 = fresh_workspace;
+$A = open_window(wm_class => 'mark_A');
+
+$ws2 = fresh_workspace;
+$B = open_window(wm_class => 'mark_B');
+open_window;
+$expected_focus = get_focused($ws2);
+
+cmd '[con_mark=B] swap container with mark A';
+
+$nodes = get_ws_content($ws1);
+is($nodes->[0]->{window}, $B->{id}, 'B is on the first workspace');
+
+$nodes = get_ws_content($ws2);
+is($nodes->[0]->{window}, $A->{id}, 'A is on the left of the second workspace');
+is(get_focused($ws2), $expected_focus, 'F is still focused');
+
+exit_gracefully($pid);
+
+###############################################################################
+# 1. A container cannot be swapped with its parent.
+# 2. A container cannot be swapped with one of its children.
+#
+#      ↓A↓
+# +---+---+    Layout: H1[ X V1[ Y B ] ]
+# |   | Y |        (with A := V1)
+# | X +---+
+# |   | B |
+# +---+---+
+###############################################################################
+$pid = launch_with_config($config);
+
+$ws = fresh_workspace;
+open_window;
+open_window;
+cmd 'split v';
+$B = open_window(wm_class => 'mark_B');
+cmd 'focus parent, mark A, focus child';
+
+$result = cmd '[con_mark=B] swap container with mark A';
+is($result->[0]->{success}, 0, 'B cannot be swappd with its parent');
+
+$result = cmd '[con_mark=A] swap container with mark B';
+is($result->[0]->{success}, 0, 'A cannot be swappd with one of its children');
+
+exit_gracefully($pid);
+
+###############################################################################
+# Swapping two containers preserves the geometry of the container they are
+# being swapped with.
+#
+# Before:
+# +---+-------+
+# | A |   B   |
+# +---+-------+
+#
+# After:
+# +---+-------+
+# | B |   A   |
+# +---+-------+
+###############################################################################
+$pid = launch_with_config($config);
+
+$ws = fresh_workspace;
+$A = open_window(wm_class => 'mark_A');
+$B = open_window(wm_class => 'mark_B');
+cmd 'resize grow width 0 or 25 ppt';
+
+# sanity checks
+$nodes = get_ws_content($ws);
+cmp_float($nodes->[0]->{percent}, 0.25, 'A has 25% width');
+cmp_float($nodes->[1]->{percent}, 0.75, 'B has 75% width');
+
+cmd '[con_mark=B] swap container with mark A';
+
+$nodes = get_ws_content($ws);
+cmp_float($nodes->[0]->{percent}, 0.25, 'B has 25% width');
+cmp_float($nodes->[1]->{percent}, 0.75, 'A has 75% width');
+
+exit_gracefully($pid);
+
+###############################################################################
+# Swapping containers not sharing the same parent preserves the geometry of
+# the container they are swapped with.
+#
+# Before:
+# +---+-----+
+# | A |     |
+# +---+  B  |
+# |   |     |
+# | Y +-----+
+# |   |  X  |
+# +---+-----+
+#
+# After:
+# +---+-----+
+# | B |     |
+# +---+  A  |
+# |   |     |
+# | Y +-----+
+# |   |  X  |
+# +---+-----+
+###############################################################################
+$pid = launch_with_config($config);
+$ws = fresh_workspace;
+
+$A = open_window(wm_class => 'mark_A');
+$B = open_window(wm_class => 'mark_B');
+cmd 'split v';
+open_window;
+cmd 'focus up, resize grow height 0 or 25 ppt';
+cmd 'focus left, split v';
+open_window;
+cmd 'resize grow height 0 or 25 ppt';
+
+# sanity checks
+$nodes = get_ws_content($ws);
+cmp_float($nodes->[0]->{nodes}->[0]->{percent}, 0.25, 'A has 25% height');
+cmp_float($nodes->[1]->{nodes}->[0]->{percent}, 0.75, 'B has 75% height');
+
+cmd '[con_mark=B] swap container with mark A';
+
+$nodes = get_ws_content($ws);
+cmp_float($nodes->[0]->{nodes}->[0]->{percent}, 0.25, 'B has 25% height');
+cmp_float($nodes->[1]->{nodes}->[0]->{percent}, 0.75, 'A has 75% height');
+
+exit_gracefully($pid);
+
+###############################################################################
+# Swapping containers moves the urgency hint correctly.
+###############################################################################
+$pid = launch_with_config($config);
+
+$ws1 = fresh_workspace;
+$A = open_window(wm_class => 'mark_A');
+$ws2 = fresh_workspace;
+$B = open_window(wm_class => 'mark_B');
+open_window;
+
+$B->add_hint('urgency');
+sync_with_i3;
+
+cmd '[con_mark=B] swap container with mark A';
+
+@urgent = grep { $_->{urgent} } @{get_ws_content($ws1)};
+is(@urgent, 1, 'B is marked urgent');
+is(get_ws($ws1)->{urgent}, 1, 'the first workspace is marked urgent');
+
+@urgent = grep { $_->{urgent} } @{get_ws_content($ws2)};
+is(@urgent, 0, 'A is not marked urgent');
+is(get_ws($ws2)->{urgent}, 0, 'the second workspace is not marked urgent');
+
+exit_gracefully($pid);
+
+###############################################################################
+
+done_testing;
diff --git a/testcases/t/266-net-moveresize-window.t b/testcases/t/266-net-moveresize-window.t
new file mode 100644 (file)
index 0000000..69542f9
--- /dev/null
@@ -0,0 +1,117 @@
+#!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)
+#
+# Tests for _NET_MOVERESIZE_WINDOW.
+# Ticket: #2603
+use i3test i3_autostart => 0;
+
+sub moveresize_window {
+    my ($win, $pos_x, $pos_y, $width, $height) = @_;
+
+    my $flags = 0;
+    $flags |= (1 << 8) if $pos_x >= 0;
+    $flags |= (1 << 9) if $pos_y >= 0;
+    $flags |= (1 << 10) if $width >= 0;
+    $flags |= (1 << 11) if $height >= 0;
+
+    my $msg = pack "CCSLLLLLLL",
+        X11::XCB::CLIENT_MESSAGE, # response_type
+        32, # format
+        0, # sequence
+        $win->id, # window
+        $x->atom(name => '_NET_MOVERESIZE_WINDOW')->id, # message type
+        $flags, # data32[0] (flags)
+        $pos_x, # data32[1] (x)
+        $pos_y, # data32[2] (y)
+        $width, # data32[3] (width)
+        $height; # data32[4] (height)
+
+    $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg);
+    sync_with_i3;
+}
+
+my $config = <<EOT;
+# i3 config file (v4)
+font font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+new_window none
+new_float none
+EOT
+
+my ($pid, $ws, $window, $content);
+
+###############################################################################
+
+###############################################################################
+# A _NET_MOVERESIZE_WINDOW client message can change the position and size
+# of a floating window.
+###############################################################################
+
+$pid = launch_with_config($config);
+$ws = fresh_workspace;
+
+$window = open_floating_window(rect => [50, 50, 100, 100]);
+moveresize_window($window, 0, 0, 555, 666);
+
+$content = get_ws($ws);
+is($content->{floating_nodes}->[0]->{rect}->{x}, 0, 'the x coordinate is correct');
+is($content->{floating_nodes}->[0]->{rect}->{y}, 0, 'the y coordinate is correct');
+is($content->{floating_nodes}->[0]->{rect}->{width}, 555, 'the width is correct');
+is($content->{floating_nodes}->[0]->{rect}->{height}, 666, 'the height is correct');
+
+exit_gracefully($pid);
+
+###############################################################################
+# A _NET_MOVERESIZE_WINDOW client message can change only the position of a
+# window.
+###############################################################################
+
+$pid = launch_with_config($config);
+$ws = fresh_workspace;
+
+$window = open_floating_window(rect => [50, 50, 100, 100]);
+moveresize_window($window, 100, 100, -1, -1);
+
+$content = get_ws($ws);
+is($content->{floating_nodes}->[0]->{rect}->{x}, 100, 'the x coordinate is correct');
+is($content->{floating_nodes}->[0]->{rect}->{y}, 100, 'the y coordinate is correct');
+is($content->{floating_nodes}->[0]->{rect}->{width}, 100, 'the width is unchanged');
+is($content->{floating_nodes}->[0]->{rect}->{height}, 100, 'the height is unchanged');
+
+exit_gracefully($pid);
+
+###############################################################################
+# A _NET_MOVERESIZE_WINDOW client message can change only the size of a
+# window.
+###############################################################################
+
+$pid = launch_with_config($config);
+$ws = fresh_workspace;
+
+$window = open_floating_window(rect => [50, 50, 100, 100]);
+moveresize_window($window, -1, -1, 200, 200);
+
+$content = get_ws($ws);
+is($content->{floating_nodes}->[0]->{rect}->{x}, 50, 'the x coordinate is unchanged');
+is($content->{floating_nodes}->[0]->{rect}->{y}, 50, 'the y coordinate is unchanged');
+is($content->{floating_nodes}->[0]->{rect}->{width}, 200, 'the width is correct');
+is($content->{floating_nodes}->[0]->{rect}->{height}, 200, 'the height is correct');
+
+exit_gracefully($pid);
+
+###############################################################################
+
+done_testing;
diff --git a/testcases/t/267-regress-mark-restart.t b/testcases/t/267-regress-mark-restart.t
new file mode 100644 (file)
index 0000000..220d765
--- /dev/null
@@ -0,0 +1,30 @@
+#!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)
+#
+# Regression test to check if mark and restart commands crash i3
+#
+use i3test;
+
+cmd 'open';
+cmd 'mark foo';
+
+cmd 'restart';
+
+diag('Checking if i3 still lives');
+
+does_i3_live;
+
+done_testing;
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;
diff --git a/testcases/t/269-focus-stack-above.t b/testcases/t/269-focus-stack-above.t
new file mode 100644 (file)
index 0000000..a902d96
--- /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 a ConfigureWindow request with stack-mode=Above is translated into
+# focusing the target window by i3.
+# Ticket: #2708
+# Bug still in: 4.13-207-gafdf6792
+use i3test;
+use X11::XCB qw(CONFIG_WINDOW_STACK_MODE STACK_MODE_ABOVE);
+
+my $ws = fresh_workspace;
+my $left_window = open_window;
+my $right_window = open_window;
+
+is($x->input_focus, $right_window->id, 'right window has focus');
+my $old_focus = get_focused($ws);
+
+$x->configure_window($left_window->id, CONFIG_WINDOW_STACK_MODE, (STACK_MODE_ABOVE));
+$x->flush;
+
+sync_with_i3;
+
+is($x->input_focus, $left_window->id, 'left window has focus');
+isnt(get_focused($ws), $old_focus, 'right window is no longer focused');
+
+################################################################################
+# Verify the ConfigureWindow request is only applied when on the active
+# workspace.
+################################################################################
+
+$ws = fresh_workspace;
+my $new_window = open_window;
+
+is($x->input_focus, $new_window->id, 'new window has focus');
+$x->configure_window($left_window->id, CONFIG_WINDOW_STACK_MODE, (STACK_MODE_ABOVE));
+$x->flush;
+
+sync_with_i3;
+
+is($x->input_focus, $new_window->id, 'new window still has focus');
+
+done_testing;
diff --git a/testcases/t/533-randr15.t b/testcases/t/533-randr15.t
new file mode 100644 (file)
index 0000000..08fa88c
--- /dev/null
@@ -0,0 +1,108 @@
+#!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)
+#
+# TODO: Description of this file.
+# Ticket: #999
+# Bug still in: 4.13-12-g2ff3d9d
+use File::Temp qw(tempfile);
+use i3test i3_autostart => 0;
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+EOT
+
+my ($outfh, $outname) = tempfile('i3-randr15reply-XXXXXX', UNLINK => 1);
+
+# Prepare a RRGetMonitors reply, see A.2.4 in
+# https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
+my $reply = pack('cxSLLLLx[LLL]',
+     1, # reply
+     0, # sequence (will be filled in by inject_randr15)
+     # 56 = length($reply) + length($monitor1)
+     # 32 = minimum X11 reply length
+     (56-32) / 4, # length in words
+     0, # timestamp TODO
+     1, # nmonitors
+     0); # noutputs
+
+# Manually intern _NET_CURRENT_DESKTOP as $x->atom will not create atoms if
+# they are not yet interned.
+my $atom_cookie = $x->intern_atom(0, length("DP3"), "DP3");
+my $DP3 = $x->intern_atom_reply($atom_cookie->{sequence})->{atom};
+
+# MONITORINFO is defined in A.1.1 in
+# https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt
+my $monitor1 = pack('LccSssSSLL',
+        $DP3, # name (ATOM)
+        1, # primary
+        1, # automatic
+        0, # ncrtcs
+        0, # x
+        0, # y
+        3840, # width in pixels
+        2160, # height in pixels
+        520, # width in millimeters
+        290); # height in millimeters
+
+print $outfh $reply;
+print $outfh $monitor1;
+
+close($outfh);
+
+my $pid = launch_with_config($config, inject_randr15 => $outname);
+
+my $tree = i3->get_tree->recv;
+my @outputs = map { $_->{name} } @{$tree->{nodes}};
+is_deeply(\@outputs, [ '__i3', 'DP3' ], 'outputs are __i3 and DP3');
+
+my ($dp3) = grep { $_->{name} eq 'DP3' } @{$tree->{nodes}};
+is_deeply($dp3->{rect}, {
+        width => 3840,
+        height => 2160,
+        x => 0,
+        y => 0,
+    }, 'Output DP3 at 3840x2160+0+0');
+
+exit_gracefully($pid);
+
+################################################################################
+# Verify that adding monitors with RandR 1.5 results in i3 outputs.
+################################################################################
+
+# When inject_randr15 is defined but false, fake-xinerama will be turned off,
+# but inject_randr15 will not actually be used.
+my $pid = launch_with_config($config, inject_randr15 => '');
+
+$tree = i3->get_tree->recv;
+@outputs = map { $_->{name} } @{$tree->{nodes}};
+is_deeply(\@outputs, [ '__i3', 'default' ], 'outputs are __i3 and default');
+
+SKIP: {
+    skip 'xrandr --setmonitor failed (xrandr too old?)', 1 unless
+        system(q|xrandr --setmonitor up2414q 3840/527x2160/296+1280+0 none|) == 0;
+
+    sync_with_i3;
+
+    $tree = i3->get_tree->recv;
+    @outputs = map { $_->{name} } @{$tree->{nodes}};
+    is_deeply(\@outputs, [ '__i3', 'default', 'up2414q' ], 'outputs are __i3, default and up2414q');
+}
+
+exit_gracefully($pid);
+
+done_testing;
diff --git a/testcases/t/534-dont-warp.t b/testcases/t/534-dont-warp.t
new file mode 100644 (file)
index 0000000..8f84f9a
--- /dev/null
@@ -0,0 +1,61 @@
+#!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 i3 doesn’t warp when a new floating window is opened under the cursor
+# over an unfocused workspace.
+# Ticket: #2681
+# Bug still in: 4.13-210-g80c23afa
+use i3test i3_autostart => 0;
+
+# Ensure the pointer is at (0, 0) so that we really start on the first
+# (the left) workspace.
+$x->root->warp_pointer(0, 0);
+
+my $config = <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
+
+fake-outputs 1024x768+0+0,1024x768+1024+0
+
+focus_follows_mouse no
+EOT
+
+my $pid = launch_with_config($config);
+
+cmd 'focus output fake-0';
+my $s0_ws = fresh_workspace;
+
+cmd 'focus output fake-1';
+my $s1_ws = fresh_workspace;
+open_window;
+
+# Move mouse to fake-0
+sync_with_i3;
+$x->root->warp_pointer(500, 0);
+sync_with_i3;
+
+my $dropdown = open_floating_window;
+$dropdown->rect(X11::XCB::Rect->new(x => 1, y => 1, width => 100, height => 100));
+sync_with_i3;
+
+my $cookie = $x->query_pointer($dropdown->{id});
+my $reply = $x->query_pointer_reply($cookie->{sequence});
+cmp_ok($reply->{root_x}, '<', 1024, 'pointer still on fake-0');
+cmp_ok($reply->{root_y}, '<', 768, 'pointer still on fake-0');
+
+exit_gracefully($pid);
+
+done_testing;
index ead251579060dfbbf151a3bb4584e234fd628926..ff406bea2042a3c1833e44e2d9e33aa5fb2c8d27 100755 (executable)
@@ -3,4 +3,4 @@
 set -e
 set -x
 
-clang-format-3.5 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false)
+clang-format-3.8 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false)
index 9f2b81295a0efb9ed4c7cb4ffae4690030c52201..ddb3874ec6765f3a197f2b0bc5f76bd99eb24685 100644 (file)
@@ -13,12 +13,12 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
 # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
 
 # Install mk-build-deps (for installing the i3 build dependencies),
-# clang and clang-format-3.5 (for checking formatting and building with clang),
+# clang and clang-format-3.8 (for checking formatting and building with clang),
 # lintian (for checking spelling errors),
 RUN linux32 apt-get update && \
     DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
     dpkg-dev devscripts git equivs \
-    clang clang-format-3.5 \
+    clang clang-format-3.8 \
     lintian && \
     rm -rf /var/lib/apt/lists/*
 
index 65f123c88a379f18ab0c7f474d62ccde5fc027fb..1014407a39fcfc8bd0fb61e67594c98166f5a996 100644 (file)
@@ -13,12 +13,12 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
 # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
 
 # Install mk-build-deps (for installing the i3 build dependencies),
-# clang and clang-format-3.5 (for checking formatting and building with clang),
+# clang and clang-format-3.8 (for checking formatting and building with clang),
 # lintian (for checking spelling errors),
 RUN linux32 apt-get update && \
     DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
     dpkg-dev devscripts git equivs \
-    clang clang-format-3.5 \
+    clang clang-format-3.8 \
     lintian && \
     rm -rf /var/lib/apt/lists/*
 
index ab47469124ad9d72fe2689c13f9796141b6119dd..0b4ec206156a2eb2a247f19b005f42a1951f4a2c 100644 (file)
@@ -13,13 +13,13 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
 # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
 
 # Install mk-build-deps (for installing the i3 build dependencies),
-# clang and clang-format-3.5 (for checking formatting and building with clang),
+# clang and clang-format-3.8 (for checking formatting and building with clang),
 # lintian (for checking spelling errors),
 # test suite dependencies (for running tests)
 RUN apt-get update && \
     DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
     dpkg-dev devscripts git equivs \
-    clang clang-format-3.5 \
+    clang clang-format-3.8 \
     lintian && \
     rm -rf /var/lib/apt/lists/*
 
index 85fa27523172b4c3de34518787345f1498b88a84..5704d8e4636587fd0ee21dcbf7e477da6b27a293 100644 (file)
@@ -11,15 +11,15 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
 # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
 
 # Install mk-build-deps (for installing the i3 build dependencies),
-# clang and clang-format-3.5 (for checking formatting and building with clang),
+# clang and clang-format-3.8 (for checking formatting and building with clang),
 # lintian (for checking spelling errors),
 # test suite dependencies (for running tests)
 RUN apt-get update && \
     DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
     dpkg-dev devscripts git equivs \
-    clang clang-format-3.5 \
+    clang clang-format-3.8 \
     lintian \
-    libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libinline-c-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libipc-run-perl libxcb-xtest0-dev libx11-xcb-perl libanyevent-i3-perl && \
+    libmodule-install-perl libanyevent-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libinline-c-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libipc-run-perl libxcb-xtest0-dev libx11-xcb-perl libjson-xs-perl && \
     rm -rf /var/lib/apt/lists/*
 
 # Install i3 build dependencies.