Merge branch 'next' into stable

This commit is contained in:
Michael Stapelberg
2025-12-19 08:20:08 +01:00
512 changed files with 6486 additions and 4224 deletions

View File

@ -1,13 +1,14 @@
BasedOnStyle: google
AllowShortBlocksOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortBlocksOnASingleLine: false
AlwaysBreakBeforeMultilineStrings: false
IndentWidth: 4
PointerBindsToType: false
BasedOnStyle: google
ColumnLimit: 0
SpaceBeforeParens: ControlStatements
SortIncludes: false
ForEachMacros: [ TAILQ_FOREACH, TAILQ_FOREACH_REVERSE, SLIST_FOREACH, CIRCLEQ_FOREACH, CIRCLEQ_FOREACH_REVERSE, NODES_FOREACH, NODES_FOREACH_REVERSE, FOREACH_NONINTERNAL]
IndentWidth: 4
InsertBraces: true
PointerBindsToType: false
SortIncludes: false
SpaceBeforeParens: ControlStatements
TypenameMacros: [ SLIST_HEAD, SLIST_ENTRY, LIST_HEAD, LIST_ENTRY, SIMPLEQ_HEAD, SIMPLEQ_ENTRY, TAILQ_HEAD, TAILQ_ENTRY, CIRCLEQ_HEAD, CIRCLEQ_ENTRY ]

View File

@ -1,72 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
---
<!--
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
-->
## I'm submitting a…
<!-- Please check one of the following options with "x" -->
<pre>
[x] Bug
[ ] Feature Request
[ ] Documentation Request
[ ] Other (Please describe in detail)
</pre>
## Current Behavior
<!--
Describe the current behavior,
e.g., »When pressing Alt+j (focus left), the window above the current window is focused.«
-->
## Expected Behavior
<!--
Describe the desired behavior you expect after mitigation of the issue,
e.g., »The window left next to the current window should be focused.«
-->
## Reproduction Instructions
<!--
Please provide detailed instructions on how the bug can be reproduced.
E.g., »Open three windows in a V[A H[B C]] layout on a new workspace«
-->
## Environment
<!--
Please include your exact i3 version.
Note that we only support the latest major release and the current development version. If you are using an older version of i3, please first update to the current release version and reproduce the issue there.
-->
Output of `i3 --moreversion 2>&-`:
<pre>
i3 version:
</pre>
<!--
Please include your (complete) i3 config with which the issue occurs. You can either paste the file directly or provide a link to a service such as pastebin.
If you would like to help debugging the issue, please try to reduce the config such that it is as close to the default config as possible while still reproducing the issue. This can help us bisect the root cause.
-->
<details><summary>Config file</summary><pre>
</pre>
</details>
<!--
Providing a logfile can help us trace the root cause of an issue much quicker. You can learn how to generate the logfile here:
https://i3wm.org/docs/debugging.html
Providing the logfile is optional.
-->
<pre>
Logfile URL:
</pre>
<!--
Please also answer the questions below to help us process your issue faster. If you have any other information to share, please add it here as well.
-->
<pre>
- Linux Distribution & Version:
- Are you using a compositor (e.g., xcompmgr or compton):
</pre>

107
.github/ISSUE_TEMPLATE/bug_report.yml vendored Executable file
View File

@ -0,0 +1,107 @@
name: Bug Report
description: Create a report to help us improve.
labels: [bug]
body:
- type: checkboxes
id: terms
attributes:
label: Welcome
options:
- label: Yes, I'm using the latest major release or the current development version. These are the only supported versions.
required: true
- label: Yes, I've searched similar issues and discussions on GitHub and didn't find any.
required: true
- type: textarea
id: current
attributes:
label: Current Behavior
placeholder: |-
Describe the current behavior,
e.g., »When pressing Alt+j (focus left), the window above the current window is focused.«
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behavior
placeholder: |-
Describe the desired behavior you expect after mitigation of the issue,
e.g., »The window left next to the current window should be focused.«
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Reproduction Instructions
placeholder: |-
Please provide detailed instructions on how the bug can be reproduced.
E.g., »Open three windows in a V[A H[B C]] layout on a new workspace«
validations:
required: true
- type: textarea
id: version
attributes:
label: i3 version
description: |-
Paste the output of
```
i3 --moreversion 2>&-
```
render: text
validations:
required: true
- type: textarea
id: config
attributes:
label: Config file
description: |-
Please include your (complete) i3 config with which the issue occurs.
If you would like to help debugging the issue, please try to reduce the config such that it is as close to the default config as possible while still reproducing the issue. This can help us bisect the root cause.
render: text
validations:
required: true
- type: input
id: distro
attributes:
label: Linux distribution & Version
validations:
required: true
- type: dropdown
id: compositor
attributes:
label: Are you using a compositor?
description: |-
Try running
```shell
pidof picom
pidof compton
```
If any IDs show up, you are running a compositor
options:
- I don't know
- I am sure I don't run any compositor
- picom
- compton
- Other
validations:
required: true
- type: input
id: verbose-output
attributes:
label: Logfile
description: |-
Providing the URL to a logfile can help us trace the root cause of an issue much quicker. You can learn how to generate the logfile here:
https://i3wm.org/docs/debugging.html
Providing the logfile is optional.
validations:
required: false

View File

@ -1,4 +1,7 @@
contact_links:
- name: Userguide
url: https://i3wm.org/docs/userguide.html
about: i3 Users Guide
- name: Ask a question or request support for using i3
url: https://github.com/i3/i3/discussions/new
about: Ask a question or request support for using i3

View File

@ -1,56 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
---
<!--
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
-->
## I'm submitting a…
<!-- Check one of the following options with "x" -->
<pre>
[ ] Bug
[x] Feature Request
[ ] Documentation Request
[ ] Other (Please describe in detail)
</pre>
## Current Behavior
<!--
Describe the current behavior,
e.g., »When pressing Alt+j (focus left), the window above the current window is focused.«
-->
## Desired Behavior
<!--
Describe the desired behavior you expect after mitigation of the issue,
e.g., »The window left next to the current window should be focused.«
-->
## Impact
<!--
Please note that at this point we focus on maintaining i3 and fixing bugs, and will rarely consider features which require further configuration or significant complexity.
In such cases you should consider and present specific benefits derived from adding this feature such that it can be weighed against the cost of additional complexity and maintenance.
-->
<pre>
[ ] This feature requires new configuration and/or commands
</pre>
## Environment
<!--
Please include your exact i3 version.
Note that we only support the latest major release and the current development version. If you are using an older version of i3, please first update to the current release version and reproduce the issue there.
-->
Output of `i3 --moreversion 2>&-`:
<pre>
i3 version:
</pre>
<!--
Please also answer the questions below to help us process your issue faster. If you have any other information to share, please add it here as well.
-->
<pre>
- Linux Distribution & Version:
- Are you using a compositor (e.g., xcompmgr or compton):
</pre>

57
.github/ISSUE_TEMPLATE/feature_request.yml vendored Executable file
View File

@ -0,0 +1,57 @@
name: Feature request
description: Suggest an idea for this project
labels: [enhancement]
body:
- type: checkboxes
id: terms
attributes:
label: Welcome
options:
- label: Yes, I've searched similar issues and discussions on GitHub and didn't find any.
required: true
- type: checkboxes
id: impact
attributes:
label: Impact
description: |-
Please note that at this point we focus on maintaining i3 and fixing bugs, and will rarely consider features which require further configuration or significant complexity.
In such cases you should consider and present specific benefits derived from adding this feature such that it can be weighed against the cost of additional complexity and maintenance.
Keep in mind that i3 provides a powerful way to interact with it through its IPC interface: https://i3wm.org/docs/ipc.html.
options:
- label: This feature requires new configuration and/or commands
required: false
- type: textarea
id: current
attributes:
label: Current Behavior
placeholder: |-
Describe the current behavior,
e.g., »When pressing Alt+j (focus left), the window above the current window is focused.«
validations:
required: true
- type: textarea
id: desired
attributes:
label: Desired Behavior
placeholder: |-
Describe the desired behavior you expect after mitigation of the issue,
e.g., »The window left next to the current window should be focused.«
validations:
required: true
- type: textarea
id: version
attributes:
label: i3 version
description: |-
Paste the output of
```
i3 --moreversion 2>&-
```
render: text
validations:
required: true

View File

