Merge branch 'next' into stable

This commit is contained in:
Michael Stapelberg 2021-10-19 08:38:14 +02:00
commit 533b76378a
110 changed files with 3399 additions and 1022 deletions

View File

@ -4,7 +4,7 @@
Note that bug reports and feature requests for related projects should be filed in the corresponding repositories for [i3status](https://github.com/i3/i3status) and [i3lock](https://github.com/i3/i3lock).
## i3 bug reports and feature requests
## i3 bug reports
1. Read the [debugging instructions](https://i3wm.org/docs/debugging.html).
2. Make sure you include a link to your logfile in your report (section 3).
@ -18,6 +18,20 @@ Note that bug reports and feature requests for related projects should be filed
encountered the issue you are about to report while using a compositor,
please try reproducing it without a compositor.
## i3 feature requests
1. Read the [project goals](https://i3wm.org) on the website and make sure that
they are compatible with the feature you want to suggest.
2. We are generally happy with the current feature set of i3 and instead focus
on maintenance such as stability and fixing bugs. New features will rarely
be considered if they require additional configuration and/or commands, or
if they add significant complexity (either through the exposed configuration
or mental complexity) to the project.
3. Explain in detail what problem the feature addresses and why existing
features fall short.
4. Consider whether the feature could instead be implemented using the
[IPC](https://i3wm.org/docs/ipc.html) or other external tooling.
## Pull requests
* Before sending a pull request for new features, please check with us that the

View File

@ -3,7 +3,12 @@ PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATI
-->
## I'm submitting a…
<!-- Please check one of the following options with "x" -->
<!--
Check one of the following options with "x".
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>
[ ] Bug
[ ] Feature Request

4
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,4 @@
contact_links:
- 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

@ -8,7 +8,7 @@ PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATI
-->
## I'm submitting a…
<!-- Please check one of the following options with "x" -->
<!-- Check one of the following options with "x" -->
<pre>
[ ] Bug
[x] Feature Request
@ -28,6 +28,15 @@ 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.

94
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,94 @@
name: GitHub Actions
on:
push:
branches: [ next, master, actions ]
pull_request:
branches: [ next ]
jobs:
build:
name: build and test
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
env:
CC: ${{ matrix.compiler }}
DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
DOCKER_EMAIL: ${{ secrets.DOCKER_EMAIL }}
DOCKER_USER: ${{ secrets.DOCKER_USER }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
BALTO_TOKEN: ${{ secrets.BALTO_TOKEN }}
steps:
- uses: actions/checkout@v2
- 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
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::"
- name: verify safe wrapper functions are used
run: |
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-safe-wrappers.sh
- name: verify code formatting
run: |
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-formatting.sh
- 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'
- name: check spelling
run: |
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-spelling.pl
- name: run i3 tests
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
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 && ninja -v dist'
- name: build Debian packages
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
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
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
run: |
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/docs.sh
- name: push docs to GitHub pages
run: |
./travis/skip-pkg.sh || travis/deploy-github-pages.sh

View File

@ -1,71 +0,0 @@
dist: trusty
services:
- docker
language: c
compiler:
- gcc
- clang
addons:
apt:
packages:
# For https support in HTTP::Tiny.
- libio-socket-ssl-perl
env:
global:
- BASENAME="i3wm/travis-base:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base.Dockerfile)"
- BASENAME_386="i3wm/travis-base-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-386.Dockerfile)"
- BASENAME_UBUNTU="i3wm/travis-base-ubuntu:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu.Dockerfile)"
- BASENAME_UBUNTU_386="i3wm/travis-base-ubuntu-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu-386.Dockerfile)"
- secure: "B5IICA8MPx/FKaB50rTPqL8P1NU+Q0yuWl+lElL4+a9xSyLikfm3NzUPHoVwx8lNw2AVK6br7p0OmF7vMFjqAgrgc1cajTtEae5uFRKNUrWLpXM046YgNEYLLIHsQOjInxE+S4O6EFVzsUqsu8aeo2Xhq4sm4iUocG7e5isYgYo=" # DOCKER_PASS
- secure: "EIvrq8PG7lRjidppG0RCv4F0X4GP3DT9F5+ixVuGPfhK/hZT3jYC2AVY9G+NnUcXVwQEpW92rlqpftQ/qZ13FoyWokC8ZyoyD06fr5FPCfoFF3OczZwAJzZYkObI/hE9+/hXcylx/Os6N4INd2My1ntGk3JPsWL9riopod5EjSg=" # DOCKER_EMAIL
- secure: "hvhBunS4xXTgnIOsk/BPT7I7FrJhvVwCSt5PfxxvMqNaztOJI9BuK7ZrZ5Cy38KyHwlh3VHAH5AaCygJcPauoSQCV3bpnlbaWn3ruq2F0Q697Q5uNf73liXzyUqb9/Zvfvge4y4WWOhP5tVz1C6ZBe/NfhU7pqKLMA+6ads+99c=" # DOCKER_USER
- secure: "uJuuefmnJUuEH15ZD8xQilibx7EeBvMHBLoIZ8bgGHeleEImBD0XbD1ypvhYJKpviOmw5BkZmc9bVO8DGDEHYbSlIa2xDlF6vGrwgCEaxcMIhOAhv+dW9C/maJVieLOEPM01/fK2qdKESZaLvlopkWmxZwDyMObI9L7AMW9zQD8=" # BINTRAY_USER
- secure: "L3aPSNLySPXtWCW+xf8h/AAdquwNgxyTQpYOwexJmTPav82Qx8uQlp1yJkUmt+a+FLZDFfQeMivaHq0311RvuQVmkAJx49DjaddrwqOJut2UPsoVDn1WeuAcSHIXOq/0H+zgFMr/PGY0HXIsw1mTMhgheGJNqg09BvYWROCEAcA=" # BINTRAY_KEY
- secure: "sBMVn4C/WRWgoAytEFGx4CC5O55Q63h02AcuBnb1jXcBm0RenoBpzUPtxSseJwDPUA1o/UkuEDDjm3PosT5NF+dvED01VDFMsPVE11K0u6+avYy3jYXqyUEDW3G2o6Wo/2aqNjmd++8jskBdS9+Cx9gaFbgxfzSp0Yfu3oJm/4c=" # GH_TOKEN
install:
- if [ -a .git/shallow ]; then git fetch --unshallow; fi
- docker pull ${BASENAME} || ./travis/docker-build-and-push.sh ${BASENAME} travis/travis-base.Dockerfile
- ./travis/skip-pkg.sh || docker pull ${BASENAME_UBUNTU} || ./travis/docker-build-and-push.sh ${BASENAME_UBUNTU} travis/travis-base-ubuntu.Dockerfile
- ./travis/skip-pkg.sh || docker pull ${BASENAME_386} || ./travis/docker-build-and-push.sh ${BASENAME_386} travis/travis-base-386.Dockerfile
- ./travis/skip-pkg.sh || docker pull ${BASENAME_UBUNTU_386} || ./travis/docker-build-and-push.sh ${BASENAME_UBUNTU_386} travis/travis-base-ubuntu-386.Dockerfile
script:
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-safe-wrappers.sh
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-formatting.sh
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${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'
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-spelling.pl
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} ./travis/run-tests.sh
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} /bin/sh -c 'rm -rf distbuild; mkdir distbuild && cd distbuild && meson .. -Ddocs=true -Dmans=true && ninja -v dist'
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/debian-build.sh deb/debian-amd64/DIST
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_UBUNTU} ./travis/debian-build.sh deb/ubuntu-amd64/DIST
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_386} linux32 ./travis/debian-build.sh deb/debian-i386/DIST
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_UBUNTU_386} linux32 ./travis/debian-build.sh deb/ubuntu-i386/DIST
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/docs.sh
- ./travis/skip-pkg.sh || travis/prep-bintray.sh
deploy:
- provider: bintray
file: travis/bintray-autobuild-debian.json
user: $BINTRAY_USER
key: $BINTRAY_KEY
skip_cleanup: true
on:
branch: next
condition: $CC = gcc
- provider: bintray
file: travis/bintray-autobuild-ubuntu.json
user: $BINTRAY_USER
key: $BINTRAY_KEY
skip_cleanup: true
on:
branch: next
condition: $CC = gcc
- provider: script
script: travis/deploy-github-pages.sh
skip_cleanup: true
on:
branch: next
condition: $CC = gcc
after_deploy:
- travis/cleanup-bintray.pl i3-autobuild
- travis/cleanup-bintray.pl i3-autobuild-ubuntu

View File

@ -1,7 +1,7 @@
![Logo](docs/logo-30.png) i3: A tiling window manager
=====================================================
[![Build Status](https://travis-ci.org/i3/i3.svg?branch=next)](https://travis-ci.org/i3/i3)
[![Build Status](https://github.com/i3/i3/actions/workflows/main.yml/badge.svg)](https://github.com/i3/i3/actions/workflows/main.yml)
[![Issue Stats](https://img.shields.io/github/issues/i3/i3.svg)](https://github.com/i3/i3/issues)
[![Pull Request Stats](https://img.shields.io/github/issues-pr/i3/i3.svg)](https://github.com/i3/i3/pulls)

View File

@ -1,25 +0,0 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.19.2 │
└──────────────────────────────┘
This is i3 v4.19. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
This is a bugfix release for v4.19
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• fix release tarball version number and debug log settings
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
Stuart Henderson
-- Michael Stapelberg, 2021-02-27

93
RELEASE-NOTES-4.20 Normal file
View File

@ -0,0 +1,93 @@
┌──────────────────────────────┐
│ Release notes for i3 v4.20 │
└──────────────────────────────┘
This is i3 v4.20. This version is considered stable. All users of i3 are
strongly encouraged to upgrade.
Two long-awaited features have been added:
1. You can now use an “include” directive in your i3 config:
https://i3wm.org/docs/userguide.html#include
2. You can now enable showing window icons in window titlebars:
https://i3wm.org/docs/userguide.html#title_window_icon
In case you notice any issues regarding your background/wallpaper, note:
Some login managers (e.g. gdm) start the X11 server with the -background none
flag. When this flag is set, a background needs to be explicitly set later in
the X11 session, otherwise stale copies of closed windows remain visible on
the X11 root window (symptom looks like “my terminal window is not closing”).
i3 works around this situation by setting a screenshot as background when
starting. Any background you set before starting i3 (e.g. in your Xsession) or
after starting i3 (e.g. via exec statements in the i3 config) will be visible.
A downside of this workaround is that if you have any windows already open in
your X11 session, those will be part of the screenshot.
To fix this issue, starting in v4.20, i3 detects whether the -background none
option is enabled and only then sets a screenshot as background.
┌────────────────────────────┐
│ Changes in i3 v4.20 │
└────────────────────────────┘
• default config: use dex for XDG autostart
• docs/ipc: document scratchpad_state
• ipc: the GET_CONFIG request now returns all included files and their details
• i3-nagbar: position on focused monitor by default
• i3-nagbar: add option to position on primary monitor
• i3bar: use first bar config by default
• i3-dmenu-desktop: ignore duplicate files and directories (fixes crash on NixOS)
• i3-dump-log -f now uses UNIX sockets instead of pthreads. The UNIX socket approach
should be more reliable and also more portable.
• When clicking on a tab, focus its child (like when scrolling), or (if
already focused), focus the tab container (alternatingly).
• Implement the include config directive:
https://i3wm.org/docs/userguide.html#include
• Implement optionally showing window icons in titlebar:
https://i3wm.org/docs/userguide.html#title_window_icon
• Allow for_window to match against WM_CLIENT_MACHINE
• Add %machine placeholder (WM_CLIENT_MACHINE) to title_format
• 'move container|workspace to output': toggle a workspace (or container)
between multiple outputs when multiple output names specified.
• Add 'move container|workspace to output next'
• Add 'all' window matching criterion
• Acquire the WM_Sn selection when starting as required by ICCCM
• Add --replace command line argument to replace an existing WM
• Notify systemd when i3 is ready, allowing other services in a systemd user session
to use i3 as a dependency
┌────────────────────────────┐
│ Bugfixes │
└────────────────────────────┘
• i3bar: properly close file descriptors
• i3bar: properly restart status command after config change
• i3bar: exit with 1 when a wrong command line argument is used
• ipc: return proper signed int for container positions: negative values were
returned as large 32 bits integers
• when initializing new outputs, avoid duplicating workspace numbers
• fix workspaces not moving to assigned output after output becomes available
• fix duplicate bindcode after i3-config-wizard
• fix commented-out rofi call in default i3 config
• clear pixmap before drawing to prevent visual garbage
• fix crash with "layout default"
• send an "output" event on XRandR 1.5 monitor configuration change
┌────────────────────────────┐
│ Thanks! │
└────────────────────────────┘
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
ajakk, Albert Safin, Anaël Beutot, Antoine Martin, Dmitri Goutnik, ekarpp,
Imran Virani, Ingo Bürk, Isaac Garzon, Ivan Milov, Jay Khandkar, j-jzk, Ken
Gilmer, Kjetil Torgrim Homme, lbonn, Michael Stapelberg, Orestis Floros, Ralph
Gutkowski, Romuald Brunet, tomty89, Tristan Giles, Tudor Brindus, Uli
Schlachter, Vincent Bernat, Vladimir Panteleev
-- Michael Stapelberg, 2021-10-19

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
i3-wm (4.19.2-1) unstable; urgency=medium
* New upstream release.
-- Michael Stapelberg <stapelberg@debian.org> Sat, 27 Feb 2021 10:32:17 +0100
i3-wm (4.19-1) unstable; urgency=medium
* New upstream release.

View File

@ -400,8 +400,8 @@ the correct state.
Then, it looks through all bindings and gets the one which matches the received
event.
The bound command is parsed by the cmdparse lexer/parser, see +parse_cmd+ in
+src/cmdparse.y+.
The bound command is parsed by the i3 parser, see +parse_command+ in
+src/commands_parser.c+.
== Manage windows (src/main.c, manage_window() and reparent_window())

View File

@ -83,7 +83,7 @@ $parser->html_header_after_title(
<ul id="nav">
<li><a style="border-bottom: 2px solid #fff" href="/docs">Docs</a></li>
<li><a href="/screenshots">Screens</a></li>
<li><a href="https://www.reddit.com/r/i3wm/">FAQ</a></li>
<li><a href="https://www.github.com/i3/i3/discussions">Get Help</a></li>
<li><a href="/contact">Contact</a></li>
<li><a href="https://bugs.i3wm.org/">Bugs</a></li>
</ul>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -191,7 +191,7 @@ separator_block_width::
is 9 pixels), since the separator line is drawn in the middle.
markup::
A string that indicates how the text of the block should be parsed. Set to
+"pango"+ to use https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup].
+"pango"+ to use https://developer.gnome.org/pango/1.46/[Pango markup].
Set to +"none"+ to not use any markup (default). Pango markup only works
if you use a pango font.

235
docs/ipc
View File

@ -14,7 +14,8 @@ 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
calling +i3 --get-socketpath+.
executing +i3 --get-socketpath+, which will print the path to the standard
output (plus a newline).
All i3 utilities, like +i3-msg+ and +i3-input+ will read the +I3_SOCKET_PATH+
X11 property, stored on the X11 root window.
@ -39,12 +40,12 @@ my $sock = IO::Socket::UNIX->new(Peer => $path);
== Sending messages to i3
To send a message to i3, you have to format in the binary message format which
i3 expects. This format specifies a magic string in the beginning to ensure
the integrity of messages (to prevent follow-up errors). Following the magic
string comes the length of the payload of the message as 32-bit integer, and
the type of the message as 32-bit integer (the integers are not converted, so
they are in native byte order).
To send a message to i3, you have to format it in the binary message format
which i3 expects. This format specifies a magic string in the beginning to
ensure the integrity of messages (to prevent follow-up errors). Following the
magic string comes the length of the payload of the message as a 32-bit
integer, and the type of the message as a 32-bit integer (the integers are not
converted, so they are in native byte order).
The magic string currently is "i3-ipc" and will only be changed when a change
in the IPC API is done which breaks compatibility (we hope that we dont need
@ -96,18 +97,30 @@ $sock->write(format_ipc_command("exit"));
== Receiving replies from i3
Replies from i3 usually consist of a simple string (the length of the string
is the message_length, so you can consider them length-prefixed) which in turn
contain the JSON serialization of a data structure. For example, the
GET_WORKSPACES message returns an array of workspaces (each workspace is a map
with certain attributes).
Each message sent to i3 will cause exactly one reply to be sent in return. The
order of the sent replies will always correspond to the order of the sent
requests. The only exception to this is <<events>>, which (once subscribed to)
may be sent at any time (though never in the middle of another event or reply).
=== Reply format
It is generally safe to send several messages to i3 without first waiting for a
reply for each one (pipelining) -- though, note that depending on the language /
network library you use, writing to the socket without also reading from it may
cause a deadlock due to the socket buffers getting full.
The reply format is identical to the normal message format. There also is
the magic string, then the message length, then the message type and the
payload.
The payload of replies from i3 usually consists of a simple string (the length
of the string is the message_length, so you can consider them length-prefixed),
which in turn contain the JSON serialization of a data structure. For example,
the GET_WORKSPACES message returns an array of workspaces (each workspace is a
map with certain attributes).
Replies currently have a 1:1 correspondence to messages, with the message type
of the reply corresponding to the message type of the message which caused the
reply to be sent.
The following reply types are implemented:
COMMAND (0)::
@ -127,16 +140,30 @@ BAR_CONFIG (6)::
VERSION (7)::
Reply to the GET_VERSION message.
BINDING_MODES (8)::
Reply to the GET_BINDING_MODES message.
Reply to the GET_BINDING_MODES message.
GET_CONFIG (9)::
Reply to the GET_CONFIG message.
TICK (10)::
Reply to the SEND_TICK message.
SYNC (11)::
Reply to the SYNC message.
GET_BINDING_STATE (12)::
Reply to the GET_BINDING_STATE message.
== Messages and replies
[[_command_reply]]
=== COMMAND reply
=== RUN_COMMAND / COMMAND
Run the payload as an https://i3wm.org/docs/userguide.html#list_of_commands[i3
command] (like the commands you can bind to keys).
*Message:*
The message payload is the string containing the command to execute. There is
no JSON encoding or trailing newline.
*Reply:*
The reply consists of a list of serialized maps for each command that was
parsed. Each has the property +success (bool)+ and may also include a
@ -170,7 +197,15 @@ When the specified command cannot be parsed, `success` will be false and
-------------------
[[_workspaces_reply]]
=== WORKSPACES reply
=== GET_WORKSPACES / WORKSPACES
Get the list of current workspaces.
*Message:*
No payload.
*Reply:*
The reply consists of a serialized list of workspaces. Each workspace has the
following properties:
@ -234,7 +269,16 @@ output (string)::
-------------------
[[_subscribe_reply]]
=== SUBSCRIBE reply
=== SUBSCRIBE
Subscribe this IPC connection to the event types specified in the message
payload. See <<events>>.
*Message:*
A JSON-encoded array of event types to subscribe to.
*Reply:*
The reply consists of a single serialized map. The only property is
+success (bool)+, indicating whether the subscription was successful (the
@ -246,7 +290,15 @@ default) or whether a JSON parse error occurred.
-------------------
[[_outputs_reply]]
=== OUTPUTS reply
=== GET_OUTPUTS / OUTPUTS
Get the list of current outputs.
*Message:*
No payload.
*Reply:*
The reply consists of a serialized list of outputs. Each output has the
following properties:
@ -257,7 +309,7 @@ active (boolean)::
Whether this output is currently active (has a valid mode).
primary (boolean)::
Whether this output is currently the primary output.
current_workspace (string)::
current_workspace (string or null)::
The name of the current workspace that is visible on this output. +null+ if
the output is not active.
rect (map)::
@ -293,7 +345,15 @@ rect (map)::
-------------------
[[_tree_reply]]
=== TREE reply
=== GET_TREE / TREE
Get the i3 layout tree.
*Message:*
No payload.
*Reply:*
The reply consists of a serialized tree. Each node in the tree (representing
one container) has at least the properties listed below. While the nodes might
@ -329,7 +389,7 @@ orientation (string)::
"vertical".
THIS FIELD IS OBSOLETE. It is still present, but your code should not
use it. Instead, rely on the layout field.
percent (float)::
percent (float or null)::
The percentage which this container takes in its parent. A value of
+null+ means that the percent property does not make sense for this
container, for example for the root container.
@ -353,14 +413,15 @@ deco_rect (map)::
geometry (map)::
The original geometry the window specified when i3 mapped it. Used when
switching a window to floating mode, for example.
window (integer)::
window (integer or null)::
The X11 window ID of the *actual client window* inside this container.
This field is set to null for split containers or otherwise empty
This field is set to +null+ for split containers or otherwise empty
containers. This ID corresponds to what xwininfo(1) and other
X11-related tools display (usually in hex).
window_properties (map)::
This optional field contains all available X11 window properties from the
following list: *title*, *instance*, *class*, *window_role* and *transient_for*.
following list: *title*, *instance*, *class*, *window_role*, *machine*
and *transient_for*.
window_type (string)::
The window type (_NET_WM_WINDOW_TYPE). Possible values are undefined, normal,
dialog, utility, toolbar, splash, menu, dropdown_menu, popup_menu, tooltip and
@ -386,12 +447,18 @@ fullscreen_mode (integer)::
+1+ (fullscreened on output) or
+2+ (fullscreened globally).
Note that all workspaces are considered fullscreened on their respective output.
floating (string)::
Floating state of container.
Can be either "auto_on", "auto_off", "user_on" or "user_off"
nodes (array of node)::
The tiling (i.e. non-floating) child containers of this node.
floating_nodes (array of node)::
The floating child containers of this node. Only non-empty on nodes with
type +workspace+.
scratchpad_state (string)::
Whether the window is not in the scratchpad ("none"), freshly moved to
the scratchpad but not yet resized ("fresh") or moved to the scratchpad
and resized ("changed").
Please note that in the following example, I have left out some keys/values
which are not relevant for the type of the node. Otherwise, the example would
@ -532,7 +599,15 @@ JSON dump:
-----------------------
[[_marks_reply]]
=== MARKS reply
=== GET_MARKS / MARKS
Gets the names of all currently set marks.
*Message:*
No payload.
*Reply:*
The reply consists of a single array of strings for each container that has a
mark. A mark can only be set on one container, so the array is unique.
@ -541,7 +616,15 @@ The order of that array is undefined.
If no window has a mark the response will be the empty array [].
[[_bar_config_reply]]
=== BAR_CONFIG reply
=== GET_BAR_CONFIG / BAR_CONFIG
Gets the specified bar configuration or the names of all bar configurations if payload is empty.
*Message:*
No payload, or the ID of the bar whose configuration to retrieve.
*Reply:*
This can be used by third-party workspace bars (especially i3bar, but others
are free to implement compatible alternatives) to get the +bar+ block
@ -641,7 +724,15 @@ binding_mode_text/binding_mode_bg/binding_mode_border::
--------------
[[_version_reply]]
=== VERSION reply
=== GET_VERSION / VERSION
Gets the i3 version.
*Message:*
No payload.
*Reply:*
The reply consists of a single JSON dictionary with the following keys:
@ -674,7 +765,15 @@ loaded_config_file_name (string)::
-------------------
[[_binding_modes_reply]]
=== BINDING_MODES reply
=== GET_BINDING_MODES / BINDING_MODES
Gets the names of all currently configured binding modes.
*Message:*
No payload.
*Reply:*
The reply consists of an array of all currently configured binding modes.
@ -684,18 +783,66 @@ The reply consists of an array of all currently configured binding modes.
---------------------
[[_config_reply]]
=== CONFIG reply
=== GET_CONFIG / CONFIG
The config reply is a map which currently only contains the "config" member,
which is a string containing the config file as loaded by i3 most recently.
Returns the last loaded i3 config.
*Message:*
No payload.
*Reply:*
The config reply is a map which contains the following fields:
config (string)::
The top-level config file contents that i3 has loaded most recently.
This field is kept for backwards compatibility. See +included_configs+
instead.
included_configs (array of maps)::
i3 adds one entry to this array for each config file it loads, in
order. The first entrys +raw_contents+ are identical to the +config+
field.
Each +included_configs+ entry contains the following fields
path (string)::
Absolute path name to the config file that i3 loaded.
raw_contents (string)::
The raw contents of the file as i3 read them.
variable_replaced_contents (string)::
The contents of the file after i3 replaced all variables. This is useful
for debugging variable replacement.
*Example:*
-------------------
{ "config": "font pango:monospace 8\nbindsym Mod4+q exit\n" }
{
"config": "include font.cfg\n",
"included_configs": [
{
"path": "/home/michael/configfiles/i3/config",
"raw_contents": "include font.cfg\n",
"variable_replaced_contents": "include font.cfg\n"
},
{
"path": "/home/michael/configfiles/i3/font.cfg",
"raw_contents": "set $font pango:monospace 8\nfont $font",
"variable_replaced_contents": "set pango:monospace 8 pango:monospace 8\nfont pango:monospace 8\n"
}
],
}
-------------------
[[_tick_reply]]
=== TICK reply
=== SEND_TICK / TICK
Sends a tick event with the specified payload.
*Message:*
The payload of the tick event to send to IPC event listeners.
*Reply:*
The reply is a map containing the "success" member. After the reply was
received, the tick event has been written to all IPC connections which subscribe
@ -709,7 +856,15 @@ events generated prior to the +SEND_TICK+ message (happened-before relation).
-------------------
[[_sync_reply]]
=== SYNC reply
=== SYNC
Sends an i3 sync event with the specified random value to the specified window.
*Message:*
A JSON-encoded map with the properties "rnd" and "window" (both integer).
*Reply:*
The reply is a map containing the "success" member. After the reply was
received, the https://i3wm.org/docs/testsuite.html#i3_sync[i3 sync message] was
@ -721,7 +876,15 @@ responded to.
-------------------
[[_binding_state_reply]]
=== GET_BINDING_STATE reply
=== GET_BINDING_STATE
Request the current binding state, i.e. the currently active binding mode name.
*Message:*
No payload.
*Reply:*
The binding_state reply is a map which currently only contains the "name"
member, which is the name of the currently active binding mode as a string.

View File

@ -185,9 +185,9 @@ Therefore, if you just start Emacs via dmenu, it will not get swallowed by that
container. Only if you start Emacs with the proper instance name (+emacs24
--name notmuch+), it will get swallowed.
You can match on "class", "instance", "window_role" and "title". All values are
case-sensitive regular expressions (PCRE). Use +xprop(1)+ and click into a
window to see its properties:
You can match on "class", "instance", "window_role", "title" and "machine". All
values are case-sensitive regular expressions (PCRE). Use +xprop(1)+ and click
into a window to see its properties:
--------------------------------------------------------------------------------
$ xprop

View File

@ -481,7 +481,7 @@ an i3 crash resulting in the testcase being unable to communicate with i3 via
IPC anymore.
[[i3_sync]]
== Appendix A: The i3 sync protocol
== Appendix A: The I3_SYNC protocol
Consider the following situation: You open two windows in your testcase, then
you use +focus left+ and want to verify that the X11 focus has been updated
@ -499,9 +499,9 @@ is($x->input_focus, $left->id, 'left window focused');
However, the test fails. Sometimes. Apparently, there is a race condition in
your test. If you think about it, this is because you are using two different
pieces of software: You tell i3 to update focus, i3 confirms that, and then you
ask X11 to give you the current focus. There is a certain time i3 needs to
update the X11 state. If the testcase gets CPU time before X11 processed i3's
requests, the test will fail.
ask X11 to give you the current focus. There is a certain time that the X11
server needs to process the requests from i3. If the testcase's request for the
input focus is processed before i3's requests, the test will fail.
image::i3-sync.png["Diagram of the race condition", title="Diagram of the race condition"]
@ -531,10 +531,10 @@ less robust.
The real solution for this problem is a mechanism which I call "the i3 sync
protocol". The idea is to send a request (which does not modify state) via X11
to i3 which will then be answered. Due to the request's position in the event
queue (*after* all previous events), you can be sure that by the time you
receive the reply, all other events have been dealt with by i3 (and, more
importantly, X11).
to i3 which will then be answered, again via X11. Because this answer is
generated via an X11 request, it will be sent to the X11 server *after* all
previous requests. Thus, you can be sure that by the time you receive the reply,
all other events have been dealt with by i3 (and, more importantly, X11).
image::i3-sync-working.png["Diagram of the i3 sync solution", title="Diagram of the i3 sync solution"]
@ -569,7 +569,35 @@ i3 will then reply with the same ClientMessage, sent to the window specified in
request. You should use a random value in +data[1]+ and check that you received
the same one when getting the reply.
== Appendix B: Socket activation
== Appendix B: The sync IPC command
The above I3_SYNC protocol allows to synchronise with i3. However, it is not
enough for tests that also involve i3bar: There might still be messages from
i3bar in-flight even after synchronising with i3. Thus, there also exists a sync
IPC command, that is however not meant to be used directly. Instead, i3bar uses
it for implementing the I3_SYNC protocol.
The intended usage works like this:
1. You send an I3_SYNC message to i3bar's window. See <<i3_sync>>.
2. i3bar sends a SYNC IPC command to i3 with payload
+{"window":your-window-here,"rnd":your-random-value}+.
3. i3 reacts to this IPC command as if it received an I3_SYNC request via X11.
This protocol is used, for example, in t/525-i3bar-mouse-bindings.t: A mouse
button press on i3bar is triggered. i3bar reacts to this by sending IPC commands
to i3.
The necessary synchronisation is achieved by sending an I3_SYNC event to i3bar:
Because i3bar reacts with a sync IPC command to i3, all previous IPC commands from
i3bar will be handled first. Because i3 reacts via X11, all previous X11
requests from i3 will be handled by the X11 server first.
The actual test also has to sync with i3 first due to how X11 handling works.
For more details, refer to the documentation for +XAllowEvents+ with mode
+ReplayPointer+.
== Appendix C: Socket activation
Socket activation is a mechanism which was made popular by systemd, an init
replacement. It basically describes creating a listening socket before starting

View File

@ -3,9 +3,9 @@ i3 Users Guide
Michael Stapelberg <michael@i3wm.org>
This document contains all the information you need to configure and use the i3
window manager. If it does not, please check https://www.reddit.com/r/i3wm/
first, then contact us on IRC (preferred) or post your question(s) on the
mailing list.
window manager. If it does not you can https://i3wm.org/contact/[contact us] on
https://github.com/i3/i3/discussions[GitHub Discussions], IRC, or the mailing
list.
== Default keybindings
@ -53,12 +53,23 @@ existing window (rotated displays).
image:two_terminals.png[Two terminals]
To move the focus between the two terminals, you can use the direction keys
which you might know from the editor +vi+. However, in i3, your homerow is used
for these keys (in +vi+, the keys are shifted to the left by one for
compatibility with most keyboard layouts). Therefore, +$mod+j+ is left, +$mod+k+
is down, +$mod+l+ is up and `$mod+;` is right. So, to switch between the
terminals, use +$mod+k+ or +$mod+l+. Of course, you can also use the arrow keys.
To move the focus between the two terminals, you can use the arrow keys. For
convenience, the arrows are also available directly on the
https://en.wikipedia.org/wiki/Touch_typing[keyboards home row] underneath your
right hand:
|===
| `$mod+j` | left
| `$mod+k` | down
| `$mod+l` | up
| `$mod+;` | right
|===
Note that this differs by one key from the popular text editor `vi`, which was
https://twitter.com/hillelogram/status/1326600125569961991[developed on an
ADM-3A terminal and therefore uses `hjkl` instead of `jkl;`] -- i3s default is
meant to require minimal finger movement, but some `vi` users change their i3
config for consistency.
At the moment, your workspace is split (it contains two terminals) in a
specific direction (horizontal by default). Every window can be split
@ -67,7 +78,7 @@ horizontally or vertically again, just like the workspace. The terminology is
or browser) and "split container" for containers that consist of one or more
windows.
TODO: picture of the tree
//TODO: picture of the tree
To split a window vertically, press +$mod+v+ before you create the new window.
To split it horizontally, press +$mod+h+.
@ -308,6 +319,90 @@ include the following line in your config file:
# i3 config file (v4)
---------------------
[[include]]
=== Include directive
Since i3 v4.20, it is possible to include other configuration files from your i3
configuration.
*Syntax*:
-----------------
include <pattern>
-----------------
i3 expands `pattern` using shell-like word expansion, specifically using the
https://manpages.debian.org/wordexp.3[`wordexp(3)` C standard library function].
*Examples*:
--------------------------------------------------------------------------------
# Tilde expands to the users home directory:
include ~/.config/i3/assignments.conf
# Environment variables are expanded:
include $HOME/.config/i3/assignments.conf
# Wildcards are expanded:
include ~/.config/i3/config.d/*.conf
# Command substitution:
include ~/.config/i3/`hostname`.conf
# i3 loads each path only once, so including the i3 config will not result
# in an endless loop, but in an error:
include ~/.config/i3/config
# i3 changes the working directory while parsing a config file
# so that relative paths are interpreted relative to the directory
# of the config file that contains the path:
include assignments.conf
--------------------------------------------------------------------------------
If a specified file cannot be read, for example because of a lack of file
permissions, or because of a dangling symlink, i3 will report an error and
continue processing your remaining configuration.
To list all loaded configuration files, run `i3 --moreversion`:
--------------------------------------------------------------------------------
% i3 --moreversion
Binary i3 version: 4.19.2-87-gfcae64f7+ © 2009 Michael Stapelberg and contributors
Running i3 version: 4.19.2-87-gfcae64f7+ (pid 963940)
Loaded i3 config:
/tmp/i3.cfg (main) (last modified: 2021-05-13T16:42:31 CEST, 463 seconds ago)
/tmp/included.cfg (included) (last modified: 2021-05-13T16:42:43 CEST, 451 seconds ago)
/tmp/another.cfg (included) (last modified: 2021-05-13T16:42:46 CEST, 448 seconds ago)
--------------------------------------------------------------------------------
Variables are shared between all config files, but beware of the following limitation:
* You can define a variable and use it within an included file.
* You cannot use (in the parent file) a variable that was defined within an included file.
This is a technical limitation: variable expansion happens in a separate stage
before parsing include directives.
Conceptually, included files can only add to the configuration, not undo the
effects of already-processed configuration. For example, you can only add new
key bindings, not overwrite or remove existing key bindings. This means:
* The `include` directive is suitable for organizing large configurations into
separate files, possibly selecting files based on conditionals.
* The `include` directive is not suitable for expressing “use the default
configuration with the following changes”. For that case, we still recommend
copying and modifying the default config.
[NOTE]
====
Implementation-wise, i3 does not currently construct one big configuration from
all `include` directives. Instead, i3s config file parser interprets all
configuration directives in its `parse_file()` function. When processing an
`include` configuration directive, the parser recursively calls `parse_file()`.
This means the evaluation order of files forms a tree, or one could say i3 uses
depth-first traversal.
====
=== Comments
It is possible and recommended to use comments in your configuration file to
@ -597,9 +692,10 @@ Default is +left+
title_align left|center|right
---------------------------------------------
[[default_border]]
=== Default border style for new windows
This option determines which border style new windows will have. The default is
This option determines which border style *new* windows will have. The default is
+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.
@ -1863,12 +1959,17 @@ bindsym $mod+x [class="Firefox" window_role="About"] kill
# enable floating mode and move container to workspace 4
for_window [class="^evil-app$"] floating enable, move container to workspace 4
# enable window icons for all windows with extra horizontal padding of 1px
for_window [all] title_window_icon padding 1px
# move all floating windows to the scratchpad
bindsym $mod+x [floating] move scratchpad
------------------------------------
The criteria which are currently implemented are:
all::
Matches all windows. This criterion requires no value.
class::
Compares the window class (the second part of WM_CLASS). Use the
special value +\_\_focused__+ to match all windows having the same window
@ -1885,6 +1986,10 @@ window_type::
Compare the window type (_NET_WM_WINDOW_TYPE). Possible values are
+normal+, +dialog+, +utility+, +toolbar+, +splash+, +menu+, +dropdown_menu+,
+popup_menu+, +tooltip+ and +notification+.
machine::
Compares the name of the machine the client window is running on
(WM_CLIENT_MACHINE). Usually, it is equal to the hostname of the local
machine, but it may differ if remote X11 apps are used.
id::
Compares the X11 window ID, which you can get via +xwininfo+ for example.
title::
@ -1922,9 +2027,9 @@ tiling_from::
tiling are matched. With "user", only windows that the user made tiling
are matched.
The criteria +class+, +instance+, +role+, +title+, +workspace+ and +mark+ are
actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for
information on how to use them.
The criteria +class+, +instance+, +role+, +title+, +workspace+, +machine+ and
+mark+ are actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc
perlre+ for information on how to use them.
[[exec]]
=== Executing applications (exec)
@ -2264,9 +2369,8 @@ exhausting numbered ones and looks for numbered ones after exhausting named ones
See <<move_to_outputs>> for how to move a container/workspace to a different
RandR output.
Workspace names are parsed as
https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup]
by i3bar.
Workspace names are parsed as https://developer.gnome.org/pango/1.46/[Pango
markup] by i3bar.
[[back_and_forth]]
To switch back to the previously focused workspace, use +workspace
@ -2391,15 +2495,18 @@ To move a container to another RandR output (addressed by names like +LVDS1+ or
*Syntax*:
------------------------------------------------------------
move container to output left|right|down|up|current|primary|<output>
move workspace to output left|right|down|up|current|primary|<output>
------------------------------------------------------------
move container to output left|right|down|up|current|primary|next|<output1> [output2]…
move workspace to output left|right|down|up|current|primary|next|<output1> [output2]…
-------------------------------------------------------------------------------------
*Examples*:
--------------------------------------------------------
# Move the current workspace to the next output
# (effectively toggles when you only have two outputs)
bindsym $mod+x move workspace to output right
bindsym $mod+x move workspace to output next
# Cycle this workspace between outputs VGA1 and LVDS1 but not DVI0
bindsym $mod+x move workspace to output VGA1 LVDS1
# Put this window on the presentation output.
bindsym $mod+x move container to output VGA1
@ -2408,6 +2515,11 @@ bindsym $mod+x move container to output VGA1
bindsym $mod+x move container to output primary
--------------------------------------------------------
If you specify more than one output, the container/workspace is cycled through
them: If it is already in one of the outputs of the list, it will move to the
next output in the list. If it is in an output not in the list, it will move to
the first specified output. Non-existing outputs are skipped.
Note that you might not have a primary output configured yet. To do so, run:
-------------------------
xrandr --output <output> --primary
@ -2558,9 +2670,8 @@ unmark irssi
By default, i3 will simply print the X11 window title. Using +title_format+,
this can be customized by setting the format to the desired output. This
directive supports
https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup]
and the following placeholders which will be replaced:
directive supports https://developer.gnome.org/pango/1.46/[Pango markup] and the
following placeholders which will be replaced:
+%title+::
For normal windows, this is the X11 window title (_NET_WM_NAME or WM_NAME
@ -2573,6 +2684,9 @@ and the following placeholders which will be replaced:
+%instance+::
The X11 window instance (first part of WM_CLASS). This corresponds to the
+instance+ criterion, see <<command_criteria>>.
+%machine+::
The X11 name of the machine (WM_CLIENT_MACHINE). This corresponds to the
+machine+ criterion, see <<command_criteria>>.
Using the <<for_window>> directive, you can set the title format for any window
based on <<command_criteria>>.
@ -2594,6 +2708,32 @@ for_window [class=".*"] title_format "<b>%title</b>"
for_window [class="(?i)firefox"] title_format "<span foreground='red'>%title</span>"
-------------------------------------------------------------------------------------
[[title_window_icon]]
=== Window title icon
By default, i3 does not display the window icon in the title bar.
Starting with i3 v4.20, you can optionally enable window icons either for
specific windows or for all windows (using the <<for_window>> directive).
*Syntax*:
-----------------------------
title_window_icon <yes|no>
title_window_icon padding <px>
------------------------------
*Examples*:
-------------------------------------------------------------------------------------
# show the window icon for the focused window to make it stand out
bindsym $mod+p title_window_icon on
# enable window icons for all windows
for_window [all] title_window_icon on
# enable window icons for all windows with extra horizontal padding
for_window [all] title_window_icon padding 3px
-------------------------------------------------------------------------------------
=== Changing border style
To change the border of the current client, you can use +border normal+ to use the normal
@ -2623,10 +2763,16 @@ border 1pixel
bindsym $mod+t border normal 0
# use no window title and a thick border
bindsym $mod+y border pixel 3
# use window title *and* a thick border
bindsym $mod+y border normal 3
# use neither window title nor border
bindsym $mod+u border none
# no border on VLC
for_window [class="vlc"] border none
----------------------------------------------
To change the default for all windows, see the directive <<default_border>>.
[[shmlog]]
=== Enabling shared memory logging

View File

@ -17,6 +17,10 @@ font pango:monospace 8
# text rendering and scalability on retina/hidpi displays (thanks to pango).
#font pango:DejaVu Sans Mono 8
# Start XDG autostart .desktop files using dex. See also
# https://wiki.archlinux.org/index.php/XDG_Autostart
exec --no-startup-id dex --autostart --environment i3
# The combination of xss-lock, nm-applet and pactl is a popular choice, so
# they are included here as an example. Modify as you see fit.

View File

@ -18,6 +18,10 @@ font pango:monospace 8
# text rendering and scalability on retina/hidpi displays (thanks to pango).
#font pango:DejaVu Sans Mono 8
# Start XDG autostart .desktop files using dex. See also
# https://wiki.archlinux.org/index.php/XDG_Autostart
exec --no-startup-id dex --autostart --environment i3
# The combination of xss-lock, nm-applet and pactl is a popular choice, so
# they are included here as an example. Modify as you see fit.

View File

@ -133,7 +133,7 @@ close($enumfh);
open(my $callfh, '>', "GENERATED_${prefix}_call.h");
my $resultname = uc(substr($prefix, 0, 1)) . substr($prefix, 1) . 'ResultIR';
say $callfh '#pragma once';
say $callfh "static void GENERATED_call(const int call_identifier, struct $resultname *result) {";
say $callfh "static void GENERATED_call(Match *current_match, struct stack *stack, const int call_identifier, struct $resultname *result) {";
say $callfh ' switch (call_identifier) {';
my $call_id = 0;
for my $state (@keys) {
@ -150,8 +150,8 @@ for my $state (@keys) {
# calls to 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("$1")/g;
$cmd =~ s/\&([a-z_]+)/get_long("$1")/g;
$cmd =~ s/\$([a-z_]+)/get_string(stack, "$1")/g;
$cmd =~ s/\&([a-z_]+)/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
@ -175,9 +175,9 @@ for my $state (@keys) {
say $callfh '#ifndef TEST_PARSER';
my $real_cmd = $cmd;
if ($real_cmd =~ /\(\)/) {
$real_cmd =~ s/\(/(&current_match, result/;
$real_cmd =~ s/\(/(current_match, result/;
} else {
$real_cmd =~ s/\(/(&current_match, result, /;
$real_cmd =~ s/\(/(current_match, result, /;
}
say $callfh " $real_cmd;";
say $callfh '#else';

View File

@ -154,6 +154,8 @@ find(
},
no_chdir => 1,
follow_fast => 1,
# Ignore any duplicate files and directories and proceed normally:
follow_skip => 2,
},
@searchdirs
);

View File

@ -25,10 +25,10 @@
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#if !defined(__OpenBSD__)
static uint32_t offset_next_write;
#endif
static uint32_t wrap_count;
static i3_shmlog_header *header;
@ -36,12 +36,6 @@ static char *logbuffer,
*walk;
static int ipcfd = -1;
static volatile bool interrupted = false;
static void sighandler(int signal) {
interrupted = true;
}
static void disable_shmlog(void) {
const char *disablecmd = "debuglog off; shmlog off";
if (ipc_send_message(ipcfd, strlen(disablecmd),
@ -188,22 +182,25 @@ int main(int argc, char *argv[]) {
/* NB: While we must never write, we need O_RDWR for the pthread condvar. */
int logbuffer_shm = shm_open(shmname, O_RDWR, 0);
if (logbuffer_shm == -1)
if (logbuffer_shm == -1) {
err(EXIT_FAILURE, "Could not shm_open SHM segment for the i3 log (%s)", shmname);
}
if (fstat(logbuffer_shm, &statbuf) != 0)
if (fstat(logbuffer_shm, &statbuf) != 0) {
err(EXIT_FAILURE, "stat(%s)", shmname);
}
/* NB: While we must never write, we need PROT_WRITE for the pthread condvar. */
logbuffer = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, logbuffer_shm, 0);
if (logbuffer == MAP_FAILED)
logbuffer = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, logbuffer_shm, 0);
if (logbuffer == MAP_FAILED) {
err(EXIT_FAILURE, "Could not mmap SHM segment for the i3 log");
}
header = (i3_shmlog_header *)logbuffer;
if (verbose)
if (verbose) {
printf("next_write = %d, last_wrap = %d, logbuffer_size = %d, shmname = %s\n",
header->offset_next_write, header->offset_last_wrap, header->size, shmname);
}
free(shmname);
walk = logbuffer + header->offset_next_write;
@ -233,25 +230,38 @@ int main(int argc, char *argv[]) {
return 0;
}
/* Handle SIGINT gracefully to invoke atexit handlers, if any. */
struct sigaction action;
action.sa_handler = sighandler;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
sigaction(SIGINT, &action, NULL);
char *log_stream_socket_path = root_atom_contents("I3_LOG_STREAM_SOCKET_PATH", NULL, 0);
if (log_stream_socket_path == NULL) {
errx(EXIT_FAILURE, "could not determine i3 log stream socket path: possible i3-dump-log and i3 version mismatch");
}
/* Since pthread_cond_wait() expects a mutex, we need to provide one.
* To not lock i3 (thats bad, mhkay?) we just define one outside of
* the shared memory. */
pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&dummy_mutex);
while (!interrupted) {
pthread_cond_wait(&(header->condvar), &dummy_mutex);
/* If this was not a spurious wakeup, print the new lines. */
if (header->offset_next_write != offset_next_write) {
offset_next_write = header->offset_next_write;
print_till_end();
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd == -1) {
err(EXIT_FAILURE, "Could not create socket");
}
(void)fcntl(sockfd, F_SETFD, FD_CLOEXEC);
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
strncpy(addr.sun_path, log_stream_socket_path, sizeof(addr.sun_path) - 1);
if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
err(EXIT_FAILURE, "Could not connect to i3 on socket %s", log_stream_socket_path);
}
/* Same size as the buffer used in log.c vlog(): */
char buf[4096];
for (;;) {
const int n = read(sockfd, buf, sizeof(buf));
if (n == -1) {
err(EXIT_FAILURE, "read(log-stream-socket):");
}
if (n == 0) {
exit(0); /* i3 closed the socket */
}
buf[n] = '\0';
swrite(STDOUT_FILENO, buf, n);
}
#endif

View File

@ -156,6 +156,7 @@ int main(int argc, char *argv[]) {
char *payload = NULL;
bool quiet = false;
bool monitor = false;
bool raw_reply = false;
static struct option long_options[] = {
{"socket", required_argument, 0, 's'},
@ -164,9 +165,10 @@ int main(int argc, char *argv[]) {
{"quiet", no_argument, 0, 'q'},
{"monitor", no_argument, 0, 'm'},
{"help", no_argument, 0, 'h'},
{"raw", no_argument, 0, 'r'},
{0, 0, 0, 0}};
char *options_string = "s:t:vhqm";
char *options_string = "s:t:vhqmr";
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
if (o == 's') {
@ -217,6 +219,8 @@ int main(int argc, char *argv[]) {
return 0;
} else if (o == '?') {
exit(EXIT_FAILURE);
} else if (o == 'r') {
raw_reply = true;
}
}
@ -262,32 +266,38 @@ int main(int argc, char *argv[]) {
/* 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) {
yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
yajl_free(handle);
if (!raw_reply) {
yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
yajl_free(handle);
switch (state) {
case yajl_status_ok:
break;
case yajl_status_client_canceled:
case yajl_status_error:
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
switch (state) {
case yajl_status_ok:
break;
case yajl_status_client_canceled:
case yajl_status_error:
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
}
}
if (!quiet) {
if (!quiet || raw_reply) {
printf("%.*s\n", reply_length, reply);
}
} else if (reply_type == I3_IPC_REPLY_TYPE_CONFIG) {
yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
yajl_free(handle);
if (raw_reply) {
printf("%.*s\n", reply_length, reply);
} else {
yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
yajl_free(handle);
switch (state) {
case yajl_status_ok:
break;
case yajl_status_client_canceled:
case yajl_status_error:
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
switch (state) {
case yajl_status_ok:
break;
case yajl_status_client_canceled:
case yajl_status_error:
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
}
}
} else if (reply_type == I3_IPC_REPLY_TYPE_SUBSCRIBE) {
do {

View File

@ -266,13 +266,9 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
}
/**
* Return the position and size the i3-nagbar window should use.
* This will be the primary output or a fallback if it cannot be determined.
* Tries to position the rectangle on the primary output.
*/
static xcb_rectangle_t get_window_position(void) {
/* Default values if we cannot determine the primary output or its CRTC info. */
xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + 2 * MSG_PADDING + BAR_BORDER};
static void set_window_position_primary(xcb_rectangle_t *result) {
xcb_randr_get_screen_resources_current_cookie_t rcookie = xcb_randr_get_screen_resources_current(conn, root);
xcb_randr_get_output_primary_cookie_t pcookie = xcb_randr_get_output_primary(conn, root);
@ -314,14 +310,61 @@ static xcb_rectangle_t get_window_position(void) {
goto free_resources;
}
result.x = crtc->x;
result.y = crtc->y;
result->x = crtc->x;
result->y = crtc->y;
goto free_resources;
free_resources:
free(res);
free(primary);
return result;
}
/**
* Tries to position the rectangle on the output with input focus.
* If unsuccessful, try to position on primary output.
*/
static void set_window_position_focus(xcb_rectangle_t *result) {
bool success = false;
xcb_get_input_focus_reply_t *input_focus = NULL;
xcb_get_geometry_reply_t *geometry = NULL;
xcb_translate_coordinates_reply_t *coordinates = NULL;
/* To avoid the input window disappearing while determining its position */
xcb_grab_server(conn);
input_focus = xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL);
if (input_focus == NULL || input_focus->focus == XCB_NONE) {
LOG("Failed to receive the current input focus or no window has the input focus right now.\n");
goto free_resources;
}
geometry = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, input_focus->focus), NULL);
if (geometry == NULL) {
LOG("Failed to received window geometry.\n");
goto free_resources;
}
coordinates = xcb_translate_coordinates_reply(
conn, xcb_translate_coordinates(conn, input_focus->focus, root, geometry->x, geometry->y), NULL);
if (coordinates == NULL) {
LOG("Failed to translate coordinates.\n");
goto free_resources;
}
LOG("Found current focus at x = %i / y = %i.\n", coordinates->dst_x, coordinates->dst_y);
result->x = coordinates->dst_x;
result->y = coordinates->dst_y;
success = true;
free_resources:
xcb_ungrab_server(conn);
free(input_focus);
free(coordinates);
free(geometry);
if (!success) {
LOG("Could not position on focused output, trying to position on primary output.\n");
set_window_position_primary(result);
}
}
int main(int argc, char *argv[]) {
@ -360,6 +403,7 @@ int main(int argc, char *argv[]) {
argv0 = argv[0];
bool position_on_primary = false;
char *pattern = sstrdup("pango:monospace 8");
int o, option_index = 0;
enum { TYPE_ERROR = 0,
@ -373,9 +417,10 @@ int main(int argc, char *argv[]) {
{"help", no_argument, 0, 'h'},
{"message", required_argument, 0, 'm'},
{"type", required_argument, 0, 't'},
{"primary", no_argument, 0, 'p'},
{0, 0, 0, 0}};
char *options_string = "b:B:f:m:t:vh";
char *options_string = "b:B:f:m:t:vhp";
prompt = i3string_from_utf8("Please do not run this program.");
@ -399,8 +444,11 @@ int main(int argc, char *argv[]) {
case 'h':
free(pattern);
printf("i3-nagbar " I3_VERSION "\n");
printf("i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
printf("i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v] [-p]\n");
return 0;
case 'p':
position_on_primary = true;
break;
case 'b':
case 'B':
buttons = srealloc(buttons, sizeof(button_t) * (buttoncnt + 1));
@ -464,7 +512,13 @@ int main(int argc, char *argv[]) {
err(EXIT_FAILURE, "pledge");
#endif
xcb_rectangle_t win_pos = get_window_position();
/* 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) {
set_window_position_primary(&win_pos);
} else {
set_window_position_focus(&win_pos);
}
xcb_cursor_context_t *cursor_ctx;
if (xcb_cursor_context_new(conn, root_screen, &cursor_ctx) < 0) {

View File

@ -8,7 +8,13 @@
# We welcome patches that add distribution-specific mechanisms to find the
# preferred terminal emulator. On Debian, there is the x-terminal-emulator
# symlink for example.
for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper; do
#
# Invariants:
# 1. $TERMINAL must come first
# 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; do
if command -v "$terminal" > /dev/null 2>&1; then
exec "$terminal" "$@"
fi

View File

@ -42,6 +42,12 @@ typedef struct {
bool click_events_init;
} i3bar_child;
/*
* Remove all blocks from the given statusline.
* If free_resources is set, the fields of each status block will be free'd.
*/
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

View File

@ -74,6 +74,13 @@ extern config_t config;
*/
void parse_config_json(char *json);
/**
* Start parsing 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);
/**
* free()s the color strings as soon as they are not needed anymore.
*

View File

@ -18,7 +18,7 @@
* socket_path must be a valid path to the ipc_socket of i3
*
*/
int init_connection(const char *socket_path);
void init_connection(const char *socket_path);
/*
* Destroy the connection to i3.

View File

@ -27,7 +27,7 @@
#include <yajl/yajl_parse.h>
/* Global variables for child_*() */
i3bar_child 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)
@ -66,7 +66,7 @@ int child_stdin;
* Remove all blocks from the given statusline.
* If free_resources is set, the fields of each status block will be free'd.
*/
static void clear_statusline(struct statusline_head *head, bool free_resources) {
void clear_statusline(struct statusline_head *head, bool free_resources) {
struct status_block *first;
while (!TAILQ_EMPTY(head)) {
first = TAILQ_FIRST(head);
@ -139,6 +139,10 @@ 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;
}
if (child_sig != NULL) {

View File

@ -184,7 +184,6 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
if (!strcmp(cur_key, "status_command")) {
DLOG("command = %.*s\n", len, val);
FREE(config.command);
sasprintf(&config.command, "%.*s", len, val);
return 1;
}
@ -367,14 +366,12 @@ static yajl_callbacks outputs_callbacks = {
*
*/
void parse_config_json(char *json) {
yajl_handle handle;
yajl_status state;
handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
TAILQ_INIT(&(config.bindings));
TAILQ_INIT(&(config.tray_outputs));
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
yajl_status state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
/* FIXME: Proper error handling for JSON parsing */
switch (state) {
@ -390,6 +387,25 @@ void parse_config_json(char *json) {
yajl_free(handle);
}
static int i3bar_config_string_cb(void *params_, const unsigned char *val, size_t _len) {
sasprintf(&config.bar_id, "%.*s", (int)_len, val);
return 0; /* Stop parsing */
}
/*
* Start parsing 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) {
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_free(handle);
}
/*
* free()s the color strings as soon as they are not needed anymore.
*

View File

@ -85,6 +85,20 @@ static void got_output_reply(char *reply) {
*
*/
static void got_bar_config(char *reply) {
if (!config.bar_id) {
DLOG("Received bar list \"%s\"\n", reply);
parse_get_first_i3bar_config(reply);
if (!config.bar_id) {
ELOG("No bar configuration found, please configure a bar block in your i3 config file.\n");
exit(EXIT_FAILURE);
}
LOG("Using first bar config: %s. Use --bar_id to manually select a different bar configuration.\n", config.bar_id);
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id);
return;
}
DLOG("Received bar config \"%s\"\n", reply);
/* We initiate the main function by requesting infos about the outputs and
* workspaces. Everything else (creating the bars, showing the right workspace-
@ -141,9 +155,6 @@ static void got_workspace_event(char *event) {
static void got_output_event(char *event) {
DLOG("Got output event!\n");
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
if (!config.disable_ws) {
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
}
}
/*
@ -156,6 +167,15 @@ static void got_mode_event(char *event) {
draw_bars(false);
}
static bool strings_differ(char *a, char *b) {
const bool a_null = (a == NULL);
const bool b_null = (b == NULL);
if (a_null != b_null) {
return true;
}
return strcmp(a, b) != 0;
}
/*
* Called, when a barconfig_update event arrives (i.e. i3 changed the bar hidden_state or mode)
*
@ -176,8 +196,11 @@ static void got_bar_config_update(char *event) {
/* update the configuration with the received settings */
DLOG("Received bar config update \"%s\"\n", event);
char *old_command = config.command ? sstrdup(config.command) : NULL;
char *old_command = config.command;
config.command = NULL;
bar_display_mode_t old_mode = config.hide_on_modifier;
parse_config_json(event);
if (old_mode != config.hide_on_modifier) {
reconfig_windows(true);
@ -188,8 +211,9 @@ static void got_bar_config_update(char *event) {
init_colors(&(config.colors));
/* restart status command process */
if (old_command && strcmp(old_command, config.command) != 0) {
if (strings_differ(old_command, config.command)) {
kill_child();
clear_statusline(&statusline_head, true);
start_child(config.command);
}
free(old_command);
@ -328,13 +352,12 @@ int i3_send_msg(uint32_t type, const char *payload) {
* socket_path must be a valid path to the ipc_socket of i3
*
*/
int init_connection(const char *socket_path) {
void init_connection(const char *socket_path) {
sock_path = socket_path;
int sockfd = ipc_connect(socket_path);
i3_connection = smalloc(sizeof(ev_io));
ev_io_init(i3_connection, &got_data, sockfd, EV_READ);
ev_io_start(main_loop, i3_connection);
return 1;
}
/*

View File

@ -56,9 +56,9 @@ static char *expand_path(char *path) {
}
static void print_usage(char *elf_name) {
printf("Usage: %s -b bar_id [-s sock_path] [-t] [-h] [-v]\n", elf_name);
printf("Usage: %s [-b bar_id] [-s sock_path] [-t] [-h] [-v] [-V]\n", elf_name);
printf("\n");
printf("-b, --bar_id <bar_id>\tBar ID for which to get the configuration\n");
printf("-b, --bar_id <bar_id>\tBar ID for which to get the configuration, defaults to the first bar from the i3 config\n");
printf("-s, --socket <sock_path>\tConnect to i3 via <sock_path>\n");
printf("-t, --transparency Enable transparency (RGBA colors)\n");
printf("-h, --help Display this help message and exit\n");
@ -128,17 +128,12 @@ int main(int argc, char **argv) {
break;
default:
print_usage(argv[0]);
exit(EXIT_SUCCESS);
exit(EXIT_FAILURE);
break;
}
}
if (!config.bar_id) {
/* TODO: maybe we want -f which will automatically ask i3 for the first
* configured bar (and error out if there are too many)? */
ELOG("No bar_id passed. Please let i3 start i3bar or specify --bar_id\n");
exit(EXIT_FAILURE);
}
LOG("i3bar version " I3_VERSION "\n");
main_loop = ev_default_loop(0); /* needed in init_xcb_early */
char *atom_sock_path = init_xcb_early();
@ -166,10 +161,13 @@ int main(int argc, char **argv) {
init_dpi();
init_outputs();
if (init_connection(socket_path)) {
/* Request the bar configuration. When it arrives, we fill the config array. */
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id);
}
init_connection(socket_path);
/* Request the bar configuration. When it arrives, we fill the config
* array. In case that config.bar_id is empty, we will receive a list of
* available configs and then request the configuration for the first bar.
* See got_bar_config for more. */
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id);
free(socket_path);
/* We listen to SIGTERM/QUIT/INT and try to exit cleanly, by stopping the main loop.

View File

@ -1532,7 +1532,6 @@ void clean_xcb(void) {
free_font();
xcb_free_cursor(xcb_connection, cursor);
xcb_flush(xcb_connection);
xcb_aux_sync(xcb_connection);
xcb_disconnect(xcb_connection);

View File

@ -56,7 +56,6 @@
#include "render.h"
#include "window.h"
#include "match.h"
#include "cmdparse.h"
#include "xcursor.h"
#include "resize.h"
#include "sighandler.h"

View File

@ -1,14 +0,0 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* cmdparse.y: the parser for commands you send to i3 (or bind on keys)
*
*/
#pragma once
#include <config.h>
char *parse_cmd(const char *new);

View File

@ -138,7 +138,7 @@ void cmd_mode(I3_CMD, const char *mode);
* Implementation of 'move [window|container] [to] output <str>'.
*
*/
void cmd_move_con_to_output(I3_CMD, const char *name);
void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace);
/**
* Implementation of 'move [window|container] [to] mark <str>'.
@ -152,12 +152,6 @@ void cmd_move_con_to_mark(I3_CMD, const char *mark);
*/
void cmd_floating(I3_CMD, const char *floating_mode);
/**
* Implementation of 'move workspace to [output] <str>'.
*
*/
void cmd_move_workspace_to_output(I3_CMD, const char *name);
/**
* Implementation of 'split v|h|t|vertical|horizontal|toggle'.
*
@ -337,3 +331,9 @@ void cmd_shmlog(I3_CMD, const char *argument);
*
*/
void cmd_debuglog(I3_CMD, const char *argument);
/**
* Implementation of 'title_window_icon <yes|no>' and 'title_window_icon padding <px>'
*
*/
void cmd_title_window_icon(I3_CMD, const char *enable, int padding);

View File

@ -39,6 +39,7 @@ CFGFUN(criteria_init, int _state);
CFGFUN(criteria_add, const char *ctype, const char *cvalue);
CFGFUN(criteria_pop_state);
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);

View File

@ -16,6 +16,50 @@
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;
/* A list which contains the states that lead to the current state, e.g.
* INITIAL, WORKSPACE_LAYOUT.
* When jumping back to INITIAL, statelist_idx will simply be set to 1
* (likewise for other states, e.g. MODE or BAR).
* This list is used to process the nearest error token. */
int statelist[10];
/* NB: statelist_idx points to where the next entry will be inserted */
int statelist_idx;
/*******************************************************************************
* The (small) stack where identified literals are stored during the parsing
* of a single config directive (like $workspace).
******************************************************************************/
struct stack *stack;
struct variables_head variables;
bool has_errors;
};
/**
* An intermediate reprsentation of the result of a parse_config call.
* Currently unused, but the JSON output will be useful in the future when we
@ -23,22 +67,34 @@ extern pid_t config_error_nagbar_pid;
*
*/
struct ConfigResultIR {
/* The JSON generator to append a reply to. */
yajl_gen json_gen;
struct parser_ctx *ctx;
/* The next state to transition to. Passed to the function so that we can
* determine the next state as a result of a function call, like
* cfg_criteria_pop_state() does. */
int next_state;
};
struct ConfigResultIR *parse_config(const char *input, struct context *context);
/* Whether any error happened while processing this config directive. */
bool has_errors;
};
/**
* launch nagbar to indicate errors in the configuration file.
*/
void start_config_error_nagbar(const char *configpath, bool has_errors);
/**
* Releases the memory of all variables in ctx.
*
*/
void free_variables(struct parser_ctx *ctx);
typedef enum {
PARSE_FILE_FAILED = -1,
PARSE_FILE_SUCCESS = 0,
PARSE_FILE_CONFIG_ERRORS = 1,
} parse_file_result_t;
/**
* Parses the given file by first replacing the variables, then calling
* parse_config and launching i3-nagbar if use_nagbar is true.
@ -47,4 +103,4 @@ void start_config_error_nagbar(const char *configpath, bool has_errors);
* parsing.
*
*/
bool parse_file(const char *f, bool use_nagbar);
parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFile *included_file);

View File

@ -15,6 +15,7 @@
#include "queue.h"
#include "i3.h"
typedef struct IncludedFile IncludedFile;
typedef struct Config Config;
typedef struct Barconfig Barconfig;
extern char *current_configpath;
@ -22,6 +23,7 @@ extern char *current_config;
extern Config config;
extern SLIST_HEAD(modes_head, Mode) modes;
extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;
extern TAILQ_HEAD(includedfiles_head, IncludedFile) included_files;
/**
* Used during the config file lexing/parsing to keep the state of the lexer
@ -69,6 +71,18 @@ struct Variable {
SLIST_ENTRY(Variable) variables;
};
/**
* List entry struct for an included file.
*
*/
struct IncludedFile {
char *path;
char *raw_contents;
char *variable_replaced_contents;
TAILQ_ENTRY(IncludedFile) files;
};
/**
* The configuration file can contain multiple sets of bindings. Apart from the
* default set (name == "default"), you can specify other sets and change the

View File

@ -15,6 +15,7 @@
#include <xcb/randr.h>
#include <pcre.h>
#include <sys/time.h>
#include <cairo/cairo.h>
#include "queue.h"
@ -414,6 +415,9 @@ struct Window {
* for_window. */
char *role;
/** WM_CLIENT_MACHINE of the window */
char *machine;
/** Flag to force re-rendering the decoration upon changes */
bool name_x_changed;
@ -468,6 +472,9 @@ struct Window {
double min_aspect_ratio;
double max_aspect_ratio;
/** Window icon, as Cairo surface */
cairo_surface_t *icon;
/** The window has a nonrectangular shape. */
bool shaped;
/** The window has a nonrectangular input shape. */
@ -500,6 +507,7 @@ struct Match {
struct regex *mark;
struct regex *window_role;
struct regex *workspace;
struct regex *machine;
xcb_atom_t window_type;
enum {
U_DONTCHECK = -1,
@ -522,6 +530,7 @@ struct Match {
WM_FLOATING_USER,
WM_FLOATING } window_mode;
Con *con_id;
bool match_all_windows;
/* Where the window looking for a match should be inserted:
*
@ -652,6 +661,11 @@ struct Con {
/** The format with which the window's name should be displayed. */
char *title_format;
/** Whether the window icon should be displayed, and with what padding. -1
* means display no window icon (default behavior), 0 means display without
* any padding, 1 means display with 1 pixel of padding and so on. */
int window_icon_padding;
/* a sticky-group is an identifier which bundles several containers to a
* group. The contents are shared between all of them, that is they are
* displayed on whichever of the containers is currently visible */

View File

@ -3,6 +3,7 @@
xmacro(_NET_WM_USER_TIME) \
xmacro(_NET_STARTUP_ID) \
xmacro(_NET_WORKAREA) \
xmacro(_NET_WM_ICON) \
xmacro(WM_PROTOCOLS) \
xmacro(WM_DELETE_WINDOW) \
xmacro(UTF8_STRING) \
@ -15,8 +16,10 @@ xmacro(I3_CONFIG_PATH) \
xmacro(I3_SYNC) \
xmacro(I3_SHMLOG_PATH) \
xmacro(I3_PID) \
xmacro(I3_LOG_STREAM_SOCKET_PATH) \
xmacro(I3_FLOATING_WINDOW) \
xmacro(_NET_REQUEST_FRAME_EXTENTS) \
xmacro(_NET_FRAME_EXTENTS) \
xmacro(_MOTIF_WM_HINTS) \
xmacro(WM_CHANGE_STATE)
xmacro(WM_CHANGE_STATE) \
xmacro(MANAGER)

View File

@ -39,6 +39,7 @@ extern bool debug_build;
/** The number of file descriptors passed via socket activation. */
extern int listen_fds;
extern int conn_screen;
extern xcb_atom_t wm_sn;
/**
* The EWMH support window that is used to indicate that an EWMH-compliant
* window manager is present. This window is created when i3 starts and

View File

@ -80,13 +80,6 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents);
*/
ipc_client *ipc_new_client_on_fd(EV_P_ int fd);
/**
* Creates the UNIX domain socket at the given path, sets it to non-blocking
* mode, bind()s and listen()s on it.
*
*/
int ipc_create_socket(const char *filename);
/**
* Sends the specified event to all IPC clients which are currently connected
* and subscribed to this kind of event.

View File

@ -445,18 +445,13 @@ bool font_is_pango(void);
* specified coordinates (from the top left corner of the leftmost, uppermost
* glyph) and using the provided gc.
*
* The given cairo surface must refer to the specified X drawable.
*
* Text must be specified as an i3String.
*
*/
void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc,
xcb_visualtype_t *visual, int x, int y, int max_width);
/**
* ASCII version of draw_text to print static strings.
*
*/
void draw_text_ascii(const char *text, xcb_drawable_t drawable,
xcb_gcontext_t gc, int x, int y, int max_width);
cairo_surface_t *surface, int x, int y, int max_width);
/**
* Predict the text width in pixels for the given text. Text must be
@ -571,8 +566,6 @@ typedef struct surface_t {
/* A classic XCB graphics context. */
xcb_gcontext_t gc;
xcb_visualtype_t *visual_type;
int width;
int height;
@ -618,6 +611,11 @@ color_t draw_util_hex_to_color(const char *color);
*/
void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width);
/**
* Draw the given image using libi3.
*/
void draw_util_image(cairo_surface_t *image, surface_t *surface, int x, int y, int width, int height);
/**
* Draws a filled rectangle.
* This function is a convenience wrapper and takes care of flushing the
@ -638,3 +636,46 @@ void draw_util_clear_surface(surface_t *surface, color_t color);
*/
void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y,
double dest_x, double dest_y, double width, double height);
/**
* Puts the given socket file descriptor into non-blocking mode or dies if
* setting O_NONBLOCK failed. Non-blocking sockets are a good idea for our
* IPC model because we should by no means block the window manager.
*
*/
void set_nonblock(int sockfd);
/**
* Creates the UNIX domain socket at the given path, sets it to non-blocking
* mode, bind()s and listen()s on it.
*
* The full path to the socket is stored in the char* that out_socketpath points
* to.
*
*/
int create_socket(const char *filename, char **out_socketpath);
/**
* Checks if the given path exists by calling stat().
*
*/
bool path_exists(const char *path);
/**
* Grab a screenshot of the screen's root window and set it as the wallpaper.
*/
void set_screenshot_as_wallpaper(xcb_connection_t *conn, xcb_screen_t *screen);
/**
* Test whether the screen's root window has a background set.
*
* This opens & closes a window and test whether the root window still shows the
* content of the window.
*/
bool is_background_set(xcb_connection_t *conn, xcb_screen_t *screen);
/**
* Reports whether str represents the enabled state (1, yes, true, ).
*
*/
bool boolstr(const char *str);

View File

@ -10,6 +10,7 @@
#pragma once
#include <config.h>
#include <ev.h>
/* We will include libi3.h which define its own version of LOG, ELOG.
* We want *our* version, so we undef the libi3 one. */
@ -31,6 +32,7 @@
extern char *errorfilename;
extern char *shmlogname;
extern int shmlog_size;
extern char *current_log_stream_socket_path;
/**
* Initializes logging by creating an error logfile in /tmp (or
@ -100,3 +102,5 @@ void verboselog(char *fmt, ...)
* failures. This function is invoked automatically when exiting.
*/
void purge_zerobyte_logfile(void);
void log_new_client(EV_P_ struct ev_io *w, int revents);

View File

@ -12,10 +12,6 @@
#include <config.h>
#if !defined(__OpenBSD__)
#include <pthread.h>
#endif
/* Default shmlog size if not set by user. */
extern const int default_shmlog_size;
@ -39,11 +35,4 @@ typedef struct i3_shmlog_header {
* coincidentally be exactly the same as previously). Overflows can happen
* and dont matter clients use an equality check (==). */
uint32_t wrap_count;
#if !defined(__OpenBSD__)
/* pthread condvar which will be broadcasted whenever there is a new
* message in the log. i3-dump-log uses this to implement -f (follow, like
* tail -f) in an efficient way. */
pthread_cond_t condvar;
#endif
} i3_shmlog_header;

View File

@ -95,3 +95,15 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
*
*/
void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
/**
* Updates the WM_CLIENT_MACHINE
*
*/
void window_update_machine(i3Window *win, xcb_get_property_reply_t *prop);
/**
* Updates the _NET_WM_ICON
*
*/
void window_update_icon(i3Window *win, xcb_get_property_reply_t *prop);

25
libi3/boolstr.c Normal file
View File

@ -0,0 +1,25 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include "libi3.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
/*
* Reports whether str represents the enabled state (1, yes, true, ).
*
*/
bool boolstr(const char *str) {
return (strcasecmp(str, "1") == 0 ||
strcasecmp(str, "yes") == 0 ||
strcasecmp(str, "true") == 0 ||
strcasecmp(str, "on") == 0 ||
strcasecmp(str, "enable") == 0 ||
strcasecmp(str, "active") == 0);
}

70
libi3/create_socket.c Normal file
View File

@ -0,0 +1,70 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include "libi3.h"
#include <unistd.h>
#include <libgen.h>
#include <err.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
/*
* Creates the UNIX domain socket at the given path, sets it to non-blocking
* mode, bind()s and listen()s on it.
*
* The full path to the socket is stored in the char* that out_socketpath points
* to.
*
*/
int create_socket(const char *filename, char **out_socketpath) {
char *resolved = resolve_tilde(filename);
DLOG("Creating UNIX socket at %s\n", resolved);
char *copy = sstrdup(resolved);
const char *dir = dirname(copy);
if (!path_exists(dir)) {
mkdirp(dir, DEFAULT_DIR_MODE);
}
free(copy);
/* Unlink the unix domain socket before */
unlink(resolved);
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket()");
free(resolved);
return -1;
}
(void)fcntl(sockfd, F_SETFD, FD_CLOEXEC);
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
strncpy(addr.sun_path, resolved, sizeof(addr.sun_path) - 1);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
perror("bind()");
free(resolved);
return -1;
}
set_nonblock(sockfd);
if (listen(sockfd, 5) < 0) {
perror("listen()");
free(resolved);
return -1;
}
free(*out_socketpath);
*out_socketpath = resolved;
return sockfd;
}

View File

@ -35,19 +35,22 @@ static void draw_util_set_source_color(surface_t *surface, color_t color);
void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_drawable_t drawable,
xcb_visualtype_t *visual, int width, int height) {
surface->id = drawable;
surface->visual_type = ((visual == NULL) ? visual_type : visual);
surface->width = width;
surface->height = height;
if (visual == NULL)
visual = visual_type;
surface->gc = xcb_generate_id(conn);
xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(conn, surface->gc, surface->id, 0, NULL);
xcb_generic_error_t *error = xcb_request_check(conn, gc_cookie);
if (error != NULL) {
ELOG("Could not create graphical context. Error code: %d. Please report this bug.\n", error->error_code);
free(error);
}
surface->surface = cairo_xcb_surface_create(conn, surface->id, surface->visual_type, width, height);
surface->surface = cairo_xcb_surface_create(conn, surface->id, visual, width, height);
surface->cr = cairo_create(surface->surface);
}
@ -56,6 +59,19 @@ void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_draw
*
*/
void draw_util_surface_free(xcb_connection_t *conn, surface_t *surface) {
cairo_status_t status = CAIRO_STATUS_SUCCESS;
if (surface->cr) {
status = cairo_status(surface->cr);
}
if (status != CAIRO_STATUS_SUCCESS) {
LOG("Found cairo context in an error status while freeing, error %d is %s",
status, cairo_status_to_string(status));
}
/* NOTE: This function is also called on uninitialised surface_t instances.
* The x11 error from xcb_free_gc(conn, XCB_NONE) is silently ignored
* elsewhere.
*/
xcb_free_gc(conn, surface->gc);
cairo_surface_destroy(surface->surface);
cairo_destroy(surface->cr);
@ -133,12 +149,36 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_
CAIRO_SURFACE_FLUSH(surface->surface);
set_font_colors(surface->gc, fg_color, bg_color);
draw_text(text, surface->id, surface->gc, surface->visual_type, x, y, max_width);
draw_text(text, surface->id, surface->gc, surface->surface, x, y, max_width);
/* Notify cairo that we (possibly) used another way to draw on the surface. */
cairo_surface_mark_dirty(surface->surface);
}
/**
* Draw the given image using libi3.
* This function is a convenience wrapper and takes care of flushing the
* surface as well as restoring the cairo state.
*
*/
void draw_util_image(cairo_surface_t *image, surface_t *surface, int x, int y, int width, int height) {
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
cairo_save(surface->cr);
cairo_translate(surface->cr, x, y);
const int src_width = cairo_image_surface_get_width(image);
const int src_height = cairo_image_surface_get_height(image);
double scale = MIN((double)width / src_width, (double)height / src_height);
cairo_scale(surface->cr, scale, scale);
cairo_set_source_surface(surface->cr, image, 0, 0);
cairo_paint(surface->cr);
cairo_restore(surface->cr);
}
/*
* Draws a filled rectangle.
* This function is a convenience wrapper and takes care of flushing the

View File

@ -82,12 +82,10 @@ static bool load_pango_font(i3Font *font, const char *desc) {
*
*/
static void draw_text_pango(const char *text, size_t text_len,
xcb_drawable_t drawable, xcb_visualtype_t *visual, int x, int y,
int max_width, bool pango_markup) {
xcb_drawable_t drawable, cairo_surface_t *surface,
int x, int y, int max_width, bool pango_markup) {
/* Create the Pango layout */
/* root_visual_type is cached in load_pango_font */
cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable,
visual, x + max_width, y + savedFont->height);
cairo_t *cr = cairo_create(surface);
PangoLayout *layout = create_layout_with_dpi(cr);
gint height;
@ -115,7 +113,6 @@ static void draw_text_pango(const char *text, size_t text_len,
/* Free resources */
g_object_unref(layout);
cairo_destroy(cr);
cairo_surface_destroy(surface);
}
/*
@ -358,11 +355,8 @@ static void draw_text_xcb(const xcb_char2b_t *text, size_t text_len, xcb_drawabl
*
*/
void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc,
xcb_visualtype_t *visual, int x, int y, int max_width) {
cairo_surface_t *surface, int x, int y, int max_width) {
assert(savedFont != NULL);
if (visual == NULL) {
visual = root_visual_type;
}
switch (savedFont->type) {
case FONT_TYPE_NONE:
@ -375,42 +369,7 @@ void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc,
case FONT_TYPE_PANGO:
/* Render the text using Pango */
draw_text_pango(i3string_as_utf8(text), i3string_get_num_bytes(text),
drawable, visual, x, y, max_width, i3string_is_markup(text));
return;
}
}
/*
* ASCII version of draw_text to print static strings.
*
*/
void draw_text_ascii(const char *text, xcb_drawable_t drawable,
xcb_gcontext_t gc, int x, int y, int max_width) {
assert(savedFont != NULL);
switch (savedFont->type) {
case FONT_TYPE_NONE:
/* Nothing to do */
return;
case FONT_TYPE_XCB: {
size_t text_len = strlen(text);
if (text_len > 255) {
/* The text is too long to draw it directly to X */
i3String *str = i3string_from_utf8(text);
draw_text(str, drawable, gc, NULL, x, y, max_width);
i3string_free(str);
} else {
/* X11 coordinates for fonts start at the baseline */
int pos_y = y + savedFont->specific.xcb.info->font_ascent;
xcb_image_text_8(conn, text_len, drawable, gc, x, pos_y, text);
}
break;
}
case FONT_TYPE_PANGO:
/* Render the text using Pango */
draw_text_pango(text, strlen(text),
drawable, root_visual_type, x, y, max_width, false);
drawable, surface, x, y, max_width, i3string_is_markup(text));
return;
}
}

View File

@ -11,15 +11,6 @@
#include <string.h>
#include <sys/stat.h>
/*
* Checks if the given path exists by calling stat().
*
*/
static bool path_exists(const char *path) {
struct stat buf;
return (stat(path, &buf) == 0);
}
/*
* Get the path of the first configuration file found. If override_configpath is
* specified, that path is returned and saved for further calls. Otherwise,

View File

@ -27,15 +27,14 @@ char *get_process_filename(const char *prefix) {
char *tmp;
sasprintf(&tmp, "%s/i3", dir);
dir = tmp;
struct stat buf;
if (stat(dir, &buf) != 0) {
if (mkdir(dir, 0700) == -1) {
warn("Could not mkdir(%s)", dir);
errx(EXIT_FAILURE, "Check permissions of $XDG_RUNTIME_DIR = '%s'",
getenv("XDG_RUNTIME_DIR"));
perror("mkdir()");
return NULL;
}
/* mkdirp() should prevent race between multiple i3 instances started
* in parallel from causing problem */
if (mkdirp(dir, 0700) == -1) {
warn("Could not mkdirp(%s)", dir);
errx(EXIT_FAILURE, "Check permissions of $XDG_RUNTIME_DIR = '%s'",
getenv("XDG_RUNTIME_DIR"));
perror("mkdirp()");
return NULL;
}
} else {
/* If not, we create a (secure) temp directory using the template

117
libi3/is_background_set.c Normal file
View File

@ -0,0 +1,117 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include "libi3.h"
#include <xcb/xcb_aux.h>
/**
* Find the region in the given window that is not covered by a mapped child
* window.
*/
static cairo_region_t *unobscured_region(xcb_connection_t *conn, xcb_window_t window,
uint16_t window_width, uint16_t window_height) {
cairo_rectangle_int_t rectangle;
cairo_region_t *region;
rectangle.x = 0;
rectangle.y = 0;
rectangle.width = window_width;
rectangle.height = window_height;
region = cairo_region_create_rectangle(&rectangle);
xcb_query_tree_reply_t *tree = xcb_query_tree_reply(conn, xcb_query_tree_unchecked(conn, window), NULL);
if (!tree) {
return region;
}
/* Get information about children */
uint16_t n_children = tree->children_len;
xcb_window_t *children = xcb_query_tree_children(tree);
xcb_get_geometry_cookie_t geometries[n_children];
xcb_get_window_attributes_cookie_t attributes[n_children];
for (int i = 0; i < n_children; i++) {
geometries[i] = xcb_get_geometry_unchecked(conn, children[i]);
attributes[i] = xcb_get_window_attributes_unchecked(conn, children[i]);
}
/* Remove every visible child from the region */
for (int i = 0; i < n_children; i++) {
xcb_get_geometry_reply_t *geom = xcb_get_geometry_reply(conn, geometries[i], NULL);
xcb_get_window_attributes_reply_t *attr = xcb_get_window_attributes_reply(conn, attributes[i], NULL);
if (geom && attr && attr->map_state == XCB_MAP_STATE_VIEWABLE) {
rectangle.x = geom->x;
rectangle.y = geom->y;
rectangle.width = geom->width;
rectangle.height = geom->height;
cairo_region_subtract_rectangle(region, &rectangle);
}
free(geom);
free(attr);
}
free(tree);
return region;
}
static void find_unobscured_pixel(xcb_connection_t *conn, xcb_window_t window,
uint16_t window_width, uint16_t window_height,
uint16_t *x, uint16_t *y) {
cairo_region_t *region = unobscured_region(conn, window, window_width, window_height);
if (cairo_region_num_rectangles(region) > 0) {
/* Return the top left pixel of the first rectangle */
cairo_rectangle_int_t rect;
cairo_region_get_rectangle(region, 0, &rect);
*x = rect.x;
*y = rect.y;
} else {
/* No unobscured area found */
*x = 0;
*y = 0;
}
cairo_region_destroy(region);
}
static uint32_t flicker_window_at(xcb_connection_t *conn, xcb_screen_t *screen, int16_t x, int16_t y, xcb_window_t window,
uint32_t pixel) {
xcb_create_window(conn, XCB_COPY_FROM_PARENT, window, screen->root, x, y, 10, 10,
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT,
XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT, (uint32_t[]){pixel, 1});
xcb_map_window(conn, window);
xcb_clear_area(conn, 0, window, 0, 0, 0, 0);
xcb_aux_sync(conn);
xcb_destroy_window(conn, window);
xcb_get_image_reply_t *img = xcb_get_image_reply(conn,
xcb_get_image_unchecked(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, screen->root, x, y, 1, 1, ~0),
NULL);
uint32_t result = 0;
if (img) {
uint8_t *data = xcb_get_image_data(img);
uint8_t depth = img->depth;
for (int i = 0; i < MIN(depth, 4); i++) {
result = (result << 8) | data[i];
}
free(img);
}
return result;
}
bool is_background_set(xcb_connection_t *conn, xcb_screen_t *screen) {
uint16_t x, y;
find_unobscured_pixel(conn, screen->root, screen->width_in_pixels, screen->height_in_pixels, &x, &y);
xcb_window_t window = xcb_generate_id(conn);
uint32_t pixel1 = flicker_window_at(conn, screen, x, y, window, screen->black_pixel);
uint32_t pixel2 = flicker_window_at(conn, screen, x, y, window, screen->white_pixel);
return pixel1 == pixel2;
}

21
libi3/nonblock.c Normal file
View File

@ -0,0 +1,21 @@
#include "libi3.h"
#include <err.h>
#include <fcntl.h>
/*
* Puts the given socket file descriptor into non-blocking mode or dies if
* setting O_NONBLOCK failed. Non-blocking sockets are a good idea for our
* IPC model because we should by no means block the window manager.
*
*/
void set_nonblock(int sockfd) {
int flags = fcntl(sockfd, F_GETFL, 0);
if (flags & O_NONBLOCK) {
return;
}
flags |= O_NONBLOCK;
if (fcntl(sockfd, F_SETFL, flags) < 0) {
err(-1, "Could not set O_NONBLOCK");
}
}

21
libi3/path_exists.c Normal file
View File

@ -0,0 +1,21 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include "libi3.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
/*
* Checks if the given path exists by calling stat().
*
*/
bool path_exists(const char *path) {
struct stat buf;
return (stat(path, &buf) == 0);
}

View File

@ -0,0 +1,27 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved dynamic tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
*/
#include "libi3.h"
void set_screenshot_as_wallpaper(xcb_connection_t *conn, xcb_screen_t *screen) {
uint16_t width = screen->width_in_pixels;
uint16_t height = screen->height_in_pixels;
xcb_pixmap_t pixmap = xcb_generate_id(conn);
xcb_gcontext_t gc = xcb_generate_id(conn);
xcb_create_pixmap(conn, screen->root_depth, pixmap, screen->root, width, height);
xcb_create_gc(conn, gc, screen->root,
XCB_GC_FUNCTION | XCB_GC_PLANE_MASK | XCB_GC_FILL_STYLE | XCB_GC_SUBWINDOW_MODE,
(uint32_t[]){XCB_GX_COPY, ~0, XCB_FILL_STYLE_SOLID, XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS});
xcb_copy_area(conn, screen->root, pixmap, gc, 0, 0, 0, 0, width, height);
xcb_change_window_attributes(conn, screen->root, XCB_CW_BACK_PIXMAP, (uint32_t[]){pixmap});
xcb_free_gc(conn, gc);
xcb_free_pixmap(conn, pixmap);
xcb_flush(conn);
}

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
@ -13,9 +12,10 @@
height="297mm"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.46"
sodipodi:docname="logo_i3_linuxfr_bapt_v2.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
inkscape:version="1.0.1 (c497b03c, 2020-09-10)"
sodipodi:docname="logo.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
version="1.1">
<defs
id="defs4">
<linearGradient
@ -39,10 +39,10 @@
</linearGradient>
<inkscape:perspective
id="perspective3661"
inkscape:persp3d-origin="750.50629 : 505.26732 : 1"
inkscape:vp_z="683.5728 : 1230.5721 : 1"
inkscape:vp_y="0 : 1946.8917 : 0"
inkscape:vp_x="-526.84957 : 2.2065866e-13 : 0"
inkscape:persp3d-origin="800.54004 : 538.95181 : 1"
inkscape:vp_z="729.14432 : 1312.6102 : 1"
inkscape:vp_y="0 : 2076.6845 : 0"
inkscape:vp_x="-561.97287 : 2.3536924e-13 : 0"
sodipodi:type="inkscape:persp3d" />
<linearGradient
id="linearGradient3284">
@ -112,10 +112,10 @@
</linearGradient>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="-526.84957 : 2.9848654e-13 : 0"
inkscape:vp_y="1.192088e-13 : 1946.8917 : 0"
inkscape:vp_z="680.54236 : 1232.3792 : 1"
inkscape:persp3d-origin="730.30325 : 937.39936 : 1"
inkscape:vp_x="-561.97287 : 3.1838564e-13 : 0"
inkscape:vp_y="1.2715605e-13 : 2076.6845 : 0"
inkscape:vp_z="725.91185 : 1314.5378 : 1"
inkscape:persp3d-origin="778.99013 : 999.89265 : 1"
id="perspective10" />
<radialGradient
inkscape:collect="always"
@ -152,10 +152,10 @@
gradientUnits="userSpaceOnUse" />
<inkscape:perspective
id="perspective3373"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:persp3d-origin="396.85039 : 374.17322 : 1"
inkscape:vp_z="793.70078 : 561.25983 : 1"
inkscape:vp_y="0 : 1066.6667 : 0"
inkscape:vp_x="0 : 561.25983 : 1"
sodipodi:type="inkscape:persp3d" />
<linearGradient
id="linearGradient3211">
@ -282,15 +282,17 @@
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="239.17981"
inkscape:cy="807.75327"
inkscape:cx="164.67981"
inkscape:cy="285.75327"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1272"
inkscape:window-height="950"
inkscape:window-height="856"
inkscape:window-x="24"
inkscape:window-y="24" />
inkscape:window-y="22"
inkscape:document-rotation="0"
inkscape:window-maximized="0" />
<metadata
id="metadata7">
<rdf:RDF>
@ -495,7 +497,7 @@
<path
style="fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;display:inline"
d="M 182.85714,106.6479 L 182.85714,415.21935 L 182.85714,106.6479 z"
id="rect3246"
id="path934"
sodipodi:nodetypes="ccc" />
<path
sodipodi:nodetypes="ccc"
@ -515,7 +517,7 @@
style="display:inline">
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:60;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline;opacity:0.87;color:#000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-dashoffset:0;visibility:visible;overflow:visible;enable-background:accumulate"
d="M 232.58675,170.48705 C 227.06049,176.20076 227.20767,185.31117 232.91561,190.84339 C 232.91561,190.84339 262.59553,220.39981 299.69461,257.34451 C 307.623,265.23991 308.90149,274.72214 304.93625,288.45787 C 300.97101,302.19359 290.70796,318.30838 277.52977,331.54163 C 264.35159,344.77488 248.27973,355.10504 234.56067,359.12752 C 220.8416,363.15 211.35411,361.91106 203.42572,354.01566 C 184.92972,335.59662 168.41707,318.85373 156.40736,306.74834 C 150.40251,300.69564 145.54261,295.80887 142.03518,292.37245 C 140.28147,290.65424 138.89319,289.33126 137.69845,288.24431 C 137.10109,287.70084 136.61259,287.25572 135.77496,286.60406 C 135.35615,286.27823 134.96551,285.91185 133.67159,285.18696 C 133.02464,284.82451 132.24966,284.30398 130.06183,283.75178 C 127.87401,283.19957 121.6694,282.14423 116.27918,287.55701 C 113.07203,290.69556 111.51088,295.14955 112.05678,299.60357 C 112.60268,304.05759 115.19327,308.00272 119.06345,310.27384 C 119.7757,310.94033 120.58858,311.68637 121.88019,312.95183 C 125.15148,316.1569 129.98569,321.01459 135.96535,327.0419 C 147.92468,339.09652 164.49757,355.90001 183.10064,374.42567 C 199.28665,390.54432 222.47127,392.68834 242.66987,386.76604 C 262.86847,380.84373 281.97067,367.90254 297.93978,351.86671 C 313.90889,335.83087 326.77031,316.67487 332.60834,296.45175 C 338.44637,276.22863 336.20569,253.05315 320.01968,236.9345 C 282.92061,199.9898 253.24069,170.43338 253.24069,170.43338 C 250.52156,167.6512 246.79264,166.08723 242.90239,166.09734 C 239.01212,166.10745 235.29138,167.69077 232.58675,170.48705 L 232.58675,170.48705 z"
d="m 232.58675,170.48705 c -5.52626,5.71371 -5.37908,14.82412 0.32886,20.35634 0,0 29.67992,29.55642 66.779,66.50112 7.92839,7.8954 9.20688,17.37763 5.24164,31.11336 -3.96524,13.73572 -14.22829,29.85051 -27.40648,43.08376 -13.17818,13.23325 -29.25004,23.56341 -42.9691,27.58589 -13.71907,4.02248 -23.20656,2.78354 -31.13495,-5.11186 -18.496,-18.41904 -35.00865,-35.16193 -47.01836,-47.26732 -6.00485,-6.0527 -10.86475,-10.93947 -14.37218,-14.37589 -1.75371,-1.71821 -3.14199,-3.04119 -4.33673,-4.12814 -0.59736,-0.54347 -1.08586,-0.98859 -1.92349,-1.64025 -0.41881,-0.32583 -0.80945,-0.69221 -2.10337,-1.4171 -0.64695,-0.36245 -1.42193,-0.88298 -3.60976,-1.43518 -2.18782,-0.55221 -8.39243,-1.60755 -13.78265,3.80523 -3.20715,3.13855 -4.7683,7.59254 -4.2224,12.04656 0.5459,4.45402 3.90276,7.82444 7.00667,10.67027 0.71225,0.66649 1.52513,1.41253 2.81674,2.67799 3.27129,3.20507 8.1055,8.06276 14.08516,14.09007 11.95933,12.05462 28.53222,28.85811 47.13529,47.38377 16.18601,16.11865 39.37063,18.26267 59.56923,12.34037 20.1986,-5.92231 39.3008,-18.8635 55.26991,-34.89933 15.96911,-16.03584 28.83053,-35.19184 34.66856,-55.41496 5.83803,-20.22312 3.59735,-43.3986 -12.58866,-59.51725 l -66.77899,-66.50112 c -2.71913,-2.78218 -6.44805,-4.34615 -10.3383,-4.33604 -3.89027,0.0101 -7.61101,1.59343 -10.31564,4.38971 zm 232.58675,170.48705 c -5.52626,5.71371 -5.37908,14.82412 0.32886,20.35634 0,0 29.67992,29.55642 66.779,66.50112 7.92839,7.8954 9.20688,17.37763 5.24164,31.11336 -3.96524,13.73572 -14.22829,29.85051 -27.40648,43.08376 -13.17818,13.23325 -29.25004,23.56341 -42.9691,27.58589 -13.71907,4.02248 -23.20656,2.78354 -31.13495,-5.11186 -18.496,-18.41904 -35.00865,-35.16193 -47.01836,-47.26732 -6.00485,-6.0527 -10.86475,-10.93947 -14.37218,-14.37589 -1.75371,-1.71821 -3.14199,-3.04119 -4.33673,-4.12814 -0.59736,-0.54347 -1.08586,-0.98859 -1.92349,-1.64025 -0.41881,-0.32583 -0.80945,-0.69221 -2.10337,-1.4171 -0.64695,-0.36245 -1.42193,-0.88298 -3.60976,-1.43518 -2.18782,-0.55221 -8.39243,-1.60755 -13.78265,3.80523 -3.20715,3.13855 -4.7683,7.59254 -4.2224,12.04656 0.5459,4.45402 3.90276,7.82444 7.00667,10.67027 0.71225,0.66649 1.52513,1.41253 2.81674,2.67799 3.27129,3.20507 8.1055,8.06276 14.08516,14.09007 11.95933,12.05462 28.53222,28.85811 47.13529,47.38377 16.18601,16.11865 39.37063,18.26267 59.56923,12.34037 20.1986,-5.92231 39.3008,-18.8635 55.26991,-34.89933 15.96911,-16.03584 28.83053,-35.19184 34.66856,-55.41496 5.83803,-20.22312 3.59735,-43.3986 -12.58866,-59.51725 l -66.77899,-66.50112 c -2.71913,-2.78218 -6.44805,-4.34615 -10.3383,-4.33604 -3.89027,0.0101 -7.61101,1.59343 -10.31564,4.38971 z"
id="path2405" />
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:60;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline;opacity:0.87"

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -9,7 +9,7 @@ i3-msg - send messages to i3 window manager
== SYNOPSIS
i3-msg [-q] [-v] [-h] [-s socket] [-t type] [message]
i3-msg [-q] [-v] [-h] [-s socket] [-t type] [-r] [message]
== OPTIONS
@ -36,6 +36,10 @@ Instead of exiting right after receiving the first subscribed event,
wait indefinitely for all of them. Can only be used with "-t subscribe".
See the "subscribe" IPC message type below for details.
*-q, --raw*::
Display the raw JSON reply instead of pretty-printing errors (for commands) or
displaying the top-level config file contents (for GET_CONFIG).
*message*::
Send ipc message, see below.

View File

@ -9,7 +9,7 @@ i3-nagbar - displays an error bar on top of your screen
== SYNOPSIS
i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v]
i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v] [-p]
== OPTIONS
@ -39,6 +39,10 @@ i3-sensible-terminal.
Same as above, but will execute the shell commands directly, without launching a
terminal emulator.
*-p, --primary*::
Always opens the i3-nagbar on the primary monitor. By default it opens on the
focused monitor.
== DESCRIPTION
i3-nagbar is used by i3 to tell you about errors in your configuration file

View File

@ -23,20 +23,20 @@ It tries to start one of the following (in that order):
* $TERMINAL (this is a non-standard variable)
* x-terminal-emulator (only present on Debian and derivatives)
* mate-terminal
* gnome-terminal
* terminator
* xfce4-terminal
* urxvt
* rxvt
* termit
* terminator
* Eterm
* aterm
* uxterm
* xterm
* gnome-terminal
* roxterm
* xfce4-terminal
* termite
* lxterminal
* mate-terminal
* terminology
* st
* qterminal

View File

@ -44,6 +44,9 @@ Retrieve the i3 IPC socket path from X11, print it, then exit.
Limits the size of the i3 SHM log to <limit> bytes. Setting this to 0 disables
SHM logging entirely. The default is 0 bytes.
--replace::
Replace an existing window manager.
== DESCRIPTION
=== INTRODUCTION

View File

@ -9,7 +9,7 @@ i3bar - xcb-based status- and workspace-bar
== SYNOPSIS
*i3bar* [*-s* 'sock_path'] [*-b* 'bar_id'] [*-v*] [*-h*]
*i3bar* [*-b* 'bar_id'] [*-s* 'sock_path'] [*-t*] [*-h*] [*-v*] [*-V*]
== WARNING
@ -25,13 +25,20 @@ You have been warned!
Overwrites the path to the i3 IPC socket.
*-b, --bar_id* 'bar_id'::
Specifies the bar ID for which to get the configuration from i3.
Specifies the bar ID for which to get the configuration from i3. By default,
i3bar will use the first bar block as configured in i3.
*-t, --transparency*::
Enable transparency (RGBA colors)
*-h, --help*::
Display a short help-message and exit
*-v, --version*::
Display version number and exit.
*-h, --help*::
Display a short help-message and exit
*-V*::
Be verbose.
== DESCRIPTION

View File

@ -6,7 +6,7 @@
project(
'i3',
'c',
version: '4.19.2',
version: '4.20',
default_options: [
'c_std=c11',
'warning_level=1', # enable all warnings (-Wall)
@ -325,6 +325,8 @@ ev_dep = cc.find_library('ev')
inc = include_directories('include')
libi3srcs = [
'libi3/boolstr.c',
'libi3/create_socket.c',
'libi3/dpi.c',
'libi3/draw_util.c',
'libi3/fake_configure_notify.c',
@ -341,11 +343,15 @@ libi3srcs = [
'libi3/ipc_recv_message.c',
'libi3/ipc_send_message.c',
'libi3/is_debug_build.c',
'libi3/path_exists.c',
'libi3/resolve_tilde.c',
'libi3/root_atom_contents.c',
'libi3/safewrappers.c',
'libi3/string.c',
'libi3/ucs2_conversion.c',
'libi3/nonblock.c',
'libi3/screenshot_wallpaper.c',
'libi3/is_background_set.c',
]
if not cdata.get('HAVE_STRNDUP')
@ -514,6 +520,7 @@ executable(
'i3-config-wizard/i3-config-wizard-atoms.xmacro.h',
'i3-config-wizard/main.c',
'i3-config-wizard/xcb.h',
config_parser,
],
install: true,
include_directories: include_directories('include', 'i3-config-wizard'),
@ -642,7 +649,7 @@ if get_option('docs')
'@OUTPUT@',
],
install: true,
install_dir: join_paths(get_option('datadir'), 'doc', 'i3'),
install_dir: docdir,
)
custom_target(
@ -655,7 +662,7 @@ if get_option('docs')
'@OUTPUT@',
],
install: true,
install_dir: join_paths(get_option('datadir'), 'doc', 'i3'),
install_dir: docdir,
)
endif
@ -669,7 +676,10 @@ executable(
executable(
'test.commands_parser',
'src/commands_parser.c',
[
'src/commands_parser.c',
command_parser,
],
include_directories: inc,
c_args: '-DTEST_PARSER',
dependencies: common_deps,
@ -678,7 +688,10 @@ executable(
executable(
'test.config_parser',
'src/config_parser.c',
[
'src/config_parser.c',
config_parser,
],
include_directories: inc,
c_args: '-DTEST_PARSER',
dependencies: common_deps,
@ -706,6 +719,7 @@ if meson.version().version_compare('>=0.46.0')
anyevent_i3,
i3test_pm,
],
timeout: 120, # Default of 30 seconds can cause timeouts on slower machines
)
else
# meson < 0.46.0 does not support the depends arg in test targets.

View File

@ -40,6 +40,7 @@ state INITIAL:
'scratchpad' -> SCRATCHPAD
'swap' -> SWAP
'title_format' -> TITLE_FORMAT
'title_window_icon' -> TITLE_WINDOW_ICON
'mode' -> MODE
'bar' -> BAR
@ -54,7 +55,8 @@ state CRITERIA:
ctype = 'title' -> CRITERION
ctype = 'urgent' -> CRITERION
ctype = 'workspace' -> CRITERION
ctype = 'tiling', 'floating'
ctype = 'machine' -> CRITERION
ctype = 'tiling', 'floating', 'all'
-> call cmd_criteria_add($ctype, NULL); CRITERIA
']' -> call cmd_criteria_match_windows(); INITIAL
@ -384,8 +386,10 @@ state MOVE_WORKSPACE_NUMBER:
-> call cmd_move_con_to_workspace_number($number, $no_auto_back_and_forth)
state MOVE_TO_OUTPUT:
output = string
-> call cmd_move_con_to_output($output)
output = word
-> call cmd_move_con_to_output($output, 0); MOVE_TO_OUTPUT
end
-> call cmd_move_con_to_output(NULL, 0); INITIAL
state MOVE_TO_MARK:
mark = string
@ -393,9 +397,13 @@ state MOVE_TO_MARK:
state MOVE_WORKSPACE_TO_OUTPUT:
'output'
->
output = string
-> call cmd_move_workspace_to_output($output)
-> MOVE_WORKSPACE_TO_OUTPUT_WORD
state MOVE_WORKSPACE_TO_OUTPUT_WORD:
output = word
-> call cmd_move_con_to_output($output, 1); MOVE_WORKSPACE_TO_OUTPUT_WORD
end
-> call cmd_move_con_to_output(NULL, 1); INITIAL
state MOVE_TO_ABSOLUTE_POSITION:
'position'
@ -455,6 +463,20 @@ state TITLE_FORMAT:
format = string
-> call cmd_title_format($format)
state TITLE_WINDOW_ICON:
'padding'
-> TITLE_WINDOW_ICON_PADDING
enable = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive'
-> call cmd_title_window_icon($enable, 0)
state TITLE_WINDOW_ICON_PADDING:
end
-> call cmd_title_window_icon($enable, &padding)
'px'
-> call cmd_title_window_icon($enable, &padding)
padding = number
->
# bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>]
state BAR:
'hidden_state'

View File

@ -20,6 +20,7 @@ state INITIAL:
'set ' -> IGNORE_LINE
'set ' -> IGNORE_LINE
'set_from_resource' -> IGNORE_LINE
'include' -> INCLUDE
bindtype = 'bindsym', 'bindcode', 'bind' -> BINDING
'bar' -> BARBRACE
'font' -> FONT
@ -63,6 +64,11 @@ state IGNORE_LINE:
line
-> INITIAL
# include <pattern>
state INCLUDE:
pattern = string
-> call cfg_include($pattern)
# floating_minimum_size <width> x <height>
state FLOATING_MINIMUM_SIZE_WIDTH:
width = number
@ -191,9 +197,10 @@ state CRITERIA:
ctype = 'title' -> CRITERION
ctype = 'urgent' -> CRITERION
ctype = 'workspace' -> CRITERION
ctype = 'machine' -> CRITERION
ctype = 'floating_from' -> CRITERION_FROM
ctype = 'tiling_from' -> CRITERION_FROM
ctype = 'tiling', 'floating'
ctype = 'tiling', 'floating', 'all'
-> call cfg_criteria_add($ctype, NULL); CRITERIA
']'
-> call cfg_criteria_pop_state()
@ -393,6 +400,8 @@ state BINDCOMMAND:
->
command = string
-> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)
end
-> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)
################################################################################
# Mode configuration

View File

@ -228,3 +228,4 @@ echo "Announce on:"
echo " twitter"
echo " #i3 topic"
echo " reddit /r/i3wm (link post to changelog)"
echo " GitHub Discussions → Announcements"

View File

@ -717,6 +717,40 @@ void reorder_bindings(void) {
}
}
/*
* Returns true if a is a key binding for the same key as b.
*
*/
static bool binding_same_key(Binding *a, Binding *b) {
/* Check if the input types are different */
if (a->input_type != b->input_type) {
return false;
}
/* Check if one is using keysym while the other is using bindsym. */
if ((a->symbol == NULL && b->symbol != NULL) ||
(a->symbol != NULL && b->symbol == NULL)) {
return false;
}
/* If a is NULL, b has to be NULL, too (see previous conditional).
* If the keycodes differ, it can't be a duplicate. */
if (a->symbol != NULL &&
strcasecmp(a->symbol, b->symbol) != 0) {
return false;
}
/* Check if the keycodes or modifiers are different. If so, they
* can't be duplicate */
if (a->keycode != b->keycode ||
a->event_state_mask != b->event_state_mask ||
a->release != b->release) {
return false;
}
return true;
}
/*
* Checks for duplicate key bindings (the same keycode or keysym is configured
* more than once). If a duplicate binding is found, a message is printed to
@ -730,31 +764,13 @@ void check_for_duplicate_bindings(struct context *context) {
TAILQ_FOREACH (bind, bindings, bindings) {
/* Abort when we reach the current keybinding, only check the
* bindings before */
if (bind == current)
if (bind == current) {
break;
}
/* Check if the input types are different */
if (bind->input_type != current->input_type)
continue;
/* Check if one is using keysym while the other is using bindsym.
* If so, skip. */
if ((bind->symbol == NULL && current->symbol != NULL) ||
(bind->symbol != NULL && current->symbol == NULL))
continue;
/* If bind is NULL, current has to be NULL, too (see above).
* If the keycodes differ, it can't be a duplicate. */
if (bind->symbol != NULL &&
strcasecmp(bind->symbol, current->symbol) != 0)
continue;
/* Check if the keycodes or modifiers are different. If so, they
* can't be duplicate */
if (bind->keycode != current->keycode ||
bind->event_state_mask != current->event_state_mask ||
bind->release != current->release)
if (!binding_same_key(bind, current)) {
continue;
}
context->has_errors = true;
if (current->keycode != 0) {

View File

@ -218,8 +218,20 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
goto done;
}
/* 2: focus this con. */
con_activate(con);
/* 2: focus this con or one of its children. */
Con *con_to_focus = con;
if (in_stacked && dest == CLICK_DECORATION) {
/* If the container is a tab/stacked container and the click happened
* on a tab, switch to the tab. If the tab contents were already
* focused, focus the tab container itself. If the tab container was
* already focused, cycle back to focusing the tab contents. */
if (was_focused || !con_has_parent(focused, con)) {
while (!TAILQ_EMPTY(&(con_to_focus->focus_head))) {
con_to_focus = TAILQ_FIRST(&(con_to_focus->focus_head));
}
}
}
con_activate(con_to_focus);
/* 3: For floating containers, we also want to raise them on click.
* We will skip handling events on floating cons in fullscreen mode */

View File

@ -1024,23 +1024,113 @@ void cmd_mode(I3_CMD, const char *mode) {
}
/*
* Implementation of 'move [window|container] [to] output <str>'.
* Implementation of 'move [window|container|workspace] [to] output <strings>'.
*
*/
void cmd_move_con_to_output(I3_CMD, const char *name) {
DLOG("Should move window to output \"%s\".\n", name);
HANDLE_EMPTY_MATCH;
void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) {
/* Initialize a data structure that is used to save multiple user-specified
* output names since this function is called multiple types for each
* command call. */
typedef struct user_output_name {
char *name;
TAILQ_ENTRY(user_output_name) user_output_names;
} user_output_name;
static TAILQ_HEAD(user_output_names_head, user_output_name) user_output_names = TAILQ_HEAD_INITIALIZER(user_output_names);
owindow *current;
bool had_error = false;
TAILQ_FOREACH (current, &owindows, owindows) {
DLOG("matching: %p / %s\n", current->con, current->con->name);
if (name) {
if (strcmp(name, "next") == 0) {
/* "next" here works like a wildcard: It "expands" to all available
* outputs. */
Output *output;
TAILQ_FOREACH (output, &outputs, outputs) {
user_output_name *co = scalloc(sizeof(user_output_name), 1);
co->name = sstrdup(output_primary_name(output));
TAILQ_INSERT_TAIL(&user_output_names, co, user_output_names);
}
return;
}
had_error |= !con_move_to_output_name(current->con, name, true);
user_output_name *co = scalloc(sizeof(user_output_name), 1);
co->name = sstrdup(name);
TAILQ_INSERT_TAIL(&user_output_names, co, user_output_names);
return;
}
cmd_output->needs_tree_render = true;
ysuccess(!had_error);
HANDLE_EMPTY_MATCH;
if (TAILQ_EMPTY(&user_output_names)) {
yerror("At least one output must be specified");
return;
}
bool success = false;
user_output_name *uo;
owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) {
Con *ws = con_get_workspace(current->con);
if (con_is_internal(ws)) {
continue;
}
Output *current_output = get_output_for_con(ws);
Output *target_output = NULL;
TAILQ_FOREACH (uo, &user_output_names, user_output_names) {
if (strcasecmp(output_primary_name(current_output), uo->name) == 0) {
/* The current output is in the user list */
while (true) {
/* This corrupts the outer loop but it is ok since we are
* going to break anyway. */
uo = TAILQ_NEXT(uo, user_output_names);
if (!uo) {
/* We reached the end of the list. We should use the
* first available output that, if it exists, is
* already saved in target_output. */
break;
}
Output *out = get_output_from_string(current_output, uo->name);
if (out) {
DLOG("Found next target for workspace %s from user list: %s\n", ws->name, uo->name);
target_output = out;
break;
}
}
break;
}
if (!target_output) {
/* The first available output from the list is used in 2 cases:
* 1. When we must wrap around the user list. For example, if
* user specifies outputs A B C and C is `current_output`.
* 2. When the current output is not in the user list. For
* example, user specifies A B C and D is `current_output`.
*/
DLOG("Found first target for workspace %s from user list: %s\n", ws->name, uo->name);
target_output = get_output_from_string(current_output, uo->name);
}
}
if (target_output) {
if (move_workspace) {
workspace_move_to_output(ws, target_output);
} else {
con_move_to_output(current->con, target_output, true);
}
success = true;
}
}
while (!TAILQ_EMPTY(&user_output_names)) {
uo = TAILQ_FIRST(&user_output_names);
free(uo->name);
TAILQ_REMOVE(&user_output_names, uo, user_output_names);
free(uo);
}
cmd_output->needs_tree_render = success;
if (success) {
ysuccess(true);
} else {
yerror("No output matched");
}
}
/*
@ -1094,36 +1184,6 @@ void cmd_floating(I3_CMD, const char *floating_mode) {
ysuccess(true);
}
/*
* Implementation of 'move workspace to [output] <str>'.
*
*/
void cmd_move_workspace_to_output(I3_CMD, const char *name) {
DLOG("should move workspace to output %s\n", name);
HANDLE_EMPTY_MATCH;
owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) {
Con *ws = con_get_workspace(current->con);
if (con_is_internal(ws)) {
continue;
}
Output *current_output = get_output_for_con(ws);
Output *target_output = get_output_from_string(current_output, name);
if (!target_output) {
yerror("Could not get output from string \"%s\"", name);
return;
}
workspace_move_to_output(ws, target_output);
}
cmd_output->needs_tree_render = true;
ysuccess(true);
}
/*
* Implementation of 'split v|h|t|vertical|horizontal|toggle'.
*
@ -1661,6 +1721,9 @@ void cmd_restart(I3_CMD) {
}
ipc_shutdown(SHUTDOWN_REASON_RESTART, exempt_fd);
unlink(config.ipc_socket_path);
if (current_log_stream_socket_path != NULL) {
unlink(current_log_stream_socket_path);
}
/* We need to call this manually since atexit handlers dont get called
* when exec()ing */
purge_zerobyte_logfile();
@ -1974,6 +2037,35 @@ void cmd_title_format(I3_CMD, const char *format) {
ysuccess(true);
}
/*
* Implementation of 'title_window_icon <yes|no>' and 'title_window_icon padding <px>'
*
*/
void cmd_title_window_icon(I3_CMD, const char *enable, int padding) {
if (enable != NULL && !boolstr(enable)) {
padding = -1;
}
DLOG("setting window_icon=%d\n", padding);
HANDLE_EMPTY_MATCH;
owindow *current;
TAILQ_FOREACH (current, &owindows, owindows) {
DLOG("setting window_icon for %p / %s\n", current->con, current->con->name);
current->con->window_icon_padding = padding;
if (current->con->window != NULL) {
/* Make sure the window title is redrawn immediately. */
current->con->window->name_x_changed = true;
} else {
/* For windowless containers we also need to force the redrawing. */
FREE(current->con->deco_render_params);
}
}
cmd_output->needs_tree_render = true;
ysuccess(true);
}
/*
* Implementation of 'rename workspace [<name>] to <name>'
*

View File

@ -56,40 +56,19 @@ typedef struct tokenptr {
#include "GENERATED_command_tokens.h"
/*******************************************************************************
* The (small) stack where identified literals are stored during the parsing
* of a single command (like $workspace).
******************************************************************************/
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;
};
/* 10 entries should be enough for everybody. */
static struct stack_entry stack[10];
/*
* Pushes a string (identified by 'identifier') on the stack. We simply use a
* single array, since the number of entries we have to store is very small.
*
*/
static void push_string(const char *identifier, char *str) {
static void push_string(struct stack *stack, const char *identifier, char *str) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier != NULL)
if (stack->stack[c].identifier != NULL)
continue;
/* Found a free slot, lets store it here. */
stack[c].identifier = identifier;
stack[c].val.str = str;
stack[c].type = STACK_STR;
stack->stack[c].identifier = identifier;
stack->stack[c].val.str = str;
stack->stack[c].type = STACK_STR;
return;
}
@ -103,15 +82,15 @@ static void push_string(const char *identifier, char *str) {
}
// TODO move to a common util
static void push_long(const char *identifier, long num) {
static void push_long(struct stack *stack, const char *identifier, long num) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier != NULL) {
if (stack->stack[c].identifier != NULL) {
continue;
}
stack[c].identifier = identifier;
stack[c].val.num = num;
stack[c].type = STACK_LONG;
stack->stack[c].identifier = identifier;
stack->stack[c].val.num = num;
stack->stack[c].type = STACK_LONG;
return;
}
@ -125,36 +104,36 @@ static void push_long(const char *identifier, long num) {
}
// TODO move to a common util
static const char *get_string(const char *identifier) {
static const char *get_string(struct stack *stack, const char *identifier) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier == NULL)
if (stack->stack[c].identifier == NULL)
break;
if (strcmp(identifier, stack[c].identifier) == 0)
return stack[c].val.str;
if (strcmp(identifier, stack->stack[c].identifier) == 0)
return stack->stack[c].val.str;
}
return NULL;
}
// TODO move to a common util
static long get_long(const char *identifier) {
static long get_long(struct stack *stack, const char *identifier) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier == NULL)
if (stack->stack[c].identifier == NULL)
break;
if (strcmp(identifier, stack[c].identifier) == 0)
return stack[c].val.num;
if (strcmp(identifier, stack->stack[c].identifier) == 0)
return stack->stack[c].val.num;
}
return 0;
}
// TODO move to a common util
static void clear_stack(void) {
static void clear_stack(struct stack *stack) {
for (int c = 0; c < 10; c++) {
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;
if (stack->stack[c].type == STACK_STR)
free(stack->stack[c].val.str);
stack->stack[c].identifier = NULL;
stack->stack[c].val.str = NULL;
stack->stack[c].val.num = 0;
}
}
@ -163,9 +142,12 @@ static void clear_stack(void) {
******************************************************************************/
static cmdp_state state;
#ifndef TEST_PARSER
static Match current_match;
#endif
/*******************************************************************************
* The (small) stack where identified literals are stored during the parsing
* of a single command (like $workspace).
******************************************************************************/
static struct stack stack;
static struct CommandResultIR subcommand_output;
static struct CommandResultIR command_output;
@ -176,19 +158,19 @@ static void next_state(const cmdp_token *token) {
subcommand_output.json_gen = command_output.json_gen;
subcommand_output.client = command_output.client;
subcommand_output.needs_tree_render = false;
GENERATED_call(token->extra.call_identifier, &subcommand_output);
GENERATED_call(&current_match, &stack, token->extra.call_identifier, &subcommand_output);
state = subcommand_output.next_state;
/* If any subcommand requires a tree_render(), we need to make the
* whole parser result request a tree_render(). */
if (subcommand_output.needs_tree_render)
command_output.needs_tree_render = true;
clear_stack();
clear_stack(&stack);
return;
}
state = token->next_state;
if (state == INITIAL) {
clear_stack();
clear_stack(&stack);
}
}
@ -296,8 +278,9 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
/* A literal. */
if (token->name[0] == '\'') {
if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
if (token->identifier != NULL)
push_string(token->identifier, sstrdup(token->name + 1));
if (token->identifier != NULL) {
push_string(&stack, token->identifier, sstrdup(token->name + 1));
}
walk += strlen(token->name) - 1;
next_state(token);
token_handled = true;
@ -319,8 +302,9 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
if (end == walk)
continue;
if (token->identifier != NULL)
push_long(token->identifier, num);
if (token->identifier != NULL) {
push_long(&stack, token->identifier, num);
}
/* Set walk to the first non-number character */
walk = end;
@ -333,8 +317,9 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
strcmp(token->name, "word") == 0) {
char *str = parse_string(&walk, (token->name[0] != 's'));
if (str != NULL) {
if (token->identifier)
push_string(token->identifier, str);
if (token->identifier) {
push_string(&stack, token->identifier, str);
}
/* If we are at the end of a quoted string, skip the ending
* double quote. */
if (*walk == '"')
@ -436,7 +421,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
y(map_close);
free(position);
clear_stack();
clear_stack(&stack);
break;
}
}

View File

@ -43,6 +43,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) {
new->window = window;
new->border_style = config.default_border;
new->current_border_width = -1;
new->window_icon_padding = -1;
if (window) {
new->depth = window->depth;
} else {
@ -1855,9 +1856,9 @@ void con_set_layout(Con *con, layout_t layout) {
con_attach(new, con, false);
tree_flatten(croot);
con_force_split_parents_redraw(con);
return;
}
con_force_split_parents_redraw(con);
return;
}
if (layout == L_DEFAULT) {
@ -2304,20 +2305,25 @@ i3String *con_parse_title_format(Con *con) {
char *title;
char *class;
char *instance;
char *machine;
if (win == NULL) {
title = pango_escape_markup(con_get_tree_representation(con));
class = sstrdup("i3-frame");
instance = sstrdup("i3-frame");
machine = sstrdup("");
} else {
title = pango_escape_markup(sstrdup((win->name == NULL) ? "" : i3string_as_utf8(win->name)));
class = pango_escape_markup(sstrdup((win->class_class == NULL) ? "" : win->class_class));
instance = pango_escape_markup(sstrdup((win->class_instance == NULL) ? "" : win->class_instance));
machine = pango_escape_markup(sstrdup((win->machine == NULL) ? "" : win->machine));
}
placeholder_t placeholders[] = {
{.name = "%title", .value = title},
{.name = "%class", .value = class},
{.name = "%instance", .value = instance}};
{.name = "%instance", .value = instance},
{.name = "%machine", .value = machine},
};
const size_t num = sizeof(placeholders) / sizeof(placeholder_t);
char *formatted_str = format_placeholders(con->title_format, &placeholders[0], num);

View File

@ -10,13 +10,16 @@
*/
#include "all.h"
#include <libgen.h>
#include <unistd.h>
#include <xkbcommon/xkbcommon.h>
char *current_configpath = NULL;
char *current_config = NULL;
Config config;
struct modes_head modes;
struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs);
struct includedfiles_head included_files = TAILQ_HEAD_INITIALIZER(included_files);
/*
* Ungrabs all keys, to be called before re-grabbing the keys because of a
@ -225,8 +228,43 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
"$XDG_CONFIG_HOME/i3/config, ~/.i3/config, $XDG_CONFIG_DIRS/i3/config "
"and " SYSCONFDIR "/i3/config)");
}
LOG("Parsing configfile %s\n", current_configpath);
const bool result = parse_file(current_configpath, load_type != C_VALIDATE);
IncludedFile *file;
while (!TAILQ_EMPTY(&included_files)) {
file = TAILQ_FIRST(&included_files);
FREE(file->path);
FREE(file->raw_contents);
FREE(file->variable_replaced_contents);
TAILQ_REMOVE(&included_files, file, files);
FREE(file);
}
char resolved_path[PATH_MAX] = {'\0'};
if (realpath(current_configpath, resolved_path) == NULL) {
die("realpath(%s): %s", current_configpath, strerror(errno));
}
file = scalloc(1, sizeof(IncludedFile));
file->path = sstrdup(resolved_path);
TAILQ_INSERT_TAIL(&included_files, file, files);
LOG("Parsing configfile %s\n", resolved_path);
struct stack stack;
memset(&stack, '\0', sizeof(struct stack));
struct parser_ctx ctx = {
.use_nagbar = (load_type != C_VALIDATE),
.assume_v4 = false,
.stack = &stack,
};
SLIST_INIT(&(ctx.variables));
const int result = parse_file(&ctx, resolved_path, file);
free_variables(&ctx);
if (result == -1) {
die("Could not open configuration file: %s\n", strerror(errno));
}
extract_workspace_names_from_bindings();
reorder_bindings();
if (config.font.type == FONT_TYPE_NONE && load_type != C_VALIDATE) {
ELOG("You did not specify required configuration option \"font\"\n");
@ -245,5 +283,5 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
xcb_flush(conn);
}
return result;
return result == 0;
}

View File

@ -9,6 +9,86 @@
*/
#include "all.h"
#include <wordexp.h>
/*******************************************************************************
* Include functions.
******************************************************************************/
CFGFUN(include, const char *pattern) {
DLOG("include %s\n", pattern);
wordexp_t p;
const int ret = wordexp(pattern, &p, 0);
if (ret != 0) {
ELOG("wordexp(%s): error %d\n", pattern, ret);
result->has_errors = true;
return;
}
char **w = p.we_wordv;
for (size_t i = 0; i < p.we_wordc; i++) {
char resolved_path[PATH_MAX] = {'\0'};
if (realpath(w[i], resolved_path) == NULL) {
LOG("Skipping %s: %s\n", w[i], strerror(errno));
continue;
}
bool skip = false;
IncludedFile *file;
TAILQ_FOREACH (file, &included_files, files) {
if (strcmp(file->path, resolved_path) == 0) {
skip = true;
break;
}
}
if (skip) {
LOG("Skipping file %s (already included)\n", resolved_path);
continue;
}
LOG("Including config file %s\n", resolved_path);
file = scalloc(1, sizeof(IncludedFile));
file->path = sstrdup(resolved_path);
TAILQ_INSERT_TAIL(&included_files, file, files);
struct stack stack;
memset(&stack, '\0', sizeof(struct stack));
struct parser_ctx ctx = {
.use_nagbar = result->ctx->use_nagbar,
/* The include mechanism was added in v4, so we can skip the
* auto-detection and get rid of the risk of detecting the wrong
* version in potentially very short include fragments: */
.assume_v4 = true,
.stack = &stack,
.variables = result->ctx->variables,
};
switch (parse_file(&ctx, resolved_path, file)) {
case PARSE_FILE_SUCCESS:
break;
case PARSE_FILE_FAILED:
ELOG("including config file %s: %s\n", resolved_path, strerror(errno));
/* fallthrough */
case PARSE_FILE_CONFIG_ERRORS:
result->has_errors = true;
TAILQ_REMOVE(&included_files, file, files);
FREE(file->path);
FREE(file->raw_contents);
FREE(file->variable_replaced_contents);
FREE(file);
break;
default:
/* missing case statement */
assert(false);
break;
}
}
wordfree(&p);
}
/*******************************************************************************
* Criteria functions.
******************************************************************************/
@ -45,15 +125,6 @@ CFGFUN(criteria_add, const char *ctype, const char *cvalue) {
* Utility functions
******************************************************************************/
static bool eval_boolstr(const char *str) {
return (strcasecmp(str, "1") == 0 ||
strcasecmp(str, "yes") == 0 ||
strcasecmp(str, "true") == 0 ||
strcasecmp(str, "on") == 0 ||
strcasecmp(str, "enable") == 0 ||
strcasecmp(str, "active") == 0);
}
/*
* A utility function to convert a string containing the group and modifiers to
* the corresponding bit mask.
@ -237,14 +308,14 @@ CFGFUN(hide_edge_borders, const char *borders) {
config.hide_edge_borders = HEBM_BOTH;
else if (strcmp(borders, "none") == 0)
config.hide_edge_borders = HEBM_NONE;
else if (eval_boolstr(borders))
else if (boolstr(borders))
config.hide_edge_borders = HEBM_VERTICAL;
else
config.hide_edge_borders = HEBM_NONE;
}
CFGFUN(focus_follows_mouse, const char *value) {
config.disable_focus_follows_mouse = !eval_boolstr(value);
config.disable_focus_follows_mouse = !boolstr(value);
}
CFGFUN(mouse_warping, const char *value) {
@ -255,11 +326,11 @@ CFGFUN(mouse_warping, const char *value) {
}
CFGFUN(force_xinerama, const char *value) {
config.force_xinerama = eval_boolstr(value);
config.force_xinerama = boolstr(value);
}
CFGFUN(disable_randr15, const char *value) {
config.disable_randr15 = eval_boolstr(value);
config.disable_randr15 = boolstr(value);
}
CFGFUN(focus_wrapping, const char *value) {
@ -267,7 +338,7 @@ CFGFUN(focus_wrapping, const char *value) {
config.focus_wrapping = FOCUS_WRAPPING_FORCE;
} else if (strcmp(value, "workspace") == 0) {
config.focus_wrapping = FOCUS_WRAPPING_WORKSPACE;
} else if (eval_boolstr(value)) {
} else if (boolstr(value)) {
config.focus_wrapping = FOCUS_WRAPPING_ON;
} else {
config.focus_wrapping = FOCUS_WRAPPING_OFF;
@ -276,7 +347,7 @@ CFGFUN(focus_wrapping, const char *value) {
CFGFUN(force_focus_wrapping, const char *value) {
/* Legacy syntax. */
if (eval_boolstr(value)) {
if (boolstr(value)) {
config.focus_wrapping = FOCUS_WRAPPING_FORCE;
} else {
/* For "force_focus_wrapping off", don't enable or disable
@ -288,7 +359,7 @@ CFGFUN(force_focus_wrapping, const char *value) {
}
CFGFUN(workspace_back_and_forth, const char *value) {
config.workspace_auto_back_and_forth = eval_boolstr(value);
config.workspace_auto_back_and_forth = boolstr(value);
}
CFGFUN(fake_outputs, const char *outputs) {
@ -330,7 +401,7 @@ CFGFUN(title_align, const char *alignment) {
}
CFGFUN(show_marks, const char *value) {
config.show_marks = eval_boolstr(value);
config.show_marks = boolstr(value);
}
static char *current_workspace = NULL;
@ -518,7 +589,7 @@ CFGFUN(bar_output, const char *output) {
}
CFGFUN(bar_verbose, const char *verbose) {
current_bar->verbose = eval_boolstr(verbose);
current_bar->verbose = boolstr(verbose);
}
CFGFUN(bar_modifier, const char *modifiers) {
@ -638,11 +709,11 @@ CFGFUN(bar_status_command, const char *command) {
}
CFGFUN(bar_binding_mode_indicator, const char *value) {
current_bar->hide_binding_mode_indicator = !eval_boolstr(value);
current_bar->hide_binding_mode_indicator = !boolstr(value);
}
CFGFUN(bar_workspace_buttons, const char *value) {
current_bar->hide_workspace_buttons = !eval_boolstr(value);
current_bar->hide_workspace_buttons = !boolstr(value);
}
CFGFUN(bar_workspace_min_width, const long width) {
@ -650,11 +721,11 @@ CFGFUN(bar_workspace_min_width, const long width) {
}
CFGFUN(bar_strip_workspace_numbers, const char *value) {
current_bar->strip_workspace_numbers = eval_boolstr(value);
current_bar->strip_workspace_numbers = boolstr(value);
}
CFGFUN(bar_strip_workspace_name, const char *value) {
current_bar->strip_workspace_name = eval_boolstr(value);
current_bar->strip_workspace_name = boolstr(value);
}
CFGFUN(bar_start) {

View File

@ -35,18 +35,14 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <libgen.h>
#include <xcb/xcb_xrm.h>
// Macros to make the YAJL API a bit easier to use.
#define y(x, ...) yajl_gen_##x(command_output.json_gen, ##__VA_ARGS__)
#define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char *)str, strlen(str))
xcb_xrm_database_t *database = NULL;
#ifndef TEST_PARSER
pid_t config_error_nagbar_pid = -1;
static struct context *context;
#endif
/*******************************************************************************
@ -76,46 +72,25 @@ typedef struct tokenptr {
#include "GENERATED_config_tokens.h"
/*******************************************************************************
* The (small) stack where identified literals are stored during the parsing
* of a single command (like $workspace).
******************************************************************************/
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;
};
/* 10 entries should be enough for everybody. */
static struct stack_entry stack[10];
/*
* Pushes a string (identified by 'identifier') on the stack. We simply use a
* single array, since the number of entries we have to store is very small.
*
*/
static void push_string(const char *identifier, const char *str) {
static void push_string(struct stack *ctx, const char *identifier, const char *str) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier != NULL &&
strcmp(stack[c].identifier, identifier) != 0)
if (ctx->stack[c].identifier != NULL &&
strcmp(ctx->stack[c].identifier, identifier) != 0)
continue;
if (stack[c].identifier == NULL) {
if (ctx->stack[c].identifier == NULL) {
/* Found a free slot, lets store it here. */
stack[c].identifier = identifier;
stack[c].val.str = sstrdup(str);
stack[c].type = STACK_STR;
ctx->stack[c].identifier = identifier;
ctx->stack[c].val.str = sstrdup(str);
ctx->stack[c].type = STACK_STR;
} else {
/* Append the value. */
char *prev = stack[c].val.str;
sasprintf(&(stack[c].val.str), "%s,%s", prev, str);
char *prev = ctx->stack[c].val.str;
sasprintf(&(ctx->stack[c].val.str), "%s,%s", prev, str);
free(prev);
}
return;
@ -130,14 +105,15 @@ static void push_string(const char *identifier, const char *str) {
exit(EXIT_FAILURE);
}
static void push_long(const char *identifier, long num) {
static void push_long(struct stack *ctx, const char *identifier, long num) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier != NULL)
if (ctx->stack[c].identifier != NULL) {
continue;
}
/* Found a free slot, lets store it here. */
stack[c].identifier = identifier;
stack[c].val.num = num;
stack[c].type = STACK_LONG;
ctx->stack[c].identifier = identifier;
ctx->stack[c].val.num = num;
ctx->stack[c].type = STACK_LONG;
return;
}
@ -150,33 +126,33 @@ static void push_long(const char *identifier, long num) {
exit(EXIT_FAILURE);
}
static const char *get_string(const char *identifier) {
static const char *get_string(struct stack *ctx, const char *identifier) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier == NULL)
if (ctx->stack[c].identifier == NULL)
break;
if (strcmp(identifier, stack[c].identifier) == 0)
return stack[c].val.str;
if (strcmp(identifier, ctx->stack[c].identifier) == 0)
return ctx->stack[c].val.str;
}
return NULL;
}
static long get_long(const char *identifier) {
static long get_long(struct stack *ctx, const char *identifier) {
for (int c = 0; c < 10; c++) {
if (stack[c].identifier == NULL)
if (ctx->stack[c].identifier == NULL)
break;
if (strcmp(identifier, stack[c].identifier) == 0)
return stack[c].val.num;
if (strcmp(identifier, ctx->stack[c].identifier) == 0)
return ctx->stack[c].val.num;
}
return 0;
}
static void clear_stack(void) {
static void clear_stack(struct stack *ctx) {
for (int c = 0; c < 10; c++) {
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;
if (ctx->stack[c].type == STACK_STR)
free(ctx->stack[c].val.str);
ctx->stack[c].identifier = NULL;
ctx->stack[c].val.str = NULL;
ctx->stack[c].val.num = 0;
}
}
@ -184,50 +160,42 @@ static void clear_stack(void) {
* The parser itself.
******************************************************************************/
static cmdp_state state;
static Match current_match;
static struct ConfigResultIR subcommand_output;
static struct ConfigResultIR command_output;
/* A list which contains the states that lead to the current state, e.g.
* INITIAL, WORKSPACE_LAYOUT.
* When jumping back to INITIAL, statelist_idx will simply be set to 1
* (likewise for other states, e.g. MODE or BAR).
* This list is used to process the nearest error token. */
static cmdp_state statelist[10] = {INITIAL};
/* NB: statelist_idx points to where the next entry will be inserted */
static int statelist_idx = 1;
#include "GENERATED_config_call.h"
static void next_state(const cmdp_token *token) {
static void next_state(const cmdp_token *token, struct parser_ctx *ctx) {
cmdp_state _next_state = token->next_state;
//printf("token = name %s identifier %s\n", token->name, token->identifier);
//printf("next_state = %d\n", token->next_state);
if (token->next_state == __CALL) {
subcommand_output.json_gen = command_output.json_gen;
GENERATED_call(token->extra.call_identifier, &subcommand_output);
struct ConfigResultIR subcommand_output = {
.ctx = ctx,
};
GENERATED_call(&(ctx->current_match), ctx->stack, token->extra.call_identifier, &subcommand_output);
if (subcommand_output.has_errors) {
ctx->has_errors = true;
}
_next_state = subcommand_output.next_state;
clear_stack();
clear_stack(ctx->stack);
}
state = _next_state;
if (state == INITIAL) {
clear_stack();
ctx->state = _next_state;
if (ctx->state == INITIAL) {
clear_stack(ctx->stack);
}
/* 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)
for (int i = 0; i < ctx->statelist_idx; i++) {
if ((cmdp_state)(ctx->statelist[i]) != _next_state) {
continue;
statelist_idx = i + 1;
}
ctx->statelist_idx = i + 1;
return;
}
/* Otherwise, the state is new and we add it to the list */
statelist[statelist_idx++] = _next_state;
ctx->statelist[ctx->statelist_idx++] = _next_state;
}
/*
@ -257,7 +225,7 @@ static char *single_line(const char *start) {
return result;
}
struct ConfigResultIR *parse_config(const char *input, struct context *context) {
static void parse_config(struct parser_ctx *ctx, const char *input, struct context *context) {
/* Dump the entire config file into the debug log. We cannot just use
* DLOG("%s", input); because one log message must not exceed 4 KiB. */
const char *dumpwalk = input;
@ -273,13 +241,11 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
}
linecnt++;
}
state = INITIAL;
statelist_idx = 1;
/* A YAJL JSON generator used for formatting replies. */
command_output.json_gen = yajl_gen_alloc(NULL);
y(array_open);
ctx->state = INITIAL;
for (int i = 0; i < 10; i++) {
ctx->statelist[i] = INITIAL;
}
ctx->statelist_idx = 1;
const char *walk = input;
const size_t len = strlen(input);
@ -290,7 +256,10 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
// TODO: make this testable
#ifndef TEST_PARSER
cfg_criteria_init(&current_match, &subcommand_output, INITIAL);
struct ConfigResultIR subcommand_output = {
.ctx = ctx,
};
cfg_criteria_init(&(ctx->current_match), &subcommand_output, INITIAL);
#endif
/* The "<=" operator is intentional: We also handle the terminating 0-byte
@ -303,7 +272,7 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
//printf("remaining input: %s\n", walk);
cmdp_token_ptr *ptr = &(tokens[state]);
cmdp_token_ptr *ptr = &(tokens[ctx->state]);
token_handled = false;
for (c = 0; c < ptr->n; c++) {
token = &(ptr->array[c]);
@ -311,10 +280,11 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
/* A literal. */
if (token->name[0] == '\'') {
if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
if (token->identifier != NULL)
push_string(token->identifier, token->name + 1);
if (token->identifier != NULL) {
push_string(ctx->stack, token->identifier, token->name + 1);
}
walk += strlen(token->name) - 1;
next_state(token);
next_state(token, ctx);
token_handled = true;
break;
}
@ -334,12 +304,13 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
if (end == walk)
continue;
if (token->identifier != NULL)
push_long(token->identifier, num);
if (token->identifier != NULL) {
push_long(ctx->stack, token->identifier, num);
}
/* Set walk to the first non-number character */
walk = end;
next_state(token);
next_state(token, ctx);
token_handled = true;
break;
}
@ -382,14 +353,15 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
inpos++;
str[outpos] = beginning[inpos];
}
if (token->identifier)
push_string(token->identifier, str);
if (token->identifier) {
push_string(ctx->stack, token->identifier, str);
}
free(str);
/* If we are at the end of a quoted string, skip the ending
* double quote. */
if (*walk == '"')
walk++;
next_state(token);
next_state(token, ctx);
token_handled = true;
break;
}
@ -398,7 +370,7 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
if (strcmp(token->name, "line") == 0) {
while (*walk != '\0' && *walk != '\n' && *walk != '\r')
walk++;
next_state(token);
next_state(token, ctx);
token_handled = true;
linecnt++;
walk++;
@ -408,7 +380,7 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
if (strcmp(token->name, "end") == 0) {
//printf("checking for end: *%s*\n", walk);
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
next_state(token);
next_state(token, ctx);
token_handled = true;
/* To make sure we start with an appropriate matching
* datastructure for commands which do *not* specify any
@ -416,7 +388,7 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
* every command. */
// TODO: make this testable
#ifndef TEST_PARSER
cfg_criteria_init(&current_match, &subcommand_output, INITIAL);
cfg_criteria_init(&(ctx->current_match), &subcommand_output, INITIAL);
#endif
linecnt++;
walk++;
@ -515,41 +487,24 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
context->has_errors = true;
/* Format this error message as a JSON reply. */
y(map_open);
ystr("success");
y(bool, false);
/* We set parse_error to true to distinguish this from other
* errors. i3-nagbar is spawned upon keypresses only for parser
* errors. */
ystr("parse_error");
y(bool, true);
ystr("error");
ystr(errormessage);
ystr("input");
ystr(input);
ystr("errorposition");
ystr(position);
y(map_close);
/* Skip the rest of this line, but continue parsing. */
while ((size_t)(walk - input) <= len && *walk != '\n')
walk++;
free(position);
free(errormessage);
clear_stack();
clear_stack(ctx->stack);
/* To figure out in which state to go (e.g. MODE or INITIAL),
* we find the nearest state which contains an <error> token
* and follow that one. */
bool error_token_found = false;
for (int i = statelist_idx - 1; (i >= 0) && !error_token_found; i--) {
cmdp_token_ptr *errptr = &(tokens[statelist[i]]);
for (int i = ctx->statelist_idx - 1; (i >= 0) && !error_token_found; i--) {
cmdp_token_ptr *errptr = &(tokens[ctx->statelist[i]]);
for (int j = 0; j < errptr->n; j++) {
if (strcmp(errptr->array[j].name, "error") != 0)
continue;
next_state(&(errptr->array[j]));
next_state(&(errptr->array[j]), ctx);
error_token_found = true;
break;
}
@ -558,10 +513,6 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
assert(error_token_found);
}
}
y(array_close);
return &command_output;
}
/*******************************************************************************
@ -612,9 +563,17 @@ int main(int argc, char *argv[]) {
fprintf(stderr, "Syntax: %s <command>\n", argv[0]);
return 1;
}
struct stack stack;
memset(&stack, '\0', sizeof(struct stack));
struct parser_ctx ctx = {
.use_nagbar = false,
.assume_v4 = false,
.stack = &stack,
};
SLIST_INIT(&(ctx.variables));
struct context context;
context.filename = "<stdin>";
parse_config(argv[1], &context);
parse_config(&ctx, argv[1], &context);
}
#else
@ -636,6 +595,7 @@ static int detect_version(char *buf) {
/* check for some v4-only statements */
if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 ||
strncasecmp(line, "include", strlen("include")) == 0 ||
strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 ||
strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 ||
strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) {
@ -877,34 +837,63 @@ static char *get_resource(char *name) {
return resource;
}
/*
* Releases the memory of all variables in ctx.
*
*/
void free_variables(struct parser_ctx *ctx) {
struct Variable *current;
while (!SLIST_EMPTY(&(ctx->variables))) {
current = SLIST_FIRST(&(ctx->variables));
FREE(current->key);
FREE(current->value);
SLIST_REMOVE_HEAD(&(ctx->variables), variables);
FREE(current);
}
}
/*
* Parses the given file by first replacing the variables, then calling
* parse_config and possibly launching i3-nagbar.
*
*/
bool parse_file(const char *f, bool use_nagbar) {
struct variables_head variables = SLIST_HEAD_INITIALIZER(&variables);
parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFile *included_file) {
int fd;
struct stat stbuf;
char *buf;
FILE *fstr;
char buffer[4096], key[512], value[4096], *continuation = NULL;
if ((fd = open(f, O_RDONLY)) == -1)
die("Could not open configuration file: %s\n", strerror(errno));
char *old_dir = get_current_dir_name();
char *dir = NULL;
/* dirname(3) might modify the buffer, so make a copy: */
char *dirbuf = sstrdup(f);
if ((dir = dirname(dirbuf)) != NULL) {
LOG("Changing working directory to config file directory %s\n", dir);
if (chdir(dir) == -1) {
ELOG("chdir(%s) failed: %s\n", dir, strerror(errno));
return PARSE_FILE_FAILED;
}
}
free(dirbuf);
if (fstat(fd, &stbuf) == -1)
die("Could not fstat file: %s\n", strerror(errno));
if ((fd = open(f, O_RDONLY)) == -1) {
return PARSE_FILE_FAILED;
}
if (fstat(fd, &stbuf) == -1) {
return PARSE_FILE_FAILED;
}
buf = scalloc(stbuf.st_size + 1, 1);
if ((fstr = fdopen(fd, "r")) == NULL)
die("Could not fdopen: %s\n", strerror(errno));
if ((fstr = fdopen(fd, "r")) == NULL) {
return PARSE_FILE_FAILED;
}
FREE(current_config);
current_config = scalloc(stbuf.st_size + 1, 1);
if ((ssize_t)fread(current_config, 1, stbuf.st_size, fstr) != stbuf.st_size) {
die("Could not fread: %s\n", strerror(errno));
included_file->raw_contents = scalloc(stbuf.st_size + 1, 1);
if ((ssize_t)fread(included_file->raw_contents, 1, stbuf.st_size, fstr) != stbuf.st_size) {
return PARSE_FILE_FAILED;
}
rewind(fstr);
@ -916,7 +905,7 @@ bool parse_file(const char *f, bool use_nagbar) {
if (fgets(continuation, sizeof(buffer) - (continuation - buffer), fstr) == NULL) {
if (feof(fstr))
break;
die("Could not read configuration file\n");
return PARSE_FILE_FAILED;
}
if (buffer[strlen(buffer) - 1] != '\n' && !feof(fstr)) {
ELOG("Your line continuation is too long, it exceeds %zd bytes\n", sizeof(buffer));
@ -960,7 +949,7 @@ bool parse_file(const char *f, bool use_nagbar) {
continue;
}
upsert_variable(&variables, v_key, v_value);
upsert_variable(&(ctx->variables), v_key, v_value);
continue;
} else if (strcasecmp(key, "set_from_resource") == 0) {
char res_name[512] = {'\0'};
@ -993,7 +982,7 @@ bool parse_file(const char *f, bool use_nagbar) {
res_value = sstrdup(fallback);
}
upsert_variable(&variables, v_key, res_value);
upsert_variable(&(ctx->variables), v_key, res_value);
FREE(res_value);
continue;
}
@ -1014,7 +1003,7 @@ bool parse_file(const char *f, bool use_nagbar) {
* variables (otherwise we will count them twice, which is bad when
* 'extra' is negative) */
char *bufcopy = sstrdup(buf);
SLIST_FOREACH (current, &variables, variables) {
SLIST_FOREACH (current, &(ctx->variables), variables) {
int extra = (strlen(current->value) - strlen(current->key));
char *next;
for (next = bufcopy;
@ -1034,12 +1023,12 @@ bool parse_file(const char *f, bool use_nagbar) {
destwalk = new;
while (walk < (buf + stbuf.st_size)) {
/* Find the next variable */
SLIST_FOREACH (current, &variables, variables) {
SLIST_FOREACH (current, &(ctx->variables), variables) {
current->next_match = strcasestr(walk, current->key);
}
nearest = NULL;
int distance = stbuf.st_size;
SLIST_FOREACH (current, &variables, variables) {
SLIST_FOREACH (current, &(ctx->variables), variables) {
if (current->next_match == NULL)
continue;
if ((current->next_match - walk) < distance) {
@ -1064,7 +1053,10 @@ bool parse_file(const char *f, bool use_nagbar) {
/* analyze the string to find out whether this is an old config file (3.x)
* or a new config file (4.x). If its old, we run the converter script. */
int version = detect_version(buf);
int version = 4;
if (!ctx->assume_v4) {
version = detect_version(buf);
}
if (version == 3) {
/* We need to convert this v3 configuration */
char *converted = migrate_config(new, strlen(new));
@ -1090,17 +1082,18 @@ bool parse_file(const char *f, bool use_nagbar) {
}
}
context = scalloc(1, sizeof(struct context));
included_file->variable_replaced_contents = sstrdup(new);
struct context *context = scalloc(1, sizeof(struct context));
context->filename = f;
parse_config(ctx, new, context);
if (ctx->has_errors) {
context->has_errors = true;
}
struct ConfigResultIR *config_output = parse_config(new, context);
yajl_gen_free(config_output->json_gen);
extract_workspace_names_from_bindings();
check_for_duplicate_bindings(context);
reorder_bindings();
if (use_nagbar && (context->has_errors || context->has_warnings || invalid_sets)) {
if (ctx->use_nagbar && (context->has_errors || context->has_warnings || invalid_sets)) {
ELOG("FYI: You are using i3 version %s\n", i3_version);
if (version == 3)
ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
@ -1108,22 +1101,22 @@ bool parse_file(const char *f, bool use_nagbar) {
start_config_error_nagbar(f, context->has_errors || invalid_sets);
}
bool has_errors = context->has_errors;
const bool has_errors = context->has_errors;
FREE(context->line_copy);
free(context);
free(new);
free(buf);
while (!SLIST_EMPTY(&variables)) {
current = SLIST_FIRST(&variables);
FREE(current->key);
FREE(current->value);
SLIST_REMOVE_HEAD(&variables, variables);
FREE(current);
if (chdir(old_dir) == -1) {
ELOG("chdir(%s) failed: %s\n", old_dir, strerror(errno));
return PARSE_FILE_FAILED;
}
return !has_errors;
free(old_dir);
if (has_errors) {
return PARSE_FILE_CONFIG_ERRORS;
}
return PARSE_FILE_SUCCESS;
}
#endif

View File

@ -14,22 +14,34 @@
#include <time.h>
#include <unistd.h>
static bool human_readable_key, loaded_config_file_name_key;
static char *human_readable_version, *loaded_config_file_name;
static bool human_readable_key;
static bool loaded_config_file_name_key;
static bool included_config_file_names;
static char *human_readable_version;
static char *loaded_config_file_name;
static int version_string(void *ctx, const unsigned char *val, size_t len) {
if (human_readable_key)
if (human_readable_key) {
sasprintf(&human_readable_version, "%.*s", (int)len, val);
if (loaded_config_file_name_key)
}
if (loaded_config_file_name_key) {
sasprintf(&loaded_config_file_name, "%.*s", (int)len, val);
}
if (included_config_file_names) {
IncludedFile *file = scalloc(1, sizeof(IncludedFile));
sasprintf(&(file->path), "%.*s", (int)len, val);
TAILQ_INSERT_TAIL(&included_files, file, files);
}
return 1;
}
static int version_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
human_readable_key = (stringlen == strlen("human_readable") &&
strncmp((const char *)stringval, "human_readable", strlen("human_readable")) == 0);
loaded_config_file_name_key = (stringlen == strlen("loaded_config_file_name") &&
strncmp((const char *)stringval, "loaded_config_file_name", strlen("loaded_config_file_name")) == 0);
#define KEY_MATCHES(x) (stringlen == strlen(x) && strncmp((const char *)stringval, x, strlen(x)) == 0)
human_readable_key = KEY_MATCHES("human_readable");
loaded_config_file_name_key = KEY_MATCHES("loaded_config_file_name");
included_config_file_names = KEY_MATCHES("included_config_file_names");
#undef KEY_MATCHES
return 1;
}
@ -38,6 +50,22 @@ static yajl_callbacks version_callbacks = {
.yajl_map_key = version_map_key,
};
static void print_config_path(const char *path, const char *role) {
struct stat sb;
time_t now;
char mtime[64];
printf(" %s (%s)", path, role);
if (stat(path, &sb) == -1) {
printf("\n");
ELOG("Cannot stat config file \"%s\"\n", path);
} else {
strftime(mtime, sizeof(mtime), "%c", localtime(&(sb.st_mtime)));
time(&now);
printf(" (last modified: %s, %.f seconds ago)\n", mtime, difftime(now, sb.st_mtime));
}
}
/*
* Connects to i3 to find out the currently running version. Useful since it
* might be different from the version compiled into this binary (maybe the
@ -98,17 +126,11 @@ void display_running_version(void) {
printf("Running i3 version: %s (pid %s)\n", human_readable_version, pid_from_atom);
if (loaded_config_file_name) {
struct stat sb;
time_t now;
char mtime[64];
printf("Loaded i3 config: %s", loaded_config_file_name);
if (stat(loaded_config_file_name, &sb) == -1) {
printf("\n");
ELOG("Cannot stat config file \"%s\"\n", loaded_config_file_name);
} else {
strftime(mtime, sizeof(mtime), "%c", localtime(&(sb.st_mtime)));
time(&now);
printf(" (Last modified: %s, %.f seconds ago)\n", mtime, difftime(now, sb.st_mtime));
printf("Loaded i3 config:\n");
print_config_path(loaded_config_file_name, "main");
IncludedFile *file;
TAILQ_FOREACH (file, &included_files, files) {
print_config_path(file->path, "included");
}
}

View File

@ -278,10 +278,10 @@ void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows) {
*/
void ewmh_update_sticky(xcb_window_t window, bool sticky) {
if (sticky) {
DLOG("Setting _NET_WM_STATE_STICKY for window = %d.\n", window);
DLOG("Setting _NET_WM_STATE_STICKY for window = %08x.\n", window);
xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY);
} else {
DLOG("Removing _NET_WM_STATE_STICKY for window = %d.\n", window);
DLOG("Removing _NET_WM_STATE_STICKY for window = %08x.\n", window);
xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY);
}
}
@ -292,10 +292,10 @@ void ewmh_update_sticky(xcb_window_t window, bool sticky) {
*/
void ewmh_update_focused(xcb_window_t window, bool is_focused) {
if (is_focused) {
DLOG("Setting _NET_WM_STATE_FOCUSED for window = %d.\n", window);
DLOG("Setting _NET_WM_STATE_FOCUSED for window = %08x.\n", window);
xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
} else {
DLOG("Removing _NET_WM_STATE_FOCUSED for window = %d.\n", window);
DLOG("Removing _NET_WM_STATE_FOCUSED for window = %08x.\n", window);
xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
}
}

View File

@ -803,12 +803,12 @@ static void handle_client_message(xcb_client_message_event_t *event) {
if (event->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) {
/* For compatiblity reasons, Wine will request iconic state and cannot ensure that the WM has agreed on it;
* immediately revert to normal to avoid being stuck in a paused state. */
DLOG("Client has requested iconic state, rejecting. (window = %d)\n", event->window);
DLOG("Client has requested iconic state, rejecting. (window = %08x)\n", event->window);
long data[] = {XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE};
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, event->window,
A_WM_STATE, A_WM_STATE, 32, 2, data);
} else {
DLOG("Not handling WM_CHANGE_STATE request. (window = %d, state = %d)\n", event->window, event->data.data32[0]);
DLOG("Not handling WM_CHANGE_STATE request. (window = %08x, state = %d)\n", event->window, event->data.data32[0]);
}
} else if (event->type == A__NET_CURRENT_DESKTOP) {
/* This request is used by pagers and bars to change the current
@ -876,7 +876,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
tree_close_internal(con, KILL_WINDOW, false);
tree_render();
} else {
DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %d)\n", event->window);
DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %08x)\n", event->window);
}
} else if (event->type == A__NET_WM_MOVERESIZE) {
/*
@ -885,7 +885,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
*/
Con *con = con_by_window_id(event->window);
if (!con || !con_is_floating(con)) {
DLOG("Couldn't find con for _NET_WM_MOVERESIZE request, or con not floating (window = %d)\n", event->window);
DLOG("Couldn't find con for _NET_WM_MOVERESIZE request, or con not floating (window = %08x)\n", event->window);
return;
}
DLOG("Handling _NET_WM_MOVERESIZE request (con = %p)\n", con);
@ -1058,6 +1058,76 @@ static void handle_focus_in(xcb_focus_in_event_t *event) {
tree_render();
}
/*
* Log FocusOut events.
*
*/
static void handle_focus_out(xcb_focus_in_event_t *event) {
Con *con = con_by_window_id(event->event);
const char *window_name, *mode, *detail;
if (con != NULL) {
window_name = con->name;
if (window_name == NULL) {
window_name = "<unnamed con>";
}
} else if (event->event == root) {
window_name = "<the root window>";
} else {
window_name = "<unknown window>";
}
switch (event->mode) {
case XCB_NOTIFY_MODE_NORMAL:
mode = "Normal";
break;
case XCB_NOTIFY_MODE_GRAB:
mode = "Grab";
break;
case XCB_NOTIFY_MODE_UNGRAB:
mode = "Ungrab";
break;
case XCB_NOTIFY_MODE_WHILE_GRABBED:
mode = "WhileGrabbed";
break;
default:
mode = "<unknown>";
break;
}
switch (event->detail) {
case XCB_NOTIFY_DETAIL_ANCESTOR:
detail = "Ancestor";
break;
case XCB_NOTIFY_DETAIL_VIRTUAL:
detail = "Virtual";
break;
case XCB_NOTIFY_DETAIL_INFERIOR:
detail = "Inferior";
break;
case XCB_NOTIFY_DETAIL_NONLINEAR:
detail = "Nonlinear";
break;
case XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL:
detail = "NonlinearVirtual";
break;
case XCB_NOTIFY_DETAIL_POINTER:
detail = "Pointer";
break;
case XCB_NOTIFY_DETAIL_POINTER_ROOT:
detail = "PointerRoot";
break;
case XCB_NOTIFY_DETAIL_NONE:
detail = "NONE";
break;
default:
detail = "unknown";
break;
}
DLOG("focus change out: window 0x%08x (con %p, %s) lost focus with detail=%s, mode=%s\n", event->event, con, window_name, detail, mode);
}
/*
* Handles ConfigureNotify events for the root window, which are generated when
* the monitor configuration changed.
@ -1074,6 +1144,23 @@ static void handle_configure_notify(xcb_configure_notify_event_t *event) {
return;
}
randr_query_outputs();
ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
}
/*
* Handles SelectionClear events for the root window, which are generated when
* we lose ownership of a selection.
*/
static void handle_selection_clear(xcb_selection_clear_event_t *event) {
if (event->selection != wm_sn) {
DLOG("SelectionClear for unknown selection %d, ignoring\n", event->selection);
return;
}
LOG("Lost WM_Sn selection, exiting.\n");
exit(EXIT_SUCCESS);
/* unreachable */
}
/*
@ -1086,6 +1173,16 @@ static bool handle_class_change(Con *con, xcb_get_property_reply_t *prop) {
return true;
}
/*
* Handles the WM_CLIENT_MACHINE property for assignments and criteria selection.
*
*/
static bool handle_machine_change(Con *con, xcb_get_property_reply_t *prop) {
window_update_machine(con->window, prop);
con = remanage_window(con);
return true;
}
/*
* Handles the _MOTIF_WM_HINTS property of specifing window deocration settings.
*
@ -1175,6 +1272,14 @@ static bool handle_i3_floating(Con *con, xcb_get_property_reply_t *prop) {
return true;
}
static bool handle_windowicon_change(Con *con, xcb_get_property_reply_t *prop) {
window_update_icon(con->window, prop);
x_push_changes(croot);
return true;
}
/* Returns false if the event could not be processed (e.g. the window could not
* be found), true otherwise */
typedef bool (*cb_property_handler_t)(Con *con, xcb_get_property_reply_t *property);
@ -1197,7 +1302,9 @@ static struct property_handler_t property_handlers[] = {
{0, UINT_MAX, handle_strut_partial_change},
{0, UINT_MAX, handle_window_type},
{0, UINT_MAX, handle_i3_floating},
{0, 5 * sizeof(uint64_t), handle_motif_hints_change}};
{0, 128, handle_machine_change},
{0, 5 * sizeof(uint64_t), handle_motif_hints_change},
{0, UINT_MAX, handle_windowicon_change}};
#define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
/*
@ -1219,7 +1326,9 @@ void property_handlers_init(void) {
property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL;
property_handlers[9].atom = A__NET_WM_WINDOW_TYPE;
property_handlers[10].atom = A_I3_FLOATING_WINDOW;
property_handlers[11].atom = A__MOTIF_WM_HINTS;
property_handlers[11].atom = XCB_ATOM_WM_CLIENT_MACHINE;
property_handlers[12].atom = A__MOTIF_WM_HINTS;
property_handlers[13].atom = A__NET_WM_ICON;
}
static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
@ -1395,6 +1504,10 @@ void handle_event(int type, xcb_generic_event_t *event) {
handle_focus_in((xcb_focus_in_event_t *)event);
break;
case XCB_FOCUS_OUT:
handle_focus_out((xcb_focus_out_event_t *)event);
break;
case XCB_PROPERTY_NOTIFY: {
xcb_property_notify_event_t *e = (xcb_property_notify_event_t *)event;
last_timestamp = e->time;
@ -1406,6 +1519,10 @@ void handle_event(int type, xcb_generic_event_t *event) {
handle_configure_notify((xcb_configure_notify_event_t *)event);
break;
case XCB_SELECTION_CLEAR:
handle_selection_clear((xcb_selection_clear_event_t *)event);
break;
default:
//DLOG("Unhandled event of type %d\n", type);
break;

104
src/ipc.c
View File

@ -27,22 +27,6 @@ char *current_socketpath = NULL;
TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
/*
* Puts the given socket file descriptor into non-blocking mode or dies if
* setting O_NONBLOCK failed. Non-blocking sockets are a good idea for our
* IPC model because we should by no means block the window manager.
*
*/
static void set_nonblock(int sockfd) {
int flags = fcntl(sockfd, F_GETFL, 0);
if (flags & O_NONBLOCK) {
return;
}
flags |= O_NONBLOCK;
if (fcntl(sockfd, F_SETFL, flags) < 0)
err(-1, "Could not set O_NONBLOCK");
}
static void ipc_client_timeout(EV_P_ ev_timer *w, int revents);
static void ipc_socket_writeable_cb(EV_P_ struct ev_io *w, int revents);
@ -251,9 +235,9 @@ static void dump_rect(yajl_gen gen, const char *name, Rect r) {
ystr(name);
y(map_open);
ystr("x");
y(integer, r.x);
y(integer, (int32_t)r.x);
ystr("y");
y(integer, r.y);
y(integer, (int32_t)r.y);
ystr("width");
y(integer, r.width);
ystr("height");
@ -514,6 +498,9 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
ystr(con->title_format);
}
ystr("window_icon_padding");
y(integer, con->window_icon_padding);
if (con->type == CT_WORKSPACE) {
ystr("num");
y(integer, con->num);
@ -573,6 +560,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
DUMP_PROPERTY("class", class_class);
DUMP_PROPERTY("instance", class_instance);
DUMP_PROPERTY("window_role", role);
DUMP_PROPERTY("machine", machine);
if (con->window->name != NULL) {
ystr("title");
@ -662,6 +650,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
DUMP_REGEX(instance);
DUMP_REGEX(window_role);
DUMP_REGEX(title);
DUMP_REGEX(machine);
#undef DUMP_REGEX
y(map_close);
@ -1054,6 +1043,17 @@ IPC_HANDLER(get_version) {
ystr("loaded_config_file_name");
ystr(current_configpath);
ystr("included_config_file_names");
y(array_open);
IncludedFile *file;
TAILQ_FOREACH (file, &included_files, files) {
if (file == TAILQ_FIRST(&included_files)) {
/* Skip the first file, which is current_configpath. */
continue;
}
ystr(file->path);
}
y(array_close);
y(map_close);
const unsigned char *payload;
@ -1236,7 +1236,22 @@ IPC_HANDLER(get_config) {
y(map_open);
ystr("config");
ystr(current_config);
IncludedFile *file = TAILQ_FIRST(&included_files);
ystr(file->raw_contents);
ystr("included_configs");
y(array_open);
TAILQ_FOREACH (file, &included_files, files) {
y(map_open);
ystr("path");
ystr(file->path);
ystr("raw_contents");
ystr(file->raw_contents);
ystr("variable_replaced_contents");
ystr(file->variable_replaced_contents);
y(map_close);
}
y(array_close);
y(map_close);
@ -1523,57 +1538,6 @@ ipc_client *ipc_new_client_on_fd(EV_P_ int fd) {
return client;
}
/*
* Creates the UNIX domain socket at the given path, sets it to non-blocking
* mode, bind()s and listen()s on it.
*
*/
int ipc_create_socket(const char *filename) {
int sockfd;
FREE(current_socketpath);
char *resolved = resolve_tilde(filename);
DLOG("Creating IPC-socket at %s\n", resolved);
char *copy = sstrdup(resolved);
const char *dir = dirname(copy);
if (!path_exists(dir))
mkdirp(dir, DEFAULT_DIR_MODE);
free(copy);
/* Unlink the unix domain socket before */
unlink(resolved);
if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
perror("socket()");
free(resolved);
return -1;
}
(void)fcntl(sockfd, F_SETFD, FD_CLOEXEC);
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
strncpy(addr.sun_path, resolved, sizeof(addr.sun_path) - 1);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
perror("bind()");
free(resolved);
return -1;
}
set_nonblock(sockfd);
if (listen(sockfd, 5) < 0) {
perror("listen()");
free(resolved);
return -1;
}
current_socketpath = resolved;
return sockfd;
}
/*
* Generates a json workspace event. Returns a dynamically allocated yajl
* generator. Free with yajl_gen_free().

View File

@ -285,6 +285,9 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) {
} else if (strcasecmp(last_key, "title") == 0) {
current_swallow->title = regex_new(sval);
swallow_is_empty = false;
} else if (strcasecmp(last_key, "machine") == 0) {
current_swallow->machine = regex_new(sval);
swallow_is_empty = false;
} else {
ELOG("swallow key %s unknown\n", last_key);
}
@ -452,6 +455,10 @@ static int json_int(void *ctx, long long val) {
if (strcasecmp(last_key, "current_border_width") == 0)
json_node->current_border_width = val;
if (strcasecmp(last_key, "window_icon_padding") == 0) {
json_node->window_icon_padding = val;
}
if (strcasecmp(last_key, "depth") == 0)
json_node->depth = val;

View File

@ -12,6 +12,10 @@
#include "all.h"
#include "shmlog.h"
#include <ev.h>
#include <libgen.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
@ -23,9 +27,6 @@
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#if !defined(__OpenBSD__)
#include <pthread.h>
#endif
#if defined(__APPLE__)
#include <sys/sysctl.h>
@ -60,6 +61,18 @@ static int logbuffer_shm;
/* Size (in bytes) of physical memory */
static long long physical_mem_bytes;
typedef struct log_client {
int fd;
TAILQ_ENTRY(log_client)
clients;
} log_client;
TAILQ_HEAD(log_client_head, log_client)
log_clients = TAILQ_HEAD_INITIALIZER(log_clients);
void log_broadcast_to_clients(const char *message, size_t len);
/*
* Writes the offsets for the next write and for the last wrap to the
* shmlog_header.
@ -161,14 +174,6 @@ void open_logbuffer(void) {
header = (i3_shmlog_header *)logbuffer;
#if !defined(__OpenBSD__)
pthread_condattr_t cond_attr;
pthread_condattr_init(&cond_attr);
if (pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED) != 0)
fprintf(stderr, "pthread_condattr_setpshared() failed, i3-dump-log -f will not work!\n");
pthread_cond_init(&(header->condvar), &cond_attr);
#endif
logwalk = logbuffer + sizeof(i3_shmlog_header);
loglastwrap = logbuffer + logbuffer_size;
store_log_markers();
@ -283,13 +288,10 @@ static void vlog(const bool print, const char *fmt, va_list args) {
store_log_markers();
#if !defined(__OpenBSD__)
/* Wake up all (i3-dump-log) processes waiting for condvar. */
pthread_cond_broadcast(&(header->condvar));
#endif
if (print)
fwrite(message, len, 1, stdout);
log_broadcast_to_clients(message, len);
}
}
@ -370,3 +372,51 @@ void purge_zerobyte_logfile(void) {
rmdir(errorfilename);
}
}
char *current_log_stream_socket_path = NULL;
/*
* Handler for activity on the listening socket, meaning that a new client
* has just connected and we should accept() them. Sets up the event handler
* for activity on the new connection and inserts the file descriptor into
* the list of log clients.
*
*/
void log_new_client(EV_P_ struct ev_io *w, int revents) {
struct sockaddr_un peer;
socklen_t len = sizeof(struct sockaddr_un);
int fd;
if ((fd = accept(w->fd, (struct sockaddr *)&peer, &len)) < 0) {
if (errno != EINTR) {
perror("accept()");
}
return;
}
/* Close this file descriptor on exec() */
(void)fcntl(fd, F_SETFD, FD_CLOEXEC);
set_nonblock(fd);
log_client *client = scalloc(1, sizeof(log_client));
client->fd = fd;
TAILQ_INSERT_TAIL(&log_clients, client, clients);
DLOG("log: new client connected on fd %d\n", fd);
}
void log_broadcast_to_clients(const char *message, size_t len) {
log_client *current = TAILQ_FIRST(&log_clients);
while (current != TAILQ_END(&log_clients)) {
/* XXX: In case slow connections turn out to be a problem here
* (unlikely as long as i3-dump-log is the only consumer), introduce
* buffering, similar to the IPC interface. */
ssize_t n = writeall(current->fd, message, len);
log_client *previous = current;
current = TAILQ_NEXT(current, clients);
if (n < 0) {
TAILQ_REMOVE(&log_clients, previous, clients);
free(previous);
}
}
}

View File

@ -24,6 +24,9 @@
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <xcb/xcb_atom.h>
#include <xcb/xinerama.h>
#include <xcb/bigreq.h>
#ifdef I3_ASAN_ENABLED
#include <sanitizer/lsan_interface.h>
@ -63,6 +66,9 @@ xcb_timestamp_t last_timestamp = XCB_CURRENT_TIME;
xcb_screen_t *root_screen;
xcb_window_t root;
xcb_window_t wm_sn_selection_owner;
xcb_atom_t wm_sn;
/* Color depth, visual id and colormap to use when creating windows and
* pixmaps. Will use 32 bit depth and an appropriate visual, if available,
* otherwise the root windows default (usually 24 bit TrueColor). */
@ -181,6 +187,9 @@ static void i3_exit(void) {
}
ipc_shutdown(SHUTDOWN_REASON_EXIT, -1);
unlink(config.ipc_socket_path);
if (current_log_stream_socket_path != NULL) {
unlink(current_log_stream_socket_path);
}
xcb_disconnect(conn);
/* If a nagbar is active, kill it */
@ -279,6 +288,7 @@ int main(int argc, char *argv[]) {
char *fake_outputs = NULL;
bool disable_signalhandler = false;
bool only_check_config = false;
bool replace_wm = false;
static struct option long_options[] = {
{"no-autostart", no_argument, 0, 'a'},
{"config", required_argument, 0, 'c'},
@ -301,6 +311,7 @@ int main(int argc, char *argv[]) {
{"fake_outputs", required_argument, 0, 0},
{"fake-outputs", required_argument, 0, 0},
{"force-old-config-parser-v4.4-only", no_argument, 0, 0},
{"replace", no_argument, 0, 'r'},
{0, 0, 0, 0}};
int option_index = 0, opt;
@ -325,7 +336,7 @@ int main(int argc, char *argv[]) {
start_argv = argv;
while ((opt = getopt_long(argc, argv, "c:CvmaL:hld:V", long_options, &option_index)) != -1) {
while ((opt = getopt_long(argc, argv, "c:CvmaL:hld:Vr", long_options, &option_index)) != -1) {
switch (opt) {
case 'a':
LOG("Autostart disabled using -a\n");
@ -363,6 +374,9 @@ int main(int argc, char *argv[]) {
case 'l':
/* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */
break;
case 'r':
replace_wm = true;
break;
case 0:
if (strcmp(long_options[option_index].name, "force-xinerama") == 0 ||
strcmp(long_options[option_index].name, "force_xinerama") == 0) {
@ -443,6 +457,9 @@ int main(int argc, char *argv[]) {
"\tThe default is %d bytes.\n",
shmlog_size);
fprintf(stderr, "\n");
fprintf(stderr, "\t--replace\n"
"\tReplace an existing window manager.\n");
fprintf(stderr, "\n");
fprintf(stderr, "If you pass plain text arguments, i3 will interpret them as a command\n"
"to send to a currently running i3 (like i3-msg). This allows you to\n"
"use nice and logical commands, such as:\n"
@ -570,6 +587,21 @@ int main(int argc, char *argv[]) {
root_screen = xcb_aux_get_screen(conn, conn_screen);
root = root_screen->root;
/* Prefetch X11 extensions that we are interested in. */
xcb_prefetch_extension_data(conn, &xcb_xkb_id);
xcb_prefetch_extension_data(conn, &xcb_shape_id);
/* BIG-REQUESTS is used by libxcb internally. */
xcb_prefetch_extension_data(conn, &xcb_big_requests_id);
if (force_xinerama) {
xcb_prefetch_extension_data(conn, &xcb_xinerama_id);
} else {
xcb_prefetch_extension_data(conn, &xcb_randr_id);
}
/* Prepare for us to get a current timestamp as recommended by ICCCM */
xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){XCB_EVENT_MASK_PROPERTY_CHANGE});
xcb_change_property(conn, XCB_PROP_MODE_APPEND, root, XCB_ATOM_SUPERSCRIPT_X, XCB_ATOM_CARDINAL, 32, 0, "");
/* Place requests for the atoms we need as soon as possible */
#define xmacro(atom) \
xcb_intern_atom_cookie_t atom##_cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
@ -599,6 +631,8 @@ int main(int argc, char *argv[]) {
visual_type = get_visualtype(root_screen);
}
xcb_prefetch_maximum_request_length(conn);
init_dpi();
DLOG("root_depth = %d, visual_id = 0x%08x.\n", root_depth, visual_type->visual_id);
@ -609,6 +643,22 @@ int main(int argc, char *argv[]) {
xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root);
xcb_query_pointer_cookie_t pointercookie = xcb_query_pointer(conn, root);
/* Get the PropertyNotify event we caused above */
xcb_flush(conn);
{
xcb_generic_event_t *event;
DLOG("waiting for PropertyNotify event\n");
while ((event = xcb_wait_for_event(conn)) != NULL) {
if (event->response_type == XCB_PROPERTY_NOTIFY) {
last_timestamp = ((xcb_property_notify_event_t *)event)->time;
free(event);
break;
}
free(event);
}
DLOG("got timestamp %d\n", last_timestamp);
}
/* Setup NetWM atoms */
#define xmacro(name) \
do { \
@ -638,6 +688,103 @@ int main(int argc, char *argv[]) {
force_xinerama = true;
}
/* Acquire the WM_Sn selection. */
{
/* Get the WM_Sn atom */
char *atom_name = xcb_atom_name_by_screen("WM_S", conn_screen);
wm_sn_selection_owner = xcb_generate_id(conn);
if (atom_name == NULL) {
ELOG("xcb_atom_name_by_screen(\"WM_S\", %d) failed, exiting\n", conn_screen);
return 1;
}
xcb_intern_atom_reply_t *atom_reply;
atom_reply = xcb_intern_atom_reply(conn,
xcb_intern_atom_unchecked(conn,
0,
strlen(atom_name),
atom_name),
NULL);
free(atom_name);
if (atom_reply == NULL) {
ELOG("Failed to intern the WM_Sn atom, exiting\n");
return 1;
}
wm_sn = atom_reply->atom;
free(atom_reply);
/* Check if the selection is already owned */
xcb_get_selection_owner_reply_t *selection_reply =
xcb_get_selection_owner_reply(conn,
xcb_get_selection_owner(conn, wm_sn),
NULL);
if (selection_reply && selection_reply->owner != XCB_NONE && !replace_wm) {
ELOG("Another window manager is already running (WM_Sn is owned)");
return 1;
}
/* Become the selection owner */
xcb_create_window(conn,
root_screen->root_depth,
wm_sn_selection_owner, /* window id */
root_screen->root, /* parent */
-1, -1, 1, 1, /* geometry */
0, /* border width */
XCB_WINDOW_CLASS_INPUT_OUTPUT,
root_screen->root_visual,
0, NULL);
xcb_change_property(conn,
XCB_PROP_MODE_REPLACE,
wm_sn_selection_owner,
XCB_ATOM_WM_CLASS,
XCB_ATOM_STRING,
8,
(strlen("i3-WM_Sn") + 1) * 2,
"i3-WM_Sn\0i3-WM_Sn\0");
xcb_set_selection_owner(conn, wm_sn_selection_owner, wm_sn, last_timestamp);
if (selection_reply && selection_reply->owner != XCB_NONE) {
unsigned int usleep_time = 100000; /* 0.1 seconds */
int check_rounds = 150; /* Wait for a maximum of 15 seconds */
xcb_get_geometry_reply_t *geom_reply = NULL;
DLOG("waiting for old WM_Sn selection owner to exit");
do {
free(geom_reply);
usleep(usleep_time);
if (check_rounds-- == 0) {
ELOG("The old window manager is not exiting");
return 1;
}
geom_reply = xcb_get_geometry_reply(conn,
xcb_get_geometry(conn, selection_reply->owner),
NULL);
} while (geom_reply != NULL);
}
free(selection_reply);
/* Announce that we are the new owner */
/* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
* In order to properly initialize these bytes, we allocate 32 bytes even
* though we only need less for an xcb_client_message_event_t */
union {
xcb_client_message_event_t message;
char storage[32];
} event;
memset(&event, 0, sizeof(event));
event.message.response_type = XCB_CLIENT_MESSAGE;
event.message.window = root_screen->root;
event.message.format = 32;
event.message.type = A_MANAGER;
event.message.data.data32[0] = last_timestamp;
event.message.data.data32[1] = wm_sn;
event.message.data.data32[2] = wm_sn_selection_owner;
xcb_send_event(conn, 0, root_screen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY, event.storage);
}
xcb_void_cookie_t cookie;
cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ROOT_EVENT_MASK});
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
@ -663,9 +810,6 @@ int main(int argc, char *argv[]) {
xcursor_set_root_cursor(XCURSOR_CURSOR_POINTER);
const xcb_query_extension_reply_t *extreply;
xcb_prefetch_extension_data(conn, &xcb_xkb_id);
xcb_prefetch_extension_data(conn, &xcb_shape_id);
extreply = xcb_get_extension_data(conn, &xcb_xkb_id);
xkb_supported = extreply->present;
if (!extreply->present) {
@ -845,7 +989,7 @@ int main(int argc, char *argv[]) {
tree_render();
/* Create the UNIX domain socket for IPC */
int ipc_socket = ipc_create_socket(config.ipc_socket_path);
int ipc_socket = create_socket(config.ipc_socket_path, &current_socketpath);
if (ipc_socket == -1) {
ELOG("Could not create the IPC socket, IPC disabled\n");
} else {
@ -854,9 +998,21 @@ int main(int argc, char *argv[]) {
ev_io_start(main_loop, ipc_io);
}
/* Also handle the UNIX domain sockets passed via socket activation. The
* parameter 1 means "remove the environment variables", we dont want to
* pass these to child processes. */
/* Chose a file name in /tmp/ based on the PID */
char *log_stream_socket_path = get_process_filename("log-stream-socket");
int log_socket = create_socket(log_stream_socket_path, &current_log_stream_socket_path);
free(log_stream_socket_path);
if (log_socket == -1) {
ELOG("Could not create the log socket, i3-dump-log -f will not work\n");
} else {
struct ev_io *log_io = scalloc(1, sizeof(struct ev_io));
ev_io_init(log_io, log_new_client, log_socket, EV_READ);
ev_io_start(main_loop, log_io);
}
/* Also handle the UNIX domain sockets passed via socket
* activation. The parameter 0 means "do not remove the
* environment variables", we need to be able to reexec. */
listen_fds = sd_listen_fds(0);
if (listen_fds < 0)
ELOG("socket activation: Error in sd_listen_fds\n");
@ -950,24 +1106,23 @@ int main(int argc, char *argv[]) {
xcb_ungrab_server(conn);
if (autostart) {
LOG("This is not an in-place restart, copying root window contents to a pixmap\n");
/* When the root's window background is set to NONE, that might mean
* that old content stays visible when a window is closed. That has
* unpleasant effect of "my terminal (does not seem to) close!".
*
* There does not seem to be an easy way to query for this problem, so
* we test for it: Open & close a window and check if the background is
* redrawn or the window contents stay visible.
*/
LOG("This is not an in-place restart, checking if a wallpaper is set.\n");
xcb_screen_t *root = xcb_aux_get_screen(conn, conn_screen);
uint16_t width = root->width_in_pixels;
uint16_t height = root->height_in_pixels;
xcb_pixmap_t pixmap = xcb_generate_id(conn);
xcb_gcontext_t gc = xcb_generate_id(conn);
xcb_create_pixmap(conn, root->root_depth, pixmap, root->root, width, height);
xcb_create_gc(conn, gc, root->root,
XCB_GC_FUNCTION | XCB_GC_PLANE_MASK | XCB_GC_FILL_STYLE | XCB_GC_SUBWINDOW_MODE,
(uint32_t[]){XCB_GX_COPY, ~0, XCB_FILL_STYLE_SOLID, XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS});
xcb_copy_area(conn, root->root, pixmap, gc, 0, 0, 0, 0, width, height);
xcb_change_window_attributes(conn, root->root, XCB_CW_BACK_PIXMAP, (uint32_t[]){pixmap});
xcb_flush(conn);
xcb_free_gc(conn, gc);
xcb_free_pixmap(conn, pixmap);
if (is_background_set(conn, root)) {
LOG("A wallpaper is set, so no screenshot is necessary.\n");
} else {
LOG("No wallpaper set, copying root window contents to a pixmap\n");
set_screenshot_as_wallpaper(conn, root);
}
}
#if defined(__OpenBSD__)
@ -1041,5 +1196,6 @@ int main(int argc, char *argv[]) {
* when calling exit() */
atexit(i3_exit);
sd_notify(1, "READY=1");
ev_loop(main_loop, 0);
}

View File

@ -116,7 +116,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
utf8_title_cookie, title_cookie,
class_cookie, leader_cookie, transient_cookie,
role_cookie, startup_id_cookie, wm_hints_cookie,
wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie, wm_desktop_cookie;
wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie, wm_desktop_cookie,
wm_machine_cookie;
xcb_get_property_cookie_t wm_icon_cookie;
geomc = xcb_get_geometry(conn, d);
@ -189,6 +192,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
motif_wm_hints_cookie = GET_PROPERTY(A__MOTIF_WM_HINTS, 5 * sizeof(uint64_t));
wm_user_time_cookie = GET_PROPERTY(A__NET_WM_USER_TIME, UINT32_MAX);
wm_desktop_cookie = GET_PROPERTY(A__NET_WM_DESKTOP, UINT32_MAX);
wm_machine_cookie = GET_PROPERTY(XCB_ATOM_WM_CLIENT_MACHINE, UINT32_MAX);
wm_icon_cookie = GET_PROPERTY(A__NET_WM_ICON, UINT32_MAX);
i3Window *cwindow = scalloc(1, sizeof(i3Window));
cwindow->id = window;
@ -202,6 +207,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL));
window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL));
window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL));
window_update_icon(cwindow, xcb_get_property_reply(conn, wm_icon_cookie, NULL));
window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL));
window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
@ -211,6 +217,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
border_style_t motif_border_style = BS_NORMAL;
window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
window_update_normal_hints(cwindow, xcb_get_property_reply(conn, wm_normal_hints_cookie, NULL), geom);
window_update_machine(cwindow, xcb_get_property_reply(conn, wm_machine_cookie, NULL));
xcb_get_property_reply_t *type_reply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
xcb_get_property_reply_t *state_reply = xcb_get_property_reply(conn, state_cookie, NULL);

View File

@ -47,12 +47,14 @@ bool match_is_empty(Match *match) {
match->instance == NULL &&
match->window_role == NULL &&
match->workspace == NULL &&
match->machine == NULL &&
match->urgent == U_DONTCHECK &&
match->id == XCB_NONE &&
match->window_type == UINT32_MAX &&
match->con_id == NULL &&
match->dock == M_NODOCK &&
match->window_mode == WM_ANY);
match->window_mode == WM_ANY &&
match->match_all_windows == false);
}
/*
@ -130,6 +132,8 @@ bool match_matches_window(Match *match, i3Window *window) {
}
}
CHECK_WINDOW_FIELD(machine, machine, str);
Con *con = NULL;
if (match->urgent == U_LATEST) {
/* if the window isn't urgent, no sense in searching */
@ -257,6 +261,10 @@ bool match_matches_window(Match *match, i3Window *window) {
LOG("window_mode matches\n");
}
/* NOTE: See the comment regarding 'all' in match_parse_property()
* for an explanation of why match_all_windows isn't explicitly
* checked. */
return true;
}
@ -273,6 +281,7 @@ void match_free(Match *match) {
regex_free(match->mark);
regex_free(match->window_role);
regex_free(match->workspace);
regex_free(match->machine);
}
/*
@ -390,6 +399,12 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) {
return;
}
if (strcmp(ctype, "machine") == 0) {
regex_free(match->machine);
match->machine = regex_new(cvalue);
return;
}
if (strcmp(ctype, "tiling") == 0) {
match->window_mode = WM_TILING;
return;
@ -428,5 +443,16 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) {
return;
}
/* match_matches_window() only checks negatively, so match_all_windows
* won't actually be used there, but that's OK because if no negative
* match is found (e.g. because of a more restrictive criterion) the
* return value of match_matches_window() is true.
* Setting it here only serves to cause match_is_empty() to return false,
* otherwise empty criteria rules apply, and that's not what we want. */
if (strcmp(ctype, "all") == 0) {
match->match_all_windows = true;
return;
}
ELOG("Unknown criterion: %s\n", ctype);
}

View File

@ -134,7 +134,6 @@ static void update_placeholder_contents(placeholder_state *state) {
draw_util_clear_surface(&(state->surface), background);
// TODO: make i3font functions per-connection, at least these two for now…?
xcb_flush(restore_conn);
xcb_aux_sync(restore_conn);
Match *swallows;
@ -153,6 +152,7 @@ static void update_placeholder_contents(placeholder_state *state) {
APPEND_REGEX(instance);
APPEND_REGEX(window_role);
APPEND_REGEX(title);
APPEND_REGEX(machine);
if (serialized == NULL) {
DLOG("This swallows specification is not serializable?!\n");
@ -179,7 +179,6 @@ static void update_placeholder_contents(placeholder_state *state) {
int y = (state->rect.height / 2) - (config.font.height / 2);
draw_util_text(line, &(state->surface), foreground, background, x, y, text_width);
i3string_free(line);
xcb_flush(restore_conn);
xcb_aux_sync(restore_conn);
}

View File

@ -176,15 +176,6 @@ void exec_i3_utility(char *name, char *argv[]) {
_exit(2);
}
/*
* Checks if the given path exists by calling stat().
*
*/
bool path_exists(const char *path) {
struct stat buf;
return (stat(path, &buf) == 0);
}
/*
* Goes through the list of arguments (for exec()) and add/replace the given option,
* including the option name, its argument, and the option character.

View File

@ -18,7 +18,10 @@
void window_free(i3Window *win) {
FREE(win->class_class);
FREE(win->class_instance);
FREE(win->role);
FREE(win->machine);
i3string_free(win->name);
cairo_surface_destroy(win->icon);
FREE(win->ran_assignments);
FREE(win);
}
@ -466,3 +469,130 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
#undef MWM_DECOR_BORDER
#undef MWM_DECOR_TITLE
}
/*
* Updates the WM_CLIENT_MACHINE
*
*/
void window_update_machine(i3Window *win, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
DLOG("WM_CLIENT_MACHINE not set.\n");
FREE(prop);
return;
}
FREE(win->machine);
win->machine = sstrndup((char *)xcb_get_property_value(prop), xcb_get_property_value_length(prop));
LOG("WM_CLIENT_MACHINE changed to \"%s\"\n", win->machine);
free(prop);
}
void window_update_icon(i3Window *win, xcb_get_property_reply_t *prop) {
uint32_t *data = NULL;
uint32_t width, height;
uint64_t len = 0;
const uint32_t pref_size = (uint32_t)(render_deco_height() - logical_px(2));
if (!prop || prop->type != XCB_ATOM_CARDINAL || prop->format != 32) {
DLOG("_NET_WM_ICON is not set\n");
FREE(prop);
return;
}
uint32_t prop_value_len = xcb_get_property_value_length(prop);
uint32_t *prop_value = (uint32_t *)xcb_get_property_value(prop);
/* Find an icon matching the preferred size.
* If there is no such icon, take the smallest icon having at least
* the preferred size.
* If all icons are smaller than the preferred size, chose the largest.
*/
while (prop_value_len > (sizeof(uint32_t) * 2) && prop_value &&
prop_value[0] && prop_value[1]) {
const uint32_t cur_width = prop_value[0];
const uint32_t cur_height = prop_value[1];
/* Check that the property is as long as it should be (in bytes),
handling integer overflow. "+2" to handle the width and height
fields. */
const uint64_t cur_len = cur_width * (uint64_t)cur_height;
const uint64_t expected_len = (cur_len + 2) * 4;
if (expected_len > prop_value_len) {
break;
}
DLOG("Found _NET_WM_ICON of size: (%d,%d)\n", cur_width, cur_height);
const bool at_least_preferred_size = (cur_width >= pref_size &&
cur_height >= pref_size);
const bool smaller_than_current = (cur_width < width ||
cur_height < height);
const bool larger_than_current = (cur_width > width ||
cur_height > height);
const bool not_yet_at_preferred = (width < pref_size ||
height < pref_size);
if (len == 0 ||
(at_least_preferred_size &&
(smaller_than_current || not_yet_at_preferred)) ||
(!at_least_preferred_size &&
not_yet_at_preferred &&
larger_than_current)) {
len = cur_len;
width = cur_width;
height = cur_height;
data = prop_value;
}
if (width == pref_size && height == pref_size) {
break;
}
/* Find pointer to next icon in the reply. */
prop_value_len -= expected_len;
prop_value = (uint32_t *)(((uint8_t *)prop_value) + expected_len);
}
if (!data) {
DLOG("Could not get _NET_WM_ICON\n");
FREE(prop);
return;
}
DLOG("Using icon of size (%d,%d) (preferred size: %d)\n",
width, height, pref_size);
win->name_x_changed = true; /* trigger a redraw */
uint32_t *icon = smalloc(len * 4);
for (uint64_t i = 0; i < len; i++) {
uint8_t r, g, b, a;
const uint32_t pixel = data[2 + i];
a = (pixel >> 24) & 0xff;
r = (pixel >> 16) & 0xff;
g = (pixel >> 8) & 0xff;
b = (pixel >> 0) & 0xff;
/* Cairo uses premultiplied alpha */
r = (r * a) / 0xff;
g = (g * a) / 0xff;
b = (b * a) / 0xff;
icon[i] = ((uint32_t)a << 24) | (r << 16) | (g << 8) | b;
}
if (win->icon != NULL) {
cairo_surface_destroy(win->icon);
}
win->icon = cairo_image_surface_create_for_data(
(unsigned char *)icon,
CAIRO_FORMAT_ARGB32,
width,
height,
width * 4);
static cairo_user_data_key_t free_data;
cairo_surface_set_user_data(win->icon, &free_data, icon, free);
FREE(prop);
}

View File

@ -134,7 +134,7 @@ Con *workspace_get(const char *num) {
/* We set workspace->num to the number if this workspaces name begins with
* a positive number. Otherwise its a named ws and num will be 1. */
const long parsed_num = ws_name_to_number(num);
const int parsed_num = ws_name_to_number(num);
Con *output = get_assigned_output(num, parsed_num);
/* if an assignment is not found, we create this workspace on the current output */
@ -238,7 +238,6 @@ void extract_workspace_names_from_bindings(void) {
*/
Con *create_workspace_on_output(Output *output, Con *content) {
/* add a workspace to this output */
char *name;
bool exists = true;
Con *ws = con_new(NULL, NULL);
ws->type = CT_WORKSPACE;
@ -254,13 +253,16 @@ Con *create_workspace_on_output(Output *output, Con *content) {
continue;
}
exists = (get_existing_workspace_by_name(target_name) != NULL);
const int num = ws_name_to_number(target_name);
exists = (num == -1)
? get_existing_workspace_by_name(target_name)
: get_existing_workspace_by_num(num);
if (!exists) {
ws->name = sstrdup(target_name);
/* Set ->num to the number of the workspace, if the name actually
* is a number or starts with a number */
ws->num = ws_name_to_number(ws->name);
LOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
ws->num = num;
DLOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
break;
}
@ -281,6 +283,7 @@ Con *create_workspace_on_output(Output *output, Con *content) {
}
con_attach(ws, content, false);
char *name;
sasprintf(&name, "[i3 con] workspace %s", ws->name);
x_set_name(ws, name);
free(name);
@ -987,14 +990,14 @@ void workspace_move_to_output(Con *ws, Output *output) {
bool used_assignment = false;
struct Workspace_Assignment *assignment;
TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
bool attached;
int num;
if (!output_triggers_assignment(current_output, assignment)) {
continue;
}
/* check if this workspace's name or num is already attached to the tree */
num = ws_name_to_number(assignment->name);
attached = ((num == -1) ? get_existing_workspace_by_name(assignment->name) : get_existing_workspace_by_num(num)) != NULL;
const int num = ws_name_to_number(assignment->name);
const bool attached = (num == -1)
? get_existing_workspace_by_name(assignment->name)
: get_existing_workspace_by_num(num);
if (attached) {
continue;
}

68
src/x.c
View File

@ -537,6 +537,9 @@ void x_draw_decoration(Con *con) {
/* 2: draw the client.background, but only for the parts around the window_rect */
if (con->window != NULL) {
/* Clear visible windows before beginning to draw */
draw_util_clear_surface(&(con->frame_buffer), (color_t){.red = 0.0, .green = 0.0, .blue = 0.0});
/* top area */
draw_util_rectangle(&(con->frame_buffer), config.client.background,
0, 0, r->width, w->y);
@ -609,11 +612,14 @@ void x_draw_decoration(Con *con) {
/* 5: draw title border */
x_draw_title_border(con, p);
/* 6: draw the title */
/* 6: draw the icon and title */
int text_offset_y = (con->deco_rect.height - config.font.height) / 2;
const int title_padding = logical_px(2);
struct Window *win = con->window;
const int deco_width = (int)con->deco_rect.width;
const int title_padding = logical_px(2);
int mark_width = 0;
if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) {
char *formatted_mark = sstrdup("");
@ -652,7 +658,6 @@ void x_draw_decoration(Con *con) {
}
i3String *title = NULL;
struct Window *win = con->window;
if (win == NULL) {
if (con->title_format == NULL) {
char *_title;
@ -672,25 +677,45 @@ void x_draw_decoration(Con *con) {
goto copy_pixmaps;
}
/* icon_padding is applied horizontally only, the icon will always use all
* available vertical space. */
int icon_size = max(0, con->deco_rect.height - logical_px(2));
int icon_padding = logical_px(max(1, con->window_icon_padding));
int total_icon_space = icon_size + 2 * icon_padding;
const bool has_icon = (con->window_icon_padding > -1) && win && win->icon && (total_icon_space < deco_width);
if (!has_icon) {
icon_size = icon_padding = total_icon_space = 0;
}
/* Determine x offsets according to title alignment */
int icon_offset_x;
int title_offset_x;
switch (config.title_align) {
case ALIGN_LEFT:
/* (pad)[text ](pad)[mark + its pad) */
title_offset_x = title_padding;
/* (pad)[(pad)(icon)(pad)][text ](pad)[mark + its pad)
* ^ ^--- title_offset_x
* ^--- icon_offset_x */
icon_offset_x = icon_padding;
title_offset_x = title_padding + total_icon_space;
break;
case ALIGN_CENTER:
/* (pad)[ text ](pad)[mark + its pad)
* To center the text inside its allocated space, the surface
* between the brackets, we use the formula
* (surface_width - predict_text_width) / 2
* where surface_width = deco_width - 2 * pad - mark_width
* so, offset = pad + (surface_width - predict_text_width) / 2 =
* = = (deco_width - mark_width - predict_text_width) / 2 */
title_offset_x = max(title_padding, (deco_width - mark_width - predict_text_width(title)) / 2);
/* (pad)[ ][(pad)(icon)(pad)][text ](pad)[mark + its pad)
* ^ ^--- title_offset_x
* ^--- icon_offset_x
* Text should come right after the icon (+padding). We calculate
* the offset for the icon (white space in the title) by dividing
* by two the total available area. That's the decoration width
* minus the elements that come after icon_offset_x (icon, its
* padding, text, marks). */
icon_offset_x = max(icon_padding, (deco_width - icon_padding - icon_size - predict_text_width(title) - title_padding - mark_width) / 2);
title_offset_x = max(title_padding, icon_offset_x + icon_padding + icon_size);
break;
case ALIGN_RIGHT:
/* [mark + its pad](pad)[ text](pad) */
title_offset_x = max(title_padding + mark_width, deco_width - title_padding - predict_text_width(title));
/* [mark + its pad](pad)[ text][(pad)(icon)(pad)](pad)
* ^ ^--- icon_offset_x
* ^--- title_offset_x */
title_offset_x = max(title_padding + mark_width, deco_width - title_padding - predict_text_width(title) - total_icon_space);
/* Make sure the icon does not escape title boundaries */
icon_offset_x = min(deco_width - icon_size - icon_padding - title_padding, title_offset_x + predict_text_width(title) + icon_padding);
break;
}
@ -698,7 +723,16 @@ void x_draw_decoration(Con *con) {
p->color->text, p->color->background,
con->deco_rect.x + title_offset_x,
con->deco_rect.y + text_offset_y,
deco_width - mark_width - 2 * title_padding);
deco_width - mark_width - 2 * title_padding - total_icon_space);
if (has_icon) {
draw_util_image(
win->icon,
&(parent->frame_buffer),
con->deco_rect.x + icon_offset_x,
con->deco_rect.y + logical_px(1),
icon_size,
icon_size);
}
if (win == NULL || con->title_format != NULL) {
I3STRING_FREE(title);
@ -1418,6 +1452,8 @@ void x_set_i3_atoms(void) {
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_PID, XCB_ATOM_CARDINAL, 32, 1, &pid);
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8,
strlen(current_configpath), current_configpath);
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_LOG_STREAM_SOCKET_PATH, A_UTF8_STRING, 8,
strlen(current_log_stream_socket_path), current_log_stream_socket_path);
update_shmlog_atom();
}

View File

@ -410,7 +410,7 @@ Returns the name of the output on which this workspace resides
cmd 'focus output fake-1';
cmd 'workspace 1';
is(get_output_for_workspace('1', 'fake-0', 'Workspace 1 in output fake-0');
is(get_output_for_workspace('1'), 'fake-0', 'Workspace 1 in output fake-0');
=cut
sub get_output_for_workspace {
@ -419,10 +419,12 @@ sub get_output_for_workspace {
my $tree = $i3->get_tree->recv;
my @outputs = @{$tree->{nodes}};
foreach (grep { not $_->{name} =~ /^__/ } @outputs) {
my $output = $_->{name};
foreach (grep { $_->{name} =~ "content" } @{$_->{nodes}}) {
return $output if $_->{nodes}[0]->{name} =~ $ws_name;
for my $output (@outputs) {
next if $output->{name} eq '__i3';
# get the first CT_CON of each output
my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
if (grep {$_->{name} eq $ws_name} @{$content->{nodes}}){
return $output->{name};
}
}
}

View File

@ -73,6 +73,7 @@ my $expected = {
workspace_layout => 'default',
current_border_width => -1,
marks => $ignore,
window_icon_padding => -1,
};
# a shallow copy is sufficient, since we only ignore values at the root

View File

@ -54,6 +54,7 @@ is(parser_calls(
'move workspace foobar; ' .
'move workspace torrent; ' .
'move workspace to output LVDS1; ' .
'move to output LVDS1 DVI1; ' .
'move workspace 3: foobar; ' .
'move workspace "3: foobar"; ' .
'move workspace "3: foobar, baz"; '),
@ -62,7 +63,11 @@ is(parser_calls(
"cmd_move_con_to_workspace_name(3, (null))\n" .
"cmd_move_con_to_workspace_name(foobar, (null))\n" .
"cmd_move_con_to_workspace_name(torrent, (null))\n" .
"cmd_move_workspace_to_output(LVDS1)\n" .
"cmd_move_con_to_output(LVDS1, 1)\n" .
"cmd_move_con_to_output(NULL, 1)\n" .
"cmd_move_con_to_output(LVDS1, 0)\n" .
"cmd_move_con_to_output(DVI1, 0)\n" .
"cmd_move_con_to_output(NULL, 0)\n" .
"cmd_move_con_to_workspace_name(3: foobar, (null))\n" .
"cmd_move_con_to_workspace_name(3: foobar, (null))\n" .
"cmd_move_con_to_workspace_name(3: foobar, baz, (null))",
@ -171,6 +176,7 @@ is(parser_calls('unknown_literal'),
scratchpad
swap
title_format
title_window_icon
mode
bar
)) . "'\n" .

View File

@ -506,6 +506,7 @@ EOT
my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '#', '" . join("', '", 'set ', 'set ', qw(
set_from_resource
include
bindsym
bindcode
bind

View File

@ -41,4 +41,12 @@ is_num_children($ws_top_right, 1, 'one container on the upper right workspace');
is_num_children($ws_bottom_left, 0, 'no containers on the lower left workspace');
is_num_children($ws_bottom_right, 1, 'one container on the lower right workspace');
# Also test with multiple explicit outputs
cmd '[class="moveme"] move window to output fake-1 fake-2';
is_num_children($ws_top_left, 0, 'no containers on the upper left workspace');
is_num_children($ws_top_right, 1, 'one container on the upper right workspace');
is_num_children($ws_bottom_left, 1, 'one container on the lower left workspace');
is_num_children($ws_bottom_right, 0, 'no containers on the lower right workspace');
done_testing;

View File

@ -0,0 +1,25 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • https://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • https://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • https://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Verifies i3 does not crash when using layout default.
# Ticket: #4408
# Bug still in: 4.19.2-83-gc8158875
use i3test;
cmd 'layout default';
fresh_workspace;
done_testing;

361
testcases/t/313-include.t Normal file
View File

@ -0,0 +1,361 @@
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • https://build.i3wm.org/docs/testsuite.html
# (or docs/testsuite)
#
# • https://build.i3wm.org/docs/lib-i3test.html
# (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • https://build.i3wm.org/docs/ipc.html
# (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
# (unless you are already familiar with Perl)
#
# Verifies the include directive.
use File::Temp qw(tempfile tempdir);
use File::Basename qw(basename);
use i3test i3_autostart => 0;
# starts i3 with the given config, opens a window, returns its border style
sub launch_get_border {
my ($config) = @_;
my $pid = launch_with_config($config);
my $i3 = i3(get_socket_path(0));
my $tmp = fresh_workspace;
my $window = open_window(name => 'special title');
my @content = @{get_ws_content($tmp)};
cmp_ok(@content, '==', 1, 'one node on this workspace now');
my $border = $content[0]->{border};
exit_gracefully($pid);
return $border;
}
#####################################################################
# test thet windows get the default border
#####################################################################
my $config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
EOT
is(launch_get_border($config), 'normal', 'normal border');
#####################################################################
# now use a variable and for_window
#####################################################################
my ($fh, $filename) = tempfile(UNLINK => 1);
my $varconfig = <<'EOT';
set $vartest special title
for_window [title="$vartest"] border none
EOT
print $fh $varconfig;
$fh->flush;
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
include $filename
EOT
is(launch_get_border($config), 'none', 'no border');
################################################################################
# nested includes
################################################################################
my ($indirectfh, $indirectfilename) = tempfile(UNLINK => 1);
print $indirectfh <<EOT;
include $filename
EOT
$indirectfh->flush;
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
include $indirectfilename
EOT
is(launch_get_border($config), 'none', 'no border');
################################################################################
# nested includes with relative paths
################################################################################
my $relative = basename($filename);
my ($indirectfh2, $indirectfilename2) = tempfile(UNLINK => 1);
print $indirectfh2 <<EOT;
include $relative
EOT
$indirectfh2->flush;
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
include $indirectfilename2
EOT
is(launch_get_border($config), 'none', 'no border');
################################################################################
# command substitution
################################################################################
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
include `echo $filename`
EOT
is(launch_get_border($config), 'none', 'no border');
################################################################################
# failing command substitution
################################################################################
$config = <<'EOT';
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
include i3-`false`.conf
set $vartest special title
for_window [title="$vartest"] border none
EOT
is(launch_get_border($config), 'none', 'no border');
################################################################################
# permission denied
################################################################################
my ($permissiondeniedfh, $permissiondenied) = tempfile(UNLINK => 1);
$permissiondeniedfh->flush;
my $mode = 0055;
chmod($mode, $permissiondenied);
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
include $permissiondenied
include $filename
EOT
is(launch_get_border($config), 'none', 'no border');
################################################################################
# dangling symlink
################################################################################
my ($danglingfh, $dangling) = tempfile(UNLINK => 1);
unlink($dangling);
symlink("/dangling", $dangling);
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
include $dangling
set \$vartest special title
for_window [title="\$vartest"] border none
EOT
is(launch_get_border($config), 'none', 'no border');
################################################################################
# variables defined in the main file and used in the included file
################################################################################
my ($varfh, $var) = tempfile(UNLINK => 1);
print $varfh <<'EOT';
for_window [title="$vartest"] border none
EOT
$varfh->flush;
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
set \$vartest special title
include $var
EOT
is(launch_get_border($config), 'none', 'no border');
SKIP: {
skip "not implemented";
################################################################################
# variables defined in the included file and used in the main file
################################################################################
($varfh, $var) = tempfile(UNLINK => 1);
print $varfh <<'EOT';
set $vartest special title
EOT
$varfh->flush;
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
include $var
for_window [title="\$vartest"] border none
EOT
is(launch_get_border($config), 'none', 'no border');
}
################################################################################
# workspace names are loaded in the correct order (before reorder_bindings)
################################################################################
# The included config can be empty, the issue lies with calling parse_file
# multiple times.
my ($wsfh, $ws) = tempfile(UNLINK => 1);
$wsfh->flush;
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
bindsym 1 workspace 1: eggs
bindsym Mod4+Shift+1 workspace 11: tomatoes
include $var
EOT
# starts i3 with the given config, opens a window, returns its border style
sub launch_get_workspace_name {
my ($config) = @_;
my $pid = launch_with_config($config);
my $i3 = i3(get_socket_path(0));
my $name = $i3->get_workspaces->recv->[0]->{name};
exit_gracefully($pid);
return $name;
}
is(launch_get_workspace_name($config), '1: eggs', 'workspace name');
################################################################################
# loop prevention
################################################################################
my ($loopfh1, $loopname1) = tempfile(UNLINK => 1);
my ($loopfh2, $loopname2) = tempfile(UNLINK => 1);
print $loopfh1 <<EOT;
include $loopname2
EOT
$loopfh1->flush;
print $loopfh2 <<EOT;
include $loopname1
EOT
$loopfh2->flush;
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
# loop
include $loopname1
set \$vartest special title
for_window [title="\$vartest"] border none
EOT
is(launch_get_border($config), 'none', 'no border');
################################################################################
# Verify the GET_VERSION IPC reply contains all included files
################################################################################
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
include $indirectfilename2
EOT
my $pid = launch_with_config($config);
my $i3 = i3(get_socket_path(0));
my $version = $i3->get_version()->recv;
my $included = $version->{included_config_file_names};
is_deeply($included, [ $indirectfilename2, $filename ], 'included config file names correct');
exit_gracefully($pid);
################################################################################
# Verify the GET_CONFIG IPC reply returns the top-level config
################################################################################
my $tmpdir = tempdir(CLEANUP => 1);
my $socketpath = $tmpdir . "/config.sock";
ok(! -e $socketpath, "$socketpath does not exist yet");
my ($indirectfh3, $indirectfilename3) = tempfile(UNLINK => 1);
my $indirectconfig = <<EOT;
for_window [title="\$vartest"] border none
include $relative
EOT
print $indirectfh3 $indirectconfig;
$indirectfh3->flush;
$config = <<EOT;
# i3 config file (v4)
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
set \$vartest special title
include $indirectfilename3
ipc-socket $socketpath
EOT
my $pid = launch_with_config($config, dont_add_socket_path => 1, dont_create_temp_dir => 1);
my $i3 = i3(get_socket_path(0));
my $config_reply = $i3->get_config()->recv;
is($config_reply->{config}, $config, 'GET_CONFIG returns the top-level config file');
my $included = $config_reply->{included_configs};
is(scalar @{$included}, 3, 'included_configs contains all 3 files');
is($included->[0]->{raw_contents}, $config, 'included_configs->[0]->{raw_contents} contains top-level config');
is($included->[1]->{raw_contents}, $indirectconfig, 'included_configs->[1]->{raw_contents} contains indirect config');
is($included->[2]->{raw_contents}, $varconfig, 'included_configs->[2]->{raw_contents} contains variable config');
my $indirect_replaced_config = <<EOT;
for_window [title="special title"] border none
include $relative
EOT
is($included->[1]->{variable_replaced_contents}, $indirect_replaced_config, 'included_configs->[1]->{variable_replaced_contents} contains config with variables replaced');
exit_gracefully($pid);
done_testing;

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