Merge branch 'next' into master

This commit is contained in:
Michael Stapelberg 2017-09-04 07:53:39 +02:00
commit 7af2faebd8
130 changed files with 5962 additions and 2198 deletions

View File

@ -8,3 +8,4 @@ IndentWidth: 4
PointerBindsToType: false
ColumnLimit: 0
SpaceBeforeParens: ControlStatements
SortIncludes: false

View File

@ -14,6 +14,9 @@ Note that bug reports and feature requests for related projects should be filed
having access to the source code is too time-consuming. Additionally,
experience has shown that often, the software in question is responsible for
the issue. Please raise an issue with the software in question, not i3.
5. Please note that i3 does not support compositors (e.g. compton). If you
encountered the issue you are about to report while using a compositor,
please try reproducing it without a compositor.
## Pull requests

30
.github/GOVERNANCE.md vendored Normal file
View File

@ -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 GitHubs “Member” badge.
* BDFL: a single individual who makes decisions when consensus cannot be
reached. i3s 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).

View File

@ -1,4 +1,4 @@
sudo: required
sudo: false
dist: trusty
services:
- docker

73
AnyEvent-I3/Changes Normal file
View File

@ -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 its ->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

22
AnyEvent-I3/MANIFEST Normal file
View File

@ -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

11
AnyEvent-I3/MANIFEST.SKIP Normal file
View File

@ -0,0 +1,11 @@
^\.git/
\.bak$
blib/
^Makefile$
^Makefile.old$
Build
Build.bat
^pm_to_blib
\.tar\.gz$
^pod2htm(.*).tmp$
^AnyEvent-I3-

17
AnyEvent-I3/Makefile.PL Normal file
View File

@ -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;

40
AnyEvent-I3/README Normal file
View File

@ -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.

View File

@ -0,0 +1,586 @@
package AnyEvent::I3;
# vim:ts=4:sw=4:expandtab
use strict;
use warnings;
use JSON::XS;
use AnyEvent::Handle;
use AnyEvent::Socket;
use AnyEvent;
use Encode;
use Scalar::Util qw(tainted);
=head1 NAME
AnyEvent::I3 - communicate with the i3 window manager
=cut
our $VERSION = '0.18';
=head1 VERSION
Version 0.18
=head1 SYNOPSIS
This module connects to the i3 window manager using the UNIX socket based
IPC interface it provides (if enabled in the configuration file). You can
then subscribe to events or send messages and receive their replies.
use AnyEvent::I3 qw(:all);
my $i3 = i3();
$i3->connect->recv or die "Error connecting";
say "Connected to i3";
my $workspaces = $i3->message(TYPE_GET_WORKSPACES)->recv;
say "Currently, you use " . @{$workspaces} . " workspaces";
...or, using the sugar methods:
use AnyEvent::I3;
my $workspaces = i3->get_workspaces->recv;
say "Currently, you use " . @{$workspaces} . " workspaces";
A somewhat more involved example which dumps the i3 layout tree whenever there
is a workspace event:
use Data::Dumper;
use AnyEvent;
use AnyEvent::I3;
my $i3 = i3();
$i3->connect->recv or die "Error connecting to i3";
$i3->subscribe({
workspace => sub {
$i3->get_tree->cb(sub {
my ($tree) = @_;
say "tree: " . Dumper($tree);
});
}
})->recv->{success} or die "Error subscribing to events";
AE::cv->recv
=head1 EXPORT
=head2 $i3 = i3([ $path ]);
Creates a new C<AnyEvent::I3> object and returns it.
C<path> is an optional path of the UNIX socket to connect to. It is strongly
advised to NOT specify this unless you're absolutely sure you need it.
C<AnyEvent::I3> will automatically figure it out by querying the running i3
instance on the current DISPLAY which is almost always what you want.
=head1 SUBROUTINES/METHODS
=cut
use Exporter qw(import);
use base 'Exporter';
our @EXPORT = qw(i3);
use constant TYPE_COMMAND => 0;
use constant TYPE_GET_WORKSPACES => 1;
use constant TYPE_SUBSCRIBE => 2;
use constant TYPE_GET_OUTPUTS => 3;
use constant TYPE_GET_TREE => 4;
use constant TYPE_GET_MARKS => 5;
use constant TYPE_GET_BAR_CONFIG => 6;
use constant TYPE_GET_VERSION => 7;
use constant TYPE_GET_BINDING_MODES => 8;
use constant TYPE_GET_CONFIG => 9;
our %EXPORT_TAGS = ( 'all' => [
qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS
TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION
TYPE_GET_BINDING_MODES TYPE_GET_CONFIG)
] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
my $magic = "i3-ipc";
# TODO: auto-generate this from the header file? (i3/ipc.h)
my $event_mask = (1 << 31);
my %events = (
workspace => ($event_mask | 0),
output => ($event_mask | 1),
mode => ($event_mask | 2),
window => ($event_mask | 3),
barconfig_update => ($event_mask | 4),
binding => ($event_mask | 5),
shutdown => ($event_mask | 6),
_error => 0xFFFFFFFF,
);
sub i3 {
AnyEvent::I3->new(@_)
}
# Calls i3, even when running in taint mode.
sub _call_i3 {
my ($args) = @_;
my $path_tainted = tainted($ENV{PATH});
# This effectively circumvents taint mode checking for $ENV{PATH}. We
# do this because users might specify PATH explicitly to call i3 in a
# custom location (think ~/.bin/).
(local $ENV{PATH}) = ($ENV{PATH} =~ /(.*)/);
# In taint mode, we also need to remove all relative directories from
# PATH (like . or ../bin). We only do this in taint mode and warn the
# user, since this might break a real-world use case for some people.
if ($path_tainted) {
my @dirs = split /:/, $ENV{PATH};
my @filtered = grep !/^\./, @dirs;
if (scalar @dirs != scalar @filtered) {
$ENV{PATH} = join ':', @filtered;
warn qq|Removed relative directories from PATH because you | .
qq|are running Perl with taint mode enabled. Remove -T | .
qq|to be able to use relative directories in PATH. | .
qq|New PATH is "$ENV{PATH}"|;
}
}
# Otherwise the qx() operator wont work:
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
chomp(my $result = qx(i3 $args));
# Circumventing taint mode again: the socket can be anywhere on the
# system and thats okay.
if ($result =~ /^([^\0]+)$/) {
return $1;
}
warn "Calling i3 $args failed. Is DISPLAY set and is i3 in your PATH?";
return undef;
}
=head2 $i3 = AnyEvent::I3->new([ $path ])
Creates a new C<AnyEvent::I3> object and returns it.
C<path> is an optional path of the UNIX socket to connect to. It is strongly
advised to NOT specify this unless you're absolutely sure you need it.
C<AnyEvent::I3> will automatically figure it out by querying the running i3
instance on the current DISPLAY which is almost always what you want.
=cut
sub new {
my ($class, $path) = @_;
$path = _call_i3('--get-socketpath') unless $path;
# This is the old default path (v3.*). This fallback line can be removed in
# a year from now. -- Michael, 2012-07-09
$path ||= '~/.i3/ipc.sock';
# Check if we need to resolve ~
if ($path =~ /~/) {
# We use getpwuid() instead of $ENV{HOME} because the latter is tainted
# and thus produces warnings when running tests with perl -T
my $home = (getpwuid($<))[7];
die "Could not get home directory" unless $home and -d $home;
$path =~ s/~/$home/g;
}
bless { path => $path } => $class;
}
=head2 $i3->connect
Establishes the connection to i3. Returns an C<AnyEvent::CondVar> which will
be triggered with a boolean (true if the connection was established) as soon as
the connection has been established.
if ($i3->connect->recv) {
say "Connected to i3";
}
=cut
sub connect {
my ($self) = @_;
my $cv = AnyEvent->condvar;
tcp_connect "unix/", $self->{path}, sub {
my ($fh) = @_;
return $cv->send(0) unless $fh;
$self->{ipchdl} = AnyEvent::Handle->new(
fh => $fh,
on_read => sub { my ($hdl) = @_; $self->_data_available($hdl) },
on_error => sub {
my ($hdl, $fatal, $msg) = @_;
delete $self->{ipchdl};
$hdl->destroy;
my $cb = $self->{callbacks};
# Trigger all one-time callbacks with undef
for my $type (keys %{$cb}) {
next if ($type & $event_mask) == $event_mask;
$cb->{$type}->();
delete $cb->{$type};
}
# Trigger _error callback, if set
my $type = $events{_error};
return unless defined($cb->{$type});
$cb->{$type}->($msg);
}
);
$cv->send(1)
};
$cv
}
sub _data_available {
my ($self, $hdl) = @_;
$hdl->unshift_read(
chunk => length($magic) + 4 + 4,
sub {
my $header = $_[1];
# Unpack message length and read the payload
my ($len, $type) = unpack("LL", substr($header, length($magic)));
$hdl->unshift_read(
chunk => $len,
sub { $self->_handle_i3_message($type, $_[1]) }
);
}
);
}
sub _handle_i3_message {
my ($self, $type, $payload) = @_;
return unless defined($self->{callbacks}->{$type});
my $cb = $self->{callbacks}->{$type};
$cb->(decode_json $payload);
return if ($type & $event_mask) == $event_mask;
# If this was a one-time callback, we delete it
# (when connection is lost, all one-time callbacks get triggered)
delete $self->{callbacks}->{$type};
}
=head2 $i3->subscribe(\%callbacks)
Subscribes to the given event types. This function awaits a hashref with the
key being the name of the event and the value being a callback.
my %callbacks = (
workspace => sub { say "Workspaces changed" }
);
if ($i3->subscribe(\%callbacks)->recv->{success}) {
say "Successfully subscribed";
}
The special callback with name C<_error> is called when the connection to i3
is killed (because of a crash, exit or restart of i3 most likely). You can
use it to print an appropriate message and exit cleanly or to try to reconnect.
my %callbacks = (
_error => sub {
my ($msg) = @_;
say "I am sorry. I am so sorry: $msg";
exit 1;
}
);
$i3->subscribe(\%callbacks)->recv;
=cut
sub subscribe {
my ($self, $callbacks) = @_;
# Register callbacks for each message type
for my $key (keys %{$callbacks}) {
my $type = $events{$key};
$self->{callbacks}->{$type} = $callbacks->{$key};
}
$self->message(TYPE_SUBSCRIBE, [ keys %{$callbacks} ])
}
=head2 $i3->message($type, $content)
Sends a message of the specified C<type> to i3, possibly containing the data
structure C<content> (or C<content>, encoded as utf8, if C<content> is a
scalar), if specified.
my $reply = $i3->message(TYPE_COMMAND, "reload")->recv;
if ($reply->{success}) {
say "Configuration successfully reloaded";
}
=cut
sub message {
my ($self, $type, $content) = @_;
die "No message type specified" unless defined($type);
die "No connection to i3" unless defined($self->{ipchdl});
my $payload = "";
if ($content) {
if (not ref($content)) {
# Convert from Perls 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 dont 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 doesnt support GET_VERSION yet.";
my $version = _call_i3('--version');
$version =~ s/^i3 version //;
my $patch = 0;
my ($major, $minor) = ($version =~ /^([0-9]+)\.([0-9]+)/);
if ($version =~ /^[0-9]+\.[0-9]+\.([0-9]+)/) {
$patch = $1;
}
# Strip everything from the © sign on.
$version =~ s/ ©.*$//g;
$cv->send({
major => int($major),
minor => int($minor),
patch => int($patch),
human_readable => $version,
});
undef $timeout;
},
);
$version_cv->cb(sub {
undef $timeout;
$cv->send($version_cv->recv);
});
return $cv;
}
=head2 get_config
Gets the raw last read config from i3. Requires i3 >= 4.14
=cut
sub get_config {
my ($self) = @_;
$self->_ensure_connection;
$self->message(TYPE_GET_CONFIG);
}
=head2 command($content)
Makes i3 execute the given command
my $reply = i3->command("reload")->recv;
die "command failed" unless $reply->{success};
=cut
sub command {
my ($self, $content) = @_;
$self->_ensure_connection;
$self->message(TYPE_COMMAND, $content)
}
=head1 AUTHOR
Michael Stapelberg, C<< <michael at i3wm.org> >>
=head1 BUGS
Please report any bugs or feature requests to C<bug-anyevent-i3 at
rt.cpan.org>, or through the web interface at
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=AnyEvent-I3>. I will be
notified, and then you'll automatically be notified of progress on your bug as
I make changes.
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc AnyEvent::I3
You can also look for information at:
=over 2
=item * RT: CPAN's request tracker
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-I3>
=item * The i3 window manager website
L<http://i3wm.org>
=back
=head1 ACKNOWLEDGEMENTS
=head1 LICENSE AND COPYRIGHT
Copyright 2010-2012 Michael Stapelberg.
This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.
=cut
1; # End of AnyEvent::I3

10
AnyEvent-I3/t/00-load.t Normal file
View File

@ -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" );

View File

@ -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" );

29
AnyEvent-I3/t/02-sugar.t Normal file
View File

@ -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" );

View File

@ -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');
}

13
AnyEvent-I3/t/manifest.t Normal file
View File

@ -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();

View File

@ -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();

12
AnyEvent-I3/t/pod.t Normal file
View File

@ -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();

View File

@ -1 +1 @@
4.13-non-git
4.14-non-git

View File

@ -45,11 +45,17 @@ dist_xsessions_DATA = \
noinst_LIBRARIES = libi3.a
check_PROGRAMS = test.commands_parser test.config_parser
check_PROGRAMS = \
test.commands_parser \
test.config_parser \
test.inject_randr15
check_SCRIPTS = \
testcases/complete-run.pl
check_DATA = \
anyevent-i3.stamp
clean-check:
rm -rf testsuite-* latest i3-cfg-for-* _Inline
clean-local: clean-check
@ -94,7 +100,7 @@ EXTRA_DIST = \
I3_VERSION \
LICENSE \
PACKAGE-MAINTAINER \
RELEASE-NOTES-4.13 \
RELEASE-NOTES-4.14 \
generate-command-parser.pl \
parser-specs/commands.spec \
parser-specs/config.spec \
@ -212,6 +218,7 @@ asciidoc_MANS =
endif
AM_CPPFLAGS = \
-DSYSCONFDIR="\"$(sysconfdir)\"" \
-I$(top_builddir)/parser \
-I$(top_srcdir)/include \
@AX_EXTEND_SRCDIR_CPPFLAGS@
@ -401,6 +408,19 @@ i3_config_wizard_i3_config_wizard_SOURCES = \
i3-config-wizard/main.c \
i3-config-wizard/xcb.h
test_inject_randr15_CPPFLAGS = \
$(AM_CPPFLAGS)
test_inject_randr15_CFLAGS = \
$(AM_CFLAGS) \
$(i3_CFLAGS)
test_inject_randr15_SOURCES = \
testcases/inject_randr1.5.c
test_inject_randr15_LDADD = \
$(i3_LDADD)
test_commands_parser_CPPFLAGS = \
$(AM_CPPFLAGS) \
-DTEST_PARSER
@ -457,7 +477,6 @@ i3_SOURCES = \
include/config_parser.h \
include/con.h \
include/data.h \
include/debug.h \
include/display_version.h \
include/ewmh.h \
include/fake_outputs.h \
@ -502,7 +521,6 @@ i3_SOURCES = \
src/config.c \
src/config_directives.c \
src/config_parser.c \
src/debug.c \
src/display_version.c \
src/ewmh.c \
src/fake_outputs.c \
@ -558,6 +576,15 @@ i3-config-parser.stamp: parser/$(dirstamp) generate-command-parser.pl parser-spe
$(AM_V_at) mv GENERATED_config_* $(top_builddir)/parser
$(AM_V_at) touch $@
################################################################################
# AnyEvent-I3 build process
################################################################################
anyevent-i3.stamp: AnyEvent-I3/lib/AnyEvent/I3.pm
$(AM_V_BUILD) (cd $(top_srcdir)/AnyEvent-I3 && perl Makefile.PL && make)
$(AM_V_at) touch $@
CLEANFILES = \
i3-command-parser.stamp \
i3-config-parser.stamp
i3-config-parser.stamp \
anyevent-i3.stamp

View File

@ -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 servers DPI (based on screen
resolution and physical size). Looking at Xft.dpi is more consistent with
other software, more likely to be correct (because its 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 dont 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)
• dont 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)
• dont 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)
• dont 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

94
RELEASE-NOTES-4.14 Normal file
View File

@ -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 i3s 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

View File

@ -2,7 +2,7 @@
# Run autoreconf -fi to generate a configure script from this file.
AC_PREREQ([2.69])
AC_INIT([i3], [4.13], [https://github.com/i3/i3/issues])
AC_INIT([i3], [4.14], [https://github.com/i3/i3/issues])
# For AX_EXTEND_SRCDIR
AX_ENABLE_BUILDDIR
AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2])
@ -15,6 +15,12 @@ AC_CONFIG_SRCDIR([libi3/ipc_recv_message.c])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIR([m4])
dnl Verify macros defined in m4/ such as AX_SANITIZERS are not present in the
dnl output, i.e. are replaced as expected. This line results in a better error
dnl message when using aclocal < 1.13 (which does not understand
dnl AC_CONFIG_MACRO_DIR) without passing the -I m4 parameter.
m4_pattern_forbid([AX_SANITIZERS])
# Verify we are using GNU make because we use '%'-style pattern rules in
# Makefile.am, which are a GNU make extension. Pull requests to replace
# '%'-style pattern rules with a more portable alternative are welcome.
@ -25,8 +31,8 @@ AX_EXTEND_SRCDIR
AS_IF([test -d ${srcdir}/.git],
[
VERSION="$(git describe --tags --abbrev=0)"
I3_VERSION="$(git describe --tags --always) ($(git log --pretty=format:%cd --date=short -n1), branch \\\"$(git describe --tags --always --all | sed s:heads/::)\\\")"
VERSION="$(git -C ${srcdir} describe --tags --abbrev=0)"
I3_VERSION="$(git -C ${srcdir} describe --tags --always) ($(git -C ${srcdir} log --pretty=format:%cd --date=short -n1), branch \\\"$(git -C ${srcdir} describe --tags --always --all | sed s:heads/::)\\\")"
# Mirrors what libi3/is_debug_build.c does:
is_release=$(test $(echo "${I3_VERSION}" | cut -d '(' -f 1 | wc -m) -lt 10 && echo yes || echo no)
],
@ -53,8 +59,6 @@ AX_CHECK_ENABLE_DEBUG([yes], , [UNUSED_NDEBUG], [$is_release])
AC_PROG_CC_C99
AC_DEFINE_UNQUOTED(SYSCONFDIR, "`eval echo $sysconfdir`", [Location of system configuration files])
# For strnlen() and vasprintf().
AC_USE_SYSTEM_EXTENSIONS
@ -79,6 +83,8 @@ AC_SEARCH_LIBS([ev_run], [ev], , [AC_MSG_FAILURE([cannot find the required ev_ru
AC_SEARCH_LIBS([shm_open], [rt])
AC_SEARCH_LIBS([iconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])])
AX_PTHREAD
dnl Each prefix corresponds to a source tarball which users might have
@ -146,8 +152,9 @@ else
print_BUILD_MANS=no
fi
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`
if test -n "$git_dir"; then
srcdir=`dirname "$git_dir"`
exclude_dir=`pwd | sed "s,^$srcdir,,g"`
if ! grep -q "^$exclude_dir" "$git_dir/info/exclude"; then

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
i3-wm (4.13.1-1) unstable; urgency=medium
* New upstream release.
-- Michael Stapelberg <stapelberg@debian.org> Tue, 08 Nov 2016 21:31:13 +0100
i3-wm (4.13-1) unstable; urgency=medium
* New upstream release.

View File

@ -119,9 +119,6 @@ src/config.c::
Contains all functions handling the configuration file (calling the parser
src/config_parser.c) with the correct path, switching key bindings mode).
src/debug.c::
Contains debugging functions to print unhandled X events.
src/ewmh.c::
Functions to get/set certain EWMH properties easily.
@ -993,6 +990,47 @@ New features are only found in the “next” branch. Therefore, if you are work
on a new feature, use the “next” branch. If you are working on a bugfix, use the
“next” branch, too, but make sure your code also works on “master”.
=== How to build?
You can build i3 like you build any other software package which uses autotools.
Heres 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 shouldnt 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 dont 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 dont forget our

View File

@ -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

View File

@ -259,3 +259,27 @@ container:
]
}
--------------------------------------------------------------------------------
=== Placeholders using window title matches don't swallow the window
If you use the +title+ attribute to match a window and find that it doesn't
work or only works sometimes, the reason might be that the application sets the
title only after making the window visible. This will be especially true for
programs running inside terminal emulators, e.g., +urxvt -e irssi+ when
matching on +title: "irssi"+.
One way to deal with this is to not rely on the title, but instead use, e.g.,
the +instance+ attribute and running the program to set this window instance to
that value:
--------------------------------------------------------------------------------
# Run irssi via
# urxvt -name "irssi-container" -e irssi
"swallows": [
{
"class": "URxvt",
"instance": "irssi-container"
}
]
--------------------------------------------------------------------------------

View File

@ -82,6 +82,8 @@ The tests additionally require +Xephyr(1)+ to run a nested X server. Install
$ cd ~/i3/testcases
$ sudo apt-get install cpanminus
$ sudo cpanm .
$ cd ~/i3/AnyEvent-I3
$ sudo cpanm .
--------------------------------------------------------------------------------
If you dont want to use cpanminus for some reason, the same works with cpan:
@ -90,6 +92,8 @@ If you dont 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 dont 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

View File

@ -297,6 +297,15 @@ keyboard layout. To start the wizard, use the command +i3-config-wizard+.
Please note that you must not have +~/.i3/config+, otherwise the wizard will
exit.
Since i3 4.0, a new configuration format is used. i3 will try to automatically
detect the format version of a config file based on a few different keywords,
but if you want to make sure that your config is read with the new format,
include the following line in your config file:
---------------------
# i3 config file (v4)
---------------------
=== Comments
It is possible and recommended to use comments in your configuration file to
@ -412,9 +421,9 @@ button in the scope of the clicked container (see <<command_criteria>>). You
can configure mouse bindings in a similar way to key bindings.
*Syntax*:
-------------------------------------------------------------------------------
bindsym [--release] [--border] [--whole-window] [<Modifiers>+]button<n> command
-------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------
bindsym [--release] [--border] [--whole-window] [--exclude-titlebar] [<Modifiers>+]button<n> command
----------------------------------------------------------------------------------------------------
By default, the binding will only run when you click on the titlebar of the
window. If the +--release+ flag is given, it will run when the mouse button
@ -424,6 +433,9 @@ If the +--whole-window+ flag is given, the binding will also run when any part
of the window is clicked, with the exception of the border. To have a bind run
when the border is clicked, specify the +--border+ flag.
If the +--exclude-titlebar+ flag is given, the titlebar will not be considered
for the keybinding.
*Examples*:
--------------------------------
# The middle button over a titlebar kills the window
@ -479,7 +491,7 @@ mode <name>
*Example*:
------------------------------------------------------------------------
# Press $mod+o followed by either f, t, Esc or Return to launch firefox,
# Press $mod+o followed by either f, t, Escape or Return to launch firefox,
# thunderbird or return to the default mode, respectively.
set $mode_launcher Launch: [f]irefox [t]hunderbird
bindsym $mod+o mode "$mode_launcher"
@ -488,7 +500,7 @@ mode "$mode_launcher" {
bindsym f exec firefox
bindsym t exec thunderbird
bindsym Esc mode "default"
bindsym Escape mode "default"
bindsym Return mode "default"
}
------------------------------------------------------------------------
@ -946,12 +958,12 @@ the next section.
=== Focus follows mouse
By default, window focus follows your mouse movements. However, if you have a
setup where your mouse usually is in your way (like a touchpad on your laptop
which you do not want to disable completely), you might want to disable 'focus
follows mouse' and control focus only by using your keyboard. The mouse will
still be useful inside the currently active window (for example to click on
links in your browser window).
By default, window focus follows your mouse movements as the mouse crosses
window borders. However, if you have a setup where your mouse usually is in your
way (like a touchpad on your laptop which you do not want to disable
completely), you might want to disable 'focus follows mouse' and control focus
only by using your keyboard. The mouse will still be useful inside the
currently active window (for example to click on links in your browser window).
*Syntax*:
--------------------------
@ -1127,9 +1139,9 @@ none::
[[show_marks]]
=== Drawing marks on window decoration
If activated, marks on windows are drawn in their window decoration. However,
any mark starting with an underscore in its name (+_+) will not be drawn even if
this option is activated.
If activated, marks (see <<vim_like_marks>>) on windows are drawn in their window
decoration. However, any mark starting with an underscore in its name (+_+) will
not be drawn even if this option is activated.
The default for this option is +yes+.
@ -1378,7 +1390,7 @@ directive multiple times.
*Syntax*:
---------------
output <output>
output primary|<output>
---------------
*Example*:
@ -1400,7 +1412,19 @@ bar {
statusline #ffffff
}
}
# show bar on the primary monitor and on HDMI2
bar {
output primary
output HDMI2
status_command i3status
}
-------------------------------
Note that you might not have a primary output configured yet. To do so, run:
-------------------------
xrandr --output <output> --primary
-------------------------
=== Tray output
@ -1802,7 +1826,8 @@ The +toggle+ option will toggle the orientation of the split container if it
contains a single window. Otherwise it makes the current window a split
container with opposite orientation compared to the parent container.
Use +layout toggle split+ to change the layout of any split container from
splitv to splith or vice-versa.
splitv to splith or vice-versa. You can also define a custom sequence of layouts
to cycle through with +layout toggle+, see <<manipulating_layout>>.
*Syntax*:
--------------------------------
@ -1822,6 +1847,11 @@ Use +layout toggle split+, +layout stacking+, +layout tabbed+, +layout splitv+
or +layout splith+ to change the current container layout to splith/splitv,
stacking, tabbed layout, splitv or splith, respectively.
Specify up to four layouts after +layout toggle+ to cycle through them. Every
time the command is executed, the layout specified after the currently active
one will be applied. If the currently active layout is not in the list, the
first layout in the list will be activated.
To make the current window (!) fullscreen, use +fullscreen enable+ (or
+fullscreen enable global+ for the global mode), to leave either fullscreen
mode use +fullscreen disable+, and to toggle between these two states use
@ -1834,6 +1864,7 @@ enable+ respectively +floating disable+ (or +floating toggle+):
--------------------------------------------
layout default|tabbed|stacking|splitv|splith
layout toggle [split|all]
layout toggle [split|tabbed|stacking|splitv|splith] [split|tabbed|stacking|splitv|splith]…
--------------------------------------------
*Examples*:
@ -1848,6 +1879,15 @@ bindsym $mod+x layout toggle
# Toggle between stacking/tabbed/splith/splitv:
bindsym $mod+x layout toggle all
# Toggle between stacking/tabbed/splith:
bindsym $mod+x layout toggle stacking tabbed splith
# Toggle between splitv/tabbed
bindsym $mod+x layout toggle splitv tabbed
# Toggle between last split layout/tabbed/stacking
bindsym $mod+x layout toggle split tabbed stacking
# Toggle fullscreen
bindsym $mod+f fullscreen toggle
@ -1882,7 +1922,7 @@ output::
----------------------------------------------
focus left|right|down|up
focus parent|child|floating|tiling|mode_toggle
focus output left|right|up|down|<output>
focus output left|right|up|down|primary|<output>
----------------------------------------------
*Examples*:
@ -1904,8 +1944,17 @@ bindsym $mod+x focus output right
# Focus the big output
bindsym $mod+x focus output HDMI-2
# Focus the primary output
bindsym $mod+x focus output primary
-------------------------------------------------
-------------------------------
Note that you might not have a primary output configured yet. To do so, run:
-------------------------
xrandr --output <output> --primary
-------------------------
=== Moving containers
Use the +move+ command to move a container.
@ -1921,7 +1970,8 @@ move <left|right|down|up> [<px> px]
# Moves the container either to a specific location
# or to the center of the screen. If 'absolute' is
# used, it is moved to the center of all outputs.
move [absolute] position [[<px> px] [<px> px]|center]
move [absolute] position <pos_x> [px] <pos_y> [px]
move [absolute] position center
# Moves the container to the current position of the
# mouse cursor. Only affects floating containers.
@ -1947,6 +1997,39 @@ bindsym $mod+c move absolute position center
bindsym $mod+m move position mouse
-------------------------------------------------------
=== Swapping containers
Two containers can be swapped (i.e., move to each other's position) by using
the +swap+ command. They will assume the position and geometry of the container
they are swapped with.
The first container to participate in the swapping can be selected through the
normal command criteria process with the focused window being the usual
fallback if no criteria are specified. The second container can be selected
using one of the following methods:
+id+:: The X11 window ID of a client window.
+con_id+:: The i3 container ID of a container.
+mark+:: A container with the specified mark, see <<vim_like_marks>>.
Note that swapping does not work with all containers. Most notably, swapping
floating containers or containers that have a parent-child relationship to one
another does not work.
*Syntax*:
----------------------------------------
swap container with id|con_id|mark <arg>
----------------------------------------
*Examples*:
-----------------------------------------------------------------
# Swaps the focused container with the container marked »swapee«.
swap container with mark swapee
# Swaps container marked »A« and »B«
[con_mark="^A$"] swap container with mark B
-----------------------------------------------------------------
=== Sticky floating windows
If you want a window to stick to the glass, i.e., have it stay on screen even
@ -2089,6 +2172,23 @@ i3-msg 'rename workspace to "2: mail"'
bindsym $mod+r exec i3-input -F 'rename workspace to "%s"' -P 'New name: '
--------------------------------------------------------------------------
If you want to rename workspaces on demand while keeping the navigation stable,
you can use a setup like this:
*Example*:
-------------------------
bindsym $mod+1 workspace number "1: www"
bindsym $mod+2 workspace number "2: mail"
...
-------------------------
If a workspace does not exist, the command +workspace number "1: mail"+ will
create workspace "1: mail".
If a workspace with number 1 does already exist, the command will switch to this
workspace and ignore the text part. So even when the workspace has been renamed
to "1: web", the above command will still switch to it.
=== Moving workspaces to a different screen
See <<move_to_outputs>> for how to move a container/workspace to a different
@ -2104,8 +2204,8 @@ To move a container to another RandR output (addressed by names like +LVDS1+ or
*Syntax*:
------------------------------------------------------------
move container to output left|right|down|up|current|<output>
move workspace to output left|right|down|up|current|<output>
move container to output left|right|down|up|current|primary|<output>
move workspace to output left|right|down|up|current|primary|<output>
------------------------------------------------------------
*Examples*:
@ -2116,8 +2216,17 @@ bindsym $mod+x move workspace to output right
# Put this window on the presentation output.
bindsym $mod+x move container to output VGA1
# Put this window on the primary output.
bindsym $mod+x move container to output primary
--------------------------------------------------------
-------------------------------
Note that you might not have a primary output configured yet. To do so, run:
-------------------------
xrandr --output <output> --primary
-------------------------
=== Moving containers/windows to marks
To move a container to another container with a specific mark (see <<vim_like_marks>>),

View File

@ -65,7 +65,7 @@ for my $line (@raw_lines) {
my $current_state;
for my $line (@lines) {
if (my ($state) = ($line =~ /^state ([A-Z_]+):$/)) {
if (my ($state) = ($line =~ /^state ([A-Z0-9_]+):$/)) {
#say "got a new state: $state";
$current_state = $state;
} else {
@ -155,12 +155,20 @@ for my $state (@keys) {
# to generate a format string. The format uses %d for <number>s,
# literal numbers or state IDs and %s for NULL, <string>s and literal
# strings.
# remove the function name temporarily, so that the following
# replacements only apply to the arguments.
my ($funcname) = ($fmt =~ /^(.+)\(/);
$fmt =~ s/^$funcname//;
$fmt =~ s/$_/%d/g for @keys;
$fmt =~ s/\$([a-z_]+)/%s/g;
$fmt =~ s/\&([a-z_]+)/%ld/g;
$fmt =~ s/"([a-z0-9_]+)"/%s/g;
$fmt =~ s/(?:-?|\b)[0-9]+\b/%d/g;
$fmt = $funcname . $fmt;
say $callfh " case $call_id:";
say $callfh " result->next_state = $next_state;";
say $callfh '#ifndef TEST_PARSER';

View File

@ -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, "<Enter>");
txt(4, 6, "<Enter>", green, black);
/* red */
set_font_colors(pixmap_gc, draw_util_hex_to_color("#FF0000"), draw_util_hex_to_color("#000000"));
txt(logical_px(31), 8, "<ESC>");
txt(5, 8, "<ESC>", red, black);
}
if (current_step == STEP_GENERATE) {
set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000"));
txt(logical_px(10), 2, "Please choose either:");
txt(logical_px(85), 4, "Win as default modifier");
txt(logical_px(85), 5, "Alt as default modifier");
txt(logical_px(10), 7, "Afterwards, press");
txt(logical_px(85), 9, "to write the config");
txt(logical_px(85), 10, "to abort");
txt(2, 2, "Please choose either:", white, black);
txt(13, 4, "Win as default modifier", white, black);
txt(13, 5, "Alt as default modifier", white, black);
txt(2, 7, "Afterwards, press", white, black);
txt(13, 9, "to write the config", white, black);
txt(13, 10, "to abort", white, black);
/* the not-selected modifier */
if (modifier == MOD_Mod4)
txt(logical_px(31), 5, "<Alt>");
txt(5, 5, "<Alt>", white, black);
else
txt(logical_px(31), 4, "<Win>");
txt(5, 4, "<Win>", white, black);
/* the selected modifier */
set_font(&bold_font);
set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000"));
if (modifier == MOD_Mod4)
txt(logical_px(10), 4, "-> <Win>");
txt(2, 4, "-> <Win>", white, black);
else
txt(logical_px(10), 5, "-> <Alt>");
txt(2, 5, "-> <Alt>", white, black);
/* green */
set_font(&font);
set_font_colors(pixmap_gc, draw_util_hex_to_color("#00FF00"), draw_util_hex_to_color("#000000"));
txt(logical_px(25), 9, "<Enter>");
txt(4, 9, "<Enter>", green, black);
/* red */
set_font_colors(pixmap_gc, draw_util_hex_to_color("#FF0000"), draw_util_hex_to_color("#000000"));
txt(logical_px(31), 10, "<ESC>");
txt(5, 10, "<ESC>", red, black);
}
/* Copy the contents of the pixmap to the real window */
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, logical_px(500), logical_px(500));
xcb_flush(conn);
return 1;
@ -625,8 +621,7 @@ static void handle_button_press(xcb_button_press_event_t *event) {
if (current_step != STEP_GENERATE)
return;
if (event->event_x < logical_px(32) ||
event->event_x > (logical_px(32) + char_width * 5))
if (event->event_x < col_x(5) || event->event_x > col_x(10))
return;
if (event->event_y >= row_y(4) && event->event_y <= (row_y(4) + font.height)) {
@ -854,6 +849,7 @@ int main(int argc, char *argv[]) {
xcb_numlock_mask = get_mod_mask_for(XCB_NUM_LOCK, symbols, modmap_reply);
init_dpi();
font = load_font(pattern, true);
bold_font = load_font(patternbold, true);
@ -868,7 +864,7 @@ int main(int argc, char *argv[]) {
XCB_COPY_FROM_PARENT,
win, /* the window id */
root, /* parent == root */
logical_px(490), logical_px(297), logical_px(300), window_height(), /* dimensions */
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 */
@ -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:
if (((xcb_expose_event_t *)event)->count == 0) {
handle_expose();
}
break;
}
free(event);
}
/* Dismiss drawable surface */
draw_util_surface_free(conn, &surface);
return 0;
}

View File

@ -279,17 +279,43 @@ 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);
}
# Dont add “geany” if “Geany” is already present.
my @keys = map { lc } keys %choices;
next if (scalar grep { $_ eq lc(basename($command)) } @keys) > 0;
if (!(scalar grep { $_ eq lc(basename($command)) } @keys) > 0) {
$choices{basename($command)} = $app;
}
next;
}
if ((scalar grep { $_ eq 'filename' } @entry_types) > 0) {
my $filename = basename($app, '.desktop');

View File

@ -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-inputs 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:
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;
}

View File

@ -119,6 +119,43 @@ static yajl_callbacks reply_callbacks = {
.yajl_end_map = reply_end_map_cb,
};
/*******************************************************************************
* Config reply callbacks
*******************************************************************************/
static char *config_last_key = NULL;
static int config_string_cb(void *params, const unsigned char *val, size_t len) {
char *str = scalloc(len + 1, 1);
strncpy(str, (const char *)val, len);
if (strcmp(config_last_key, "config") == 0) {
fprintf(stdout, "%s", str);
}
free(str);
return 1;
}
static int config_start_map_cb(void *params) {
return 1;
}
static int config_end_map_cb(void *params) {
return 1;
}
static int config_map_key_cb(void *params, const unsigned char *keyVal, size_t keyLen) {
config_last_key = scalloc(keyLen + 1, 1);
strncpy(config_last_key, (const char *)keyVal, keyLen);
return 1;
}
static yajl_callbacks config_callbacks = {
.yajl_string = config_string_cb,
.yajl_start_map = config_start_map_cb,
.yajl_map_key = config_map_key_cb,
.yajl_end_map = config_end_map_cb,
};
int main(int argc, char *argv[]) {
#if defined(__OpenBSD__)
if (pledge("stdio rpath unix", NULL) == -1)
@ -150,25 +187,27 @@ int main(int argc, char *argv[]) {
free(socket_path);
socket_path = sstrdup(optarg);
} else if (o == 't') {
if (strcasecmp(optarg, "command") == 0)
if (strcasecmp(optarg, "command") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
else if (strcasecmp(optarg, "get_workspaces") == 0)
} else if (strcasecmp(optarg, "get_workspaces") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES;
else if (strcasecmp(optarg, "get_outputs") == 0)
} else if (strcasecmp(optarg, "get_outputs") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS;
else if (strcasecmp(optarg, "get_tree") == 0)
} else if (strcasecmp(optarg, "get_tree") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_TREE;
else if (strcasecmp(optarg, "get_marks") == 0)
} else if (strcasecmp(optarg, "get_marks") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS;
else if (strcasecmp(optarg, "get_bar_config") == 0)
} else if (strcasecmp(optarg, "get_bar_config") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG;
else if (strcasecmp(optarg, "get_binding_modes") == 0)
} else if (strcasecmp(optarg, "get_binding_modes") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_BINDING_MODES;
else if (strcasecmp(optarg, "get_version") == 0)
} else if (strcasecmp(optarg, "get_version") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION;
else {
} else if (strcasecmp(optarg, "get_config") == 0) {
message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG;
} else {
printf("Unknown message type\n");
printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version\n");
printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config\n");
exit(EXIT_FAILURE);
}
} else if (o == 'q') {
@ -180,6 +219,8 @@ int main(int argc, char *argv[]) {
printf("i3-msg " I3_VERSION "\n");
printf("i3-msg [-s <socket>] [-t <type>] <message>\n");
return 0;
} else if (o == '?') {
exit(EXIT_FAILURE);
}
}
@ -239,7 +280,7 @@ int main(int argc, char *argv[]) {
errx(EXIT_FAILURE, "IPC: Received reply of type %d but expected %d", reply_type, message_type);
/* For the reply of commands, have a look if that command was successful.
* If not, nicely format the error message. */
if (reply_type == I3_IPC_MESSAGE_TYPE_COMMAND) {
if (reply_type == I3_IPC_REPLY_TYPE_COMMAND) {
yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
yajl_free(handle);
@ -254,8 +295,24 @@ int main(int argc, char *argv[]) {
/* NB: We still fall-through and print the reply, because even if one
* command failed, that doesnt 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);

View File

@ -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:
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;
}

View File

@ -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

View File

@ -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

View File

@ -74,10 +74,12 @@ struct status_block {
char *name;
char *instance;
TAILQ_ENTRY(status_block) blocks;
TAILQ_ENTRY(status_block)
blocks;
};
TAILQ_HEAD(statusline_head, status_block) statusline_head;
TAILQ_HEAD(statusline_head, status_block)
statusline_head;
#include "child.h"
#include "ipc.h"

View File

@ -28,18 +28,23 @@ typedef struct binding_t {
int input_code;
char *command;
TAILQ_ENTRY(binding_t) bindings;
TAILQ_ENTRY(binding_t)
bindings;
} binding_t;
typedef struct tray_output_t {
char *output;
TAILQ_ENTRY(tray_output_t) tray_outputs;
TAILQ_ENTRY(tray_output_t)
tray_outputs;
} tray_output_t;
typedef struct config_t {
int modifier;
TAILQ_HEAD(bindings_head, binding_t) bindings;
TAILQ_HEAD(bindings_head, binding_t)
bindings;
position_t position;
int verbose;
struct xcb_color_strings_t colors;
@ -50,7 +55,10 @@ typedef struct config_t {
char *command;
char *fontname;
i3String *separator_symbol;
TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs;
TAILQ_HEAD(tray_outputs_head, tray_output_t)
tray_outputs;
int tray_padding;
int num_outputs;
char **outputs;

View File

@ -67,5 +67,6 @@ struct i3_output {
struct ws_head* workspaces; /* The workspaces on this output */
struct tc_head* trayclients; /* The tray clients on this output */
SLIST_ENTRY(i3_output) slist; /* Pointer for the SLIST-Macro */
SLIST_ENTRY(i3_output)
slist; /* Pointer for the SLIST-Macro */
};

View File

@ -18,5 +18,6 @@ struct trayclient {
bool mapped; /* Whether this window is mapped */
int xe_version; /* The XEMBED version supported by the client */
TAILQ_ENTRY(trayclient) tailq; /* Pointer for the TAILQ-Macro */
TAILQ_ENTRY(trayclient)
tailq; /* Pointer for the TAILQ-Macro */
};

View File

@ -40,5 +40,6 @@ struct i3_ws {
rect rect; /* The rect of the ws (not used (yet)) */
struct i3_output *output; /* The current output of the ws */
TAILQ_ENTRY(i3_ws) tailq; /* Pointer for the TAILQ-Macro */
TAILQ_ENTRY(i3_ws)
tailq; /* Pointer for the TAILQ-Macro */
};

View File

@ -112,13 +112,13 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha
struct status_block *err_block = scalloc(1, sizeof(struct status_block));
err_block->full_text = i3string_from_utf8("Error: ");
err_block->name = sstrdup("error");
err_block->color = sstrdup("red");
err_block->color = sstrdup("#ff0000");
err_block->no_separator = true;
struct status_block *message_block = scalloc(1, sizeof(struct status_block));
message_block->full_text = i3string_from_utf8(message);
message_block->name = sstrdup("error_message");
message_block->color = sstrdup("red");
message_block->color = sstrdup("#ff0000");
message_block->no_separator = true;
TAILQ_INSERT_HEAD(&statusline_head, err_block, blocks);

View File

@ -185,6 +185,7 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
if (!strcmp(cur_key, "status_command")) {
DLOG("command = %.*s\n", len, val);
FREE(config.command);
sasprintf(&config.command, "%.*s", len, val);
return 1;
}

View File

@ -63,16 +63,26 @@ void got_subscribe_reply(char *reply) {
*
*/
void got_output_reply(char *reply) {
DLOG("Clearing old output configuration...\n");
i3_output *o_walk;
SLIST_FOREACH(o_walk, outputs, slist) {
destroy_window(o_walk);
}
FREE_SLIST(outputs, i3_output);
DLOG("Parsing outputs JSON...\n");
parse_outputs_json(reply);
DLOG("Reconfiguring windows...\n");
reconfig_windows(false);
i3_output *o_walk;
SLIST_FOREACH(o_walk, outputs, slist) {
kick_tray_clients(o_walk);
}
if (!config.disable_ws) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
}
draw_bars(false);
}
@ -103,7 +113,6 @@ void got_bar_config(char *reply) {
init_colors(&(config.colors));
start_child(config.command);
FREE(config.command);
}
/* Data structure to easily call the reply handlers later */
@ -168,6 +177,7 @@ void got_bar_config_update(char *event) {
/* update the configuration with the received settings */
DLOG("Received bar config update \"%s\"\n", event);
char *old_command = sstrdup(config.command);
bar_display_mode_t old_mode = config.hide_on_modifier;
parse_config_json(event);
if (old_mode != config.hide_on_modifier) {
@ -178,6 +188,13 @@ void got_bar_config_update(char *event) {
init_xcb_late(config.fontname);
init_colors(&(config.colors));
/* restart status command process */
if (strcmp(old_command, config.command) != 0) {
kill_child();
start_child(config.command);
}
free(old_command);
draw_bars(false);
}

View File

@ -149,6 +149,8 @@ int main(int argc, char **argv) {
socket_path = expand_path(i3_default_sock_path);
}
init_dpi();
init_outputs();
if (init_connection(socket_path)) {
/* Request the bar configuration. When it arrives, we fill the config array. */

View File

@ -189,12 +189,13 @@ 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;
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",
params->outputs_walk->name);

View File

@ -181,7 +181,7 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b
uint32_t center_x = x - sep_offset;
if (config.separator_symbol == NULL) {
/* Draw a classic one pixel, vertical separator. */
draw_util_rectangle(xcb_connection, &output->statusline_buffer, sep_fg,
draw_util_rectangle(&output->statusline_buffer, sep_fg,
center_x,
logical_px(sep_voff_px),
logical_px(1),
@ -250,7 +250,7 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_color
struct status_block *block;
color_t bar_color = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg);
draw_util_clear_surface(xcb_connection, &output->statusline_buffer, bar_color);
draw_util_clear_surface(&output->statusline_buffer, bar_color);
/* Use unsigned integer wraparound to clip off the left side.
* For example, if clip_left is 75, then x will start at the very large
@ -301,13 +301,13 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_color
}
/* Draw the border. */
draw_util_rectangle(xcb_connection, &output->statusline_buffer, border_color,
draw_util_rectangle(&output->statusline_buffer, border_color,
x, logical_px(1),
full_render_width,
bar_height - logical_px(2));
/* Draw the background. */
draw_util_rectangle(xcb_connection, &output->statusline_buffer, bg_color,
draw_util_rectangle(&output->statusline_buffer, bg_color,
x + border_width,
logical_px(1) + border_width,
full_render_width - 2 * border_width,
@ -531,7 +531,8 @@ void handle_button(xcb_button_press_event_t *event) {
return;
}
switch (event->detail) {
case 4:
case XCB_BUTTON_SCROLL_UP:
case XCB_BUTTON_SCROLL_LEFT:
/* Mouse wheel up. We select the previous ws, if any.
* If there is no more workspace, dont even send the workspace
* command, otherwise (with workspace auto_back_and_forth) wed 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, dont even send the workspace
* command, otherwise (with workspace auto_back_and_forth) wed 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:
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 xcbs
* 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);
}

View File

@ -31,3 +31,4 @@ xmacro(_NET_DESKTOP_NAMES)
xmacro(_NET_DESKTOP_VIEWPORT)
xmacro(_NET_ACTIVE_WINDOW)
xmacro(_NET_CLOSE_WINDOW)
xmacro(_NET_MOVERESIZE_WINDOW)

View File

@ -27,7 +27,8 @@ extern const char *DEFAULT_BINDING_MODE;
*/
Binding *configure_binding(const char *bindtype, const char *modifiers, const char *input_code,
const char *release, const char *border, const char *whole_window,
const char *command, const char *mode, bool pango_markup);
const char *exclude_titlebar, const char *command, const char *mode,
bool pango_markup);
/**
* Grab the bound keys (tell X to send us keypress events for those keycodes)

View File

@ -290,6 +290,12 @@ void cmd_move_scratchpad(I3_CMD);
*/
void cmd_scratchpad_show(I3_CMD);
/**
* Implementation of 'swap [container] [with] id|con_id|mark <arg>'.
*
*/
void cmd_swap(I3_CMD, const char *mode, const char *arg);
/**
* Implementation of 'title_format <format>'
*

View File

@ -139,6 +139,12 @@ Con *con_inside_floating(Con *con);
*/
bool con_inside_focused(Con *con);
/**
* Checks if the container has the given parent as an actual parent.
*
*/
bool con_has_parent(Con *con, Con *parent);
/**
* Returns the container with the given client window ID or NULL if no such
* container exists.
@ -461,3 +467,9 @@ void con_force_split_parents_redraw(Con *con);
*
*/
i3String *con_parse_title_format(Con *con);
/**
* Swaps the two containers.
*
*/
bool con_swap(Con *first, Con *second);

View File

@ -51,6 +51,7 @@ CFGFUN(focus_follows_mouse, const char *value);
CFGFUN(mouse_warping, const char *value);
CFGFUN(force_focus_wrapping, const char *value);
CFGFUN(force_xinerama, const char *value);
CFGFUN(disable_randr15, const char *value);
CFGFUN(fake_outputs, const char *outputs);
CFGFUN(force_display_urgency_hint, const long duration_ms);
CFGFUN(focus_on_window_activation, const char *mode);
@ -66,10 +67,10 @@ CFGFUN(color_single, const char *colorclass, const char *color);
CFGFUN(floating_modifier, const char *modifiers);
CFGFUN(new_window, const char *windowtype, const char *border, const long width);
CFGFUN(workspace, const char *workspace, const char *output);
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command);
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command);
CFGFUN(enter_mode, const char *pango_markup, const char *mode);
CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command);
CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command);
CFGFUN(bar_font, const char *font);
CFGFUN(bar_separator_symbol, const char *separator);

View File

@ -21,6 +21,7 @@
typedef struct Config Config;
typedef struct Barconfig Barconfig;
extern char *current_configpath;
extern char *current_config;
extern Config config;
extern SLIST_HEAD(modes_head, Mode) modes;
extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;
@ -68,7 +69,8 @@ struct Variable {
char *value;
char *next_match;
SLIST_ENTRY(Variable) variables;
SLIST_ENTRY(Variable)
variables;
};
/**
@ -82,7 +84,8 @@ struct Mode {
bool pango_markup;
struct bindings_head *bindings;
SLIST_ENTRY(Mode) modes;
SLIST_ENTRY(Mode)
modes;
};
/**
@ -154,6 +157,9 @@ struct Config {
* is fetched once and never updated. */
bool force_xinerama;
/** Dont 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;
};
/**

View File

@ -199,7 +199,8 @@ struct Workspace_Assignment {
char *name;
char *output;
TAILQ_ENTRY(Workspace_Assignment) ws_assignments;
TAILQ_ENTRY(Workspace_Assignment)
ws_assignments;
};
struct Ignore_Event {
@ -207,7 +208,8 @@ struct Ignore_Event {
int response_type;
time_t added;
SLIST_ENTRY(Ignore_Event) ignore_events;
SLIST_ENTRY(Ignore_Event)
ignore_events;
};
/**
@ -226,7 +228,8 @@ struct Startup_Sequence {
* completed) */
time_t delete_at;
TAILQ_ENTRY(Startup_Sequence) sequences;
TAILQ_ENTRY(Startup_Sequence)
sequences;
};
/**
@ -252,7 +255,9 @@ struct regex {
struct Binding_Keycode {
xcb_keycode_t keycode;
i3_event_state_mask_t modifiers;
TAILQ_ENTRY(Binding_Keycode) keycodes;
TAILQ_ENTRY(Binding_Keycode)
keycodes;
};
/******************************************************************************
@ -293,6 +298,10 @@ struct Binding {
* title bar (default). */
bool whole_window;
/** If this is true for a mouse binding, the binding should only be
* executed if the button press was not on the titlebar. */
bool exclude_titlebar;
/** Keycode to bind */
uint32_t keycode;
@ -309,12 +318,14 @@ struct Binding {
/** Only in use if symbol != NULL. Contains keycodes which generate the
* specified symbol. Useful for unbinding and checking which binding was
* used when a key press event comes in. */
TAILQ_HEAD(keycodes_head, Binding_Keycode) keycodes_head;
TAILQ_HEAD(keycodes_head, Binding_Keycode)
keycodes_head;
/** Command, like in command mode */
char *command;
TAILQ_ENTRY(Binding) bindings;
TAILQ_ENTRY(Binding)
bindings;
};
/**
@ -330,8 +341,12 @@ struct Autostart {
/** no_startup_id flag for start_application(). Determines whether a
* startup notification context/ID should be created. */
bool no_startup_id;
TAILQ_ENTRY(Autostart) autostarts;
TAILQ_ENTRY(Autostart) autostarts_always;
TAILQ_ENTRY(Autostart)
autostarts;
TAILQ_ENTRY(Autostart)
autostarts_always;
};
/**
@ -364,7 +379,8 @@ struct xoutput {
/** x, y, width, height */
Rect rect;
TAILQ_ENTRY(xoutput) outputs;
TAILQ_ENTRY(xoutput)
outputs;
};
/**
@ -438,6 +454,10 @@ struct Window {
int width_increment;
int height_increment;
/* Minimum size specified for the window. */
int min_width;
int min_height;
/* aspect ratio from WM_NORMAL_HINTS (MPlayer uses this for example) */
double aspect_ratio;
};
@ -493,7 +513,8 @@ struct Match {
M_ASSIGN_WS,
M_BELOW } insert_where;
TAILQ_ENTRY(Match) matches;
TAILQ_ENTRY(Match)
matches;
/* Whether this match was generated when restarting i3 inplace.
* Leads to not setting focus when managing a new window, because the old
@ -537,7 +558,8 @@ struct Assignment {
char *workspace;
} dest;
TAILQ_ENTRY(Assignment) assignments;
TAILQ_ENTRY(Assignment)
assignments;
};
/** Fullscreen modes. Used by Con.fullscreen_mode. */
@ -548,7 +570,8 @@ typedef enum { CF_NONE = 0,
struct mark_t {
char *name;
TAILQ_ENTRY(mark_t) marks;
TAILQ_ENTRY(mark_t)
marks;
};
/**
@ -612,7 +635,8 @@ struct Con {
char *sticky_group;
/* user-definable marks to jump to this container later */
TAILQ_HEAD(marks_head, mark_t) marks_head;
TAILQ_HEAD(marks_head, mark_t)
marks_head;
/* cached to decide whether a redraw is needed */
bool mark_changed;
@ -631,12 +655,17 @@ struct Con {
struct deco_render_params *deco_render_params;
/* Only workspace-containers can have floating clients */
TAILQ_HEAD(floating_head, Con) floating_head;
TAILQ_HEAD(floating_head, Con)
floating_head;
TAILQ_HEAD(nodes_head, Con) nodes_head;
TAILQ_HEAD(focus_head, Con) focus_head;
TAILQ_HEAD(nodes_head, Con)
nodes_head;
TAILQ_HEAD(swallow_head, Match) swallow_head;
TAILQ_HEAD(focus_head, Con)
focus_head;
TAILQ_HEAD(swallow_head, Match)
swallow_head;
fullscreen_mode_t fullscreen_mode;
@ -674,10 +703,17 @@ struct Con {
FLOATING_USER_ON = 3
} floating;
TAILQ_ENTRY(Con) nodes;
TAILQ_ENTRY(Con) focused;
TAILQ_ENTRY(Con) all_cons;
TAILQ_ENTRY(Con) floating_windows;
TAILQ_ENTRY(Con)
nodes;
TAILQ_ENTRY(Con)
focused;
TAILQ_ENTRY(Con)
all_cons;
TAILQ_ENTRY(Con)
floating_windows;
/** callbacks */
void (*on_remove_child)(Con *);

View File

@ -1,15 +0,0 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* debug.c: Debugging functions, especially FormatEvent, which prints unhandled
* events. This code is from xcb-util.
*
*/
#pragma once
#include <config.h>
int handle_event(void *ignored, xcb_connection_t *c, xcb_generic_event_t *e);

View File

@ -54,6 +54,9 @@ typedef struct i3_ipc_header {
/** Request a list of configured binding modes. */
#define I3_IPC_MESSAGE_TYPE_GET_BINDING_MODES 8
/** Request the raw last loaded i3 config. */
#define I3_IPC_MESSAGE_TYPE_GET_CONFIG 9
/*
* Messages from i3 to clients
*
@ -67,6 +70,7 @@ typedef struct i3_ipc_header {
#define I3_IPC_REPLY_TYPE_BAR_CONFIG 6
#define I3_IPC_REPLY_TYPE_VERSION 7
#define I3_IPC_REPLY_TYPE_BINDING_MODES 8
#define I3_IPC_REPLY_TYPE_CONFIG 9
/*
* Events from i3 to clients. Events have the first bit set high.
@ -91,3 +95,6 @@ typedef struct i3_ipc_header {
/** The binding event will be triggered when bindings run */
#define I3_IPC_EVENT_BINDING (I3_IPC_EVENT_MASK | 5)
/** The shutdown event will be triggered when the ipc shuts down */
#define I3_IPC_EVENT_SHUTDOWN (I3_IPC_EVENT_MASK | 6)

View File

@ -31,7 +31,8 @@ typedef struct ipc_client {
int num_events;
char **events;
TAILQ_ENTRY(ipc_client) clients;
TAILQ_ENTRY(ipc_client)
clients;
} ipc_client;
/*
@ -76,11 +77,18 @@ int ipc_create_socket(const char *filename);
void ipc_send_event(const char *event, uint32_t message_type, const char *payload);
/**
* Calls shutdown() on each socket and closes it. This function to be called
* when exiting or restarting only!
* Calls to ipc_shutdown() should provide a reason for the shutdown.
*/
typedef enum {
SHUTDOWN_REASON_RESTART,
SHUTDOWN_REASON_EXIT
} shutdown_reason_t;
/**
* Calls shutdown() on each socket and closes it.
*
*/
void ipc_shutdown(void);
void ipc_shutdown(shutdown_reason_t reason);
void dump_node(yajl_gen gen, Con *con, bool inplace_restart);

View File

@ -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);

View File

@ -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 { \

View File

@ -29,7 +29,7 @@ typedef enum {
* XRandR information to setup workspaces for each screen.
*
*/
void randr_init(int *event_base);
void randr_init(int *event_base, const bool disable_randr15);
/**
* Initializes a CT_OUTPUT Con (searches existing ones from inplace restart
@ -75,10 +75,11 @@ void randr_disable_output(Output *output);
Output *get_first_output(void);
/**
* Returns the output with the given name if it is active (!) or NULL.
* Returns the output with the given name or NULL.
* If require_active is true, only active outputs are considered.
*
*/
Output *get_output_by_name(const char *name);
Output *get_output_by_name(const char *name, const bool require_active);
/**
* Returns the active (!) output which contains the coordinates x, y or NULL

View File

@ -3,10 +3,6 @@
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009 Jan-Erik Rediger
*
* sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers
* to restart inplace).
*
*/
#pragma once
@ -14,7 +10,8 @@
#include <config.h>
/**
* Setup signal handlers to safely handle SIGSEGV and SIGFPE
* Configured a signal handler to gracefully handle crashes and allow the user
* to generate a backtrace and rescue their session.
*
*/
void setup_signal_handler(void);

View File

@ -69,6 +69,14 @@ Rect rect_sub(Rect a, Rect b);
*/
__attribute__((pure)) bool name_is_digits(const char *name);
/**
* Set 'out' to the layout_t value for the given layout. The function
* returns true on success or false if the passed string is not a valid
* layout name.
*
*/
bool layout_from_name(const char *layout_str, layout_t *out);
/**
* Parses the workspace name as a number. Returns -1 if the workspace should be
* interpreted as a "named workspace".
@ -149,3 +157,10 @@ void start_nagbar(pid_t *nagbar_pid, char *argv[]);
*
*/
void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it);
/**
* Converts a string into a long using strtol().
* This is a convenience wrapper checking the parsing result. It returns true
* if the number could be parsed.
*/
bool parse_long(const char *str, long *out, int base);

View File

@ -69,22 +69,6 @@ extern unsigned int xcb_numlock_mask;
xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t depth, xcb_visualid_t visual,
uint16_t window_class, enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values);
/**
* Draws a line from x,y to to_x,to_y using the given color
*
*/
void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable,
xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x,
uint32_t y, uint32_t to_x, uint32_t to_y);
/**
* Draws a rectangle from x,y with width,height using the given color
*
*/
void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable,
xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x,
uint32_t y, uint32_t width, uint32_t height);
/**
* Generates a configure_notify_event with absolute coordinates (relative to
* the X root window, not to the clients 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.

View File

@ -53,6 +53,10 @@ void init_dpi(void) {
DLOG("Found Xft.dpi = %ld.\n", dpi);
init_dpi_end:
if (resource != NULL) {
free(resource);
}
if (database != NULL) {
xcb_xrm_database_free(database);
}
@ -64,6 +68,14 @@ init_dpi_end:
}
}
/*
* This function returns the value of the DPI setting.
*
*/
long get_dpi_value(void) {
return dpi;
}
/*
* Convert a logical amount of pixels (e.g. 2 pixels on a standard 96 DPI
* screen) to a corresponding amount of physical pixels on a standard or retina

View File

@ -19,7 +19,7 @@
xcb_visualtype_t *visual_type;
/* Forward declarations */
static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color);
static void draw_util_set_source_color(surface_t *surface, color_t color);
#define RETURN_UNLESS_SURFACE_INITIALIZED(surface) \
do { \
@ -84,6 +84,11 @@ void draw_util_surface_set_size(surface_t *surface, int width, int height) {
*
*/
color_t draw_util_hex_to_color(const char *color) {
if (strlen(color) < 6 || color[0] != '#') {
ELOG("Could not parse color: %s\n", color);
return draw_util_hex_to_color("#A9A9A9");
}
char alpha[2];
if (strlen(color) == strlen("#rrggbbaa")) {
alpha[0] = color[7];
@ -110,7 +115,7 @@ color_t draw_util_hex_to_color(const char *color) {
* Set the given color as the source color on the surface.
*
*/
static void draw_util_set_source_color(xcb_connection_t *conn, surface_t *surface, color_t color) {
static void draw_util_set_source_color(surface_t *surface, color_t color) {
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
cairo_set_source_rgba(surface->cr, color.red, color.green, color.blue, color.alpha);
@ -141,7 +146,7 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_
* surface as well as restoring the cairo state.
*
*/
void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t color, double x, double y, double w, double h) {
void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h) {
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
cairo_save(surface->cr);
@ -150,7 +155,7 @@ void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t col
* onto the surface rather than blending it. This is a bit more efficient and
* allows better color control for the user when using opacity. */
cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE);
draw_util_set_source_color(conn, surface, color);
draw_util_set_source_color(surface, color);
cairo_rectangle(surface->cr, x, y, w, h);
cairo_fill(surface->cr);
@ -166,7 +171,7 @@ void draw_util_rectangle(xcb_connection_t *conn, surface_t *surface, color_t col
* Clears a surface with the given color.
*
*/
void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t color) {
void draw_util_clear_surface(surface_t *surface, color_t color) {
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
cairo_save(surface->cr);
@ -175,7 +180,7 @@ void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t
* onto the surface rather than blending it. This is a bit more efficient and
* allows better color control for the user when using opacity. */
cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE);
draw_util_set_source_color(conn, surface, color);
draw_util_set_source_color(surface, color);
cairo_paint(surface->cr);
@ -190,7 +195,7 @@ void draw_util_clear_surface(xcb_connection_t *conn, surface_t *surface, color_t
* Copies a surface onto another surface.
*
*/
void draw_util_copy_surface(xcb_connection_t *conn, surface_t *src, surface_t *dest, double src_x, double src_y,
void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y,
double dest_x, double dest_y, double width, double height) {
RETURN_UNLESS_SURFACE_INITIALIZED(src);
RETURN_UNLESS_SURFACE_INITIALIZED(dest);

View File

@ -24,24 +24,12 @@ static double pango_font_red;
static double pango_font_green;
static double pango_font_blue;
/* Necessary to track whether the dpi changes and trigger a LOG() message,
* which is more easily visible to users. */
static double logged_dpi = 0.0;
static PangoLayout *create_layout_with_dpi(cairo_t *cr) {
PangoLayout *layout;
PangoContext *context;
context = pango_cairo_create_context(cr);
const double dpi = (double)root_screen->height_in_pixels * 25.4 /
(double)root_screen->height_in_millimeters;
if (logged_dpi != dpi) {
logged_dpi = dpi;
LOG("X11 root window dictates %f DPI\n", dpi);
} else {
DLOG("X11 root window dictates %f DPI\n", dpi);
}
pango_cairo_context_set_resolution(context, dpi);
pango_cairo_context_set_resolution(context, get_dpi_value());
layout = pango_layout_new(context);
g_object_unref(context);

View File

@ -71,7 +71,7 @@ char *get_config_path(const char *override_configpath, bool use_system_paths) {
/* 4: check for $XDG_CONFIG_DIRS/i3/config */
if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL)
xdg_config_dirs = "/etc/xdg";
xdg_config_dirs = SYSCONFDIR "/xdg";
char *buf = sstrdup(xdg_config_dirs);
char *tok = strtok(buf, ":");

View File

@ -74,7 +74,7 @@
AC_DEFUN([AX_EXTEND_SRCDIR],
[dnl
AS_CASE([$srcdir],
[.|.*],
[.|.*|/*],
[
# pwd -P is specified in IEEE 1003.1 from 2004
as_dir=`cd "$srcdir" && pwd -P`

View File

@ -29,7 +29,7 @@ It tries to start one of the following (in that order):
* mg
* jed
* gedit
* mc-edit
* mcedit
Please dont complain about the order: If the user has any preference, they will
have $VISUAL or $EDITOR set.

View File

@ -40,6 +40,10 @@ It tries to start one of the following (in that order):
* terminology
* st
* qterminal
* lilyterm
* tilix
* terminix
* konsole
Please dont complain about the order: If the user has any preference, they will
have $TERMINAL set or modified their i3 configuration file.

View File

@ -170,10 +170,10 @@ Exits i3.
When starting, i3 looks for configuration files in the following order:
1. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set)
2. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set)
3. ~/.i3/config
4. /etc/i3/config
1. ~/.i3/config
2. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set)
3. /etc/i3/config
4. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set)
You can specify a custom path using the -c option.

View File

@ -38,6 +38,7 @@ state INITIAL:
'rename' -> RENAME
'nop' -> NOP
'scratchpad' -> SCRATCHPAD
'swap' -> SWAP
'title_format' -> TITLE_FORMAT
'mode' -> MODE
'bar' -> BAR
@ -110,7 +111,7 @@ state LAYOUT:
state LAYOUT_TOGGLE:
end
-> call cmd_layout_toggle($toggle_mode)
toggle_mode = 'split', 'all'
toggle_mode = string
-> call cmd_layout_toggle($toggle_mode)
# append_layout <path>
@ -273,24 +274,28 @@ state RENAME:
-> RENAME_WORKSPACE
state RENAME_WORKSPACE:
old_name = 'to'
'to'
-> RENAME_WORKSPACE_LIKELY_TO
old_name = word
-> RENAME_WORKSPACE_TO
state RENAME_WORKSPACE_LIKELY_TO:
'to '
-> RENAME_WORKSPACE_NEW_NAME
-> 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 <window>
# swap [container] [with] con_id <con_id>
# swap [container] [with] mark <mark>
state SWAP:
'container'
->
'with'
->
mode = 'id', 'con_id', 'mark'
-> SWAP_ARGUMENT
state SWAP_ARGUMENT:
arg = string
-> call cmd_swap($mode, $arg)
state TITLE_FORMAT:
format = string
-> call cmd_title_format($format)

View File

@ -18,6 +18,7 @@ state INITIAL:
error ->
'#' -> 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)

View File

@ -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"

View File

@ -32,9 +32,10 @@ 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 */
mode = scalloc(1, sizeof(struct Mode));
@ -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,25 +241,32 @@ 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
* user pressed. We therefore mark it as B_UPON_KEYRELEASE_IGNORE_MODS
* for later, so that the user can release the modifiers before 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 dont
* 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;
}

View File

@ -178,15 +178,15 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
if (con->parent->type == CT_DOCKAREA)
goto done;
const bool is_left_or_right_click = (event->detail == XCB_BUTTON_INDEX_1 ||
event->detail == XCB_BUTTON_INDEX_3);
const bool is_left_or_right_click = (event->detail == XCB_BUTTON_CLICK_LEFT ||
event->detail == XCB_BUTTON_CLICK_RIGHT);
/* if the user has bound an action to this click, it should override the
* default behavior. */
if (dest == CLICK_DECORATION || dest == CLICK_INSIDE || dest == CLICK_BORDER) {
Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event);
if (bind != NULL && (dest == CLICK_DECORATION ||
if (bind != NULL && ((dest == CLICK_DECORATION && !bind->exclude_titlebar) ||
(dest == CLICK_INSIDE && bind->whole_window) ||
(dest == CLICK_BORDER && bind->border))) {
CommandResult *result = run_binding(bind, con);
@ -228,8 +228,10 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
/* 1: see if the user scrolled on the decoration of a stacked/tabbed con */
if (in_stacked &&
dest == CLICK_DECORATION &&
(event->detail == XCB_BUTTON_INDEX_4 ||
event->detail == XCB_BUTTON_INDEX_5)) {
(event->detail == XCB_BUTTON_SCROLL_UP ||
event->detail == XCB_BUTTON_SCROLL_DOWN ||
event->detail == XCB_BUTTON_SCROLL_LEFT ||
event->detail == XCB_BUTTON_SCROLL_RIGHT)) {
DLOG("Scrolling on a window decoration\n");
orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ);
/* Focus the currently focused container on the same level that the
@ -244,10 +246,12 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
* #557), we first check if scrolling is possible at all. */
bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL);
if (event->detail == XCB_BUTTON_INDEX_4 && scroll_prev_possible)
if ((event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) && scroll_prev_possible) {
tree_next('p', orientation);
else if (event->detail == XCB_BUTTON_INDEX_5 && scroll_next_possible)
} else if ((event->detail == XCB_BUTTON_SCROLL_DOWN || event->detail == XCB_BUTTON_SCROLL_RIGHT) && scroll_next_possible) {
tree_next('n', orientation);
}
goto done;
}
@ -261,7 +265,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
floating_raise_con(floatingcon);
/* 4: floating_modifier plus left mouse button drags */
if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1) {
if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) {
floating_drag_window(floatingcon, event);
return 1;
}
@ -269,7 +273,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
/* 5: resize (floating) if this was a (left or right) click on the
* left/right/bottom border, or a right click on the decoration.
* also try resizing (tiling) if it was a click on the top */
if (mod_pressed && event->detail == XCB_BUTTON_INDEX_3) {
if (mod_pressed && event->detail == XCB_BUTTON_CLICK_RIGHT) {
DLOG("floating resize due to floatingmodifier\n");
floating_resize_window(floatingcon, proportional, event);
return 1;
@ -283,7 +287,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
goto done;
}
if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_INDEX_3) {
if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_CLICK_RIGHT) {
DLOG("floating resize due to decoration right click\n");
floating_resize_window(floatingcon, proportional, event);
return 1;
@ -298,7 +302,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
/* 6: dragging, if this was a click on a decoration (which did not lead
* to a resize) */
if (!in_stacked && dest == CLICK_DECORATION &&
(event->detail == XCB_BUTTON_INDEX_1)) {
(event->detail == XCB_BUTTON_CLICK_LEFT)) {
floating_drag_window(floatingcon, event);
return 1;
}
@ -313,7 +317,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
}
/* 7: floating modifier pressed, initiate a resize */
if (dest == CLICK_INSIDE && mod_pressed && event->detail == XCB_BUTTON_INDEX_3) {
if (dest == CLICK_INSIDE && mod_pressed && event->detail == XCB_BUTTON_CLICK_RIGHT) {
if (floating_mod_on_tiled_client(con, event))
return 1;
}

View File

@ -142,7 +142,9 @@ static Con *maybe_auto_back_and_forth_workspace(Con *workspace) {
*/
typedef struct owindow {
Con *con;
TAILQ_ENTRY(owindow) owindows;
TAILQ_ENTRY(owindow)
owindows;
} owindow;
typedef TAILQ_HEAD(owindows_head, owindow) owindows_head;
@ -891,8 +893,10 @@ void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_a
cmd_output->needs_tree_render = true;
return;
}
if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, workspace->name))
if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, workspace->name)) {
ysuccess(true);
return;
}
workspace_show(workspace);
cmd_output->needs_tree_render = true;
@ -938,8 +942,10 @@ void cmd_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_
}
DLOG("should switch to workspace %s\n", name);
if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, name))
if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, name)) {
ysuccess(true);
return;
}
workspace_show_by_name(name);
cmd_output->needs_tree_render = true;
@ -1483,21 +1489,8 @@ void cmd_move_direction(I3_CMD, const char *direction, long move_px) {
void cmd_layout(I3_CMD, const char *layout_str) {
HANDLE_EMPTY_MATCH;
if (strcmp(layout_str, "stacking") == 0)
layout_str = "stacked";
layout_t layout;
/* default is a special case which will be handled in con_set_layout(). */
if (strcmp(layout_str, "default") == 0)
layout = L_DEFAULT;
else if (strcmp(layout_str, "stacked") == 0)
layout = L_STACKED;
else if (strcmp(layout_str, "tabbed") == 0)
layout = L_TABBED;
else if (strcmp(layout_str, "splitv") == 0)
layout = L_SPLITV;
else if (strcmp(layout_str, "splith") == 0)
layout = L_SPLITH;
else {
if (!layout_from_name(layout_str, &layout)) {
ELOG("Unknown layout \"%s\", this is a mismatch between code and parser spec.\n", layout_str);
return;
}
@ -1556,7 +1549,7 @@ void cmd_exit(I3_CMD) {
#ifdef I3_ASAN_ENABLED
__lsan_do_leak_check();
#endif
ipc_shutdown();
ipc_shutdown(SHUTDOWN_REASON_EXIT);
unlink(config.ipc_socket_path);
xcb_disconnect(conn);
exit(0);
@ -1589,7 +1582,7 @@ void cmd_reload(I3_CMD) {
*/
void cmd_restart(I3_CMD) {
LOG("restarting i3\n");
ipc_shutdown();
ipc_shutdown(SHUTDOWN_REASON_RESTART);
unlink(config.ipc_socket_path);
/* We need to call this manually since atexit handlers dont get called
* when exec()ing */
@ -1819,6 +1812,65 @@ void cmd_scratchpad_show(I3_CMD) {
ysuccess(true);
}
/*
* Implementation of 'swap [container] [with] id|con_id|mark <arg>'.
*
*/
void cmd_swap(I3_CMD, const char *mode, const char *arg) {
HANDLE_EMPTY_MATCH;
owindow *match = TAILQ_FIRST(&owindows);
if (match == NULL) {
DLOG("No match found for swapping.\n");
return;
}
Con *con;
if (strcmp(mode, "id") == 0) {
long target;
if (!parse_long(arg, &target, 0)) {
yerror("Failed to parse %s into a window id.\n", arg);
return;
}
con = con_by_window_id(target);
} else if (strcmp(mode, "con_id") == 0) {
long target;
if (!parse_long(arg, &target, 0)) {
yerror("Failed to parse %s into a container id.\n", arg);
return;
}
con = (Con *)target;
} else if (strcmp(mode, "mark") == 0) {
con = con_by_mark(arg);
} else {
yerror("Unhandled swap mode \"%s\". This is a bug.\n", mode);
return;
}
if (con == NULL) {
yerror("Could not find container for %s = %s\n", mode, arg);
return;
}
if (match == TAILQ_LAST(&owindows, owindows_head)) {
DLOG("More than one container matched the swap command, only using the first one.");
}
if (match->con == NULL) {
DLOG("Match %p has no container.\n", match);
ysuccess(false);
return;
}
DLOG("Swapping %p with %p.\n", match->con, con);
bool result = con_swap(match->con, con);
cmd_output->needs_tree_render = true;
ysuccess(result);
}
/*
* Implementation of 'title_format <format>'
*

304
src/con.c
View File

@ -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,24 +822,27 @@ 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);
}
}
}
}
// 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)
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
child->percent = 1.0 / children;
}
} else if (total != 1.0) {
TAILQ_FOREACH(child, &(con->nodes_head), nodes)
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
child->percent /= total;
}
}
}
/*
* Toggles fullscreen mode for the given container. If there already is a
@ -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 */
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, dont 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) {
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)
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);
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;
}
}
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;
}

View File

@ -13,6 +13,7 @@
#include <xkbcommon/xkbcommon.h>
char *current_configpath = NULL;
char *current_config = NULL;
Config config;
struct modes_head modes;
struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs);

View File

@ -106,8 +106,8 @@ CFGFUN(font, const char *font) {
font_pattern = sstrdup(font);
}
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) {
configure_binding(bindtype, modifiers, key, release, border, whole_window, command, DEFAULT_BINDING_MODE, false);
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, DEFAULT_BINDING_MODE, false);
}
/*******************************************************************************
@ -117,15 +117,23 @@ CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, co
static char *current_mode;
static bool current_mode_pango_markup;
CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *command) {
configure_binding(bindtype, modifiers, key, release, border, whole_window, command, current_mode, current_mode_pango_markup);
CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, current_mode, current_mode_pango_markup);
}
CFGFUN(enter_mode, const char *pango_markup, const char *modename) {
if (strcasecmp(modename, DEFAULT_BINDING_MODE) == 0) {
ELOG("You cannot use the name %s for your mode\n", DEFAULT_BINDING_MODE);
exit(1);
return;
}
struct Mode *mode;
SLIST_FOREACH(mode, &modes, modes) {
if (strcmp(mode->name, modename) == 0) {
ELOG("The binding mode with name \"%s\" is defined at least twice.\n", modename);
}
}
DLOG("\t now in mode %s\n", modename);
FREE(current_mode);
current_mode = sstrdup(modename);
@ -252,6 +260,10 @@ CFGFUN(force_xinerama, const char *value) {
config.force_xinerama = eval_boolstr(value);
}
CFGFUN(disable_randr15, const char *value) {
config.disable_randr15 = eval_boolstr(value);
}
CFGFUN(force_focus_wrapping, const char *value) {
config.force_focus_wrapping = eval_boolstr(value);
}

View File

@ -235,7 +235,7 @@ static void next_state(const cmdp_token *token) {
*
*/
static const char *start_of_line(const char *walk, const char *beginning) {
while (*walk != '\n' && *walk != '\r' && walk >= beginning) {
while (walk >= beginning && *walk != '\n' && *walk != '\r') {
walk--;
}
@ -898,6 +898,13 @@ bool parse_file(const char *f, bool use_nagbar) {
if ((fstr = fdopen(fd, "r")) == NULL)
die("Could not fdopen: %s\n", strerror(errno));
FREE(current_config);
current_config = scalloc(stbuf.st_size + 1, 1);
fread(current_config, 1, stbuf.st_size, fstr);
rewind(fstr);
bool invalid_sets = false;
while (!feof(fstr)) {
if (!continuation)
continuation = buffer;
@ -911,6 +918,7 @@ bool parse_file(const char *f, bool use_nagbar) {
}
/* sscanf implicitly strips whitespace. */
value[0] = '\0';
const bool skip_line = (sscanf(buffer, "%511s %4095[^\n]", key, value) < 1 || strlen(key) < 3);
const bool comment = (key[0] == '#');
value[4095] = '\n';
@ -931,26 +939,28 @@ bool parse_file(const char *f, bool use_nagbar) {
continue;
}
if (strcasecmp(key, "set") == 0) {
if (strcasecmp(key, "set") == 0 && *value != '\0') {
char v_key[512];
char v_value[4096];
char v_value[4096] = {'\0'};
if (sscanf(value, "%511s %4095[^\n]", v_key, v_value) < 1) {
ELOG("Failed to parse variable specification '%s', skipping it.\n", value);
invalid_sets = true;
continue;
}
if (v_key[0] != '$') {
ELOG("Malformed variable assignment, name has to start with $\n");
invalid_sets = true;
continue;
}
upsert_variable(&variables, v_key, v_value);
continue;
} else if (strcasecmp(key, "set_from_resource") == 0) {
char res_name[512];
char res_name[512] = {'\0'};
char v_key[512];
char fallback[4096];
char fallback[4096] = {'\0'};
/* Ensure that this string is terminated. For example, a user might
* want a variable to be empty if the resource can't be found and
@ -962,11 +972,13 @@ bool parse_file(const char *f, bool use_nagbar) {
if (sscanf(value, "%511s %511s %4095[^\n]", v_key, res_name, fallback) < 1) {
ELOG("Failed to parse resource specification '%s', skipping it.\n", value);
invalid_sets = true;
continue;
}
if (v_key[0] != '$') {
ELOG("Malformed variable assignment, name has to start with $\n");
invalid_sets = true;
continue;
}
@ -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;

View File

@ -1,245 +0,0 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* debug.c: Debugging functions, especially FormatEvent, which prints unhandled
* events. This code is from xcb-util.
*
*/
#include <config.h>
#include <stdio.h>
#include <xcb/xcb.h>
#include "log.h"
static const char *labelError[] = {
"Success",
"BadRequest",
"BadValue",
"BadWindow",
"BadPixmap",
"BadAtom",
"BadCursor",
"BadFont",
"BadMatch",
"BadDrawable",
"BadAccess",
"BadAlloc",
"BadColor",
"BadGC",
"BadIDChoice",
"BadName",
"BadLength",
"BadImplementation",
};
static const char *labelRequest[] = {
"no request",
"CreateWindow",
"ChangeWindowAttributes",
"GetWindowAttributes",
"DestroyWindow",
"DestroySubwindows",
"ChangeSaveSet",
"ReparentWindow",
"MapWindow",
"MapSubwindows",
"UnmapWindow",
"UnmapSubwindows",
"ConfigureWindow",
"CirculateWindow",
"GetGeometry",
"QueryTree",
"InternAtom",
"GetAtomName",
"ChangeProperty",
"DeleteProperty",
"GetProperty",
"ListProperties",
"SetSelectionOwner",
"GetSelectionOwner",
"ConvertSelection",
"SendEvent",
"GrabPointer",
"UngrabPointer",
"GrabButton",
"UngrabButton",
"ChangeActivePointerGrab",
"GrabKeyboard",
"UngrabKeyboard",
"GrabKey",
"UngrabKey",
"AllowEvents",
"GrabServer",
"UngrabServer",
"QueryPointer",
"GetMotionEvents",
"TranslateCoords",
"WarpPointer",
"SetInputFocus",
"GetInputFocus",
"QueryKeymap",
"OpenFont",
"CloseFont",
"QueryFont",
"QueryTextExtents",
"ListFonts",
"ListFontsWithInfo",
"SetFontPath",
"GetFontPath",
"CreatePixmap",
"FreePixmap",
"CreateGC",
"ChangeGC",
"CopyGC",
"SetDashes",
"SetClipRectangles",
"FreeGC",
"ClearArea",
"CopyArea",
"CopyPlane",
"PolyPoint",
"PolyLine",
"PolySegment",
"PolyRectangle",
"PolyArc",
"FillPoly",
"PolyFillRectangle",
"PolyFillArc",
"PutImage",
"GetImage",
"PolyText",
"PolyText",
"ImageText",
"ImageText",
"CreateColormap",
"FreeColormap",
"CopyColormapAndFree",
"InstallColormap",
"UninstallColormap",
"ListInstalledColormaps",
"AllocColor",
"AllocNamedColor",
"AllocColorCells",
"AllocColorPlanes",
"FreeColors",
"StoreColors",
"StoreNamedColor",
"QueryColors",
"LookupColor",
"CreateCursor",
"CreateGlyphCursor",
"FreeCursor",
"RecolorCursor",
"QueryBestSize",
"QueryExtension",
"ListExtensions",
"ChangeKeyboardMapping",
"GetKeyboardMapping",
"ChangeKeyboardControl",
"GetKeyboardControl",
"Bell",
"ChangePointerControl",
"GetPointerControl",
"SetScreenSaver",
"GetScreenSaver",
"ChangeHosts",
"ListHosts",
"SetAccessControl",
"SetCloseDownMode",
"KillClient",
"RotateProperties",
"ForceScreenSaver",
"SetPointerMapping",
"GetPointerMapping",
"SetModifierMapping",
"GetModifierMapping",
"major 120",
"major 121",
"major 122",
"major 123",
"major 124",
"major 125",
"major 126",
"NoOperation",
};
static const char *labelEvent[] = {
"error",
"reply",
"KeyPress",
"KeyRelease",
"ButtonPress",
"ButtonRelease",
"MotionNotify",
"EnterNotify",
"LeaveNotify",
"FocusIn",
"FocusOut",
"KeymapNotify",
"Expose",
"GraphicsExpose",
"NoExpose",
"VisibilityNotify",
"CreateNotify",
"DestroyNotify",
"UnmapNotify",
"MapNotify",
"MapRequest",
"ReparentNotify",
"ConfigureNotify",
"ConfigureRequest",
"GravityNotify",
"ResizeRequest",
"CirculateNotify",
"CirculateRequest",
"PropertyNotify",
"SelectionClear",
"SelectionRequest",
"SelectionNotify",
"ColormapNotify",
"ClientMessage",
"MappingNotify",
};
static const char *labelSendEvent[] = {
"",
" (from SendEvent)",
};
int format_event(xcb_generic_event_t *e) {
uint8_t sendEvent;
uint16_t seqnum;
sendEvent = (e->response_type & 0x80) ? 1 : 0;
e->response_type &= ~0x80;
seqnum = *((uint16_t *)e + 1);
switch (e->response_type) {
case 0:
DLOG("Error %s on seqnum %d (%s).\n",
labelError[*((uint8_t *)e + 1)],
seqnum,
labelRequest[*((uint8_t *)e + 10)]);
break;
default:
if (e->response_type > sizeof(labelEvent) / sizeof(char *))
break;
DLOG("Event %s following seqnum %d%s.\n",
labelEvent[e->response_type],
seqnum,
labelSendEvent[sendEvent]);
break;
case XCB_KEYMAP_NOTIFY:
DLOG("Event %s%s.\n",
labelEvent[e->response_type],
labelSendEvent[sendEvent]);
break;
}
fflush(stdout);
return 1;
}

View File

@ -182,4 +182,7 @@ void display_running_version(void) {
#endif
yajl_free(handle);
free(reply);
free(pid_from_atom);
free(socket_path);
}

View File

@ -72,18 +72,29 @@ void floating_check_size(Con *floating_con) {
Rect floating_sane_max_dimensions;
Con *focused_con = con_descend_focused(floating_con);
/* obey size increments */
if (focused_con->window != NULL && (focused_con->window->height_increment || focused_con->window->width_increment)) {
Rect border_rect = con_border_style_rect(focused_con);
/* We have to do the opposite calculations that render_con() do
* to get the exact size we want. */
border_rect.width = -border_rect.width;
border_rect.width += 2 * focused_con->border_width;
border_rect.height = -border_rect.height;
border_rect.height += 2 * focused_con->border_width;
if (con_border_style(focused_con) == BS_NORMAL)
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,37 +111,51 @@ 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;
}
}
void floating_enable(Con *con, bool automatic) {
@ -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 GIMPs 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);

View File

@ -400,11 +400,52 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
DLOG("Dock client will not be moved, we only support moving it to another output.\n");
}
}
fake_absolute_configure_notify(con);
return;
}
fake_absolute_configure_notify(con);
if (event->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) {
DLOG("window 0x%08x wants to be stacked %d\n", event->window, event->stack_mode);
return;
/* Emacs and IntelliJ Idea “request focus” by stacking their window
* above all others. */
if (event->stack_mode != XCB_STACK_MODE_ABOVE) {
DLOG("stack_mode != XCB_STACK_MODE_ABOVE, ignoring ConfigureRequest\n");
goto out;
}
if (fullscreen || !con_is_leaf(con)) {
DLOG("fullscreen or not a leaf, ignoring ConfigureRequest\n");
goto out;
}
Con *ws = con_get_workspace(con);
if (ws == NULL) {
DLOG("Window is not being managed, ignoring ConfigureRequest\n");
goto out;
}
if (strcmp(ws->name, "__i3_scratch") == 0) {
DLOG("This is a scratchpad container, ignoring ConfigureRequest\n");
goto out;
}
if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) {
DLOG("Focusing con = %p\n", con);
workspace_show(ws);
con_focus(con);
tree_render();
} else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
DLOG("Marking con = %p urgent\n", con);
con_set_urgency(con, true);
tree_render();
} else {
DLOG("Ignoring request for con = %p.\n", con);
}
}
out:
fake_absolute_configure_notify(con);
}
/*
@ -614,12 +655,9 @@ static void handle_expose_event(xcb_expose_event_t *event) {
}
/* Since we render to our surface on every change anyways, expose events
* only tell us that the X server lost (parts of) the window contents. We
* can handle that by copying the appropriate part from our surface to the
* window. */
draw_util_copy_surface(conn, &(parent->frame_buffer), &(parent->frame),
event->x, event->y, event->x, event->y,
event->width, event->height);
* only tell us that the X server lost (parts of) the window contents. */
draw_util_copy_surface(&(parent->frame_buffer), &(parent->frame),
0, 0, 0, 0, parent->rect.width, parent->rect.height);
xcb_flush(conn);
return;
}
@ -637,6 +675,11 @@ static void handle_expose_event(xcb_expose_event_t *event) {
#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */
#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */
#define _NET_MOVERESIZE_WINDOW_X (1 << 8)
#define _NET_MOVERESIZE_WINDOW_Y (1 << 9)
#define _NET_MOVERESIZE_WINDOW_WIDTH (1 << 10)
#define _NET_MOVERESIZE_WINDOW_HEIGHT (1 << 11)
/*
* Handle client messages (EWMH)
*
@ -897,6 +940,35 @@ static void handle_client_message(xcb_client_message_event_t *event) {
DLOG("_NET_WM_MOVERESIZE direction %d not implemented\n", direction);
break;
}
} else if (event->type == A__NET_MOVERESIZE_WINDOW) {
DLOG("Received _NET_MOVE_RESIZE_WINDOW. Handling by faking a configure request.\n");
void *_generated_event = scalloc(32, 1);
xcb_configure_request_event_t *generated_event = _generated_event;
generated_event->window = event->window;
generated_event->response_type = XCB_CONFIGURE_REQUEST;
generated_event->value_mask = 0;
if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_X) {
generated_event->value_mask |= XCB_CONFIG_WINDOW_X;
generated_event->x = event->data.data32[1];
}
if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_Y) {
generated_event->value_mask |= XCB_CONFIG_WINDOW_Y;
generated_event->y = event->data.data32[2];
}
if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_WIDTH) {
generated_event->value_mask |= XCB_CONFIG_WINDOW_WIDTH;
generated_event->width = event->data.data32[3];
}
if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_HEIGHT) {
generated_event->value_mask |= XCB_CONFIG_WINDOW_HEIGHT;
generated_event->height = event->data.data32[4];
}
handle_configure_request(generated_event);
FREE(generated_event);
} else {
DLOG("Skipping client message for unhandled type %d\n", event->type);
}
@ -929,54 +1001,72 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
xcb_size_hints_t size_hints;
//CLIENT_LOG(client);
/* If the hints were already in this event, use them, if not, request them */
if (reply != NULL)
if (reply != NULL) {
xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply);
else
} else {
xcb_icccm_get_wm_normal_hints_reply(conn, xcb_icccm_get_wm_normal_hints_unchecked(conn, con->window->id), &size_hints, NULL);
}
int win_width = con->window_rect.width;
int win_height = con->window_rect.height;
if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) {
// TODO: Minimum size is not yet implemented
DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
con->window->min_width = size_hints.min_width;
con->window->min_height = size_hints.min_height;
}
if (con_is_floating(con)) {
win_width = MAX(win_width, con->window->min_width);
win_height = MAX(win_height, con->window->min_height);
}
bool changed = false;
if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) {
if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF)
if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) {
if (con->window->width_increment != size_hints.width_inc) {
con->window->width_increment = size_hints.width_inc;
changed = true;
}
if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF)
}
if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF) {
if (con->window->height_increment != size_hints.height_inc) {
con->window->height_increment = size_hints.height_inc;
changed = true;
}
if (changed)
DLOG("resize increments changed\n");
}
int base_width = 0, base_height = 0;
if (changed) {
DLOG("resize increments changed\n");
}
}
/* 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 */
bool has_base_size = false;
int base_width = 0;
int base_height = 0;
/* 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:
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;

View File

@ -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,
};
/*

View File

@ -29,12 +29,16 @@ static bool parsing_focus;
static bool parsing_marks;
struct Match *current_swallow;
static bool swallow_is_empty;
static int num_marks;
static char **marks;
/* This list is used for reordering the focus stack after parsing the 'focus'
* array. */
struct focus_mapping {
int old_id;
TAILQ_ENTRY(focus_mapping) focus_mappings;
TAILQ_ENTRY(focus_mapping)
focus_mappings;
};
static TAILQ_HEAD(focus_mappings_head, focus_mapping) focus_mappings =
@ -146,6 +150,16 @@ static int json_end_map(void *ctx) {
floating_check_size(json_node);
}
if (num_marks > 0) {
for (int i = 0; i < num_marks; i++) {
con_mark(json_node, marks[i], MM_ADD);
free(marks[i]);
}
free(marks);
num_marks = 0;
}
LOG("attaching\n");
con_attach(json_node, json_node->parent, true);
LOG("Creating window\n");
@ -228,8 +242,10 @@ static int json_key(void *ctx, const unsigned char *val, size_t len) {
if (strcasecmp(last_key, "focus") == 0)
parsing_focus = true;
if (strcasecmp(last_key, "marks") == 0)
if (strcasecmp(last_key, "marks") == 0) {
num_marks = 0;
parsing_marks = true;
}
return 1;
}
@ -259,7 +275,8 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) {
char *mark;
sasprintf(&mark, "%.*s", (int)len, val);
con_mark(json_node, mark, MM_ADD);
marks = srealloc(marks, (++num_marks) * sizeof(char *));
marks[num_marks - 1] = sstrdup(mark);
} else {
if (strcasecmp(last_key, "name") == 0) {
json_node->name = scalloc(len + 1, 1);

View File

@ -88,11 +88,16 @@ void init_logging(void) {
fprintf(stderr, "Could not initialize errorlog\n");
else {
errorfile = fopen(errorfilename, "w");
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");
}
}
}
}
if (physical_mem_bytes == 0) {
#if defined(__APPLE__)
int mib[2] = {CTL_HW, HW_MEMSIZE};

View File

@ -21,6 +21,10 @@
#include <libgen.h>
#include "shmlog.h"
#ifdef I3_ASAN_ENABLED
#include <sanitizer/lsan_interface.h>
#endif
#include "sd-daemon.h"
/* The original value of RLIMIT_CORE when i3 was started. We need to restore
@ -194,6 +198,7 @@ int main(int argc, char *argv[]) {
char *layout_path = NULL;
bool delete_layout_path = false;
bool force_xinerama = false;
bool disable_randr15 = false;
char *fake_outputs = NULL;
bool disable_signalhandler = false;
bool only_check_config = false;
@ -209,6 +214,8 @@ int main(int argc, char *argv[]) {
{"restart", required_argument, 0, 0},
{"force-xinerama", no_argument, 0, 0},
{"force_xinerama", no_argument, 0, 0},
{"disable-randr15", no_argument, 0, 0},
{"disable_randr15", no_argument, 0, 0},
{"disable-signalhandler", no_argument, 0, 0},
{"shmlog-size", required_argument, 0, 0},
{"shmlog_size", required_argument, 0, 0},
@ -289,6 +296,10 @@ int main(int argc, char *argv[]) {
"Please check if your driver really does not support RandR "
"and disable this option as soon as you can.\n");
break;
} else if (strcmp(long_options[option_index].name, "disable-randr15") == 0 ||
strcmp(long_options[option_index].name, "disable_randr15") == 0) {
disable_randr15 = true;
break;
} else if (strcmp(long_options[option_index].name, "disable-signalhandler") == 0) {
disable_signalhandler = true;
break;
@ -544,6 +555,9 @@ int main(int argc, char *argv[]) {
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
if (error != NULL) {
ELOG("Another window manager seems to be running (X error %d)\n", error->error_code);
#ifdef I3_ASAN_ENABLED
__lsan_do_leak_check();
#endif
return 1;
}
@ -661,7 +675,7 @@ int main(int argc, char *argv[]) {
xinerama_init();
} else {
DLOG("Checking for XRandR...\n");
randr_init(&randr_base);
randr_init(&randr_base, disable_randr15 || config.disable_randr15);
}
/* We need to force disabling outputs which have been loaded from the

View File

@ -491,6 +491,12 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
geom->height = wm_size_hints.height;
}
if (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
DLOG("Window specifies minimum size %d x %d\n", wm_size_hints.min_width, wm_size_hints.min_height);
nc->window->min_width = wm_size_hints.min_width;
nc->window->min_height = wm_size_hints.min_height;
}
/* Store the requested geometry. The width/height gets raised to at least
* 75x50 when entering floating mode, which is the minimum size for a
* window to be useful (smaller windows are usually overlays/toolbars/

View File

@ -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 {

View File

@ -41,7 +41,7 @@ Output *get_output_from_string(Output *current_output, const char *output_str) {
return get_output_next_wrap(D_DOWN, current_output);
}
return get_output_by_name(output_str);
return get_output_by_name(output_str, true);
}
Output *get_output_for_con(Con *con) {
@ -51,7 +51,7 @@ Output *get_output_for_con(Con *con) {
return NULL;
}
Output *output = get_output_by_name(output_con->name);
Output *output = get_output_by_name(output_con->name, true);
if (output == NULL) {
ELOG("Could not get output from name \"%s\".\n", output_con->name);
return NULL;

View File

@ -14,11 +14,6 @@
#include <time.h>
#include <xcb/randr.h>
/* While a clean namespace is usually a pretty good thing, we really need
* to use shorter names than the whole xcb_randr_* default names. */
typedef xcb_randr_get_crtc_info_reply_t crtc_info;
typedef xcb_randr_get_screen_resources_current_reply_t resources_reply;
/* Pointer to the result of the query for primary output */
xcb_randr_get_output_primary_reply_t *primary;
@ -27,6 +22,7 @@ struct outputs_head outputs = TAILQ_HEAD_INITIALIZER(outputs);
/* This is the output covering the root window */
static Output *root_output;
static bool has_randr_1_5 = false;
/*
* Get a specific output by its internal X11 id. Used by randr_query_outputs
@ -44,15 +40,19 @@ static Output *get_output_by_id(xcb_randr_output_t id) {
}
/*
* Returns the output with the given name if it is active (!) or NULL.
* Returns the output with the given name or NULL.
* If require_active is true, only active outputs are considered.
*
*/
Output *get_output_by_name(const char *name) {
Output *get_output_by_name(const char *name, const bool require_active) {
Output *output;
TAILQ_FOREACH(output, &outputs, outputs)
if (output->active &&
strcasecmp(output->name, name) == 0)
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,14 +725,21 @@ 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;
const int len = xcb_randr_get_screen_resources_current_outputs_length(res);
/* 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);
/* Request information for each output */
xcb_randr_get_output_info_cookie_t ocookie[len];
@ -664,6 +756,21 @@ void randr_query_outputs(void) {
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)

View File

@ -38,6 +38,7 @@ struct regex *regex_new(const char *pattern) {
}
ELOG("PCRE regular expression compilation failed at %d: %s\n",
offset, error);
regex_free(re);
return NULL;
}
re->extra = pcre_study(re->regex, 0, &error);

View File

@ -15,6 +15,8 @@
#include <sanitizer/lsan_interface.h>
#endif
#define TEXT_PADDING logical_px(2)
typedef struct placeholder_state {
/** The X11 placeholder window. */
xcb_window_t window;
@ -24,12 +26,11 @@ typedef struct placeholder_state {
/** Current size of the placeholder window (to detect size changes). */
Rect rect;
/** The pixmap to render on (back buffer). */
xcb_pixmap_t pixmap;
/** The graphics context for “pixmap”. */
xcb_gcontext_t gc;
/** The drawable surface */
surface_t surface;
TAILQ_ENTRY(placeholder_state) state;
TAILQ_ENTRY(placeholder_state)
state;
} placeholder_state;
static TAILQ_HEAD(state_head, placeholder_state) state_head =
@ -137,17 +138,15 @@ void restore_connect(void) {
}
static void update_placeholder_contents(placeholder_state *state) {
xcb_change_gc(restore_conn, state->gc, XCB_GC_FOREGROUND,
(uint32_t[]){config.client.placeholder.background.colorpixel});
xcb_poly_fill_rectangle(restore_conn, state->pixmap, state->gc, 1,
(xcb_rectangle_t[]){{0, 0, state->rect.width, state->rect.height}});
const color_t foreground = config.client.placeholder.text;
const color_t background = config.client.placeholder.background;
draw_util_clear_surface(&(state->surface), background);
// TODO: make i3font functions per-connection, at least these two for now…?
xcb_flush(restore_conn);
xcb_aux_sync(restore_conn);
set_font_colors(state->gc, config.client.placeholder.text, config.client.placeholder.background);
Match *swallows;
int n = 0;
TAILQ_FOREACH(swallows, &(state->con->swallow_head), matches) {
@ -174,7 +173,10 @@ static void update_placeholder_contents(placeholder_state *state) {
DLOG("con %p (placeholder 0x%08x) line %d: %s\n", state->con, state->window, n, serialized);
i3String *str = i3string_from_utf8(serialized);
draw_text(str, state->pixmap, state->gc, NULL, 2, (n * (config.font.height + 2)) + 2, state->rect.width - 2);
draw_util_text(str, &(state->surface), foreground, background,
TEXT_PADDING,
(n * (config.font.height + TEXT_PADDING)) + TEXT_PADDING,
state->rect.width - 2 * TEXT_PADDING);
i3string_free(str);
n++;
free(serialized);
@ -185,7 +187,7 @@ static void update_placeholder_contents(placeholder_state *state) {
int text_width = predict_text_width(line);
int x = (state->rect.width / 2) - (text_width / 2);
int y = (state->rect.height / 2) - (config.font.height / 2);
draw_text(line, state->pixmap, state->gc, NULL, x, y, text_width);
draw_util_text(line, &(state->surface), foreground, background, x, y, text_width);
i3string_free(line);
xcb_flush(conn);
xcb_aux_sync(conn);
@ -227,11 +229,8 @@ static void open_placeholder_window(Con *con) {
state->window = placeholder;
state->con = con;
state->rect = con->rect;
state->pixmap = xcb_generate_id(restore_conn);
xcb_create_pixmap(restore_conn, root_depth, state->pixmap,
state->window, state->rect.width, state->rect.height);
state->gc = xcb_generate_id(restore_conn);
xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){0});
draw_util_surface_init(conn, &(state->surface), placeholder, get_visualtype(root_screen), state->rect.width, state->rect.height);
update_placeholder_contents(state);
TAILQ_INSERT_TAIL(&state_head, state, state);
@ -285,8 +284,7 @@ bool restore_kill_placeholder(xcb_window_t placeholder) {
continue;
xcb_destroy_window(restore_conn, state->window);
xcb_free_pixmap(restore_conn, state->pixmap);
xcb_free_gc(restore_conn, state->gc);
draw_util_surface_free(restore_conn, &(state->surface));
TAILQ_REMOVE(&state_head, state, state);
free(state);
DLOG("placeholder window 0x%08x destroyed.\n", placeholder);
@ -305,14 +303,8 @@ static void expose_event(xcb_expose_event_t *event) {
DLOG("refreshing window 0x%08x contents (con %p)\n", state->window, state->con);
/* Since we render to our pixmap on every change anyways, expose events
* only tell us that the X server lost (parts of) the window contents. We
* can handle that by copying the appropriate part from our pixmap to the
* window. */
xcb_copy_area(restore_conn, state->pixmap, state->window, state->gc,
event->x, event->y, event->x, event->y,
event->width, event->height);
xcb_flush(restore_conn);
update_placeholder_contents(state);
return;
}
@ -337,19 +329,10 @@ static void configure_notify(xcb_configure_notify_event_t *event) {
state->rect.width = event->width;
state->rect.height = event->height;
xcb_free_pixmap(restore_conn, state->pixmap);
xcb_free_gc(restore_conn, state->gc);
state->pixmap = xcb_generate_id(restore_conn);
xcb_create_pixmap(restore_conn, root_depth, state->pixmap,
state->window, state->rect.width, state->rect.height);
state->gc = xcb_generate_id(restore_conn);
xcb_create_gc(restore_conn, state->gc, state->pixmap, XCB_GC_GRAPHICS_EXPOSURES, (uint32_t[]){0});
draw_util_surface_set_size(&(state->surface), state->rect.width, state->rect.height);
update_placeholder_contents(state);
xcb_copy_area(restore_conn, state->pixmap, state->window, state->gc,
0, 0, 0, 0, state->rect.width, state->rect.height);
xcb_flush(restore_conn);
return;
}
@ -359,7 +342,10 @@ static void configure_notify(xcb_configure_notify_event_t *event) {
static void restore_handle_event(int type, xcb_generic_event_t *event) {
switch (type) {
case XCB_EXPOSE:
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);

View File

@ -3,10 +3,6 @@
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
* © 2009 Jan-Erik Rediger
*
* sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers
* to restart inplace).
*
*/
#include "all.h"
@ -20,28 +16,44 @@
#include <X11/keysym.h>
static void open_popups(void);
typedef struct dialog_t {
xcb_window_t id;
xcb_colormap_t colormap;
Rect dims;
surface_t surface;
static xcb_gcontext_t pixmap_gc;
static xcb_pixmap_t pixmap;
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"));
dialog_width = width_longest_message + 2 * border_width + 2 * margin;
dialog_height = num_lines * config.font.height + 2 * border_width + 2 * margin;
}
/* Copy the contents of the pixmap to the real window */
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, width, height);
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);
return 1;
}
/*
* 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_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) {

View File

@ -329,6 +329,12 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
match_free(match);
free(match);
}
while (!TAILQ_EMPTY(&(con->marks_head))) {
mark_t *mark = TAILQ_FIRST(&(con->marks_head));
TAILQ_REMOVE(&(con->marks_head), mark, marks);
FREE(mark->name);
FREE(mark);
}
free(con);
/* in the case of floating windows, we already focused another container
@ -377,7 +383,11 @@ void tree_split(Con *con, orientation_t orientation) {
if (con->type == CT_WORKSPACE) {
if (con_num_children(con) < 2) {
DLOG("Just changing orientation of workspace\n");
if (con_num_children(con) == 0) {
DLOG("Changing workspace_layout to L_DEFAULT\n");
con->workspace_layout = L_DEFAULT;
}
DLOG("Changing orientation of workspace\n");
con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
return;
} else {

View File

@ -66,6 +66,34 @@ __attribute__((pure)) bool name_is_digits(const char *name) {
return true;
}
/*
* Set 'out' to the layout_t value for the given layout. The function
* returns true on success or false if the passed string is not a valid
* layout name.
*
*/
bool layout_from_name(const char *layout_str, layout_t *out) {
if (strcmp(layout_str, "default") == 0) {
*out = L_DEFAULT;
return true;
} else if (strcasecmp(layout_str, "stacked") == 0 ||
strcasecmp(layout_str, "stacking") == 0) {
*out = L_STACKED;
return true;
} else if (strcasecmp(layout_str, "tabbed") == 0) {
*out = L_TABBED;
return true;
} else if (strcasecmp(layout_str, "splitv") == 0) {
*out = L_SPLITV;
return true;
} else if (strcasecmp(layout_str, "splith") == 0) {
*out = L_SPLITH;
return true;
}
return false;
}
/*
* Parses the workspace name as a number. Returns -1 if the workspace should be
* interpreted as a "named workspace".
@ -259,7 +287,7 @@ void i3_restart(bool forget_layout) {
restore_geometry();
ipc_shutdown();
ipc_shutdown(SHUTDOWN_REASON_RESTART);
LOG("restarting \"%s\"...\n", start_argv[0]);
/* make sure -a is in the argument list or add it */
@ -430,3 +458,20 @@ void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it) {
* waitpid() here. */
waitpid(*nagbar_pid, NULL, 0);
}
/*
* Converts a string into a long using strtol().
* This is a convenience wrapper checking the parsing result. It returns true
* if the number could be parsed.
*/
bool parse_long(const char *str, long *out, int base) {
char *end;
long result = strtol(str, &end, base);
if (result == LONG_MIN || result == LONG_MAX || result < 0 || (end != NULL && *end != '\0')) {
*out = result;
return false;
}
*out = result;
return true;
}

76
src/x.c
View File

@ -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,9 +904,10 @@ 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)
TAILQ_FOREACH(current, &(con->focus_head), focused) {
x_push_node(current);
}
}
/*
* Same idea as in x_push_node(), but this function only unmaps windows. It is

View File

@ -28,7 +28,7 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims,
visual = XCB_COPY_FROM_PARENT;
}
xcb_create_window(conn,
xcb_void_cookie_t gc_cookie = xcb_create_window(conn,
depth,
result, /* the window id */
root, /* parent == root */
@ -39,6 +39,11 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims,
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) {
mask = XCB_CW_CURSOR;
@ -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 clients 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.

Some files were not shown because too many files have changed in this diff Show More