diff --git a/.clang-format b/.clang-format index 1d840132..6e49d835 100644 --- a/.clang-format +++ b/.clang-format @@ -8,3 +8,4 @@ IndentWidth: 4 PointerBindsToType: false ColumnLimit: 0 SpaceBeforeParens: ControlStatements +SortIncludes: false diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 474355ad..c19ac81e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -14,6 +14,9 @@ Note that bug reports and feature requests for related projects should be filed having access to the source code is too time-consuming. Additionally, experience has shown that often, the software in question is responsible for the issue. Please raise an issue with the software in question, not i3. +5. Please note that i3 does not support compositors (e.g. compton). If you + encountered the issue you are about to report while using a compositor, + please try reproducing it without a compositor. ## Pull requests diff --git a/.github/GOVERNANCE.md b/.github/GOVERNANCE.md new file mode 100644 index 00000000..44e13345 --- /dev/null +++ b/.github/GOVERNANCE.md @@ -0,0 +1,30 @@ +# i3 project governance + +## Overview + +The i3 project uses a governance model commonly described as Benevolent +Dictator For Life (BDFL). This document outlines our understanding of what this +means. + +## Roles + +* user: anyone who interacts with the i3 project +* core contributor: a handful of people who have contributed significantly to + the project by any means (issue triage, support, documentation, code, etc.). + Core contributors are recognizable via GitHub’s “Member” badge. +* BDFL: a single individual who makes decisions when consensus cannot be + reached. i3’s current BDFL is [@stapelberg](https://github.com/stapelberg). + +## Decision making process + +In general, we try to reach consensus in discussions. In case consensus cannot +be reached, the BDFL makes a decision. + +For feature requests and code contributions specifically, the values with which +we consider them can be found on the bottom of https://i3wm.org/. These values +are not set in stone and are to be treated as guiding principles, not absolute +rules that must be followed in every case. + +## Contribution process + +Please see [CONTRIBUTING](CONTRIBUTING.md). diff --git a/.travis.yml b/.travis.yml index f9744335..63f69ac8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -sudo: required +sudo: false dist: trusty services: - docker diff --git a/AnyEvent-I3/Changes b/AnyEvent-I3/Changes new file mode 100644 index 00000000..d763437f --- /dev/null +++ b/AnyEvent-I3/Changes @@ -0,0 +1,73 @@ +Revision history for AnyEvent-I3 + +0.18 2017-08-19 + + * support the GET_CONFIG command + +0.17 2017-04-09 + + * support the shutdown event + * use lib '.' for Perl 5.25.11+ + +0.16 2014-10-03 + + * support the barconfig_update and binding event + +0.15 2013-02-18 + + * support the window event + +0.14 2012-09-22 + + * support the mode event + +0.13 2012-08-05 + + * support the GET_VERSION request with a fall-back to i3 --version + +0.12 2012-07-11 + + * taint mode fix: remove relative directories from $ENV{PATH} + +0.11 2012-07-10 + + * taint mode fix for FreeBSD + +0.10 2012-07-09 + + * Use i3 --get-socketpath by default for determining the socket path + * Bugfix: Also delete callbacks which are triggered due to an error + +0.09 2011-10-12 + + * Implement GET_BAR_CONFIG request + +0.08 2011-09-26 + + * Implement GET_MARKS request + * The synopsis mentioned ->workspaces, but it’s ->get_workspaces + +0.07 2010-11-21 + + * Implement GET_TREE request + +0.06 2010-06-16 + + * Add check to Makefile to abort in a Windows environment (neither i3 nor + unix sockets available) + +0.05 2010-06-09 + + * use getpwuid() to resolve ~ in socket paths instead of glob() + +0.04 2010-03-27 + + * use new default ipc-socket path, glob() path, bump version + +0.03 2010-03-26 + + * fix MANIFEST + +0.02 2010-03-23 + + * first upload to CPAN diff --git a/AnyEvent-I3/MANIFEST b/AnyEvent-I3/MANIFEST new file mode 100644 index 00000000..34c8a8fb --- /dev/null +++ b/AnyEvent-I3/MANIFEST @@ -0,0 +1,22 @@ +Changes +inc/Module/Install.pm +inc/Module/Install/Base.pm +inc/Module/Install/Can.pm +inc/Module/Install/Fetch.pm +inc/Module/Install/Makefile.pm +inc/Module/Install/Metadata.pm +inc/Module/Install/Win32.pm +inc/Module/Install/WriteAll.pm +lib/AnyEvent/I3.pm +Makefile.PL +MANIFEST +MANIFEST.SKIP +META.yml +README +t/00-load.t +t/01-workspaces.t +t/02-sugar.t +t/boilerplate.t +t/manifest.t +t/pod-coverage.t +t/pod.t diff --git a/AnyEvent-I3/MANIFEST.SKIP b/AnyEvent-I3/MANIFEST.SKIP new file mode 100644 index 00000000..01bee91f --- /dev/null +++ b/AnyEvent-I3/MANIFEST.SKIP @@ -0,0 +1,11 @@ +^\.git/ +\.bak$ +blib/ +^Makefile$ +^Makefile.old$ +Build +Build.bat +^pm_to_blib +\.tar\.gz$ +^pod2htm(.*).tmp$ +^AnyEvent-I3- diff --git a/AnyEvent-I3/Makefile.PL b/AnyEvent-I3/Makefile.PL new file mode 100644 index 00000000..5d2ab32e --- /dev/null +++ b/AnyEvent-I3/Makefile.PL @@ -0,0 +1,17 @@ +use lib '.'; +use inc::Module::Install; + +name 'AnyEvent-I3'; +all_from 'lib/AnyEvent/I3.pm'; +author 'Michael Stapelberg'; + +requires 'AnyEvent'; +requires 'AnyEvent::Handle'; +requires 'AnyEvent::Socket'; +requires 'JSON::XS'; + +if ($^O eq 'MSWin32') { + die "AnyEvent::I3 cannot be used on win32 (unix sockets are missing)"; +} + +WriteAll; diff --git a/AnyEvent-I3/README b/AnyEvent-I3/README new file mode 100644 index 00000000..4658ba16 --- /dev/null +++ b/AnyEvent-I3/README @@ -0,0 +1,40 @@ +AnyEvent-I3 + +This module connects to the i3 window manager using the UNIX socket based +IPC interface it provides (if enabled in the configuration file). You can +then subscribe to events or send messages and receive their replies. + +INSTALLATION + +To install this module, run the following commands: + + perl Makefile.PL + make + make test + make install + +SUPPORT AND DOCUMENTATION + +After installing, you can find documentation for this module with the +perldoc command. + + perldoc AnyEvent::I3 + +You can also look for information at: + + RT, CPAN's request tracker + http://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-I3 + + The i3 window manager website + http://i3.zekjur.net/ + + +LICENSE AND COPYRIGHT + +Copyright (C) 2010 Michael Stapelberg + +This program is free software; you can redistribute it and/or modify it +under the terms of either: the GNU General Public License as published +by the Free Software Foundation; or the Artistic License. + +See http://dev.perl.org/licenses/ for more information. diff --git a/AnyEvent-I3/lib/AnyEvent/I3.pm b/AnyEvent-I3/lib/AnyEvent/I3.pm new file mode 100644 index 00000000..75845ccd --- /dev/null +++ b/AnyEvent-I3/lib/AnyEvent/I3.pm @@ -0,0 +1,586 @@ +package AnyEvent::I3; +# vim:ts=4:sw=4:expandtab + +use strict; +use warnings; +use JSON::XS; +use AnyEvent::Handle; +use AnyEvent::Socket; +use AnyEvent; +use Encode; +use Scalar::Util qw(tainted); + +=head1 NAME + +AnyEvent::I3 - communicate with the i3 window manager + +=cut + +our $VERSION = '0.18'; + +=head1 VERSION + +Version 0.18 + +=head1 SYNOPSIS + +This module connects to the i3 window manager using the UNIX socket based +IPC interface it provides (if enabled in the configuration file). You can +then subscribe to events or send messages and receive their replies. + + use AnyEvent::I3 qw(:all); + + my $i3 = i3(); + + $i3->connect->recv or die "Error connecting"; + say "Connected to i3"; + + my $workspaces = $i3->message(TYPE_GET_WORKSPACES)->recv; + say "Currently, you use " . @{$workspaces} . " workspaces"; + +...or, using the sugar methods: + + use AnyEvent::I3; + + my $workspaces = i3->get_workspaces->recv; + say "Currently, you use " . @{$workspaces} . " workspaces"; + +A somewhat more involved example which dumps the i3 layout tree whenever there +is a workspace event: + + use Data::Dumper; + use AnyEvent; + use AnyEvent::I3; + + my $i3 = i3(); + + $i3->connect->recv or die "Error connecting to i3"; + + $i3->subscribe({ + workspace => sub { + $i3->get_tree->cb(sub { + my ($tree) = @_; + say "tree: " . Dumper($tree); + }); + } + })->recv->{success} or die "Error subscribing to events"; + + AE::cv->recv + +=head1 EXPORT + +=head2 $i3 = i3([ $path ]); + +Creates a new C object and returns it. + +C 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 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 object and returns it. + +C 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 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 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 to i3, possibly containing the data +structure C (or C, encoded as utf8, if C 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<< >> + +=head1 BUGS + +Please report any bugs or feature requests to C, or through the web interface at +L. 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 + +=item * The i3 window manager website + +L + +=back + + +=head1 ACKNOWLEDGEMENTS + + +=head1 LICENSE AND COPYRIGHT + +Copyright 2010-2012 Michael Stapelberg. + +This program is free software; you can redistribute it and/or modify it +under the terms of either: the GNU General Public License as published +by the Free Software Foundation; or the Artistic License. + +See http://dev.perl.org/licenses/ for more information. + + +=cut + +1; # End of AnyEvent::I3 diff --git a/AnyEvent-I3/t/00-load.t b/AnyEvent-I3/t/00-load.t new file mode 100644 index 00000000..4bf6151e --- /dev/null +++ b/AnyEvent-I3/t/00-load.t @@ -0,0 +1,10 @@ +#!perl -T + +use Test::More tests => 1; + +BEGIN { + use_ok( 'AnyEvent::I3' ) || print "Bail out! +"; +} + +diag( "Testing AnyEvent::I3 $AnyEvent::I3::VERSION, Perl $], $^X" ); diff --git a/AnyEvent-I3/t/01-workspaces.t b/AnyEvent-I3/t/01-workspaces.t new file mode 100644 index 00000000..f3206d89 --- /dev/null +++ b/AnyEvent-I3/t/01-workspaces.t @@ -0,0 +1,29 @@ +#!perl -T +# vim:ts=4:sw=4:expandtab + +use Test::More tests => 3; +use AnyEvent::I3; +use AnyEvent; + +my $i3 = i3(); +my $cv = AnyEvent->condvar; + +# Try to connect to i3 +$i3->connect->cb(sub { my ($v) = @_; $cv->send($v->recv) }); + +# But cancel if we are not connected after 0.5 seconds +my $t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0) }); +my $connected = $cv->recv; + +SKIP: { + skip 'No connection to i3', 3 unless $connected; + + my $workspaces = $i3->message(1)->recv; + isa_ok($workspaces, 'ARRAY'); + + ok(@{$workspaces} > 0, 'More than zero workspaces found'); + + ok(defined(@{$workspaces}[0]->{num}), 'JSON deserialized'); +} + +diag( "Testing AnyEvent::I3 $AnyEvent::I3::VERSION, Perl $], $^X" ); diff --git a/AnyEvent-I3/t/02-sugar.t b/AnyEvent-I3/t/02-sugar.t new file mode 100644 index 00000000..a3e2cc79 --- /dev/null +++ b/AnyEvent-I3/t/02-sugar.t @@ -0,0 +1,29 @@ +#!perl -T +# vim:ts=4:sw=4:expandtab + +use Test::More tests => 3; +use AnyEvent::I3; +use AnyEvent; + +my $i3 = i3(); +my $cv = AnyEvent->condvar; + +# Try to connect to i3 +$i3->connect->cb(sub { my ($v) = @_; $cv->send($v->recv) }); + +# But cancel if we are not connected after 0.5 seconds +my $t = AnyEvent->timer(after => 0.5, cb => sub { $cv->send(0) }); +my $connected = $cv->recv; + +SKIP: { + skip 'No connection to i3', 3 unless $connected; + + my $workspaces = i3->get_workspaces->recv; + isa_ok($workspaces, 'ARRAY'); + + ok(@{$workspaces} > 0, 'More than zero workspaces found'); + + ok(defined(@{$workspaces}[0]->{num}), 'JSON deserialized'); +} + +diag( "Testing AnyEvent::I3 $AnyEvent::I3::VERSION, Perl $], $^X" ); diff --git a/AnyEvent-I3/t/boilerplate.t b/AnyEvent-I3/t/boilerplate.t new file mode 100644 index 00000000..effb65b6 --- /dev/null +++ b/AnyEvent-I3/t/boilerplate.t @@ -0,0 +1,55 @@ +#!perl -T + +use strict; +use warnings; +use Test::More tests => 3; + +sub not_in_file_ok { + my ($filename, %regex) = @_; + open( my $fh, '<', $filename ) + or die "couldn't open $filename for reading: $!"; + + my %violated; + + while (my $line = <$fh>) { + while (my ($desc, $regex) = each %regex) { + if ($line =~ $regex) { + push @{$violated{$desc}||=[]}, $.; + } + } + } + + if (%violated) { + fail("$filename contains boilerplate text"); + diag "$_ appears on lines @{$violated{$_}}" for keys %violated; + } else { + pass("$filename contains no boilerplate text"); + } +} + +sub module_boilerplate_ok { + my ($module) = @_; + not_in_file_ok($module => + 'the great new $MODULENAME' => qr/ - The great new /, + 'boilerplate description' => qr/Quick summary of what the module/, + 'stub function definition' => qr/function[12]/, + ); +} + +TODO: { + local $TODO = "Need to replace the boilerplate text"; + + not_in_file_ok(README => + "The README is used..." => qr/The README is used/, + "'version information here'" => qr/to provide version information/, + ); + + not_in_file_ok(Changes => + "placeholder date/time" => qr(Date/time) + ); + + module_boilerplate_ok('lib/AnyEvent/I3.pm'); + + +} + diff --git a/AnyEvent-I3/t/manifest.t b/AnyEvent-I3/t/manifest.t new file mode 100644 index 00000000..45eb83fd --- /dev/null +++ b/AnyEvent-I3/t/manifest.t @@ -0,0 +1,13 @@ +#!perl -T + +use strict; +use warnings; +use Test::More; + +unless ( $ENV{RELEASE_TESTING} ) { + plan( skip_all => "Author tests not required for installation" ); +} + +eval "use Test::CheckManifest 0.9"; +plan skip_all => "Test::CheckManifest 0.9 required" if $@; +ok_manifest(); diff --git a/AnyEvent-I3/t/pod-coverage.t b/AnyEvent-I3/t/pod-coverage.t new file mode 100644 index 00000000..fc40a57c --- /dev/null +++ b/AnyEvent-I3/t/pod-coverage.t @@ -0,0 +1,18 @@ +use strict; +use warnings; +use Test::More; + +# Ensure a recent version of Test::Pod::Coverage +my $min_tpc = 1.08; +eval "use Test::Pod::Coverage $min_tpc"; +plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage" + if $@; + +# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version, +# but older versions don't recognize some common documentation styles +my $min_pc = 0.18; +eval "use Pod::Coverage $min_pc"; +plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage" + if $@; + +all_pod_coverage_ok(); diff --git a/AnyEvent-I3/t/pod.t b/AnyEvent-I3/t/pod.t new file mode 100644 index 00000000..ee8b18ad --- /dev/null +++ b/AnyEvent-I3/t/pod.t @@ -0,0 +1,12 @@ +#!perl -T + +use strict; +use warnings; +use Test::More; + +# Ensure a recent version of Test::Pod +my $min_tp = 1.22; +eval "use Test::Pod $min_tp"; +plan skip_all => "Test::Pod $min_tp required for testing POD" if $@; + +all_pod_files_ok(); diff --git a/I3_VERSION b/I3_VERSION index d4cfa42a..af1432e3 100644 --- a/I3_VERSION +++ b/I3_VERSION @@ -1 +1 @@ -4.13-non-git +4.14-non-git diff --git a/Makefile.am b/Makefile.am index c90e26c7..3ea300e5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -45,11 +45,17 @@ dist_xsessions_DATA = \ noinst_LIBRARIES = libi3.a -check_PROGRAMS = test.commands_parser test.config_parser +check_PROGRAMS = \ + test.commands_parser \ + test.config_parser \ + test.inject_randr15 check_SCRIPTS = \ testcases/complete-run.pl +check_DATA = \ + anyevent-i3.stamp + clean-check: rm -rf testsuite-* latest i3-cfg-for-* _Inline clean-local: clean-check @@ -94,7 +100,7 @@ EXTRA_DIST = \ I3_VERSION \ LICENSE \ PACKAGE-MAINTAINER \ - RELEASE-NOTES-4.13 \ + RELEASE-NOTES-4.14 \ generate-command-parser.pl \ parser-specs/commands.spec \ parser-specs/config.spec \ @@ -212,6 +218,7 @@ asciidoc_MANS = endif AM_CPPFLAGS = \ + -DSYSCONFDIR="\"$(sysconfdir)\"" \ -I$(top_builddir)/parser \ -I$(top_srcdir)/include \ @AX_EXTEND_SRCDIR_CPPFLAGS@ @@ -401,6 +408,19 @@ i3_config_wizard_i3_config_wizard_SOURCES = \ i3-config-wizard/main.c \ i3-config-wizard/xcb.h +test_inject_randr15_CPPFLAGS = \ + $(AM_CPPFLAGS) + +test_inject_randr15_CFLAGS = \ + $(AM_CFLAGS) \ + $(i3_CFLAGS) + +test_inject_randr15_SOURCES = \ + testcases/inject_randr1.5.c + +test_inject_randr15_LDADD = \ + $(i3_LDADD) + test_commands_parser_CPPFLAGS = \ $(AM_CPPFLAGS) \ -DTEST_PARSER @@ -457,7 +477,6 @@ i3_SOURCES = \ include/config_parser.h \ include/con.h \ include/data.h \ - include/debug.h \ include/display_version.h \ include/ewmh.h \ include/fake_outputs.h \ @@ -502,7 +521,6 @@ i3_SOURCES = \ src/config.c \ src/config_directives.c \ src/config_parser.c \ - src/debug.c \ src/display_version.c \ src/ewmh.c \ src/fake_outputs.c \ @@ -558,6 +576,15 @@ i3-config-parser.stamp: parser/$(dirstamp) generate-command-parser.pl parser-spe $(AM_V_at) mv GENERATED_config_* $(top_builddir)/parser $(AM_V_at) touch $@ +################################################################################ +# AnyEvent-I3 build process +################################################################################ + +anyevent-i3.stamp: AnyEvent-I3/lib/AnyEvent/I3.pm + $(AM_V_BUILD) (cd $(top_srcdir)/AnyEvent-I3 && perl Makefile.PL && make) + $(AM_V_at) touch $@ + CLEANFILES = \ i3-command-parser.stamp \ - i3-config-parser.stamp + i3-config-parser.stamp \ + anyevent-i3.stamp diff --git a/RELEASE-NOTES-4.13 b/RELEASE-NOTES-4.13 deleted file mode 100644 index 0e854569..00000000 --- a/RELEASE-NOTES-4.13 +++ /dev/null @@ -1,114 +0,0 @@ - - ┌────────────────────────────┐ - │ Release notes for i3 v4.13 │ - └────────────────────────────┘ - -This is i3 v4.13. This version is considered stable. All users of i3 are -strongly encouraged to upgrade. - -For users, there are two changes to be aware of: - -1. The X server DPI is read from the Xft.dpi X resource (if available). - Previously, i3 used to directly look at the X server’s DPI (based on screen - resolution and physical size). Looking at Xft.dpi is more consistent with - other software, more likely to be correct (because it’s user-specified and - not read from possibly broken hardware information) and allows users to - override the value. - -2. It is now possible to set config file variables from X resources using the - “set_from_resource” directive. This allows users to have a single source of - truth for e.g. theming X11 applications (specify “*color0: #121212” and have - it apply to URxvt and your i3 config). - -For packagers, there are three changes that likely require action: - -1. cairo/pango are now required dependencies, as announced in the i3 v4.12 - release notes. - -2. The aforementioned “set_from_resource” feature requires the new dependency - libxcb-util-xrm. - -3. i3 now uses the GNU build system (autotools). Please see - https://github.com/i3/i3/commit/4a52a7e9fb6fb2e1f0256b2e086cfa313f411cd8 for - a lot more details about the rationale and what this means for your package. - Bottomline, things should get simpler for you, though :). - - ┌────────────────────────────┐ - │ Changes in i3 v4.13 │ - └────────────────────────────┘ - - • build: wire up version handling for non-release tarballs (as opposed to git - checkouts) - • build: switch to the GNU build system - • i3bar: disable pango markup for plain-text input - • man/i3-msg: point out default ipc message type - • config: introduce support for specifying variables from X resources - • config: ensure variables match on longest-length, eliminating problems - where one variable was a prefix of another - • config: do not count '\' in comment lines as line continuation - • ipc: introduce a new GET_BINDING_MODES command - • ipc: implement new window::mark event - • ipc: add “output” to IPC events referencing a container - • make fullscreen windows open on the output which is indicated by their - geometry (fixes LibreOffice Impress multi-monitor presentations) - • focus newly managed windows only if they don’t use the globally active - input mode (fixes issues with RubyMine) - • remove title indentation in nested containers (rationale was unclear, - nobody spoke up when we asked about the feature on i3-discuss) - • use the last known timestamp when calling xcb_set_input_focus (might fix - rare race conditions in focus handling) - • introduce the “smart” option for hide_edge_borders, which will hide borders - when there is precisely one window on the workspace - • handle _MOTIF_WM_HINTS changes (_MOTIF_WM_HINTS were previously only - considered when managing a new window) - • don’t change border style if BS_NORMAL is requested in _MOTIF_WM_HINTS - • only add numlock fallback for keybindings where necessary (allows users to - correctly bind keys on the numpad) - • do not match docks in config and command criteria - • get DPI from the Xft.dpi resource instead of directly looking at the screen - resolution/size - • handle _NET_ACTIVE_WINDOW for scratchpad windows (for pagers) - • set _NET_WM_DESKTOP to sticky for scratchpad windows - • add new criteria “tiling” and “floating” - • implement special output name “current” for commands - • handle ResizeRequests for tray clients (fixes VLC tray icon) - - ┌────────────────────────────┐ - │ Bugfixes │ - └────────────────────────────┘ - - • i3bar: fix crash when the I3SOCK environment variable is present - • i3-dmenu-desktop: do not die on failed open - • i3-input: properly position in non-standard cases (fixes an issue where - i3-input would launch off-screen) - • i3-save-tree: rename “mark” to “marks” to reflect our recent change to - allow multiple marks - • mouse bindings: only grab the mouse buttons that need to be grabbed - • no_focus: correctly count the number of windows (makes no_focus work with - tabbed/stacked workspace layouts). - • properly close disabled outputs restored during a restart (this fixes state - handling when RandR changes happen during i3 restarts) - • don’t trigger bindings on window border clicks unless --border was - specified for the binding - • traverse numbered workspaces in correct order - • fix transition from named to numbered workspaces in “workspace next|prev” - • avoid setting urgency hint on content containers and above (fixes crashes) - • don’t trigger unrelated key bindings for --release bindings - • fix colormap handling for containers (fixes taking screenshots using xwd) - • check output crossing on ENTER_NOTIFY to dockarea (fixes pointer jumping) - • fix a use-after-free bug (fixes “floating enable” on single split windows) - - ┌────────────────────────────┐ - │ Thanks! │ - └────────────────────────────┘ - -Thanks for testing, bugfixes, discussions and everything I forgot go out to: - - Benedikt Heine, Cedric Buissart, Chih-Chyuan Hwang, Denton Liu, eplanet, Eric - Engeström, EvilPudding, Ferdinand Bachmann, Hong, Ingo Bürk, Jakob Schnell, - Jakub Wilk, johannes karoff, Johannes Lange, joshrosso, Julien Lequertier, - Kacper Kowalik, Kenneth Lyons, Kyle Kneitinger, madroach, Michael Vetter, - Nathan Schulte, Øsse, Peder Stray, Tony Crisci, Trevor Merrifield, wentasah, - yshui, Zamarin Arthur - --- Michael Stapelberg, 2016-11-08 diff --git a/RELEASE-NOTES-4.14 b/RELEASE-NOTES-4.14 new file mode 100644 index 00000000..2ab058f2 --- /dev/null +++ b/RELEASE-NOTES-4.14 @@ -0,0 +1,94 @@ + + ┌────────────────────────────┐ + │ Release notes for i3 v4.14 │ + └────────────────────────────┘ + +This is i3 v4.14. This version is considered stable. All users of i3 are +strongly encouraged to upgrade. + +Aside from many bug and documentation fixes, the “swap” command is a notable +addition of this release. As is almost tradition at this point, keybinding +handling has seen some fixes as well. A noticeable change for users with such +monitors is i3’s support for RandR 1.5, which transparently supports the TILE +property of first-gen 4K monitors and current 5K or 8K monitors. + + ┌────────────────────────────┐ + │ Changes in i3 v4.14 │ + └────────────────────────────┘ + + • build: link libiconv explicitly for systems which need it + • build: move AnyEvent-I3 into the i3 repository + • docs/hacking-howto: add compilation instructions + • docs/ipc: add missing cases to the workspace event + • docs/ipc: document the “primary” field of the OUTPUTS reply + • docs/ipc: replace Go IPC library with a maintained one + • docs/ipc: add link to the ocaml-i3ipc library + • docs/ipc: fix invalid trailing commas in JSON examples + • docs/layout-saving: add section about troubleshooting window titles + • docs/testsuite: update for the move to autotools + • docs/userguide: clarify the move command syntax + • docs/userguide: correct “Esc” to “Escape” + • docs/userguide: clarify focus_follows_mouse behavior + • docs/userguide: expand on combining “workspace number” with a name + • docs/userguide: mention the magic v4 config marker + • man/i3.man: correct configuration lookup order + • i3bar, i3-config-wizard, i3-nagbar: use the Xft.dpi setting (see 4.13 notes) + • i3bar: restart bar status command on reload if it changed + • i3bar: treat left/right scrolling like up/down scrolling + • i3bar: accept “primary” in the “output” configuration directive + • i3-input: do not set input focus, grabbing the keyboard suffices + • i3-msg: return an exit code when missing the -t argument + • i3-sensible-editor: correct “mc-edit” to “mcedit” + • i3-sensible-terminal: add lilyterm, tilix, terminix, konsole + • respect SYSCONFDIR when looking for the default xdg directory + • use RandR 1.5 to query screens, supporting the TILE property commonly used + by multi-stream transport (MST) monitors, such as first-gen 4K monitors, or + current 5K and 8K monitors + • respect minimum size hints for floating windows + • support the _NET_MOVERESIZE_WINDOW client message (for e.g. wmctrl) + • validate binding modes are not defined more than once + • only react to the last ExposeEvent in a series of events + • add the shutdown IPC event (upon “restart” or “exit”) + • treat left/right scrolling like up/down scrolling (on window titles) + • make the “layout toggle” command optionally take a sequence of layouts + • introduce --exclude-titlebar flag for mouse bindings + • introduce the “swap” command + • support the primary output in the “focus” and “move” commands + • compare keybinding modifiers for equality, not subset + • introduce the GET_CONFIG ipc request (i3-msg -t get_config) + • start i3-nagbar when encountering invalid set statements + • focus windows upon ConfigureWindow requests with stack-mode=Above + + ┌────────────────────────────┐ + │ Bugfixes │ + └────────────────────────────┘ + + • i3bar: correct the color codes used for statusline errors + • i3bar: avoid freeze after VisibilityNotify + • i3-dmenu-desktop: fix quoted command names + • i3-dmenu-desktop: avoid adding items multiple times + • fix various X11 resource leaks, memory leaks and memory errors + • fix IPC success reply for the workspace command + • report errors during logfile creation + • fix the signal handler being blank + • display marks and the title even if the title is empty (for title_format) + • fix changing workspace layout from stacked/tabbed for empty workspaces + • add numlock fallback to “bindcode” where necessary + • fix a crash on restart when using marks + • fix renaming workspaces when the new name starts with “to” + • respect dont_warp flag when moving containers + + ┌────────────────────────────┐ + │ Thanks! │ + └────────────────────────────┘ + +Thanks for testing, bugfixes, discussions and everything I forgot go out to: + + akash akya, Armaël Guéneau, Baptiste Daroussin, Chih-Chyuan Hwang, cresh, + David Jimenez Sequero, Franz König, fred777, Ingo Bürk, Jakub Wilk, + Jens-Wolfhard Schicke-Uffmann, Johannes Lange, lasers, lebenlechzer, + loungecube, Maarten Dirkse, Manuel Mendez, Max Fisher, Mihai Coman, Nathan + Schulte, s3rb31, Sebastian Larsson, Stefan Hagen, Tobias Hänel, Tony Crisci, + Trevor Merrifield, Zbyněk Moravec + +-- Michael Stapelberg, 2017-09-04 diff --git a/configure.ac b/configure.ac index 1a91b3b1..7d274e36 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Run autoreconf -fi to generate a configure script from this file. AC_PREREQ([2.69]) -AC_INIT([i3], [4.13], [https://github.com/i3/i3/issues]) +AC_INIT([i3], [4.14], [https://github.com/i3/i3/issues]) # For AX_EXTEND_SRCDIR AX_ENABLE_BUILDDIR AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2]) @@ -15,6 +15,12 @@ AC_CONFIG_SRCDIR([libi3/ipc_recv_message.c]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIR([m4]) +dnl Verify macros defined in m4/ such as AX_SANITIZERS are not present in the +dnl output, i.e. are replaced as expected. This line results in a better error +dnl message when using aclocal < 1.13 (which does not understand +dnl AC_CONFIG_MACRO_DIR) without passing the -I m4 parameter. +m4_pattern_forbid([AX_SANITIZERS]) + # Verify we are using GNU make because we use '%'-style pattern rules in # Makefile.am, which are a GNU make extension. Pull requests to replace # '%'-style pattern rules with a more portable alternative are welcome. @@ -25,8 +31,8 @@ AX_EXTEND_SRCDIR AS_IF([test -d ${srcdir}/.git], [ - VERSION="$(git describe --tags --abbrev=0)" - I3_VERSION="$(git describe --tags --always) ($(git log --pretty=format:%cd --date=short -n1), branch \\\"$(git describe --tags --always --all | sed s:heads/::)\\\")" + VERSION="$(git -C ${srcdir} describe --tags --abbrev=0)" + I3_VERSION="$(git -C ${srcdir} describe --tags --always) ($(git -C ${srcdir} log --pretty=format:%cd --date=short -n1), branch \\\"$(git -C ${srcdir} describe --tags --always --all | sed s:heads/::)\\\")" # Mirrors what libi3/is_debug_build.c does: is_release=$(test $(echo "${I3_VERSION}" | cut -d '(' -f 1 | wc -m) -lt 10 && echo yes || echo no) ], @@ -53,8 +59,6 @@ AX_CHECK_ENABLE_DEBUG([yes], , [UNUSED_NDEBUG], [$is_release]) AC_PROG_CC_C99 -AC_DEFINE_UNQUOTED(SYSCONFDIR, "`eval echo $sysconfdir`", [Location of system configuration files]) - # For strnlen() and vasprintf(). AC_USE_SYSTEM_EXTENSIONS @@ -79,6 +83,8 @@ AC_SEARCH_LIBS([ev_run], [ev], , [AC_MSG_FAILURE([cannot find the required ev_ru AC_SEARCH_LIBS([shm_open], [rt]) +AC_SEARCH_LIBS([iconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])]) + AX_PTHREAD dnl Each prefix corresponds to a source tarball which users might have @@ -146,8 +152,9 @@ else print_BUILD_MANS=no fi -git_dir=`git rev-parse --git-dir 2>/dev/null` -if test -n "$git_dir"; then +in_git_worktree=`git rev-parse --is-inside-work-tree 2>/dev/null` +if [[ "$in_git_worktree" = "true" ]]; then + git_dir=`git rev-parse --git-dir 2>/dev/null` srcdir=`dirname "$git_dir"` exclude_dir=`pwd | sed "s,^$srcdir,,g"` if ! grep -q "^$exclude_dir" "$git_dir/info/exclude"; then diff --git a/debian/changelog b/debian/changelog index bb08c7fd..9333c665 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +i3-wm (4.13.1-1) unstable; urgency=medium + + * New upstream release. + + -- Michael Stapelberg Tue, 08 Nov 2016 21:31:13 +0100 + i3-wm (4.13-1) unstable; urgency=medium * New upstream release. diff --git a/docs/hacking-howto b/docs/hacking-howto index 74a690e7..52436da6 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -119,9 +119,6 @@ src/config.c:: Contains all functions handling the configuration file (calling the parser src/config_parser.c) with the correct path, switching key bindings mode). -src/debug.c:: -Contains debugging functions to print unhandled X events. - src/ewmh.c:: Functions to get/set certain EWMH properties easily. @@ -993,6 +990,47 @@ New features are only found in the “next” branch. Therefore, if you are work on a new feature, use the “next” branch. If you are working on a bugfix, use the “next” branch, too, but make sure your code also works on “master”. +=== How to build? + +You can build i3 like you build any other software package which uses autotools. +Here’s a memory refresher: + + $ autoreconf -fi + $ mkdir -p build && cd build + $ ../configure + $ make -j8 + +(The autoreconf -fi step is unnecessary if you are building from a release tarball, + but shouldn’t hurt either.) + +==== Build system features + +* We use the AX_ENABLE_BUILDDIR macro to enforce builds happening in a separate + directory. This is a prerequisite for the AX_EXTEND_SRCDIR macro and building + in a separate directory is common practice anyway. In case this causes any + trouble when packaging i3 for your distribution, please open an issue. + +* “make check” runs the i3 testsuite. See docs/testsuite for details. + +* “make distcheck” (runs testsuite on “make dist” result, tiny bit quicker + feedback cycle than waiting for the travis build to catch the issue). + +* “make uninstall” (occasionally requested by users who compile from source) + +* “make” will build manpages/docs by default if the tools are installed. + Conversely, manpages/docs are not tried to be built for users who don’t want + to install all these dependencies to get started hacking on i3. + +* non-release builds will enable address sanitizer by default. Use the + --disable-sanitizers configure option to turn off all sanitizers, and see + --help for available sanitizers. + +* Support for pre-compiled headers (PCH) has been dropped for now in the + interest of simplicity. If you need support for PCH, please open an issue. + +* Coverage reports are now generated using “make check-code-coverage”, which + requires specifying --enable-code-coverage when calling configure. + == Thought experiments In this section, we collect thought experiments, so that we don’t forget our diff --git a/docs/ipc b/docs/ipc index fda289a0..65723577 100644 --- a/docs/ipc +++ b/docs/ipc @@ -232,6 +232,8 @@ name (string):: The name of this output (as seen in +xrandr(1)+). Encoded in UTF-8. active (boolean):: Whether this output is currently active (has a valid mode). +primary (boolean):: + Whether this output is currently the primary output. current_workspace (string):: The name of the current workspace that is visible on this output. +null+ if the output is not active. @@ -262,7 +264,7 @@ rect (map):: "y": 0, "width": 1280, "height": 1024 - }, + } } ] ------------------- @@ -390,7 +392,7 @@ JSON dump: "y": 0, "width": 1280, "height": 0 - }, + } }, { @@ -671,6 +673,8 @@ barconfig_update (4):: binding (5):: Sent when a configured command binding is triggered with the keyboard or mouse +shutdown (6):: + Sent when the ipc shuts down because of a restart or exit by user command *Example:* -------------------------------------------------------------------- @@ -694,9 +698,9 @@ if ($is_event) { This event consists of a single serialized map containing a property +change (string)+ which indicates the type of the change ("focus", "init", -"empty", "urgent"). A +current (object)+ property will be present with the -affected workspace whenever the type of event affects a workspace (otherwise, -it will be +null). +"empty", "urgent", "reload", "rename", "restored", "move"). A ++current (object)+ property will be present with the affected workspace +whenever the type of event affects a workspace (otherwise, it will be +null). When the change is "focus", an +old (object)+ property will be present with the previous workspace. When the first switch occurs (when i3 focuses the @@ -791,7 +795,7 @@ same as a +GET_BAR_CONFIG+ reply for the bar with the given id. === binding event This event consists of a single serialized map reporting on the details of a -binding that ran a command because of user input. The +change (sring)+ field +binding that ran a command because of user input. The +change (string)+ field indicates what sort of binding event was triggered (right now it will always be +"run"+ but may be expanded in the future). @@ -829,6 +833,20 @@ input_type (string):: } --------------------------- +=== shutdown event + +This event is triggered when the connection to the ipc is about to shutdown +because of a user action such as a +restart+ or +exit+ command. The +change +(string)+ field indicates why the ipc is shutting down. It can be either ++"restart"+ or +"exit"+. + +*Example:* +--------------------------- +{ + "change": "restart" +} +--------------------------- + == See also (existing libraries) [[libraries]] @@ -843,7 +861,7 @@ C:: C++:: * https://github.com/drmgc/i3ipcpp Go:: - * https://github.com/proxypoke/i3ipc + * https://github.com/mdirkse/i3ipc-go JavaScript:: * https://github.com/acrisci/i3ipc-gjs Lua:: @@ -859,3 +877,5 @@ Ruby:: * https://github.com/badboy/i3-ipc (not maintained) Rust:: * https://github.com/tmerr/i3ipc-rs +OCaml:: + * https://github.com/Armael/ocaml-i3ipc diff --git a/docs/layout-saving b/docs/layout-saving index 5897036e..6ca08fa2 100644 --- a/docs/layout-saving +++ b/docs/layout-saving @@ -259,3 +259,27 @@ container: ] } -------------------------------------------------------------------------------- + +=== Placeholders using window title matches don't swallow the window + +If you use the +title+ attribute to match a window and find that it doesn't +work or only works sometimes, the reason might be that the application sets the +title only after making the window visible. This will be especially true for +programs running inside terminal emulators, e.g., +urxvt -e irssi+ when +matching on +title: "irssi"+. + +One way to deal with this is to not rely on the title, but instead use, e.g., +the +instance+ attribute and running the program to set this window instance to +that value: + +-------------------------------------------------------------------------------- +# Run irssi via +# urxvt -name "irssi-container" -e irssi + +"swallows": [ + { + "class": "URxvt", + "instance": "irssi-container" + } +] +-------------------------------------------------------------------------------- diff --git a/docs/testsuite b/docs/testsuite index 71c6a427..795be042 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -82,6 +82,8 @@ The tests additionally require +Xephyr(1)+ to run a nested X server. Install $ cd ~/i3/testcases $ sudo apt-get install cpanminus $ sudo cpanm . +$ cd ~/i3/AnyEvent-I3 +$ sudo cpanm . -------------------------------------------------------------------------------- If you don’t want to use cpanminus for some reason, the same works with cpan: @@ -90,6 +92,8 @@ If you don’t want to use cpanminus for some reason, the same works with cpan: -------------------------------------------------------------------------------- $ cd ~/i3/testcases $ sudo cpan . +$ cd ~/i3/AnyEvent-I3 +$ sudo cpan . -------------------------------------------------------------------------------- In case you don’t have root permissions, you can also install into your home @@ -112,9 +116,20 @@ the tests without an X session with Xvfb, such as with +xvfb-run ./complete-run+. This will also speed up the tests significantly especially on machines without a powerful video card. -.Example invocation of complete-run.pl+ +.Example invocation of +complete-run.pl+ --------------------------------------- -$ cd ~/i3/testcases +$ cd ~/i3 + +$ autoreconf -fi + +$ mkdir -p build && cd build + +$ ../configure + +$ make -j8 +# output omitted because it is very long + +$ cd testcases $ ./complete-run.pl # output omitted because it is very long @@ -160,6 +175,41 @@ $ ./complete-run.pl --parallel=1 --keep-xserver-output This will show the output of Xephyr, which is the X server implementation we use for testing. +===== make command: +make check+ +Make check runs the i3 testsuite. +You can still use ./testcases/complete-run.pl to get the interactive progress output. + +.Example invocation of +make check+ +--------------------------------------- +$ cd ~/i3 + +$ autoreconf -fi + +$ mkdir -p build && cd build + +$ ../configure + +$ make -j8 +# output omitted because it is very long + +$ make check +# output omitted because it is very long +PASS: testcases/complete-run.pl +============================================================================ +Testsuite summary for i3 4.13 +============================================================================ +# TOTAL: 1 +# PASS: 1 +# SKIP: 0 +# XFAIL: 0 +# FAIL: 0 +# XPASS: 0 +# ERROR: 0 +============================================================================ + +$ less test-suite.log +---------------------------------------- + ==== Coverage testing Coverage testing is possible with +lcov+, the front-end for GCC's coverage diff --git a/docs/userguide b/docs/userguide index acdc0a58..0d5de3b9 100644 --- a/docs/userguide +++ b/docs/userguide @@ -297,6 +297,15 @@ keyboard layout. To start the wizard, use the command +i3-config-wizard+. Please note that you must not have +~/.i3/config+, otherwise the wizard will exit. +Since i3 4.0, a new configuration format is used. i3 will try to automatically +detect the format version of a config file based on a few different keywords, +but if you want to make sure that your config is read with the new format, +include the following line in your config file: + +--------------------- +# i3 config file (v4) +--------------------- + === Comments It is possible and recommended to use comments in your configuration file to @@ -412,9 +421,9 @@ button in the scope of the clicked container (see <>). You can configure mouse bindings in a similar way to key bindings. *Syntax*: -------------------------------------------------------------------------------- -bindsym [--release] [--border] [--whole-window] [+]button command -------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------------------- +bindsym [--release] [--border] [--whole-window] [--exclude-titlebar] [+]button command +---------------------------------------------------------------------------------------------------- By default, the binding will only run when you click on the titlebar of the window. If the +--release+ flag is given, it will run when the mouse button @@ -424,6 +433,9 @@ If the +--whole-window+ flag is given, the binding will also run when any part of the window is clicked, with the exception of the border. To have a bind run when the border is clicked, specify the +--border+ flag. +If the +--exclude-titlebar+ flag is given, the titlebar will not be considered +for the keybinding. + *Examples*: -------------------------------- # The middle button over a titlebar kills the window @@ -479,7 +491,7 @@ mode *Example*: ------------------------------------------------------------------------ -# Press $mod+o followed by either f, t, Esc or Return to launch firefox, +# Press $mod+o followed by either f, t, Escape or Return to launch firefox, # thunderbird or return to the default mode, respectively. set $mode_launcher Launch: [f]irefox [t]hunderbird bindsym $mod+o mode "$mode_launcher" @@ -488,7 +500,7 @@ mode "$mode_launcher" { bindsym f exec firefox bindsym t exec thunderbird - bindsym Esc mode "default" + bindsym Escape mode "default" bindsym Return mode "default" } ------------------------------------------------------------------------ @@ -946,12 +958,12 @@ the next section. === Focus follows mouse -By default, window focus follows your mouse movements. However, if you have a -setup where your mouse usually is in your way (like a touchpad on your laptop -which you do not want to disable completely), you might want to disable 'focus -follows mouse' and control focus only by using your keyboard. The mouse will -still be useful inside the currently active window (for example to click on -links in your browser window). +By default, window focus follows your mouse movements as the mouse crosses +window borders. However, if you have a setup where your mouse usually is in your +way (like a touchpad on your laptop which you do not want to disable +completely), you might want to disable 'focus follows mouse' and control focus +only by using your keyboard. The mouse will still be useful inside the +currently active window (for example to click on links in your browser window). *Syntax*: -------------------------- @@ -1127,9 +1139,9 @@ none:: [[show_marks]] === Drawing marks on window decoration -If activated, marks on windows are drawn in their window decoration. However, -any mark starting with an underscore in its name (+_+) will not be drawn even if -this option is activated. +If activated, marks (see <>) on windows are drawn in their window +decoration. However, any mark starting with an underscore in its name (+_+) will +not be drawn even if this option is activated. The default for this option is +yes+. @@ -1378,7 +1390,7 @@ directive multiple times. *Syntax*: --------------- -output +output primary| --------------- *Example*: @@ -1400,7 +1412,19 @@ bar { statusline #ffffff } } + +# show bar on the primary monitor and on HDMI2 +bar { + output primary + output HDMI2 + status_command i3status +} + ------------------------------- +Note that you might not have a primary output configured yet. To do so, run: +------------------------- +xrandr --output --primary +------------------------- === Tray output @@ -1802,7 +1826,8 @@ The +toggle+ option will toggle the orientation of the split container if it contains a single window. Otherwise it makes the current window a split container with opposite orientation compared to the parent container. Use +layout toggle split+ to change the layout of any split container from -splitv to splith or vice-versa. +splitv to splith or vice-versa. You can also define a custom sequence of layouts +to cycle through with +layout toggle+, see <>. *Syntax*: -------------------------------- @@ -1822,6 +1847,11 @@ Use +layout toggle split+, +layout stacking+, +layout tabbed+, +layout splitv+ or +layout splith+ to change the current container layout to splith/splitv, stacking, tabbed layout, splitv or splith, respectively. +Specify up to four layouts after +layout toggle+ to cycle through them. Every +time the command is executed, the layout specified after the currently active +one will be applied. If the currently active layout is not in the list, the +first layout in the list will be activated. + To make the current window (!) fullscreen, use +fullscreen enable+ (or +fullscreen enable global+ for the global mode), to leave either fullscreen mode use +fullscreen disable+, and to toggle between these two states use @@ -1834,6 +1864,7 @@ enable+ respectively +floating disable+ (or +floating toggle+): -------------------------------------------- layout default|tabbed|stacking|splitv|splith layout toggle [split|all] +layout toggle [split|tabbed|stacking|splitv|splith] [split|tabbed|stacking|splitv|splith]… -------------------------------------------- *Examples*: @@ -1848,6 +1879,15 @@ bindsym $mod+x layout toggle # Toggle between stacking/tabbed/splith/splitv: bindsym $mod+x layout toggle all +# Toggle between stacking/tabbed/splith: +bindsym $mod+x layout toggle stacking tabbed splith + +# Toggle between splitv/tabbed +bindsym $mod+x layout toggle splitv tabbed + +# Toggle between last split layout/tabbed/stacking +bindsym $mod+x layout toggle split tabbed stacking + # Toggle fullscreen bindsym $mod+f fullscreen toggle @@ -1882,7 +1922,7 @@ output:: ---------------------------------------------- focus left|right|down|up focus parent|child|floating|tiling|mode_toggle -focus output left|right|up|down| +focus output left|right|up|down|primary| ---------------------------------------------- *Examples*: @@ -1904,8 +1944,17 @@ bindsym $mod+x focus output right # Focus the big output bindsym $mod+x focus output HDMI-2 + +# Focus the primary output +bindsym $mod+x focus output primary ------------------------------------------------- +------------------------------- +Note that you might not have a primary output configured yet. To do so, run: +------------------------- +xrandr --output --primary +------------------------- + === Moving containers Use the +move+ command to move a container. @@ -1921,7 +1970,8 @@ move [ px] # Moves the container either to a specific location # or to the center of the screen. If 'absolute' is # used, it is moved to the center of all outputs. -move [absolute] position [[ px] [ px]|center] +move [absolute] position [px] [px] +move [absolute] position center # Moves the container to the current position of the # mouse cursor. Only affects floating containers. @@ -1947,6 +1997,39 @@ bindsym $mod+c move absolute position center bindsym $mod+m move position mouse ------------------------------------------------------- +=== Swapping containers + +Two containers can be swapped (i.e., move to each other's position) by using +the +swap+ command. They will assume the position and geometry of the container +they are swapped with. + +The first container to participate in the swapping can be selected through the +normal command criteria process with the focused window being the usual +fallback if no criteria are specified. The second container can be selected +using one of the following methods: + ++id+:: The X11 window ID of a client window. ++con_id+:: The i3 container ID of a container. ++mark+:: A container with the specified mark, see <>. + +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 +---------------------------------------- + +*Examples*: +----------------------------------------------------------------- +# Swaps the focused container with the container marked »swapee«. +swap container with mark swapee + +# Swaps container marked »A« and »B« +[con_mark="^A$"] swap container with mark B +----------------------------------------------------------------- + === Sticky floating windows If you want a window to stick to the glass, i.e., have it stay on screen even @@ -2089,6 +2172,23 @@ i3-msg 'rename workspace to "2: mail"' bindsym $mod+r exec i3-input -F 'rename workspace to "%s"' -P 'New name: ' -------------------------------------------------------------------------- +If you want to rename workspaces on demand while keeping the navigation stable, +you can use a setup like this: + +*Example*: +------------------------- +bindsym $mod+1 workspace number "1: www" +bindsym $mod+2 workspace number "2: mail" +... +------------------------- + +If a workspace does not exist, the command +workspace number "1: mail"+ will +create workspace "1: mail". + +If a workspace with number 1 does already exist, the command will switch to this +workspace and ignore the text part. So even when the workspace has been renamed +to "1: web", the above command will still switch to it. + === Moving workspaces to a different screen See <> for how to move a container/workspace to a different @@ -2104,8 +2204,8 @@ To move a container to another RandR output (addressed by names like +LVDS1+ or *Syntax*: ------------------------------------------------------------ -move container to output left|right|down|up|current| -move workspace to output left|right|down|up|current| +move container to output left|right|down|up|current|primary| +move workspace to output left|right|down|up|current|primary| ------------------------------------------------------------ *Examples*: @@ -2116,8 +2216,17 @@ bindsym $mod+x move workspace to output right # Put this window on the presentation output. bindsym $mod+x move container to output VGA1 + +# Put this window on the primary output. +bindsym $mod+x move container to output primary -------------------------------------------------------- +------------------------------- +Note that you might not have a primary output configured yet. To do so, run: +------------------------- +xrandr --output --primary +------------------------- + === Moving containers/windows to marks To move a container to another container with a specific mark (see <>), diff --git a/generate-command-parser.pl b/generate-command-parser.pl index 6208945d..a7687c7b 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -65,7 +65,7 @@ for my $line (@raw_lines) { my $current_state; for my $line (@lines) { - if (my ($state) = ($line =~ /^state ([A-Z_]+):$/)) { + if (my ($state) = ($line =~ /^state ([A-Z0-9_]+):$/)) { #say "got a new state: $state"; $current_state = $state; } else { @@ -155,12 +155,20 @@ for my $state (@keys) { # to generate a format string. The format uses %d for s, # literal numbers or state IDs and %s for NULL, 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'; diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index 2bb43270..dd58fd12 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -69,10 +69,16 @@ #include "xcb.h" #include "libi3.h" +#define TEXT_PADDING logical_px(4) +#define WIN_POS_X logical_px(490) +#define WIN_POS_Y logical_px(297) +#define WIN_WIDTH logical_px(300) +#define WIN_HEIGHT (15 * font.height + TEXT_PADDING) + +#define col_x(col) \ + (((col)-1) * char_width + TEXT_PADDING) #define row_y(row) \ - (((row)-1) * font.height + logical_px(4)) -#define window_height() \ - (row_y(15) + font.height) + (((row)-1) * font.height + TEXT_PADDING) enum { STEP_WELCOME, STEP_GENERATE } current_step = STEP_WELCOME; @@ -90,8 +96,7 @@ static i3Font bold_font; static int char_width; static char *socket_path; static xcb_window_t win; -static xcb_pixmap_t pixmap; -static xcb_gcontext_t pixmap_gc; +static surface_t surface; static xcb_key_symbols_t *symbols; xcb_window_t root; static struct xkb_keymap *xkb_keymap; @@ -463,82 +468,73 @@ void errorlog(char *fmt, ...) { void debuglog(char *fmt, ...) { } +static void txt(int col, int row, char *text, color_t fg, color_t bg) { + int x = col_x(col); + int y = row_y(row); + i3String *string = i3string_from_utf8(text); + draw_util_text(string, &surface, fg, bg, x, y, WIN_WIDTH - x - TEXT_PADDING); + i3string_free(string); +} + /* * Handles expose events, that is, draws the window contents. * */ static int handle_expose() { - /* re-draw the background */ - xcb_rectangle_t border = {0, 0, logical_px(300), window_height()}; - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); + const color_t black = draw_util_hex_to_color("#000000"); + const color_t white = draw_util_hex_to_color("#FFFFFF"); + const color_t green = draw_util_hex_to_color("#00FF00"); + const color_t red = draw_util_hex_to_color("#FF0000"); + + /* draw background */ + draw_util_clear_surface(&surface, black); set_font(&font); -#define txt(x, row, text) \ - draw_text_ascii(text, pixmap, pixmap_gc, \ - x, row_y(row), logical_px(500) - x * 2) - 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, ""); + txt(4, 6, "", 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, ""); + txt(5, 8, "", 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, ""); + txt(5, 5, "", white, black); else - txt(logical_px(31), 4, ""); + txt(5, 4, "", 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, "-> "); + txt(2, 4, "-> ", white, black); else - txt(logical_px(10), 5, "-> "); + txt(2, 5, "-> ", 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, ""); + txt(4, 9, "", 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, ""); + txt(5, 10, "", red, black); } - /* Copy the contents of the pixmap to the real window */ - xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, logical_px(500), logical_px(500)); xcb_flush(conn); return 1; @@ -625,8 +621,7 @@ static void handle_button_press(xcb_button_press_event_t *event) { if (current_step != STEP_GENERATE) return; - if (event->event_x < logical_px(32) || - event->event_x > (logical_px(32) + char_width * 5)) + if (event->event_x < col_x(5) || event->event_x > col_x(10)) return; if (event->event_y >= row_y(4) && event->event_y <= (row_y(4) + font.height)) { @@ -854,6 +849,7 @@ int main(int argc, char *argv[]) { xcb_numlock_mask = get_mod_mask_for(XCB_NUM_LOCK, symbols, modmap_reply); + init_dpi(); font = load_font(pattern, true); bold_font = load_font(patternbold, true); @@ -866,10 +862,10 @@ int main(int argc, char *argv[]) { xcb_create_window( conn, XCB_COPY_FROM_PARENT, - win, /* the window id */ - root, /* parent == root */ - logical_px(490), logical_px(297), logical_px(300), window_height(), /* dimensions */ - 0, /* X11 border = 0, we draw our own */ + win, /* the window id */ + root, /* parent == root */ + WIN_POS_X, WIN_POS_Y, WIN_WIDTH, WIN_HEIGHT, /* dimensions */ + 0, /* X11 border = 0, we draw our own */ XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */ XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, @@ -914,11 +910,8 @@ int main(int argc, char *argv[]) { strlen("i3: first configuration"), "i3: first configuration"); - /* Create pixmap */ - pixmap = xcb_generate_id(conn); - pixmap_gc = xcb_generate_id(conn); - xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, logical_px(500), logical_px(500)); - xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + /* Initialize drawable surface */ + draw_util_surface_init(conn, &surface, win, get_visualtype(root_screen), WIN_WIDTH, WIN_HEIGHT); /* Grab the keyboard to get all input */ xcb_flush(conn); @@ -965,12 +958,18 @@ int main(int argc, char *argv[]) { break; case XCB_EXPOSE: - handle_expose(); + if (((xcb_expose_event_t *)event)->count == 0) { + handle_expose(); + } + break; } free(event); } + /* Dismiss drawable surface */ + draw_util_surface_free(conn, &surface); + return 0; } diff --git a/i3-dmenu-desktop b/i3-dmenu-desktop index 3b81cb20..aee5dc2f 100755 --- a/i3-dmenu-desktop +++ b/i3-dmenu-desktop @@ -279,16 +279,42 @@ for my $app (keys %apps) { } $choices{$name} = $app; + next; } if ((scalar grep { $_ eq 'command' } @entry_types) > 0) { - my ($command) = split(' ', $apps{$app}->{Exec}); + my $command = $apps{$app}->{Exec}; + + # Handle escape sequences (should be done for all string values, but does + # matter here). + my %escapes = ( + '\\s' => ' ', + '\\n' => '\n', + '\\t' => '\t', + '\\r' => '\r', + '\\\\' => '\\', + ); + $command =~ s/(\\[sntr\\])/$escapes{$1}/go; + + # Extract executable + if ($command =~ m/^\s*([^\s\"]+)(?:\s|$)/) { + # No quotes + $command = $1; + } elsif ($command =~ m/^\s*\"([^\"\\]*(?:\\.[^\"\\]*)*)\"(?:\s|$)/) { + # Quoted, remove quotes and fix escaped characters + $command = $1; + $command =~ s/\\([\"\`\$\\])/$1/g; + } else { + # Invalid quotes, fallback to whitespace + ($command) = split(' ', $command); + } # Don’t add “geany” if “Geany” is already present. my @keys = map { lc } keys %choices; - next if (scalar grep { $_ eq lc(basename($command)) } @keys) > 0; - - $choices{basename($command)} = $app; + if (!(scalar grep { $_ eq lc(basename($command)) } @keys) > 0) { + $choices{basename($command)} = $app; + } + next; } if ((scalar grep { $_ eq 'filename' } @entry_types) > 0) { diff --git a/i3-input/keysym2ucs.c b/i3-input/keysym2ucs.c index b8e45a14..52bdc044 100644 --- a/i3-input/keysym2ucs.c +++ b/i3-input/keysym2ucs.c @@ -38,780 +38,780 @@ struct codepair { unsigned short keysym; unsigned short ucs; } keysymtab[] = { - {0x01a1, 0x0104}, /* Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */ - {0x01a2, 0x02d8}, /* breve ˘ BREVE */ - {0x01a3, 0x0141}, /* Lstroke Ł LATIN CAPITAL LETTER L WITH STROKE */ - {0x01a5, 0x013d}, /* Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */ - {0x01a6, 0x015a}, /* Sacute Ś LATIN CAPITAL LETTER S WITH ACUTE */ - {0x01a9, 0x0160}, /* Scaron Š LATIN CAPITAL LETTER S WITH CARON */ - {0x01aa, 0x015e}, /* Scedilla Ş LATIN CAPITAL LETTER S WITH CEDILLA */ - {0x01ab, 0x0164}, /* Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */ - {0x01ac, 0x0179}, /* Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */ - {0x01ae, 0x017d}, /* Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */ - {0x01af, 0x017b}, /* Zabovedot Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE */ - {0x01b1, 0x0105}, /* aogonek ą LATIN SMALL LETTER A WITH OGONEK */ - {0x01b2, 0x02db}, /* ogonek ˛ OGONEK */ - {0x01b3, 0x0142}, /* lstroke ł LATIN SMALL LETTER L WITH STROKE */ - {0x01b5, 0x013e}, /* lcaron ľ LATIN SMALL LETTER L WITH CARON */ - {0x01b6, 0x015b}, /* sacute ś LATIN SMALL LETTER S WITH ACUTE */ - {0x01b7, 0x02c7}, /* caron ˇ CARON */ - {0x01b9, 0x0161}, /* scaron š LATIN SMALL LETTER S WITH CARON */ - {0x01ba, 0x015f}, /* scedilla ş LATIN SMALL LETTER S WITH CEDILLA */ - {0x01bb, 0x0165}, /* tcaron ť LATIN SMALL LETTER T WITH CARON */ - {0x01bc, 0x017a}, /* zacute ź LATIN SMALL LETTER Z WITH ACUTE */ - {0x01bd, 0x02dd}, /* doubleacute ˝ DOUBLE ACUTE ACCENT */ - {0x01be, 0x017e}, /* zcaron ž LATIN SMALL LETTER Z WITH CARON */ - {0x01bf, 0x017c}, /* zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */ - {0x01c0, 0x0154}, /* Racute Ŕ LATIN CAPITAL LETTER R WITH ACUTE */ - {0x01c3, 0x0102}, /* Abreve Ă LATIN CAPITAL LETTER A WITH BREVE */ - {0x01c5, 0x0139}, /* Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */ - {0x01c6, 0x0106}, /* Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */ - {0x01c8, 0x010c}, /* Ccaron Č LATIN CAPITAL LETTER C WITH CARON */ - {0x01ca, 0x0118}, /* Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */ - {0x01cc, 0x011a}, /* Ecaron Ě LATIN CAPITAL LETTER E WITH CARON */ - {0x01cf, 0x010e}, /* Dcaron Ď LATIN CAPITAL LETTER D WITH CARON */ - {0x01d0, 0x0110}, /* Dstroke Đ LATIN CAPITAL LETTER D WITH STROKE */ - {0x01d1, 0x0143}, /* Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */ - {0x01d2, 0x0147}, /* Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */ - {0x01d5, 0x0150}, /* Odoubleacute Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */ - {0x01d8, 0x0158}, /* Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */ - {0x01d9, 0x016e}, /* Uring Ů LATIN CAPITAL LETTER U WITH RING ABOVE */ - {0x01db, 0x0170}, /* Udoubleacute Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */ - {0x01de, 0x0162}, /* Tcedilla Ţ LATIN CAPITAL LETTER T WITH CEDILLA */ - {0x01e0, 0x0155}, /* racute ŕ LATIN SMALL LETTER R WITH ACUTE */ - {0x01e3, 0x0103}, /* abreve ă LATIN SMALL LETTER A WITH BREVE */ - {0x01e5, 0x013a}, /* lacute ĺ LATIN SMALL LETTER L WITH ACUTE */ - {0x01e6, 0x0107}, /* cacute ć LATIN SMALL LETTER C WITH ACUTE */ - {0x01e8, 0x010d}, /* ccaron č LATIN SMALL LETTER C WITH CARON */ - {0x01ea, 0x0119}, /* eogonek ę LATIN SMALL LETTER E WITH OGONEK */ - {0x01ec, 0x011b}, /* ecaron ě LATIN SMALL LETTER E WITH CARON */ - {0x01ef, 0x010f}, /* dcaron ď LATIN SMALL LETTER D WITH CARON */ - {0x01f0, 0x0111}, /* dstroke đ LATIN SMALL LETTER D WITH STROKE */ - {0x01f1, 0x0144}, /* nacute ń LATIN SMALL LETTER N WITH ACUTE */ - {0x01f2, 0x0148}, /* ncaron ň LATIN SMALL LETTER N WITH CARON */ - {0x01f5, 0x0151}, /* odoubleacute ő LATIN SMALL LETTER O WITH DOUBLE ACUTE */ - {0x01f8, 0x0159}, /* rcaron ř LATIN SMALL LETTER R WITH CARON */ - {0x01f9, 0x016f}, /* uring ů LATIN SMALL LETTER U WITH RING ABOVE */ - {0x01fb, 0x0171}, /* udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */ - {0x01fe, 0x0163}, /* tcedilla ţ LATIN SMALL LETTER T WITH CEDILLA */ - {0x01ff, 0x02d9}, /* abovedot ˙ DOT ABOVE */ - {0x02a1, 0x0126}, /* Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */ - {0x02a6, 0x0124}, /* Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */ - {0x02a9, 0x0130}, /* Iabovedot İ LATIN CAPITAL LETTER I WITH DOT ABOVE */ - {0x02ab, 0x011e}, /* Gbreve Ğ LATIN CAPITAL LETTER G WITH BREVE */ - {0x02ac, 0x0134}, /* Jcircumflex Ĵ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */ - {0x02b1, 0x0127}, /* hstroke ħ LATIN SMALL LETTER H WITH STROKE */ - {0x02b6, 0x0125}, /* hcircumflex ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX */ - {0x02b9, 0x0131}, /* idotless ı LATIN SMALL LETTER DOTLESS I */ - {0x02bb, 0x011f}, /* gbreve ğ LATIN SMALL LETTER G WITH BREVE */ - {0x02bc, 0x0135}, /* jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */ - {0x02c5, 0x010a}, /* Cabovedot Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE */ - {0x02c6, 0x0108}, /* Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */ - {0x02d5, 0x0120}, /* Gabovedot Ġ LATIN CAPITAL LETTER G WITH DOT ABOVE */ - {0x02d8, 0x011c}, /* Gcircumflex Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */ - {0x02dd, 0x016c}, /* Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */ - {0x02de, 0x015c}, /* Scircumflex Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */ - {0x02e5, 0x010b}, /* cabovedot ċ LATIN SMALL LETTER C WITH DOT ABOVE */ - {0x02e6, 0x0109}, /* ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */ - {0x02f5, 0x0121}, /* gabovedot ġ LATIN SMALL LETTER G WITH DOT ABOVE */ - {0x02f8, 0x011d}, /* gcircumflex ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX */ - {0x02fd, 0x016d}, /* ubreve ŭ LATIN SMALL LETTER U WITH BREVE */ - {0x02fe, 0x015d}, /* scircumflex ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX */ - {0x03a2, 0x0138}, /* kra ĸ LATIN SMALL LETTER KRA */ - {0x03a3, 0x0156}, /* Rcedilla Ŗ LATIN CAPITAL LETTER R WITH CEDILLA */ - {0x03a5, 0x0128}, /* Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */ - {0x03a6, 0x013b}, /* Lcedilla Ļ LATIN CAPITAL LETTER L WITH CEDILLA */ - {0x03aa, 0x0112}, /* Emacron Ē LATIN CAPITAL LETTER E WITH MACRON */ - {0x03ab, 0x0122}, /* Gcedilla Ģ LATIN CAPITAL LETTER G WITH CEDILLA */ - {0x03ac, 0x0166}, /* Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */ - {0x03b3, 0x0157}, /* rcedilla ŗ LATIN SMALL LETTER R WITH CEDILLA */ - {0x03b5, 0x0129}, /* itilde ĩ LATIN SMALL LETTER I WITH TILDE */ - {0x03b6, 0x013c}, /* lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */ - {0x03ba, 0x0113}, /* emacron ē LATIN SMALL LETTER E WITH MACRON */ - {0x03bb, 0x0123}, /* gcedilla ģ LATIN SMALL LETTER G WITH CEDILLA */ - {0x03bc, 0x0167}, /* tslash ŧ LATIN SMALL LETTER T WITH STROKE */ - {0x03bd, 0x014a}, /* ENG Ŋ LATIN CAPITAL LETTER ENG */ - {0x03bf, 0x014b}, /* eng ŋ LATIN SMALL LETTER ENG */ - {0x03c0, 0x0100}, /* Amacron Ā LATIN CAPITAL LETTER A WITH MACRON */ - {0x03c7, 0x012e}, /* Iogonek Į LATIN CAPITAL LETTER I WITH OGONEK */ - {0x03cc, 0x0116}, /* Eabovedot Ė LATIN CAPITAL LETTER E WITH DOT ABOVE */ - {0x03cf, 0x012a}, /* Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */ - {0x03d1, 0x0145}, /* Ncedilla Ņ LATIN CAPITAL LETTER N WITH CEDILLA */ - {0x03d2, 0x014c}, /* Omacron Ō LATIN CAPITAL LETTER O WITH MACRON */ - {0x03d3, 0x0136}, /* Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */ - {0x03d9, 0x0172}, /* Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */ - {0x03dd, 0x0168}, /* Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */ - {0x03de, 0x016a}, /* Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */ - {0x03e0, 0x0101}, /* amacron ā LATIN SMALL LETTER A WITH MACRON */ - {0x03e7, 0x012f}, /* iogonek į LATIN SMALL LETTER I WITH OGONEK */ - {0x03ec, 0x0117}, /* eabovedot ė LATIN SMALL LETTER E WITH DOT ABOVE */ - {0x03ef, 0x012b}, /* imacron ī LATIN SMALL LETTER I WITH MACRON */ - {0x03f1, 0x0146}, /* ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */ - {0x03f2, 0x014d}, /* omacron ō LATIN SMALL LETTER O WITH MACRON */ - {0x03f3, 0x0137}, /* kcedilla ķ LATIN SMALL LETTER K WITH CEDILLA */ - {0x03f9, 0x0173}, /* uogonek ų LATIN SMALL LETTER U WITH OGONEK */ - {0x03fd, 0x0169}, /* utilde ũ LATIN SMALL LETTER U WITH TILDE */ - {0x03fe, 0x016b}, /* umacron ū LATIN SMALL LETTER U WITH MACRON */ - {0x047e, 0x203e}, /* overline ‾ OVERLINE */ - {0x04a1, 0x3002}, /* kana_fullstop 。 IDEOGRAPHIC FULL STOP */ - {0x04a2, 0x300c}, /* kana_openingbracket 「 LEFT CORNER BRACKET */ - {0x04a3, 0x300d}, /* kana_closingbracket 」 RIGHT CORNER BRACKET */ - {0x04a4, 0x3001}, /* kana_comma 、 IDEOGRAPHIC COMMA */ - {0x04a5, 0x30fb}, /* kana_conjunctive ・ KATAKANA MIDDLE DOT */ - {0x04a6, 0x30f2}, /* kana_WO ヲ KATAKANA LETTER WO */ - {0x04a7, 0x30a1}, /* kana_a ァ KATAKANA LETTER SMALL A */ - {0x04a8, 0x30a3}, /* kana_i ィ KATAKANA LETTER SMALL I */ - {0x04a9, 0x30a5}, /* kana_u ゥ KATAKANA LETTER SMALL U */ - {0x04aa, 0x30a7}, /* kana_e ェ KATAKANA LETTER SMALL E */ - {0x04ab, 0x30a9}, /* kana_o ォ KATAKANA LETTER SMALL O */ - {0x04ac, 0x30e3}, /* kana_ya ャ KATAKANA LETTER SMALL YA */ - {0x04ad, 0x30e5}, /* kana_yu ュ KATAKANA LETTER SMALL YU */ - {0x04ae, 0x30e7}, /* kana_yo ョ KATAKANA LETTER SMALL YO */ - {0x04af, 0x30c3}, /* kana_tsu ッ KATAKANA LETTER SMALL TU */ - {0x04b0, 0x30fc}, /* prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */ - {0x04b1, 0x30a2}, /* kana_A ア KATAKANA LETTER A */ - {0x04b2, 0x30a4}, /* kana_I イ KATAKANA LETTER I */ - {0x04b3, 0x30a6}, /* kana_U ウ KATAKANA LETTER U */ - {0x04b4, 0x30a8}, /* kana_E エ KATAKANA LETTER E */ - {0x04b5, 0x30aa}, /* kana_O オ KATAKANA LETTER O */ - {0x04b6, 0x30ab}, /* kana_KA カ KATAKANA LETTER KA */ - {0x04b7, 0x30ad}, /* kana_KI キ KATAKANA LETTER KI */ - {0x04b8, 0x30af}, /* kana_KU ク KATAKANA LETTER KU */ - {0x04b9, 0x30b1}, /* kana_KE ケ KATAKANA LETTER KE */ - {0x04ba, 0x30b3}, /* kana_KO コ KATAKANA LETTER KO */ - {0x04bb, 0x30b5}, /* kana_SA サ KATAKANA LETTER SA */ - {0x04bc, 0x30b7}, /* kana_SHI シ KATAKANA LETTER SI */ - {0x04bd, 0x30b9}, /* kana_SU ス KATAKANA LETTER SU */ - {0x04be, 0x30bb}, /* kana_SE セ KATAKANA LETTER SE */ - {0x04bf, 0x30bd}, /* kana_SO ソ KATAKANA LETTER SO */ - {0x04c0, 0x30bf}, /* kana_TA タ KATAKANA LETTER TA */ - {0x04c1, 0x30c1}, /* kana_CHI チ KATAKANA LETTER TI */ - {0x04c2, 0x30c4}, /* kana_TSU ツ KATAKANA LETTER TU */ - {0x04c3, 0x30c6}, /* kana_TE テ KATAKANA LETTER TE */ - {0x04c4, 0x30c8}, /* kana_TO ト KATAKANA LETTER TO */ - {0x04c5, 0x30ca}, /* kana_NA ナ KATAKANA LETTER NA */ - {0x04c6, 0x30cb}, /* kana_NI ニ KATAKANA LETTER NI */ - {0x04c7, 0x30cc}, /* kana_NU ヌ KATAKANA LETTER NU */ - {0x04c8, 0x30cd}, /* kana_NE ネ KATAKANA LETTER NE */ - {0x04c9, 0x30ce}, /* kana_NO ノ KATAKANA LETTER NO */ - {0x04ca, 0x30cf}, /* kana_HA ハ KATAKANA LETTER HA */ - {0x04cb, 0x30d2}, /* kana_HI ヒ KATAKANA LETTER HI */ - {0x04cc, 0x30d5}, /* kana_FU フ KATAKANA LETTER HU */ - {0x04cd, 0x30d8}, /* kana_HE ヘ KATAKANA LETTER HE */ - {0x04ce, 0x30db}, /* kana_HO ホ KATAKANA LETTER HO */ - {0x04cf, 0x30de}, /* kana_MA マ KATAKANA LETTER MA */ - {0x04d0, 0x30df}, /* kana_MI ミ KATAKANA LETTER MI */ - {0x04d1, 0x30e0}, /* kana_MU ム KATAKANA LETTER MU */ - {0x04d2, 0x30e1}, /* kana_ME メ KATAKANA LETTER ME */ - {0x04d3, 0x30e2}, /* kana_MO モ KATAKANA LETTER MO */ - {0x04d4, 0x30e4}, /* kana_YA ヤ KATAKANA LETTER YA */ - {0x04d5, 0x30e6}, /* kana_YU ユ KATAKANA LETTER YU */ - {0x04d6, 0x30e8}, /* kana_YO ヨ KATAKANA LETTER YO */ - {0x04d7, 0x30e9}, /* kana_RA ラ KATAKANA LETTER RA */ - {0x04d8, 0x30ea}, /* kana_RI リ KATAKANA LETTER RI */ - {0x04d9, 0x30eb}, /* kana_RU ル KATAKANA LETTER RU */ - {0x04da, 0x30ec}, /* kana_RE レ KATAKANA LETTER RE */ - {0x04db, 0x30ed}, /* kana_RO ロ KATAKANA LETTER RO */ - {0x04dc, 0x30ef}, /* kana_WA ワ KATAKANA LETTER WA */ - {0x04dd, 0x30f3}, /* kana_N ン KATAKANA LETTER N */ - {0x04de, 0x309b}, /* voicedsound ゛ KATAKANA-HIRAGANA VOICED SOUND MARK */ - {0x04df, 0x309c}, /* semivoicedsound ゜ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ - {0x05ac, 0x060c}, /* Arabic_comma ، ARABIC COMMA */ - {0x05bb, 0x061b}, /* Arabic_semicolon ؛ ARABIC SEMICOLON */ - {0x05bf, 0x061f}, /* Arabic_question_mark ؟ ARABIC QUESTION MARK */ - {0x05c1, 0x0621}, /* Arabic_hamza ء ARABIC LETTER HAMZA */ - {0x05c2, 0x0622}, /* Arabic_maddaonalef آ ARABIC LETTER ALEF WITH MADDA ABOVE */ - {0x05c3, 0x0623}, /* Arabic_hamzaonalef أ ARABIC LETTER ALEF WITH HAMZA ABOVE */ - {0x05c4, 0x0624}, /* Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */ - {0x05c5, 0x0625}, /* Arabic_hamzaunderalef إ ARABIC LETTER ALEF WITH HAMZA BELOW */ - {0x05c6, 0x0626}, /* Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */ - {0x05c7, 0x0627}, /* Arabic_alef ا ARABIC LETTER ALEF */ - {0x05c8, 0x0628}, /* Arabic_beh ب ARABIC LETTER BEH */ - {0x05c9, 0x0629}, /* Arabic_tehmarbuta ة ARABIC LETTER TEH MARBUTA */ - {0x05ca, 0x062a}, /* Arabic_teh ت ARABIC LETTER TEH */ - {0x05cb, 0x062b}, /* Arabic_theh ث ARABIC LETTER THEH */ - {0x05cc, 0x062c}, /* Arabic_jeem ج ARABIC LETTER JEEM */ - {0x05cd, 0x062d}, /* Arabic_hah ح ARABIC LETTER HAH */ - {0x05ce, 0x062e}, /* Arabic_khah خ ARABIC LETTER KHAH */ - {0x05cf, 0x062f}, /* Arabic_dal د ARABIC LETTER DAL */ - {0x05d0, 0x0630}, /* Arabic_thal ذ ARABIC LETTER THAL */ - {0x05d1, 0x0631}, /* Arabic_ra ر ARABIC LETTER REH */ - {0x05d2, 0x0632}, /* Arabic_zain ز ARABIC LETTER ZAIN */ - {0x05d3, 0x0633}, /* Arabic_seen س ARABIC LETTER SEEN */ - {0x05d4, 0x0634}, /* Arabic_sheen ش ARABIC LETTER SHEEN */ - {0x05d5, 0x0635}, /* Arabic_sad ص ARABIC LETTER SAD */ - {0x05d6, 0x0636}, /* Arabic_dad ض ARABIC LETTER DAD */ - {0x05d7, 0x0637}, /* Arabic_tah ط ARABIC LETTER TAH */ - {0x05d8, 0x0638}, /* Arabic_zah ظ ARABIC LETTER ZAH */ - {0x05d9, 0x0639}, /* Arabic_ain ع ARABIC LETTER AIN */ - {0x05da, 0x063a}, /* Arabic_ghain غ ARABIC LETTER GHAIN */ - {0x05e0, 0x0640}, /* Arabic_tatweel ـ ARABIC TATWEEL */ - {0x05e1, 0x0641}, /* Arabic_feh ف ARABIC LETTER FEH */ - {0x05e2, 0x0642}, /* Arabic_qaf ق ARABIC LETTER QAF */ - {0x05e3, 0x0643}, /* Arabic_kaf ك ARABIC LETTER KAF */ - {0x05e4, 0x0644}, /* Arabic_lam ل ARABIC LETTER LAM */ - {0x05e5, 0x0645}, /* Arabic_meem م ARABIC LETTER MEEM */ - {0x05e6, 0x0646}, /* Arabic_noon ن ARABIC LETTER NOON */ - {0x05e7, 0x0647}, /* Arabic_ha ه ARABIC LETTER HEH */ - {0x05e8, 0x0648}, /* Arabic_waw و ARABIC LETTER WAW */ - {0x05e9, 0x0649}, /* Arabic_alefmaksura ى ARABIC LETTER ALEF MAKSURA */ - {0x05ea, 0x064a}, /* Arabic_yeh ي ARABIC LETTER YEH */ - {0x05eb, 0x064b}, /* Arabic_fathatan ً ARABIC FATHATAN */ - {0x05ec, 0x064c}, /* Arabic_dammatan ٌ ARABIC DAMMATAN */ - {0x05ed, 0x064d}, /* Arabic_kasratan ٍ ARABIC KASRATAN */ - {0x05ee, 0x064e}, /* Arabic_fatha َ ARABIC FATHA */ - {0x05ef, 0x064f}, /* Arabic_damma ُ ARABIC DAMMA */ - {0x05f0, 0x0650}, /* Arabic_kasra ِ ARABIC KASRA */ - {0x05f1, 0x0651}, /* Arabic_shadda ّ ARABIC SHADDA */ - {0x05f2, 0x0652}, /* Arabic_sukun ْ ARABIC SUKUN */ - {0x06a1, 0x0452}, /* Serbian_dje ђ CYRILLIC SMALL LETTER DJE */ - {0x06a2, 0x0453}, /* Macedonia_gje ѓ CYRILLIC SMALL LETTER GJE */ - {0x06a3, 0x0451}, /* Cyrillic_io ё CYRILLIC SMALL LETTER IO */ - {0x06a4, 0x0454}, /* Ukrainian_ie є CYRILLIC SMALL LETTER UKRAINIAN IE */ - {0x06a5, 0x0455}, /* Macedonia_dse ѕ CYRILLIC SMALL LETTER DZE */ - {0x06a6, 0x0456}, /* Ukrainian_i і CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */ - {0x06a7, 0x0457}, /* Ukrainian_yi ї CYRILLIC SMALL LETTER YI */ - {0x06a8, 0x0458}, /* Cyrillic_je ј CYRILLIC SMALL LETTER JE */ - {0x06a9, 0x0459}, /* Cyrillic_lje љ CYRILLIC SMALL LETTER LJE */ - {0x06aa, 0x045a}, /* Cyrillic_nje њ CYRILLIC SMALL LETTER NJE */ - {0x06ab, 0x045b}, /* Serbian_tshe ћ CYRILLIC SMALL LETTER TSHE */ - {0x06ac, 0x045c}, /* Macedonia_kje ќ CYRILLIC SMALL LETTER KJE */ - /* 0x06ad Ukrainian_ghe_with_upturn ? ??? */ - {0x06ae, 0x045e}, /* Byelorussian_shortu ў CYRILLIC SMALL LETTER SHORT U */ - {0x06af, 0x045f}, /* Cyrillic_dzhe џ CYRILLIC SMALL LETTER DZHE */ - {0x06b0, 0x2116}, /* numerosign № NUMERO SIGN */ - {0x06b1, 0x0402}, /* Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */ - {0x06b2, 0x0403}, /* Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */ - {0x06b3, 0x0401}, /* Cyrillic_IO Ё CYRILLIC CAPITAL LETTER IO */ - {0x06b4, 0x0404}, /* Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */ - {0x06b5, 0x0405}, /* Macedonia_DSE Ѕ CYRILLIC CAPITAL LETTER DZE */ - {0x06b6, 0x0406}, /* Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */ - {0x06b7, 0x0407}, /* Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */ - {0x06b8, 0x0408}, /* Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */ - {0x06b9, 0x0409}, /* Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */ - {0x06ba, 0x040a}, /* Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */ - {0x06bb, 0x040b}, /* Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */ - {0x06bc, 0x040c}, /* Macedonia_KJE Ќ CYRILLIC CAPITAL LETTER KJE */ - /* 0x06bd Ukrainian_GHE_WITH_UPTURN ? ??? */ - {0x06be, 0x040e}, /* Byelorussian_SHORTU Ў CYRILLIC CAPITAL LETTER SHORT U */ - {0x06bf, 0x040f}, /* Cyrillic_DZHE Џ CYRILLIC CAPITAL LETTER DZHE */ - {0x06c0, 0x044e}, /* Cyrillic_yu ю CYRILLIC SMALL LETTER YU */ - {0x06c1, 0x0430}, /* Cyrillic_a а CYRILLIC SMALL LETTER A */ - {0x06c2, 0x0431}, /* Cyrillic_be б CYRILLIC SMALL LETTER BE */ - {0x06c3, 0x0446}, /* Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */ - {0x06c4, 0x0434}, /* Cyrillic_de д CYRILLIC SMALL LETTER DE */ - {0x06c5, 0x0435}, /* Cyrillic_ie е CYRILLIC SMALL LETTER IE */ - {0x06c6, 0x0444}, /* Cyrillic_ef ф CYRILLIC SMALL LETTER EF */ - {0x06c7, 0x0433}, /* Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */ - {0x06c8, 0x0445}, /* Cyrillic_ha х CYRILLIC SMALL LETTER HA */ - {0x06c9, 0x0438}, /* Cyrillic_i и CYRILLIC SMALL LETTER I */ - {0x06ca, 0x0439}, /* Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */ - {0x06cb, 0x043a}, /* Cyrillic_ka к CYRILLIC SMALL LETTER KA */ - {0x06cc, 0x043b}, /* Cyrillic_el л CYRILLIC SMALL LETTER EL */ - {0x06cd, 0x043c}, /* Cyrillic_em м CYRILLIC SMALL LETTER EM */ - {0x06ce, 0x043d}, /* Cyrillic_en н CYRILLIC SMALL LETTER EN */ - {0x06cf, 0x043e}, /* Cyrillic_o о CYRILLIC SMALL LETTER O */ - {0x06d0, 0x043f}, /* Cyrillic_pe п CYRILLIC SMALL LETTER PE */ - {0x06d1, 0x044f}, /* Cyrillic_ya я CYRILLIC SMALL LETTER YA */ - {0x06d2, 0x0440}, /* Cyrillic_er р CYRILLIC SMALL LETTER ER */ - {0x06d3, 0x0441}, /* Cyrillic_es с CYRILLIC SMALL LETTER ES */ - {0x06d4, 0x0442}, /* Cyrillic_te т CYRILLIC SMALL LETTER TE */ - {0x06d5, 0x0443}, /* Cyrillic_u у CYRILLIC SMALL LETTER U */ - {0x06d6, 0x0436}, /* Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */ - {0x06d7, 0x0432}, /* Cyrillic_ve в CYRILLIC SMALL LETTER VE */ - {0x06d8, 0x044c}, /* Cyrillic_softsign ь CYRILLIC SMALL LETTER SOFT SIGN */ - {0x06d9, 0x044b}, /* Cyrillic_yeru ы CYRILLIC SMALL LETTER YERU */ - {0x06da, 0x0437}, /* Cyrillic_ze з CYRILLIC SMALL LETTER ZE */ - {0x06db, 0x0448}, /* Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */ - {0x06dc, 0x044d}, /* Cyrillic_e э CYRILLIC SMALL LETTER E */ - {0x06dd, 0x0449}, /* Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */ - {0x06de, 0x0447}, /* Cyrillic_che ч CYRILLIC SMALL LETTER CHE */ - {0x06df, 0x044a}, /* Cyrillic_hardsign ъ CYRILLIC SMALL LETTER HARD SIGN */ - {0x06e0, 0x042e}, /* Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */ - {0x06e1, 0x0410}, /* Cyrillic_A А CYRILLIC CAPITAL LETTER A */ - {0x06e2, 0x0411}, /* Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */ - {0x06e3, 0x0426}, /* Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */ - {0x06e4, 0x0414}, /* Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */ - {0x06e5, 0x0415}, /* Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */ - {0x06e6, 0x0424}, /* Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */ - {0x06e7, 0x0413}, /* Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */ - {0x06e8, 0x0425}, /* Cyrillic_HA Х CYRILLIC CAPITAL LETTER HA */ - {0x06e9, 0x0418}, /* Cyrillic_I И CYRILLIC CAPITAL LETTER I */ - {0x06ea, 0x0419}, /* Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */ - {0x06eb, 0x041a}, /* Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */ - {0x06ec, 0x041b}, /* Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */ - {0x06ed, 0x041c}, /* Cyrillic_EM М CYRILLIC CAPITAL LETTER EM */ - {0x06ee, 0x041d}, /* Cyrillic_EN Н CYRILLIC CAPITAL LETTER EN */ - {0x06ef, 0x041e}, /* Cyrillic_O О CYRILLIC CAPITAL LETTER O */ - {0x06f0, 0x041f}, /* Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */ - {0x06f1, 0x042f}, /* Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */ - {0x06f2, 0x0420}, /* Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */ - {0x06f3, 0x0421}, /* Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */ - {0x06f4, 0x0422}, /* Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */ - {0x06f5, 0x0423}, /* Cyrillic_U У CYRILLIC CAPITAL LETTER U */ - {0x06f6, 0x0416}, /* Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */ - {0x06f7, 0x0412}, /* Cyrillic_VE В CYRILLIC CAPITAL LETTER VE */ - {0x06f8, 0x042c}, /* Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */ - {0x06f9, 0x042b}, /* Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */ - {0x06fa, 0x0417}, /* Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */ - {0x06fb, 0x0428}, /* Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */ - {0x06fc, 0x042d}, /* Cyrillic_E Э CYRILLIC CAPITAL LETTER E */ - {0x06fd, 0x0429}, /* Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */ - {0x06fe, 0x0427}, /* Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */ - {0x06ff, 0x042a}, /* Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */ - {0x07a1, 0x0386}, /* Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */ - {0x07a2, 0x0388}, /* Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */ - {0x07a3, 0x0389}, /* Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */ - {0x07a4, 0x038a}, /* Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */ - {0x07a5, 0x03aa}, /* Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */ - {0x07a7, 0x038c}, /* Greek_OMICRONaccent Ό GREEK CAPITAL LETTER OMICRON WITH TONOS */ - {0x07a8, 0x038e}, /* Greek_UPSILONaccent Ύ GREEK CAPITAL LETTER UPSILON WITH TONOS */ - {0x07a9, 0x03ab}, /* Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */ - {0x07ab, 0x038f}, /* Greek_OMEGAaccent Ώ GREEK CAPITAL LETTER OMEGA WITH TONOS */ - {0x07ae, 0x0385}, /* Greek_accentdieresis ΅ GREEK DIALYTIKA TONOS */ - {0x07af, 0x2015}, /* Greek_horizbar ― HORIZONTAL BAR */ - {0x07b1, 0x03ac}, /* Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */ - {0x07b2, 0x03ad}, /* Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */ - {0x07b3, 0x03ae}, /* Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */ - {0x07b4, 0x03af}, /* Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */ - {0x07b5, 0x03ca}, /* Greek_iotadieresis ϊ GREEK SMALL LETTER IOTA WITH DIALYTIKA */ - {0x07b6, 0x0390}, /* Greek_iotaaccentdieresis ΐ GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */ - {0x07b7, 0x03cc}, /* Greek_omicronaccent ό GREEK SMALL LETTER OMICRON WITH TONOS */ - {0x07b8, 0x03cd}, /* Greek_upsilonaccent ύ GREEK SMALL LETTER UPSILON WITH TONOS */ - {0x07b9, 0x03cb}, /* Greek_upsilondieresis ϋ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */ - {0x07ba, 0x03b0}, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */ - {0x07bb, 0x03ce}, /* Greek_omegaaccent ώ GREEK SMALL LETTER OMEGA WITH TONOS */ - {0x07c1, 0x0391}, /* Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */ - {0x07c2, 0x0392}, /* Greek_BETA Β GREEK CAPITAL LETTER BETA */ - {0x07c3, 0x0393}, /* Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */ - {0x07c4, 0x0394}, /* Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */ - {0x07c5, 0x0395}, /* Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */ - {0x07c6, 0x0396}, /* Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */ - {0x07c7, 0x0397}, /* Greek_ETA Η GREEK CAPITAL LETTER ETA */ - {0x07c8, 0x0398}, /* Greek_THETA Θ GREEK CAPITAL LETTER THETA */ - {0x07c9, 0x0399}, /* Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */ - {0x07ca, 0x039a}, /* Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */ - {0x07cb, 0x039b}, /* Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMDA */ - {0x07cc, 0x039c}, /* Greek_MU Μ GREEK CAPITAL LETTER MU */ - {0x07cd, 0x039d}, /* Greek_NU Ν GREEK CAPITAL LETTER NU */ - {0x07ce, 0x039e}, /* Greek_XI Ξ GREEK CAPITAL LETTER XI */ - {0x07cf, 0x039f}, /* Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */ - {0x07d0, 0x03a0}, /* Greek_PI Π GREEK CAPITAL LETTER PI */ - {0x07d1, 0x03a1}, /* Greek_RHO Ρ GREEK CAPITAL LETTER RHO */ - {0x07d2, 0x03a3}, /* Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */ - {0x07d4, 0x03a4}, /* Greek_TAU Τ GREEK CAPITAL LETTER TAU */ - {0x07d5, 0x03a5}, /* Greek_UPSILON Υ GREEK CAPITAL LETTER UPSILON */ - {0x07d6, 0x03a6}, /* Greek_PHI Φ GREEK CAPITAL LETTER PHI */ - {0x07d7, 0x03a7}, /* Greek_CHI Χ GREEK CAPITAL LETTER CHI */ - {0x07d8, 0x03a8}, /* Greek_PSI Ψ GREEK CAPITAL LETTER PSI */ - {0x07d9, 0x03a9}, /* Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */ - {0x07e1, 0x03b1}, /* Greek_alpha α GREEK SMALL LETTER ALPHA */ - {0x07e2, 0x03b2}, /* Greek_beta β GREEK SMALL LETTER BETA */ - {0x07e3, 0x03b3}, /* Greek_gamma γ GREEK SMALL LETTER GAMMA */ - {0x07e4, 0x03b4}, /* Greek_delta δ GREEK SMALL LETTER DELTA */ - {0x07e5, 0x03b5}, /* Greek_epsilon ε GREEK SMALL LETTER EPSILON */ - {0x07e6, 0x03b6}, /* Greek_zeta ζ GREEK SMALL LETTER ZETA */ - {0x07e7, 0x03b7}, /* Greek_eta η GREEK SMALL LETTER ETA */ - {0x07e8, 0x03b8}, /* Greek_theta θ GREEK SMALL LETTER THETA */ - {0x07e9, 0x03b9}, /* Greek_iota ι GREEK SMALL LETTER IOTA */ - {0x07ea, 0x03ba}, /* Greek_kappa κ GREEK SMALL LETTER KAPPA */ - {0x07eb, 0x03bb}, /* Greek_lambda λ GREEK SMALL LETTER LAMDA */ - {0x07ec, 0x03bc}, /* Greek_mu μ GREEK SMALL LETTER MU */ - {0x07ed, 0x03bd}, /* Greek_nu ν GREEK SMALL LETTER NU */ - {0x07ee, 0x03be}, /* Greek_xi ξ GREEK SMALL LETTER XI */ - {0x07ef, 0x03bf}, /* Greek_omicron ο GREEK SMALL LETTER OMICRON */ - {0x07f0, 0x03c0}, /* Greek_pi π GREEK SMALL LETTER PI */ - {0x07f1, 0x03c1}, /* Greek_rho ρ GREEK SMALL LETTER RHO */ - {0x07f2, 0x03c3}, /* Greek_sigma σ GREEK SMALL LETTER SIGMA */ - {0x07f3, 0x03c2}, /* Greek_finalsmallsigma ς GREEK SMALL LETTER FINAL SIGMA */ - {0x07f4, 0x03c4}, /* Greek_tau τ GREEK SMALL LETTER TAU */ - {0x07f5, 0x03c5}, /* Greek_upsilon υ GREEK SMALL LETTER UPSILON */ - {0x07f6, 0x03c6}, /* Greek_phi φ GREEK SMALL LETTER PHI */ - {0x07f7, 0x03c7}, /* Greek_chi χ GREEK SMALL LETTER CHI */ - {0x07f8, 0x03c8}, /* Greek_psi ψ GREEK SMALL LETTER PSI */ - {0x07f9, 0x03c9}, /* Greek_omega ω GREEK SMALL LETTER OMEGA */ - /* 0x08a1 leftradical ? ??? */ - /* 0x08a2 topleftradical ? ??? */ - /* 0x08a3 horizconnector ? ??? */ - {0x08a4, 0x2320}, /* topintegral ⌠ TOP HALF INTEGRAL */ - {0x08a5, 0x2321}, /* botintegral ⌡ BOTTOM HALF INTEGRAL */ - {0x08a6, 0x2502}, /* vertconnector │ BOX DRAWINGS LIGHT VERTICAL */ - /* 0x08a7 topleftsqbracket ? ??? */ - /* 0x08a8 botleftsqbracket ? ??? */ - /* 0x08a9 toprightsqbracket ? ??? */ - /* 0x08aa botrightsqbracket ? ??? */ - /* 0x08ab topleftparens ? ??? */ - /* 0x08ac botleftparens ? ??? */ - /* 0x08ad toprightparens ? ??? */ - /* 0x08ae botrightparens ? ??? */ - /* 0x08af leftmiddlecurlybrace ? ??? */ - /* 0x08b0 rightmiddlecurlybrace ? ??? */ - /* 0x08b1 topleftsummation ? ??? */ - /* 0x08b2 botleftsummation ? ??? */ - /* 0x08b3 topvertsummationconnector ? ??? */ - /* 0x08b4 botvertsummationconnector ? ??? */ - /* 0x08b5 toprightsummation ? ??? */ - /* 0x08b6 botrightsummation ? ??? */ - /* 0x08b7 rightmiddlesummation ? ??? */ - {0x08bc, 0x2264}, /* lessthanequal ≤ LESS-THAN OR EQUAL TO */ - {0x08bd, 0x2260}, /* notequal ≠ NOT EQUAL TO */ - {0x08be, 0x2265}, /* greaterthanequal ≥ GREATER-THAN OR EQUAL TO */ - {0x08bf, 0x222b}, /* integral ∫ INTEGRAL */ - {0x08c0, 0x2234}, /* therefore ∴ THEREFORE */ - {0x08c1, 0x221d}, /* variation ∝ PROPORTIONAL TO */ - {0x08c2, 0x221e}, /* infinity ∞ INFINITY */ - {0x08c5, 0x2207}, /* nabla ∇ NABLA */ - {0x08c8, 0x2245}, /* approximate ≅ APPROXIMATELY EQUAL TO */ - /* 0x08c9 similarequal ? ??? */ - {0x08cd, 0x21d4}, /* ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */ - {0x08ce, 0x21d2}, /* implies ⇒ RIGHTWARDS DOUBLE ARROW */ - {0x08cf, 0x2261}, /* identical ≡ IDENTICAL TO */ - {0x08d6, 0x221a}, /* radical √ SQUARE ROOT */ - {0x08da, 0x2282}, /* includedin ⊂ SUBSET OF */ - {0x08db, 0x2283}, /* includes ⊃ SUPERSET OF */ - {0x08dc, 0x2229}, /* intersection ∩ INTERSECTION */ - {0x08dd, 0x222a}, /* union ∪ UNION */ - {0x08de, 0x2227}, /* logicaland ∧ LOGICAL AND */ - {0x08df, 0x2228}, /* logicalor ∨ LOGICAL OR */ - {0x08ef, 0x2202}, /* partialderivative ∂ PARTIAL DIFFERENTIAL */ - {0x08f6, 0x0192}, /* function ƒ LATIN SMALL LETTER F WITH HOOK */ - {0x08fb, 0x2190}, /* leftarrow ← LEFTWARDS ARROW */ - {0x08fc, 0x2191}, /* uparrow ↑ UPWARDS ARROW */ - {0x08fd, 0x2192}, /* rightarrow → RIGHTWARDS ARROW */ - {0x08fe, 0x2193}, /* downarrow ↓ DOWNWARDS ARROW */ - {0x09df, 0x2422}, /* blank ␢ BLANK SYMBOL */ - {0x09e0, 0x25c6}, /* soliddiamond ◆ BLACK DIAMOND */ - {0x09e1, 0x2592}, /* checkerboard ▒ MEDIUM SHADE */ - {0x09e2, 0x2409}, /* ht ␉ SYMBOL FOR HORIZONTAL TABULATION */ - {0x09e3, 0x240c}, /* ff ␌ SYMBOL FOR FORM FEED */ - {0x09e4, 0x240d}, /* cr ␍ SYMBOL FOR CARRIAGE RETURN */ - {0x09e5, 0x240a}, /* lf ␊ SYMBOL FOR LINE FEED */ - {0x09e8, 0x2424}, /* nl ␤ SYMBOL FOR NEWLINE */ - {0x09e9, 0x240b}, /* vt ␋ SYMBOL FOR VERTICAL TABULATION */ - {0x09ea, 0x2518}, /* lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */ - {0x09eb, 0x2510}, /* uprightcorner ┐ BOX DRAWINGS LIGHT DOWN AND LEFT */ - {0x09ec, 0x250c}, /* upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */ - {0x09ed, 0x2514}, /* lowleftcorner └ BOX DRAWINGS LIGHT UP AND RIGHT */ - {0x09ee, 0x253c}, /* crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ - /* 0x09ef horizlinescan1 ? ??? */ - /* 0x09f0 horizlinescan3 ? ??? */ - {0x09f1, 0x2500}, /* horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */ - /* 0x09f2 horizlinescan7 ? ??? */ - /* 0x09f3 horizlinescan9 ? ??? */ - {0x09f4, 0x251c}, /* leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ - {0x09f5, 0x2524}, /* rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */ - {0x09f6, 0x2534}, /* bott ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL */ - {0x09f7, 0x252c}, /* topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ - {0x09f8, 0x2502}, /* vertbar │ BOX DRAWINGS LIGHT VERTICAL */ - {0x0aa1, 0x2003}, /* emspace   EM SPACE */ - {0x0aa2, 0x2002}, /* enspace   EN SPACE */ - {0x0aa3, 0x2004}, /* em3space   THREE-PER-EM SPACE */ - {0x0aa4, 0x2005}, /* em4space   FOUR-PER-EM SPACE */ - {0x0aa5, 0x2007}, /* digitspace   FIGURE SPACE */ - {0x0aa6, 0x2008}, /* punctspace   PUNCTUATION SPACE */ - {0x0aa7, 0x2009}, /* thinspace   THIN SPACE */ - {0x0aa8, 0x200a}, /* hairspace   HAIR SPACE */ - {0x0aa9, 0x2014}, /* emdash — EM DASH */ - {0x0aaa, 0x2013}, /* endash – EN DASH */ - /* 0x0aac signifblank ? ??? */ - {0x0aae, 0x2026}, /* ellipsis … HORIZONTAL ELLIPSIS */ - /* 0x0aaf doubbaselinedot ? ??? */ - {0x0ab0, 0x2153}, /* onethird ⅓ VULGAR FRACTION ONE THIRD */ - {0x0ab1, 0x2154}, /* twothirds ⅔ VULGAR FRACTION TWO THIRDS */ - {0x0ab2, 0x2155}, /* onefifth ⅕ VULGAR FRACTION ONE FIFTH */ - {0x0ab3, 0x2156}, /* twofifths ⅖ VULGAR FRACTION TWO FIFTHS */ - {0x0ab4, 0x2157}, /* threefifths ⅗ VULGAR FRACTION THREE FIFTHS */ - {0x0ab5, 0x2158}, /* fourfifths ⅘ VULGAR FRACTION FOUR FIFTHS */ - {0x0ab6, 0x2159}, /* onesixth ⅙ VULGAR FRACTION ONE SIXTH */ - {0x0ab7, 0x215a}, /* fivesixths ⅚ VULGAR FRACTION FIVE SIXTHS */ - {0x0ab8, 0x2105}, /* careof ℅ CARE OF */ - {0x0abb, 0x2012}, /* figdash ‒ FIGURE DASH */ - {0x0abc, 0x2329}, /* leftanglebracket 〈 LEFT-POINTING ANGLE BRACKET */ - {0x0abd, 0x002e}, /* decimalpoint . FULL STOP */ - {0x0abe, 0x232a}, /* rightanglebracket 〉 RIGHT-POINTING ANGLE BRACKET */ - /* 0x0abf marker ? ??? */ - {0x0ac3, 0x215b}, /* oneeighth ⅛ VULGAR FRACTION ONE EIGHTH */ - {0x0ac4, 0x215c}, /* threeeighths ⅜ VULGAR FRACTION THREE EIGHTHS */ - {0x0ac5, 0x215d}, /* fiveeighths ⅝ VULGAR FRACTION FIVE EIGHTHS */ - {0x0ac6, 0x215e}, /* seveneighths ⅞ VULGAR FRACTION SEVEN EIGHTHS */ - {0x0ac9, 0x2122}, /* trademark ™ TRADE MARK SIGN */ - {0x0aca, 0x2613}, /* signaturemark ☓ SALTIRE */ - /* 0x0acb trademarkincircle ? ??? */ - {0x0acc, 0x25c1}, /* leftopentriangle ◁ WHITE LEFT-POINTING TRIANGLE */ - {0x0acd, 0x25b7}, /* rightopentriangle ▷ WHITE RIGHT-POINTING TRIANGLE */ - {0x0ace, 0x25cb}, /* emopencircle ○ WHITE CIRCLE */ - {0x0acf, 0x25a1}, /* emopenrectangle □ WHITE SQUARE */ - {0x0ad0, 0x2018}, /* leftsinglequotemark ‘ LEFT SINGLE QUOTATION MARK */ - {0x0ad1, 0x2019}, /* rightsinglequotemark ’ RIGHT SINGLE QUOTATION MARK */ - {0x0ad2, 0x201c}, /* leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */ - {0x0ad3, 0x201d}, /* rightdoublequotemark ” RIGHT DOUBLE QUOTATION MARK */ - {0x0ad4, 0x211e}, /* prescription ℞ PRESCRIPTION TAKE */ - {0x0ad6, 0x2032}, /* minutes ′ PRIME */ - {0x0ad7, 0x2033}, /* seconds ″ DOUBLE PRIME */ - {0x0ad9, 0x271d}, /* latincross ✝ LATIN CROSS */ - /* 0x0ada hexagram ? ??? */ - {0x0adb, 0x25ac}, /* filledrectbullet ▬ BLACK RECTANGLE */ - {0x0adc, 0x25c0}, /* filledlefttribullet ◀ BLACK LEFT-POINTING TRIANGLE */ - {0x0add, 0x25b6}, /* filledrighttribullet ▶ BLACK RIGHT-POINTING TRIANGLE */ - {0x0ade, 0x25cf}, /* emfilledcircle ● BLACK CIRCLE */ - {0x0adf, 0x25a0}, /* emfilledrect ■ BLACK SQUARE */ - {0x0ae0, 0x25e6}, /* enopencircbullet ◦ WHITE BULLET */ - {0x0ae1, 0x25ab}, /* enopensquarebullet ▫ WHITE SMALL SQUARE */ - {0x0ae2, 0x25ad}, /* openrectbullet ▭ WHITE RECTANGLE */ - {0x0ae3, 0x25b3}, /* opentribulletup △ WHITE UP-POINTING TRIANGLE */ - {0x0ae4, 0x25bd}, /* opentribulletdown ▽ WHITE DOWN-POINTING TRIANGLE */ - {0x0ae5, 0x2606}, /* openstar ☆ WHITE STAR */ - {0x0ae6, 0x2022}, /* enfilledcircbullet • BULLET */ - {0x0ae7, 0x25aa}, /* enfilledsqbullet ▪ BLACK SMALL SQUARE */ - {0x0ae8, 0x25b2}, /* filledtribulletup ▲ BLACK UP-POINTING TRIANGLE */ - {0x0ae9, 0x25bc}, /* filledtribulletdown ▼ BLACK DOWN-POINTING TRIANGLE */ - {0x0aea, 0x261c}, /* leftpointer ☜ WHITE LEFT POINTING INDEX */ - {0x0aeb, 0x261e}, /* rightpointer ☞ WHITE RIGHT POINTING INDEX */ - {0x0aec, 0x2663}, /* club ♣ BLACK CLUB SUIT */ - {0x0aed, 0x2666}, /* diamond ♦ BLACK DIAMOND SUIT */ - {0x0aee, 0x2665}, /* heart ♥ BLACK HEART SUIT */ - {0x0af0, 0x2720}, /* maltesecross ✠ MALTESE CROSS */ - {0x0af1, 0x2020}, /* dagger † DAGGER */ - {0x0af2, 0x2021}, /* doubledagger ‡ DOUBLE DAGGER */ - {0x0af3, 0x2713}, /* checkmark ✓ CHECK MARK */ - {0x0af4, 0x2717}, /* ballotcross ✗ BALLOT X */ - {0x0af5, 0x266f}, /* musicalsharp ♯ MUSIC SHARP SIGN */ - {0x0af6, 0x266d}, /* musicalflat ♭ MUSIC FLAT SIGN */ - {0x0af7, 0x2642}, /* malesymbol ♂ MALE SIGN */ - {0x0af8, 0x2640}, /* femalesymbol ♀ FEMALE SIGN */ - {0x0af9, 0x260e}, /* telephone ☎ BLACK TELEPHONE */ - {0x0afa, 0x2315}, /* telephonerecorder ⌕ TELEPHONE RECORDER */ - {0x0afb, 0x2117}, /* phonographcopyright ℗ SOUND RECORDING COPYRIGHT */ - {0x0afc, 0x2038}, /* caret ‸ CARET */ - {0x0afd, 0x201a}, /* singlelowquotemark ‚ SINGLE LOW-9 QUOTATION MARK */ - {0x0afe, 0x201e}, /* doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */ - /* 0x0aff cursor ? ??? */ - {0x0ba3, 0x003c}, /* leftcaret < LESS-THAN SIGN */ - {0x0ba6, 0x003e}, /* rightcaret > GREATER-THAN SIGN */ - {0x0ba8, 0x2228}, /* downcaret ∨ LOGICAL OR */ - {0x0ba9, 0x2227}, /* upcaret ∧ LOGICAL AND */ - {0x0bc0, 0x00af}, /* overbar ¯ MACRON */ - {0x0bc2, 0x22a4}, /* downtack ⊤ DOWN TACK */ - {0x0bc3, 0x2229}, /* upshoe ∩ INTERSECTION */ - {0x0bc4, 0x230a}, /* downstile ⌊ LEFT FLOOR */ - {0x0bc6, 0x005f}, /* underbar _ LOW LINE */ - {0x0bca, 0x2218}, /* jot ∘ RING OPERATOR */ - {0x0bcc, 0x2395}, /* quad ⎕ APL FUNCTIONAL SYMBOL QUAD */ - {0x0bce, 0x22a5}, /* uptack ⊥ UP TACK */ - {0x0bcf, 0x25cb}, /* circle ○ WHITE CIRCLE */ - {0x0bd3, 0x2308}, /* upstile ⌈ LEFT CEILING */ - {0x0bd6, 0x222a}, /* downshoe ∪ UNION */ - {0x0bd8, 0x2283}, /* rightshoe ⊃ SUPERSET OF */ - {0x0bda, 0x2282}, /* leftshoe ⊂ SUBSET OF */ - {0x0bdc, 0x22a3}, /* lefttack ⊣ LEFT TACK */ - {0x0bfc, 0x22a2}, /* righttack ⊢ RIGHT TACK */ - {0x0cdf, 0x2017}, /* hebrew_doublelowline ‗ DOUBLE LOW LINE */ - {0x0ce0, 0x05d0}, /* hebrew_aleph א HEBREW LETTER ALEF */ - {0x0ce1, 0x05d1}, /* hebrew_bet ב HEBREW LETTER BET */ - {0x0ce2, 0x05d2}, /* hebrew_gimel ג HEBREW LETTER GIMEL */ - {0x0ce3, 0x05d3}, /* hebrew_dalet ד HEBREW LETTER DALET */ - {0x0ce4, 0x05d4}, /* hebrew_he ה HEBREW LETTER HE */ - {0x0ce5, 0x05d5}, /* hebrew_waw ו HEBREW LETTER VAV */ - {0x0ce6, 0x05d6}, /* hebrew_zain ז HEBREW LETTER ZAYIN */ - {0x0ce7, 0x05d7}, /* hebrew_chet ח HEBREW LETTER HET */ - {0x0ce8, 0x05d8}, /* hebrew_tet ט HEBREW LETTER TET */ - {0x0ce9, 0x05d9}, /* hebrew_yod י HEBREW LETTER YOD */ - {0x0cea, 0x05da}, /* hebrew_finalkaph ך HEBREW LETTER FINAL KAF */ - {0x0ceb, 0x05db}, /* hebrew_kaph כ HEBREW LETTER KAF */ - {0x0cec, 0x05dc}, /* hebrew_lamed ל HEBREW LETTER LAMED */ - {0x0ced, 0x05dd}, /* hebrew_finalmem ם HEBREW LETTER FINAL MEM */ - {0x0cee, 0x05de}, /* hebrew_mem מ HEBREW LETTER MEM */ - {0x0cef, 0x05df}, /* hebrew_finalnun ן HEBREW LETTER FINAL NUN */ - {0x0cf0, 0x05e0}, /* hebrew_nun נ HEBREW LETTER NUN */ - {0x0cf1, 0x05e1}, /* hebrew_samech ס HEBREW LETTER SAMEKH */ - {0x0cf2, 0x05e2}, /* hebrew_ayin ע HEBREW LETTER AYIN */ - {0x0cf3, 0x05e3}, /* hebrew_finalpe ף HEBREW LETTER FINAL PE */ - {0x0cf4, 0x05e4}, /* hebrew_pe פ HEBREW LETTER PE */ - {0x0cf5, 0x05e5}, /* hebrew_finalzade ץ HEBREW LETTER FINAL TSADI */ - {0x0cf6, 0x05e6}, /* hebrew_zade צ HEBREW LETTER TSADI */ - {0x0cf7, 0x05e7}, /* hebrew_qoph ק HEBREW LETTER QOF */ - {0x0cf8, 0x05e8}, /* hebrew_resh ר HEBREW LETTER RESH */ - {0x0cf9, 0x05e9}, /* hebrew_shin ש HEBREW LETTER SHIN */ - {0x0cfa, 0x05ea}, /* hebrew_taw ת HEBREW LETTER TAV */ - {0x0da1, 0x0e01}, /* Thai_kokai ก THAI CHARACTER KO KAI */ - {0x0da2, 0x0e02}, /* Thai_khokhai ข THAI CHARACTER KHO KHAI */ - {0x0da3, 0x0e03}, /* Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */ - {0x0da4, 0x0e04}, /* Thai_khokhwai ค THAI CHARACTER KHO KHWAI */ - {0x0da5, 0x0e05}, /* Thai_khokhon ฅ THAI CHARACTER KHO KHON */ - {0x0da6, 0x0e06}, /* Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */ - {0x0da7, 0x0e07}, /* Thai_ngongu ง THAI CHARACTER NGO NGU */ - {0x0da8, 0x0e08}, /* Thai_chochan จ THAI CHARACTER CHO CHAN */ - {0x0da9, 0x0e09}, /* Thai_choching ฉ THAI CHARACTER CHO CHING */ - {0x0daa, 0x0e0a}, /* Thai_chochang ช THAI CHARACTER CHO CHANG */ - {0x0dab, 0x0e0b}, /* Thai_soso ซ THAI CHARACTER SO SO */ - {0x0dac, 0x0e0c}, /* Thai_chochoe ฌ THAI CHARACTER CHO CHOE */ - {0x0dad, 0x0e0d}, /* Thai_yoying ญ THAI CHARACTER YO YING */ - {0x0dae, 0x0e0e}, /* Thai_dochada ฎ THAI CHARACTER DO CHADA */ - {0x0daf, 0x0e0f}, /* Thai_topatak ฏ THAI CHARACTER TO PATAK */ - {0x0db0, 0x0e10}, /* Thai_thothan ฐ THAI CHARACTER THO THAN */ - {0x0db1, 0x0e11}, /* Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */ - {0x0db2, 0x0e12}, /* Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */ - {0x0db3, 0x0e13}, /* Thai_nonen ณ THAI CHARACTER NO NEN */ - {0x0db4, 0x0e14}, /* Thai_dodek ด THAI CHARACTER DO DEK */ - {0x0db5, 0x0e15}, /* Thai_totao ต THAI CHARACTER TO TAO */ - {0x0db6, 0x0e16}, /* Thai_thothung ถ THAI CHARACTER THO THUNG */ - {0x0db7, 0x0e17}, /* Thai_thothahan ท THAI CHARACTER THO THAHAN */ - {0x0db8, 0x0e18}, /* Thai_thothong ธ THAI CHARACTER THO THONG */ - {0x0db9, 0x0e19}, /* Thai_nonu น THAI CHARACTER NO NU */ - {0x0dba, 0x0e1a}, /* Thai_bobaimai บ THAI CHARACTER BO BAIMAI */ - {0x0dbb, 0x0e1b}, /* Thai_popla ป THAI CHARACTER PO PLA */ - {0x0dbc, 0x0e1c}, /* Thai_phophung ผ THAI CHARACTER PHO PHUNG */ - {0x0dbd, 0x0e1d}, /* Thai_fofa ฝ THAI CHARACTER FO FA */ - {0x0dbe, 0x0e1e}, /* Thai_phophan พ THAI CHARACTER PHO PHAN */ - {0x0dbf, 0x0e1f}, /* Thai_fofan ฟ THAI CHARACTER FO FAN */ - {0x0dc0, 0x0e20}, /* Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */ - {0x0dc1, 0x0e21}, /* Thai_moma ม THAI CHARACTER MO MA */ - {0x0dc2, 0x0e22}, /* Thai_yoyak ย THAI CHARACTER YO YAK */ - {0x0dc3, 0x0e23}, /* Thai_rorua ร THAI CHARACTER RO RUA */ - {0x0dc4, 0x0e24}, /* Thai_ru ฤ THAI CHARACTER RU */ - {0x0dc5, 0x0e25}, /* Thai_loling ล THAI CHARACTER LO LING */ - {0x0dc6, 0x0e26}, /* Thai_lu ฦ THAI CHARACTER LU */ - {0x0dc7, 0x0e27}, /* Thai_wowaen ว THAI CHARACTER WO WAEN */ - {0x0dc8, 0x0e28}, /* Thai_sosala ศ THAI CHARACTER SO SALA */ - {0x0dc9, 0x0e29}, /* Thai_sorusi ษ THAI CHARACTER SO RUSI */ - {0x0dca, 0x0e2a}, /* Thai_sosua ส THAI CHARACTER SO SUA */ - {0x0dcb, 0x0e2b}, /* Thai_hohip ห THAI CHARACTER HO HIP */ - {0x0dcc, 0x0e2c}, /* Thai_lochula ฬ THAI CHARACTER LO CHULA */ - {0x0dcd, 0x0e2d}, /* Thai_oang อ THAI CHARACTER O ANG */ - {0x0dce, 0x0e2e}, /* Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */ - {0x0dcf, 0x0e2f}, /* Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */ - {0x0dd0, 0x0e30}, /* Thai_saraa ะ THAI CHARACTER SARA A */ - {0x0dd1, 0x0e31}, /* Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */ - {0x0dd2, 0x0e32}, /* Thai_saraaa า THAI CHARACTER SARA AA */ - {0x0dd3, 0x0e33}, /* Thai_saraam ำ THAI CHARACTER SARA AM */ - {0x0dd4, 0x0e34}, /* Thai_sarai ิ THAI CHARACTER SARA I */ - {0x0dd5, 0x0e35}, /* Thai_saraii ี THAI CHARACTER SARA II */ - {0x0dd6, 0x0e36}, /* Thai_saraue ึ THAI CHARACTER SARA UE */ - {0x0dd7, 0x0e37}, /* Thai_sarauee ื THAI CHARACTER SARA UEE */ - {0x0dd8, 0x0e38}, /* Thai_sarau ุ THAI CHARACTER SARA U */ - {0x0dd9, 0x0e39}, /* Thai_sarauu ู THAI CHARACTER SARA UU */ - {0x0dda, 0x0e3a}, /* Thai_phinthu ฺ THAI CHARACTER PHINTHU */ - {0x0dde, 0x0e3e}, /* Thai_maihanakat_maitho ฾ ??? */ - {0x0ddf, 0x0e3f}, /* Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */ - {0x0de0, 0x0e40}, /* Thai_sarae เ THAI CHARACTER SARA E */ - {0x0de1, 0x0e41}, /* Thai_saraae แ THAI CHARACTER SARA AE */ - {0x0de2, 0x0e42}, /* Thai_sarao โ THAI CHARACTER SARA O */ - {0x0de3, 0x0e43}, /* Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */ - {0x0de4, 0x0e44}, /* Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */ - {0x0de5, 0x0e45}, /* Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */ - {0x0de6, 0x0e46}, /* Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */ - {0x0de7, 0x0e47}, /* Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */ - {0x0de8, 0x0e48}, /* Thai_maiek ่ THAI CHARACTER MAI EK */ - {0x0de9, 0x0e49}, /* Thai_maitho ้ THAI CHARACTER MAI THO */ - {0x0dea, 0x0e4a}, /* Thai_maitri ๊ THAI CHARACTER MAI TRI */ - {0x0deb, 0x0e4b}, /* Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */ - {0x0dec, 0x0e4c}, /* Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */ - {0x0ded, 0x0e4d}, /* Thai_nikhahit ํ THAI CHARACTER NIKHAHIT */ - {0x0df0, 0x0e50}, /* Thai_leksun ๐ THAI DIGIT ZERO */ - {0x0df1, 0x0e51}, /* Thai_leknung ๑ THAI DIGIT ONE */ - {0x0df2, 0x0e52}, /* Thai_leksong ๒ THAI DIGIT TWO */ - {0x0df3, 0x0e53}, /* Thai_leksam ๓ THAI DIGIT THREE */ - {0x0df4, 0x0e54}, /* Thai_leksi ๔ THAI DIGIT FOUR */ - {0x0df5, 0x0e55}, /* Thai_lekha ๕ THAI DIGIT FIVE */ - {0x0df6, 0x0e56}, /* Thai_lekhok ๖ THAI DIGIT SIX */ - {0x0df7, 0x0e57}, /* Thai_lekchet ๗ THAI DIGIT SEVEN */ - {0x0df8, 0x0e58}, /* Thai_lekpaet ๘ THAI DIGIT EIGHT */ - {0x0df9, 0x0e59}, /* Thai_lekkao ๙ THAI DIGIT NINE */ - {0x0ea1, 0x3131}, /* Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */ - {0x0ea2, 0x3132}, /* Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */ - {0x0ea3, 0x3133}, /* Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */ - {0x0ea4, 0x3134}, /* Hangul_Nieun ㄴ HANGUL LETTER NIEUN */ - {0x0ea5, 0x3135}, /* Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */ - {0x0ea6, 0x3136}, /* Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */ - {0x0ea7, 0x3137}, /* Hangul_Dikeud ㄷ HANGUL LETTER TIKEUT */ - {0x0ea8, 0x3138}, /* Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */ - {0x0ea9, 0x3139}, /* Hangul_Rieul ㄹ HANGUL LETTER RIEUL */ - {0x0eaa, 0x313a}, /* Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */ - {0x0eab, 0x313b}, /* Hangul_RieulMieum ㄻ HANGUL LETTER RIEUL-MIEUM */ - {0x0eac, 0x313c}, /* Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */ - {0x0ead, 0x313d}, /* Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */ - {0x0eae, 0x313e}, /* Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */ - {0x0eaf, 0x313f}, /* Hangul_RieulPhieuf ㄿ HANGUL LETTER RIEUL-PHIEUPH */ - {0x0eb0, 0x3140}, /* Hangul_RieulHieuh ㅀ HANGUL LETTER RIEUL-HIEUH */ - {0x0eb1, 0x3141}, /* Hangul_Mieum ㅁ HANGUL LETTER MIEUM */ - {0x0eb2, 0x3142}, /* Hangul_Pieub ㅂ HANGUL LETTER PIEUP */ - {0x0eb3, 0x3143}, /* Hangul_SsangPieub ㅃ HANGUL LETTER SSANGPIEUP */ - {0x0eb4, 0x3144}, /* Hangul_PieubSios ㅄ HANGUL LETTER PIEUP-SIOS */ - {0x0eb5, 0x3145}, /* Hangul_Sios ㅅ HANGUL LETTER SIOS */ - {0x0eb6, 0x3146}, /* Hangul_SsangSios ㅆ HANGUL LETTER SSANGSIOS */ - {0x0eb7, 0x3147}, /* Hangul_Ieung ㅇ HANGUL LETTER IEUNG */ - {0x0eb8, 0x3148}, /* Hangul_Jieuj ㅈ HANGUL LETTER CIEUC */ - {0x0eb9, 0x3149}, /* Hangul_SsangJieuj ㅉ HANGUL LETTER SSANGCIEUC */ - {0x0eba, 0x314a}, /* Hangul_Cieuc ㅊ HANGUL LETTER CHIEUCH */ - {0x0ebb, 0x314b}, /* Hangul_Khieuq ㅋ HANGUL LETTER KHIEUKH */ - {0x0ebc, 0x314c}, /* Hangul_Tieut ㅌ HANGUL LETTER THIEUTH */ - {0x0ebd, 0x314d}, /* Hangul_Phieuf ㅍ HANGUL LETTER PHIEUPH */ - {0x0ebe, 0x314e}, /* Hangul_Hieuh ㅎ HANGUL LETTER HIEUH */ - {0x0ebf, 0x314f}, /* Hangul_A ㅏ HANGUL LETTER A */ - {0x0ec0, 0x3150}, /* Hangul_AE ㅐ HANGUL LETTER AE */ - {0x0ec1, 0x3151}, /* Hangul_YA ㅑ HANGUL LETTER YA */ - {0x0ec2, 0x3152}, /* Hangul_YAE ㅒ HANGUL LETTER YAE */ - {0x0ec3, 0x3153}, /* Hangul_EO ㅓ HANGUL LETTER EO */ - {0x0ec4, 0x3154}, /* Hangul_E ㅔ HANGUL LETTER E */ - {0x0ec5, 0x3155}, /* Hangul_YEO ㅕ HANGUL LETTER YEO */ - {0x0ec6, 0x3156}, /* Hangul_YE ㅖ HANGUL LETTER YE */ - {0x0ec7, 0x3157}, /* Hangul_O ㅗ HANGUL LETTER O */ - {0x0ec8, 0x3158}, /* Hangul_WA ㅘ HANGUL LETTER WA */ - {0x0ec9, 0x3159}, /* Hangul_WAE ㅙ HANGUL LETTER WAE */ - {0x0eca, 0x315a}, /* Hangul_OE ㅚ HANGUL LETTER OE */ - {0x0ecb, 0x315b}, /* Hangul_YO ㅛ HANGUL LETTER YO */ - {0x0ecc, 0x315c}, /* Hangul_U ㅜ HANGUL LETTER U */ - {0x0ecd, 0x315d}, /* Hangul_WEO ㅝ HANGUL LETTER WEO */ - {0x0ece, 0x315e}, /* Hangul_WE ㅞ HANGUL LETTER WE */ - {0x0ecf, 0x315f}, /* Hangul_WI ㅟ HANGUL LETTER WI */ - {0x0ed0, 0x3160}, /* Hangul_YU ㅠ HANGUL LETTER YU */ - {0x0ed1, 0x3161}, /* Hangul_EU ㅡ HANGUL LETTER EU */ - {0x0ed2, 0x3162}, /* Hangul_YI ㅢ HANGUL LETTER YI */ - {0x0ed3, 0x3163}, /* Hangul_I ㅣ HANGUL LETTER I */ - {0x0ed4, 0x11a8}, /* Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */ - {0x0ed5, 0x11a9}, /* Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */ - {0x0ed6, 0x11aa}, /* Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */ - {0x0ed7, 0x11ab}, /* Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */ - {0x0ed8, 0x11ac}, /* Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */ - {0x0ed9, 0x11ad}, /* Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */ - {0x0eda, 0x11ae}, /* Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */ - {0x0edb, 0x11af}, /* Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */ - {0x0edc, 0x11b0}, /* Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */ - {0x0edd, 0x11b1}, /* Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */ - {0x0ede, 0x11b2}, /* Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */ - {0x0edf, 0x11b3}, /* Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */ - {0x0ee0, 0x11b4}, /* Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */ - {0x0ee1, 0x11b5}, /* Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */ - {0x0ee2, 0x11b6}, /* Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */ - {0x0ee3, 0x11b7}, /* Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */ - {0x0ee4, 0x11b8}, /* Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */ - {0x0ee5, 0x11b9}, /* Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */ - {0x0ee6, 0x11ba}, /* Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */ - {0x0ee7, 0x11bb}, /* Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */ - {0x0ee8, 0x11bc}, /* Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */ - {0x0ee9, 0x11bd}, /* Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */ - {0x0eea, 0x11be}, /* Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */ - {0x0eeb, 0x11bf}, /* Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */ - {0x0eec, 0x11c0}, /* Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */ - {0x0eed, 0x11c1}, /* Hangul_J_Phieuf ᇁ HANGUL JONGSEONG PHIEUPH */ - {0x0eee, 0x11c2}, /* Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */ - {0x0eef, 0x316d}, /* Hangul_RieulYeorinHieuh ㅭ HANGUL LETTER RIEUL-YEORINHIEUH */ - {0x0ef0, 0x3171}, /* Hangul_SunkyeongeumMieum ㅱ HANGUL LETTER KAPYEOUNMIEUM */ - {0x0ef1, 0x3178}, /* Hangul_SunkyeongeumPieub ㅸ HANGUL LETTER KAPYEOUNPIEUP */ - {0x0ef2, 0x317f}, /* Hangul_PanSios ㅿ HANGUL LETTER PANSIOS */ - /* 0x0ef3 Hangul_KkogjiDalrinIeung ? ??? */ - {0x0ef4, 0x3184}, /* Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */ - {0x0ef5, 0x3186}, /* Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */ - {0x0ef6, 0x318d}, /* Hangul_AraeA ㆍ HANGUL LETTER ARAEA */ - {0x0ef7, 0x318e}, /* Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */ - {0x0ef8, 0x11eb}, /* Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */ - /* 0x0ef9 Hangul_J_KkogjiDalrinIeung ? ??? */ - {0x0efa, 0x11f9}, /* Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */ - {0x0eff, 0x20a9}, /* Korean_Won ₩ WON SIGN */ - {0x13bc, 0x0152}, /* OE Œ LATIN CAPITAL LIGATURE OE */ - {0x13bd, 0x0153}, /* oe œ LATIN SMALL LIGATURE OE */ - {0x13be, 0x0178}, /* Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */ - {0x20ac, 0x20ac}, /* EuroSign € EURO SIGN */ + {0x01a1, 0x0104}, /* Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */ + {0x01a2, 0x02d8}, /* breve ˘ BREVE */ + {0x01a3, 0x0141}, /* Lstroke Ł LATIN CAPITAL LETTER L WITH STROKE */ + {0x01a5, 0x013d}, /* Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */ + {0x01a6, 0x015a}, /* Sacute Ś LATIN CAPITAL LETTER S WITH ACUTE */ + {0x01a9, 0x0160}, /* Scaron Š LATIN CAPITAL LETTER S WITH CARON */ + {0x01aa, 0x015e}, /* Scedilla Ş LATIN CAPITAL LETTER S WITH CEDILLA */ + {0x01ab, 0x0164}, /* Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */ + {0x01ac, 0x0179}, /* Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */ + {0x01ae, 0x017d}, /* Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */ + {0x01af, 0x017b}, /* Zabovedot Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE */ + {0x01b1, 0x0105}, /* aogonek ą LATIN SMALL LETTER A WITH OGONEK */ + {0x01b2, 0x02db}, /* ogonek ˛ OGONEK */ + {0x01b3, 0x0142}, /* lstroke ł LATIN SMALL LETTER L WITH STROKE */ + {0x01b5, 0x013e}, /* lcaron ľ LATIN SMALL LETTER L WITH CARON */ + {0x01b6, 0x015b}, /* sacute ś LATIN SMALL LETTER S WITH ACUTE */ + {0x01b7, 0x02c7}, /* caron ˇ CARON */ + {0x01b9, 0x0161}, /* scaron š LATIN SMALL LETTER S WITH CARON */ + {0x01ba, 0x015f}, /* scedilla ş LATIN SMALL LETTER S WITH CEDILLA */ + {0x01bb, 0x0165}, /* tcaron ť LATIN SMALL LETTER T WITH CARON */ + {0x01bc, 0x017a}, /* zacute ź LATIN SMALL LETTER Z WITH ACUTE */ + {0x01bd, 0x02dd}, /* doubleacute ˝ DOUBLE ACUTE ACCENT */ + {0x01be, 0x017e}, /* zcaron ž LATIN SMALL LETTER Z WITH CARON */ + {0x01bf, 0x017c}, /* zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */ + {0x01c0, 0x0154}, /* Racute Ŕ LATIN CAPITAL LETTER R WITH ACUTE */ + {0x01c3, 0x0102}, /* Abreve Ă LATIN CAPITAL LETTER A WITH BREVE */ + {0x01c5, 0x0139}, /* Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */ + {0x01c6, 0x0106}, /* Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */ + {0x01c8, 0x010c}, /* Ccaron Č LATIN CAPITAL LETTER C WITH CARON */ + {0x01ca, 0x0118}, /* Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */ + {0x01cc, 0x011a}, /* Ecaron Ě LATIN CAPITAL LETTER E WITH CARON */ + {0x01cf, 0x010e}, /* Dcaron Ď LATIN CAPITAL LETTER D WITH CARON */ + {0x01d0, 0x0110}, /* Dstroke Đ LATIN CAPITAL LETTER D WITH STROKE */ + {0x01d1, 0x0143}, /* Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */ + {0x01d2, 0x0147}, /* Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */ + {0x01d5, 0x0150}, /* Odoubleacute Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */ + {0x01d8, 0x0158}, /* Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */ + {0x01d9, 0x016e}, /* Uring Ů LATIN CAPITAL LETTER U WITH RING ABOVE */ + {0x01db, 0x0170}, /* Udoubleacute Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */ + {0x01de, 0x0162}, /* Tcedilla Ţ LATIN CAPITAL LETTER T WITH CEDILLA */ + {0x01e0, 0x0155}, /* racute ŕ LATIN SMALL LETTER R WITH ACUTE */ + {0x01e3, 0x0103}, /* abreve ă LATIN SMALL LETTER A WITH BREVE */ + {0x01e5, 0x013a}, /* lacute ĺ LATIN SMALL LETTER L WITH ACUTE */ + {0x01e6, 0x0107}, /* cacute ć LATIN SMALL LETTER C WITH ACUTE */ + {0x01e8, 0x010d}, /* ccaron č LATIN SMALL LETTER C WITH CARON */ + {0x01ea, 0x0119}, /* eogonek ę LATIN SMALL LETTER E WITH OGONEK */ + {0x01ec, 0x011b}, /* ecaron ě LATIN SMALL LETTER E WITH CARON */ + {0x01ef, 0x010f}, /* dcaron ď LATIN SMALL LETTER D WITH CARON */ + {0x01f0, 0x0111}, /* dstroke đ LATIN SMALL LETTER D WITH STROKE */ + {0x01f1, 0x0144}, /* nacute ń LATIN SMALL LETTER N WITH ACUTE */ + {0x01f2, 0x0148}, /* ncaron ň LATIN SMALL LETTER N WITH CARON */ + {0x01f5, 0x0151}, /* odoubleacute ő LATIN SMALL LETTER O WITH DOUBLE ACUTE */ + {0x01f8, 0x0159}, /* rcaron ř LATIN SMALL LETTER R WITH CARON */ + {0x01f9, 0x016f}, /* uring ů LATIN SMALL LETTER U WITH RING ABOVE */ + {0x01fb, 0x0171}, /* udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */ + {0x01fe, 0x0163}, /* tcedilla ţ LATIN SMALL LETTER T WITH CEDILLA */ + {0x01ff, 0x02d9}, /* abovedot ˙ DOT ABOVE */ + {0x02a1, 0x0126}, /* Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */ + {0x02a6, 0x0124}, /* Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */ + {0x02a9, 0x0130}, /* Iabovedot İ LATIN CAPITAL LETTER I WITH DOT ABOVE */ + {0x02ab, 0x011e}, /* Gbreve Ğ LATIN CAPITAL LETTER G WITH BREVE */ + {0x02ac, 0x0134}, /* Jcircumflex Ĵ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */ + {0x02b1, 0x0127}, /* hstroke ħ LATIN SMALL LETTER H WITH STROKE */ + {0x02b6, 0x0125}, /* hcircumflex ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX */ + {0x02b9, 0x0131}, /* idotless ı LATIN SMALL LETTER DOTLESS I */ + {0x02bb, 0x011f}, /* gbreve ğ LATIN SMALL LETTER G WITH BREVE */ + {0x02bc, 0x0135}, /* jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */ + {0x02c5, 0x010a}, /* Cabovedot Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE */ + {0x02c6, 0x0108}, /* Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */ + {0x02d5, 0x0120}, /* Gabovedot Ġ LATIN CAPITAL LETTER G WITH DOT ABOVE */ + {0x02d8, 0x011c}, /* Gcircumflex Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */ + {0x02dd, 0x016c}, /* Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */ + {0x02de, 0x015c}, /* Scircumflex Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */ + {0x02e5, 0x010b}, /* cabovedot ċ LATIN SMALL LETTER C WITH DOT ABOVE */ + {0x02e6, 0x0109}, /* ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */ + {0x02f5, 0x0121}, /* gabovedot ġ LATIN SMALL LETTER G WITH DOT ABOVE */ + {0x02f8, 0x011d}, /* gcircumflex ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX */ + {0x02fd, 0x016d}, /* ubreve ŭ LATIN SMALL LETTER U WITH BREVE */ + {0x02fe, 0x015d}, /* scircumflex ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX */ + {0x03a2, 0x0138}, /* kra ĸ LATIN SMALL LETTER KRA */ + {0x03a3, 0x0156}, /* Rcedilla Ŗ LATIN CAPITAL LETTER R WITH CEDILLA */ + {0x03a5, 0x0128}, /* Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */ + {0x03a6, 0x013b}, /* Lcedilla Ļ LATIN CAPITAL LETTER L WITH CEDILLA */ + {0x03aa, 0x0112}, /* Emacron Ē LATIN CAPITAL LETTER E WITH MACRON */ + {0x03ab, 0x0122}, /* Gcedilla Ģ LATIN CAPITAL LETTER G WITH CEDILLA */ + {0x03ac, 0x0166}, /* Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */ + {0x03b3, 0x0157}, /* rcedilla ŗ LATIN SMALL LETTER R WITH CEDILLA */ + {0x03b5, 0x0129}, /* itilde ĩ LATIN SMALL LETTER I WITH TILDE */ + {0x03b6, 0x013c}, /* lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */ + {0x03ba, 0x0113}, /* emacron ē LATIN SMALL LETTER E WITH MACRON */ + {0x03bb, 0x0123}, /* gcedilla ģ LATIN SMALL LETTER G WITH CEDILLA */ + {0x03bc, 0x0167}, /* tslash ŧ LATIN SMALL LETTER T WITH STROKE */ + {0x03bd, 0x014a}, /* ENG Ŋ LATIN CAPITAL LETTER ENG */ + {0x03bf, 0x014b}, /* eng ŋ LATIN SMALL LETTER ENG */ + {0x03c0, 0x0100}, /* Amacron Ā LATIN CAPITAL LETTER A WITH MACRON */ + {0x03c7, 0x012e}, /* Iogonek Į LATIN CAPITAL LETTER I WITH OGONEK */ + {0x03cc, 0x0116}, /* Eabovedot Ė LATIN CAPITAL LETTER E WITH DOT ABOVE */ + {0x03cf, 0x012a}, /* Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */ + {0x03d1, 0x0145}, /* Ncedilla Ņ LATIN CAPITAL LETTER N WITH CEDILLA */ + {0x03d2, 0x014c}, /* Omacron Ō LATIN CAPITAL LETTER O WITH MACRON */ + {0x03d3, 0x0136}, /* Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */ + {0x03d9, 0x0172}, /* Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */ + {0x03dd, 0x0168}, /* Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */ + {0x03de, 0x016a}, /* Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */ + {0x03e0, 0x0101}, /* amacron ā LATIN SMALL LETTER A WITH MACRON */ + {0x03e7, 0x012f}, /* iogonek į LATIN SMALL LETTER I WITH OGONEK */ + {0x03ec, 0x0117}, /* eabovedot ė LATIN SMALL LETTER E WITH DOT ABOVE */ + {0x03ef, 0x012b}, /* imacron ī LATIN SMALL LETTER I WITH MACRON */ + {0x03f1, 0x0146}, /* ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */ + {0x03f2, 0x014d}, /* omacron ō LATIN SMALL LETTER O WITH MACRON */ + {0x03f3, 0x0137}, /* kcedilla ķ LATIN SMALL LETTER K WITH CEDILLA */ + {0x03f9, 0x0173}, /* uogonek ų LATIN SMALL LETTER U WITH OGONEK */ + {0x03fd, 0x0169}, /* utilde ũ LATIN SMALL LETTER U WITH TILDE */ + {0x03fe, 0x016b}, /* umacron ū LATIN SMALL LETTER U WITH MACRON */ + {0x047e, 0x203e}, /* overline ‾ OVERLINE */ + {0x04a1, 0x3002}, /* kana_fullstop 。 IDEOGRAPHIC FULL STOP */ + {0x04a2, 0x300c}, /* kana_openingbracket 「 LEFT CORNER BRACKET */ + {0x04a3, 0x300d}, /* kana_closingbracket 」 RIGHT CORNER BRACKET */ + {0x04a4, 0x3001}, /* kana_comma 、 IDEOGRAPHIC COMMA */ + {0x04a5, 0x30fb}, /* kana_conjunctive ・ KATAKANA MIDDLE DOT */ + {0x04a6, 0x30f2}, /* kana_WO ヲ KATAKANA LETTER WO */ + {0x04a7, 0x30a1}, /* kana_a ァ KATAKANA LETTER SMALL A */ + {0x04a8, 0x30a3}, /* kana_i ィ KATAKANA LETTER SMALL I */ + {0x04a9, 0x30a5}, /* kana_u ゥ KATAKANA LETTER SMALL U */ + {0x04aa, 0x30a7}, /* kana_e ェ KATAKANA LETTER SMALL E */ + {0x04ab, 0x30a9}, /* kana_o ォ KATAKANA LETTER SMALL O */ + {0x04ac, 0x30e3}, /* kana_ya ャ KATAKANA LETTER SMALL YA */ + {0x04ad, 0x30e5}, /* kana_yu ュ KATAKANA LETTER SMALL YU */ + {0x04ae, 0x30e7}, /* kana_yo ョ KATAKANA LETTER SMALL YO */ + {0x04af, 0x30c3}, /* kana_tsu ッ KATAKANA LETTER SMALL TU */ + {0x04b0, 0x30fc}, /* prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */ + {0x04b1, 0x30a2}, /* kana_A ア KATAKANA LETTER A */ + {0x04b2, 0x30a4}, /* kana_I イ KATAKANA LETTER I */ + {0x04b3, 0x30a6}, /* kana_U ウ KATAKANA LETTER U */ + {0x04b4, 0x30a8}, /* kana_E エ KATAKANA LETTER E */ + {0x04b5, 0x30aa}, /* kana_O オ KATAKANA LETTER O */ + {0x04b6, 0x30ab}, /* kana_KA カ KATAKANA LETTER KA */ + {0x04b7, 0x30ad}, /* kana_KI キ KATAKANA LETTER KI */ + {0x04b8, 0x30af}, /* kana_KU ク KATAKANA LETTER KU */ + {0x04b9, 0x30b1}, /* kana_KE ケ KATAKANA LETTER KE */ + {0x04ba, 0x30b3}, /* kana_KO コ KATAKANA LETTER KO */ + {0x04bb, 0x30b5}, /* kana_SA サ KATAKANA LETTER SA */ + {0x04bc, 0x30b7}, /* kana_SHI シ KATAKANA LETTER SI */ + {0x04bd, 0x30b9}, /* kana_SU ス KATAKANA LETTER SU */ + {0x04be, 0x30bb}, /* kana_SE セ KATAKANA LETTER SE */ + {0x04bf, 0x30bd}, /* kana_SO ソ KATAKANA LETTER SO */ + {0x04c0, 0x30bf}, /* kana_TA タ KATAKANA LETTER TA */ + {0x04c1, 0x30c1}, /* kana_CHI チ KATAKANA LETTER TI */ + {0x04c2, 0x30c4}, /* kana_TSU ツ KATAKANA LETTER TU */ + {0x04c3, 0x30c6}, /* kana_TE テ KATAKANA LETTER TE */ + {0x04c4, 0x30c8}, /* kana_TO ト KATAKANA LETTER TO */ + {0x04c5, 0x30ca}, /* kana_NA ナ KATAKANA LETTER NA */ + {0x04c6, 0x30cb}, /* kana_NI ニ KATAKANA LETTER NI */ + {0x04c7, 0x30cc}, /* kana_NU ヌ KATAKANA LETTER NU */ + {0x04c8, 0x30cd}, /* kana_NE ネ KATAKANA LETTER NE */ + {0x04c9, 0x30ce}, /* kana_NO ノ KATAKANA LETTER NO */ + {0x04ca, 0x30cf}, /* kana_HA ハ KATAKANA LETTER HA */ + {0x04cb, 0x30d2}, /* kana_HI ヒ KATAKANA LETTER HI */ + {0x04cc, 0x30d5}, /* kana_FU フ KATAKANA LETTER HU */ + {0x04cd, 0x30d8}, /* kana_HE ヘ KATAKANA LETTER HE */ + {0x04ce, 0x30db}, /* kana_HO ホ KATAKANA LETTER HO */ + {0x04cf, 0x30de}, /* kana_MA マ KATAKANA LETTER MA */ + {0x04d0, 0x30df}, /* kana_MI ミ KATAKANA LETTER MI */ + {0x04d1, 0x30e0}, /* kana_MU ム KATAKANA LETTER MU */ + {0x04d2, 0x30e1}, /* kana_ME メ KATAKANA LETTER ME */ + {0x04d3, 0x30e2}, /* kana_MO モ KATAKANA LETTER MO */ + {0x04d4, 0x30e4}, /* kana_YA ヤ KATAKANA LETTER YA */ + {0x04d5, 0x30e6}, /* kana_YU ユ KATAKANA LETTER YU */ + {0x04d6, 0x30e8}, /* kana_YO ヨ KATAKANA LETTER YO */ + {0x04d7, 0x30e9}, /* kana_RA ラ KATAKANA LETTER RA */ + {0x04d8, 0x30ea}, /* kana_RI リ KATAKANA LETTER RI */ + {0x04d9, 0x30eb}, /* kana_RU ル KATAKANA LETTER RU */ + {0x04da, 0x30ec}, /* kana_RE レ KATAKANA LETTER RE */ + {0x04db, 0x30ed}, /* kana_RO ロ KATAKANA LETTER RO */ + {0x04dc, 0x30ef}, /* kana_WA ワ KATAKANA LETTER WA */ + {0x04dd, 0x30f3}, /* kana_N ン KATAKANA LETTER N */ + {0x04de, 0x309b}, /* voicedsound ゛ KATAKANA-HIRAGANA VOICED SOUND MARK */ + {0x04df, 0x309c}, /* semivoicedsound ゜ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ + {0x05ac, 0x060c}, /* Arabic_comma ، ARABIC COMMA */ + {0x05bb, 0x061b}, /* Arabic_semicolon ؛ ARABIC SEMICOLON */ + {0x05bf, 0x061f}, /* Arabic_question_mark ؟ ARABIC QUESTION MARK */ + {0x05c1, 0x0621}, /* Arabic_hamza ء ARABIC LETTER HAMZA */ + {0x05c2, 0x0622}, /* Arabic_maddaonalef آ ARABIC LETTER ALEF WITH MADDA ABOVE */ + {0x05c3, 0x0623}, /* Arabic_hamzaonalef أ ARABIC LETTER ALEF WITH HAMZA ABOVE */ + {0x05c4, 0x0624}, /* Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */ + {0x05c5, 0x0625}, /* Arabic_hamzaunderalef إ ARABIC LETTER ALEF WITH HAMZA BELOW */ + {0x05c6, 0x0626}, /* Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */ + {0x05c7, 0x0627}, /* Arabic_alef ا ARABIC LETTER ALEF */ + {0x05c8, 0x0628}, /* Arabic_beh ب ARABIC LETTER BEH */ + {0x05c9, 0x0629}, /* Arabic_tehmarbuta ة ARABIC LETTER TEH MARBUTA */ + {0x05ca, 0x062a}, /* Arabic_teh ت ARABIC LETTER TEH */ + {0x05cb, 0x062b}, /* Arabic_theh ث ARABIC LETTER THEH */ + {0x05cc, 0x062c}, /* Arabic_jeem ج ARABIC LETTER JEEM */ + {0x05cd, 0x062d}, /* Arabic_hah ح ARABIC LETTER HAH */ + {0x05ce, 0x062e}, /* Arabic_khah خ ARABIC LETTER KHAH */ + {0x05cf, 0x062f}, /* Arabic_dal د ARABIC LETTER DAL */ + {0x05d0, 0x0630}, /* Arabic_thal ذ ARABIC LETTER THAL */ + {0x05d1, 0x0631}, /* Arabic_ra ر ARABIC LETTER REH */ + {0x05d2, 0x0632}, /* Arabic_zain ز ARABIC LETTER ZAIN */ + {0x05d3, 0x0633}, /* Arabic_seen س ARABIC LETTER SEEN */ + {0x05d4, 0x0634}, /* Arabic_sheen ش ARABIC LETTER SHEEN */ + {0x05d5, 0x0635}, /* Arabic_sad ص ARABIC LETTER SAD */ + {0x05d6, 0x0636}, /* Arabic_dad ض ARABIC LETTER DAD */ + {0x05d7, 0x0637}, /* Arabic_tah ط ARABIC LETTER TAH */ + {0x05d8, 0x0638}, /* Arabic_zah ظ ARABIC LETTER ZAH */ + {0x05d9, 0x0639}, /* Arabic_ain ع ARABIC LETTER AIN */ + {0x05da, 0x063a}, /* Arabic_ghain غ ARABIC LETTER GHAIN */ + {0x05e0, 0x0640}, /* Arabic_tatweel ـ ARABIC TATWEEL */ + {0x05e1, 0x0641}, /* Arabic_feh ف ARABIC LETTER FEH */ + {0x05e2, 0x0642}, /* Arabic_qaf ق ARABIC LETTER QAF */ + {0x05e3, 0x0643}, /* Arabic_kaf ك ARABIC LETTER KAF */ + {0x05e4, 0x0644}, /* Arabic_lam ل ARABIC LETTER LAM */ + {0x05e5, 0x0645}, /* Arabic_meem م ARABIC LETTER MEEM */ + {0x05e6, 0x0646}, /* Arabic_noon ن ARABIC LETTER NOON */ + {0x05e7, 0x0647}, /* Arabic_ha ه ARABIC LETTER HEH */ + {0x05e8, 0x0648}, /* Arabic_waw و ARABIC LETTER WAW */ + {0x05e9, 0x0649}, /* Arabic_alefmaksura ى ARABIC LETTER ALEF MAKSURA */ + {0x05ea, 0x064a}, /* Arabic_yeh ي ARABIC LETTER YEH */ + {0x05eb, 0x064b}, /* Arabic_fathatan ً ARABIC FATHATAN */ + {0x05ec, 0x064c}, /* Arabic_dammatan ٌ ARABIC DAMMATAN */ + {0x05ed, 0x064d}, /* Arabic_kasratan ٍ ARABIC KASRATAN */ + {0x05ee, 0x064e}, /* Arabic_fatha َ ARABIC FATHA */ + {0x05ef, 0x064f}, /* Arabic_damma ُ ARABIC DAMMA */ + {0x05f0, 0x0650}, /* Arabic_kasra ِ ARABIC KASRA */ + {0x05f1, 0x0651}, /* Arabic_shadda ّ ARABIC SHADDA */ + {0x05f2, 0x0652}, /* Arabic_sukun ْ ARABIC SUKUN */ + {0x06a1, 0x0452}, /* Serbian_dje ђ CYRILLIC SMALL LETTER DJE */ + {0x06a2, 0x0453}, /* Macedonia_gje ѓ CYRILLIC SMALL LETTER GJE */ + {0x06a3, 0x0451}, /* Cyrillic_io ё CYRILLIC SMALL LETTER IO */ + {0x06a4, 0x0454}, /* Ukrainian_ie є CYRILLIC SMALL LETTER UKRAINIAN IE */ + {0x06a5, 0x0455}, /* Macedonia_dse ѕ CYRILLIC SMALL LETTER DZE */ + {0x06a6, 0x0456}, /* Ukrainian_i і CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */ + {0x06a7, 0x0457}, /* Ukrainian_yi ї CYRILLIC SMALL LETTER YI */ + {0x06a8, 0x0458}, /* Cyrillic_je ј CYRILLIC SMALL LETTER JE */ + {0x06a9, 0x0459}, /* Cyrillic_lje љ CYRILLIC SMALL LETTER LJE */ + {0x06aa, 0x045a}, /* Cyrillic_nje њ CYRILLIC SMALL LETTER NJE */ + {0x06ab, 0x045b}, /* Serbian_tshe ћ CYRILLIC SMALL LETTER TSHE */ + {0x06ac, 0x045c}, /* Macedonia_kje ќ CYRILLIC SMALL LETTER KJE */ + /* 0x06ad Ukrainian_ghe_with_upturn ? ??? */ + {0x06ae, 0x045e}, /* Byelorussian_shortu ў CYRILLIC SMALL LETTER SHORT U */ + {0x06af, 0x045f}, /* Cyrillic_dzhe џ CYRILLIC SMALL LETTER DZHE */ + {0x06b0, 0x2116}, /* numerosign № NUMERO SIGN */ + {0x06b1, 0x0402}, /* Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */ + {0x06b2, 0x0403}, /* Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */ + {0x06b3, 0x0401}, /* Cyrillic_IO Ё CYRILLIC CAPITAL LETTER IO */ + {0x06b4, 0x0404}, /* Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */ + {0x06b5, 0x0405}, /* Macedonia_DSE Ѕ CYRILLIC CAPITAL LETTER DZE */ + {0x06b6, 0x0406}, /* Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */ + {0x06b7, 0x0407}, /* Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */ + {0x06b8, 0x0408}, /* Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */ + {0x06b9, 0x0409}, /* Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */ + {0x06ba, 0x040a}, /* Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */ + {0x06bb, 0x040b}, /* Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */ + {0x06bc, 0x040c}, /* Macedonia_KJE Ќ CYRILLIC CAPITAL LETTER KJE */ + /* 0x06bd Ukrainian_GHE_WITH_UPTURN ? ??? */ + {0x06be, 0x040e}, /* Byelorussian_SHORTU Ў CYRILLIC CAPITAL LETTER SHORT U */ + {0x06bf, 0x040f}, /* Cyrillic_DZHE Џ CYRILLIC CAPITAL LETTER DZHE */ + {0x06c0, 0x044e}, /* Cyrillic_yu ю CYRILLIC SMALL LETTER YU */ + {0x06c1, 0x0430}, /* Cyrillic_a а CYRILLIC SMALL LETTER A */ + {0x06c2, 0x0431}, /* Cyrillic_be б CYRILLIC SMALL LETTER BE */ + {0x06c3, 0x0446}, /* Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */ + {0x06c4, 0x0434}, /* Cyrillic_de д CYRILLIC SMALL LETTER DE */ + {0x06c5, 0x0435}, /* Cyrillic_ie е CYRILLIC SMALL LETTER IE */ + {0x06c6, 0x0444}, /* Cyrillic_ef ф CYRILLIC SMALL LETTER EF */ + {0x06c7, 0x0433}, /* Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */ + {0x06c8, 0x0445}, /* Cyrillic_ha х CYRILLIC SMALL LETTER HA */ + {0x06c9, 0x0438}, /* Cyrillic_i и CYRILLIC SMALL LETTER I */ + {0x06ca, 0x0439}, /* Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */ + {0x06cb, 0x043a}, /* Cyrillic_ka к CYRILLIC SMALL LETTER KA */ + {0x06cc, 0x043b}, /* Cyrillic_el л CYRILLIC SMALL LETTER EL */ + {0x06cd, 0x043c}, /* Cyrillic_em м CYRILLIC SMALL LETTER EM */ + {0x06ce, 0x043d}, /* Cyrillic_en н CYRILLIC SMALL LETTER EN */ + {0x06cf, 0x043e}, /* Cyrillic_o о CYRILLIC SMALL LETTER O */ + {0x06d0, 0x043f}, /* Cyrillic_pe п CYRILLIC SMALL LETTER PE */ + {0x06d1, 0x044f}, /* Cyrillic_ya я CYRILLIC SMALL LETTER YA */ + {0x06d2, 0x0440}, /* Cyrillic_er р CYRILLIC SMALL LETTER ER */ + {0x06d3, 0x0441}, /* Cyrillic_es с CYRILLIC SMALL LETTER ES */ + {0x06d4, 0x0442}, /* Cyrillic_te т CYRILLIC SMALL LETTER TE */ + {0x06d5, 0x0443}, /* Cyrillic_u у CYRILLIC SMALL LETTER U */ + {0x06d6, 0x0436}, /* Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */ + {0x06d7, 0x0432}, /* Cyrillic_ve в CYRILLIC SMALL LETTER VE */ + {0x06d8, 0x044c}, /* Cyrillic_softsign ь CYRILLIC SMALL LETTER SOFT SIGN */ + {0x06d9, 0x044b}, /* Cyrillic_yeru ы CYRILLIC SMALL LETTER YERU */ + {0x06da, 0x0437}, /* Cyrillic_ze з CYRILLIC SMALL LETTER ZE */ + {0x06db, 0x0448}, /* Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */ + {0x06dc, 0x044d}, /* Cyrillic_e э CYRILLIC SMALL LETTER E */ + {0x06dd, 0x0449}, /* Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */ + {0x06de, 0x0447}, /* Cyrillic_che ч CYRILLIC SMALL LETTER CHE */ + {0x06df, 0x044a}, /* Cyrillic_hardsign ъ CYRILLIC SMALL LETTER HARD SIGN */ + {0x06e0, 0x042e}, /* Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */ + {0x06e1, 0x0410}, /* Cyrillic_A А CYRILLIC CAPITAL LETTER A */ + {0x06e2, 0x0411}, /* Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */ + {0x06e3, 0x0426}, /* Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */ + {0x06e4, 0x0414}, /* Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */ + {0x06e5, 0x0415}, /* Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */ + {0x06e6, 0x0424}, /* Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */ + {0x06e7, 0x0413}, /* Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */ + {0x06e8, 0x0425}, /* Cyrillic_HA Х CYRILLIC CAPITAL LETTER HA */ + {0x06e9, 0x0418}, /* Cyrillic_I И CYRILLIC CAPITAL LETTER I */ + {0x06ea, 0x0419}, /* Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */ + {0x06eb, 0x041a}, /* Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */ + {0x06ec, 0x041b}, /* Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */ + {0x06ed, 0x041c}, /* Cyrillic_EM М CYRILLIC CAPITAL LETTER EM */ + {0x06ee, 0x041d}, /* Cyrillic_EN Н CYRILLIC CAPITAL LETTER EN */ + {0x06ef, 0x041e}, /* Cyrillic_O О CYRILLIC CAPITAL LETTER O */ + {0x06f0, 0x041f}, /* Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */ + {0x06f1, 0x042f}, /* Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */ + {0x06f2, 0x0420}, /* Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */ + {0x06f3, 0x0421}, /* Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */ + {0x06f4, 0x0422}, /* Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */ + {0x06f5, 0x0423}, /* Cyrillic_U У CYRILLIC CAPITAL LETTER U */ + {0x06f6, 0x0416}, /* Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */ + {0x06f7, 0x0412}, /* Cyrillic_VE В CYRILLIC CAPITAL LETTER VE */ + {0x06f8, 0x042c}, /* Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */ + {0x06f9, 0x042b}, /* Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */ + {0x06fa, 0x0417}, /* Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */ + {0x06fb, 0x0428}, /* Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */ + {0x06fc, 0x042d}, /* Cyrillic_E Э CYRILLIC CAPITAL LETTER E */ + {0x06fd, 0x0429}, /* Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */ + {0x06fe, 0x0427}, /* Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */ + {0x06ff, 0x042a}, /* Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */ + {0x07a1, 0x0386}, /* Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */ + {0x07a2, 0x0388}, /* Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */ + {0x07a3, 0x0389}, /* Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */ + {0x07a4, 0x038a}, /* Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */ + {0x07a5, 0x03aa}, /* Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */ + {0x07a7, 0x038c}, /* Greek_OMICRONaccent Ό GREEK CAPITAL LETTER OMICRON WITH TONOS */ + {0x07a8, 0x038e}, /* Greek_UPSILONaccent Ύ GREEK CAPITAL LETTER UPSILON WITH TONOS */ + {0x07a9, 0x03ab}, /* Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */ + {0x07ab, 0x038f}, /* Greek_OMEGAaccent Ώ GREEK CAPITAL LETTER OMEGA WITH TONOS */ + {0x07ae, 0x0385}, /* Greek_accentdieresis ΅ GREEK DIALYTIKA TONOS */ + {0x07af, 0x2015}, /* Greek_horizbar ― HORIZONTAL BAR */ + {0x07b1, 0x03ac}, /* Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */ + {0x07b2, 0x03ad}, /* Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */ + {0x07b3, 0x03ae}, /* Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */ + {0x07b4, 0x03af}, /* Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */ + {0x07b5, 0x03ca}, /* Greek_iotadieresis ϊ GREEK SMALL LETTER IOTA WITH DIALYTIKA */ + {0x07b6, 0x0390}, /* Greek_iotaaccentdieresis ΐ GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */ + {0x07b7, 0x03cc}, /* Greek_omicronaccent ό GREEK SMALL LETTER OMICRON WITH TONOS */ + {0x07b8, 0x03cd}, /* Greek_upsilonaccent ύ GREEK SMALL LETTER UPSILON WITH TONOS */ + {0x07b9, 0x03cb}, /* Greek_upsilondieresis ϋ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */ + {0x07ba, 0x03b0}, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */ + {0x07bb, 0x03ce}, /* Greek_omegaaccent ώ GREEK SMALL LETTER OMEGA WITH TONOS */ + {0x07c1, 0x0391}, /* Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */ + {0x07c2, 0x0392}, /* Greek_BETA Β GREEK CAPITAL LETTER BETA */ + {0x07c3, 0x0393}, /* Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */ + {0x07c4, 0x0394}, /* Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */ + {0x07c5, 0x0395}, /* Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */ + {0x07c6, 0x0396}, /* Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */ + {0x07c7, 0x0397}, /* Greek_ETA Η GREEK CAPITAL LETTER ETA */ + {0x07c8, 0x0398}, /* Greek_THETA Θ GREEK CAPITAL LETTER THETA */ + {0x07c9, 0x0399}, /* Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */ + {0x07ca, 0x039a}, /* Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */ + {0x07cb, 0x039b}, /* Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMDA */ + {0x07cc, 0x039c}, /* Greek_MU Μ GREEK CAPITAL LETTER MU */ + {0x07cd, 0x039d}, /* Greek_NU Ν GREEK CAPITAL LETTER NU */ + {0x07ce, 0x039e}, /* Greek_XI Ξ GREEK CAPITAL LETTER XI */ + {0x07cf, 0x039f}, /* Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */ + {0x07d0, 0x03a0}, /* Greek_PI Π GREEK CAPITAL LETTER PI */ + {0x07d1, 0x03a1}, /* Greek_RHO Ρ GREEK CAPITAL LETTER RHO */ + {0x07d2, 0x03a3}, /* Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */ + {0x07d4, 0x03a4}, /* Greek_TAU Τ GREEK CAPITAL LETTER TAU */ + {0x07d5, 0x03a5}, /* Greek_UPSILON Υ GREEK CAPITAL LETTER UPSILON */ + {0x07d6, 0x03a6}, /* Greek_PHI Φ GREEK CAPITAL LETTER PHI */ + {0x07d7, 0x03a7}, /* Greek_CHI Χ GREEK CAPITAL LETTER CHI */ + {0x07d8, 0x03a8}, /* Greek_PSI Ψ GREEK CAPITAL LETTER PSI */ + {0x07d9, 0x03a9}, /* Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */ + {0x07e1, 0x03b1}, /* Greek_alpha α GREEK SMALL LETTER ALPHA */ + {0x07e2, 0x03b2}, /* Greek_beta β GREEK SMALL LETTER BETA */ + {0x07e3, 0x03b3}, /* Greek_gamma γ GREEK SMALL LETTER GAMMA */ + {0x07e4, 0x03b4}, /* Greek_delta δ GREEK SMALL LETTER DELTA */ + {0x07e5, 0x03b5}, /* Greek_epsilon ε GREEK SMALL LETTER EPSILON */ + {0x07e6, 0x03b6}, /* Greek_zeta ζ GREEK SMALL LETTER ZETA */ + {0x07e7, 0x03b7}, /* Greek_eta η GREEK SMALL LETTER ETA */ + {0x07e8, 0x03b8}, /* Greek_theta θ GREEK SMALL LETTER THETA */ + {0x07e9, 0x03b9}, /* Greek_iota ι GREEK SMALL LETTER IOTA */ + {0x07ea, 0x03ba}, /* Greek_kappa κ GREEK SMALL LETTER KAPPA */ + {0x07eb, 0x03bb}, /* Greek_lambda λ GREEK SMALL LETTER LAMDA */ + {0x07ec, 0x03bc}, /* Greek_mu μ GREEK SMALL LETTER MU */ + {0x07ed, 0x03bd}, /* Greek_nu ν GREEK SMALL LETTER NU */ + {0x07ee, 0x03be}, /* Greek_xi ξ GREEK SMALL LETTER XI */ + {0x07ef, 0x03bf}, /* Greek_omicron ο GREEK SMALL LETTER OMICRON */ + {0x07f0, 0x03c0}, /* Greek_pi π GREEK SMALL LETTER PI */ + {0x07f1, 0x03c1}, /* Greek_rho ρ GREEK SMALL LETTER RHO */ + {0x07f2, 0x03c3}, /* Greek_sigma σ GREEK SMALL LETTER SIGMA */ + {0x07f3, 0x03c2}, /* Greek_finalsmallsigma ς GREEK SMALL LETTER FINAL SIGMA */ + {0x07f4, 0x03c4}, /* Greek_tau τ GREEK SMALL LETTER TAU */ + {0x07f5, 0x03c5}, /* Greek_upsilon υ GREEK SMALL LETTER UPSILON */ + {0x07f6, 0x03c6}, /* Greek_phi φ GREEK SMALL LETTER PHI */ + {0x07f7, 0x03c7}, /* Greek_chi χ GREEK SMALL LETTER CHI */ + {0x07f8, 0x03c8}, /* Greek_psi ψ GREEK SMALL LETTER PSI */ + {0x07f9, 0x03c9}, /* Greek_omega ω GREEK SMALL LETTER OMEGA */ + /* 0x08a1 leftradical ? ??? */ + /* 0x08a2 topleftradical ? ??? */ + /* 0x08a3 horizconnector ? ??? */ + {0x08a4, 0x2320}, /* topintegral ⌠ TOP HALF INTEGRAL */ + {0x08a5, 0x2321}, /* botintegral ⌡ BOTTOM HALF INTEGRAL */ + {0x08a6, 0x2502}, /* vertconnector │ BOX DRAWINGS LIGHT VERTICAL */ + /* 0x08a7 topleftsqbracket ? ??? */ + /* 0x08a8 botleftsqbracket ? ??? */ + /* 0x08a9 toprightsqbracket ? ??? */ + /* 0x08aa botrightsqbracket ? ??? */ + /* 0x08ab topleftparens ? ??? */ + /* 0x08ac botleftparens ? ??? */ + /* 0x08ad toprightparens ? ??? */ + /* 0x08ae botrightparens ? ??? */ + /* 0x08af leftmiddlecurlybrace ? ??? */ + /* 0x08b0 rightmiddlecurlybrace ? ??? */ + /* 0x08b1 topleftsummation ? ??? */ + /* 0x08b2 botleftsummation ? ??? */ + /* 0x08b3 topvertsummationconnector ? ??? */ + /* 0x08b4 botvertsummationconnector ? ??? */ + /* 0x08b5 toprightsummation ? ??? */ + /* 0x08b6 botrightsummation ? ??? */ + /* 0x08b7 rightmiddlesummation ? ??? */ + {0x08bc, 0x2264}, /* lessthanequal ≤ LESS-THAN OR EQUAL TO */ + {0x08bd, 0x2260}, /* notequal ≠ NOT EQUAL TO */ + {0x08be, 0x2265}, /* greaterthanequal ≥ GREATER-THAN OR EQUAL TO */ + {0x08bf, 0x222b}, /* integral ∫ INTEGRAL */ + {0x08c0, 0x2234}, /* therefore ∴ THEREFORE */ + {0x08c1, 0x221d}, /* variation ∝ PROPORTIONAL TO */ + {0x08c2, 0x221e}, /* infinity ∞ INFINITY */ + {0x08c5, 0x2207}, /* nabla ∇ NABLA */ + {0x08c8, 0x2245}, /* approximate ≅ APPROXIMATELY EQUAL TO */ + /* 0x08c9 similarequal ? ??? */ + {0x08cd, 0x21d4}, /* ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */ + {0x08ce, 0x21d2}, /* implies ⇒ RIGHTWARDS DOUBLE ARROW */ + {0x08cf, 0x2261}, /* identical ≡ IDENTICAL TO */ + {0x08d6, 0x221a}, /* radical √ SQUARE ROOT */ + {0x08da, 0x2282}, /* includedin ⊂ SUBSET OF */ + {0x08db, 0x2283}, /* includes ⊃ SUPERSET OF */ + {0x08dc, 0x2229}, /* intersection ∩ INTERSECTION */ + {0x08dd, 0x222a}, /* union ∪ UNION */ + {0x08de, 0x2227}, /* logicaland ∧ LOGICAL AND */ + {0x08df, 0x2228}, /* logicalor ∨ LOGICAL OR */ + {0x08ef, 0x2202}, /* partialderivative ∂ PARTIAL DIFFERENTIAL */ + {0x08f6, 0x0192}, /* function ƒ LATIN SMALL LETTER F WITH HOOK */ + {0x08fb, 0x2190}, /* leftarrow ← LEFTWARDS ARROW */ + {0x08fc, 0x2191}, /* uparrow ↑ UPWARDS ARROW */ + {0x08fd, 0x2192}, /* rightarrow → RIGHTWARDS ARROW */ + {0x08fe, 0x2193}, /* downarrow ↓ DOWNWARDS ARROW */ + {0x09df, 0x2422}, /* blank ␢ BLANK SYMBOL */ + {0x09e0, 0x25c6}, /* soliddiamond ◆ BLACK DIAMOND */ + {0x09e1, 0x2592}, /* checkerboard ▒ MEDIUM SHADE */ + {0x09e2, 0x2409}, /* ht ␉ SYMBOL FOR HORIZONTAL TABULATION */ + {0x09e3, 0x240c}, /* ff ␌ SYMBOL FOR FORM FEED */ + {0x09e4, 0x240d}, /* cr ␍ SYMBOL FOR CARRIAGE RETURN */ + {0x09e5, 0x240a}, /* lf ␊ SYMBOL FOR LINE FEED */ + {0x09e8, 0x2424}, /* nl ␤ SYMBOL FOR NEWLINE */ + {0x09e9, 0x240b}, /* vt ␋ SYMBOL FOR VERTICAL TABULATION */ + {0x09ea, 0x2518}, /* lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */ + {0x09eb, 0x2510}, /* uprightcorner ┐ BOX DRAWINGS LIGHT DOWN AND LEFT */ + {0x09ec, 0x250c}, /* upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */ + {0x09ed, 0x2514}, /* lowleftcorner └ BOX DRAWINGS LIGHT UP AND RIGHT */ + {0x09ee, 0x253c}, /* crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ + /* 0x09ef horizlinescan1 ? ??? */ + /* 0x09f0 horizlinescan3 ? ??? */ + {0x09f1, 0x2500}, /* horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */ + /* 0x09f2 horizlinescan7 ? ??? */ + /* 0x09f3 horizlinescan9 ? ??? */ + {0x09f4, 0x251c}, /* leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ + {0x09f5, 0x2524}, /* rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */ + {0x09f6, 0x2534}, /* bott ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL */ + {0x09f7, 0x252c}, /* topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ + {0x09f8, 0x2502}, /* vertbar │ BOX DRAWINGS LIGHT VERTICAL */ + {0x0aa1, 0x2003}, /* emspace   EM SPACE */ + {0x0aa2, 0x2002}, /* enspace   EN SPACE */ + {0x0aa3, 0x2004}, /* em3space   THREE-PER-EM SPACE */ + {0x0aa4, 0x2005}, /* em4space   FOUR-PER-EM SPACE */ + {0x0aa5, 0x2007}, /* digitspace   FIGURE SPACE */ + {0x0aa6, 0x2008}, /* punctspace   PUNCTUATION SPACE */ + {0x0aa7, 0x2009}, /* thinspace   THIN SPACE */ + {0x0aa8, 0x200a}, /* hairspace   HAIR SPACE */ + {0x0aa9, 0x2014}, /* emdash — EM DASH */ + {0x0aaa, 0x2013}, /* endash – EN DASH */ + /* 0x0aac signifblank ? ??? */ + {0x0aae, 0x2026}, /* ellipsis … HORIZONTAL ELLIPSIS */ + /* 0x0aaf doubbaselinedot ? ??? */ + {0x0ab0, 0x2153}, /* onethird ⅓ VULGAR FRACTION ONE THIRD */ + {0x0ab1, 0x2154}, /* twothirds ⅔ VULGAR FRACTION TWO THIRDS */ + {0x0ab2, 0x2155}, /* onefifth ⅕ VULGAR FRACTION ONE FIFTH */ + {0x0ab3, 0x2156}, /* twofifths ⅖ VULGAR FRACTION TWO FIFTHS */ + {0x0ab4, 0x2157}, /* threefifths ⅗ VULGAR FRACTION THREE FIFTHS */ + {0x0ab5, 0x2158}, /* fourfifths ⅘ VULGAR FRACTION FOUR FIFTHS */ + {0x0ab6, 0x2159}, /* onesixth ⅙ VULGAR FRACTION ONE SIXTH */ + {0x0ab7, 0x215a}, /* fivesixths ⅚ VULGAR FRACTION FIVE SIXTHS */ + {0x0ab8, 0x2105}, /* careof ℅ CARE OF */ + {0x0abb, 0x2012}, /* figdash ‒ FIGURE DASH */ + {0x0abc, 0x2329}, /* leftanglebracket 〈 LEFT-POINTING ANGLE BRACKET */ + {0x0abd, 0x002e}, /* decimalpoint . FULL STOP */ + {0x0abe, 0x232a}, /* rightanglebracket 〉 RIGHT-POINTING ANGLE BRACKET */ + /* 0x0abf marker ? ??? */ + {0x0ac3, 0x215b}, /* oneeighth ⅛ VULGAR FRACTION ONE EIGHTH */ + {0x0ac4, 0x215c}, /* threeeighths ⅜ VULGAR FRACTION THREE EIGHTHS */ + {0x0ac5, 0x215d}, /* fiveeighths ⅝ VULGAR FRACTION FIVE EIGHTHS */ + {0x0ac6, 0x215e}, /* seveneighths ⅞ VULGAR FRACTION SEVEN EIGHTHS */ + {0x0ac9, 0x2122}, /* trademark ™ TRADE MARK SIGN */ + {0x0aca, 0x2613}, /* signaturemark ☓ SALTIRE */ + /* 0x0acb trademarkincircle ? ??? */ + {0x0acc, 0x25c1}, /* leftopentriangle ◁ WHITE LEFT-POINTING TRIANGLE */ + {0x0acd, 0x25b7}, /* rightopentriangle ▷ WHITE RIGHT-POINTING TRIANGLE */ + {0x0ace, 0x25cb}, /* emopencircle ○ WHITE CIRCLE */ + {0x0acf, 0x25a1}, /* emopenrectangle □ WHITE SQUARE */ + {0x0ad0, 0x2018}, /* leftsinglequotemark ‘ LEFT SINGLE QUOTATION MARK */ + {0x0ad1, 0x2019}, /* rightsinglequotemark ’ RIGHT SINGLE QUOTATION MARK */ + {0x0ad2, 0x201c}, /* leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */ + {0x0ad3, 0x201d}, /* rightdoublequotemark ” RIGHT DOUBLE QUOTATION MARK */ + {0x0ad4, 0x211e}, /* prescription ℞ PRESCRIPTION TAKE */ + {0x0ad6, 0x2032}, /* minutes ′ PRIME */ + {0x0ad7, 0x2033}, /* seconds ″ DOUBLE PRIME */ + {0x0ad9, 0x271d}, /* latincross ✝ LATIN CROSS */ + /* 0x0ada hexagram ? ??? */ + {0x0adb, 0x25ac}, /* filledrectbullet ▬ BLACK RECTANGLE */ + {0x0adc, 0x25c0}, /* filledlefttribullet ◀ BLACK LEFT-POINTING TRIANGLE */ + {0x0add, 0x25b6}, /* filledrighttribullet ▶ BLACK RIGHT-POINTING TRIANGLE */ + {0x0ade, 0x25cf}, /* emfilledcircle ● BLACK CIRCLE */ + {0x0adf, 0x25a0}, /* emfilledrect ■ BLACK SQUARE */ + {0x0ae0, 0x25e6}, /* enopencircbullet ◦ WHITE BULLET */ + {0x0ae1, 0x25ab}, /* enopensquarebullet ▫ WHITE SMALL SQUARE */ + {0x0ae2, 0x25ad}, /* openrectbullet ▭ WHITE RECTANGLE */ + {0x0ae3, 0x25b3}, /* opentribulletup △ WHITE UP-POINTING TRIANGLE */ + {0x0ae4, 0x25bd}, /* opentribulletdown ▽ WHITE DOWN-POINTING TRIANGLE */ + {0x0ae5, 0x2606}, /* openstar ☆ WHITE STAR */ + {0x0ae6, 0x2022}, /* enfilledcircbullet • BULLET */ + {0x0ae7, 0x25aa}, /* enfilledsqbullet ▪ BLACK SMALL SQUARE */ + {0x0ae8, 0x25b2}, /* filledtribulletup ▲ BLACK UP-POINTING TRIANGLE */ + {0x0ae9, 0x25bc}, /* filledtribulletdown ▼ BLACK DOWN-POINTING TRIANGLE */ + {0x0aea, 0x261c}, /* leftpointer ☜ WHITE LEFT POINTING INDEX */ + {0x0aeb, 0x261e}, /* rightpointer ☞ WHITE RIGHT POINTING INDEX */ + {0x0aec, 0x2663}, /* club ♣ BLACK CLUB SUIT */ + {0x0aed, 0x2666}, /* diamond ♦ BLACK DIAMOND SUIT */ + {0x0aee, 0x2665}, /* heart ♥ BLACK HEART SUIT */ + {0x0af0, 0x2720}, /* maltesecross ✠ MALTESE CROSS */ + {0x0af1, 0x2020}, /* dagger † DAGGER */ + {0x0af2, 0x2021}, /* doubledagger ‡ DOUBLE DAGGER */ + {0x0af3, 0x2713}, /* checkmark ✓ CHECK MARK */ + {0x0af4, 0x2717}, /* ballotcross ✗ BALLOT X */ + {0x0af5, 0x266f}, /* musicalsharp ♯ MUSIC SHARP SIGN */ + {0x0af6, 0x266d}, /* musicalflat ♭ MUSIC FLAT SIGN */ + {0x0af7, 0x2642}, /* malesymbol ♂ MALE SIGN */ + {0x0af8, 0x2640}, /* femalesymbol ♀ FEMALE SIGN */ + {0x0af9, 0x260e}, /* telephone ☎ BLACK TELEPHONE */ + {0x0afa, 0x2315}, /* telephonerecorder ⌕ TELEPHONE RECORDER */ + {0x0afb, 0x2117}, /* phonographcopyright ℗ SOUND RECORDING COPYRIGHT */ + {0x0afc, 0x2038}, /* caret ‸ CARET */ + {0x0afd, 0x201a}, /* singlelowquotemark ‚ SINGLE LOW-9 QUOTATION MARK */ + {0x0afe, 0x201e}, /* doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */ + /* 0x0aff cursor ? ??? */ + {0x0ba3, 0x003c}, /* leftcaret < LESS-THAN SIGN */ + {0x0ba6, 0x003e}, /* rightcaret > GREATER-THAN SIGN */ + {0x0ba8, 0x2228}, /* downcaret ∨ LOGICAL OR */ + {0x0ba9, 0x2227}, /* upcaret ∧ LOGICAL AND */ + {0x0bc0, 0x00af}, /* overbar ¯ MACRON */ + {0x0bc2, 0x22a4}, /* downtack ⊤ DOWN TACK */ + {0x0bc3, 0x2229}, /* upshoe ∩ INTERSECTION */ + {0x0bc4, 0x230a}, /* downstile ⌊ LEFT FLOOR */ + {0x0bc6, 0x005f}, /* underbar _ LOW LINE */ + {0x0bca, 0x2218}, /* jot ∘ RING OPERATOR */ + {0x0bcc, 0x2395}, /* quad ⎕ APL FUNCTIONAL SYMBOL QUAD */ + {0x0bce, 0x22a5}, /* uptack ⊥ UP TACK */ + {0x0bcf, 0x25cb}, /* circle ○ WHITE CIRCLE */ + {0x0bd3, 0x2308}, /* upstile ⌈ LEFT CEILING */ + {0x0bd6, 0x222a}, /* downshoe ∪ UNION */ + {0x0bd8, 0x2283}, /* rightshoe ⊃ SUPERSET OF */ + {0x0bda, 0x2282}, /* leftshoe ⊂ SUBSET OF */ + {0x0bdc, 0x22a3}, /* lefttack ⊣ LEFT TACK */ + {0x0bfc, 0x22a2}, /* righttack ⊢ RIGHT TACK */ + {0x0cdf, 0x2017}, /* hebrew_doublelowline ‗ DOUBLE LOW LINE */ + {0x0ce0, 0x05d0}, /* hebrew_aleph א HEBREW LETTER ALEF */ + {0x0ce1, 0x05d1}, /* hebrew_bet ב HEBREW LETTER BET */ + {0x0ce2, 0x05d2}, /* hebrew_gimel ג HEBREW LETTER GIMEL */ + {0x0ce3, 0x05d3}, /* hebrew_dalet ד HEBREW LETTER DALET */ + {0x0ce4, 0x05d4}, /* hebrew_he ה HEBREW LETTER HE */ + {0x0ce5, 0x05d5}, /* hebrew_waw ו HEBREW LETTER VAV */ + {0x0ce6, 0x05d6}, /* hebrew_zain ז HEBREW LETTER ZAYIN */ + {0x0ce7, 0x05d7}, /* hebrew_chet ח HEBREW LETTER HET */ + {0x0ce8, 0x05d8}, /* hebrew_tet ט HEBREW LETTER TET */ + {0x0ce9, 0x05d9}, /* hebrew_yod י HEBREW LETTER YOD */ + {0x0cea, 0x05da}, /* hebrew_finalkaph ך HEBREW LETTER FINAL KAF */ + {0x0ceb, 0x05db}, /* hebrew_kaph כ HEBREW LETTER KAF */ + {0x0cec, 0x05dc}, /* hebrew_lamed ל HEBREW LETTER LAMED */ + {0x0ced, 0x05dd}, /* hebrew_finalmem ם HEBREW LETTER FINAL MEM */ + {0x0cee, 0x05de}, /* hebrew_mem מ HEBREW LETTER MEM */ + {0x0cef, 0x05df}, /* hebrew_finalnun ן HEBREW LETTER FINAL NUN */ + {0x0cf0, 0x05e0}, /* hebrew_nun נ HEBREW LETTER NUN */ + {0x0cf1, 0x05e1}, /* hebrew_samech ס HEBREW LETTER SAMEKH */ + {0x0cf2, 0x05e2}, /* hebrew_ayin ע HEBREW LETTER AYIN */ + {0x0cf3, 0x05e3}, /* hebrew_finalpe ף HEBREW LETTER FINAL PE */ + {0x0cf4, 0x05e4}, /* hebrew_pe פ HEBREW LETTER PE */ + {0x0cf5, 0x05e5}, /* hebrew_finalzade ץ HEBREW LETTER FINAL TSADI */ + {0x0cf6, 0x05e6}, /* hebrew_zade צ HEBREW LETTER TSADI */ + {0x0cf7, 0x05e7}, /* hebrew_qoph ק HEBREW LETTER QOF */ + {0x0cf8, 0x05e8}, /* hebrew_resh ר HEBREW LETTER RESH */ + {0x0cf9, 0x05e9}, /* hebrew_shin ש HEBREW LETTER SHIN */ + {0x0cfa, 0x05ea}, /* hebrew_taw ת HEBREW LETTER TAV */ + {0x0da1, 0x0e01}, /* Thai_kokai ก THAI CHARACTER KO KAI */ + {0x0da2, 0x0e02}, /* Thai_khokhai ข THAI CHARACTER KHO KHAI */ + {0x0da3, 0x0e03}, /* Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */ + {0x0da4, 0x0e04}, /* Thai_khokhwai ค THAI CHARACTER KHO KHWAI */ + {0x0da5, 0x0e05}, /* Thai_khokhon ฅ THAI CHARACTER KHO KHON */ + {0x0da6, 0x0e06}, /* Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */ + {0x0da7, 0x0e07}, /* Thai_ngongu ง THAI CHARACTER NGO NGU */ + {0x0da8, 0x0e08}, /* Thai_chochan จ THAI CHARACTER CHO CHAN */ + {0x0da9, 0x0e09}, /* Thai_choching ฉ THAI CHARACTER CHO CHING */ + {0x0daa, 0x0e0a}, /* Thai_chochang ช THAI CHARACTER CHO CHANG */ + {0x0dab, 0x0e0b}, /* Thai_soso ซ THAI CHARACTER SO SO */ + {0x0dac, 0x0e0c}, /* Thai_chochoe ฌ THAI CHARACTER CHO CHOE */ + {0x0dad, 0x0e0d}, /* Thai_yoying ญ THAI CHARACTER YO YING */ + {0x0dae, 0x0e0e}, /* Thai_dochada ฎ THAI CHARACTER DO CHADA */ + {0x0daf, 0x0e0f}, /* Thai_topatak ฏ THAI CHARACTER TO PATAK */ + {0x0db0, 0x0e10}, /* Thai_thothan ฐ THAI CHARACTER THO THAN */ + {0x0db1, 0x0e11}, /* Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */ + {0x0db2, 0x0e12}, /* Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */ + {0x0db3, 0x0e13}, /* Thai_nonen ณ THAI CHARACTER NO NEN */ + {0x0db4, 0x0e14}, /* Thai_dodek ด THAI CHARACTER DO DEK */ + {0x0db5, 0x0e15}, /* Thai_totao ต THAI CHARACTER TO TAO */ + {0x0db6, 0x0e16}, /* Thai_thothung ถ THAI CHARACTER THO THUNG */ + {0x0db7, 0x0e17}, /* Thai_thothahan ท THAI CHARACTER THO THAHAN */ + {0x0db8, 0x0e18}, /* Thai_thothong ธ THAI CHARACTER THO THONG */ + {0x0db9, 0x0e19}, /* Thai_nonu น THAI CHARACTER NO NU */ + {0x0dba, 0x0e1a}, /* Thai_bobaimai บ THAI CHARACTER BO BAIMAI */ + {0x0dbb, 0x0e1b}, /* Thai_popla ป THAI CHARACTER PO PLA */ + {0x0dbc, 0x0e1c}, /* Thai_phophung ผ THAI CHARACTER PHO PHUNG */ + {0x0dbd, 0x0e1d}, /* Thai_fofa ฝ THAI CHARACTER FO FA */ + {0x0dbe, 0x0e1e}, /* Thai_phophan พ THAI CHARACTER PHO PHAN */ + {0x0dbf, 0x0e1f}, /* Thai_fofan ฟ THAI CHARACTER FO FAN */ + {0x0dc0, 0x0e20}, /* Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */ + {0x0dc1, 0x0e21}, /* Thai_moma ม THAI CHARACTER MO MA */ + {0x0dc2, 0x0e22}, /* Thai_yoyak ย THAI CHARACTER YO YAK */ + {0x0dc3, 0x0e23}, /* Thai_rorua ร THAI CHARACTER RO RUA */ + {0x0dc4, 0x0e24}, /* Thai_ru ฤ THAI CHARACTER RU */ + {0x0dc5, 0x0e25}, /* Thai_loling ล THAI CHARACTER LO LING */ + {0x0dc6, 0x0e26}, /* Thai_lu ฦ THAI CHARACTER LU */ + {0x0dc7, 0x0e27}, /* Thai_wowaen ว THAI CHARACTER WO WAEN */ + {0x0dc8, 0x0e28}, /* Thai_sosala ศ THAI CHARACTER SO SALA */ + {0x0dc9, 0x0e29}, /* Thai_sorusi ษ THAI CHARACTER SO RUSI */ + {0x0dca, 0x0e2a}, /* Thai_sosua ส THAI CHARACTER SO SUA */ + {0x0dcb, 0x0e2b}, /* Thai_hohip ห THAI CHARACTER HO HIP */ + {0x0dcc, 0x0e2c}, /* Thai_lochula ฬ THAI CHARACTER LO CHULA */ + {0x0dcd, 0x0e2d}, /* Thai_oang อ THAI CHARACTER O ANG */ + {0x0dce, 0x0e2e}, /* Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */ + {0x0dcf, 0x0e2f}, /* Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */ + {0x0dd0, 0x0e30}, /* Thai_saraa ะ THAI CHARACTER SARA A */ + {0x0dd1, 0x0e31}, /* Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */ + {0x0dd2, 0x0e32}, /* Thai_saraaa า THAI CHARACTER SARA AA */ + {0x0dd3, 0x0e33}, /* Thai_saraam ำ THAI CHARACTER SARA AM */ + {0x0dd4, 0x0e34}, /* Thai_sarai ิ THAI CHARACTER SARA I */ + {0x0dd5, 0x0e35}, /* Thai_saraii ี THAI CHARACTER SARA II */ + {0x0dd6, 0x0e36}, /* Thai_saraue ึ THAI CHARACTER SARA UE */ + {0x0dd7, 0x0e37}, /* Thai_sarauee ื THAI CHARACTER SARA UEE */ + {0x0dd8, 0x0e38}, /* Thai_sarau ุ THAI CHARACTER SARA U */ + {0x0dd9, 0x0e39}, /* Thai_sarauu ู THAI CHARACTER SARA UU */ + {0x0dda, 0x0e3a}, /* Thai_phinthu ฺ THAI CHARACTER PHINTHU */ + {0x0dde, 0x0e3e}, /* Thai_maihanakat_maitho ฾ ??? */ + {0x0ddf, 0x0e3f}, /* Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */ + {0x0de0, 0x0e40}, /* Thai_sarae เ THAI CHARACTER SARA E */ + {0x0de1, 0x0e41}, /* Thai_saraae แ THAI CHARACTER SARA AE */ + {0x0de2, 0x0e42}, /* Thai_sarao โ THAI CHARACTER SARA O */ + {0x0de3, 0x0e43}, /* Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */ + {0x0de4, 0x0e44}, /* Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */ + {0x0de5, 0x0e45}, /* Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */ + {0x0de6, 0x0e46}, /* Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */ + {0x0de7, 0x0e47}, /* Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */ + {0x0de8, 0x0e48}, /* Thai_maiek ่ THAI CHARACTER MAI EK */ + {0x0de9, 0x0e49}, /* Thai_maitho ้ THAI CHARACTER MAI THO */ + {0x0dea, 0x0e4a}, /* Thai_maitri ๊ THAI CHARACTER MAI TRI */ + {0x0deb, 0x0e4b}, /* Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */ + {0x0dec, 0x0e4c}, /* Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */ + {0x0ded, 0x0e4d}, /* Thai_nikhahit ํ THAI CHARACTER NIKHAHIT */ + {0x0df0, 0x0e50}, /* Thai_leksun ๐ THAI DIGIT ZERO */ + {0x0df1, 0x0e51}, /* Thai_leknung ๑ THAI DIGIT ONE */ + {0x0df2, 0x0e52}, /* Thai_leksong ๒ THAI DIGIT TWO */ + {0x0df3, 0x0e53}, /* Thai_leksam ๓ THAI DIGIT THREE */ + {0x0df4, 0x0e54}, /* Thai_leksi ๔ THAI DIGIT FOUR */ + {0x0df5, 0x0e55}, /* Thai_lekha ๕ THAI DIGIT FIVE */ + {0x0df6, 0x0e56}, /* Thai_lekhok ๖ THAI DIGIT SIX */ + {0x0df7, 0x0e57}, /* Thai_lekchet ๗ THAI DIGIT SEVEN */ + {0x0df8, 0x0e58}, /* Thai_lekpaet ๘ THAI DIGIT EIGHT */ + {0x0df9, 0x0e59}, /* Thai_lekkao ๙ THAI DIGIT NINE */ + {0x0ea1, 0x3131}, /* Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */ + {0x0ea2, 0x3132}, /* Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */ + {0x0ea3, 0x3133}, /* Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */ + {0x0ea4, 0x3134}, /* Hangul_Nieun ㄴ HANGUL LETTER NIEUN */ + {0x0ea5, 0x3135}, /* Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */ + {0x0ea6, 0x3136}, /* Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */ + {0x0ea7, 0x3137}, /* Hangul_Dikeud ㄷ HANGUL LETTER TIKEUT */ + {0x0ea8, 0x3138}, /* Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */ + {0x0ea9, 0x3139}, /* Hangul_Rieul ㄹ HANGUL LETTER RIEUL */ + {0x0eaa, 0x313a}, /* Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */ + {0x0eab, 0x313b}, /* Hangul_RieulMieum ㄻ HANGUL LETTER RIEUL-MIEUM */ + {0x0eac, 0x313c}, /* Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */ + {0x0ead, 0x313d}, /* Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */ + {0x0eae, 0x313e}, /* Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */ + {0x0eaf, 0x313f}, /* Hangul_RieulPhieuf ㄿ HANGUL LETTER RIEUL-PHIEUPH */ + {0x0eb0, 0x3140}, /* Hangul_RieulHieuh ㅀ HANGUL LETTER RIEUL-HIEUH */ + {0x0eb1, 0x3141}, /* Hangul_Mieum ㅁ HANGUL LETTER MIEUM */ + {0x0eb2, 0x3142}, /* Hangul_Pieub ㅂ HANGUL LETTER PIEUP */ + {0x0eb3, 0x3143}, /* Hangul_SsangPieub ㅃ HANGUL LETTER SSANGPIEUP */ + {0x0eb4, 0x3144}, /* Hangul_PieubSios ㅄ HANGUL LETTER PIEUP-SIOS */ + {0x0eb5, 0x3145}, /* Hangul_Sios ㅅ HANGUL LETTER SIOS */ + {0x0eb6, 0x3146}, /* Hangul_SsangSios ㅆ HANGUL LETTER SSANGSIOS */ + {0x0eb7, 0x3147}, /* Hangul_Ieung ㅇ HANGUL LETTER IEUNG */ + {0x0eb8, 0x3148}, /* Hangul_Jieuj ㅈ HANGUL LETTER CIEUC */ + {0x0eb9, 0x3149}, /* Hangul_SsangJieuj ㅉ HANGUL LETTER SSANGCIEUC */ + {0x0eba, 0x314a}, /* Hangul_Cieuc ㅊ HANGUL LETTER CHIEUCH */ + {0x0ebb, 0x314b}, /* Hangul_Khieuq ㅋ HANGUL LETTER KHIEUKH */ + {0x0ebc, 0x314c}, /* Hangul_Tieut ㅌ HANGUL LETTER THIEUTH */ + {0x0ebd, 0x314d}, /* Hangul_Phieuf ㅍ HANGUL LETTER PHIEUPH */ + {0x0ebe, 0x314e}, /* Hangul_Hieuh ㅎ HANGUL LETTER HIEUH */ + {0x0ebf, 0x314f}, /* Hangul_A ㅏ HANGUL LETTER A */ + {0x0ec0, 0x3150}, /* Hangul_AE ㅐ HANGUL LETTER AE */ + {0x0ec1, 0x3151}, /* Hangul_YA ㅑ HANGUL LETTER YA */ + {0x0ec2, 0x3152}, /* Hangul_YAE ㅒ HANGUL LETTER YAE */ + {0x0ec3, 0x3153}, /* Hangul_EO ㅓ HANGUL LETTER EO */ + {0x0ec4, 0x3154}, /* Hangul_E ㅔ HANGUL LETTER E */ + {0x0ec5, 0x3155}, /* Hangul_YEO ㅕ HANGUL LETTER YEO */ + {0x0ec6, 0x3156}, /* Hangul_YE ㅖ HANGUL LETTER YE */ + {0x0ec7, 0x3157}, /* Hangul_O ㅗ HANGUL LETTER O */ + {0x0ec8, 0x3158}, /* Hangul_WA ㅘ HANGUL LETTER WA */ + {0x0ec9, 0x3159}, /* Hangul_WAE ㅙ HANGUL LETTER WAE */ + {0x0eca, 0x315a}, /* Hangul_OE ㅚ HANGUL LETTER OE */ + {0x0ecb, 0x315b}, /* Hangul_YO ㅛ HANGUL LETTER YO */ + {0x0ecc, 0x315c}, /* Hangul_U ㅜ HANGUL LETTER U */ + {0x0ecd, 0x315d}, /* Hangul_WEO ㅝ HANGUL LETTER WEO */ + {0x0ece, 0x315e}, /* Hangul_WE ㅞ HANGUL LETTER WE */ + {0x0ecf, 0x315f}, /* Hangul_WI ㅟ HANGUL LETTER WI */ + {0x0ed0, 0x3160}, /* Hangul_YU ㅠ HANGUL LETTER YU */ + {0x0ed1, 0x3161}, /* Hangul_EU ㅡ HANGUL LETTER EU */ + {0x0ed2, 0x3162}, /* Hangul_YI ㅢ HANGUL LETTER YI */ + {0x0ed3, 0x3163}, /* Hangul_I ㅣ HANGUL LETTER I */ + {0x0ed4, 0x11a8}, /* Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */ + {0x0ed5, 0x11a9}, /* Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */ + {0x0ed6, 0x11aa}, /* Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */ + {0x0ed7, 0x11ab}, /* Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */ + {0x0ed8, 0x11ac}, /* Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */ + {0x0ed9, 0x11ad}, /* Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */ + {0x0eda, 0x11ae}, /* Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */ + {0x0edb, 0x11af}, /* Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */ + {0x0edc, 0x11b0}, /* Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */ + {0x0edd, 0x11b1}, /* Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */ + {0x0ede, 0x11b2}, /* Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */ + {0x0edf, 0x11b3}, /* Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */ + {0x0ee0, 0x11b4}, /* Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */ + {0x0ee1, 0x11b5}, /* Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */ + {0x0ee2, 0x11b6}, /* Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */ + {0x0ee3, 0x11b7}, /* Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */ + {0x0ee4, 0x11b8}, /* Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */ + {0x0ee5, 0x11b9}, /* Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */ + {0x0ee6, 0x11ba}, /* Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */ + {0x0ee7, 0x11bb}, /* Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */ + {0x0ee8, 0x11bc}, /* Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */ + {0x0ee9, 0x11bd}, /* Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */ + {0x0eea, 0x11be}, /* Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */ + {0x0eeb, 0x11bf}, /* Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */ + {0x0eec, 0x11c0}, /* Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */ + {0x0eed, 0x11c1}, /* Hangul_J_Phieuf ᇁ HANGUL JONGSEONG PHIEUPH */ + {0x0eee, 0x11c2}, /* Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */ + {0x0eef, 0x316d}, /* Hangul_RieulYeorinHieuh ㅭ HANGUL LETTER RIEUL-YEORINHIEUH */ + {0x0ef0, 0x3171}, /* Hangul_SunkyeongeumMieum ㅱ HANGUL LETTER KAPYEOUNMIEUM */ + {0x0ef1, 0x3178}, /* Hangul_SunkyeongeumPieub ㅸ HANGUL LETTER KAPYEOUNPIEUP */ + {0x0ef2, 0x317f}, /* Hangul_PanSios ㅿ HANGUL LETTER PANSIOS */ + /* 0x0ef3 Hangul_KkogjiDalrinIeung ? ??? */ + {0x0ef4, 0x3184}, /* Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */ + {0x0ef5, 0x3186}, /* Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */ + {0x0ef6, 0x318d}, /* Hangul_AraeA ㆍ HANGUL LETTER ARAEA */ + {0x0ef7, 0x318e}, /* Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */ + {0x0ef8, 0x11eb}, /* Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */ + /* 0x0ef9 Hangul_J_KkogjiDalrinIeung ? ??? */ + {0x0efa, 0x11f9}, /* Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */ + {0x0eff, 0x20a9}, /* Korean_Won ₩ WON SIGN */ + {0x13bc, 0x0152}, /* OE Œ LATIN CAPITAL LIGATURE OE */ + {0x13bd, 0x0153}, /* oe œ LATIN SMALL LIGATURE OE */ + {0x13be, 0x0178}, /* Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */ + {0x20ac, 0x20ac}, /* EuroSign € EURO SIGN */ }; long keysym2ucs(xcb_keysym_t keysym) { diff --git a/i3-input/main.c b/i3-input/main.c index 97d574a2..785a133f 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -33,6 +33,10 @@ #include "i3-input.h" +#define MAX_WIDTH logical_px(500) +#define BORDER logical_px(2) +#define PADDING logical_px(2) + /* IPC format string. %s will be replaced with what the user entered, then * the command will be sent to i3 */ static char *format; @@ -42,8 +46,7 @@ static int sockfd; static xcb_key_symbols_t *symbols; static bool modeswitch_active = false; static xcb_window_t win; -static xcb_pixmap_t pixmap; -static xcb_gcontext_t pixmap_gc; +static surface_t surface; static xcb_char2b_t glyphs_ucs[512]; static char *glyphs_utf8[512]; static int input_position; @@ -54,7 +57,6 @@ static int limit; xcb_window_t root; xcb_connection_t *conn; xcb_screen_t *root_screen; -static xcb_get_input_focus_cookie_t focus_cookie; /* * Having verboselog(), errorlog() and debuglog() is necessary when using libi3. @@ -79,24 +81,6 @@ void errorlog(char *fmt, ...) { void debuglog(char *fmt, ...) { } -/* - * Restores the X11 input focus to wherever it was before. - * This is necessary because i3-input’s window has override_redirect=1 - * (→ unmanaged by the window manager) and thus i3-input changes focus itself. - * This function is called on exit(). - * - */ -static void restore_input_focus(void) { - xcb_generic_error_t *error; - xcb_get_input_focus_reply_t *reply = xcb_get_input_focus_reply(conn, focus_cookie, &error); - if (error != NULL) { - fprintf(stderr, "[i3-input] ERROR: Could not restore input focus (X error %d)\n", error->error_code); - return; - } - xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, reply->focus, XCB_CURRENT_TIME); - xcb_flush(conn); -} - /* * Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for * rendering it (UCS-2) or sending it to i3 (UTF-8). @@ -128,30 +112,30 @@ static uint8_t *concat_strings(char **glyphs, int max) { static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) { printf("expose!\n"); - /* re-draw the background */ - xcb_rectangle_t border = {0, 0, logical_px(500), font.height + logical_px(8)}, - inner = {logical_px(2), logical_px(2), logical_px(496), font.height + logical_px(8) - logical_px(4)}; - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#FF0000")}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); + color_t border_color = draw_util_hex_to_color("#FF0000"); + color_t fg_color = draw_util_hex_to_color("#FFFFFF"); + color_t bg_color = draw_util_hex_to_color("#000000"); - /* restore font color */ - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); + int text_offset = BORDER + PADDING; + + /* draw border */ + draw_util_rectangle(&surface, border_color, 0, 0, surface.width, surface.height); + + /* draw background */ + draw_util_rectangle(&surface, bg_color, BORDER, BORDER, surface.width - 2 * BORDER, surface.height - 2 * BORDER); /* draw the prompt … */ if (prompt != NULL) { - draw_text(prompt, pixmap, pixmap_gc, NULL, logical_px(4), logical_px(4), logical_px(492)); + draw_util_text(prompt, &surface, fg_color, bg_color, text_offset, text_offset, MAX_WIDTH - text_offset); } + /* … and the text */ if (input_position > 0) { i3String *input = i3string_from_ucs2(glyphs_ucs, input_position); - draw_text(input, pixmap, pixmap_gc, NULL, prompt_offset + logical_px(4), logical_px(4), logical_px(492)); + draw_util_text(input, &surface, fg_color, bg_color, text_offset + prompt_offset, text_offset, MAX_WIDTH - text_offset); i3string_free(input); } - /* Copy the contents of the pixmap to the real window */ - xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, logical_px(500), font.height + logical_px(8)); xcb_flush(conn); return 1; @@ -208,10 +192,6 @@ static void finish_input() { /* prefix the command if a prefix was specified on commandline */ printf("command = %s\n", full); - restore_input_focus(); - - xcb_aux_sync(conn); - ipc_send_message(sockfd, strlen(full), 0, (uint8_t *)full); free(full); @@ -265,7 +245,6 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press return 1; } if (sym == XK_Escape) { - restore_input_focus(); exit(0); } @@ -311,7 +290,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press } static xcb_rectangle_t get_window_position(void) { - xcb_rectangle_t result = (xcb_rectangle_t){logical_px(50), logical_px(50), logical_px(500), font.height + logical_px(8)}; + xcb_rectangle_t result = (xcb_rectangle_t){logical_px(50), logical_px(50), MAX_WIDTH, font.height + 2 * BORDER + 2 * PADDING}; xcb_get_property_reply_t *supporting_wm_reply = NULL; xcb_get_input_focus_reply_t *input_focus = NULL; @@ -467,14 +446,12 @@ int main(int argc, char *argv[]) { sockfd = ipc_connect(socket_path); - /* Request the current InputFocus to restore when i3-input exits. */ - focus_cookie = xcb_get_input_focus(conn); - root_screen = xcb_aux_get_screen(conn, screen); root = root_screen->root; symbols = xcb_key_symbols_alloc(conn); + init_dpi(); font = load_font(pattern, true); set_font(&font); @@ -503,15 +480,8 @@ int main(int argc, char *argv[]) { /* Map the window (make it visible) */ xcb_map_window(conn, win); - /* Create pixmap */ - pixmap = xcb_generate_id(conn); - pixmap_gc = xcb_generate_id(conn); - xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, logical_px(500), font.height + logical_px(8)); - xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); - - /* Set input focus (we have override_redirect=1, so the wm will not do - * this for us) */ - xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME); + /* Initialize the drawable surface */ + draw_util_surface_init(conn, &surface, win, get_visualtype(root_screen), win_pos.width, win_pos.height); /* Grab the keyboard to get all input */ xcb_flush(conn); @@ -531,7 +501,6 @@ int main(int argc, char *argv[]) { if (reply->status != XCB_GRAB_STATUS_SUCCESS) { fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status); - restore_input_focus(); exit(-1); } @@ -557,12 +526,16 @@ int main(int argc, char *argv[]) { break; case XCB_EXPOSE: - handle_expose(NULL, conn, (xcb_expose_event_t *)event); + if (((xcb_expose_event_t *)event)->count == 0) { + handle_expose(NULL, conn, (xcb_expose_event_t *)event); + } + break; } free(event); } + draw_util_surface_free(conn, &surface); return 0; } diff --git a/i3-msg/main.c b/i3-msg/main.c index 02e156a1..1a172789 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -119,6 +119,43 @@ static yajl_callbacks reply_callbacks = { .yajl_end_map = reply_end_map_cb, }; +/******************************************************************************* + * Config reply callbacks + *******************************************************************************/ + +static char *config_last_key = NULL; + +static int config_string_cb(void *params, const unsigned char *val, size_t len) { + char *str = scalloc(len + 1, 1); + strncpy(str, (const char *)val, len); + if (strcmp(config_last_key, "config") == 0) { + fprintf(stdout, "%s", str); + } + free(str); + return 1; +} + +static int config_start_map_cb(void *params) { + return 1; +} + +static int config_end_map_cb(void *params) { + return 1; +} + +static int config_map_key_cb(void *params, const unsigned char *keyVal, size_t keyLen) { + config_last_key = scalloc(keyLen + 1, 1); + strncpy(config_last_key, (const char *)keyVal, keyLen); + return 1; +} + +static yajl_callbacks config_callbacks = { + .yajl_string = config_string_cb, + .yajl_start_map = config_start_map_cb, + .yajl_map_key = config_map_key_cb, + .yajl_end_map = config_end_map_cb, +}; + int main(int argc, char *argv[]) { #if defined(__OpenBSD__) if (pledge("stdio rpath unix", NULL) == -1) @@ -150,25 +187,27 @@ int main(int argc, char *argv[]) { free(socket_path); socket_path = sstrdup(optarg); } else if (o == 't') { - if (strcasecmp(optarg, "command") == 0) + if (strcasecmp(optarg, "command") == 0) { message_type = I3_IPC_MESSAGE_TYPE_COMMAND; - else if (strcasecmp(optarg, "get_workspaces") == 0) + } else if (strcasecmp(optarg, "get_workspaces") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES; - else if (strcasecmp(optarg, "get_outputs") == 0) + } else if (strcasecmp(optarg, "get_outputs") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS; - else if (strcasecmp(optarg, "get_tree") == 0) + } else if (strcasecmp(optarg, "get_tree") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_TREE; - else if (strcasecmp(optarg, "get_marks") == 0) + } else if (strcasecmp(optarg, "get_marks") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS; - else if (strcasecmp(optarg, "get_bar_config") == 0) + } else if (strcasecmp(optarg, "get_bar_config") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG; - else if (strcasecmp(optarg, "get_binding_modes") == 0) + } else if (strcasecmp(optarg, "get_binding_modes") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_BINDING_MODES; - else if (strcasecmp(optarg, "get_version") == 0) + } else if (strcasecmp(optarg, "get_version") == 0) { message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION; - else { + } else if (strcasecmp(optarg, "get_config") == 0) { + message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG; + } else { printf("Unknown message type\n"); - printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version\n"); + printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config\n"); exit(EXIT_FAILURE); } } else if (o == 'q') { @@ -180,6 +219,8 @@ int main(int argc, char *argv[]) { printf("i3-msg " I3_VERSION "\n"); printf("i3-msg [-s ] [-t ] \n"); return 0; + } else if (o == '?') { + exit(EXIT_FAILURE); } } @@ -239,7 +280,7 @@ int main(int argc, char *argv[]) { errx(EXIT_FAILURE, "IPC: Received reply of type %d but expected %d", reply_type, message_type); /* For the reply of commands, have a look if that command was successful. * If not, nicely format the error message. */ - if (reply_type == I3_IPC_MESSAGE_TYPE_COMMAND) { + if (reply_type == I3_IPC_REPLY_TYPE_COMMAND) { yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL); yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length); yajl_free(handle); @@ -254,8 +295,24 @@ int main(int argc, char *argv[]) { /* NB: We still fall-through and print the reply, because even if one * command failed, that doesn’t mean that all commands failed. */ + } else if (reply_type == I3_IPC_REPLY_TYPE_CONFIG) { + yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL); + yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length); + yajl_free(handle); + + switch (state) { + case yajl_status_ok: + break; + case yajl_status_client_canceled: + case yajl_status_error: + errx(EXIT_FAILURE, "IPC: Could not parse JSON reply."); + } + + goto exit; } printf("%.*s\n", reply_length, reply); + +exit: free(reply); close(sockfd); diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 2e9e77d4..7d38f731 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -38,6 +38,13 @@ * constant for that. */ #define XCB_CURSOR_LEFT_PTR 68 +#define MSG_PADDING logical_px(8) +#define BTN_PADDING logical_px(3) +#define BTN_BORDER logical_px(3) +#define BTN_GAP logical_px(20) +#define CLOSE_BTN_GAP logical_px(15) +#define BAR_BORDER logical_px(2) + static char *argv0 = NULL; typedef struct { @@ -48,11 +55,12 @@ typedef struct { } button_t; static xcb_window_t win; -static xcb_pixmap_t pixmap; -static xcb_gcontext_t pixmap_gc; -static xcb_rectangle_t rect = {0, 0, 600, 20}; +static surface_t bar; + static i3Font font; static i3String *prompt; + +static button_t btn_close; static button_t *buttons; static int buttoncnt; @@ -138,7 +146,7 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve printf("button released on x = %d, y = %d\n", event->event_x, event->event_y); /* If the user hits the close button, we exit(0) */ - if (event->event_x >= (rect.width - logical_px(32))) + if (event->event_x >= btn_close.x && event->event_x < btn_close.x + btn_close.width) exit(0); button_t *button = get_button_at(event->event_x, event->event_y); if (!button) @@ -190,108 +198,64 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve /* TODO: unset flag, re-render */ } +/* + * Draws a button and returns its width + * + */ +static int button_draw(button_t *button, int position) { + int text_width = predict_text_width(button->label); + button->width = text_width + 2 * BTN_PADDING + 2 * BTN_BORDER; + button->x = position - button->width; + + /* draw border */ + draw_util_rectangle(&bar, color_border, + position - button->width, + MSG_PADDING - BTN_PADDING - BTN_BORDER, + button->width, + font.height + 2 * BTN_PADDING + 2 * BTN_BORDER); + /* draw background */ + draw_util_rectangle(&bar, color_button_background, + position - button->width + BTN_BORDER, + MSG_PADDING - BTN_PADDING, + text_width + 2 * BTN_PADDING, + font.height + 2 * BTN_PADDING); + /* draw label */ + draw_util_text(button->label, &bar, color_text, color_button_background, + position - button->width + BTN_BORDER + BTN_PADDING, + MSG_PADDING, + 200); + return button->width; +} + /* * Handles expose events (redraws of the window) and rendering in general. Will * be called from the code with event == NULL or from X with event != NULL. * */ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { - /* re-draw the background */ - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_background.colorpixel}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect); + /* draw background */ + draw_util_clear_surface(&bar, color_background); + /* draw message */ + draw_util_text(prompt, &bar, color_text, color_background, + MSG_PADDING, MSG_PADDING, + bar.width - 2 * MSG_PADDING); - /* restore font color */ - set_font_colors(pixmap_gc, color_text, color_background); - draw_text(prompt, pixmap, pixmap_gc, NULL, - logical_px(4) + logical_px(4), - logical_px(4) + logical_px(4), - rect.width - logical_px(4) - logical_px(4)); + int position = bar.width - (MSG_PADDING - BTN_BORDER - BTN_PADDING); /* render close button */ - const char *close_button_label = "X"; - int line_width = logical_px(4); - /* set width to the width of the label */ - int w = predict_text_width(i3string_from_utf8(close_button_label)); - /* account for left/right padding, which seems to be set to 8px (total) below */ - w += logical_px(8); - int y = rect.width; - uint32_t values[3]; - values[0] = color_button_background.colorpixel; - values[1] = line_width; - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); - - xcb_rectangle_t close = {y - w - (2 * line_width), 0, w + (2 * line_width), rect.height}; - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); - - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border.colorpixel}); - xcb_point_t points[] = { - {y - w - (2 * line_width), line_width / 2}, - {y - (line_width / 2), line_width / 2}, - {y - (line_width / 2), (rect.height - (line_width / 2)) - logical_px(2)}, - {y - w - (2 * line_width), (rect.height - (line_width / 2)) - logical_px(2)}, - {y - w - (2 * line_width), line_width / 2}}; - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points); - - values[0] = 1; - set_font_colors(pixmap_gc, color_text, color_button_background); - /* the x term here seems to set left/right padding */ - draw_text_ascii(close_button_label, pixmap, pixmap_gc, - y - w - line_width + w / 2 - logical_px(4), - logical_px(4) + logical_px(3), - rect.width - y + w + line_width - w / 2 + logical_px(4)); - y -= w; - - y -= logical_px(20); + position -= button_draw(&btn_close, position); + position -= CLOSE_BTN_GAP; /* render custom buttons */ - line_width = 1; - for (int c = 0; c < buttoncnt; c++) { - /* set w to the width of the label */ - w = predict_text_width(buttons[c].label); - /* account for left/right padding, which seems to be set to 12px (total) below */ - w += logical_px(12); - y -= logical_px(30); - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_button_background.colorpixel}); - close = (xcb_rectangle_t){y - w - (2 * line_width), logical_px(2), w + (2 * line_width), rect.height - logical_px(6)}; - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close); - - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border.colorpixel}); - buttons[c].x = y - w - (2 * line_width); - buttons[c].width = w; - xcb_point_t points2[] = { - {y - w - (2 * line_width), (line_width / 2) + logical_px(2)}, - {y - (line_width / 2), (line_width / 2) + logical_px(2)}, - {y - (line_width / 2), (rect.height - logical_px(4) - (line_width / 2))}, - {y - w - (2 * line_width), (rect.height - logical_px(4) - (line_width / 2))}, - {y - w - (2 * line_width), (line_width / 2) + logical_px(2)}}; - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2); - - values[0] = color_text.colorpixel; - values[1] = color_button_background.colorpixel; - set_font_colors(pixmap_gc, color_text, color_button_background); - /* the x term seems to set left/right padding */ - draw_text(buttons[c].label, pixmap, pixmap_gc, NULL, - y - w - line_width + logical_px(6), - logical_px(4) + logical_px(3), - rect.width - y + w + line_width - logical_px(6)); - - y -= w; + for (int i = 0; i < buttoncnt; i++) { + position -= BTN_GAP; + position -= button_draw(&buttons[i], position); } /* border line at the bottom */ - line_width = logical_px(2); - values[0] = color_border_bottom.colorpixel; - values[1] = line_width; - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values); - xcb_point_t bottom[] = { - {0, rect.height - 0}, - {rect.width, rect.height - 0}}; - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 2, bottom); + draw_util_rectangle(&bar, color_border_bottom, 0, bar.height - BAR_BORDER, bar.width, BAR_BORDER); - /* 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); - return 1; } @@ -301,7 +265,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { */ static xcb_rectangle_t get_window_position(void) { /* Default values if we cannot determine the primary output or its CRTC info. */ - xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + logical_px(8) + logical_px(8)}; + xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + 2 * MSG_PADDING + BAR_BORDER}; xcb_randr_get_screen_resources_current_cookie_t rcookie = xcb_randr_get_screen_resources_current(conn, root); xcb_randr_get_output_primary_cookie_t pcookie = xcb_randr_get_output_primary(conn, root); @@ -438,6 +402,8 @@ int main(int argc, char *argv[]) { } } + btn_close.label = i3string_from_utf8("X"); + int screens; if ((conn = xcb_connect(NULL, &screens)) == NULL || xcb_connection_has_error(conn)) @@ -468,6 +434,7 @@ int main(int argc, char *argv[]) { color_border_bottom = draw_util_hex_to_color("#ab7100"); } + init_dpi(); font = load_font(pattern, true); set_font(&font); @@ -574,11 +541,8 @@ int main(int argc, char *argv[]) { 12, &strut_partial); - /* Create pixmap */ - pixmap = xcb_generate_id(conn); - pixmap_gc = xcb_generate_id(conn); - xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + logical_px(8)); - xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + /* Initialize the drawable bar */ + draw_util_surface_init(conn, &bar, win, get_visualtype(root_screen), win_pos.width, win_pos.height); /* Grab the keyboard to get all input */ xcb_flush(conn); @@ -595,7 +559,10 @@ int main(int argc, char *argv[]) { switch (type) { case XCB_EXPOSE: - handle_expose(conn, (xcb_expose_event_t *)event); + if (((xcb_expose_event_t *)event)->count == 0) { + handle_expose(conn, (xcb_expose_event_t *)event); + } + break; case XCB_BUTTON_PRESS: @@ -608,18 +575,7 @@ int main(int argc, char *argv[]) { case XCB_CONFIGURE_NOTIFY: { xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t *)event; - rect = (xcb_rectangle_t){ - configure_notify->x, - configure_notify->y, - configure_notify->width, - configure_notify->height}; - - /* Recreate the pixmap / gc */ - xcb_free_pixmap(conn, pixmap); - xcb_free_gc(conn, pixmap_gc); - - xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, rect.width, rect.height); - xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0); + draw_util_surface_set_size(&bar, configure_notify->width, configure_notify->height); break; } } @@ -628,6 +584,7 @@ int main(int argc, char *argv[]) { } FREE(pattern); + draw_util_surface_free(conn, &bar); return 0; } diff --git a/i3-sensible-editor b/i3-sensible-editor index b93893a1..ad3f6bdd 100755 --- a/i3-sensible-editor +++ b/i3-sensible-editor @@ -9,7 +9,7 @@ # mechanism to find the preferred editor # Hopefully one of these is installed (no flamewars about preference please!): -for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mc-edit; do +for editor in "$VISUAL" "$EDITOR" nano nvim vim vi emacs pico qe mg jed gedit mcedit; do if command -v "$editor" > /dev/null 2>&1; then exec "$editor" "$@" fi diff --git a/i3-sensible-terminal b/i3-sensible-terminal index 4ef1c75f..f92ff224 100755 --- a/i3-sensible-terminal +++ b/i3-sensible-terminal @@ -8,7 +8,7 @@ # We welcome patches that add distribution-specific mechanisms to find the # preferred terminal emulator. On Debian, there is the x-terminal-emulator # symlink for example. -for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal; do +for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole; do if command -v "$terminal" > /dev/null 2>&1; then exec "$terminal" "$@" fi diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 0929e408..77be3182 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -74,10 +74,12 @@ struct status_block { char *name; char *instance; - TAILQ_ENTRY(status_block) blocks; + TAILQ_ENTRY(status_block) + blocks; }; -TAILQ_HEAD(statusline_head, status_block) statusline_head; +TAILQ_HEAD(statusline_head, status_block) +statusline_head; #include "child.h" #include "ipc.h" diff --git a/i3bar/include/configuration.h b/i3bar/include/configuration.h index c7c1f5e2..e77e891b 100644 --- a/i3bar/include/configuration.h +++ b/i3bar/include/configuration.h @@ -28,18 +28,23 @@ typedef struct binding_t { int input_code; char *command; - TAILQ_ENTRY(binding_t) bindings; + TAILQ_ENTRY(binding_t) + bindings; } binding_t; typedef struct tray_output_t { char *output; - TAILQ_ENTRY(tray_output_t) tray_outputs; + TAILQ_ENTRY(tray_output_t) + tray_outputs; } tray_output_t; typedef struct config_t { int modifier; - TAILQ_HEAD(bindings_head, binding_t) bindings; + + TAILQ_HEAD(bindings_head, binding_t) + bindings; + position_t position; int verbose; struct xcb_color_strings_t colors; @@ -50,7 +55,10 @@ typedef struct config_t { char *command; char *fontname; i3String *separator_symbol; - TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs; + + TAILQ_HEAD(tray_outputs_head, tray_output_t) + tray_outputs; + int tray_padding; int num_outputs; char **outputs; diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h index 3067581d..de960270 100644 --- a/i3bar/include/outputs.h +++ b/i3bar/include/outputs.h @@ -67,5 +67,6 @@ struct i3_output { struct ws_head* workspaces; /* The workspaces on this output */ struct tc_head* trayclients; /* The tray clients on this output */ - SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */ + SLIST_ENTRY(i3_output) + slist; /* Pointer for the SLIST-Macro */ }; diff --git a/i3bar/include/trayclients.h b/i3bar/include/trayclients.h index 694faa48..db954bb1 100644 --- a/i3bar/include/trayclients.h +++ b/i3bar/include/trayclients.h @@ -18,5 +18,6 @@ struct trayclient { bool mapped; /* Whether this window is mapped */ int xe_version; /* The XEMBED version supported by the client */ - TAILQ_ENTRY(trayclient) tailq; /* Pointer for the TAILQ-Macro */ + TAILQ_ENTRY(trayclient) + tailq; /* Pointer for the TAILQ-Macro */ }; diff --git a/i3bar/include/workspaces.h b/i3bar/include/workspaces.h index dde8b6e1..e1f9e887 100644 --- a/i3bar/include/workspaces.h +++ b/i3bar/include/workspaces.h @@ -40,5 +40,6 @@ struct i3_ws { rect rect; /* The rect of the ws (not used (yet)) */ struct i3_output *output; /* The current output of the ws */ - TAILQ_ENTRY(i3_ws) tailq; /* Pointer for the TAILQ-Macro */ + TAILQ_ENTRY(i3_ws) + tailq; /* Pointer for the TAILQ-Macro */ }; diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 60ab462a..814f0411 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -112,13 +112,13 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha struct status_block *err_block = scalloc(1, sizeof(struct status_block)); err_block->full_text = i3string_from_utf8("Error: "); err_block->name = sstrdup("error"); - err_block->color = sstrdup("red"); + err_block->color = sstrdup("#ff0000"); err_block->no_separator = true; struct status_block *message_block = scalloc(1, sizeof(struct status_block)); message_block->full_text = i3string_from_utf8(message); message_block->name = sstrdup("error_message"); - message_block->color = sstrdup("red"); + message_block->color = sstrdup("#ff0000"); message_block->no_separator = true; TAILQ_INSERT_HEAD(&statusline_head, err_block, blocks); diff --git a/i3bar/src/config.c b/i3bar/src/config.c index d065ff4c..cbe84d50 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -185,6 +185,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len if (!strcmp(cur_key, "status_command")) { DLOG("command = %.*s\n", len, val); + FREE(config.command); sasprintf(&config.command, "%.*s", len, val); return 1; } diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index 4a090ad7..c932aaf7 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -63,16 +63,26 @@ void got_subscribe_reply(char *reply) { * */ void got_output_reply(char *reply) { + DLOG("Clearing old output configuration...\n"); + i3_output *o_walk; + SLIST_FOREACH(o_walk, outputs, slist) { + destroy_window(o_walk); + } + FREE_SLIST(outputs, i3_output); + DLOG("Parsing outputs JSON...\n"); parse_outputs_json(reply); DLOG("Reconfiguring windows...\n"); reconfig_windows(false); - i3_output *o_walk; SLIST_FOREACH(o_walk, outputs, slist) { kick_tray_clients(o_walk); } + if (!config.disable_ws) { + i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); + } + draw_bars(false); } @@ -103,7 +113,6 @@ void got_bar_config(char *reply) { init_colors(&(config.colors)); start_child(config.command); - FREE(config.command); } /* Data structure to easily call the reply handlers later */ @@ -168,6 +177,7 @@ void got_bar_config_update(char *event) { /* update the configuration with the received settings */ DLOG("Received bar config update \"%s\"\n", event); + char *old_command = sstrdup(config.command); bar_display_mode_t old_mode = config.hide_on_modifier; parse_config_json(event); if (old_mode != config.hide_on_modifier) { @@ -178,6 +188,13 @@ void got_bar_config_update(char *event) { init_xcb_late(config.fontname); init_colors(&(config.colors)); + /* restart status command process */ + if (strcmp(old_command, config.command) != 0) { + kill_child(); + start_child(config.command); + } + free(old_command); + draw_bars(false); } diff --git a/i3bar/src/main.c b/i3bar/src/main.c index be684fc5..910e9524 100644 --- a/i3bar/src/main.c +++ b/i3bar/src/main.c @@ -149,6 +149,8 @@ int main(int argc, char **argv) { socket_path = expand_path(i3_default_sock_path); } + init_dpi(); + init_outputs(); if (init_connection(socket_path)) { /* Request the bar configuration. When it arrives, we fill the config array. */ diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index 480c00be..bd056a70 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -189,11 +189,12 @@ static int outputs_end_map_cb(void *params_) { if (config.num_outputs > 0) { bool handle_output = false; for (int c = 0; c < config.num_outputs; c++) { - if (strcasecmp(params->outputs_walk->name, config.outputs[c]) != 0) - continue; - - handle_output = true; - break; + if (strcasecmp(params->outputs_walk->name, config.outputs[c]) == 0 || + (strcasecmp(config.outputs[c], "primary") == 0 && + params->outputs_walk->primary)) { + handle_output = true; + break; + } } if (!handle_output) { DLOG("Ignoring output \"%s\", not configured to handle it.\n", diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 2715e447..2ba446b1 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -181,7 +181,7 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b uint32_t center_x = x - sep_offset; if (config.separator_symbol == NULL) { /* Draw a classic one pixel, vertical separator. */ - draw_util_rectangle(xcb_connection, &output->statusline_buffer, sep_fg, + draw_util_rectangle(&output->statusline_buffer, sep_fg, center_x, logical_px(sep_voff_px), logical_px(1), @@ -250,7 +250,7 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_color struct status_block *block; color_t bar_color = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg); - draw_util_clear_surface(xcb_connection, &output->statusline_buffer, bar_color); + draw_util_clear_surface(&output->statusline_buffer, bar_color); /* Use unsigned integer wraparound to clip off the left side. * For example, if clip_left is 75, then x will start at the very large @@ -301,13 +301,13 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_color } /* Draw the border. */ - draw_util_rectangle(xcb_connection, &output->statusline_buffer, border_color, + draw_util_rectangle(&output->statusline_buffer, border_color, x, logical_px(1), full_render_width, bar_height - logical_px(2)); /* Draw the background. */ - draw_util_rectangle(xcb_connection, &output->statusline_buffer, bg_color, + draw_util_rectangle(&output->statusline_buffer, bg_color, x + border_width, logical_px(1) + border_width, full_render_width - 2 * border_width, @@ -531,7 +531,8 @@ void handle_button(xcb_button_press_event_t *event) { return; } switch (event->detail) { - case 4: + case XCB_BUTTON_SCROLL_UP: + case XCB_BUTTON_SCROLL_LEFT: /* Mouse wheel up. We select the previous ws, if any. * If there is no more workspace, don’t even send the workspace * command, otherwise (with workspace auto_back_and_forth) we’d end @@ -541,7 +542,8 @@ void handle_button(xcb_button_press_event_t *event) { cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq); break; - case 5: + case XCB_BUTTON_SCROLL_DOWN: + case XCB_BUTTON_SCROLL_RIGHT: /* Mouse wheel down. We select the next ws, if any. * If there is no more workspace, don’t even send the workspace * command, otherwise (with workspace auto_back_and_forth) we’d end @@ -870,11 +872,13 @@ static void handle_destroy_notify(xcb_destroy_notify_event_t *event) { DLOG("checking output %s\n", walk->name); trayclient *trayclient; TAILQ_FOREACH(trayclient, walk->trayclients, tailq) { - if (trayclient->win != event->window) + if (trayclient->win != event->window) { continue; + } DLOG("Removing tray client with window ID %08x\n", event->window); TAILQ_REMOVE(walk->trayclients, trayclient, tailq); + FREE(trayclient); /* Trigger an update, we now have more space for the statusline */ configure_trayclients(); @@ -1147,8 +1151,11 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { handle_visibility_notify((xcb_visibility_notify_event_t *)event); break; case XCB_EXPOSE: - /* Expose-events happen, when the window needs to be redrawn */ - redraw_bars(); + if (((xcb_expose_event_t *)event)->count == 0) { + /* Expose-events happen, when the window needs to be redrawn */ + redraw_bars(); + } + break; case XCB_BUTTON_PRESS: /* Button press events are mouse buttons clicked on one of our bars */ @@ -1177,6 +1184,7 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) { case XCB_CONFIGURE_REQUEST: /* ConfigureRequest, sent by a tray child */ handle_configure_request((xcb_configure_request_event_t *)event); + break; case XCB_RESIZE_REQUEST: /* ResizeRequest sent by a tray child using override_redirect. */ handle_resize_request((xcb_resize_request_event_t *)event); @@ -1247,6 +1255,12 @@ char *init_xcb_early() { ev_prepare_init(xcb_prep, &xcb_prep_cb); ev_check_init(xcb_chk, &xcb_chk_cb); + /* Within an event loop iteration, run the xcb_chk watcher last: other + * watchers might call xcb_flush(), which, unexpectedly, can also read + * events into the queue (see _xcb_conn_wait). Hence, we need to drain xcb’s + * queue last, otherwise we risk dead-locking. */ + ev_set_priority(xcb_chk, EV_MINPRI); + ev_io_start(main_loop, xcb_io); ev_prepare_start(main_loop, xcb_prep); ev_check_start(main_loop, xcb_chk); @@ -1558,6 +1572,7 @@ void kick_tray_clients(i3_output *output) { /* We remove the trayclient right here. We might receive an UnmapNotify * event afterwards, but better safe than sorry. */ TAILQ_REMOVE(output->trayclients, trayclient, tailq); + FREE(trayclient); } /* Fake a DestroyNotify so that Qt re-adds tray icons. @@ -1943,8 +1958,7 @@ void draw_bars(bool unhide) { bool use_focus_colors = output_has_focus(outputs_walk); /* First things first: clear the backbuffer */ - draw_util_clear_surface(xcb_connection, &(outputs_walk->buffer), - (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg)); + draw_util_clear_surface(&(outputs_walk->buffer), (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg)); if (!config.disable_ws) { i3_ws *ws_walk; @@ -1974,14 +1988,14 @@ void draw_bars(bool unhide) { } /* Draw the border of the button. */ - draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), border_color, + draw_util_rectangle(&(outputs_walk->buffer), border_color, workspace_width, logical_px(1), ws_walk->name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1), font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1)); /* Draw the inside of the button. */ - draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), bg_color, + draw_util_rectangle(&(outputs_walk->buffer), bg_color, workspace_width + logical_px(1), 2 * logical_px(1), ws_walk->name_width + 2 * logical_px(ws_hoff_px), @@ -2004,13 +2018,13 @@ void draw_bars(bool unhide) { color_t fg_color = colors.binding_mode_fg; color_t bg_color = colors.binding_mode_bg; - draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), colors.binding_mode_border, + draw_util_rectangle(&(outputs_walk->buffer), colors.binding_mode_border, workspace_width, logical_px(1), binding.width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1), font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1)); - draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), bg_color, + draw_util_rectangle(&(outputs_walk->buffer), bg_color, workspace_width + logical_px(1), 2 * logical_px(1), binding.width + 2 * logical_px(ws_hoff_px), @@ -2046,7 +2060,7 @@ void draw_bars(bool unhide) { int x_dest = outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width; draw_statusline(outputs_walk, clip_left, use_focus_colors, use_short_text); - draw_util_copy_surface(xcb_connection, &outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0, + draw_util_copy_surface(&outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0, x_dest, 0, visible_statusline_width, (int16_t)bar_height); outputs_walk->statusline_width = statusline_width; @@ -2077,7 +2091,7 @@ void redraw_bars(void) { continue; } - draw_util_copy_surface(xcb_connection, &(outputs_walk->buffer), &(outputs_walk->bar), 0, 0, + draw_util_copy_surface(&(outputs_walk->buffer), &(outputs_walk->bar), 0, 0, 0, 0, outputs_walk->rect.w, outputs_walk->rect.h); xcb_flush(xcb_connection); } diff --git a/include/atoms_NET_SUPPORTED.xmacro b/include/atoms_NET_SUPPORTED.xmacro index 1358e0f1..a7b9676d 100644 --- a/include/atoms_NET_SUPPORTED.xmacro +++ b/include/atoms_NET_SUPPORTED.xmacro @@ -31,3 +31,4 @@ xmacro(_NET_DESKTOP_NAMES) xmacro(_NET_DESKTOP_VIEWPORT) xmacro(_NET_ACTIVE_WINDOW) xmacro(_NET_CLOSE_WINDOW) +xmacro(_NET_MOVERESIZE_WINDOW) diff --git a/include/bindings.h b/include/bindings.h index 0fcc4df4..df3c32a5 100644 --- a/include/bindings.h +++ b/include/bindings.h @@ -27,7 +27,8 @@ extern const char *DEFAULT_BINDING_MODE; */ Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code, const char *release, const char *border, const char *whole_window, - const char *command, const char *mode, bool pango_markup); + const char *exclude_titlebar, const char *command, const char *mode, + bool pango_markup); /** * Grab the bound keys (tell X to send us keypress events for those keycodes) diff --git a/include/commands.h b/include/commands.h index a57b2925..9780f788 100644 --- a/include/commands.h +++ b/include/commands.h @@ -290,6 +290,12 @@ void cmd_move_scratchpad(I3_CMD); */ void cmd_scratchpad_show(I3_CMD); +/** + * Implementation of 'swap [container] [with] id|con_id|mark '. + * + */ +void cmd_swap(I3_CMD, const char *mode, const char *arg); + /** * Implementation of 'title_format ' * diff --git a/include/con.h b/include/con.h index 0c532207..0fa660a1 100644 --- a/include/con.h +++ b/include/con.h @@ -139,6 +139,12 @@ Con *con_inside_floating(Con *con); */ bool con_inside_focused(Con *con); +/** + * Checks if the container has the given parent as an actual parent. + * + */ +bool con_has_parent(Con *con, Con *parent); + /** * Returns the container with the given client window ID or NULL if no such * container exists. @@ -461,3 +467,9 @@ void con_force_split_parents_redraw(Con *con); * */ i3String *con_parse_title_format(Con *con); + +/** + * Swaps the two containers. + * + */ +bool con_swap(Con *first, Con *second); diff --git a/include/config_directives.h b/include/config_directives.h index 7f1bfe4a..f35666f3 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -51,6 +51,7 @@ CFGFUN(focus_follows_mouse, const char *value); CFGFUN(mouse_warping, const char *value); CFGFUN(force_focus_wrapping, const char *value); CFGFUN(force_xinerama, const char *value); +CFGFUN(disable_randr15, const char *value); CFGFUN(fake_outputs, const char *outputs); CFGFUN(force_display_urgency_hint, const long duration_ms); CFGFUN(focus_on_window_activation, const char *mode); @@ -66,10 +67,10 @@ CFGFUN(color_single, const char *colorclass, const char *color); CFGFUN(floating_modifier, const char *modifiers); CFGFUN(new_window, const char *windowtype, const char *border, const long width); CFGFUN(workspace, const char *workspace, const char *output); -CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command); +CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command); CFGFUN(enter_mode, const char *pango_markup, const char *mode); -CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command); +CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command); CFGFUN(bar_font, const char *font); CFGFUN(bar_separator_symbol, const char *separator); diff --git a/include/configuration.h b/include/configuration.h index b7cdc804..4f6e5ce8 100644 --- a/include/configuration.h +++ b/include/configuration.h @@ -21,6 +21,7 @@ typedef struct Config Config; typedef struct Barconfig Barconfig; extern char *current_configpath; +extern char *current_config; extern Config config; extern SLIST_HEAD(modes_head, Mode) modes; extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs; @@ -68,7 +69,8 @@ struct Variable { char *value; char *next_match; - SLIST_ENTRY(Variable) variables; + SLIST_ENTRY(Variable) + variables; }; /** @@ -82,7 +84,8 @@ struct Mode { bool pango_markup; struct bindings_head *bindings; - SLIST_ENTRY(Mode) modes; + SLIST_ENTRY(Mode) + modes; }; /** @@ -154,6 +157,9 @@ struct Config { * is fetched once and never updated. */ bool force_xinerama; + /** Don’t use RandR 1.5 for querying outputs. */ + bool disable_randr15; + /** Overwrites output detection (for testing), see src/fake_outputs.c */ char *fake_outputs; @@ -253,7 +259,8 @@ struct Barconfig { /* List of outputs on which the tray is allowed to be shown, in order. * The special value "none" disables it (per default, it will be shown) and * the special value "primary" enabled it on the primary output. */ - TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs; + TAILQ_HEAD(tray_outputs_head, tray_output_t) + tray_outputs; /* Padding around the tray icons. */ int tray_padding; @@ -284,7 +291,8 @@ struct Barconfig { M_MOD5 = 7 } modifier; - TAILQ_HEAD(bar_bindings_head, Barbinding) bar_bindings; + TAILQ_HEAD(bar_bindings_head, Barbinding) + bar_bindings; /** Bar position (bottom by default). */ enum { P_BOTTOM = 0, @@ -351,7 +359,8 @@ struct Barconfig { char *binding_mode_text; } colors; - TAILQ_ENTRY(Barconfig) configs; + TAILQ_ENTRY(Barconfig) + configs; }; /** @@ -366,13 +375,15 @@ struct Barbinding { /** The command which is to be executed for this button. */ char *command; - TAILQ_ENTRY(Barbinding) bindings; + TAILQ_ENTRY(Barbinding) + bindings; }; struct tray_output_t { char *output; - TAILQ_ENTRY(tray_output_t) tray_outputs; + TAILQ_ENTRY(tray_output_t) + tray_outputs; }; /** diff --git a/include/data.h b/include/data.h index a729b21e..69a79ade 100644 --- a/include/data.h +++ b/include/data.h @@ -199,7 +199,8 @@ struct Workspace_Assignment { char *name; char *output; - TAILQ_ENTRY(Workspace_Assignment) ws_assignments; + TAILQ_ENTRY(Workspace_Assignment) + ws_assignments; }; struct Ignore_Event { @@ -207,7 +208,8 @@ struct Ignore_Event { int response_type; time_t added; - SLIST_ENTRY(Ignore_Event) ignore_events; + SLIST_ENTRY(Ignore_Event) + ignore_events; }; /** @@ -226,7 +228,8 @@ struct Startup_Sequence { * completed) */ time_t delete_at; - TAILQ_ENTRY(Startup_Sequence) sequences; + TAILQ_ENTRY(Startup_Sequence) + sequences; }; /** @@ -252,7 +255,9 @@ struct regex { struct Binding_Keycode { xcb_keycode_t keycode; i3_event_state_mask_t modifiers; - TAILQ_ENTRY(Binding_Keycode) keycodes; + + TAILQ_ENTRY(Binding_Keycode) + keycodes; }; /****************************************************************************** @@ -293,6 +298,10 @@ struct Binding { * title bar (default). */ bool whole_window; + /** If this is true for a mouse binding, the binding should only be + * executed if the button press was not on the titlebar. */ + bool exclude_titlebar; + /** Keycode to bind */ uint32_t keycode; @@ -309,12 +318,14 @@ struct Binding { /** Only in use if symbol != NULL. Contains keycodes which generate the * specified symbol. Useful for unbinding and checking which binding was * used when a key press event comes in. */ - TAILQ_HEAD(keycodes_head, Binding_Keycode) keycodes_head; + TAILQ_HEAD(keycodes_head, Binding_Keycode) + keycodes_head; /** Command, like in command mode */ char *command; - TAILQ_ENTRY(Binding) bindings; + TAILQ_ENTRY(Binding) + bindings; }; /** @@ -330,8 +341,12 @@ struct Autostart { /** no_startup_id flag for start_application(). Determines whether a * startup notification context/ID should be created. */ bool no_startup_id; - TAILQ_ENTRY(Autostart) autostarts; - TAILQ_ENTRY(Autostart) autostarts_always; + + TAILQ_ENTRY(Autostart) + autostarts; + + TAILQ_ENTRY(Autostart) + autostarts_always; }; /** @@ -364,7 +379,8 @@ struct xoutput { /** x, y, width, height */ Rect rect; - TAILQ_ENTRY(xoutput) outputs; + TAILQ_ENTRY(xoutput) + outputs; }; /** @@ -438,6 +454,10 @@ struct Window { int width_increment; int height_increment; + /* Minimum size specified for the window. */ + int min_width; + int min_height; + /* aspect ratio from WM_NORMAL_HINTS (MPlayer uses this for example) */ double aspect_ratio; }; @@ -493,7 +513,8 @@ struct Match { M_ASSIGN_WS, M_BELOW } insert_where; - TAILQ_ENTRY(Match) matches; + TAILQ_ENTRY(Match) + matches; /* Whether this match was generated when restarting i3 inplace. * Leads to not setting focus when managing a new window, because the old @@ -537,7 +558,8 @@ struct Assignment { char *workspace; } dest; - TAILQ_ENTRY(Assignment) assignments; + TAILQ_ENTRY(Assignment) + assignments; }; /** Fullscreen modes. Used by Con.fullscreen_mode. */ @@ -548,7 +570,8 @@ typedef enum { CF_NONE = 0, struct mark_t { char *name; - TAILQ_ENTRY(mark_t) marks; + TAILQ_ENTRY(mark_t) + marks; }; /** @@ -612,7 +635,8 @@ struct Con { char *sticky_group; /* user-definable marks to jump to this container later */ - TAILQ_HEAD(marks_head, mark_t) marks_head; + TAILQ_HEAD(marks_head, mark_t) + marks_head; /* cached to decide whether a redraw is needed */ bool mark_changed; @@ -631,12 +655,17 @@ struct Con { struct deco_render_params *deco_render_params; /* Only workspace-containers can have floating clients */ - TAILQ_HEAD(floating_head, Con) floating_head; + TAILQ_HEAD(floating_head, Con) + floating_head; - TAILQ_HEAD(nodes_head, Con) nodes_head; - TAILQ_HEAD(focus_head, Con) focus_head; + TAILQ_HEAD(nodes_head, Con) + nodes_head; - TAILQ_HEAD(swallow_head, Match) swallow_head; + TAILQ_HEAD(focus_head, Con) + focus_head; + + TAILQ_HEAD(swallow_head, Match) + swallow_head; fullscreen_mode_t fullscreen_mode; @@ -674,10 +703,17 @@ struct Con { FLOATING_USER_ON = 3 } floating; - TAILQ_ENTRY(Con) nodes; - TAILQ_ENTRY(Con) focused; - TAILQ_ENTRY(Con) all_cons; - TAILQ_ENTRY(Con) floating_windows; + TAILQ_ENTRY(Con) + nodes; + + TAILQ_ENTRY(Con) + focused; + + TAILQ_ENTRY(Con) + all_cons; + + TAILQ_ENTRY(Con) + floating_windows; /** callbacks */ void (*on_remove_child)(Con *); diff --git a/include/debug.h b/include/debug.h deleted file mode 100644 index ab5f3808..00000000 --- a/include/debug.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * vim:ts=4:sw=4:expandtab - * - * i3 - an improved dynamic tiling window manager - * © 2009 Michael Stapelberg and contributors (see also: LICENSE) - * - * debug.c: Debugging functions, especially FormatEvent, which prints unhandled - * events. This code is from xcb-util. - * - */ -#pragma once - -#include - -int handle_event(void *ignored, xcb_connection_t *c, xcb_generic_event_t *e); diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 98ac35b0..e3891454 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -54,6 +54,9 @@ typedef struct i3_ipc_header { /** Request a list of configured binding modes. */ #define I3_IPC_MESSAGE_TYPE_GET_BINDING_MODES 8 +/** Request the raw last loaded i3 config. */ +#define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9 + /* * Messages from i3 to clients * @@ -67,6 +70,7 @@ typedef struct i3_ipc_header { #define I3_IPC_REPLY_TYPE_BAR_CONFIG 6 #define I3_IPC_REPLY_TYPE_VERSION 7 #define I3_IPC_REPLY_TYPE_BINDING_MODES 8 +#define I3_IPC_REPLY_TYPE_CONFIG 9 /* * Events from i3 to clients. Events have the first bit set high. @@ -91,3 +95,6 @@ typedef struct i3_ipc_header { /** The binding event will be triggered when bindings run */ #define I3_IPC_EVENT_BINDING (I3_IPC_EVENT_MASK | 5) + +/** The shutdown event will be triggered when the ipc shuts down */ +#define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6) diff --git a/include/ipc.h b/include/ipc.h index 5c528a6d..7ffbf7a8 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -31,7 +31,8 @@ typedef struct ipc_client { int num_events; char **events; - TAILQ_ENTRY(ipc_client) clients; + TAILQ_ENTRY(ipc_client) + clients; } ipc_client; /* @@ -76,11 +77,18 @@ int ipc_create_socket(const char *filename); void ipc_send_event(const char *event, uint32_t message_type, const char *payload); /** - * Calls shutdown() on each socket and closes it. This function to be called - * when exiting or restarting only! + * Calls to ipc_shutdown() should provide a reason for the shutdown. + */ +typedef enum { + SHUTDOWN_REASON_RESTART, + SHUTDOWN_REASON_EXIT +} shutdown_reason_t; + +/** + * Calls shutdown() on each socket and closes it. * */ -void ipc_shutdown(void); +void ipc_shutdown(shutdown_reason_t reason); void dump_node(yajl_gen gen, Con *con, bool inplace_restart); diff --git a/include/libi3.h b/include/libi3.h index 11ca3127..dbb29e1f 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -24,6 +24,16 @@ #define DEFAULT_DIR_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) +/** Mouse buttons */ +#define XCB_BUTTON_CLICK_LEFT XCB_BUTTON_INDEX_1 +#define XCB_BUTTON_CLICK_MIDDLE XCB_BUTTON_INDEX_2 +#define XCB_BUTTON_CLICK_RIGHT XCB_BUTTON_INDEX_3 +#define XCB_BUTTON_SCROLL_UP XCB_BUTTON_INDEX_4 +#define XCB_BUTTON_SCROLL_DOWN XCB_BUTTON_INDEX_5 +/* xcb doesn't define constants for these. */ +#define XCB_BUTTON_SCROLL_LEFT 6 +#define XCB_BUTTON_SCROLL_RIGHT 7 + /** * XCB connection and root screen * @@ -473,6 +483,12 @@ char *get_exe_path(const char *argv0); */ void init_dpi(void); +/** + * This function returns the value of the DPI setting. + * + */ +long get_dpi_value(void); + /** * Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI * screen) to a corresponding amount of physical pixels on a standard or retina @@ -591,17 +607,17 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_ * surface as well as restoring the cairo state. * */ -void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t color, double x, double y, double w, double h); +void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h); /** * Clears a surface with the given color. * */ -void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t color); +void draw_util_clear_surface(surface_t *surface, color_t color); /** * Copies a surface onto another surface. * */ -void draw_util_copy_surface(xcb_connection_t *conn, surface_t *src, surface_t *dest, double src_x, double src_y, +void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, double dest_x, double dest_y, double width, double height); diff --git a/include/queue.h b/include/queue.h index 9fb9ba5e..9b410449 100644 --- a/include/queue.h +++ b/include/queue.h @@ -446,7 +446,10 @@ } #define CIRCLEQ_HEAD_INITIALIZER(head) \ - { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } + { \ + CIRCLEQ_END(&head) \ + , CIRCLEQ_END(&head) \ + } #define CIRCLEQ_ENTRY(type) \ struct { \ diff --git a/include/randr.h b/include/randr.h index 55068316..568b52f0 100644 --- a/include/randr.h +++ b/include/randr.h @@ -29,7 +29,7 @@ typedef enum { * XRandR information to setup workspaces for each screen. * */ -void randr_init(int *event_base); +void randr_init(int *event_base, const bool disable_randr15); /** * Initializes a CT_OUTPUT Con (searches existing ones from inplace restart @@ -75,10 +75,11 @@ void randr_disable_output(Output *output); Output *get_first_output(void); /** - * Returns the output with the given name if it is active (!) or NULL. + * Returns the output with the given name or NULL. + * If require_active is true, only active outputs are considered. * */ -Output *get_output_by_name(const char *name); +Output *get_output_by_name(const char *name, const bool require_active); /** * Returns the active (!) output which contains the coordinates x, y or NULL diff --git a/include/sighandler.h b/include/sighandler.h index 20ede4eb..2cc20cd2 100644 --- a/include/sighandler.h +++ b/include/sighandler.h @@ -3,10 +3,6 @@ * * i3 - an improved dynamic tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) - * © 2009 Jan-Erik Rediger - * - * sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers - * to restart inplace). * */ #pragma once @@ -14,7 +10,8 @@ #include /** - * 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); diff --git a/include/util.h b/include/util.h index e5ba3341..6c5fc4c6 100644 --- a/include/util.h +++ b/include/util.h @@ -69,6 +69,14 @@ Rect rect_sub(Rect a, Rect b); */ __attribute__((pure)) bool name_is_digits(const char *name); +/** + * Set 'out' to the layout_t value for the given layout. The function + * returns true on success or false if the passed string is not a valid + * layout name. + * + */ +bool layout_from_name(const char *layout_str, layout_t *out); + /** * Parses the workspace name as a number. Returns -1 if the workspace should be * interpreted as a "named workspace". @@ -149,3 +157,10 @@ void start_nagbar(pid_t *nagbar_pid, char *argv[]); * */ void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it); + +/** + * Converts a string into a long using strtol(). + * This is a convenience wrapper checking the parsing result. It returns true + * if the number could be parsed. + */ +bool parse_long(const char *str, long *out, int base); diff --git a/include/xcb.h b/include/xcb.h index 94f2945d..92be7b89 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -69,22 +69,6 @@ extern unsigned int xcb_numlock_mask; xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t depth, xcb_visualid_t visual, uint16_t window_class, enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values); -/** - * Draws a line from x,y to to_x,to_y using the given color - * - */ -void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, - xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x, - uint32_t y, uint32_t to_x, uint32_t to_y); - -/** - * Draws a rectangle from x,y with width,height using the given color - * - */ -void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, - xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x, - uint32_t y, uint32_t width, uint32_t height); - /** * Generates a configure_notify_event with absolute coordinates (relative to * the X root window, not to the client’s frame) for the given client. @@ -98,12 +82,6 @@ void fake_absolute_configure_notify(Con *con); */ void send_take_focus(xcb_window_t window, xcb_timestamp_t timestamp); -/** - * Raises the given window (typically client->frame) above all other windows - * - */ -void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window); - /** * Configures the given window to have the size/position specified by given rect * @@ -122,12 +100,6 @@ xcb_atom_t xcb_get_preferred_window_type(xcb_get_property_reply_t *reply); */ bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom); -/** - * Moves the mouse pointer into the middle of rect. - * - */ -void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect); - /** * Set the cursor of the root window to the given cursor id. * This function should only be used if xcursor_supported == false. diff --git a/libi3/dpi.c b/libi3/dpi.c index a832a689..ce85cacc 100644 --- a/libi3/dpi.c +++ b/libi3/dpi.c @@ -53,6 +53,10 @@ void init_dpi(void) { DLOG("Found Xft.dpi = %ld.\n", dpi); init_dpi_end: + if (resource != NULL) { + free(resource); + } + if (database != NULL) { xcb_xrm_database_free(database); } @@ -64,6 +68,14 @@ init_dpi_end: } } +/* + * This function returns the value of the DPI setting. + * + */ +long get_dpi_value(void) { + return dpi; +} + /* * Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI * screen) to a corresponding amount of physical pixels on a standard or retina diff --git a/libi3/draw_util.c b/libi3/draw_util.c index e471405b..6a2e93dc 100644 --- a/libi3/draw_util.c +++ b/libi3/draw_util.c @@ -19,7 +19,7 @@ xcb_visualtype_t *visual_type; /* Forward declarations */ -static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color); +static void draw_util_set_source_color(surface_t *surface, color_t color); #define RETURN_UNLESS_SURFACE_INITIALIZED(surface) \ do { \ @@ -84,6 +84,11 @@ void draw_util_surface_set_size(surface_t *surface, int width, int height) { * */ color_t draw_util_hex_to_color(const char *color) { + if (strlen(color) < 6 || color[0] != '#') { + ELOG("Could not parse color: %s\n", color); + return draw_util_hex_to_color("#A9A9A9"); + } + char alpha[2]; if (strlen(color) == strlen("#rrggbbaa")) { alpha[0] = color[7]; @@ -110,7 +115,7 @@ color_t draw_util_hex_to_color(const char *color) { * Set the given color as the source color on the surface. * */ -static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color) { +static void draw_util_set_source_color(surface_t *surface, color_t color) { RETURN_UNLESS_SURFACE_INITIALIZED(surface); cairo_set_source_rgba(surface->cr, color.red, color.green, color.blue, color.alpha); @@ -141,7 +146,7 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_ * surface as well as restoring the cairo state. * */ -void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t color, double x, double y, double w, double h) { +void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h) { RETURN_UNLESS_SURFACE_INITIALIZED(surface); cairo_save(surface->cr); @@ -150,7 +155,7 @@ void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t col * onto the surface rather than blending it. This is a bit more efficient and * allows better color control for the user when using opacity. */ cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE); - draw_util_set_source_color(conn, surface, color); + draw_util_set_source_color(surface, color); cairo_rectangle(surface->cr, x, y, w, h); cairo_fill(surface->cr); @@ -166,7 +171,7 @@ void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t col * Clears a surface with the given color. * */ -void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t color) { +void draw_util_clear_surface(surface_t *surface, color_t color) { RETURN_UNLESS_SURFACE_INITIALIZED(surface); cairo_save(surface->cr); @@ -175,7 +180,7 @@ void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t * onto the surface rather than blending it. This is a bit more efficient and * allows better color control for the user when using opacity. */ cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE); - draw_util_set_source_color(conn, surface, color); + draw_util_set_source_color(surface, color); cairo_paint(surface->cr); @@ -190,7 +195,7 @@ void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t * Copies a surface onto another surface. * */ -void draw_util_copy_surface(xcb_connection_t *conn, surface_t *src, surface_t *dest, double src_x, double src_y, +void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, double dest_x, double dest_y, double width, double height) { RETURN_UNLESS_SURFACE_INITIALIZED(src); RETURN_UNLESS_SURFACE_INITIALIZED(dest); diff --git a/libi3/font.c b/libi3/font.c index fa848481..81091ea7 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -24,24 +24,12 @@ static double pango_font_red; static double pango_font_green; static double pango_font_blue; -/* Necessary to track whether the dpi changes and trigger a LOG() message, - * which is more easily visible to users. */ -static double logged_dpi = 0.0; - static PangoLayout *create_layout_with_dpi(cairo_t *cr) { PangoLayout *layout; PangoContext *context; context = pango_cairo_create_context(cr); - const double dpi = (double)root_screen->height_in_pixels * 25.4 / - (double)root_screen->height_in_millimeters; - if (logged_dpi != dpi) { - logged_dpi = dpi; - LOG("X11 root window dictates %f DPI\n", dpi); - } else { - DLOG("X11 root window dictates %f DPI\n", dpi); - } - pango_cairo_context_set_resolution(context, dpi); + pango_cairo_context_set_resolution(context, get_dpi_value()); layout = pango_layout_new(context); g_object_unref(context); diff --git a/libi3/get_config_path.c b/libi3/get_config_path.c index c47e6dd8..efece5cd 100644 --- a/libi3/get_config_path.c +++ b/libi3/get_config_path.c @@ -71,7 +71,7 @@ char *get_config_path(const char *override_configpath, bool use_system_paths) { /* 4: check for $XDG_CONFIG_DIRS/i3/config */ if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL) - xdg_config_dirs = "/etc/xdg"; + xdg_config_dirs = SYSCONFDIR "/xdg"; char *buf = sstrdup(xdg_config_dirs); char *tok = strtok(buf, ":"); diff --git a/m4/ax_extend_srcdir.m4 b/m4/ax_extend_srcdir.m4 index a308d67d..40f37878 100644 --- a/m4/ax_extend_srcdir.m4 +++ b/m4/ax_extend_srcdir.m4 @@ -74,7 +74,7 @@ AC_DEFUN([AX_EXTEND_SRCDIR], [dnl AS_CASE([$srcdir], - [.|.*], + [.|.*|/*], [ # pwd -P is specified in IEEE 1003.1 from 2004 as_dir=`cd "$srcdir" && pwd -P` diff --git a/man/i3-sensible-editor.man b/man/i3-sensible-editor.man index bfc5c5c6..effae6c0 100644 --- a/man/i3-sensible-editor.man +++ b/man/i3-sensible-editor.man @@ -29,7 +29,7 @@ It tries to start one of the following (in that order): * mg * jed * gedit -* mc-edit +* mcedit Please don’t complain about the order: If the user has any preference, they will have $VISUAL or $EDITOR set. diff --git a/man/i3-sensible-terminal.man b/man/i3-sensible-terminal.man index b830cd09..20a6810c 100644 --- a/man/i3-sensible-terminal.man +++ b/man/i3-sensible-terminal.man @@ -40,6 +40,10 @@ It tries to start one of the following (in that order): * terminology * st * qterminal +* lilyterm +* tilix +* terminix +* konsole Please don’t complain about the order: If the user has any preference, they will have $TERMINAL set or modified their i3 configuration file. diff --git a/man/i3.man b/man/i3.man index 16302e08..c1f7eca2 100644 --- a/man/i3.man +++ b/man/i3.man @@ -170,10 +170,10 @@ Exits i3. When starting, i3 looks for configuration files in the following order: -1. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set) -2. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set) -3. ~/.i3/config -4. /etc/i3/config +1. ~/.i3/config +2. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set) +3. /etc/i3/config +4. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set) You can specify a custom path using the -c option. diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index d4b3dbc6..a5873328 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -38,6 +38,7 @@ state INITIAL: 'rename' -> RENAME 'nop' -> NOP 'scratchpad' -> SCRATCHPAD + 'swap' -> SWAP 'title_format' -> TITLE_FORMAT 'mode' -> MODE 'bar' -> BAR @@ -110,7 +111,7 @@ state LAYOUT: state LAYOUT_TOGGLE: end -> call cmd_layout_toggle($toggle_mode) - toggle_mode = 'split', 'all' + toggle_mode = string -> call cmd_layout_toggle($toggle_mode) # append_layout @@ -273,24 +274,28 @@ state RENAME: -> RENAME_WORKSPACE state RENAME_WORKSPACE: - old_name = 'to' + 'to' -> RENAME_WORKSPACE_LIKELY_TO old_name = word -> RENAME_WORKSPACE_TO state RENAME_WORKSPACE_LIKELY_TO: - 'to' - -> RENAME_WORKSPACE_NEW_NAME + 'to ' + -> RENAME_WORKSPACE_LIKELY_TO_NEW_NAME new_name = word -> call cmd_rename_workspace(NULL, $new_name) -state RENAME_WORKSPACE_TO: - 'to' - -> RENAME_WORKSPACE_NEW_NAME - -state RENAME_WORKSPACE_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_TO_NEW_NAME + +state RENAME_WORKSPACE_TO_NEW_NAME: new_name = string -> call cmd_rename_workspace($old_name, $new_name) @@ -406,6 +411,21 @@ state SCRATCHPAD: 'show' -> call cmd_scratchpad_show() +# swap [container] [with] id +# swap [container] [with] con_id +# swap [container] [with] 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) diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 90296819..4aa320bf 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -17,7 +17,8 @@ state INITIAL: end -> error -> '#' -> IGNORE_LINE - 'set' -> IGNORE_LINE + 'set ' -> IGNORE_LINE + 'set ' -> IGNORE_LINE 'set_from_resource' -> IGNORE_LINE bindtype = 'bindsym', 'bindcode', 'bind' -> BINDING 'bar' -> BARBRACE @@ -37,6 +38,7 @@ state INITIAL: 'mouse_warping' -> MOUSE_WARPING 'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING 'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA + 'disable_randr15', 'disable-randr15' -> DISABLE_RANDR15 'workspace_auto_back_and_forth' -> WORKSPACE_BACK_AND_FORTH 'fake_outputs', 'fake-outputs' -> FAKE_OUTPUTS 'force_display_urgency_hint' -> FORCE_DISPLAY_URGENCY_HINT @@ -205,6 +207,11 @@ state FORCE_XINERAMA: value = word -> call cfg_force_xinerama($value) +# disable_randr15 +state DISABLE_RANDR15: + value = word + -> call cfg_disable_randr15($value) + # workspace_back_and_forth state WORKSPACE_BACK_AND_FORTH: value = word @@ -315,6 +322,8 @@ state BINDING: -> whole_window = '--whole-window' -> + exclude_titlebar = '--exclude-titlebar' + -> modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod' -> '+' @@ -329,8 +338,10 @@ state BINDCOMMAND: -> whole_window = '--whole-window' -> + exclude_titlebar = '--exclude-titlebar' + -> command = string - -> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $command) + -> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command) ################################################################################ # Mode configuration @@ -370,6 +381,8 @@ state MODE_BINDING: -> whole_window = '--whole-window' -> + exclude_titlebar = '--exclude-titlebar' + -> modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod' -> '+' @@ -384,8 +397,10 @@ state MODE_BINDCOMMAND: -> whole_window = '--whole-window' -> + exclude_titlebar = '--exclude-titlebar' + -> command = string - -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $command); MODE + -> call cfg_mode_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command); MODE ################################################################################ # Bar configuration (i3bar) diff --git a/release.sh b/release.sh index 9101332c..8f537c0e 100755 --- a/release.sh +++ b/release.sh @@ -1,8 +1,8 @@ #!/bin/zsh # This script is used to prepare a new release of i3. -export RELEASE_VERSION="4.12" -export PREVIOUS_VERSION="4.11" +export RELEASE_VERSION="4.13" +export PREVIOUS_VERSION="4.12" export RELEASE_BRANCH="next" if [ ! -e "../i3.github.io" ] @@ -232,7 +232,12 @@ echo "" echo " cd ${TMPDIR}" echo " sendmail -t < email.txt" echo "" +echo "Update milestones on GitHub:" +echo " Set due date of ${RELEASE_VERSION} to $(date +'%Y-$m-%d') and close the milestone" +echo " Create milestone for the next version with unset due date" +echo "" echo "Announce on:" echo " twitter" echo " google+" echo " #i3 topic" +echo " reddit /r/i3wm" diff --git a/src/bindings.c b/src/bindings.c index eec821b6..3e2bb965 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -32,8 +32,9 @@ static struct Mode *mode_from_name(const char *name, bool pango_markup) { /* Try to find the mode in the list of modes and return it */ SLIST_FOREACH(mode, &modes, modes) { - if (strcmp(mode->name, name) == 0) + if (strcmp(mode->name, name) == 0) { return mode; + } } /* If the mode was not found, create a new one */ @@ -55,12 +56,14 @@ static struct Mode *mode_from_name(const char *name, bool pango_markup) { */ Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code, const char *release, const char *border, const char *whole_window, - const char *command, const char *modename, bool pango_markup) { + const char *exclude_titlebar, const char *command, const char *modename, + bool pango_markup) { Binding *new_binding = scalloc(1, sizeof(Binding)); DLOG("Binding %p bindtype %s, modifiers %s, input code %s, release %s\n", new_binding, bindtype, modifiers, input_code, release); new_binding->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS); new_binding->border = (border != NULL); new_binding->whole_window = (whole_window != NULL); + new_binding->exclude_titlebar = (exclude_titlebar != NULL); if (strcmp(bindtype, "bindsym") == 0) { new_binding->input_type = (strncasecmp(input_code, "button", (sizeof("button") - 1)) == 0 ? B_MOUSE @@ -68,15 +71,15 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch new_binding->symbol = sstrdup(input_code); } else { - char *endptr; - long keycode = strtol(input_code, &endptr, 10); - new_binding->keycode = keycode; - new_binding->input_type = B_KEYBOARD; - if (keycode == LONG_MAX || keycode == LONG_MIN || keycode < 0 || *endptr != '\0' || endptr == input_code) { + long keycode; + if (!parse_long(input_code, &keycode, 10)) { ELOG("Could not parse \"%s\" as an input code, ignoring this binding.\n", input_code); FREE(new_binding); return NULL; } + + new_binding->keycode = keycode; + new_binding->input_type = B_KEYBOARD; } new_binding->command = sstrdup(command); new_binding->event_state_mask = event_state_from_str(modifiers); @@ -188,17 +191,6 @@ void regrab_all_buttons(xcb_connection_t *conn) { xcb_ungrab_server(conn); } -static bool modifiers_match(const uint32_t modifiers_mask, const uint32_t modifiers_state) { - /* modifiers_mask is a special case: a value of 0 does not mean “match - * all”, but rather “match exactly when no modifiers are present”. */ - if (modifiers_mask == 0) { - /* Verify no modifiers are pressed. A bitwise AND would lead to - * false positives, see issue #2002. */ - return (modifiers_state == 0); - } - return ((modifiers_state & modifiers_mask) == modifiers_mask); -} - /* * Returns a pointer to the Binding with the specified modifiers and * keycode or NULL if no such binding exists. @@ -221,8 +213,9 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas const uint32_t xkb_group_state = (state_filtered & 0xFFFF0000); const uint32_t modifiers_state = (state_filtered & 0x0000FFFF); TAILQ_FOREACH(bind, bindings, bindings) { - if (bind->input_type != input_type) + if (bind->input_type != input_type) { continue; + } const uint32_t xkb_group_mask = (bind->event_state_mask & 0xFFFF0000); const bool groups_match = ((xkb_group_state & xkb_group_mask) == xkb_group_mask); @@ -240,7 +233,7 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas struct Binding_Keycode *binding_keycode; TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) { const uint32_t modifiers_mask = (binding_keycode->modifiers & 0x0000FFFF); - const bool mods_match = modifiers_match(modifiers_mask, modifiers_state); + const bool mods_match = (modifiers_mask == modifiers_state); DLOG("binding_keycode->modifiers = %d, modifiers_mask = %d, modifiers_state = %d, mods_match = %s\n", binding_keycode->modifiers, modifiers_mask, modifiers_state, (mods_match ? "yes" : "no")); if (binding_keycode->keycode == input_keycode && mods_match) { @@ -248,23 +241,30 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas break; } } - if (!found_keycode) + if (!found_keycode) { continue; + } } else { - const uint32_t modifiers_mask = (bind->event_state_mask & 0x0000FFFF); - const bool mods_match = modifiers_match(modifiers_mask, modifiers_state); - DLOG("binding mods_match = %s\n", (mods_match ? "yes" : "no")); - /* First compare the state_filtered (unless this is a - * B_UPON_KEYRELEASE_IGNORE_MODS binding and this is a KeyRelease - * event) */ - if (!mods_match && - (bind->release != B_UPON_KEYRELEASE_IGNORE_MODS || - !is_release)) - continue; - /* This case is easier: The user specified a keycode */ - if (bind->keycode != input_code) + if (bind->keycode != input_code) { continue; + } + + bool found_keycode = false; + struct Binding_Keycode *binding_keycode; + TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) { + const uint32_t modifiers_mask = (binding_keycode->modifiers & 0x0000FFFF); + const bool mods_match = (modifiers_mask == modifiers_state); + DLOG("binding_keycode->modifiers = %d, modifiers_mask = %d, modifiers_state = %d, mods_match = %s\n", + binding_keycode->modifiers, modifiers_mask, modifiers_state, (mods_match ? "yes" : "no")); + if (mods_match || (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS && is_release)) { + found_keycode = true; + break; + } + } + if (!found_keycode) { + continue; + } } /* If this binding is a release binding, it matches the key which the @@ -283,8 +283,9 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas /* Check if the binding is for a press or a release event */ if ((bind->release == B_UPON_KEYPRESS && is_release) || - (bind->release >= B_UPON_KEYRELEASE && !is_release)) + (bind->release >= B_UPON_KEYRELEASE && !is_release)) { continue; + } break; } @@ -457,26 +458,23 @@ void translate_keysyms(void) { bool has_errors = false; Binding *bind; TAILQ_FOREACH(bind, bindings, bindings) { +#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 (bind->input_type == B_MOUSE) { - char *endptr; - long button = strtol(bind->symbol + (sizeof("button") - 1), &endptr, 10); - bind->keycode = button; - - 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 translate string to button: \"%s\"\n", bind->symbol); + } - continue; - } - - if (bind->keycode > 0) - continue; - - /* We need to translate the symbol to a keycode */ - const xkb_keysym_t keysym = xkb_keysym_from_name(bind->symbol, XKB_KEYSYM_NO_FLAGS); - if (keysym == XKB_KEY_NoSymbol) { - ELOG("Could not translate string to key symbol: \"%s\"\n", - bind->symbol); - continue; + xcb_keycode_t key = button; + bind->keycode = key; + DLOG("Binding Mouse button, Keycode = %d\n", key); } xkb_layout_index_t group = XCB_XKB_GROUP_1; @@ -530,6 +528,52 @@ void translate_keysyms(void) { 0 /* xkb_layout_index_t latched_group, */, group /* xkb_layout_index_t locked_group, */); + if (bind->keycode > 0) { + /* We need to specify modifiers for the keycode binding (numlock + * fallback). */ + while (!TAILQ_EMPTY(&(bind->keycodes_head))) { + struct Binding_Keycode *first = TAILQ_FIRST(&(bind->keycodes_head)); + TAILQ_REMOVE(&(bind->keycodes_head), first, keycodes); + FREE(first); + } + + ADD_TRANSLATED_KEY(bind->keycode, bind->event_state_mask); + + /* Also bind the key with active CapsLock */ + ADD_TRANSLATED_KEY(bind->keycode, bind->event_state_mask | XCB_MOD_MASK_LOCK); + + /* If this binding is not explicitly for NumLock, check whether we need to + * add a fallback. */ + if ((bind->event_state_mask & xcb_numlock_mask) != xcb_numlock_mask) { + /* Check whether the keycode results in the same keysym when NumLock is + * active. If so, grab the key with NumLock as well, so that users don’t + * need to duplicate every key binding with an additional Mod2 specified. + */ + xkb_keysym_t sym = xkb_state_key_get_one_sym(dummy_state, bind->keycode); + xkb_keysym_t sym_numlock = xkb_state_key_get_one_sym(dummy_state_numlock, bind->keycode); + if (sym == sym_numlock) { + /* Also bind the key with active NumLock */ + ADD_TRANSLATED_KEY(bind->keycode, bind->event_state_mask | xcb_numlock_mask); + + /* Also bind the key with active NumLock+CapsLock */ + ADD_TRANSLATED_KEY(bind->keycode, bind->event_state_mask | xcb_numlock_mask | XCB_MOD_MASK_LOCK); + } else { + DLOG("Skipping automatic numlock fallback, key %d resolves to 0x%x with numlock\n", + bind->keycode, sym_numlock); + } + } + + continue; + } + + /* We need to translate the symbol to a keycode */ + const xkb_keysym_t keysym = xkb_keysym_from_name(bind->symbol, XKB_KEYSYM_NO_FLAGS); + if (keysym == XKB_KEY_NoSymbol) { + ELOG("Could not translate string to key symbol: \"%s\"\n", + bind->symbol); + continue; + } + struct resolve resolving = { .bind = bind, .keysym = keysym, @@ -572,6 +616,8 @@ void translate_keysyms(void) { DLOG("state=0x%x, cfg=\"%s\", sym=0x%x → keycodes%s (%d)\n", bind->event_state_mask, bind->symbol, keysym, keycodes, num_keycodes); free(keycodes); + +#undef ADD_TRANSLATED_KEY } xkb_state_unref(dummy_state); @@ -973,15 +1019,14 @@ int *bindings_get_buttons_to_grab(void) { if (bind->input_type != B_MOUSE || !bind->whole_window) continue; - char *endptr; - long button = strtol(bind->symbol + (sizeof("button") - 1), &endptr, 10); - if (button == LONG_MAX || button == LONG_MIN || button < 0 || *endptr != '\0' || endptr == bind->symbol) { + long button; + if (!parse_long(bind->symbol + (sizeof("button") - 1), &button, 10)) { ELOG("Could not parse button number, skipping this binding. Please report this bug in i3.\n"); continue; } /* Avoid duplicates. */ - for (int i = 0; i < num_max; i++) { + for (int i = 0; i < num; i++) { if (buffer[i] == button) continue; } diff --git a/src/click.c b/src/click.c index 913741b4..e5cdc8b2 100644 --- a/src/click.c +++ b/src/click.c @@ -178,15 +178,15 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod if (con->parent->type == CT_DOCKAREA) goto done; - const bool is_left_or_right_click = (event->detail == XCB_BUTTON_INDEX_1 || - event->detail == XCB_BUTTON_INDEX_3); + const bool is_left_or_right_click = (event->detail == XCB_BUTTON_CLICK_LEFT || + event->detail == XCB_BUTTON_CLICK_RIGHT); /* if the user has bound an action to this click, it should override the * default behavior. */ if (dest == CLICK_DECORATION || dest == CLICK_INSIDE || dest == CLICK_BORDER) { Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event); - if (bind != NULL && (dest == CLICK_DECORATION || + if (bind != NULL && ((dest == CLICK_DECORATION && !bind->exclude_titlebar) || (dest == CLICK_INSIDE && bind->whole_window) || (dest == CLICK_BORDER && bind->border))) { CommandResult *result = run_binding(bind, con); @@ -228,8 +228,10 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod /* 1: see if the user scrolled on the decoration of a stacked/tabbed con */ if (in_stacked && dest == CLICK_DECORATION && - (event->detail == XCB_BUTTON_INDEX_4 || - event->detail == XCB_BUTTON_INDEX_5)) { + (event->detail == XCB_BUTTON_SCROLL_UP || + event->detail == XCB_BUTTON_SCROLL_DOWN || + event->detail == XCB_BUTTON_SCROLL_LEFT || + event->detail == XCB_BUTTON_SCROLL_RIGHT)) { DLOG("Scrolling on a window decoration\n"); orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ); /* Focus the currently focused container on the same level that the @@ -244,10 +246,12 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod * #557), we first check if scrolling is possible at all. */ bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL); bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL); - if (event->detail == XCB_BUTTON_INDEX_4 && scroll_prev_possible) + if ((event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) && scroll_prev_possible) { tree_next('p', orientation); - else if (event->detail == XCB_BUTTON_INDEX_5 && scroll_next_possible) + } else if ((event->detail == XCB_BUTTON_SCROLL_DOWN || event->detail == XCB_BUTTON_SCROLL_RIGHT) && scroll_next_possible) { tree_next('n', orientation); + } + goto done; } @@ -261,7 +265,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod floating_raise_con(floatingcon); /* 4: floating_modifier plus left mouse button drags */ - if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1) { + if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) { floating_drag_window(floatingcon, event); return 1; } @@ -269,7 +273,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod /* 5: resize (floating) if this was a (left or right) click on the * left/right/bottom border, or a right click on the decoration. * also try resizing (tiling) if it was a click on the top */ - if (mod_pressed && event->detail == XCB_BUTTON_INDEX_3) { + if (mod_pressed && event->detail == XCB_BUTTON_CLICK_RIGHT) { DLOG("floating resize due to floatingmodifier\n"); floating_resize_window(floatingcon, proportional, event); return 1; @@ -283,7 +287,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod goto done; } - if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_INDEX_3) { + if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_CLICK_RIGHT) { DLOG("floating resize due to decoration right click\n"); floating_resize_window(floatingcon, proportional, event); return 1; @@ -298,7 +302,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod /* 6: dragging, if this was a click on a decoration (which did not lead * to a resize) */ if (!in_stacked && dest == CLICK_DECORATION && - (event->detail == XCB_BUTTON_INDEX_1)) { + (event->detail == XCB_BUTTON_CLICK_LEFT)) { floating_drag_window(floatingcon, event); return 1; } @@ -313,7 +317,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod } /* 7: floating modifier pressed, initiate a resize */ - if (dest == CLICK_INSIDE && mod_pressed && event->detail == XCB_BUTTON_INDEX_3) { + if (dest == CLICK_INSIDE && mod_pressed && event->detail == XCB_BUTTON_CLICK_RIGHT) { if (floating_mod_on_tiled_client(con, event)) return 1; } diff --git a/src/commands.c b/src/commands.c index b91c71a4..393d7018 100644 --- a/src/commands.c +++ b/src/commands.c @@ -142,7 +142,9 @@ static Con *maybe_auto_back_and_forth_workspace(Con *workspace) { */ typedef struct owindow { Con *con; - TAILQ_ENTRY(owindow) owindows; + + TAILQ_ENTRY(owindow) + owindows; } owindow; typedef TAILQ_HEAD(owindows_head, owindow) owindows_head; @@ -891,8 +893,10 @@ void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_a cmd_output->needs_tree_render = true; return; } - if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, workspace->name)) + if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, workspace->name)) { + ysuccess(true); return; + } workspace_show(workspace); cmd_output->needs_tree_render = true; @@ -938,8 +942,10 @@ void cmd_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_ } DLOG("should switch to workspace %s\n", name); - if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, name)) + if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, name)) { + ysuccess(true); return; + } workspace_show_by_name(name); cmd_output->needs_tree_render = true; @@ -1483,21 +1489,8 @@ void cmd_move_direction(I3_CMD, const char *direction, long move_px) { void cmd_layout(I3_CMD, const char *layout_str) { HANDLE_EMPTY_MATCH; - if (strcmp(layout_str, "stacking") == 0) - layout_str = "stacked"; layout_t layout; - /* default is a special case which will be handled in con_set_layout(). */ - if (strcmp(layout_str, "default") == 0) - layout = L_DEFAULT; - else if (strcmp(layout_str, "stacked") == 0) - layout = L_STACKED; - else if (strcmp(layout_str, "tabbed") == 0) - layout = L_TABBED; - else if (strcmp(layout_str, "splitv") == 0) - layout = L_SPLITV; - else if (strcmp(layout_str, "splith") == 0) - layout = L_SPLITH; - else { + if (!layout_from_name(layout_str, &layout)) { ELOG("Unknown layout \"%s\", this is a mismatch between code and parser spec.\n", layout_str); return; } @@ -1556,7 +1549,7 @@ void cmd_exit(I3_CMD) { #ifdef I3_ASAN_ENABLED __lsan_do_leak_check(); #endif - ipc_shutdown(); + ipc_shutdown(SHUTDOWN_REASON_EXIT); unlink(config.ipc_socket_path); xcb_disconnect(conn); exit(0); @@ -1589,7 +1582,7 @@ void cmd_reload(I3_CMD) { */ void cmd_restart(I3_CMD) { LOG("restarting i3\n"); - ipc_shutdown(); + ipc_shutdown(SHUTDOWN_REASON_RESTART); unlink(config.ipc_socket_path); /* We need to call this manually since atexit handlers don’t get called * when exec()ing */ @@ -1819,6 +1812,65 @@ void cmd_scratchpad_show(I3_CMD) { ysuccess(true); } +/* + * Implementation of 'swap [container] [with] id|con_id|mark '. + * + */ +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 ' * diff --git a/src/con.c b/src/con.c index 36225415..cf923ec8 100644 --- a/src/con.c +++ b/src/con.c @@ -22,9 +22,11 @@ static void con_on_remove_child(Con *con); void con_force_split_parents_redraw(Con *con) { Con *parent = con; - while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { - if (!con_is_leaf(parent)) + while (parent != NULL && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { + if (!con_is_leaf(parent)) { FREE(parent->deco_render_params); + } + parent = parent->parent; } } @@ -145,7 +147,7 @@ static void _con_attach(Con *con, Con *parent, Con *previous, bool ignore_focus) /* Insert the container after the tiling container, if found. * When adding to a CT_OUTPUT, just append one after another. */ - if (current && parent->type != CT_OUTPUT) { + if (current != NULL && parent->type != CT_OUTPUT) { DLOG("Inserting con = %p after con %p\n", con, current); TAILQ_INSERT_AFTER(nodes_head, current, con, nodes); } else @@ -410,7 +412,8 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation) { struct bfs_entry { Con *con; - TAILQ_ENTRY(bfs_entry) entries; + TAILQ_ENTRY(bfs_entry) + entries; }; /* @@ -422,7 +425,9 @@ Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode) { /* TODO: is breadth-first-search really appropriate? (check as soon as * fullscreen levels and fullscreen for containers is implemented) */ - TAILQ_HEAD(bfs_head, bfs_entry) bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head); + TAILQ_HEAD(bfs_head, bfs_entry) + bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head); + struct bfs_entry *entry = smalloc(sizeof(struct bfs_entry)); entry->con = con; TAILQ_INSERT_TAIL(&bfs_head, entry, entries); @@ -522,6 +527,23 @@ bool con_inside_focused(Con *con) { return con_inside_focused(con->parent); } +/* + * Checks if the container has the given parent as an actual parent. + * + */ +bool con_has_parent(Con *con, Con *parent) { + Con *current = con->parent; + if (current == NULL) { + return false; + } + + if (current == parent) { + return true; + } + + return con_has_parent(current, parent); +} + /* * Returns the container with the given client window ID or NULL if no such * container exists. @@ -800,10 +822,11 @@ void con_fix_percent(Con *con) { if (children_with_percent != children) { TAILQ_FOREACH(child, &(con->nodes_head), nodes) { if (child->percent <= 0.0) { - if (children_with_percent == 0) + if (children_with_percent == 0) { total += (child->percent = 1.0); - else + } else { total += (child->percent = total / children_with_percent); + } } } } @@ -811,11 +834,13 @@ void con_fix_percent(Con *con) { // if we got a zero, just distribute the space equally, otherwise // distribute according to the proportions we got if (total == 0.0) { - TAILQ_FOREACH(child, &(con->nodes_head), nodes) - child->percent = 1.0 / children; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + child->percent = 1.0 / children; + } } else if (total != 1.0) { - TAILQ_FOREACH(child, &(con->nodes_head), nodes) - child->percent /= total; + TAILQ_FOREACH(child, &(con->nodes_head), nodes) { + child->percent /= total; + } } } @@ -941,7 +966,7 @@ void con_disable_fullscreen(Con *con) { con_set_fullscreen_mode(con, CF_NONE); } -static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fix_coordinates, bool dont_warp, bool ignore_focus) { +static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fix_coordinates, bool dont_warp, bool ignore_focus, bool fix_percentage) { Con *orig_target = target; /* Prevent moving if this would violate the fullscreen focus restrictions. */ @@ -1050,9 +1075,11 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi _con_attach(con, target, behind_focused ? NULL : orig_target, !behind_focused); /* 5: fix the percentages */ - con_fix_percent(parent); - con->percent = 0.0; - con_fix_percent(target); + if (fix_percentage) { + con_fix_percent(parent); + con->percent = 0.0; + con_fix_percent(target); + } /* 6: focus the con on the target workspace, but only within that * workspace, that is, don’t move focus away if the target workspace is @@ -1078,8 +1105,13 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* Descend focus stack in case focus_next is a workspace which can * occur if we move to the same workspace. Also show current workspace * to ensure it is focused. */ - if (!ignore_focus) + if (!ignore_focus) { workspace_show(current_ws); + if (dont_warp) { + DLOG("x_set_warp_to(NULL) because dont_warp is set\n"); + x_set_warp_to(NULL); + } + } /* Set focus only if con was on current workspace before moving. * Otherwise we would give focus to some window on different workspace. */ @@ -1124,6 +1156,9 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi con_set_urgency(con, true); } + /* Ensure the container will be redrawn. */ + FREE(con->deco_render_params); + CALL(parent, on_remove_child); ipc_send_window_event("move", con); @@ -1163,12 +1198,12 @@ bool con_move_to_mark(Con *con, const char *mark) { target = TAILQ_FIRST(&(target->focus_head)); } - if (con == target || con == target->parent) { + if (con == target || con_has_parent(target, con)) { DLOG("cannot move the container to or inside itself, aborting.\n"); return false; } - return _con_move_to_con(con, target, false, true, false, false); + return _con_move_to_con(con, target, false, true, false, false, true); } /* @@ -1201,7 +1236,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool } Con *target = con_descend_focused(workspace); - _con_move_to_con(con, target, true, fix_coordinates, dont_warp, ignore_focus); + _con_move_to_con(con, target, true, fix_coordinates, dont_warp, ignore_focus, true); } /* @@ -1308,15 +1343,16 @@ Con *con_next_focused(Con *con) { } else { /* try to focus the next container on the same level as this one or fall * back to its parent */ - if (!(next = TAILQ_NEXT(con, focused))) + if (!(next = TAILQ_NEXT(con, focused))) { next = con->parent; + } } /* now go down the focus stack as far as * possible, excluding the current container */ - while (!TAILQ_EMPTY(&(next->focus_head)) && - TAILQ_FIRST(&(next->focus_head)) != con) + while (!TAILQ_EMPTY(&(next->focus_head)) && TAILQ_FIRST(&(next->focus_head)) != con) { next = TAILQ_FIRST(&(next->focus_head)); + } return next; } @@ -1636,12 +1672,14 @@ void con_set_layout(Con *con, layout_t layout) { * whole workspace into stacked/tabbed mode. To do this and still allow * intuitive operations (like level-up and then opening a new window), we * need to create a new split container. */ - if (con->type == CT_WORKSPACE && - (layout == L_STACKED || layout == L_TABBED)) { + if (con->type == CT_WORKSPACE) { if (con_num_children(con) == 0) { - DLOG("Setting workspace_layout to %d\n", layout); - con->workspace_layout = layout; - } else { + layout_t ws_layout = (layout == L_STACKED || layout == L_TABBED) ? layout : L_DEFAULT; + DLOG("Setting workspace_layout to %d\n", ws_layout); + con->workspace_layout = ws_layout; + DLOG("Setting layout to %d\n", layout); + con->layout = layout; + } else if (layout == L_STACKED || layout == L_TABBED) { DLOG("Creating new split container\n"); /* 1: create a new split container */ Con *new = con_new(NULL, NULL); @@ -1716,28 +1754,64 @@ void con_toggle_layout(Con *con, const char *toggle_mode) { parent = con->parent; DLOG("con_toggle_layout(%p, %s), parent = %p\n", con, toggle_mode, parent); - if (strcmp(toggle_mode, "split") == 0) { - /* Toggle between splits. When the current layout is not a split - * layout, we just switch back to last_split_layout. Otherwise, we - * change to the opposite split layout. */ - if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) - con_set_layout(con, parent->last_split_layout); - else { - if (parent->layout == L_SPLITH) - con_set_layout(con, L_SPLITV); - else - con_set_layout(con, L_SPLITH); + const char delim[] = " "; + + if (strcasecmp(toggle_mode, "split") == 0 || strstr(toggle_mode, delim)) { + /* L_DEFAULT is used as a placeholder value to distinguish if + * the first layout has already been saved. (it can never be L_DEFAULT) */ + layout_t new_layout = L_DEFAULT; + bool current_layout_found = false; + char *tm_dup = sstrdup(toggle_mode); + char *cur_tok = strtok(tm_dup, delim); + + for (layout_t layout; cur_tok != NULL; cur_tok = strtok(NULL, delim)) { + if (strcasecmp(cur_tok, "split") == 0) { + /* Toggle between splits. When the current layout is not a split + * layout, we just switch back to last_split_layout. Otherwise, we + * change to the opposite split layout. */ + if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) { + layout = parent->last_split_layout; + } else { + layout = (parent->layout == L_SPLITH) ? L_SPLITV : L_SPLITH; + } + } else { + bool success = layout_from_name(cur_tok, &layout); + if (!success || layout == L_DEFAULT) { + ELOG("The token '%s' was not recognized and has been skipped.\n", cur_tok); + continue; + } + } + + /* If none of the specified layouts match the current, + * fall back to the first layout in the list */ + if (new_layout == L_DEFAULT) { + new_layout = layout; + } + + /* We found the active layout in the last iteration, so + * now let's activate the current layout (next in list) */ + if (current_layout_found) { + new_layout = layout; + free(tm_dup); + break; + } + + if (parent->layout == layout) { + current_layout_found = true; + } } - } else { + + con_set_layout(con, new_layout); + } else if (strcasecmp(toggle_mode, "all") == 0 || strcasecmp(toggle_mode, "default") == 0) { if (parent->layout == L_STACKED) con_set_layout(con, L_TABBED); else if (parent->layout == L_TABBED) { - if (strcmp(toggle_mode, "all") == 0) + if (strcasecmp(toggle_mode, "all") == 0) con_set_layout(con, L_SPLITH); else con_set_layout(con, parent->last_split_layout); } else if (parent->layout == L_SPLITH || parent->layout == L_SPLITV) { - if (strcmp(toggle_mode, "all") == 0) { + if (strcasecmp(toggle_mode, "all") == 0) { /* When toggling through all modes, we toggle between * splith/splitv, whereas normally we just directly jump to * stacked. */ @@ -2120,3 +2194,169 @@ i3String *con_parse_title_format(Con *con) { return formatted; } + +/* + * Swaps the two containers. + * + */ +bool con_swap(Con *first, Con *second) { + assert(first != NULL); + assert(second != NULL); + DLOG("Swapping containers %p / %p\n", first, second); + + if (first->type != CT_CON) { + ELOG("Only regular containers can be swapped, but found con = %p with type = %d.\n", first, first->type); + return false; + } + + if (second->type != CT_CON) { + ELOG("Only regular containers can be swapped, but found con = %p with type = %d.\n", second, second->type); + return false; + } + + if (con_is_floating(first) || con_is_floating(second)) { + ELOG("Floating windows cannot be swapped.\n"); + return false; + } + + if (first == second) { + DLOG("Swapping container %p with itself, nothing to do.\n", first); + return false; + } + + if (con_has_parent(first, second) || con_has_parent(second, first)) { + ELOG("Cannot swap containers %p and %p because they are in a parent-child relationship.\n", first, second); + return false; + } + + Con *old_focus = focused; + + Con *first_ws = con_get_workspace(first); + Con *second_ws = con_get_workspace(second); + Con *current_ws = con_get_workspace(old_focus); + const bool focused_within_first = (first == old_focus || con_has_parent(old_focus, first)); + const bool focused_within_second = (second == old_focus || con_has_parent(old_focus, second)); + + if (!con_fullscreen_permits_focusing(first_ws)) { + DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", first_ws->name); + return false; + } + + if (!con_fullscreen_permits_focusing(second_ws)) { + DLOG("Cannot swap because target workspace \"%s\" is obscured.\n", second_ws->name); + return false; + } + + double first_percent = first->percent; + double second_percent = second->percent; + + /* De- and reattaching the containers will insert them at the tail of the + * focus_heads. We will need to fix this. But we need to make sure first + * and second don't get in each other's way if they share the same parent, + * so we select the closest previous focus_head that isn't involved. */ + Con *first_prev_focus_head = first; + while (first_prev_focus_head == first || first_prev_focus_head == second) { + first_prev_focus_head = TAILQ_PREV(first_prev_focus_head, focus_head, focused); + } + + Con *second_prev_focus_head = second; + while (second_prev_focus_head == second || second_prev_focus_head == first) { + second_prev_focus_head = TAILQ_PREV(second_prev_focus_head, focus_head, focused); + } + + /* We use a fake container to mark the spot of where the second container needs to go. */ + Con *fake = con_new(NULL, NULL); + fake->layout = L_SPLITH; + _con_attach(fake, first->parent, first, true); + + bool result = true; + /* Swap the containers. We set the ignore_focus flag here because after the + * container is attached, the focus order is not yet correct and would + * result in wrong windows being focused. */ + + /* Move first to second. */ + result &= _con_move_to_con(first, second, false, false, false, true, false); + + /* If we moved the container holding the focused window to another + * workspace we need to ensure the visible workspace has the focused + * container. + * We don't need to check this for the second container because we've only + * moved the first one at this point.*/ + if (first_ws != second_ws && focused_within_first) { + con_focus(con_descend_focused(current_ws)); + } + + /* Move second to where first has been originally. */ + result &= _con_move_to_con(second, fake, false, false, false, true, false); + + /* If swapping the containers didn't work we don't need to mess with the focus. */ + if (!result) { + goto swap_end; + } + + /* Swapping will have inserted the containers at the tail of their parents' + * focus head. We fix this now by putting them in the position of the focus + * head the container they swapped with was in. */ + TAILQ_REMOVE(&(first->parent->focus_head), first, focused); + TAILQ_REMOVE(&(second->parent->focus_head), second, focused); + + if (second_prev_focus_head == NULL) { + TAILQ_INSERT_HEAD(&(first->parent->focus_head), first, focused); + } else { + TAILQ_INSERT_AFTER(&(first->parent->focus_head), second_prev_focus_head, first, focused); + } + + if (first_prev_focus_head == NULL) { + TAILQ_INSERT_HEAD(&(second->parent->focus_head), second, focused); + } else { + TAILQ_INSERT_AFTER(&(second->parent->focus_head), first_prev_focus_head, second, focused); + } + + /* If the focus was within any of the swapped containers, do the following: + * - If swapping took place within a workspace, ensure the previously + * focused container stays focused. + * - Otherwise, focus the container that has been swapped in. + * + * To understand why fixing the focus_head previously wasn't enough, + * consider the scenario + * H[ V[ A X ] V[ Y B ] ] + * with B being focused, but X being the focus_head within its parent. If + * we swap A and B now, fixing the focus_head would focus X, but since B + * was the focused container before it should stay focused. + */ + if (focused_within_first) { + if (first_ws == second_ws) { + con_focus(old_focus); + } else { + con_focus(con_descend_focused(second)); + } + } else if (focused_within_second) { + if (first_ws == second_ws) { + con_focus(old_focus); + } else { + con_focus(con_descend_focused(first)); + } + } + + /* We need to copy each other's percentages to ensure that the geometry + * doesn't change during the swap. This needs to happen _before_ we close + * the fake container as closing the tree will recalculate percentages. */ + first->percent = second_percent; + second->percent = first_percent; + fake->percent = 0.0; + +swap_end: + /* We don't actually need this since percentages-wise we haven't changed + * anything, but we'll better be safe than sorry and just make sure as we'd + * otherwise crash i3. */ + con_fix_percent(first->parent); + con_fix_percent(second->parent); + + /* We can get rid of the fake container again now. */ + con_close(fake, DONT_KILL_WINDOW); + + con_force_split_parents_redraw(first); + con_force_split_parents_redraw(second); + + return result; +} diff --git a/src/config.c b/src/config.c index d4441d5d..7e08b520 100644 --- a/src/config.c +++ b/src/config.c @@ -13,6 +13,7 @@ #include char *current_configpath = NULL; +char *current_config = NULL; Config config; struct modes_head modes; struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs); diff --git a/src/config_directives.c b/src/config_directives.c index 6b5464f1..7ca6e102 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -106,8 +106,8 @@ CFGFUN(font, const char *font) { font_pattern = sstrdup(font); } -CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) { - configure_binding(bindtype, modifiers, key, release, border, whole_window, command, DEFAULT_BINDING_MODE, false); +CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) { + configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, DEFAULT_BINDING_MODE, false); } /******************************************************************************* @@ -117,15 +117,23 @@ CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, co static char *current_mode; static bool current_mode_pango_markup; -CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) { - configure_binding(bindtype, modifiers, key, release, border, whole_window, command, current_mode, current_mode_pango_markup); +CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) { + configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, current_mode, current_mode_pango_markup); } CFGFUN(enter_mode, const char *pango_markup, const char *modename) { if (strcasecmp(modename, DEFAULT_BINDING_MODE) == 0) { ELOG("You cannot use the name %s for your mode\n", DEFAULT_BINDING_MODE); - exit(1); + return; } + + struct Mode *mode; + SLIST_FOREACH(mode, &modes, modes) { + if (strcmp(mode->name, modename) == 0) { + ELOG("The binding mode with name \"%s\" is defined at least twice.\n", modename); + } + } + DLOG("\t now in mode %s\n", modename); FREE(current_mode); current_mode = sstrdup(modename); @@ -252,6 +260,10 @@ CFGFUN(force_xinerama, const char *value) { config.force_xinerama = eval_boolstr(value); } +CFGFUN(disable_randr15, const char *value) { + config.disable_randr15 = eval_boolstr(value); +} + CFGFUN(force_focus_wrapping, const char *value) { config.force_focus_wrapping = eval_boolstr(value); } diff --git a/src/config_parser.c b/src/config_parser.c index a8265869..c88e9d1e 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -235,7 +235,7 @@ static void next_state(const cmdp_token *token) { * */ static const char *start_of_line(const char *walk, const char *beginning) { - while (*walk != '\n' && *walk != '\r' && walk >= beginning) { + while (walk >= beginning && *walk != '\n' && *walk != '\r') { walk--; } @@ -898,6 +898,13 @@ bool parse_file(const char *f, bool use_nagbar) { if ((fstr = fdopen(fd, "r")) == NULL) die("Could not fdopen: %s\n", strerror(errno)); + FREE(current_config); + current_config = scalloc(stbuf.st_size + 1, 1); + fread(current_config, 1, stbuf.st_size, fstr); + rewind(fstr); + + bool invalid_sets = false; + while (!feof(fstr)) { if (!continuation) continuation = buffer; @@ -911,6 +918,7 @@ bool parse_file(const char *f, bool use_nagbar) { } /* sscanf implicitly strips whitespace. */ + value[0] = '\0'; const bool skip_line = (sscanf(buffer, "%511s %4095[^\n]", key, value) < 1 || strlen(key) < 3); const bool comment = (key[0] == '#'); value[4095] = '\n'; @@ -931,26 +939,28 @@ bool parse_file(const char *f, bool use_nagbar) { continue; } - if (strcasecmp(key, "set") == 0) { + if (strcasecmp(key, "set") == 0 && *value != '\0') { char v_key[512]; - char v_value[4096]; + char v_value[4096] = {'\0'}; if (sscanf(value, "%511s %4095[^\n]", v_key, v_value) < 1) { ELOG("Failed to parse variable specification '%s', skipping it.\n", value); + invalid_sets = true; continue; } if (v_key[0] != '$') { ELOG("Malformed variable assignment, name has to start with $\n"); + invalid_sets = true; continue; } upsert_variable(&variables, v_key, v_value); continue; } else if (strcasecmp(key, "set_from_resource") == 0) { - char res_name[512]; + char res_name[512] = {'\0'}; char v_key[512]; - char fallback[4096]; + char fallback[4096] = {'\0'}; /* Ensure that this string is terminated. For example, a user might * want a variable to be empty if the resource can't be found and @@ -962,11 +972,13 @@ bool parse_file(const char *f, bool use_nagbar) { if (sscanf(value, "%511s %511s %4095[^\n]", v_key, res_name, fallback) < 1) { ELOG("Failed to parse resource specification '%s', skipping it.\n", value); + invalid_sets = true; continue; } if (v_key[0] != '$') { ELOG("Malformed variable assignment, name has to start with $\n"); + invalid_sets = true; continue; } @@ -1002,7 +1014,7 @@ bool parse_file(const char *f, bool use_nagbar) { char *next; for (next = bufcopy; next < (bufcopy + stbuf.st_size) && - (next = strcasestr(next, current->key)) != NULL; + (next = strcasestr(next, current->key)) != NULL; next += strlen(current->key)) { *next = '_'; extra_bytes += extra; @@ -1082,12 +1094,12 @@ bool parse_file(const char *f, bool use_nagbar) { check_for_duplicate_bindings(context); reorder_bindings(); - if (use_nagbar && (context->has_errors || context->has_warnings)) { + if (use_nagbar && (context->has_errors || context->has_warnings || invalid_sets)) { ELOG("FYI: You are using i3 version %s\n", i3_version); if (version == 3) ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n"); - start_config_error_nagbar(f, context->has_errors); + start_config_error_nagbar(f, context->has_errors || invalid_sets); } bool has_errors = context->has_errors; diff --git a/src/debug.c b/src/debug.c deleted file mode 100644 index ea4ca997..00000000 --- a/src/debug.c +++ /dev/null @@ -1,245 +0,0 @@ -/* - * vim:ts=4:sw=4:expandtab - * - * i3 - an improved dynamic tiling window manager - * © 2009 Michael Stapelberg and contributors (see also: LICENSE) - * - * debug.c: Debugging functions, especially FormatEvent, which prints unhandled - * events. This code is from xcb-util. - * - */ -#include - -#include -#include - -#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; -} diff --git a/src/display_version.c b/src/display_version.c index 0e650e81..764ee753 100644 --- a/src/display_version.c +++ b/src/display_version.c @@ -182,4 +182,7 @@ void display_running_version(void) { #endif yajl_free(handle); + free(reply); + free(pid_from_atom); + free(socket_path); } diff --git a/src/floating.c b/src/floating.c index 0a8b6957..f2994339 100644 --- a/src/floating.c +++ b/src/floating.c @@ -72,18 +72,29 @@ void floating_check_size(Con *floating_con) { Rect floating_sane_max_dimensions; Con *focused_con = con_descend_focused(floating_con); - /* obey size increments */ - if (focused_con->window != NULL && (focused_con->window->height_increment || focused_con->window->width_increment)) { - Rect border_rect = con_border_style_rect(focused_con); + 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(); + } - /* We have to do the opposite calculations that render_con() do - * to get the exact size we want. */ - border_rect.width = -border_rect.width; - border_rect.width += 2 * focused_con->border_width; - border_rect.height = -border_rect.height; - border_rect.height += 2 * focused_con->border_width; - if (con_border_style(focused_con) == BS_NORMAL) - border_rect.height += render_deco_height(); + if (focused_con->window != NULL) { + if (focused_con->window->min_width) { + floating_con->rect.width -= border_rect.width; + floating_con->rect.width = max(floating_con->rect.width, focused_con->window->min_width); + floating_con->rect.width += border_rect.width; + } + + if (focused_con->window->min_height) { + floating_con->rect.height -= border_rect.height; + floating_con->rect.height = max(floating_con->rect.height, focused_con->window->min_height); + floating_con->rect.height += border_rect.height; + } if (focused_con->window->height_increment && floating_con->rect.height >= focused_con->window->base_height + border_rect.height) { @@ -100,36 +111,50 @@ void floating_check_size(Con *floating_con) { } } + /* Unless user requests otherwise (-1), raise the width/height to + * reasonable minimum dimensions */ + if (config.floating_minimum_height != -1) { + 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 { + 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) { + 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 { + 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), ensure width/height do not exceed * configured maxima or, if unconfigured, limit to combined width of all * outputs */ - if (config.floating_minimum_height != -1) { - if (config.floating_minimum_height == 0) - floating_con->rect.height = max(floating_con->rect.height, floating_sane_min_height); - else - floating_con->rect.height = max(floating_con->rect.height, config.floating_minimum_height); - } - if (config.floating_minimum_width != -1) { - if (config.floating_minimum_width == 0) - floating_con->rect.width = max(floating_con->rect.width, floating_sane_min_width); - else - floating_con->rect.width = max(floating_con->rect.width, config.floating_minimum_width); - } - - /* Unless user requests otherwise (-1), raise the width/height to - * reasonable minimum dimensions */ floating_sane_max_dimensions = total_outputs_dimensions(); if (config.floating_maximum_height != -1) { - if (config.floating_maximum_height == 0) + floating_con->rect.height -= border_rect.height; + if (config.floating_maximum_height == 0) { floating_con->rect.height = min(floating_con->rect.height, floating_sane_max_dimensions.height); - else + } else { floating_con->rect.height = min(floating_con->rect.height, config.floating_maximum_height); + } + floating_con->rect.height += border_rect.height; } + if (config.floating_maximum_width != -1) { - if (config.floating_maximum_width == 0) + floating_con->rect.width -= border_rect.width; + if (config.floating_maximum_width == 0) { floating_con->rect.width = min(floating_con->rect.width, floating_sane_max_dimensions.width); - else + } else { floating_con->rect.width = min(floating_con->rect.width, config.floating_maximum_width); + } + floating_con->rect.width += border_rect.width; } } @@ -208,7 +233,8 @@ void floating_enable(Con *con, bool automatic) { } } - floating_check_size(nc); + TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); + TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused); /* 3: attach the child to the new parent container. We need to do this * because con_border_style_rect() needs to access con->parent. */ @@ -227,13 +253,16 @@ void floating_enable(Con *con, bool automatic) { nc->rect.width -= border_style_rect.width; /* Add some more pixels for the title bar */ - if (con_border_style(con) == BS_NORMAL) + if (con_border_style(con) == BS_NORMAL) { nc->rect.height += deco_height; + } /* Honor the X11 border */ nc->rect.height += con->border_width * 2; nc->rect.width += con->border_width * 2; + floating_check_size(nc); + /* Some clients (like GIMP’s color picker window) get mapped * to (0, 0), so we push them to a reasonable position * (centered over their leader) */ @@ -280,9 +309,6 @@ void floating_enable(Con *con, bool automatic) { DLOG("Corrected y = %d (deco_height = %d)\n", nc->rect.y, deco_height); - TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes); - TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused); - /* render the cons to get initial window_rect correct */ render_con(nc, false); render_con(con, false); diff --git a/src/handlers.c b/src/handlers.c index 7dfacef7..c273e116 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -400,11 +400,52 @@ static void handle_configure_request(xcb_configure_request_event_t *event) { DLOG("Dock client will not be moved, we only support moving it to another output.\n"); } } + fake_absolute_configure_notify(con); + return; } - fake_absolute_configure_notify(con); + if (event->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) { + DLOG("window 0x%08x wants to be stacked %d\n", event->window, event->stack_mode); - return; + /* Emacs and IntelliJ Idea “request focus” by stacking their window + * above all others. */ + if (event->stack_mode != XCB_STACK_MODE_ABOVE) { + DLOG("stack_mode != XCB_STACK_MODE_ABOVE, ignoring ConfigureRequest\n"); + goto out; + } + + if (fullscreen || !con_is_leaf(con)) { + DLOG("fullscreen or not a leaf, ignoring ConfigureRequest\n"); + goto out; + } + + Con *ws = con_get_workspace(con); + if (ws == NULL) { + DLOG("Window is not being managed, ignoring ConfigureRequest\n"); + goto out; + } + + if (strcmp(ws->name, "__i3_scratch") == 0) { + DLOG("This is a scratchpad container, ignoring ConfigureRequest\n"); + goto out; + } + + if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) { + DLOG("Focusing con = %p\n", con); + workspace_show(ws); + con_focus(con); + tree_render(); + } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) { + DLOG("Marking con = %p urgent\n", con); + con_set_urgency(con, true); + tree_render(); + } else { + DLOG("Ignoring request for con = %p.\n", con); + } + } + +out: + fake_absolute_configure_notify(con); } /* @@ -614,12 +655,9 @@ static void handle_expose_event(xcb_expose_event_t *event) { } /* Since we render to our surface on every change anyways, expose events - * only tell us that the X server lost (parts of) the window contents. We - * can handle that by copying the appropriate part from our surface to the - * window. */ - draw_util_copy_surface(conn, &(parent->frame_buffer), &(parent->frame), - event->x, event->y, event->x, event->y, - event->width, event->height); + * only tell us that the X server lost (parts of) the window contents. */ + draw_util_copy_surface(&(parent->frame_buffer), &(parent->frame), + 0, 0, 0, 0, parent->rect.width, parent->rect.height); xcb_flush(conn); return; } @@ -637,6 +675,11 @@ static void handle_expose_event(xcb_expose_event_t *event) { #define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */ #define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */ +#define _NET_MOVERESIZE_WINDOW_X (1 << 8) +#define _NET_MOVERESIZE_WINDOW_Y (1 << 9) +#define _NET_MOVERESIZE_WINDOW_WIDTH (1 << 10) +#define _NET_MOVERESIZE_WINDOW_HEIGHT (1 << 11) + /* * Handle client messages (EWMH) * @@ -897,6 +940,35 @@ static void handle_client_message(xcb_client_message_event_t *event) { DLOG("_NET_WM_MOVERESIZE direction %d not implemented\n", direction); break; } + } else if (event->type == A__NET_MOVERESIZE_WINDOW) { + DLOG("Received _NET_MOVE_RESIZE_WINDOW. Handling by faking a configure request.\n"); + + void *_generated_event = scalloc(32, 1); + xcb_configure_request_event_t *generated_event = _generated_event; + + generated_event->window = event->window; + generated_event->response_type = XCB_CONFIGURE_REQUEST; + + generated_event->value_mask = 0; + if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_X) { + generated_event->value_mask |= XCB_CONFIG_WINDOW_X; + generated_event->x = event->data.data32[1]; + } + if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_Y) { + generated_event->value_mask |= XCB_CONFIG_WINDOW_Y; + generated_event->y = event->data.data32[2]; + } + if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_WIDTH) { + generated_event->value_mask |= XCB_CONFIG_WINDOW_WIDTH; + generated_event->width = event->data.data32[3]; + } + if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_HEIGHT) { + generated_event->value_mask |= XCB_CONFIG_WINDOW_HEIGHT; + generated_event->height = event->data.data32[4]; + } + + handle_configure_request(generated_event); + FREE(generated_event); } else { DLOG("Skipping client message for unhandled type %d\n", event->type); } @@ -929,54 +1001,72 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat xcb_size_hints_t size_hints; - //CLIENT_LOG(client); - /* If the hints were already in this event, use them, if not, request them */ - if (reply != NULL) + if (reply != NULL) { xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply); - else + } else { xcb_icccm_get_wm_normal_hints_reply(conn, xcb_icccm_get_wm_normal_hints_unchecked(conn, con->window->id), &size_hints, NULL); + } + + int win_width = con->window_rect.width; + int win_height = con->window_rect.height; if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) { - // TODO: Minimum size is not yet implemented DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height); + + con->window->min_width = size_hints.min_width; + con->window->min_height = size_hints.min_height; + } + + if (con_is_floating(con)) { + win_width = MAX(win_width, con->window->min_width); + win_height = MAX(win_height, con->window->min_height); } bool changed = false; if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) { - if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) + if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) { if (con->window->width_increment != size_hints.width_inc) { con->window->width_increment = size_hints.width_inc; changed = true; } - if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF) + } + + if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF) { if (con->window->height_increment != size_hints.height_inc) { con->window->height_increment = size_hints.height_inc; changed = true; } + } - if (changed) + if (changed) { DLOG("resize increments changed\n"); + } } - int base_width = 0, base_height = 0; + bool has_base_size = false; + int base_width = 0; + int base_height = 0; - /* base_width/height are the desired size of the window. - We check if either the program-specified size or the program-specified - min-size is available */ + /* The base width / height is the desired size of the window. */ if (size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) { base_width = size_hints.base_width; base_height = size_hints.base_height; - } else if (size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) { - /* TODO: is this right? icccm says not */ + has_base_size = true; + } + + /* If the window didn't specify a base size, the ICCCM tells us to fall + * back to the minimum size instead, if available. */ + if (!has_base_size && size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) { base_width = size_hints.min_width; base_height = size_hints.min_height; } - if (base_width != con->window->base_width || - base_height != con->window->base_height) { + // TODO XXX Should we only do this is the base size is > 0? + if (base_width != con->window->base_width || base_height != con->window->base_height) { con->window->base_width = base_width; con->window->base_height = base_height; + DLOG("client's base_height changed to %d\n", base_height); DLOG("client's base_width changed to %d\n", base_width); changed = true; @@ -989,9 +1079,13 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat goto render_and_return; } - /* XXX: do we really use rect here, not window_rect? */ - double width = con->rect.width - base_width; - double height = con->rect.height - base_height; + /* The ICCCM says to subtract the base size from the window size for aspect + * ratio calculations. However, unlike determining the base size itself we + * must not fall back to using the minimum size in this case according to + * the ICCCM. */ + double width = win_width - base_width * has_base_size; + double height = win_height - base_height * has_base_size; + /* Convert numerator/denominator to a double */ double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den; double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den; @@ -1000,8 +1094,9 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat DLOG("width = %f, height = %f\n", width, height); /* Sanity checks, this is user-input, in a way */ - if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0) + if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0) { goto render_and_return; + } /* Check if we need to set proportional_* variables using the correct ratio */ double aspect_ratio = 0.0; @@ -1009,8 +1104,9 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat aspect_ratio = min_aspect; } else if ((width / height) > max_aspect) { aspect_ratio = max_aspect; - } else + } else { goto render_and_return; + } if (fabs(con->window->aspect_ratio - aspect_ratio) > DBL_EPSILON) { con->window->aspect_ratio = aspect_ratio; @@ -1018,8 +1114,10 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat } render_and_return: - if (changed) + if (changed) { tree_render(); + } + FREE(reply); return true; } @@ -1150,6 +1248,21 @@ static void handle_focus_in(xcb_focus_in_event_t *event) { return; } +/* + * Handles ConfigureNotify events for the root window, which are generated when + * the monitor configuration changed. + * + */ +static void handle_configure_notify(xcb_configure_notify_event_t *event) { + if (event->event != root) { + DLOG("ConfigureNotify for non-root window 0x%08x, ignoring\n", event->event); + return; + } + DLOG("ConfigureNotify for root window 0x%08x\n", event->event); + + randr_query_outputs(); +} + /* * Handles the WM_CLASS property for assignments and criteria selection. * @@ -1436,7 +1549,10 @@ void handle_event(int type, xcb_generic_event_t *event) { break; case XCB_EXPOSE: - handle_expose_event((xcb_expose_event_t *)event); + if (((xcb_expose_event_t *)event)->count == 0) { + handle_expose_event((xcb_expose_event_t *)event); + } + break; case XCB_MOTION_NOTIFY: @@ -1476,6 +1592,10 @@ void handle_event(int type, xcb_generic_event_t *event) { break; } + case XCB_CONFIGURE_NOTIFY: + handle_configure_notify((xcb_configure_notify_event_t *)event); + break; + default: //DLOG("Unhandled event of type %d\n", type); break; diff --git a/src/ipc.c b/src/ipc.c index c0dfb1ec..18d6075d 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -22,7 +22,8 @@ char *current_socketpath = NULL; -TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients); +TAILQ_HEAD(ipc_client_head, ipc_client) +all_clients = TAILQ_HEAD_INITIALIZER(all_clients); /* * Puts the given socket file descriptor into non-blocking mode or dies if @@ -61,11 +62,39 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa } /* - * Calls shutdown() on each socket and closes it. This function to be called + * For shutdown events, we send the reason for the shutdown. + */ +static void ipc_send_shutdown_event(shutdown_reason_t reason) { + yajl_gen gen = ygenalloc(); + y(map_open); + + ystr("change"); + + if (reason == SHUTDOWN_REASON_RESTART) { + ystr("restart"); + } else if (reason == SHUTDOWN_REASON_EXIT) { + ystr("exit"); + } + + y(map_close); + + const unsigned char *payload; + ylength length; + + y(get_buf, &payload, &length); + ipc_send_event("shutdown", I3_IPC_EVENT_SHUTDOWN, (const char *)payload); + + y(free); +} + +/* + * Calls shutdown() on each socket and closes it. This function is to be called * when exiting or restarting only! * */ -void ipc_shutdown(void) { +void ipc_shutdown(shutdown_reason_t reason) { + ipc_send_shutdown_event(reason); + ipc_client *current; while (!TAILQ_EMPTY(&all_clients)) { current = TAILQ_FIRST(&all_clients); @@ -1058,9 +1087,30 @@ IPC_HANDLER(subscribe) { ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply); } +/* + * Returns the raw last loaded i3 configuration file contents. + */ +IPC_HANDLER(get_config) { + yajl_gen gen = ygenalloc(); + + y(map_open); + + ystr("config"); + ystr(current_config); + + y(map_close); + + const unsigned char *payload; + ylength length; + y(get_buf, &payload, &length); + + ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_CONFIG, payload); + y(free); +} + /* The index of each callback function corresponds to the numeric * value of the message type (see include/i3/ipc.h) */ -handler_t handlers[9] = { +handler_t handlers[10] = { handle_command, handle_get_workspaces, handle_subscribe, @@ -1070,6 +1120,7 @@ handler_t handlers[9] = { handle_get_bar_config, handle_get_version, handle_get_binding_modes, + handle_get_config, }; /* diff --git a/src/load_layout.c b/src/load_layout.c index 7004a859..632c6ec7 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -29,12 +29,16 @@ static bool parsing_focus; static bool parsing_marks; struct Match *current_swallow; static bool swallow_is_empty; +static int num_marks; +static char **marks; /* This list is used for reordering the focus stack after parsing the 'focus' * array. */ struct focus_mapping { int old_id; - TAILQ_ENTRY(focus_mapping) focus_mappings; + + TAILQ_ENTRY(focus_mapping) + focus_mappings; }; static TAILQ_HEAD(focus_mappings_head, focus_mapping) focus_mappings = @@ -146,6 +150,16 @@ static int json_end_map(void *ctx) { floating_check_size(json_node); } + if (num_marks > 0) { + for (int i = 0; i < num_marks; i++) { + con_mark(json_node, marks[i], MM_ADD); + free(marks[i]); + } + + free(marks); + num_marks = 0; + } + LOG("attaching\n"); con_attach(json_node, json_node->parent, true); LOG("Creating window\n"); @@ -228,8 +242,10 @@ static int json_key(void *ctx, const unsigned char *val, size_t len) { if (strcasecmp(last_key, "focus") == 0) parsing_focus = true; - if (strcasecmp(last_key, "marks") == 0) + if (strcasecmp(last_key, "marks") == 0) { + num_marks = 0; parsing_marks = true; + } return 1; } @@ -259,7 +275,8 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { char *mark; sasprintf(&mark, "%.*s", (int)len, val); - con_mark(json_node, mark, MM_ADD); + marks = srealloc(marks, (++num_marks) * sizeof(char *)); + marks[num_marks - 1] = sstrdup(mark); } else { if (strcasecmp(last_key, "name") == 0) { json_node->name = scalloc(len + 1, 1); diff --git a/src/log.c b/src/log.c index 1c33649a..916085f4 100644 --- a/src/log.c +++ b/src/log.c @@ -88,8 +88,13 @@ void init_logging(void) { fprintf(stderr, "Could not initialize errorlog\n"); else { errorfile = fopen(errorfilename, "w"); - if (fcntl(fileno(errorfile), F_SETFD, FD_CLOEXEC)) { - fprintf(stderr, "Could not set close-on-exec flag\n"); + if (!errorfile) { + fprintf(stderr, "Could not initialize errorlog on %s: %s\n", + errorfilename, strerror(errno)); + } else { + if (fcntl(fileno(errorfile), F_SETFD, FD_CLOEXEC)) { + fprintf(stderr, "Could not set close-on-exec flag\n"); + } } } } diff --git a/src/main.c b/src/main.c index 4737175b..21265446 100644 --- a/src/main.c +++ b/src/main.c @@ -21,6 +21,10 @@ #include #include "shmlog.h" +#ifdef I3_ASAN_ENABLED +#include +#endif + #include "sd-daemon.h" /* The original value of RLIMIT_CORE when i3 was started. We need to restore @@ -194,6 +198,7 @@ int main(int argc, char *argv[]) { char *layout_path = NULL; bool delete_layout_path = false; bool force_xinerama = false; + bool disable_randr15 = false; char *fake_outputs = NULL; bool disable_signalhandler = false; bool only_check_config = false; @@ -209,6 +214,8 @@ int main(int argc, char *argv[]) { {"restart", required_argument, 0, 0}, {"force-xinerama", no_argument, 0, 0}, {"force_xinerama", no_argument, 0, 0}, + {"disable-randr15", no_argument, 0, 0}, + {"disable_randr15", no_argument, 0, 0}, {"disable-signalhandler", no_argument, 0, 0}, {"shmlog-size", required_argument, 0, 0}, {"shmlog_size", required_argument, 0, 0}, @@ -289,6 +296,10 @@ int main(int argc, char *argv[]) { "Please check if your driver really does not support RandR " "and disable this option as soon as you can.\n"); break; + } else if (strcmp(long_options[option_index].name, "disable-randr15") == 0 || + strcmp(long_options[option_index].name, "disable_randr15") == 0) { + disable_randr15 = true; + break; } else if (strcmp(long_options[option_index].name, "disable-signalhandler") == 0) { disable_signalhandler = true; break; @@ -544,6 +555,9 @@ int main(int argc, char *argv[]) { xcb_generic_error_t *error = xcb_request_check(conn, cookie); if (error != NULL) { ELOG("Another window manager seems to be running (X error %d)\n", error->error_code); +#ifdef I3_ASAN_ENABLED + __lsan_do_leak_check(); +#endif return 1; } @@ -661,7 +675,7 @@ int main(int argc, char *argv[]) { xinerama_init(); } else { DLOG("Checking for XRandR...\n"); - randr_init(&randr_base); + randr_init(&randr_base, disable_randr15 || config.disable_randr15); } /* We need to force disabling outputs which have been loaded from the diff --git a/src/manage.c b/src/manage.c index 81ee16fd..86a361c3 100644 --- a/src/manage.c +++ b/src/manage.c @@ -491,6 +491,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki geom->height = wm_size_hints.height; } + if (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) { + DLOG("Window specifies minimum size %d x %d\n", wm_size_hints.min_width, wm_size_hints.min_height); + nc->window->min_width = wm_size_hints.min_width; + nc->window->min_height = wm_size_hints.min_height; + } + /* Store the requested geometry. The width/height gets raised to at least * 75x50 when entering floating mode, which is the minimum size for a * window to be useful (smaller windows are usually overlays/toolbars/… diff --git a/src/match.c b/src/match.c index 4d87c560..b3136ab9 100644 --- a/src/match.c +++ b/src/match.c @@ -167,7 +167,7 @@ bool match_matches_window(Match *match, i3Window *window) { /* if we find a window that is newer than this one, bail */ TAILQ_FOREACH(con, &all_cons, all_cons) { if ((con->window != NULL) && - _i3_timercmp(con->window->urgent, window->urgent, > )) { + _i3_timercmp(con->window->urgent, window->urgent, >)) { return false; } } @@ -183,7 +183,7 @@ bool match_matches_window(Match *match, i3Window *window) { TAILQ_FOREACH(con, &all_cons, all_cons) { if ((con->window != NULL) && (con->window->urgent.tv_sec != 0) && - _i3_timercmp(con->window->urgent, window->urgent, < )) { + _i3_timercmp(con->window->urgent, window->urgent, <)) { return false; } } @@ -307,12 +307,8 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { return; } - char *end; - long parsed = strtol(cvalue, &end, 0); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { + long parsed; + if (!parse_long(cvalue, &parsed, 0)) { ELOG("Could not parse con id \"%s\"\n", cvalue); match->error = sstrdup("invalid con_id"); } else { @@ -323,12 +319,8 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) { } if (strcmp(ctype, "id") == 0) { - char *end; - long parsed = strtol(cvalue, &end, 0); - if (parsed == LONG_MIN || - parsed == LONG_MAX || - parsed < 0 || - (end && *end != '\0')) { + long parsed; + if (!parse_long(cvalue, &parsed, 0)) { ELOG("Could not parse window id \"%s\"\n", cvalue); match->error = sstrdup("invalid id"); } else { diff --git a/src/output.c b/src/output.c index 0f2bd617..e3c54a67 100644 --- a/src/output.c +++ b/src/output.c @@ -41,7 +41,7 @@ Output *get_output_from_string(Output *current_output, const char *output_str) { return get_output_next_wrap(D_DOWN, current_output); } - return get_output_by_name(output_str); + return get_output_by_name(output_str, true); } Output *get_output_for_con(Con *con) { @@ -51,7 +51,7 @@ Output *get_output_for_con(Con *con) { return NULL; } - Output *output = get_output_by_name(output_con->name); + Output *output = get_output_by_name(output_con->name, true); if (output == NULL) { ELOG("Could not get output from name \"%s\".\n", output_con->name); return NULL; diff --git a/src/randr.c b/src/randr.c index e5dcddfc..48bffb46 100644 --- a/src/randr.c +++ b/src/randr.c @@ -14,11 +14,6 @@ #include #include -/* While a clean namespace is usually a pretty good thing, we really need - * to use shorter names than the whole xcb_randr_* default names. */ -typedef xcb_randr_get_crtc_info_reply_t crtc_info; -typedef xcb_randr_get_screen_resources_current_reply_t resources_reply; - /* Pointer to the result of the query for primary output */ xcb_randr_get_output_primary_reply_t *primary; @@ -27,6 +22,7 @@ struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs); /* This is the output covering the root window */ static Output *root_output; +static bool has_randr_1_5 = false; /* * Get a specific output by its internal X11 id. Used by randr_query_outputs @@ -44,15 +40,19 @@ static Output *get_output_by_id(xcb_randr_output_t id) { } /* - * Returns the output with the given name if it is active (!) or NULL. + * Returns the output with the given name or NULL. + * If require_active is true, only active outputs are considered. * */ -Output *get_output_by_name(const char *name) { +Output *get_output_by_name(const char *name, const bool require_active) { Output *output; - TAILQ_FOREACH(output, &outputs, outputs) - if (output->active && - strcasecmp(output->name, name) == 0) - return output; + bool get_primary = (strcasecmp("primary", name) == 0); + TAILQ_FOREACH(output, &outputs, outputs) { + if ((output->primary && get_primary) || + ((!require_active || output->active) && strcasecmp(output->name, name) == 0)) { + return output; + } + } return NULL; } @@ -443,7 +443,7 @@ void init_ws_for_output(Output *output, Con *content) { if (visible && previous == NULL) { LOG("There is no workspace left on \"%s\", re-initializing\n", workspace_out->name); - init_ws_for_output(get_output_by_name(workspace_out->name), + init_ws_for_output(get_output_by_name(workspace_out->name, true), output_get_content(workspace_out)); DLOG("Done re-initializing, continuing with \"%s\"\n", output->name); } @@ -534,18 +534,112 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { } /* - * Gets called by randr_query_outputs() for each output. The function adds new - * outputs to the list of outputs, checks if the mode of existing outputs has - * been changed or if an existing output has been disabled. It will then change - * either the "changed" or the "to_be_deleted" flag of the output, if + * randr_query_outputs_15 uses RandR ≥ 1.5 to update outputs. + * + */ +static bool randr_query_outputs_15(void) { +#if XCB_RANDR_MINOR_VERSION < 5 + return false; +#else + /* RandR 1.5 available at compile-time, i.e. libxcb is new enough */ + if (!has_randr_1_5) { + return false; + } + /* RandR 1.5 available at run-time (supported by the server and not + * disabled by the user) */ + DLOG("Querying outputs using RandR 1.5\n"); + xcb_generic_error_t *err; + xcb_randr_get_monitors_reply_t *monitors = + xcb_randr_get_monitors_reply( + conn, xcb_randr_get_monitors(conn, root, true), &err); + if (err != NULL) { + ELOG("Could not get RandR monitors: X11 error code %d\n", err->error_code); + free(err); + /* Fall back to RandR ≤ 1.4 */ + return false; + } + + /* Mark all outputs as to_be_disabled, since xcb_randr_get_monitors() will + * only return active outputs. */ + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) { + if (output != root_output) { + output->to_be_disabled = true; + } + } + + DLOG("%d RandR monitors found (timestamp %d)\n", + xcb_randr_get_monitors_monitors_length(monitors), + monitors->timestamp); + + xcb_randr_monitor_info_iterator_t iter; + for (iter = xcb_randr_get_monitors_monitors_iterator(monitors); + iter.rem; + xcb_randr_monitor_info_next(&iter)) { + const xcb_randr_monitor_info_t *monitor_info = iter.data; + xcb_get_atom_name_reply_t *atom_reply = + xcb_get_atom_name_reply( + conn, xcb_get_atom_name(conn, monitor_info->name), &err); + if (err != NULL) { + ELOG("Could not get RandR monitor name: X11 error code %d\n", err->error_code); + free(err); + continue; + } + char *name; + sasprintf(&name, "%.*s", + xcb_get_atom_name_name_length(atom_reply), + xcb_get_atom_name_name(atom_reply)); + free(atom_reply); + + Output *new = get_output_by_name(name, false); + if (new == NULL) { + new = scalloc(1, sizeof(Output)); + new->name = sstrdup(name); + if (monitor_info->primary) { + TAILQ_INSERT_HEAD(&outputs, new, outputs); + } else { + TAILQ_INSERT_TAIL(&outputs, new, outputs); + } + } + /* We specified get_active == true in xcb_randr_get_monitors(), so we + * will only receive active outputs. */ + new->active = true; + new->to_be_disabled = false; + + new->primary = monitor_info->primary; + + new->changed = + update_if_necessary(&(new->rect.x), monitor_info->x) | + update_if_necessary(&(new->rect.y), monitor_info->y) | + update_if_necessary(&(new->rect.width), monitor_info->width) | + update_if_necessary(&(new->rect.height), monitor_info->height); + + DLOG("name %s, x %d, y %d, width %d px, height %d px, width %d mm, height %d mm, primary %d, automatic %d\n", + name, + monitor_info->x, monitor_info->y, monitor_info->width, monitor_info->height, + monitor_info->width_in_millimeters, monitor_info->height_in_millimeters, + monitor_info->primary, monitor_info->automatic); + free(name); + } + free(monitors); + return true; +#endif +} + +/* + * Gets called by randr_query_outputs_14() for each output. The function adds + * new outputs to the list of outputs, checks if the mode of existing outputs + * has been changed or if an existing output has been disabled. It will then + * change either the "changed" or the "to_be_deleted" flag of the output, if * appropriate. * */ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, xcb_randr_get_output_info_reply_t *output, - xcb_timestamp_t cts, resources_reply *res) { + xcb_timestamp_t cts, + xcb_randr_get_screen_resources_current_reply_t *res) { /* each CRT controller has a position in which we are interested in */ - crtc_info *crtc; + xcb_randr_get_crtc_info_reply_t *crtc; Output *new = get_output_by_id(id); bool existing = (new != NULL); @@ -614,25 +708,16 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, } /* - * (Re-)queries the outputs via RandR and stores them in the list of outputs. - * - * If no outputs are found use the root window. + * randr_query_outputs_14 uses RandR ≤ 1.4 to update outputs. * */ -void randr_query_outputs(void) { - Output *output, *other; - xcb_randr_get_output_primary_cookie_t pcookie; - xcb_randr_get_screen_resources_current_cookie_t rcookie; - - /* timestamp of the configuration so that we get consistent replies to all - * requests (if the configuration changes between our different calls) */ - xcb_timestamp_t cts; - - /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */ - xcb_randr_output_t *randr_outputs; +static void randr_query_outputs_14(void) { + DLOG("Querying outputs using RandR ≤ 1.4\n"); /* Get screen resources (primary output, crtcs, outputs, modes) */ + xcb_randr_get_screen_resources_current_cookie_t rcookie; rcookie = xcb_randr_get_screen_resources_current(conn, root); + xcb_randr_get_output_primary_cookie_t pcookie; pcookie = xcb_randr_get_output_primary(conn, root); if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL) @@ -640,30 +725,52 @@ void randr_query_outputs(void) { else DLOG("primary output is %08x\n", primary->output); - resources_reply *res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL); + xcb_randr_get_screen_resources_current_reply_t *res = + xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL); if (res == NULL) { ELOG("Could not query screen resources.\n"); - } else { - cts = res->config_timestamp; + return; + } - int len = xcb_randr_get_screen_resources_current_outputs_length(res); - randr_outputs = xcb_randr_get_screen_resources_current_outputs(res); + /* timestamp of the configuration so that we get consistent replies to all + * requests (if the configuration changes between our different calls) */ + const xcb_timestamp_t cts = res->config_timestamp; - /* Request information for each output */ - xcb_randr_get_output_info_cookie_t ocookie[len]; - for (int i = 0; i < len; i++) - ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); + const int len = xcb_randr_get_screen_resources_current_outputs_length(res); - /* Loop through all outputs available for this X11 screen */ - for (int i = 0; i < len; i++) { - xcb_randr_get_output_info_reply_t *output; + /* an output is VGA-1, LVDS-1, etc. (usually physical video outputs) */ + xcb_randr_output_t *randr_outputs = xcb_randr_get_screen_resources_current_outputs(res); - if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) - continue; + /* Request information for each output */ + xcb_randr_get_output_info_cookie_t ocookie[len]; + for (int i = 0; i < len; i++) + ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); - handle_output(conn, randr_outputs[i], output, cts, res); - free(output); - } + /* Loop through all outputs available for this X11 screen */ + for (int i = 0; i < len; i++) { + xcb_randr_get_output_info_reply_t *output; + + if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) + continue; + + handle_output(conn, randr_outputs[i], output, cts, res); + free(output); + } + + FREE(res); +} + +/* + * (Re-)queries the outputs via RandR and stores them in the list of outputs. + * + * If no outputs are found use the root window. + * + */ +void randr_query_outputs(void) { + Output *output, *other; + + if (!randr_query_outputs_15()) { + randr_query_outputs_14(); } /* If there's no randr output, enable the output covering the root window. */ @@ -763,7 +870,6 @@ void randr_query_outputs(void) { /* render_layout flushes */ tree_render(); - FREE(res); FREE(primary); } @@ -857,12 +963,18 @@ void randr_disable_output(Output *output) { output->changed = false; } +static void fallback_to_root_output(void) { + root_output->active = true; + output_init_con(root_output); + init_ws_for_output(root_output, output_get_content(root_output->con)); +} + /* * We have just established a connection to the X server and need the initial * XRandR information to setup workspaces for each screen. * */ -void randr_init(int *event_base) { +void randr_init(int *event_base, const bool disable_randr15) { const xcb_query_extension_reply_t *extreply; root_output = create_root_output(conn); @@ -871,13 +983,27 @@ void randr_init(int *event_base) { extreply = xcb_get_extension_data(conn, &xcb_randr_id); if (!extreply->present) { DLOG("RandR is not present, activating root output.\n"); - root_output->active = true; - output_init_con(root_output); - init_ws_for_output(root_output, output_get_content(root_output->con)); - + fallback_to_root_output(); return; } + xcb_generic_error_t *err; + xcb_randr_query_version_reply_t *randr_version = + xcb_randr_query_version_reply( + conn, xcb_randr_query_version(conn, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION), &err); + if (err != NULL) { + free(err); + ELOG("Could not query RandR version: X11 error code %d\n", err->error_code); + fallback_to_root_output(); + return; + } + + has_randr_1_5 = (randr_version->major_version >= 1) && + (randr_version->minor_version >= 5) && + !disable_randr15; + + free(randr_version); + randr_query_outputs(); if (event_base != NULL) diff --git a/src/regex.c b/src/regex.c index 296b91dd..8f039157 100644 --- a/src/regex.c +++ b/src/regex.c @@ -38,6 +38,7 @@ struct regex *regex_new(const char *pattern) { } ELOG("PCRE regular expression compilation failed at %d: %s\n", offset, error); + regex_free(re); return NULL; } re->extra = pcre_study(re->regex, 0, &error); diff --git a/src/restore_layout.c b/src/restore_layout.c index 131196e9..d567e971 100644 --- a/src/restore_layout.c +++ b/src/restore_layout.c @@ -15,6 +15,8 @@ #include #endif +#define TEXT_PADDING logical_px(2) + typedef struct placeholder_state { /** The X11 placeholder window. */ xcb_window_t window; @@ -24,12 +26,11 @@ typedef struct placeholder_state { /** Current size of the placeholder window (to detect size changes). */ Rect rect; - /** The pixmap to render on (back buffer). */ - xcb_pixmap_t pixmap; - /** The graphics context for “pixmap”. */ - xcb_gcontext_t gc; + /** The drawable surface */ + surface_t surface; - TAILQ_ENTRY(placeholder_state) state; + TAILQ_ENTRY(placeholder_state) + state; } placeholder_state; static TAILQ_HEAD(state_head, placeholder_state) state_head = @@ -137,17 +138,15 @@ void restore_connect(void) { } static void update_placeholder_contents(placeholder_state *state) { - xcb_change_gc(restore_conn, state->gc, XCB_GC_FOREGROUND, - (uint32_t[]){config.client.placeholder.background.colorpixel}); - xcb_poly_fill_rectangle(restore_conn, state->pixmap, state->gc, 1, - (xcb_rectangle_t[]){{0, 0, state->rect.width, state->rect.height}}); + const color_t foreground = config.client.placeholder.text; + const color_t background = config.client.placeholder.background; + + draw_util_clear_surface(&(state->surface), background); // TODO: make i3font functions per-connection, at least these two for now…? xcb_flush(restore_conn); xcb_aux_sync(restore_conn); - set_font_colors(state->gc, config.client.placeholder.text, config.client.placeholder.background); - Match *swallows; int n = 0; TAILQ_FOREACH(swallows, &(state->con->swallow_head), matches) { @@ -174,7 +173,10 @@ static void update_placeholder_contents(placeholder_state *state) { DLOG("con %p (placeholder 0x%08x) line %d: %s\n", state->con, state->window, n, serialized); i3String *str = i3string_from_utf8(serialized); - draw_text(str, state->pixmap, state->gc, NULL, 2, (n * (config.font.height + 2)) + 2, state->rect.width - 2); + draw_util_text(str, &(state->surface), foreground, background, + TEXT_PADDING, + (n * (config.font.height + TEXT_PADDING)) + TEXT_PADDING, + state->rect.width - 2 * TEXT_PADDING); i3string_free(str); n++; free(serialized); @@ -185,7 +187,7 @@ static void update_placeholder_contents(placeholder_state *state) { int text_width = predict_text_width(line); int x = (state->rect.width / 2) - (text_width / 2); int y = (state->rect.height / 2) - (config.font.height / 2); - draw_text(line, state->pixmap, state->gc, NULL, x, y, text_width); + draw_util_text(line, &(state->surface), foreground, background, x, y, text_width); i3string_free(line); xcb_flush(conn); xcb_aux_sync(conn); @@ -227,11 +229,8 @@ static void open_placeholder_window(Con *con) { state->window = placeholder; state->con = con; state->rect = con->rect; - state->pixmap = xcb_generate_id(restore_conn); - xcb_create_pixmap(restore_conn, root_depth, state->pixmap, - state->window, state->rect.width, state->rect.height); - state->gc = xcb_generate_id(restore_conn); - xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){0}); + + draw_util_surface_init(conn, &(state->surface), placeholder, get_visualtype(root_screen), state->rect.width, state->rect.height); update_placeholder_contents(state); TAILQ_INSERT_TAIL(&state_head, state, state); @@ -285,8 +284,7 @@ bool restore_kill_placeholder(xcb_window_t placeholder) { continue; xcb_destroy_window(restore_conn, state->window); - xcb_free_pixmap(restore_conn, state->pixmap); - xcb_free_gc(restore_conn, state->gc); + draw_util_surface_free(restore_conn, &(state->surface)); TAILQ_REMOVE(&state_head, state, state); free(state); DLOG("placeholder window 0x%08x destroyed.\n", placeholder); @@ -305,14 +303,8 @@ static void expose_event(xcb_expose_event_t *event) { DLOG("refreshing window 0x%08x contents (con %p)\n", state->window, state->con); - /* Since we render to our pixmap on every change anyways, expose events - * only tell us that the X server lost (parts of) the window contents. We - * can handle that by copying the appropriate part from our pixmap to the - * window. */ - xcb_copy_area(restore_conn, state->pixmap, state->window, state->gc, - event->x, event->y, event->x, event->y, - event->width, event->height); - xcb_flush(restore_conn); + update_placeholder_contents(state); + return; } @@ -337,19 +329,10 @@ static void configure_notify(xcb_configure_notify_event_t *event) { state->rect.width = event->width; state->rect.height = event->height; - xcb_free_pixmap(restore_conn, state->pixmap); - xcb_free_gc(restore_conn, state->gc); - - state->pixmap = xcb_generate_id(restore_conn); - xcb_create_pixmap(restore_conn, root_depth, state->pixmap, - state->window, state->rect.width, state->rect.height); - state->gc = xcb_generate_id(restore_conn); - xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){0}); + draw_util_surface_set_size(&(state->surface), state->rect.width, state->rect.height); update_placeholder_contents(state); - xcb_copy_area(restore_conn, state->pixmap, state->window, state->gc, - 0, 0, 0, 0, state->rect.width, state->rect.height); - xcb_flush(restore_conn); + return; } @@ -359,7 +342,10 @@ static void configure_notify(xcb_configure_notify_event_t *event) { static void restore_handle_event(int type, xcb_generic_event_t *event) { switch (type) { case XCB_EXPOSE: - expose_event((xcb_expose_event_t *)event); + if (((xcb_expose_event_t *)event)->count == 0) { + expose_event((xcb_expose_event_t *)event); + } + break; case XCB_CONFIGURE_NOTIFY: configure_notify((xcb_configure_notify_event_t *)event); diff --git a/src/sighandler.c b/src/sighandler.c index 79f90d9a..b1e7d166 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -3,10 +3,6 @@ * * i3 - an improved dynamic tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) - * © 2009 Jan-Erik Rediger - * - * sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers - * to restart inplace). * */ #include "all.h" @@ -20,28 +16,44 @@ #include -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; + TAILQ_ENTRY(dialog_t) + dialogs; +} dialog_t; + +static TAILQ_HEAD(dialogs_head, dialog_t) dialogs = TAILQ_HEAD_INITIALIZER(dialogs); static int raised_signal; - -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 int backtrace_done = 0; +static int sighandler_backtrace(void); +static void sighandler_setup(void); +static void sighandler_create_dialogs(void); +static void sighandler_destroy_dialogs(void); +static void sighandler_handle_expose(void); +static void sighandler_draw_dialog(dialog_t *dialog); +static void sighandler_handle_key_press(xcb_key_press_event_t *event); + +static i3String *message_intro; +static i3String *message_intro2; +static i3String *message_option_backtrace; +static i3String *message_option_restart; +static i3String *message_option_forget; +static int dialog_width; +static int dialog_height; + +static int border_width = 2; +static int margin = 4; + /* * Attach gdb to pid_parent and dump a backtrace to i3-backtrace.$pid in the * tmpdir */ -static int backtrace(void) { +static int sighandler_backtrace(void) { char *tmpdir = getenv("TMPDIR"); if (tmpdir == NULL) tmpdir = "/tmp"; @@ -125,53 +137,144 @@ static int backtrace(void) { return 1; } -/* - * Draw the window containing the info text - * - */ -static int sig_draw_window(xcb_window_t win, int width, int height, int font_height, i3String **crash_text_i3strings) { - /* re-draw the background */ - xcb_rectangle_t border = {0, 0, width, height}, - inner = {2, 2, width - 4, height - 4}; - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#FF0000")}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border); - xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")}); - xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner); +static void sighandler_setup(void) { + border_width = logical_px(border_width); + margin = logical_px(margin); - /* restore font color */ - set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000")); + 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"); - char *bt_colour = "#FFFFFF"; - if (backtrace_done < 0) - bt_colour = "#AA0000"; - else if (backtrace_done > 0) - bt_colour = "#00AA00"; + int width_longest_message = predict_text_width(message_intro2); - 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")); - } - - /* 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); - - return 1; + 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) { +static void sighandler_create_dialogs(void) { + Output *output; + TAILQ_FOREACH(output, &outputs, outputs) { + if (!output->active) { + continue; + } + + dialog_t *dialog = scalloc(1, sizeof(struct dialog_t)); + TAILQ_INSERT_TAIL(&dialogs, dialog, dialogs); + + 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); + + uint32_t mask = 0; + uint32_t values[4]; + int i = 0; + + /* Needs to be set in the case of a 32-bit root depth. */ + mask |= XCB_CW_BACK_PIXEL; + values[i++] = root_screen->black_pixel; + + /* Needs to be set in the case of a 32-bit root depth. */ + mask |= XCB_CW_BORDER_PIXEL; + values[i++] = root_screen->black_pixel; + + mask |= XCB_CW_OVERRIDE_REDIRECT; + values[i++] = 1; + + /* Needs to be set in the case of a 32-bit root depth. */ + mask |= XCB_CW_COLORMAP; + values[i++] = dialog->colormap; + + 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; + + /* Make sure the dialog is centered. */ + dialog->dims.x -= dialog->dims.width / 2; + dialog->dims.y -= dialog->dims.height / 2; + + dialog->id = create_window(conn, dialog->dims, root_depth, visual, + XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER, + true, mask, values); + + draw_util_surface_init(conn, &(dialog->surface), dialog->id, get_visualtype_by_id(visual), + dialog->dims.width, dialog->dims.height); + + xcb_grab_keyboard(conn, false, dialog->id, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); + + /* 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); + } + + 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); + } + + xcb_flush(conn); +} + +static void sighandler_handle_expose(void) { + dialog_t *current; + TAILQ_FOREACH(current, &dialogs, dialogs) { + sighandler_draw_dialog(current); + } + + xcb_flush(conn); +} + +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; + + char *bt_color = "#FFFFFF"; + if (backtrace_done < 0) { + bt_color = "#AA0000"; + } else if (backtrace_done > 0) { + bt_color = "#00AA00"; + } + draw_util_text(message_option_backtrace, &(dialog->surface), draw_util_hex_to_color(bt_color), black, x, y, max_width); + y += config.font.height; + + draw_util_text(message_option_restart, &(dialog->surface), white, black, x, y, max_width); + y += config.font.height; + + draw_util_text(message_option_forget, &(dialog->surface), white, black, x, y, max_width); + y += config.font.height; +} + +static void sighandler_handle_key_press(xcb_key_press_event_t *event) { uint16_t state = event->state; /* Apparently, after activating numlock once, the numlock modifier @@ -186,106 +289,17 @@ static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_p /* fork and exec/attach GDB to the parent to get a backtrace in the * tmpdir */ - backtrace_done = backtrace(); - - /* re-open the windows to indicate that it's finished */ - open_popups(); - } - - if (sym == 'r') + backtrace_done = sighandler_backtrace(); + sighandler_handle_expose(); + } else if (sym == 'r') { + sighandler_destroy_dialogs(); i3_restart(false); - - if (sym == 'f') + } else if (sym == 'f') { + sighandler_destroy_dialogs(); i3_restart(true); - - return 1; -} - -/* - * 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); - - uint32_t mask = 0; - uint32_t values[2]; - - mask |= XCB_CW_BACK_PIXEL; - values[0] = 0; - - mask |= XCB_CW_OVERRIDE_REDIRECT; - values[1] = 1; - - /* 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)); - - 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); - - /* Map the window (= make it visible) */ - xcb_map_window(conn, win); - - return win; -} - -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); - - 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]); - } - 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); - - /* 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); - - /* Grab the keyboard to get all input */ - xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); - - /* 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); - - sig_draw_window(win, width, height, config.font.height, crash_text_i3strings); - xcb_flush(conn); } } -/* - * Handle signals - * It creates a window asking the user to restart in-place - * or exit to generate a core dump - * - */ void handle_signal(int sig, siginfo_t *info, void *data) { DLOG("i3 crashed. SIG: %d\n", sig); @@ -294,22 +308,33 @@ void handle_signal(int sig, siginfo_t *info, void *data) { sigaction(sig, &action, NULL); raised_signal = sig; - open_popups(); + sighandler_setup(); + sighandler_create_dialogs(); xcb_generic_event_t *event; /* Yay, more own eventhandlers… */ while ((event = xcb_wait_for_event(conn))) { /* Strip off the highest bit (set if the event is generated) */ int type = (event->response_type & 0x7F); - if (type == XCB_KEY_PRESS) { - sig_handle_key_press(NULL, conn, (xcb_key_press_event_t *)event); + switch (type) { + case XCB_KEY_PRESS: + sighandler_handle_key_press((xcb_key_press_event_t *)event); + break; + case XCB_EXPOSE: + if (((xcb_expose_event_t *)event)->count == 0) { + sighandler_handle_expose(); + } + + break; } + free(event); } } /* - * Setup signal handlers to safely handle SIGSEGV and SIGFPE + * Configured a signal handler to gracefully handle crashes and allow the user + * to generate a backtrace and rescue their session. * */ void setup_signal_handler(void) { diff --git a/src/tree.c b/src/tree.c index 6e128363..2d4647f8 100644 --- a/src/tree.c +++ b/src/tree.c @@ -329,6 +329,12 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par match_free(match); free(match); } + while (!TAILQ_EMPTY(&(con->marks_head))) { + mark_t *mark = TAILQ_FIRST(&(con->marks_head)); + TAILQ_REMOVE(&(con->marks_head), mark, marks); + FREE(mark->name); + FREE(mark); + } free(con); /* in the case of floating windows, we already focused another container @@ -377,7 +383,11 @@ void tree_split(Con *con, orientation_t orientation) { if (con->type == CT_WORKSPACE) { if (con_num_children(con) < 2) { - DLOG("Just changing orientation of workspace\n"); + if (con_num_children(con) == 0) { + DLOG("Changing workspace_layout to L_DEFAULT\n"); + con->workspace_layout = L_DEFAULT; + } + DLOG("Changing orientation of workspace\n"); con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV; return; } else { diff --git a/src/util.c b/src/util.c index cfe4c953..32c3c57e 100644 --- a/src/util.c +++ b/src/util.c @@ -66,6 +66,34 @@ __attribute__((pure)) bool name_is_digits(const char *name) { return true; } +/* + * Set 'out' to the layout_t value for the given layout. The function + * returns true on success or false if the passed string is not a valid + * layout name. + * + */ +bool layout_from_name(const char *layout_str, layout_t *out) { + if (strcmp(layout_str, "default") == 0) { + *out = L_DEFAULT; + return true; + } else if (strcasecmp(layout_str, "stacked") == 0 || + strcasecmp(layout_str, "stacking") == 0) { + *out = L_STACKED; + return true; + } else if (strcasecmp(layout_str, "tabbed") == 0) { + *out = L_TABBED; + return true; + } else if (strcasecmp(layout_str, "splitv") == 0) { + *out = L_SPLITV; + return true; + } else if (strcasecmp(layout_str, "splith") == 0) { + *out = L_SPLITH; + return true; + } + + return false; +} + /* * Parses the workspace name as a number. Returns -1 if the workspace should be * interpreted as a "named workspace". @@ -259,7 +287,7 @@ void i3_restart(bool forget_layout) { restore_geometry(); - ipc_shutdown(); + ipc_shutdown(SHUTDOWN_REASON_RESTART); LOG("restarting \"%s\"...\n", start_argv[0]); /* make sure -a is in the argument list or add it */ @@ -430,3 +458,20 @@ void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it) { * waitpid() here. */ waitpid(*nagbar_pid, NULL, 0); } + +/* + * Converts a string into a long using strtol(). + * This is a convenience wrapper checking the parsing result. It returns true + * if the number could be parsed. + */ +bool parse_long(const char *str, long *out, int base) { + char *end; + long result = strtol(str, &end, base); + if (result == LONG_MIN || result == LONG_MAX || result < 0 || (end != NULL && *end != '\0')) { + *out = result; + return false; + } + + *out = result; + return true; +} diff --git a/src/x.c b/src/x.c index 8d7d3dd8..ee5ed2ce 100644 --- a/src/x.c +++ b/src/x.c @@ -58,18 +58,26 @@ typedef struct con_state { char *name; - CIRCLEQ_ENTRY(con_state) state; - CIRCLEQ_ENTRY(con_state) old_state; - TAILQ_ENTRY(con_state) initial_mapping_order; + CIRCLEQ_ENTRY(con_state) + state; + + CIRCLEQ_ENTRY(con_state) + old_state; + + TAILQ_ENTRY(con_state) + initial_mapping_order; } con_state; -CIRCLEQ_HEAD(state_head, con_state) state_head = +CIRCLEQ_HEAD(state_head, con_state) +state_head = CIRCLEQ_HEAD_INITIALIZER(state_head); -CIRCLEQ_HEAD(old_state_head, con_state) old_state_head = +CIRCLEQ_HEAD(old_state_head, con_state) +old_state_head = CIRCLEQ_HEAD_INITIALIZER(old_state_head); -TAILQ_HEAD(initial_mapping_head, con_state) initial_mapping_head = +TAILQ_HEAD(initial_mapping_head, con_state) +initial_mapping_head = TAILQ_HEAD_INITIALIZER(initial_mapping_head); /* @@ -322,10 +330,10 @@ static void x_draw_title_border(Con *con, struct deco_render_params *p) { deco_diff_r = 0; } - draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, + draw_util_rectangle(&(con->parent->frame_buffer), p->color->border, dr->x, dr->y, dr->width, 1); - draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, + draw_util_rectangle(&(con->parent->frame_buffer), p->color->border, dr->x + deco_diff_l, dr->y + dr->height - 1, dr->width - (deco_diff_l + deco_diff_r), 1); } @@ -341,7 +349,7 @@ static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p /* We actually only redraw the far right two pixels as that is the * distance we keep from the edge (not the entire border width). * Redrawing the entire border would cause text to be cut off. */ - draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->background, + draw_util_rectangle(&(con->parent->frame_buffer), p->color->background, dr->x + dr->width - 2 * logical_px(1), dr->y, 2 * logical_px(1), @@ -352,11 +360,11 @@ static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p * be easily distinguished. */ if (con->parent->layout == L_TABBED) { /* Left side */ - draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, + draw_util_rectangle(&(con->parent->frame_buffer), p->color->border, dr->x, dr->y, 1, dr->height); /* Right side */ - draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border, + draw_util_rectangle(&(con->parent->frame_buffer), p->color->border, dr->x + dr->width - 1, dr->y, 1, dr->height); } @@ -450,16 +458,16 @@ void x_draw_decoration(Con *con) { /* 2: draw the client.background, but only for the parts around the window_rect */ if (con->window != NULL) { /* top area */ - draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, + draw_util_rectangle(&(con->frame_buffer), config.client.background, 0, 0, r->width, w->y); /* bottom area */ - draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, + draw_util_rectangle(&(con->frame_buffer), config.client.background, 0, w->y + w->height, r->width, r->height - (w->y + w->height)); /* left area */ - draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, + draw_util_rectangle(&(con->frame_buffer), config.client.background, 0, 0, w->x, r->height); /* right area */ - draw_util_rectangle(conn, &(con->frame_buffer), config.client.background, + draw_util_rectangle(&(con->frame_buffer), config.client.background, w->x + w->width, 0, r->width - (w->x + w->width), r->height); } @@ -476,21 +484,21 @@ void x_draw_decoration(Con *con) { * rectangle because some childs are not freely resizable and we want * their background color to "shine through". */ if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), p->color->child_border, 0, 0, br.x, r->height); + draw_util_rectangle(&(con->frame_buffer), p->color->child_border, 0, 0, br.x, r->height); } if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), + draw_util_rectangle(&(con->frame_buffer), p->color->child_border, r->width + (br.width + br.x), 0, -(br.width + br.x), r->height); } if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), + draw_util_rectangle(&(con->frame_buffer), p->color->child_border, br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)); } /* pixel border needs an additional line at the top */ if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) { - draw_util_rectangle(conn, &(con->frame_buffer), + draw_util_rectangle(&(con->frame_buffer), p->color->child_border, br.x, 0, r->width + br.width, br.y); } @@ -502,10 +510,10 @@ void x_draw_decoration(Con *con) { TAILQ_PREV(con, nodes_head, nodes) == NULL && con->parent->type != CT_FLOATING_CON) { if (p->parent_layout == L_SPLITH) { - draw_util_rectangle(conn, &(con->frame_buffer), p->color->indicator, + draw_util_rectangle(&(con->frame_buffer), p->color->indicator, r->width + (br.width + br.x), br.y, -(br.width + br.x), r->height + br.height); } else if (p->parent_layout == L_SPLITV) { - draw_util_rectangle(conn, &(con->frame_buffer), p->color->indicator, + draw_util_rectangle(&(con->frame_buffer), p->color->indicator, br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y)); } } @@ -525,12 +533,12 @@ void x_draw_decoration(Con *con) { * garbage left on there. This is important to avoid tearing when using * transparency. */ if (con == TAILQ_FIRST(&(con->parent->nodes_head))) { - draw_util_clear_surface(conn, &(con->parent->frame_buffer), COLOR_TRANSPARENT); + draw_util_clear_surface(&(con->parent->frame_buffer), COLOR_TRANSPARENT); FREE(con->parent->deco_render_params); } /* 4: paint the bar */ - draw_util_rectangle(conn, &(parent->frame_buffer), p->color->background, + draw_util_rectangle(&(parent->frame_buffer), p->color->background, con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height); /* 5: draw two unconnected horizontal lines in border color */ @@ -564,9 +572,6 @@ void x_draw_decoration(Con *con) { goto after_title; } - if (win->name == NULL) - goto copy_pixmaps; - int mark_width = 0; if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) { char *formatted_mark = sstrdup(""); @@ -600,18 +605,24 @@ void x_draw_decoration(Con *con) { } i3String *title = con->title_format == NULL ? win->name : con_parse_title_format(con); + if (title == NULL) { + goto copy_pixmaps; + } + draw_util_text(title, &(parent->frame_buffer), p->color->text, p->color->background, con->deco_rect.x + logical_px(2), con->deco_rect.y + text_offset_y, con->deco_rect.width - mark_width - 2 * logical_px(2)); - if (con->title_format != NULL) + + if (con->title_format != NULL) { I3STRING_FREE(title); + } after_title: x_draw_decoration_after_title(con, p); copy_pixmaps: - draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); + draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); } /* @@ -634,7 +645,7 @@ void x_deco_recurse(Con *con) { x_deco_recurse(current); if (state->mapped) { - draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); + draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); } } @@ -821,7 +832,7 @@ void x_push_node(Con *con) { xcb_flush(conn); xcb_set_window_rect(conn, con->frame.id, rect); if (con->frame_buffer.id != XCB_NONE) { - draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); + draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); } xcb_flush(conn); @@ -873,7 +884,7 @@ void x_push_node(Con *con) { /* copy the pixmap contents to the frame window immediately after mapping */ if (con->frame_buffer.id != XCB_NONE) { - draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); + draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height); } xcb_flush(conn); @@ -893,8 +904,9 @@ void x_push_node(Con *con) { /* Handle all children and floating windows of this node. We recurse * in focus order to display the focused client in a stack first when * switching workspaces (reduces flickering). */ - TAILQ_FOREACH(current, &(con->focus_head), focused) - x_push_node(current); + TAILQ_FOREACH(current, &(con->focus_head), focused) { + x_push_node(current); + } } /* diff --git a/src/xcb.c b/src/xcb.c index 900840a2..bdfb08bc 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -28,16 +28,21 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, visual = XCB_COPY_FROM_PARENT; } - xcb_create_window(conn, - depth, - result, /* the window id */ - root, /* parent == root */ - dims.x, dims.y, dims.width, dims.height, /* dimensions */ - 0, /* border = 0, we draw our own */ - window_class, - visual, - mask, - values); + xcb_void_cookie_t gc_cookie = xcb_create_window(conn, + depth, + result, /* the window id */ + root, /* parent == root */ + dims.x, dims.y, dims.width, dims.height, /* dimensions */ + 0, /* border = 0, we draw our own */ + window_class, + visual, + mask, + values); + + xcb_generic_error_t *error = xcb_request_check(conn, gc_cookie); + if (error != NULL) { + ELOG("Could not create window. Error code: %d.\n", error->error_code); + } /* Set the cursor */ if (xcursor_supported) { @@ -62,28 +67,6 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, return result; } -/* - * Draws a line from x,y to to_x,to_y using the given color - * - */ -void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc, - uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t to_x, uint32_t to_y) { - xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, (uint32_t[]){colorpixel}); - xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, drawable, gc, 2, - (xcb_point_t[]){{x, y}, {to_x, to_y}}); -} - -/* - * Draws a rectangle from x,y with width,height using the given color - * - */ -void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable, xcb_gcontext_t gc, - uint32_t colorpixel, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { - xcb_change_gc(conn, gc, XCB_GC_FOREGROUND, (uint32_t[]){colorpixel}); - xcb_rectangle_t rect = {x, y, width, height}; - xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); -} - /* * Generates a configure_notify_event with absolute coordinates (relative to the X root * window, not to the client’s frame) for the given client. @@ -127,15 +110,6 @@ void send_take_focus(xcb_window_t window, xcb_timestamp_t timestamp) { free(event); } -/* - * Raises the given window (typically client->frame) above all other windows - * - */ -void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) { - uint32_t values[] = {XCB_STACK_MODE_ABOVE}; - xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values); -} - /* * Configures the given window to have the size/position specified by given rect * @@ -201,18 +175,6 @@ bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom) { return false; } -/** - * Moves the mouse pointer into the middle of rect. - * - */ -void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect) { - int mid_x = rect->x + (rect->width / 2); - int mid_y = rect->y + (rect->height / 2); - - LOG("warp pointer to: %d %d\n", mid_x, mid_y); - xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y); -} - /* * Set the cursor of the root window to the given cursor id. * This function should only be used if xcursor_supported == false. diff --git a/testcases/Makefile.PL b/testcases/Makefile.PL index 3c2a26f9..0b1f3055 100755 --- a/testcases/Makefile.PL +++ b/testcases/Makefile.PL @@ -8,7 +8,6 @@ WriteMakefile( MIN_PERL_VERSION => '5.010000', # 5.10.0 PREREQ_PM => { 'AnyEvent' => 0, - 'AnyEvent::I3' => '0.16', 'X11::XCB' => '0.12', 'Inline' => 0, 'Inline::C' => 0, diff --git a/testcases/complete-run.pl.in b/testcases/complete-run.pl.in index d872bda1..ba192469 100755 --- a/testcases/complete-run.pl.in +++ b/testcases/complete-run.pl.in @@ -18,7 +18,7 @@ use Time::HiRes qw(time); use IO::Handle; # these are shipped with the testsuite -use lib qw(@abs_top_builddir@/testcases/lib @abs_top_srcdir@/testcases/lib); +use lib qw(@abs_top_builddir@/testcases/lib @abs_top_srcdir@/testcases/lib @abs_top_srcdir@/AnyEvent-I3/blib/lib); use i3test::Util qw(slurp); use StartXServer; use StatusLine; @@ -87,6 +87,17 @@ foreach my $binary (@binaries) { die "$binary is not an executable" unless -x $binary; } +my @test_binaries = qw( + @abs_top_builddir@/test.commands_parser + @abs_top_builddir@/test.config_parser + @abs_top_builddir@/test.inject_randr15 + ); + +foreach my $binary (@test_binaries) { + die "$binary executable not found, did you run “make check”?" unless -e $binary; + die "$binary is not an executable" unless -x $binary; +} + $ENV{PATH} = join(':', '@abs_top_builddir@/i3-nagbar', '@abs_top_builddir@/i3-msg', diff --git a/testcases/inject_randr1.5.c b/testcases/inject_randr1.5.c new file mode 100644 index 00000000..5796ef05 --- /dev/null +++ b/testcases/inject_randr1.5.c @@ -0,0 +1,440 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) + * + * inject_randr1.5.c: An X11 proxy which interprets RandR 1.5 GetMonitors + * requests and overwrites their reply with a custom reply. + * + * This tool can be refactored as necessary in order to perform the same + * purpose for other request types. The RandR 1.5 specific portions of the code + * have been marked as such to make such a refactoring easier. + * + */ +#include "all.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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] \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); +} diff --git a/testcases/lib/SocketActivation.pm b/testcases/lib/SocketActivation.pm index 53dbb3b6..0f307eb3 100644 --- a/testcases/lib/SocketActivation.pm +++ b/testcases/lib/SocketActivation.pm @@ -88,7 +88,10 @@ sub activate_i3 { # the interactive signalhandler to make it crash immediately instead. # Also disable logging to SHM since we redirect the logs anyways. # Force Xinerama because we use Xdmx for multi-monitor tests. - my $i3cmd = q|i3 --shmlog-size=0 --disable-signalhandler --force-xinerama|; + my $i3cmd = q|i3 --shmlog-size=0 --disable-signalhandler|; + if (!defined($args{inject_randr15})) { + $i3cmd .= q| --force-xinerama|; + } if (!$args{validate_config}) { # We only set logging if i3 is actually started, but not if we only # validate the config file. This is to keep logging to a minimum as @@ -139,6 +142,13 @@ sub activate_i3 { 'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"'; } + if ($args{inject_randr15}) { + # See comment in $args{strace} branch. + $cmd = 'test.inject_randr15 --getmonitors_reply="' . + $args{inject_randr15} . '" -- ' . + 'sh -c "export LISTEN_PID=\$\$; ' . $cmd . '"'; + } + # We need to use the shell due to using output redirections. exec '/bin/sh', '-c', $cmd; diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index f9f6e821..18bebb52 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -7,6 +7,7 @@ use Test::Builder; use X11::XCB::Rect; use X11::XCB::Window; use X11::XCB qw(:all); +use lib qw(@abs_top_srcdir@/AnyEvent-I3/blib/lib); use AnyEvent::I3; use List::Util qw(first); use Time::HiRes qw(sleep); @@ -861,6 +862,7 @@ sub launch_with_config { cv => $cv, dont_create_temp_dir => $args{dont_create_temp_dir}, validate_config => $args{validate_config}, + inject_randr15 => $args{inject_randr15}, ); # If we called i3 with -C, we wait for it to exit and then return as diff --git a/testcases/lib/i3test/XTEST.pm b/testcases/lib/i3test/XTEST.pm index 92adde42..3937b70a 100644 --- a/testcases/lib/i3test/XTEST.pm +++ b/testcases/lib/i3test/XTEST.pm @@ -6,6 +6,7 @@ use warnings; use v5.10; use i3test i3_autostart => 0; +use lib qw(@abs_top_srcdir@/AnyEvent-I3/blib/lib); use AnyEvent::I3; use ExtUtils::PkgConfig; diff --git a/testcases/t/000-load-deps.t b/testcases/t/000-load-deps.t index ab93233a..e0408338 100644 --- a/testcases/t/000-load-deps.t +++ b/testcases/t/000-load-deps.t @@ -8,7 +8,6 @@ BEGIN { X11::XCB::Connection X11::XCB::Window AnyEvent - AnyEvent::I3 IPC::Run ExtUtils::PkgConfig Inline diff --git a/testcases/t/117-workspace.t b/testcases/t/117-workspace.t index 01d51cc0..40b2cbb6 100644 --- a/testcases/t/117-workspace.t +++ b/testcases/t/117-workspace.t @@ -279,6 +279,12 @@ is(focused_ws(), 'bla', 'now on workspace bla'); cmd 'rename workspace to to'; ok(!workspace_exists('bla'), 'workspace bla does not exist anymore'); is(focused_ws(), 'to', 'now on workspace to'); +cmd 'rename workspace to bla'; +ok(!workspace_exists('to'), 'workspace to does not exist anymore'); +is(focused_ws(), 'bla', 'now on workspace bla'); +cmd 'rename workspace to tosomething'; +ok(!workspace_exists('bla'), 'workspace bla does not exist anymore'); +is(focused_ws(), 'tosomething', 'now on workspace tosomething'); # 6: already existing workspace my $result = cmd 'rename workspace qux to 11: bar'; diff --git a/testcases/t/141-resize.t b/testcases/t/141-resize.t index 44ad9bbf..64e33bc3 100644 --- a/testcases/t/141-resize.t +++ b/testcases/t/141-resize.t @@ -298,11 +298,11 @@ sub get_floating_rect { # focus is on the right window, so we resize the left one using criteria my $leftold = get_floating_rect($left->id); my $rightold = get_floating_rect($right->id); -cmd '[id="' . $left->id . '"] resize shrink height 10px or 10ppt'; +cmd '[id="' . $left->id . '"] resize grow height 10px or 10ppt'; my $leftnew = get_floating_rect($left->id); my $rightnew = get_floating_rect($right->id); is($rightnew->{height}, $rightold->{height}, 'height of right container unchanged'); -is($leftnew->{height}, $leftold->{height} - 10, 'height of left container changed'); +is($leftnew->{height}, $leftold->{height} + 10, 'height of left container changed'); done_testing; diff --git a/testcases/t/167-workspace_layout.t b/testcases/t/167-workspace_layout.t index 033a31f2..1ed09990 100644 --- a/testcases/t/167-workspace_layout.t +++ b/testcases/t/167-workspace_layout.t @@ -145,6 +145,237 @@ is($x->input_focus, $second->id, 'second window focused'); ok(@content == 1, 'one con at workspace level'); is($content[0]->{layout}, 'stacked', 'layout stacked'); +##################################################################### +# 8: when the workspace is empty check that its layout can be changed +# from stacked to horizontal split using the 'layout splith' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout stacked'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'stacked', 'layout stacked'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'layout splith'; +$first = open_window; +$second = open_window; +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'stacked', 'layout not stacked'); +isnt($content[1]->{layout}, 'stacked', 'layout not stacked'); + +##################################################################### +# 9: when the workspace is empty check that its layout can be changed +# from stacked to vertical split using the 'layout splitv' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout stacked'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'stacked', 'layout stacked'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'layout splitv'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'stacked', 'layout not stacked'); +isnt($content[1]->{layout}, 'stacked', 'layout not stacked'); + +##################################################################### +# 10: when the workspace is empty check that its layout can be changed +# from tabbed to horizontal split using the 'layout splith' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout tabbed'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'tabbed', 'layout tabbed'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'layout splith'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed'); +isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed'); + +##################################################################### +# 11: when the workspace is empty check that its layout can be changed +# from tabbed to vertical split using the 'layout splitv' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout tabbed'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'tabbed', 'layout tabbed'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'layout splitv'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed'); +isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed'); + +##################################################################### +# 12: when the workspace is empty check that its layout can be changed +# from stacked to horizontal split using the 'split horizontal' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout stacked'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'stacked', 'layout stacked'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'split horizontal'; +$first = open_window; +$second = open_window; +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'stacked', 'layout not stacked'); +isnt($content[1]->{layout}, 'stacked', 'layout not stacked'); + +##################################################################### +# 13: when the workspace is empty check that its layout can be changed +# from stacked to vertical split using the 'split vertical' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout stacked'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'stacked', 'layout stacked'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'split vertical'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'stacked', 'layout not stacked'); +isnt($content[1]->{layout}, 'stacked', 'layout not stacked'); + +##################################################################### +# 14: when the workspace is empty check that its layout can be changed +# from tabbed to horizontal split using the 'split horizontal' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout tabbed'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'tabbed', 'layout tabbed'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'split horizontal'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed'); +isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed'); + +##################################################################### +# 15: when the workspace is empty check that its layout can be changed +# from tabbed to vertical split using the 'split vertical' command. +##################################################################### + +$tmp = fresh_workspace; + +cmd 'layout tabbed'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +is($content[0]->{layout}, 'tabbed', 'layout tabbed'); + +cmd '[id="' . $first->id . '"] kill'; +cmd '[id="' . $second->id . '"] kill'; +sync_with_i3; + +ok(@{get_ws_content($tmp)} == 0, 'workspace is empty'); + +cmd 'split vertical'; +$first = open_window; +$second = open_window; + +@content = @{get_ws_content($tmp)}; +ok(@content == 2, 'two containers opened'); +isnt($content[0]->{layout}, 'tabbed', 'layout not tabbed'); +isnt($content[1]->{layout}, 'tabbed', 'layout not tabbed'); + + exit_gracefully($pid); done_testing; diff --git a/testcases/t/171-config-migrate.t b/testcases/t/171-config-migrate.t index 5bd21128..d855e818 100644 --- a/testcases/t/171-config-migrate.t +++ b/testcases/t/171-config-migrate.t @@ -153,7 +153,7 @@ ok(line_exists($output, qr|^bindsym Mod1\+s restart$|), 'restart unchanged'); ok(line_exists($output, qr|^bindsym Mod1\+s reload$|), 'reload unchanged'); ok(line_exists($output, qr|^bindsym Mod1\+s exit$|), 'exit unchanged'); ok(line_exists($output, qr|^bindcode Mod1\+c exec /usr/bin/urxvt$|), 'bind changed to bindcode'); -ok(line_exists($output, qr|^mode "asdf" {$|), 'mode asdf unchanged'); +ok(line_exists($output, qr|^mode "asdf" \{$|), 'mode asdf unchanged'); ok(line_exists($output, qr|^bindcode 36 mode \"default\"$|), 'mode default unchanged'); ok(line_exists($output, qr|^}$|), 'closing mode bracket still there'); @@ -336,13 +336,13 @@ ok(line_exists($output, qr|^bindsym Mod1\+3 move container to workspace work|), ##################################################################### $output = migrate_config(''); -ok(line_exists($output, qr|bar {|), 'i3bar added'); +ok(line_exists($output, qr|bar \{|), 'i3bar added'); $output = migrate_config('workspace_bar enable'); -ok(line_exists($output, qr|bar {|), 'i3bar added'); +ok(line_exists($output, qr|bar \{|), 'i3bar added'); $output = migrate_config('workspace_bar no'); -ok(!line_exists($output, qr|bar {|), 'no i3bar added'); +ok(!line_exists($output, qr|bar \{|), 'no i3bar added'); ##################################################################### # check whether the mode command gets quotes diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t index dc5b67ed..5d3a0ff5 100644 --- a/testcases/t/187-commands-parser.t +++ b/testcases/t/187-commands-parser.t @@ -169,6 +169,7 @@ is(parser_calls('unknown_literal'), rename nop scratchpad + swap title_format mode bar diff --git a/testcases/t/192-layout.t b/testcases/t/192-layout.t index 6fd6eae8..c54f27a2 100644 --- a/testcases/t/192-layout.t +++ b/testcases/t/192-layout.t @@ -95,4 +95,72 @@ cmd 'layout toggle all'; ($nodes, $focus) = get_ws_content($tmp); is($nodes->[1]->{layout}, 'splitv', 'layout now splitv'); +cmd 'layout toggle splith splitv'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splith', 'layout now splith'); + +cmd 'layout toggle splith splitv'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splitv', 'layout now splitv'); + +cmd 'layout toggle stacked splitv tabbed'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed'); + +cmd 'layout toggle stacking splitv tabbed'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'stacked', 'layout now stacked'); + +cmd 'layout toggle stacking splitv tabbed'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splitv', 'layout now splitv'); + +cmd 'layout toggle splitv i stacking tabbed'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'stacked', 'layout now stacked'); + +cmd 'layout toggle stacked'; +($nodes, $focus) = get_ws_content($tmp); +# this is correct if it does nothing +is($nodes->[1]->{layout}, 'stacked', 'layout now stacked'); + +cmd 'layout toggle tabbed stacked'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed'); + +# obsoletes 'split' ;) +cmd 'layout toggle splith splitv'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splith', 'layout now splith'); + +# nonsense but works expectedly +cmd 'layout toggle split split'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splitv', 'layout now splitv'); + +cmd 'layout toggle split split'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splith', 'layout now splith'); + +# testing with arbitrary length and garbage +cmd 'layout toggle stacking splith tabbed splitv stacking'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed'); + +cmd 'layout toggle stacking splith garbage tabbed splitv stacking'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splitv', 'layout now splitv'); + +cmd 'layout toggle stacking splith garbage tabbed splitv stacking'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'stacked', 'layout now stacked'); + +cmd 'layout toggle splitv splith garbage splitv tabbed stacking splitv'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'splitv', 'layout now splitv'); + +cmd 'layout toggle splitv garbage tabbed'; +($nodes, $focus) = get_ws_content($tmp); +is($nodes->[1]->{layout}, 'tabbed', 'layout now tabbed'); + done_testing; diff --git a/testcases/t/201-config-parser.t b/testcases/t/201-config-parser.t index 6cd84b6f..fb3130d5 100644 --- a/testcases/t/201-config-parser.t +++ b/testcases/t/201-config-parser.t @@ -49,18 +49,22 @@ mode "meh" { bindsym --release --whole-window button3 nop bindsym --border button3 nop bindsym --release --border button3 nop + bindsym --exclude-titlebar button3 nop + bindsym --whole-window --border --exclude-titlebar button3 nop } EOT my $expected = <<'EOT'; cfg_enter_mode((null), meh) -cfg_mode_binding(bindsym, Mod1,Shift, x, (null), (null), (null), resize grow) -cfg_mode_binding(bindcode, Mod1, 44, (null), (null), (null), resize shrink) -cfg_mode_binding(bindsym, Mod1, x, --release, (null), (null), exec foo) -cfg_mode_binding(bindsym, (null), button3, (null), (null), --whole-window, nop) -cfg_mode_binding(bindsym, (null), button3, --release, (null), --whole-window, nop) -cfg_mode_binding(bindsym, (null), button3, (null), --border, (null), nop) -cfg_mode_binding(bindsym, (null), button3, --release, --border, (null), nop) +cfg_mode_binding(bindsym, Mod1,Shift, x, (null), (null), (null), (null), resize grow) +cfg_mode_binding(bindcode, Mod1, 44, (null), (null), (null), (null), resize shrink) +cfg_mode_binding(bindsym, Mod1, x, --release, (null), (null), (null), exec foo) +cfg_mode_binding(bindsym, (null), button3, (null), (null), --whole-window, (null), nop) +cfg_mode_binding(bindsym, (null), button3, --release, (null), --whole-window, (null), nop) +cfg_mode_binding(bindsym, (null), button3, (null), --border, (null), (null), nop) +cfg_mode_binding(bindsym, (null), button3, --release, --border, (null), (null), nop) +cfg_mode_binding(bindsym, (null), button3, (null), (null), (null), --exclude-titlebar, nop) +cfg_mode_binding(bindsym, (null), button3, (null), --border, --whole-window, --exclude-titlebar, nop) EOT is(parser_calls($config), @@ -442,8 +446,7 @@ hide_edge_border both client.focused #4c7899 #285577 #ffffff #2e9ef4 EOT -my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: , '#', '" . join("', '", qw( - set +my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: , '#', '" . join("', '", 'set ', 'set ', qw( set_from_resource bindsym bindcode @@ -467,6 +470,8 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: , ' force_focus_wrapping force_xinerama force-xinerama + disable_randr15 + disable-randr15 workspace_auto_back_and_forth fake_outputs fake-outputs @@ -672,7 +677,7 @@ EOT $expected = <<'EOT'; cfg_enter_mode((null), yo) -cfg_mode_binding(bindsym, (null), x, (null), (null), (null), resize shrink left) +cfg_mode_binding(bindsym, (null), x, (null), (null), (null), (null), resize shrink left) ERROR: CONFIG: Expected one of these tokens: , '#', 'set', 'bindsym', 'bindcode', 'bind', '}' ERROR: CONFIG: (in file ) ERROR: CONFIG: Line 1: mode "yo" { diff --git a/testcases/t/221-floating-type-hints.t b/testcases/t/221-floating-type-hints.t index 01c73a75..ae0f05c3 100644 --- a/testcases/t/221-floating-type-hints.t +++ b/testcases/t/221-floating-type-hints.t @@ -62,10 +62,10 @@ sub open_with_fixed_size { my $flags = $XCB_ICCCM_SIZE_HINT_P_MIN_SIZE | $XCB_ICCCM_SIZE_HINT_P_MAX_SIZE; - my $min_width = 55; - my $max_width = 55; - my $min_height = 77; - my $max_height = 77; + my $min_width = 150; + my $max_width = 150; + my $min_height = 100; + my $max_height = 100; my $pad = 0x00; @@ -81,7 +81,7 @@ sub open_with_fixed_size { $atomname->id, $atomtype->id, 32, - 12, + 13, pack('C5N8', $flags, $pad, $pad, $pad, $pad, 0, 0, 0, $min_width, $min_height, $max_width, $max_height), ); }, @@ -114,6 +114,8 @@ $window->unmap; $window = open_with_fixed_size; is(get_ws($ws)->{floating_nodes}[0]->{nodes}[0]->{window}, $window->id, 'Fixed size window opened floating'); +is(get_ws($ws)->{floating_nodes}[0]->{nodes}[0]->{window_rect}->{width}, 150, 'Fixed size window opened with minimum width'); +is(get_ws($ws)->{floating_nodes}[0]->{nodes}[0]->{window_rect}->{height}, 100, 'Fixed size window opened with minimum height'); $window->unmap; done_testing; diff --git a/testcases/t/264-ipc-shutdown-event.t b/testcases/t/264-ipc-shutdown-event.t new file mode 100644 index 00000000..379b9bf2 --- /dev/null +++ b/testcases/t/264-ipc-shutdown-event.t @@ -0,0 +1,71 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Test the ipc shutdown event. This event is triggered when the connection to +# the ipc is about to shutdown because of a user action such as with a +# `restart` or `exit` command. The `change` field indicates why the ipc is +# shutting down. It can be either "restart" or "exit". +# +# Ticket: #2318 +# Bug still in: 4.12-46-g2123888 +use i3test; + +SKIP: { + skip "AnyEvent::I3 too old (need >= 0.17)", 1 if $AnyEvent::I3::VERSION < 0.17; + +my $i3 = i3(get_socket_path()); +$i3->connect->recv; + +my $cv = AE::cv; +my $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; + +$i3->subscribe({ + shutdown => sub { + $cv->send(shift); + } + })->recv; + +cmd 'restart'; + +my $e = $cv->recv; + +diag "Event:\n", Dumper($e); +ok($e, 'the shutdown event should emit when the ipc is restarted by command'); +is($e->{change}, 'restart', 'the `change` field should tell the reason for the shutdown'); + +# restarting kills the ipc client so we have to make a new one +$i3 = i3(get_socket_path()); +$i3->connect->recv; + +$cv = AE::cv; +$timer = AE::timer 0.5, 0, sub { $cv->send(0); }; + +$i3->subscribe({ + shutdown => sub { + $cv->send(shift); + } + })->recv; + +cmd 'exit'; + +$e = $cv->recv; + +diag "Event:\n", Dumper($e); +ok($e, 'the shutdown event should emit when the ipc is exited by command'); +is($e->{change}, 'exit', 'the `change` field should tell the reason for the shutdown'); +} + +done_testing; diff --git a/testcases/t/264-keypress-numlock.t b/testcases/t/264-keypress-numlock.t index 45ec7e88..fcc39ead 100644 --- a/testcases/t/264-keypress-numlock.t +++ b/testcases/t/264-keypress-numlock.t @@ -44,6 +44,9 @@ bindsym Shift+Escape nop Shift+Escape # Binding which should work with numlock and without, see issue #2418. bindsym Mod1+Shift+q nop Mod1+Shift+q + +# Binding which should work with numlock and without, see issue #2559. +bindcode 39 nop s EOT my $pid = launch_with_config($config); @@ -159,7 +162,6 @@ is(listen_for_binding( 'Mod1+Shift+q', 'triggered the "Mod1+Shift+q" keybinding'); - is(listen_for_binding( sub { xtest_key_press(77); # enable Num_Lock @@ -177,8 +179,101 @@ is(listen_for_binding( 'Mod1+Shift+q', 'triggered the "Mod1+Shift+q" keybinding'); +is(listen_for_binding( + sub { + xtest_key_press(39); # s + xtest_key_release(39); # s + }, + ), + 's', + 'triggered the "s" keybinding without Num_Lock'); + +is(listen_for_binding( + sub { + xtest_key_press(77); # enable Num_Lock + xtest_key_release(77); # enable Num_Lock + xtest_key_press(39); # s + xtest_key_release(39); # s + xtest_key_press(77); # disable Num_Lock + xtest_key_release(77); # disable Num_Lock + }, + ), + 's', + 'triggered the "s" keybinding with Num_Lock'); + sync_with_i3; -is(scalar @i3test::XTEST::binding_events, 10, 'Received exactly 10 binding events'); +is(scalar @i3test::XTEST::binding_events, 12, 'Received exactly 12 binding events'); + +exit_gracefully($pid); + +################################################################################ +# Verify bindings for modifiers work +################################################################################ + +$config = < 0; + +my $config = < 'mark_A'); +$B = open_window(wm_class => 'mark_B'); +$expected_focus = get_focused($ws); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws); +is($nodes->[0]->{window}, $B->{id}, 'B is on the left'); +is($nodes->[1]->{window}, $A->{id}, 'A is on the right'); +is(get_focused($ws), $expected_focus, 'B is still focused'); + +exit_gracefully($pid); + +############################################################################### +# Swap two containers with different parents. +# In this test, the focus head of the left v-split container is A. +# The focused container is B. +# +# +---+---+ Layout: H1[ V1[ A Y ] V2[ X B ] ] +# | A | X | Focus Stacks: +# +---+---+ H1: V2, V1 +# | Y | B | V1: A, Y +# +---+---+ V2: B, X +############################################################################### +$pid = launch_with_config($config); +$ws = fresh_workspace; + +$A = open_window(wm_class => 'mark_A'); +$B = open_window(wm_class => 'mark_B'); +cmd 'split v'; +open_window; +cmd 'move up, focus left'; +cmd 'split v'; +open_window; +cmd 'focus up, focus right, focus down'; +$expected_focus = get_focused($ws); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws); +is($nodes->[0]->{nodes}->[0]->{window}, $B->{id}, 'B is on the top left'); +is($nodes->[1]->{nodes}->[1]->{window}, $A->{id}, 'A is on the bottom right'); +is(get_focused($ws), $expected_focus, 'B is still focused'); + +exit_gracefully($pid); + +############################################################################### +# Swap two containers with different parents. +# In this test, the focus head of the left v-split container is _not_ A. +# The focused container is B. +# +# +---+---+ Layout: H1[ V1[ A Y ] V2[ X B ] ] +# | A | X | Focus Stacks: +# +---+---+ H1: V2, V1 +# | Y | B | V1: Y, A +# +---+---+ V2: B, X +############################################################################### +$pid = launch_with_config($config); +$ws = fresh_workspace; + +$A = open_window(wm_class => 'mark_A'); +$B = open_window(wm_class => 'mark_B'); +cmd 'split v'; +open_window; +cmd 'move up, focus left'; +cmd 'split v'; +open_window; +cmd 'focus right, focus down'; +$expected_focus = get_focused($ws); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws); +is($nodes->[0]->{nodes}->[0]->{window}, $B->{id}, 'B is on the top left'); +is($nodes->[1]->{nodes}->[1]->{window}, $A->{id}, 'A is on the bottom right'); +is(get_focused($ws), $expected_focus, 'B is still focused'); + +exit_gracefully($pid); + +############################################################################### +# Swap two containers with one being on a different workspace. +# The focused container is B. +# +# Layout: O1[ W1[ H1 ] W2[ H2 ] ] +# Focus Stacks: +# O1: W2, W1 +# +# +---+---+ Layout: H1[ A X ] +# | A | X | Focus Stacks: +# +---+---+ H1: A, X +# +# +---+---+ Layout: H2[ Y, B ] +# | Y | B | Focus Stacks: +# +---+---+ H2: B, Y +############################################################################### +$pid = launch_with_config($config); + +$ws1 = fresh_workspace; +$A = open_window(wm_class => 'mark_A'); +$expected_focus = get_focused($ws1); +open_window; +cmd 'focus left'; + +$ws2 = fresh_workspace; +open_window; +$B = open_window(wm_class => 'mark_B'); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws1); +is($nodes->[0]->{window}, $B->{id}, 'B is on ws1:left'); + +$nodes = get_ws_content($ws2); +is($nodes->[1]->{window}, $A->{id}, 'A is on ws1:right'); +is(get_focused($ws2), $expected_focus, 'A is focused'); + +exit_gracefully($pid); + +############################################################################### +# Swap two non-focused containers within the same workspace. +# +# +---+---+ Layout: H1[ V1[ A X ] V2[ F B ] ] +# | A | F | Focus Stacks: +# +---+---+ H1: V2, V1 +# | X | B | V1: A, X +# +---+---+ V2: F, B +############################################################################### +$pid = launch_with_config($config); +$ws = fresh_workspace; + +$A = open_window(wm_class => 'mark_A'); +$B = open_window(wm_class => 'mark_B'); +cmd 'split v'; +open_window; +cmd 'move up, focus left'; +cmd 'split v'; +open_window; +cmd 'focus up, focus right'; +$expected_focus = get_focused($ws); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws); +is($nodes->[0]->{nodes}->[0]->{window}, $B->{id}, 'B is on the top left'); +is($nodes->[1]->{nodes}->[1]->{window}, $A->{id}, 'A is on the bottom right'); +is(get_focused($ws), $expected_focus, 'F is still focused'); + +exit_gracefully($pid); + +############################################################################### +# Swap two non-focused containers which are both on different workspaces. +# +# Layout: O1[ W1[ A ] W2[ B ] W3[ F ] ] +# Focus Stacks: +# O1: W3, W2, W1 +# +# +---+ +# | A | +# +---+ +# +# +---+ +# | B | +# +---+ +# +# +---+ +# | F | +# +---+ +############################################################################### +$pid = launch_with_config($config); + +$ws1 = fresh_workspace; +$A = open_window(wm_class => 'mark_A'); + +$ws2 = fresh_workspace; +$B = open_window(wm_class => 'mark_B'); + +$ws3 = fresh_workspace; +open_window; +$expected_focus = get_focused($ws3); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws1); +is($nodes->[0]->{window}, $B->{id}, 'B is on the first workspace'); + +$nodes = get_ws_content($ws2); +is($nodes->[0]->{window}, $A->{id}, 'A is on the second workspace'); + +is(get_focused($ws3), $expected_focus, 'F is still focused'); + +exit_gracefully($pid); + +############################################################################### +# Swap two non-focused containers with one being on a different workspace. +# +# Layout: O1[ W1[ A ] W2[ H2 ] ] +# Focus Stacks: +# O1: W2, W1 +# +# +---+ +# | A | +# +---+ +# +# +---+---+ Layout: H2[ B, F ] +# | B | F | Focus Stacks: +# +---+---+ H2: F, B +############################################################################### +$pid = launch_with_config($config); + +$ws1 = fresh_workspace; +$A = open_window(wm_class => 'mark_A'); + +$ws2 = fresh_workspace; +$B = open_window(wm_class => 'mark_B'); +open_window; +$expected_focus = get_focused($ws2); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws1); +is($nodes->[0]->{window}, $B->{id}, 'B is on the first workspace'); + +$nodes = get_ws_content($ws2); +is($nodes->[0]->{window}, $A->{id}, 'A is on the left of the second workspace'); +is(get_focused($ws2), $expected_focus, 'F is still focused'); + +exit_gracefully($pid); + +############################################################################### +# 1. A container cannot be swapped with its parent. +# 2. A container cannot be swapped with one of its children. +# +# ↓A↓ +# +---+---+ Layout: H1[ X V1[ Y B ] ] +# | | Y | (with A := V1) +# | X +---+ +# | | B | +# +---+---+ +############################################################################### +$pid = launch_with_config($config); + +$ws = fresh_workspace; +open_window; +open_window; +cmd 'split v'; +$B = open_window(wm_class => 'mark_B'); +cmd 'focus parent, mark A, focus child'; + +$result = cmd '[con_mark=B] swap container with mark A'; +is($result->[0]->{success}, 0, 'B cannot be swappd with its parent'); + +$result = cmd '[con_mark=A] swap container with mark B'; +is($result->[0]->{success}, 0, 'A cannot be swappd with one of its children'); + +exit_gracefully($pid); + +############################################################################### +# Swapping two containers preserves the geometry of the container they are +# being swapped with. +# +# Before: +# +---+-------+ +# | A | B | +# +---+-------+ +# +# After: +# +---+-------+ +# | B | A | +# +---+-------+ +############################################################################### +$pid = launch_with_config($config); + +$ws = fresh_workspace; +$A = open_window(wm_class => 'mark_A'); +$B = open_window(wm_class => 'mark_B'); +cmd 'resize grow width 0 or 25 ppt'; + +# sanity checks +$nodes = get_ws_content($ws); +cmp_float($nodes->[0]->{percent}, 0.25, 'A has 25% width'); +cmp_float($nodes->[1]->{percent}, 0.75, 'B has 75% width'); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws); +cmp_float($nodes->[0]->{percent}, 0.25, 'B has 25% width'); +cmp_float($nodes->[1]->{percent}, 0.75, 'A has 75% width'); + +exit_gracefully($pid); + +############################################################################### +# Swapping containers not sharing the same parent preserves the geometry of +# the container they are swapped with. +# +# Before: +# +---+-----+ +# | A | | +# +---+ B | +# | | | +# | Y +-----+ +# | | X | +# +---+-----+ +# +# After: +# +---+-----+ +# | B | | +# +---+ A | +# | | | +# | Y +-----+ +# | | X | +# +---+-----+ +############################################################################### +$pid = launch_with_config($config); +$ws = fresh_workspace; + +$A = open_window(wm_class => 'mark_A'); +$B = open_window(wm_class => 'mark_B'); +cmd 'split v'; +open_window; +cmd 'focus up, resize grow height 0 or 25 ppt'; +cmd 'focus left, split v'; +open_window; +cmd 'resize grow height 0 or 25 ppt'; + +# sanity checks +$nodes = get_ws_content($ws); +cmp_float($nodes->[0]->{nodes}->[0]->{percent}, 0.25, 'A has 25% height'); +cmp_float($nodes->[1]->{nodes}->[0]->{percent}, 0.75, 'B has 75% height'); + +cmd '[con_mark=B] swap container with mark A'; + +$nodes = get_ws_content($ws); +cmp_float($nodes->[0]->{nodes}->[0]->{percent}, 0.25, 'B has 25% height'); +cmp_float($nodes->[1]->{nodes}->[0]->{percent}, 0.75, 'A has 75% height'); + +exit_gracefully($pid); + +############################################################################### +# Swapping containers moves the urgency hint correctly. +############################################################################### +$pid = launch_with_config($config); + +$ws1 = fresh_workspace; +$A = open_window(wm_class => 'mark_A'); +$ws2 = fresh_workspace; +$B = open_window(wm_class => 'mark_B'); +open_window; + +$B->add_hint('urgency'); +sync_with_i3; + +cmd '[con_mark=B] swap container with mark A'; + +@urgent = grep { $_->{urgent} } @{get_ws_content($ws1)}; +is(@urgent, 1, 'B is marked urgent'); +is(get_ws($ws1)->{urgent}, 1, 'the first workspace is marked urgent'); + +@urgent = grep { $_->{urgent} } @{get_ws_content($ws2)}; +is(@urgent, 0, 'A is not marked urgent'); +is(get_ws($ws2)->{urgent}, 0, 'the second workspace is not marked urgent'); + +exit_gracefully($pid); + +############################################################################### + +done_testing; diff --git a/testcases/t/266-net-moveresize-window.t b/testcases/t/266-net-moveresize-window.t new file mode 100644 index 00000000..69542f9c --- /dev/null +++ b/testcases/t/266-net-moveresize-window.t @@ -0,0 +1,117 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests for _NET_MOVERESIZE_WINDOW. +# Ticket: #2603 +use i3test i3_autostart => 0; + +sub moveresize_window { + my ($win, $pos_x, $pos_y, $width, $height) = @_; + + my $flags = 0; + $flags |= (1 << 8) if $pos_x >= 0; + $flags |= (1 << 9) if $pos_y >= 0; + $flags |= (1 << 10) if $width >= 0; + $flags |= (1 << 11) if $height >= 0; + + my $msg = pack "CCSLLLLLLL", + X11::XCB::CLIENT_MESSAGE, # response_type + 32, # format + 0, # sequence + $win->id, # window + $x->atom(name => '_NET_MOVERESIZE_WINDOW')->id, # message type + $flags, # data32[0] (flags) + $pos_x, # data32[1] (x) + $pos_y, # data32[2] (y) + $width, # data32[3] (width) + $height; # data32[4] (height) + + $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); + sync_with_i3; +} + +my $config = < [50, 50, 100, 100]); +moveresize_window($window, 0, 0, 555, 666); + +$content = get_ws($ws); +is($content->{floating_nodes}->[0]->{rect}->{x}, 0, 'the x coordinate is correct'); +is($content->{floating_nodes}->[0]->{rect}->{y}, 0, 'the y coordinate is correct'); +is($content->{floating_nodes}->[0]->{rect}->{width}, 555, 'the width is correct'); +is($content->{floating_nodes}->[0]->{rect}->{height}, 666, 'the height is correct'); + +exit_gracefully($pid); + +############################################################################### +# A _NET_MOVERESIZE_WINDOW client message can change only the position of a +# window. +############################################################################### + +$pid = launch_with_config($config); +$ws = fresh_workspace; + +$window = open_floating_window(rect => [50, 50, 100, 100]); +moveresize_window($window, 100, 100, -1, -1); + +$content = get_ws($ws); +is($content->{floating_nodes}->[0]->{rect}->{x}, 100, 'the x coordinate is correct'); +is($content->{floating_nodes}->[0]->{rect}->{y}, 100, 'the y coordinate is correct'); +is($content->{floating_nodes}->[0]->{rect}->{width}, 100, 'the width is unchanged'); +is($content->{floating_nodes}->[0]->{rect}->{height}, 100, 'the height is unchanged'); + +exit_gracefully($pid); + +############################################################################### +# A _NET_MOVERESIZE_WINDOW client message can change only the size of a +# window. +############################################################################### + +$pid = launch_with_config($config); +$ws = fresh_workspace; + +$window = open_floating_window(rect => [50, 50, 100, 100]); +moveresize_window($window, -1, -1, 200, 200); + +$content = get_ws($ws); +is($content->{floating_nodes}->[0]->{rect}->{x}, 50, 'the x coordinate is unchanged'); +is($content->{floating_nodes}->[0]->{rect}->{y}, 50, 'the y coordinate is unchanged'); +is($content->{floating_nodes}->[0]->{rect}->{width}, 200, 'the width is correct'); +is($content->{floating_nodes}->[0]->{rect}->{height}, 200, 'the height is correct'); + +exit_gracefully($pid); + +############################################################################### + +done_testing; diff --git a/testcases/t/267-regress-mark-restart.t b/testcases/t/267-regress-mark-restart.t new file mode 100644 index 00000000..220d765b --- /dev/null +++ b/testcases/t/267-regress-mark-restart.t @@ -0,0 +1,30 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Regression test to check if mark and restart commands crash i3 +# +use i3test; + +cmd 'open'; +cmd 'mark foo'; + +cmd 'restart'; + +diag('Checking if i3 still lives'); + +does_i3_live; + +done_testing; diff --git a/testcases/t/268-ipc-config.t b/testcases/t/268-ipc-config.t new file mode 100644 index 00000000..bb578e13 --- /dev/null +++ b/testcases/t/268-ipc-config.t @@ -0,0 +1,55 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies that the config file is returned raw via the IPC interface. +# Ticket: #2856 +# Bug still in: 4.13-133-ge4da07e7 +use i3test i3_autostart => 0; +use File::Temp qw(tempdir); + +my $tmpdir = tempdir(CLEANUP => 1); +my $socketpath = $tmpdir . "/config.sock"; +ok(! -e $socketpath, "$socketpath does not exist yet"); + +my $config = <<'EOT'; +# i3 config file (v4) +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +nop foo \ +continued + +set $var normal title +for_window [title="$vartest"] border none +EOT + +$config .= "ipc-socket $socketpath"; + +my $pid = launch_with_config($config, dont_add_socket_path => 1, dont_create_temp_dir => 1); +get_socket_path(0); +my $i3 = i3(get_socket_path()); +$i3->connect->recv; + +my $cv = AE::cv; +my $timer = AE::timer 0.5, 0, sub { $cv->send(0); }; + +my $last_config = $i3->get_config()->recv; +chomp($last_config->{config}); +is($last_config->{config}, $config, + 'received config is not equal to written config'); + +exit_gracefully($pid); + +done_testing; diff --git a/testcases/t/269-focus-stack-above.t b/testcases/t/269-focus-stack-above.t new file mode 100644 index 00000000..a902d968 --- /dev/null +++ b/testcases/t/269-focus-stack-above.t @@ -0,0 +1,55 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies a ConfigureWindow request with stack-mode=Above is translated into +# focusing the target window by i3. +# Ticket: #2708 +# Bug still in: 4.13-207-gafdf6792 +use i3test; +use X11::XCB qw(CONFIG_WINDOW_STACK_MODE STACK_MODE_ABOVE); + +my $ws = fresh_workspace; +my $left_window = open_window; +my $right_window = open_window; + +is($x->input_focus, $right_window->id, 'right window has focus'); +my $old_focus = get_focused($ws); + +$x->configure_window($left_window->id, CONFIG_WINDOW_STACK_MODE, (STACK_MODE_ABOVE)); +$x->flush; + +sync_with_i3; + +is($x->input_focus, $left_window->id, 'left window has focus'); +isnt(get_focused($ws), $old_focus, 'right window is no longer focused'); + +################################################################################ +# Verify the ConfigureWindow request is only applied when on the active +# workspace. +################################################################################ + +$ws = fresh_workspace; +my $new_window = open_window; + +is($x->input_focus, $new_window->id, 'new window has focus'); +$x->configure_window($left_window->id, CONFIG_WINDOW_STACK_MODE, (STACK_MODE_ABOVE)); +$x->flush; + +sync_with_i3; + +is($x->input_focus, $new_window->id, 'new window still has focus'); + +done_testing; diff --git a/testcases/t/533-randr15.t b/testcases/t/533-randr15.t new file mode 100644 index 00000000..08fa88cc --- /dev/null +++ b/testcases/t/533-randr15.t @@ -0,0 +1,108 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# TODO: Description of this file. +# Ticket: #999 +# Bug still in: 4.13-12-g2ff3d9d +use File::Temp qw(tempfile); +use i3test i3_autostart => 0; + +my $config = < 1); + +# Prepare a RRGetMonitors reply, see A.2.4 in +# https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt +my $reply = pack('cxSLLLLx[LLL]', + 1, # reply + 0, # sequence (will be filled in by inject_randr15) + # 56 = length($reply) + length($monitor1) + # 32 = minimum X11 reply length + (56-32) / 4, # length in words + 0, # timestamp TODO + 1, # nmonitors + 0); # noutputs + +# Manually intern _NET_CURRENT_DESKTOP as $x->atom will not create atoms if +# they are not yet interned. +my $atom_cookie = $x->intern_atom(0, length("DP3"), "DP3"); +my $DP3 = $x->intern_atom_reply($atom_cookie->{sequence})->{atom}; + +# MONITORINFO is defined in A.1.1 in +# https://cgit.freedesktop.org/xorg/proto/randrproto/tree/randrproto.txt +my $monitor1 = pack('LccSssSSLL', + $DP3, # name (ATOM) + 1, # primary + 1, # automatic + 0, # ncrtcs + 0, # x + 0, # y + 3840, # width in pixels + 2160, # height in pixels + 520, # width in millimeters + 290); # height in millimeters + +print $outfh $reply; +print $outfh $monitor1; + +close($outfh); + +my $pid = launch_with_config($config, inject_randr15 => $outname); + +my $tree = i3->get_tree->recv; +my @outputs = map { $_->{name} } @{$tree->{nodes}}; +is_deeply(\@outputs, [ '__i3', 'DP3' ], 'outputs are __i3 and DP3'); + +my ($dp3) = grep { $_->{name} eq 'DP3' } @{$tree->{nodes}}; +is_deeply($dp3->{rect}, { + width => 3840, + height => 2160, + x => 0, + y => 0, + }, 'Output DP3 at 3840x2160+0+0'); + +exit_gracefully($pid); + +################################################################################ +# Verify that adding monitors with RandR 1.5 results in i3 outputs. +################################################################################ + +# When inject_randr15 is defined but false, fake-xinerama will be turned off, +# but inject_randr15 will not actually be used. +my $pid = launch_with_config($config, inject_randr15 => ''); + +$tree = i3->get_tree->recv; +@outputs = map { $_->{name} } @{$tree->{nodes}}; +is_deeply(\@outputs, [ '__i3', 'default' ], 'outputs are __i3 and default'); + +SKIP: { + skip 'xrandr --setmonitor failed (xrandr too old?)', 1 unless + system(q|xrandr --setmonitor up2414q 3840/527x2160/296+1280+0 none|) == 0; + + sync_with_i3; + + $tree = i3->get_tree->recv; + @outputs = map { $_->{name} } @{$tree->{nodes}}; + is_deeply(\@outputs, [ '__i3', 'default', 'up2414q' ], 'outputs are __i3, default and up2414q'); +} + +exit_gracefully($pid); + +done_testing; diff --git a/testcases/t/534-dont-warp.t b/testcases/t/534-dont-warp.t new file mode 100644 index 00000000..8f84f9ae --- /dev/null +++ b/testcases/t/534-dont-warp.t @@ -0,0 +1,61 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verifies i3 doesn’t warp when a new floating window is opened under the cursor +# over an unfocused workspace. +# Ticket: #2681 +# Bug still in: 4.13-210-g80c23afa +use i3test i3_autostart => 0; + +# Ensure the pointer is at (0, 0) so that we really start on the first +# (the left) workspace. +$x->root->warp_pointer(0, 0); + +my $config = <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; diff --git a/travis/check-formatting.sh b/travis/check-formatting.sh index ead25157..ff406bea 100755 --- a/travis/check-formatting.sh +++ b/travis/check-formatting.sh @@ -3,4 +3,4 @@ set -e set -x -clang-format-3.5 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false) +clang-format-3.8 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false) diff --git a/travis/travis-base-386.Dockerfile b/travis/travis-base-386.Dockerfile index 9f2b8129..ddb3874e 100644 --- a/travis/travis-base-386.Dockerfile +++ b/travis/travis-base-386.Dockerfile @@ -13,12 +13,12 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. # Install mk-build-deps (for installing the i3 build dependencies), -# clang and clang-format-3.5 (for checking formatting and building with clang), +# clang and clang-format-3.8 (for checking formatting and building with clang), # lintian (for checking spelling errors), RUN linux32 apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ dpkg-dev devscripts git equivs \ - clang clang-format-3.5 \ + clang clang-format-3.8 \ lintian && \ rm -rf /var/lib/apt/lists/* diff --git a/travis/travis-base-ubuntu-386.Dockerfile b/travis/travis-base-ubuntu-386.Dockerfile index 65f123c8..1014407a 100644 --- a/travis/travis-base-ubuntu-386.Dockerfile +++ b/travis/travis-base-ubuntu-386.Dockerfile @@ -13,12 +13,12 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. # Install mk-build-deps (for installing the i3 build dependencies), -# clang and clang-format-3.5 (for checking formatting and building with clang), +# clang and clang-format-3.8 (for checking formatting and building with clang), # lintian (for checking spelling errors), RUN linux32 apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ dpkg-dev devscripts git equivs \ - clang clang-format-3.5 \ + clang clang-format-3.8 \ lintian && \ rm -rf /var/lib/apt/lists/* diff --git a/travis/travis-base-ubuntu.Dockerfile b/travis/travis-base-ubuntu.Dockerfile index ab474691..0b4ec206 100644 --- a/travis/travis-base-ubuntu.Dockerfile +++ b/travis/travis-base-ubuntu.Dockerfile @@ -13,13 +13,13 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. # Install mk-build-deps (for installing the i3 build dependencies), -# clang and clang-format-3.5 (for checking formatting and building with clang), +# clang and clang-format-3.8 (for checking formatting and building with clang), # lintian (for checking spelling errors), # test suite dependencies (for running tests) RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ dpkg-dev devscripts git equivs \ - clang clang-format-3.5 \ + clang clang-format-3.8 \ lintian && \ rm -rf /var/lib/apt/lists/* diff --git a/travis/travis-base.Dockerfile b/travis/travis-base.Dockerfile index 85fa2752..5704d8e4 100644 --- a/travis/travis-base.Dockerfile +++ b/travis/travis-base.Dockerfile @@ -11,15 +11,15 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. # Install mk-build-deps (for installing the i3 build dependencies), -# clang and clang-format-3.5 (for checking formatting and building with clang), +# clang and clang-format-3.8 (for checking formatting and building with clang), # lintian (for checking spelling errors), # test suite dependencies (for running tests) RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ dpkg-dev devscripts git equivs \ - clang clang-format-3.5 \ + clang clang-format-3.8 \ lintian \ - libanyevent-perl libanyevent-i3-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libinline-c-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libipc-run-perl libxcb-xtest0-dev libx11-xcb-perl libanyevent-i3-perl && \ + libmodule-install-perl libanyevent-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libinline-c-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libipc-run-perl libxcb-xtest0-dev libx11-xcb-perl libjson-xs-perl && \ rm -rf /var/lib/apt/lists/* # Install i3 build dependencies.