Merge branch 'next' into master
This commit is contained in:
commit
7af2faebd8
|
@ -8,3 +8,4 @@ IndentWidth: 4
|
||||||
PointerBindsToType: false
|
PointerBindsToType: false
|
||||||
ColumnLimit: 0
|
ColumnLimit: 0
|
||||||
SpaceBeforeParens: ControlStatements
|
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,
|
having access to the source code is too time-consuming. Additionally,
|
||||||
experience has shown that often, the software in question is responsible for
|
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.
|
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
|
## 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
|
dist: trusty
|
||||||
services:
|
services:
|
||||||
- docker
|
- 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
|
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 = \
|
check_SCRIPTS = \
|
||||||
testcases/complete-run.pl
|
testcases/complete-run.pl
|
||||||
|
|
||||||
|
check_DATA = \
|
||||||
|
anyevent-i3.stamp
|
||||||
|
|
||||||
clean-check:
|
clean-check:
|
||||||
rm -rf testsuite-* latest i3-cfg-for-* _Inline
|
rm -rf testsuite-* latest i3-cfg-for-* _Inline
|
||||||
clean-local: clean-check
|
clean-local: clean-check
|
||||||
|
@ -94,7 +100,7 @@ EXTRA_DIST = \
|
||||||
I3_VERSION \
|
I3_VERSION \
|
||||||
LICENSE \
|
LICENSE \
|
||||||
PACKAGE-MAINTAINER \
|
PACKAGE-MAINTAINER \
|
||||||
RELEASE-NOTES-4.13 \
|
RELEASE-NOTES-4.14 \
|
||||||
generate-command-parser.pl \
|
generate-command-parser.pl \
|
||||||
parser-specs/commands.spec \
|
parser-specs/commands.spec \
|
||||||
parser-specs/config.spec \
|
parser-specs/config.spec \
|
||||||
|
@ -212,6 +218,7 @@ asciidoc_MANS =
|
||||||
endif
|
endif
|
||||||
|
|
||||||
AM_CPPFLAGS = \
|
AM_CPPFLAGS = \
|
||||||
|
-DSYSCONFDIR="\"$(sysconfdir)\"" \
|
||||||
-I$(top_builddir)/parser \
|
-I$(top_builddir)/parser \
|
||||||
-I$(top_srcdir)/include \
|
-I$(top_srcdir)/include \
|
||||||
@AX_EXTEND_SRCDIR_CPPFLAGS@
|
@AX_EXTEND_SRCDIR_CPPFLAGS@
|
||||||
|
@ -401,6 +408,19 @@ i3_config_wizard_i3_config_wizard_SOURCES = \
|
||||||
i3-config-wizard/main.c \
|
i3-config-wizard/main.c \
|
||||||
i3-config-wizard/xcb.h
|
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 = \
|
test_commands_parser_CPPFLAGS = \
|
||||||
$(AM_CPPFLAGS) \
|
$(AM_CPPFLAGS) \
|
||||||
-DTEST_PARSER
|
-DTEST_PARSER
|
||||||
|
@ -457,7 +477,6 @@ i3_SOURCES = \
|
||||||
include/config_parser.h \
|
include/config_parser.h \
|
||||||
include/con.h \
|
include/con.h \
|
||||||
include/data.h \
|
include/data.h \
|
||||||
include/debug.h \
|
|
||||||
include/display_version.h \
|
include/display_version.h \
|
||||||
include/ewmh.h \
|
include/ewmh.h \
|
||||||
include/fake_outputs.h \
|
include/fake_outputs.h \
|
||||||
|
@ -502,7 +521,6 @@ i3_SOURCES = \
|
||||||
src/config.c \
|
src/config.c \
|
||||||
src/config_directives.c \
|
src/config_directives.c \
|
||||||
src/config_parser.c \
|
src/config_parser.c \
|
||||||
src/debug.c \
|
|
||||||
src/display_version.c \
|
src/display_version.c \
|
||||||
src/ewmh.c \
|
src/ewmh.c \
|
||||||
src/fake_outputs.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) mv GENERATED_config_* $(top_builddir)/parser
|
||||||
$(AM_V_at) touch $@
|
$(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 = \
|
CLEANFILES = \
|
||||||
i3-command-parser.stamp \
|
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
|
21
configure.ac
21
configure.ac
|
@ -2,7 +2,7 @@
|
||||||
# Run autoreconf -fi to generate a configure script from this file.
|
# Run autoreconf -fi to generate a configure script from this file.
|
||||||
|
|
||||||
AC_PREREQ([2.69])
|
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
|
# For AX_EXTEND_SRCDIR
|
||||||
AX_ENABLE_BUILDDIR
|
AX_ENABLE_BUILDDIR
|
||||||
AM_INIT_AUTOMAKE([foreign subdir-objects -Wall no-dist-gzip dist-bzip2])
|
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_HEADERS([config.h])
|
||||||
AC_CONFIG_MACRO_DIR([m4])
|
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
|
# 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
|
# Makefile.am, which are a GNU make extension. Pull requests to replace
|
||||||
# '%'-style pattern rules with a more portable alternative are welcome.
|
# '%'-style pattern rules with a more portable alternative are welcome.
|
||||||
|
@ -25,8 +31,8 @@ AX_EXTEND_SRCDIR
|
||||||
|
|
||||||
AS_IF([test -d ${srcdir}/.git],
|
AS_IF([test -d ${srcdir}/.git],
|
||||||
[
|
[
|
||||||
VERSION="$(git describe --tags --abbrev=0)"
|
VERSION="$(git -C ${srcdir} 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/::)\\\")"
|
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:
|
# 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)
|
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_PROG_CC_C99
|
||||||
|
|
||||||
AC_DEFINE_UNQUOTED(SYSCONFDIR, "`eval echo $sysconfdir`", [Location of system configuration files])
|
|
||||||
|
|
||||||
# For strnlen() and vasprintf().
|
# For strnlen() and vasprintf().
|
||||||
AC_USE_SYSTEM_EXTENSIONS
|
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([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
|
AX_PTHREAD
|
||||||
|
|
||||||
dnl Each prefix corresponds to a source tarball which users might have
|
dnl Each prefix corresponds to a source tarball which users might have
|
||||||
|
@ -146,8 +152,9 @@ else
|
||||||
print_BUILD_MANS=no
|
print_BUILD_MANS=no
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git_dir=`git rev-parse --git-dir 2>/dev/null`
|
in_git_worktree=`git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||||
if test -n "$git_dir"; then
|
if [[ "$in_git_worktree" = "true" ]]; then
|
||||||
|
git_dir=`git rev-parse --git-dir 2>/dev/null`
|
||||||
srcdir=`dirname "$git_dir"`
|
srcdir=`dirname "$git_dir"`
|
||||||
exclude_dir=`pwd | sed "s,^$srcdir,,g"`
|
exclude_dir=`pwd | sed "s,^$srcdir,,g"`
|
||||||
if ! grep -q "^$exclude_dir" "$git_dir/info/exclude"; then
|
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
|
i3-wm (4.13-1) unstable; urgency=medium
|
||||||
|
|
||||||
* New upstream release.
|
* New upstream release.
|
||||||
|
|
|
@ -119,9 +119,6 @@ src/config.c::
|
||||||
Contains all functions handling the configuration file (calling the parser
|
Contains all functions handling the configuration file (calling the parser
|
||||||
src/config_parser.c) with the correct path, switching key bindings mode).
|
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::
|
src/ewmh.c::
|
||||||
Functions to get/set certain EWMH properties easily.
|
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
|
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”.
|
“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
|
== Thought experiments
|
||||||
|
|
||||||
In this section, we collect thought experiments, so that we don’t forget our
|
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.
|
The name of this output (as seen in +xrandr(1)+). Encoded in UTF-8.
|
||||||
active (boolean)::
|
active (boolean)::
|
||||||
Whether this output is currently active (has a valid mode).
|
Whether this output is currently active (has a valid mode).
|
||||||
|
primary (boolean)::
|
||||||
|
Whether this output is currently the primary output.
|
||||||
current_workspace (string)::
|
current_workspace (string)::
|
||||||
The name of the current workspace that is visible on this output. +null+ if
|
The name of the current workspace that is visible on this output. +null+ if
|
||||||
the output is not active.
|
the output is not active.
|
||||||
|
@ -262,7 +264,7 @@ rect (map)::
|
||||||
"y": 0,
|
"y": 0,
|
||||||
"width": 1280,
|
"width": 1280,
|
||||||
"height": 1024
|
"height": 1024
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
-------------------
|
-------------------
|
||||||
|
@ -390,7 +392,7 @@ JSON dump:
|
||||||
"y": 0,
|
"y": 0,
|
||||||
"width": 1280,
|
"width": 1280,
|
||||||
"height": 0
|
"height": 0
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -671,6 +673,8 @@ barconfig_update (4)::
|
||||||
binding (5)::
|
binding (5)::
|
||||||
Sent when a configured command binding is triggered with the keyboard or
|
Sent when a configured command binding is triggered with the keyboard or
|
||||||
mouse
|
mouse
|
||||||
|
shutdown (6)::
|
||||||
|
Sent when the ipc shuts down because of a restart or exit by user command
|
||||||
|
|
||||||
*Example:*
|
*Example:*
|
||||||
--------------------------------------------------------------------
|
--------------------------------------------------------------------
|
||||||
|
@ -694,9 +698,9 @@ if ($is_event) {
|
||||||
|
|
||||||
This event consists of a single serialized map containing a property
|
This event consists of a single serialized map containing a property
|
||||||
+change (string)+ which indicates the type of the change ("focus", "init",
|
+change (string)+ which indicates the type of the change ("focus", "init",
|
||||||
"empty", "urgent"). A +current (object)+ property will be present with the
|
"empty", "urgent", "reload", "rename", "restored", "move"). A
|
||||||
affected workspace whenever the type of event affects a workspace (otherwise,
|
+current (object)+ property will be present with the affected workspace
|
||||||
it will be +null).
|
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
|
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
|
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
|
=== binding event
|
||||||
|
|
||||||
This event consists of a single serialized map reporting on the details of a
|
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
|
indicates what sort of binding event was triggered (right now it will always be
|
||||||
+"run"+ but may be expanded in the future).
|
+"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)
|
== See also (existing libraries)
|
||||||
|
|
||||||
[[libraries]]
|
[[libraries]]
|
||||||
|
@ -843,7 +861,7 @@ C::
|
||||||
C++::
|
C++::
|
||||||
* https://github.com/drmgc/i3ipcpp
|
* https://github.com/drmgc/i3ipcpp
|
||||||
Go::
|
Go::
|
||||||
* https://github.com/proxypoke/i3ipc
|
* https://github.com/mdirkse/i3ipc-go
|
||||||
JavaScript::
|
JavaScript::
|
||||||
* https://github.com/acrisci/i3ipc-gjs
|
* https://github.com/acrisci/i3ipc-gjs
|
||||||
Lua::
|
Lua::
|
||||||
|
@ -859,3 +877,5 @@ Ruby::
|
||||||
* https://github.com/badboy/i3-ipc (not maintained)
|
* https://github.com/badboy/i3-ipc (not maintained)
|
||||||
Rust::
|
Rust::
|
||||||
* https://github.com/tmerr/i3ipc-rs
|
* 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
|
$ cd ~/i3/testcases
|
||||||
$ sudo apt-get install cpanminus
|
$ sudo apt-get install cpanminus
|
||||||
$ sudo cpanm .
|
$ sudo cpanm .
|
||||||
|
$ cd ~/i3/AnyEvent-I3
|
||||||
|
$ sudo cpanm .
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
If you don’t want to use cpanminus for some reason, the same works with cpan:
|
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
|
$ cd ~/i3/testcases
|
||||||
$ sudo cpan .
|
$ sudo cpan .
|
||||||
|
$ cd ~/i3/AnyEvent-I3
|
||||||
|
$ sudo cpan .
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
In case you don’t have root permissions, you can also install into your home
|
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
|
./complete-run+. This will also speed up the tests significantly especially on
|
||||||
machines without a powerful video card.
|
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
|
$ ./complete-run.pl
|
||||||
# output omitted because it is very long
|
# 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
|
This will show the output of Xephyr, which is the X server implementation we
|
||||||
use for testing.
|
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
|
||||||
|
|
||||||
Coverage testing is possible with +lcov+, the front-end for GCC's coverage
|
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
|
Please note that you must not have +~/.i3/config+, otherwise the wizard will
|
||||||
exit.
|
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
|
=== Comments
|
||||||
|
|
||||||
It is possible and recommended to use comments in your configuration file to
|
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.
|
can configure mouse bindings in a similar way to key bindings.
|
||||||
|
|
||||||
*Syntax*:
|
*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
|
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
|
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
|
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.
|
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*:
|
*Examples*:
|
||||||
--------------------------------
|
--------------------------------
|
||||||
# The middle button over a titlebar kills the window
|
# The middle button over a titlebar kills the window
|
||||||
|
@ -479,7 +491,7 @@ mode <name>
|
||||||
|
|
||||||
*Example*:
|
*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.
|
# thunderbird or return to the default mode, respectively.
|
||||||
set $mode_launcher Launch: [f]irefox [t]hunderbird
|
set $mode_launcher Launch: [f]irefox [t]hunderbird
|
||||||
bindsym $mod+o mode "$mode_launcher"
|
bindsym $mod+o mode "$mode_launcher"
|
||||||
|
@ -488,7 +500,7 @@ mode "$mode_launcher" {
|
||||||
bindsym f exec firefox
|
bindsym f exec firefox
|
||||||
bindsym t exec thunderbird
|
bindsym t exec thunderbird
|
||||||
|
|
||||||
bindsym Esc mode "default"
|
bindsym Escape mode "default"
|
||||||
bindsym Return mode "default"
|
bindsym Return mode "default"
|
||||||
}
|
}
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
|
@ -946,12 +958,12 @@ the next section.
|
||||||
|
|
||||||
=== Focus follows mouse
|
=== Focus follows mouse
|
||||||
|
|
||||||
By default, window focus follows your mouse movements. However, if you have a
|
By default, window focus follows your mouse movements as the mouse crosses
|
||||||
setup where your mouse usually is in your way (like a touchpad on your laptop
|
window borders. However, if you have a setup where your mouse usually is in your
|
||||||
which you do not want to disable completely), you might want to disable 'focus
|
way (like a touchpad on your laptop which you do not want to disable
|
||||||
follows mouse' and control focus only by using your keyboard. The mouse will
|
completely), you might want to disable 'focus follows mouse' and control focus
|
||||||
still be useful inside the currently active window (for example to click on
|
only by using your keyboard. The mouse will still be useful inside the
|
||||||
links in your browser window).
|
currently active window (for example to click on links in your browser window).
|
||||||
|
|
||||||
*Syntax*:
|
*Syntax*:
|
||||||
--------------------------
|
--------------------------
|
||||||
|
@ -1127,9 +1139,9 @@ none::
|
||||||
[[show_marks]]
|
[[show_marks]]
|
||||||
=== Drawing marks on window decoration
|
=== Drawing marks on window decoration
|
||||||
|
|
||||||
If activated, marks on windows are drawn in their window decoration. However,
|
If activated, marks (see <<vim_like_marks>>) on windows are drawn in their window
|
||||||
any mark starting with an underscore in its name (+_+) will not be drawn even if
|
decoration. However, any mark starting with an underscore in its name (+_+) will
|
||||||
this option is activated.
|
not be drawn even if this option is activated.
|
||||||
|
|
||||||
The default for this option is +yes+.
|
The default for this option is +yes+.
|
||||||
|
|
||||||
|
@ -1378,7 +1390,7 @@ directive multiple times.
|
||||||
|
|
||||||
*Syntax*:
|
*Syntax*:
|
||||||
---------------
|
---------------
|
||||||
output <output>
|
output primary|<output>
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
*Example*:
|
*Example*:
|
||||||
|
@ -1400,7 +1412,19 @@ bar {
|
||||||
statusline #ffffff
|
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
|
=== 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
|
contains a single window. Otherwise it makes the current window a split
|
||||||
container with opposite orientation compared to the parent container.
|
container with opposite orientation compared to the parent container.
|
||||||
Use +layout toggle split+ to change the layout of any split container from
|
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*:
|
*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,
|
or +layout splith+ to change the current container layout to splith/splitv,
|
||||||
stacking, tabbed layout, splitv or splith, respectively.
|
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
|
To make the current window (!) fullscreen, use +fullscreen enable+ (or
|
||||||
+fullscreen enable global+ for the global mode), to leave either fullscreen
|
+fullscreen enable global+ for the global mode), to leave either fullscreen
|
||||||
mode use +fullscreen disable+, and to toggle between these two states use
|
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 default|tabbed|stacking|splitv|splith
|
||||||
layout toggle [split|all]
|
layout toggle [split|all]
|
||||||
|
layout toggle [split|tabbed|stacking|splitv|splith] [split|tabbed|stacking|splitv|splith]…
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
*Examples*:
|
*Examples*:
|
||||||
|
@ -1848,6 +1879,15 @@ bindsym $mod+x layout toggle
|
||||||
# Toggle between stacking/tabbed/splith/splitv:
|
# Toggle between stacking/tabbed/splith/splitv:
|
||||||
bindsym $mod+x layout toggle all
|
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
|
# Toggle fullscreen
|
||||||
bindsym $mod+f fullscreen toggle
|
bindsym $mod+f fullscreen toggle
|
||||||
|
|
||||||
|
@ -1882,7 +1922,7 @@ output::
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
focus left|right|down|up
|
focus left|right|down|up
|
||||||
focus parent|child|floating|tiling|mode_toggle
|
focus parent|child|floating|tiling|mode_toggle
|
||||||
focus output left|right|up|down|<output>
|
focus output left|right|up|down|primary|<output>
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
*Examples*:
|
*Examples*:
|
||||||
|
@ -1904,8 +1944,17 @@ bindsym $mod+x focus output right
|
||||||
|
|
||||||
# Focus the big output
|
# Focus the big output
|
||||||
bindsym $mod+x focus output HDMI-2
|
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
|
=== Moving containers
|
||||||
|
|
||||||
Use the +move+ command to move a container.
|
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
|
# Moves the container either to a specific location
|
||||||
# or to the center of the screen. If 'absolute' is
|
# or to the center of the screen. If 'absolute' is
|
||||||
# used, it is moved to the center of all outputs.
|
# 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
|
# Moves the container to the current position of the
|
||||||
# mouse cursor. Only affects floating containers.
|
# mouse cursor. Only affects floating containers.
|
||||||
|
@ -1947,6 +1997,39 @@ bindsym $mod+c move absolute position center
|
||||||
bindsym $mod+m move position mouse
|
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
|
=== Sticky floating windows
|
||||||
|
|
||||||
If you want a window to stick to the glass, i.e., have it stay on screen even
|
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: '
|
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
|
=== Moving workspaces to a different screen
|
||||||
|
|
||||||
See <<move_to_outputs>> for how to move a container/workspace to a different
|
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*:
|
*Syntax*:
|
||||||
------------------------------------------------------------
|
------------------------------------------------------------
|
||||||
move container 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|<output>
|
move workspace to output left|right|down|up|current|primary|<output>
|
||||||
------------------------------------------------------------
|
------------------------------------------------------------
|
||||||
|
|
||||||
*Examples*:
|
*Examples*:
|
||||||
|
@ -2116,8 +2216,17 @@ bindsym $mod+x move workspace to output right
|
||||||
|
|
||||||
# Put this window on the presentation output.
|
# Put this window on the presentation output.
|
||||||
bindsym $mod+x move container to output VGA1
|
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
|
=== Moving containers/windows to marks
|
||||||
|
|
||||||
To move a container to another container with a specific mark (see <<vim_like_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;
|
my $current_state;
|
||||||
|
|
||||||
for my $line (@lines) {
|
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";
|
#say "got a new state: $state";
|
||||||
$current_state = $state;
|
$current_state = $state;
|
||||||
} else {
|
} else {
|
||||||
|
@ -155,12 +155,20 @@ for my $state (@keys) {
|
||||||
# to generate a format string. The format uses %d for <number>s,
|
# 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
|
# literal numbers or state IDs and %s for NULL, <string>s and literal
|
||||||
# strings.
|
# 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/$_/%d/g for @keys;
|
||||||
$fmt =~ s/\$([a-z_]+)/%s/g;
|
$fmt =~ s/\$([a-z_]+)/%s/g;
|
||||||
$fmt =~ s/\&([a-z_]+)/%ld/g;
|
$fmt =~ s/\&([a-z_]+)/%ld/g;
|
||||||
$fmt =~ s/"([a-z0-9_]+)"/%s/g;
|
$fmt =~ s/"([a-z0-9_]+)"/%s/g;
|
||||||
$fmt =~ s/(?:-?|\b)[0-9]+\b/%d/g;
|
$fmt =~ s/(?:-?|\b)[0-9]+\b/%d/g;
|
||||||
|
|
||||||
|
$fmt = $funcname . $fmt;
|
||||||
|
|
||||||
say $callfh " case $call_id:";
|
say $callfh " case $call_id:";
|
||||||
say $callfh " result->next_state = $next_state;";
|
say $callfh " result->next_state = $next_state;";
|
||||||
say $callfh '#ifndef TEST_PARSER';
|
say $callfh '#ifndef TEST_PARSER';
|
||||||
|
|
|
@ -69,10 +69,16 @@
|
||||||
#include "xcb.h"
|
#include "xcb.h"
|
||||||
#include "libi3.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) \
|
#define row_y(row) \
|
||||||
(((row)-1) * font.height + logical_px(4))
|
(((row)-1) * font.height + TEXT_PADDING)
|
||||||
#define window_height() \
|
|
||||||
(row_y(15) + font.height)
|
|
||||||
|
|
||||||
enum { STEP_WELCOME,
|
enum { STEP_WELCOME,
|
||||||
STEP_GENERATE } current_step = STEP_WELCOME;
|
STEP_GENERATE } current_step = STEP_WELCOME;
|
||||||
|
@ -90,8 +96,7 @@ static i3Font bold_font;
|
||||||
static int char_width;
|
static int char_width;
|
||||||
static char *socket_path;
|
static char *socket_path;
|
||||||
static xcb_window_t win;
|
static xcb_window_t win;
|
||||||
static xcb_pixmap_t pixmap;
|
static surface_t surface;
|
||||||
static xcb_gcontext_t pixmap_gc;
|
|
||||||
static xcb_key_symbols_t *symbols;
|
static xcb_key_symbols_t *symbols;
|
||||||
xcb_window_t root;
|
xcb_window_t root;
|
||||||
static struct xkb_keymap *xkb_keymap;
|
static struct xkb_keymap *xkb_keymap;
|
||||||
|
@ -463,82 +468,73 @@ void errorlog(char *fmt, ...) {
|
||||||
void debuglog(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.
|
* Handles expose events, that is, draws the window contents.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static int handle_expose() {
|
static int handle_expose() {
|
||||||
/* re-draw the background */
|
const color_t black = draw_util_hex_to_color("#000000");
|
||||||
xcb_rectangle_t border = {0, 0, logical_px(300), window_height()};
|
const color_t white = draw_util_hex_to_color("#FFFFFF");
|
||||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")});
|
const color_t green = draw_util_hex_to_color("#00FF00");
|
||||||
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
|
const color_t red = draw_util_hex_to_color("#FF0000");
|
||||||
|
|
||||||
|
/* draw background */
|
||||||
|
draw_util_clear_surface(&surface, black);
|
||||||
|
|
||||||
set_font(&font);
|
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) {
|
if (current_step == STEP_WELCOME) {
|
||||||
/* restore font color */
|
txt(2, 2, "You have not configured i3 yet.", white, black);
|
||||||
set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000"));
|
txt(2, 3, "Do you want me to generate a config at", white, black);
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
char *msg;
|
char *msg;
|
||||||
sasprintf(&msg, "%s?", config_path);
|
sasprintf(&msg, "%s?", config_path);
|
||||||
txt(logical_px(10), 4, msg);
|
txt(2, 4, msg, white, black);
|
||||||
free(msg);
|
free(msg);
|
||||||
|
|
||||||
txt(logical_px(85), 6, "Yes, generate the config");
|
txt(13, 6, "Yes, generate the config", white, black);
|
||||||
txt(logical_px(85), 8, "No, I will use the defaults");
|
txt(13, 8, "No, I will use the defaults", white, black);
|
||||||
|
|
||||||
/* green */
|
txt(4, 6, "<Enter>", green, black);
|
||||||
set_font_colors(pixmap_gc, draw_util_hex_to_color("#00FF00"), draw_util_hex_to_color("#000000"));
|
|
||||||
txt(logical_px(25), 6, "<Enter>");
|
|
||||||
|
|
||||||
/* red */
|
txt(5, 8, "<ESC>", red, black);
|
||||||
set_font_colors(pixmap_gc, draw_util_hex_to_color("#FF0000"), draw_util_hex_to_color("#000000"));
|
|
||||||
txt(logical_px(31), 8, "<ESC>");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current_step == STEP_GENERATE) {
|
if (current_step == STEP_GENERATE) {
|
||||||
set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000"));
|
txt(2, 2, "Please choose either:", white, black);
|
||||||
|
txt(13, 4, "Win as default modifier", white, black);
|
||||||
txt(logical_px(10), 2, "Please choose either:");
|
txt(13, 5, "Alt as default modifier", white, black);
|
||||||
txt(logical_px(85), 4, "Win as default modifier");
|
txt(2, 7, "Afterwards, press", white, black);
|
||||||
txt(logical_px(85), 5, "Alt as default modifier");
|
txt(13, 9, "to write the config", white, black);
|
||||||
txt(logical_px(10), 7, "Afterwards, press");
|
txt(13, 10, "to abort", white, black);
|
||||||
txt(logical_px(85), 9, "to write the config");
|
|
||||||
txt(logical_px(85), 10, "to abort");
|
|
||||||
|
|
||||||
/* the not-selected modifier */
|
/* the not-selected modifier */
|
||||||
if (modifier == MOD_Mod4)
|
if (modifier == MOD_Mod4)
|
||||||
txt(logical_px(31), 5, "<Alt>");
|
txt(5, 5, "<Alt>", white, black);
|
||||||
else
|
else
|
||||||
txt(logical_px(31), 4, "<Win>");
|
txt(5, 4, "<Win>", white, black);
|
||||||
|
|
||||||
/* the selected modifier */
|
/* the selected modifier */
|
||||||
set_font(&bold_font);
|
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)
|
if (modifier == MOD_Mod4)
|
||||||
txt(logical_px(10), 4, "-> <Win>");
|
txt(2, 4, "-> <Win>", white, black);
|
||||||
else
|
else
|
||||||
txt(logical_px(10), 5, "-> <Alt>");
|
txt(2, 5, "-> <Alt>", white, black);
|
||||||
|
|
||||||
/* green */
|
|
||||||
set_font(&font);
|
set_font(&font);
|
||||||
set_font_colors(pixmap_gc, draw_util_hex_to_color("#00FF00"), draw_util_hex_to_color("#000000"));
|
txt(4, 9, "<Enter>", green, black);
|
||||||
txt(logical_px(25), 9, "<Enter>");
|
|
||||||
|
|
||||||
/* red */
|
txt(5, 10, "<ESC>", red, black);
|
||||||
set_font_colors(pixmap_gc, draw_util_hex_to_color("#FF0000"), draw_util_hex_to_color("#000000"));
|
|
||||||
txt(logical_px(31), 10, "<ESC>");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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);
|
xcb_flush(conn);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -625,8 +621,7 @@ static void handle_button_press(xcb_button_press_event_t *event) {
|
||||||
if (current_step != STEP_GENERATE)
|
if (current_step != STEP_GENERATE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (event->event_x < logical_px(32) ||
|
if (event->event_x < col_x(5) || event->event_x > col_x(10))
|
||||||
event->event_x > (logical_px(32) + char_width * 5))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (event->event_y >= row_y(4) && event->event_y <= (row_y(4) + font.height)) {
|
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);
|
xcb_numlock_mask = get_mod_mask_for(XCB_NUM_LOCK, symbols, modmap_reply);
|
||||||
|
|
||||||
|
init_dpi();
|
||||||
font = load_font(pattern, true);
|
font = load_font(pattern, true);
|
||||||
bold_font = load_font(patternbold, true);
|
bold_font = load_font(patternbold, true);
|
||||||
|
|
||||||
|
@ -868,7 +864,7 @@ int main(int argc, char *argv[]) {
|
||||||
XCB_COPY_FROM_PARENT,
|
XCB_COPY_FROM_PARENT,
|
||||||
win, /* the window id */
|
win, /* the window id */
|
||||||
root, /* parent == root */
|
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 */
|
0, /* X11 border = 0, we draw our own */
|
||||||
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||||
XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
|
XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
|
||||||
|
@ -914,11 +910,8 @@ int main(int argc, char *argv[]) {
|
||||||
strlen("i3: first configuration"),
|
strlen("i3: first configuration"),
|
||||||
"i3: first configuration");
|
"i3: first configuration");
|
||||||
|
|
||||||
/* Create pixmap */
|
/* Initialize drawable surface */
|
||||||
pixmap = xcb_generate_id(conn);
|
draw_util_surface_init(conn, &surface, win, get_visualtype(root_screen), WIN_WIDTH, WIN_HEIGHT);
|
||||||
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);
|
|
||||||
|
|
||||||
/* Grab the keyboard to get all input */
|
/* Grab the keyboard to get all input */
|
||||||
xcb_flush(conn);
|
xcb_flush(conn);
|
||||||
|
@ -965,12 +958,18 @@ int main(int argc, char *argv[]) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case XCB_EXPOSE:
|
case XCB_EXPOSE:
|
||||||
|
if (((xcb_expose_event_t *)event)->count == 0) {
|
||||||
handle_expose();
|
handle_expose();
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(event);
|
free(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dismiss drawable surface */
|
||||||
|
draw_util_surface_free(conn, &surface);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -279,17 +279,43 @@ for my $app (keys %apps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$choices{$name} = $app;
|
$choices{$name} = $app;
|
||||||
|
next;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((scalar grep { $_ eq 'command' } @entry_types) > 0) {
|
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.
|
# Don’t add “geany” if “Geany” is already present.
|
||||||
my @keys = map { lc } keys %choices;
|
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;
|
$choices{basename($command)} = $app;
|
||||||
}
|
}
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
if ((scalar grep { $_ eq 'filename' } @entry_types) > 0) {
|
if ((scalar grep { $_ eq 'filename' } @entry_types) > 0) {
|
||||||
my $filename = basename($app, '.desktop');
|
my $filename = basename($app, '.desktop');
|
||||||
|
|
|
@ -33,6 +33,10 @@
|
||||||
|
|
||||||
#include "i3-input.h"
|
#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
|
/* IPC format string. %s will be replaced with what the user entered, then
|
||||||
* the command will be sent to i3 */
|
* the command will be sent to i3 */
|
||||||
static char *format;
|
static char *format;
|
||||||
|
@ -42,8 +46,7 @@ static int sockfd;
|
||||||
static xcb_key_symbols_t *symbols;
|
static xcb_key_symbols_t *symbols;
|
||||||
static bool modeswitch_active = false;
|
static bool modeswitch_active = false;
|
||||||
static xcb_window_t win;
|
static xcb_window_t win;
|
||||||
static xcb_pixmap_t pixmap;
|
static surface_t surface;
|
||||||
static xcb_gcontext_t pixmap_gc;
|
|
||||||
static xcb_char2b_t glyphs_ucs[512];
|
static xcb_char2b_t glyphs_ucs[512];
|
||||||
static char *glyphs_utf8[512];
|
static char *glyphs_utf8[512];
|
||||||
static int input_position;
|
static int input_position;
|
||||||
|
@ -54,7 +57,6 @@ static int limit;
|
||||||
xcb_window_t root;
|
xcb_window_t root;
|
||||||
xcb_connection_t *conn;
|
xcb_connection_t *conn;
|
||||||
xcb_screen_t *root_screen;
|
xcb_screen_t *root_screen;
|
||||||
static xcb_get_input_focus_cookie_t focus_cookie;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Having verboselog(), errorlog() and debuglog() is necessary when using libi3.
|
* Having verboselog(), errorlog() and debuglog() is necessary when using libi3.
|
||||||
|
@ -79,24 +81,6 @@ void errorlog(char *fmt, ...) {
|
||||||
void debuglog(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
|
* 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).
|
* 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) {
|
static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) {
|
||||||
printf("expose!\n");
|
printf("expose!\n");
|
||||||
|
|
||||||
/* re-draw the background */
|
color_t border_color = draw_util_hex_to_color("#FF0000");
|
||||||
xcb_rectangle_t border = {0, 0, logical_px(500), font.height + logical_px(8)},
|
color_t fg_color = draw_util_hex_to_color("#FFFFFF");
|
||||||
inner = {logical_px(2), logical_px(2), logical_px(496), font.height + logical_px(8) - logical_px(4)};
|
color_t bg_color = draw_util_hex_to_color("#000000");
|
||||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#FF0000")});
|
|
||||||
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
|
|
||||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")});
|
|
||||||
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
|
|
||||||
|
|
||||||
/* restore font color */
|
int text_offset = BORDER + PADDING;
|
||||||
set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000"));
|
|
||||||
|
/* 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 … */
|
/* draw the prompt … */
|
||||||
if (prompt != NULL) {
|
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 */
|
/* … and the text */
|
||||||
if (input_position > 0) {
|
if (input_position > 0) {
|
||||||
i3String *input = i3string_from_ucs2(glyphs_ucs, input_position);
|
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);
|
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);
|
xcb_flush(conn);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -208,10 +192,6 @@ static void finish_input() {
|
||||||
/* prefix the command if a prefix was specified on commandline */
|
/* prefix the command if a prefix was specified on commandline */
|
||||||
printf("command = %s\n", full);
|
printf("command = %s\n", full);
|
||||||
|
|
||||||
restore_input_focus();
|
|
||||||
|
|
||||||
xcb_aux_sync(conn);
|
|
||||||
|
|
||||||
ipc_send_message(sockfd, strlen(full), 0, (uint8_t *)full);
|
ipc_send_message(sockfd, strlen(full), 0, (uint8_t *)full);
|
||||||
|
|
||||||
free(full);
|
free(full);
|
||||||
|
@ -265,7 +245,6 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (sym == XK_Escape) {
|
if (sym == XK_Escape) {
|
||||||
restore_input_focus();
|
|
||||||
exit(0);
|
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) {
|
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_property_reply_t *supporting_wm_reply = NULL;
|
||||||
xcb_get_input_focus_reply_t *input_focus = 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);
|
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_screen = xcb_aux_get_screen(conn, screen);
|
||||||
root = root_screen->root;
|
root = root_screen->root;
|
||||||
|
|
||||||
symbols = xcb_key_symbols_alloc(conn);
|
symbols = xcb_key_symbols_alloc(conn);
|
||||||
|
|
||||||
|
init_dpi();
|
||||||
font = load_font(pattern, true);
|
font = load_font(pattern, true);
|
||||||
set_font(&font);
|
set_font(&font);
|
||||||
|
|
||||||
|
@ -503,15 +480,8 @@ int main(int argc, char *argv[]) {
|
||||||
/* Map the window (make it visible) */
|
/* Map the window (make it visible) */
|
||||||
xcb_map_window(conn, win);
|
xcb_map_window(conn, win);
|
||||||
|
|
||||||
/* Create pixmap */
|
/* Initialize the drawable surface */
|
||||||
pixmap = xcb_generate_id(conn);
|
draw_util_surface_init(conn, &surface, win, get_visualtype(root_screen), win_pos.width, win_pos.height);
|
||||||
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);
|
|
||||||
|
|
||||||
/* Grab the keyboard to get all input */
|
/* Grab the keyboard to get all input */
|
||||||
xcb_flush(conn);
|
xcb_flush(conn);
|
||||||
|
@ -531,7 +501,6 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
|
if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
|
||||||
fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
|
fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
|
||||||
restore_input_focus();
|
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -557,12 +526,16 @@ int main(int argc, char *argv[]) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case XCB_EXPOSE:
|
case XCB_EXPOSE:
|
||||||
|
if (((xcb_expose_event_t *)event)->count == 0) {
|
||||||
handle_expose(NULL, conn, (xcb_expose_event_t *)event);
|
handle_expose(NULL, conn, (xcb_expose_event_t *)event);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(event);
|
free(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
draw_util_surface_free(conn, &surface);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,43 @@ static yajl_callbacks reply_callbacks = {
|
||||||
.yajl_end_map = reply_end_map_cb,
|
.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[]) {
|
int main(int argc, char *argv[]) {
|
||||||
#if defined(__OpenBSD__)
|
#if defined(__OpenBSD__)
|
||||||
if (pledge("stdio rpath unix", NULL) == -1)
|
if (pledge("stdio rpath unix", NULL) == -1)
|
||||||
|
@ -150,25 +187,27 @@ int main(int argc, char *argv[]) {
|
||||||
free(socket_path);
|
free(socket_path);
|
||||||
socket_path = sstrdup(optarg);
|
socket_path = sstrdup(optarg);
|
||||||
} else if (o == 't') {
|
} else if (o == 't') {
|
||||||
if (strcasecmp(optarg, "command") == 0)
|
if (strcasecmp(optarg, "command") == 0) {
|
||||||
message_type = I3_IPC_MESSAGE_TYPE_COMMAND;
|
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;
|
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;
|
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;
|
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;
|
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;
|
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;
|
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;
|
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("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);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
} else if (o == 'q') {
|
} else if (o == 'q') {
|
||||||
|
@ -180,6 +219,8 @@ int main(int argc, char *argv[]) {
|
||||||
printf("i3-msg " I3_VERSION "\n");
|
printf("i3-msg " I3_VERSION "\n");
|
||||||
printf("i3-msg [-s <socket>] [-t <type>] <message>\n");
|
printf("i3-msg [-s <socket>] [-t <type>] <message>\n");
|
||||||
return 0;
|
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);
|
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.
|
/* For the reply of commands, have a look if that command was successful.
|
||||||
* If not, nicely format the error message. */
|
* 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_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
|
||||||
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
|
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
|
||||||
yajl_free(handle);
|
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
|
/* NB: We still fall-through and print the reply, because even if one
|
||||||
* command failed, that doesn’t mean that all commands failed. */
|
* 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);
|
printf("%.*s\n", reply_length, reply);
|
||||||
|
|
||||||
|
exit:
|
||||||
free(reply);
|
free(reply);
|
||||||
|
|
||||||
close(sockfd);
|
close(sockfd);
|
||||||
|
|
173
i3-nagbar/main.c
173
i3-nagbar/main.c
|
@ -38,6 +38,13 @@
|
||||||
* constant for that. */
|
* constant for that. */
|
||||||
#define XCB_CURSOR_LEFT_PTR 68
|
#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;
|
static char *argv0 = NULL;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -48,11 +55,12 @@ typedef struct {
|
||||||
} button_t;
|
} button_t;
|
||||||
|
|
||||||
static xcb_window_t win;
|
static xcb_window_t win;
|
||||||
static xcb_pixmap_t pixmap;
|
static surface_t bar;
|
||||||
static xcb_gcontext_t pixmap_gc;
|
|
||||||
static xcb_rectangle_t rect = {0, 0, 600, 20};
|
|
||||||
static i3Font font;
|
static i3Font font;
|
||||||
static i3String *prompt;
|
static i3String *prompt;
|
||||||
|
|
||||||
|
static button_t btn_close;
|
||||||
static button_t *buttons;
|
static button_t *buttons;
|
||||||
static int buttoncnt;
|
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",
|
printf("button released on x = %d, y = %d\n",
|
||||||
event->event_x, event->event_y);
|
event->event_x, event->event_y);
|
||||||
/* If the user hits the close button, we exit(0) */
|
/* 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);
|
exit(0);
|
||||||
button_t *button = get_button_at(event->event_x, event->event_y);
|
button_t *button = get_button_at(event->event_x, event->event_y);
|
||||||
if (!button)
|
if (!button)
|
||||||
|
@ -190,108 +198,64 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
|
||||||
/* TODO: unset flag, re-render */
|
/* 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
|
* 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.
|
* 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) {
|
static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
|
||||||
/* re-draw the background */
|
/* draw background */
|
||||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){color_background.colorpixel});
|
draw_util_clear_surface(&bar, color_background);
|
||||||
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &rect);
|
/* draw message */
|
||||||
|
draw_util_text(prompt, &bar, color_text, color_background,
|
||||||
|
MSG_PADDING, MSG_PADDING,
|
||||||
|
bar.width - 2 * MSG_PADDING);
|
||||||
|
|
||||||
/* restore font color */
|
int position = bar.width - (MSG_PADDING - BTN_BORDER - BTN_PADDING);
|
||||||
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));
|
|
||||||
|
|
||||||
/* render close button */
|
/* render close button */
|
||||||
const char *close_button_label = "X";
|
position -= button_draw(&btn_close, position);
|
||||||
int line_width = logical_px(4);
|
position -= CLOSE_BTN_GAP;
|
||||||
/* 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);
|
|
||||||
|
|
||||||
/* render custom buttons */
|
/* render custom buttons */
|
||||||
line_width = 1;
|
for (int i = 0; i < buttoncnt; i++) {
|
||||||
for (int c = 0; c < buttoncnt; c++) {
|
position -= BTN_GAP;
|
||||||
/* set w to the width of the label */
|
position -= button_draw(&buttons[i], position);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* border line at the bottom */
|
/* border line at the bottom */
|
||||||
line_width = logical_px(2);
|
draw_util_rectangle(&bar, color_border_bottom, 0, bar.height - BAR_BORDER, bar.width, BAR_BORDER);
|
||||||
values[0] = color_border_bottom.colorpixel;
|
|
||||||
values[1] = line_width;
|
|
||||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND | XCB_GC_LINE_WIDTH, values);
|
|
||||||
xcb_point_t bottom[] = {
|
|
||||||
{0, rect.height - 0},
|
|
||||||
{rect.width, rect.height - 0}};
|
|
||||||
xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, pixmap, pixmap_gc, 2, bottom);
|
|
||||||
|
|
||||||
/* Copy the contents of the pixmap to the real window */
|
|
||||||
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, rect.width, rect.height);
|
|
||||||
xcb_flush(conn);
|
xcb_flush(conn);
|
||||||
|
|
||||||
return 1;
|
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) {
|
static xcb_rectangle_t get_window_position(void) {
|
||||||
/* Default values if we cannot determine the primary output or its CRTC info. */
|
/* 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_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);
|
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;
|
int screens;
|
||||||
if ((conn = xcb_connect(NULL, &screens)) == NULL ||
|
if ((conn = xcb_connect(NULL, &screens)) == NULL ||
|
||||||
xcb_connection_has_error(conn))
|
xcb_connection_has_error(conn))
|
||||||
|
@ -468,6 +434,7 @@ int main(int argc, char *argv[]) {
|
||||||
color_border_bottom = draw_util_hex_to_color("#ab7100");
|
color_border_bottom = draw_util_hex_to_color("#ab7100");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init_dpi();
|
||||||
font = load_font(pattern, true);
|
font = load_font(pattern, true);
|
||||||
set_font(&font);
|
set_font(&font);
|
||||||
|
|
||||||
|
@ -574,11 +541,8 @@ int main(int argc, char *argv[]) {
|
||||||
12,
|
12,
|
||||||
&strut_partial);
|
&strut_partial);
|
||||||
|
|
||||||
/* Create pixmap */
|
/* Initialize the drawable bar */
|
||||||
pixmap = xcb_generate_id(conn);
|
draw_util_surface_init(conn, &bar, win, get_visualtype(root_screen), win_pos.width, win_pos.height);
|
||||||
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);
|
|
||||||
|
|
||||||
/* Grab the keyboard to get all input */
|
/* Grab the keyboard to get all input */
|
||||||
xcb_flush(conn);
|
xcb_flush(conn);
|
||||||
|
@ -595,7 +559,10 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case XCB_EXPOSE:
|
case XCB_EXPOSE:
|
||||||
|
if (((xcb_expose_event_t *)event)->count == 0) {
|
||||||
handle_expose(conn, (xcb_expose_event_t *)event);
|
handle_expose(conn, (xcb_expose_event_t *)event);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case XCB_BUTTON_PRESS:
|
case XCB_BUTTON_PRESS:
|
||||||
|
@ -608,18 +575,7 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
case XCB_CONFIGURE_NOTIFY: {
|
case XCB_CONFIGURE_NOTIFY: {
|
||||||
xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t *)event;
|
xcb_configure_notify_event_t *configure_notify = (xcb_configure_notify_event_t *)event;
|
||||||
rect = (xcb_rectangle_t){
|
draw_util_surface_set_size(&bar, configure_notify->width, configure_notify->height);
|
||||||
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);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -628,6 +584,7 @@ int main(int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
FREE(pattern);
|
FREE(pattern);
|
||||||
|
draw_util_surface_free(conn, &bar);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
# mechanism to find the preferred editor
|
# mechanism to find the preferred editor
|
||||||
|
|
||||||
# Hopefully one of these is installed (no flamewars about preference please!):
|
# 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
|
if command -v "$editor" > /dev/null 2>&1; then
|
||||||
exec "$editor" "$@"
|
exec "$editor" "$@"
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
# We welcome patches that add distribution-specific mechanisms to find the
|
# We welcome patches that add distribution-specific mechanisms to find the
|
||||||
# preferred terminal emulator. On Debian, there is the x-terminal-emulator
|
# preferred terminal emulator. On Debian, there is the x-terminal-emulator
|
||||||
# symlink for example.
|
# 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
|
if command -v "$terminal" > /dev/null 2>&1; then
|
||||||
exec "$terminal" "$@"
|
exec "$terminal" "$@"
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -74,10 +74,12 @@ struct status_block {
|
||||||
char *name;
|
char *name;
|
||||||
char *instance;
|
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 "child.h"
|
||||||
#include "ipc.h"
|
#include "ipc.h"
|
||||||
|
|
|
@ -28,18 +28,23 @@ typedef struct binding_t {
|
||||||
int input_code;
|
int input_code;
|
||||||
char *command;
|
char *command;
|
||||||
|
|
||||||
TAILQ_ENTRY(binding_t) bindings;
|
TAILQ_ENTRY(binding_t)
|
||||||
|
bindings;
|
||||||
} binding_t;
|
} binding_t;
|
||||||
|
|
||||||
typedef struct tray_output_t {
|
typedef struct tray_output_t {
|
||||||
char *output;
|
char *output;
|
||||||
|
|
||||||
TAILQ_ENTRY(tray_output_t) tray_outputs;
|
TAILQ_ENTRY(tray_output_t)
|
||||||
|
tray_outputs;
|
||||||
} tray_output_t;
|
} tray_output_t;
|
||||||
|
|
||||||
typedef struct config_t {
|
typedef struct config_t {
|
||||||
int modifier;
|
int modifier;
|
||||||
TAILQ_HEAD(bindings_head, binding_t) bindings;
|
|
||||||
|
TAILQ_HEAD(bindings_head, binding_t)
|
||||||
|
bindings;
|
||||||
|
|
||||||
position_t position;
|
position_t position;
|
||||||
int verbose;
|
int verbose;
|
||||||
struct xcb_color_strings_t colors;
|
struct xcb_color_strings_t colors;
|
||||||
|
@ -50,7 +55,10 @@ typedef struct config_t {
|
||||||
char *command;
|
char *command;
|
||||||
char *fontname;
|
char *fontname;
|
||||||
i3String *separator_symbol;
|
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 tray_padding;
|
||||||
int num_outputs;
|
int num_outputs;
|
||||||
char **outputs;
|
char **outputs;
|
||||||
|
|
|
@ -67,5 +67,6 @@ struct i3_output {
|
||||||
struct ws_head* workspaces; /* The workspaces on this output */
|
struct ws_head* workspaces; /* The workspaces on this output */
|
||||||
struct tc_head* trayclients; /* The tray clients 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 */
|
bool mapped; /* Whether this window is mapped */
|
||||||
int xe_version; /* The XEMBED version supported by the client */
|
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)) */
|
rect rect; /* The rect of the ws (not used (yet)) */
|
||||||
struct i3_output *output; /* The current output of the ws */
|
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));
|
struct status_block *err_block = scalloc(1, sizeof(struct status_block));
|
||||||
err_block->full_text = i3string_from_utf8("Error: ");
|
err_block->full_text = i3string_from_utf8("Error: ");
|
||||||
err_block->name = sstrdup("error");
|
err_block->name = sstrdup("error");
|
||||||
err_block->color = sstrdup("red");
|
err_block->color = sstrdup("#ff0000");
|
||||||
err_block->no_separator = true;
|
err_block->no_separator = true;
|
||||||
|
|
||||||
struct status_block *message_block = scalloc(1, sizeof(struct status_block));
|
struct status_block *message_block = scalloc(1, sizeof(struct status_block));
|
||||||
message_block->full_text = i3string_from_utf8(message);
|
message_block->full_text = i3string_from_utf8(message);
|
||||||
message_block->name = sstrdup("error_message");
|
message_block->name = sstrdup("error_message");
|
||||||
message_block->color = sstrdup("red");
|
message_block->color = sstrdup("#ff0000");
|
||||||
message_block->no_separator = true;
|
message_block->no_separator = true;
|
||||||
|
|
||||||
TAILQ_INSERT_HEAD(&statusline_head, err_block, blocks);
|
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")) {
|
if (!strcmp(cur_key, "status_command")) {
|
||||||
DLOG("command = %.*s\n", len, val);
|
DLOG("command = %.*s\n", len, val);
|
||||||
|
FREE(config.command);
|
||||||
sasprintf(&config.command, "%.*s", len, val);
|
sasprintf(&config.command, "%.*s", len, val);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,16 +63,26 @@ void got_subscribe_reply(char *reply) {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void got_output_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");
|
DLOG("Parsing outputs JSON...\n");
|
||||||
parse_outputs_json(reply);
|
parse_outputs_json(reply);
|
||||||
DLOG("Reconfiguring windows...\n");
|
DLOG("Reconfiguring windows...\n");
|
||||||
reconfig_windows(false);
|
reconfig_windows(false);
|
||||||
|
|
||||||
i3_output *o_walk;
|
|
||||||
SLIST_FOREACH(o_walk, outputs, slist) {
|
SLIST_FOREACH(o_walk, outputs, slist) {
|
||||||
kick_tray_clients(o_walk);
|
kick_tray_clients(o_walk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!config.disable_ws) {
|
||||||
|
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
draw_bars(false);
|
draw_bars(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +113,6 @@ void got_bar_config(char *reply) {
|
||||||
init_colors(&(config.colors));
|
init_colors(&(config.colors));
|
||||||
|
|
||||||
start_child(config.command);
|
start_child(config.command);
|
||||||
FREE(config.command);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Data structure to easily call the reply handlers later */
|
/* 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 */
|
/* update the configuration with the received settings */
|
||||||
DLOG("Received bar config update \"%s\"\n", event);
|
DLOG("Received bar config update \"%s\"\n", event);
|
||||||
|
char *old_command = sstrdup(config.command);
|
||||||
bar_display_mode_t old_mode = config.hide_on_modifier;
|
bar_display_mode_t old_mode = config.hide_on_modifier;
|
||||||
parse_config_json(event);
|
parse_config_json(event);
|
||||||
if (old_mode != config.hide_on_modifier) {
|
if (old_mode != config.hide_on_modifier) {
|
||||||
|
@ -178,6 +188,13 @@ void got_bar_config_update(char *event) {
|
||||||
init_xcb_late(config.fontname);
|
init_xcb_late(config.fontname);
|
||||||
init_colors(&(config.colors));
|
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);
|
draw_bars(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,6 +149,8 @@ int main(int argc, char **argv) {
|
||||||
socket_path = expand_path(i3_default_sock_path);
|
socket_path = expand_path(i3_default_sock_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init_dpi();
|
||||||
|
|
||||||
init_outputs();
|
init_outputs();
|
||||||
if (init_connection(socket_path)) {
|
if (init_connection(socket_path)) {
|
||||||
/* Request the bar configuration. When it arrives, we fill the config array. */
|
/* 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) {
|
if (config.num_outputs > 0) {
|
||||||
bool handle_output = false;
|
bool handle_output = false;
|
||||||
for (int c = 0; c < config.num_outputs; c++) {
|
for (int c = 0; c < config.num_outputs; c++) {
|
||||||
if (strcasecmp(params->outputs_walk->name, config.outputs[c]) != 0)
|
if (strcasecmp(params->outputs_walk->name, config.outputs[c]) == 0 ||
|
||||||
continue;
|
(strcasecmp(config.outputs[c], "primary") == 0 &&
|
||||||
|
params->outputs_walk->primary)) {
|
||||||
handle_output = true;
|
handle_output = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!handle_output) {
|
if (!handle_output) {
|
||||||
DLOG("Ignoring output \"%s\", not configured to handle it.\n",
|
DLOG("Ignoring output \"%s\", not configured to handle it.\n",
|
||||||
params->outputs_walk->name);
|
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;
|
uint32_t center_x = x - sep_offset;
|
||||||
if (config.separator_symbol == NULL) {
|
if (config.separator_symbol == NULL) {
|
||||||
/* Draw a classic one pixel, vertical separator. */
|
/* 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,
|
center_x,
|
||||||
logical_px(sep_voff_px),
|
logical_px(sep_voff_px),
|
||||||
logical_px(1),
|
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;
|
struct status_block *block;
|
||||||
|
|
||||||
color_t bar_color = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg);
|
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.
|
/* 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
|
* 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 the border. */
|
||||||
draw_util_rectangle(xcb_connection, &output->statusline_buffer, border_color,
|
draw_util_rectangle(&output->statusline_buffer, border_color,
|
||||||
x, logical_px(1),
|
x, logical_px(1),
|
||||||
full_render_width,
|
full_render_width,
|
||||||
bar_height - logical_px(2));
|
bar_height - logical_px(2));
|
||||||
|
|
||||||
/* Draw the background. */
|
/* Draw the background. */
|
||||||
draw_util_rectangle(xcb_connection, &output->statusline_buffer, bg_color,
|
draw_util_rectangle(&output->statusline_buffer, bg_color,
|
||||||
x + border_width,
|
x + border_width,
|
||||||
logical_px(1) + border_width,
|
logical_px(1) + border_width,
|
||||||
full_render_width - 2 * border_width,
|
full_render_width - 2 * border_width,
|
||||||
|
@ -531,7 +531,8 @@ void handle_button(xcb_button_press_event_t *event) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (event->detail) {
|
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.
|
/* Mouse wheel up. We select the previous ws, if any.
|
||||||
* If there is no more workspace, don’t even send the workspace
|
* If there is no more workspace, don’t even send the workspace
|
||||||
* command, otherwise (with workspace auto_back_and_forth) we’d end
|
* 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);
|
cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq);
|
||||||
break;
|
break;
|
||||||
case 5:
|
case XCB_BUTTON_SCROLL_DOWN:
|
||||||
|
case XCB_BUTTON_SCROLL_RIGHT:
|
||||||
/* Mouse wheel down. We select the next ws, if any.
|
/* Mouse wheel down. We select the next ws, if any.
|
||||||
* If there is no more workspace, don’t even send the workspace
|
* If there is no more workspace, don’t even send the workspace
|
||||||
* command, otherwise (with workspace auto_back_and_forth) we’d end
|
* 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);
|
DLOG("checking output %s\n", walk->name);
|
||||||
trayclient *trayclient;
|
trayclient *trayclient;
|
||||||
TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
|
TAILQ_FOREACH(trayclient, walk->trayclients, tailq) {
|
||||||
if (trayclient->win != event->window)
|
if (trayclient->win != event->window) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
DLOG("Removing tray client with window ID %08x\n", event->window);
|
DLOG("Removing tray client with window ID %08x\n", event->window);
|
||||||
TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
|
TAILQ_REMOVE(walk->trayclients, trayclient, tailq);
|
||||||
|
FREE(trayclient);
|
||||||
|
|
||||||
/* Trigger an update, we now have more space for the statusline */
|
/* Trigger an update, we now have more space for the statusline */
|
||||||
configure_trayclients();
|
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);
|
handle_visibility_notify((xcb_visibility_notify_event_t *)event);
|
||||||
break;
|
break;
|
||||||
case XCB_EXPOSE:
|
case XCB_EXPOSE:
|
||||||
|
if (((xcb_expose_event_t *)event)->count == 0) {
|
||||||
/* Expose-events happen, when the window needs to be redrawn */
|
/* Expose-events happen, when the window needs to be redrawn */
|
||||||
redraw_bars();
|
redraw_bars();
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case XCB_BUTTON_PRESS:
|
case XCB_BUTTON_PRESS:
|
||||||
/* Button press events are mouse buttons clicked on one of our bars */
|
/* 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:
|
case XCB_CONFIGURE_REQUEST:
|
||||||
/* ConfigureRequest, sent by a tray child */
|
/* ConfigureRequest, sent by a tray child */
|
||||||
handle_configure_request((xcb_configure_request_event_t *)event);
|
handle_configure_request((xcb_configure_request_event_t *)event);
|
||||||
|
break;
|
||||||
case XCB_RESIZE_REQUEST:
|
case XCB_RESIZE_REQUEST:
|
||||||
/* ResizeRequest sent by a tray child using override_redirect. */
|
/* ResizeRequest sent by a tray child using override_redirect. */
|
||||||
handle_resize_request((xcb_resize_request_event_t *)event);
|
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_prepare_init(xcb_prep, &xcb_prep_cb);
|
||||||
ev_check_init(xcb_chk, &xcb_chk_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_io_start(main_loop, xcb_io);
|
||||||
ev_prepare_start(main_loop, xcb_prep);
|
ev_prepare_start(main_loop, xcb_prep);
|
||||||
ev_check_start(main_loop, xcb_chk);
|
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
|
/* We remove the trayclient right here. We might receive an UnmapNotify
|
||||||
* event afterwards, but better safe than sorry. */
|
* event afterwards, but better safe than sorry. */
|
||||||
TAILQ_REMOVE(output->trayclients, trayclient, tailq);
|
TAILQ_REMOVE(output->trayclients, trayclient, tailq);
|
||||||
|
FREE(trayclient);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fake a DestroyNotify so that Qt re-adds tray icons.
|
/* 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);
|
bool use_focus_colors = output_has_focus(outputs_walk);
|
||||||
|
|
||||||
/* First things first: clear the backbuffer */
|
/* First things first: clear the backbuffer */
|
||||||
draw_util_clear_surface(xcb_connection, &(outputs_walk->buffer),
|
draw_util_clear_surface(&(outputs_walk->buffer), (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg));
|
||||||
(use_focus_colors ? colors.focus_bar_bg : colors.bar_bg));
|
|
||||||
|
|
||||||
if (!config.disable_ws) {
|
if (!config.disable_ws) {
|
||||||
i3_ws *ws_walk;
|
i3_ws *ws_walk;
|
||||||
|
@ -1974,14 +1988,14 @@ void draw_bars(bool unhide) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Draw the border of the button. */
|
/* 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,
|
workspace_width,
|
||||||
logical_px(1),
|
logical_px(1),
|
||||||
ws_walk->name_width + 2 * logical_px(ws_hoff_px) + 2 * 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));
|
font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1));
|
||||||
|
|
||||||
/* Draw the inside of the button. */
|
/* 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),
|
workspace_width + logical_px(1),
|
||||||
2 * logical_px(1),
|
2 * logical_px(1),
|
||||||
ws_walk->name_width + 2 * logical_px(ws_hoff_px),
|
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 fg_color = colors.binding_mode_fg;
|
||||||
color_t bg_color = colors.binding_mode_bg;
|
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,
|
workspace_width,
|
||||||
logical_px(1),
|
logical_px(1),
|
||||||
binding.width + 2 * logical_px(ws_hoff_px) + 2 * 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));
|
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),
|
workspace_width + logical_px(1),
|
||||||
2 * logical_px(1),
|
2 * logical_px(1),
|
||||||
binding.width + 2 * logical_px(ws_hoff_px),
|
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;
|
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_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);
|
x_dest, 0, visible_statusline_width, (int16_t)bar_height);
|
||||||
|
|
||||||
outputs_walk->statusline_width = statusline_width;
|
outputs_walk->statusline_width = statusline_width;
|
||||||
|
@ -2077,7 +2091,7 @@ void redraw_bars(void) {
|
||||||
continue;
|
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);
|
0, 0, outputs_walk->rect.w, outputs_walk->rect.h);
|
||||||
xcb_flush(xcb_connection);
|
xcb_flush(xcb_connection);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,3 +31,4 @@ xmacro(_NET_DESKTOP_NAMES)
|
||||||
xmacro(_NET_DESKTOP_VIEWPORT)
|
xmacro(_NET_DESKTOP_VIEWPORT)
|
||||||
xmacro(_NET_ACTIVE_WINDOW)
|
xmacro(_NET_ACTIVE_WINDOW)
|
||||||
xmacro(_NET_CLOSE_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,
|
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 *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)
|
* 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);
|
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>'
|
* Implementation of 'title_format <format>'
|
||||||
*
|
*
|
||||||
|
|
|
@ -139,6 +139,12 @@ Con *con_inside_floating(Con *con);
|
||||||
*/
|
*/
|
||||||
bool con_inside_focused(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
|
* Returns the container with the given client window ID or NULL if no such
|
||||||
* container exists.
|
* container exists.
|
||||||
|
@ -461,3 +467,9 @@ void con_force_split_parents_redraw(Con *con);
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
i3String *con_parse_title_format(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(mouse_warping, const char *value);
|
||||||
CFGFUN(force_focus_wrapping, const char *value);
|
CFGFUN(force_focus_wrapping, const char *value);
|
||||||
CFGFUN(force_xinerama, const char *value);
|
CFGFUN(force_xinerama, const char *value);
|
||||||
|
CFGFUN(disable_randr15, const char *value);
|
||||||
CFGFUN(fake_outputs, const char *outputs);
|
CFGFUN(fake_outputs, const char *outputs);
|
||||||
CFGFUN(force_display_urgency_hint, const long duration_ms);
|
CFGFUN(force_display_urgency_hint, const long duration_ms);
|
||||||
CFGFUN(focus_on_window_activation, const char *mode);
|
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(floating_modifier, const char *modifiers);
|
||||||
CFGFUN(new_window, const char *windowtype, const char *border, const long width);
|
CFGFUN(new_window, const char *windowtype, const char *border, const long width);
|
||||||
CFGFUN(workspace, const char *workspace, const char *output);
|
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(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_font, const char *font);
|
||||||
CFGFUN(bar_separator_symbol, const char *separator);
|
CFGFUN(bar_separator_symbol, const char *separator);
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
typedef struct Config Config;
|
typedef struct Config Config;
|
||||||
typedef struct Barconfig Barconfig;
|
typedef struct Barconfig Barconfig;
|
||||||
extern char *current_configpath;
|
extern char *current_configpath;
|
||||||
|
extern char *current_config;
|
||||||
extern Config config;
|
extern Config config;
|
||||||
extern SLIST_HEAD(modes_head, Mode) modes;
|
extern SLIST_HEAD(modes_head, Mode) modes;
|
||||||
extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;
|
extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;
|
||||||
|
@ -68,7 +69,8 @@ struct Variable {
|
||||||
char *value;
|
char *value;
|
||||||
char *next_match;
|
char *next_match;
|
||||||
|
|
||||||
SLIST_ENTRY(Variable) variables;
|
SLIST_ENTRY(Variable)
|
||||||
|
variables;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,7 +84,8 @@ struct Mode {
|
||||||
bool pango_markup;
|
bool pango_markup;
|
||||||
struct bindings_head *bindings;
|
struct bindings_head *bindings;
|
||||||
|
|
||||||
SLIST_ENTRY(Mode) modes;
|
SLIST_ENTRY(Mode)
|
||||||
|
modes;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,6 +157,9 @@ struct Config {
|
||||||
* is fetched once and never updated. */
|
* is fetched once and never updated. */
|
||||||
bool force_xinerama;
|
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 */
|
/** Overwrites output detection (for testing), see src/fake_outputs.c */
|
||||||
char *fake_outputs;
|
char *fake_outputs;
|
||||||
|
|
||||||
|
@ -253,7 +259,8 @@ struct Barconfig {
|
||||||
/* List of outputs on which the tray is allowed to be shown, in order.
|
/* 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 "none" disables it (per default, it will be shown) and
|
||||||
* the special value "primary" enabled it on the primary output. */
|
* 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. */
|
/* Padding around the tray icons. */
|
||||||
int tray_padding;
|
int tray_padding;
|
||||||
|
@ -284,7 +291,8 @@ struct Barconfig {
|
||||||
M_MOD5 = 7
|
M_MOD5 = 7
|
||||||
} modifier;
|
} modifier;
|
||||||
|
|
||||||
TAILQ_HEAD(bar_bindings_head, Barbinding) bar_bindings;
|
TAILQ_HEAD(bar_bindings_head, Barbinding)
|
||||||
|
bar_bindings;
|
||||||
|
|
||||||
/** Bar position (bottom by default). */
|
/** Bar position (bottom by default). */
|
||||||
enum { P_BOTTOM = 0,
|
enum { P_BOTTOM = 0,
|
||||||
|
@ -351,7 +359,8 @@ struct Barconfig {
|
||||||
char *binding_mode_text;
|
char *binding_mode_text;
|
||||||
} colors;
|
} 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. */
|
/** The command which is to be executed for this button. */
|
||||||
char *command;
|
char *command;
|
||||||
|
|
||||||
TAILQ_ENTRY(Barbinding) bindings;
|
TAILQ_ENTRY(Barbinding)
|
||||||
|
bindings;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct tray_output_t {
|
struct tray_output_t {
|
||||||
char *output;
|
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 *name;
|
||||||
char *output;
|
char *output;
|
||||||
|
|
||||||
TAILQ_ENTRY(Workspace_Assignment) ws_assignments;
|
TAILQ_ENTRY(Workspace_Assignment)
|
||||||
|
ws_assignments;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Ignore_Event {
|
struct Ignore_Event {
|
||||||
|
@ -207,7 +208,8 @@ struct Ignore_Event {
|
||||||
int response_type;
|
int response_type;
|
||||||
time_t added;
|
time_t added;
|
||||||
|
|
||||||
SLIST_ENTRY(Ignore_Event) ignore_events;
|
SLIST_ENTRY(Ignore_Event)
|
||||||
|
ignore_events;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -226,7 +228,8 @@ struct Startup_Sequence {
|
||||||
* completed) */
|
* completed) */
|
||||||
time_t delete_at;
|
time_t delete_at;
|
||||||
|
|
||||||
TAILQ_ENTRY(Startup_Sequence) sequences;
|
TAILQ_ENTRY(Startup_Sequence)
|
||||||
|
sequences;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -252,7 +255,9 @@ struct regex {
|
||||||
struct Binding_Keycode {
|
struct Binding_Keycode {
|
||||||
xcb_keycode_t keycode;
|
xcb_keycode_t keycode;
|
||||||
i3_event_state_mask_t modifiers;
|
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). */
|
* title bar (default). */
|
||||||
bool whole_window;
|
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 */
|
/** Keycode to bind */
|
||||||
uint32_t keycode;
|
uint32_t keycode;
|
||||||
|
|
||||||
|
@ -309,12 +318,14 @@ struct Binding {
|
||||||
/** Only in use if symbol != NULL. Contains keycodes which generate the
|
/** Only in use if symbol != NULL. Contains keycodes which generate the
|
||||||
* specified symbol. Useful for unbinding and checking which binding was
|
* specified symbol. Useful for unbinding and checking which binding was
|
||||||
* used when a key press event comes in. */
|
* 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 */
|
/** Command, like in command mode */
|
||||||
char *command;
|
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
|
/** no_startup_id flag for start_application(). Determines whether a
|
||||||
* startup notification context/ID should be created. */
|
* startup notification context/ID should be created. */
|
||||||
bool no_startup_id;
|
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 */
|
/** x, y, width, height */
|
||||||
Rect rect;
|
Rect rect;
|
||||||
|
|
||||||
TAILQ_ENTRY(xoutput) outputs;
|
TAILQ_ENTRY(xoutput)
|
||||||
|
outputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -438,6 +454,10 @@ struct Window {
|
||||||
int width_increment;
|
int width_increment;
|
||||||
int height_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) */
|
/* aspect ratio from WM_NORMAL_HINTS (MPlayer uses this for example) */
|
||||||
double aspect_ratio;
|
double aspect_ratio;
|
||||||
};
|
};
|
||||||
|
@ -493,7 +513,8 @@ struct Match {
|
||||||
M_ASSIGN_WS,
|
M_ASSIGN_WS,
|
||||||
M_BELOW } insert_where;
|
M_BELOW } insert_where;
|
||||||
|
|
||||||
TAILQ_ENTRY(Match) matches;
|
TAILQ_ENTRY(Match)
|
||||||
|
matches;
|
||||||
|
|
||||||
/* Whether this match was generated when restarting i3 inplace.
|
/* Whether this match was generated when restarting i3 inplace.
|
||||||
* Leads to not setting focus when managing a new window, because the old
|
* Leads to not setting focus when managing a new window, because the old
|
||||||
|
@ -537,7 +558,8 @@ struct Assignment {
|
||||||
char *workspace;
|
char *workspace;
|
||||||
} dest;
|
} dest;
|
||||||
|
|
||||||
TAILQ_ENTRY(Assignment) assignments;
|
TAILQ_ENTRY(Assignment)
|
||||||
|
assignments;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Fullscreen modes. Used by Con.fullscreen_mode. */
|
/** Fullscreen modes. Used by Con.fullscreen_mode. */
|
||||||
|
@ -548,7 +570,8 @@ typedef enum { CF_NONE = 0,
|
||||||
struct mark_t {
|
struct mark_t {
|
||||||
char *name;
|
char *name;
|
||||||
|
|
||||||
TAILQ_ENTRY(mark_t) marks;
|
TAILQ_ENTRY(mark_t)
|
||||||
|
marks;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -612,7 +635,8 @@ struct Con {
|
||||||
char *sticky_group;
|
char *sticky_group;
|
||||||
|
|
||||||
/* user-definable marks to jump to this container later */
|
/* 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 */
|
/* cached to decide whether a redraw is needed */
|
||||||
bool mark_changed;
|
bool mark_changed;
|
||||||
|
|
||||||
|
@ -631,12 +655,17 @@ struct Con {
|
||||||
struct deco_render_params *deco_render_params;
|
struct deco_render_params *deco_render_params;
|
||||||
|
|
||||||
/* Only workspace-containers can have floating clients */
|
/* 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(nodes_head, Con)
|
||||||
TAILQ_HEAD(focus_head, Con) focus_head;
|
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;
|
fullscreen_mode_t fullscreen_mode;
|
||||||
|
|
||||||
|
@ -674,10 +703,17 @@ struct Con {
|
||||||
FLOATING_USER_ON = 3
|
FLOATING_USER_ON = 3
|
||||||
} floating;
|
} floating;
|
||||||
|
|
||||||
TAILQ_ENTRY(Con) nodes;
|
TAILQ_ENTRY(Con)
|
||||||
TAILQ_ENTRY(Con) focused;
|
nodes;
|
||||||
TAILQ_ENTRY(Con) all_cons;
|
|
||||||
TAILQ_ENTRY(Con) floating_windows;
|
TAILQ_ENTRY(Con)
|
||||||
|
focused;
|
||||||
|
|
||||||
|
TAILQ_ENTRY(Con)
|
||||||
|
all_cons;
|
||||||
|
|
||||||
|
TAILQ_ENTRY(Con)
|
||||||
|
floating_windows;
|
||||||
|
|
||||||
/** callbacks */
|
/** callbacks */
|
||||||
void (*on_remove_child)(Con *);
|
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. */
|
/** Request a list of configured binding modes. */
|
||||||
#define I3_IPC_MESSAGE_TYPE_GET_BINDING_MODES 8
|
#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
|
* 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_BAR_CONFIG 6
|
||||||
#define I3_IPC_REPLY_TYPE_VERSION 7
|
#define I3_IPC_REPLY_TYPE_VERSION 7
|
||||||
#define I3_IPC_REPLY_TYPE_BINDING_MODES 8
|
#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.
|
* 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 */
|
/** The binding event will be triggered when bindings run */
|
||||||
#define I3_IPC_EVENT_BINDING (I3_IPC_EVENT_MASK | 5)
|
#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;
|
int num_events;
|
||||||
char **events;
|
char **events;
|
||||||
|
|
||||||
TAILQ_ENTRY(ipc_client) clients;
|
TAILQ_ENTRY(ipc_client)
|
||||||
|
clients;
|
||||||
} ipc_client;
|
} 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);
|
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
|
* Calls to ipc_shutdown() should provide a reason for the shutdown.
|
||||||
* when exiting or restarting only!
|
*/
|
||||||
|
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);
|
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)
|
#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
|
* XCB connection and root screen
|
||||||
*
|
*
|
||||||
|
@ -473,6 +483,12 @@ char *get_exe_path(const char *argv0);
|
||||||
*/
|
*/
|
||||||
void init_dpi(void);
|
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
|
* 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
|
* 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.
|
* 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.
|
* 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.
|
* 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);
|
double dest_x, double dest_y, double width, double height);
|
||||||
|
|
|
@ -446,7 +446,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CIRCLEQ_HEAD_INITIALIZER(head) \
|
#define CIRCLEQ_HEAD_INITIALIZER(head) \
|
||||||
{ CIRCLEQ_END(&head), CIRCLEQ_END(&head) }
|
{ \
|
||||||
|
CIRCLEQ_END(&head) \
|
||||||
|
, CIRCLEQ_END(&head) \
|
||||||
|
}
|
||||||
|
|
||||||
#define CIRCLEQ_ENTRY(type) \
|
#define CIRCLEQ_ENTRY(type) \
|
||||||
struct { \
|
struct { \
|
||||||
|
|
|
@ -29,7 +29,7 @@ typedef enum {
|
||||||
* XRandR information to setup workspaces for each screen.
|
* 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
|
* 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);
|
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
|
* Returns the active (!) output which contains the coordinates x, y or NULL
|
||||||
|
|
|
@ -3,10 +3,6 @@
|
||||||
*
|
*
|
||||||
* i3 - an improved dynamic tiling window manager
|
* i3 - an improved dynamic tiling window manager
|
||||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
* © 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
|
#pragma once
|
||||||
|
@ -14,7 +10,8 @@
|
||||||
#include <config.h>
|
#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);
|
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);
|
__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
|
* Parses the workspace name as a number. Returns -1 if the workspace should be
|
||||||
* interpreted as a "named workspace".
|
* 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);
|
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,
|
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);
|
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
|
* Generates a configure_notify_event with absolute coordinates (relative to
|
||||||
* the X root window, not to the client’s frame) for the given client.
|
* 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);
|
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
|
* 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);
|
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.
|
* Set the cursor of the root window to the given cursor id.
|
||||||
* This function should only be used if xcursor_supported == false.
|
* 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);
|
DLOG("Found Xft.dpi = %ld.\n", dpi);
|
||||||
|
|
||||||
init_dpi_end:
|
init_dpi_end:
|
||||||
|
if (resource != NULL) {
|
||||||
|
free(resource);
|
||||||
|
}
|
||||||
|
|
||||||
if (database != NULL) {
|
if (database != NULL) {
|
||||||
xcb_xrm_database_free(database);
|
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
|
* 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
|
* screen) to a corresponding amount of physical pixels on a standard or retina
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
xcb_visualtype_t *visual_type;
|
xcb_visualtype_t *visual_type;
|
||||||
|
|
||||||
/* Forward declarations */
|
/* 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) \
|
#define RETURN_UNLESS_SURFACE_INITIALIZED(surface) \
|
||||||
do { \
|
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) {
|
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];
|
char alpha[2];
|
||||||
if (strlen(color) == strlen("#rrggbbaa")) {
|
if (strlen(color) == strlen("#rrggbbaa")) {
|
||||||
alpha[0] = color[7];
|
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.
|
* 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);
|
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
|
||||||
|
|
||||||
cairo_set_source_rgba(surface->cr, color.red, color.green, color.blue, color.alpha);
|
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.
|
* 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);
|
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
|
||||||
|
|
||||||
cairo_save(surface->cr);
|
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
|
* onto the surface rather than blending it. This is a bit more efficient and
|
||||||
* allows better color control for the user when using opacity. */
|
* allows better color control for the user when using opacity. */
|
||||||
cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE);
|
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_rectangle(surface->cr, x, y, w, h);
|
||||||
cairo_fill(surface->cr);
|
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.
|
* 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);
|
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
|
||||||
|
|
||||||
cairo_save(surface->cr);
|
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
|
* onto the surface rather than blending it. This is a bit more efficient and
|
||||||
* allows better color control for the user when using opacity. */
|
* allows better color control for the user when using opacity. */
|
||||||
cairo_set_operator(surface->cr, CAIRO_OPERATOR_SOURCE);
|
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);
|
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.
|
* 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) {
|
double dest_x, double dest_y, double width, double height) {
|
||||||
RETURN_UNLESS_SURFACE_INITIALIZED(src);
|
RETURN_UNLESS_SURFACE_INITIALIZED(src);
|
||||||
RETURN_UNLESS_SURFACE_INITIALIZED(dest);
|
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_green;
|
||||||
static double pango_font_blue;
|
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) {
|
static PangoLayout *create_layout_with_dpi(cairo_t *cr) {
|
||||||
PangoLayout *layout;
|
PangoLayout *layout;
|
||||||
PangoContext *context;
|
PangoContext *context;
|
||||||
|
|
||||||
context = pango_cairo_create_context(cr);
|
context = pango_cairo_create_context(cr);
|
||||||
const double dpi = (double)root_screen->height_in_pixels * 25.4 /
|
pango_cairo_context_set_resolution(context, get_dpi_value());
|
||||||
(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);
|
|
||||||
layout = pango_layout_new(context);
|
layout = pango_layout_new(context);
|
||||||
g_object_unref(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 */
|
/* 4: check for $XDG_CONFIG_DIRS/i3/config */
|
||||||
if ((xdg_config_dirs = getenv("XDG_CONFIG_DIRS")) == NULL)
|
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 *buf = sstrdup(xdg_config_dirs);
|
||||||
char *tok = strtok(buf, ":");
|
char *tok = strtok(buf, ":");
|
||||||
|
|
|
@ -74,7 +74,7 @@
|
||||||
AC_DEFUN([AX_EXTEND_SRCDIR],
|
AC_DEFUN([AX_EXTEND_SRCDIR],
|
||||||
[dnl
|
[dnl
|
||||||
AS_CASE([$srcdir],
|
AS_CASE([$srcdir],
|
||||||
[.|.*],
|
[.|.*|/*],
|
||||||
[
|
[
|
||||||
# pwd -P is specified in IEEE 1003.1 from 2004
|
# pwd -P is specified in IEEE 1003.1 from 2004
|
||||||
as_dir=`cd "$srcdir" && pwd -P`
|
as_dir=`cd "$srcdir" && pwd -P`
|
||||||
|
|
|
@ -29,7 +29,7 @@ It tries to start one of the following (in that order):
|
||||||
* mg
|
* mg
|
||||||
* jed
|
* jed
|
||||||
* gedit
|
* gedit
|
||||||
* mc-edit
|
* mcedit
|
||||||
|
|
||||||
Please don’t complain about the order: If the user has any preference, they will
|
Please don’t complain about the order: If the user has any preference, they will
|
||||||
have $VISUAL or $EDITOR set.
|
have $VISUAL or $EDITOR set.
|
||||||
|
|
|
@ -40,6 +40,10 @@ It tries to start one of the following (in that order):
|
||||||
* terminology
|
* terminology
|
||||||
* st
|
* st
|
||||||
* qterminal
|
* qterminal
|
||||||
|
* lilyterm
|
||||||
|
* tilix
|
||||||
|
* terminix
|
||||||
|
* konsole
|
||||||
|
|
||||||
Please don’t complain about the order: If the user has any preference, they will
|
Please don’t complain about the order: If the user has any preference, they will
|
||||||
have $TERMINAL set or modified their i3 configuration file.
|
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:
|
When starting, i3 looks for configuration files in the following order:
|
||||||
|
|
||||||
1. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set)
|
1. ~/.i3/config
|
||||||
2. /etc/xdg/i3/config (or $XDG_CONFIG_DIRS/i3/config if set)
|
2. ~/.config/i3/config (or $XDG_CONFIG_HOME/i3/config if set)
|
||||||
3. ~/.i3/config
|
3. /etc/i3/config
|
||||||
4. /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.
|
You can specify a custom path using the -c option.
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ state INITIAL:
|
||||||
'rename' -> RENAME
|
'rename' -> RENAME
|
||||||
'nop' -> NOP
|
'nop' -> NOP
|
||||||
'scratchpad' -> SCRATCHPAD
|
'scratchpad' -> SCRATCHPAD
|
||||||
|
'swap' -> SWAP
|
||||||
'title_format' -> TITLE_FORMAT
|
'title_format' -> TITLE_FORMAT
|
||||||
'mode' -> MODE
|
'mode' -> MODE
|
||||||
'bar' -> BAR
|
'bar' -> BAR
|
||||||
|
@ -110,7 +111,7 @@ state LAYOUT:
|
||||||
state LAYOUT_TOGGLE:
|
state LAYOUT_TOGGLE:
|
||||||
end
|
end
|
||||||
-> call cmd_layout_toggle($toggle_mode)
|
-> call cmd_layout_toggle($toggle_mode)
|
||||||
toggle_mode = 'split', 'all'
|
toggle_mode = string
|
||||||
-> call cmd_layout_toggle($toggle_mode)
|
-> call cmd_layout_toggle($toggle_mode)
|
||||||
|
|
||||||
# append_layout <path>
|
# append_layout <path>
|
||||||
|
@ -273,24 +274,28 @@ state RENAME:
|
||||||
-> RENAME_WORKSPACE
|
-> RENAME_WORKSPACE
|
||||||
|
|
||||||
state RENAME_WORKSPACE:
|
state RENAME_WORKSPACE:
|
||||||
old_name = 'to'
|
'to'
|
||||||
-> RENAME_WORKSPACE_LIKELY_TO
|
-> RENAME_WORKSPACE_LIKELY_TO
|
||||||
old_name = word
|
old_name = word
|
||||||
-> RENAME_WORKSPACE_TO
|
-> RENAME_WORKSPACE_TO
|
||||||
|
|
||||||
state RENAME_WORKSPACE_LIKELY_TO:
|
state RENAME_WORKSPACE_LIKELY_TO:
|
||||||
'to'
|
'to '
|
||||||
-> RENAME_WORKSPACE_NEW_NAME
|
-> RENAME_WORKSPACE_LIKELY_TO_NEW_NAME
|
||||||
new_name = word
|
new_name = word
|
||||||
-> call cmd_rename_workspace(NULL, $new_name)
|
-> call cmd_rename_workspace(NULL, $new_name)
|
||||||
|
|
||||||
state RENAME_WORKSPACE_TO:
|
state RENAME_WORKSPACE_LIKELY_TO_NEW_NAME:
|
||||||
'to'
|
new_name = string
|
||||||
-> RENAME_WORKSPACE_NEW_NAME
|
-> call cmd_rename_workspace("to", $new_name)
|
||||||
|
|
||||||
state RENAME_WORKSPACE_NEW_NAME:
|
|
||||||
end
|
end
|
||||||
-> call cmd_rename_workspace(NULL, "to")
|
-> 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
|
new_name = string
|
||||||
-> call cmd_rename_workspace($old_name, $new_name)
|
-> call cmd_rename_workspace($old_name, $new_name)
|
||||||
|
|
||||||
|
@ -406,6 +411,21 @@ state SCRATCHPAD:
|
||||||
'show'
|
'show'
|
||||||
-> call cmd_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:
|
state TITLE_FORMAT:
|
||||||
format = string
|
format = string
|
||||||
-> call cmd_title_format($format)
|
-> call cmd_title_format($format)
|
||||||
|
|
|
@ -17,7 +17,8 @@ state INITIAL:
|
||||||
end ->
|
end ->
|
||||||
error ->
|
error ->
|
||||||
'#' -> IGNORE_LINE
|
'#' -> IGNORE_LINE
|
||||||
'set' -> IGNORE_LINE
|
'set ' -> IGNORE_LINE
|
||||||
|
'set ' -> IGNORE_LINE
|
||||||
'set_from_resource' -> IGNORE_LINE
|
'set_from_resource' -> IGNORE_LINE
|
||||||
bindtype = 'bindsym', 'bindcode', 'bind' -> BINDING
|
bindtype = 'bindsym', 'bindcode', 'bind' -> BINDING
|
||||||
'bar' -> BARBRACE
|
'bar' -> BARBRACE
|
||||||
|
@ -37,6 +38,7 @@ state INITIAL:
|
||||||
'mouse_warping' -> MOUSE_WARPING
|
'mouse_warping' -> MOUSE_WARPING
|
||||||
'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING
|
'force_focus_wrapping' -> FORCE_FOCUS_WRAPPING
|
||||||
'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA
|
'force_xinerama', 'force-xinerama' -> FORCE_XINERAMA
|
||||||
|
'disable_randr15', 'disable-randr15' -> DISABLE_RANDR15
|
||||||
'workspace_auto_back_and_forth' -> WORKSPACE_BACK_AND_FORTH
|
'workspace_auto_back_and_forth' -> WORKSPACE_BACK_AND_FORTH
|
||||||
'fake_outputs', 'fake-outputs' -> FAKE_OUTPUTS
|
'fake_outputs', 'fake-outputs' -> FAKE_OUTPUTS
|
||||||
'force_display_urgency_hint' -> FORCE_DISPLAY_URGENCY_HINT
|
'force_display_urgency_hint' -> FORCE_DISPLAY_URGENCY_HINT
|
||||||
|
@ -205,6 +207,11 @@ state FORCE_XINERAMA:
|
||||||
value = word
|
value = word
|
||||||
-> call cfg_force_xinerama($value)
|
-> call cfg_force_xinerama($value)
|
||||||
|
|
||||||
|
# disable_randr15
|
||||||
|
state DISABLE_RANDR15:
|
||||||
|
value = word
|
||||||
|
-> call cfg_disable_randr15($value)
|
||||||
|
|
||||||
# workspace_back_and_forth
|
# workspace_back_and_forth
|
||||||
state WORKSPACE_BACK_AND_FORTH:
|
state WORKSPACE_BACK_AND_FORTH:
|
||||||
value = word
|
value = word
|
||||||
|
@ -315,6 +322,8 @@ state BINDING:
|
||||||
->
|
->
|
||||||
whole_window = '--whole-window'
|
whole_window = '--whole-window'
|
||||||
->
|
->
|
||||||
|
exclude_titlebar = '--exclude-titlebar'
|
||||||
|
->
|
||||||
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod'
|
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'
|
whole_window = '--whole-window'
|
||||||
->
|
->
|
||||||
|
exclude_titlebar = '--exclude-titlebar'
|
||||||
|
->
|
||||||
command = string
|
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
|
# Mode configuration
|
||||||
|
@ -370,6 +381,8 @@ state MODE_BINDING:
|
||||||
->
|
->
|
||||||
whole_window = '--whole-window'
|
whole_window = '--whole-window'
|
||||||
->
|
->
|
||||||
|
exclude_titlebar = '--exclude-titlebar'
|
||||||
|
->
|
||||||
modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl', 'Mode_switch', 'Group1', 'Group2', 'Group3', 'Group4', '$mod'
|
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'
|
whole_window = '--whole-window'
|
||||||
->
|
->
|
||||||
|
exclude_titlebar = '--exclude-titlebar'
|
||||||
|
->
|
||||||
command = string
|
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)
|
# Bar configuration (i3bar)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#!/bin/zsh
|
#!/bin/zsh
|
||||||
# This script is used to prepare a new release of i3.
|
# This script is used to prepare a new release of i3.
|
||||||
|
|
||||||
export RELEASE_VERSION="4.12"
|
export RELEASE_VERSION="4.13"
|
||||||
export PREVIOUS_VERSION="4.11"
|
export PREVIOUS_VERSION="4.12"
|
||||||
export RELEASE_BRANCH="next"
|
export RELEASE_BRANCH="next"
|
||||||
|
|
||||||
if [ ! -e "../i3.github.io" ]
|
if [ ! -e "../i3.github.io" ]
|
||||||
|
@ -232,7 +232,12 @@ echo ""
|
||||||
echo " cd ${TMPDIR}"
|
echo " cd ${TMPDIR}"
|
||||||
echo " sendmail -t < email.txt"
|
echo " sendmail -t < email.txt"
|
||||||
echo ""
|
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 "Announce on:"
|
||||||
echo " twitter"
|
echo " twitter"
|
||||||
echo " google+"
|
echo " google+"
|
||||||
echo " #i3 topic"
|
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 */
|
/* Try to find the mode in the list of modes and return it */
|
||||||
SLIST_FOREACH(mode, &modes, modes) {
|
SLIST_FOREACH(mode, &modes, modes) {
|
||||||
if (strcmp(mode->name, name) == 0)
|
if (strcmp(mode->name, name) == 0) {
|
||||||
return mode;
|
return mode;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* If the mode was not found, create a new one */
|
/* If the mode was not found, create a new one */
|
||||||
mode = scalloc(1, sizeof(struct Mode));
|
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,
|
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 *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));
|
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);
|
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->release = (release != NULL ? B_UPON_KEYRELEASE : B_UPON_KEYPRESS);
|
||||||
new_binding->border = (border != NULL);
|
new_binding->border = (border != NULL);
|
||||||
new_binding->whole_window = (whole_window != NULL);
|
new_binding->whole_window = (whole_window != NULL);
|
||||||
|
new_binding->exclude_titlebar = (exclude_titlebar != NULL);
|
||||||
if (strcmp(bindtype, "bindsym") == 0) {
|
if (strcmp(bindtype, "bindsym") == 0) {
|
||||||
new_binding->input_type = (strncasecmp(input_code, "button", (sizeof("button") - 1)) == 0
|
new_binding->input_type = (strncasecmp(input_code, "button", (sizeof("button") - 1)) == 0
|
||||||
? B_MOUSE
|
? B_MOUSE
|
||||||
|
@ -68,15 +71,15 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch
|
||||||
|
|
||||||
new_binding->symbol = sstrdup(input_code);
|
new_binding->symbol = sstrdup(input_code);
|
||||||
} else {
|
} else {
|
||||||
char *endptr;
|
long keycode;
|
||||||
long keycode = strtol(input_code, &endptr, 10);
|
if (!parse_long(input_code, &keycode, 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) {
|
|
||||||
ELOG("Could not parse \"%s\" as an input code, ignoring this binding.\n", input_code);
|
ELOG("Could not parse \"%s\" as an input code, ignoring this binding.\n", input_code);
|
||||||
FREE(new_binding);
|
FREE(new_binding);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
new_binding->keycode = keycode;
|
||||||
|
new_binding->input_type = B_KEYBOARD;
|
||||||
}
|
}
|
||||||
new_binding->command = sstrdup(command);
|
new_binding->command = sstrdup(command);
|
||||||
new_binding->event_state_mask = event_state_from_str(modifiers);
|
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);
|
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
|
* Returns a pointer to the Binding with the specified modifiers and
|
||||||
* keycode or NULL if no such binding exists.
|
* 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 xkb_group_state = (state_filtered & 0xFFFF0000);
|
||||||
const uint32_t modifiers_state = (state_filtered & 0x0000FFFF);
|
const uint32_t modifiers_state = (state_filtered & 0x0000FFFF);
|
||||||
TAILQ_FOREACH(bind, bindings, bindings) {
|
TAILQ_FOREACH(bind, bindings, bindings) {
|
||||||
if (bind->input_type != input_type)
|
if (bind->input_type != input_type) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const uint32_t xkb_group_mask = (bind->event_state_mask & 0xFFFF0000);
|
const uint32_t xkb_group_mask = (bind->event_state_mask & 0xFFFF0000);
|
||||||
const bool groups_match = ((xkb_group_state & xkb_group_mask) == xkb_group_mask);
|
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;
|
struct Binding_Keycode *binding_keycode;
|
||||||
TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) {
|
TAILQ_FOREACH(binding_keycode, &(bind->keycodes_head), keycodes) {
|
||||||
const uint32_t modifiers_mask = (binding_keycode->modifiers & 0x0000FFFF);
|
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",
|
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"));
|
binding_keycode->modifiers, modifiers_mask, modifiers_state, (mods_match ? "yes" : "no"));
|
||||||
if (binding_keycode->keycode == input_keycode && mods_match) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found_keycode)
|
if (!found_keycode) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
} else {
|
} 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 */
|
/* This case is easier: The user specified a keycode */
|
||||||
if (bind->keycode != input_code)
|
if (bind->keycode != input_code) {
|
||||||
continue;
|
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
|
/* 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
|
* user pressed. We therefore mark it as B_UPON_KEYRELEASE_IGNORE_MODS
|
||||||
* for later, so that the user can release the modifiers before the
|
* 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 */
|
/* Check if the binding is for a press or a release event */
|
||||||
if ((bind->release == B_UPON_KEYPRESS && is_release) ||
|
if ((bind->release == B_UPON_KEYPRESS && is_release) ||
|
||||||
(bind->release >= B_UPON_KEYRELEASE && !is_release))
|
(bind->release >= B_UPON_KEYRELEASE && !is_release)) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -457,26 +458,23 @@ void translate_keysyms(void) {
|
||||||
bool has_errors = false;
|
bool has_errors = false;
|
||||||
Binding *bind;
|
Binding *bind;
|
||||||
TAILQ_FOREACH(bind, bindings, bindings) {
|
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) {
|
if (bind->input_type == B_MOUSE) {
|
||||||
char *endptr;
|
long button;
|
||||||
long button = strtol(bind->symbol + (sizeof("button") - 1), &endptr, 10);
|
if (!parse_long(bind->symbol + (sizeof("button") - 1), &button, 10)) {
|
||||||
bind->keycode = button;
|
|
||||||
|
|
||||||
if (button == LONG_MAX || button == LONG_MIN || button < 0 || *endptr != '\0' || endptr == bind->symbol)
|
|
||||||
ELOG("Could not translate string to button: \"%s\"\n", bind->symbol);
|
ELOG("Could not translate string to button: \"%s\"\n", bind->symbol);
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bind->keycode > 0)
|
xcb_keycode_t key = button;
|
||||||
continue;
|
bind->keycode = key;
|
||||||
|
DLOG("Binding Mouse button, Keycode = %d\n", key);
|
||||||
/* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
xkb_layout_index_t group = XCB_XKB_GROUP_1;
|
xkb_layout_index_t group = XCB_XKB_GROUP_1;
|
||||||
|
@ -530,6 +528,52 @@ void translate_keysyms(void) {
|
||||||
0 /* xkb_layout_index_t latched_group, */,
|
0 /* xkb_layout_index_t latched_group, */,
|
||||||
group /* xkb_layout_index_t locked_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 = {
|
struct resolve resolving = {
|
||||||
.bind = bind,
|
.bind = bind,
|
||||||
.keysym = keysym,
|
.keysym = keysym,
|
||||||
|
@ -572,6 +616,8 @@ void translate_keysyms(void) {
|
||||||
DLOG("state=0x%x, cfg=\"%s\", sym=0x%x → keycodes%s (%d)\n",
|
DLOG("state=0x%x, cfg=\"%s\", sym=0x%x → keycodes%s (%d)\n",
|
||||||
bind->event_state_mask, bind->symbol, keysym, keycodes, num_keycodes);
|
bind->event_state_mask, bind->symbol, keysym, keycodes, num_keycodes);
|
||||||
free(keycodes);
|
free(keycodes);
|
||||||
|
|
||||||
|
#undef ADD_TRANSLATED_KEY
|
||||||
}
|
}
|
||||||
|
|
||||||
xkb_state_unref(dummy_state);
|
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)
|
if (bind->input_type != B_MOUSE || !bind->whole_window)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
char *endptr;
|
long button;
|
||||||
long button = strtol(bind->symbol + (sizeof("button") - 1), &endptr, 10);
|
if (!parse_long(bind->symbol + (sizeof("button") - 1), &button, 10)) {
|
||||||
if (button == LONG_MAX || button == LONG_MIN || button < 0 || *endptr != '\0' || endptr == bind->symbol) {
|
|
||||||
ELOG("Could not parse button number, skipping this binding. Please report this bug in i3.\n");
|
ELOG("Could not parse button number, skipping this binding. Please report this bug in i3.\n");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Avoid duplicates. */
|
/* Avoid duplicates. */
|
||||||
for (int i = 0; i < num_max; i++) {
|
for (int i = 0; i < num; i++) {
|
||||||
if (buffer[i] == button)
|
if (buffer[i] == button)
|
||||||
continue;
|
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)
|
if (con->parent->type == CT_DOCKAREA)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
const bool is_left_or_right_click = (event->detail == XCB_BUTTON_INDEX_1 ||
|
const bool is_left_or_right_click = (event->detail == XCB_BUTTON_CLICK_LEFT ||
|
||||||
event->detail == XCB_BUTTON_INDEX_3);
|
event->detail == XCB_BUTTON_CLICK_RIGHT);
|
||||||
|
|
||||||
/* if the user has bound an action to this click, it should override the
|
/* if the user has bound an action to this click, it should override the
|
||||||
* default behavior. */
|
* default behavior. */
|
||||||
if (dest == CLICK_DECORATION || dest == CLICK_INSIDE || dest == CLICK_BORDER) {
|
if (dest == CLICK_DECORATION || dest == CLICK_INSIDE || dest == CLICK_BORDER) {
|
||||||
Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event);
|
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_INSIDE && bind->whole_window) ||
|
||||||
(dest == CLICK_BORDER && bind->border))) {
|
(dest == CLICK_BORDER && bind->border))) {
|
||||||
CommandResult *result = run_binding(bind, con);
|
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 */
|
/* 1: see if the user scrolled on the decoration of a stacked/tabbed con */
|
||||||
if (in_stacked &&
|
if (in_stacked &&
|
||||||
dest == CLICK_DECORATION &&
|
dest == CLICK_DECORATION &&
|
||||||
(event->detail == XCB_BUTTON_INDEX_4 ||
|
(event->detail == XCB_BUTTON_SCROLL_UP ||
|
||||||
event->detail == XCB_BUTTON_INDEX_5)) {
|
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");
|
DLOG("Scrolling on a window decoration\n");
|
||||||
orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ);
|
orientation_t orientation = (con->parent->layout == L_STACKED ? VERT : HORIZ);
|
||||||
/* Focus the currently focused container on the same level that the
|
/* 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. */
|
* #557), we first check if scrolling is possible at all. */
|
||||||
bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
|
bool scroll_prev_possible = (TAILQ_PREV(focused, nodes_head, nodes) != NULL);
|
||||||
bool scroll_next_possible = (TAILQ_NEXT(focused, 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);
|
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);
|
tree_next('n', orientation);
|
||||||
|
}
|
||||||
|
|
||||||
goto done;
|
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);
|
floating_raise_con(floatingcon);
|
||||||
|
|
||||||
/* 4: floating_modifier plus left mouse button drags */
|
/* 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);
|
floating_drag_window(floatingcon, event);
|
||||||
return 1;
|
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
|
/* 5: resize (floating) if this was a (left or right) click on the
|
||||||
* left/right/bottom border, or a right click on the decoration.
|
* left/right/bottom border, or a right click on the decoration.
|
||||||
* also try resizing (tiling) if it was a click on the top */
|
* 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");
|
DLOG("floating resize due to floatingmodifier\n");
|
||||||
floating_resize_window(floatingcon, proportional, event);
|
floating_resize_window(floatingcon, proportional, event);
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -283,7 +287,7 @@ static int route_click(Con *con, xcb_button_press_event_t *event, const bool mod
|
||||||
goto done;
|
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");
|
DLOG("floating resize due to decoration right click\n");
|
||||||
floating_resize_window(floatingcon, proportional, event);
|
floating_resize_window(floatingcon, proportional, event);
|
||||||
return 1;
|
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
|
/* 6: dragging, if this was a click on a decoration (which did not lead
|
||||||
* to a resize) */
|
* to a resize) */
|
||||||
if (!in_stacked && dest == CLICK_DECORATION &&
|
if (!in_stacked && dest == CLICK_DECORATION &&
|
||||||
(event->detail == XCB_BUTTON_INDEX_1)) {
|
(event->detail == XCB_BUTTON_CLICK_LEFT)) {
|
||||||
floating_drag_window(floatingcon, event);
|
floating_drag_window(floatingcon, event);
|
||||||
return 1;
|
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 */
|
/* 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))
|
if (floating_mod_on_tiled_client(con, event))
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,9 @@ static Con *maybe_auto_back_and_forth_workspace(Con *workspace) {
|
||||||
*/
|
*/
|
||||||
typedef struct owindow {
|
typedef struct owindow {
|
||||||
Con *con;
|
Con *con;
|
||||||
TAILQ_ENTRY(owindow) owindows;
|
|
||||||
|
TAILQ_ENTRY(owindow)
|
||||||
|
owindows;
|
||||||
} owindow;
|
} owindow;
|
||||||
|
|
||||||
typedef TAILQ_HEAD(owindows_head, owindow) owindows_head;
|
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;
|
cmd_output->needs_tree_render = true;
|
||||||
return;
|
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;
|
return;
|
||||||
|
}
|
||||||
workspace_show(workspace);
|
workspace_show(workspace);
|
||||||
|
|
||||||
cmd_output->needs_tree_render = true;
|
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);
|
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;
|
return;
|
||||||
|
}
|
||||||
workspace_show_by_name(name);
|
workspace_show_by_name(name);
|
||||||
|
|
||||||
cmd_output->needs_tree_render = true;
|
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) {
|
void cmd_layout(I3_CMD, const char *layout_str) {
|
||||||
HANDLE_EMPTY_MATCH;
|
HANDLE_EMPTY_MATCH;
|
||||||
|
|
||||||
if (strcmp(layout_str, "stacking") == 0)
|
|
||||||
layout_str = "stacked";
|
|
||||||
layout_t layout;
|
layout_t layout;
|
||||||
/* default is a special case which will be handled in con_set_layout(). */
|
if (!layout_from_name(layout_str, &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 {
|
|
||||||
ELOG("Unknown layout \"%s\", this is a mismatch between code and parser spec.\n", layout_str);
|
ELOG("Unknown layout \"%s\", this is a mismatch between code and parser spec.\n", layout_str);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1556,7 +1549,7 @@ void cmd_exit(I3_CMD) {
|
||||||
#ifdef I3_ASAN_ENABLED
|
#ifdef I3_ASAN_ENABLED
|
||||||
__lsan_do_leak_check();
|
__lsan_do_leak_check();
|
||||||
#endif
|
#endif
|
||||||
ipc_shutdown();
|
ipc_shutdown(SHUTDOWN_REASON_EXIT);
|
||||||
unlink(config.ipc_socket_path);
|
unlink(config.ipc_socket_path);
|
||||||
xcb_disconnect(conn);
|
xcb_disconnect(conn);
|
||||||
exit(0);
|
exit(0);
|
||||||
|
@ -1589,7 +1582,7 @@ void cmd_reload(I3_CMD) {
|
||||||
*/
|
*/
|
||||||
void cmd_restart(I3_CMD) {
|
void cmd_restart(I3_CMD) {
|
||||||
LOG("restarting i3\n");
|
LOG("restarting i3\n");
|
||||||
ipc_shutdown();
|
ipc_shutdown(SHUTDOWN_REASON_RESTART);
|
||||||
unlink(config.ipc_socket_path);
|
unlink(config.ipc_socket_path);
|
||||||
/* We need to call this manually since atexit handlers don’t get called
|
/* We need to call this manually since atexit handlers don’t get called
|
||||||
* when exec()ing */
|
* when exec()ing */
|
||||||
|
@ -1819,6 +1812,65 @@ void cmd_scratchpad_show(I3_CMD) {
|
||||||
ysuccess(true);
|
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>'
|
* 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) {
|
void con_force_split_parents_redraw(Con *con) {
|
||||||
Con *parent = con;
|
Con *parent = con;
|
||||||
|
|
||||||
while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) {
|
while (parent != NULL && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) {
|
||||||
if (!con_is_leaf(parent))
|
if (!con_is_leaf(parent)) {
|
||||||
FREE(parent->deco_render_params);
|
FREE(parent->deco_render_params);
|
||||||
|
}
|
||||||
|
|
||||||
parent = parent->parent;
|
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.
|
/* Insert the container after the tiling container, if found.
|
||||||
* When adding to a CT_OUTPUT, just append one after another. */
|
* 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);
|
DLOG("Inserting con = %p after con %p\n", con, current);
|
||||||
TAILQ_INSERT_AFTER(nodes_head, current, con, nodes);
|
TAILQ_INSERT_AFTER(nodes_head, current, con, nodes);
|
||||||
} else
|
} else
|
||||||
|
@ -410,7 +412,8 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation) {
|
||||||
struct bfs_entry {
|
struct bfs_entry {
|
||||||
Con *con;
|
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
|
/* TODO: is breadth-first-search really appropriate? (check as soon as
|
||||||
* fullscreen levels and fullscreen for containers is implemented) */
|
* 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));
|
struct bfs_entry *entry = smalloc(sizeof(struct bfs_entry));
|
||||||
entry->con = con;
|
entry->con = con;
|
||||||
TAILQ_INSERT_TAIL(&bfs_head, entry, entries);
|
TAILQ_INSERT_TAIL(&bfs_head, entry, entries);
|
||||||
|
@ -522,6 +527,23 @@ bool con_inside_focused(Con *con) {
|
||||||
return con_inside_focused(con->parent);
|
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
|
* Returns the container with the given client window ID or NULL if no such
|
||||||
* container exists.
|
* container exists.
|
||||||
|
@ -800,23 +822,26 @@ void con_fix_percent(Con *con) {
|
||||||
if (children_with_percent != children) {
|
if (children_with_percent != children) {
|
||||||
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
|
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
|
||||||
if (child->percent <= 0.0) {
|
if (child->percent <= 0.0) {
|
||||||
if (children_with_percent == 0)
|
if (children_with_percent == 0) {
|
||||||
total += (child->percent = 1.0);
|
total += (child->percent = 1.0);
|
||||||
else
|
} else {
|
||||||
total += (child->percent = total / children_with_percent);
|
total += (child->percent = total / children_with_percent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if we got a zero, just distribute the space equally, otherwise
|
// if we got a zero, just distribute the space equally, otherwise
|
||||||
// distribute according to the proportions we got
|
// distribute according to the proportions we got
|
||||||
if (total == 0.0) {
|
if (total == 0.0) {
|
||||||
TAILQ_FOREACH(child, &(con->nodes_head), nodes)
|
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
|
||||||
child->percent = 1.0 / children;
|
child->percent = 1.0 / children;
|
||||||
|
}
|
||||||
} else if (total != 1.0) {
|
} else if (total != 1.0) {
|
||||||
TAILQ_FOREACH(child, &(con->nodes_head), nodes)
|
TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
|
||||||
child->percent /= total;
|
child->percent /= total;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -941,7 +966,7 @@ void con_disable_fullscreen(Con *con) {
|
||||||
con_set_fullscreen_mode(con, CF_NONE);
|
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;
|
Con *orig_target = target;
|
||||||
|
|
||||||
/* Prevent moving if this would violate the fullscreen focus restrictions. */
|
/* 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);
|
_con_attach(con, target, behind_focused ? NULL : orig_target, !behind_focused);
|
||||||
|
|
||||||
/* 5: fix the percentages */
|
/* 5: fix the percentages */
|
||||||
|
if (fix_percentage) {
|
||||||
con_fix_percent(parent);
|
con_fix_percent(parent);
|
||||||
con->percent = 0.0;
|
con->percent = 0.0;
|
||||||
con_fix_percent(target);
|
con_fix_percent(target);
|
||||||
|
}
|
||||||
|
|
||||||
/* 6: focus the con on the target workspace, but only within that
|
/* 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
|
* 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
|
/* Descend focus stack in case focus_next is a workspace which can
|
||||||
* occur if we move to the same workspace. Also show current workspace
|
* occur if we move to the same workspace. Also show current workspace
|
||||||
* to ensure it is focused. */
|
* to ensure it is focused. */
|
||||||
if (!ignore_focus)
|
if (!ignore_focus) {
|
||||||
workspace_show(current_ws);
|
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.
|
/* Set focus only if con was on current workspace before moving.
|
||||||
* Otherwise we would give focus to some window on different workspace. */
|
* 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);
|
con_set_urgency(con, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure the container will be redrawn. */
|
||||||
|
FREE(con->deco_render_params);
|
||||||
|
|
||||||
CALL(parent, on_remove_child);
|
CALL(parent, on_remove_child);
|
||||||
|
|
||||||
ipc_send_window_event("move", con);
|
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));
|
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");
|
DLOG("cannot move the container to or inside itself, aborting.\n");
|
||||||
return false;
|
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 *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 {
|
} else {
|
||||||
/* try to focus the next container on the same level as this one or fall
|
/* try to focus the next container on the same level as this one or fall
|
||||||
* back to its parent */
|
* back to its parent */
|
||||||
if (!(next = TAILQ_NEXT(con, focused)))
|
if (!(next = TAILQ_NEXT(con, focused))) {
|
||||||
next = con->parent;
|
next = con->parent;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* now go down the focus stack as far as
|
/* now go down the focus stack as far as
|
||||||
* possible, excluding the current container */
|
* possible, excluding the current container */
|
||||||
while (!TAILQ_EMPTY(&(next->focus_head)) &&
|
while (!TAILQ_EMPTY(&(next->focus_head)) && TAILQ_FIRST(&(next->focus_head)) != con) {
|
||||||
TAILQ_FIRST(&(next->focus_head)) != con)
|
|
||||||
next = TAILQ_FIRST(&(next->focus_head));
|
next = TAILQ_FIRST(&(next->focus_head));
|
||||||
|
}
|
||||||
|
|
||||||
return next;
|
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
|
* whole workspace into stacked/tabbed mode. To do this and still allow
|
||||||
* intuitive operations (like level-up and then opening a new window), we
|
* intuitive operations (like level-up and then opening a new window), we
|
||||||
* need to create a new split container. */
|
* need to create a new split container. */
|
||||||
if (con->type == CT_WORKSPACE &&
|
if (con->type == CT_WORKSPACE) {
|
||||||
(layout == L_STACKED || layout == L_TABBED)) {
|
|
||||||
if (con_num_children(con) == 0) {
|
if (con_num_children(con) == 0) {
|
||||||
DLOG("Setting workspace_layout to %d\n", layout);
|
layout_t ws_layout = (layout == L_STACKED || layout == L_TABBED) ? layout : L_DEFAULT;
|
||||||
con->workspace_layout = layout;
|
DLOG("Setting workspace_layout to %d\n", ws_layout);
|
||||||
} else {
|
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");
|
DLOG("Creating new split container\n");
|
||||||
/* 1: create a new split container */
|
/* 1: create a new split container */
|
||||||
Con *new = con_new(NULL, NULL);
|
Con *new = con_new(NULL, NULL);
|
||||||
|
@ -1716,28 +1754,64 @@ void con_toggle_layout(Con *con, const char *toggle_mode) {
|
||||||
parent = con->parent;
|
parent = con->parent;
|
||||||
DLOG("con_toggle_layout(%p, %s), parent = %p\n", con, toggle_mode, 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
|
/* Toggle between splits. When the current layout is not a split
|
||||||
* layout, we just switch back to last_split_layout. Otherwise, we
|
* layout, we just switch back to last_split_layout. Otherwise, we
|
||||||
* change to the opposite split layout. */
|
* change to the opposite split layout. */
|
||||||
if (parent->layout != L_SPLITH && parent->layout != L_SPLITV)
|
if (parent->layout != L_SPLITH && parent->layout != L_SPLITV) {
|
||||||
con_set_layout(con, parent->last_split_layout);
|
layout = parent->last_split_layout;
|
||||||
else {
|
} else {
|
||||||
if (parent->layout == L_SPLITH)
|
layout = (parent->layout == L_SPLITH) ? L_SPLITV : L_SPLITH;
|
||||||
con_set_layout(con, L_SPLITV);
|
|
||||||
else
|
|
||||||
con_set_layout(con, L_SPLITH);
|
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
if (parent->layout == L_STACKED)
|
||||||
con_set_layout(con, L_TABBED);
|
con_set_layout(con, L_TABBED);
|
||||||
else if (parent->layout == 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);
|
con_set_layout(con, L_SPLITH);
|
||||||
else
|
else
|
||||||
con_set_layout(con, parent->last_split_layout);
|
con_set_layout(con, parent->last_split_layout);
|
||||||
} else if (parent->layout == L_SPLITH || parent->layout == L_SPLITV) {
|
} 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
|
/* When toggling through all modes, we toggle between
|
||||||
* splith/splitv, whereas normally we just directly jump to
|
* splith/splitv, whereas normally we just directly jump to
|
||||||
* stacked. */
|
* stacked. */
|
||||||
|
@ -2120,3 +2194,169 @@ i3String *con_parse_title_format(Con *con) {
|
||||||
|
|
||||||
return formatted;
|
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>
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
char *current_configpath = NULL;
|
char *current_configpath = NULL;
|
||||||
|
char *current_config = NULL;
|
||||||
Config config;
|
Config config;
|
||||||
struct modes_head modes;
|
struct modes_head modes;
|
||||||
struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs);
|
struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs);
|
||||||
|
|
|
@ -106,8 +106,8 @@ CFGFUN(font, const char *font) {
|
||||||
font_pattern = sstrdup(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) {
|
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, command, DEFAULT_BINDING_MODE, false);
|
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 char *current_mode;
|
||||||
static bool current_mode_pango_markup;
|
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) {
|
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, command, current_mode, current_mode_pango_markup);
|
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) {
|
CFGFUN(enter_mode, const char *pango_markup, const char *modename) {
|
||||||
if (strcasecmp(modename, DEFAULT_BINDING_MODE) == 0) {
|
if (strcasecmp(modename, DEFAULT_BINDING_MODE) == 0) {
|
||||||
ELOG("You cannot use the name %s for your mode\n", DEFAULT_BINDING_MODE);
|
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);
|
DLOG("\t now in mode %s\n", modename);
|
||||||
FREE(current_mode);
|
FREE(current_mode);
|
||||||
current_mode = sstrdup(modename);
|
current_mode = sstrdup(modename);
|
||||||
|
@ -252,6 +260,10 @@ CFGFUN(force_xinerama, const char *value) {
|
||||||
config.force_xinerama = eval_boolstr(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) {
|
CFGFUN(force_focus_wrapping, const char *value) {
|
||||||
config.force_focus_wrapping = eval_boolstr(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) {
|
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--;
|
walk--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -898,6 +898,13 @@ bool parse_file(const char *f, bool use_nagbar) {
|
||||||
if ((fstr = fdopen(fd, "r")) == NULL)
|
if ((fstr = fdopen(fd, "r")) == NULL)
|
||||||
die("Could not fdopen: %s\n", strerror(errno));
|
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)) {
|
while (!feof(fstr)) {
|
||||||
if (!continuation)
|
if (!continuation)
|
||||||
continuation = buffer;
|
continuation = buffer;
|
||||||
|
@ -911,6 +918,7 @@ bool parse_file(const char *f, bool use_nagbar) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* sscanf implicitly strips whitespace. */
|
/* sscanf implicitly strips whitespace. */
|
||||||
|
value[0] = '\0';
|
||||||
const bool skip_line = (sscanf(buffer, "%511s %4095[^\n]", key, value) < 1 || strlen(key) < 3);
|
const bool skip_line = (sscanf(buffer, "%511s %4095[^\n]", key, value) < 1 || strlen(key) < 3);
|
||||||
const bool comment = (key[0] == '#');
|
const bool comment = (key[0] == '#');
|
||||||
value[4095] = '\n';
|
value[4095] = '\n';
|
||||||
|
@ -931,26 +939,28 @@ bool parse_file(const char *f, bool use_nagbar) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcasecmp(key, "set") == 0) {
|
if (strcasecmp(key, "set") == 0 && *value != '\0') {
|
||||||
char v_key[512];
|
char v_key[512];
|
||||||
char v_value[4096];
|
char v_value[4096] = {'\0'};
|
||||||
|
|
||||||
if (sscanf(value, "%511s %4095[^\n]", v_key, v_value) < 1) {
|
if (sscanf(value, "%511s %4095[^\n]", v_key, v_value) < 1) {
|
||||||
ELOG("Failed to parse variable specification '%s', skipping it.\n", value);
|
ELOG("Failed to parse variable specification '%s', skipping it.\n", value);
|
||||||
|
invalid_sets = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v_key[0] != '$') {
|
if (v_key[0] != '$') {
|
||||||
ELOG("Malformed variable assignment, name has to start with $\n");
|
ELOG("Malformed variable assignment, name has to start with $\n");
|
||||||
|
invalid_sets = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
upsert_variable(&variables, v_key, v_value);
|
upsert_variable(&variables, v_key, v_value);
|
||||||
continue;
|
continue;
|
||||||
} else if (strcasecmp(key, "set_from_resource") == 0) {
|
} else if (strcasecmp(key, "set_from_resource") == 0) {
|
||||||
char res_name[512];
|
char res_name[512] = {'\0'};
|
||||||
char v_key[512];
|
char v_key[512];
|
||||||
char fallback[4096];
|
char fallback[4096] = {'\0'};
|
||||||
|
|
||||||
/* Ensure that this string is terminated. For example, a user might
|
/* 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
|
* 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) {
|
if (sscanf(value, "%511s %511s %4095[^\n]", v_key, res_name, fallback) < 1) {
|
||||||
ELOG("Failed to parse resource specification '%s', skipping it.\n", value);
|
ELOG("Failed to parse resource specification '%s', skipping it.\n", value);
|
||||||
|
invalid_sets = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v_key[0] != '$') {
|
if (v_key[0] != '$') {
|
||||||
ELOG("Malformed variable assignment, name has to start with $\n");
|
ELOG("Malformed variable assignment, name has to start with $\n");
|
||||||
|
invalid_sets = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1082,12 +1094,12 @@ bool parse_file(const char *f, bool use_nagbar) {
|
||||||
check_for_duplicate_bindings(context);
|
check_for_duplicate_bindings(context);
|
||||||
reorder_bindings();
|
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);
|
ELOG("FYI: You are using i3 version %s\n", i3_version);
|
||||||
if (version == 3)
|
if (version == 3)
|
||||||
ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
|
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;
|
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
|
#endif
|
||||||
|
|
||||||
yajl_free(handle);
|
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;
|
Rect floating_sane_max_dimensions;
|
||||||
Con *focused_con = con_descend_focused(floating_con);
|
Con *focused_con = con_descend_focused(floating_con);
|
||||||
|
|
||||||
/* obey size increments */
|
|
||||||
if (focused_con->window != NULL && (focused_con->window->height_increment || focused_con->window->width_increment)) {
|
|
||||||
Rect border_rect = con_border_style_rect(focused_con);
|
Rect border_rect = con_border_style_rect(focused_con);
|
||||||
|
|
||||||
/* We have to do the opposite calculations that render_con() do
|
/* We have to do the opposite calculations that render_con() do
|
||||||
* to get the exact size we want. */
|
* to get the exact size we want. */
|
||||||
border_rect.width = -border_rect.width;
|
border_rect.width = -border_rect.width;
|
||||||
border_rect.width += 2 * focused_con->border_width;
|
border_rect.width += 2 * focused_con->border_width;
|
||||||
border_rect.height = -border_rect.height;
|
border_rect.height = -border_rect.height;
|
||||||
border_rect.height += 2 * focused_con->border_width;
|
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();
|
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 &&
|
if (focused_con->window->height_increment &&
|
||||||
floating_con->rect.height >= focused_con->window->base_height + border_rect.height) {
|
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
|
/* Unless user requests otherwise (-1), ensure width/height do not exceed
|
||||||
* configured maxima or, if unconfigured, limit to combined width of all
|
* configured maxima or, if unconfigured, limit to combined width of all
|
||||||
* outputs */
|
* 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();
|
floating_sane_max_dimensions = total_outputs_dimensions();
|
||||||
if (config.floating_maximum_height != -1) {
|
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);
|
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 = 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 != -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);
|
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 = min(floating_con->rect.width, config.floating_maximum_width);
|
||||||
}
|
}
|
||||||
|
floating_con->rect.width += border_rect.width;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void floating_enable(Con *con, bool automatic) {
|
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
|
/* 3: attach the child to the new parent container. We need to do this
|
||||||
* because con_border_style_rect() needs to access con->parent. */
|
* 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;
|
nc->rect.width -= border_style_rect.width;
|
||||||
|
|
||||||
/* Add some more pixels for the title bar */
|
/* 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;
|
nc->rect.height += deco_height;
|
||||||
|
}
|
||||||
|
|
||||||
/* Honor the X11 border */
|
/* Honor the X11 border */
|
||||||
nc->rect.height += con->border_width * 2;
|
nc->rect.height += con->border_width * 2;
|
||||||
nc->rect.width += 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
|
/* Some clients (like GIMP’s color picker window) get mapped
|
||||||
* to (0, 0), so we push them to a reasonable position
|
* to (0, 0), so we push them to a reasonable position
|
||||||
* (centered over their leader) */
|
* (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);
|
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 the cons to get initial window_rect correct */
|
||||||
render_con(nc, false);
|
render_con(nc, false);
|
||||||
render_con(con, 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");
|
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
|
/* 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
|
* only tell us that the X server lost (parts of) the window contents. */
|
||||||
* can handle that by copying the appropriate part from our surface to the
|
draw_util_copy_surface(&(parent->frame_buffer), &(parent->frame),
|
||||||
* window. */
|
0, 0, 0, 0, parent->rect.width, parent->rect.height);
|
||||||
draw_util_copy_surface(conn, &(parent->frame_buffer), &(parent->frame),
|
|
||||||
event->x, event->y, event->x, event->y,
|
|
||||||
event->width, event->height);
|
|
||||||
xcb_flush(conn);
|
xcb_flush(conn);
|
||||||
return;
|
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_MOVE_KEYBOARD 10 /* move via keyboard */
|
||||||
#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */
|
#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)
|
* 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);
|
DLOG("_NET_WM_MOVERESIZE direction %d not implemented\n", direction);
|
||||||
break;
|
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 {
|
} else {
|
||||||
DLOG("Skipping client message for unhandled type %d\n", event->type);
|
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;
|
xcb_size_hints_t size_hints;
|
||||||
|
|
||||||
//CLIENT_LOG(client);
|
|
||||||
|
|
||||||
/* If the hints were already in this event, use them, if not, request them */
|
/* 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);
|
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);
|
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)) {
|
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);
|
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;
|
bool changed = false;
|
||||||
if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) {
|
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) {
|
if (con->window->width_increment != size_hints.width_inc) {
|
||||||
con->window->width_increment = size_hints.width_inc;
|
con->window->width_increment = size_hints.width_inc;
|
||||||
changed = true;
|
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) {
|
if (con->window->height_increment != size_hints.height_inc) {
|
||||||
con->window->height_increment = size_hints.height_inc;
|
con->window->height_increment = size_hints.height_inc;
|
||||||
changed = true;
|
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.
|
bool has_base_size = false;
|
||||||
We check if either the program-specified size or the program-specified
|
int base_width = 0;
|
||||||
min-size is available */
|
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) {
|
if (size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) {
|
||||||
base_width = size_hints.base_width;
|
base_width = size_hints.base_width;
|
||||||
base_height = size_hints.base_height;
|
base_height = size_hints.base_height;
|
||||||
} else if (size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
|
has_base_size = true;
|
||||||
/* TODO: is this right? icccm says not */
|
}
|
||||||
|
|
||||||
|
/* 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_width = size_hints.min_width;
|
||||||
base_height = size_hints.min_height;
|
base_height = size_hints.min_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (base_width != con->window->base_width ||
|
// TODO XXX Should we only do this is the base size is > 0?
|
||||||
base_height != con->window->base_height) {
|
if (base_width != con->window->base_width || base_height != con->window->base_height) {
|
||||||
con->window->base_width = base_width;
|
con->window->base_width = base_width;
|
||||||
con->window->base_height = base_height;
|
con->window->base_height = base_height;
|
||||||
|
|
||||||
DLOG("client's base_height changed to %d\n", base_height);
|
DLOG("client's base_height changed to %d\n", base_height);
|
||||||
DLOG("client's base_width changed to %d\n", base_width);
|
DLOG("client's base_width changed to %d\n", base_width);
|
||||||
changed = true;
|
changed = true;
|
||||||
|
@ -989,9 +1079,13 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
|
||||||
goto render_and_return;
|
goto render_and_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* XXX: do we really use rect here, not window_rect? */
|
/* The ICCCM says to subtract the base size from the window size for aspect
|
||||||
double width = con->rect.width - base_width;
|
* ratio calculations. However, unlike determining the base size itself we
|
||||||
double height = con->rect.height - base_height;
|
* 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 */
|
/* Convert numerator/denominator to a double */
|
||||||
double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
|
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;
|
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);
|
DLOG("width = %f, height = %f\n", width, height);
|
||||||
|
|
||||||
/* Sanity checks, this is user-input, in a way */
|
/* 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;
|
goto render_and_return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Check if we need to set proportional_* variables using the correct ratio */
|
/* Check if we need to set proportional_* variables using the correct ratio */
|
||||||
double aspect_ratio = 0.0;
|
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;
|
aspect_ratio = min_aspect;
|
||||||
} else if ((width / height) > max_aspect) {
|
} else if ((width / height) > max_aspect) {
|
||||||
aspect_ratio = max_aspect;
|
aspect_ratio = max_aspect;
|
||||||
} else
|
} else {
|
||||||
goto render_and_return;
|
goto render_and_return;
|
||||||
|
}
|
||||||
|
|
||||||
if (fabs(con->window->aspect_ratio - aspect_ratio) > DBL_EPSILON) {
|
if (fabs(con->window->aspect_ratio - aspect_ratio) > DBL_EPSILON) {
|
||||||
con->window->aspect_ratio = aspect_ratio;
|
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:
|
render_and_return:
|
||||||
if (changed)
|
if (changed) {
|
||||||
tree_render();
|
tree_render();
|
||||||
|
}
|
||||||
|
|
||||||
FREE(reply);
|
FREE(reply);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1150,6 +1248,21 @@ static void handle_focus_in(xcb_focus_in_event_t *event) {
|
||||||
return;
|
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.
|
* 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;
|
break;
|
||||||
|
|
||||||
case XCB_EXPOSE:
|
case XCB_EXPOSE:
|
||||||
|
if (((xcb_expose_event_t *)event)->count == 0) {
|
||||||
handle_expose_event((xcb_expose_event_t *)event);
|
handle_expose_event((xcb_expose_event_t *)event);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case XCB_MOTION_NOTIFY:
|
case XCB_MOTION_NOTIFY:
|
||||||
|
@ -1476,6 +1592,10 @@ void handle_event(int type, xcb_generic_event_t *event) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case XCB_CONFIGURE_NOTIFY:
|
||||||
|
handle_configure_notify((xcb_configure_notify_event_t *)event);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
//DLOG("Unhandled event of type %d\n", type);
|
//DLOG("Unhandled event of type %d\n", type);
|
||||||
break;
|
break;
|
||||||
|
|
59
src/ipc.c
59
src/ipc.c
|
@ -22,7 +22,8 @@
|
||||||
|
|
||||||
char *current_socketpath = NULL;
|
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
|
* 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!
|
* when exiting or restarting only!
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void ipc_shutdown(void) {
|
void ipc_shutdown(shutdown_reason_t reason) {
|
||||||
|
ipc_send_shutdown_event(reason);
|
||||||
|
|
||||||
ipc_client *current;
|
ipc_client *current;
|
||||||
while (!TAILQ_EMPTY(&all_clients)) {
|
while (!TAILQ_EMPTY(&all_clients)) {
|
||||||
current = TAILQ_FIRST(&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);
|
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
|
/* The index of each callback function corresponds to the numeric
|
||||||
* value of the message type (see include/i3/ipc.h) */
|
* value of the message type (see include/i3/ipc.h) */
|
||||||
handler_t handlers[9] = {
|
handler_t handlers[10] = {
|
||||||
handle_command,
|
handle_command,
|
||||||
handle_get_workspaces,
|
handle_get_workspaces,
|
||||||
handle_subscribe,
|
handle_subscribe,
|
||||||
|
@ -1070,6 +1120,7 @@ handler_t handlers[9] = {
|
||||||
handle_get_bar_config,
|
handle_get_bar_config,
|
||||||
handle_get_version,
|
handle_get_version,
|
||||||
handle_get_binding_modes,
|
handle_get_binding_modes,
|
||||||
|
handle_get_config,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -29,12 +29,16 @@ static bool parsing_focus;
|
||||||
static bool parsing_marks;
|
static bool parsing_marks;
|
||||||
struct Match *current_swallow;
|
struct Match *current_swallow;
|
||||||
static bool swallow_is_empty;
|
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'
|
/* This list is used for reordering the focus stack after parsing the 'focus'
|
||||||
* array. */
|
* array. */
|
||||||
struct focus_mapping {
|
struct focus_mapping {
|
||||||
int old_id;
|
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 =
|
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);
|
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");
|
LOG("attaching\n");
|
||||||
con_attach(json_node, json_node->parent, true);
|
con_attach(json_node, json_node->parent, true);
|
||||||
LOG("Creating window\n");
|
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)
|
if (strcasecmp(last_key, "focus") == 0)
|
||||||
parsing_focus = true;
|
parsing_focus = true;
|
||||||
|
|
||||||
if (strcasecmp(last_key, "marks") == 0)
|
if (strcasecmp(last_key, "marks") == 0) {
|
||||||
|
num_marks = 0;
|
||||||
parsing_marks = true;
|
parsing_marks = true;
|
||||||
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -259,7 +275,8 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) {
|
||||||
char *mark;
|
char *mark;
|
||||||
sasprintf(&mark, "%.*s", (int)len, val);
|
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 {
|
} else {
|
||||||
if (strcasecmp(last_key, "name") == 0) {
|
if (strcasecmp(last_key, "name") == 0) {
|
||||||
json_node->name = scalloc(len + 1, 1);
|
json_node->name = scalloc(len + 1, 1);
|
||||||
|
|
|
@ -88,11 +88,16 @@ void init_logging(void) {
|
||||||
fprintf(stderr, "Could not initialize errorlog\n");
|
fprintf(stderr, "Could not initialize errorlog\n");
|
||||||
else {
|
else {
|
||||||
errorfile = fopen(errorfilename, "w");
|
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)) {
|
if (fcntl(fileno(errorfile), F_SETFD, FD_CLOEXEC)) {
|
||||||
fprintf(stderr, "Could not set close-on-exec flag\n");
|
fprintf(stderr, "Could not set close-on-exec flag\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (physical_mem_bytes == 0) {
|
if (physical_mem_bytes == 0) {
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
int mib[2] = {CTL_HW, HW_MEMSIZE};
|
int mib[2] = {CTL_HW, HW_MEMSIZE};
|
||||||
|
|
16
src/main.c
16
src/main.c
|
@ -21,6 +21,10 @@
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
#include "shmlog.h"
|
#include "shmlog.h"
|
||||||
|
|
||||||
|
#ifdef I3_ASAN_ENABLED
|
||||||
|
#include <sanitizer/lsan_interface.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "sd-daemon.h"
|
#include "sd-daemon.h"
|
||||||
|
|
||||||
/* The original value of RLIMIT_CORE when i3 was started. We need to restore
|
/* 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;
|
char *layout_path = NULL;
|
||||||
bool delete_layout_path = false;
|
bool delete_layout_path = false;
|
||||||
bool force_xinerama = false;
|
bool force_xinerama = false;
|
||||||
|
bool disable_randr15 = false;
|
||||||
char *fake_outputs = NULL;
|
char *fake_outputs = NULL;
|
||||||
bool disable_signalhandler = false;
|
bool disable_signalhandler = false;
|
||||||
bool only_check_config = false;
|
bool only_check_config = false;
|
||||||
|
@ -209,6 +214,8 @@ int main(int argc, char *argv[]) {
|
||||||
{"restart", required_argument, 0, 0},
|
{"restart", required_argument, 0, 0},
|
||||||
{"force-xinerama", no_argument, 0, 0},
|
{"force-xinerama", no_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},
|
{"disable-signalhandler", no_argument, 0, 0},
|
||||||
{"shmlog-size", required_argument, 0, 0},
|
{"shmlog-size", required_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 "
|
"Please check if your driver really does not support RandR "
|
||||||
"and disable this option as soon as you can.\n");
|
"and disable this option as soon as you can.\n");
|
||||||
break;
|
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) {
|
} else if (strcmp(long_options[option_index].name, "disable-signalhandler") == 0) {
|
||||||
disable_signalhandler = true;
|
disable_signalhandler = true;
|
||||||
break;
|
break;
|
||||||
|
@ -544,6 +555,9 @@ int main(int argc, char *argv[]) {
|
||||||
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
|
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
|
||||||
if (error != NULL) {
|
if (error != NULL) {
|
||||||
ELOG("Another window manager seems to be running (X error %d)\n", error->error_code);
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -661,7 +675,7 @@ int main(int argc, char *argv[]) {
|
||||||
xinerama_init();
|
xinerama_init();
|
||||||
} else {
|
} else {
|
||||||
DLOG("Checking for XRandR...\n");
|
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
|
/* 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;
|
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
|
/* Store the requested geometry. The width/height gets raised to at least
|
||||||
* 75x50 when entering floating mode, which is the minimum size for a
|
* 75x50 when entering floating mode, which is the minimum size for a
|
||||||
* window to be useful (smaller windows are usually overlays/toolbars/…
|
* window to be useful (smaller windows are usually overlays/toolbars/…
|
||||||
|
|
20
src/match.c
20
src/match.c
|
@ -167,7 +167,7 @@ bool match_matches_window(Match *match, i3Window *window) {
|
||||||
/* if we find a window that is newer than this one, bail */
|
/* if we find a window that is newer than this one, bail */
|
||||||
TAILQ_FOREACH(con, &all_cons, all_cons) {
|
TAILQ_FOREACH(con, &all_cons, all_cons) {
|
||||||
if ((con->window != NULL) &&
|
if ((con->window != NULL) &&
|
||||||
_i3_timercmp(con->window->urgent, window->urgent, > )) {
|
_i3_timercmp(con->window->urgent, window->urgent, >)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,7 +183,7 @@ bool match_matches_window(Match *match, i3Window *window) {
|
||||||
TAILQ_FOREACH(con, &all_cons, all_cons) {
|
TAILQ_FOREACH(con, &all_cons, all_cons) {
|
||||||
if ((con->window != NULL) &&
|
if ((con->window != NULL) &&
|
||||||
(con->window->urgent.tv_sec != 0) &&
|
(con->window->urgent.tv_sec != 0) &&
|
||||||
_i3_timercmp(con->window->urgent, window->urgent, < )) {
|
_i3_timercmp(con->window->urgent, window->urgent, <)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,12 +307,8 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *end;
|
long parsed;
|
||||||
long parsed = strtol(cvalue, &end, 0);
|
if (!parse_long(cvalue, &parsed, 0)) {
|
||||||
if (parsed == LONG_MIN ||
|
|
||||||
parsed == LONG_MAX ||
|
|
||||||
parsed < 0 ||
|
|
||||||
(end && *end != '\0')) {
|
|
||||||
ELOG("Could not parse con id \"%s\"\n", cvalue);
|
ELOG("Could not parse con id \"%s\"\n", cvalue);
|
||||||
match->error = sstrdup("invalid con_id");
|
match->error = sstrdup("invalid con_id");
|
||||||
} else {
|
} else {
|
||||||
|
@ -323,12 +319,8 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(ctype, "id") == 0) {
|
if (strcmp(ctype, "id") == 0) {
|
||||||
char *end;
|
long parsed;
|
||||||
long parsed = strtol(cvalue, &end, 0);
|
if (!parse_long(cvalue, &parsed, 0)) {
|
||||||
if (parsed == LONG_MIN ||
|
|
||||||
parsed == LONG_MAX ||
|
|
||||||
parsed < 0 ||
|
|
||||||
(end && *end != '\0')) {
|
|
||||||
ELOG("Could not parse window id \"%s\"\n", cvalue);
|
ELOG("Could not parse window id \"%s\"\n", cvalue);
|
||||||
match->error = sstrdup("invalid id");
|
match->error = sstrdup("invalid id");
|
||||||
} else {
|
} 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_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) {
|
Output *get_output_for_con(Con *con) {
|
||||||
|
@ -51,7 +51,7 @@ Output *get_output_for_con(Con *con) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Output *output = get_output_by_name(output_con->name);
|
Output *output = get_output_by_name(output_con->name, true);
|
||||||
if (output == NULL) {
|
if (output == NULL) {
|
||||||
ELOG("Could not get output from name \"%s\".\n", output_con->name);
|
ELOG("Could not get output from name \"%s\".\n", output_con->name);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
210
src/randr.c
210
src/randr.c
|
@ -14,11 +14,6 @@
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <xcb/randr.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 */
|
/* Pointer to the result of the query for primary output */
|
||||||
xcb_randr_get_output_primary_reply_t *primary;
|
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 */
|
/* This is the output covering the root window */
|
||||||
static Output *root_output;
|
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
|
* 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;
|
Output *output;
|
||||||
TAILQ_FOREACH(output, &outputs, outputs)
|
bool get_primary = (strcasecmp("primary", name) == 0);
|
||||||
if (output->active &&
|
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||||
strcasecmp(output->name, name) == 0)
|
if ((output->primary && get_primary) ||
|
||||||
|
((!require_active || output->active) && strcasecmp(output->name, name) == 0)) {
|
||||||
return output;
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -443,7 +443,7 @@ void init_ws_for_output(Output *output, Con *content) {
|
||||||
if (visible && previous == NULL) {
|
if (visible && previous == NULL) {
|
||||||
LOG("There is no workspace left on \"%s\", re-initializing\n",
|
LOG("There is no workspace left on \"%s\", re-initializing\n",
|
||||||
workspace_out->name);
|
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));
|
output_get_content(workspace_out));
|
||||||
DLOG("Done re-initializing, continuing with \"%s\"\n", output->name);
|
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
|
* randr_query_outputs_15 uses RandR ≥ 1.5 to update outputs.
|
||||||
* 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
|
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.
|
* appropriate.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
|
static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id,
|
||||||
xcb_randr_get_output_info_reply_t *output,
|
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 */
|
/* 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);
|
Output *new = get_output_by_id(id);
|
||||||
bool existing = (new != NULL);
|
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.
|
* randr_query_outputs_14 uses RandR ≤ 1.4 to update outputs.
|
||||||
*
|
|
||||||
* If no outputs are found use the root window.
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void randr_query_outputs(void) {
|
static void randr_query_outputs_14(void) {
|
||||||
Output *output, *other;
|
DLOG("Querying outputs using RandR ≤ 1.4\n");
|
||||||
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;
|
|
||||||
|
|
||||||
/* Get screen resources (primary output, crtcs, outputs, modes) */
|
/* 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);
|
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);
|
pcookie = xcb_randr_get_output_primary(conn, root);
|
||||||
|
|
||||||
if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL)
|
if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL)
|
||||||
|
@ -640,14 +725,21 @@ void randr_query_outputs(void) {
|
||||||
else
|
else
|
||||||
DLOG("primary output is %08x\n", primary->output);
|
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) {
|
if (res == NULL) {
|
||||||
ELOG("Could not query screen resources.\n");
|
ELOG("Could not query screen resources.\n");
|
||||||
} else {
|
return;
|
||||||
cts = res->config_timestamp;
|
}
|
||||||
|
|
||||||
int len = xcb_randr_get_screen_resources_current_outputs_length(res);
|
/* timestamp of the configuration so that we get consistent replies to all
|
||||||
randr_outputs = xcb_randr_get_screen_resources_current_outputs(res);
|
* 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 */
|
/* Request information for each output */
|
||||||
xcb_randr_get_output_info_cookie_t ocookie[len];
|
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);
|
handle_output(conn, randr_outputs[i], output, cts, res);
|
||||||
free(output);
|
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. */
|
/* 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 */
|
/* render_layout flushes */
|
||||||
tree_render();
|
tree_render();
|
||||||
|
|
||||||
FREE(res);
|
|
||||||
FREE(primary);
|
FREE(primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -857,12 +963,18 @@ void randr_disable_output(Output *output) {
|
||||||
output->changed = false;
|
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
|
* We have just established a connection to the X server and need the initial
|
||||||
* XRandR information to setup workspaces for each screen.
|
* 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;
|
const xcb_query_extension_reply_t *extreply;
|
||||||
|
|
||||||
root_output = create_root_output(conn);
|
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);
|
extreply = xcb_get_extension_data(conn, &xcb_randr_id);
|
||||||
if (!extreply->present) {
|
if (!extreply->present) {
|
||||||
DLOG("RandR is not present, activating root output.\n");
|
DLOG("RandR is not present, activating root output.\n");
|
||||||
root_output->active = true;
|
fallback_to_root_output();
|
||||||
output_init_con(root_output);
|
|
||||||
init_ws_for_output(root_output, output_get_content(root_output->con));
|
|
||||||
|
|
||||||
return;
|
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();
|
randr_query_outputs();
|
||||||
|
|
||||||
if (event_base != NULL)
|
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",
|
ELOG("PCRE regular expression compilation failed at %d: %s\n",
|
||||||
offset, error);
|
offset, error);
|
||||||
|
regex_free(re);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
re->extra = pcre_study(re->regex, 0, &error);
|
re->extra = pcre_study(re->regex, 0, &error);
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
#include <sanitizer/lsan_interface.h>
|
#include <sanitizer/lsan_interface.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define TEXT_PADDING logical_px(2)
|
||||||
|
|
||||||
typedef struct placeholder_state {
|
typedef struct placeholder_state {
|
||||||
/** The X11 placeholder window. */
|
/** The X11 placeholder window. */
|
||||||
xcb_window_t window;
|
xcb_window_t window;
|
||||||
|
@ -24,12 +26,11 @@ typedef struct placeholder_state {
|
||||||
/** Current size of the placeholder window (to detect size changes). */
|
/** Current size of the placeholder window (to detect size changes). */
|
||||||
Rect rect;
|
Rect rect;
|
||||||
|
|
||||||
/** The pixmap to render on (back buffer). */
|
/** The drawable surface */
|
||||||
xcb_pixmap_t pixmap;
|
surface_t surface;
|
||||||
/** The graphics context for “pixmap”. */
|
|
||||||
xcb_gcontext_t gc;
|
|
||||||
|
|
||||||
TAILQ_ENTRY(placeholder_state) state;
|
TAILQ_ENTRY(placeholder_state)
|
||||||
|
state;
|
||||||
} placeholder_state;
|
} placeholder_state;
|
||||||
|
|
||||||
static TAILQ_HEAD(state_head, placeholder_state) state_head =
|
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) {
|
static void update_placeholder_contents(placeholder_state *state) {
|
||||||
xcb_change_gc(restore_conn, state->gc, XCB_GC_FOREGROUND,
|
const color_t foreground = config.client.placeholder.text;
|
||||||
(uint32_t[]){config.client.placeholder.background.colorpixel});
|
const color_t background = config.client.placeholder.background;
|
||||||
xcb_poly_fill_rectangle(restore_conn, state->pixmap, state->gc, 1,
|
|
||||||
(xcb_rectangle_t[]){{0, 0, state->rect.width, state->rect.height}});
|
draw_util_clear_surface(&(state->surface), background);
|
||||||
|
|
||||||
// TODO: make i3font functions per-connection, at least these two for now…?
|
// TODO: make i3font functions per-connection, at least these two for now…?
|
||||||
xcb_flush(restore_conn);
|
xcb_flush(restore_conn);
|
||||||
xcb_aux_sync(restore_conn);
|
xcb_aux_sync(restore_conn);
|
||||||
|
|
||||||
set_font_colors(state->gc, config.client.placeholder.text, config.client.placeholder.background);
|
|
||||||
|
|
||||||
Match *swallows;
|
Match *swallows;
|
||||||
int n = 0;
|
int n = 0;
|
||||||
TAILQ_FOREACH(swallows, &(state->con->swallow_head), matches) {
|
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);
|
DLOG("con %p (placeholder 0x%08x) line %d: %s\n", state->con, state->window, n, serialized);
|
||||||
|
|
||||||
i3String *str = i3string_from_utf8(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);
|
i3string_free(str);
|
||||||
n++;
|
n++;
|
||||||
free(serialized);
|
free(serialized);
|
||||||
|
@ -185,7 +187,7 @@ static void update_placeholder_contents(placeholder_state *state) {
|
||||||
int text_width = predict_text_width(line);
|
int text_width = predict_text_width(line);
|
||||||
int x = (state->rect.width / 2) - (text_width / 2);
|
int x = (state->rect.width / 2) - (text_width / 2);
|
||||||
int y = (state->rect.height / 2) - (config.font.height / 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);
|
i3string_free(line);
|
||||||
xcb_flush(conn);
|
xcb_flush(conn);
|
||||||
xcb_aux_sync(conn);
|
xcb_aux_sync(conn);
|
||||||
|
@ -227,11 +229,8 @@ static void open_placeholder_window(Con *con) {
|
||||||
state->window = placeholder;
|
state->window = placeholder;
|
||||||
state->con = con;
|
state->con = con;
|
||||||
state->rect = con->rect;
|
state->rect = con->rect;
|
||||||
state->pixmap = xcb_generate_id(restore_conn);
|
|
||||||
xcb_create_pixmap(restore_conn, root_depth, state->pixmap,
|
draw_util_surface_init(conn, &(state->surface), placeholder, get_visualtype(root_screen), state->rect.width, state->rect.height);
|
||||||
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});
|
|
||||||
update_placeholder_contents(state);
|
update_placeholder_contents(state);
|
||||||
TAILQ_INSERT_TAIL(&state_head, state, state);
|
TAILQ_INSERT_TAIL(&state_head, state, state);
|
||||||
|
|
||||||
|
@ -285,8 +284,7 @@ bool restore_kill_placeholder(xcb_window_t placeholder) {
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
xcb_destroy_window(restore_conn, state->window);
|
xcb_destroy_window(restore_conn, state->window);
|
||||||
xcb_free_pixmap(restore_conn, state->pixmap);
|
draw_util_surface_free(restore_conn, &(state->surface));
|
||||||
xcb_free_gc(restore_conn, state->gc);
|
|
||||||
TAILQ_REMOVE(&state_head, state, state);
|
TAILQ_REMOVE(&state_head, state, state);
|
||||||
free(state);
|
free(state);
|
||||||
DLOG("placeholder window 0x%08x destroyed.\n", placeholder);
|
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);
|
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
|
update_placeholder_contents(state);
|
||||||
* 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);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,19 +329,10 @@ static void configure_notify(xcb_configure_notify_event_t *event) {
|
||||||
state->rect.width = event->width;
|
state->rect.width = event->width;
|
||||||
state->rect.height = event->height;
|
state->rect.height = event->height;
|
||||||
|
|
||||||
xcb_free_pixmap(restore_conn, state->pixmap);
|
draw_util_surface_set_size(&(state->surface), state->rect.width, state->rect.height);
|
||||||
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});
|
|
||||||
|
|
||||||
update_placeholder_contents(state);
|
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;
|
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) {
|
static void restore_handle_event(int type, xcb_generic_event_t *event) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case XCB_EXPOSE:
|
case XCB_EXPOSE:
|
||||||
|
if (((xcb_expose_event_t *)event)->count == 0) {
|
||||||
expose_event((xcb_expose_event_t *)event);
|
expose_event((xcb_expose_event_t *)event);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case XCB_CONFIGURE_NOTIFY:
|
case XCB_CONFIGURE_NOTIFY:
|
||||||
configure_notify((xcb_configure_notify_event_t *)event);
|
configure_notify((xcb_configure_notify_event_t *)event);
|
||||||
|
|
343
src/sighandler.c
343
src/sighandler.c
|
@ -3,10 +3,6 @@
|
||||||
*
|
*
|
||||||
* i3 - an improved dynamic tiling window manager
|
* i3 - an improved dynamic tiling window manager
|
||||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||||
* © 2009 Jan-Erik Rediger
|
|
||||||
*
|
|
||||||
* sighandler.c: Interactive crash dialog upon SIGSEGV/SIGABRT/SIGFPE (offers
|
|
||||||
* to restart inplace).
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#include "all.h"
|
#include "all.h"
|
||||||
|
@ -20,28 +16,44 @@
|
||||||
|
|
||||||
#include <X11/keysym.h>
|
#include <X11/keysym.h>
|
||||||
|
|
||||||
static void open_popups(void);
|
typedef struct dialog_t {
|
||||||
|
xcb_window_t id;
|
||||||
|
xcb_colormap_t colormap;
|
||||||
|
Rect dims;
|
||||||
|
surface_t surface;
|
||||||
|
|
||||||
static xcb_gcontext_t pixmap_gc;
|
TAILQ_ENTRY(dialog_t)
|
||||||
static xcb_pixmap_t pixmap;
|
dialogs;
|
||||||
|
} dialog_t;
|
||||||
|
|
||||||
|
static TAILQ_HEAD(dialogs_head, dialog_t) dialogs = TAILQ_HEAD_INITIALIZER(dialogs);
|
||||||
static int raised_signal;
|
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 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
|
* Attach gdb to pid_parent and dump a backtrace to i3-backtrace.$pid in the
|
||||||
* tmpdir
|
* tmpdir
|
||||||
*/
|
*/
|
||||||
static int backtrace(void) {
|
static int sighandler_backtrace(void) {
|
||||||
char *tmpdir = getenv("TMPDIR");
|
char *tmpdir = getenv("TMPDIR");
|
||||||
if (tmpdir == NULL)
|
if (tmpdir == NULL)
|
||||||
tmpdir = "/tmp";
|
tmpdir = "/tmp";
|
||||||
|
@ -125,53 +137,144 @@ static int backtrace(void) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static void sighandler_setup(void) {
|
||||||
* Draw the window containing the info text
|
border_width = logical_px(border_width);
|
||||||
*
|
margin = logical_px(margin);
|
||||||
*/
|
|
||||||
static int sig_draw_window(xcb_window_t win, int width, int height, int font_height, i3String **crash_text_i3strings) {
|
|
||||||
/* re-draw the background */
|
|
||||||
xcb_rectangle_t border = {0, 0, width, height},
|
|
||||||
inner = {2, 2, width - 4, height - 4};
|
|
||||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#FF0000")});
|
|
||||||
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
|
|
||||||
xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){get_colorpixel("#000000")});
|
|
||||||
xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
|
|
||||||
|
|
||||||
/* restore font color */
|
int num_lines = 5;
|
||||||
set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000"));
|
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";
|
int width_longest_message = predict_text_width(message_intro2);
|
||||||
if (backtrace_done < 0)
|
|
||||||
bt_colour = "#AA0000";
|
|
||||||
else if (backtrace_done > 0)
|
|
||||||
bt_colour = "#00AA00";
|
|
||||||
|
|
||||||
for (int i = 0; crash_text_i3strings[i] != NULL; ++i) {
|
dialog_width = width_longest_message + 2 * border_width + 2 * margin;
|
||||||
/* fix the colour for the backtrace line when it finished */
|
dialog_height = num_lines * config.font.height + 2 * border_width + 2 * margin;
|
||||||
if (i == backtrace_string_index)
|
|
||||||
set_font_colors(pixmap_gc, draw_util_hex_to_color(bt_colour), draw_util_hex_to_color("#000000"));
|
|
||||||
|
|
||||||
draw_text(crash_text_i3strings[i], pixmap, pixmap_gc, NULL,
|
|
||||||
8, 5 + i * font_height, width - 16);
|
|
||||||
|
|
||||||
/* and reset the colour again for other lines */
|
|
||||||
if (i == backtrace_string_index)
|
|
||||||
set_font_colors(pixmap_gc, draw_util_hex_to_color("#FFFFFF"), draw_util_hex_to_color("#000000"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy the contents of the pixmap to the real window */
|
|
||||||
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, width, height);
|
|
||||||
xcb_flush(conn);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static void sighandler_create_dialogs(void) {
|
||||||
* Handles keypresses of 'b', 'r' and 'f' to get a backtrace or restart i3
|
Output *output;
|
||||||
*
|
TAILQ_FOREACH(output, &outputs, outputs) {
|
||||||
*/
|
if (!output->active) {
|
||||||
static int sig_handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog_t *dialog = scalloc(1, sizeof(struct dialog_t));
|
||||||
|
TAILQ_INSERT_TAIL(&dialogs, dialog, dialogs);
|
||||||
|
|
||||||
|
xcb_visualid_t visual = get_visualid_by_depth(root_depth);
|
||||||
|
dialog->colormap = xcb_generate_id(conn);
|
||||||
|
xcb_create_colormap(conn, XCB_COLORMAP_ALLOC_NONE, dialog->colormap, root, visual);
|
||||||
|
|
||||||
|
uint32_t mask = 0;
|
||||||
|
uint32_t values[4];
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
/* Needs to be set in the case of a 32-bit root depth. */
|
||||||
|
mask |= XCB_CW_BACK_PIXEL;
|
||||||
|
values[i++] = root_screen->black_pixel;
|
||||||
|
|
||||||
|
/* Needs to be set in the case of a 32-bit root depth. */
|
||||||
|
mask |= XCB_CW_BORDER_PIXEL;
|
||||||
|
values[i++] = root_screen->black_pixel;
|
||||||
|
|
||||||
|
mask |= XCB_CW_OVERRIDE_REDIRECT;
|
||||||
|
values[i++] = 1;
|
||||||
|
|
||||||
|
/* Needs to be set in the case of a 32-bit root depth. */
|
||||||
|
mask |= XCB_CW_COLORMAP;
|
||||||
|
values[i++] = dialog->colormap;
|
||||||
|
|
||||||
|
dialog->dims.x = output->rect.x + (output->rect.width / 2);
|
||||||
|
dialog->dims.y = output->rect.y + (output->rect.height / 2);
|
||||||
|
dialog->dims.width = dialog_width;
|
||||||
|
dialog->dims.height = dialog_height;
|
||||||
|
|
||||||
|
/* Make sure the dialog is centered. */
|
||||||
|
dialog->dims.x -= dialog->dims.width / 2;
|
||||||
|
dialog->dims.y -= dialog->dims.height / 2;
|
||||||
|
|
||||||
|
dialog->id = create_window(conn, dialog->dims, root_depth, visual,
|
||||||
|
XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_POINTER,
|
||||||
|
true, mask, values);
|
||||||
|
|
||||||
|
draw_util_surface_init(conn, &(dialog->surface), dialog->id, get_visualtype_by_id(visual),
|
||||||
|
dialog->dims.width, dialog->dims.height);
|
||||||
|
|
||||||
|
xcb_grab_keyboard(conn, false, dialog->id, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
|
||||||
|
|
||||||
|
/* Confine the pointer to the crash dialog. */
|
||||||
|
xcb_grab_pointer(conn, false, dialog->id, XCB_NONE, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, dialog->id,
|
||||||
|
XCB_NONE, XCB_CURRENT_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
sighandler_handle_expose();
|
||||||
|
xcb_flush(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sighandler_destroy_dialogs(void) {
|
||||||
|
while (!TAILQ_EMPTY(&dialogs)) {
|
||||||
|
dialog_t *dialog = TAILQ_FIRST(&dialogs);
|
||||||
|
|
||||||
|
xcb_free_colormap(conn, dialog->colormap);
|
||||||
|
draw_util_surface_free(conn, &(dialog->surface));
|
||||||
|
xcb_destroy_window(conn, dialog->id);
|
||||||
|
|
||||||
|
TAILQ_REMOVE(&dialogs, dialog, dialogs);
|
||||||
|
free(dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_flush(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sighandler_handle_expose(void) {
|
||||||
|
dialog_t *current;
|
||||||
|
TAILQ_FOREACH(current, &dialogs, dialogs) {
|
||||||
|
sighandler_draw_dialog(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_flush(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sighandler_draw_dialog(dialog_t *dialog) {
|
||||||
|
const color_t black = draw_util_hex_to_color("#000000");
|
||||||
|
const color_t white = draw_util_hex_to_color("#FFFFFF");
|
||||||
|
const color_t red = draw_util_hex_to_color("#FF0000");
|
||||||
|
|
||||||
|
/* Start with a clean slate and draw a red border. */
|
||||||
|
draw_util_clear_surface(&(dialog->surface), red);
|
||||||
|
draw_util_rectangle(&(dialog->surface), black, border_width, border_width,
|
||||||
|
dialog->dims.width - 2 * border_width, dialog->dims.height - 2 * border_width);
|
||||||
|
|
||||||
|
int y = border_width + margin;
|
||||||
|
const int x = border_width + margin;
|
||||||
|
const int max_width = dialog->dims.width - 2 * x;
|
||||||
|
|
||||||
|
draw_util_text(message_intro, &(dialog->surface), white, black, x, y, max_width);
|
||||||
|
y += config.font.height;
|
||||||
|
|
||||||
|
draw_util_text(message_intro2, &(dialog->surface), white, black, x, y, max_width);
|
||||||
|
y += config.font.height;
|
||||||
|
|
||||||
|
char *bt_color = "#FFFFFF";
|
||||||
|
if (backtrace_done < 0) {
|
||||||
|
bt_color = "#AA0000";
|
||||||
|
} else if (backtrace_done > 0) {
|
||||||
|
bt_color = "#00AA00";
|
||||||
|
}
|
||||||
|
draw_util_text(message_option_backtrace, &(dialog->surface), draw_util_hex_to_color(bt_color), black, x, y, max_width);
|
||||||
|
y += config.font.height;
|
||||||
|
|
||||||
|
draw_util_text(message_option_restart, &(dialog->surface), white, black, x, y, max_width);
|
||||||
|
y += config.font.height;
|
||||||
|
|
||||||
|
draw_util_text(message_option_forget, &(dialog->surface), white, black, x, y, max_width);
|
||||||
|
y += config.font.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sighandler_handle_key_press(xcb_key_press_event_t *event) {
|
||||||
uint16_t state = event->state;
|
uint16_t state = event->state;
|
||||||
|
|
||||||
/* Apparently, after activating numlock once, the numlock modifier
|
/* 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
|
/* fork and exec/attach GDB to the parent to get a backtrace in the
|
||||||
* tmpdir */
|
* tmpdir */
|
||||||
backtrace_done = backtrace();
|
backtrace_done = sighandler_backtrace();
|
||||||
|
sighandler_handle_expose();
|
||||||
/* re-open the windows to indicate that it's finished */
|
} else if (sym == 'r') {
|
||||||
open_popups();
|
sighandler_destroy_dialogs();
|
||||||
}
|
|
||||||
|
|
||||||
if (sym == 'r')
|
|
||||||
i3_restart(false);
|
i3_restart(false);
|
||||||
|
} else if (sym == 'f') {
|
||||||
if (sym == 'f')
|
sighandler_destroy_dialogs();
|
||||||
i3_restart(true);
|
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) {
|
void handle_signal(int sig, siginfo_t *info, void *data) {
|
||||||
DLOG("i3 crashed. SIG: %d\n", sig);
|
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);
|
sigaction(sig, &action, NULL);
|
||||||
raised_signal = sig;
|
raised_signal = sig;
|
||||||
|
|
||||||
open_popups();
|
sighandler_setup();
|
||||||
|
sighandler_create_dialogs();
|
||||||
|
|
||||||
xcb_generic_event_t *event;
|
xcb_generic_event_t *event;
|
||||||
/* Yay, more own eventhandlers… */
|
/* Yay, more own eventhandlers… */
|
||||||
while ((event = xcb_wait_for_event(conn))) {
|
while ((event = xcb_wait_for_event(conn))) {
|
||||||
/* Strip off the highest bit (set if the event is generated) */
|
/* Strip off the highest bit (set if the event is generated) */
|
||||||
int type = (event->response_type & 0x7F);
|
int type = (event->response_type & 0x7F);
|
||||||
if (type == XCB_KEY_PRESS) {
|
switch (type) {
|
||||||
sig_handle_key_press(NULL, conn, (xcb_key_press_event_t *)event);
|
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);
|
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) {
|
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);
|
match_free(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);
|
free(con);
|
||||||
|
|
||||||
/* in the case of floating windows, we already focused another container
|
/* 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->type == CT_WORKSPACE) {
|
||||||
if (con_num_children(con) < 2) {
|
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;
|
con->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
|
47
src/util.c
47
src/util.c
|
@ -66,6 +66,34 @@ __attribute__((pure)) bool name_is_digits(const char *name) {
|
||||||
return true;
|
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
|
* Parses the workspace name as a number. Returns -1 if the workspace should be
|
||||||
* interpreted as a "named workspace".
|
* interpreted as a "named workspace".
|
||||||
|
@ -259,7 +287,7 @@ void i3_restart(bool forget_layout) {
|
||||||
|
|
||||||
restore_geometry();
|
restore_geometry();
|
||||||
|
|
||||||
ipc_shutdown();
|
ipc_shutdown(SHUTDOWN_REASON_RESTART);
|
||||||
|
|
||||||
LOG("restarting \"%s\"...\n", start_argv[0]);
|
LOG("restarting \"%s\"...\n", start_argv[0]);
|
||||||
/* make sure -a is in the argument list or add it */
|
/* 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() here. */
|
||||||
waitpid(*nagbar_pid, NULL, 0);
|
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;
|
char *name;
|
||||||
|
|
||||||
CIRCLEQ_ENTRY(con_state) state;
|
CIRCLEQ_ENTRY(con_state)
|
||||||
CIRCLEQ_ENTRY(con_state) old_state;
|
state;
|
||||||
TAILQ_ENTRY(con_state) initial_mapping_order;
|
|
||||||
|
CIRCLEQ_ENTRY(con_state)
|
||||||
|
old_state;
|
||||||
|
|
||||||
|
TAILQ_ENTRY(con_state)
|
||||||
|
initial_mapping_order;
|
||||||
} con_state;
|
} 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_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);
|
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);
|
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;
|
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);
|
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);
|
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
|
/* We actually only redraw the far right two pixels as that is the
|
||||||
* distance we keep from the edge (not the entire border width).
|
* distance we keep from the edge (not the entire border width).
|
||||||
* Redrawing the entire border would cause text to be cut off. */
|
* 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->x + dr->width - 2 * logical_px(1),
|
||||||
dr->y,
|
dr->y,
|
||||||
2 * logical_px(1),
|
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. */
|
* be easily distinguished. */
|
||||||
if (con->parent->layout == L_TABBED) {
|
if (con->parent->layout == L_TABBED) {
|
||||||
/* Left side */
|
/* 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);
|
dr->x, dr->y, 1, dr->height);
|
||||||
|
|
||||||
/* Right side */
|
/* 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);
|
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 */
|
/* 2: draw the client.background, but only for the parts around the window_rect */
|
||||||
if (con->window != NULL) {
|
if (con->window != NULL) {
|
||||||
/* top area */
|
/* 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);
|
0, 0, r->width, w->y);
|
||||||
/* bottom area */
|
/* 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));
|
0, w->y + w->height, r->width, r->height - (w->y + w->height));
|
||||||
/* left area */
|
/* 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);
|
0, 0, w->x, r->height);
|
||||||
/* right area */
|
/* 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);
|
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
|
* rectangle because some childs are not freely resizable and we want
|
||||||
* their background color to "shine through". */
|
* their background color to "shine through". */
|
||||||
if (!(borders_to_hide & ADJ_LEFT_SCREEN_EDGE)) {
|
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)) {
|
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,
|
p->color->child_border, r->width + (br.width + br.x), 0,
|
||||||
-(br.width + br.x), r->height);
|
-(br.width + br.x), r->height);
|
||||||
}
|
}
|
||||||
if (!(borders_to_hide & ADJ_LOWER_SCREEN_EDGE)) {
|
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),
|
p->color->child_border, br.x, r->height + (br.height + br.y),
|
||||||
r->width + br.width, -(br.height + br.y));
|
r->width + br.width, -(br.height + br.y));
|
||||||
}
|
}
|
||||||
/* pixel border needs an additional line at the top */
|
/* pixel border needs an additional line at the top */
|
||||||
if (p->border_style == BS_PIXEL && !(borders_to_hide & ADJ_UPPER_SCREEN_EDGE)) {
|
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);
|
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 &&
|
TAILQ_PREV(con, nodes_head, nodes) == NULL &&
|
||||||
con->parent->type != CT_FLOATING_CON) {
|
con->parent->type != CT_FLOATING_CON) {
|
||||||
if (p->parent_layout == L_SPLITH) {
|
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);
|
r->width + (br.width + br.x), br.y, -(br.width + br.x), r->height + br.height);
|
||||||
} else if (p->parent_layout == L_SPLITV) {
|
} 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));
|
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
|
* garbage left on there. This is important to avoid tearing when using
|
||||||
* transparency. */
|
* transparency. */
|
||||||
if (con == TAILQ_FIRST(&(con->parent->nodes_head))) {
|
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);
|
FREE(con->parent->deco_render_params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 4: paint the bar */
|
/* 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);
|
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 */
|
/* 5: draw two unconnected horizontal lines in border color */
|
||||||
|
@ -564,9 +572,6 @@ void x_draw_decoration(Con *con) {
|
||||||
goto after_title;
|
goto after_title;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (win->name == NULL)
|
|
||||||
goto copy_pixmaps;
|
|
||||||
|
|
||||||
int mark_width = 0;
|
int mark_width = 0;
|
||||||
if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) {
|
if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) {
|
||||||
char *formatted_mark = sstrdup("");
|
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);
|
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),
|
draw_util_text(title, &(parent->frame_buffer),
|
||||||
p->color->text, p->color->background,
|
p->color->text, p->color->background,
|
||||||
con->deco_rect.x + logical_px(2),
|
con->deco_rect.x + logical_px(2),
|
||||||
con->deco_rect.y + text_offset_y,
|
con->deco_rect.y + text_offset_y,
|
||||||
con->deco_rect.width - mark_width - 2 * logical_px(2));
|
con->deco_rect.width - mark_width - 2 * logical_px(2));
|
||||||
if (con->title_format != NULL)
|
|
||||||
|
if (con->title_format != NULL) {
|
||||||
I3STRING_FREE(title);
|
I3STRING_FREE(title);
|
||||||
|
}
|
||||||
|
|
||||||
after_title:
|
after_title:
|
||||||
x_draw_decoration_after_title(con, p);
|
x_draw_decoration_after_title(con, p);
|
||||||
copy_pixmaps:
|
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);
|
x_deco_recurse(current);
|
||||||
|
|
||||||
if (state->mapped) {
|
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_flush(conn);
|
||||||
xcb_set_window_rect(conn, con->frame.id, rect);
|
xcb_set_window_rect(conn, con->frame.id, rect);
|
||||||
if (con->frame_buffer.id != XCB_NONE) {
|
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);
|
xcb_flush(conn);
|
||||||
|
|
||||||
|
@ -873,7 +884,7 @@ void x_push_node(Con *con) {
|
||||||
|
|
||||||
/* copy the pixmap contents to the frame window immediately after mapping */
|
/* copy the pixmap contents to the frame window immediately after mapping */
|
||||||
if (con->frame_buffer.id != XCB_NONE) {
|
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);
|
xcb_flush(conn);
|
||||||
|
|
||||||
|
@ -893,8 +904,9 @@ void x_push_node(Con *con) {
|
||||||
/* Handle all children and floating windows of this node. We recurse
|
/* Handle all children and floating windows of this node. We recurse
|
||||||
* in focus order to display the focused client in a stack first when
|
* in focus order to display the focused client in a stack first when
|
||||||
* switching workspaces (reduces flickering). */
|
* switching workspaces (reduces flickering). */
|
||||||
TAILQ_FOREACH(current, &(con->focus_head), focused)
|
TAILQ_FOREACH(current, &(con->focus_head), focused) {
|
||||||
x_push_node(current);
|
x_push_node(current);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
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;
|
visual = XCB_COPY_FROM_PARENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_create_window(conn,
|
xcb_void_cookie_t gc_cookie = xcb_create_window(conn,
|
||||||
depth,
|
depth,
|
||||||
result, /* the window id */
|
result, /* the window id */
|
||||||
root, /* parent == root */
|
root, /* parent == root */
|
||||||
|
@ -39,6 +39,11 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims,
|
||||||
mask,
|
mask,
|
||||||
values);
|
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 */
|
/* Set the cursor */
|
||||||
if (xcursor_supported) {
|
if (xcursor_supported) {
|
||||||
mask = XCB_CW_CURSOR;
|
mask = XCB_CW_CURSOR;
|
||||||
|
@ -62,28 +67,6 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims,
|
||||||
return result;
|
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
|
* Generates a configure_notify_event with absolute coordinates (relative to the X root
|
||||||
* window, not to the client’s frame) for the given client.
|
* 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);
|
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
|
* 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;
|
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.
|
* Set the cursor of the root window to the given cursor id.
|
||||||
* This function should only be used if xcursor_supported == false.
|
* 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