Merge branch 'next' into master
This commit is contained in:
commit
7af2faebd8
|
@ -8,3 +8,4 @@ IndentWidth: 4
|
|||
PointerBindsToType: false
|
||||
ColumnLimit: 0
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SortIncludes: false
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
# i3 project governance
|
||||
|
||||
## Overview
|
||||
|
||||
The i3 project uses a governance model commonly described as Benevolent
|
||||
Dictator For Life (BDFL). This document outlines our understanding of what this
|
||||
means.
|
||||
|
||||
## Roles
|
||||
|
||||
* user: anyone who interacts with the i3 project
|
||||
* core contributor: a handful of people who have contributed significantly to
|
||||
the project by any means (issue triage, support, documentation, code, etc.).
|
||||
Core contributors are recognizable via GitHub’s “Member” badge.
|
||||
* BDFL: a single individual who makes decisions when consensus cannot be
|
||||
reached. i3’s current BDFL is [@stapelberg](https://github.com/stapelberg).
|
||||
|
||||
## Decision making process
|
||||
|
||||
In general, we try to reach consensus in discussions. In case consensus cannot
|
||||
be reached, the BDFL makes a decision.
|
||||
|
||||
For feature requests and code contributions specifically, the values with which
|
||||
we consider them can be found on the bottom of https://i3wm.org/. These values
|
||||
are not set in stone and are to be treated as guiding principles, not absolute
|
||||
rules that must be followed in every case.
|
||||
|
||||
## Contribution process
|
||||
|
||||
Please see [CONTRIBUTING](CONTRIBUTING.md).
|
|
@ -1,4 +1,4 @@
|
|||
sudo: required
|
||||
sudo: false
|
||||
dist: trusty
|
||||
services:
|
||||
- docker
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
Revision history for AnyEvent-I3
|
||||
|
||||
0.18 2017-08-19
|
||||
|
||||
* support the GET_CONFIG command
|
||||
|
||||
0.17 2017-04-09
|
||||
|
||||
* support the shutdown event
|
||||
* use lib '.' for Perl 5.25.11+
|
||||
|
||||
0.16 2014-10-03
|
||||
|
||||
* support the barconfig_update and binding event
|
||||
|
||||
0.15 2013-02-18
|
||||
|
||||
* support the window event
|
||||
|
||||
0.14 2012-09-22
|
||||
|
||||
* support the mode event
|
||||
|
||||
0.13 2012-08-05
|
||||
|
||||
* support the GET_VERSION request with a fall-back to i3 --version
|
||||
|
||||
0.12 2012-07-11
|
||||
|
||||
* taint mode fix: remove relative directories from $ENV{PATH}
|
||||
|
||||
0.11 2012-07-10
|
||||
|
||||
* taint mode fix for FreeBSD
|
||||
|
||||
0.10 2012-07-09
|
||||
|
||||
* Use i3 --get-socketpath by default for determining the socket path
|
||||
* Bugfix: Also delete callbacks which are triggered due to an error
|
||||
|
||||
0.09 2011-10-12
|
||||
|
||||
* Implement GET_BAR_CONFIG request
|
||||
|
||||
0.08 2011-09-26
|
||||
|
||||
* Implement GET_MARKS request
|
||||
* The synopsis mentioned ->workspaces, but it’s ->get_workspaces
|
||||
|
||||
0.07 2010-11-21
|
||||
|
||||
* Implement GET_TREE request
|
||||
|
||||
0.06 2010-06-16
|
||||
|
||||
* Add check to Makefile to abort in a Windows environment (neither i3 nor
|
||||
unix sockets available)
|
||||
|
||||
0.05 2010-06-09
|
||||
|
||||
* use getpwuid() to resolve ~ in socket paths instead of glob()
|
||||
|
||||
0.04 2010-03-27
|
||||
|
||||
* use new default ipc-socket path, glob() path, bump version
|
||||
|
||||
0.03 2010-03-26
|
||||
|
||||
* fix MANIFEST
|
||||
|
||||
0.02 2010-03-23
|
||||
|
||||
* first upload to CPAN
|
|
@ -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
|
|
@ -0,0 +1,11 @@
|
|||
^\.git/
|
||||
\.bak$
|
||||
blib/
|
||||
^Makefile$
|
||||
^Makefile.old$
|
||||
Build
|
||||
Build.bat
|
||||
^pm_to_blib
|
||||
\.tar\.gz$
|
||||
^pod2htm(.*).tmp$
|
||||
^AnyEvent-I3-
|
|
@ -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;
|
|
@ -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.
|
|
@ -0,0 +1,586 @@
|
|||
package AnyEvent::I3;
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use JSON::XS;
|
||||
use AnyEvent::Handle;
|
||||
use AnyEvent::Socket;
|
||||
use AnyEvent;
|
||||
use Encode;
|
||||
use Scalar::Util qw(tainted);
|
||||
|
||||
=head1 NAME
|
||||
|
||||
AnyEvent::I3 - communicate with the i3 window manager
|
||||
|
||||
=cut
|
||||
|
||||
our $VERSION = '0.18';
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
Version 0.18
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
This module connects to the i3 window manager using the UNIX socket based
|
||||
IPC interface it provides (if enabled in the configuration file). You can
|
||||
then subscribe to events or send messages and receive their replies.
|
||||
|
||||
use AnyEvent::I3 qw(:all);
|
||||
|
||||
my $i3 = i3();
|
||||
|
||||
$i3->connect->recv or die "Error connecting";
|
||||
say "Connected to i3";
|
||||
|
||||
my $workspaces = $i3->message(TYPE_GET_WORKSPACES)->recv;
|
||||
say "Currently, you use " . @{$workspaces} . " workspaces";
|
||||
|
||||
...or, using the sugar methods:
|
||||
|
||||
use AnyEvent::I3;
|
||||
|
||||
my $workspaces = i3->get_workspaces->recv;
|
||||
say "Currently, you use " . @{$workspaces} . " workspaces";
|
||||
|
||||
A somewhat more involved example which dumps the i3 layout tree whenever there
|
||||
is a workspace event:
|
||||
|
||||
use Data::Dumper;
|
||||
use AnyEvent;
|
||||
use AnyEvent::I3;
|
||||
|
||||
my $i3 = i3();
|
||||
|
||||
$i3->connect->recv or die "Error connecting to i3";
|
||||
|
||||
$i3->subscribe({
|
||||
workspace => sub {
|
||||
$i3->get_tree->cb(sub {
|
||||
my ($tree) = @_;
|
||||
say "tree: " . Dumper($tree);
|
||||
});
|
||||
}
|
||||
})->recv->{success} or die "Error subscribing to events";
|
||||
|
||||
AE::cv->recv
|
||||
|
||||
=head1 EXPORT
|
||||
|
||||
=head2 $i3 = i3([ $path ]);
|
||||
|
||||
Creates a new C<AnyEvent::I3> object and returns it.
|
||||
|
||||
C<path> is an optional path of the UNIX socket to connect to. It is strongly
|
||||
advised to NOT specify this unless you're absolutely sure you need it.
|
||||
C<AnyEvent::I3> will automatically figure it out by querying the running i3
|
||||
instance on the current DISPLAY which is almost always what you want.
|
||||
|
||||
=head1 SUBROUTINES/METHODS
|
||||
|
||||
=cut
|
||||
|
||||
use Exporter qw(import);
|
||||
use base 'Exporter';
|
||||
|
||||
our @EXPORT = qw(i3);
|
||||
|
||||
use constant TYPE_COMMAND => 0;
|
||||
use constant TYPE_GET_WORKSPACES => 1;
|
||||
use constant TYPE_SUBSCRIBE => 2;
|
||||
use constant TYPE_GET_OUTPUTS => 3;
|
||||
use constant TYPE_GET_TREE => 4;
|
||||
use constant TYPE_GET_MARKS => 5;
|
||||
use constant TYPE_GET_BAR_CONFIG => 6;
|
||||
use constant TYPE_GET_VERSION => 7;
|
||||
use constant TYPE_GET_BINDING_MODES => 8;
|
||||
use constant TYPE_GET_CONFIG => 9;
|
||||
|
||||
our %EXPORT_TAGS = ( 'all' => [
|
||||
qw(i3 TYPE_COMMAND TYPE_GET_WORKSPACES TYPE_SUBSCRIBE TYPE_GET_OUTPUTS
|
||||
TYPE_GET_TREE TYPE_GET_MARKS TYPE_GET_BAR_CONFIG TYPE_GET_VERSION
|
||||
TYPE_GET_BINDING_MODES TYPE_GET_CONFIG)
|
||||
] );
|
||||
|
||||
our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
|
||||
|
||||
my $magic = "i3-ipc";
|
||||
|
||||
# TODO: auto-generate this from the header file? (i3/ipc.h)
|
||||
my $event_mask = (1 << 31);
|
||||
my %events = (
|
||||
workspace => ($event_mask | 0),
|
||||
output => ($event_mask | 1),
|
||||
mode => ($event_mask | 2),
|
||||
window => ($event_mask | 3),
|
||||
barconfig_update => ($event_mask | 4),
|
||||
binding => ($event_mask | 5),
|
||||
shutdown => ($event_mask | 6),
|
||||
_error => 0xFFFFFFFF,
|
||||
);
|
||||
|
||||
sub i3 {
|
||||
AnyEvent::I3->new(@_)
|
||||
}
|
||||
|
||||
# Calls i3, even when running in taint mode.
|
||||
sub _call_i3 {
|
||||
my ($args) = @_;
|
||||
|
||||
my $path_tainted = tainted($ENV{PATH});
|
||||
# This effectively circumvents taint mode checking for $ENV{PATH}. We
|
||||
# do this because users might specify PATH explicitly to call i3 in a
|
||||
# custom location (think ~/.bin/).
|
||||
(local $ENV{PATH}) = ($ENV{PATH} =~ /(.*)/);
|
||||
|
||||
# In taint mode, we also need to remove all relative directories from
|
||||
# PATH (like . or ../bin). We only do this in taint mode and warn the
|
||||
# user, since this might break a real-world use case for some people.
|
||||
if ($path_tainted) {
|
||||
my @dirs = split /:/, $ENV{PATH};
|
||||
my @filtered = grep !/^\./, @dirs;
|
||||
if (scalar @dirs != scalar @filtered) {
|
||||
$ENV{PATH} = join ':', @filtered;
|
||||
warn qq|Removed relative directories from PATH because you | .
|
||||
qq|are running Perl with taint mode enabled. Remove -T | .
|
||||
qq|to be able to use relative directories in PATH. | .
|
||||
qq|New PATH is "$ENV{PATH}"|;
|
||||
}
|
||||
}
|
||||
# Otherwise the qx() operator wont work:
|
||||
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
|
||||
chomp(my $result = qx(i3 $args));
|
||||
# Circumventing taint mode again: the socket can be anywhere on the
|
||||
# system and that’s okay.
|
||||
if ($result =~ /^([^\0]+)$/) {
|
||||
return $1;
|
||||
}
|
||||
|
||||
warn "Calling i3 $args failed. Is DISPLAY set and is i3 in your PATH?";
|
||||
return undef;
|
||||
}
|
||||
|
||||
=head2 $i3 = AnyEvent::I3->new([ $path ])
|
||||
|
||||
Creates a new C<AnyEvent::I3> object and returns it.
|
||||
|
||||
C<path> is an optional path of the UNIX socket to connect to. It is strongly
|
||||
advised to NOT specify this unless you're absolutely sure you need it.
|
||||
C<AnyEvent::I3> will automatically figure it out by querying the running i3
|
||||
instance on the current DISPLAY which is almost always what you want.
|
||||
|
||||
=cut
|
||||
sub new {
|
||||
my ($class, $path) = @_;
|
||||
|
||||
$path = _call_i3('--get-socketpath') unless $path;
|
||||
|
||||
# This is the old default path (v3.*). This fallback line can be removed in
|
||||
# a year from now. -- Michael, 2012-07-09
|
||||
$path ||= '~/.i3/ipc.sock';
|
||||
|
||||
# Check if we need to resolve ~
|
||||
if ($path =~ /~/) {
|
||||
# We use getpwuid() instead of $ENV{HOME} because the latter is tainted
|
||||
# and thus produces warnings when running tests with perl -T
|
||||
my $home = (getpwuid($<))[7];
|
||||
die "Could not get home directory" unless $home and -d $home;
|
||||
$path =~ s/~/$home/g;
|
||||
}
|
||||
|
||||
bless { path => $path } => $class;
|
||||
}
|
||||
|
||||
=head2 $i3->connect
|
||||
|
||||
Establishes the connection to i3. Returns an C<AnyEvent::CondVar> which will
|
||||
be triggered with a boolean (true if the connection was established) as soon as
|
||||
the connection has been established.
|
||||
|
||||
if ($i3->connect->recv) {
|
||||
say "Connected to i3";
|
||||
}
|
||||
|
||||
=cut
|
||||
sub connect {
|
||||
my ($self) = @_;
|
||||
my $cv = AnyEvent->condvar;
|
||||
|
||||
tcp_connect "unix/", $self->{path}, sub {
|
||||
my ($fh) = @_;
|
||||
|
||||
return $cv->send(0) unless $fh;
|
||||
|
||||
$self->{ipchdl} = AnyEvent::Handle->new(
|
||||
fh => $fh,
|
||||
on_read => sub { my ($hdl) = @_; $self->_data_available($hdl) },
|
||||
on_error => sub {
|
||||
my ($hdl, $fatal, $msg) = @_;
|
||||
delete $self->{ipchdl};
|
||||
$hdl->destroy;
|
||||
|
||||
my $cb = $self->{callbacks};
|
||||
|
||||
# Trigger all one-time callbacks with undef
|
||||
for my $type (keys %{$cb}) {
|
||||
next if ($type & $event_mask) == $event_mask;
|
||||
$cb->{$type}->();
|
||||
delete $cb->{$type};
|
||||
}
|
||||
|
||||
# Trigger _error callback, if set
|
||||
my $type = $events{_error};
|
||||
return unless defined($cb->{$type});
|
||||
$cb->{$type}->($msg);
|
||||
}
|
||||
);
|
||||
|
||||
$cv->send(1)
|
||||
};
|
||||
|
||||
$cv
|
||||
}
|
||||
|
||||
sub _data_available {
|
||||
my ($self, $hdl) = @_;
|
||||
|
||||
$hdl->unshift_read(
|
||||
chunk => length($magic) + 4 + 4,
|
||||
sub {
|
||||
my $header = $_[1];
|
||||
# Unpack message length and read the payload
|
||||
my ($len, $type) = unpack("LL", substr($header, length($magic)));
|
||||
$hdl->unshift_read(
|
||||
chunk => $len,
|
||||
sub { $self->_handle_i3_message($type, $_[1]) }
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
sub _handle_i3_message {
|
||||
my ($self, $type, $payload) = @_;
|
||||
|
||||
return unless defined($self->{callbacks}->{$type});
|
||||
|
||||
my $cb = $self->{callbacks}->{$type};
|
||||
$cb->(decode_json $payload);
|
||||
|
||||
return if ($type & $event_mask) == $event_mask;
|
||||
|
||||
# If this was a one-time callback, we delete it
|
||||
# (when connection is lost, all one-time callbacks get triggered)
|
||||
delete $self->{callbacks}->{$type};
|
||||
}
|
||||
|
||||
=head2 $i3->subscribe(\%callbacks)
|
||||
|
||||
Subscribes to the given event types. This function awaits a hashref with the
|
||||
key being the name of the event and the value being a callback.
|
||||
|
||||
my %callbacks = (
|
||||
workspace => sub { say "Workspaces changed" }
|
||||
);
|
||||
|
||||
if ($i3->subscribe(\%callbacks)->recv->{success}) {
|
||||
say "Successfully subscribed";
|
||||
}
|
||||
|
||||
The special callback with name C<_error> is called when the connection to i3
|
||||
is killed (because of a crash, exit or restart of i3 most likely). You can
|
||||
use it to print an appropriate message and exit cleanly or to try to reconnect.
|
||||
|
||||
my %callbacks = (
|
||||
_error => sub {
|
||||
my ($msg) = @_;
|
||||
say "I am sorry. I am so sorry: $msg";
|
||||
exit 1;
|
||||
}
|
||||
);
|
||||
|
||||
$i3->subscribe(\%callbacks)->recv;
|
||||
|
||||
=cut
|
||||
sub subscribe {
|
||||
my ($self, $callbacks) = @_;
|
||||
|
||||
# Register callbacks for each message type
|
||||
for my $key (keys %{$callbacks}) {
|
||||
my $type = $events{$key};
|
||||
$self->{callbacks}->{$type} = $callbacks->{$key};
|
||||
}
|
||||
|
||||
$self->message(TYPE_SUBSCRIBE, [ keys %{$callbacks} ])
|
||||
}
|
||||
|
||||
=head2 $i3->message($type, $content)
|
||||
|
||||
Sends a message of the specified C<type> to i3, possibly containing the data
|
||||
structure C<content> (or C<content>, encoded as utf8, if C<content> is a
|
||||
scalar), if specified.
|
||||
|
||||
my $reply = $i3->message(TYPE_COMMAND, "reload")->recv;
|
||||
if ($reply->{success}) {
|
||||
say "Configuration successfully reloaded";
|
||||
}
|
||||
|
||||
=cut
|
||||
sub message {
|
||||
my ($self, $type, $content) = @_;
|
||||
|
||||
die "No message type specified" unless defined($type);
|
||||
|
||||
die "No connection to i3" unless defined($self->{ipchdl});
|
||||
|
||||
my $payload = "";
|
||||
if ($content) {
|
||||
if (not ref($content)) {
|
||||
# Convert from Perl’s internal encoding to UTF8 octets
|
||||
$payload = encode_utf8($content);
|
||||
} else {
|
||||
$payload = encode_json $content;
|
||||
}
|
||||
}
|
||||
my $message = $magic . pack("LL", length($payload), $type) . $payload;
|
||||
$self->{ipchdl}->push_write($message);
|
||||
|
||||
my $cv = AnyEvent->condvar;
|
||||
|
||||
# We don’t preserve the old callback as it makes no sense to
|
||||
# have a callback on message reply types (only on events)
|
||||
$self->{callbacks}->{$type} =
|
||||
sub {
|
||||
my ($reply) = @_;
|
||||
$cv->send($reply);
|
||||
undef $self->{callbacks}->{$type};
|
||||
};
|
||||
|
||||
$cv
|
||||
}
|
||||
|
||||
=head1 SUGAR METHODS
|
||||
|
||||
These methods intend to make your scripts as beautiful as possible. All of
|
||||
them automatically establish a connection to i3 blockingly (if it does not
|
||||
already exist).
|
||||
|
||||
=cut
|
||||
|
||||
sub _ensure_connection {
|
||||
my ($self) = @_;
|
||||
|
||||
return if defined($self->{ipchdl});
|
||||
|
||||
$self->connect->recv or die "Unable to connect to i3 (socket path " . $self->{path} . ")";
|
||||
}
|
||||
|
||||
=head2 get_workspaces
|
||||
|
||||
Gets the current workspaces from i3.
|
||||
|
||||
my $ws = i3->get_workspaces->recv;
|
||||
say Dumper($ws);
|
||||
|
||||
=cut
|
||||
sub get_workspaces {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->_ensure_connection;
|
||||
|
||||
$self->message(TYPE_GET_WORKSPACES)
|
||||
}
|
||||
|
||||
=head2 get_outputs
|
||||
|
||||
Gets the current outputs from i3.
|
||||
|
||||
my $outs = i3->get_outputs->recv;
|
||||
say Dumper($outs);
|
||||
|
||||
=cut
|
||||
sub get_outputs {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->_ensure_connection;
|
||||
|
||||
$self->message(TYPE_GET_OUTPUTS)
|
||||
}
|
||||
|
||||
=head2 get_tree
|
||||
|
||||
Gets the layout tree from i3 (>= v4.0).
|
||||
|
||||
my $tree = i3->get_tree->recv;
|
||||
say Dumper($tree);
|
||||
|
||||
=cut
|
||||
sub get_tree {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->_ensure_connection;
|
||||
|
||||
$self->message(TYPE_GET_TREE)
|
||||
}
|
||||
|
||||
=head2 get_marks
|
||||
|
||||
Gets all the window identifier marks from i3 (>= v4.1).
|
||||
|
||||
my $marks = i3->get_marks->recv;
|
||||
say Dumper($marks);
|
||||
|
||||
=cut
|
||||
sub get_marks {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->_ensure_connection;
|
||||
|
||||
$self->message(TYPE_GET_MARKS)
|
||||
}
|
||||
|
||||
=head2 get_bar_config
|
||||
|
||||
Gets the bar configuration for the specific bar id from i3 (>= v4.1).
|
||||
|
||||
my $config = i3->get_bar_config($id)->recv;
|
||||
say Dumper($config);
|
||||
|
||||
=cut
|
||||
sub get_bar_config {
|
||||
my ($self, $id) = @_;
|
||||
|
||||
$self->_ensure_connection;
|
||||
|
||||
$self->message(TYPE_GET_BAR_CONFIG, $id)
|
||||
}
|
||||
|
||||
=head2 get_version
|
||||
|
||||
Gets the i3 version via IPC, with a fall-back that parses the output of i3
|
||||
--version (for i3 < v4.3).
|
||||
|
||||
my $version = i3->get_version()->recv;
|
||||
say "major: " . $version->{major} . ", minor = " . $version->{minor};
|
||||
|
||||
=cut
|
||||
sub get_version {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->_ensure_connection;
|
||||
|
||||
my $cv = AnyEvent->condvar;
|
||||
|
||||
my $version_cv = $self->message(TYPE_GET_VERSION);
|
||||
my $timeout;
|
||||
$timeout = AnyEvent->timer(
|
||||
after => 1,
|
||||
cb => sub {
|
||||
warn "Falling back to i3 --version since the running i3 doesn’t support GET_VERSION yet.";
|
||||
my $version = _call_i3('--version');
|
||||
$version =~ s/^i3 version //;
|
||||
my $patch = 0;
|
||||
my ($major, $minor) = ($version =~ /^([0-9]+)\.([0-9]+)/);
|
||||
if ($version =~ /^[0-9]+\.[0-9]+\.([0-9]+)/) {
|
||||
$patch = $1;
|
||||
}
|
||||
# Strip everything from the © sign on.
|
||||
$version =~ s/ ©.*$//g;
|
||||
$cv->send({
|
||||
major => int($major),
|
||||
minor => int($minor),
|
||||
patch => int($patch),
|
||||
human_readable => $version,
|
||||
});
|
||||
undef $timeout;
|
||||
},
|
||||
);
|
||||
$version_cv->cb(sub {
|
||||
undef $timeout;
|
||||
$cv->send($version_cv->recv);
|
||||
});
|
||||
|
||||
return $cv;
|
||||
}
|
||||
|
||||
=head2 get_config
|
||||
|
||||
Gets the raw last read config from i3. Requires i3 >= 4.14
|
||||
|
||||
=cut
|
||||
sub get_config {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->_ensure_connection;
|
||||
|
||||
$self->message(TYPE_GET_CONFIG);
|
||||
}
|
||||
|
||||
|
||||
=head2 command($content)
|
||||
|
||||
Makes i3 execute the given command
|
||||
|
||||
my $reply = i3->command("reload")->recv;
|
||||
die "command failed" unless $reply->{success};
|
||||
|
||||
=cut
|
||||
sub command {
|
||||
my ($self, $content) = @_;
|
||||
|
||||
$self->_ensure_connection;
|
||||
|
||||
$self->message(TYPE_COMMAND, $content)
|
||||
}
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Michael Stapelberg, C<< <michael at i3wm.org> >>
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
Please report any bugs or feature requests to C<bug-anyevent-i3 at
|
||||
rt.cpan.org>, or through the web interface at
|
||||
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=AnyEvent-I3>. I will be
|
||||
notified, and then you'll automatically be notified of progress on your bug as
|
||||
I make changes.
|
||||
|
||||
=head1 SUPPORT
|
||||
|
||||
You can find documentation for this module with the perldoc command.
|
||||
|
||||
perldoc AnyEvent::I3
|
||||
|
||||
You can also look for information at:
|
||||
|
||||
=over 2
|
||||
|
||||
=item * RT: CPAN's request tracker
|
||||
|
||||
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=AnyEvent-I3>
|
||||
|
||||
=item * The i3 window manager website
|
||||
|
||||
L<http://i3wm.org>
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head1 ACKNOWLEDGEMENTS
|
||||
|
||||
|
||||
=head1 LICENSE AND COPYRIGHT
|
||||
|
||||
Copyright 2010-2012 Michael Stapelberg.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of either: the GNU General Public License as published
|
||||
by the Free Software Foundation; or the Artistic License.
|
||||
|
||||
See http://dev.perl.org/licenses/ for more information.
|
||||
|
||||
|
||||
=cut
|
||||
|
||||
1; # End of AnyEvent::I3
|
|
@ -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" );
|
|
@ -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" );
|
|
@ -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" );
|
|
@ -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');
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -1 +1 @@
|
|||
4.13-non-git
|
||||
4.14-non-git
|
||||
|
|
37
Makefile.am
37
Makefile.am
|
@ -45,11 +45,17 @@ dist_xsessions_DATA = \
|
|||
|
||||
noinst_LIBRARIES = libi3.a
|
||||
|
||||
check_PROGRAMS = test.commands_parser test.config_parser
|
||||
check_PROGRAMS = \
|
||||
test.commands_parser \
|
||||
test.config_parser \
|
||||
test.inject_randr15
|
||||
|
||||
check_SCRIPTS = \
|
||||
testcases/complete-run.pl
|
||||
|
||||
check_DATA = \
|
||||
anyevent-i3.stamp
|
||||
|
||||
clean-check:
|
||||
rm -rf testsuite-* latest i3-cfg-for-* _Inline
|
||||
clean-local: clean-check
|
||||
|
@ -94,7 +100,7 @@ EXTRA_DIST = \
|
|||
I3_VERSION \
|
||||
LICENSE \
|
||||
PACKAGE-MAINTAINER \
|
||||
RELEASE-NOTES-4.13 \
|
||||
RELEASE-NOTES-4.14 \
|
||||
generate-command-parser.pl \
|
||||
parser-specs/commands.spec \
|
||||
parser-specs/config.spec \
|
||||
|
@ -212,6 +218,7 @@ asciidoc_MANS =
|
|||
endif
|
||||
|
||||
AM_CPPFLAGS = \
|
||||
-DSYSCONFDIR="\"$(sysconfdir)\"" \
|
||||
-I$(top_builddir)/parser \
|
||||
-I$(top_srcdir)/include \
|
||||
@AX_EXTEND_SRCDIR_CPPFLAGS@
|
||||
|
@ -401,6 +408,19 @@ i3_config_wizard_i3_config_wizard_SOURCES = \
|
|||
i3-config-wizard/main.c \
|
||||
i3-config-wizard/xcb.h
|
||||
|
||||
test_inject_randr15_CPPFLAGS = \
|
||||
$(AM_CPPFLAGS)
|
||||
|
||||
test_inject_randr15_CFLAGS = \
|
||||
$(AM_CFLAGS) \
|
||||
$(i3_CFLAGS)
|
||||
|
||||
test_inject_randr15_SOURCES = \
|
||||
testcases/inject_randr1.5.c
|
||||
|
||||
test_inject_randr15_LDADD = \
|
||||
$(i3_LDADD)
|
||||
|
||||
test_commands_parser_CPPFLAGS = \
|
||||
$(AM_CPPFLAGS) \
|
||||
-DTEST_PARSER
|
||||
|
@ -457,7 +477,6 @@ i3_SOURCES = \
|
|||
include/config_parser.h \
|
||||
include/con.h \
|
||||
include/data.h \
|
||||
include/debug.h \
|
||||
include/display_version.h \
|
||||
include/ewmh.h \
|
||||
include/fake_outputs.h \
|
||||
|
@ -502,7 +521,6 @@ i3_SOURCES = \
|
|||
src/config.c \
|
||||
src/config_directives.c \
|
||||
src/config_parser.c \
|
||||
src/debug.c \
|
||||
src/display_version.c \
|
||||
src/ewmh.c \
|
||||
src/fake_outputs.c \
|
||||
|
@ -558,6 +576,15 @@ i3-config-parser.stamp: parser/$(dirstamp) generate-command-parser.pl parser-spe
|
|||
$(AM_V_at) mv GENERATED_config_* $(top_builddir)/parser
|
||||
$(AM_V_at) touch $@
|
||||
|
||||
################################################################################
|
||||
# AnyEvent-I3 build process
|
||||
################################################################################
|
||||
|
||||
anyevent-i3.stamp: AnyEvent-I3/lib/AnyEvent/I3.pm
|
||||
$(AM_V_BUILD) (cd $(top_srcdir)/AnyEvent-I3 && perl Makefile.PL && make)
|
||||
$(AM_V_at) touch $@
|
||||
|
||||
CLEANFILES = \
|
||||
i3-command-parser.stamp \
|
||||
i3-config-parser.stamp
|
||||
i3-config-parser.stamp \
|
||||
anyevent-i3.stamp
|
||||
|
|
|
@ -1,114 +0,0 @@
|
|||
|
||||
┌────────────────────────────┐
|
||||
│ Release notes for i3 v4.13 │
|
||||
└────────────────────────────┘
|
||||
|
||||
This is i3 v4.13. This version is considered stable. All users of i3 are
|
||||
strongly encouraged to upgrade.
|
||||
|
||||
For users, there are two changes to be aware of:
|
||||
|
||||
1. The X server DPI is read from the Xft.dpi X resource (if available).
|
||||
Previously, i3 used to directly look at the X server’s DPI (based on screen
|
||||
resolution and physical size). Looking at Xft.dpi is more consistent with
|
||||
other software, more likely to be correct (because it’s user-specified and
|
||||
not read from possibly broken hardware information) and allows users to
|
||||
override the value.
|
||||
|
||||
2. It is now possible to set config file variables from X resources using the
|
||||
“set_from_resource” directive. This allows users to have a single source of
|
||||
truth for e.g. theming X11 applications (specify “*color0: #121212” and have
|
||||
it apply to URxvt and your i3 config).
|
||||
|
||||
For packagers, there are three changes that likely require action:
|
||||
|
||||
1. cairo/pango are now required dependencies, as announced in the i3 v4.12
|
||||
release notes.
|
||||
|
||||
2. The aforementioned “set_from_resource” feature requires the new dependency
|
||||
libxcb-util-xrm.
|
||||
|
||||
3. i3 now uses the GNU build system (autotools). Please see
|
||||
https://github.com/i3/i3/commit/4a52a7e9fb6fb2e1f0256b2e086cfa313f411cd8 for
|
||||
a lot more details about the rationale and what this means for your package.
|
||||
Bottomline, things should get simpler for you, though :).
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Changes in i3 v4.13 │
|
||||
└────────────────────────────┘
|
||||
|
||||
• build: wire up version handling for non-release tarballs (as opposed to git
|
||||
checkouts)
|
||||
• build: switch to the GNU build system
|
||||
• i3bar: disable pango markup for plain-text input
|
||||
• man/i3-msg: point out default ipc message type
|
||||
• config: introduce support for specifying variables from X resources
|
||||
• config: ensure variables match on longest-length, eliminating problems
|
||||
where one variable was a prefix of another
|
||||
• config: do not count '\' in comment lines as line continuation
|
||||
• ipc: introduce a new GET_BINDING_MODES command
|
||||
• ipc: implement new window::mark event
|
||||
• ipc: add “output” to IPC events referencing a container
|
||||
• make fullscreen windows open on the output which is indicated by their
|
||||
geometry (fixes LibreOffice Impress multi-monitor presentations)
|
||||
• focus newly managed windows only if they don’t use the globally active
|
||||
input mode (fixes issues with RubyMine)
|
||||
• remove title indentation in nested containers (rationale was unclear,
|
||||
nobody spoke up when we asked about the feature on i3-discuss)
|
||||
• use the last known timestamp when calling xcb_set_input_focus (might fix
|
||||
rare race conditions in focus handling)
|
||||
• introduce the “smart” option for hide_edge_borders, which will hide borders
|
||||
when there is precisely one window on the workspace
|
||||
• handle _MOTIF_WM_HINTS changes (_MOTIF_WM_HINTS were previously only
|
||||
considered when managing a new window)
|
||||
• don’t change border style if BS_NORMAL is requested in _MOTIF_WM_HINTS
|
||||
• only add numlock fallback for keybindings where necessary (allows users to
|
||||
correctly bind keys on the numpad)
|
||||
• do not match docks in config and command criteria
|
||||
• get DPI from the Xft.dpi resource instead of directly looking at the screen
|
||||
resolution/size
|
||||
• handle _NET_ACTIVE_WINDOW for scratchpad windows (for pagers)
|
||||
• set _NET_WM_DESKTOP to sticky for scratchpad windows
|
||||
• add new criteria “tiling” and “floating”
|
||||
• implement special output name “current” for commands
|
||||
• handle ResizeRequests for tray clients (fixes VLC tray icon)
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Bugfixes │
|
||||
└────────────────────────────┘
|
||||
|
||||
• i3bar: fix crash when the I3SOCK environment variable is present
|
||||
• i3-dmenu-desktop: do not die on failed open
|
||||
• i3-input: properly position in non-standard cases (fixes an issue where
|
||||
i3-input would launch off-screen)
|
||||
• i3-save-tree: rename “mark” to “marks” to reflect our recent change to
|
||||
allow multiple marks
|
||||
• mouse bindings: only grab the mouse buttons that need to be grabbed
|
||||
• no_focus: correctly count the number of windows (makes no_focus work with
|
||||
tabbed/stacked workspace layouts).
|
||||
• properly close disabled outputs restored during a restart (this fixes state
|
||||
handling when RandR changes happen during i3 restarts)
|
||||
• don’t trigger bindings on window border clicks unless --border was
|
||||
specified for the binding
|
||||
• traverse numbered workspaces in correct order
|
||||
• fix transition from named to numbered workspaces in “workspace next|prev”
|
||||
• avoid setting urgency hint on content containers and above (fixes crashes)
|
||||
• don’t trigger unrelated key bindings for --release bindings
|
||||
• fix colormap handling for containers (fixes taking screenshots using xwd)
|
||||
• check output crossing on ENTER_NOTIFY to dockarea (fixes pointer jumping)
|
||||
• fix a use-after-free bug (fixes “floating enable” on single split windows)
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Thanks! │
|
||||
└────────────────────────────┘
|
||||
|
||||
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
|
||||
|
||||
Benedikt Heine, Cedric Buissart, Chih-Chyuan Hwang, Denton Liu, eplanet, Eric
|
||||
Engeström, EvilPudding, Ferdinand Bachmann, Hong, Ingo Bürk, Jakob Schnell,
|
||||
Jakub Wilk, johannes karoff, Johannes Lange, joshrosso, Julien Lequertier,
|
||||
Kacper Kowalik, Kenneth Lyons, Kyle Kneitinger, madroach, Michael Vetter,
|
||||
Nathan Schulte, Øsse, Peder Stray, Tony Crisci, Trevor Merrifield, wentasah,
|
||||
yshui, Zamarin Arthur
|
||||
|
||||
-- Michael Stapelberg, 2016-11-08
|
|
@ -0,0 +1,94 @@
|
|||
|
||||
┌────────────────────────────┐
|
||||
│ Release notes for i3 v4.14 │
|
||||
└────────────────────────────┘
|
||||
|
||||
This is i3 v4.14. This version is considered stable. All users of i3 are
|
||||
strongly encouraged to upgrade.
|
||||
|
||||
Aside from many bug and documentation fixes, the “swap” command is a notable
|
||||
addition of this release. As is almost tradition at this point, keybinding
|
||||
handling has seen some fixes as well. A noticeable change for users with such
|
||||
monitors is i3’s support for RandR 1.5, which transparently supports the TILE
|
||||
property of first-gen 4K monitors and current 5K or 8K monitors.
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Changes in i3 v4.14 │
|
||||
└────────────────────────────┘
|
||||
|
||||
• build: link libiconv explicitly for systems which need it
|
||||
• build: move AnyEvent-I3 into the i3 repository
|
||||
• docs/hacking-howto: add compilation instructions
|
||||
• docs/ipc: add missing cases to the workspace event
|
||||
• docs/ipc: document the “primary” field of the OUTPUTS reply
|
||||
• docs/ipc: replace Go IPC library with a maintained one
|
||||
• docs/ipc: add link to the ocaml-i3ipc library
|
||||
• docs/ipc: fix invalid trailing commas in JSON examples
|
||||
• docs/layout-saving: add section about troubleshooting window titles
|
||||
• docs/testsuite: update for the move to autotools
|
||||
• docs/userguide: clarify the move command syntax
|
||||
• docs/userguide: correct “Esc” to “Escape”
|
||||
• docs/userguide: clarify focus_follows_mouse behavior
|
||||
• docs/userguide: expand on combining “workspace number” with a name
|
||||
• docs/userguide: mention the magic v4 config marker
|
||||
• man/i3.man: correct configuration lookup order
|
||||
• i3bar, i3-config-wizard, i3-nagbar: use the Xft.dpi setting (see 4.13 notes)
|
||||
• i3bar: restart bar status command on reload if it changed
|
||||
• i3bar: treat left/right scrolling like up/down scrolling
|
||||
• i3bar: accept “primary” in the “output” configuration directive
|
||||
• i3-input: do not set input focus, grabbing the keyboard suffices
|
||||
• i3-msg: return an exit code when missing the -t argument
|
||||
• i3-sensible-editor: correct “mc-edit” to “mcedit”
|
||||
• i3-sensible-terminal: add lilyterm, tilix, terminix, konsole
|
||||
• respect SYSCONFDIR when looking for the default xdg directory
|
||||
• use RandR 1.5 to query screens, supporting the TILE property commonly used
|
||||
by multi-stream transport (MST) monitors, such as first-gen 4K monitors, or
|
||||
current 5K and 8K monitors
|
||||
• respect minimum size hints for floating windows
|
||||
• support the _NET_MOVERESIZE_WINDOW client message (for e.g. wmctrl)
|
||||
• validate binding modes are not defined more than once
|
||||
• only react to the last ExposeEvent in a series of events
|
||||
• add the shutdown IPC event (upon “restart” or “exit”)
|
||||
• treat left/right scrolling like up/down scrolling (on window titles)
|
||||
• make the “layout toggle” command optionally take a sequence of layouts
|
||||
• introduce --exclude-titlebar flag for mouse bindings
|
||||
• introduce the “swap” command
|
||||
• support the primary output in the “focus” and “move” commands
|
||||
• compare keybinding modifiers for equality, not subset
|
||||
• introduce the GET_CONFIG ipc request (i3-msg -t get_config)
|
||||
• start i3-nagbar when encountering invalid set statements
|
||||
• focus windows upon ConfigureWindow requests with stack-mode=Above
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Bugfixes │
|
||||
└────────────────────────────┘
|
||||
|
||||
• i3bar: correct the color codes used for statusline errors
|
||||
• i3bar: avoid freeze after VisibilityNotify
|
||||
• i3-dmenu-desktop: fix quoted command names
|
||||
• i3-dmenu-desktop: avoid adding items multiple times
|
||||
• fix various X11 resource leaks, memory leaks and memory errors
|
||||
• fix IPC success reply for the workspace command
|
||||
• report errors during logfile creation
|
||||
• fix the signal handler being blank
|
||||
• display marks and the title even if the title is empty (for title_format)
|
||||
• fix changing workspace layout from stacked/tabbed for empty workspaces
|
||||
• add numlock fallback to “bindcode” where necessary
|
||||
• fix a crash on restart when using marks
|
||||
• fix renaming workspaces when the new name starts with “to”
|
||||
• respect dont_warp flag when moving containers
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Thanks! │
|
||||
└────────────────────────────┘
|
||||
|
||||
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
|
||||
|
||||
akash akya, Armaël Guéneau, Baptiste Daroussin, Chih-Chyuan Hwang, cresh,
|
||||
David Jimenez Sequero, Franz König, fred777, Ingo Bürk, Jakub Wilk,
|
||||
Jens-Wolfhard Schicke-Uffmann, Johannes Lange, lasers, lebenlechzer,
|
||||
loungecube, Maarten Dirkse, Manuel Mendez, Max Fisher, Mihai Coman, Nathan
|
||||
Schulte, s3rb31, Sebastian Larsson, Stefan Hagen, Tobias Hänel, Tony Crisci,
|
||||
Trevor Merrifield, Zbyněk Moravec
|
||||
|
||||
-- Michael Stapelberg, 2017-09-04
|
19
configure.ac
19
configure.ac
|
@ -2,7 +2,7 @@
|
|||
# Run autoreconf -fi to generate a configure script from this file.
|
||||
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT([i3], [4.13], [https://github.com/i3/i3/issues])
|
||||
AC_INIT([i3], [4.14], [https://github.com/i3/i3/issues])
|
||||
# For AX_EXTEND_SRCDIR
|
||||
AX_ENABLE_BUILDDIR
|
||||
AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2])
|
||||
|
@ -15,6 +15,12 @@ AC_CONFIG_SRCDIR([libi3/ipc_recv_message.c])
|
|||
AC_CONFIG_HEADERS([config.h])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
|
||||
dnl Verify macros defined in m4/ such as AX_SANITIZERS are not present in the
|
||||
dnl output, i.e. are replaced as expected. This line results in a better error
|
||||
dnl message when using aclocal < 1.13 (which does not understand
|
||||
dnl AC_CONFIG_MACRO_DIR) without passing the -I m4 parameter.
|
||||
m4_pattern_forbid([AX_SANITIZERS])
|
||||
|
||||
# Verify we are using GNU make because we use '%'-style pattern rules in
|
||||
# Makefile.am, which are a GNU make extension. Pull requests to replace
|
||||
# '%'-style pattern rules with a more portable alternative are welcome.
|
||||
|
@ -25,8 +31,8 @@ AX_EXTEND_SRCDIR
|
|||
|
||||
AS_IF([test -d ${srcdir}/.git],
|
||||
[
|
||||
VERSION="$(git describe --tags --abbrev=0)"
|
||||
I3_VERSION="$(git describe --tags --always) ($(git log --pretty=format:%cd --date=short -n1), branch \\\"$(git describe --tags --always --all | sed s:heads/::)\\\")"
|
||||
VERSION="$(git -C ${srcdir} describe --tags --abbrev=0)"
|
||||
I3_VERSION="$(git -C ${srcdir} describe --tags --always) ($(git -C ${srcdir} log --pretty=format:%cd --date=short -n1), branch \\\"$(git -C ${srcdir} describe --tags --always --all | sed s:heads/::)\\\")"
|
||||
# Mirrors what libi3/is_debug_build.c does:
|
||||
is_release=$(test $(echo "${I3_VERSION}" | cut -d '(' -f 1 | wc -m) -lt 10 && echo yes || echo no)
|
||||
],
|
||||
|
@ -53,8 +59,6 @@ AX_CHECK_ENABLE_DEBUG([yes], , [UNUSED_NDEBUG], [$is_release])
|
|||
|
||||
AC_PROG_CC_C99
|
||||
|
||||
AC_DEFINE_UNQUOTED(SYSCONFDIR, "`eval echo $sysconfdir`", [Location of system configuration files])
|
||||
|
||||
# For strnlen() and vasprintf().
|
||||
AC_USE_SYSTEM_EXTENSIONS
|
||||
|
||||
|
@ -79,6 +83,8 @@ AC_SEARCH_LIBS([ev_run], [ev], , [AC_MSG_FAILURE([cannot find the required ev_ru
|
|||
|
||||
AC_SEARCH_LIBS([shm_open], [rt])
|
||||
|
||||
AC_SEARCH_LIBS([iconv_open], [iconv], , [AC_MSG_FAILURE([cannot find the required iconv_open() function despite trying to link with -liconv])])
|
||||
|
||||
AX_PTHREAD
|
||||
|
||||
dnl Each prefix corresponds to a source tarball which users might have
|
||||
|
@ -146,8 +152,9 @@ else
|
|||
print_BUILD_MANS=no
|
||||
fi
|
||||
|
||||
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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -119,9 +119,6 @@ src/config.c::
|
|||
Contains all functions handling the configuration file (calling the parser
|
||||
src/config_parser.c) with the correct path, switching key bindings mode).
|
||||
|
||||
src/debug.c::
|
||||
Contains debugging functions to print unhandled X events.
|
||||
|
||||
src/ewmh.c::
|
||||
Functions to get/set certain EWMH properties easily.
|
||||
|
||||
|
@ -993,6 +990,47 @@ New features are only found in the “next” branch. Therefore, if you are work
|
|||
on a new feature, use the “next” branch. If you are working on a bugfix, use the
|
||||
“next” branch, too, but make sure your code also works on “master”.
|
||||
|
||||
=== How to build?
|
||||
|
||||
You can build i3 like you build any other software package which uses autotools.
|
||||
Here’s a memory refresher:
|
||||
|
||||
$ autoreconf -fi
|
||||
$ mkdir -p build && cd build
|
||||
$ ../configure
|
||||
$ make -j8
|
||||
|
||||
(The autoreconf -fi step is unnecessary if you are building from a release tarball,
|
||||
but shouldn’t hurt either.)
|
||||
|
||||
==== Build system features
|
||||
|
||||
* We use the AX_ENABLE_BUILDDIR macro to enforce builds happening in a separate
|
||||
directory. This is a prerequisite for the AX_EXTEND_SRCDIR macro and building
|
||||
in a separate directory is common practice anyway. In case this causes any
|
||||
trouble when packaging i3 for your distribution, please open an issue.
|
||||
|
||||
* “make check” runs the i3 testsuite. See docs/testsuite for details.
|
||||
|
||||
* “make distcheck” (runs testsuite on “make dist” result, tiny bit quicker
|
||||
feedback cycle than waiting for the travis build to catch the issue).
|
||||
|
||||
* “make uninstall” (occasionally requested by users who compile from source)
|
||||
|
||||
* “make” will build manpages/docs by default if the tools are installed.
|
||||
Conversely, manpages/docs are not tried to be built for users who don’t want
|
||||
to install all these dependencies to get started hacking on i3.
|
||||
|
||||
* non-release builds will enable address sanitizer by default. Use the
|
||||
--disable-sanitizers configure option to turn off all sanitizers, and see
|
||||
--help for available sanitizers.
|
||||
|
||||
* Support for pre-compiled headers (PCH) has been dropped for now in the
|
||||
interest of simplicity. If you need support for PCH, please open an issue.
|
||||
|
||||
* Coverage reports are now generated using “make check-code-coverage”, which
|
||||
requires specifying --enable-code-coverage when calling configure.
|
||||
|
||||
== Thought experiments
|
||||
|
||||
In this section, we collect thought experiments, so that we don’t forget our
|
||||
|
|
34
docs/ipc
34
docs/ipc
|
@ -232,6 +232,8 @@ name (string)::
|
|||
The name of this output (as seen in +xrandr(1)+). Encoded in UTF-8.
|
||||
active (boolean)::
|
||||
Whether this output is currently active (has a valid mode).
|
||||
primary (boolean)::
|
||||
Whether this output is currently the primary output.
|
||||
current_workspace (string)::
|
||||
The name of the current workspace that is visible on this output. +null+ if
|
||||
the output is not active.
|
||||
|
@ -262,7 +264,7 @@ rect (map)::
|
|||
"y": 0,
|
||||
"width": 1280,
|
||||
"height": 1024
|
||||
},
|
||||
}
|
||||
}
|
||||
]
|
||||
-------------------
|
||||
|
@ -390,7 +392,7 @@ JSON dump:
|
|||
"y": 0,
|
||||
"width": 1280,
|
||||
"height": 0
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -671,6 +673,8 @@ barconfig_update (4)::
|
|||
binding (5)::
|
||||
Sent when a configured command binding is triggered with the keyboard or
|
||||
mouse
|
||||
shutdown (6)::
|
||||
Sent when the ipc shuts down because of a restart or exit by user command
|
||||
|
||||
*Example:*
|
||||
--------------------------------------------------------------------
|
||||
|
@ -694,9 +698,9 @@ if ($is_event) {
|
|||
|
||||
This event consists of a single serialized map containing a property
|
||||
+change (string)+ which indicates the type of the change ("focus", "init",
|
||||
"empty", "urgent"). A +current (object)+ property will be present with the
|
||||
affected workspace whenever the type of event affects a workspace (otherwise,
|
||||
it will be +null).
|
||||
"empty", "urgent", "reload", "rename", "restored", "move"). A
|
||||
+current (object)+ property will be present with the affected workspace
|
||||
whenever the type of event affects a workspace (otherwise, it will be +null).
|
||||
|
||||
When the change is "focus", an +old (object)+ property will be present with the
|
||||
previous workspace. When the first switch occurs (when i3 focuses the
|
||||
|
@ -791,7 +795,7 @@ same as a +GET_BAR_CONFIG+ reply for the bar with the given id.
|
|||
=== binding event
|
||||
|
||||
This event consists of a single serialized map reporting on the details of a
|
||||
binding that ran a command because of user input. The +change (sring)+ field
|
||||
binding that ran a command because of user input. The +change (string)+ field
|
||||
indicates what sort of binding event was triggered (right now it will always be
|
||||
+"run"+ but may be expanded in the future).
|
||||
|
||||
|
@ -829,6 +833,20 @@ input_type (string)::
|
|||
}
|
||||
---------------------------
|
||||
|
||||
=== shutdown event
|
||||
|
||||
This event is triggered when the connection to the ipc is about to shutdown
|
||||
because of a user action such as a +restart+ or +exit+ command. The +change
|
||||
(string)+ field indicates why the ipc is shutting down. It can be either
|
||||
+"restart"+ or +"exit"+.
|
||||
|
||||
*Example:*
|
||||
---------------------------
|
||||
{
|
||||
"change": "restart"
|
||||
}
|
||||
---------------------------
|
||||
|
||||
== See also (existing libraries)
|
||||
|
||||
[[libraries]]
|
||||
|
@ -843,7 +861,7 @@ C::
|
|||
C++::
|
||||
* https://github.com/drmgc/i3ipcpp
|
||||
Go::
|
||||
* https://github.com/proxypoke/i3ipc
|
||||
* https://github.com/mdirkse/i3ipc-go
|
||||
JavaScript::
|
||||
* https://github.com/acrisci/i3ipc-gjs
|
||||
Lua::
|
||||
|
@ -859,3 +877,5 @@ Ruby::
|
|||
* https://github.com/badboy/i3-ipc (not maintained)
|
||||
Rust::
|
||||
* https://github.com/tmerr/i3ipc-rs
|
||||
OCaml::
|
||||
* https://github.com/Armael/ocaml-i3ipc
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
--------------------------------------------------------------------------------
|
||||
|
|
|
@ -82,6 +82,8 @@ The tests additionally require +Xephyr(1)+ to run a nested X server. Install
|
|||
$ cd ~/i3/testcases
|
||||
$ sudo apt-get install cpanminus
|
||||
$ sudo cpanm .
|
||||
$ cd ~/i3/AnyEvent-I3
|
||||
$ sudo cpanm .
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
If you don’t want to use cpanminus for some reason, the same works with cpan:
|
||||
|
@ -90,6 +92,8 @@ If you don’t want to use cpanminus for some reason, the same works with cpan:
|
|||
--------------------------------------------------------------------------------
|
||||
$ cd ~/i3/testcases
|
||||
$ sudo cpan .
|
||||
$ cd ~/i3/AnyEvent-I3
|
||||
$ sudo cpan .
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
In case you don’t have root permissions, you can also install into your home
|
||||
|
@ -112,9 +116,20 @@ the tests without an X session with Xvfb, such as with +xvfb-run
|
|||
./complete-run+. This will also speed up the tests significantly especially on
|
||||
machines without a powerful video card.
|
||||
|
||||
.Example invocation of complete-run.pl+
|
||||
.Example invocation of +complete-run.pl+
|
||||
---------------------------------------
|
||||
$ cd ~/i3/testcases
|
||||
$ cd ~/i3
|
||||
|
||||
$ autoreconf -fi
|
||||
|
||||
$ mkdir -p build && cd build
|
||||
|
||||
$ ../configure
|
||||
|
||||
$ make -j8
|
||||
# output omitted because it is very long
|
||||
|
||||
$ cd testcases
|
||||
|
||||
$ ./complete-run.pl
|
||||
# output omitted because it is very long
|
||||
|
@ -160,6 +175,41 @@ $ ./complete-run.pl --parallel=1 --keep-xserver-output
|
|||
This will show the output of Xephyr, which is the X server implementation we
|
||||
use for testing.
|
||||
|
||||
===== make command: +make check+
|
||||
Make check runs the i3 testsuite.
|
||||
You can still use ./testcases/complete-run.pl to get the interactive progress output.
|
||||
|
||||
.Example invocation of +make check+
|
||||
---------------------------------------
|
||||
$ cd ~/i3
|
||||
|
||||
$ autoreconf -fi
|
||||
|
||||
$ mkdir -p build && cd build
|
||||
|
||||
$ ../configure
|
||||
|
||||
$ make -j8
|
||||
# output omitted because it is very long
|
||||
|
||||
$ make check
|
||||
# output omitted because it is very long
|
||||
PASS: testcases/complete-run.pl
|
||||
============================================================================
|
||||
Testsuite summary for i3 4.13
|
||||
============================================================================
|
||||
# TOTAL: 1
|
||||
# PASS: 1
|
||||
# SKIP: 0
|
||||
# XFAIL: 0
|
||||
# FAIL: 0
|
||||
# XPASS: 0
|
||||
# ERROR: 0
|
||||
============================================================================
|
||||
|
||||
$ less test-suite.log
|
||||
----------------------------------------
|
||||
|
||||
==== Coverage testing
|
||||
|
||||
Coverage testing is possible with +lcov+, the front-end for GCC's coverage
|
||||
|
|
149
docs/userguide
149
docs/userguide
|
@ -297,6 +297,15 @@ keyboard layout. To start the wizard, use the command +i3-config-wizard+.
|
|||
Please note that you must not have +~/.i3/config+, otherwise the wizard will
|
||||
exit.
|
||||
|
||||
Since i3 4.0, a new configuration format is used. i3 will try to automatically
|
||||
detect the format version of a config file based on a few different keywords,
|
||||
but if you want to make sure that your config is read with the new format,
|
||||
include the following line in your config file:
|
||||
|
||||
---------------------
|
||||
# i3 config file (v4)
|
||||
---------------------
|
||||
|
||||
=== Comments
|
||||
|
||||
It is possible and recommended to use comments in your configuration file to
|
||||
|
@ -412,9 +421,9 @@ button in the scope of the clicked container (see <<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>>),
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
# Don’t add “geany” if “Geany” is already present.
|
||||
my @keys = map { lc } keys %choices;
|
||||
next if (scalar grep { $_ eq lc(basename($command)) } @keys) > 0;
|
||||
|
||||
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');
|
||||
|
|
|
@ -33,6 +33,10 @@
|
|||
|
||||
#include "i3-input.h"
|
||||
|
||||
#define MAX_WIDTH logical_px(500)
|
||||
#define BORDER logical_px(2)
|
||||
#define PADDING logical_px(2)
|
||||
|
||||
/* IPC format string. %s will be replaced with what the user entered, then
|
||||
* the command will be sent to i3 */
|
||||
static char *format;
|
||||
|
@ -42,8 +46,7 @@ static int sockfd;
|
|||
static xcb_key_symbols_t *symbols;
|
||||
static bool modeswitch_active = false;
|
||||
static xcb_window_t win;
|
||||
static xcb_pixmap_t pixmap;
|
||||
static xcb_gcontext_t pixmap_gc;
|
||||
static surface_t surface;
|
||||
static xcb_char2b_t glyphs_ucs[512];
|
||||
static char *glyphs_utf8[512];
|
||||
static int input_position;
|
||||
|
@ -54,7 +57,6 @@ static int limit;
|
|||
xcb_window_t root;
|
||||
xcb_connection_t *conn;
|
||||
xcb_screen_t *root_screen;
|
||||
static xcb_get_input_focus_cookie_t focus_cookie;
|
||||
|
||||
/*
|
||||
* Having verboselog(), errorlog() and debuglog() is necessary when using libi3.
|
||||
|
@ -79,24 +81,6 @@ void errorlog(char *fmt, ...) {
|
|||
void debuglog(char *fmt, ...) {
|
||||
}
|
||||
|
||||
/*
|
||||
* Restores the X11 input focus to wherever it was before.
|
||||
* This is necessary because i3-input’s window has override_redirect=1
|
||||
* (→ unmanaged by the window manager) and thus i3-input changes focus itself.
|
||||
* This function is called on exit().
|
||||
*
|
||||
*/
|
||||
static void restore_input_focus(void) {
|
||||
xcb_generic_error_t *error;
|
||||
xcb_get_input_focus_reply_t *reply = xcb_get_input_focus_reply(conn, focus_cookie, &error);
|
||||
if (error != NULL) {
|
||||
fprintf(stderr, "[i3-input] ERROR: Could not restore input focus (X error %d)\n", error->error_code);
|
||||
return;
|
||||
}
|
||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, reply->focus, XCB_CURRENT_TIME);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for
|
||||
* rendering it (UCS-2) or sending it to i3 (UTF-8).
|
||||
|
@ -128,30 +112,30 @@ static uint8_t *concat_strings(char **glyphs, int max) {
|
|||
static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) {
|
||||
printf("expose!\n");
|
||||
|
||||
/* re-draw the background */
|
||||
xcb_rectangle_t border = {0, 0, logical_px(500), font.height + logical_px(8)},
|
||||
inner = {logical_px(2), logical_px(2), logical_px(496), font.height + logical_px(8) - logical_px(4)};
|
||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#FF0000")});
|
||||
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
|
||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")});
|
||||
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
|
||||
color_t border_color = draw_util_hex_to_color("#FF0000");
|
||||
color_t fg_color = draw_util_hex_to_color("#FFFFFF");
|
||||
color_t bg_color = draw_util_hex_to_color("#000000");
|
||||
|
||||
/* restore font color */
|
||||
set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000"));
|
||||
int text_offset = BORDER + PADDING;
|
||||
|
||||
/* draw border */
|
||||
draw_util_rectangle(&surface, border_color, 0, 0, surface.width, surface.height);
|
||||
|
||||
/* draw background */
|
||||
draw_util_rectangle(&surface, bg_color, BORDER, BORDER, surface.width - 2 * BORDER, surface.height - 2 * BORDER);
|
||||
|
||||
/* draw the prompt … */
|
||||
if (prompt != NULL) {
|
||||
draw_text(prompt, pixmap, pixmap_gc, NULL, logical_px(4), logical_px(4), logical_px(492));
|
||||
draw_util_text(prompt, &surface, fg_color, bg_color, text_offset, text_offset, MAX_WIDTH - text_offset);
|
||||
}
|
||||
|
||||
/* … and the text */
|
||||
if (input_position > 0) {
|
||||
i3String *input = i3string_from_ucs2(glyphs_ucs, input_position);
|
||||
draw_text(input, pixmap, pixmap_gc, NULL, prompt_offset + logical_px(4), logical_px(4), logical_px(492));
|
||||
draw_util_text(input, &surface, fg_color, bg_color, text_offset + prompt_offset, text_offset, MAX_WIDTH - text_offset);
|
||||
i3string_free(input);
|
||||
}
|
||||
|
||||
/* Copy the contents of the pixmap to the real window */
|
||||
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, logical_px(500), font.height + logical_px(8));
|
||||
xcb_flush(conn);
|
||||
|
||||
return 1;
|
||||
|
@ -208,10 +192,6 @@ static void finish_input() {
|
|||
/* prefix the command if a prefix was specified on commandline */
|
||||
printf("command = %s\n", full);
|
||||
|
||||
restore_input_focus();
|
||||
|
||||
xcb_aux_sync(conn);
|
||||
|
||||
ipc_send_message(sockfd, strlen(full), 0, (uint8_t *)full);
|
||||
|
||||
free(full);
|
||||
|
@ -265,7 +245,6 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
|||
return 1;
|
||||
}
|
||||
if (sym == XK_Escape) {
|
||||
restore_input_focus();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
@ -311,7 +290,7 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
|||
}
|
||||
|
||||
static xcb_rectangle_t get_window_position(void) {
|
||||
xcb_rectangle_t result = (xcb_rectangle_t){logical_px(50), logical_px(50), logical_px(500), font.height + logical_px(8)};
|
||||
xcb_rectangle_t result = (xcb_rectangle_t){logical_px(50), logical_px(50), MAX_WIDTH, font.height + 2 * BORDER + 2 * PADDING};
|
||||
|
||||
xcb_get_property_reply_t *supporting_wm_reply = NULL;
|
||||
xcb_get_input_focus_reply_t *input_focus = NULL;
|
||||
|
@ -467,14 +446,12 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
sockfd = ipc_connect(socket_path);
|
||||
|
||||
/* Request the current InputFocus to restore when i3-input exits. */
|
||||
focus_cookie = xcb_get_input_focus(conn);
|
||||
|
||||
root_screen = xcb_aux_get_screen(conn, screen);
|
||||
root = root_screen->root;
|
||||
|
||||
symbols = xcb_key_symbols_alloc(conn);
|
||||
|
||||
init_dpi();
|
||||
font = load_font(pattern, true);
|
||||
set_font(&font);
|
||||
|
||||
|
@ -503,15 +480,8 @@ int main(int argc, char *argv[]) {
|
|||
/* Map the window (make it visible) */
|
||||
xcb_map_window(conn, win);
|
||||
|
||||
/* Create pixmap */
|
||||
pixmap = xcb_generate_id(conn);
|
||||
pixmap_gc = xcb_generate_id(conn);
|
||||
xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, logical_px(500), font.height + logical_px(8));
|
||||
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
|
||||
|
||||
/* Set input focus (we have override_redirect=1, so the wm will not do
|
||||
* this for us) */
|
||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
|
||||
/* Initialize the drawable surface */
|
||||
draw_util_surface_init(conn, &surface, win, get_visualtype(root_screen), win_pos.width, win_pos.height);
|
||||
|
||||
/* Grab the keyboard to get all input */
|
||||
xcb_flush(conn);
|
||||
|
@ -531,7 +501,6 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
|
||||
fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
|
||||
restore_input_focus();
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
|
@ -557,12 +526,16 @@ int main(int argc, char *argv[]) {
|
|||
break;
|
||||
|
||||
case XCB_EXPOSE:
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -119,6 +119,43 @@ static yajl_callbacks reply_callbacks = {
|
|||
.yajl_end_map = reply_end_map_cb,
|
||||
};
|
||||
|
||||
/*******************************************************************************
|
||||
* Config reply callbacks
|
||||
*******************************************************************************/
|
||||
|
||||
static char *config_last_key = NULL;
|
||||
|
||||
static int config_string_cb(void *params, const unsigned char *val, size_t len) {
|
||||
char *str = scalloc(len + 1, 1);
|
||||
strncpy(str, (const char *)val, len);
|
||||
if (strcmp(config_last_key, "config") == 0) {
|
||||
fprintf(stdout, "%s", str);
|
||||
}
|
||||
free(str);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int config_start_map_cb(void *params) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int config_end_map_cb(void *params) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int config_map_key_cb(void *params, const unsigned char *keyVal, size_t keyLen) {
|
||||
config_last_key = scalloc(keyLen + 1, 1);
|
||||
strncpy(config_last_key, (const char *)keyVal, keyLen);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static yajl_callbacks config_callbacks = {
|
||||
.yajl_string = config_string_cb,
|
||||
.yajl_start_map = config_start_map_cb,
|
||||
.yajl_map_key = config_map_key_cb,
|
||||
.yajl_end_map = config_end_map_cb,
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
#if defined(__OpenBSD__)
|
||||
if (pledge("stdio rpath unix", NULL) == -1)
|
||||
|
@ -150,25 +187,27 @@ int main(int argc, char *argv[]) {
|
|||
free(socket_path);
|
||||
socket_path = sstrdup(optarg);
|
||||
} else if (o == 't') {
|
||||
if (strcasecmp(optarg, "command") == 0)
|
||||
if (strcasecmp(optarg, "command") == 0) {
|
||||
message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
|
||||
else if (strcasecmp(optarg, "get_workspaces") == 0)
|
||||
} else if (strcasecmp(optarg, "get_workspaces") == 0) {
|
||||
message_type = I3_IPC_MESSAGE_TYPE_GET_WORKSPACES;
|
||||
else if (strcasecmp(optarg, "get_outputs") == 0)
|
||||
} else if (strcasecmp(optarg, "get_outputs") == 0) {
|
||||
message_type = I3_IPC_MESSAGE_TYPE_GET_OUTPUTS;
|
||||
else if (strcasecmp(optarg, "get_tree") == 0)
|
||||
} else if (strcasecmp(optarg, "get_tree") == 0) {
|
||||
message_type = I3_IPC_MESSAGE_TYPE_GET_TREE;
|
||||
else if (strcasecmp(optarg, "get_marks") == 0)
|
||||
} else if (strcasecmp(optarg, "get_marks") == 0) {
|
||||
message_type = I3_IPC_MESSAGE_TYPE_GET_MARKS;
|
||||
else if (strcasecmp(optarg, "get_bar_config") == 0)
|
||||
} else if (strcasecmp(optarg, "get_bar_config") == 0) {
|
||||
message_type = I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG;
|
||||
else if (strcasecmp(optarg, "get_binding_modes") == 0)
|
||||
} else if (strcasecmp(optarg, "get_binding_modes") == 0) {
|
||||
message_type = I3_IPC_MESSAGE_TYPE_GET_BINDING_MODES;
|
||||
else if (strcasecmp(optarg, "get_version") == 0)
|
||||
} else if (strcasecmp(optarg, "get_version") == 0) {
|
||||
message_type = I3_IPC_MESSAGE_TYPE_GET_VERSION;
|
||||
else {
|
||||
} else if (strcasecmp(optarg, "get_config") == 0) {
|
||||
message_type = I3_IPC_MESSAGE_TYPE_GET_CONFIG;
|
||||
} else {
|
||||
printf("Unknown message type\n");
|
||||
printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version\n");
|
||||
printf("Known types: command, get_workspaces, get_outputs, get_tree, get_marks, get_bar_config, get_binding_modes, get_version, get_config\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (o == 'q') {
|
||||
|
@ -180,6 +219,8 @@ int main(int argc, char *argv[]) {
|
|||
printf("i3-msg " I3_VERSION "\n");
|
||||
printf("i3-msg [-s <socket>] [-t <type>] <message>\n");
|
||||
return 0;
|
||||
} else if (o == '?') {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,7 +280,7 @@ int main(int argc, char *argv[]) {
|
|||
errx(EXIT_FAILURE, "IPC: Received reply of type %d but expected %d", reply_type, message_type);
|
||||
/* For the reply of commands, have a look if that command was successful.
|
||||
* If not, nicely format the error message. */
|
||||
if (reply_type == I3_IPC_MESSAGE_TYPE_COMMAND) {
|
||||
if (reply_type == I3_IPC_REPLY_TYPE_COMMAND) {
|
||||
yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
|
||||
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
|
||||
yajl_free(handle);
|
||||
|
@ -254,8 +295,24 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
/* NB: We still fall-through and print the reply, because even if one
|
||||
* command failed, that doesn’t mean that all commands failed. */
|
||||
} else if (reply_type == I3_IPC_REPLY_TYPE_CONFIG) {
|
||||
yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
|
||||
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
|
||||
yajl_free(handle);
|
||||
|
||||
switch (state) {
|
||||
case yajl_status_ok:
|
||||
break;
|
||||
case yajl_status_client_canceled:
|
||||
case yajl_status_error:
|
||||
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
|
||||
}
|
||||
|
||||
goto exit;
|
||||
}
|
||||
printf("%.*s\n", reply_length, reply);
|
||||
|
||||
exit:
|
||||
free(reply);
|
||||
|
||||
close(sockfd);
|
||||
|
|
173
i3-nagbar/main.c
173
i3-nagbar/main.c
|
@ -38,6 +38,13 @@
|
|||
* constant for that. */
|
||||
#define XCB_CURSOR_LEFT_PTR 68
|
||||
|
||||
#define MSG_PADDING logical_px(8)
|
||||
#define BTN_PADDING logical_px(3)
|
||||
#define BTN_BORDER logical_px(3)
|
||||
#define BTN_GAP logical_px(20)
|
||||
#define CLOSE_BTN_GAP logical_px(15)
|
||||
#define BAR_BORDER logical_px(2)
|
||||
|
||||
static char *argv0 = NULL;
|
||||
|
||||
typedef struct {
|
||||
|
@ -48,11 +55,12 @@ typedef struct {
|
|||
} button_t;
|
||||
|
||||
static xcb_window_t win;
|
||||
static xcb_pixmap_t pixmap;
|
||||
static xcb_gcontext_t pixmap_gc;
|
||||
static xcb_rectangle_t rect = {0, 0, 600, 20};
|
||||
static surface_t bar;
|
||||
|
||||
static i3Font font;
|
||||
static i3String *prompt;
|
||||
|
||||
static button_t btn_close;
|
||||
static button_t *buttons;
|
||||
static int buttoncnt;
|
||||
|
||||
|
@ -138,7 +146,7 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
|
|||
printf("button released on x = %d, y = %d\n",
|
||||
event->event_x, event->event_y);
|
||||
/* If the user hits the close button, we exit(0) */
|
||||
if (event->event_x >= (rect.width - logical_px(32)))
|
||||
if (event->event_x >= btn_close.x && event->event_x < btn_close.x + btn_close.width)
|
||||
exit(0);
|
||||
button_t *button = get_button_at(event->event_x, event->event_y);
|
||||
if (!button)
|
||||
|
@ -190,108 +198,64 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
|
|||
/* TODO: unset flag, re-render */
|
||||
}
|
||||
|
||||
/*
|
||||
* Draws a button and returns its width
|
||||
*
|
||||
*/
|
||||
static int button_draw(button_t *button, int position) {
|
||||
int text_width = predict_text_width(button->label);
|
||||
button->width = text_width + 2 * BTN_PADDING + 2 * BTN_BORDER;
|
||||
button->x = position - button->width;
|
||||
|
||||
/* draw border */
|
||||
draw_util_rectangle(&bar, color_border,
|
||||
position - button->width,
|
||||
MSG_PADDING - BTN_PADDING - BTN_BORDER,
|
||||
button->width,
|
||||
font.height + 2 * BTN_PADDING + 2 * BTN_BORDER);
|
||||
/* draw background */
|
||||
draw_util_rectangle(&bar, color_button_background,
|
||||
position - button->width + BTN_BORDER,
|
||||
MSG_PADDING - BTN_PADDING,
|
||||
text_width + 2 * BTN_PADDING,
|
||||
font.height + 2 * BTN_PADDING);
|
||||
/* draw label */
|
||||
draw_util_text(button->label, &bar, color_text, color_button_background,
|
||||
position - button->width + BTN_BORDER + BTN_PADDING,
|
||||
MSG_PADDING,
|
||||
200);
|
||||
return button->width;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handles expose events (redraws of the window) and rendering in general. Will
|
||||
* be called from the code with event == NULL or from X with event != NULL.
|
||||
*
|
||||
*/
|
||||
static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
|
||||
/* re-draw the background */
|
||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_background.colorpixel});
|
||||
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect);
|
||||
/* draw background */
|
||||
draw_util_clear_surface(&bar, color_background);
|
||||
/* draw message */
|
||||
draw_util_text(prompt, &bar, color_text, color_background,
|
||||
MSG_PADDING, MSG_PADDING,
|
||||
bar.width - 2 * MSG_PADDING);
|
||||
|
||||
/* restore font color */
|
||||
set_font_colors(pixmap_gc, color_text, color_background);
|
||||
draw_text(prompt, pixmap, pixmap_gc, NULL,
|
||||
logical_px(4) + logical_px(4),
|
||||
logical_px(4) + logical_px(4),
|
||||
rect.width - logical_px(4) - logical_px(4));
|
||||
int position = bar.width - (MSG_PADDING - BTN_BORDER - BTN_PADDING);
|
||||
|
||||
/* render close button */
|
||||
const char *close_button_label = "X";
|
||||
int line_width = logical_px(4);
|
||||
/* set width to the width of the label */
|
||||
int w = predict_text_width(i3string_from_utf8(close_button_label));
|
||||
/* account for left/right padding, which seems to be set to 8px (total) below */
|
||||
w += logical_px(8);
|
||||
int y = rect.width;
|
||||
uint32_t values[3];
|
||||
values[0] = color_button_background.colorpixel;
|
||||
values[1] = line_width;
|
||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
|
||||
|
||||
xcb_rectangle_t close = {y - w - (2 * line_width), 0, w + (2 * line_width), rect.height};
|
||||
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
|
||||
|
||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border.colorpixel});
|
||||
xcb_point_t points[] = {
|
||||
{y - w - (2 * line_width), line_width / 2},
|
||||
{y - (line_width / 2), line_width / 2},
|
||||
{y - (line_width / 2), (rect.height - (line_width / 2)) - logical_px(2)},
|
||||
{y - w - (2 * line_width), (rect.height - (line_width / 2)) - logical_px(2)},
|
||||
{y - w - (2 * line_width), line_width / 2}};
|
||||
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points);
|
||||
|
||||
values[0] = 1;
|
||||
set_font_colors(pixmap_gc, color_text, color_button_background);
|
||||
/* the x term here seems to set left/right padding */
|
||||
draw_text_ascii(close_button_label, pixmap, pixmap_gc,
|
||||
y - w - line_width + w / 2 - logical_px(4),
|
||||
logical_px(4) + logical_px(3),
|
||||
rect.width - y + w + line_width - w / 2 + logical_px(4));
|
||||
y -= w;
|
||||
|
||||
y -= logical_px(20);
|
||||
position -= button_draw(&btn_close, position);
|
||||
position -= CLOSE_BTN_GAP;
|
||||
|
||||
/* render custom buttons */
|
||||
line_width = 1;
|
||||
for (int c = 0; c < buttoncnt; c++) {
|
||||
/* set w to the width of the label */
|
||||
w = predict_text_width(buttons[c].label);
|
||||
/* account for left/right padding, which seems to be set to 12px (total) below */
|
||||
w += logical_px(12);
|
||||
y -= logical_px(30);
|
||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_button_background.colorpixel});
|
||||
close = (xcb_rectangle_t){y - w - (2 * line_width), logical_px(2), w + (2 * line_width), rect.height - logical_px(6)};
|
||||
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &close);
|
||||
|
||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_border.colorpixel});
|
||||
buttons[c].x = y - w - (2 * line_width);
|
||||
buttons[c].width = w;
|
||||
xcb_point_t points2[] = {
|
||||
{y - w - (2 * line_width), (line_width / 2) + logical_px(2)},
|
||||
{y - (line_width / 2), (line_width / 2) + logical_px(2)},
|
||||
{y - (line_width / 2), (rect.height - logical_px(4) - (line_width / 2))},
|
||||
{y - w - (2 * line_width), (rect.height - logical_px(4) - (line_width / 2))},
|
||||
{y - w - (2 * line_width), (line_width / 2) + logical_px(2)}};
|
||||
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 5, points2);
|
||||
|
||||
values[0] = color_text.colorpixel;
|
||||
values[1] = color_button_background.colorpixel;
|
||||
set_font_colors(pixmap_gc, color_text, color_button_background);
|
||||
/* the x term seems to set left/right padding */
|
||||
draw_text(buttons[c].label, pixmap, pixmap_gc, NULL,
|
||||
y - w - line_width + logical_px(6),
|
||||
logical_px(4) + logical_px(3),
|
||||
rect.width - y + w + line_width - logical_px(6));
|
||||
|
||||
y -= w;
|
||||
for (int i = 0; i < buttoncnt; i++) {
|
||||
position -= BTN_GAP;
|
||||
position -= button_draw(&buttons[i], position);
|
||||
}
|
||||
|
||||
/* border line at the bottom */
|
||||
line_width = logical_px(2);
|
||||
values[0] = color_border_bottom.colorpixel;
|
||||
values[1] = line_width;
|
||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
|
||||
xcb_point_t bottom[] = {
|
||||
{0, rect.height - 0},
|
||||
{rect.width, rect.height - 0}};
|
||||
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 2, bottom);
|
||||
draw_util_rectangle(&bar, color_border_bottom, 0, bar.height - BAR_BORDER, bar.width, BAR_BORDER);
|
||||
|
||||
/* Copy the contents of the pixmap to the real window */
|
||||
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, rect.width, rect.height);
|
||||
xcb_flush(conn);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -301,7 +265,7 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
|
|||
*/
|
||||
static xcb_rectangle_t get_window_position(void) {
|
||||
/* Default values if we cannot determine the primary output or its CRTC info. */
|
||||
xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + logical_px(8) + logical_px(8)};
|
||||
xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + 2 * MSG_PADDING + BAR_BORDER};
|
||||
|
||||
xcb_randr_get_screen_resources_current_cookie_t rcookie = xcb_randr_get_screen_resources_current(conn, root);
|
||||
xcb_randr_get_output_primary_cookie_t pcookie = xcb_randr_get_output_primary(conn, root);
|
||||
|
@ -438,6 +402,8 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
}
|
||||
|
||||
btn_close.label = i3string_from_utf8("X");
|
||||
|
||||
int screens;
|
||||
if ((conn = xcb_connect(NULL, &screens)) == NULL ||
|
||||
xcb_connection_has_error(conn))
|
||||
|
@ -468,6 +434,7 @@ int main(int argc, char *argv[]) {
|
|||
color_border_bottom = draw_util_hex_to_color("#ab7100");
|
||||
}
|
||||
|
||||
init_dpi();
|
||||
font = load_font(pattern, true);
|
||||
set_font(&font);
|
||||
|
||||
|
@ -574,11 +541,8 @@ int main(int argc, char *argv[]) {
|
|||
12,
|
||||
&strut_partial);
|
||||
|
||||
/* Create pixmap */
|
||||
pixmap = xcb_generate_id(conn);
|
||||
pixmap_gc = xcb_generate_id(conn);
|
||||
xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font.height + logical_px(8));
|
||||
xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
|
||||
/* Initialize the drawable bar */
|
||||
draw_util_surface_init(conn, &bar, win, get_visualtype(root_screen), win_pos.width, win_pos.height);
|
||||
|
||||
/* Grab the keyboard to get all input */
|
||||
xcb_flush(conn);
|
||||
|
@ -595,7 +559,10 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
switch (type) {
|
||||
case XCB_EXPOSE:
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
};
|
||||
|
|
|
@ -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 */
|
||||
};
|
||||
|
|
|
@ -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 */
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -181,7 +181,7 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b
|
|||
uint32_t center_x = x - sep_offset;
|
||||
if (config.separator_symbol == NULL) {
|
||||
/* Draw a classic one pixel, vertical separator. */
|
||||
draw_util_rectangle(xcb_connection, &output->statusline_buffer, sep_fg,
|
||||
draw_util_rectangle(&output->statusline_buffer, sep_fg,
|
||||
center_x,
|
||||
logical_px(sep_voff_px),
|
||||
logical_px(1),
|
||||
|
@ -250,7 +250,7 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_color
|
|||
struct status_block *block;
|
||||
|
||||
color_t bar_color = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg);
|
||||
draw_util_clear_surface(xcb_connection, &output->statusline_buffer, bar_color);
|
||||
draw_util_clear_surface(&output->statusline_buffer, bar_color);
|
||||
|
||||
/* Use unsigned integer wraparound to clip off the left side.
|
||||
* For example, if clip_left is 75, then x will start at the very large
|
||||
|
@ -301,13 +301,13 @@ void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_color
|
|||
}
|
||||
|
||||
/* Draw the border. */
|
||||
draw_util_rectangle(xcb_connection, &output->statusline_buffer, border_color,
|
||||
draw_util_rectangle(&output->statusline_buffer, border_color,
|
||||
x, logical_px(1),
|
||||
full_render_width,
|
||||
bar_height - logical_px(2));
|
||||
|
||||
/* Draw the background. */
|
||||
draw_util_rectangle(xcb_connection, &output->statusline_buffer, bg_color,
|
||||
draw_util_rectangle(&output->statusline_buffer, bg_color,
|
||||
x + border_width,
|
||||
logical_px(1) + border_width,
|
||||
full_render_width - 2 * border_width,
|
||||
|
@ -531,7 +531,8 @@ void handle_button(xcb_button_press_event_t *event) {
|
|||
return;
|
||||
}
|
||||
switch (event->detail) {
|
||||
case 4:
|
||||
case XCB_BUTTON_SCROLL_UP:
|
||||
case XCB_BUTTON_SCROLL_LEFT:
|
||||
/* Mouse wheel up. We select the previous ws, if any.
|
||||
* If there is no more workspace, don’t even send the workspace
|
||||
* command, otherwise (with workspace auto_back_and_forth) we’d end
|
||||
|
@ -541,7 +542,8 @@ void handle_button(xcb_button_press_event_t *event) {
|
|||
|
||||
cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq);
|
||||
break;
|
||||
case 5:
|
||||
case XCB_BUTTON_SCROLL_DOWN:
|
||||
case XCB_BUTTON_SCROLL_RIGHT:
|
||||
/* Mouse wheel down. We select the next ws, if any.
|
||||
* If there is no more workspace, don’t even send the workspace
|
||||
* command, otherwise (with workspace auto_back_and_forth) we’d end
|
||||
|
@ -870,11 +872,13 @@ static void handle_destroy_notify(xcb_destroy_notify_event_t *event) {
|
|||
DLOG("checking output %s\n", walk->name);
|
||||
trayclient *trayclient;
|
||||
TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
|
||||
if (trayclient->win != event->window)
|
||||
if (trayclient->win != event->window) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DLOG("Removing tray client with window ID %08x\n", event->window);
|
||||
TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
|
||||
FREE(trayclient);
|
||||
|
||||
/* Trigger an update, we now have more space for the statusline */
|
||||
configure_trayclients();
|
||||
|
@ -1147,8 +1151,11 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
|
|||
handle_visibility_notify((xcb_visibility_notify_event_t *)event);
|
||||
break;
|
||||
case XCB_EXPOSE:
|
||||
if (((xcb_expose_event_t *)event)->count == 0) {
|
||||
/* Expose-events happen, when the window needs to be redrawn */
|
||||
redraw_bars();
|
||||
}
|
||||
|
||||
break;
|
||||
case XCB_BUTTON_PRESS:
|
||||
/* Button press events are mouse buttons clicked on one of our bars */
|
||||
|
@ -1177,6 +1184,7 @@ void xcb_chk_cb(struct ev_loop *loop, ev_check *watcher, int revents) {
|
|||
case XCB_CONFIGURE_REQUEST:
|
||||
/* ConfigureRequest, sent by a tray child */
|
||||
handle_configure_request((xcb_configure_request_event_t *)event);
|
||||
break;
|
||||
case XCB_RESIZE_REQUEST:
|
||||
/* ResizeRequest sent by a tray child using override_redirect. */
|
||||
handle_resize_request((xcb_resize_request_event_t *)event);
|
||||
|
@ -1247,6 +1255,12 @@ char *init_xcb_early() {
|
|||
ev_prepare_init(xcb_prep, &xcb_prep_cb);
|
||||
ev_check_init(xcb_chk, &xcb_chk_cb);
|
||||
|
||||
/* Within an event loop iteration, run the xcb_chk watcher last: other
|
||||
* watchers might call xcb_flush(), which, unexpectedly, can also read
|
||||
* events into the queue (see _xcb_conn_wait). Hence, we need to drain xcb’s
|
||||
* queue last, otherwise we risk dead-locking. */
|
||||
ev_set_priority(xcb_chk, EV_MINPRI);
|
||||
|
||||
ev_io_start(main_loop, xcb_io);
|
||||
ev_prepare_start(main_loop, xcb_prep);
|
||||
ev_check_start(main_loop, xcb_chk);
|
||||
|
@ -1558,6 +1572,7 @@ void kick_tray_clients(i3_output *output) {
|
|||
/* We remove the trayclient right here. We might receive an UnmapNotify
|
||||
* event afterwards, but better safe than sorry. */
|
||||
TAILQ_REMOVE(output->trayclients, trayclient, tailq);
|
||||
FREE(trayclient);
|
||||
}
|
||||
|
||||
/* Fake a DestroyNotify so that Qt re-adds tray icons.
|
||||
|
@ -1943,8 +1958,7 @@ void draw_bars(bool unhide) {
|
|||
bool use_focus_colors = output_has_focus(outputs_walk);
|
||||
|
||||
/* First things first: clear the backbuffer */
|
||||
draw_util_clear_surface(xcb_connection, &(outputs_walk->buffer),
|
||||
(use_focus_colors ? colors.focus_bar_bg : colors.bar_bg));
|
||||
draw_util_clear_surface(&(outputs_walk->buffer), (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg));
|
||||
|
||||
if (!config.disable_ws) {
|
||||
i3_ws *ws_walk;
|
||||
|
@ -1974,14 +1988,14 @@ void draw_bars(bool unhide) {
|
|||
}
|
||||
|
||||
/* Draw the border of the button. */
|
||||
draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), border_color,
|
||||
draw_util_rectangle(&(outputs_walk->buffer), border_color,
|
||||
workspace_width,
|
||||
logical_px(1),
|
||||
ws_walk->name_width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
|
||||
font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
|
||||
|
||||
/* Draw the inside of the button. */
|
||||
draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), bg_color,
|
||||
draw_util_rectangle(&(outputs_walk->buffer), bg_color,
|
||||
workspace_width + logical_px(1),
|
||||
2 * logical_px(1),
|
||||
ws_walk->name_width + 2 * logical_px(ws_hoff_px),
|
||||
|
@ -2004,13 +2018,13 @@ void draw_bars(bool unhide) {
|
|||
color_t fg_color = colors.binding_mode_fg;
|
||||
color_t bg_color = colors.binding_mode_bg;
|
||||
|
||||
draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), colors.binding_mode_border,
|
||||
draw_util_rectangle(&(outputs_walk->buffer), colors.binding_mode_border,
|
||||
workspace_width,
|
||||
logical_px(1),
|
||||
binding.width + 2 * logical_px(ws_hoff_px) + 2 * logical_px(1),
|
||||
font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
|
||||
|
||||
draw_util_rectangle(xcb_connection, &(outputs_walk->buffer), bg_color,
|
||||
draw_util_rectangle(&(outputs_walk->buffer), bg_color,
|
||||
workspace_width + logical_px(1),
|
||||
2 * logical_px(1),
|
||||
binding.width + 2 * logical_px(ws_hoff_px),
|
||||
|
@ -2046,7 +2060,7 @@ void draw_bars(bool unhide) {
|
|||
int x_dest = outputs_walk->rect.w - tray_width - logical_px(sb_hoff_px) - visible_statusline_width;
|
||||
|
||||
draw_statusline(outputs_walk, clip_left, use_focus_colors, use_short_text);
|
||||
draw_util_copy_surface(xcb_connection, &outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0,
|
||||
draw_util_copy_surface(&outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0,
|
||||
x_dest, 0, visible_statusline_width, (int16_t)bar_height);
|
||||
|
||||
outputs_walk->statusline_width = statusline_width;
|
||||
|
@ -2077,7 +2091,7 @@ void redraw_bars(void) {
|
|||
continue;
|
||||
}
|
||||
|
||||
draw_util_copy_surface(xcb_connection, &(outputs_walk->buffer), &(outputs_walk->bar), 0, 0,
|
||||
draw_util_copy_surface(&(outputs_walk->buffer), &(outputs_walk->bar), 0, 0,
|
||||
0, 0, outputs_walk->rect.w, outputs_walk->rect.h);
|
||||
xcb_flush(xcb_connection);
|
||||
}
|
||||
|
|
|
@ -31,3 +31,4 @@ xmacro(_NET_DESKTOP_NAMES)
|
|||
xmacro(_NET_DESKTOP_VIEWPORT)
|
||||
xmacro(_NET_ACTIVE_WINDOW)
|
||||
xmacro(_NET_CLOSE_WINDOW)
|
||||
xmacro(_NET_MOVERESIZE_WINDOW)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>'
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
typedef struct Config Config;
|
||||
typedef struct Barconfig Barconfig;
|
||||
extern char *current_configpath;
|
||||
extern char *current_config;
|
||||
extern Config config;
|
||||
extern SLIST_HEAD(modes_head, Mode) modes;
|
||||
extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;
|
||||
|
@ -68,7 +69,8 @@ struct Variable {
|
|||
char *value;
|
||||
char *next_match;
|
||||
|
||||
SLIST_ENTRY(Variable) variables;
|
||||
SLIST_ENTRY(Variable)
|
||||
variables;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -82,7 +84,8 @@ struct Mode {
|
|||
bool pango_markup;
|
||||
struct bindings_head *bindings;
|
||||
|
||||
SLIST_ENTRY(Mode) modes;
|
||||
SLIST_ENTRY(Mode)
|
||||
modes;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -154,6 +157,9 @@ struct Config {
|
|||
* is fetched once and never updated. */
|
||||
bool force_xinerama;
|
||||
|
||||
/** Don’t use RandR 1.5 for querying outputs. */
|
||||
bool disable_randr15;
|
||||
|
||||
/** Overwrites output detection (for testing), see src/fake_outputs.c */
|
||||
char *fake_outputs;
|
||||
|
||||
|
@ -253,7 +259,8 @@ struct Barconfig {
|
|||
/* List of outputs on which the tray is allowed to be shown, in order.
|
||||
* The special value "none" disables it (per default, it will be shown) and
|
||||
* the special value "primary" enabled it on the primary output. */
|
||||
TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs;
|
||||
TAILQ_HEAD(tray_outputs_head, tray_output_t)
|
||||
tray_outputs;
|
||||
|
||||
/* Padding around the tray icons. */
|
||||
int tray_padding;
|
||||
|
@ -284,7 +291,8 @@ struct Barconfig {
|
|||
M_MOD5 = 7
|
||||
} modifier;
|
||||
|
||||
TAILQ_HEAD(bar_bindings_head, Barbinding) bar_bindings;
|
||||
TAILQ_HEAD(bar_bindings_head, Barbinding)
|
||||
bar_bindings;
|
||||
|
||||
/** Bar position (bottom by default). */
|
||||
enum { P_BOTTOM = 0,
|
||||
|
@ -351,7 +359,8 @@ struct Barconfig {
|
|||
char *binding_mode_text;
|
||||
} colors;
|
||||
|
||||
TAILQ_ENTRY(Barconfig) configs;
|
||||
TAILQ_ENTRY(Barconfig)
|
||||
configs;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -366,13 +375,15 @@ struct Barbinding {
|
|||
/** The command which is to be executed for this button. */
|
||||
char *command;
|
||||
|
||||
TAILQ_ENTRY(Barbinding) bindings;
|
||||
TAILQ_ENTRY(Barbinding)
|
||||
bindings;
|
||||
};
|
||||
|
||||
struct tray_output_t {
|
||||
char *output;
|
||||
|
||||
TAILQ_ENTRY(tray_output_t) tray_outputs;
|
||||
TAILQ_ENTRY(tray_output_t)
|
||||
tray_outputs;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 *);
|
||||
|
|
|
@ -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);
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 { \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -69,22 +69,6 @@ extern unsigned int xcb_numlock_mask;
|
|||
xcb_window_t create_window(xcb_connection_t *conn, Rect r, uint16_t depth, xcb_visualid_t visual,
|
||||
uint16_t window_class, enum xcursor_cursor_t cursor, bool map, uint32_t mask, uint32_t *values);
|
||||
|
||||
/**
|
||||
* Draws a line from x,y to to_x,to_y using the given color
|
||||
*
|
||||
*/
|
||||
void xcb_draw_line(xcb_connection_t *conn, xcb_drawable_t drawable,
|
||||
xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x,
|
||||
uint32_t y, uint32_t to_x, uint32_t to_y);
|
||||
|
||||
/**
|
||||
* Draws a rectangle from x,y with width,height using the given color
|
||||
*
|
||||
*/
|
||||
void xcb_draw_rect(xcb_connection_t *conn, xcb_drawable_t drawable,
|
||||
xcb_gcontext_t gc, uint32_t colorpixel, uint32_t x,
|
||||
uint32_t y, uint32_t width, uint32_t height);
|
||||
|
||||
/**
|
||||
* Generates a configure_notify_event with absolute coordinates (relative to
|
||||
* the X root window, not to the client’s frame) for the given client.
|
||||
|
@ -98,12 +82,6 @@ void fake_absolute_configure_notify(Con *con);
|
|||
*/
|
||||
void send_take_focus(xcb_window_t window, xcb_timestamp_t timestamp);
|
||||
|
||||
/**
|
||||
* Raises the given window (typically client->frame) above all other windows
|
||||
*
|
||||
*/
|
||||
void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window);
|
||||
|
||||
/**
|
||||
* Configures the given window to have the size/position specified by given rect
|
||||
*
|
||||
|
@ -122,12 +100,6 @@ xcb_atom_t xcb_get_preferred_window_type(xcb_get_property_reply_t *reply);
|
|||
*/
|
||||
bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom);
|
||||
|
||||
/**
|
||||
* Moves the mouse pointer into the middle of rect.
|
||||
*
|
||||
*/
|
||||
void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect);
|
||||
|
||||
/**
|
||||
* Set the cursor of the root window to the given cursor id.
|
||||
* This function should only be used if xcursor_supported == false.
|
||||
|
|
12
libi3/dpi.c
12
libi3/dpi.c
|
@ -53,6 +53,10 @@ void init_dpi(void) {
|
|||
DLOG("Found Xft.dpi = %ld.\n", dpi);
|
||||
|
||||
init_dpi_end:
|
||||
if (resource != NULL) {
|
||||
free(resource);
|
||||
}
|
||||
|
||||
if (database != NULL) {
|
||||
xcb_xrm_database_free(database);
|
||||
}
|
||||
|
@ -64,6 +68,14 @@ init_dpi_end:
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function returns the value of the DPI setting.
|
||||
*
|
||||
*/
|
||||
long get_dpi_value(void) {
|
||||
return dpi;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI
|
||||
* screen) to a corresponding amount of physical pixels on a standard or retina
|
||||
|
|
|
@ -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);
|
||||
|
|
14
libi3/font.c
14
libi3/font.c
|
@ -24,24 +24,12 @@ static double pango_font_red;
|
|||
static double pango_font_green;
|
||||
static double pango_font_blue;
|
||||
|
||||
/* Necessary to track whether the dpi changes and trigger a LOG() message,
|
||||
* which is more easily visible to users. */
|
||||
static double logged_dpi = 0.0;
|
||||
|
||||
static PangoLayout *create_layout_with_dpi(cairo_t *cr) {
|
||||
PangoLayout *layout;
|
||||
PangoContext *context;
|
||||
|
||||
context = pango_cairo_create_context(cr);
|
||||
const double dpi = (double)root_screen->height_in_pixels * 25.4 /
|
||||
(double)root_screen->height_in_millimeters;
|
||||
if (logged_dpi != dpi) {
|
||||
logged_dpi = dpi;
|
||||
LOG("X11 root window dictates %f DPI\n", dpi);
|
||||
} else {
|
||||
DLOG("X11 root window dictates %f DPI\n", dpi);
|
||||
}
|
||||
pango_cairo_context_set_resolution(context, dpi);
|
||||
pango_cairo_context_set_resolution(context, get_dpi_value());
|
||||
layout = pango_layout_new(context);
|
||||
g_object_unref(context);
|
||||
|
||||
|
|
|
@ -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, ":");
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -29,7 +29,7 @@ It tries to start one of the following (in that order):
|
|||
* mg
|
||||
* jed
|
||||
* gedit
|
||||
* mc-edit
|
||||
* mcedit
|
||||
|
||||
Please don’t complain about the order: If the user has any preference, they will
|
||||
have $VISUAL or $EDITOR set.
|
||||
|
|
|
@ -40,6 +40,10 @@ It tries to start one of the following (in that order):
|
|||
* terminology
|
||||
* st
|
||||
* qterminal
|
||||
* lilyterm
|
||||
* tilix
|
||||
* terminix
|
||||
* konsole
|
||||
|
||||
Please don’t complain about the order: If the user has any preference, they will
|
||||
have $TERMINAL set or modified their i3 configuration file.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
153
src/bindings.c
153
src/bindings.c
|
@ -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 don’t
|
||||
* need to duplicate every key binding with an additional Mod2 specified.
|
||||
*/
|
||||
xkb_keysym_t sym = xkb_state_key_get_one_sym(dummy_state, bind->keycode);
|
||||
xkb_keysym_t sym_numlock = xkb_state_key_get_one_sym(dummy_state_numlock, bind->keycode);
|
||||
if (sym == sym_numlock) {
|
||||
/* Also bind the key with active NumLock */
|
||||
ADD_TRANSLATED_KEY(bind->keycode, bind->event_state_mask | xcb_numlock_mask);
|
||||
|
||||
/* Also bind the key with active NumLock+CapsLock */
|
||||
ADD_TRANSLATED_KEY(bind->keycode, bind->event_state_mask | xcb_numlock_mask | XCB_MOD_MASK_LOCK);
|
||||
} else {
|
||||
DLOG("Skipping automatic numlock fallback, key %d resolves to 0x%x with numlock\n",
|
||||
bind->keycode, sym_numlock);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We need to translate the symbol to a keycode */
|
||||
const xkb_keysym_t keysym = xkb_keysym_from_name(bind->symbol, XKB_KEYSYM_NO_FLAGS);
|
||||
if (keysym == XKB_KEY_NoSymbol) {
|
||||
ELOG("Could not translate string to key symbol: \"%s\"\n",
|
||||
bind->symbol);
|
||||
continue;
|
||||
}
|
||||
|
||||
struct resolve resolving = {
|
||||
.bind = bind,
|
||||
.keysym = keysym,
|
||||
|
@ -572,6 +616,8 @@ void translate_keysyms(void) {
|
|||
DLOG("state=0x%x, cfg=\"%s\", sym=0x%x → keycodes%s (%d)\n",
|
||||
bind->event_state_mask, bind->symbol, keysym, keycodes, num_keycodes);
|
||||
free(keycodes);
|
||||
|
||||
#undef ADD_TRANSLATED_KEY
|
||||
}
|
||||
|
||||
xkb_state_unref(dummy_state);
|
||||
|
@ -973,15 +1019,14 @@ int *bindings_get_buttons_to_grab(void) {
|
|||
if (bind->input_type != B_MOUSE || !bind->whole_window)
|
||||
continue;
|
||||
|
||||
char *endptr;
|
||||
long button = strtol(bind->symbol + (sizeof("button") - 1), &endptr, 10);
|
||||
if (button == LONG_MAX || button == LONG_MIN || button < 0 || *endptr != '\0' || endptr == bind->symbol) {
|
||||
long button;
|
||||
if (!parse_long(bind->symbol + (sizeof("button") - 1), &button, 10)) {
|
||||
ELOG("Could not parse button number, skipping this binding. Please report this bug in i3.\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Avoid duplicates. */
|
||||
for (int i = 0; i < num_max; i++) {
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (buffer[i] == button)
|
||||
continue;
|
||||
}
|
||||
|
|
28
src/click.c
28
src/click.c
|
@ -178,15 +178,15 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
|||
if (con->parent->type == CT_DOCKAREA)
|
||||
goto done;
|
||||
|
||||
const bool is_left_or_right_click = (event->detail == XCB_BUTTON_INDEX_1 ||
|
||||
event->detail == XCB_BUTTON_INDEX_3);
|
||||
const bool is_left_or_right_click = (event->detail == XCB_BUTTON_CLICK_LEFT ||
|
||||
event->detail == XCB_BUTTON_CLICK_RIGHT);
|
||||
|
||||
/* if the user has bound an action to this click, it should override the
|
||||
* default behavior. */
|
||||
if (dest == CLICK_DECORATION || dest == CLICK_INSIDE || dest == CLICK_BORDER) {
|
||||
Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event);
|
||||
|
||||
if (bind != NULL && (dest == CLICK_DECORATION ||
|
||||
if (bind != NULL && ((dest == CLICK_DECORATION && !bind->exclude_titlebar) ||
|
||||
(dest == CLICK_INSIDE && bind->whole_window) ||
|
||||
(dest == CLICK_BORDER && bind->border))) {
|
||||
CommandResult *result = run_binding(bind, con);
|
||||
|
@ -228,8 +228,10 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
|||
/* 1: see if the user scrolled on the decoration of a stacked/tabbed con */
|
||||
if (in_stacked &&
|
||||
dest == CLICK_DECORATION &&
|
||||
(event->detail == XCB_BUTTON_INDEX_4 ||
|
||||
event->detail == XCB_BUTTON_INDEX_5)) {
|
||||
(event->detail == XCB_BUTTON_SCROLL_UP ||
|
||||
event->detail == XCB_BUTTON_SCROLL_DOWN ||
|
||||
event->detail == XCB_BUTTON_SCROLL_LEFT ||
|
||||
event->detail == XCB_BUTTON_SCROLL_RIGHT)) {
|
||||
DLOG("Scrolling on a window decoration\n");
|
||||
orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ);
|
||||
/* Focus the currently focused container on the same level that the
|
||||
|
@ -244,10 +246,12 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
|||
* #557), we first check if scrolling is possible at all. */
|
||||
bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
|
||||
bool scroll_next_possible = (TAILQ_NEXT(focused, nodes) != NULL);
|
||||
if (event->detail == XCB_BUTTON_INDEX_4 && scroll_prev_possible)
|
||||
if ((event->detail == XCB_BUTTON_SCROLL_UP || event->detail == XCB_BUTTON_SCROLL_LEFT) && scroll_prev_possible) {
|
||||
tree_next('p', orientation);
|
||||
else if (event->detail == XCB_BUTTON_INDEX_5 && scroll_next_possible)
|
||||
} else if ((event->detail == XCB_BUTTON_SCROLL_DOWN || event->detail == XCB_BUTTON_SCROLL_RIGHT) && scroll_next_possible) {
|
||||
tree_next('n', orientation);
|
||||
}
|
||||
|
||||
goto done;
|
||||
}
|
||||
|
||||
|
@ -261,7 +265,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
|||
floating_raise_con(floatingcon);
|
||||
|
||||
/* 4: floating_modifier plus left mouse button drags */
|
||||
if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1) {
|
||||
if (mod_pressed && event->detail == XCB_BUTTON_CLICK_LEFT) {
|
||||
floating_drag_window(floatingcon, event);
|
||||
return 1;
|
||||
}
|
||||
|
@ -269,7 +273,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
|||
/* 5: resize (floating) if this was a (left or right) click on the
|
||||
* left/right/bottom border, or a right click on the decoration.
|
||||
* also try resizing (tiling) if it was a click on the top */
|
||||
if (mod_pressed && event->detail == XCB_BUTTON_INDEX_3) {
|
||||
if (mod_pressed && event->detail == XCB_BUTTON_CLICK_RIGHT) {
|
||||
DLOG("floating resize due to floatingmodifier\n");
|
||||
floating_resize_window(floatingcon, proportional, event);
|
||||
return 1;
|
||||
|
@ -283,7 +287,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
|||
goto done;
|
||||
}
|
||||
|
||||
if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_INDEX_3) {
|
||||
if (dest == CLICK_DECORATION && event->detail == XCB_BUTTON_CLICK_RIGHT) {
|
||||
DLOG("floating resize due to decoration right click\n");
|
||||
floating_resize_window(floatingcon, proportional, event);
|
||||
return 1;
|
||||
|
@ -298,7 +302,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
|||
/* 6: dragging, if this was a click on a decoration (which did not lead
|
||||
* to a resize) */
|
||||
if (!in_stacked && dest == CLICK_DECORATION &&
|
||||
(event->detail == XCB_BUTTON_INDEX_1)) {
|
||||
(event->detail == XCB_BUTTON_CLICK_LEFT)) {
|
||||
floating_drag_window(floatingcon, event);
|
||||
return 1;
|
||||
}
|
||||
|
@ -313,7 +317,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
|||
}
|
||||
|
||||
/* 7: floating modifier pressed, initiate a resize */
|
||||
if (dest == CLICK_INSIDE && mod_pressed && event->detail == XCB_BUTTON_INDEX_3) {
|
||||
if (dest == CLICK_INSIDE && mod_pressed && event->detail == XCB_BUTTON_CLICK_RIGHT) {
|
||||
if (floating_mod_on_tiled_client(con, event))
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -142,7 +142,9 @@ static Con *maybe_auto_back_and_forth_workspace(Con *workspace) {
|
|||
*/
|
||||
typedef struct owindow {
|
||||
Con *con;
|
||||
TAILQ_ENTRY(owindow) owindows;
|
||||
|
||||
TAILQ_ENTRY(owindow)
|
||||
owindows;
|
||||
} owindow;
|
||||
|
||||
typedef TAILQ_HEAD(owindows_head, owindow) owindows_head;
|
||||
|
@ -891,8 +893,10 @@ void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_a
|
|||
cmd_output->needs_tree_render = true;
|
||||
return;
|
||||
}
|
||||
if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, workspace->name))
|
||||
if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, workspace->name)) {
|
||||
ysuccess(true);
|
||||
return;
|
||||
}
|
||||
workspace_show(workspace);
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
|
@ -938,8 +942,10 @@ void cmd_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_
|
|||
}
|
||||
|
||||
DLOG("should switch to workspace %s\n", name);
|
||||
if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, name))
|
||||
if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, name)) {
|
||||
ysuccess(true);
|
||||
return;
|
||||
}
|
||||
workspace_show_by_name(name);
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
|
@ -1483,21 +1489,8 @@ void cmd_move_direction(I3_CMD, const char *direction, long move_px) {
|
|||
void cmd_layout(I3_CMD, const char *layout_str) {
|
||||
HANDLE_EMPTY_MATCH;
|
||||
|
||||
if (strcmp(layout_str, "stacking") == 0)
|
||||
layout_str = "stacked";
|
||||
layout_t layout;
|
||||
/* default is a special case which will be handled in con_set_layout(). */
|
||||
if (strcmp(layout_str, "default") == 0)
|
||||
layout = L_DEFAULT;
|
||||
else if (strcmp(layout_str, "stacked") == 0)
|
||||
layout = L_STACKED;
|
||||
else if (strcmp(layout_str, "tabbed") == 0)
|
||||
layout = L_TABBED;
|
||||
else if (strcmp(layout_str, "splitv") == 0)
|
||||
layout = L_SPLITV;
|
||||
else if (strcmp(layout_str, "splith") == 0)
|
||||
layout = L_SPLITH;
|
||||
else {
|
||||
if (!layout_from_name(layout_str, &layout)) {
|
||||
ELOG("Unknown layout \"%s\", this is a mismatch between code and parser spec.\n", layout_str);
|
||||
return;
|
||||
}
|
||||
|
@ -1556,7 +1549,7 @@ void cmd_exit(I3_CMD) {
|
|||
#ifdef I3_ASAN_ENABLED
|
||||
__lsan_do_leak_check();
|
||||
#endif
|
||||
ipc_shutdown();
|
||||
ipc_shutdown(SHUTDOWN_REASON_EXIT);
|
||||
unlink(config.ipc_socket_path);
|
||||
xcb_disconnect(conn);
|
||||
exit(0);
|
||||
|
@ -1589,7 +1582,7 @@ void cmd_reload(I3_CMD) {
|
|||
*/
|
||||
void cmd_restart(I3_CMD) {
|
||||
LOG("restarting i3\n");
|
||||
ipc_shutdown();
|
||||
ipc_shutdown(SHUTDOWN_REASON_RESTART);
|
||||
unlink(config.ipc_socket_path);
|
||||
/* We need to call this manually since atexit handlers don’t get called
|
||||
* when exec()ing */
|
||||
|
@ -1819,6 +1812,65 @@ void cmd_scratchpad_show(I3_CMD) {
|
|||
ysuccess(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation of 'swap [container] [with] id|con_id|mark <arg>'.
|
||||
*
|
||||
*/
|
||||
void cmd_swap(I3_CMD, const char *mode, const char *arg) {
|
||||
HANDLE_EMPTY_MATCH;
|
||||
|
||||
owindow *match = TAILQ_FIRST(&owindows);
|
||||
if (match == NULL) {
|
||||
DLOG("No match found for swapping.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Con *con;
|
||||
if (strcmp(mode, "id") == 0) {
|
||||
long target;
|
||||
if (!parse_long(arg, &target, 0)) {
|
||||
yerror("Failed to parse %s into a window id.\n", arg);
|
||||
return;
|
||||
}
|
||||
|
||||
con = con_by_window_id(target);
|
||||
} else if (strcmp(mode, "con_id") == 0) {
|
||||
long target;
|
||||
if (!parse_long(arg, &target, 0)) {
|
||||
yerror("Failed to parse %s into a container id.\n", arg);
|
||||
return;
|
||||
}
|
||||
|
||||
con = (Con *)target;
|
||||
} else if (strcmp(mode, "mark") == 0) {
|
||||
con = con_by_mark(arg);
|
||||
} else {
|
||||
yerror("Unhandled swap mode \"%s\". This is a bug.\n", mode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (con == NULL) {
|
||||
yerror("Could not find container for %s = %s\n", mode, arg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (match == TAILQ_LAST(&owindows, owindows_head)) {
|
||||
DLOG("More than one container matched the swap command, only using the first one.");
|
||||
}
|
||||
|
||||
if (match->con == NULL) {
|
||||
DLOG("Match %p has no container.\n", match);
|
||||
ysuccess(false);
|
||||
return;
|
||||
}
|
||||
|
||||
DLOG("Swapping %p with %p.\n", match->con, con);
|
||||
bool result = con_swap(match->con, con);
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
ysuccess(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation of 'title_format <format>'
|
||||
*
|
||||
|
|
304
src/con.c
304
src/con.c
|
@ -22,9 +22,11 @@ static void con_on_remove_child(Con *con);
|
|||
void con_force_split_parents_redraw(Con *con) {
|
||||
Con *parent = con;
|
||||
|
||||
while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) {
|
||||
if (!con_is_leaf(parent))
|
||||
while (parent != NULL && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) {
|
||||
if (!con_is_leaf(parent)) {
|
||||
FREE(parent->deco_render_params);
|
||||
}
|
||||
|
||||
parent = parent->parent;
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +147,7 @@ static void _con_attach(Con *con, Con *parent, Con *previous, bool ignore_focus)
|
|||
|
||||
/* Insert the container after the tiling container, if found.
|
||||
* When adding to a CT_OUTPUT, just append one after another. */
|
||||
if (current && parent->type != CT_OUTPUT) {
|
||||
if (current != NULL && parent->type != CT_OUTPUT) {
|
||||
DLOG("Inserting con = %p after con %p\n", con, current);
|
||||
TAILQ_INSERT_AFTER(nodes_head, current, con, nodes);
|
||||
} else
|
||||
|
@ -410,7 +412,8 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation) {
|
|||
struct bfs_entry {
|
||||
Con *con;
|
||||
|
||||
TAILQ_ENTRY(bfs_entry) entries;
|
||||
TAILQ_ENTRY(bfs_entry)
|
||||
entries;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -422,7 +425,9 @@ Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode) {
|
|||
|
||||
/* TODO: is breadth-first-search really appropriate? (check as soon as
|
||||
* fullscreen levels and fullscreen for containers is implemented) */
|
||||
TAILQ_HEAD(bfs_head, bfs_entry) bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head);
|
||||
TAILQ_HEAD(bfs_head, bfs_entry)
|
||||
bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head);
|
||||
|
||||
struct bfs_entry *entry = smalloc(sizeof(struct bfs_entry));
|
||||
entry->con = con;
|
||||
TAILQ_INSERT_TAIL(&bfs_head, entry, entries);
|
||||
|
@ -522,6 +527,23 @@ bool con_inside_focused(Con *con) {
|
|||
return con_inside_focused(con->parent);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if the container has the given parent as an actual parent.
|
||||
*
|
||||
*/
|
||||
bool con_has_parent(Con *con, Con *parent) {
|
||||
Con *current = con->parent;
|
||||
if (current == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current == parent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return con_has_parent(current, parent);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the container with the given client window ID or NULL if no such
|
||||
* container exists.
|
||||
|
@ -800,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, don’t move focus away if the target workspace is
|
||||
|
@ -1078,8 +1105,13 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
|
|||
/* Descend focus stack in case focus_next is a workspace which can
|
||||
* occur if we move to the same workspace. Also show current workspace
|
||||
* to ensure it is focused. */
|
||||
if (!ignore_focus)
|
||||
if (!ignore_focus) {
|
||||
workspace_show(current_ws);
|
||||
if (dont_warp) {
|
||||
DLOG("x_set_warp_to(NULL) because dont_warp is set\n");
|
||||
x_set_warp_to(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set focus only if con was on current workspace before moving.
|
||||
* Otherwise we would give focus to some window on different workspace. */
|
||||
|
@ -1124,6 +1156,9 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi
|
|||
con_set_urgency(con, true);
|
||||
}
|
||||
|
||||
/* Ensure the container will be redrawn. */
|
||||
FREE(con->deco_render_params);
|
||||
|
||||
CALL(parent, on_remove_child);
|
||||
|
||||
ipc_send_window_event("move", con);
|
||||
|
@ -1163,12 +1198,12 @@ bool con_move_to_mark(Con *con, const char *mark) {
|
|||
target = TAILQ_FIRST(&(target->focus_head));
|
||||
}
|
||||
|
||||
if (con == target || con == target->parent) {
|
||||
if (con == target || con_has_parent(target, con)) {
|
||||
DLOG("cannot move the container to or inside itself, aborting.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return _con_move_to_con(con, target, false, true, false, false);
|
||||
return _con_move_to_con(con, target, false, true, false, false, true);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1201,7 +1236,7 @@ void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool
|
|||
}
|
||||
|
||||
Con *target = con_descend_focused(workspace);
|
||||
_con_move_to_con(con, target, true, fix_coordinates, dont_warp, ignore_focus);
|
||||
_con_move_to_con(con, target, true, fix_coordinates, dont_warp, ignore_focus, true);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1308,15 +1343,16 @@ Con *con_next_focused(Con *con) {
|
|||
} else {
|
||||
/* try to focus the next container on the same level as this one or fall
|
||||
* back to its parent */
|
||||
if (!(next = TAILQ_NEXT(con, focused)))
|
||||
if (!(next = TAILQ_NEXT(con, focused))) {
|
||||
next = con->parent;
|
||||
}
|
||||
}
|
||||
|
||||
/* now go down the focus stack as far as
|
||||
* possible, excluding the current container */
|
||||
while (!TAILQ_EMPTY(&(next->focus_head)) &&
|
||||
TAILQ_FIRST(&(next->focus_head)) != con)
|
||||
while (!TAILQ_EMPTY(&(next->focus_head)) && TAILQ_FIRST(&(next->focus_head)) != con) {
|
||||
next = TAILQ_FIRST(&(next->focus_head));
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
@ -1636,12 +1672,14 @@ void con_set_layout(Con *con, layout_t layout) {
|
|||
* whole workspace into stacked/tabbed mode. To do this and still allow
|
||||
* intuitive operations (like level-up and then opening a new window), we
|
||||
* need to create a new split container. */
|
||||
if (con->type == CT_WORKSPACE &&
|
||||
(layout == L_STACKED || layout == L_TABBED)) {
|
||||
if (con->type == CT_WORKSPACE) {
|
||||
if (con_num_children(con) == 0) {
|
||||
DLOG("Setting workspace_layout to %d\n", layout);
|
||||
con->workspace_layout = layout;
|
||||
} else {
|
||||
layout_t ws_layout = (layout == L_STACKED || layout == L_TABBED) ? layout : L_DEFAULT;
|
||||
DLOG("Setting workspace_layout to %d\n", ws_layout);
|
||||
con->workspace_layout = ws_layout;
|
||||
DLOG("Setting layout to %d\n", layout);
|
||||
con->layout = layout;
|
||||
} else if (layout == L_STACKED || layout == L_TABBED) {
|
||||
DLOG("Creating new split container\n");
|
||||
/* 1: create a new split container */
|
||||
Con *new = con_new(NULL, NULL);
|
||||
|
@ -1716,28 +1754,64 @@ void con_toggle_layout(Con *con, const char *toggle_mode) {
|
|||
parent = con->parent;
|
||||
DLOG("con_toggle_layout(%p, %s), parent = %p\n", con, toggle_mode, parent);
|
||||
|
||||
if (strcmp(toggle_mode, "split") == 0) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
245
src/debug.c
245
src/debug.c
|
@ -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;
|
||||
}
|
|
@ -182,4 +182,7 @@ void display_running_version(void) {
|
|||
#endif
|
||||
|
||||
yajl_free(handle);
|
||||
free(reply);
|
||||
free(pid_from_atom);
|
||||
free(socket_path);
|
||||
}
|
||||
|
|
|
@ -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 GIMP’s color picker window) get mapped
|
||||
* to (0, 0), so we push them to a reasonable position
|
||||
* (centered over their leader) */
|
||||
|
@ -280,9 +309,6 @@ void floating_enable(Con *con, bool automatic) {
|
|||
|
||||
DLOG("Corrected y = %d (deco_height = %d)\n", nc->rect.y, deco_height);
|
||||
|
||||
TAILQ_INSERT_TAIL(&(nc->nodes_head), con, nodes);
|
||||
TAILQ_INSERT_TAIL(&(nc->focus_head), con, focused);
|
||||
|
||||
/* render the cons to get initial window_rect correct */
|
||||
render_con(nc, false);
|
||||
render_con(con, false);
|
||||
|
|
184
src/handlers.c
184
src/handlers.c
|
@ -400,11 +400,52 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
|
|||
DLOG("Dock client will not be moved, we only support moving it to another output.\n");
|
||||
}
|
||||
}
|
||||
fake_absolute_configure_notify(con);
|
||||
return;
|
||||
}
|
||||
|
||||
fake_absolute_configure_notify(con);
|
||||
if (event->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) {
|
||||
DLOG("window 0x%08x wants to be stacked %d\n", event->window, event->stack_mode);
|
||||
|
||||
return;
|
||||
/* Emacs and IntelliJ Idea “request focus” by stacking their window
|
||||
* above all others. */
|
||||
if (event->stack_mode != XCB_STACK_MODE_ABOVE) {
|
||||
DLOG("stack_mode != XCB_STACK_MODE_ABOVE, ignoring ConfigureRequest\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (fullscreen || !con_is_leaf(con)) {
|
||||
DLOG("fullscreen or not a leaf, ignoring ConfigureRequest\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
Con *ws = con_get_workspace(con);
|
||||
if (ws == NULL) {
|
||||
DLOG("Window is not being managed, ignoring ConfigureRequest\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (strcmp(ws->name, "__i3_scratch") == 0) {
|
||||
DLOG("This is a scratchpad container, ignoring ConfigureRequest\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) {
|
||||
DLOG("Focusing con = %p\n", con);
|
||||
workspace_show(ws);
|
||||
con_focus(con);
|
||||
tree_render();
|
||||
} else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) {
|
||||
DLOG("Marking con = %p urgent\n", con);
|
||||
con_set_urgency(con, true);
|
||||
tree_render();
|
||||
} else {
|
||||
DLOG("Ignoring request for con = %p.\n", con);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
fake_absolute_configure_notify(con);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -614,12 +655,9 @@ static void handle_expose_event(xcb_expose_event_t *event) {
|
|||
}
|
||||
|
||||
/* Since we render to our surface on every change anyways, expose events
|
||||
* only tell us that the X server lost (parts of) the window contents. We
|
||||
* can handle that by copying the appropriate part from our surface to the
|
||||
* window. */
|
||||
draw_util_copy_surface(conn, &(parent->frame_buffer), &(parent->frame),
|
||||
event->x, event->y, event->x, event->y,
|
||||
event->width, event->height);
|
||||
* only tell us that the X server lost (parts of) the window contents. */
|
||||
draw_util_copy_surface(&(parent->frame_buffer), &(parent->frame),
|
||||
0, 0, 0, 0, parent->rect.width, parent->rect.height);
|
||||
xcb_flush(conn);
|
||||
return;
|
||||
}
|
||||
|
@ -637,6 +675,11 @@ static void handle_expose_event(xcb_expose_event_t *event) {
|
|||
#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */
|
||||
#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */
|
||||
|
||||
#define _NET_MOVERESIZE_WINDOW_X (1 << 8)
|
||||
#define _NET_MOVERESIZE_WINDOW_Y (1 << 9)
|
||||
#define _NET_MOVERESIZE_WINDOW_WIDTH (1 << 10)
|
||||
#define _NET_MOVERESIZE_WINDOW_HEIGHT (1 << 11)
|
||||
|
||||
/*
|
||||
* Handle client messages (EWMH)
|
||||
*
|
||||
|
@ -897,6 +940,35 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
|||
DLOG("_NET_WM_MOVERESIZE direction %d not implemented\n", direction);
|
||||
break;
|
||||
}
|
||||
} else if (event->type == A__NET_MOVERESIZE_WINDOW) {
|
||||
DLOG("Received _NET_MOVE_RESIZE_WINDOW. Handling by faking a configure request.\n");
|
||||
|
||||
void *_generated_event = scalloc(32, 1);
|
||||
xcb_configure_request_event_t *generated_event = _generated_event;
|
||||
|
||||
generated_event->window = event->window;
|
||||
generated_event->response_type = XCB_CONFIGURE_REQUEST;
|
||||
|
||||
generated_event->value_mask = 0;
|
||||
if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_X) {
|
||||
generated_event->value_mask |= XCB_CONFIG_WINDOW_X;
|
||||
generated_event->x = event->data.data32[1];
|
||||
}
|
||||
if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_Y) {
|
||||
generated_event->value_mask |= XCB_CONFIG_WINDOW_Y;
|
||||
generated_event->y = event->data.data32[2];
|
||||
}
|
||||
if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_WIDTH) {
|
||||
generated_event->value_mask |= XCB_CONFIG_WINDOW_WIDTH;
|
||||
generated_event->width = event->data.data32[3];
|
||||
}
|
||||
if (event->data.data32[0] & _NET_MOVERESIZE_WINDOW_HEIGHT) {
|
||||
generated_event->value_mask |= XCB_CONFIG_WINDOW_HEIGHT;
|
||||
generated_event->height = event->data.data32[4];
|
||||
}
|
||||
|
||||
handle_configure_request(generated_event);
|
||||
FREE(generated_event);
|
||||
} else {
|
||||
DLOG("Skipping client message for unhandled type %d\n", event->type);
|
||||
}
|
||||
|
@ -929,54 +1001,72 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
|
|||
|
||||
xcb_size_hints_t size_hints;
|
||||
|
||||
//CLIENT_LOG(client);
|
||||
|
||||
/* If the hints were already in this event, use them, if not, request them */
|
||||
if (reply != NULL)
|
||||
if (reply != NULL) {
|
||||
xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply);
|
||||
else
|
||||
} else {
|
||||
xcb_icccm_get_wm_normal_hints_reply(conn, xcb_icccm_get_wm_normal_hints_unchecked(conn, con->window->id), &size_hints, NULL);
|
||||
}
|
||||
|
||||
int win_width = con->window_rect.width;
|
||||
int win_height = con->window_rect.height;
|
||||
|
||||
if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) {
|
||||
// TODO: Minimum size is not yet implemented
|
||||
DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
|
||||
|
||||
con->window->min_width = size_hints.min_width;
|
||||
con->window->min_height = size_hints.min_height;
|
||||
}
|
||||
|
||||
if (con_is_floating(con)) {
|
||||
win_width = MAX(win_width, con->window->min_width);
|
||||
win_height = MAX(win_height, con->window->min_height);
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) {
|
||||
if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF)
|
||||
if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) {
|
||||
if (con->window->width_increment != size_hints.width_inc) {
|
||||
con->window->width_increment = size_hints.width_inc;
|
||||
changed = true;
|
||||
}
|
||||
if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF)
|
||||
}
|
||||
|
||||
if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF) {
|
||||
if (con->window->height_increment != size_hints.height_inc) {
|
||||
con->window->height_increment = size_hints.height_inc;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
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;
|
||||
|
|
59
src/ipc.c
59
src/ipc.c
|
@ -22,7 +22,8 @@
|
|||
|
||||
char *current_socketpath = NULL;
|
||||
|
||||
TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
|
||||
TAILQ_HEAD(ipc_client_head, ipc_client)
|
||||
all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
|
||||
|
||||
/*
|
||||
* Puts the given socket file descriptor into non-blocking mode or dies if
|
||||
|
@ -61,11 +62,39 @@ void ipc_send_event(const char *event, uint32_t message_type, const char *payloa
|
|||
}
|
||||
|
||||
/*
|
||||
* Calls shutdown() on each socket and closes it. This function to be called
|
||||
* For shutdown events, we send the reason for the shutdown.
|
||||
*/
|
||||
static void ipc_send_shutdown_event(shutdown_reason_t reason) {
|
||||
yajl_gen gen = ygenalloc();
|
||||
y(map_open);
|
||||
|
||||
ystr("change");
|
||||
|
||||
if (reason == SHUTDOWN_REASON_RESTART) {
|
||||
ystr("restart");
|
||||
} else if (reason == SHUTDOWN_REASON_EXIT) {
|
||||
ystr("exit");
|
||||
}
|
||||
|
||||
y(map_close);
|
||||
|
||||
const unsigned char *payload;
|
||||
ylength length;
|
||||
|
||||
y(get_buf, &payload, &length);
|
||||
ipc_send_event("shutdown", I3_IPC_EVENT_SHUTDOWN, (const char *)payload);
|
||||
|
||||
y(free);
|
||||
}
|
||||
|
||||
/*
|
||||
* Calls shutdown() on each socket and closes it. This function is to be called
|
||||
* when exiting or restarting only!
|
||||
*
|
||||
*/
|
||||
void ipc_shutdown(void) {
|
||||
void ipc_shutdown(shutdown_reason_t reason) {
|
||||
ipc_send_shutdown_event(reason);
|
||||
|
||||
ipc_client *current;
|
||||
while (!TAILQ_EMPTY(&all_clients)) {
|
||||
current = TAILQ_FIRST(&all_clients);
|
||||
|
@ -1058,9 +1087,30 @@ IPC_HANDLER(subscribe) {
|
|||
ipc_send_message(fd, strlen(reply), I3_IPC_REPLY_TYPE_SUBSCRIBE, (const uint8_t *)reply);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the raw last loaded i3 configuration file contents.
|
||||
*/
|
||||
IPC_HANDLER(get_config) {
|
||||
yajl_gen gen = ygenalloc();
|
||||
|
||||
y(map_open);
|
||||
|
||||
ystr("config");
|
||||
ystr(current_config);
|
||||
|
||||
y(map_close);
|
||||
|
||||
const unsigned char *payload;
|
||||
ylength length;
|
||||
y(get_buf, &payload, &length);
|
||||
|
||||
ipc_send_message(fd, length, I3_IPC_REPLY_TYPE_CONFIG, payload);
|
||||
y(free);
|
||||
}
|
||||
|
||||
/* The index of each callback function corresponds to the numeric
|
||||
* value of the message type (see include/i3/ipc.h) */
|
||||
handler_t handlers[9] = {
|
||||
handler_t handlers[10] = {
|
||||
handle_command,
|
||||
handle_get_workspaces,
|
||||
handle_subscribe,
|
||||
|
@ -1070,6 +1120,7 @@ handler_t handlers[9] = {
|
|||
handle_get_bar_config,
|
||||
handle_get_version,
|
||||
handle_get_binding_modes,
|
||||
handle_get_config,
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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};
|
||||
|
|
16
src/main.c
16
src/main.c
|
@ -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
|
||||
|
|
|
@ -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/…
|
||||
|
|
16
src/match.c
16
src/match.c
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
210
src/randr.c
210
src/randr.c
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
337
src/sighandler.c
337
src/sighandler.c
|
@ -3,10 +3,6 @@
|
|||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
* © 2009 Jan-Erik Rediger
|
||||
*
|
||||
* sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers
|
||||
* to restart inplace).
|
||||
*
|
||||
*/
|
||||
#include "all.h"
|
||||
|
@ -20,28 +16,44 @@
|
|||
|
||||
#include <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) {
|
||||
|
|
12
src/tree.c
12
src/tree.c
|
@ -329,6 +329,12 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par
|
|||
match_free(match);
|
||||
free(match);
|
||||
}
|
||||
while (!TAILQ_EMPTY(&(con->marks_head))) {
|
||||
mark_t *mark = TAILQ_FIRST(&(con->marks_head));
|
||||
TAILQ_REMOVE(&(con->marks_head), mark, marks);
|
||||
FREE(mark->name);
|
||||
FREE(mark);
|
||||
}
|
||||
free(con);
|
||||
|
||||
/* in the case of floating windows, we already focused another container
|
||||
|
@ -377,7 +383,11 @@ void tree_split(Con *con, orientation_t orientation) {
|
|||
|
||||
if (con->type == CT_WORKSPACE) {
|
||||
if (con_num_children(con) < 2) {
|
||||
DLOG("Just changing orientation of workspace\n");
|
||||
if (con_num_children(con) == 0) {
|
||||
DLOG("Changing workspace_layout to L_DEFAULT\n");
|
||||
con->workspace_layout = L_DEFAULT;
|
||||
}
|
||||
DLOG("Changing orientation of workspace\n");
|
||||
con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
|
||||
return;
|
||||
} else {
|
||||
|
|
47
src/util.c
47
src/util.c
|
@ -66,6 +66,34 @@ __attribute__((pure)) bool name_is_digits(const char *name) {
|
|||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set 'out' to the layout_t value for the given layout. The function
|
||||
* returns true on success or false if the passed string is not a valid
|
||||
* layout name.
|
||||
*
|
||||
*/
|
||||
bool layout_from_name(const char *layout_str, layout_t *out) {
|
||||
if (strcmp(layout_str, "default") == 0) {
|
||||
*out = L_DEFAULT;
|
||||
return true;
|
||||
} else if (strcasecmp(layout_str, "stacked") == 0 ||
|
||||
strcasecmp(layout_str, "stacking") == 0) {
|
||||
*out = L_STACKED;
|
||||
return true;
|
||||
} else if (strcasecmp(layout_str, "tabbed") == 0) {
|
||||
*out = L_TABBED;
|
||||
return true;
|
||||
} else if (strcasecmp(layout_str, "splitv") == 0) {
|
||||
*out = L_SPLITV;
|
||||
return true;
|
||||
} else if (strcasecmp(layout_str, "splith") == 0) {
|
||||
*out = L_SPLITH;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parses the workspace name as a number. Returns -1 if the workspace should be
|
||||
* interpreted as a "named workspace".
|
||||
|
@ -259,7 +287,7 @@ void i3_restart(bool forget_layout) {
|
|||
|
||||
restore_geometry();
|
||||
|
||||
ipc_shutdown();
|
||||
ipc_shutdown(SHUTDOWN_REASON_RESTART);
|
||||
|
||||
LOG("restarting \"%s\"...\n", start_argv[0]);
|
||||
/* make sure -a is in the argument list or add it */
|
||||
|
@ -430,3 +458,20 @@ void kill_nagbar(pid_t *nagbar_pid, bool wait_for_it) {
|
|||
* waitpid() here. */
|
||||
waitpid(*nagbar_pid, NULL, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Converts a string into a long using strtol().
|
||||
* This is a convenience wrapper checking the parsing result. It returns true
|
||||
* if the number could be parsed.
|
||||
*/
|
||||
bool parse_long(const char *str, long *out, int base) {
|
||||
char *end;
|
||||
long result = strtol(str, &end, base);
|
||||
if (result == LONG_MIN || result == LONG_MAX || result < 0 || (end != NULL && *end != '\0')) {
|
||||
*out = result;
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = result;
|
||||
return true;
|
||||
}
|
||||
|
|
76
src/x.c
76
src/x.c
|
@ -58,18 +58,26 @@ typedef struct con_state {
|
|||
|
||||
char *name;
|
||||
|
||||
CIRCLEQ_ENTRY(con_state) state;
|
||||
CIRCLEQ_ENTRY(con_state) old_state;
|
||||
TAILQ_ENTRY(con_state) initial_mapping_order;
|
||||
CIRCLEQ_ENTRY(con_state)
|
||||
state;
|
||||
|
||||
CIRCLEQ_ENTRY(con_state)
|
||||
old_state;
|
||||
|
||||
TAILQ_ENTRY(con_state)
|
||||
initial_mapping_order;
|
||||
} con_state;
|
||||
|
||||
CIRCLEQ_HEAD(state_head, con_state) state_head =
|
||||
CIRCLEQ_HEAD(state_head, con_state)
|
||||
state_head =
|
||||
CIRCLEQ_HEAD_INITIALIZER(state_head);
|
||||
|
||||
CIRCLEQ_HEAD(old_state_head, con_state) old_state_head =
|
||||
CIRCLEQ_HEAD(old_state_head, con_state)
|
||||
old_state_head =
|
||||
CIRCLEQ_HEAD_INITIALIZER(old_state_head);
|
||||
|
||||
TAILQ_HEAD(initial_mapping_head, con_state) initial_mapping_head =
|
||||
TAILQ_HEAD(initial_mapping_head, con_state)
|
||||
initial_mapping_head =
|
||||
TAILQ_HEAD_INITIALIZER(initial_mapping_head);
|
||||
|
||||
/*
|
||||
|
@ -322,10 +330,10 @@ static void x_draw_title_border(Con *con, struct deco_render_params *p) {
|
|||
deco_diff_r = 0;
|
||||
}
|
||||
|
||||
draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border,
|
||||
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
|
||||
dr->x, dr->y, dr->width, 1);
|
||||
|
||||
draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border,
|
||||
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
|
||||
dr->x + deco_diff_l, dr->y + dr->height - 1, dr->width - (deco_diff_l + deco_diff_r), 1);
|
||||
}
|
||||
|
||||
|
@ -341,7 +349,7 @@ static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p
|
|||
/* We actually only redraw the far right two pixels as that is the
|
||||
* distance we keep from the edge (not the entire border width).
|
||||
* Redrawing the entire border would cause text to be cut off. */
|
||||
draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->background,
|
||||
draw_util_rectangle(&(con->parent->frame_buffer), p->color->background,
|
||||
dr->x + dr->width - 2 * logical_px(1),
|
||||
dr->y,
|
||||
2 * logical_px(1),
|
||||
|
@ -352,11 +360,11 @@ static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p
|
|||
* be easily distinguished. */
|
||||
if (con->parent->layout == L_TABBED) {
|
||||
/* Left side */
|
||||
draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border,
|
||||
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
|
||||
dr->x, dr->y, 1, dr->height);
|
||||
|
||||
/* Right side */
|
||||
draw_util_rectangle(conn, &(con->parent->frame_buffer), p->color->border,
|
||||
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
|
||||
dr->x + dr->width - 1, dr->y, 1, dr->height);
|
||||
}
|
||||
|
||||
|
@ -450,16 +458,16 @@ void x_draw_decoration(Con *con) {
|
|||
/* 2: draw the client.background, but only for the parts around the window_rect */
|
||||
if (con->window != NULL) {
|
||||
/* top area */
|
||||
draw_util_rectangle(conn, &(con->frame_buffer), config.client.background,
|
||||
draw_util_rectangle(&(con->frame_buffer), config.client.background,
|
||||
0, 0, r->width, w->y);
|
||||
/* bottom area */
|
||||
draw_util_rectangle(conn, &(con->frame_buffer), config.client.background,
|
||||
draw_util_rectangle(&(con->frame_buffer), config.client.background,
|
||||
0, w->y + w->height, r->width, r->height - (w->y + w->height));
|
||||
/* left area */
|
||||
draw_util_rectangle(conn, &(con->frame_buffer), config.client.background,
|
||||
draw_util_rectangle(&(con->frame_buffer), config.client.background,
|
||||
0, 0, w->x, r->height);
|
||||
/* right area */
|
||||
draw_util_rectangle(conn, &(con->frame_buffer), config.client.background,
|
||||
draw_util_rectangle(&(con->frame_buffer), config.client.background,
|
||||
w->x + w->width, 0, r->width - (w->x + w->width), r->height);
|
||||
}
|
||||
|
||||
|
@ -476,21 +484,21 @@ void x_draw_decoration(Con *con) {
|
|||
* rectangle because some childs are not freely resizable and we want
|
||||
* their background color to "shine through". */
|
||||
if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) {
|
||||
draw_util_rectangle(conn, &(con->frame_buffer), p->color->child_border, 0, 0, br.x, r->height);
|
||||
draw_util_rectangle(&(con->frame_buffer), p->color->child_border, 0, 0, br.x, r->height);
|
||||
}
|
||||
if (!(borders_to_hide & ADJ_RIGHT_SCREEN_EDGE)) {
|
||||
draw_util_rectangle(conn, &(con->frame_buffer),
|
||||
draw_util_rectangle(&(con->frame_buffer),
|
||||
p->color->child_border, r->width + (br.width + br.x), 0,
|
||||
-(br.width + br.x), r->height);
|
||||
}
|
||||
if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) {
|
||||
draw_util_rectangle(conn, &(con->frame_buffer),
|
||||
draw_util_rectangle(&(con->frame_buffer),
|
||||
p->color->child_border, br.x, r->height + (br.height + br.y),
|
||||
r->width + br.width, -(br.height + br.y));
|
||||
}
|
||||
/* pixel border needs an additional line at the top */
|
||||
if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) {
|
||||
draw_util_rectangle(conn, &(con->frame_buffer),
|
||||
draw_util_rectangle(&(con->frame_buffer),
|
||||
p->color->child_border, br.x, 0, r->width + br.width, br.y);
|
||||
}
|
||||
|
||||
|
@ -502,10 +510,10 @@ void x_draw_decoration(Con *con) {
|
|||
TAILQ_PREV(con, nodes_head, nodes) == NULL &&
|
||||
con->parent->type != CT_FLOATING_CON) {
|
||||
if (p->parent_layout == L_SPLITH) {
|
||||
draw_util_rectangle(conn, &(con->frame_buffer), p->color->indicator,
|
||||
draw_util_rectangle(&(con->frame_buffer), p->color->indicator,
|
||||
r->width + (br.width + br.x), br.y, -(br.width + br.x), r->height + br.height);
|
||||
} else if (p->parent_layout == L_SPLITV) {
|
||||
draw_util_rectangle(conn, &(con->frame_buffer), p->color->indicator,
|
||||
draw_util_rectangle(&(con->frame_buffer), p->color->indicator,
|
||||
br.x, r->height + (br.height + br.y), r->width + br.width, -(br.height + br.y));
|
||||
}
|
||||
}
|
||||
|
@ -525,12 +533,12 @@ void x_draw_decoration(Con *con) {
|
|||
* garbage left on there. This is important to avoid tearing when using
|
||||
* transparency. */
|
||||
if (con == TAILQ_FIRST(&(con->parent->nodes_head))) {
|
||||
draw_util_clear_surface(conn, &(con->parent->frame_buffer), COLOR_TRANSPARENT);
|
||||
draw_util_clear_surface(&(con->parent->frame_buffer), COLOR_TRANSPARENT);
|
||||
FREE(con->parent->deco_render_params);
|
||||
}
|
||||
|
||||
/* 4: paint the bar */
|
||||
draw_util_rectangle(conn, &(parent->frame_buffer), p->color->background,
|
||||
draw_util_rectangle(&(parent->frame_buffer), p->color->background,
|
||||
con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height);
|
||||
|
||||
/* 5: draw two unconnected horizontal lines in border color */
|
||||
|
@ -564,9 +572,6 @@ void x_draw_decoration(Con *con) {
|
|||
goto after_title;
|
||||
}
|
||||
|
||||
if (win->name == NULL)
|
||||
goto copy_pixmaps;
|
||||
|
||||
int mark_width = 0;
|
||||
if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) {
|
||||
char *formatted_mark = sstrdup("");
|
||||
|
@ -600,18 +605,24 @@ void x_draw_decoration(Con *con) {
|
|||
}
|
||||
|
||||
i3String *title = con->title_format == NULL ? win->name : con_parse_title_format(con);
|
||||
if (title == NULL) {
|
||||
goto copy_pixmaps;
|
||||
}
|
||||
|
||||
draw_util_text(title, &(parent->frame_buffer),
|
||||
p->color->text, p->color->background,
|
||||
con->deco_rect.x + logical_px(2),
|
||||
con->deco_rect.y + text_offset_y,
|
||||
con->deco_rect.width - mark_width - 2 * logical_px(2));
|
||||
if (con->title_format != NULL)
|
||||
|
||||
if (con->title_format != NULL) {
|
||||
I3STRING_FREE(title);
|
||||
}
|
||||
|
||||
after_title:
|
||||
x_draw_decoration_after_title(con, p);
|
||||
copy_pixmaps:
|
||||
draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
|
||||
draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -634,7 +645,7 @@ void x_deco_recurse(Con *con) {
|
|||
x_deco_recurse(current);
|
||||
|
||||
if (state->mapped) {
|
||||
draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
|
||||
draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -821,7 +832,7 @@ void x_push_node(Con *con) {
|
|||
xcb_flush(conn);
|
||||
xcb_set_window_rect(conn, con->frame.id, rect);
|
||||
if (con->frame_buffer.id != XCB_NONE) {
|
||||
draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
|
||||
draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
|
||||
}
|
||||
xcb_flush(conn);
|
||||
|
||||
|
@ -873,7 +884,7 @@ void x_push_node(Con *con) {
|
|||
|
||||
/* copy the pixmap contents to the frame window immediately after mapping */
|
||||
if (con->frame_buffer.id != XCB_NONE) {
|
||||
draw_util_copy_surface(conn, &(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
|
||||
draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
|
||||
}
|
||||
xcb_flush(conn);
|
||||
|
||||
|
@ -893,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
|
||||
|
|
50
src/xcb.c
50
src/xcb.c
|
@ -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 client’s frame) for the given client.
|
||||
|
@ -127,15 +110,6 @@ void send_take_focus(xcb_window_t window, xcb_timestamp_t timestamp) {
|
|||
free(event);
|
||||
}
|
||||
|
||||
/*
|
||||
* Raises the given window (typically client->frame) above all other windows
|
||||
*
|
||||
*/
|
||||
void xcb_raise_window(xcb_connection_t *conn, xcb_window_t window) {
|
||||
uint32_t values[] = {XCB_STACK_MODE_ABOVE};
|
||||
xcb_configure_window(conn, window, XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
}
|
||||
|
||||
/*
|
||||
* Configures the given window to have the size/position specified by given rect
|
||||
*
|
||||
|
@ -201,18 +175,6 @@ bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom) {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the mouse pointer into the middle of rect.
|
||||
*
|
||||
*/
|
||||
void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect) {
|
||||
int mid_x = rect->x + (rect->width / 2);
|
||||
int mid_y = rect->y + (rect->height / 2);
|
||||
|
||||
LOG("warp pointer to: %d %d\n", mid_x, mid_y);
|
||||
xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the cursor of the root window to the given cursor id.
|
||||
* This function should only be used if xcursor_supported == false.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue