PointerBindsToType: false
ColumnLimit: 0
SpaceBeforeParens: ControlStatements
+SortIncludes: false
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
--- /dev/null
+# 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).
-sudo: required
+sudo: false
dist: trusty
services:
- docker
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+^\.git/
+\.bak$
+blib/
+^Makefile$
+^Makefile.old$
+Build
+Build.bat
+^pm_to_blib
+\.tar\.gz$
+^pod2htm(.*).tmp$
+^AnyEvent-I3-
--- /dev/null
+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;
--- /dev/null
+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.
--- /dev/null
+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
--- /dev/null
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+ use_ok( 'AnyEvent::I3' ) || print "Bail out!
+";
+}
+
+diag( "Testing AnyEvent::I3 $AnyEvent::I3::VERSION, Perl $], $^X" );
--- /dev/null
+#!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" );
--- /dev/null
+#!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" );
--- /dev/null
+#!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');
+
+
+}
+
--- /dev/null
+#!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();
--- /dev/null
+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();
--- /dev/null
+#!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();
-4.13-non-git
+4.14-non-git
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
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 \
endif
AM_CPPFLAGS = \
+ -DSYSCONFDIR="\"$(sysconfdir)\"" \
-I$(top_builddir)/parser \
-I$(top_srcdir)/include \
@AX_EXTEND_SRCDIR_CPPFLAGS@
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
include/config_parser.h \
include/con.h \
include/data.h \
- include/debug.h \
include/display_version.h \
include/ewmh.h \
include/fake_outputs.h \
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 \
$(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
+++ /dev/null
-
- ┌────────────────────────────┐
- │ 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
--- /dev/null
+
+ ┌────────────────────────────┐
+ │ 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
# 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])
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.
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)
],
AC_PROG_CC_C99
-AC_DEFINE_UNQUOTED(SYSCONFDIR, "`eval echo $sysconfdir`", [Location of system configuration files])
-
# For strnlen() and vasprintf().
AC_USE_SYSTEM_EXTENSIONS
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
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
+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.
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.
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
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.
"y": 0,
"width": 1280,
"height": 1024
- },
+ }
}
]
-------------------
"y": 0,
"width": 1280,
"height": 0
- },
+ }
},
{
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:*
--------------------------------------------------------------------
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
=== 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).
}
---------------------------
+=== 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]]
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::
* https://github.com/badboy/i3-ipc (not maintained)
Rust::
* https://github.com/tmerr/i3ipc-rs
+OCaml::
+ * https://github.com/Armael/ocaml-i3ipc
]
}
--------------------------------------------------------------------------------
+
+=== 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"
+ }
+]
+--------------------------------------------------------------------------------
$ 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:
--------------------------------------------------------------------------------
$ 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
./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
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
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
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
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
*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"
bindsym f exec firefox
bindsym t exec thunderbird
- bindsym Esc mode "default"
+ bindsym Escape mode "default"
bindsym Return mode "default"
}
------------------------------------------------------------------------
=== 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*:
--------------------------
[[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+.
*Syntax*:
---------------
-output <output>
+output primary|<output>
---------------
*Example*:
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
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*:
--------------------------------
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
--------------------------------------------
layout default|tabbed|stacking|splitv|splith
layout toggle [split|all]
+layout toggle [split|tabbed|stacking|splitv|splith] [split|tabbed|stacking|splitv|splith]…
--------------------------------------------
*Examples*:
# 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
----------------------------------------------
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*:
# 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.
# 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.
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
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
*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*:
# 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>>),
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 {
# 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';
#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;
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;
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;
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)) {
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);
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,
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);
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;
}
}
$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) {
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) {
#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;
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;
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.
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).
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;
/* 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);
return 1;
}
if (sym == XK_Escape) {
- restore_input_focus();
exit(0);
}
}
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;
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);
/* 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);
if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
- restore_input_focus();
exit(-1);
}
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;
}
.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)
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') {
printf("i3-msg " I3_VERSION "\n");
printf("i3-msg [-s <socket>] [-t <type>] <message>\n");
return 0;
+ } else if (o == '?') {
+ exit(EXIT_FAILURE);
}
}
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);
/* 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);
* 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 {
} 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;
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)
/* 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;
}
*/
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);
}
}
+ btn_close.label = i3string_from_utf8("X");
+
int screens;
if ((conn = xcb_connect(NULL, &screens)) == NULL ||
xcb_connection_has_error(conn))
color_border_bottom = draw_util_hex_to_color("#ab7100");
}
+ init_dpi();
font = load_font(pattern, true);
set_font(&font);
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);
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:
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;
}
}
}
FREE(pattern);
+ draw_util_surface_free(conn, &bar);
return 0;
}
# 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
# 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
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"
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;
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;
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 */
};
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 */
};
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 */
};
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);
if (!strcmp(cur_key, "status_command")) {
DLOG("command = %.*s\n", len, val);
+ FREE(config.command);
sasprintf(&config.command, "%.*s", len, val);
return 1;
}
*
*/
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);
}
init_colors(&(config.colors));
start_child(config.command);
- FREE(config.command);
}
/* Data structure to easily call the reply handlers later */
/* 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) {
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);
}
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. */
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",
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),
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
}
/* 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,
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
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
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();
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 */
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);
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);
/* 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.
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;
}
/* 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),
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),
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;
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);
}
xmacro(_NET_DESKTOP_VIEWPORT)
xmacro(_NET_ACTIVE_WINDOW)
xmacro(_NET_CLOSE_WINDOW)
+xmacro(_NET_MOVERESIZE_WINDOW)
*/
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)
*/
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>'
*
*/
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.
*
*/
i3String *con_parse_title_format(Con *con);
+
+/**
+ * Swaps the two containers.
+ *
+ */
+bool con_swap(Con *first, Con *second);
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);
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);
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;
char *value;
char *next_match;
- SLIST_ENTRY(Variable) variables;
+ SLIST_ENTRY(Variable)
+ variables;
};
/**
bool pango_markup;
struct bindings_head *bindings;
- SLIST_ENTRY(Mode) modes;
+ SLIST_ENTRY(Mode)
+ modes;
};
/**
* 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;
/* 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;
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,
char *binding_mode_text;
} colors;
- TAILQ_ENTRY(Barconfig) configs;
+ TAILQ_ENTRY(Barconfig)
+ configs;
};
/**
/** 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;
};
/**
char *name;
char *output;
- TAILQ_ENTRY(Workspace_Assignment) ws_assignments;
+ TAILQ_ENTRY(Workspace_Assignment)
+ ws_assignments;
};
struct Ignore_Event {
int response_type;
time_t added;
- SLIST_ENTRY(Ignore_Event) ignore_events;
+ SLIST_ENTRY(Ignore_Event)
+ ignore_events;
};
/**
* completed) */
time_t delete_at;
- TAILQ_ENTRY(Startup_Sequence) sequences;
+ TAILQ_ENTRY(Startup_Sequence)
+ sequences;
};
/**
struct Binding_Keycode {
xcb_keycode_t keycode;
i3_event_state_mask_t modifiers;
- TAILQ_ENTRY(Binding_Keycode) keycodes;
+
+ TAILQ_ENTRY(Binding_Keycode)
+ keycodes;
};
/******************************************************************************
* 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;
/** 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;
};
/**
/** 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;
};
/**
/** x, y, width, height */
Rect rect;
- TAILQ_ENTRY(xoutput) outputs;
+ TAILQ_ENTRY(xoutput)
+ outputs;
};
/**
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;
};
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
char *workspace;
} dest;
- TAILQ_ENTRY(Assignment) assignments;
+ TAILQ_ENTRY(Assignment)
+ assignments;
};
/** Fullscreen modes. Used by Con.fullscreen_mode. */
struct mark_t {
char *name;
- TAILQ_ENTRY(mark_t) marks;
+ TAILQ_ENTRY(mark_t)
+ marks;
};
/**
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;
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;
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 *);
+++ /dev/null
-/*
- * 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);
/** 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
*
#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.
/** 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)
int num_events;
char **events;
- TAILQ_ENTRY(ipc_client) clients;
+ TAILQ_ENTRY(ipc_client)
+ clients;
} ipc_client;
/*
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);
#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
*
*/
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
* 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);
}
#define CIRCLEQ_HEAD_INITIALIZER(head) \
- { CIRCLEQ_END(&head), CIRCLEQ_END(&head) }
+ { \
+ CIRCLEQ_END(&head) \
+ , CIRCLEQ_END(&head) \
+ }
#define CIRCLEQ_ENTRY(type) \
struct { \
* 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
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
*
* 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
#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);
*/
__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".
*
*/
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);
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.
*/
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
*
*/
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.
DLOG("Found Xft.dpi = %ld.\n", dpi);
init_dpi_end:
+ if (resource != NULL) {
+ free(resource);
+ }
+
if (database != NULL) {
xcb_xrm_database_free(database);
}
}
}
+/*
+ * 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
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 { \
*
*/
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];
* 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);
* 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);
* 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);
* 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);
* 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);
* 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);
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);
/* 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, ":");
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`
* 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.
* 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.
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.
'rename' -> RENAME
'nop' -> NOP
'scratchpad' -> SCRATCHPAD
+ 'swap' -> SWAP
'title_format' -> TITLE_FORMAT
'mode' -> MODE
'bar' -> BAR
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>
-> 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)
'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)
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
'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
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
->
whole_window = '--whole-window'
->
+ exclude_titlebar = '--exclude-titlebar'
+ ->
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod'
->
'+'
->
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
->
whole_window = '--whole-window'
->
+ exclude_titlebar = '--exclude-titlebar'
+ ->
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod'
->
'+'
->
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)
#!/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" ]
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"
/* 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 */
*/
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
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);
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.
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);
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) {
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
/* 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;
}
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;
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,
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);
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;
}
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);
/* 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
* #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;
}
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;
}
/* 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;
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;
/* 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;
}
}
/* 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;
}
*/
typedef struct owindow {
Con *con;
- TAILQ_ENTRY(owindow) owindows;
+
+ TAILQ_ENTRY(owindow)
+ owindows;
} owindow;
typedef TAILQ_HEAD(owindows_head, owindow) owindows_head;
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;
}
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;
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;
}
#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);
*/
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 */
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>'
*
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;
}
}
/* 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
struct bfs_entry {
Con *con;
- TAILQ_ENTRY(bfs_entry) entries;
+ TAILQ_ENTRY(bfs_entry)
+ entries;
};
/*
/* 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);
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.
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);
+ }
}
}
}
// 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;
+ }
}
}
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. */
_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
/* 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. */
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);
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);
}
/*
}
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);
}
/*
} 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;
}
* 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);
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. */
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;
+}
#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);
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);
}
/*******************************************************************************
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);
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);
}
*
*/
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--;
}
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;
}
/* 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';
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
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;
}
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;
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;
+++ /dev/null
-/*
- * 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;
-}
#endif
yajl_free(handle);
+ free(reply);
+ free(pid_from_atom);
+ free(socket_path);
}
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) {
}
}
- /* 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;
}
}
}
}
- 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. */
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) */
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);
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);
}
/*
}
/* 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;
}
#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)
*
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);
}
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;
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;
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;
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;
}
render_and_return:
- if (changed)
+ if (changed) {
tree_render();
+ }
+
FREE(reply);
return true;
}
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.
*
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:
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;
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
}
/*
- * 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);
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,
handle_get_bar_config,
handle_get_version,
handle_get_binding_modes,
+ handle_get_config,
};
/*
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 =
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");
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;
}
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);
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");
+ }
}
}
}
#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
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;
{"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},
"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;
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;
}
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
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/…
/* 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;
}
}
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;
}
}
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 {
}
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 {
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) {
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;
#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;
/* 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
}
/*
- * 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;
}
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);
}
}
/*
- * 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);
}
/*
- * (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)
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. */
/* render_layout flushes */
tree_render();
- FREE(res);
FREE(primary);
}
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);
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)
}
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);
#include <sanitizer/lsan_interface.h>
#endif
+#define TEXT_PADDING logical_px(2)
+
typedef struct placeholder_state {
/** The X11 placeholder window. */
xcb_window_t window;
/** 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 =
}
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) {
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);
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);
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);
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);
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;
}
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;
}
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);
*
* 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";
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);
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) {
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
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 {
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".
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 */
* 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;
+}
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);
/*
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);
}
/* 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),
* 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);
}
/* 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);
}
* 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);
}
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));
}
}
* 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 */
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("");
}
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);
}
/*
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);
}
}
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);
/* 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);
/* 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);
+ }
}
/*
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) {
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.
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
*
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.
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,
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;
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',
--- /dev/null
+/*
+ * 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);
+}
# 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
'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;
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);
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
use v5.10;
use i3test i3_autostart => 0;
+use lib qw(@abs_top_srcdir@/AnyEvent-I3/blib/lib);
use AnyEvent::I3;
use ExtUtils::PkgConfig;
X11::XCB::Connection
X11::XCB::Window
AnyEvent
- AnyEvent::I3
IPC::Run
ExtUtils::PkgConfig
Inline
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';
# 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;
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;
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');
#####################################################################
$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
rename
nop
scratchpad
+ swap
title_format
mode
bar
($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;
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),
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
force_focus_wrapping
force_xinerama
force-xinerama
+ disable_randr15
+ disable-randr15
workspace_auto_back_and_forth
fake_outputs
fake-outputs
$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" {
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;
$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),
);
},
$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;
--- /dev/null
+#!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;
# 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);
'Mod1+Shift+q',
'triggered the "Mod1+Shift+q" keybinding');
-
is(listen_for_binding(
sub {
xtest_key_press(77); # enable Num_Lock
'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);
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);
'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
'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);
--- /dev/null
+#!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;
--- /dev/null
+#!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;
--- /dev/null
+#!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;
--- /dev/null
+#!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;
--- /dev/null
+#!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;
--- /dev/null
+#!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;
--- /dev/null
+#!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;
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)
# (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/*
# (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/*
# (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/*
# (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.