@ -20,34 +20,36 @@ jobs:
DOCKER_EMAIL: ${{ secrets.DOCKER_EMAIL }}
DOCKER_USER: ${{ secrets.DOCKER_USER }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
BALTO_TOKEN: ${{ secrets.BALTO_TOKEN }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: git fetch --prune --unshallow
- name: construct container name
run: |
echo "BASENAME=i3wm/travis-base:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base.Dockerfile)" >> $GITHUB_ENV
echo "BASENAME_386=i3wm/travis-base-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-386.Dockerfile)" >> $GITHUB_ENV
echo "BASENAME_UBUNTU=i3wm/travis-base-ubuntu:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu.Dockerfile)" >> $GITHUB_ENV
echo "BASENAME_UBUNTU_386=i3wm/travis-base-ubuntu-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu-386.Dockerfile)" >> $GITHUB_ENV
- name: fetch or build Docker container
run: |
docker pull ${{ env.BASENAME }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME }} travis/travis-base.Dockerfile
- name: fetch or build extra Docker containers
if: github.ref == 'refs/heads/next' && matrix.compiler == 'gcc'
run: |
echo "::group::Ubuntu amd64"
./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_UBUNTU }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU }} travis/travis-base-ubuntu.Dockerfile
echo "::endgroup::"
echo "::group::Debian i386"
./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_386 }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_386 }} travis/travis-base-386.Dockerfile
echo "::endgroup::"
echo "::group::Ubuntu i386"
./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_UBUNTU_386 }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU_386 }} travis/travis-base-ubuntu-386.Dockerfile
echo "::endgroup::"
docker pull ${{ env.BASENAME_UBUNTU }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU }} travis/travis-base-ubuntu.Dockerfile
- name: build i3
run: |
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf build; mkdir -p build && cd build && CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror -fno-common" meson .. -Ddocs=true -Dmans=true -Db_sanitize=address && ninja -v'
CFLAGS="-Wformat -Wformat-security -Wall -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror -fno-common -D_FORTIFY_SOURCE=3"
if [ "${{ matrix.compiler }}" = "gcc" ]; then
CFLAGS="$CFLAGS -Wsuggest-attribute=pure -Wsuggest-attribute=const -Wsuggest-attribute=format"
fi
export CFLAGS
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC -e CFLAGS ${{ env.BASENAME }} /bin/sh -c 'rm -rf build; mkdir -p build && cd build && meson setup .. -Ddocs=true -Dmans=true -Db_sanitize="address,undefined" --buildtype=debugoptimized && ninja -v'
- name: Upload docs html for manual inspection
uses: actions/upload-artifact@v4
with:
name: i3-docs
path: |
build/*.html
if: matrix.compiler == 'gcc'
- name: check spelling
run: |
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-spelling.pl
@ -55,48 +57,42 @@ jobs:
run: |
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} ./travis/run-tests.sh
- name: Archive test logs
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: test-logs
path: build/testsuite-*
if: ${{ failure() }}
- name: build dist tarball
run: |
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf distbuild; mkdir distbuild && cd distbuild && meson .. -Ddocs=true -Dmans=true && meson dist --no-tests'
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf distbuild; mkdir distbuild && cd distbuild && meson setup .. -Ddocs=true -Dmans=true && meson dist --no-tests'
- name: build Debian packages
if: github.ref == 'refs/heads/next' && matrix.compiler == 'gcc'
run: |
echo "::group::Debian amd64"
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/debian-build.sh deb/debian-amd64/DIST
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/debian-build.sh deb/debian-amd64/DIST
echo "::endgroup::"
echo "::group::Ubuntu amd64"
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_UBUNTU }} ./travis/debian-build.sh deb/ubuntu-amd64/DIST
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_UBUNTU }} ./travis/debian-build.sh deb/ubuntu-amd64/DIST
echo "::endgroup::"
echo "::group::Debian i386"
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_386 }} linux32 ./travis/debian-build.sh deb/debian-i386/DIST
echo "::endgroup::"
echo "::group::Ubuntu i386"
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_UBUNTU_386 }} linux32 ./travis/debian-build.sh deb/ubuntu-i386/DIST
echo "::endgroup::"
- name: push Debian packages to balto
run: |
./travis/skip-pkg.sh || travis/push-balto.sh
- name: build docs
if: github.ref == 'refs/heads/next' && matrix.compiler == 'gcc'
run: |
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/docs.sh
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/docs.sh
- name: push docs to GitHub pages
if: github.ref == 'refs/heads/next' && matrix.compiler == 'gcc'
run: |
./travis/skip-pkg.sh || travis/deploy-github-pages.sh
travis/deploy-github-pages.sh
formatting:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: check & print release notes
run: ./release-notes/generator.pl
- name: Install dependencies
run: |
sudo apt-get install -y clang-format-12
sudo apt-get install -y clang-format-20
- name: Check formatting
run: clang-format-12 --dry-run --Werror $(git ls-files '*.c' '*.h')
run: clang-format-20 --dry-run --Werror $(git ls-files '*.c' '*.h')
- name: Verify safe wrapper functions are used
run: ./travis/check-safe-wrappers.sh

1
.gitignore vendored
View File

@ -50,4 +50,3 @@ LAST_VERSION
# it is up to you to arrange for it to be ignored by git,
# e.g. by listing your directory in .git/info/exclude.
/build

View File

@ -1,5 +1,14 @@
Revision history for AnyEvent-I3
0.19 2024-04-09
* use Carp for errors (includes stacktraces)
* introduce (preferred) RUN_COMMAND spelling
* migrate tooling to ExtUtils::MakeMaker
* implement the tick event
* introduce the sync IPC command
* introduce the GET_BINDING_STATE IPC command
0.18 2017-08-19
* support the GET_CONFIG command

View File

@ -8,7 +8,6 @@ use AnyEvent::Handle;
use AnyEvent::Socket;
use AnyEvent;
use Encode;
use Scalar::Util qw(tainted);
use Carp;
=head1 NAME
@ -17,11 +16,11 @@ AnyEvent::I3 - communicate with the i3 window manager
=cut
our $VERSION = '0.18';
our $VERSION = '0.19';
=head1 VERSION
Version 0.18
Version 0.19
=head1 SYNOPSIS
@ -132,35 +131,10 @@ sub i3 {
AnyEvent::I3->new(@_)
}
# Calls i3, even when running in taint mode.
sub _call_i3 {
my ($args) = @_;
my $path_tainted = tainted($ENV{PATH});
# This effectively circumvents taint mode checking for $ENV{PATH}. We
# do this because users might specify PATH explicitly to call i3 in a
# custom location (think ~/.bin/).
(local $ENV{PATH}) = ($ENV{PATH} =~ /(.*)/);
# In taint mode, we also need to remove all relative directories from
# PATH (like . or ../bin). We only do this in taint mode and warn the
# user, since this might break a real-world use case for some people.
if ($path_tainted) {
my @dirs = split /:/, $ENV{PATH};
my @filtered = grep !/^\./, @dirs;
if (scalar @dirs != scalar @filtered) {
$ENV{PATH} = join ':', @filtered;
warn qq|Removed relative directories from PATH because you | .
qq|are running Perl with taint mode enabled. Remove -T | .
qq|to be able to use relative directories in PATH. | .
qq|New PATH is "$ENV{PATH}"|;
}
}
# Otherwise the qx() operator wont work:
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
chomp(my $result = qx(i3 $args));
# Circumventing taint mode again: the socket can be anywhere on the
# system and thats okay.
if ($result =~ /^([^\0]+)$/) {
return $1;
}
@ -182,21 +156,21 @@ instance on the current DISPLAY which is almost always what you want.
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';
# We have I3SOCK now
$path ||= $ENV{I3SOCK};
$path ||= _call_i3('--get-socketpath');
# 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];
my $home = $ENV{HOME};
confess "Could not get home directory" unless $home and -d $home;
$path =~ s/~/$home/g;
}
if(!-S $path) {
die "$path is not a socket", $/;
}
bless { path => $path } => $class;
}
@ -315,6 +289,11 @@ sub subscribe {
# Register callbacks for each message type
for my $key (keys %{$callbacks}) {
if (!exists $events{$key}) {
warn "Could not subscribe to event type '$key'." .
" Supported events are " . join(" ", sort keys %events), $/;
next;
}
my $type = $events{$key};
$self->{callbacks}->{$type} = $callbacks->{$key};
}

View File

@ -1,4 +1,4 @@
#!perl -T
#!perl
use Test::More tests => 1;

View File

@ -1,4 +1,4 @@
#!perl -T
#!perl
# vim:ts=4:sw=4:expandtab
use Test::More tests => 3;

View File

@ -1,4 +1,4 @@
#!perl -T
#!perl
# vim:ts=4:sw=4:expandtab
use Test::More tests => 3;

View File

@ -1,4 +1,4 @@
#!perl -T
#!perl
use strict;
use warnings;

View File

@ -1,4 +1,4 @@
#!perl -T
#!perl
use strict;
use warnings;

View File

@ -1,4 +1,4 @@
#!perl -T
#!perl
use strict;
use warnings;

View File

@ -27,7 +27,7 @@ https://mesonbuild.com/Quick-guide.html#compiling-a-meson-project
In case youre unfamiliar:
$ mkdir -p build && cd build
$ meson ..
$ meson setup
$ ninja
Please make sure that i3-migrate-config-to-v4 and i3-config-wizard are

View File

@ -1,55 +0,0 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.22 │
└──────────────────────────────┘
This is i3 v4.22. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
The biggest change in this release is the merge of the i3-gaps fork.
The i3-gaps fork was the most popular fork of i3, adding the option to
show gaps between tiled windows and/or the screen edges.
See https://i3wm.org/docs/userguide.html#gaps for more details.
Instead of maintaining two versions of i3 (both upstream and downstream,
meaning in Linux distributions and other package collections),
we concluded it would be better for everyone to merge this feature.
For users of i3: gaps are off by default, so there is no change in behavior.
For users of i3-gaps: the configuration is compatible, so you can switch
to i3 v4.22 or newer, without any changes in behavior.
Thanks to Ingo Bürk for maintaining i3-gaps for many years,
for becoming a core i3 maintainer and for helping make this merge possible!
┌────────────────────────────┐
│ Changes in i3 v4.22 │
└────────────────────────────┘
• i3bar: bar { padding } config directive now implemented (supports bar { height } from i3-gaps)
• i3-dmenu-desktop: allow more than one --entry-type with the --show-duplicates flag
• You can now enable gaps using the gaps config directive and/or command
• colors now support an optional alpha value at the end (#rrggbbaa)
• the hide_edge_borders option now supports the smart_no_gaps keyword
• Support nonprimary keyword for outputs
• add "mode" field in binding event
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• gaps: workspace gaps assignments are no longer order-dependent
• Fix compliance to _MOTIF_WM_HINTS spec when all decorations are set
• The floating_from and tiling_from criteria now also work in commands
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
bodea, Demian, Erich Heine, Ingo Bürk, Matias Goldfeld, Orestis Floros,
Tudor Brindus
-- Michael Stapelberg, 2023-01-02

49
RELEASE-NOTES-4.25 Normal file
View File

@ -0,0 +1,49 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.25 │
└──────────────────────────────┘
This is i3 v4.25. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
Most changes in this release cycle have been “behind the scenes”,
without an observable effect for users. For example, we switched
to clang-format-20, fixed a number of (benign) memory issues,
fixed a few flaky tests and maintained our GitHub Actions setup,
e.g. for the shutdown of baltocdn (our old host for nightly builds).
We also investigated shipping an i3.service systemd user unit,
but could not make that work without breaking existing setups.
If distribution maintainers (or anyone) has advice, please share
(after reading up!) over in https://github.com/i3/i3/issues/5186
┌────────────────────────────┐
│ Changes in i3 v4.25 │
└────────────────────────────┘
• Set _NET_FRAME_EXTENTS according to the actual decoration size.
This improves compatibility with picom and other software.
• The command parser is now reentrant, which fixes a few
advanced for_window usages (e.g. multiple criteria).
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• fix paragraph separators cutting off window titles
• fix crash when a container parent is focused
and a tiling drag causes it to be killed
• fix crash when using for_window [...] reload
• fix append_layout when containers use a mark
• randr: fix memleak and use-after-free
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
Dmitry, Emeric Planet, FedGuy699, Garrett Marcinak, Michele Piazzai,
Orestis Floros, Sergey Vlasov, Vladimir Panteleev, algonell
-- Michael Stapelberg, 2025-12-19

View File

@ -319,7 +319,7 @@
<dc:title>steckdenis</dc:title>
</cc:Agent>
</dc:creator>
<dc:description>Logo for I3, an improved dynamic tiling window manager: http://i3.zekjur.net/</dc:description>
<dc:description>Logo for i3 - an improved tiling window manager: http://i3.zekjur.net/</dc:description>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" />
</cc:Work>

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -329,7 +329,7 @@
<dc:title>steckdenis</dc:title>
</cc:Agent>
</dc:creator>
<dc:description>Logo for I3, an improved dynamic tiling window manager: http://i3.zekjur.net/</dc:description>
<dc:description>Logo for i3 - an improved tiling window manager: http://i3.zekjur.net/</dc:description>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" />
</cc:Work>

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

14
debian/changelog vendored
View File

@ -1,8 +1,20 @@
i3-wm (4.24-1) unstable; urgency=medium
* New upstream release.
-- Michael Stapelberg <stapelberg@debian.org> Wed, 06 Nov 2024 18:34:06 +0100
i3-wm (4.23-1) unstable; urgency=medium
* New upstream release.
-- Michael Stapelberg <stapelberg@debian.org> Sun, 29 Oct 2023 15:42:11 +0100
i3-wm (4.22-1) unstable; urgency=medium
* New upstream release.
-- Michael Stapelberg <stapelberg@debian.org> Mon, 02 Jan 2023 09:34:02 +0100
-- Michael Stapelberg <stapelberg@debian.org> Mon, 02 Jan 2023 09:46:22 +0100
i3-wm (4.21.2-1) unstable; urgency=medium

2
debian/control vendored
View File

@ -44,7 +44,7 @@ Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
Provides: x-window-manager
Recommends: xfonts-base, fonts-dejavu-core, libanyevent-i3-perl (>= 0.12), libjson-xs-perl, rxvt-unicode | x-terminal-emulator
Description: improved dynamic tiling window manager
Description: improved tiling window manager
Key features of i3 are good documentation, reasonable defaults (changeable in
a simple configuration file) and good multi-monitor support. The user
interface is designed for power users and emphasizes keyboard usage. i3 uses

View File

@ -29,7 +29,7 @@
add_ignore_event, xcb_intern_atom, xcb_intern_atom_reply, fprintf, printf, free, load_configuration,%
XInternAtom, exit, strlen}}
}{}
\title{i3 - an improved dynamic tiling window manager}
\title{i3 - an improved tiling window manager}
\author{sECuRE beim NoName e.V.\\
~\\
powered by \LaTeX, of course}

View File

@ -147,16 +147,17 @@ After pressing "b" in the crash dialog, you will get a file called
id (PID) and the second one is incremented each time you generate a backtrace,
starting at 0.
== Sending bug reports/debugging on IRC
In Linux, if the backtrace just says +No stack.+, that's because gdb does not
have necessary permissions to attach to a running process. You can fix that by
running from a terminal (you can open a new tty, e.g. with ctrl-alt-F2):
When sending bug reports, please attach the *whole* log file. Even if you think
you found the section which clearly highlights the problem, additional
information might be necessary to completely diagnose the problem.
---------------------------------------------------------------------
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
---------------------------------------------------------------------
When debugging with us in IRC, be prepared to use a so-called nopaste service
such as https://pastebin.com because pasting large amounts of text in IRC
sometimes leads to incomplete lines (servers have line length limitations) or
flood kicks.
Afterwards, try re-generating the stack trace. Note that this setting re-sets
after reboot, see more info at
https://www.kernel.org/doc/Documentation/security/Yama.txt.
== Debugging i3bar

View File

@ -14,7 +14,7 @@ you find necessary, please do not hesitate to contact me.
<p>
++++
This document is not 100% up to date. Specifically, everything up to and
including <<data_structures>> has been updated recently. The rest might contain
including <<startup>> has been updated recently. The rest might contain
outdated information.
++++
</p>
@ -28,36 +28,24 @@ https://mesonbuild.com/[The Meson Build system]; see
https://mesonbuild.com/Quick-guide.html#compiling-a-meson-project[Quickstart
Guide → Compiling a Meson project]. In case youre unfamiliar:
$ mkdir -p build && cd build
$ meson ..
$ ninja
mkdir -p build
meson setup build
meson compile -C build
=== 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.
* +ninja test+ runs the i3 testsuite. See docs/testsuite for details.
* +make check+ runs the i3 testsuite. See docs/testsuite for details.
* +meson dist+ builds a release tarball and runs tests on the result.
* +make distcheck+ (runs testsuite on +make dist+ result, tiny bit quicker
feedback cycle than waiting for the travis build to catch the issue).
* +meson -Ddocs=true -Dmans=true+ will enable the options to build docs and
manpages. These options require additional dependencies that are normally not
required for users who just want to build i3.
* +make uninstall+ (occasionally requested by users who compile from source)
* +make+ will build manpages/docs by default if the tools are installed.
Conversely, manpages/docs are not tried to be built for users who dont want
to install all these dependencies to get started hacking on i3. Manpages and
docs can be disabled with the +--disable-mans++ and ++--disable-docs++
configure options respectively.
* 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.
* Coverage reports are now generated using +make check-code-coverage+, which
requires specifying +--enable-code-coverage+ when calling configure.
* +meson -Db_sanitize=address+ will enable the address sanitizer which is
disabled by default. A summary of memory leaks will be printed on program
exit. This can include false-positives. For other options of the +b_sanitize+
flag see https://mesonbuild.com/Builtin-options.html.
== Pull requests
@ -341,30 +329,26 @@ ensure that the operating system on which i3 is compiled has all the expected
features, i3 comes with +include/queue.h+. On BSD systems, you can use +man
queue(3)+. On Linux, you have to use google (or read the source).
The lists used are +SLIST+ (single linked lists), +CIRCLEQ+ (circular
queues) and +TAILQ+ (tail queues). Usually, only forward traversal is necessary,
so an +SLIST+ works fine. If inserting elements at arbitrary positions or at
the end of a list is necessary, a +TAILQ+ is used instead. However, for the
windows inside a container, a +CIRCLEQ+ is necessary to go from the currently
selected window to the window above/below.
The lists used are +SLIST+ (single linked lists), +CIRCLEQ+ (circular queues)
and +TAILQ+ (tail queues). Usually, +TAILQ+ is used which allows inserting
elements at arbitrary positions or at the end of the list. If only forward
traversal is necessary, an +SLIST+ can be used. +CIRCLEQ+ is used just to
manage the X11 state of each window.
== Naming conventions
[[startup]]
== Startup (src/main.c, main())
There is a row of standard variables used in many events. The following names
should be chosen for those:
* +conn+ is the xcb_connection_t
* +event+ is the event of the particular type
* +con+ names a container
* +current+ is a loop variable when using +TAILQ_FOREACH+ etc.
== Startup (src/mainx.c, main())
Among other things, the main() function does the following:
* Establish the xcb connection
* Load the i3 config
* Check for XKB extension on the separate X connection, load Xcursor
* Check for RandR screens (with a fall-back to Xinerama)
* Set up EWMH hints
* Grab the keycodes for which bindings exist
* Check for XRandR screens
* Manage all existing windows
* Exec configured startup processes
* Start i3bar if configured
* Enter the event loop
== Keybindings

View File

@ -0,0 +1,184 @@
i3bar workspace buttons protocol
================================
This document explains the protocol in which i3bar expects input for
configuring workspace buttons. This feature is available since i3 version 4.23.
The program defined by the +workspace_command+ configuration option for i3bar can
modify the workspace buttons displayed by i3bar. The command should constantly
print in its standard output a stream of messages following the protocol
defined in this page.
If you are looking for the status line protocol instead, see https://i3wm.org/docs/i3bar-protocol.html.
== The protocol
Each message should be a newline-delimited JSON array. The array is in the same
format as the +GET_WORKSPACES+ ipc event, see
https://i3wm.org/docs/ipc.html#_workspaces_reply.
As an example, this is the output of the +i3-msg -t get_workspaces+ command:
------------------------------
[
{
"id": 94131549984064,
"num": 1,
"name": "1",
"visible": false,
"focused": false,
"output": "HDMI-A-0",
"urgent": false
},
{
"id": 94131550477584,
"num": 2,
"name": "2",
"visible": true,
"focused": true,
"output": "HDMI-A-0",
"urgent": false
},
{
"id": 94131550452704,
"num": 3,
"name": "3:some workspace",
"visible": false,
"focused": false,
"output": "HDMI-A-0",
"urgent": false
}
]
------------------------------
Please note that this example was pretty printed for human consumption, with
the +"rect"+ field removed. Workspace button commands should output each array
in one line.
Each element in the array represents a workspace. i3bar creates one workspace
button for each element in the array. The order of these buttons is the same as
the order of the elements in the array.
In general, we recommend subscribing to the +workspace+ and +output+
https://i3wm.org/docs/ipc.html#_workspace_event[events],
fetching the current workspace information with +GET_WORKSPACES+, modifying the
JSON array in the response according to your needs and then printing it to the
standard output. However, you are free to build a new message from the ground
up.
=== Workspace objects in detail
The documentation of +GET_WORKSPACES+ should be sufficient to understand the
meaning of each property but here we provide extra notes for each property and
its meaning with respect to i3bar.
All properties but +name+ are optional.
id (integer)::
If it is included it will be used to switch to that workspace when you
click the corresponding button. If it's not provided, the +name+ will be
used. You can use the +id+ field to present workspaces under a modified
name.
num (integer)::
The only use of a workspace's number is if the +strip_workspace_numbers+
setting is enabled.
name (string)::
The only required property. If an +id+ is provided you can freely change
the +name+ as you wish, effectively renaming the buttons of i3bar.
visible (boolean)::
Defaults to +false+ if not included. +focused+ takes precedence over it,
however +visible+ is important for more than one monitor.
focused (boolean)::
Defaults to +false+ if not included. Generally, exactly one of the
workspaces should be +focused+. If not, no button will have the
+focused_workspace+ color.
urgent (boolean)::
Defaults to +false+ if not included.
rect (map)::
Not used by i3bar but will be ignored.
output (string)::
Defaults to the primary output if not included.
== Examples
These example scripts require the https://stedolan.github.io/jq/[jq] utility to
be installed but otherwise just use the standard +i3-msg+ utility included with
i3. However, you can write your own scripts in your preferred language, with
the help of one of the
https://i3wm.org/docs/ipc.html#_see_also_existing_libraries[pre-existing i3
libraries]
=== Base configuration
------------------------------
bar {
workspace_command /path/to/your/script.sh
}
------------------------------
=== Re-create the default behaviour of i3bar
Not very useful by itself but this will be the basic building block of all the
following scripts. This one does not require +jq+.
------------------------------
#!/bin/sh
i3-msg -t subscribe -m '["workspace", "output"]' | {
# Initially print the current workspaces before we receive any events. This
# avoids having an empty bar when starting up.
i3-msg -t get_workspaces;
# Then, while we receive events, update the workspace information.
while read EVENT; do i3-msg -t get_workspaces; done;
}
------------------------------
=== Hide workspace named +foo+ unless if it is focused.
------------------------------
#!/bin/sh
i3-msg -t subscribe -m '["workspace", "output"]' | {
i3-msg -t get_workspaces;
while read EVENT; do i3-msg -t get_workspaces; done;
} | jq --unbuffered -c '[ .[] | select(.name != "foo" or .focused) ]'
------------------------------
Important! Make sure you use the +--unbuffered+ flag with +jq+, otherwise you
might not get the changes in real-time but whenever they are flushed, which
might mean that you are getting an empty bar until enough events are written.
=== Show empty workspaces +foo+ and +bar+ on LVDS1 even if they do not exist at the moment.
------------------------------
#!/bin/sh
i3-msg -t subscribe -m '["workspace", "output"]' | {
i3-msg -t get_workspaces;
while read EVENT; do i3-msg -t get_workspaces; done;
} | jq --unbuffered -c '
def fake_ws(name): {
name: name,
output: "LVDS1",
};
. + [ fake_ws("foo"), fake_ws("bar") ] | unique_by(.name)
'
------------------------------
=== Sort workspaces in reverse alphanumeric order
------------------------------
#!/bin/sh
i3-msg -t subscribe -m '["workspace", "output"]' | {
i3-msg -t get_workspaces;
while read EVENT; do i3-msg -t get_workspaces; done;
} | jq --unbuffered -c 'sort_by(.name) | reverse'
------------------------------
=== Append "foo" to the name of each workspace
------------------------------
#!/bin/sh
i3-msg -t subscribe -m '["workspace", "output"]' | {
i3-msg -t get_workspaces;
while read EVENT; do i3-msg -t get_workspaces; done;
} | jq --unbuffered -c '[ .[] | .name |= . + " foo" ]'
------------------------------

View File

@ -10,15 +10,22 @@ workspace bar.
The method of choice for IPC in our case is a unix socket because it has very
little overhead on both sides and is usually available without headaches in
most languages. In the default configuration file, the ipc-socket gets created
in +/tmp/i3-%u.XXXXXX/ipc-socket.%p+ where +%u+ is your UNIX username, +%p+ is
the PID of i3 and XXXXXX is a string of random characters from the portable
filename character set (see mkdtemp(3)). You can get the socketpath from i3 by
executing +i3 --get-socketpath+, which will print the path to the standard
output (plus a newline).
most languages.
By default i3 will set the path of the IPC socket based on:
All i3 utilities, like +i3-msg+ and +i3-input+ will read the +I3_SOCKET_PATH+
X11 property, stored on the X11 root window.
1. The +ipc-socket+ configuration directive if it is used
2. The +I3SOCK+ environmental variable if it is set
3. +$XDG_RUNTIME_DIR/i3/ipc-socket.%p+ if the directory is available where +%p+
is the PID of i3 and XXXXXX is a string of random characters
4. +/tmp/i3-%u.XXXXXX/ipc-socket.%p+ where +%u+ is your UNIX username
You can get the socketpath from i3 by executing +i3 --get-socketpath+, which
will print the path to the standard output (plus a newline) or by reading the
+I3SOCK+ environmental variable.
All i3 utilities, like +i3-msg+ and +i3-input+ will determine the path of the
IPC socket from the +I3SOCK+ environmental variable if it is set or the
+I3_SOCKET_PATH+ X11 property, stored on the X11 root window.
[WARNING]
.Use an existing library!
@ -997,9 +1004,18 @@ if ($is_event) {
=== workspace event
This event consists of a single serialized map containing a property
+change (string)+ which indicates the type of the change ("focus", "init",
"empty", "urgent", "reload", "rename", "restored", "move"). A
+current (object)+ property will be present with the affected workspace
+change (string)+ which indicates the type of the change.
* +empty+ the workspace has become empty
* +focus+ the workspace has received input focus
* +init+ the workspace has been created
* +move+ the workspace has been moved to a different output
* +reload+ i3 config has been reloaded
* +rename+ the workspace's name has changed
* +restored+ the workspace's layout has changed to a previously saved layout
* +urgent+ the workspace has become urgent or lost its urgent status
A +current (object)+ property will be present with the affected workspace
whenever the type of event affects a workspace (otherwise, it will be +null+).
When the change is "focus", an +old (object)+ property will be present with the

View File

@ -36,8 +36,8 @@ that, but it will also be useful for every future change.
Apart from this document, you should also have a look at:
1. The "Modern Perl" book, which can be found at
http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
1. The "Modern Perl" book:
https://i3wm.org/downloads/modern_perl_a4.pdf
2. The latest Perl documentation of the "i3test" (general testcase setup) and
"i3test::Test" (additional test instructions) modules:
https://build.i3wm.org/docs/lib-i3test.html respectively
@ -76,30 +76,20 @@ used to install the testsuite. Many users prefer to use the more modern
The tests additionally require +Xephyr(1)+ to run a nested X server. Install
+xserver-xephyr+ on Debian or +xorg-server-xephyr+ on Arch Linux.
.Installing testsuite dependencies using cpanminus (preferred)
.Installing testsuite dependencies using cpanminus
--------------------------------------------------------------------------------
$ cd ~/i3/testcases
$ sudo apt-get install cpanminus
$ sudo cpanm .
# Install testsuite system-level dependencies. Xvfb is optional but recommended.
$ sudo apt-get install xcb-proto cpanminus xvfb xserver-xephyr
# Install dependencies in ~/perl5 local library
$ cpanm --local-lib=~/perl5 local::lib App::cpanminus Module::Install
# Activate the local library
$ eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib)
$ cd ~/i3/testcases/
$ cpanm .
$ cd ~/i3/AnyEvent-I3
$ sudo cpanm Module::Install
$ sudo cpanm .
$ cpanm .
--------------------------------------------------------------------------------
If you dont want to use cpanminus for some reason, the same works with cpan:
.Installing testsuite dependencies using cpan
--------------------------------------------------------------------------------
$ cd ~/i3/testcases
$ sudo cpan .
$ cd ~/i3/AnyEvent-I3
$ sudo cpan Module::Install
$ sudo cpan .
--------------------------------------------------------------------------------
In case you dont have root permissions, you can also install into your home
directory, see https://michael.stapelberg.de/cpan/
=== Mechanisms
==== Script: complete-run
@ -119,48 +109,57 @@ tests are run under Xvfb.
---------------------------------------
$ cd ~/i3
$ mkdir -p build && cd build
$ mkdir -p build
$ meson ..
$ meson setup build
$ cd build
$ ninja
$ meson compile
# output omitted because it is very long
$ cd testcases
$ ./complete-run.pl
# output omitted because it is very long
All tests successful.
Files=78, Tests=734, 27 wallclock secs ( 0.38 usr 0.48 sys + 17.65 cusr 3.21 csys = 21.72 CPU)
Result: PASS
$ ./complete-run.pl t/04-floating.t
[:3] i3 startup: took 0.07s, status = 1
[:3] Running t/04-floating.t with logfile testsuite-2011-09-24-16-06-04-4.0.2-226-g1eb011a/i3-log-for-04-floating.t
[:3] t/04-floating.t finished
[:3] killing i3
output for t/04-floating.t:
ok 1 - use X11::XCB::Window;
ok 2 - The object isa X11::XCB::Window
ok 3 - Window is mapped
ok 4 - i3 raised the width to 75
ok 5 - i3 raised the height to 50
ok 6 - i3 did not map it to (0x0)
ok 7 - The object isa X11::XCB::Window
ok 8 - i3 let the width at 80
ok 9 - i3 let the height at 90
ok 10 - i3 mapped it to x=1
ok 11 - i3 mapped it to y=18
ok 12 - The object isa X11::XCB::Window
ok 13 - i3 let the width at 80
ok 14 - i3 let the height at 90
1..14
$ ./complete-run.pl t/005-floating.t
Running tests under Xvfb display :99
Starting 1 Xephyr instances, starting at :100...
Rough time estimate for this run: 9.65 seconds
Writing logfile to 'testsuite-2024-05-01-21-33-45-4.23-28-g5834b7e8/complete-run.log'...
[:100] i3/testcases/t/005-floating.t: finished
completed 0 of 1 tests
All tests successful.
Files=1, Tests=14, 0 wallclock secs ( 0.01 usr 0.00 sys + 0.19 cusr 0.03 csys = 0.23 CPU)
Files=1, Tests=13, 0 wallclock secs ( 0.00 usr + 0.00 sys = 0.00 CPU)
Result: PASS
$ less latest/i3-log-for-04-floating.t
The slowest tests are:
i3/testcases/t/005-floating.t with 0.07 seconds
Test output:
[:100] i3/testcases/t/005-floating.t: starting
[:100] i3/testcases/t/005-floating.t: finished
output for i3/testcases/t/005-floating.t:
ok 1 - An object of class 'X11::XCB::Window' isa 'X11::XCB::Window'
ok 2 - Window is mapped
ok 3 - i3 raised the width to 75
ok 4 - i3 raised the height to 50
ok 5 - i3 did not map it to (0x0)
ok 6 - An object of class 'X11::XCB::Window' isa 'X11::XCB::Window'
ok 7 - i3 let the width at 80
ok 8 - i3 let the height at 90
ok 9 - i3 mapped it to x=20
ok 10 - i3 mapped it to y=20
ok 11 - An object of class 'X11::XCB::Window' isa 'X11::XCB::Window'
ok 12 - i3 let the width at 80
ok 13 - i3 let the height at 90
1..13
$ less latest/i3-log-for-005-floating.t
----------------------------------------
If your attempt to run the tests with a bare call to ./complete-run.pl fails, try this:
@ -172,37 +171,34 @@ $ ./complete-run.pl --parallel=1 --keep-xserver-output
This will show the output of Xephyr, which is the X server implementation we
use for testing.
===== make command: +make check+
Make check runs the i3 testsuite.
You can still use ./testcases/complete-run.pl to get the interactive progress output.
===== ninja command: +ninja test+
+ninja test+ runs the i3 testsuite.
You can still use ./complete-run.pl to get the interactive progress output.
.Example invocation of +make check+
.Example invocation of +ninja test+
---------------------------------------
$ cd ~/i3
$ mkdir -p build && cd build
$ mkdir -p build
$ meson ..
$ meson setup build
$ cd build
$ ninja
# output omitted because it is very long
$ ninja test
[1/102] Generating config.h with a custom command
[1/2] Running all tests.
1/1 complete-run OK 34.39s
$ 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
============================================================================
Ok: 1
Expected Fail: 0
Fail: 0
Unexpected Pass: 0
Skipped: 0
Timeout: 0
$ less test-suite.log
Full log written to i3/build/meson-logs/testlog.txt
$ less latest/complete-run.log
----------------------------------------
==== Coverage testing

View File

@ -202,7 +202,8 @@ Floating windows are always on top of tiling windows.
Since i3 4.21, it's possible to drag tiling containers using the mouse. The
drag can be initiated either by dragging the window's titlebar or by pressing
the <<floating_modifier>> and dragging the container while holding the
left-click button.
left-click button. See the <<config_tiling_drag>> option for configuring which
action triggers the tiling drag.
Once the drag is initiated and the cursor has left the original container, drop
indicators are created according to the position of the cursor relatively to
@ -215,6 +216,10 @@ Drop on container::
This happens when the mouse is relatively near the center of a container.
If the mouse is released, the result is exactly as if you had run the
+move container to mark+ command. See <<move_to_mark>>.
If the swap modifier is pressed before initiating the drag (+tiling_drag
swap_modifier+ set to Shift by default), the containers are swapped
instead. In that case, the result is exactly as if you had run the +swap
container with mark+ command. See <<swapping_containers>>.
Drop as sibling::
This happens when the mouse is relatively near the edge of a container. If
the mouse is released, the dragged container will become a sibling of the
@ -317,7 +322,7 @@ single workspace on which you open three terminal windows. All these terminal
windows are directly attached to one node inside i3s layout tree, the
workspace node. By default, the workspace nodes orientation is +horizontal+.
Now you move one of these terminals down (+$mod+Shift+j+ by default). The
Now you move one of these terminals down (+$mod+Shift+k+ by default). The
workspace nodes orientation will be changed to +vertical+. The terminal window
you moved down is directly attached to the workspace and appears on the bottom
of the screen. A new (horizontal) container was created to accommodate the
@ -353,15 +358,6 @@ keyboard layout. To start the wizard, use the command +i3-config-wizard+.
Please note that you must not have +~/.i3/config+, otherwise the wizard will
exit.
Since i3 4.0, a new configuration format is used. i3 will try to automatically
detect the format version of a config file based on a few different keywords,
but if you want to make sure that your config is read with the new format,
include the following line in your config file:
---------------------
# i3 config file (v4)
---------------------
[[include]]
=== Include directive
@ -511,8 +507,8 @@ your bindings in the same physical location on the keyboard, use keycodes.
If you dont switch layouts, and want a clean and simple config file, use
keysyms.
Some tools (such as +import+ or +xdotool+) might be unable to run upon a
KeyPress event, because the keyboard/pointer is still grabbed. For these
Some tools (such as +xdotool+) might be unable to run upon a
KeyPress event, because the keyboard is still grabbed. For these
situations, the +--release+ flag can be used, which will execute the command
after the keys have been released.
@ -742,8 +738,11 @@ This option determines which border style *new* windows will have. The default
+normal+. Note that default_floating_border applies only to windows which are starting out as
floating windows, e.g., dialog windows, but not windows that are floated later on.
Setting border style to +pixel+ eliminates title bars. The border style +normal+ allows you to
adjust edge border width while keeping your title bar.
Setting border style to +pixel+ eliminates title bars in split layouts. The border style
+normal+ allows you to adjust edge border width while keeping your title bar.
The title bar is always visible in stacking and tabbed layouts, and this cannot be changed
through configuration.
*Syntax*:
---------------------------------------------
@ -788,6 +787,10 @@ The "smart_no_gaps" setting hides edge-specific borders of a container if the
container is the only container on its workspace and the gaps to the screen edge
are +0+.
[[_smart_borders]]
+hide_edge_borders+ has replaced the old +smart_borders+ syntax. Use the former
instead of the latter.
*Syntax*:
-----------------------------------------------
hide_edge_borders none|vertical|horizontal|both|smart|smart_no_gaps
@ -798,27 +801,6 @@ hide_edge_borders none|vertical|horizontal|both|smart|smart_no_gaps
hide_edge_borders vertical
----------------------
[[_smart_borders]]
=== Smart borders
Smart borders will draw borders on windows only if there is more than one window
in a workspace. This feature can also be enabled only if the gap size between
window and screen edge is +0+.
*Syntax*:
-----------------------------------------------
smart_borders on|off|no_gaps
-----------------------------------------------
*Example*:
----------------------
# Activate smart borders (always)
smart_borders on
# Activate smart borders (only when there are effectively no gaps)
smart_borders no_gaps
----------------------
[[for_window]]
=== Arbitrary commands for specific windows (for_window)
@ -1242,19 +1224,21 @@ mouse_warping none
When you are in fullscreen mode, some applications still open popup windows
(take Xpdf for example). This is because these applications might not be aware
that they are in fullscreen mode (they do not check the corresponding hint).
There are three things which are possible to do in this situation:
i3 supports four options for this situation:
1. Display the popup if it belongs to the fullscreen application only. This is
the default and should be reasonable behavior for most users.
2. Just ignore the popup (dont map it). This wont interrupt you while you are
in fullscreen. However, some apps might react badly to this (deadlock until
you go out of fullscreen).
3. Leave fullscreen mode.
1. +smart+: Display the popup if it belongs to the fullscreen application only.
This is the default and should be reasonable behavior for most users.
2. +ignore+: Just ignore the popup (dont map it). This wont interrupt you
while you are in fullscreen. However, some apps might react badly to this
(deadlock until you go out of fullscreen).
3. +leave_fullscreen+: Leave fullscreen mode.
4. +all+: Since i3 4.24: Display all floating windows regardless to which
application they belong to.
*Syntax*:
-----------------------------------------------------
popup_during_fullscreen smart|ignore|leave_fullscreen
-----------------------------------------------------
---------------------------------------------------------
popup_during_fullscreen smart|ignore|leave_fullscreen|all
---------------------------------------------------------
*Example*:
------------------------------
@ -1438,10 +1422,18 @@ bindsym Mod1+F fullscreen toggle
You can configure how to initiate the tiling drag feature (see <<tiling_drag>>).
The default is +modifier+.
Since i3 4.24, you can configure a modifier key which, when pressed, will swap
instead of moving containers when dropping directly onto another container.
Defaults to +Shift+. Note that you have to be pressing both the floating
modifier and the swap modifier before the drag is initiated.
*Syntax*:
--------------------------------
tiling_drag off
tiling_drag modifier|titlebar [modifier|titlebar]
tiling_drag swap_modifier <modifier>
--------------------------------
*Examples*:
@ -1454,6 +1446,14 @@ tiling_drag modifier titlebar
# Disable tiling drag altogether
tiling_drag off
# Use Control to swap containers
tiling_drag swap_modifier Control
# Setting the swap_modifier to be the same key as the floating modifier will
# always swap without the need to hold two keys
floating_modifier Mod4
tiling_drag swap_modifier Mod4
--------------------------------
[[gaps]]
@ -1611,6 +1611,30 @@ bar {
}
-------------------------------------------------
[[workspace_command]]
=== Workspace buttons command
Since i3 4.23, i3bar can run a program and use its +stdout+ output to define
the workspace buttons displayed on the left hand side of the bar. With this
feature, you can, for example, rename the buttons of workspaces, hide specific
workspaces, always show a workspace button even if the workspace does not exist
or change the order of the buttons.
Also see <<status_command>> for the statusline option and
https://i3wm.org/docs/i3bar-workspace-protocol.html for the detailed protocol.
*Syntax*:
------------------------
workspace_command <command>
------------------------
*Example*:
-------------------------------------------------
bar {
workspace_command /path/to/script.sh
}
-------------------------------------------------
=== Display mode
You can either have i3bar be visible permanently at one edge of the screen
@ -2174,6 +2198,10 @@ bindsym $mod+x [class="(?i)firefox"] kill
# kill only the About dialog from Firefox
bindsym $mod+x [class="Firefox" window_role="About"] kill
# kill all windows except for Firefox and Gnome Terminal.
# case-insensitive and uses negative lookaheads, supported by PCRE
bindsym $mod+x [class="^(?i)(?!firefox)(?!gnome-terminal).*"] kill
# enable floating mode and move container to workspace 4
for_window [class="^evil-app$"] floating enable, move container to workspace 4
@ -2397,6 +2425,9 @@ available:
<criteria>::
Sets focus to the container that matches the specified criteria.
See <<command_criteria>>.
workspace::
Sets focus to the workspace that contains the container that matches the
specified criteria.
left|right|up|down::
Sets focus to the nearest container in the given direction.
parent::
@ -2423,6 +2454,7 @@ output::
*Syntax*:
----------------------------------------------
<criteria> focus
<criteria> focus workspace
focus left|right|down|up
focus parent|child|floating|tiling|mode_toggle
focus next|prev [sibling]
@ -2434,6 +2466,10 @@ focus output left|right|down|up|current|primary|nonprimary|next|<output1> [outpu
# Focus firefox
bindsym $mod+F1 [class="Firefox"] focus
# Focus the workspace where firefox is, without necessarily focusing firefox
# itself.
bindsym $mod+x [class="Firefox"] focus workspace
# Focus container on the left, bottom, top, right
bindsym $mod+j focus left
bindsym $mod+k focus down
@ -2447,7 +2483,7 @@ bindsym $mod+u focus parent
bindsym $mod+g focus mode_toggle
# Focus the next output (effectively toggles when you only have two outputs)
bindsym $mod+x move workspace to output next
bindsym $mod+x focus output next
# Focus the output right to the current one
bindsym $mod+x focus output right
@ -2462,7 +2498,7 @@ bindsym $mod+x focus output primary
bindsym $mod+x focus output nonprimary
# Cycle focus between outputs VGA1 and LVDS1 but not DVI0
bindsym $mod+x move workspace to output VGA1 LVDS1
bindsym $mod+x focus output VGA1 LVDS1
-------------------------------------------------
Note that you might not have a primary output configured yet. To do so, run:
@ -2519,6 +2555,7 @@ bindsym $mod+c move absolute position center
bindsym $mod+m move position mouse
-------------------------------------------------------
[[swapping_containers]]
=== Swapping containers
Two containers can be swapped (i.e., move to each other's position) by using

View File

@ -1,7 +1,7 @@
#!/usr/bin/env perl
# vim:ts=4:sw=4:expandtab
#
# i3 - an improved dynamic tiling window manager
# i3 - an improved tiling window manager
# © 2009 Michael Stapelberg and contributors (see also: LICENSE)
#
# generate-command-parser.pl: script to generate parts of the command parser
@ -147,11 +147,12 @@ for my $state (@keys) {
$next_state ||= 'INITIAL';
my $fmt = $cmd;
# Replace the references to identified literals (like $workspace) with
# calls to get_string(). Also replaces state names (like FOR_WINDOW)
# with their ID (useful for cfg_criteria_init(FOR_WINDOW) e.g.).
# calls to parser_get_string(). Also replaces state names (like
# FOR_WINDOW) with their ID (useful for cfg_criteria_init(FOR_WINDOW)
# e.g.).
$cmd =~ s/$_/$statenum{$_}/g for @keys;
$cmd =~ s/\$([a-z_]+)/get_string(stack, "$1")/g;
$cmd =~ s/\&([a-z_]+)/get_long(stack, "$1")/g;
$cmd =~ s/\$([a-z_]+)/parser_get_string(stack, "$1")/g;
$cmd =~ s/\&([a-z_]+)/parser_get_long(stack, "$1")/g;
# For debugging/testing, we print the call using printf() and thus need
# 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

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* i3-config-wizard: Program to convert configs using keycodes to configs using
@ -75,9 +75,9 @@ xcb_visualtype_t *visual_type = NULL;
#define WIN_HEIGHT (15 * font.height + TEXT_PADDING)
#define col_x(col) \
(((col)-1) * char_width + TEXT_PADDING)
(((col) - 1) * char_width + TEXT_PADDING)
#define row_y(row) \
(((row)-1) * font.height + TEXT_PADDING)
(((row) - 1) * font.height + TEXT_PADDING)
enum { STEP_WELCOME,
STEP_GENERATE } current_step = STEP_WELCOME;
@ -157,8 +157,9 @@ static struct stack_entry stack[10];
static void push_string(const char *identifier, const char *str) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier != NULL &&
strcmp(stack[c].identifier, identifier) != 0)
strcmp(stack[c].identifier, identifier) != 0) {
continue;
}
if (stack[c].identifier == NULL) {
/* Found a free slot, lets store it here. */
stack[c].identifier = identifier;
@ -184,8 +185,9 @@ static void push_string(const char *identifier, const char *str) {
static void push_long(const char *identifier, long num) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier != NULL)
if (stack[c].identifier != NULL) {
continue;
}
/* Found a free slot, lets store it here. */
stack[c].identifier = identifier;
stack[c].val.num = num;
@ -204,18 +206,21 @@ static void push_long(const char *identifier, long num) {
static const char *get_string(const char *identifier) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier == NULL)
if (stack[c].identifier == NULL) {
break;
if (strcmp(identifier, stack[c].identifier) == 0)
}
if (strcmp(identifier, stack[c].identifier) == 0) {
return stack[c].val.str;
}
}
return NULL;
}
static void clear_stack(void) {
for (int c = 0; c < 10; c++) {
if (stack[c].type == STACK_STR)
if (stack[c].type == STACK_STR) {
free(stack[c].val.str);
}
stack[c].identifier = NULL;
stack[c].val.str = NULL;
stack[c].val.num = 0;
@ -233,11 +238,13 @@ static bool keysym_used_on_other_key(KeySym sym, xcb_keycode_t except_keycode) {
max_keycode = xcb_get_setup(conn)->max_keycode;
for (i = min_keycode; i && i <= max_keycode; i++) {
if (i == except_keycode)
if (i == except_keycode) {
continue;
}
for (int level = 0; level < 4; level++) {
if (xcb_key_symbols_get_keysym(keysyms, i, level) != sym)
if (xcb_key_symbols_get_keysym(keysyms, i, level) != sym) {
continue;
}
return true;
}
}
@ -269,22 +276,27 @@ static char *next_state(const cmdp_token *token) {
* qwerty (yes, that happens quite often). */
const xkb_keysym_t *syms;
int num = xkb_keymap_key_get_syms_by_level(xkb_keymap, keycode, 0, 0, &syms);
if (num == 0)
if (num == 0) {
errx(1, "xkb_keymap_key_get_syms_by_level returned no symbols for keycode %d", keycode);
if (!keysym_used_on_other_key(syms[0], keycode))
}
if (!keysym_used_on_other_key(syms[0], keycode)) {
level = 0;
}
}
const xkb_keysym_t *syms;
int num = xkb_keymap_key_get_syms_by_level(xkb_keymap, keycode, 0, level, &syms);
if (num == 0)
if (num == 0) {
errx(1, "xkb_keymap_key_get_syms_by_level returned no symbols for keycode %d", keycode);
if (num > 1)
}
if (num > 1) {
printf("xkb_keymap_key_get_syms_by_level (keycode = %d) returned %d symbolsinstead of 1, using only the first one.\n", keycode, num);
}
char str[4096];
if (xkb_keysym_get_name(syms[0], str, sizeof(str)) == -1)
if (xkb_keysym_get_name(syms[0], str, sizeof(str)) == -1) {
errx(EXIT_FAILURE, "xkb_keysym_get_name(%u) failed", syms[0]);
}
const char *release = get_string("release");
char *res;
char *modrep = (modifiers == NULL ? sstrdup("") : sstrdup(modifiers));
@ -303,8 +315,9 @@ static char *next_state(const cmdp_token *token) {
/* See if we are jumping back to a state in which we were in previously
* (statelist contains INITIAL) and just move statelist_idx accordingly. */
for (int i = 0; i < statelist_idx; i++) {
if (statelist[i] != _next_state)
if (statelist[i] != _next_state) {
continue;
}
statelist_idx = i + 1;
return NULL;
}
@ -329,8 +342,9 @@ static char *rewrite_binding(const char *input) {
while ((size_t)(walk - input) <= len) {
/* Skip whitespace before every token, newlines are relevant since they
* separate configuration directives. */
while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
while ((*walk == ' ' || *walk == '\t') && *walk != '\0') {
walk++;
}
cmdp_token_ptr *ptr = &(tokens[state]);
for (c = 0; c < ptr->n; c++) {
@ -339,11 +353,13 @@ static char *rewrite_binding(const char *input) {
/* A literal. */
if (token->name[0] == '\'') {
if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
if (token->identifier != NULL)
if (token->identifier != NULL) {
push_string(token->identifier, token->name + 1);
}
walk += strlen(token->name) - 1;
if ((result = next_state(token)) != NULL)
if ((result = next_state(token)) != NULL) {
return result;
}
break;
}
continue;
@ -355,20 +371,24 @@ static char *rewrite_binding(const char *input) {
errno = 0;
long int num = strtol(walk, &end, 10);
if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) ||
(errno != 0 && num == 0))
(errno != 0 && num == 0)) {
continue;
}
/* No valid numbers found */
if (end == walk)
if (end == walk) {
continue;
}
if (token->identifier != NULL)
if (token->identifier != NULL) {
push_long(token->identifier, num);
}
/* Set walk to the first non-number character */
walk = end;
if ((result = next_state(token)) != NULL)
if ((result = next_state(token)) != NULL) {
return result;
}
break;
}
@ -379,12 +399,14 @@ static char *rewrite_binding(const char *input) {
if (*walk == '"') {
beginning++;
walk++;
while (*walk != '\0' && (*walk != '"' || *(walk - 1) == '\\'))
while (*walk != '\0' && (*walk != '"' || *(walk - 1) == '\\')) {
walk++;
}
} else {
if (token->name[0] == 's') {
while (*walk != '\0' && *walk != '\r' && *walk != '\n')
while (*walk != '\0' && *walk != '\r' && *walk != '\n') {
walk++;
}
} else {
/* For a word, the delimiters are white space (' ' or
* '\t'), closing square bracket (]), comma (,) and
@ -392,8 +414,9 @@ static char *rewrite_binding(const char *input) {
while (*walk != ' ' && *walk != '\t' &&
*walk != ']' && *walk != ',' &&
*walk != ';' && *walk != '\r' &&
*walk != '\n' && *walk != '\0')
*walk != '\n' && *walk != '\0') {
walk++;
}
}
}
if (walk != beginning) {
@ -406,27 +429,32 @@ static char *rewrite_binding(const char *input) {
/* We only handle escaped double quotes to not break
* backwards compatibility with people using \w in
* regular expressions etc. */
if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"')
if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"') {
inpos++;
}
str[outpos] = beginning[inpos];
}
if (token->identifier)
if (token->identifier) {
push_string(token->identifier, str);
}
free(str);
/* If we are at the end of a quoted string, skip the ending
* double quote. */
if (*walk == '"')
if (*walk == '"') {
walk++;
if ((result = next_state(token)) != NULL)
}
if ((result = next_state(token)) != NULL) {
return result;
}
break;
}
}
if (strcmp(token->name, "end") == 0) {
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
if ((result = next_state(token)) != NULL)
if ((result = next_state(token)) != NULL) {
return result;
}
/* To make sure we start with an appropriate matching data
* structure for commands which do *not* specify any
* criteria, we re-initialize the criteria system after
@ -513,17 +541,19 @@ static int handle_expose(void) {
txt(13, 10, "to abort", white, black);
/* the not-selected modifier */
if (modifier == MOD_Mod4)
if (modifier == MOD_Mod4) {
txt(5, 5, "<Alt>", white, black);
else
} else {
txt(5, 4, "<Win>", white, black);
}
/* the selected modifier */
set_font(&bold_font);
if (modifier == MOD_Mod4)
if (modifier == MOD_Mod4) {
txt(2, 4, "-> <Win>", white, black);
else
} else {
txt(2, 5, "-> <Alt>", white, black);
}
set_font(&font);
txt(4, 9, "<Enter>", green, black);
@ -562,8 +592,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
strlen("i3: generate config"),
"i3: generate config");
xcb_flush(conn);
} else
} else {
finish();
}
}
/* Swap between modifiers when up or down is pressed. */
@ -573,8 +604,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
}
/* cancel any time */
if (sym == XK_Escape)
if (sym == XK_Escape) {
exit(0);
}
/* Check if this is Mod1 or Mod4. The modmap contains Shift, Lock, Control,
* Mod1, Mod2, Mod3, Mod4, Mod5 (in that order) */
@ -583,8 +615,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
int mask = 3;
for (int i = 0; i < modmap_reply->keycodes_per_modifier; i++) {
xcb_keycode_t code = modmap[(mask * modmap_reply->keycodes_per_modifier) + i];
if (code == XCB_NONE)
if (code == XCB_NONE) {
continue;
}
printf("Modifier keycode for Mod1: 0x%02x\n", code);
if (code == event->detail) {
modifier = MOD_Mod1;
@ -596,8 +629,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
mask = 6;
for (int i = 0; i < modmap_reply->keycodes_per_modifier; i++) {
xcb_keycode_t code = modmap[(mask * modmap_reply->keycodes_per_modifier) + i];
if (code == XCB_NONE)
if (code == XCB_NONE) {
continue;
}
printf("Modifier keycode for Mod4: 0x%02x\n", code);
if (code == event->detail) {
modifier = MOD_Mod4;
@ -614,11 +648,13 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
*
*/
static void handle_button_press(xcb_button_press_event_t *event) {
if (current_step != STEP_GENERATE)
if (current_step != STEP_GENERATE) {
return;
}
if (event->event_x < col_x(5) || event->event_x > col_x(10))
if (event->event_x < col_x(5) || event->event_x > col_x(10)) {
return;
}
if (event->event_y >= row_y(4) && event->event_y <= (row_y(4) + font.height)) {
modifier = MOD_Mod4;
@ -640,20 +676,24 @@ static void finish(void) {
struct xkb_context *xkb_context;
if ((xkb_context = xkb_context_new(0)) == NULL)
if ((xkb_context = xkb_context_new(0)) == NULL) {
errx(1, "could not create xkbcommon context");
}
int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn);
if ((xkb_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL)
if ((xkb_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) {
errx(1, "xkb_x11_keymap_new_from_device failed");
}
FILE *kc_config = fopen(SYSCONFDIR "/i3/config.keycodes", "r");
if (kc_config == NULL)
if (kc_config == NULL) {
err(1, "Could not open input file \"%s\"", SYSCONFDIR "/i3/config.keycodes");
}
FILE *ks_config = fopen(config_path, "w");
if (ks_config == NULL)
if (ks_config == NULL) {
err(1, "Could not open output config file \"%s\"", config_path);
}
free(config_path);
char *line = NULL;
@ -684,8 +724,9 @@ static void finish(void) {
#endif
/* skip the warning block at the beginning of the input file */
if (head_of_file &&
strncmp("# WARNING", line, strlen("# WARNING")) == 0)
strncmp("# WARNING", line, strlen("# WARNING")) == 0) {
continue;
}
head_of_file = false;
@ -699,10 +740,11 @@ static void finish(void) {
/* Set the modifier the user chose */
if (strncmp(walk, "set $mod ", strlen("set $mod ")) == 0) {
if (modifier == MOD_Mod1)
if (modifier == MOD_Mod1) {
fputs("set $mod Mod1\n", ks_config);
else
} else {
fputs("set $mod Mod4\n", ks_config);
}
continue;
}
@ -767,12 +809,13 @@ int main(int argc, char *argv[]) {
return 0;
case 'm':
headless_run = true;
if (strcmp(optarg, "alt") == 0)
if (strcmp(optarg, "alt") == 0) {
modifier = MOD_Mod1;
else if (strcmp(optarg, "win") == 0)
} else if (strcmp(optarg, "win") == 0) {
modifier = MOD_Mod4;
else
} else {
err(EXIT_FAILURE, "Invalid modifier key %s", optarg);
}
break;
case 'h':
printf("i3-config-wizard " I3_VERSION "\n");
@ -789,8 +832,9 @@ int main(int argc, char *argv[]) {
}
/* Always write to $XDG_CONFIG_HOME/i3/config by default. */
if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) {
xdg_config_home = "~/.config";
}
xdg_config_home = resolve_tilde(xdg_config_home);
sasprintf(&config_path, "%s/i3/config", xdg_config_home);
@ -799,9 +843,11 @@ int main(int argc, char *argv[]) {
char *config_dir;
struct stat stbuf;
sasprintf(&config_dir, "%s/i3", xdg_config_home);
if (stat(config_dir, &stbuf) != 0)
if (mkdirp(config_dir, DEFAULT_DIR_MODE) != 0)
if (stat(config_dir, &stbuf) != 0) {
if (mkdirp(config_dir, DEFAULT_DIR_MODE) != 0) {
err(EXIT_FAILURE, "mkdirp(%s) failed", config_dir);
}
}
free(config_dir);
free(xdg_config_home);
@ -815,8 +861,9 @@ int main(int argc, char *argv[]) {
int screen;
if ((conn = xcb_connect(NULL, &screen)) == NULL ||
xcb_connection_has_error(conn))
xcb_connection_has_error(conn)) {
errx(1, "Cannot open display");
}
if (xkb_x11_setup_xkb_extension(conn,
XKB_X11_MIN_MAJOR_XKB_VERSION,
@ -825,8 +872,9 @@ int main(int argc, char *argv[]) {
NULL,
NULL,
&xkb_base_event,
&xkb_base_error) != 1)
&xkb_base_error) != 1) {
errx(EXIT_FAILURE, "Could not setup XKB extension.");
}
keysyms = xcb_key_symbols_alloc(conn);
xcb_get_modifier_mapping_cookie_t modmap_cookie;
@ -852,8 +900,9 @@ int main(int argc, char *argv[]) {
root_screen = xcb_aux_get_screen(conn, screen);
root = root_screen->root;
if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL)))
if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL))) {
errx(EXIT_FAILURE, "Could not get modifier mapping");
}
xcb_numlock_mask = get_mod_mask_for(XCB_NUM_LOCK, symbols, modmap_reply);

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* i3-dump-log/main.c: Dumps the i3 SHM log to stdout.
@ -39,8 +39,9 @@ static int ipcfd = -1;
static void disable_shmlog(void) {
const char *disablecmd = "debuglog off; shmlog off";
if (ipc_send_message(ipcfd, strlen(disablecmd),
I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)disablecmd) != 0)
I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)disablecmd) != 0) {
err(EXIT_FAILURE, "IPC send");
}
/* Ensure the command was sent by waiting for the reply: */
uint32_t reply_length = 0;
@ -53,8 +54,9 @@ static void disable_shmlog(void) {
}
static int check_for_wrap(void) {
if (wrap_count == header->wrap_count)
if (wrap_count == header->wrap_count) {
return 0;
}
/* The log wrapped. Print the remaining content and reset walk to the top
* of the log. */
@ -94,8 +96,7 @@ int main(int argc, char *argv[]) {
{"follow", no_argument, 0, 'f'},
#endif
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
{0, 0, 0, 0}};
#if !defined(__OpenBSD__)
char *options_string = "s:vfVh";
@ -148,8 +149,9 @@ int main(int argc, char *argv[]) {
ipcfd = ipc_connect(NULL);
const char *enablecmd = "debuglog on; shmlog 5242880";
if (ipc_send_message(ipcfd, strlen(enablecmd),
I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)enablecmd) != 0)
I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)enablecmd) != 0) {
err(EXIT_FAILURE, "IPC send");
}
/* By the time we receive a reply, I3_SHMLOG_PATH is set: */
uint32_t reply_length = 0;
uint8_t *reply = NULL;
@ -175,8 +177,9 @@ int main(int argc, char *argv[]) {
}
}
if (*shmname == '\0')
if (*shmname == '\0') {
errx(EXIT_FAILURE, "Cannot dump log: SHM logging is disabled in i3.");
}
struct stat statbuf;
@ -211,8 +214,9 @@ int main(int argc, char *argv[]) {
/* In case there was a write to the buffer already, skip the first
* old line, it very likely is mangled. Not a problem, though, the log
* is chatty enough to have plenty lines left. */
while (*walk != '\n')
while (*walk != '\n') {
walk++;
}
walk++;
}

View File

@ -822,21 +822,23 @@ long keysym2ucs(xcb_keysym_t keysym) {
/* first check for Latin-1 characters (1:1 mapping) */
if ((keysym >= 0x0020 && keysym <= 0x007e) ||
(keysym >= 0x00a0 && keysym <= 0x00ff))
(keysym >= 0x00a0 && keysym <= 0x00ff)) {
return keysym;
}
/* also check for directly encoded 24-bit UCS characters */
if ((keysym & 0xff000000) == 0x01000000)
if ((keysym & 0xff000000) == 0x01000000) {
return keysym & 0x00ffffff;
}
/* binary search in table */
while (max >= min) {
mid = (min + max) / 2;
if (keysymtab[mid].keysym < keysym)
if (keysymtab[mid].keysym < keysym) {
min = mid + 1;
else if (keysymtab[mid].keysym > keysym)
} else if (keysymtab[mid].keysym > keysym) {
max = mid - 1;
else {
} else {
/* found it */
return keysymtab[mid].ucs;
}

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* i3-input/main.c: Utility which lets the user input commands and sends them
@ -102,7 +102,7 @@ static uint8_t *concat_strings(char **glyphs, int max) {
walk += strlen(glyphs[c]);
}
}
printf("output = %s\n", output);
printf("output = %s\n", (char *)output);
return output;
}
@ -111,7 +111,7 @@ static uint8_t *concat_strings(char **glyphs, int max) {
* be called from the code with event == NULL or from X with event != NULL.
*
*/
static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) {
static int handle_expose(xcb_connection_t *conn) {
printf("expose!\n");
color_t border_color = draw_util_hex_to_color("#FF0000");
@ -163,29 +163,32 @@ static void finish_input(void) {
char *command = (char *)concat_strings(glyphs_utf8, input_position);
/* count the occurrences of %s in the string */
int c;
int len = strlen(format);
int cnt = 0;
for (c = 0; c < (len - 1); c++)
if (format[c] == '%' && format[c + 1] == 's')
const size_t len = strlen(format);
size_t cnt = 0;
for (size_t c = 0; c < (len - 1); c++) {
if (format[c] == '%' && format[c + 1] == 's') {
cnt++;
printf("occurrences = %d\n", cnt);
}
}
printf("occurrences = %zu\n", cnt);
/* allocate space for the output */
int inputlen = strlen(command);
char *full = scalloc(strlen(format) - (2 * cnt) /* format without all %s */
+ (inputlen * cnt) /* replaced %s */
+ 1, /* trailing NUL */
1);
const size_t input_len = strlen(command);
const size_t full_len = MAX(input_len, /* avoid compiler warning */
strlen(format) - (2 * cnt) /* format without all %s */
+ (input_len * cnt) /* replaced %s */
+ 1 /* trailing NUL */
);
char *full = scalloc(full_len, 1);
char *dest = full;
for (c = 0; c < len; c++) {
/* if this is not % or it is % but without a following 's',
* just copy the character */
if (format[c] != '%' || (c == (len - 1)) || format[c + 1] != 's')
for (size_t c = 0; c < len; c++) {
/* if this is not % or it is % but without a following 's', just copy
* the character */
if (format[c] != '%' || (c == (len - 1)) || format[c + 1] != 's') {
*(dest++) = format[c];
else {
strncat(dest, command, inputlen);
dest += inputlen;
} else {
strncat(dest, command, input_len);
dest += input_len;
/* skip the following 's' of '%s' */
c++;
}
@ -223,8 +226,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
/* If modeswitch is currently active, we need to look in group 2 or 3,
* respectively. */
if (modeswitch_active)
if (modeswitch_active) {
col += 2;
}
xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, col);
if (sym == XK_Mode_switch) {
@ -233,17 +237,19 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
return 1;
}
if (sym == XK_Return)
if (sym == XK_Return) {
finish_input();
}
if (sym == XK_BackSpace) {
if (input_position == 0)
if (input_position == 0) {
return 1;
}
input_position--;
free(glyphs_utf8[input_position]);
handle_expose(NULL, conn, NULL);
handle_expose(conn);
return 1;
}
if (sym == XK_Escape) {
@ -259,8 +265,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym));
printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym));
if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym))
if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym)) {
return 1;
}
printf("sym = %c (%d)\n", sym, sym);
@ -284,10 +291,11 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
glyphs_utf8[input_position] = out;
input_position++;
if (input_position == limit)
if (input_position == limit) {
finish_input();
}
handle_expose(NULL, conn, NULL);
handle_expose(conn);
return 1;
}
@ -399,7 +407,7 @@ int main(int argc, char *argv[]) {
socket_path = sstrdup(optarg);
break;
case 'v':
printf("i3-input " I3_VERSION);
printf("i3-input " I3_VERSION "\n");
return EXIT_OK;
case 'p':
/* This option is deprecated, but will still work in i3 v4.1, 4.2 and 4.3 */
@ -439,8 +447,9 @@ int main(int argc, char *argv[]) {
int screen;
conn = xcb_connect(NULL, &screen);
if (!conn || xcb_connection_has_error(conn))
if (!conn || xcb_connection_has_error(conn)) {
die("Cannot open display");
}
sockfd = ipc_connect(socket_path);
@ -453,8 +462,9 @@ int main(int argc, char *argv[]) {
font = load_font(pattern ? pattern : "pango:monospace 8", true);
set_font(&font);
if (prompt != NULL)
if (prompt != NULL) {
prompt_offset = predict_text_width(prompt);
}
const xcb_rectangle_t win_pos = get_window_position();
@ -508,11 +518,12 @@ int main(int argc, char *argv[]) {
while ((event = xcb_wait_for_event(conn)) != NULL) {
if (event->response_type == 0) {
fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
free(event);
continue;
}
/* Strip off the highest bit (set if the event is generated) */
int type = (event->response_type & 0x7F);
const int type = (event->response_type & 0x7F);
switch (type) {
case XCB_KEY_PRESS:
@ -525,9 +536,10 @@ int main(int argc, char *argv[]) {
case XCB_EXPOSE:
if (((xcb_expose_event_t *)event)->count == 0) {
handle_expose(NULL, conn, (xcb_expose_event_t *)event);
handle_expose(conn);
}
break;
default:
break;
}

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* i3-msg/main.c: Utility which sends messages to a running i3-instance using
@ -61,22 +61,24 @@ static int exit_code = 0;
static reply_t last_reply;
static int reply_boolean_cb(void *params, int val) {
if (strcmp(last_key, "success") == 0)
if (strcmp(last_key, "success") == 0) {
last_reply.success = val;
}
return 1;
}
static int reply_string_cb(void *params, const unsigned char *val, size_t len) {
char *str = sstrndup((const char *)val, len);
if (strcmp(last_key, "error") == 0)
if (strcmp(last_key, "error") == 0) {
last_reply.error = str;
else if (strcmp(last_key, "input") == 0)
} else if (strcmp(last_key, "input") == 0) {
last_reply.input = str;
else if (strcmp(last_key, "errorposition") == 0)
} else if (strcmp(last_key, "errorposition") == 0) {
last_reply.errorposition = str;
else
} else {
free(str);
}
return 1;
}
@ -146,10 +148,6 @@ static yajl_callbacks config_callbacks = {
};
int main(int argc, char *argv[]) {
#if defined(__OpenBSD__)
if (pledge("stdio rpath unix", NULL) == -1)
err(EXIT_FAILURE, "pledge");
#endif
char *socket_path = NULL;
int o, option_index = 0;
uint32_t message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND;
@ -244,12 +242,14 @@ int main(int argc, char *argv[]) {
optind++;
}
if (!payload)
if (!payload) {
payload = sstrdup("");
}
int sockfd = ipc_connect(socket_path);
if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t *)payload) == -1)
if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t *)payload) == -1) {
err(EXIT_FAILURE, "IPC: write()");
}
free(payload);
uint32_t reply_length;
@ -257,12 +257,14 @@ int main(int argc, char *argv[]) {
uint8_t *reply;
int ret;
if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
if (ret == -1)
if (ret == -1) {
err(EXIT_FAILURE, "IPC: read()");
}
exit(1);
}
if (reply_type != message_type)
if (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.
* If not, nicely format the error message. */
if (reply_type == I3_IPC_REPLY_TYPE_COMMAND) {
@ -303,8 +305,9 @@ int main(int argc, char *argv[]) {
do {
free(reply);
if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
if (ret == -1)
if (ret == -1) {
err(EXIT_FAILURE, "IPC: read()");
}
exit(1);
}

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* i3-nagbar is a utility which displays a nag message, for example in the case
@ -118,7 +118,7 @@ static void start_application(const char *command) {
if (fork() == 0) {
/* This is the child */
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL);
/* not reached */
err(EXIT_FAILURE, "execl return"); /* only reached on error */
}
exit(0);
}
@ -126,9 +126,11 @@ static void start_application(const char *command) {
}
static button_t *get_button_at(int16_t x, int16_t y) {
for (int c = 0; c < buttoncnt; c++)
if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width))
for (int c = 0; c < buttoncnt; c++) {
if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width)) {
return &buttons[c];
}
}
return NULL;
}
@ -148,11 +150,13 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
printf("button released on x = %d, y = %d\n",
event->event_x, event->event_y);
/* If the user hits the close button, we exit(0) */
if (event->event_x >= btn_close.x && event->event_x < btn_close.x + btn_close.width)
if (event->event_x >= btn_close.x && event->event_x < btn_close.x + btn_close.width) {
exit(0);
}
button_t *button = get_button_at(event->event_x, event->event_y);
if (!button)
if (!button) {
return;
}
/* We need to create a custom script containing our actual command
* since not every terminal emulator which is contained in
@ -238,7 +242,7 @@ static int button_draw(button_t *button, int position) {
* 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) {
/* draw background */
draw_util_clear_surface(&bar, color_background);
/* draw message */
@ -460,8 +464,9 @@ int main(int argc, char *argv[]) {
buttons[buttoncnt].action);
buttoncnt++;
printf("now %d buttons\n", buttoncnt);
if (optind < argc)
if (optind < argc) {
optind++;
}
break;
}
}
@ -470,8 +475,9 @@ int main(int argc, char *argv[]) {
int screens;
if ((conn = xcb_connect(NULL, &screens)) == NULL ||
xcb_connection_has_error(conn))
xcb_connection_has_error(conn)) {
die("Cannot open display");
}
/* Place requests for the atoms we need as soon as possible */
#define xmacro(atom) \
@ -497,7 +503,7 @@ int main(int argc, char *argv[]) {
} else {
/* Yellowish theme for warnings */
color_button_background = draw_util_hex_to_color("#ffc100");
color_background = draw_util_hex_to_color("#ffa8000");
color_background = draw_util_hex_to_color("#ffa800");
color_text = draw_util_hex_to_color("#000000");
color_border = draw_util_hex_to_color("#ab7100");
color_border_bottom = draw_util_hex_to_color("#ab7100");
@ -507,11 +513,6 @@ int main(int argc, char *argv[]) {
font = load_font(pattern, true);
set_font(&font);
#if defined(__OpenBSD__)
if (pledge("stdio rpath wpath cpath getpw proc exec", NULL) == -1)
err(EXIT_FAILURE, "pledge");
#endif
/* Default values if we cannot determine the preferred window position. */
xcb_rectangle_t win_pos = (xcb_rectangle_t){50, 50, 500, font.height + 2 * MSG_PADDING + BAR_BORDER};
if (position_on_primary) {
@ -632,7 +633,7 @@ int main(int argc, char *argv[]) {
switch (type) {
case XCB_EXPOSE:
if (((xcb_expose_event_t *)event)->count == 0) {
handle_expose(conn, (xcb_expose_event_t *)event);
handle_expose(conn);
}
break;

View File

@ -14,7 +14,7 @@
# 2. Distribution-specific mechanisms come next, e.g. x-terminal-emulator
# 3. The terminal emulator with best accessibility comes first.
# 4. No order is guaranteed/desired for the remaining terminal emulators.
for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper wezterm; do
for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper wezterm rio; do
if command -v "$terminal" > /dev/null 2>&1; then
exec "$terminal" "$@"
fi

1
i3bar/.gitignore vendored
View File

@ -1,4 +1,3 @@
i3bar
*.o
core
doc/i3bar.1

View File

@ -11,7 +11,7 @@
#include <config.h>
#include <stdbool.h>
#include <ev.h>
#define STDIN_CHUNK_SIZE 1024
@ -40,6 +40,18 @@ typedef struct {
*/
bool click_events;
bool click_events_init;
/**
* stdin- and SIGCHLD-watchers
*/
ev_io *stdin_io;
ev_child *child_sig;
int stdin_fd;
/**
* Line read from child that did not include a newline character.
*/
char *pending_line;
} i3bar_child;
/*
@ -50,36 +62,66 @@ void clear_statusline(struct statusline_head *head, bool free_resources);
/*
* Start a child process with the specified command and reroute stdin.
* We actually start a $SHELL to execute the command so we don't have to care
* about arguments and such
* We actually start a shell to execute the command so we don't have to care
* about arguments and such.
*
* If `command' is NULL, such as in the case when no `status_command' is given
* in the bar config, no child will be started.
*
*/
void start_child(char *command);
/*
* Same as start_child but starts the configured client that manages workspace
* buttons.
*
*/
void start_ws_child(char *command);
/*
* Returns true if the status child process is alive.
*
*/
bool status_child_is_alive(void);
/*
* Returns true if the workspace child process is alive.
*
*/
bool ws_child_is_alive(void);
/*
* kill()s the child process (if any). Called when exit()ing.
*
*/
void kill_child_at_exit(void);
void kill_children_at_exit(void);
/*
* kill()s the child process (if any) and closes and
* free()s the stdin- and SIGCHLD-watchers
* kill()s the child process (if any) and closes and free()s the stdin- and
* SIGCHLD-watchers
*
*/
void kill_child(void);
/*
* kill()s the workspace child process (if any) and closes and free()s the
* stdin- and SIGCHLD-watchers.
* Similar to kill_child.
*
*/
void kill_ws_child(void);
/*
* Sends a SIGSTOP to the child process (if existent)
*
*/
void stop_child(void);
void stop_children(void);
/*
* Sends a SIGCONT to the child process (if existent)
*
*/
void cont_child(void);
void cont_children(void);
/*
* Whether or not the child want click events
@ -92,3 +134,14 @@ bool child_want_click_events(void);
*
*/
void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int out_x, int out_y, int width, int height, int mods);
/*
* When workspace_command is enabled this function is used to re-parse the
* latest received JSON from the client.
*/
void repeat_last_ws_json(void);
/*
* Replaces the workspace buttons with an error message.
*/
void set_workspace_button_error(const char *message);

View File

@ -46,6 +46,9 @@ struct status_block {
i3String *full_text;
i3String *short_text;
bool use_short;
uint32_t render_length;
char *color;
char *background;
char *border;

View File

@ -62,6 +62,7 @@ typedef struct config_t {
bool strip_ws_name;
char *bar_id;
char *command;
char *workspace_command;
char *fontname;
i3String *separator_symbol;
TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs;
@ -79,17 +80,17 @@ typedef struct config_t {
extern config_t config;
/**
* Start parsing the received bar configuration JSON string
* Parse the received bar configuration JSON string
*
*/
void parse_config_json(char *json);
void parse_config_json(const unsigned char *json, size_t size);
/**
* Start parsing the received bar configuration list. The only usecase right
* now is to automatically get the first bar id.
* Parse the received bar configuration list. The only usecase right now is to
* automatically get the first bar id.
*
*/
void parse_get_first_i3bar_config(char *json);
void parse_get_first_i3bar_config(const unsigned char *json, size_t size);
/**
* free()s the color strings as soon as they are not needed anymore.

View File

@ -24,7 +24,7 @@ struct mode {
typedef struct mode mode;
/*
* Start parsing the received JSON string
* Parse the received JSON string
*
*/
void parse_mode_json(char *json);
void parse_mode_json(const unsigned char *json, size_t size);

View File

@ -22,10 +22,10 @@ SLIST_HEAD(outputs_head, i3_output);
extern struct outputs_head* outputs;
/*
* Start parsing the received JSON string
* Parse the received JSON string
*
*/
void parse_outputs_json(char* json);
void parse_outputs_json(const unsigned char* json, size_t size);
/*
* Initiate the outputs list
@ -65,8 +65,6 @@ struct i3_output {
surface_t statusline_buffer;
/* How much of statusline_buffer's horizontal space was used on last statusline render. */
int statusline_width;
/* Whether statusline block short texts where used on last statusline render. */
bool statusline_short_text;
/* The actual window on which we draw. */
surface_t bar;

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
*/

View File

@ -18,10 +18,10 @@ typedef struct i3_ws i3_ws;
TAILQ_HEAD(ws_head, i3_ws);
/*
* Start parsing the received JSON string
* Parse the received JSON string
*
*/
void parse_workspaces_json(char *json);
void parse_workspaces_json(const unsigned char *json, size_t size);
/*
* free() all workspace data structures
@ -38,7 +38,6 @@ struct i3_ws {
bool visible; /* If the ws is currently visible on an output */
bool focused; /* If the ws is currently focused */
bool urgent; /* If the urgent hint of the ws is set */
rect rect; /* The rect of the ws (not used (yet)) */
struct i3_output *output; /* The current output of the ws */
TAILQ_ENTRY(i3_ws) tailq; /* Pointer for the TAILQ-Macro */

View File

@ -12,7 +12,6 @@
#include <config.h>
#include <stdint.h>
//#include "outputs.h"
#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0
#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1

View File

@ -8,8 +8,10 @@
*
*/
#include "common.h"
#include "queue.h"
#include "yajl_utils.h"
#include <ctype.h> /* isspace */
#include <err.h>
#include <errno.h>
#include <ev.h>
@ -20,6 +22,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/wait.h>
#include <unistd.h>
@ -27,14 +30,30 @@
#include <yajl/yajl_parse.h>
/* Global variables for child_*() */
i3bar_child child = {0};
#define DLOG_CHILD DLOG("%s: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
__func__, (long)child.pid, child.stopped, child.stop_signal, child.cont_signal, child.click_events, child.click_events_init)
i3bar_child status_child = {0};
i3bar_child ws_child = {0};
/* stdin- and SIGCHLD-watchers */
ev_io *stdin_io;
int stdin_fd;
ev_child *child_sig;
#define DLOG_CHILD(c) \
do { \
if ((c).pid == 0) { \
DLOG("%s: child pid = 0\n", __func__); \
} else if ((c).pid == status_child.pid) { \
DLOG("%s: status_command: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
__func__, (long)(c).pid, (c).stopped, (c).stop_signal, (c).cont_signal, (c).click_events, (c).click_events_init); \
} else if ((c).pid == ws_child.pid) { \
DLOG("%s: workspace_command: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
__func__, (long)(c).pid, (c).stopped, (c).stop_signal, (c).cont_signal, (c).click_events, (c).click_events_init); \
} else { \
ELOG("%s: unknown child, this should never happen " \
"pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
__func__, (long)(c).pid, (c).stopped, (c).stop_signal, (c).cont_signal, (c).click_events, (c).click_events_init); \
} \
} while (0)
#define DLOG_CHILDREN \
do { \
DLOG_CHILD(status_child); \
DLOG_CHILD(ws_child); \
} while (0)
/* JSON parser for stdin */
yajl_handle parser;
@ -127,7 +146,7 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha
TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks);
finish:
FREE(message);
free(message);
va_end(args);
}
@ -135,22 +154,27 @@ finish:
* Stop and free() the stdin- and SIGCHLD-watchers
*
*/
static void cleanup(void) {
if (stdin_io != NULL) {
ev_io_stop(main_loop, stdin_io);
FREE(stdin_io);
close(stdin_fd);
stdin_fd = 0;
close(child_stdin);
child_stdin = 0;
static void cleanup(i3bar_child *c) {
DLOG_CHILD(*c);
if (c->stdin_io != NULL) {
ev_io_stop(main_loop, c->stdin_io);
FREE(c->stdin_io);
if (c->pid == status_child.pid) {
close(child_stdin);
child_stdin = 0;
}
close(c->stdin_fd);
}
if (child_sig != NULL) {
ev_child_stop(main_loop, child_sig);
FREE(child_sig);
if (c->child_sig != NULL) {
ev_child_stop(main_loop, c->child_sig);
FREE(c->child_sig);
}
memset(&child, 0, sizeof(i3bar_child));
FREE(c->pending_line);
memset(c, 0, sizeof(i3bar_child));
}
/*
@ -173,10 +197,11 @@ static int stdin_start_map(void *context) {
memset(&(ctx->block), '\0', sizeof(struct status_block));
/* Default width of the separator block. */
if (config.separator_symbol == NULL)
if (config.separator_symbol == NULL) {
ctx->block.sep_block_width = logical_px(9);
else
} else {
ctx->block.sep_block_width = logical_px(8) + separator_symbol_width;
}
/* By default we draw all four borders if a border is set. */
ctx->block.border_top = 1;
@ -190,7 +215,7 @@ static int stdin_start_map(void *context) {
static int stdin_map_key(void *context, const unsigned char *key, size_t len) {
parser_ctx *ctx = context;
FREE(ctx->last_map_key);
sasprintf(&(ctx->last_map_key), "%.*s", len, key);
sasprintf(&(ctx->last_map_key), "%.*s", (int)len, key);
return 1;
}
@ -229,15 +254,15 @@ static int stdin_string(void *context, const unsigned char *val, size_t len) {
return 1;
}
if (strcasecmp(ctx->last_map_key, "color") == 0) {
sasprintf(&(ctx->block.color), "%.*s", len, val);
sasprintf(&(ctx->block.color), "%.*s", (int)len, val);
return 1;
}
if (strcasecmp(ctx->last_map_key, "background") == 0) {
sasprintf(&(ctx->block.background), "%.*s", len, val);
sasprintf(&(ctx->block.background), "%.*s", (int)len, val);
return 1;
}
if (strcasecmp(ctx->last_map_key, "border") == 0) {
sasprintf(&(ctx->block.border), "%.*s", len, val);
sasprintf(&(ctx->block.border), "%.*s", (int)len, val);
return 1;
}
if (strcasecmp(ctx->last_map_key, "markup") == 0) {
@ -255,15 +280,15 @@ static int stdin_string(void *context, const unsigned char *val, size_t len) {
return 1;
}
if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
sasprintf(&(ctx->block.min_width_str), "%.*s", len, val);
sasprintf(&(ctx->block.min_width_str), "%.*s", (int)len, val);
return 1;
}
if (strcasecmp(ctx->last_map_key, "name") == 0) {
sasprintf(&(ctx->block.name), "%.*s", len, val);
sasprintf(&(ctx->block.name), "%.*s", (int)len, val);
return 1;
}
if (strcasecmp(ctx->last_map_key, "instance") == 0) {
sasprintf(&(ctx->block.instance), "%.*s", len, val);
sasprintf(&(ctx->block.instance), "%.*s", (int)len, val);
return 1;
}
@ -315,10 +340,12 @@ static int stdin_end_map(void *context) {
memcpy(new_block, &(ctx->block), sizeof(struct status_block));
/* Ensure we have a full_text set, so that when it is missing (or null),
* i3bar doesnt crash and the user gets an annoying message. */
if (!new_block->full_text)
if (!new_block->full_text) {
new_block->full_text = i3string_from_utf8("SPEC VIOLATION: full_text is NULL!");
if (new_block->urgent)
}
if (new_block->urgent) {
ctx->has_urgent = true;
}
if (new_block->min_width_str) {
i3String *text = i3string_from_utf8(new_block->min_width_str);
@ -329,10 +356,13 @@ static int stdin_end_map(void *context) {
i3string_set_markup(new_block->full_text, new_block->pango_markup);
if (new_block->short_text != NULL)
new_block->use_short = false;
if (new_block->short_text != NULL) {
i3string_set_markup(new_block->short_text, new_block->pango_markup);
}
TAILQ_INSERT_TAIL(&statusline_buffer, new_block, blocks);
return 1;
}
@ -362,15 +392,13 @@ static int stdin_end_array(void *context) {
* Returns NULL on EOF.
*
*/
static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
int fd = watcher->fd;
int n = 0;
static unsigned char *get_buffer(int fd, int *ret_buffer_len) {
int rec = 0;
int buffer_len = STDIN_CHUNK_SIZE;
unsigned char *buffer = smalloc(buffer_len + 1);
buffer[0] = '\0';
while (1) {
n = read(fd, buffer + rec, buffer_len - rec);
const ssize_t n = read(fd, buffer + rec, buffer_len - rec);
if (n == -1) {
if (errno == EAGAIN) {
/* finish up */
@ -390,10 +418,11 @@ static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
if (rec == buffer_len) {
buffer_len += STDIN_CHUNK_SIZE;
buffer = srealloc(buffer, buffer_len);
buffer = srealloc(buffer, buffer_len + 1);
}
}
if (*buffer == '\0') {
buffer[rec] = '\0';
if (buffer[0] == '\0') {
FREE(buffer);
rec = -1;
}
@ -423,8 +452,9 @@ static bool read_json_input(unsigned char *input, int length) {
char *message = (char *)yajl_get_error(parser, 0, input, length);
/* strip the newline yajl adds to the error message */
if (message[strlen(message) - 1] == '\n')
if (message[strlen(message) - 1] == '\n') {
message[strlen(message) - 1] = '\0';
}
fprintf(stderr, "[i3bar] Could not parse JSON input (code = %d, message = %s): %.*s\n",
status, message, length, input);
@ -443,13 +473,14 @@ static bool read_json_input(unsigned char *input, int length) {
* in statusline
*
*/
static void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
static void stdin_io_cb(int fd) {
int rec;
unsigned char *buffer = get_buffer(watcher, &rec);
if (buffer == NULL)
unsigned char *buffer = get_buffer(fd, &rec);
if (buffer == NULL) {
return;
}
bool has_urgent = false;
if (child.version > 0) {
if (status_child.version > 0) {
has_urgent = read_json_input(buffer, rec);
} else {
read_flat_input((char *)buffer, rec);
@ -463,22 +494,23 @@ static void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
* whether this is JSON or plain text
*
*/
static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
static void stdin_io_first_line_cb(int fd) {
int rec;
unsigned char *buffer = get_buffer(watcher, &rec);
if (buffer == NULL)
unsigned char *buffer = get_buffer(fd, &rec);
if (buffer == NULL) {
return;
}
DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
/* Detect whether this is JSON or plain text. */
unsigned int consumed = 0;
/* At the moment, we dont care for the version. This might change
* in the future, but for now, we just discard it. */
parse_json_header(&child, buffer, rec, &consumed);
if (child.version > 0) {
/* If hide-on-modifier is set, we start of by sending the
* child a SIGSTOP, because the bars aren't mapped at start */
parse_json_header(&status_child, buffer, rec, &consumed);
if (status_child.version > 0) {
/* If hide-on-modifier is set, we start of by sending the status_child
* a SIGSTOP, because the bars aren't mapped at start */
if (config.hide_on_modifier) {
stop_child();
stop_children();
}
draw_bars(read_json_input(buffer + consumed, rec - consumed));
} else {
@ -489,9 +521,133 @@ static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int rev
read_flat_input((char *)buffer, rec);
}
free(buffer);
ev_io_stop(main_loop, stdin_io);
ev_io_init(stdin_io, &stdin_io_cb, stdin_fd, EV_READ);
ev_io_start(main_loop, stdin_io);
}
static bool isempty(char *s) {
while (*s != '\0') {
if (!isspace(*s)) {
return false;
}
s++;
}
return true;
}
static char *append_string(const char *previous, const char *str) {
if (previous != NULL) {
char *result;
sasprintf(&result, "%s%s", previous, str);
return result;
}
return sstrdup(str);
}
static char *ws_last_json;
static void ws_stdin_io_cb(int fd) {
int rec;
unsigned char *buffer = get_buffer(fd, &rec);
if (buffer == NULL) {
return;
}
gchar **strings = g_strsplit((const char *)buffer, "\n", 0);
for (int idx = 0; strings[idx] != NULL; idx++) {
if (ws_child.pending_line == NULL && isempty(strings[idx])) {
/* In the normal case where the buffer ends with '\n', the last
* string should be empty */
continue;
}
if (strings[idx + 1] == NULL) {
/* This is the last string but it is not empty, meaning that we have
* read data that is incomplete, save it for later. */
char *new = append_string(ws_child.pending_line, strings[idx]);
free(ws_child.pending_line);
ws_child.pending_line = new;
continue;
}
free(ws_last_json);
ws_last_json = append_string(ws_child.pending_line, strings[idx]);
FREE(ws_child.pending_line);
parse_workspaces_json((const unsigned char *)ws_last_json, strlen(ws_last_json));
}
g_strfreev(strings);
free(buffer);
draw_bars(false);
}
static void common_stdin_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
if (watcher == status_child.stdin_io) {
if (status_child.version == (uint32_t)-1) {
stdin_io_first_line_cb(watcher->fd);
} else {
stdin_io_cb(watcher->fd);
}
} else if (watcher == ws_child.stdin_io) {
ws_stdin_io_cb(watcher->fd);
} else {
ELOG("Got callback for unknown watcher fd=%d\n", watcher->fd);
}
}
/*
* When workspace_command is enabled this function is used to re-parse the
* latest received JSON from the client.
*/
void repeat_last_ws_json(void) {
if (ws_last_json) {
DLOG("Repeating last workspace JSON\n");
parse_workspaces_json((const unsigned char *)ws_last_json, strlen(ws_last_json));
}
}
/*
* Wrapper around set_workspace_button_error to mimic the call of
* set_statusline_error.
*/
__attribute__((format(printf, 1, 2))) static void set_workspace_button_error_f(const char *format, ...) {
char *message;
va_list args;
va_start(args, format);
if (vasprintf(&message, format, args) == -1) {
goto finish;
}
set_workspace_button_error(message);
finish:
free(message);
va_end(args);
}
/*
* Replaces the workspace buttons with an error message.
*/
void set_workspace_button_error(const char *message) {
free_workspaces();
char *name = NULL;
sasprintf(&name, "Error: %s", message);
i3_output *output;
SLIST_FOREACH (output, outputs, slist) {
i3_ws *fake_ws = scalloc(1, sizeof(i3_ws));
/* Don't set the canonical_name field to make this workspace unfocusable. */
fake_ws->name = i3string_from_utf8(name);
fake_ws->name_width = predict_text_width(fake_ws->name);
fake_ws->num = -1;
fake_ws->urgent = fake_ws->visible = true;
fake_ws->output = output;
TAILQ_INSERT_TAIL(output->workspaces, fake_ws, tailq);
}
free(name);
}
/*
@ -501,27 +657,45 @@ static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int rev
*
*/
static void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
int exit_status = WEXITSTATUS(watcher->rstatus);
const int exit_status = WEXITSTATUS(watcher->rstatus);
ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
child.pid,
watcher->pid,
exit_status);
__attribute__((format(printf, 1, 2))) void (*error_function_pointer)(const char *, ...) = NULL;
const char *command_type = "";
i3bar_child *c = NULL;
if (watcher->pid == status_child.pid) {
command_type = "status_command";
error_function_pointer = set_statusline_error;
c = &status_child;
} else if (watcher->pid == ws_child.pid) {
command_type = "workspace_command";
error_function_pointer = set_workspace_button_error_f;
c = &ws_child;
} else {
ELOG("Unknown child pid, this should never happen\n");
return;
}
DLOG_CHILD(*c);
/* this error is most likely caused by a user giving a nonexecutable or
* nonexistent file, so we will handle those cases separately. */
if (exit_status == 126)
set_statusline_error("status_command is not executable (exit %d)", exit_status);
else if (exit_status == 127)
set_statusline_error("status_command not found or is missing a library dependency (exit %d)", exit_status);
else
set_statusline_error("status_command process exited unexpectedly (exit %d)", exit_status);
if (exit_status == 126) {
error_function_pointer("%s is not executable (exit %d)", command_type, exit_status);
} else if (exit_status == 127) {
error_function_pointer("%s not found or is missing a library dependency (exit %d)", command_type, exit_status);
} else {
error_function_pointer("%s process exited unexpectedly (exit %d)", command_type, exit_status);
}
cleanup();
cleanup(c);
draw_bars(false);
}
static void child_write_output(void) {
if (child.click_events) {
if (status_child.click_events) {
const unsigned char *output;
size_t size;
ssize_t n;
@ -529,13 +703,14 @@ static void child_write_output(void) {
yajl_gen_get_buf(gen, &output, &size);
n = writeall(child_stdin, output, size);
if (n != -1)
if (n != -1) {
n = writeall(child_stdin, "\n", 1);
}
yajl_gen_clear(gen);
if (n == -1) {
child.click_events = false;
status_child.click_events = false;
kill_child();
set_statusline_error("child_write_output failed");
draw_bars(false);
@ -543,6 +718,42 @@ static void child_write_output(void) {
}
}
static pid_t sfork(void) {
const pid_t pid = fork();
if (pid == -1) {
ELOG("Couldn't fork(): %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
return pid;
}
static void spipe(int pipedes[2]) {
if (pipe(pipedes) == -1) {
err(EXIT_FAILURE, "pipe(pipe_in)");
}
}
static void exec_shell(char *command) {
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char *)NULL);
err(EXIT_FAILURE, "execl return"); /* only reached on error */
}
static void setup_child_cb(i3bar_child *child) {
/* We set O_NONBLOCK because blocking is evil in event-driven software */
fcntl(child->stdin_fd, F_SETFL, O_NONBLOCK);
child->stdin_io = smalloc(sizeof(ev_io));
ev_io_init(child->stdin_io, &common_stdin_cb, child->stdin_fd, EV_READ);
ev_io_start(main_loop, child->stdin_io);
/* We must cleanup, if the child unexpectedly terminates */
child->child_sig = smalloc(sizeof(ev_child));
ev_child_init(child->child_sig, &child_sig_cb, child->pid, 0);
ev_child_start(main_loop, child->child_sig);
DLOG_CHILD(*child);
}
/*
* Start a child process with the specified command and reroute stdin.
* We actually start a shell to execute the command so we don't have to care
@ -553,8 +764,9 @@ static void child_write_output(void) {
*
*/
void start_child(char *command) {
if (command == NULL)
if (command == NULL) {
return;
}
/* Allocate a yajl parser which will be used to parse stdin. */
static yajl_callbacks callbacks = {
@ -568,69 +780,76 @@ void start_child(char *command) {
.yajl_end_array = stdin_end_array,
};
parser = yajl_alloc(&callbacks, NULL, &parser_context);
gen = yajl_gen_alloc(NULL);
int pipe_in[2]; /* pipe we read from */
int pipe_out[2]; /* pipe we write to */
spipe(pipe_in);
spipe(pipe_out);
if (pipe(pipe_in) == -1)
err(EXIT_FAILURE, "pipe(pipe_in)");
if (pipe(pipe_out) == -1)
err(EXIT_FAILURE, "pipe(pipe_out)");
status_child.pid = sfork();
if (status_child.pid == 0) {
/* Child-process. Reroute streams and start shell */
close(pipe_in[0]);
close(pipe_out[1]);
child.pid = fork();
switch (child.pid) {
case -1:
ELOG("Couldn't fork(): %s\n", strerror(errno));
exit(EXIT_FAILURE);
case 0:
/* Child-process. Reroute streams and start shell */
dup2(pipe_in[1], STDOUT_FILENO);
dup2(pipe_out[0], STDIN_FILENO);
close(pipe_in[0]);
close(pipe_out[1]);
setpgid(status_child.pid, 0);
exec_shell(command);
return;
}
/* Parent-process. Reroute streams */
close(pipe_in[1]);
close(pipe_out[0]);
dup2(pipe_in[1], STDOUT_FILENO);
dup2(pipe_out[0], STDIN_FILENO);
status_child.stdin_fd = pipe_in[0];
child_stdin = pipe_out[1];
status_child.version = -1;
setpgid(child.pid, 0);
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char *)NULL);
return;
default:
/* Parent-process. Reroute streams */
setup_child_cb(&status_child);
}
close(pipe_in[1]);
close(pipe_out[0]);
stdin_fd = pipe_in[0];
child_stdin = pipe_out[1];
break;
/*
* Same as start_child but starts the configured client that manages workspace
* buttons.
*
*/
void start_ws_child(char *command) {
if (command == NULL) {
return;
}
/* We set O_NONBLOCK because blocking is evil in event-driven software */
fcntl(stdin_fd, F_SETFL, O_NONBLOCK);
ws_child.stop_signal = SIGSTOP;
ws_child.cont_signal = SIGCONT;
stdin_io = smalloc(sizeof(ev_io));
ev_io_init(stdin_io, &stdin_io_first_line_cb, stdin_fd, EV_READ);
ev_io_start(main_loop, stdin_io);
int pipe_in[2]; /* pipe we read from */
spipe(pipe_in);
/* We must cleanup, if the child unexpectedly terminates */
child_sig = smalloc(sizeof(ev_child));
ev_child_init(child_sig, &child_sig_cb, child.pid, 0);
ev_child_start(main_loop, child_sig);
ws_child.pid = sfork();
if (ws_child.pid == 0) {
/* Child-process. Reroute streams and start shell */
close(pipe_in[0]);
dup2(pipe_in[1], STDOUT_FILENO);
atexit(kill_child_at_exit);
DLOG_CHILD;
setpgid(ws_child.pid, 0);
exec_shell(command);
}
/* Parent-process. Reroute streams */
close(pipe_in[1]);
ws_child.stdin_fd = pipe_in[0];
setup_child_cb(&ws_child);
}
static void child_click_events_initialize(void) {
DLOG_CHILD;
DLOG_CHILD(status_child);
if (!child.click_events_init) {
if (!status_child.click_events_init) {
yajl_gen_array_open(gen);
child_write_output();
child.click_events_init = true;
status_child.click_events_init = true;
}
}
@ -639,7 +858,7 @@ static void child_click_events_initialize(void) {
*
*/
void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int out_x, int out_y, int width, int height, int mods) {
if (!child.click_events) {
if (!status_child.click_events) {
return;
}
@ -662,20 +881,27 @@ void send_block_clicked(int button, const char *name, const char *instance, int
ystr("modifiers");
yajl_gen_array_open(gen);
if (mods & XCB_MOD_MASK_SHIFT)
if (mods & XCB_MOD_MASK_SHIFT) {
ystr("Shift");
if (mods & XCB_MOD_MASK_CONTROL)
}
if (mods & XCB_MOD_MASK_CONTROL) {
ystr("Control");
if (mods & XCB_MOD_MASK_1)
}
if (mods & XCB_MOD_MASK_1) {
ystr("Mod1");
if (mods & XCB_MOD_MASK_2)
}
if (mods & XCB_MOD_MASK_2) {
ystr("Mod2");
if (mods & XCB_MOD_MASK_3)
}
if (mods & XCB_MOD_MASK_3) {
ystr("Mod3");
if (mods & XCB_MOD_MASK_4)
}
if (mods & XCB_MOD_MASK_4) {
ystr("Mod4");
if (mods & XCB_MOD_MASK_5)
}
if (mods & XCB_MOD_MASK_5) {
ystr("Mod5");
}
yajl_gen_array_close(gen);
ystr("x");
@ -706,35 +932,85 @@ void send_block_clicked(int button, const char *name, const char *instance, int
child_write_output();
}
static bool is_alive(i3bar_child *c) {
return c->pid > 0;
}
/*
* Returns true if the status child process is alive.
*
*/
bool status_child_is_alive(void) {
return is_alive(&status_child);
}
/*
* Returns true if the workspace child process is alive.
*
*/
bool ws_child_is_alive(void) {
return is_alive(&ws_child);
}
/*
* kill()s the child process (if any). Called when exit()ing.
*
*/
void kill_child_at_exit(void) {
DLOG_CHILD;
void kill_children_at_exit(void) {
DLOG_CHILDREN;
cont_children();
if (child.pid > 0) {
if (child.cont_signal > 0 && child.stopped)
killpg(child.pid, child.cont_signal);
killpg(child.pid, SIGTERM);
if (is_alive(&status_child)) {
killpg(status_child.pid, SIGTERM);
}
if (is_alive(&ws_child)) {
killpg(ws_child.pid, SIGTERM);
}
}
static void cont_child(i3bar_child *c) {
if (is_alive(c) && c->cont_signal > 0 && c->stopped) {
c->stopped = false;
killpg(c->pid, c->cont_signal);
}
}
static void kill_and_wait(i3bar_child *c) {
DLOG_CHILD(*c);
if (!is_alive(c)) {
return;
}
cont_child(c);
killpg(c->pid, SIGTERM);
int status;
waitpid(c->pid, &status, 0);
cleanup(c);
}
/*
* kill()s the child process (if existent) and closes and
* free()s the stdin- and SIGCHLD-watchers
* kill()s the child process (if any) and closes and free()s the stdin- and
* SIGCHLD-watchers
*
*/
void kill_child(void) {
DLOG_CHILD;
kill_and_wait(&status_child);
}
if (child.pid > 0) {
if (child.cont_signal > 0 && child.stopped)
killpg(child.pid, child.cont_signal);
killpg(child.pid, SIGTERM);
int status;
waitpid(child.pid, &status, 0);
cleanup();
/*
* kill()s the workspace child process (if any) and closes and free()s the
* stdin- and SIGCHLD-watchers.
* Similar to kill_child.
*
*/
void kill_ws_child(void) {
kill_and_wait(&ws_child);
}
static void stop_child(i3bar_child *c) {
if (c->stop_signal > 0 && !c->stopped) {
c->stopped = true;
killpg(c->pid, c->stop_signal);
}
}
@ -742,26 +1018,21 @@ void kill_child(void) {
* Sends a SIGSTOP to the child process (if existent)
*
*/
void stop_child(void) {
DLOG_CHILD;
if (child.stop_signal > 0 && !child.stopped) {
child.stopped = true;
killpg(child.pid, child.stop_signal);
}
void stop_children(void) {
DLOG_CHILDREN;
stop_child(&status_child);
stop_child(&ws_child);
}
/*
* Sends a SIGCONT to the child process (if existent)
*
*/
void cont_child(void) {
DLOG_CHILD;
void cont_children(void) {
DLOG_CHILDREN;
if (child.cont_signal > 0 && child.stopped) {
child.stopped = false;
killpg(child.pid, child.cont_signal);
}
cont_child(&status_child);
cont_child(&ws_child);
}
/*
@ -769,5 +1040,5 @@ void cont_child(void) {
*
*/
bool child_want_click_events(void) {
return child.click_events;
return status_child.click_events;
}

View File

@ -29,7 +29,7 @@ static bool parsing_padding;
*/
static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) {
FREE(cur_key);
sasprintf(&(cur_key), "%.*s", keyLen, keyVal);
sasprintf(&(cur_key), "%.*s", (int)keyLen, keyVal);
if (strcmp(cur_key, "bindings") == 0) {
parsing_bindings = true;
@ -76,8 +76,9 @@ static int config_null_cb(void *params_) {
static int config_string_cb(void *params_, const unsigned char *val, size_t _len) {
int len = (int)_len;
/* The id and socket_path are ignored, we already know them. */
if (!strcmp(cur_key, "id") || !strcmp(cur_key, "socket_path"))
if (!strcmp(cur_key, "id") || !strcmp(cur_key, "socket_path")) {
return 1;
}
if (parsing_bindings) {
if (strcmp(cur_key, "command") == 0) {
@ -188,11 +189,17 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
}
if (!strcmp(cur_key, "status_command")) {
DLOG("command = %.*s\n", len, val);
DLOG("status_command = %.*s\n", len, val);
sasprintf(&config.command, "%.*s", len, val);
return 1;
}
if (!strcmp(cur_key, "workspace_command")) {
DLOG("workspace_command = %.*s\n", len, val);
sasprintf(&config.workspace_command, "%.*s", len, val);
return 1;
}
if (!strcmp(cur_key, "font")) {
DLOG("font = %.*s\n", len, val);
FREE(config.fontname);
@ -396,16 +403,15 @@ static yajl_callbacks outputs_callbacks = {
};
/*
* Start parsing the received bar configuration JSON string
* Parse the received bar configuration JSON string
*
*/
void parse_config_json(char *json) {
yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
void parse_config_json(const unsigned char *json, size_t size) {
TAILQ_INIT(&(config.bindings));
TAILQ_INIT(&(config.tray_outputs));
yajl_status state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
yajl_status state = yajl_parse(handle, json, size);
/* FIXME: Proper error handling for JSON parsing */
switch (state) {
@ -418,6 +424,11 @@ void parse_config_json(char *json) {
break;
}
if (config.disable_ws && config.workspace_command) {
ELOG("You have specified 'workspace_buttons no'. Your 'workspace_command %s' will be ignored.\n", config.workspace_command);
FREE(config.workspace_command);
}
yajl_free(handle);
}
@ -427,16 +438,16 @@ static int i3bar_config_string_cb(void *params_, const unsigned char *val, size_
}
/*
* Start parsing the received bar configuration list. The only usecase right
* now is to automatically get the first bar id.
* Parse the received bar configuration list. The only usecase right now is to
* automatically get the first bar id.
*
*/
void parse_get_first_i3bar_config(char *json) {
void parse_get_first_i3bar_config(const unsigned char *json, size_t size) {
yajl_callbacks configs_callbacks = {
.yajl_string = i3bar_config_string_cb,
};
yajl_handle handle = yajl_alloc(&configs_callbacks, NULL, NULL);
yajl_parse(handle, (const unsigned char *)json, strlen(json));
yajl_parse(handle, json, size);
yajl_free(handle);
}

View File

@ -24,14 +24,24 @@ ev_io *i3_connection;
const char *sock_path;
typedef void (*handler_t)(char *);
typedef void (*handler_t)(const unsigned char *, size_t);
/*
* Returns true when i3bar is configured to read workspace information from i3
* via JSON over the i3 IPC interface, as opposed to reading workspace
* information from the workspace_command via JSON over stdout.
*
*/
static bool i3_provides_workspaces(void) {
return !config.disable_ws && config.workspace_command == NULL;
}
/*
* Called, when we get a reply to a command from i3.
* Since i3 does not give us much feedback on commands, we do not much
*
*/
static void got_command_reply(char *reply) {
static void got_command_reply(const unsigned char *reply, size_t size) {
/* TODO: Error handling for command replies */
}
@ -39,9 +49,9 @@ static void got_command_reply(char *reply) {
* Called, when we get a reply with workspaces data
*
*/
static void got_workspace_reply(char *reply) {
static void got_workspace_reply(const unsigned char *reply, size_t size) {
DLOG("Got workspace data!\n");
parse_workspaces_json(reply);
parse_workspaces_json(reply, size);
draw_bars(false);
}
@ -50,7 +60,7 @@ static void got_workspace_reply(char *reply) {
* Since i3 does not give us much feedback on commands, we do not much
*
*/
static void got_subscribe_reply(char *reply) {
static void got_subscribe_reply(const unsigned char *reply, size_t size) {
DLOG("Got subscribe reply: %s\n", reply);
/* TODO: Error handling for subscribe commands */
}
@ -59,12 +69,12 @@ static void got_subscribe_reply(char *reply) {
* Called, when we get a reply with outputs data
*
*/
static void got_output_reply(char *reply) {
static void got_output_reply(const unsigned char *reply, size_t size) {
DLOG("Clearing old output configuration...\n");
free_outputs();
DLOG("Parsing outputs JSON...\n");
parse_outputs_json(reply);
parse_outputs_json(reply, size);
DLOG("Reconfiguring windows...\n");
reconfig_windows(false);
@ -73,8 +83,19 @@ static void got_output_reply(char *reply) {
kick_tray_clients(o_walk);
}
if (!config.disable_ws) {
if (i3_provides_workspaces()) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
} else if (config.workspace_command) {
/* Communication with the workspace child is one-way. Since we called
* free_outputs() and free_workspaces() we have lost our workspace
* information which will result in no workspace buttons. A
* well-behaving client should be subscribed to output events as well
* and re-send the output information to i3bar. Even in that case
* though there is a race condition where the child can send the new
* workspace information after the output change before i3bar receives
* the output event from i3. For this reason, we re-parse the latest
* received JSON. */
repeat_last_ws_json();
}
draw_bars(false);
@ -84,10 +105,10 @@ static void got_output_reply(char *reply) {
* Called when we get the configuration for our bar instance
*
*/
static void got_bar_config(char *reply) {
static void got_bar_config(const unsigned char *reply, size_t size) {
if (!config.bar_id) {
DLOG("Received bar list \"%s\"\n", reply);
parse_get_first_i3bar_config(reply);
parse_get_first_i3bar_config(reply, size);
if (!config.bar_id) {
ELOG("No bar configuration found, please configure a bar block in your i3 config file.\n");
@ -106,13 +127,14 @@ static void got_bar_config(char *reply) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
free_colors(&(config.colors));
parse_config_json(reply);
parse_config_json(reply, size);
/* Now we can actually use 'config', so let's subscribe to the appropriate
* events and request the workspaces if necessary. */
subscribe_events();
if (!config.disable_ws)
if (i3_provides_workspaces()) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
}
/* Initialize the rest of XCB */
init_xcb_late(config.fontname);
@ -121,6 +143,7 @@ static void got_bar_config(char *reply) {
init_colors(&(config.colors));
start_child(config.command);
start_ws_child(config.workspace_command);
}
/* Data structure to easily call the reply handlers later */
@ -143,7 +166,7 @@ handler_t reply_handlers[] = {
* Called, when a workspace event arrives (i.e. the user changed the workspace)
*
*/
static void got_workspace_event(char *event) {
static void got_workspace_event(const unsigned char *event, size_t size) {
DLOG("Got workspace event!\n");
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
}
@ -152,7 +175,7 @@ static void got_workspace_event(char *event) {
* Called, when an output event arrives (i.e. the screen configuration changed)
*
*/
static void got_output_event(char *event) {
static void got_output_event(const unsigned char *event, size_t size) {
DLOG("Got output event!\n");
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
}
@ -161,9 +184,9 @@ static void got_output_event(char *event) {
* Called, when a mode event arrives (i3 changed binding mode).
*
*/
static void got_mode_event(char *event) {
static void got_mode_event(const unsigned char *event, size_t size) {
DLOG("Got mode event!\n");
parse_mode_json(event);
parse_mode_json(event, size);
draw_bars(false);
}
@ -183,14 +206,15 @@ static bool strings_differ(char *a, char *b) {
* Called, when a barconfig_update event arrives (i.e. i3 changed the bar hidden_state or mode)
*
*/
static void got_bar_config_update(char *event) {
static void got_bar_config_update(const unsigned char *event, size_t size) {
/* check whether this affect this bar instance by checking the bar_id */
char *expected_id;
sasprintf(&expected_id, "\"id\":\"%s\"", config.bar_id);
char *found_id = strstr(event, expected_id);
char *found_id = strstr((const char *)event, expected_id);
FREE(expected_id);
if (found_id == NULL)
if (found_id == NULL) {
return;
}
/* reconfigure the bar based on the current outputs */
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
@ -201,10 +225,12 @@ static void got_bar_config_update(char *event) {
DLOG("Received bar config update \"%s\"\n", event);
char *old_command = config.command;
char *old_workspace_command = config.workspace_command;
config.command = NULL;
config.workspace_command = NULL;
bar_display_mode_t old_mode = config.hide_on_modifier;
parse_config_json(event);
parse_config_json(event, size);
if (old_mode != config.hide_on_modifier) {
reconfig_windows(true);
}
@ -214,13 +240,21 @@ static void got_bar_config_update(char *event) {
init_colors(&(config.colors));
/* restart status command process */
if (strings_differ(old_command, config.command)) {
if (!status_child_is_alive() || strings_differ(old_command, config.command)) {
kill_child();
clear_statusline(&statusline_head, true);
start_child(config.command);
}
free(old_command);
/* restart workspace command process */
if (!ws_child_is_alive() || strings_differ(old_workspace_command, config.workspace_command)) {
free_workspaces();
kill_ws_child();
start_ws_child(config.workspace_command);
}
free(old_workspace_command);
draw_bars(false);
}
@ -249,7 +283,7 @@ static void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
* we have to expect */
uint32_t rec = 0;
while (rec < header_len) {
int n = read(fd, header + rec, header_len - rec);
const int n = read(fd, header + rec, header_len - rec);
if (n == -1) {
ELOG("read() failed: %s\n", strerror(errno));
exit(EXIT_FAILURE);
@ -258,10 +292,11 @@ static void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
/* EOF received. Since i3 will restart i3bar instances as appropriate,
* we exit here. */
DLOG("EOF received, exiting...\n");
clean_xcb();
free(header);
#ifdef I3_ASAN_ENABLED
__lsan_do_leak_check();
#endif
clean_xcb();
exit(EXIT_SUCCESS);
}
rec += n;
@ -284,7 +319,7 @@ static void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
/* Now that we know, what to expect, we can start read()ing the rest
* of the message */
char *buffer = smalloc(size + 1);
unsigned char *buffer = smalloc(size + 1);
rec = 0;
while (rec < size) {
@ -304,10 +339,11 @@ static void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
/* And call the callback (indexed by the type) */
if (type & (1UL << 31)) {
type ^= 1UL << 31;
event_handlers[type](buffer);
event_handlers[type](buffer, size);
} else {
if (reply_handlers[type])
reply_handlers[type](buffer);
if (reply_handlers[type]) {
reply_handlers[type](buffer, size);
}
}
FREE(header);
@ -340,8 +376,9 @@ int i3_send_msg(uint32_t type, const char *payload) {
memcpy(walk, &type, sizeof(uint32_t));
walk += sizeof(uint32_t);
if (payload != NULL)
strncpy(walk, payload, len);
if (payload != NULL) {
memcpy(walk, payload, len);
}
swrite(i3_connection->fd, buffer, to_write);
@ -376,9 +413,9 @@ void destroy_connection(void) {
*
*/
void subscribe_events(void) {
if (config.disable_ws) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\", \"barconfig_update\" ]");
} else {
if (i3_provides_workspaces()) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\", \"barconfig_update\" ]");
} else {
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\", \"barconfig_update\" ]");
}
}

View File

@ -185,12 +185,12 @@ int main(int argc, char **argv) {
ev_signal_start(main_loop, sig_int);
ev_signal_start(main_loop, sig_hup);
atexit(kill_children_at_exit);
/* From here on everything should run smooth for itself, just start listening for
* events. We stop simply stop the event loop, when we are finished */
ev_loop(main_loop, 0);
kill_child();
clean_xcb();
ev_default_destroy();

View File

@ -16,7 +16,6 @@
/* A datatype to pass through the callbacks to save the state */
struct mode_json_params {
char *json;
char *cur_key;
char *name;
bool pango_markup;
@ -31,7 +30,7 @@ static int mode_string_cb(void *params_, const unsigned char *val, size_t len) {
struct mode_json_params *params = (struct mode_json_params *)params_;
if (!strcmp(params->cur_key, "change")) {
sasprintf(&(params->name), "%.*s", len, val);
sasprintf(&(params->name), "%.*s", (int)len, val);
FREE(params->cur_key);
return 1;
}
@ -68,7 +67,7 @@ static int mode_boolean_cb(void *params_, int val) {
static int mode_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) {
struct mode_json_params *params = (struct mode_json_params *)params_;
FREE(params->cur_key);
sasprintf(&(params->cur_key), "%.*s", keyLen, keyVal);
sasprintf(&(params->cur_key), "%.*s", (int)keyLen, keyVal);
return 1;
}
@ -96,26 +95,17 @@ static yajl_callbacks mode_callbacks = {
};
/*
* Start parsing the received JSON string
* Parse the received JSON string
*
*/
void parse_mode_json(char *json) {
/* FIXME: Fasciliate stream processing, i.e. allow starting to interpret
* JSON in chunks */
void parse_mode_json(const unsigned char *json, size_t size) {
struct mode_json_params params;
mode binding;
params.cur_key = NULL;
params.json = json;
params.mode = &binding;
yajl_handle handle;
yajl_status state;
handle = yajl_alloc(&mode_callbacks, NULL, (void *)&params);
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
yajl_handle handle = yajl_alloc(&mode_callbacks, NULL, (void *)&params);
yajl_status state = yajl_parse(handle, json, size);
/* FIXME: Proper error handling for JSON parsing */
switch (state) {
@ -129,8 +119,9 @@ void parse_mode_json(char *json) {
}
/* We don't want to indicate default binding mode */
if (strcmp("default", i3string_as_utf8(params.mode->name)) == 0)
if (strcmp("default", i3string_as_utf8(params.mode->name)) == 0) {
I3STRING_FREE(params.mode->name);
}
/* Set the new binding mode */
set_current_mode(&binding);

View File

@ -18,10 +18,8 @@
/* A datatype to pass through the callbacks to save the state */
struct outputs_json_params {
struct outputs_head *outputs;
i3_output *outputs_walk;
char *cur_key;
char *json;
bool in_rect;
};
@ -108,14 +106,15 @@ static int outputs_string_cb(void *params_, const unsigned char *val, size_t len
if (!strcmp(params->cur_key, "current_workspace")) {
char *copy = NULL;
sasprintf(&copy, "%.*s", len, val);
sasprintf(&copy, "%.*s", (int)len, val);
char *end;
errno = 0;
long parsed_num = strtol(copy, &end, 10);
if (errno == 0 &&
(end && *end == '\0'))
(end && *end == '\0')) {
params->outputs_walk->ws = parsed_num;
}
FREE(copy);
FREE(params->cur_key);
@ -126,7 +125,7 @@ static int outputs_string_cb(void *params_, const unsigned char *val, size_t len
return 0;
}
sasprintf(&(params->outputs_walk->name), "%.*s", len, val);
sasprintf(&(params->outputs_walk->name), "%.*s", (int)len, val);
FREE(params->cur_key);
return 1;
@ -148,7 +147,6 @@ static int outputs_start_map_cb(void *params_) {
new_output->visible = false;
new_output->ws = 0,
new_output->statusline_width = 0;
new_output->statusline_short_text = false;
memset(&new_output->rect, 0, sizeof(rect));
memset(&new_output->bar, 0, sizeof(surface_t));
memset(&new_output->buffer, 0, sizeof(surface_t));
@ -237,7 +235,7 @@ static int outputs_end_map_cb(void *params_) {
static int outputs_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) {
struct outputs_json_params *params = (struct outputs_json_params *)params_;
FREE(params->cur_key);
sasprintf(&(params->cur_key), "%.*s", keyLen, keyVal);
sasprintf(&(params->cur_key), "%.*s", (int)keyLen, keyVal);
return 1;
}
@ -263,21 +261,17 @@ void init_outputs(void) {
}
/*
* Start parsing the received JSON string
* Parse the received JSON string
*
*/
void parse_outputs_json(char *json) {
void parse_outputs_json(const unsigned char *json, size_t size) {
struct outputs_json_params params;
params.outputs_walk = NULL;
params.cur_key = NULL;
params.json = json;
params.in_rect = false;
yajl_handle handle;
yajl_status state;
handle = yajl_alloc(&outputs_callbacks, NULL, (void *)&params);
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, (void *)&params);
yajl_status state = yajl_parse(handle, json, size);
/* FIXME: Proper errorhandling for JSON-parsing */
switch (state) {
@ -291,6 +285,7 @@ void parse_outputs_json(char *json) {
}
yajl_free(handle);
free(params.cur_key);
}
/*
@ -319,12 +314,14 @@ void free_outputs(void) {
*
*/
i3_output *get_output_by_name(char *name) {
i3_output *walk;
if (name == NULL) {
return NULL;
}
const bool is_primary = !strcasecmp(name, "primary");
i3_output *walk;
SLIST_FOREACH (walk, outputs, slist) {
if (!strcmp(walk->name, name)) {
if ((is_primary && walk->primary) || !strcmp(walk->name, name)) {
break;
}
}

View File

@ -106,11 +106,13 @@ void parse_json_header(i3bar_child *child, const unsigned char *buffer, int leng
yajl_status state = yajl_parse(handle, buffer, length);
if (state != yajl_status_ok) {
child_init(child);
if (consumed != NULL)
if (consumed != NULL) {
*consumed = 0;
}
} else {
if (consumed != NULL)
if (consumed != NULL) {
*consumed = yajl_get_bytes_consumed(handle);
}
}
yajl_free(handle);

View File

@ -19,15 +19,15 @@ struct workspaces_json_params {
struct ws_head *workspaces;
i3_ws *workspaces_walk;
char *cur_key;
char *json;
bool parsing_rect;
};
/*
* Parse a boolean value (visible, focused, urgent)
*
*/
static int workspaces_boolean_cb(void *params_, int val) {
struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
static int workspaces_boolean_cb(void *params_, const int val) {
struct workspaces_json_params *params = params_;
if (!strcmp(params->cur_key, "visible")) {
params->workspaces_walk->visible = val;
@ -56,8 +56,8 @@ static int workspaces_boolean_cb(void *params_, int val) {
* Parse an integer (num or the rect)
*
*/
static int workspaces_integer_cb(void *params_, long long val) {
struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
static int workspaces_integer_cb(void *params_, const long long val) {
struct workspaces_json_params *params = params_;
if (!strcmp(params->cur_key, "id")) {
params->workspaces_walk->id = val;
@ -71,26 +71,23 @@ static int workspaces_integer_cb(void *params_, long long val) {
return 1;
}
/* rect is unused, so we don't bother to save it */
if (!strcmp(params->cur_key, "x")) {
params->workspaces_walk->rect.x = (int)val;
FREE(params->cur_key);
return 1;
}
if (!strcmp(params->cur_key, "y")) {
params->workspaces_walk->rect.y = (int)val;
FREE(params->cur_key);
return 1;
}
if (!strcmp(params->cur_key, "width")) {
params->workspaces_walk->rect.w = (int)val;
FREE(params->cur_key);
return 1;
}
if (!strcmp(params->cur_key, "height")) {
params->workspaces_walk->rect.h = (int)val;
FREE(params->cur_key);
return 1;
}
@ -103,8 +100,8 @@ static int workspaces_integer_cb(void *params_, long long val) {
* Parse a string (name, output)
*
*/
static int workspaces_string_cb(void *params_, const unsigned char *val, size_t len) {
struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
static int workspaces_string_cb(void *params_, const unsigned char *val, const size_t len) {
struct workspaces_json_params *params = params_;
if (!strcmp(params->cur_key, "name")) {
const char *ws_name = (const char *)val;
@ -120,14 +117,15 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
size_t offset = strspn(ws_name, ws_num);
/* Also strip off the conventional ws name delimiter */
if (offset && ws_name[offset] == ':')
if (offset && ws_name[offset] == ':') {
offset += 1;
}
if (config.strip_ws_numbers) {
/* Offset may be equal to length, in which case display the number */
params->workspaces_walk->name = (offset < len
? i3string_from_markup_with_length(ws_name + offset, len - offset)
: i3string_from_markup(ws_num));
params->workspaces_walk->name = offset < len
? i3string_from_markup_with_length(ws_name + offset, len - offset)
: i3string_from_markup(ws_num);
} else {
params->workspaces_walk->name = i3string_from_markup(ws_num);
}
@ -153,18 +151,18 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
if (!strcmp(params->cur_key, "output")) {
/* We add the ws to the TAILQ of the output, it belongs to */
char *output_name = NULL;
sasprintf(&output_name, "%.*s", len, val);
sasprintf(&output_name, "%.*s", (int)len, val);
i3_output *target = get_output_by_name(output_name);
i3_ws *ws = params->workspaces_walk;
if (target != NULL) {
params->workspaces_walk->output = target;
TAILQ_INSERT_TAIL(params->workspaces_walk->output->workspaces,
params->workspaces_walk,
tailq);
ws->output = target;
TAILQ_INSERT_TAIL(ws->output->workspaces, ws, tailq);
}
FREE(output_name);
FREE(params->cur_key);
return 1;
}
@ -172,28 +170,52 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
}
/*
* We hit the start of a JSON map (rect or a new output)
* We hit the start of a JSON map (rect or a new workspace)
*
*/
static int workspaces_start_map_cb(void *params_) {
struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
i3_ws *new_workspace = NULL;
struct workspaces_json_params *params = params_;
if (params->cur_key == NULL) {
new_workspace = smalloc(sizeof(i3_ws));
i3_ws *new_workspace = scalloc(1, sizeof(i3_ws));
new_workspace->num = -1;
new_workspace->name = NULL;
new_workspace->visible = 0;
new_workspace->focused = 0;
new_workspace->urgent = 0;
memset(&new_workspace->rect, 0, sizeof(rect));
new_workspace->output = NULL;
params->workspaces_walk = new_workspace;
params->parsing_rect = false;
} else {
params->parsing_rect = true;
}
return 1;
}
static int workspaces_end_map_cb(void *params_) {
struct workspaces_json_params *params = params_;
if (params->parsing_rect) {
params->parsing_rect = false;
return 1;
}
i3_ws *ws = params->workspaces_walk;
if (!ws || ws->output) {
return 1; /* workspace already assigned to output */
}
if (!ws->name || SLIST_EMPTY(outputs)) { /* Invalid state */
I3STRING_FREE(ws->name);
FREE(ws->canonical_name);
FREE(params->workspaces_walk);
return 1;
}
/* Handle no output case */
ws->output = get_output_by_name("primary");
if (ws->output == NULL) {
ws->output = SLIST_FIRST(outputs);
}
TAILQ_INSERT_TAIL(ws->output->workspaces, ws, tailq);
return 1;
}
@ -203,56 +225,55 @@ static int workspaces_start_map_cb(void *params_) {
* Essentially we just save it in the parsing state
*
*/
static int workspaces_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) {
struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
static int workspaces_map_key_cb(void *params_, const unsigned char *keyVal, const size_t keyLen) {
struct workspaces_json_params *params = params_;
FREE(params->cur_key);
sasprintf(&(params->cur_key), "%.*s", keyLen, keyVal);
sasprintf(&params->cur_key, "%.*s", (int)keyLen, keyVal);
return 1;
}
/* A datastructure to pass all these callbacks to yajl */
/* A data structure to pass all these callbacks to yajl */
static yajl_callbacks workspaces_callbacks = {
.yajl_boolean = workspaces_boolean_cb,
.yajl_integer = workspaces_integer_cb,
.yajl_string = workspaces_string_cb,
.yajl_start_map = workspaces_start_map_cb,
.yajl_end_map = workspaces_end_map_cb,
.yajl_map_key = workspaces_map_key_cb,
};
/*
* Start parsing the received JSON string
* Parse the received JSON string
*
*/
void parse_workspaces_json(char *json) {
/* FIXME: Fasciliate stream processing, i.e. allow starting to interpret
* JSON in chunks */
struct workspaces_json_params params;
void parse_workspaces_json(const unsigned char *json, const size_t size) {
free_workspaces();
params.workspaces_walk = NULL;
params.cur_key = NULL;
params.json = json;
yajl_handle handle;
yajl_status state;
handle = yajl_alloc(&workspaces_callbacks, NULL, (void *)&params);
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
struct workspaces_json_params params = {0};
const yajl_handle handle = yajl_alloc(&workspaces_callbacks, NULL, &params);
const yajl_status state = yajl_parse(handle, json, size);
/* FIXME: Proper error handling for JSON parsing */
switch (state) {
case yajl_status_ok:
break;
case yajl_status_client_canceled:
case yajl_status_error:
ELOG("Could not parse workspaces reply!\n");
exit(EXIT_FAILURE);
case yajl_status_error: {
unsigned char *err = yajl_get_error(handle, 1, json, size);
ELOG("Could not parse workspaces reply, error:\n%s\njson:---%s---\n", (char *)err, (char *)json);
yajl_free_error(handle, err);
if (config.workspace_command) {
kill_ws_child();
set_workspace_button_error("Could not parse workspace_command's JSON");
} else {
exit(EXIT_FAILURE);
}
break;
}
}
yajl_free(handle);
FREE(params.cur_key);
}
@ -261,14 +282,14 @@ void parse_workspaces_json(char *json) {
*
*/
void free_workspaces(void) {
i3_output *outputs_walk;
if (outputs == NULL) {
return;
}
i3_ws *ws_walk;
i3_output *outputs_walk;
SLIST_FOREACH (outputs_walk, outputs, slist) {
if (outputs_walk->workspaces != NULL && !TAILQ_EMPTY(outputs_walk->workspaces)) {
i3_ws *ws_walk;
TAILQ_FOREACH (ws_walk, outputs_walk->workspaces, tailq) {
I3STRING_FREE(ws_walk->name);
FREE(ws_walk->canonical_name);

View File

@ -12,6 +12,7 @@
#include <err.h>
#include <ev.h>
#include <i3/ipc.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
@ -138,8 +139,9 @@ int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
}
static uint32_t get_sep_offset(struct status_block *block) {
if (!block->no_separator && block->sep_block_width > 0)
if (!block->no_separator && block->sep_block_width > 0) {
return block->sep_block_width / 2 + block->sep_block_width % 2;
}
return 0;
}
@ -147,12 +149,14 @@ static int get_tray_width(struct tc_head *trayclients) {
trayclient *trayclient;
int tray_width = 0;
TAILQ_FOREACH_REVERSE (trayclient, trayclients, tc_head, tailq) {
if (!trayclient->mapped)
if (!trayclient->mapped) {
continue;
}
tray_width += icon_size + logical_px(config.tray_padding);
}
if (tray_width > 0)
if (tray_width > 0) {
tray_width += logical_px(tray_loff_px);
}
return tray_width;
}
@ -165,8 +169,9 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b
color_t bar_bg = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg);
uint32_t sep_offset = get_sep_offset(block);
if (TAILQ_NEXT(block, blocks) == NULL || sep_offset == 0)
if (TAILQ_NEXT(block, blocks) == NULL || sep_offset == 0) {
return;
}
uint32_t center_x = x - sep_offset;
if (config.separator_symbol == NULL) {
@ -184,50 +189,103 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b
}
}
static uint32_t predict_statusline_length(bool use_short_text) {
static void predict_block_length(struct status_block *block) {
i3String *text = block->full_text;
struct status_block_render_desc *render = &block->full_render;
if (block->use_short && block->short_text != NULL) {
text = block->short_text;
render = &block->short_render;
}
if (i3string_get_num_bytes(text) == 0) {
block->render_length = 0;
return;
}
render->width = predict_text_width(text);
if (block->border) {
render->width += logical_px(block->border_left + block->border_right);
}
/* Compute offset and append for text alignment in min_width. */
if (block->min_width <= render->width) {
render->x_offset = 0;
render->x_append = 0;
} else {
uint32_t padding_width = block->min_width - render->width;
switch (block->align) {
case ALIGN_LEFT:
render->x_append = padding_width;
break;
case ALIGN_RIGHT:
render->x_offset = padding_width;
break;
case ALIGN_CENTER:
render->x_offset = padding_width / 2;
render->x_append = padding_width / 2 + padding_width % 2;
break;
}
}
block->render_length = render->width + render->x_offset + render->x_append;
}
static uint32_t predict_statusline_length(void) {
uint32_t width = 0;
struct status_block *block;
TAILQ_FOREACH (block, &statusline_head, blocks) {
i3String *text = block->full_text;
struct status_block_render_desc *render = &block->full_render;
if (use_short_text && block->short_text != NULL) {
text = block->short_text;
render = &block->short_render;
}
if (i3string_get_num_bytes(text) == 0)
predict_block_length(block);
uint32_t block_width = block->render_length;
if (block_width == 0) {
continue;
render->width = predict_text_width(text);
if (block->border)
render->width += logical_px(block->border_left + block->border_right);
/* Compute offset and append for text alignment in min_width. */
if (block->min_width <= render->width) {
render->x_offset = 0;
render->x_append = 0;
} else {
uint32_t padding_width = block->min_width - render->width;
switch (block->align) {
case ALIGN_LEFT:
render->x_append = padding_width;
break;
case ALIGN_RIGHT:
render->x_offset = padding_width;
break;
case ALIGN_CENTER:
render->x_offset = padding_width / 2;
render->x_append = padding_width / 2 + padding_width % 2;
break;
}
}
width += render->width + render->x_offset + render->x_append;
width += block_width;
/* If this is not the last block, add some pixels for a separator. */
if (TAILQ_NEXT(block, blocks) != NULL)
if (TAILQ_NEXT(block, blocks) != NULL) {
width += block->sep_block_width;
}
}
return width;
}
static uint32_t switch_block_to_short(struct status_block *block) {
/* Skip blocks that have no short form or are already in short form */
if (block->short_text == NULL || block->use_short) {
return 0;
}
uint32_t full = block->render_length;
block->use_short = true;
predict_block_length(block);
return full - block->render_length;
}
static uint32_t adjust_statusline_length(uint32_t max_length) {
uint32_t width = predict_statusline_length();
/* Progressively switch the blocks to short mode */
struct status_block *block;
TAILQ_FOREACH (block, &statusline_head, blocks) {
if (width < max_length) {
break;
}
width -= switch_block_to_short(block);
/* Provide support for representing a single logical block using multiple
* JSON blocks: if one block is shortened, ensure that all other blocks
* with the same name are also shortened such that the entire logical block uses
* the short form text. */
if (block->name) {
struct status_block *other;
TAILQ_FOREACH (other, &statusline_head, blocks) {
if (other->name && !strcmp(other->name, block->name)) {
width -= switch_block_to_short(other);
}
}
}
}
return width;
@ -236,7 +294,7 @@ static uint32_t predict_statusline_length(bool use_short_text) {
/*
* Redraws the statusline to the output's statusline_buffer
*/
static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_colors, bool use_short_text) {
static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_colors) {
struct status_block *block;
color_t bar_color = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg);
@ -254,13 +312,14 @@ static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focu
TAILQ_FOREACH (block, &statusline_head, blocks) {
i3String *text = block->full_text;
struct status_block_render_desc *render = &block->full_render;
if (use_short_text && block->short_text != NULL) {
if (block->use_short && block->short_text != NULL) {
text = block->short_text;
render = &block->short_render;
}
if (i3string_get_num_bytes(text) == 0)
if (i3string_get_num_bytes(text) == 0) {
continue;
}
color_t fg_color;
if (block->urgent) {
@ -284,10 +343,12 @@ static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focu
border_color = colors.urgent_ws_border;
bg_color = colors.urgent_ws_bg;
} else {
if (block->border)
if (block->border) {
border_color = draw_util_hex_to_color(block->border);
if (block->background)
}
if (block->background) {
bg_color = draw_util_hex_to_color(block->background);
}
}
/* Draw the border. */
@ -334,7 +395,7 @@ static void hide_bars(void) {
}
xcb_unmap_window(xcb_connection, walk->bar.id);
}
stop_child();
stop_children();
}
/*
@ -351,7 +412,7 @@ static void unhide_bars(void) {
uint32_t mask;
uint32_t values[5];
cont_child();
cont_children();
SLIST_FOREACH (walk, outputs, slist) {
if (walk->bar.id == XCB_NONE) {
@ -363,10 +424,11 @@ static void unhide_bars(void) {
XCB_CONFIG_WINDOW_HEIGHT |
XCB_CONFIG_WINDOW_STACK_MODE;
values[0] = walk->rect.x;
if (config.position == POS_TOP)
if (config.position == POS_TOP) {
values[1] = walk->rect.y;
else
} else {
values[1] = walk->rect.y + walk->rect.h - bar_height;
}
values[2] = walk->rect.w;
values[3] = bar_height;
values[4] = XCB_STACK_MODE_ABOVE;
@ -434,8 +496,9 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
static bool execute_custom_command(xcb_keycode_t input_code, bool event_is_release) {
binding_t *binding;
TAILQ_FOREACH (binding, &(config.bindings), bindings) {
if ((binding->input_code != input_code) || (binding->release != event_is_release))
if ((binding->input_code != input_code) || (binding->release != event_is_release)) {
continue;
}
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command);
return true;
@ -454,7 +517,7 @@ static void child_handle_button(xcb_button_press_event_t *event, i3_output *outp
TAILQ_FOREACH (block, &statusline_head, blocks) {
i3String *text;
struct status_block_render_desc *render;
if (output->statusline_short_text && block->short_text != NULL) {
if (block->use_short && block->short_text != NULL) {
text = block->short_text;
render = &block->short_render;
} else {
@ -500,6 +563,49 @@ static int predict_button_width(int name_width) {
logical_px(config.ws_min_width));
}
static char *quote_workspace_name(const char *in) {
/* To properly handle workspace names with double quotes in them, we need
* to escape the double quotes. We allocate a large enough buffer (twice
* the unescaped size is always enough), then we copy character by
* character. */
const size_t namelen = strlen(in);
const size_t len = namelen + strlen("workspace \"\"") + 1;
char *out = scalloc(2 * len, 1);
memcpy(out, "workspace \"", strlen("workspace \""));
size_t inpos, outpos;
for (inpos = 0, outpos = strlen("workspace \"");
inpos < namelen;
inpos++, outpos++) {
if (in[inpos] == '"' || in[inpos] == '\\') {
out[outpos] = '\\';
outpos++;
}
out[outpos] = in[inpos];
}
out[outpos] = '"';
return out;
}
static void focus_workspace(i3_ws *ws) {
char *buffer = NULL;
if (ws->id != 0) {
/* Workspace ID has higher precedence since the workspace_command is
* allowed to change workspace names as long as it provides a valid ID. */
sasprintf(&buffer, "[con_id=%lu] focus workspace", ws->id);
goto done;
}
if (ws->canonical_name == NULL) {
return;
}
buffer = quote_workspace_name(ws->canonical_name);
done:
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, buffer);
free(buffer);
}
/*
* Handle a button press event (i.e. a mouse click on one of our bars).
* We determine, whether the click occurred on a workspace button or if the scroll-
@ -526,19 +632,27 @@ static void handle_button(xcb_button_press_event_t *event) {
/* During button release events, only check for custom commands. */
const bool event_is_release = (event->response_type & ~0x80) == XCB_BUTTON_RELEASE;
int32_t x = event->event_x >= 0 ? event->event_x : 0;
const int x = (event->event_x >= 0 ? event->event_x : 0) - logical_px(config.padding.x);
if (x < 0) {
/* Ignore clicks in padding */
return;
}
int workspace_width = 0;
i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
TAILQ_FOREACH (ws_walk, walk->workspaces, tailq) {
int w = predict_button_width(ws_walk->name_width);
if (x >= workspace_width && x <= workspace_width + w)
if (x >= workspace_width && x <= workspace_width + w) {
clicked_ws = ws_walk;
if (ws_walk->visible)
}
if (ws_walk->visible) {
cur_ws = ws_walk;
}
workspace_width += w;
if (TAILQ_NEXT(ws_walk, tailq) != NULL)
if (TAILQ_NEXT(ws_walk, tailq) != NULL) {
workspace_width += logical_px(ws_spacing_px);
}
}
if (child_want_click_events() && x > workspace_width) {
@ -583,8 +697,9 @@ static void handle_button(xcb_button_press_event_t *event) {
* If there is no more workspace, dont even send the workspace
* command, otherwise (with workspace auto_back_and_forth) wed end
* up on the wrong workspace. */
if (cur_ws == TAILQ_FIRST(walk->workspaces))
if (cur_ws == TAILQ_FIRST(walk->workspaces)) {
return;
}
cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq);
break;
@ -594,8 +709,9 @@ static void handle_button(xcb_button_press_event_t *event) {
* If there is no more workspace, dont even send the workspace
* command, otherwise (with workspace auto_back_and_forth) wed end
* up on the wrong workspace. */
if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head))
if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head)) {
return;
}
cur_ws = TAILQ_NEXT(cur_ws, tailq);
break;
@ -606,51 +722,23 @@ static void handle_button(xcb_button_press_event_t *event) {
* workspace if it is not already focused */
if (cur_ws == NULL) {
TAILQ_FOREACH (cur_ws, walk->workspaces, tailq) {
if (cur_ws->visible && !cur_ws->focused)
if (cur_ws->visible && !cur_ws->focused) {
break;
}
}
}
/* if there is nothing to focus, we are done */
if (cur_ws == NULL)
if (cur_ws == NULL) {
return;
}
break;
default:
return;
}
/* To properly handle workspace names with double quotes in them, we need
* to escape the double quotes. Unfortunately, thats rather ugly in C: We
* first count the number of double quotes, then we allocate a large enough
* buffer, then we copy character by character. */
int num_quotes = 0;
size_t namelen = 0;
const char *utf8_name = cur_ws->canonical_name;
for (const char *walk = utf8_name; *walk != '\0'; walk++) {
if (*walk == '"' || *walk == '\\')
num_quotes++;
/* While were looping through the name anyway, we can save one
* strlen(). */
namelen++;
}
const size_t len = namelen + strlen("workspace \"\"") + 1;
char *buffer = scalloc(len + num_quotes, 1);
memcpy(buffer, "workspace \"", strlen("workspace \""));
size_t inpos, outpos;
for (inpos = 0, outpos = strlen("workspace \"");
inpos < namelen;
inpos++, outpos++) {
if (utf8_name[inpos] == '"' || utf8_name[inpos] == '\\') {
buffer[outpos] = '\\';
outpos++;
}
buffer[outpos] = utf8_name[inpos];
}
buffer[outpos] = '"';
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, buffer);
free(buffer);
focus_workspace(cur_ws);
}
/*
@ -674,9 +762,9 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
}
if (num_visible == 0) {
stop_child();
stop_children();
} else {
cont_child();
cont_children();
}
}
@ -824,12 +912,10 @@ static void handle_client_message(xcb_client_message_event_t *event) {
DLOG("_NET_SYSTEM_TRAY_OPCODE received\n");
/* event->data.data32[0] is the timestamp */
uint32_t op = event->data.data32[1];
uint32_t mask;
uint32_t values[2];
if (op == SYSTEM_TRAY_REQUEST_DOCK) {
xcb_window_t client = event->data.data32[2];
mask = XCB_CW_EVENT_MASK;
const xcb_window_t client = event->data.data32[2];
uint32_t mask = XCB_CW_EVENT_MASK;
/* Needed to get the most recent value of XEMBED_MAPPED. */
values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE;
@ -872,8 +958,9 @@ static void handle_client_message(xcb_client_message_event_t *event) {
DLOG("xembed flags = %d\n", xembed[1]);
map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
xe_version = xembed[0];
if (xe_version > 1)
if (xe_version > 1) {
xe_version = 1;
}
free(xembedr);
} else {
ELOG("Window %08x violates the XEMBED protocol, _XEMBED_INFO not set\n", client);
@ -891,8 +978,9 @@ static void handle_client_message(xcb_client_message_event_t *event) {
output_for_tray->bar.id,
output_for_tray->rect.w - icon_size - logical_px(config.tray_padding),
logical_px(config.tray_padding));
if (xcb_request_failed(rcookie, "Could not reparent window. Maybe it is using an incorrect depth/visual?"))
if (xcb_request_failed(rcookie, "Could not reparent window. Maybe it is using an incorrect depth/visual?")) {
return;
}
/* We reconfigure the window to use a reasonable size. The systray
* specification explicitly says:
@ -1091,17 +1179,20 @@ static void handle_configuration_change(xcb_window_t window) {
trayclient *trayclient;
i3_output *output;
SLIST_FOREACH (output, outputs, slist) {
if (!output->active)
if (!output->active) {
continue;
}
int clients = 0;
TAILQ_FOREACH_REVERSE (trayclient, output->trayclients, tc_head, tailq) {
if (!trayclient->mapped)
if (!trayclient->mapped) {
continue;
}
clients++;
if (trayclient->win != window)
if (trayclient->win != window) {
continue;
}
xcb_rectangle_t rect;
rect.x = output->rect.w - (clients * (icon_size + logical_px(config.tray_padding)));
@ -1348,8 +1439,9 @@ static void deregister_xkb_keyevents(void) {
*
*/
void init_xcb_late(char *fontname) {
if (fontname == NULL)
if (fontname == NULL) {
fontname = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
}
/* Load the font */
font = load_font(fontname, true);
@ -1377,13 +1469,15 @@ void init_xcb_late(char *fontname) {
bar_height = default_px + padding_scaled;
icon_size = bar_height - 2 * logical_px(config.tray_padding);
if (config.separator_symbol)
if (config.separator_symbol) {
separator_symbol_width = predict_text_width(config.separator_symbol);
}
xcb_flush(xcb_connection);
if (config.hide_on_modifier == M_HIDE)
if (config.hide_on_modifier == M_HIDE) {
register_xkb_keyevents();
}
}
/*
@ -1419,11 +1513,15 @@ static void send_tray_clientmessage(void) {
static void init_tray(void) {
DLOG("Initializing system tray functionality\n");
/* request the tray manager atom for the X11 display we are running on */
char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11];
snprintf(atomname, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", screen);
/* The following line cannot use strlen as that makes compilation fail with
* some versions of clang (-Wgnu-folding-constant): */
const size_t systray_len = strlen("_NET_SYSTEM_TRAY_S") + 11;
char atomname[systray_len];
snprintf(atomname, systray_len, "_NET_SYSTEM_TRAY_S%d", screen);
xcb_intern_atom_cookie_t tray_cookie;
if (tray_reply == NULL)
if (tray_reply == NULL) {
tray_cookie = xcb_intern_atom(xcb_connection, 0, strlen(atomname), atomname);
}
/* tray support: we need a window to own the selection */
selwin = xcb_generate_id(xcb_connection);
@ -1590,8 +1688,9 @@ void get_atoms(void) {
*
*/
void kick_tray_clients(i3_output *output) {
if (TAILQ_EMPTY(output->trayclients))
if (TAILQ_EMPTY(output->trayclients)) {
return;
}
trayclient *trayclient;
while (!TAILQ_EMPTY(output->trayclients)) {
@ -1830,6 +1929,7 @@ void reconfig_windows(bool redraw_bars) {
8,
len,
class);
free(class);
char *name;
sasprintf(&name, "i3bar for output %s", walk->name);
@ -1886,10 +1986,11 @@ void reconfig_windows(bool redraw_bars) {
XCB_CONFIG_WINDOW_HEIGHT |
XCB_CONFIG_WINDOW_STACK_MODE;
values[0] = walk->rect.x;
if (config.position == POS_TOP)
if (config.position == POS_TOP) {
values[1] = walk->rect.y;
else
} else {
values[1] = walk->rect.y + walk->rect.h - bar_height;
}
values[2] = walk->rect.w;
values[3] = bar_height;
values[4] = XCB_STACK_MODE_ABOVE;
@ -1945,10 +2046,10 @@ void reconfig_windows(bool redraw_bars) {
/* Unmap the window, and draw it again when in dock mode */
umap_cookie = xcb_unmap_window_checked(xcb_connection, walk->bar.id);
if (config.hide_on_modifier == M_DOCK) {
cont_child();
cont_children();
map_cookie = xcb_map_window_checked(xcb_connection, walk->bar.id);
} else {
stop_child();
stop_children();
}
if (config.hide_on_modifier == M_HIDE) {
@ -2012,9 +2113,6 @@ static void draw_button(surface_t *surface, color_t fg_color, color_t bg_color,
void draw_bars(bool unhide) {
DLOG("Drawing bars...\n");
uint32_t full_statusline_width = predict_statusline_length(false);
uint32_t short_statusline_width = predict_statusline_length(true);
i3_output *outputs_walk;
SLIST_FOREACH (outputs_walk, outputs, slist) {
int workspace_width = logical_px(config.padding.x);
@ -2065,8 +2163,9 @@ void draw_bars(bool unhide) {
workspace_width, w, ws_walk->name_width, ws_walk->name);
workspace_width += w;
if (TAILQ_NEXT(ws_walk, tailq) != NULL)
if (TAILQ_NEXT(ws_walk, tailq) != NULL) {
workspace_width += logical_px(ws_spacing_px);
}
}
}
@ -2088,27 +2187,28 @@ void draw_bars(bool unhide) {
uint32_t hoff = logical_px(((workspace_width > 0) + (tray_width > 0)) * sb_hoff_px);
uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - hoff;
uint32_t clip_left = 0;
uint32_t statusline_width = full_statusline_width;
bool use_short_text = false;
/* Reset short mode between outputs */
struct status_block *block;
TAILQ_FOREACH (block, &statusline_head, blocks) {
block->use_short = false;
}
uint32_t statusline_width = adjust_statusline_length(max_statusline_width);
if (statusline_width > max_statusline_width) {
statusline_width = short_statusline_width;
use_short_text = true;
if (statusline_width > max_statusline_width) {
clip_left = statusline_width - max_statusline_width;
}
clip_left = statusline_width - max_statusline_width;
}
int16_t visible_statusline_width = MIN(statusline_width, max_statusline_width);
int x_dest = outputs_walk->rect.w - tray_width - logical_px((tray_width > 0) * sb_hoff_px) - visible_statusline_width;
x_dest -= logical_px(config.padding.width);
draw_statusline(outputs_walk, clip_left, use_focus_colors, use_short_text);
draw_statusline(outputs_walk, clip_left, use_focus_colors);
draw_util_copy_surface(&outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0,
x_dest, 0, visible_statusline_width, (int16_t)bar_height);
outputs_walk->statusline_width = statusline_width;
outputs_walk->statusline_short_text = use_short_text;
}
}

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* This header file includes all relevant files of i3 and the most often used
@ -28,9 +28,10 @@
#include <sys/types.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_icccm.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xkb.h>
#include <xcb/xcb_aux.h>
#include "libi3.h"
#include "data.h"

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* assignments.c: Assignments for specific windows (for_window).

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* bindings.h: Functions for configuring, finding, and running bindings.

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* click.c: Button press (mouse click) events.

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* commands.c: all command functions (see commands_parser.c)
@ -90,7 +90,7 @@ void cmd_nop(I3_CMD, const char *comment);
* Implementation of 'append_layout <path>'.
*
*/
void cmd_append_layout(I3_CMD, const char *path);
void cmd_append_layout(I3_CMD, const char *cpath);
/**
* Implementation of 'workspace next|prev|next_on_output|prev_on_output'.
@ -102,7 +102,7 @@ void cmd_workspace(I3_CMD, const char *which);
* Implementation of 'workspace [--no-auto-back-and-forth] number <number>'
*
*/
void cmd_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth);
void cmd_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth_str);
/**
* Implementation of 'workspace back_and_forth'.
@ -114,7 +114,7 @@ void cmd_workspace_back_and_forth(I3_CMD);
* Implementation of 'workspace [--no-auto-back-and-forth] <name>'
*
*/
void cmd_workspace_name(I3_CMD, const char *name, const char *no_auto_back_and_forth);
void cmd_workspace_name(I3_CMD, const char *name, const char *no_auto_back_and_forth_str);
/**
* Implementation of 'mark [--add|--replace] [--toggle] <mark>'
@ -174,13 +174,13 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command);
* Implementation of 'focus left|right|up|down'.
*
*/
void cmd_focus_direction(I3_CMD, const char *direction);
void cmd_focus_direction(I3_CMD, const char *direction_str);
/**
* Implementation of 'focus next|prev sibling'
*
*/
void cmd_focus_sibling(I3_CMD, const char *direction);
void cmd_focus_sibling(I3_CMD, const char *direction_str);
/**
* Implementation of 'focus tiling|floating|mode_toggle'.
@ -198,7 +198,7 @@ void cmd_focus_level(I3_CMD, const char *level);
* Implementation of 'focus'.
*
*/
void cmd_focus(I3_CMD);
void cmd_focus(I3_CMD, bool focus_workspace);
/**
* Implementation of 'fullscreen [enable|disable|toggle] [global]'.

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* commands.c: all command functions (see commands_parser.c)
@ -12,6 +12,18 @@
#include <config.h>
#include <yajl/yajl_gen.h>
#include "parser_util.h"
#include "queue.h"
/**
* Helper data structure for an operation window (window on which the operation
* will be performed). Used to build the TAILQ owindows.
*
*/
typedef struct owindow {
Con *con;
TAILQ_ENTRY(owindow) owindows;
} owindow;
/**
* Holds an intermediate representation of the result of a call to any command.
@ -19,6 +31,9 @@
* internally use this struct when calling cmd_floating and cmd_border.
*/
struct CommandResultIR {
/* The parser context this command is executing in. */
struct cmd_parser_ctx *ctx;
/* The JSON generator to append a reply to (may be NULL). */
yajl_gen json_gen;
@ -35,6 +50,28 @@ struct CommandResultIR {
bool needs_tree_render;
};
/* Define the owindows head structure here so it's complete */
TAILQ_HEAD(owindows_head, owindow);
/**
* Context structure for the command parser, making it re-entrant.
*/
struct cmd_parser_ctx {
int state;
Match current_match;
/* The (small) stack where identified literals are stored during the parsing
* of a single command (like $workspace). */
struct stack stack;
/* List of operation windows (windows on which operations will be performed).
* Used to build the TAILQ owindows. */
struct owindows_head owindows;
struct CommandResultIR subcommand_output;
struct CommandResultIR command_output;
};
typedef struct CommandResult CommandResult;
/**

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* con.c: Functions which deal with containers directly (creating containers,
@ -83,6 +83,22 @@ bool con_is_split(Con *con);
*/
bool con_is_hidden(Con *con);
/**
* Returns true if the container is maximized in the given orientation.
*
* If the container is floating or fullscreen, it is not considered maximized.
* Otherwise, it is maximized if it doesn't share space with any other
* container in the given orientation. For example, if a workspace contains
* a single splitv container with three children, none of them are considered
* vertically maximized, but they are all considered horizontally maximized.
*
* Passing "maximized" hints to the application can help it make the right
* choices about how to draw its borders. See discussion in
* https://github.com/i3/i3/pull/2380.
*
*/
bool con_is_maximized(Con *con, orientation_t orientation);
/**
* Returns whether the container or any of its children is sticky.
*

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* config_directives.h: all config storing functions (see config_parser.c)
@ -17,7 +17,7 @@
* A utility function to convert a string containing the group and modifiers to
* the corresponding bit mask.
*/
i3_event_state_mask_t event_state_from_str(const char *str);
i3_event_state_mask_t event_state_from_str(const char *str) __attribute__((__pure__));
/** The beginning of the prototype for every cfg_ function. */
#define I3_CFG Match *current_match, struct ConfigResultIR *result
@ -43,7 +43,7 @@ CFGFUN(include, const char *pattern);
CFGFUN(font, const char *font);
CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command);
CFGFUN(for_window, const char *command);
CFGFUN(gaps, const char *workspace, const char *type, const long value);
CFGFUN(gaps, const char *workspace, const char *scope, const long value);
CFGFUN(smart_borders, const char *enable);
CFGFUN(smart_gaps, const char *enable);
CFGFUN(floating_minimum_size, const long width, const long height);
@ -69,6 +69,7 @@ CFGFUN(no_focus);
CFGFUN(ipc_socket, const char *path);
CFGFUN(ipc_kill_timeout, const long timeout_ms);
CFGFUN(tiling_drag, const char *value);
CFGFUN(tiling_drag_swap_modifier, const char *modifiers);
CFGFUN(restart_state, const char *path);
CFGFUN(popup_during_fullscreen, const char *value);
CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border);
@ -78,7 +79,7 @@ CFGFUN(default_border, const char *windowtype, const char *border, const long wi
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 *exclude_titlebar, const char *command);
CFGFUN(enter_mode, const char *pango_markup, const char *mode);
CFGFUN(enter_mode, const char *pango_markup, const char *modename);
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);
@ -102,9 +103,10 @@ CFGFUN(bar_i3bar_command, const char *i3bar_command);
CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text);
CFGFUN(bar_socket_path, const char *socket_path);
CFGFUN(bar_tray_output, const char *output);
CFGFUN(bar_tray_padding, const long spacing_px);
CFGFUN(bar_tray_padding, const long padding_px);
CFGFUN(bar_color_single, const char *colorclass, const char *color);
CFGFUN(bar_status_command, const char *command);
CFGFUN(bar_workspace_command, const char *command);
CFGFUN(bar_binding_mode_indicator, const char *value);
CFGFUN(bar_workspace_buttons, const char *value);
CFGFUN(bar_workspace_min_width, const long width);

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* config_parser.h: config parser-related definitions
@ -13,29 +13,13 @@
#include <yajl/yajl_gen.h>
#include "parser_util.h"
SLIST_HEAD(variables_head, Variable);
extern pid_t config_error_nagbar_pid;
struct stack_entry {
/* Just a pointer, not dynamically allocated. */
const char *identifier;
enum {
STACK_STR = 0,
STACK_LONG = 1,
} type;
union {
char *str;
long num;
} val;
};
struct stack {
struct stack_entry stack[10];
};
struct parser_ctx {
bool use_nagbar;
bool assume_v4;
int state;
Match current_match;

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* include/configuration.h: Contains all structs/variables for the configurable
@ -227,6 +227,9 @@ struct Config {
/** The modifier which needs to be pressed in combination with your mouse
* buttons to do things with floating windows (move, resize) */
uint32_t floating_modifier;
/** The modifier which needs to be pressed in combination with the floating
* modifier and your mouse buttons to swap containers during tiling drag */
uint32_t swap_modifier;
/** Maximum and minimum dimensions of a floating window */
int32_t floating_maximum_width;
@ -262,6 +265,9 @@ struct Config {
/* just ignore the popup, that is, dont map it */
PDF_IGNORE = 2,
/* display all floating windows */
PDF_ALL = 3,
} popup_during_fullscreen;
/* The number of currently parsed barconfigs */
@ -272,9 +278,6 @@ struct Config {
/* Gap sizes */
gaps_t gaps;
/* Should single containers on a workspace receive a border? */
smart_borders_t smart_borders;
/* Disable gaps if there is only one container on the workspace */
smart_gaps_t smart_gaps;
};
@ -335,6 +338,10 @@ struct Barconfig {
* Will be passed to the shell. */
char *status_command;
/** Command that should be run to get the workspace buttons. Will be passed
* to the shell. */
char *workspace_command;
/** Font specification for all text rendered on the bar. */
char *font;

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* include/data.h: This file defines all data structures used by i3
@ -81,10 +81,6 @@ typedef enum { ADJ_NONE = 0,
ADJ_UPPER_SCREEN_EDGE = (1 << 2),
ADJ_LOWER_SCREEN_EDGE = (1 << 4) } adjacent_t;
typedef enum { SMART_BORDERS_OFF,
SMART_BORDERS_ON,
SMART_BORDERS_NO_GAPS } smart_borders_t;
typedef enum { SMART_GAPS_OFF,
SMART_GAPS_ON,
SMART_GAPS_INVERSE_OUTER } smart_gaps_t;

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* display_version.c: displays the running i3 version, runs as part of

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* drag.c: click and drag.

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* ewmh.c: Get/set certain EWMH properties easily.

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* Faking outputs is useful in pathological situations (like network X servers

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* floating.c: Floating windows.

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
*/

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* handlers.c: Small handlers for various events (keypresses, focus changes,

View File

@ -11,6 +11,8 @@ xmacro(_NET_WM_STATE_DEMANDS_ATTENTION) \
xmacro(_NET_WM_STATE_MODAL) \
xmacro(_NET_WM_STATE_HIDDEN) \
xmacro(_NET_WM_STATE_FOCUSED) \
xmacro(_NET_WM_STATE_MAXIMIZED_VERT) \
xmacro(_NET_WM_STATE_MAXIMIZED_HORZ) \
xmacro(_NET_WM_STATE) \
xmacro(_NET_WM_WINDOW_TYPE) \
xmacro(_NET_WM_WINDOW_TYPE_NORMAL) \

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* i3.h: global variables that are used all over i3.
@ -9,23 +9,16 @@
*/
#pragma once
#include <config.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <xcb/shape.h>
#include <xcb/xcb_keysyms.h>
#include <xcb/xkb.h>
#include <X11/XKBlib.h>
#define SN_API_NOT_YET_FROZEN 1
#include <libsn/sn-launcher.h>
#include "queue.h"
#include "data.h"
#include "xcb.h"
/** Git commit identifier, from version.c */
extern const char *i3_version;
@ -75,5 +68,4 @@ extern xcb_colormap_t colormap;
extern bool xkb_supported, shape_supported;
extern xcb_window_t root;
extern struct ev_loop *main_loop;
extern bool only_check_config;
extern bool force_xinerama;

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* This public header defines the different constants and message types to use

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* ipc.c: UNIX domain socket IPC (initialization, client handling, protocol).

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* key_press.c: key press handler

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* libi3: contains functions which are used by i3 *and* accompanying tools such
@ -158,7 +158,7 @@ char *sstrndup(const char *str, size_t size);
* there is no more memory available)
*
*/
int sasprintf(char **strp, const char *fmt, ...);
int sasprintf(char **strp, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
/**
* Wrapper around correct write which returns -1 (meaning that
@ -186,7 +186,7 @@ ssize_t swrite(int fd, const void *buf, size_t count);
* Like strcasecmp but considers the case where either string is NULL.
*
*/
int strcasecmp_nullable(const char *a, const char *b);
int strcasecmp_nullable(const char *a, const char *b) __attribute__((pure));
/**
* Build an i3String from an UTF-8 encoded string.
@ -240,12 +240,10 @@ void i3string_free(i3String *str);
* to prevent accidentally using freed memory.
*
*/
#define I3STRING_FREE(str) \
do { \
if (str != NULL) { \
i3string_free(str); \
str = NULL; \
} \
#define I3STRING_FREE(str) \
do { \
i3string_free(str); \
str = NULL; \
} while (0)
/**
@ -506,7 +504,7 @@ void init_dpi(void);
* This function returns the value of the DPI setting.
*
*/
long get_dpi_value(void);
long get_dpi_value(void) __attribute__((pure));
/**
* Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI
@ -514,7 +512,7 @@ long get_dpi_value(void);
* screen, e.g. 5 pixels on a 227 DPI MacBook Pro 13" Retina screen.
*
*/
int logical_px(const int logical);
int logical_px(int logical) __attribute__((pure));
/**
* This function resolves ~ in pathnames.
@ -572,6 +570,7 @@ typedef struct surface_t {
/* A classic XCB graphics context. */
xcb_gcontext_t gc;
bool owns_gc;
int width;
int height;
@ -686,3 +685,9 @@ bool is_background_set(xcb_connection_t *conn, xcb_screen_t *screen);
*
*/
bool boolstr(const char *str);
/**
* Get depth of visual specified by visualid
*
*/
uint16_t get_visual_depth(xcb_visualid_t visual_id);

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* load_layout.c: Restore (parts of) the layout, for example after an inplace

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* log.c: Logging functions.

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* main.c: Initialization, main loop
@ -10,6 +10,9 @@
#pragma once
#include <config.h>
#include <locale.h>
extern locale_t numericC;
/**
* Enable or disable the main X11 event handling function.

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* manage.c: Initially managing new windows (or existing ones on restart).

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* A "match" is a data structure which acts like a mask or expression to match

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* move.c: Moving containers into some direction.

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* output.c: Output (monitor) related functions.
@ -31,8 +31,10 @@ Output *get_output_from_string(Output *current_output, const char *output_str);
char *output_primary_name(Output *output);
/**
* Returns the output for the given con.
*
* Retrieves the output for a given container. Never returns NULL.
* There is an assertion that _will_ fail if the container is inside an
* internal workspace. Use con_is_internal() if needed before calling this
* function.
*/
Output *get_output_for_con(Con *con);

59
include/parser_util.h Normal file
View File

@ -0,0 +1,59 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* parser_util.h: utility functions for the config and commands parser
*
*/
#pragma once
struct stack_entry {
/* Just a pointer, not dynamically allocated. */
const char *identifier;
enum {
STACK_STR = 0,
STACK_LONG = 1,
} type;
union {
char *str;
long num;
} val;
};
struct stack {
struct stack_entry stack[10];
};
/**
* Pushes a string (identified by 'identifier') on the stack.
* If a string with the same identifier is already on the stack, the new
* string will be appended, separated by a comma.
*
*/
void parser_push_string(struct stack *stack, const char *identifier, const char *str);
/**
* Pushes a long (identified by 'identifier') on the stack.
*
*/
void parser_push_long(struct stack *stack, const char *identifier, long num);
/**
* Returns the string with the given identifier.
*
*/
const char *parser_get_string(const struct stack *stack, const char *identifier);
/**
* Returns the long with the given identifier.
*
*/
long parser_get_long(const struct stack *stack, const char *identifier);
/**
* Clears the stack.
*
*/
void parser_clear_stack(struct stack *stack);

View File

@ -96,7 +96,7 @@
}
#define SLIST_HEAD_INITIALIZER(head) \
{ NULL }
{NULL}
#define SLIST_ENTRY(type) \
struct { \
@ -174,7 +174,7 @@
}
#define LIST_HEAD_INITIALIZER(head) \
{ NULL }
{NULL}
#define LIST_ENTRY(type) \
struct { \
@ -256,7 +256,7 @@
}
#define SIMPLEQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).sqh_first }
{NULL, &(head).sqh_first}
#define SIMPLEQ_ENTRY(type) \
struct { \
@ -322,7 +322,7 @@
}
#define TAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).tqh_first }
{NULL, &(head).tqh_first}
#define TAILQ_ENTRY(type) \
struct { \
@ -447,9 +447,7 @@
#define CIRCLEQ_HEAD_INITIALIZER(head) \
{ \
CIRCLEQ_END(&head) \
, CIRCLEQ_END(&head) \
}
CIRCLEQ_END(&head), CIRCLEQ_END(&head)}
#define CIRCLEQ_ENTRY(type) \
struct { \

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* For more information on RandR, please see the X.org RandR specification at

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* regex.c: Interface to libPCRE (perl compatible regular expressions).
@ -35,4 +35,4 @@ void regex_free(struct regex *regex);
* be visible without debug logging.
*
*/
bool regex_matches(struct regex *regex, const char *input);
bool regex_matches(const struct regex *regex, const char *input);

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* render.c: Renders (determines position/sizes) the layout tree, updating the

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* resize.c: Interactive resizing.

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* restore_layout.c: Everything for restored containers that is not pure state

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* scratchpad.c: Scratchpad functions (TODO: more description)

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* The format of the shmlog data structure which i3 development versions use by

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
*/

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* startup.c: Startup notification code. Ensures a startup notification context
@ -56,7 +56,7 @@ void startup_sequence_rename_workspace(const char *old_name, const char *new_nam
* Gets the stored startup sequence for the _NET_STARTUP_ID of a given window.
*
*/
struct Startup_Sequence *startup_sequence_get(i3Window *cwindow,
struct Startup_Sequence *startup_sequence_get(const i3Window *cwindow,
xcb_get_property_reply_t *startup_id_reply, bool ignore_mapped_leader);
/**
@ -68,10 +68,10 @@ struct Startup_Sequence *startup_sequence_get(i3Window *cwindow,
* Returns NULL otherwise.
*
*/
char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply);
char *startup_workspace_for_window(const i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply);
/**
* Deletes the startup sequence for a window if it exists.
*
*/
void startup_sequence_delete_by_window(i3Window *win);
void startup_sequence_delete_by_window(const i3Window *win);

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* sync.c: i3 sync protocol: https://i3wm.org/docs/testsuite.html#i3_sync

View File

@ -1,7 +1,7 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* tiling_drag.h: Reposition tiled windows by dragging.
@ -9,8 +9,6 @@
*/
#pragma once
#include "all.h"
/**
* Tiling drag initiation modes.
*/

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