Codebase list i3-gaps / 6a17515
Update upstream source from tag 'upstream/4.21.1' Update to upstream version '4.21.1' with Debian dir ab61efedd20013c594cc42461acd923af710f1a7 Sophie Brun 1 year, 4 months ago
188 changed file(s) with 5916 addition(s) and 1527 deletion(s). Raw diff Collapse all Expand all
33
44 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).
55
6 ## i3 bug reports and feature requests
6 ## i3 bug reports
77
88 1. Read the [debugging instructions](https://i3wm.org/docs/debugging.html).
99 2. Make sure you include a link to your logfile in your report (section 3).
1717 encountered the issue you are about to report while using a compositor,
1818 please try reproducing it without a compositor.
1919
20 ## i3 feature requests
21
22 1. Read the [project goals](https://i3wm.org) on the website and make sure that
23 they are compatible with the feature you want to suggest.
24 2. We are generally happy with the current feature set of i3 and instead focus
25 on maintenance such as stability and fixing bugs. New features will rarely
26 be considered if they require additional configuration and/or commands, or
27 if they add significant complexity (either through the exposed configuration
28 or mental complexity) to the project.
29 3. Explain in detail what problem the feature addresses and why existing
30 features fall short.
31 4. Consider whether the feature could instead be implemented using the
32 [IPC](https://i3wm.org/docs/ipc.html) or other external tooling.
33
2034 ## Pull requests
2135
2236 * Before sending a pull request for new features, please check with us that the
2640 * Use `clang-format` to format your code.
2741 * Run the [testsuite](https://i3wm.org/docs/testsuite.html)
2842 * If your changes should be reported on the next release's changelog, also
29 update the [RELEASE-notes-next](../RELEASE-notes-next) file in the root
30 folder. Example of changes that should be reported are bug fixes present in
31 the latest stable version of i3 and new enhancements. Example of changes that
32 should not be reported are minor code improvements, documentation, regression
33 and fixes for bugs that were introduced in the `next` branch.
43 add a small single-line file starting with a number (see examples) containing
44 a short explanation of your change either in the
45 [changes](../release-notes/changes) or the
46 [bugfixes](../release-notes/bugfixes/) folder. Example of changes that should
47 be reported are bug fixes present in the latest stable version of i3 and new
48 enhancements. Example of changes that should not be reported are minor code
49 improvements, documentation, regression and fixes for bugs that were
50 introduced in the `next` branch.
3451
3552 ## Finding something to do
3653
0 contact_links:
1 - name: Ask a question or request support for using i3
2 url: https://github.com/i3/i3/discussions/new
3 about: Ask a question or request support for using i3
77 -->
88
99 ## I'm submitting a…
10 <!-- Please check one of the following options with "x" -->
10 <!-- Check one of the following options with "x" -->
1111 <pre>
1212 [ ] Bug
1313 [x] Feature Request
2727 e.g., »The window left next to the current window should be focused.«
2828 -->
2929
30 ## Impact
31 <!--
32 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.
33 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.
34 -->
35 <pre>
36 [ ] This feature requires new configuration and/or commands
37 </pre>
38
3039 ## Environment
3140 <!--
3241 Please include your exact i3 version.
55 -->
66
77 ## I'm submitting a…
8 <!-- Please check one of the following options with "x" -->
8 <!--
9 Check one of the following options with "x".
10
11 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.
12 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.
13 -->
914 <pre>
1015 [ ] Bug
1116 [ ] Feature Request
0 name: GitHub Actions
1
2 on:
3 push:
4 branches: [ gaps-next, gaps, actions ]
5 pull_request:
6 branches: [ gaps-next ]
7
8 jobs:
9 build:
10 name: build and test
11 runs-on: ubuntu-latest
12 strategy:
13 fail-fast: false
14 matrix:
15 compiler: [gcc, clang]
16 env:
17 CC: ${{ matrix.compiler }}
18 DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
19 DOCKER_EMAIL: ${{ secrets.DOCKER_EMAIL }}
20 DOCKER_USER: ${{ secrets.DOCKER_USER }}
21 GH_TOKEN: ${{ secrets.GH_TOKEN }}
22 BALTO_TOKEN: ${{ secrets.BALTO_TOKEN }}
23
24 steps:
25 - uses: actions/checkout@v2
26 - run: git fetch --prune --unshallow
27 - name: construct container name
28 run: |
29 echo "BASENAME=i3wm/travis-base:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base.Dockerfile)" >> $GITHUB_ENV
30 echo "BASENAME_386=i3wm/travis-base-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-386.Dockerfile)" >> $GITHUB_ENV
31 echo "BASENAME_UBUNTU=i3wm/travis-base-ubuntu:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu.Dockerfile)" >> $GITHUB_ENV
32 echo "BASENAME_UBUNTU_386=i3wm/travis-base-ubuntu-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu-386.Dockerfile)" >> $GITHUB_ENV
33 - name: fetch or build Docker container
34 run: |
35 docker pull ${{ env.BASENAME }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME }} travis/travis-base.Dockerfile
36 - name: fetch or build extra Docker containers
37 run: |
38 echo "::group::Ubuntu amd64"
39 ./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_UBUNTU }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU }} travis/travis-base-ubuntu.Dockerfile
40 echo "::endgroup::"
41 echo "::group::Debian i386"
42 ./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_386 }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_386 }} travis/travis-base-386.Dockerfile
43 echo "::endgroup::"
44 echo "::group::Ubuntu i386"
45 ./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
46 echo "::endgroup::"
47 - name: build i3
48 run: |
49 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'
50 - name: check spelling
51 run: |
52 docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-spelling.pl
53 - name: run i3 tests
54 run: |
55 docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} ./travis/run-tests.sh
56 - name: Archive test logs
57 uses: actions/upload-artifact@v2
58 with:
59 name: test-logs
60 path: build/testsuite-*
61 if: ${{ failure() }}
62 - name: build dist tarball
63 run: |
64 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'
65 - name: build Debian packages
66 run: |
67 echo "::group::Debian amd64"
68 ./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
69 echo "::endgroup::"
70 echo "::group::Ubuntu amd64"
71 ./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
72 echo "::endgroup::"
73 echo "::group::Debian i386"
74 ./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
75 echo "::endgroup::"
76 echo "::group::Ubuntu i386"
77 ./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
78 echo "::endgroup::"
79 - name: push Debian packages to balto
80 run: |
81 ./travis/skip-pkg.sh || travis/push-balto.sh
82 - name: build docs
83 run: |
84 ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/docs.sh
85 - name: push docs to GitHub pages
86 run: |
87 ./travis/skip-pkg.sh || travis/deploy-github-pages.sh
88 formatting:
89 name: Check formatting
90 runs-on: ubuntu-latest
91 steps:
92 - uses: actions/checkout@v2
93 - name: check & print release notes
94 run: ./release-notes/generator.pl
95 - name: Install dependencies
96 run: |
97 sudo apt-get install -y clang-format-10
98 - name: Check formatting
99 run: clang-format-10 --dry-run --Werror $(git ls-files '*.c' '*.h')
100 - name: Verify safe wrapper functions are used
101 run: ./travis/check-safe-wrappers.sh
+0
-71
.travis.yml less more
0 dist: trusty
1 services:
2 - docker
3 language: c
4 compiler:
5 - gcc
6 - clang
7 addons:
8 apt:
9 packages:
10 # For https support in HTTP::Tiny.
11 - libio-socket-ssl-perl
12 env:
13 global:
14 - BASENAME="i3wm/travis-base:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base.Dockerfile)"
15 - BASENAME_386="i3wm/travis-base-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-386.Dockerfile)"
16 - BASENAME_UBUNTU="i3wm/travis-base-ubuntu:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu.Dockerfile)"
17 - BASENAME_UBUNTU_386="i3wm/travis-base-ubuntu-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu-386.Dockerfile)"
18 - secure: "B5IICA8MPx/FKaB50rTPqL8P1NU+Q0yuWl+lElL4+a9xSyLikfm3NzUPHoVwx8lNw2AVK6br7p0OmF7vMFjqAgrgc1cajTtEae5uFRKNUrWLpXM046YgNEYLLIHsQOjInxE+S4O6EFVzsUqsu8aeo2Xhq4sm4iUocG7e5isYgYo=" # DOCKER_PASS
19 - secure: "EIvrq8PG7lRjidppG0RCv4F0X4GP3DT9F5+ixVuGPfhK/hZT3jYC2AVY9G+NnUcXVwQEpW92rlqpftQ/qZ13FoyWokC8ZyoyD06fr5FPCfoFF3OczZwAJzZYkObI/hE9+/hXcylx/Os6N4INd2My1ntGk3JPsWL9riopod5EjSg=" # DOCKER_EMAIL
20 - secure: "hvhBunS4xXTgnIOsk/BPT7I7FrJhvVwCSt5PfxxvMqNaztOJI9BuK7ZrZ5Cy38KyHwlh3VHAH5AaCygJcPauoSQCV3bpnlbaWn3ruq2F0Q697Q5uNf73liXzyUqb9/Zvfvge4y4WWOhP5tVz1C6ZBe/NfhU7pqKLMA+6ads+99c=" # DOCKER_USER
21 - secure: "uJuuefmnJUuEH15ZD8xQilibx7EeBvMHBLoIZ8bgGHeleEImBD0XbD1ypvhYJKpviOmw5BkZmc9bVO8DGDEHYbSlIa2xDlF6vGrwgCEaxcMIhOAhv+dW9C/maJVieLOEPM01/fK2qdKESZaLvlopkWmxZwDyMObI9L7AMW9zQD8=" # BINTRAY_USER
22 - secure: "L3aPSNLySPXtWCW+xf8h/AAdquwNgxyTQpYOwexJmTPav82Qx8uQlp1yJkUmt+a+FLZDFfQeMivaHq0311RvuQVmkAJx49DjaddrwqOJut2UPsoVDn1WeuAcSHIXOq/0H+zgFMr/PGY0HXIsw1mTMhgheGJNqg09BvYWROCEAcA=" # BINTRAY_KEY
23 - secure: "sBMVn4C/WRWgoAytEFGx4CC5O55Q63h02AcuBnb1jXcBm0RenoBpzUPtxSseJwDPUA1o/UkuEDDjm3PosT5NF+dvED01VDFMsPVE11K0u6+avYy3jYXqyUEDW3G2o6Wo/2aqNjmd++8jskBdS9+Cx9gaFbgxfzSp0Yfu3oJm/4c=" # GH_TOKEN
24 install:
25 - if [ -a .git/shallow ]; then git fetch --unshallow; fi
26 - docker pull ${BASENAME} || ./travis/docker-build-and-push.sh ${BASENAME} travis/travis-base.Dockerfile
27 - ./travis/skip-pkg.sh || docker pull ${BASENAME_UBUNTU} || ./travis/docker-build-and-push.sh ${BASENAME_UBUNTU} travis/travis-base-ubuntu.Dockerfile
28 - ./travis/skip-pkg.sh || docker pull ${BASENAME_386} || ./travis/docker-build-and-push.sh ${BASENAME_386} travis/travis-base-386.Dockerfile
29 - ./travis/skip-pkg.sh || docker pull ${BASENAME_UBUNTU_386} || ./travis/docker-build-and-push.sh ${BASENAME_UBUNTU_386} travis/travis-base-ubuntu-386.Dockerfile
30 script:
31 - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-safe-wrappers.sh
32 - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-formatting.sh
33 - 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'
34 - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-spelling.pl
35 - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} ./travis/run-tests.sh
36 - 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'
37 - ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/debian-build.sh deb/debian-amd64/DIST
38 - ./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
39 - ./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
40 - ./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
41 - ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/docs.sh
42 - ./travis/skip-pkg.sh || travis/prep-bintray.sh
43
44 deploy:
45 - provider: bintray
46 file: travis/bintray-autobuild-debian.json
47 user: $BINTRAY_USER
48 key: $BINTRAY_KEY
49 skip_cleanup: true
50 on:
51 branch: next
52 condition: $CC = gcc
53 - provider: bintray
54 file: travis/bintray-autobuild-ubuntu.json
55 user: $BINTRAY_USER
56 key: $BINTRAY_KEY
57 skip_cleanup: true
58 on:
59 branch: next
60 condition: $CC = gcc
61 - provider: script
62 script: travis/deploy-github-pages.sh
63 skip_cleanup: true
64 on:
65 branch: next
66 condition: $CC = gcc
67
68 after_deploy:
69 - travis/cleanup-bintray.pl i3-autobuild
70 - travis/cleanup-bintray.pl i3-autobuild-ubuntu
0 [![Travis](https://img.shields.io/travis/Airblader/i3.svg)](https://travis-ci.org/Airblader/i3)
0 [![Build Status](https://github.com/Airblader/i3/actions/workflows/main.yml/badge.svg)](https://github.com/Airblader/i3/actions/workflows/main.yml)
11 [![Issues](https://img.shields.io/github/issues/Airblader/i3.svg)](https://github.com/Airblader/i3/issues)
22 [![Forks](https://img.shields.io/github/forks/Airblader/i3.svg)](https://github.com/Airblader/i3/network)
33 [![Stars](https://img.shields.io/github/stars/Airblader/i3.svg)](https://github.com/Airblader/i3/stargazers)
1818
1919 For bug reports or feature requests regarding i3-gaps specifically, open an issue on [GitHub](https://www.github.com/Airblader/i3). If your issue is with core i3 functionality, please report it [upstream](https://www.github.com/i3/i3).
2020
21 For support & all other kinds of questions, you can ask your question on the official [subreddit /r/i3wm](https://www.reddit.com/r/i3wm).
21 For support & all other kinds of questions, you can ask your question on [GitHub Discussions](https://github.com/i3/i3/discussions).
2222
2323 # Features
2424
+0
-27
RELEASE-NOTES-4.19.1 less more
0
1 ┌──────────────────────────────┐
2 │ Release notes for i3 v4.19.1 │
3 └──────────────────────────────┘
4
5 This is i3 v4.19. This version is considered stable. All users of i3 are
6 strongly encouraged to upgrade.
7
8 This is a bugfix release for v4.19
9
10 ┌────────────────────────────┐
11 │ Bugfixes │
12 └────────────────────────────┘
13
14 • fix workspaces not moving to assigned output after output becomes available
15 • fix duplicate bindcode after i3-config-wizard
16 • fix commented-out rofi call in default i3 config
17
18 ┌────────────────────────────┐
19 │ Thanks! │
20 └────────────────────────────┘
21
22 Thanks for testing, bugfixes, discussions and everything I forgot go out to:
23
24 Anaël Beutot, Imran Virani, Orestis Floros
25
26 -- Michael Stapelberg, 2021-02-01
0
1 ┌──────────────────────────────┐
2 │ Release notes for i3 v4.21.1 │
3 └──────────────────────────────┘
4
5 This is i3 v4.21.1. This version is considered stable. All users of i3 are
6 strongly encouraged to upgrade.
7
8 This release fixes a few rough edges with regards to the newly-introduced
9 tiling drag feature, which is now configurable:
10 https://i3wm.org/docs/userguide.html#config_tiling_drag
11
12 ┌────────────────────────────┐
13 │ Changes in i3 v4.21.1 │
14 └────────────────────────────┘
15
16 • tiling drag: allow configuration
17 • tiling drag: allow click immediately, to focus on decoration click
18 • tiling drag: fix cursor (wrong argument passed)
19 • tiling drag: increase drag threshold, run it through logical_px
20 • tiling drag: left-click needs threshold, mod-click doesn’t
21 • tiling drag: ignore scratchpad windows when locating drop targets
22 • tiling drag: only start when there are drop targets
23 • Raise floating windows when their border is clicked
24
25 ┌────────────────────────────┐
26 │ Bugfixes │
27 └────────────────────────────┘
28
29 • docs/ipc: document sticky field of GET_TREE
30 • man/i3-config-wizard: escape ~ to prevent interpretation as subscript
31 • Motif hints: respect maximum border style configuration set by user
32 • i3-dmenu-desktop: fix quoting bug
33 • Fix segfault during config validation
34
35 ┌────────────────────────────┐
36 │ Thanks! │
37 └────────────────────────────┘
38
39 Thanks for testing, bugfixes, discussions and everything I forgot go out to:
40
41 Erich Heine, Matias Goldfeld, Orestis Floros, Tudor Brindus, bodea
42
43 -- Michael Stapelberg, 2022-10-24
399399 Then, it looks through all bindings and gets the one which matches the received
400400 event.
401401
402 The bound command is parsed by the cmdparse lexer/parser, see +parse_cmd+ in
403 +src/cmdparse.y+.
402 The bound command is parsed by the i3 parser, see +parse_command+ in
403 +src/commands_parser.c+.
404404
405405 == Manage windows (src/main.c, manage_window() and reparent_window())
406406
8282 <ul id="nav">
8383 <li><a style="border-bottom: 2px solid #fff" href="/docs">Docs</a></li>
8484 <li><a href="/screenshots">Screens</a></li>
85 <li><a href="https://www.reddit.com/r/i3wm/">FAQ</a></li>
85 <li><a href="https://www.github.com/i3/i3/discussions">Get Help</a></li>
8686 <li><a href="/contact">Contact</a></li>
8787 <li><a href="https://bugs.i3wm.org/">Bugs</a></li>
8888 </ul>
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
190190 is 9 pixels), since the separator line is drawn in the middle.
191191 markup::
192192 A string that indicates how the text of the block should be parsed. Set to
193 +"pango"+ to use https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup].
193 +"pango"+ to use https://developer.gnome.org/pango/1.46/[Pango markup].
194194 Set to +"none"+ to not use any markup (default). Pango markup only works
195195 if you use a pango font.
196196
1313 in +/tmp/i3-%u.XXXXXX/ipc-socket.%p+ where +%u+ is your UNIX username, +%p+ is
1414 the PID of i3 and XXXXXX is a string of random characters from the portable
1515 filename character set (see mkdtemp(3)). You can get the socketpath from i3 by
16 calling +i3 --get-socketpath+.
16 executing +i3 --get-socketpath+, which will print the path to the standard
17 output (plus a newline).
1718
1819 All i3 utilities, like +i3-msg+ and +i3-input+ will read the +I3_SOCKET_PATH+
1920 X11 property, stored on the X11 root window.
3839
3940 == Sending messages to i3
4041
41 To send a message to i3, you have to format in the binary message format which
42 i3 expects. This format specifies a magic string in the beginning to ensure
43 the integrity of messages (to prevent follow-up errors). Following the magic
44 string comes the length of the payload of the message as 32-bit integer, and
45 the type of the message as 32-bit integer (the integers are not converted, so
46 they are in native byte order).
42 To send a message to i3, you have to format it in the binary message format
43 which i3 expects. This format specifies a magic string in the beginning to
44 ensure the integrity of messages (to prevent follow-up errors). Following the
45 magic string comes the length of the payload of the message as a 32-bit
46 integer, and the type of the message as a 32-bit integer (the integers are not
47 converted, so they are in native byte order).
4748
4849 The magic string currently is "i3-ipc" and will only be changed when a change
4950 in the IPC API is done which breaks compatibility (we hope that we don’t need
9596
9697 == Receiving replies from i3
9798
98 Replies from i3 usually consist of a simple string (the length of the string
99 is the message_length, so you can consider them length-prefixed) which in turn
100 contain the JSON serialization of a data structure. For example, the
101 GET_WORKSPACES message returns an array of workspaces (each workspace is a map
102 with certain attributes).
103
104 === Reply format
99 Each message sent to i3 will cause exactly one reply to be sent in return. The
100 order of the sent replies will always correspond to the order of the sent
101 requests. The only exception to this is <<events>>, which (once subscribed to)
102 may be sent at any time (though never in the middle of another event or reply).
103
104 It is generally safe to send several messages to i3 without first waiting for a
105 reply for each one (pipelining) -- though, note that depending on the language /
106 network library you use, writing to the socket without also reading from it may
107 cause a deadlock due to the socket buffers getting full.
105108
106109 The reply format is identical to the normal message format. There also is
107110 the magic string, then the message length, then the message type and the
108111 payload.
112
113 The payload of replies from i3 usually consists of a simple string (the length
114 of the string is the message_length, so you can consider them length-prefixed),
115 which in turn contain the JSON serialization of a data structure. For example,
116 the GET_WORKSPACES message returns an array of workspaces (each workspace is a
117 map with certain attributes).
118
119 Replies currently have a 1:1 correspondence to messages, with the message type
120 of the reply corresponding to the message type of the message which caused the
121 reply to be sent.
109122
110123 The following reply types are implemented:
111124
126139 VERSION (7)::
127140 Reply to the GET_VERSION message.
128141 BINDING_MODES (8)::
129 Reply to the GET_BINDING_MODES message.
142 Reply to the GET_BINDING_MODES message.
130143 GET_CONFIG (9)::
131144 Reply to the GET_CONFIG message.
132145 TICK (10)::
133146 Reply to the SEND_TICK message.
147 SYNC (11)::
148 Reply to the SYNC message.
134149 GET_BINDING_STATE (12)::
135150 Reply to the GET_BINDING_STATE message.
136151
152 == Messages and replies
153
137154 [[_command_reply]]
138 === COMMAND reply
155 === RUN_COMMAND / COMMAND
156
157 Run the payload as an https://i3wm.org/docs/userguide.html#list_of_commands[i3
158 command] (like the commands you can bind to keys).
159
160 *Message:*
161
162 The message payload is the string containing the command to execute. There is
163 no JSON encoding or trailing newline.
164
165 *Reply:*
139166
140167 The reply consists of a list of serialized maps for each command that was
141168 parsed. Each has the property +success (bool)+ and may also include a
169196 -------------------
170197
171198 [[_workspaces_reply]]
172 === WORKSPACES reply
199 === GET_WORKSPACES / WORKSPACES
200
201 Get the list of current workspaces.
202
203 *Message:*
204
205 No payload.
206
207 *Reply:*
173208
174209 The reply consists of a serialized list of workspaces. Each workspace has the
175210 following properties:
233268 -------------------
234269
235270 [[_subscribe_reply]]
236 === SUBSCRIBE reply
271 === SUBSCRIBE
272
273 Subscribe this IPC connection to the event types specified in the message
274 payload. See <<events>>.
275
276 *Message:*
277
278 A JSON-encoded array of event types to subscribe to.
279
280 *Reply:*
237281
238282 The reply consists of a single serialized map. The only property is
239283 +success (bool)+, indicating whether the subscription was successful (the
245289 -------------------
246290
247291 [[_outputs_reply]]
248 === OUTPUTS reply
292 === GET_OUTPUTS / OUTPUTS
293
294 Get the list of current outputs.
295
296 *Message:*
297
298 No payload.
299
300 *Reply:*
249301
250302 The reply consists of a serialized list of outputs. Each output has the
251303 following properties:
256308 Whether this output is currently active (has a valid mode).
257309 primary (boolean)::
258310 Whether this output is currently the primary output.
259 current_workspace (string)::
311 current_workspace (string or null)::
260312 The name of the current workspace that is visible on this output. +null+ if
261313 the output is not active.
262314 rect (map)::
292344 -------------------
293345
294346 [[_tree_reply]]
295 === TREE reply
347 === GET_TREE / TREE
348
349 Get the i3 layout tree.
350
351 *Message:*
352
353 No payload.
354
355 *Reply:*
296356
297357 The reply consists of a serialized tree. Each node in the tree (representing
298358 one container) has at least the properties listed below. While the nodes might
328388 "vertical".
329389 THIS FIELD IS OBSOLETE. It is still present, but your code should not
330390 use it. Instead, rely on the layout field.
331 percent (float)::
391 percent (float or null)::
332392 The percentage which this container takes in its parent. A value of
333393 +null+ means that the percent property does not make sense for this
334394 container, for example for the root container.
352412 geometry (map)::
353413 The original geometry the window specified when i3 mapped it. Used when
354414 switching a window to floating mode, for example.
355 window (integer)::
415 window (integer or null)::
356416 The X11 window ID of the *actual client window* inside this container.
357 This field is set to null for split containers or otherwise empty
417 This field is set to +null+ for split containers or otherwise empty
358418 containers. This ID corresponds to what xwininfo(1) and other
359419 X11-related tools display (usually in hex).
360420 window_properties (map)::
361421 This optional field contains all available X11 window properties from the
362 following list: *title*, *instance*, *class*, *window_role* and *transient_for*.
422 following list: *title*, *instance*, *class*, *window_role*, *machine*
423 and *transient_for*.
363424 window_type (string)::
364 The window type (_NET_WM_WINDOW_TYPE). Possible values are undefined, normal,
365 dialog, utility, toolbar, splash, menu, dropdown_menu, popup_menu, tooltip and
366 notification.
425 The window type (_NET_WM_WINDOW_TYPE). Possible values are `undefined`,
426 unknown, normal, dialog, utility, toolbar, splash, menu, dropdown_menu,
427 popup_menu, tooltip and notification.
367428 urgent (bool)::
368429 Whether this container (window, split container, floating container or
369430 workspace) has the urgency hint set, directly or indirectly. All parent
378439 order. Traversing the tree by following the first entry in this array
379440 will result in eventually reaching the one node with +focused+ set to
380441 true.
442 sticky (bool)::
443 Whether this window is "sticky". If it is also floating, this window will
444 be present on all workspaces on the same output.
381445 fullscreen_mode (integer)::
382446 Whether this container is in fullscreen state or not.
383447 Possible values are
385449 +1+ (fullscreened on output) or
386450 +2+ (fullscreened globally).
387451 Note that all workspaces are considered fullscreened on their respective output.
388
452 floating (string)::
453 Floating state of container.
454 Can be either "auto_on", "auto_off", "user_on" or "user_off"
389455 nodes (array of node)::
390456 The tiling (i.e. non-floating) child containers of this node.
391457 floating_nodes (array of node)::
392458 The floating child containers of this node. Only non-empty on nodes with
393459 type +workspace+.
460 scratchpad_state (string)::
461 Whether the window is not in the scratchpad ("none"), freshly moved to
462 the scratchpad but not yet resized ("fresh") or moved to the scratchpad
463 and resized ("changed").
394464
395465 Please note that in the following example, I have left out some keys/values
396466 which are not relevant for the type of the node. Otherwise, the example would
531601 -----------------------
532602
533603 [[_marks_reply]]
534 === MARKS reply
604 === GET_MARKS / MARKS
605
606 Gets the names of all currently set marks.
607
608 *Message:*
609
610 No payload.
611
612 *Reply:*
535613
536614 The reply consists of a single array of strings for each container that has a
537615 mark. A mark can only be set on one container, so the array is unique.
540618 If no window has a mark the response will be the empty array [].
541619
542620 [[_bar_config_reply]]
543 === BAR_CONFIG reply
621 === GET_BAR_CONFIG / BAR_CONFIG
622
623 Gets the specified bar configuration or the names of all bar configurations if payload is empty.
624
625 *Message:*
626
627 No payload, or the ID of the bar whose configuration to retrieve.
628
629 *Reply:*
544630
545631 This can be used by third-party workspace bars (especially i3bar, but others
546632 are free to implement compatible alternatives) to get the +bar+ block
640726 --------------
641727
642728 [[_version_reply]]
643 === VERSION reply
729 === GET_VERSION / VERSION
730
731 Gets the i3 version.
732
733 *Message:*
734
735 No payload.
736
737 *Reply:*
644738
645739 The reply consists of a single JSON dictionary with the following keys:
646740
673767 -------------------
674768
675769 [[_binding_modes_reply]]
676 === BINDING_MODES reply
770 === GET_BINDING_MODES / BINDING_MODES
771
772 Gets the names of all currently configured binding modes.
773
774 *Message:*
775
776 No payload.
777
778 *Reply:*
677779
678780 The reply consists of an array of all currently configured binding modes.
679781
683785 ---------------------
684786
685787 [[_config_reply]]
686 === CONFIG reply
687
688 The config reply is a map which currently only contains the "config" member,
689 which is a string containing the config file as loaded by i3 most recently.
690
691 *Example:*
692 -------------------
693 { "config": "font pango:monospace 8\nbindsym Mod4+q exit\n" }
788 === GET_CONFIG / CONFIG
789
790 Returns the last loaded i3 config.
791
792 *Message:*
793
794 No payload.
795
796 *Reply:*
797
798 The config reply is a map which contains the following fields:
799
800 config (string)::
801 The top-level config file contents that i3 has loaded most recently.
802 This field is kept for backwards compatibility. See +included_configs+
803 instead.
804 included_configs (array of maps)::
805 i3 adds one entry to this array for each config file it loads, in
806 order. The first entry’s +raw_contents+ are identical to the +config+
807 field.
808
809 Each +included_configs+ entry contains the following fields
810
811 path (string)::
812 Absolute path name to the config file that i3 loaded.
813 raw_contents (string)::
814 The raw contents of the file as i3 read them.
815 variable_replaced_contents (string)::
816 The contents of the file after i3 replaced all variables. This is useful
817 for debugging variable replacement.
818
819 *Example:*
820 -------------------
821 {
822 "config": "include font.cfg\n",
823 "included_configs": [
824 {
825 "path": "/home/michael/configfiles/i3/config",
826 "raw_contents": "include font.cfg\n",
827 "variable_replaced_contents": "include font.cfg\n"
828 },
829 {
830 "path": "/home/michael/configfiles/i3/font.cfg",
831 "raw_contents": "set $font pango:monospace 8\nfont $font",
832 "variable_replaced_contents": "set pango:monospace 8 pango:monospace 8\nfont pango:monospace 8\n"
833 }
834 ],
835 }
694836 -------------------
695837
696838 [[_tick_reply]]
697 === TICK reply
839 === SEND_TICK / TICK
840
841 Sends a tick event with the specified payload.
842
843 *Message:*
844
845 The payload of the tick event to send to IPC event listeners.
846
847 *Reply:*
698848
699849 The reply is a map containing the "success" member. After the reply was
700850 received, the tick event has been written to all IPC connections which subscribe
708858 -------------------
709859
710860 [[_sync_reply]]
711 === SYNC reply
861 === SYNC
862
863 Sends an i3 sync event with the specified random value to the specified window.
864
865 *Message:*
866
867 A JSON-encoded map with the properties "rnd" and "window" (both integer).
868
869 *Reply:*
712870
713871 The reply is a map containing the "success" member. After the reply was
714872 received, the https://i3wm.org/docs/testsuite.html#i3_sync[i3 sync message] was
720878 -------------------
721879
722880 [[_binding_state_reply]]
723 === GET_BINDING_STATE reply
881 === GET_BINDING_STATE
882
883 Request the current binding state, i.e. the currently active binding mode name.
884
885 *Message:*
886
887 No payload.
888
889 *Reply:*
724890
725891 The binding_state reply is a map which currently only contains the "name"
726892 member, which is the name of the currently active binding mode as a string.
184184 container. Only if you start Emacs with the proper instance name (+emacs24
185185 --name notmuch+), it will get swallowed.
186186
187 You can match on "class", "instance", "window_role" and "title". All values are
188 case-sensitive regular expressions (PCRE). Use +xprop(1)+ and click into a
189 window to see its properties:
187 You can match on "class", "instance", "window_role", "title" and "machine". All
188 values are case-sensitive regular expressions (PCRE). Use +xprop(1)+ and click
189 into a window to see its properties:
190190
191191 --------------------------------------------------------------------------------
192192 $ xprop
33 September 2012
44
55 This document explains how the i3 testsuite works, how to use it and extend it.
6 It is targeted at developers who not necessarily have been doing testing before
7 or have not been testing in Perl before. In general, the testsuite is not of
6 It is targeted at developers who haven't necessarily done testing before,
7 or have not used Perl for testing before. In general, the testsuite is not of
88 interest for end users.
9
109
1110 == Introduction
1211
480479 IPC anymore.
481480
482481 [[i3_sync]]
483 == Appendix A: The i3 sync protocol
482 == Appendix A: The I3_SYNC protocol
484483
485484 Consider the following situation: You open two windows in your testcase, then
486485 you use +focus left+ and want to verify that the X11 focus has been updated
498497 However, the test fails. Sometimes. Apparently, there is a race condition in
499498 your test. If you think about it, this is because you are using two different
500499 pieces of software: You tell i3 to update focus, i3 confirms that, and then you
501 ask X11 to give you the current focus. There is a certain time i3 needs to
502 update the X11 state. If the testcase gets CPU time before X11 processed i3's
503 requests, the test will fail.
500 ask X11 to give you the current focus. There is a certain time that the X11
501 server needs to process the requests from i3. If the testcase's request for the
502 input focus is processed before i3's requests, the test will fail.
504503
505504 image::i3-sync.png["Diagram of the race condition", title="Diagram of the race condition"]
506505
530529
531530 The real solution for this problem is a mechanism which I call "the i3 sync
532531 protocol". The idea is to send a request (which does not modify state) via X11
533 to i3 which will then be answered. Due to the request's position in the event
534 queue (*after* all previous events), you can be sure that by the time you
535 receive the reply, all other events have been dealt with by i3 (and, more
536 importantly, X11).
532 to i3 which will then be answered, again via X11. Because this answer is
533 generated via an X11 request, it will be sent to the X11 server *after* all
534 previous requests. Thus, you can be sure that by the time you receive the reply,
535 all other events have been dealt with by i3 (and, more importantly, X11).
537536
538537 image::i3-sync-working.png["Diagram of the i3 sync solution", title="Diagram of the i3 sync solution"]
539538
568567 request. You should use a random value in +data[1]+ and check that you received
569568 the same one when getting the reply.
570569
571 == Appendix B: Socket activation
570 == Appendix B: The sync IPC command
571
572 The above I3_SYNC protocol allows to synchronise with i3. However, it is not
573 enough for tests that also involve i3bar: There might still be messages from
574 i3bar in-flight even after synchronising with i3. Thus, there also exists a sync
575 IPC command, that is however not meant to be used directly. Instead, i3bar uses
576 it for implementing the I3_SYNC protocol.
577
578 The intended usage works like this:
579
580 1. You send an I3_SYNC message to i3bar's window. See <<i3_sync>>.
581 2. i3bar sends a SYNC IPC command to i3 with payload
582 +{"window":your-window-here,"rnd":your-random-value}+.
583 3. i3 reacts to this IPC command as if it received an I3_SYNC request via X11.
584
585 This protocol is used, for example, in t/525-i3bar-mouse-bindings.t: A mouse
586 button press on i3bar is triggered. i3bar reacts to this by sending IPC commands
587 to i3.
588
589 The necessary synchronisation is achieved by sending an I3_SYNC event to i3bar:
590 Because i3bar reacts with a sync IPC command to i3, all previous IPC commands from
591 i3bar will be handled first. Because i3 reacts via X11, all previous X11
592 requests from i3 will be handled by the X11 server first.
593
594 The actual test also has to sync with i3 first due to how X11 handling works.
595 For more details, refer to the documentation for +XAllowEvents+ with mode
596 +ReplayPointer+.
597
598 == Appendix C: Socket activation
572599
573600 Socket activation is a mechanism which was made popular by systemd, an init
574601 replacement. It basically describes creating a listening socket before starting
22 Michael Stapelberg <[email protected]>
33
44 This document contains all the information you need to configure and use the i3
5 window manager. If it does not, please check https://www.reddit.com/r/i3wm/
6 first, then contact us on IRC (preferred) or post your question(s) on the
7 mailing list.
5 window manager. If it does not you can https://i3wm.org/contact/[contact us] on
6 https://github.com/i3/i3/discussions[GitHub Discussions], IRC, or the mailing
7 list.
88
99 == Default keybindings
1010
5252
5353 image:two_terminals.png[Two terminals]
5454
55 To move the focus between the two terminals, you can use the direction keys
56 which you might know from the editor +vi+. However, in i3, your homerow is used
57 for these keys (in +vi+, the keys are shifted to the left by one for
58 compatibility with most keyboard layouts). Therefore, +$mod+j+ is left, +$mod+k+
59 is down, +$mod+l+ is up and `$mod+;` is right. So, to switch between the
60 terminals, use +$mod+k+ or +$mod+l+. Of course, you can also use the arrow keys.
55 To move the focus between the two terminals, you can use the arrow keys. For
56 convenience, the arrows are also available directly on the
57 https://en.wikipedia.org/wiki/Touch_typing[keyboard’s home row] underneath your
58 right hand:
59
60 |===
61 | `$mod+j` | left
62 | `$mod+k` | down
63 | `$mod+l` | up
64 | `$mod+;` | right
65 |===
66
67 Note that this differs by one key from the popular text editor `vi`, which was
68 https://twitter.com/hillelogram/status/1326600125569961991[developed on an
69 ADM-3A terminal and therefore uses `hjkl` instead of `jkl;`] -- i3’s default is
70 meant to require minimal finger movement, but some `vi` users change their i3
71 config for consistency.
6172
6273 At the moment, your workspace is split (it contains two terminals) in a
6374 specific direction (horizontal by default). Every window can be split
6677 or browser) and "split container" for containers that consist of one or more
6778 windows.
6879
69 TODO: picture of the tree
80 //TODO: picture of the tree
7081
7182 To split a window vertically, press +$mod+v+ before you create the new window.
7283 To split it horizontally, press +$mod+h+.
184195
185196 Floating windows are always on top of tiling windows.
186197
198 [[tiling_drag]]
199 === Moving tiling containers with the mouse
200
201 Since i3 4.21, it's possible to drag tiling containers using the mouse. The
202 drag can be initiated either by dragging the window's titlebar or by pressing
203 the <<floating_modifier>> and dragging the container while holding the
204 left-click button.
205
206 Once the drag is initiated and the cursor has left the original container, drop
207 indicators are created according to the position of the cursor relatively to
208 the target container. These indicators help you understand what the resulting
209 <<tree>> layout is going to be after you release the mouse button.
210
211 The possible drop positions are:
212
213 Drop on container::
214 This happens when the mouse is relatively near the center of a container.
215 If the mouse is released, the result is exactly as if you had run the
216 +move container to mark+ command. See <<move_to_mark>>.
217 Drop as sibling::
218 This happens when the mouse is relatively near the edge of a container. If
219 the mouse is released, the dragged container will become a sibling of the
220 target container, placed left/right/up/down according to the position of
221 the indicator.
222 This might or might not create a new v-split or h-split according to the
223 previous layout of the target container. For example, if the target
224 container is in an h-split and you drop the dragged container below it, the
225 new layout will have to be a v-split.
226 Drop to parent::
227 This happens when the mouse is relatively near the edge of a container (but
228 even closer to the border in comparison to the sibling case above) *and* if
229 that edge is also the parent container's edge. For example, if three
230 containers are in a horizontal layout then edges where this can happen is
231 the left edge of the left container, the right edge of the right container
232 and all bottom and top edges of all three containers.
233 If the mouse is released, the container is first dropped as a sibling to
234 the target container, like in the case above, and then is moved
235 directionally like with the +move left|right|down|up+ command. See
236 <<move_direction>>.
237
238 The color of the indicator matches the +client.focused+ setting. See <<client_colors>>.
239
240 [[tree]]
187241 == Tree
188242
189243 i3 stores all information about the X11 outputs, workspaces and layout of the
307361 # i3 config file (v4)
308362 ---------------------
309363
364 [[include]]
365 === Include directive
366
367 Since i3 v4.20, it is possible to include other configuration files from your i3
368 configuration.
369
370 *Syntax*:
371 -----------------
372 include <pattern>
373 -----------------
374
375 i3 expands `pattern` using shell-like word expansion, specifically using the
376 https://manpages.debian.org/wordexp.3[`wordexp(3)` C standard library function].
377
378 *Examples*:
379 --------------------------------------------------------------------------------
380 # Tilde expands to the user’s home directory:
381 include ~/.config/i3/assignments.conf
382
383 # Environment variables are expanded:
384 include $HOME/.config/i3/assignments.conf
385
386 # Wildcards are expanded:
387 include ~/.config/i3/config.d/*.conf
388
389 # Command substitution:
390 include ~/.config/i3/`hostname`.conf
391
392 # i3 loads each path only once, so including the i3 config will not result
393 # in an endless loop, but in an error:
394 include ~/.config/i3/config
395
396 # i3 changes the working directory while parsing a config file
397 # so that relative paths are interpreted relative to the directory
398 # of the config file that contains the path:
399 include assignments.conf
400 --------------------------------------------------------------------------------
401
402 If a specified file cannot be read, for example because of a lack of file
403 permissions, or because of a dangling symlink, i3 will report an error and
404 continue processing your remaining configuration.
405
406 To list all loaded configuration files, run `i3 --moreversion`:
407
408 --------------------------------------------------------------------------------
409 % i3 --moreversion
410 Binary i3 version: 4.19.2-87-gfcae64f7+ © 2009 Michael Stapelberg and contributors
411 Running i3 version: 4.19.2-87-gfcae64f7+ (pid 963940)
412 Loaded i3 config:
413 /tmp/i3.cfg (main) (last modified: 2021-05-13T16:42:31 CEST, 463 seconds ago)
414 /tmp/included.cfg (included) (last modified: 2021-05-13T16:42:43 CEST, 451 seconds ago)
415 /tmp/another.cfg (included) (last modified: 2021-05-13T16:42:46 CEST, 448 seconds ago)
416 --------------------------------------------------------------------------------
417
418 Variables are shared between all config files, but beware of the following limitation:
419
420 * You can define a variable and use it within an included file.
421 * You cannot use (in the parent file) a variable that was defined within an included file.
422
423 This is a technical limitation: variable expansion happens in a separate stage
424 before parsing include directives.
425
426 Conceptually, included files can only add to the configuration, not undo the
427 effects of already-processed configuration. For example, you can only add new
428 key bindings, not overwrite or remove existing key bindings. This means:
429
430 * The `include` directive is suitable for organizing large configurations into
431 separate files, possibly selecting files based on conditionals.
432
433 * The `include` directive is not suitable for expressing “use the default
434 configuration with the following changes”. For that case, we still recommend
435 copying and modifying the default config.
436
437 [NOTE]
438 ====
439 Implementation-wise, i3 does not currently construct one big configuration from
440 all `include` directives. Instead, i3’s config file parser interprets all
441 configuration directives in its `parse_file()` function. When processing an
442 `include` configuration directive, the parser recursively calls `parse_file()`.
443
444 This means the evaluation order of files forms a tree, or one could say i3 uses
445 depth-first traversal.
446 ====
447
310448 === Comments
311449
312450 It is possible and recommended to use comments in your configuration file to
442580 # The middle button over a titlebar kills the window
443581 bindsym --release button2 kill
444582
445 # The middle button and a modifer over any part of the window kills the window
583 # The middle button and a modifier over any part of the window kills the window
446584 bindsym --whole-window $mod+button2 kill
447585
448586 # The right button toggles floating
596734 title_align left|center|right
597735 ---------------------------------------------
598736
737 [[default_border]]
599738 === Default border style for new windows
600739
601 This option determines which border style new windows will have. The default is
740 This option determines which border style *new* windows will have. The default is
602741 +normal+. Note that default_floating_border applies only to windows which are starting out as
603742 floating windows, e.g., dialog windows, but not windows that are floated later on.
604743
9441083 workspace "2: vim" output VGA1
9451084 ---------------------------
9461085
1086 [[client_colors]]
9471087 === Changing colors
9481088
9491089 You can change all colors which i3 uses to draw the window decorations.
9601100 client.focused_inactive::
9611101 A client which is the focused one of its container, but it does not have
9621102 the focus at the moment.
1103 client.focused_tab_title::
1104 Tab or stack container title that is the parent of the focused container
1105 but not directly focused. Defaults to focused_inactive if not specified and
1106 does not use the indicator and child_border colors.
9631107 client.unfocused::
9641108 A client which is not the focused one of its container.
9651109 client.urgent::
10031147 programs to get information from i3, such as the current workspaces
10041148 (to display a workspace bar), and to control i3.
10051149
1006 The IPC socket is enabled by default and will be created in
1150 By default, an IPC socket will be created in
10071151 +$XDG_RUNTIME_DIR/i3/ipc-socket.%p+ if the directory is available, falling back
10081152 to +/tmp/i3-%u.XXXXXX/ipc-socket.%p+, where +%u+ is your UNIX username, +%p+ is
10091153 the PID of i3 and XXXXXX is a string of random characters from the portable
12571401 # this line is not continued \
12581402 bindsym Mod1+F fullscreen toggle
12591403 -------------------
1404
1405 [[config_tiling_drag]]
1406 === Tiling drag
1407
1408 You can configure how to initiate the tiling drag feature (see <<tiling_drag>>).
1409
1410 *Syntax*:
1411 --------------------------------
1412 tiling_drag off
1413 tiling_drag modifier|titlebar [modifier|titlebar]
1414 --------------------------------
1415
1416 *Examples*:
1417 --------------------------------
1418 # Only initiate a tiling drag when the modifier is held:
1419 tiling_drag modifier
1420
1421 # Initiate a tiling drag on either titlebar click or held modifier:
1422 tiling_drag modifier titlebar
1423
1424 # Disable tiling drag altogether
1425 tiling_drag off
1426 --------------------------------
12601427
12611428 == Configuring i3bar
12621429
18792046 # enable floating mode and move container to workspace 4
18802047 for_window [class="^evil-app$"] floating enable, move container to workspace 4
18812048
2049 # enable window icons for all windows with extra horizontal padding of 1px
2050 for_window [all] title_window_icon padding 1px
2051
18822052 # move all floating windows to the scratchpad
18832053 bindsym $mod+x [floating] move scratchpad
18842054 ------------------------------------
18852055
18862056 The criteria which are currently implemented are:
18872057
2058 all::
2059 Matches all windows. This criterion requires no value.
18882060 class::
18892061 Compares the window class (the second part of WM_CLASS). Use the
18902062 special value +\_\_focused__+ to match all windows having the same window
19012073 Compare the window type (_NET_WM_WINDOW_TYPE). Possible values are
19022074 +normal+, +dialog+, +utility+, +toolbar+, +splash+, +menu+, +dropdown_menu+,
19032075 +popup_menu+, +tooltip+ and +notification+.
2076 machine::
2077 Compares the name of the machine the client window is running on
2078 (WM_CLIENT_MACHINE). Usually, it is equal to the hostname of the local
2079 machine, but it may differ if remote X11 apps are used.
19042080 id::
19052081 Compares the X11 window ID, which you can get via +xwininfo+ for example.
19062082 title::
19382114 tiling are matched. With "user", only windows that the user made tiling
19392115 are matched.
19402116
1941 The criteria +class+, +instance+, +role+, +title+, +workspace+ and +mark+ are
1942 actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for
1943 information on how to use them.
2117 The criteria +class+, +instance+, +role+, +title+, +workspace+, +machine+ and
2118 +mark+ are actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc
2119 perlre+ for information on how to use them.
19442120
19452121 [[exec]]
19462122 === Executing applications (exec)
21192295 focus left|right|down|up
21202296 focus parent|child|floating|tiling|mode_toggle
21212297 focus next|prev [sibling]
2122 focus output left|right|up|down|primary|<output>
2298 focus output left|right|down|up|current|primary|next|<output1> [output2]…
21232299 ----------------------------------------------
21242300
21252301 *Examples*:
21392315 # Focus last floating/tiling container
21402316 bindsym $mod+g focus mode_toggle
21412317
2318 # Focus the next output (effectively toggles when you only have two outputs)
2319 bindsym $mod+x move workspace to output next
2320
21422321 # Focus the output right to the current one
21432322 bindsym $mod+x focus output right
21442323
21472326
21482327 # Focus the primary output
21492328 bindsym $mod+x focus output primary
2329
2330 # Cycle focus between outputs VGA1 and LVDS1 but not DVI0
2331 bindsym $mod+x move workspace to output VGA1 LVDS1
21502332 -------------------------------------------------
21512333
21522334 Note that you might not have a primary output configured yet. To do so, run:
21542336 xrandr --output <output> --primary
21552337 -------------------------
21562338
2339 [[move_direction]]
21572340 === Moving containers
21582341
21592342 Use the +move+ command to move a container.
22802463 See <<move_to_outputs>> for how to move a container/workspace to a different
22812464 RandR output.
22822465
2283 Workspace names are parsed as
2284 https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup]
2285 by i3bar.
2466 Workspace names are parsed as https://developer.gnome.org/pango/1.46/[Pango
2467 markup] by i3bar.
22862468
22872469 [[back_and_forth]]
22882470 To switch back to the previously focused workspace, use +workspace
23892571 If a workspace does not exist, the command +workspace number "1: mail"+ will
23902572 create workspace "1: mail".
23912573
2392 If a workspace with number 1 does already exist, the command will switch to this
2574 If a workspace with number 1 already exists, the command will switch to this
23932575 workspace and ignore the text part. So even when the workspace has been renamed
2394 to "1: web", the above command will still switch to it.
2576 "1: web", the above command will still switch to it. The command +workspace 1+
2577 will however create and move to a new workspace "1" alongside the existing
2578 "1: mail" workspace.
23952579
23962580 === Moving workspaces to a different screen
23972581
24072591
24082592 *Syntax*:
24092593 ------------------------------------------------------------
2410 move container to output left|right|down|up|current|primary|<output>
2411 move workspace to output left|right|down|up|current|primary|<output>
2412 ------------------------------------------------------------
2594 move container to output left|right|down|up|current|primary|next|<output1> [output2]…
2595 move workspace to output left|right|down|up|current|primary|next|<output1> [output2]…
2596 -------------------------------------------------------------------------------------
24132597
24142598 *Examples*:
24152599 --------------------------------------------------------
24162600 # Move the current workspace to the next output
24172601 # (effectively toggles when you only have two outputs)
2418 bindsym $mod+x move workspace to output right
2602 bindsym $mod+x move workspace to output next
2603
2604 # Cycle this workspace between outputs VGA1 and LVDS1 but not DVI0
2605 bindsym $mod+x move workspace to output VGA1 LVDS1
24192606
24202607 # Put this window on the presentation output.
24212608 bindsym $mod+x move container to output VGA1
24232610 # Put this window on the primary output.
24242611 bindsym $mod+x move container to output primary
24252612 --------------------------------------------------------
2613
2614 If you specify more than one output, the container/workspace is cycled through
2615 them: If it is already in one of the outputs of the list, it will move to the
2616 next output in the list. If it is in an output not in the list, it will move to
2617 the first specified output. Non-existing outputs are skipped.
24262618
24272619 Note that you might not have a primary output configured yet. To do so, run:
24282620 -------------------------
24292621 xrandr --output <output> --primary
24302622 -------------------------
24312623
2624 [[move_to_mark]]
24322625 === Moving containers/windows to marks
24332626
24342627 To move a container to another container with a specific mark (see <<vim_like_marks>>),
25742767
25752768 By default, i3 will simply print the X11 window title. Using +title_format+,
25762769 this can be customized by setting the format to the desired output. This
2577 directive supports
2578 https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup]
2579 and the following placeholders which will be replaced:
2770 directive supports https://developer.gnome.org/pango/1.46/[Pango markup] and the
2771 following placeholders which will be replaced:
25802772
25812773 +%title+::
25822774 For normal windows, this is the X11 window title (_NET_WM_NAME or WM_NAME
25892781 +%instance+::
25902782 The X11 window instance (first part of WM_CLASS). This corresponds to the
25912783 +instance+ criterion, see <<command_criteria>>.
2784 +%machine+::
2785 The X11 name of the machine (WM_CLIENT_MACHINE). This corresponds to the
2786 +machine+ criterion, see <<command_criteria>>.
25922787
25932788 Using the <<for_window>> directive, you can set the title format for any window
25942789 based on <<command_criteria>>.
26082803
26092804 # print window titles of firefox windows red
26102805 for_window [class="(?i)firefox"] title_format "<span foreground='red'>%title</span>"
2806 -------------------------------------------------------------------------------------
2807
2808 [[title_window_icon]]
2809 === Window title icon
2810
2811 By default, i3 does not display the window icon in the title bar.
2812
2813 Starting with i3 v4.20, you can optionally enable window icons either for
2814 specific windows or for all windows (using the <<for_window>> directive).
2815
2816 *Syntax*:
2817 -----------------------------
2818 title_window_icon <yes|no|toggle>
2819 title_window_icon <padding|toggle> <px>
2820 ------------------------------
2821
2822 *Examples*:
2823 -------------------------------------------------------------------------------------
2824 # show the window icon for the focused window to make it stand out
2825 bindsym $mod+p title_window_icon on
2826
2827 # enable window icons for all windows
2828 for_window [all] title_window_icon on
2829
2830 # enable window icons for all windows with extra horizontal padding
2831 for_window [all] title_window_icon padding 3px
26112832 -------------------------------------------------------------------------------------
26122833
26132834 === Changing border style
26392860 bindsym $mod+t border normal 0
26402861 # use no window title and a thick border
26412862 bindsym $mod+y border pixel 3
2863 # use window title *and* a thick border
2864 bindsym $mod+y border normal 3
26422865 # use neither window title nor border
26432866 bindsym $mod+u border none
2867 # no border on VLC
2868 for_window [class="vlc"] border none
26442869 ----------------------------------------------
2870
2871 To change the default for all windows, see the directive <<default_border>>.
26452872
26462873 [[shmlog]]
26472874 === Enabling shared memory logging
1515 # This font is widely installed, provides lots of unicode glyphs, right-to-left
1616 # text rendering and scalability on retina/hidpi displays (thanks to pango).
1717 #font pango:DejaVu Sans Mono 8
18
19 # Start XDG autostart .desktop files using dex. See also
20 # https://wiki.archlinux.org/index.php/XDG_Autostart
21 exec --no-startup-id dex --autostart --environment i3
1822
1923 # The combination of xss-lock, nm-applet and pactl is a popular choice, so
2024 # they are included here as an example. Modify as you see fit.
4347
4448 # use Mouse+Mod1 to drag floating windows to their wanted position
4549 floating_modifier Mod1
50
51 # move tiling windows via drag & drop by left-clicking into the title bar,
52 # or left-clicking anywhere into the window while holding the floating modifier.
53 tiling_drag modifier titlebar
4654
4755 # start a terminal
4856 bindsym Mod1+Return exec i3-sensible-terminal
1616 # This font is widely installed, provides lots of unicode glyphs, right-to-left
1717 # text rendering and scalability on retina/hidpi displays (thanks to pango).
1818 #font pango:DejaVu Sans Mono 8
19
20 # Start XDG autostart .desktop files using dex. See also
21 # https://wiki.archlinux.org/index.php/XDG_Autostart
22 exec --no-startup-id dex --autostart --environment i3
1923
2024 # The combination of xss-lock, nm-applet and pactl is a popular choice, so
2125 # they are included here as an example. Modify as you see fit.
3741
3842 # Use Mouse+$mod to drag floating windows to their wanted position
3943 floating_modifier $mod
44
45 # move tiling windows via drag & drop by left-clicking into the title bar,
46 # or left-clicking anywhere into the window while holding the floating modifier.
47 tiling_drag modifier titlebar
4048
4149 # start a terminal
4250 bindcode $mod+36 exec i3-sensible-terminal
132132 open(my $callfh, '>', "GENERATED_${prefix}_call.h");
133133 my $resultname = uc(substr($prefix, 0, 1)) . substr($prefix, 1) . 'ResultIR';
134134 say $callfh '#pragma once';
135 say $callfh "static void GENERATED_call(const int call_identifier, struct $resultname *result) {";
135 say $callfh "static void GENERATED_call(Match *current_match, struct stack *stack, const int call_identifier, struct $resultname *result) {";
136136 say $callfh ' switch (call_identifier) {';
137137 my $call_id = 0;
138138 for my $state (@keys) {
149149 # calls to get_string(). Also replaces state names (like FOR_WINDOW)
150150 # with their ID (useful for cfg_criteria_init(FOR_WINDOW) e.g.).
151151 $cmd =~ s/$_/$statenum{$_}/g for @keys;
152 $cmd =~ s/\$([a-z_]+)/get_string("$1")/g;
153 $cmd =~ s/\&([a-z_]+)/get_long("$1")/g;
152 $cmd =~ s/\$([a-z_]+)/get_string(stack, "$1")/g;
153 $cmd =~ s/\&([a-z_]+)/get_long(stack, "$1")/g;
154154 # For debugging/testing, we print the call using printf() and thus need
155155 # to generate a format string. The format uses %d for <number>s,
156156 # literal numbers or state IDs and %s for NULL, <string>s and literal
174174 say $callfh '#ifndef TEST_PARSER';
175175 my $real_cmd = $cmd;
176176 if ($real_cmd =~ /\(\)/) {
177 $real_cmd =~ s/\(/(&current_match, result/;
177 $real_cmd =~ s/\(/(current_match, result/;
178178 } else {
179 $real_cmd =~ s/\(/(&current_match, result, /;
179 $real_cmd =~ s/\(/(current_match, result, /;
180180 }
181181 say $callfh " $real_cmd;";
182182 say $callfh '#else';
227227 ($call_identifier) = ($next_state =~ /^call ([0-9]+)$/);
228228 $next_state = '__CALL';
229229 }
230 my $identifier = $token->{identifier};
231 say $tokfh qq| { "$token_name", "$identifier", $next_state, { $call_identifier } },|;
230 my $identifier;
231 # Set $identifier to NULL if there is no identifier
232 if ($token->{identifier} eq ""){
233 $identifier = "NULL"
234 }
235 else{
236 $identifier = qq|"$token->{identifier}"|;
237 }
238 say $tokfh qq| { "$token_name", $identifier, $next_state, { $call_identifier } },|;
232239 }
233240 say $tokfh '};';
234241 }
330330 * separate configuration directives. */
331331 while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
332332 walk++;
333
334 //printf("remaining input: %s\n", walk);
335333
336334 cmdp_token_ptr *ptr = &(tokens[state]);
337335 for (c = 0; c < ptr->n; c++) {
425423 }
426424
427425 if (strcmp(token->name, "end") == 0) {
428 //printf("checking for end: *%s*\n", walk);
429426 if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
430427 if ((result = next_state(token)) != NULL)
431428 return result;
432 /* To make sure we start with an appropriate matching
433 * datastructure for commands which do *not* specify any
429 /* To make sure we start with an appropriate matching data
430 * structure for commands which do *not* specify any
434431 * criteria, we re-initialize the criteria system after
435432 * every command. */
436 // TODO: make this testable
437433 walk++;
438434 break;
439435 }
153153 },
154154 no_chdir => 1,
155155 follow_fast => 1,
156 # Ignore any duplicate files and directories and proceed normally:
157 follow_skip => 2,
156158 },
157159 @searchdirs
158160 );
410412 my $location = $app->{_Location};
411413
412414 # Quote as described by “The Exec key”:
413 # https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
415 # https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s07.html
414416 sub quote {
415417 my ($str) = @_;
416418 $str =~ s/("|`|\$|\\)/\\$1/g;
421423 $choice = quote($choice);
422424 $location = quote($location);
423425 $name = quote($name);
426
427 # https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s07.html:
428 #
429 # Note that the general escape rule for values of type string states that the
430 # backslash character can be escaped as ("\\") as well and that this escape rule
431 # is applied before the quoting rule. As such, to unambiguously represent a
432 # literal backslash character in a quoted argument in a desktop entry file
433 # requires the use of four successive backslash characters ("\\\\"). Likewise, a
434 # literal dollar sign in a quoted argument in a desktop entry file is
435 # unambiguously represented with ("\\$").
436 $exec =~ s/\\\\/\\/g;
424437
425438 # Remove deprecated field codes, as the spec dictates.
426439 $exec =~ s/%[dDnNvm]//g;
478491 # starts with a double quote ("), everything is parsed as-is until the next
479492 # double quote which is NOT preceded by a backslash (\).
480493 #
481 # Therefore, we escape all double quotes (") by replacing them with \"
482 $exec =~ s/"/\\"/g;
494 # Therefore, we escape all double quotes (") by replacing them with \".
495 # To not change the meaning of any double quote, backslashes need to be
496 # escaped as well.
497 $exec =~ s/(["\\])/\\$1/g;
483498
484499 if (exists($app->{StartupNotify}) && !$app->{StartupNotify}) {
485500 $nosn = '--no-startup-id';
2424 #include <sys/mman.h>
2525 #include <sys/stat.h>
2626 #include <unistd.h>
27
28 #if !defined(__OpenBSD__)
29 static uint32_t offset_next_write;
30 #endif
27 #include <sys/types.h>
28 #include <sys/socket.h>
29 #include <sys/un.h>
30
3131 static uint32_t wrap_count;
3232
3333 static i3_shmlog_header *header;
3434 static char *logbuffer,
3535 *walk;
3636 static int ipcfd = -1;
37
38 static volatile bool interrupted = false;
39
40 static void sighandler(int signal) {
41 interrupted = true;
42 }
4337
4438 static void disable_shmlog(void) {
4539 const char *disablecmd = "debuglog off; shmlog off";
187181
188182 /* NB: While we must never write, we need O_RDWR for the pthread condvar. */
189183 int logbuffer_shm = shm_open(shmname, O_RDWR, 0);
190 if (logbuffer_shm == -1)
184 if (logbuffer_shm == -1) {
191185 err(EXIT_FAILURE, "Could not shm_open SHM segment for the i3 log (%s)", shmname);
192
193 if (fstat(logbuffer_shm, &statbuf) != 0)
186 }
187
188 if (fstat(logbuffer_shm, &statbuf) != 0) {
194189 err(EXIT_FAILURE, "stat(%s)", shmname);
195
196 /* NB: While we must never write, we need PROT_WRITE for the pthread condvar. */
197 logbuffer = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, logbuffer_shm, 0);
198 if (logbuffer == MAP_FAILED)
190 }
191
192 logbuffer = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, logbuffer_shm, 0);
193 if (logbuffer == MAP_FAILED) {
199194 err(EXIT_FAILURE, "Could not mmap SHM segment for the i3 log");
195 }
200196
201197 header = (i3_shmlog_header *)logbuffer;
202198
203 if (verbose)
199 if (verbose) {
204200 printf("next_write = %d, last_wrap = %d, logbuffer_size = %d, shmname = %s\n",
205201 header->offset_next_write, header->offset_last_wrap, header->size, shmname);
202 }
206203 free(shmname);
207204 walk = logbuffer + header->offset_next_write;
208205
232229 return 0;
233230 }
234231
235 /* Handle SIGINT gracefully to invoke atexit handlers, if any. */
236 struct sigaction action;
237 action.sa_handler = sighandler;
238 sigemptyset(&action.sa_mask);
239 action.sa_flags = 0;
240 sigaction(SIGINT, &action, NULL);
241
242 /* Since pthread_cond_wait() expects a mutex, we need to provide one.
243 * To not lock i3 (that’s bad, mhkay?) we just define one outside of
244 * the shared memory. */
245 pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER;
246 pthread_mutex_lock(&dummy_mutex);
247 while (!interrupted) {
248 pthread_cond_wait(&(header->condvar), &dummy_mutex);
249 /* If this was not a spurious wakeup, print the new lines. */
250 if (header->offset_next_write != offset_next_write) {
251 offset_next_write = header->offset_next_write;
252 print_till_end();
253 }
232 char *log_stream_socket_path = root_atom_contents("I3_LOG_STREAM_SOCKET_PATH", NULL, 0);
233 if (log_stream_socket_path == NULL) {
234 errx(EXIT_FAILURE, "could not determine i3 log stream socket path: possible i3-dump-log and i3 version mismatch");
235 }
236
237 int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
238 if (sockfd == -1) {
239 err(EXIT_FAILURE, "Could not create socket");
240 }
241
242 (void)fcntl(sockfd, F_SETFD, FD_CLOEXEC);
243
244 struct sockaddr_un addr;
245 memset(&addr, 0, sizeof(struct sockaddr_un));
246 addr.sun_family = AF_LOCAL;
247 strncpy(addr.sun_path, log_stream_socket_path, sizeof(addr.sun_path) - 1);
248 if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
249 err(EXIT_FAILURE, "Could not connect to i3 on socket %s", log_stream_socket_path);
250 }
251
252 /* Same size as the buffer used in log.c vlog(): */
253 char buf[4096];
254 for (;;) {
255 const int n = read(sockfd, buf, sizeof(buf));
256 if (n == -1) {
257 err(EXIT_FAILURE, "read(log-stream-socket):");
258 }
259 if (n == 0) {
260 exit(0); /* i3 closed the socket */
261 }
262 buf[n] = '\0';
263 swrite(STDOUT_FILENO, buf, n);
254264 }
255265
256266 #endif
00 # This list can be used to convert X11 Keysyms to Unicode 2.1 character.
11 # The list is not checked for correctness by Unicode officials. Use it
2 # at your own risk and the creator is not responsable for any damage that
2 # at your own risk and the creator is not responsible for any damage that
33 # occurred due to using this list.
44 #
55 # The list is created by looking at the Keysym names and the Unicode data
155155 char *payload = NULL;
156156 bool quiet = false;
157157 bool monitor = false;
158 bool raw_reply = false;
158159
159160 static struct option long_options[] = {
160161 {"socket", required_argument, 0, 's'},
163164 {"quiet", no_argument, 0, 'q'},
164165 {"monitor", no_argument, 0, 'm'},
165166 {"help", no_argument, 0, 'h'},
167 {"raw", no_argument, 0, 'r'},
166168 {0, 0, 0, 0}};
167169
168 char *options_string = "s:t:vhqm";
170 char *options_string = "s:t:vhqmr";
169171
170172 while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
171173 if (o == 's') {
216218 return 0;
217219 } else if (o == '?') {
218220 exit(EXIT_FAILURE);
221 } else if (o == 'r') {
222 raw_reply = true;
219223 }
220224 }
221225
261265 /* For the reply of commands, have a look if that command was successful.
262266 * If not, nicely format the error message. */
263267 if (reply_type == I3_IPC_REPLY_TYPE_COMMAND) {
264 yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
265 yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
266 yajl_free(handle);
267
268 switch (state) {
269 case yajl_status_ok:
270 break;
271 case yajl_status_client_canceled:
272 case yajl_status_error:
273 errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
274 }
275
276 if (!quiet) {
268 if (!raw_reply) {
269 yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
270 yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
271 yajl_free(handle);
272
273 switch (state) {
274 case yajl_status_ok:
275 break;
276 case yajl_status_client_canceled:
277 case yajl_status_error:
278 errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
279 }
280 }
281
282 if (!quiet || raw_reply) {
277283 printf("%.*s\n", reply_length, reply);
278284 }
279285 } else if (reply_type == I3_IPC_REPLY_TYPE_CONFIG) {
280 yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
281 yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
282 yajl_free(handle);
283
284 switch (state) {
285 case yajl_status_ok:
286 break;
287 case yajl_status_client_canceled:
288 case yajl_status_error:
289 errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
286 if (raw_reply) {
287 printf("%.*s\n", reply_length, reply);
288 } else {
289 yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
290 yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
291 yajl_free(handle);
292
293 switch (state) {
294 case yajl_status_ok:
295 break;
296 case yajl_status_client_canceled:
297 case yajl_status_error:
298 errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
299 }
290300 }
291301 } else if (reply_type == I3_IPC_REPLY_TYPE_SUBSCRIBE) {
292302 do {
265265 }
266266
267267 /**
268 * Return the position and size the i3-nagbar window should use.
269 * This will be the primary output or a fallback if it cannot be determined.
270 */
271 static xcb_rectangle_t get_window_position(void) {
272 /* Default values if we cannot determine the primary output or its CRTC info. */
273 xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + 2 * MSG_PADDING + BAR_BORDER};
274
268 * Tries to position the rectangle on the primary output.
269 */
270 static void set_window_position_primary(xcb_rectangle_t *result) {
275271 xcb_randr_get_screen_resources_current_cookie_t rcookie = xcb_randr_get_screen_resources_current(conn, root);
276272 xcb_randr_get_output_primary_cookie_t pcookie = xcb_randr_get_output_primary(conn, root);
277273
313309 goto free_resources;
314310 }
315311
316 result.x = crtc->x;
317 result.y = crtc->y;
312 result->x = crtc->x;
313 result->y = crtc->y;
318314 goto free_resources;
319315
320316 free_resources:
321317 free(res);
322318 free(primary);
323 return result;
319 }
320
321 /**
322 * Tries to position the rectangle on the output with input focus.
323 * If unsuccessful, try to position on primary output.
324 */
325 static void set_window_position_focus(xcb_rectangle_t *result) {
326 bool success = false;
327 xcb_get_input_focus_reply_t *input_focus = NULL;
328 xcb_get_geometry_reply_t *geometry = NULL;
329 xcb_translate_coordinates_reply_t *coordinates = NULL;
330
331 /* To avoid the input window disappearing while determining its position */
332 xcb_grab_server(conn);
333
334 input_focus = xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL);
335 if (input_focus == NULL || input_focus->focus == XCB_NONE) {
336 LOG("Failed to receive the current input focus or no window has the input focus right now.\n");
337 goto free_resources;
338 }
339
340 geometry = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, input_focus->focus), NULL);
341 if (geometry == NULL) {
342 LOG("Failed to received window geometry.\n");
343 goto free_resources;
344 }
345
346 coordinates = xcb_translate_coordinates_reply(
347 conn, xcb_translate_coordinates(conn, input_focus->focus, root, geometry->x, geometry->y), NULL);
348 if (coordinates == NULL) {
349 LOG("Failed to translate coordinates.\n");
350 goto free_resources;
351 }
352
353 LOG("Found current focus at x = %i / y = %i.\n", coordinates->dst_x, coordinates->dst_y);
354 result->x = coordinates->dst_x;
355 result->y = coordinates->dst_y;
356 success = true;
357
358 free_resources:
359 xcb_ungrab_server(conn);
360 free(input_focus);
361 free(coordinates);
362 free(geometry);
363 if (!success) {
364 LOG("Could not position on focused output, trying to position on primary output.\n");
365 set_window_position_primary(result);
366 }
324367 }
325368
326369 int main(int argc, char *argv[]) {
359402
360403 argv0 = argv[0];
361404
405 bool position_on_primary = false;
362406 char *pattern = sstrdup("pango:monospace 8");
363407 int o, option_index = 0;
364408 enum { TYPE_ERROR = 0,
372416 {"help", no_argument, 0, 'h'},
373417 {"message", required_argument, 0, 'm'},
374418 {"type", required_argument, 0, 't'},
419 {"primary", no_argument, 0, 'p'},
375420 {0, 0, 0, 0}};
376421
377 char *options_string = "b:B:f:m:t:vh";
422 char *options_string = "b:B:f:m:t:vhp";
378423
379424 prompt = i3string_from_utf8("Please do not run this program.");
380425
398443 case 'h':
399444 free(pattern);
400445 printf("i3-nagbar " I3_VERSION "\n");
401 printf("i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
446 printf("i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v] [-p]\n");
402447 return 0;
448 case 'p':
449 position_on_primary = true;
450 break;
403451 case 'b':
404452 case 'B':
405453 buttons = srealloc(buttons, sizeof(button_t) * (buttoncnt + 1));
463511 err(EXIT_FAILURE, "pledge");
464512 #endif
465513
466 xcb_rectangle_t win_pos = get_window_position();
514 /* Default values if we cannot determine the preferred window position. */
515 xcb_rectangle_t win_pos = (xcb_rectangle_t){50, 50, 500, font.height + 2 * MSG_PADDING + BAR_BORDER};
516 if (position_on_primary) {
517 set_window_position_primary(&win_pos);
518 } else {
519 set_window_position_focus(&win_pos);
520 }
467521
468522 xcb_cursor_context_t *cursor_ctx;
469523 if (xcb_cursor_context_new(conn, root_screen, &cursor_ctx) < 0) {
77 # Distributions/packagers can enhance this script with a
88 # distribution-specific mechanism to find the preferred pager.
99
10 # The less -E and -F options exit immediately for short files, strip if present.
11 case "$LESS" in
12 *[EF]*) LESS=`echo "$LESS" | tr -d EF`
13 esac
14
1015 # Hopefully one of these is installed (no flamewars about preference please!):
1116 # We don't use 'more' because it will exit if the file is too short.
1217 # Worst case scenario we'll open the file in your editor.
77 # We welcome patches that add distribution-specific mechanisms to find the
88 # preferred terminal emulator. On Debian, there is the x-terminal-emulator
99 # symlink for example.
10 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
10 #
11 # Invariants:
12 # 1. $TERMINAL must come first
13 # 2. Distribution-specific mechanisms come next, e.g. x-terminal-emulator
14 # 3. The terminal emulator with best accessibility comes first.
15 # 4. No order is guaranteed/desired for the remaining terminal emulators.
16 for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper wezterm; do
1117 if command -v "$terminal" > /dev/null 2>&1; then
1218 exec "$terminal" "$@"
1319 fi
4242 } i3bar_child;
4343
4444 /*
45 * Remove all blocks from the given statusline.
46 * If free_resources is set, the fields of each status block will be free'd.
47 */
48 void clear_statusline(struct statusline_head *head, bool free_resources);
49
50 /*
4551 * Start a child process with the specified command and reroute stdin.
4652 * We actually start a $SHELL to execute the command so we don't have to care
4753 * about arguments and such
6565 uint32_t border_left;
6666 bool pango_markup;
6767
68 /* The amount of pixels necessary to render a separater after the block. */
68 /* The amount of pixels necessary to render a separator after the block. */
6969 uint32_t sep_block_width;
7070
7171 /* Continuously-updated information on how to render this status block. */
7575 void parse_config_json(char *json);
7676
7777 /**
78 * Start parsing the received bar configuration list. The only usecase right
79 * now is to automatically get the first bar id.
80 *
81 */
82 void parse_get_first_i3bar_config(char *json);
83
84 /**
7885 * free()s the color strings as soon as they are not needed anymore.
7986 *
8087 */
1717 * socket_path must be a valid path to the ipc_socket of i3
1818 *
1919 */
20 int init_connection(const char *socket_path);
20 void init_connection(const char *socket_path);
2121
2222 /*
2323 * Destroy the connection to i3.
2626 #include <yajl/yajl_parse.h>
2727
2828 /* Global variables for child_*() */
29 i3bar_child child;
29 i3bar_child child = {0};
3030 #define DLOG_CHILD DLOG("%s: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
3131 __func__, (long)child.pid, child.stopped, child.stop_signal, child.cont_signal, child.click_events, child.click_events_init)
3232
6565 * Remove all blocks from the given statusline.
6666 * If free_resources is set, the fields of each status block will be free'd.
6767 */
68 static void clear_statusline(struct statusline_head *head, bool free_resources) {
68 void clear_statusline(struct statusline_head *head, bool free_resources) {
6969 struct status_block *first;
7070 while (!TAILQ_EMPTY(head)) {
7171 first = TAILQ_FIRST(head);
138138 if (stdin_io != NULL) {
139139 ev_io_stop(main_loop, stdin_io);
140140 FREE(stdin_io);
141 close(stdin_fd);
142 stdin_fd = 0;
143 close(child_stdin);
144 child_stdin = 0;
141145 }
142146
143147 if (child_sig != NULL) {
1212 #include <stdlib.h>
1313 #include <string.h>
1414
15 #include <X11/Xlib.h>
1615 #include <yajl/yajl_parse.h>
1716
1817 config_t config;
125124 }
126125
127126 if (len == strlen("shift") && !strncmp((const char *)val, "shift", strlen("shift"))) {
128 config.modifier = ShiftMask;
127 config.modifier = XCB_MOD_MASK_SHIFT;
129128 return 1;
130129 }
131130 if (len == strlen("ctrl") && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) {
132 config.modifier = ControlMask;
131 config.modifier = XCB_MOD_MASK_CONTROL;
133132 return 1;
134133 }
135134 if (len == strlen("Mod") + 1 && !strncmp((const char *)val, "Mod", strlen("Mod"))) {
136135 switch (val[3]) {
137136 case '1':
138 config.modifier = Mod1Mask;
137 config.modifier = XCB_MOD_MASK_1;
139138 return 1;
140139 case '2':
141 config.modifier = Mod2Mask;
140 config.modifier = XCB_MOD_MASK_2;
142141 return 1;
143142 case '3':
144 config.modifier = Mod3Mask;
143 config.modifier = XCB_MOD_MASK_3;
145144 return 1;
146145 case '5':
147 config.modifier = Mod5Mask;
146 config.modifier = XCB_MOD_MASK_5;
148147 return 1;
149148 }
150149 }
151150
152 config.modifier = Mod4Mask;
151 config.modifier = XCB_MOD_MASK_4;
153152 return 1;
154153 }
155154
183182
184183 if (!strcmp(cur_key, "status_command")) {
185184 DLOG("command = %.*s\n", len, val);
186 FREE(config.command);
187185 sasprintf(&config.command, "%.*s", len, val);
188186 return 1;
189187 }
372370 *
373371 */
374372 void parse_config_json(char *json) {
375 yajl_handle handle;
376 yajl_status state;
377 handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
373 yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
378374
379375 TAILQ_INIT(&(config.bindings));
380376 TAILQ_INIT(&(config.tray_outputs));
381377
382 state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
378 yajl_status state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
383379
384380 /* FIXME: Proper error handling for JSON parsing */
385381 switch (state) {
392388 break;
393389 }
394390
391 yajl_free(handle);
392 }
393
394 static int i3bar_config_string_cb(void *params_, const unsigned char *val, size_t _len) {
395 sasprintf(&config.bar_id, "%.*s", (int)_len, val);
396 return 0; /* Stop parsing */
397 }
398
399 /*
400 * Start parsing the received bar configuration list. The only usecase right
401 * now is to automatically get the first bar id.
402 *
403 */
404 void parse_get_first_i3bar_config(char *json) {
405 yajl_callbacks configs_callbacks = {
406 .yajl_string = i3bar_config_string_cb,
407 };
408 yajl_handle handle = yajl_alloc(&configs_callbacks, NULL, NULL);
409 yajl_parse(handle, (const unsigned char *)json, strlen(json));
395410 yajl_free(handle);
396411 }
397412
8484 *
8585 */
8686 static void got_bar_config(char *reply) {
87 if (!config.bar_id) {
88 DLOG("Received bar list \"%s\"\n", reply);
89 parse_get_first_i3bar_config(reply);
90
91 if (!config.bar_id) {
92 ELOG("No bar configuration found, please configure a bar block in your i3 config file.\n");
93 exit(EXIT_FAILURE);
94 }
95
96 LOG("Using first bar config: %s. Use --bar_id to manually select a different bar configuration.\n", config.bar_id);
97 i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id);
98 return;
99 }
100
87101 DLOG("Received bar config \"%s\"\n", reply);
88102 /* We initiate the main function by requesting infos about the outputs and
89103 * workspaces. Everything else (creating the bars, showing the right workspace-
140154 static void got_output_event(char *event) {
141155 DLOG("Got output event!\n");
142156 i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
143 if (!config.disable_ws) {
144 i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
145 }
146157 }
147158
148159 /*
153164 DLOG("Got mode event!\n");
154165 parse_mode_json(event);
155166 draw_bars(false);
167 }
168
169 static bool strings_differ(char *a, char *b) {
170 const bool a_null = (a == NULL);
171 const bool b_null = (b == NULL);
172 if (a_null != b_null) {
173 return true;
174 }
175 if (a_null && b_null) {
176 return false;
177 }
178 return strcmp(a, b) != 0;
156179 }
157180
158181 /*
175198
176199 /* update the configuration with the received settings */
177200 DLOG("Received bar config update \"%s\"\n", event);
178 char *old_command = config.command ? sstrdup(config.command) : NULL;
201
202 char *old_command = config.command;
203 config.command = NULL;
179204 bar_display_mode_t old_mode = config.hide_on_modifier;
205
180206 parse_config_json(event);
181207 if (old_mode != config.hide_on_modifier) {
182208 reconfig_windows(true);
187213 init_colors(&(config.colors));
188214
189215 /* restart status command process */
190 if (old_command && strcmp(old_command, config.command) != 0) {
216 if (strings_differ(old_command, config.command)) {
191217 kill_child();
218 clear_statusline(&statusline_head, true);
192219 start_child(config.command);
193220 }
194221 free(old_command);
327354 * socket_path must be a valid path to the ipc_socket of i3
328355 *
329356 */
330 int init_connection(const char *socket_path) {
357 void init_connection(const char *socket_path) {
331358 sock_path = socket_path;
332359 int sockfd = ipc_connect(socket_path);
333360 i3_connection = smalloc(sizeof(ev_io));
334361 ev_io_init(i3_connection, &got_data, sockfd, EV_READ);
335362 ev_io_start(main_loop, i3_connection);
336 return 1;
337363 }
338364
339365 /*
5555 }
5656
5757 static void print_usage(char *elf_name) {
58 printf("Usage: %s -b bar_id [-s sock_path] [-t] [-h] [-v]\n", elf_name);
58 printf("Usage: %s [-b bar_id] [-s sock_path] [-t] [-h] [-v] [-V]\n", elf_name);
5959 printf("\n");
60 printf("-b, --bar_id <bar_id>\tBar ID for which to get the configuration\n");
60 printf("-b, --bar_id <bar_id>\tBar ID for which to get the configuration, defaults to the first bar from the i3 config\n");
6161 printf("-s, --socket <sock_path>\tConnect to i3 via <sock_path>\n");
6262 printf("-t, --transparency Enable transparency (RGBA colors)\n");
6363 printf("-h, --help Display this help message and exit\n");
127127 break;
128128 default:
129129 print_usage(argv[0]);
130 exit(EXIT_SUCCESS);
130 exit(EXIT_FAILURE);
131131 break;
132132 }
133133 }
134134
135 if (!config.bar_id) {
136 /* TODO: maybe we want -f which will automatically ask i3 for the first
137 * configured bar (and error out if there are too many)? */
138 ELOG("No bar_id passed. Please let i3 start i3bar or specify --bar_id\n");
139 exit(EXIT_FAILURE);
140 }
135 LOG("i3bar version " I3_VERSION "\n");
141136
142137 main_loop = ev_default_loop(0); /* needed in init_xcb_early */
143138 char *atom_sock_path = init_xcb_early();
165160 init_dpi();
166161
167162 init_outputs();
168 if (init_connection(socket_path)) {
169 /* Request the bar configuration. When it arrives, we fill the config array. */
170 i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id);
171 }
163
164 init_connection(socket_path);
165 /* Request the bar configuration. When it arrives, we fill the config
166 * array. In case that config.bar_id is empty, we will receive a list of
167 * available configs and then request the configuration for the first bar.
168 * See got_bar_config for more. */
169 i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id);
172170 free(socket_path);
173171
174172 /* We listen to SIGTERM/QUIT/INT and try to exit cleanly, by stopping the main loop.
202202 if (block->border)
203203 render->width += logical_px(block->border_left + block->border_right);
204204
205 /* Compute offset and append for text aligment in min_width. */
205 /* Compute offset and append for text alignment in min_width. */
206206 if (block->min_width <= render->width) {
207207 render->x_offset = 0;
208208 render->x_append = 0;
15391539 free_font();
15401540
15411541 xcb_free_cursor(xcb_connection, cursor);
1542 xcb_flush(xcb_connection);
15431542 xcb_aux_sync(xcb_connection);
15441543 xcb_disconnect(xcb_connection);
15451544
5555 #include "render.h"
5656 #include "window.h"
5757 #include "match.h"
58 #include "cmdparse.h"
5958 #include "xcursor.h"
6059 #include "resize.h"
60 #include "tiling_drag.h"
6161 #include "sighandler.h"
6262 #include "move.h"
6363 #include "output.h"
+0
-14
include/cmdparse.h less more
0 /*
1 * vim:ts=4:sw=4:expandtab
2 *
3 * i3 - an improved dynamic tiling window manager
4 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
5 *
6 * cmdparse.y: the parser for commands you send to i3 (or bind on keys)
7 *
8 */
9 #pragma once
10
11 #include <config.h>
12
13 char *parse_cmd(const char *new);
137137 * Implementation of 'move [window|container] [to] output <str>'.
138138 *
139139 */
140 void cmd_move_con_to_output(I3_CMD, const char *name);
140 void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace);
141141
142142 /**
143143 * Implementation of 'move [window|container] [to] mark <str>'.
152152 void cmd_floating(I3_CMD, const char *floating_mode);
153153
154154 /**
155 * Implementation of 'move workspace to [output] <str>'.
156 *
157 */
158 void cmd_move_workspace_to_output(I3_CMD, const char *name);
159
160 /**
161155 * Implementation of 'split v|h|t|vertical|horizontal|toggle'.
162156 *
163157 */
342336 *
343337 */
344338 void cmd_gaps(I3_CMD, const char *type, const char *scope, const char *mode, const char *value);
339
340 /**
341 * Implementation of 'title_window_icon <yes|no|toggle>' and 'title_window_icon <padding|toggle> <px>'
342 *
343 */
344 void cmd_title_window_icon(I3_CMD, const char *enable, int padding);
1313 #include <yajl/yajl_gen.h>
1414
1515 /**
16 * Holds an intermediate represenation of the result of a call to any command.
16 * Holds an intermediate representation of the result of a call to any command.
1717 * When calling parse_command("floating enable, border none"), the parser will
1818 * internally use this struct when calling cmd_floating and cmd_border.
1919 */
206206 *
207207 */
208208 Con *con_by_mark(const char *mark);
209
210 /**
211 * Start from a container and traverse the transient_for linked list. Returns
212 * true if target window is found in the list. Protects againsts potential
213 * cycles.
214 *
215 */
216 bool con_find_transient_for_window(Con *start, xcb_window_t target);
209217
210218 /**
211219 * Returns true if and only if the given containers holds the mark.
362370 */
363371 bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates);
364372
373 bool con_move_to_target(Con *con, Con *target);
365374 /**
366375 * Moves the given container to the given mark.
367376 *
441450 * floating window.
442451 *
443452 */
444 void con_set_border_style(Con *con, int border_style, int border_width);
453 void con_set_border_style(Con *con, border_style_t border_style, int border_width);
445454
446455 /**
447456 * This function changes the layout of a given container. Use it to handle
3838 CFGFUN(criteria_add, const char *ctype, const char *cvalue);
3939 CFGFUN(criteria_pop_state);
4040
41 CFGFUN(include, const char *pattern);
4142 CFGFUN(font, const char *font);
4243 CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command);
4344 CFGFUN(for_window, const char *command);
6667 CFGFUN(no_focus);
6768 CFGFUN(ipc_socket, const char *path);
6869 CFGFUN(ipc_kill_timeout, const long timeout_ms);
70 CFGFUN(tiling_drag, const char *value);
6971 CFGFUN(restart_state, const char *path);
7072 CFGFUN(popup_during_fullscreen, const char *value);
7173 CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border);
1515 SLIST_HEAD(variables_head, Variable);
1616 extern pid_t config_error_nagbar_pid;
1717
18 struct stack_entry {
19 /* Just a pointer, not dynamically allocated. */
20 const char *identifier;
21 enum {
22 STACK_STR = 0,
23 STACK_LONG = 1,
24 } type;
25 union {
26 char *str;
27 long num;
28 } val;
29 };
30
31 struct stack {
32 struct stack_entry stack[10];
33 };
34
35 struct parser_ctx {
36 bool use_nagbar;
37 bool assume_v4;
38
39 int state;
40 Match current_match;
41
42 /* A list which contains the states that lead to the current state, e.g.
43 * INITIAL, WORKSPACE_LAYOUT.
44 * When jumping back to INITIAL, statelist_idx will simply be set to 1
45 * (likewise for other states, e.g. MODE or BAR).
46 * This list is used to process the nearest error token. */
47 int statelist[10];
48 /* NB: statelist_idx points to where the next entry will be inserted */
49 int statelist_idx;
50
51 /*******************************************************************************
52 * The (small) stack where identified literals are stored during the parsing
53 * of a single config directive (like $workspace).
54 ******************************************************************************/
55 struct stack *stack;
56
57 struct variables_head variables;
58
59 bool has_errors;
60 };
61
1862 /**
19 * An intermediate reprsentation of the result of a parse_config call.
63 * An intermediate representation of the result of a parse_config call.
2064 * Currently unused, but the JSON output will be useful in the future when we
2165 * implement a config parsing IPC command.
2266 *
2367 */
2468 struct ConfigResultIR {
25 /* The JSON generator to append a reply to. */
26 yajl_gen json_gen;
69 struct parser_ctx *ctx;
2770
2871 /* The next state to transition to. Passed to the function so that we can
2972 * determine the next state as a result of a function call, like
3073 * cfg_criteria_pop_state() does. */
3174 int next_state;
75
76 /* Whether any error happened while processing this config directive. */
77 bool has_errors;
3278 };
33
34 struct ConfigResultIR *parse_config(const char *input, struct context *context);
3579
3680 /**
3781 * launch nagbar to indicate errors in the configuration file.
3882 */
3983 void start_config_error_nagbar(const char *configpath, bool has_errors);
84
85 /**
86 * Releases the memory of all variables in ctx.
87 *
88 */
89 void free_variables(struct parser_ctx *ctx);
90
91 typedef enum {
92 PARSE_FILE_FAILED = -1,
93 PARSE_FILE_SUCCESS = 0,
94 PARSE_FILE_CONFIG_ERRORS = 1,
95 } parse_file_result_t;
4096
4197 /**
4298 * Parses the given file by first replacing the variables, then calling
46102 * parsing.
47103 *
48104 */
49 bool parse_file(const char *f, bool use_nagbar);
105 parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFile *included_file);
1313
1414 #include "queue.h"
1515 #include "i3.h"
16
16 #include "tiling_drag.h"
17
18 typedef struct IncludedFile IncludedFile;
1719 typedef struct Config Config;
1820 typedef struct Barconfig Barconfig;
1921 extern char *current_configpath;
2123 extern Config config;
2224 extern SLIST_HEAD(modes_head, Mode) modes;
2325 extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;
26 extern TAILQ_HEAD(includedfiles_head, IncludedFile) included_files;
2427
2528 /**
2629 * Used during the config file lexing/parsing to keep the state of the lexer
6669 char *next_match;
6770
6871 SLIST_ENTRY(Variable) variables;
72 };
73
74 /**
75 * List entry struct for an included file.
76 *
77 */
78 struct IncludedFile {
79 char *path;
80 char *raw_contents;
81 char *variable_replaced_contents;
82
83 TAILQ_ENTRY(IncludedFile) files;
6984 };
7085
7186 /**
223238 color_t background;
224239 struct Colortriple focused;
225240 struct Colortriple focused_inactive;
241 struct Colortriple focused_tab_title;
226242 struct Colortriple unfocused;
227243 struct Colortriple urgent;
228244 struct Colortriple placeholder;
245 bool got_focused_tab_title;
229246 } client;
230247 struct config_bar {
231248 struct Colortriple focused;
249266 /* The number of currently parsed barconfigs */
250267 int number_barconfigs;
251268
269 tiling_drag_t tiling_drag;
270
252271 /* Gap sizes */
253272 gaps_t gaps;
254273
88 */
99 #pragma once
1010
11 #define PCRE2_CODE_UNIT_WIDTH 8
12
1113 #define SN_API_NOT_YET_FROZEN 1
1214 #include <libsn/sn-launcher.h>
1315
1416 #include <xcb/randr.h>
15 #include <pcre.h>
17 #include <pcre2.h>
1618 #include <sys/time.h>
19 #include <cairo/cairo.h>
1720
1821 #include "queue.h"
1922
5861 VERT } orientation_t;
5962 typedef enum { BEFORE,
6063 AFTER } position_t;
61 typedef enum { BS_NORMAL = 0,
62 BS_NONE = 1,
63 BS_PIXEL = 2 } border_style_t;
64 typedef enum {
65 BS_NONE = 0,
66 BS_PIXEL = 1,
67 BS_NORMAL = 2,
68 } border_style_t;
6469
6570 /** parameter to specify whether tree_close_internal() and x_window_kill() should kill
6671 * only this specific window or the whole X11 client */
265270 */
266271 struct regex {
267272 char *pattern;
268 pcre *regex;
269 pcre_extra *extra;
273 pcre2_code *regex;
270274 };
271275
272276 /**
432436 * for_window. */
433437 char *role;
434438
439 /** WM_CLIENT_MACHINE of the window */
440 char *machine;
441
435442 /** Flag to force re-rendering the decoration upon changes */
436443 bool name_x_changed;
437444
485492 /* aspect ratio from WM_NORMAL_HINTS (MPlayer uses this for example) */
486493 double min_aspect_ratio;
487494 double max_aspect_ratio;
495
496 /** Window icon, as Cairo surface */
497 cairo_surface_t *icon;
488498
489499 /** The window has a nonrectangular shape. */
490500 bool shaped;
518528 struct regex *mark;
519529 struct regex *window_role;
520530 struct regex *workspace;
531 struct regex *machine;
521532 xcb_atom_t window_type;
522533 enum {
523534 U_DONTCHECK = -1,
540551 WM_FLOATING_USER,
541552 WM_FLOATING } window_mode;
542553 Con *con_id;
554 bool match_all_windows;
543555
544556 /* Where the window looking for a match should be inserted:
545557 *
673685 /** The format with which the window's name should be displayed. */
674686 char *title_format;
675687
688 /** Whether the window icon should be displayed, and with what padding. -1
689 * means display no window icon (default behavior), 0 means display without
690 * any padding, 1 means display with 1 pixel of padding and so on. */
691 int window_icon_padding;
692
676693 /* a sticky-group is an identifier which bundles several containers to a
677694 * group. The contents are shared between all of them, that is they are
678695 * displayed on whichever of the containers is currently visible */
727744 * layout in workspace_layout and creates a new split container with that
728745 * layout whenever a new container is attached to the workspace. */
729746 layout_t layout, last_split_layout, workspace_layout;
747
730748 border_style_t border_style;
749 /* When the border style of a con changes because of motif hints, we don't
750 * want to set more decoration that the user wants. The user's preference is determined by these:
751 * 1. For new tiling windows, as set by `default_border`
752 * 2. For new floating windows, as set by `default_floating_border`
753 * 3. For all windows that the user runs the `border` command, whatever is
754 * the result of that command for that window. */
755 border_style_t max_user_border_style;
756
731757 /** floating? (= not in tiling layout) This cannot be simply a bool
732758 * because we want to keep track of whether the status was set by the
733759 * application (by setting _NET_WM_WINDOW_TYPE appropriately) or by the
1313 /**
1414 * Connects to i3 to find out the currently running version. Useful since it
1515 * might be different from the version compiled into this binary (maybe the
16 * user didn’t correctly install i3 or forgot te restart it).
16 * user didn’t correctly install i3 or forgot to restart it).
1717 *
1818 * The output looks like this:
1919 * Running i3 version: 4.2-202-gb8e782c (2012-08-12, branch "next") (pid 14804)
22 xmacro(_NET_WM_USER_TIME) \
33 xmacro(_NET_STARTUP_ID) \
44 xmacro(_NET_WORKAREA) \
5 xmacro(_NET_WM_ICON) \
56 xmacro(WM_PROTOCOLS) \
67 xmacro(WM_DELETE_WINDOW) \
78 xmacro(UTF8_STRING) \
1415 xmacro(I3_SYNC) \
1516 xmacro(I3_SHMLOG_PATH) \
1617 xmacro(I3_PID) \
18 xmacro(I3_LOG_STREAM_SOCKET_PATH) \
1719 xmacro(I3_FLOATING_WINDOW) \
1820 xmacro(_NET_REQUEST_FRAME_EXTENTS) \
1921 xmacro(_NET_FRAME_EXTENTS) \
2022 xmacro(_MOTIF_WM_HINTS) \
21 xmacro(WM_CHANGE_STATE)
23 xmacro(WM_CHANGE_STATE) \
24 xmacro(MANAGER)
3838 /** The number of file descriptors passed via socket activation. */
3939 extern int listen_fds;
4040 extern int conn_screen;
41 extern xcb_atom_t wm_sn;
4142 /**
4243 * The EWMH support window that is used to indicate that an EWMH-compliant
4344 * window manager is present. This window is created when i3 starts and
5455 extern SnDisplay *sndisplay;
5556 extern xcb_key_symbols_t *keysyms;
5657 extern char **start_argv;
57 extern Display *xlibdpy, *xkbdpy;
5858 extern int xkb_current_group;
5959 extern TAILQ_HEAD(bindings_head, Binding) *bindings;
6060 extern const char *current_binding_mode;
8080 ipc_client *ipc_new_client_on_fd(EV_P_ int fd);
8181
8282 /**
83 * Creates the UNIX domain socket at the given path, sets it to non-blocking
84 * mode, bind()s and listen()s on it.
85 *
86 */
87 int ipc_create_socket(const char *filename);
88
89 /**
9083 * Sends the specified event to all IPC clients which are currently connected
9184 * and subscribed to this kind of event.
9285 *
1414 #include <stdbool.h>
1515 #include <stdarg.h>
1616 #include <stdio.h>
17 #include <sys/stat.h>
1718 #include <xcb/xcb.h>
1819 #include <xcb/xproto.h>
1920 #include <xcb/xcb_keysyms.h>
293294 int ipc_connect(const char *socket_path);
294295
295296 /**
297 * Connects to the socket at the given path with no fallback paths. Returns
298 * -1 if connect() fails and die()s for other errors.
299 */
300 int ipc_connect_impl(const char *socket_path);
301
302 /**
296303 * Formats a message (payload) of the given size and type and sends it to i3 via
297304 * the given socket file descriptor.
298305 *
444451 * specified coordinates (from the top left corner of the leftmost, uppermost
445452 * glyph) and using the provided gc.
446453 *
454 * The given cairo surface must refer to the specified X drawable.
455 *
447456 * Text must be specified as an i3String.
448457 *
449458 */
450459 void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc,
451 xcb_visualtype_t *visual, int x, int y, int max_width);
452
453 /**
454 * ASCII version of draw_text to print static strings.
455 *
456 */
457 void draw_text_ascii(const char *text, xcb_drawable_t drawable,
458 xcb_gcontext_t gc, int x, int y, int max_width);
460 cairo_surface_t *surface, int x, int y, int max_width);
459461
460462 /**
461463 * Predict the text width in pixels for the given text. Text must be
570572 /* A classic XCB graphics context. */
571573 xcb_gcontext_t gc;
572574
573 xcb_visualtype_t *visual_type;
574
575575 int width;
576576 int height;
577577
618618 void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width);
619619
620620 /**
621 * Draw the given image using libi3.
622 */
623 void draw_util_image(cairo_surface_t *image, surface_t *surface, int x, int y, int width, int height);
624
625 /**
621626 * Draws a filled rectangle.
622627 * This function is a convenience wrapper and takes care of flushing the
623628 * surface as well as restoring the cairo state.
637642 */
638643 void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y,
639644 double dest_x, double dest_y, double width, double height);
645
646 /**
647 * Puts the given socket file descriptor into non-blocking mode or dies if
648 * setting O_NONBLOCK failed. Non-blocking sockets are a good idea for our
649 * IPC model because we should by no means block the window manager.
650 *
651 */
652 void set_nonblock(int sockfd);
653
654 /**
655 * Creates the UNIX domain socket at the given path, sets it to non-blocking
656 * mode, bind()s and listen()s on it.
657 *
658 * The full path to the socket is stored in the char* that out_socketpath points
659 * to.
660 *
661 */
662 int create_socket(const char *filename, char **out_socketpath);
663
664 /**
665 * Checks if the given path exists by calling stat().
666 *
667 */
668 bool path_exists(const char *path);
669
670 /**
671 * Grab a screenshot of the screen's root window and set it as the wallpaper.
672 */
673 void set_screenshot_as_wallpaper(xcb_connection_t *conn, xcb_screen_t *screen);
674
675 /**
676 * Test whether the screen's root window has a background set.
677 *
678 * This opens & closes a window and test whether the root window still shows the
679 * content of the window.
680 */
681 bool is_background_set(xcb_connection_t *conn, xcb_screen_t *screen);
682
683 /**
684 * Reports whether str represents the enabled state (1, yes, true, …).
685 *
686 */
687 bool boolstr(const char *str);
99 #pragma once
1010
1111 #include <config.h>
12 #include <ev.h>
1213
1314 /* We will include libi3.h which define its own version of LOG, ELOG.
1415 * We want *our* version, so we undef the libi3 one. */
3031 extern char *errorfilename;
3132 extern char *shmlogname;
3233 extern int shmlog_size;
34 extern char *current_log_stream_socket_path;
3335
3436 /**
3537 * Initializes logging by creating an error logfile in /tmp (or
99101 * failures. This function is invoked automatically when exiting.
100102 */
101103 void purge_zerobyte_logfile(void);
104
105 void log_new_client(EV_P_ struct ev_io *w, int revents);
4848 *
4949 */
5050 void init_ws_for_output(Output *output);
51
52 /**
53 * Initializes the specified output, assigning the specified workspace to it.
54 *
55 */
56 //void initialize_output(xcb_connection_t *conn, Output *output, Workspace *workspace);
5751
5852 /**
5953 * (Re-)queries the outputs via RandR and stores them in the list of outputs.
1010 #pragma once
1111
1212 #include <config.h>
13
14 #if !defined(__OpenBSD__)
15 #include <pthread.h>
16 #endif
1713
1814 /* Default shmlog size if not set by user. */
1915 extern const int default_shmlog_size;
3834 * coincidentally be exactly the same as previously). Overflows can happen
3935 * and don’t matter — clients use an equality check (==). */
4036 uint32_t wrap_count;
41
42 #if !defined(__OpenBSD__)
43 /* pthread condvar which will be broadcasted whenever there is a new
44 * message in the log. i3-dump-log uses this to implement -f (follow, like
45 * tail -f) in an efficient way. */
46 pthread_cond_t condvar;
47 #endif
4837 } i3_shmlog_header;
0 /*
1 * vim:ts=4:sw=4:expandtab
2 *
3 * i3 - an improved dynamic tiling window manager
4 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
5 *
6 * tiling_drag.h: Reposition tiled windows by dragging.
7 *
8 */
9 #pragma once
10
11 #include "all.h"
12
13 /**
14 * Tiling drag initiation modes.
15 */
16 typedef enum {
17 TILING_DRAG_OFF = 0,
18 TILING_DRAG_MODIFIER = 1,
19 TILING_DRAG_TITLEBAR = 2,
20 TILING_DRAG_MODIFIER_OR_TITLEBAR = 3
21 } tiling_drag_t;
22
23 /**
24 * Returns whether there currently are any drop targets.
25 * Used to only initiate a drag when there is something to drop onto.
26 *
27 */
28 bool has_drop_targets(void);
29
30 /**
31 * Initiates a mouse drag operation on a tiled window.
32 *
33 */
34 void tiling_drag(Con *con, xcb_button_press_event_t *event, bool use_threshold);
182182 *
183183 */
184184 direction_t direction_from_orientation_position(orientation_t orientation, position_t position);
185
186 /**
187 * Converts direction to a string representation.
188 *
189 */
190 const char *direction_to_string(direction_t direction);
191
192 /**
193 * Converts position to a string representation.
194 *
195 */
196 const char *position_to_string(position_t position);
9393 * it is still in use by popular widget toolkits such as GTK+ and Java AWT.
9494 *
9595 */
96 void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
96 bool window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
97
98 /**
99 * Updates the WM_CLIENT_MACHINE
100 *
101 */
102 void window_update_machine(i3Window *win, xcb_get_property_reply_t *prop);
103
104 /**
105 * Updates the _NET_WM_ICON
106 *
107 */
108 void window_update_icon(i3Window *win, xcb_get_property_reply_t *prop);
9191
9292 /**
9393 * Returns true if the workspace is currently visible. Especially important for
94 * multi-monitor environments, as they can have multiple currenlty active
94 * multi-monitor environments, as they can have multiple currently active
9595 * workspaces.
9696 *
9797 */
0 /*
1 * vim:ts=4:sw=4:expandtab
2 *
3 * i3 - an improved dynamic tiling window manager
4 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
5 *
6 */
7 #include "libi3.h"
8
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <unistd.h>
12
13 /*
14 * Reports whether str represents the enabled state (1, yes, true, …).
15 *
16 */
17 bool boolstr(const char *str) {
18 return (strcasecmp(str, "1") == 0 ||
19 strcasecmp(str, "yes") == 0 ||
20 strcasecmp(str, "true") == 0 ||
21 strcasecmp(str, "on") == 0 ||
22 strcasecmp(str, "enable") == 0 ||
23 strcasecmp(str, "active") == 0);
24 }
0 /*
1 * vim:ts=4:sw=4:expandtab
2 *
3 * i3 - an improved dynamic tiling window manager
4 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
5 *
6 */
7 #include "libi3.h"
8
9 #include <unistd.h>
10 #include <libgen.h>
11 #include <err.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <sys/socket.h>
17 #include <sys/un.h>
18
19 /*
20 * Creates the UNIX domain socket at the given path, sets it to non-blocking
21 * mode, bind()s and listen()s on it.
22 *
23 * The full path to the socket is stored in the char* that out_socketpath points
24 * to.
25 *
26 */
27 int create_socket(const char *filename, char **out_socketpath) {
28 char *resolved = resolve_tilde(filename);
29 DLOG("Creating UNIX socket at %s\n", resolved);
30 char *copy = sstrdup(resolved);
31 const char *dir = dirname(copy);
32 if (!path_exists(dir)) {
33 mkdirp(dir, DEFAULT_DIR_MODE);
34 }
35 free(copy);
36
37 /* Check if the socket is in use by another process (this call does not
38 * succeed if the socket is stale / the owner already exited) */
39 int sockfd = ipc_connect_impl(resolved);
40 if (sockfd != -1) {
41 ELOG("Refusing to create UNIX socket at %s: Socket is already in use\n", resolved);
42 close(sockfd);
43 errno = EEXIST;
44 return -1;
45 }
46
47 /* Unlink the unix domain socket before */
48 unlink(resolved);
49
50 sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
51 if (sockfd < 0) {
52 perror("socket()");
53 free(resolved);
54 return -1;
55 }
56
57 (void)fcntl(sockfd, F_SETFD, FD_CLOEXEC);
58
59 struct sockaddr_un addr;
60 memset(&addr, 0, sizeof(struct sockaddr_un));
61 addr.sun_family = AF_LOCAL;
62 strncpy(addr.sun_path, resolved, sizeof(addr.sun_path) - 1);
63 if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
64 perror("bind()");
65 free(resolved);
66 return -1;
67 }
68
69 set_nonblock(sockfd);
70
71 if (listen(sockfd, 5) < 0) {
72 perror("listen()");
73 free(resolved);
74 return -1;
75 }
76
77 free(*out_socketpath);
78 *out_socketpath = resolved;
79 return sockfd;
80 }
3434 void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_drawable_t drawable,
3535 xcb_visualtype_t *visual, int width, int height) {
3636 surface->id = drawable;
37 surface->visual_type = ((visual == NULL) ? visual_type : visual);
3837 surface->width = width;
3938 surface->height = height;
39
40 if (visual == NULL)
41 visual = visual_type;
4042
4143 surface->gc = xcb_generate_id(conn);
4244 xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(conn, surface->gc, surface->id, 0, NULL);
4446 xcb_generic_error_t *error = xcb_request_check(conn, gc_cookie);
4547 if (error != NULL) {
4648 ELOG("Could not create graphical context. Error code: %d. Please report this bug.\n", error->error_code);
47 }
48
49 surface->surface = cairo_xcb_surface_create(conn, surface->id, surface->visual_type, width, height);
49 free(error);
50 }
51
52 surface->surface = cairo_xcb_surface_create(conn, surface->id, visual, width, height);
5053 surface->cr = cairo_create(surface->surface);
5154 }
5255
5558 *
5659 */
5760 void draw_util_surface_free(xcb_connection_t *conn, surface_t *surface) {
61 cairo_status_t status = CAIRO_STATUS_SUCCESS;
62 if (surface->cr) {
63 status = cairo_status(surface->cr);
64 }
65 if (status != CAIRO_STATUS_SUCCESS) {
66 LOG("Found cairo context in an error status while freeing, error %d is %s",
67 status, cairo_status_to_string(status));
68 }
69
70 /* NOTE: This function is also called on uninitialised surface_t instances.
71 * The x11 error from xcb_free_gc(conn, XCB_NONE) is silently ignored
72 * elsewhere.
73 */
5874 xcb_free_gc(conn, surface->gc);
5975 cairo_surface_destroy(surface->surface);
6076 cairo_destroy(surface->cr);
132148 CAIRO_SURFACE_FLUSH(surface->surface);
133149
134150 set_font_colors(surface->gc, fg_color, bg_color);
135 draw_text(text, surface->id, surface->gc, surface->visual_type, x, y, max_width);
151 draw_text(text, surface->id, surface->gc, surface->surface, x, y, max_width);
136152
137153 /* Notify cairo that we (possibly) used another way to draw on the surface. */
138154 cairo_surface_mark_dirty(surface->surface);
155 }
156
157 /**
158 * Draw the given image using libi3.
159 * This function is a convenience wrapper and takes care of flushing the
160 * surface as well as restoring the cairo state.
161 *
162 */
163 void draw_util_image(cairo_surface_t *image, surface_t *surface, int x, int y, int width, int height) {
164 RETURN_UNLESS_SURFACE_INITIALIZED(surface);
165
166 cairo_save(surface->cr);
167
168 cairo_translate(surface->cr, x, y);
169
170 const int src_width = cairo_image_surface_get_width(image);
171 const int src_height = cairo_image_surface_get_height(image);
172 double scale = MIN((double)width / src_width, (double)height / src_height);
173 cairo_scale(surface->cr, scale, scale);
174
175 cairo_set_source_surface(surface->cr, image, 0, 0);
176 cairo_paint(surface->cr);
177
178 cairo_restore(surface->cr);
139179 }
140180
141181 /*
8282 *
8383 */
8484 static void draw_text_pango(const char *text, size_t text_len,
85 xcb_drawable_t drawable, xcb_visualtype_t *visual, int x, int y,
86 int max_width, bool pango_markup) {
85 xcb_drawable_t drawable, cairo_surface_t *surface,
86 int x, int y, int max_width, bool pango_markup) {
8787 /* Create the Pango layout */
8888 /* root_visual_type is cached in load_pango_font */
89 cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable,
90 visual, x + max_width, y + savedFont->height);
9189 cairo_t *cr = cairo_create(surface);
9290 PangoLayout *layout = create_layout_with_dpi(cr);
9391 gint height;
115113 /* Free resources */
116114 g_object_unref(layout);
117115 cairo_destroy(cr);
118 cairo_surface_destroy(surface);
119116 }
120117
121118 /*
163160 font.type = FONT_TYPE_NONE;
164161 font.pattern = NULL;
165162
166 /* No XCB connction, return early because we're just validating the
163 /* No XCB connection, return early because we're just validating the
167164 * configuration file. */
168165 if (conn == NULL) {
169166 return font;
359356 *
360357 */
361358 void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc,
362 xcb_visualtype_t *visual, int x, int y, int max_width) {
359 cairo_surface_t *surface, int x, int y, int max_width) {
363360 assert(savedFont != NULL);
364 if (visual == NULL) {
365 visual = root_visual_type;
366 }
367361
368362 switch (savedFont->type) {
369363 case FONT_TYPE_NONE:
376370 case FONT_TYPE_PANGO:
377371 /* Render the text using Pango */
378372 draw_text_pango(i3string_as_utf8(text), i3string_get_num_bytes(text),
379 drawable, visual, x, y, max_width, i3string_is_markup(text));
380 return;
381 }
382 }
383
384 /*
385 * ASCII version of draw_text to print static strings.
386 *
387 */
388 void draw_text_ascii(const char *text, xcb_drawable_t drawable,
389 xcb_gcontext_t gc, int x, int y, int max_width) {
390 assert(savedFont != NULL);
391
392 switch (savedFont->type) {
393 case FONT_TYPE_NONE:
394 /* Nothing to do */
395 return;
396 case FONT_TYPE_XCB: {
397 size_t text_len = strlen(text);
398 if (text_len > 255) {
399 /* The text is too long to draw it directly to X */
400 i3String *str = i3string_from_utf8(text);
401 draw_text(str, drawable, gc, NULL, x, y, max_width);
402 i3string_free(str);
403 } else {
404 /* X11 coordinates for fonts start at the baseline */
405 int pos_y = y + savedFont->specific.xcb.info->font_ascent;
406
407 xcb_image_text_8(conn, text_len, drawable, gc, x, pos_y, text);
408 }
409 break;
410 }
411 case FONT_TYPE_PANGO:
412 /* Render the text using Pango */
413 draw_text_pango(text, strlen(text),
414 drawable, root_visual_type, x, y, max_width, false);
373 drawable, surface, x, y, max_width, i3string_is_markup(text));
415374 return;
416375 }
417376 }
99 #include <stdlib.h>
1010 #include <string.h>
1111 #include <sys/stat.h>
12
13 /*
14 * Checks if the given path exists by calling stat().
15 *
16 */
17 static bool path_exists(const char *path) {
18 struct stat buf;
19 return (stat(path, &buf) == 0);
20 }
2112
2213 /*
2314 * Get the path of the first configuration file found. If override_configpath is
2626 char *tmp;
2727 sasprintf(&tmp, "%s/i3", dir);
2828 dir = tmp;
29 struct stat buf;
30 if (stat(dir, &buf) != 0) {
31 if (mkdir(dir, 0700) == -1) {
32 warn("Could not mkdir(%s)", dir);
33 errx(EXIT_FAILURE, "Check permissions of $XDG_RUNTIME_DIR = '%s'",
34 getenv("XDG_RUNTIME_DIR"));
35 perror("mkdir()");
36 return NULL;
37 }
29 /* mkdirp() should prevent race between multiple i3 instances started
30 * in parallel from causing problem */
31 if (mkdirp(dir, 0700) == -1) {
32 warn("Could not mkdirp(%s)", dir);
33 errx(EXIT_FAILURE, "Check permissions of $XDG_RUNTIME_DIR = '%s'",
34 getenv("XDG_RUNTIME_DIR"));
35 perror("mkdirp()");
36 return NULL;
3837 }
3938 } else {
4039 /* If not, we create a (secure) temp directory using the template
1212 #include <string.h>
1313 #include <sys/socket.h>
1414 #include <sys/un.h>
15 #include <unistd.h>
1516
1617 /*
1718 * Connects to the i3 IPC socket and returns the file descriptor for the
3839 path = sstrdup("/tmp/i3-ipc.sock");
3940 }
4041
42 int sockfd = ipc_connect_impl(path);
43 if (sockfd < 0) {
44 err(EXIT_FAILURE, "Could not connect to i3 on socket %s", path);
45 }
46 free(path);
47 return sockfd;
48 }
49
50 /**
51 * Connects to the socket at the given path with no fallback paths. Returns
52 * -1 if connect() fails and die()s for other errors.
53 *
54 */
55 int ipc_connect_impl(const char *socket_path) {
4156 int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
4257 if (sockfd == -1)
4358 err(EXIT_FAILURE, "Could not create socket");
4762 struct sockaddr_un addr;
4863 memset(&addr, 0, sizeof(struct sockaddr_un));
4964 addr.sun_family = AF_LOCAL;
50 strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
51 if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0)
52 err(EXIT_FAILURE, "Could not connect to i3 on socket %s", path);
53 free(path);
65 strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
66 if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
67 close(sockfd);
68 return -1;
69 }
5470 return sockfd;
5571 }
0 /*
1 * vim:ts=4:sw=4:expandtab
2 *
3 * i3 - an improved dynamic tiling window manager
4 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
5 *
6 */
7 #include "libi3.h"
8
9 #include <xcb/xcb_aux.h>
10
11 /**
12 * Find the region in the given window that is not covered by a mapped child
13 * window.
14 */
15 static cairo_region_t *unobscured_region(xcb_connection_t *conn, xcb_window_t window,
16 uint16_t window_width, uint16_t window_height) {
17 cairo_rectangle_int_t rectangle;
18 cairo_region_t *region;
19
20 rectangle.x = 0;
21 rectangle.y = 0;
22 rectangle.width = window_width;
23 rectangle.height = window_height;
24 region = cairo_region_create_rectangle(&rectangle);
25
26 xcb_query_tree_reply_t *tree = xcb_query_tree_reply(conn, xcb_query_tree_unchecked(conn, window), NULL);
27 if (!tree) {
28 return region;
29 }
30
31 /* Get information about children */
32 uint16_t n_children = tree->children_len;
33 xcb_window_t *children = xcb_query_tree_children(tree);
34
35 xcb_get_geometry_cookie_t geometries[n_children];
36 xcb_get_window_attributes_cookie_t attributes[n_children];
37
38 for (int i = 0; i < n_children; i++) {
39 geometries[i] = xcb_get_geometry_unchecked(conn, children[i]);
40 attributes[i] = xcb_get_window_attributes_unchecked(conn, children[i]);
41 }
42
43 /* Remove every visible child from the region */
44 for (int i = 0; i < n_children; i++) {
45 xcb_get_geometry_reply_t *geom = xcb_get_geometry_reply(conn, geometries[i], NULL);
46 xcb_get_window_attributes_reply_t *attr = xcb_get_window_attributes_reply(conn, attributes[i], NULL);
47
48 if (geom && attr && attr->map_state == XCB_MAP_STATE_VIEWABLE) {
49 rectangle.x = geom->x;
50 rectangle.y = geom->y;
51 rectangle.width = geom->width;
52 rectangle.height = geom->height;
53 cairo_region_subtract_rectangle(region, &rectangle);
54 }
55
56 free(geom);
57 free(attr);
58 }
59
60 free(tree);
61 return region;
62 }
63
64 static void find_unobscured_pixel(xcb_connection_t *conn, xcb_window_t window,
65 uint16_t window_width, uint16_t window_height,
66 uint16_t *x, uint16_t *y) {
67 cairo_region_t *region = unobscured_region(conn, window, window_width, window_height);
68 if (cairo_region_num_rectangles(region) > 0) {
69 /* Return the top left pixel of the first rectangle */
70 cairo_rectangle_int_t rect;
71 cairo_region_get_rectangle(region, 0, &rect);
72 *x = rect.x;
73 *y = rect.y;
74 } else {
75 /* No unobscured area found */
76 *x = 0;
77 *y = 0;
78 }
79 cairo_region_destroy(region);
80 }
81
82 static uint32_t flicker_window_at(xcb_connection_t *conn, xcb_screen_t *screen, int16_t x, int16_t y, xcb_window_t window,
83 uint32_t pixel) {
84 xcb_create_window(conn, XCB_COPY_FROM_PARENT, window, screen->root, x, y, 10, 10,
85 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT,
86 XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT, (uint32_t[]){pixel, 1});
87 xcb_map_window(conn, window);
88 xcb_clear_area(conn, 0, window, 0, 0, 0, 0);
89 xcb_aux_sync(conn);
90 xcb_destroy_window(conn, window);
91
92 xcb_get_image_reply_t *img = xcb_get_image_reply(conn,
93 xcb_get_image_unchecked(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, screen->root, x, y, 1, 1, ~0),
94 NULL);
95 uint32_t result = 0;
96 if (img) {
97 uint8_t *data = xcb_get_image_data(img);
98 uint8_t depth = img->depth;
99 for (int i = 0; i < MIN(depth, 4); i++) {
100 result = (result << 8) | data[i];
101 }
102 free(img);
103 }
104 return result;
105 }
106
107 bool is_background_set(xcb_connection_t *conn, xcb_screen_t *screen) {
108 uint16_t x, y;
109 find_unobscured_pixel(conn, screen->root, screen->width_in_pixels, screen->height_in_pixels, &x, &y);
110
111 xcb_window_t window = xcb_generate_id(conn);
112
113 uint32_t pixel1 = flicker_window_at(conn, screen, x, y, window, screen->black_pixel);
114 uint32_t pixel2 = flicker_window_at(conn, screen, x, y, window, screen->white_pixel);
115 return pixel1 == pixel2;
116 }
0 #include "libi3.h"
1
2 #include <err.h>
3 #include <fcntl.h>
4
5 /*
6 * Puts the given socket file descriptor into non-blocking mode or dies if
7 * setting O_NONBLOCK failed. Non-blocking sockets are a good idea for our
8 * IPC model because we should by no means block the window manager.
9 *
10 */
11 void set_nonblock(int sockfd) {
12 int flags = fcntl(sockfd, F_GETFL, 0);
13 if (flags & O_NONBLOCK) {
14 return;
15 }
16 flags |= O_NONBLOCK;
17 if (fcntl(sockfd, F_SETFL, flags) < 0) {
18 err(-1, "Could not set O_NONBLOCK");
19 }
20 }
0 /*
1 * vim:ts=4:sw=4:expandtab
2 *
3 * i3 - an improved dynamic tiling window manager
4 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
5 *
6 */
7 #include "libi3.h"
8
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 #include <unistd.h>
12
13 /*
14 * Checks if the given path exists by calling stat().
15 *
16 */
17 bool path_exists(const char *path) {
18 struct stat buf;
19 return (stat(path, &buf) == 0);
20 }
0 /*
1 * vim:ts=4:sw=4:expandtab
2 *
3 * i3 - an improved dynamic tiling window manager
4 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
5 *
6 */
7 #include "libi3.h"
8
9 void set_screenshot_as_wallpaper(xcb_connection_t *conn, xcb_screen_t *screen) {
10 uint16_t width = screen->width_in_pixels;
11 uint16_t height = screen->height_in_pixels;
12 xcb_pixmap_t pixmap = xcb_generate_id(conn);
13 xcb_gcontext_t gc = xcb_generate_id(conn);
14
15 xcb_create_pixmap(conn, screen->root_depth, pixmap, screen->root, width, height);
16
17 xcb_create_gc(conn, gc, screen->root,
18 XCB_GC_FUNCTION | XCB_GC_PLANE_MASK | XCB_GC_FILL_STYLE | XCB_GC_SUBWINDOW_MODE,
19 (uint32_t[]){XCB_GX_COPY, ~0, XCB_FILL_STYLE_SOLID, XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS});
20
21 xcb_copy_area(conn, screen->root, pixmap, gc, 0, 0, 0, 0, width, height);
22 xcb_change_window_attributes(conn, screen->root, XCB_CW_BACK_PIXMAP, (uint32_t[]){pixmap});
23 xcb_free_gc(conn, gc);
24 xcb_free_pixmap(conn, pixmap);
25 xcb_flush(conn);
26 }
00 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
1 <!-- Created with Inkscape (http://www.inkscape.org/) -->
21 <svg
32 xmlns:dc="http://purl.org/dc/elements/1.1/"
43 xmlns:cc="http://creativecommons.org/ns#"
1211 height="297mm"
1312 id="svg2"
1413 sodipodi:version="0.32"
15 inkscape:version="0.46"
16 sodipodi:docname="logo_i3_linuxfr_bapt_v2.svg"
17 inkscape:output_extension="org.inkscape.output.svg.inkscape">
14 inkscape:version="1.0.1 (c497b03c, 2020-09-10)"
15 sodipodi:docname="logo.svg"
16 inkscape:output_extension="org.inkscape.output.svg.inkscape"
17 version="1.1">
1818 <defs
1919 id="defs4">
2020 <linearGradient
3838 </linearGradient>
3939 <inkscape:perspective
4040 id="perspective3661"
41 inkscape:persp3d-origin="750.50629 : 505.26732 : 1"
42 inkscape:vp_z="683.5728 : 1230.5721 : 1"
43 inkscape:vp_y="0 : 1946.8917 : 0"
44 inkscape:vp_x="-526.84957 : 2.2065866e-13 : 0"
41 inkscape:persp3d-origin="800.54004 : 538.95181 : 1"
42 inkscape:vp_z="729.14432 : 1312.6102 : 1"
43 inkscape:vp_y="0 : 2076.6845 : 0"
44 inkscape:vp_x="-561.97287 : 2.3536924e-13 : 0"
4545 sodipodi:type="inkscape:persp3d" />
4646 <linearGradient
4747 id="linearGradient3284">
111111 </linearGradient>
112112 <inkscape:perspective
113113 sodipodi:type="inkscape:persp3d"
114 inkscape:vp_x="-526.84957 : 2.9848654e-13 : 0"
115 inkscape:vp_y="1.192088e-13 : 1946.8917 : 0"
116 inkscape:vp_z="680.54236 : 1232.3792 : 1"
117 inkscape:persp3d-origin="730.30325 : 937.39936 : 1"
114 inkscape:vp_x="-561.97287 : 3.1838564e-13 : 0"
115 inkscape:vp_y="1.2715605e-13 : 2076.6845 : 0"
116 inkscape:vp_z="725.91185 : 1314.5378 : 1"
117 inkscape:persp3d-origin="778.99013 : 999.89265 : 1"
118118 id="perspective10" />
119119 <radialGradient
120120 inkscape:collect="always"
151151 gradientUnits="userSpaceOnUse" />
152152 <inkscape:perspective
153153 id="perspective3373"
154 inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
155 inkscape:vp_z="744.09448 : 526.18109 : 1"
156 inkscape:vp_y="0 : 1000 : 0"
157 inkscape:vp_x="0 : 526.18109 : 1"
154 inkscape:persp3d-origin="396.85039 : 374.17322 : 1"
155 inkscape:vp_z="793.70078 : 561.25983 : 1"
156 inkscape:vp_y="0 : 1066.6667 : 0"
157 inkscape:vp_x="0 : 561.25983 : 1"
158158 sodipodi:type="inkscape:persp3d" />
159159 <linearGradient
160160 id="linearGradient3211">
281281 inkscape:pageopacity="0.0"
282282 inkscape:pageshadow="2"
283283 inkscape:zoom="1"
284 inkscape:cx="239.17981"
285 inkscape:cy="807.75327"
284 inkscape:cx="164.67981"
285 inkscape:cy="285.75327"
286286 inkscape:document-units="px"
287287 inkscape:current-layer="layer1"
288288 showgrid="false"
289289 inkscape:window-width="1272"
290 inkscape:window-height="950"
290 inkscape:window-height="856"
291291 inkscape:window-x="24"
292 inkscape:window-y="24" />
292 inkscape:window-y="22"
293 inkscape:document-rotation="0"
294 inkscape:window-maximized="0" />
293295 <metadata
294296 id="metadata7">
295297 <rdf:RDF>
494496 <path
495497 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"
496498 d="M 182.85714,106.6479 L 182.85714,415.21935 L 182.85714,106.6479 z"
497 id="rect3246"
499 id="path934"
498500 sodipodi:nodetypes="ccc" />
499501 <path
500502 sodipodi:nodetypes="ccc"
514516 style="display:inline">
515517 <path
516518 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"
517 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"
519 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"
518520 id="path2405" />
519521 <path
520522 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"
3434
3535 == DESCRIPTION
3636
37 i3-config-wizard is started by i3 in its default config, unless ~/.i3/config
37 i3-config-wizard is started by i3 in its default config, unless \~/.i3/config
3838 exists. i3-config-wizard creates a keysym based i3 config file (based on
3939 /etc/i3/config.keycodes) in ~/.i3/config.
4040
3838 The prompt string is not included in the user input/command.
3939
4040 -f <font>::
41 Use the specified X11 core font (use +xfontsel+ to chose a font).
41 Use the specified X11 core font (use +xfontsel+ to choose a font).
4242
4343 -v::
4444 Show version and exit.
88
99 == SYNOPSIS
1010
11 i3-msg [-q] [-v] [-h] [-s socket] [-t type] [message]
11 i3-msg [-q] [-v] [-h] [-s socket] [-t type] [-r] [message]
1212
1313 == OPTIONS
1414
3434 Instead of exiting right after receiving the first subscribed event,
3535 wait indefinitely for all of them. Can only be used with "-t subscribe".
3636 See the "subscribe" IPC message type below for details.
37
38 *-r, --raw*::
39 Display the raw JSON reply instead of pretty-printing errors (for commands) or
40 displaying the top-level config file contents (for GET_CONFIG).
3741
3842 *message*::
3943 Send ipc message, see below.
88
99 == SYNOPSIS
1010
11 i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v]
11 i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v] [-p]
1212
1313 == OPTIONS
1414
3838 Same as above, but will execute the shell commands directly, without launching a
3939 terminal emulator.
4040
41 *-p, --primary*::
42 Always opens the i3-nagbar on the primary monitor. By default it opens on the
43 focused monitor.
44
4145 == DESCRIPTION
4246
4347 i3-nagbar is used by i3 to tell you about errors in your configuration file
2222
2323 * $TERMINAL (this is a non-standard variable)
2424 * x-terminal-emulator (only present on Debian and derivatives)
25 * mate-terminal
26 * gnome-terminal
27 * terminator
28 * xfce4-terminal
2529 * urxvt
2630 * rxvt
2731 * termit
28 * terminator
2932 * Eterm
3033 * aterm
3134 * uxterm
3235 * xterm
33 * gnome-terminal
3436 * roxterm
35 * xfce4-terminal
3637 * termite
3738 * lxterminal
38 * mate-terminal
3939 * terminology
4040 * st
4141 * qterminal
4242 --shmlog-size <limit>::
4343 Limits the size of the i3 SHM log to <limit> bytes. Setting this to 0 disables
4444 SHM logging entirely. The default is 0 bytes.
45
46 --replace::
47 Replace an existing window manager.
4548
4649 == DESCRIPTION
4750
88
99 == SYNOPSIS
1010
11 *i3bar* [*-s* 'sock_path'] [*-b* 'bar_id'] [*-v*] [*-h*]
11 *i3bar* [*-b* 'bar_id'] [*-s* 'sock_path'] [*-t*] [*-h*] [*-v*] [*-V*]
1212
1313 == WARNING
1414
2424 Overwrites the path to the i3 IPC socket.
2525
2626 *-b, --bar_id* 'bar_id'::
27 Specifies the bar ID for which to get the configuration from i3.
27 Specifies the bar ID for which to get the configuration from i3. By default,
28 i3bar will use the first bar block as configured in i3.
29
30 *-t, --transparency*::
31 Enable transparency (RGBA colors)
32
33 *-h, --help*::
34 Display a short help-message and exit
2835
2936 *-v, --version*::
3037 Display version number and exit.
3138
32 *-h, --help*::
33 Display a short help-message and exit
39 *-V*::
40 Be verbose.
3441
3542 == DESCRIPTION
3643
55 project(
66 'i3',
77 'c',
8 version: '4.19.1',
8 version: '4.21.1',
99 default_options: [
1010 'c_std=c11',
1111 'warning_level=1', # enable all warnings (-Wall)
6262 sources: vcs_tag(
6363 input: config_h_in,
6464 output: 'config.h',
65 fallback: meson.project_version() + ' (2021-02-01)',
65 fallback: meson.project_version() + ' (2022-10-28)',
6666 )
6767 )
6868
228228 output: '@[email protected]',
229229 command: [
230230 xmlto,
231 '--stringparam',
232 'man.th.title.max.length=30',
231233 'man',
232234 '-o',
233235 '@OUTDIR@',
313315 xkbcommon_dep = dependency('xkbcommon', method: 'pkg-config')
314316 xkbcommon_x11_dep = dependency('xkbcommon-x11', method: 'pkg-config')
315317 yajl_dep = dependency('yajl', method: 'pkg-config')
316 libpcre_dep = dependency('libpcre', version: '>=8.10', method: 'pkg-config')
318 libpcre_dep = dependency('libpcre2-8', version: '>=10', method: 'pkg-config')
317319 cairo_dep = dependency('cairo', version: '>=1.14.4', method: 'pkg-config')
318320 pangocairo_dep = dependency('pangocairo', method: 'pkg-config')
319321 glib_dep = dependency('glib-2.0', method: 'pkg-config')
324326 inc = include_directories('include')
325327
326328 libi3srcs = [
329 'libi3/boolstr.c',
330 'libi3/create_socket.c',
327331 'libi3/dpi.c',
328332 'libi3/draw_util.c',
329333 'libi3/fake_configure_notify.c',
340344 'libi3/ipc_recv_message.c',
341345 'libi3/ipc_send_message.c',
342346 'libi3/is_debug_build.c',
347 'libi3/path_exists.c',
343348 'libi3/resolve_tilde.c',
344349 'libi3/root_atom_contents.c',
345350 'libi3/safewrappers.c',
346351 'libi3/string.c',
347352 'libi3/ucs2_conversion.c',
353 'libi3/nonblock.c',
354 'libi3/screenshot_wallpaper.c',
355 'libi3/is_background_set.c',
348356 ]
349357
350358 if not cdata.get('HAVE_STRNDUP')
400408 'src/sighandler.c',
401409 'src/startup.c',
402410 'src/sync.c',
411 'src/tiling_drag.c',
403412 'src/tree.c',
404413 'src/util.c',
405414 'src/version.c',
513522 'i3-config-wizard/i3-config-wizard-atoms.xmacro.h',
514523 'i3-config-wizard/main.c',
515524 'i3-config-wizard/xcb.h',
525 config_parser,
516526 ],
517527 install: true,
518528 include_directories: include_directories('include', 'i3-config-wizard'),
641651 '@OUTPUT@',
642652 ],
643653 install: true,
644 install_dir: join_paths(get_option('datadir'), 'doc', 'i3'),
654 install_dir: docdir,
645655 )
646656
647657 custom_target(
654664 '@OUTPUT@',
655665 ],
656666 install: true,
657 install_dir: join_paths(get_option('datadir'), 'doc', 'i3'),
667 install_dir: docdir,
658668 )
659669 endif
660670
668678
669679 executable(
670680 'test.commands_parser',
671 'src/commands_parser.c',
681 [
682 'src/commands_parser.c',
683 command_parser,
684 ],
672685 include_directories: inc,
673686 c_args: '-DTEST_PARSER',
674687 dependencies: common_deps,
677690
678691 executable(
679692 'test.config_parser',
680 'src/config_parser.c',
693 [
694 'src/config_parser.c',
695 config_parser,
696 ],
681697 include_directories: inc,
682698 c_args: '-DTEST_PARSER',
683699 dependencies: common_deps,
705721 anyevent_i3,
706722 i3test_pm,
707723 ],
724 timeout: 120, # Default of 30 seconds can cause timeouts on slower machines
708725 )
709726 else
710727 # meson < 0.46.0 does not support the depends arg in test targets.
3939 'scratchpad' -> SCRATCHPAD
4040 'swap' -> SWAP
4141 'title_format' -> TITLE_FORMAT
42 'title_window_icon' -> TITLE_WINDOW_ICON
4243 'mode' -> MODE
4344 'bar' -> BAR
4445 'gaps' -> GAPS
5455 ctype = 'title' -> CRITERION
5556 ctype = 'urgent' -> CRITERION
5657 ctype = 'workspace' -> CRITERION
57 ctype = 'tiling', 'floating'
58 ctype = 'machine' -> CRITERION
59 ctype = 'tiling', 'floating', 'all'
5860 -> call cmd_criteria_add($ctype, NULL); CRITERIA
5961 ']' -> call cmd_criteria_match_windows(); INITIAL
6062
181183 -> call cmd_focus_direction($direction)
182184
183185 state FOCUS_OUTPUT:
184 output = string
185 -> call cmd_focus_output($output)
186 output = word
187 -> call cmd_focus_output($output); FOCUS_OUTPUT
188 end
189 -> call cmd_focus_output(NULL); INITIAL
186190
187191 # kill [window|client]
188192 state KILL:
401405 -> call cmd_move_con_to_workspace_number($number, $no_auto_back_and_forth)
402406
403407 state MOVE_TO_OUTPUT:
404 output = string
405 -> call cmd_move_con_to_output($output)
408 output = word
409 -> call cmd_move_con_to_output($output, 0); MOVE_TO_OUTPUT
410 end
411 -> call cmd_move_con_to_output(NULL, 0); INITIAL
406412
407413 state MOVE_TO_MARK:
408414 mark = string
410416
411417 state MOVE_WORKSPACE_TO_OUTPUT:
412418 'output'
413 ->
414 output = string
415 -> call cmd_move_workspace_to_output($output)
419 -> MOVE_WORKSPACE_TO_OUTPUT_WORD
420
421 state MOVE_WORKSPACE_TO_OUTPUT_WORD:
422 output = word
423 -> call cmd_move_con_to_output($output, 1); MOVE_WORKSPACE_TO_OUTPUT_WORD
424 end
425 -> call cmd_move_con_to_output(NULL, 1); INITIAL
416426
417427 state MOVE_TO_ABSOLUTE_POSITION:
418428 'position'
472482 format = string
473483 -> call cmd_title_format($format)
474484
485 state TITLE_WINDOW_ICON:
486 'padding'
487 -> TITLE_WINDOW_ICON_PADDING
488 enable = 'toggle'
489 -> TITLE_WINDOW_ICON_PADDING
490 enable = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive'
491 -> call cmd_title_window_icon($enable, 0)
492
493 state TITLE_WINDOW_ICON_PADDING:
494 end
495 -> call cmd_title_window_icon($enable, &padding)
496 'px'
497 -> call cmd_title_window_icon($enable, &padding)
498 padding = number
499 ->
500
475501 # bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>]
476502 state BAR:
477503 'hidden_state'
1919 'set ' -> IGNORE_LINE
2020 'set ' -> IGNORE_LINE
2121 'set_from_resource' -> IGNORE_LINE
22 'include' -> INCLUDE
2223 bindtype = 'bindsym', 'bindcode', 'bind' -> BINDING
2324 'bar' -> BARBRACE
2425 'font' -> FONT
5455 'ipc_kill_timeout' -> IPC_KILL_TIMEOUT
5556 'restart_state' -> RESTART_STATE
5657 'popup_during_fullscreen' -> POPUP_DURING_FULLSCREEN
58 'tiling_drag' -> TILING_DRAG
5759 exectype = 'exec_always', 'exec' -> EXEC
5860 colorclass = 'client.background'
5961 -> COLOR_SINGLE
60 colorclass = 'client.focused_inactive', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
62 colorclass = 'client.focused_inactive', 'client.focused_tab_title', 'client.focused', 'client.unfocused', 'client.urgent', 'client.placeholder'
6163 -> COLOR_BORDER
6264
6365 # We ignore comments and 'set' lines (variables).
8688 state SMART_GAPS:
8789 enabled = '1', 'yes', 'true', 'on', 'enable', 'active'
8890 -> call cfg_smart_gaps($enabled)
91 enabled = '0', 'no', 'false', 'off', 'disable', 'inactive'
92 -> call cfg_smart_gaps($enabled)
8993 enabled = 'inverse_outer'
9094 -> call cfg_smart_gaps($enabled)
95
96 # include <pattern>
97 state INCLUDE:
98 pattern = string
99 -> call cfg_include($pattern)
91100
92101 # floating_minimum_size <width> x <height>
93102 state FLOATING_MINIMUM_SIZE_WIDTH:
217226 ctype = 'title' -> CRITERION
218227 ctype = 'urgent' -> CRITERION
219228 ctype = 'workspace' -> CRITERION
229 ctype = 'machine' -> CRITERION
220230 ctype = 'floating_from' -> CRITERION_FROM
221231 ctype = 'tiling_from' -> CRITERION_FROM
222 ctype = 'tiling', 'floating'
232 ctype = 'tiling', 'floating', 'all'
223233 -> call cfg_criteria_add($ctype, NULL); CRITERIA
224234 ']'
225235 -> call cfg_criteria_pop_state()
351361 state POPUP_DURING_FULLSCREEN:
352362 value = 'ignore', 'leave_fullscreen', 'smart'
353363 -> call cfg_popup_during_fullscreen($value)
364
365 state TILING_DRAG_MODE:
366 value = 'modifier', 'titlebar'
367 ->
368 end
369 -> call cfg_tiling_drag($value)
370
371 state TILING_DRAG:
372 off = '0', 'no', 'false', 'off', 'disable', 'inactive'
373 -> call cfg_tiling_drag($off)
374 value = 'modifier', 'titlebar'
375 -> TILING_DRAG_MODE
354376
355377 # client.background <hexcolor>
356378 state COLOR_SINGLE:
588610 -> call cfg_bar_position($position); BAR
589611
590612 state BAR_OUTPUT:
591 output = string
613 output = word
592614 -> call cfg_bar_output($output); BAR
593615
594616 state BAR_TRAY_OUTPUT:
0 fix crash with "layout default"
0 motif hints: respect maximum border style configuration set by user
0 Do not replace existing IPC socket on start
0 fix focus when moving container between outputs with mouse warp and focus_follows_mouse
0 tiling drag: allow click immediately, to focus on decoration click
0 fix tiling drag cursor: should be “move”, accidentally was “top right corner”
0 Fix endless loop with transient_for windows
0 tiling drag: ignore scratchpad windows when locating drop targets
0 fix wrong failed reply on move workspace to output
0 changed WM registration selection from WM_S_S<screen> to WM_S<screen>
0 avoid graphics artifacts when changing the layout tree by initializing surfaces to all black
0 Fix segfault if command in bindsym is empty
0 update parent split con titles when child con swaps position with another child con
0 fix default font not being applied to bars if defined after bar block
0 strip trailing whitespace in bar output names
0 Fix crash if config contains nested variables.
0 Fix segfault with explicit mode "default" key bindings.
0 Restore BS_NORMAL _MOTIF_WM_HINTS correctly
0 Acquire the WM_Sn selection when starting as required by ICCCM
0 tiling drag is now configurable, and defaults to “modifier” for existing configs
0 Refuse to start without valid IPC socket
0 Add client.focused_tab_title color option
0 tiling drag: only initiate when there are drop targets
0 Add support for multiple outputs in focus command
0 Allow moving tiling windows with the mouse
0 #!/usr/bin/env perl
1 use strict;
2 use warnings;
3 use v5.10;
4 use Getopt::Long;
5
6 my @template = (
7 '
8 ┌──────────────────────────────┐
9 │ Release notes for i3 v4.21 │
10 └──────────────────────────────┘
11
12 This is i3 v4.21. This version is considered stable. All users of i3 are
13 strongly encouraged to upgrade.
14
15
16 ┌────────────────────────────┐
17 │ Changes in i3 v4.21 │
18 └────────────────────────────┘
19
20 ',
21 '
22 ┌────────────────────────────┐
23 │ Bugfixes │
24 └────────────────────────────┘
25
26 ');
27
28 my $print_urls = 0;
29 my $result = GetOptions('print-urls' => \$print_urls);
30
31 sub get_number {
32 my $s = shift;
33 return $1 if $s =~ m/^(\d+)/;
34 return -1;
35 }
36
37 sub read_changefiles {
38 my $dirpath = shift;
39 opendir my $dir, $dirpath or die "Cannot open directory $dirpath: $!";
40 my @files = sort { get_number($a) <=> get_number($b) } readdir $dir;
41
42 closedir $dir;
43
44 my $s = '';
45 for my $filename (@files) {
46 next if $filename eq '.';
47 next if $filename eq '..';
48 next if $filename eq '0-example';
49
50 die "Filename $filename should start with a number (e.g. the pull request number)" unless get_number($filename) > 0;
51
52 $filename = $dirpath . '/' . $filename;
53 open my $in, '<', $filename or die "can't open $filename: $!";
54 my @lines = <$in>;
55 close $in or die "can't close $filename: $!";
56
57 my $content = trim(join("\n ", map { trim($_) } @lines));
58 die "$filename can't be empty" unless length($content) > 0;
59
60 my $url = '';
61 if ($print_urls) {
62 my $commit = `git log --diff-filter=A --pretty=format:"%H" $filename`;
63 $commit = trim($commit) if defined($commit);
64 die "$filename: git log failed to find commit" if ($?) || (length($commit) == 0);
65
66 my $pr = find_pr($commit);
67 $url = 'https://github.com/i3/i3/commit/' . $commit;
68 $url = 'https://github.com/i3/i3/pull/' . $pr if defined($pr);
69 $url = $url . "\n";
70 }
71
72 $s = $s . ' • ' . $content . "\n" . $url;
73 }
74 return $s;
75 }
76
77 sub find_pr {
78 my $hash = shift;
79 my $result = `git log --merges --ancestry-path --oneline $hash..next | grep 'Merge pull request' | tail -n1`;
80 return unless defined($result);
81
82 return unless ($result =~ /Merge pull request .([0-9]+)/);
83 return $1;
84 }
85
86 sub trim {
87 (my $s = $_[0]) =~ s/^\s+|\s+$//g;
88 return $s;
89 }
90
91 # Expected to run for i3's git root
92 my $changes = read_changefiles('release-notes/changes');
93 my $bugfixes = read_changefiles('release-notes/bugfixes');
94
95 print $template[0] . $changes . $template[1] . $bugfixes;
00 #!/bin/zsh
11 # This script is used to prepare a new release of i3.
22
3 export RELEASE_VERSION="4.19"
4 export PREVIOUS_VERSION="4.18"
3 set -eu
4
5 export RELEASE_VERSION="4.20"
6 export PREVIOUS_VERSION="4.19.2"
57 export RELEASE_BRANCH="next"
68
79 if [ ! -e "../i3.github.io" ]
1921
2022 if [ ! -e "RELEASE-NOTES-${RELEASE_VERSION}" ]
2123 then
22 echo "RELEASE-NOTES-${RELEASE_VERSION} not found."
23 exit 1
24 fi
25
26 if git diff-files --quiet --exit-code debian/changelog
27 then
28 echo "Expected debian/changelog to be changed (containing the changelog for ${RELEASE_VERSION})."
24 echo "RELEASE-NOTES-${RELEASE_VERSION} not found. Here is the output from the generator:"
25 ./release-notes/generator.pl --print-urls
2926 exit 1
3027 fi
3128
4037
4138 TMPDIR=$(mktemp -d)
4239 cd $TMPDIR
43 if ! wget https://i3wm.org/downloads/i3-${PREVIOUS_VERSION}.tar.bz2; then
44 echo "Could not download i3-${PREVIOUS_VERSION}.tar.bz2 (required for comparing files)."
40 if ! wget https://i3wm.org/downloads/i3-${PREVIOUS_VERSION}.tar.xz; then
41 echo "Could not download i3-${PREVIOUS_VERSION}.tar.xz (required for comparing files)."
4542 exit 1
4643 fi
4744 git clone --quiet --branch "${RELEASE_BRANCH}" https://github.com/i3/i3
5148 exit 1
5249 fi
5350 git checkout -b release-${RELEASE_VERSION}
51 git rm RELEASE-NOTES-*
5452 cp "${STARTDIR}/RELEASE-NOTES-${RELEASE_VERSION}" "RELEASE-NOTES-${RELEASE_VERSION}"
5553 git add RELEASE-NOTES-${RELEASE_VERSION}
56 git rm RELEASE-NOTES-${PREVIOUS_VERSION}
57 sed -i "s/^\s*version: '${PREVIOUS_VERSION}'/ version: '${RELEASE_VERSION}'/" meson.build
54 # Update the release version:
55 sed -i "s/^\s*version: '4.[^']*'/ version: '${RELEASE_VERSION}'/" meson.build
56 cp meson.build "${TMPDIR}/meson.build"
57 # Inject the release date into meson.build for the dist tarball:
58 sed -i "s/'-non-git'/' ($(date +'%Y-%m-%d'))'/" meson.build
5859 git commit -a -m "release i3 ${RELEASE_VERSION}"
5960 git tag "${RELEASE_VERSION}" -m "release i3 ${RELEASE_VERSION}" --sign --local-user=0x4AC8EE1D
6061
6162 mkdir build
6263 (cd build && meson .. && ninja dist)
63 cp build/meson-build/i3-${RELEASE_VERSION}.tar.xz .
64 cp build/meson-dist/i3-${RELEASE_VERSION}.tar.xz .
6465
6566 echo "Differences in the release tarball file lists:"
6667
6768 diff --color -u \
68 <(tar tf ../i3-${PREVIOUS_VERSION}.tar.xz | sed "s,i3-${PREVIOUS_VERSION}/,,g" | sort) \
69 <(tar tf i3-${RELEASE_VERSION}.tar.xz | sed "s,i3-${RELEASE_VERSION}/,,g" | sort)
69 <(tar tf ../i3-${PREVIOUS_VERSION}.tar.* | sed "s,i3-${PREVIOUS_VERSION}/,,g" | sort) \
70 <(tar tf i3-${RELEASE_VERSION}.tar.xz | sed "s,i3-${RELEASE_VERSION}/,,g" | sort) || true
7071
7172 gpg --armor -b i3-${RELEASE_VERSION}.tar.xz
7273
73 echo "${RELEASE_VERSION}-non-git" > I3_VERSION
74 git add I3_VERSION
75 git commit -a -m "Set non-git version to ${RELEASE_VERSION}-non-git."
74 mv "${TMPDIR}/meson.build" .
75 git add meson.build
76 git commit -a -m "Restore non-git version suffix"
7677
7778 if [ "${RELEASE_BRANCH}" = "stable" ]; then
7879 git checkout stable
9394 git config --add remote.origin.push "+refs/heads/stable:refs/heads/stable"
9495
9596 ################################################################################
96 # Section 2: Debian packaging
97 # Section 2: Debian packaging (for QA)
9798 ################################################################################
9899
99100 cd "${TMPDIR}"
132133 echo "Content of resulting package’s .changes file:"
133134 cat ${TMPDIR}/debian/*.changes
134135
135 # debsign is in devscripts, which is available in fedora and debian
136 debsign --no-re-sign -k4AC8EE1D ${TMPDIR}/debian/*.changes
137
138136 # TODO: docker cleanup
139137
140138 ################################################################################
175173
176174 (cd _docs && make)
177175
178 for i in $(find _docs -maxdepth 1 -and -type f -and \! -regex ".*\.\(html\|man\)$" -and \! -name "Makefile")
176 for i in $(find _docs -maxdepth 1 -and -type f -and \! -regex ".*\.\(html\|man\|css\)$" -and \! -name "Makefile")
179177 do
180178 base="$(basename $i)"
181179 [ -e "${TMPDIR}/i3/docs/${base}" ] && cp "_docs/${base}.html" docs/
233231 echo ""
234232 echo "Announce on:"
235233 echo " twitter"
236 echo " google+"
237234 echo " #i3 topic"
238 echo " reddit /r/i3wm"
235 echo " reddit /r/i3wm (link post to changelog)"
236 echo " GitHub Discussions → Announcements"
717717 }
718718
719719 /*
720 * Returns true if a is a key binding for the same key as b.
721 *
722 */
723 static bool binding_same_key(Binding *a, Binding *b) {
724 /* Check if the input types are different */
725 if (a->input_type != b->input_type) {
726 return false;
727 }
728
729 /* Check if one is using keysym while the other is using bindsym. */
730 if ((a->symbol == NULL && b->symbol != NULL) ||
731 (a->symbol != NULL && b->symbol == NULL)) {
732 return false;
733 }
734
735 /* If a is NULL, b has to be NULL, too (see previous conditional).
736 * If the keycodes differ, it can't be a duplicate. */
737 if (a->symbol != NULL &&
738 strcasecmp(a->symbol, b->symbol) != 0) {
739 return false;
740 }
741
742 /* Check if the keycodes or modifiers are different. If so, they
743 * can't be duplicate */
744 if (a->keycode != b->keycode ||
745 a->event_state_mask != b->event_state_mask ||
746 a->release != b->release) {
747 return false;
748 }
749
750 return true;
751 }
752
753 /*
720754 * Checks for duplicate key bindings (the same keycode or keysym is configured
721755 * more than once). If a duplicate binding is found, a message is printed to
722756 * stderr and the has_errors variable is set to true, which will start
729763 TAILQ_FOREACH (bind, bindings, bindings) {
730764 /* Abort when we reach the current keybinding, only check the
731765 * bindings before */
732 if (bind == current)
766 if (bind == current) {
733767 break;
734
735 /* Check if the input types are different */
736 if (bind->input_type != current->input_type)
768 }
769
770 if (!binding_same_key(bind, current)) {
737771 continue;
738
739 /* Check if one is using keysym while the other is using bindsym.
740 * If so, skip. */
741 if ((bind->symbol == NULL && current->symbol != NULL) ||
742 (bind->symbol != NULL && current->symbol == NULL))
743 continue;
744
745 /* If bind is NULL, current has to be NULL, too (see above).
746 * If the keycodes differ, it can't be a duplicate. */
747 if (bind->symbol != NULL &&
748 strcasecmp(bind->symbol, current->symbol) != 0)
749 continue;
750
751 /* Check if the keycodes or modifiers are different. If so, they
752 * can't be duplicate */
753 if (bind->keycode != current->keycode ||
754 bind->event_state_mask != current->event_state_mask ||
755 bind->release != current->release)
756 continue;
772 }
757773
758774 context->has_errors = true;
759775 if (current->keycode != 0) {
7979 */
8080 static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event) {
8181 /* The client is in tiling layout. We can still initiate a resize with the
82 * right mouse button, by chosing the border which is the most near one to
82 * right mouse button, by choosing the border which is the most near one to
8383 * the position of the mouse pointer */
8484 int to_right = con->rect.width - event->event_x,
8585 to_left = event->event_x,
140140 return false;
141141 }
142142
143 static void allow_replay_pointer(xcb_timestamp_t time) {
144 xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, time);
145 xcb_flush(conn);
146 tree_render();
147 }
148
143149 /*
144150 * Being called by handle_button_press, this function calls the appropriate
145151 * functions for resizing/dragging.
151157 DLOG("type = %d, name = %s\n", con->type, con->name);
152158
153159 /* don’t handle dockarea cons, they must not be focused */
154 if (con->parent->type == CT_DOCKAREA)
155 goto done;
160 if (con->parent->type == CT_DOCKAREA) {
161 allow_replay_pointer(event->time);
162 return;
163 }
156164
157165 /* if the user has bound an action to this click, it should override the
158166 * default behavior. */
172180
173181 /* There is no default behavior for button release events so we are done. */
174182 if (event->response_type == XCB_BUTTON_RELEASE) {
175 goto done;
183 allow_replay_pointer(event->time);
184 return;
176185 }
177186
178187 /* Any click in a workspace should focus that workspace. If the
183192
184193 if (!ws) {
185194 ws = TAILQ_FIRST(&(output_get_content(con_get_output(con))->focus_head));
186 if (!ws)
187 goto done;
188 }
189
190 if (ws != focused_workspace)
191 workspace_show(ws);
195 if (!ws) {
196 allow_replay_pointer(event->time);
197 return;
198 }
199 }
192200
193201 /* get the floating con */
194202 Con *floatingcon = con_inside_floating(con);
214222 Con *next = get_tree_next_sibling(current, direction);
215223 con_activate(con_descend_focused(next ? next : current));
216224
217 goto done;
218 }
219
220 /* 2: focus this con. */
221 con_activate(con);
222
223 /* 3: For floating containers, we also want to raise them on click.
225 allow_replay_pointer(event->time);
226 return;
227 }
228
229 /* 2: floating modifier pressed, initiate a drag */
230 if (mod_pressed && is_left_click && !floatingcon &&
231 (config.tiling_drag == TILING_DRAG_MODIFIER ||
232 config.tiling_drag == TILING_DRAG_MODIFIER_OR_TITLEBAR) &&
233 has_drop_targets()) {
234 const bool use_threshold = !mod_pressed;
235 tiling_drag(con, event, use_threshold);
236 allow_replay_pointer(event->time);
237 return;
238 }
239
240 /* 3: focus this con or one of its children. */
241 Con *con_to_focus = con;
242 if (in_stacked && dest == CLICK_DECORATION) {
243 /* If the container is a tab/stacked container and the click happened
244 * on a tab, switch to the tab. If the tab contents were already
245 * focused, focus the tab container itself. If the tab container was
246 * already focused, cycle back to focusing the tab contents. */
247 if (was_focused || !con_has_parent(focused, con)) {
248 while (!TAILQ_EMPTY(&(con_to_focus->focus_head))) {
249 con_to_focus = TAILQ_FIRST(&(con_to_focus->focus_head));
250 }
251 }
252 }
253 if (ws != focused_workspace) {
254 workspace_show(ws);
255 }
256 con_activate(con_to_focus);
257
258 /* 4: For floating containers, we also want to raise them on click.
224259 * We will skip handling events on floating cons in fullscreen mode */
225260 Con *fs = con_get_fullscreen_covering_ws(ws);
226261 if (floatingcon != NULL && fs != con) {
227 /* 4: floating_modifier plus left mouse button drags */
262 /* 5: floating_modifier plus left mouse button drags */
228263 if (mod_pressed && is_left_click) {
229264 floating_drag_window(floatingcon, event, false);
230265 return;
231266 }
232267
233 /* 5: resize (floating) if this was a (left or right) click on the
268 /* 6: resize (floating) if this was a (left or right) click on the
234269 * left/right/bottom border, or a right click on the decoration.
235270 * also try resizing (tiling) if possible */
236271 if (mod_pressed && is_right_click) {
243278 is_left_or_right_click) {
244279 /* try tiling resize, but continue if it doesn’t work */
245280 DLOG("tiling resize with fallback\n");
246 if (tiling_resize(con, event, dest, dest == CLICK_DECORATION && !was_focused))
247 goto done;
281 if (tiling_resize(con, event, dest, dest == CLICK_DECORATION && !was_focused)) {
282 allow_replay_pointer(event->time);
283 return;
284 }
248285 }
249286
250287 if (dest == CLICK_DECORATION && is_right_click) {
259296 return;
260297 }
261298
262 /* 6: dragging, if this was a click on a decoration (which did not lead
299 /* 7: dragging, if this was a click on a decoration (which did not lead
263300 * to a resize) */
264301 if (dest == CLICK_DECORATION && is_left_click) {
265302 floating_drag_window(floatingcon, event, !was_focused);
266303 return;
267304 }
268305
269 goto done;
270 }
271
272 /* 7: floating modifier pressed, initiate a resize */
306 allow_replay_pointer(event->time);
307 return;
308 }
309
310 /* 8: floating modifier pressed, or click in titlebar, initiate a drag */
311 if (is_left_click &&
312 ((config.tiling_drag == TILING_DRAG_TITLEBAR && dest == CLICK_DECORATION) ||
313 (config.tiling_drag == TILING_DRAG_MODIFIER_OR_TITLEBAR &&
314 (mod_pressed || dest == CLICK_DECORATION))) &&
315 has_drop_targets()) {
316 allow_replay_pointer(event->time);
317 const bool use_threshold = !mod_pressed;
318 tiling_drag(con, event, use_threshold);
319 return;
320 }
321
322 /* 9: floating modifier pressed, initiate a resize */
273323 if (dest == CLICK_INSIDE && mod_pressed && is_right_click) {
274324 if (floating_mod_on_tiled_client(con, event)) {
275325 return;
280330 xcb_flush(conn);
281331 return;
282332 }
283 /* 8: otherwise, check for border/decoration clicks and resize */
333 /* 10: otherwise, check for border/decoration clicks and resize */
284334 if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) &&
285335 is_left_or_right_click) {
286336 DLOG("Trying to resize (tiling)\n");
287337 tiling_resize(con, event, dest, dest == CLICK_DECORATION && !was_focused);
288338 }
289339
290 done:
291 xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
292 xcb_flush(conn);
293 tree_render();
340 allow_replay_pointer(event->time);
294341 }
295342
296343 /*
746746 return;
747747 }
748748
749 /* User changed the border */
750 current->con->max_user_border_style = border_style;
749751 const int con_border_width = border_width_from_style(border_style, border_width, current->con);
750752 con_set_border_style(current->con, border_style, con_border_width);
751753 }
10221024 ysuccess(true);
10231025 }
10241026
1025 /*
1026 * Implementation of 'move [window|container] [to] output <str>'.
1027 *
1028 */
1029 void cmd_move_con_to_output(I3_CMD, const char *name) {
1030 DLOG("Should move window to output \"%s\".\n", name);
1027 typedef struct user_output_name {
1028 char *name;
1029 TAILQ_ENTRY(user_output_name) user_output_names;
1030 } user_output_name;
1031 typedef TAILQ_HEAD(user_output_names_head, user_output_name) user_output_names_head;
1032
1033 static void user_output_names_add(user_output_names_head *list, const char *name) {
1034 if (strcmp(name, "next") == 0) {
1035 /* "next" here works like a wildcard: It "expands" to all available
1036 * outputs. */
1037 Output *output;
1038 TAILQ_FOREACH (output, &outputs, outputs) {
1039 user_output_name *co = scalloc(sizeof(user_output_name), 1);
1040 co->name = sstrdup(output_primary_name(output));
1041 TAILQ_INSERT_TAIL(list, co, user_output_names);
1042 }
1043 return;
1044 }
1045
1046 user_output_name *co = scalloc(sizeof(user_output_name), 1);
1047 co->name = sstrdup(name);
1048 TAILQ_INSERT_TAIL(list, co, user_output_names);
1049 return;
1050 }
1051
1052 static Output *user_output_names_find_next(user_output_names_head *names, Output *current_output) {
1053 Output *target_output = NULL;
1054 user_output_name *uo;
1055 TAILQ_FOREACH (uo, names, user_output_names) {
1056 if (!target_output) {
1057 /* The first available output from the list is used in 2 cases:
1058 * 1. When we must wrap around the user list. For example, if user
1059 * specifies outputs A B C and C is `current_output`.
1060 * 2. When the current output is not in the user list. For example,
1061 * user specifies A B C and D is `current_output`. */
1062 target_output = get_output_from_string(current_output, uo->name);
1063 }
1064 if (strcasecmp(output_primary_name(current_output), uo->name) == 0) {
1065 /* The current output is in the user list */
1066 while (true) {
1067 /* This corrupts the outer loop but it is ok since we are going
1068 * to break anyway. */
1069 uo = TAILQ_NEXT(uo, user_output_names);
1070 if (!uo) {
1071 /* We reached the end of the list. We should use the first
1072 * available output that, if it exists, is already saved in
1073 * target_output. */
1074 break;
1075 }
1076 Output *out = get_output_from_string(current_output, uo->name);
1077 if (out) {
1078 return out;
1079 }
1080 }
1081 break;
1082 }
1083 }
1084 return target_output;
1085 }
1086
1087 static void user_output_names_free(user_output_names_head *names) {
1088 user_output_name *uo;
1089 while (!TAILQ_EMPTY(names)) {
1090 uo = TAILQ_FIRST(names);
1091 free(uo->name);
1092 TAILQ_REMOVE(names, uo, user_output_names);
1093 free(uo);
1094 }
1095 }
1096
1097 /*
1098 * Implementation of 'move [window|container|workspace] [to] output <strings>'.
1099 *
1100 */
1101 void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) {
1102 /* Initialize a data structure that is used to save multiple user-specified
1103 * output names since this function is called multiple types for each
1104 * command call. */
1105 static user_output_names_head names = TAILQ_HEAD_INITIALIZER(names);
1106
1107 if (name) {
1108 user_output_names_add(&names, name);
1109 return;
1110 }
1111
10311112 HANDLE_EMPTY_MATCH;
10321113
1114 if (TAILQ_EMPTY(&names)) {
1115 yerror("At least one output must be specified");
1116 return;
1117 }
1118
1119 bool success = false;
10331120 owindow *current;
1034 bool had_error = false;
10351121 TAILQ_FOREACH (current, &owindows, owindows) {
1036 DLOG("matching: %p / %s\n", current->con, current->con->name);
1037
1038 had_error |= !con_move_to_output_name(current->con, name, true);
1039 }
1040
1041 cmd_output->needs_tree_render = true;
1042 ysuccess(!had_error);
1122 Con *ws = con_get_workspace(current->con);
1123 if (con_is_internal(ws)) {
1124 continue;
1125 }
1126
1127 Output *current_output = get_output_for_con(ws);
1128 Output *target_output = user_output_names_find_next(&names, current_output);
1129 if (target_output) {
1130 if (move_workspace) {
1131 workspace_move_to_output(ws, target_output);
1132 } else {
1133 con_move_to_output(current->con, target_output, true);
1134 }
1135 success = true;
1136 }
1137 }
1138 user_output_names_free(&names);
1139
1140 cmd_output->needs_tree_render = success;
1141 if (success) {
1142 ysuccess(true);
1143 } else {
1144 yerror("No output matched");
1145 }
10431146 }
10441147
10451148 /*
10901193
10911194 cmd_output->needs_tree_render = true;
10921195 // XXX: default reply for now, make this a better reply
1093 ysuccess(true);
1094 }
1095
1096 /*
1097 * Implementation of 'move workspace to [output] <str>'.
1098 *
1099 */
1100 void cmd_move_workspace_to_output(I3_CMD, const char *name) {
1101 DLOG("should move workspace to output %s\n", name);
1102
1103 HANDLE_EMPTY_MATCH;
1104
1105 owindow *current;
1106 TAILQ_FOREACH (current, &owindows, owindows) {
1107 Con *ws = con_get_workspace(current->con);
1108 if (con_is_internal(ws)) {
1109 continue;
1110 }
1111
1112 Output *current_output = get_output_for_con(ws);
1113 Output *target_output = get_output_from_string(current_output, name);
1114 if (!target_output) {
1115 yerror("Could not get output from string \"%s\"", name);
1116 return;
1117 }
1118
1119 workspace_move_to_output(ws, target_output);
1120 }
1121
1122 cmd_output->needs_tree_render = true;
11231196 ysuccess(true);
11241197 }
11251198
16601733 }
16611734 ipc_shutdown(SHUTDOWN_REASON_RESTART, exempt_fd);
16621735 unlink(config.ipc_socket_path);
1736 if (current_log_stream_socket_path != NULL) {
1737 unlink(current_log_stream_socket_path);
1738 }
16631739 /* We need to call this manually since atexit handlers don’t get called
16641740 * when exec()ing */
16651741 purge_zerobyte_logfile();
16931769 *
16941770 */
16951771 void cmd_focus_output(I3_CMD, const char *name) {
1772 static user_output_names_head names = TAILQ_HEAD_INITIALIZER(names);
1773 if (name) {
1774 user_output_names_add(&names, name);
1775 return;
1776 }
1777
1778 if (TAILQ_EMPTY(&names)) {
1779 yerror("At least one output must be specified");
1780 return;
1781 }
1782
16961783 HANDLE_EMPTY_MATCH;
16971784
16981785 if (TAILQ_EMPTY(&owindows)) {
17011788 }
17021789
17031790 Output *current_output = get_output_for_con(TAILQ_FIRST(&owindows)->con);
1704 Output *output = get_output_from_string(current_output, name);
1705
1706 if (!output) {
1707 yerror("Output %s not found.", name);
1708 return;
1709 }
1710
1711 /* get visible workspace on output */
1712 Con *ws = NULL;
1713 GREP_FIRST(ws, output_get_content(output->con), workspace_is_visible(child));
1714 if (!ws) {
1715 yerror("BUG: No workspace found on output.");
1716 return;
1717 }
1718
1719 workspace_show(ws);
1720
1721 cmd_output->needs_tree_render = true;
1722 ysuccess(true);
1791 Output *target_output = user_output_names_find_next(&names, current_output);
1792 user_output_names_free(&names);
1793 bool success = false;
1794 if (target_output) {
1795 success = true;
1796
1797 /* get visible workspace on output */
1798 Con *ws = NULL;
1799 GREP_FIRST(ws, output_get_content(target_output->con), workspace_is_visible(child));
1800 if (!ws) {
1801 yerror("BUG: No workspace found on output.");
1802 return;
1803 }
1804
1805 workspace_show(ws);
1806 }
1807
1808 cmd_output->needs_tree_render = success;
1809 if (success) {
1810 ysuccess(true);
1811 } else {
1812 yerror("No output matched");
1813 }
17231814 }
17241815
17251816 /*
19592050 ewmh_update_visible_name(current->con->window->id, NULL);
19602051 }
19612052 }
2053
2054 if (current->con->window != NULL) {
2055 /* Make sure the window title is redrawn immediately. */
2056 current->con->window->name_x_changed = true;
2057 } else {
2058 /* For windowless containers we also need to force the redrawing. */
2059 FREE(current->con->deco_render_params);
2060 }
2061 }
2062
2063 cmd_output->needs_tree_render = true;
2064 ysuccess(true);
2065 }
2066
2067 /*
2068 * Implementation of 'title_window_icon <yes|no|toggle>' and 'title_window_icon padding <px>'
2069 *
2070 */
2071 void cmd_title_window_icon(I3_CMD, const char *enable, int padding) {
2072 bool is_toggle = false;
2073 if (enable != NULL) {
2074 if (strcmp(enable, "toggle") == 0) {
2075 is_toggle = true;
2076 } else if (!boolstr(enable)) {
2077 padding = -1;
2078 }
2079 }
2080 DLOG("setting window_icon=%d\n", padding);
2081 HANDLE_EMPTY_MATCH;
2082
2083 owindow *current;
2084 TAILQ_FOREACH (current, &owindows, owindows) {
2085 if (is_toggle) {
2086 const int current_padding = current->con->window_icon_padding;
2087 if (padding > 0) {
2088 if (current_padding < 0) {
2089 current->con->window_icon_padding = padding;
2090 } else {
2091 /* toggle off, but store padding given */
2092 current->con->window_icon_padding = -(padding + 1);
2093 }
2094 } else {
2095 /* Set to negative of (current value+1) to keep old padding when toggling */
2096 current->con->window_icon_padding = -(current_padding + 1);
2097 }
2098 } else {
2099 current->con->window_icon_padding = padding;
2100 }
2101 DLOG("Set window_icon for %p / %s to %d\n", current->con, current->con->name, current->con->window_icon_padding);
19622102
19632103 if (current->con->window != NULL) {
19642104 /* Make sure the window title is redrawn immediately. */
5555
5656 #include "GENERATED_command_tokens.h"
5757
58 /*******************************************************************************
59 * The (small) stack where identified literals are stored during the parsing
60 * of a single command (like $workspace).
61 ******************************************************************************/
62
63 struct stack_entry {
64 /* Just a pointer, not dynamically allocated. */
65 const char *identifier;
66 enum {
67 STACK_STR = 0,
68 STACK_LONG = 1,
69 } type;
70 union {
71 char *str;
72 long num;
73 } val;
74 };
75
76 /* 10 entries should be enough for everybody. */
77 static struct stack_entry stack[10];
78
7958 /*
8059 * Pushes a string (identified by 'identifier') on the stack. We simply use a
8160 * single array, since the number of entries we have to store is very small.
8261 *
8362 */
84 static void push_string(const char *identifier, char *str) {
63 static void push_string(struct stack *stack, const char *identifier, char *str) {
8564 for (int c = 0; c < 10; c++) {
86 if (stack[c].identifier != NULL)
65 if (stack->stack[c].identifier != NULL)
8766 continue;
8867 /* Found a free slot, let’s store it here. */
89 stack[c].identifier = identifier;
90 stack[c].val.str = str;
91 stack[c].type = STACK_STR;
68 stack->stack[c].identifier = identifier;
69 stack->stack[c].val.str = str;
70 stack->stack[c].type = STACK_STR;
9271 return;
9372 }
9473
10281 }
10382
10483 // TODO move to a common util
105 static void push_long(const char *identifier, long num) {
84 static void push_long(struct stack *stack, const char *identifier, long num) {
10685 for (int c = 0; c < 10; c++) {
107 if (stack[c].identifier != NULL) {
86 if (stack->stack[c].identifier != NULL) {
10887 continue;
10988 }
11089
111 stack[c].identifier = identifier;
112 stack[c].val.num = num;
113 stack[c].type = STACK_LONG;
90 stack->stack[c].identifier = identifier;
91 stack->stack[c].val.num = num;
92 stack->stack[c].type = STACK_LONG;
11493 return;
11594 }
11695
124103 }
125104
126105 // TODO move to a common util
127 static const char *get_string(const char *identifier) {
106 static const char *get_string(struct stack *stack, const char *identifier) {
128107 for (int c = 0; c < 10; c++) {
129 if (stack[c].identifier == NULL)
108 if (stack->stack[c].identifier == NULL)
130109 break;
131 if (strcmp(identifier, stack[c].identifier) == 0)
132 return stack[c].val.str;
110 if (strcmp(identifier, stack->stack[c].identifier) == 0)
111 return stack->stack[c].val.str;
133112 }
134113 return NULL;
135114 }
136115
137116 // TODO move to a common util
138 static long get_long(const char *identifier) {
117 static long get_long(struct stack *stack, const char *identifier) {
139118 for (int c = 0; c < 10; c++) {
140 if (stack[c].identifier == NULL)
119 if (stack->stack[c].identifier == NULL)
141120 break;
142 if (strcmp(identifier, stack[c].identifier) == 0)
143 return stack[c].val.num;
121 if (strcmp(identifier, stack->stack[c].identifier) == 0)
122 return stack->stack[c].val.num;
144123 }
145124
146125 return 0;
147126 }
148127
149128 // TODO move to a common util
150 static void clear_stack(void) {
129 static void clear_stack(struct stack *stack) {
151130 for (int c = 0; c < 10; c++) {
152 if (stack[c].type == STACK_STR)
153 free(stack[c].val.str);
154 stack[c].identifier = NULL;
155 stack[c].val.str = NULL;
156 stack[c].val.num = 0;
131 if (stack->stack[c].type == STACK_STR)
132 free(stack->stack[c].val.str);
133 stack->stack[c].identifier = NULL;
134 stack->stack[c].val.str = NULL;
135 stack->stack[c].val.num = 0;
157136 }
158137 }
159138
162141 ******************************************************************************/
163142
164143 static cmdp_state state;
165 #ifndef TEST_PARSER
166144 static Match current_match;
167 #endif
145 /*******************************************************************************
146 * The (small) stack where identified literals are stored during the parsing
147 * of a single command (like $workspace).
148 ******************************************************************************/
149 static struct stack stack;
168150 static struct CommandResultIR subcommand_output;
169151 static struct CommandResultIR command_output;
170152
175157 subcommand_output.json_gen = command_output.json_gen;
176158 subcommand_output.client = command_output.client;
177159 subcommand_output.needs_tree_render = false;
178 GENERATED_call(token->extra.call_identifier, &subcommand_output);
160 GENERATED_call(&current_match, &stack, token->extra.call_identifier, &subcommand_output);
179161 state = subcommand_output.next_state;
180162 /* If any subcommand requires a tree_render(), we need to make the
181163 * whole parser result request a tree_render(). */
182164 if (subcommand_output.needs_tree_render)
183165 command_output.needs_tree_render = true;
184 clear_stack();
166 clear_stack(&stack);
185167 return;
186168 }
187169
188170 state = token->next_state;
189171 if (state == INITIAL) {
190 clear_stack();
172 clear_stack(&stack);
191173 }
192174 }
193175
295277 /* A literal. */
296278 if (token->name[0] == '\'') {
297279 if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
298 if (token->identifier != NULL)
299 push_string(token->identifier, sstrdup(token->name + 1));
280 if (token->identifier != NULL) {
281 push_string(&stack, token->identifier, sstrdup(token->name + 1));
282 }
300283 walk += strlen(token->name) - 1;
301284 next_state(token);
302285 token_handled = true;
318301 if (end == walk)
319302 continue;
320303
321 if (token->identifier != NULL)
322 push_long(token->identifier, num);
304 if (token->identifier != NULL) {
305 push_long(&stack, token->identifier, num);
306 }
323307
324308 /* Set walk to the first non-number character */
325309 walk = end;
332316 strcmp(token->name, "word") == 0) {
333317 char *str = parse_string(&walk, (token->name[0] != 's'));
334318 if (str != NULL) {
335 if (token->identifier)
336 push_string(token->identifier, str);
319 if (token->identifier) {
320 push_string(&stack, token->identifier, str);
321 }
337322 /* If we are at the end of a quoted string, skip the ending
338323 * double quote. */
339324 if (*walk == '"')
404389 free(possible_tokens);
405390
406391 /* Contains the same amount of characters as 'input' has, but with
407 * the unparseable part highlighted using ^ characters. */
392 * the unparsable part highlighted using ^ characters. */
408393 char *position = smalloc(len + 1);
409394 for (const char *copywalk = input; *copywalk != '\0'; copywalk++)
410395 position[(copywalk - input)] = (copywalk >= walk ? '^' : ' ');
435420 y(map_close);
436421
437422 free(position);
438 clear_stack();
423 clear_stack(&stack);
439424 break;
440425 }
441426 }
4040 TAILQ_INSERT_TAIL(&all_cons, new, all_cons);
4141 new->type = CT_CON;
4242 new->window = window;
43 new->border_style = config.default_border;
43 new->border_style = new->max_user_border_style = config.default_border;
4444 new->current_border_width = -1;
45 new->window_icon_padding = -1;
4546 if (window) {
4647 new->depth = window->depth;
4748 } else {
729730 }
730731
731732 /*
733 * Start from a container and traverse the transient_for linked list. Returns
734 * true if target window is found in the list. Protects againsts potential
735 * cycles.
736 *
737 */
738 bool con_find_transient_for_window(Con *start, xcb_window_t target) {
739 Con *transient_con = start;
740 int count = con_num_windows(croot);
741 while (transient_con != NULL &&
742 transient_con->window != NULL &&
743 transient_con->window->transient_for != XCB_NONE) {
744 DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, target = 0x%08x\n",
745 transient_con->window->id, transient_con->window->transient_for, target);
746 if (transient_con->window->transient_for == target) {
747 return true;
748 }
749 Con *next_transient = con_by_window_id(transient_con->window->transient_for);
750 if (next_transient == NULL) {
751 break;
752 }
753 /* Some clients (e.g. x11-ssh-askpass) actually set WM_TRANSIENT_FOR to
754 * their own window id, so break instead of looping endlessly. */
755 if (transient_con == next_transient) {
756 break;
757 }
758 transient_con = next_transient;
759
760 if (count-- <= 0) { /* Avoid cycles, see #4404 */
761 break;
762 }
763 }
764 return false;
765 }
766
767 /*
732768 * Returns true if and only if the given containers holds the mark.
733769 *
734770 */
850886 Con *con_for_window(Con *con, i3Window *window, Match **store_match) {
851887 Con *child;
852888 Match *match;
853 //DLOG("searching con for window %p starting at con %p\n", window, con);
854 //DLOG("class == %s\n", window->class_class);
855889
856890 TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
857891 TAILQ_FOREACH (match, &(child->swallow_head), matches) {
10101044 Con *child;
10111045 int children = con_num_children(con);
10121046
1013 // calculate how much we have distributed and how many containers
1014 // with a percentage set we have
1047 /* calculate how much we have distributed and how many containers with a
1048 * percentage set we have */
10151049 double total = 0.0;
10161050 int children_with_percent = 0;
10171051 TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
10211055 }
10221056 }
10231057
1024 // if there were children without a percentage set, set to a value that
1025 // will make those children proportional to all others
1058 /* if there were children without a percentage set, set to a value that
1059 * will make those children proportional to all others */
10261060 if (children_with_percent != children) {
10271061 TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
10281062 if (child->percent <= 0.0) {
10351069 }
10361070 }
10371071
1038 // if we got a zero, just distribute the space equally, otherwise
1039 // distribute according to the proportions we got
1072 /* if we got a zero, just distribute the space equally, otherwise
1073 * distribute according to the proportions we got */
10401074 if (total == 0.0) {
10411075 TAILQ_FOREACH (child, &(con->nodes_head), nodes) {
10421076 child->percent = 1.0 / children;
13541388 return true;
13551389 }
13561390
1357 /*
1358 * Moves the given container to the given mark.
1359 *
1360 */
1361 bool con_move_to_mark(Con *con, const char *mark) {
1362 Con *target = con_by_mark(mark);
1363 if (target == NULL) {
1364 DLOG("found no container with mark \"%s\"\n", mark);
1365 return false;
1366 }
1367
1391 bool con_move_to_target(Con *con, Con *target) {
13681392 /* For target containers in the scratchpad, we just send the window to the scratchpad. */
13691393 if (con_get_workspace(target) == workspace_get("__i3_scratch")) {
13701394 DLOG("target container is in the scratchpad, moving container to scratchpad.\n");
13991423 }
14001424
14011425 return _con_move_to_con(con, target, false, true, false, false, true);
1426 }
1427
1428 /*
1429 * Moves the given container to the given mark.
1430 *
1431 */
1432 bool con_move_to_mark(Con *con, const char *mark) {
1433 Con *target = con_by_mark(mark);
1434 if (target == NULL) {
1435 DLOG("found no container with mark \"%s\"\n", mark);
1436 return false;
1437 }
1438
1439 return con_move_to_target(con, target);
14021440 }
14031441
14041442 /*
17701808 * floating window.
17711809 *
17721810 */
1773 void con_set_border_style(Con *con, int border_style, int border_width) {
1811 void con_set_border_style(Con *con, border_style_t border_style, int border_width) {
1812 if (border_style > con->max_user_border_style) {
1813 border_style = con->max_user_border_style;
1814 }
1815
17741816 /* Handle the simple case: non-floating containerns */
17751817 if (!con_is_floating(con)) {
17761818 con->border_style = border_style;
17831825 * con->rect represent the absolute position of the window (same for
17841826 * parent). Then, we change the border style and subtract the new border
17851827 * pixels. For the parent, we do the same also for the decoration. */
1786 DLOG("This is a floating container\n");
1787
17881828 Con *parent = con->parent;
17891829 Rect bsr = con_border_style_rect(con);
17901830 int deco_height = (con->border_style == BS_NORMAL ? render_deco_height() : 0);
18691909 con_attach(new, con, false);
18701910
18711911 tree_flatten(croot);
1872 }
1873 con_force_split_parents_redraw(con);
1874 return;
1912 con_force_split_parents_redraw(con);
1913 return;
1914 }
18751915 }
18761916
18771917 if (layout == L_DEFAULT) {
22152255 } else
22162256 DLOG("Discarding urgency WM_HINT because timer is running\n");
22172257
2218 //CLIENT_LOG(con);
22192258 if (con->window) {
22202259 if (con->urgent) {
22212260 gettimeofday(&con->window->urgent, NULL);
23572396 char *title;
23582397 char *class;
23592398 char *instance;
2399 char *machine;
23602400 if (win == NULL) {
23612401 title = pango_escape_markup(con_get_tree_representation(con));
23622402 class = sstrdup("i3-frame");
23632403 instance = sstrdup("i3-frame");
2404 machine = sstrdup("");
23642405 } else {
23652406 title = pango_escape_markup(sstrdup((win->name == NULL) ? "" : i3string_as_utf8(win->name)));
23662407 class = pango_escape_markup(sstrdup((win->class_class == NULL) ? "" : win->class_class));
23672408 instance = pango_escape_markup(sstrdup((win->class_instance == NULL) ? "" : win->class_instance));
2409 machine = pango_escape_markup(sstrdup((win->machine == NULL) ? "" : win->machine));
23682410 }
23692411
23702412 placeholder_t placeholders[] = {
23712413 {.name = "%title", .value = title},
23722414 {.name = "%class", .value = class},
2373 {.name = "%instance", .value = instance}};
2415 {.name = "%instance", .value = instance},
2416 {.name = "%machine", .value = machine},
2417 };
23742418 const size_t num = sizeof(placeholders) / sizeof(placeholder_t);
23752419
23762420 char *formatted_str = format_placeholders(con->title_format, &placeholders[0], num);
99 */
1010 #include "all.h"
1111
12 #include <libgen.h>
13 #include <unistd.h>
14
1215 #include <xkbcommon/xkbcommon.h>
1316
1417 char *current_configpath = NULL;
15 char *current_config = NULL;
1618 Config config;
1719 struct modes_head modes;
1820 struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs);
21 struct includedfiles_head included_files = TAILQ_HEAD_INITIALIZER(included_files);
1922
2023 /*
2124 * Ungrabs all keys, to be called before re-grabbing the keys because of a
193196 INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff", "#484e50");
194197 INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888", "#292d2e");
195198 INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff", "#900000");
199 config.client.got_focused_tab_title = false;
196200
197201 /* border and indicator color are ignored for placeholder contents */
198202 INIT_COLOR(config.client.placeholder, "#000000", "#0c0c0c", "#ffffff", "#000000");
223227
224228 config.focus_wrapping = FOCUS_WRAPPING_ON;
225229
230 config.tiling_drag = TILING_DRAG_MODIFIER;
231
226232 FREE(current_configpath);
227233 current_configpath = get_config_path(override_configpath, true);
228234 if (current_configpath == NULL) {
230236 "$XDG_CONFIG_HOME/i3/config, ~/.i3/config, $XDG_CONFIG_DIRS/i3/config "
231237 "and " SYSCONFDIR "/i3/config)");
232238 }
233 LOG("Parsing configfile %s\n", current_configpath);
234 const bool result = parse_file(current_configpath, load_type != C_VALIDATE);
239
240 IncludedFile *file;
241 while (!TAILQ_EMPTY(&included_files)) {
242 file = TAILQ_FIRST(&included_files);
243 FREE(file->path);
244 FREE(file->raw_contents);
245 FREE(file->variable_replaced_contents);
246 TAILQ_REMOVE(&included_files, file, files);
247 FREE(file);
248 }
249
250 char resolved_path[PATH_MAX] = {'\0'};
251 if (realpath(current_configpath, resolved_path) == NULL) {
252 die("realpath(%s): %s", current_configpath, strerror(errno));
253 }
254
255 file = scalloc(1, sizeof(IncludedFile));
256 file->path = sstrdup(resolved_path);
257 TAILQ_INSERT_TAIL(&included_files, file, files);
258
259 LOG("Parsing configfile %s\n", resolved_path);
260 struct stack stack;
261 memset(&stack, '\0', sizeof(struct stack));
262 struct parser_ctx ctx = {
263 .use_nagbar = (load_type != C_VALIDATE),
264 .assume_v4 = false,
265 .stack = &stack,
266 };
267 SLIST_INIT(&(ctx.variables));
268 const int result = parse_file(&ctx, resolved_path, file);
269 free_variables(&ctx);
270 if (result == -1) {
271 die("Could not open configuration file: %s\n", strerror(errno));
272 }
273
274 extract_workspace_names_from_bindings();
275 reorder_bindings();
235276
236277 if (config.font.type == FONT_TYPE_NONE && load_type != C_VALIDATE) {
237278 ELOG("You did not specify required configuration option \"font\"\n");
239280 set_font(&config.font);
240281 }
241282
283 /* Make bar config blocks without a configured font use the i3-wide font. */
284 Barconfig *current;
285 if (load_type != C_VALIDATE) {
286 TAILQ_FOREACH (current, &barconfigs, configs) {
287 if (current->font != NULL) {
288 continue;
289 }
290 current->font = sstrdup(config.font.pattern);
291 }
292 }
293
242294 if (load_type == C_RELOAD) {
243295 translate_keysyms();
244296 grab_all_keys(conn);
250302 xcb_flush(conn);
251303 }
252304
253 return result;
305 return result == 0;
254306 }
88 */
99 #include "all.h"
1010
11 #include <wordexp.h>
12
13 /*******************************************************************************
14 * Include functions.
15 ******************************************************************************/
16
17 CFGFUN(include, const char *pattern) {
18 DLOG("include %s\n", pattern);
19
20 wordexp_t p;
21 const int ret = wordexp(pattern, &p, 0);
22 if (ret != 0) {
23 ELOG("wordexp(%s): error %d\n", pattern, ret);
24 result->has_errors = true;
25 return;
26 }
27 char **w = p.we_wordv;
28 for (size_t i = 0; i < p.we_wordc; i++) {
29 char resolved_path[PATH_MAX] = {'\0'};
30 if (realpath(w[i], resolved_path) == NULL) {
31 LOG("Skipping %s: %s\n", w[i], strerror(errno));
32 continue;
33 }
34
35 bool skip = false;
36 IncludedFile *file;
37 TAILQ_FOREACH (file, &included_files, files) {
38 if (strcmp(file->path, resolved_path) == 0) {
39 skip = true;
40 break;
41 }
42 }
43 if (skip) {
44 LOG("Skipping file %s (already included)\n", resolved_path);
45 continue;
46 }
47
48 LOG("Including config file %s\n", resolved_path);
49
50 file = scalloc(1, sizeof(IncludedFile));
51 file->path = sstrdup(resolved_path);
52 TAILQ_INSERT_TAIL(&included_files, file, files);
53
54 struct stack stack;
55 memset(&stack, '\0', sizeof(struct stack));
56 struct parser_ctx ctx = {
57 .use_nagbar = result->ctx->use_nagbar,
58 /* The include mechanism was added in v4, so we can skip the
59 * auto-detection and get rid of the risk of detecting the wrong
60 * version in potentially very short include fragments: */
61 .assume_v4 = true,
62 .stack = &stack,
63 .variables = result->ctx->variables,
64 };
65 switch (parse_file(&ctx, resolved_path, file)) {
66 case PARSE_FILE_SUCCESS:
67 break;
68
69 case PARSE_FILE_FAILED:
70 ELOG("including config file %s: %s\n", resolved_path, strerror(errno));
71 /* fallthrough */
72
73 case PARSE_FILE_CONFIG_ERRORS:
74 result->has_errors = true;
75 TAILQ_REMOVE(&included_files, file, files);
76 FREE(file->path);
77 FREE(file->raw_contents);
78 FREE(file->variable_replaced_contents);
79 FREE(file);
80 break;
81
82 default:
83 /* missing case statement */
84 assert(false);
85 break;
86 }
87 }
88 wordfree(&p);
89 }
90
1191 /*******************************************************************************
1292 * Criteria functions.
1393 ******************************************************************************/
43123 /*******************************************************************************
44124 * Utility functions
45125 ******************************************************************************/
46
47 static bool eval_boolstr(const char *str) {
48 return (strcasecmp(str, "1") == 0 ||
49 strcasecmp(str, "yes") == 0 ||
50 strcasecmp(str, "true") == 0 ||
51 strcasecmp(str, "on") == 0 ||
52 strcasecmp(str, "enable") == 0 ||
53 strcasecmp(str, "active") == 0);
54 }
55126
56127 /*
57128 * A utility function to convert a string containing the group and modifiers to
91162 return result;
92163 }
93164
94 static char *font_pattern;
95
96165 CFGFUN(font, const char *font) {
97166 config.font = load_font(font, true);
98167 set_font(&config.font);
99
100 /* Save the font pattern for using it as bar font later on */
101 FREE(font_pattern);
102 font_pattern = sstrdup(font);
103168 }
104169
105170 CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
114179 static bool current_mode_pango_markup;
115180
116181 CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
182 if (current_mode == NULL) {
183 /* When using an invalid mode name, e.g. “default” */
184 return;
185 }
186
117187 configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, current_mode, current_mode_pango_markup);
118188 }
119189
304374 if (!strcmp(enable, "no_gaps"))
305375 config.smart_borders = SMART_BORDERS_NO_GAPS;
306376 else
307 config.smart_borders = eval_boolstr(enable) ? SMART_BORDERS_ON : SMART_BORDERS_OFF;
377 config.smart_borders = boolstr(enable) ? SMART_BORDERS_ON : SMART_BORDERS_OFF;
308378 }
309379
310380 CFGFUN(smart_gaps, const char *enable) {
311381 if (!strcmp(enable, "inverse_outer"))
312382 config.smart_gaps = SMART_GAPS_INVERSE_OUTER;
313383 else
314 config.smart_gaps = eval_boolstr(enable) ? SMART_GAPS_ON : SMART_GAPS_OFF;
384 config.smart_gaps = boolstr(enable) ? SMART_GAPS_ON : SMART_GAPS_OFF;
315385 }
316386
317387 CFGFUN(floating_minimum_size, const long width, const long height) {
392462 config.hide_edge_borders = HEBM_BOTH;
393463 else if (strcmp(borders, "none") == 0)
394464 config.hide_edge_borders = HEBM_NONE;
395 else if (eval_boolstr(borders))
465 else if (boolstr(borders))
396466 config.hide_edge_borders = HEBM_VERTICAL;
397467 else
398468 config.hide_edge_borders = HEBM_NONE;
399469 }
400470
401471 CFGFUN(focus_follows_mouse, const char *value) {
402 config.disable_focus_follows_mouse = !eval_boolstr(value);
472 config.disable_focus_follows_mouse = !boolstr(value);
403473 }
404474
405475 CFGFUN(mouse_warping, const char *value) {
410480 }
411481
412482 CFGFUN(force_xinerama, const char *value) {
413 config.force_xinerama = eval_boolstr(value);
483 config.force_xinerama = boolstr(value);
414484 }
415485
416486 CFGFUN(disable_randr15, const char *value) {
417 config.disable_randr15 = eval_boolstr(value);
487 config.disable_randr15 = boolstr(value);
418488 }
419489
420490 CFGFUN(focus_wrapping, const char *value) {
422492 config.focus_wrapping = FOCUS_WRAPPING_FORCE;
423493 } else if (strcmp(value, "workspace") == 0) {
424494 config.focus_wrapping = FOCUS_WRAPPING_WORKSPACE;
425 } else if (eval_boolstr(value)) {
495 } else if (boolstr(value)) {
426496 config.focus_wrapping = FOCUS_WRAPPING_ON;
427497 } else {
428498 config.focus_wrapping = FOCUS_WRAPPING_OFF;
431501
432502 CFGFUN(force_focus_wrapping, const char *value) {
433503 /* Legacy syntax. */
434 if (eval_boolstr(value)) {
504 if (boolstr(value)) {
435505 config.focus_wrapping = FOCUS_WRAPPING_FORCE;
436506 } else {
437507 /* For "force_focus_wrapping off", don't enable or disable
443513 }
444514
445515 CFGFUN(workspace_back_and_forth, const char *value) {
446 config.workspace_auto_back_and_forth = eval_boolstr(value);
516 config.workspace_auto_back_and_forth = boolstr(value);
447517 }
448518
449519 CFGFUN(fake_outputs, const char *outputs) {
485555 }
486556
487557 CFGFUN(show_marks, const char *value) {
488 config.show_marks = eval_boolstr(value);
558 config.show_marks = boolstr(value);
489559 }
490560
491561 static char *current_workspace = NULL;
553623 }
554624
555625 CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border) {
556 #define APPLY_COLORS(classname) \
557 do { \
558 if (strcmp(colorclass, "client." #classname) == 0) { \
559 config.client.classname.border = draw_util_hex_to_color(border); \
560 config.client.classname.background = draw_util_hex_to_color(background); \
561 config.client.classname.text = draw_util_hex_to_color(text); \
562 if (indicator != NULL) { \
563 config.client.classname.indicator = draw_util_hex_to_color(indicator); \
564 } \
565 if (child_border != NULL) { \
566 config.client.classname.child_border = draw_util_hex_to_color(child_border); \
567 } else { \
568 config.client.classname.child_border = config.client.classname.background; \
569 } \
570 } \
626 #define APPLY_COLORS(classname) \
627 do { \
628 if (strcmp(colorclass, "client." #classname) == 0) { \
629 if (strcmp("focused_tab_title", #classname) == 0) { \
630 config.client.got_focused_tab_title = true; \
631 if (indicator || child_border) { \
632 ELOG("indicator and child_border colors have no effect for client.focused_tab_title\n"); \
633 } \
634 } \
635 config.client.classname.border = draw_util_hex_to_color(border); \
636 config.client.classname.background = draw_util_hex_to_color(background); \
637 config.client.classname.text = draw_util_hex_to_color(text); \
638 if (indicator != NULL) { \
639 config.client.classname.indicator = draw_util_hex_to_color(indicator); \
640 } \
641 if (child_border != NULL) { \
642 config.client.classname.child_border = draw_util_hex_to_color(child_border); \
643 } else { \
644 config.client.classname.child_border = config.client.classname.background; \
645 } \
646 return; \
647 } \
571648 } while (0)
572649
573650 APPLY_COLORS(focused_inactive);
651 APPLY_COLORS(focused_tab_title);
574652 APPLY_COLORS(focused);
575653 APPLY_COLORS(unfocused);
576654 APPLY_COLORS(urgent);
639717 ipc_set_kill_timeout(timeout_ms / 1000.0);
640718 }
641719
720 CFGFUN(tiling_drag, const char *value) {
721 if (strcmp(value, "modifier") == 0) {
722 config.tiling_drag = TILING_DRAG_MODIFIER;
723 } else if (strcmp(value, "titlebar") == 0) {
724 config.tiling_drag = TILING_DRAG_TITLEBAR;
725 } else if (strcmp(value, "modifier,titlebar") == 0 ||
726 strcmp(value, "titlebar,modifier") == 0) {
727 /* Switch the above to strtok() or similar if we ever grow more options */
728 config.tiling_drag = TILING_DRAG_MODIFIER_OR_TITLEBAR;
729 } else {
730 config.tiling_drag = TILING_DRAG_OFF;
731 }
732 }
733
642734 /*******************************************************************************
643735 * Bar configuration (i3bar)
644736 ******************************************************************************/
675767 }
676768
677769 CFGFUN(bar_verbose, const char *verbose) {
678 current_bar->verbose = eval_boolstr(verbose);
770 current_bar->verbose = boolstr(verbose);
679771 }
680772
681773 CFGFUN(bar_height, const long height) {
799891 }
800892
801893 CFGFUN(bar_binding_mode_indicator, const char *value) {
802 current_bar->hide_binding_mode_indicator = !eval_boolstr(value);
894 current_bar->hide_binding_mode_indicator = !boolstr(value);
803895 }
804896
805897 CFGFUN(bar_workspace_buttons, const char *value) {
806 current_bar->hide_workspace_buttons = !eval_boolstr(value);
898 current_bar->hide_workspace_buttons = !boolstr(value);
807899 }
808900
809901 CFGFUN(bar_workspace_min_width, const long width) {
811903 }
812904
813905 CFGFUN(bar_strip_workspace_numbers, const char *value) {
814 current_bar->strip_workspace_numbers = eval_boolstr(value);
906 current_bar->strip_workspace_numbers = boolstr(value);
815907 }
816908
817909 CFGFUN(bar_strip_workspace_name, const char *value) {
818 current_bar->strip_workspace_name = eval_boolstr(value);
910 current_bar->strip_workspace_name = boolstr(value);
819911 }
820912
821913 CFGFUN(bar_start) {
834926
835927 config.number_barconfigs++;
836928
837 /* If no font was explicitly set, we use the i3 font as default */
838 if (current_bar->font == NULL && font_pattern != NULL)
839 current_bar->font = sstrdup(font_pattern);
840
841929 TAILQ_INSERT_TAIL(&barconfigs, current_bar, configs);
842930 /* Simply reset the pointer, but don't free the resources. */
843931 current_bar = NULL;
3434 #include <sys/types.h>
3535 #include <sys/wait.h>
3636 #include <unistd.h>
37 #include <libgen.h>
3738
3839 #include <xcb/xcb_xrm.h>
39
40 // Macros to make the YAJL API a bit easier to use.
41 #define y(x, ...) yajl_gen_##x(command_output.json_gen, ##__VA_ARGS__)
42 #define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char *)str, strlen(str))
4340
4441 xcb_xrm_database_t *database = NULL;
4542
4643 #ifndef TEST_PARSER
4744 pid_t config_error_nagbar_pid = -1;
48 static struct context *context;
4945 #endif
5046
5147 /*******************************************************************************
7571
7672 #include "GENERATED_config_tokens.h"
7773
78 /*******************************************************************************
79 * The (small) stack where identified literals are stored during the parsing
80 * of a single command (like $workspace).
81 ******************************************************************************/
82
83 struct stack_entry {
84 /* Just a pointer, not dynamically allocated. */
85 const char *identifier;
86 enum {
87 STACK_STR = 0,
88 STACK_LONG = 1,
89 } type;
90 union {
91 char *str;
92 long num;
93 } val;
94 };
95
96 /* 10 entries should be enough for everybody. */
97 static struct stack_entry stack[10];
98
9974 /*
10075 * Pushes a string (identified by 'identifier') on the stack. We simply use a
10176 * single array, since the number of entries we have to store is very small.
10277 *
10378 */
104 static void push_string(const char *identifier, const char *str) {
79 static void push_string(struct stack *ctx, const char *identifier, const char *str) {
10580 for (int c = 0; c < 10; c++) {
106 if (stack[c].identifier != NULL &&
107 strcmp(stack[c].identifier, identifier) != 0)
81 if (ctx->stack[c].identifier != NULL &&
82 strcmp(ctx->stack[c].identifier, identifier) != 0)
10883 continue;
109 if (stack[c].identifier == NULL) {
84 if (ctx->stack[c].identifier == NULL) {
11085 /* Found a free slot, let’s store it here. */
111 stack[c].identifier = identifier;
112 stack[c].val.str = sstrdup(str);
113 stack[c].type = STACK_STR;
86 ctx->stack[c].identifier = identifier;
87 ctx->stack[c].val.str = sstrdup(str);
88 ctx->stack[c].type = STACK_STR;
11489 } else {
11590 /* Append the value. */
116 char *prev = stack[c].val.str;
117 sasprintf(&(stack[c].val.str), "%s,%s", prev, str);
91 char *prev = ctx->stack[c].val.str;
92 sasprintf(&(ctx->stack[c].val.str), "%s,%s", prev, str);
11893 free(prev);
11994 }
12095 return;
129104 exit(EXIT_FAILURE);
130105 }
131106
132 static void push_long(const char *identifier, long num) {
107 static void push_long(struct stack *ctx, const char *identifier, long num) {
133108 for (int c = 0; c < 10; c++) {
134 if (stack[c].identifier != NULL)
109 if (ctx->stack[c].identifier != NULL) {
135110 continue;
111 }
136112 /* Found a free slot, let’s store it here. */
137 stack[c].identifier = identifier;
138 stack[c].val.num = num;
139 stack[c].type = STACK_LONG;
113 ctx->stack[c].identifier = identifier;
114 ctx->stack[c].val.num = num;
115 ctx->stack[c].type = STACK_LONG;
140116 return;
141117 }
142118
149125 exit(EXIT_FAILURE);
150126 }
151127
152 static const char *get_string(const char *identifier) {
128 static const char *get_string(struct stack *ctx, const char *identifier) {
153129 for (int c = 0; c < 10; c++) {
154 if (stack[c].identifier == NULL)
130 if (ctx->stack[c].identifier == NULL)
155131 break;
156 if (strcmp(identifier, stack[c].identifier) == 0)
157 return stack[c].val.str;
132 if (strcmp(identifier, ctx->stack[c].identifier) == 0)
133 return ctx->stack[c].val.str;
158134 }
159135 return NULL;
160136 }
161137
162 static long get_long(const char *identifier) {
138 static long get_long(struct stack *ctx, const char *identifier) {
163139 for (int c = 0; c < 10; c++) {
164 if (stack[c].identifier == NULL)
140 if (ctx->stack[c].identifier == NULL)
165141 break;
166 if (strcmp(identifier, stack[c].identifier) == 0)
167 return stack[c].val.num;
142 if (strcmp(identifier, ctx->stack[c].identifier) == 0)
143 return ctx->stack[c].val.num;
168144 }
169145 return 0;
170146 }
171147
172 static void clear_stack(void) {
148 static void clear_stack(struct stack *ctx) {
173149 for (int c = 0; c < 10; c++) {
174 if (stack[c].type == STACK_STR)
175 free(stack[c].val.str);
176 stack[c].identifier = NULL;
177 stack[c].val.str = NULL;
178 stack[c].val.num = 0;
150 if (ctx->stack[c].type == STACK_STR)
151 free(ctx->stack[c].val.str);
152 ctx->stack[c].identifier = NULL;
153 ctx->stack[c].val.str = NULL;
154 ctx->stack[c].val.num = 0;
179155 }
180156 }
181157
183159 * The parser itself.
184160 ******************************************************************************/
185161
186 static cmdp_state state;
187 static Match current_match;
188 static struct ConfigResultIR subcommand_output;
189 static struct ConfigResultIR command_output;
190
191 /* A list which contains the states that lead to the current state, e.g.
192 * INITIAL, WORKSPACE_LAYOUT.
193 * When jumping back to INITIAL, statelist_idx will simply be set to 1
194 * (likewise for other states, e.g. MODE or BAR).
195 * This list is used to process the nearest error token. */
196 static cmdp_state statelist[10] = {INITIAL};
197 /* NB: statelist_idx points to where the next entry will be inserted */
198 static int statelist_idx = 1;
199
200162 #include "GENERATED_config_call.h"
201163
202 static void next_state(const cmdp_token *token) {
164 static void next_state(const cmdp_token *token, struct parser_ctx *ctx) {
203165 cmdp_state _next_state = token->next_state;
204166
205 //printf("token = name %s identifier %s\n", token->name, token->identifier);
206 //printf("next_state = %d\n", token->next_state);
207167 if (token->next_state == __CALL) {
208 subcommand_output.json_gen = command_output.json_gen;
209 GENERATED_call(token->extra.call_identifier, &subcommand_output);
168 struct ConfigResultIR subcommand_output = {
169 .ctx = ctx,
170 };
171 GENERATED_call(&(ctx->current_match), ctx->stack, token->extra.call_identifier, &subcommand_output);
172 if (subcommand_output.has_errors) {
173 ctx->has_errors = true;
174 }
210175 _next_state = subcommand_output.next_state;
211 clear_stack();
212 }
213
214 state = _next_state;
215 if (state == INITIAL) {
216 clear_stack();
176 clear_stack(ctx->stack);
177 }
178
179 ctx->state = _next_state;
180 if (ctx->state == INITIAL) {
181 clear_stack(ctx->stack);
217182 }
218183
219184 /* See if we are jumping back to a state in which we were in previously
220185 * (statelist contains INITIAL) and just move statelist_idx accordingly. */
221 for (int i = 0; i < statelist_idx; i++) {
222 if (statelist[i] != _next_state)
186 for (int i = 0; i < ctx->statelist_idx; i++) {
187 if ((cmdp_state)(ctx->statelist[i]) != _next_state) {
223188 continue;
224 statelist_idx = i + 1;
189 }
190 ctx->statelist_idx = i + 1;
225191 return;
226192 }
227193
228194 /* Otherwise, the state is new and we add it to the list */
229 statelist[statelist_idx++] = _next_state;
195 ctx->statelist[ctx->statelist_idx++] = _next_state;
230196 }
231197
232198 /*
256222 return result;
257223 }
258224
259 struct ConfigResultIR *parse_config(const char *input, struct context *context) {
225 static void parse_config(struct parser_ctx *ctx, const char *input, struct context *context) {
260226 /* Dump the entire config file into the debug log. We cannot just use
261227 * DLOG("%s", input); because one log message must not exceed 4 KiB. */
262228 const char *dumpwalk = input;
272238 }
273239 linecnt++;
274240 }
275 state = INITIAL;
276 statelist_idx = 1;
277
278 /* A YAJL JSON generator used for formatting replies. */
279 command_output.json_gen = yajl_gen_alloc(NULL);
280
281 y(array_open);
241 ctx->state = INITIAL;
242 for (int i = 0; i < 10; i++) {
243 ctx->statelist[i] = INITIAL;
244 }
245 ctx->statelist_idx = 1;
282246
283247 const char *walk = input;
284248 const size_t len = strlen(input);
287251 bool token_handled;
288252 linecnt = 1;
289253
290 // TODO: make this testable
291254 #ifndef TEST_PARSER
292 cfg_criteria_init(&current_match, &subcommand_output, INITIAL);
255 struct ConfigResultIR subcommand_output = {
256 .ctx = ctx,
257 };
258 cfg_criteria_init(&(ctx->current_match), &subcommand_output, INITIAL);
293259 #endif
294260
295261 /* The "<=" operator is intentional: We also handle the terminating 0-byte
300266 while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
301267 walk++;
302268
303 //printf("remaining input: %s\n", walk);
304
305 cmdp_token_ptr *ptr = &(tokens[state]);
269 cmdp_token_ptr *ptr = &(tokens[ctx->state]);
306270 token_handled = false;
307271 for (c = 0; c < ptr->n; c++) {
308272 token = &(ptr->array[c]);
310274 /* A literal. */
311275 if (token->name[0] == '\'') {
312276 if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
313 if (token->identifier != NULL)
314 push_string(token->identifier, token->name + 1);
277 if (token->identifier != NULL) {
278 push_string(ctx->stack, token->identifier, token->name + 1);
279 }
315280 walk += strlen(token->name) - 1;
316 next_state(token);
281 next_state(token, ctx);
317282 token_handled = true;
318283 break;
319284 }
333298 if (end == walk)
334299 continue;
335300
336 if (token->identifier != NULL)
337 push_long(token->identifier, num);
301 if (token->identifier != NULL) {
302 push_long(ctx->stack, token->identifier, num);
303 }
338304
339305 /* Set walk to the first non-number character */
340306 walk = end;
341 next_state(token);
307 next_state(token, ctx);
342308 token_handled = true;
343309 break;
344310 }
381347 inpos++;
382348 str[outpos] = beginning[inpos];
383349 }
384 if (token->identifier)
385 push_string(token->identifier, str);
350 if (token->identifier) {
351 push_string(ctx->stack, token->identifier, str);
352 }
386353 free(str);
387354 /* If we are at the end of a quoted string, skip the ending
388355 * double quote. */
389356 if (*walk == '"')
390357 walk++;
391 next_state(token);
358 next_state(token, ctx);
392359 token_handled = true;
393360 break;
394361 }
397364 if (strcmp(token->name, "line") == 0) {
398365 while (*walk != '\0' && *walk != '\n' && *walk != '\r')
399366 walk++;
400 next_state(token);
367 next_state(token, ctx);
401368 token_handled = true;
402369 linecnt++;
403370 walk++;
405372 }
406373
407374 if (strcmp(token->name, "end") == 0) {
408 //printf("checking for end: *%s*\n", walk);
409375 if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
410 next_state(token);
376 next_state(token, ctx);
411377 token_handled = true;
412378 /* To make sure we start with an appropriate matching
413379 * datastructure for commands which do *not* specify any
414380 * criteria, we re-initialize the criteria system after
415381 * every command. */
416 // TODO: make this testable
417382 #ifndef TEST_PARSER
418 cfg_criteria_init(&current_match, &subcommand_output, INITIAL);
383 cfg_criteria_init(&(ctx->current_match), &subcommand_output, INITIAL);
419384 #endif
420385 linecnt++;
421386 walk++;
472437 const char *error_line = start_of_line(walk, input);
473438
474439 /* Contains the same amount of characters as 'input' has, but with
475 * the unparseable part highlighted using ^ characters. */
440 * the unparsable part highlighted using ^ characters. */
476441 char *position = scalloc(strlen(error_line) + 1, 1);
477442 const char *copywalk;
478443 for (copywalk = error_line;
514479
515480 context->has_errors = true;
516481
517 /* Format this error message as a JSON reply. */
518 y(map_open);
519 ystr("success");
520 y(bool, false);
521 /* We set parse_error to true to distinguish this from other
522 * errors. i3-nagbar is spawned upon keypresses only for parser
523 * errors. */
524 ystr("parse_error");
525 y(bool, true);
526 ystr("error");
527 ystr(errormessage);
528 ystr("input");
529 ystr(input);
530 ystr("errorposition");
531 ystr(position);
532 y(map_close);
533
534482 /* Skip the rest of this line, but continue parsing. */
535483 while ((size_t)(walk - input) <= len && *walk != '\n')
536484 walk++;
537485
538486 free(position);
539487 free(errormessage);
540 clear_stack();
488 clear_stack(ctx->stack);
541489
542490 /* To figure out in which state to go (e.g. MODE or INITIAL),
543491 * we find the nearest state which contains an <error> token
544492 * and follow that one. */
545493 bool error_token_found = false;
546 for (int i = statelist_idx - 1; (i >= 0) && !error_token_found; i--) {
547 cmdp_token_ptr *errptr = &(tokens[statelist[i]]);
494 for (int i = ctx->statelist_idx - 1; (i >= 0) && !error_token_found; i--) {
495 cmdp_token_ptr *errptr = &(tokens[ctx->statelist[i]]);
548496 for (int j = 0; j < errptr->n; j++) {
549497 if (strcmp(errptr->array[j].name, "error") != 0)
550498 continue;
551 next_state(&(errptr->array[j]));
499 next_state(&(errptr->array[j]), ctx);
552500 error_token_found = true;
553501 break;
554502 }
557505 assert(error_token_found);
558506 }
559507 }
560
561 y(array_close);
562
563 return &command_output;
564508 }
565509
566510 /*******************************************************************************
611555 fprintf(stderr, "Syntax: %s <command>\n", argv[0]);
612556 return 1;
613557 }
558 struct stack stack;
559 memset(&stack, '\0', sizeof(struct stack));
560 struct parser_ctx ctx = {
561 .use_nagbar = false,
562 .assume_v4 = false,
563 .stack = &stack,
564 };
565 SLIST_INIT(&(ctx.variables));
614566 struct context context;
615567 context.filename = "<stdin>";
616 parse_config(argv[1], &context);
568 parse_config(&ctx, argv[1], &context);
617569 }
618570
619571 #else
635587
636588 /* check for some v4-only statements */
637589 if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 ||
590 strncasecmp(line, "include", strlen("include")) == 0 ||
638591 strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 ||
639592 strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 ||
640593 strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) {
877830 }
878831
879832 /*
833 * Releases the memory of all variables in ctx.
834 *
835 */
836 void free_variables(struct parser_ctx *ctx) {
837 struct Variable *current;
838 while (!SLIST_EMPTY(&(ctx->variables))) {
839 current = SLIST_FIRST(&(ctx->variables));
840 FREE(current->key);
841 FREE(current->value);
842 SLIST_REMOVE_HEAD(&(ctx->variables), variables);
843 FREE(current);
844 }
845 }
846
847 /*
880848 * Parses the given file by first replacing the variables, then calling
881849 * parse_config and possibly launching i3-nagbar.
882850 *
883851 */
884 bool parse_file(const char *f, bool use_nagbar) {
885 struct variables_head variables = SLIST_HEAD_INITIALIZER(&variables);
852 parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFile *included_file) {
886853 int fd;
887854 struct stat stbuf;
888855 char *buf;
889856 FILE *fstr;
890857 char buffer[4096], key[512], value[4096], *continuation = NULL;
891858
892 if ((fd = open(f, O_RDONLY)) == -1)
893 die("Could not open configuration file: %s\n", strerror(errno));
894
895 if (fstat(fd, &stbuf) == -1)
896 die("Could not fstat file: %s\n", strerror(errno));
859 char *old_dir = getcwd(NULL, 0);
860 char *dir = NULL;
861 /* dirname(3) might modify the buffer, so make a copy: */
862 char *dirbuf = sstrdup(f);
863 if ((dir = dirname(dirbuf)) != NULL) {
864 LOG("Changing working directory to config file directory %s\n", dir);
865 if (chdir(dir) == -1) {
866 ELOG("chdir(%s) failed: %s\n", dir, strerror(errno));
867 return PARSE_FILE_FAILED;
868 }
869 }
870 free(dirbuf);
871
872 if ((fd = open(f, O_RDONLY)) == -1) {
873 return PARSE_FILE_FAILED;
874 }
875
876 if (fstat(fd, &stbuf) == -1) {
877 return PARSE_FILE_FAILED;
878 }
897879
898880 buf = scalloc(stbuf.st_size + 1, 1);
899881
900 if ((fstr = fdopen(fd, "r")) == NULL)
901 die("Could not fdopen: %s\n", strerror(errno));
902
903 FREE(current_config);
904 current_config = scalloc(stbuf.st_size + 1, 1);
905 if ((ssize_t)fread(current_config, 1, stbuf.st_size, fstr) != stbuf.st_size) {
906 die("Could not fread: %s\n", strerror(errno));
882 if ((fstr = fdopen(fd, "r")) == NULL) {
883 return PARSE_FILE_FAILED;
884 }
885
886 included_file->raw_contents = scalloc(stbuf.st_size + 1, 1);
887 if ((ssize_t)fread(included_file->raw_contents, 1, stbuf.st_size, fstr) != stbuf.st_size) {
888 return PARSE_FILE_FAILED;
907889 }
908890 rewind(fstr);
909891
915897 if (fgets(continuation, sizeof(buffer) - (continuation - buffer), fstr) == NULL) {
916898 if (feof(fstr))
917899 break;
918 die("Could not read configuration file\n");
900 return PARSE_FILE_FAILED;
919901 }
920902 if (buffer[strlen(buffer) - 1] != '\n' && !feof(fstr)) {
921903 ELOG("Your line continuation is too long, it exceeds %zd bytes\n", sizeof(buffer));
959941 continue;
960942 }
961943
962 upsert_variable(&variables, v_key, v_value);
944 upsert_variable(&(ctx->variables), v_key, v_value);
963945 continue;
964946 } else if (strcasecmp(key, "set_from_resource") == 0) {
965947 char res_name[512] = {'\0'};
992974 res_value = sstrdup(fallback);
993975 }
994976
995 upsert_variable(&variables, v_key, res_value);
977 upsert_variable(&(ctx->variables), v_key, res_value);
996978 FREE(res_value);
997979 continue;
998980 }
1013995 * variables (otherwise we will count them twice, which is bad when
1014996 * 'extra' is negative) */
1015997 char *bufcopy = sstrdup(buf);
1016 SLIST_FOREACH (current, &variables, variables) {
998 SLIST_FOREACH (current, &(ctx->variables), variables) {
1017999 int extra = (strlen(current->value) - strlen(current->key));
10181000 char *next;
10191001 for (next = bufcopy;
10201002 next < (bufcopy + stbuf.st_size) &&
1021 (next = strcasestr(next, current->key)) != NULL;
1022 next += strlen(current->key)) {
1023 *next = '_';
1003 (next = strcasestr(next, current->key)) != NULL;) {
1004 /* We need to invalidate variables completely (otherwise we may count
1005 * the same variable more than once, thus causing buffer overflow or
1006 * allocation failure) with spaces (variable names cannot contain spaces) */
1007 char *end = next + strlen(current->key);
1008 while (next < end) {
1009 *next++ = ' ';
1010 }
10241011 extra_bytes += extra;
10251012 }
10261013 }
10331020 destwalk = new;
10341021 while (walk < (buf + stbuf.st_size)) {
10351022 /* Find the next variable */
1036 SLIST_FOREACH (current, &variables, variables) {
1023 SLIST_FOREACH (current, &(ctx->variables), variables) {
10371024 current->next_match = strcasestr(walk, current->key);
10381025 }
10391026 nearest = NULL;
10401027 int distance = stbuf.st_size;
1041 SLIST_FOREACH (current, &variables, variables) {
1028 SLIST_FOREACH (current, &(ctx->variables), variables) {
10421029 if (current->next_match == NULL)
10431030 continue;
10441031 if ((current->next_match - walk) < distance) {
10631050
10641051 /* analyze the string to find out whether this is an old config file (3.x)
10651052 * or a new config file (4.x). If it’s old, we run the converter script. */
1066 int version = detect_version(buf);
1053 int version = 4;
1054 if (!ctx->assume_v4) {
1055 version = detect_version(buf);
1056 }
10671057 if (version == 3) {
10681058 /* We need to convert this v3 configuration */
10691059 char *converted = migrate_config(new, strlen(new));
10891079 }
10901080 }
10911081
1092 context = scalloc(1, sizeof(struct context));
1082 included_file->variable_replaced_contents = sstrdup(new);
1083
1084 struct context *context = scalloc(1, sizeof(struct context));
10931085 context->filename = f;
1094
1095 struct ConfigResultIR *config_output = parse_config(new, context);
1096 yajl_gen_free(config_output->json_gen);
1097
1098 extract_workspace_names_from_bindings();
1086 parse_config(ctx, new, context);
1087 if (ctx->has_errors) {
1088 context->has_errors = true;
1089 }
1090
10991091 check_for_duplicate_bindings(context);
1100 reorder_bindings();
1101
1102 if (use_nagbar && (context->has_errors || context->has_warnings || invalid_sets)) {
1092
1093 if (ctx->use_nagbar && (context->has_errors || context->has_warnings || invalid_sets)) {
11031094 ELOG("FYI: You are using i3 version %s\n", i3_version);
11041095 if (version == 3)
11051096 ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
11071098 start_config_error_nagbar(f, context->has_errors || invalid_sets);
11081099 }
11091100
1110 bool has_errors = context->has_errors;
1101 const bool has_errors = context->has_errors;
11111102
11121103 FREE(context->line_copy);
11131104 free(context);
11141105 free(new);
11151106 free(buf);
11161107
1117 while (!SLIST_EMPTY(&variables)) {
1118 current = SLIST_FIRST(&variables);
1119 FREE(current->key);
1120 FREE(current->value);
1121 SLIST_REMOVE_HEAD(&variables, variables);
1122 FREE(current);
1123 }
1124
1125 return !has_errors;
1108 if (chdir(old_dir) == -1) {
1109 ELOG("chdir(%s) failed: %s\n", old_dir, strerror(errno));
1110 return PARSE_FILE_FAILED;
1111 }
1112 free(old_dir);
1113 if (has_errors) {
1114 return PARSE_FILE_CONFIG_ERRORS;
1115 }
1116 return PARSE_FILE_SUCCESS;
11261117 }
11271118
11281119 #endif
1313 #include <time.h>
1414 #include <unistd.h>
1515
16 static bool human_readable_key, loaded_config_file_name_key;
17 static char *human_readable_version, *loaded_config_file_name;
16 static bool human_readable_key;
17 static bool loaded_config_file_name_key;
18 static bool included_config_file_names;
19
20 static char *human_readable_version;
21 static char *loaded_config_file_name;
1822
1923 static int version_string(void *ctx, const unsigned char *val, size_t len) {
20 if (human_readable_key)
24 if (human_readable_key) {
2125 sasprintf(&human_readable_version, "%.*s", (int)len, val);
22 if (loaded_config_file_name_key)
26 }
27 if (loaded_config_file_name_key) {
2328 sasprintf(&loaded_config_file_name, "%.*s", (int)len, val);
29 }
30 if (included_config_file_names) {
31 IncludedFile *file = scalloc(1, sizeof(IncludedFile));
32 sasprintf(&(file->path), "%.*s", (int)len, val);
33 TAILQ_INSERT_TAIL(&included_files, file, files);
34 }
2435 return 1;
2536 }
2637
2738 static int version_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
28 human_readable_key = (stringlen == strlen("human_readable") &&
29 strncmp((const char *)stringval, "human_readable", strlen("human_readable")) == 0);
30 loaded_config_file_name_key = (stringlen == strlen("loaded_config_file_name") &&
31 strncmp((const char *)stringval, "loaded_config_file_name", strlen("loaded_config_file_name")) == 0);
39 #define KEY_MATCHES(x) (stringlen == strlen(x) && strncmp((const char *)stringval, x, strlen(x)) == 0)
40 human_readable_key = KEY_MATCHES("human_readable");
41 loaded_config_file_name_key = KEY_MATCHES("loaded_config_file_name");
42 included_config_file_names = KEY_MATCHES("included_config_file_names");
43 #undef KEY_MATCHES
3244 return 1;
3345 }
3446
3749 .yajl_map_key = version_map_key,
3850 };
3951
52 static void print_config_path(const char *path, const char *role) {
53 struct stat sb;
54 time_t now;
55 char mtime[64];
56
57 printf(" %s (%s)", path, role);
58 if (stat(path, &sb) == -1) {
59 printf("\n");
60 ELOG("Cannot stat config file \"%s\"\n", path);
61 } else {
62 strftime(mtime, sizeof(mtime), "%c", localtime(&(sb.st_mtime)));
63 time(&now);
64 printf(" (last modified: %s, %.f seconds ago)\n", mtime, difftime(now, sb.st_mtime));
65 }
66 }
67
4068 /*
4169 * Connects to i3 to find out the currently running version. Useful since it
4270 * might be different from the version compiled into this binary (maybe the
43 * user didn’t correctly install i3 or forgot te restart it).
71 * user didn’t correctly install i3 or forgot to restart it).
4472 *
4573 * The output looks like this:
4674 * Running i3 version: 4.2-202-gb8e782c (2012-08-12, branch "next") (pid 14804)
97125 printf("Running i3 version: %s (pid %s)\n", human_readable_version, pid_from_atom);
98126
99127 if (loaded_config_file_name) {
100 struct stat sb;
101 time_t now;
102 char mtime[64];
103 printf("Loaded i3 config: %s", loaded_config_file_name);
104 if (stat(loaded_config_file_name, &sb) == -1) {
105 printf("\n");
106 ELOG("Cannot stat config file \"%s\"\n", loaded_config_file_name);
107 } else {
108 strftime(mtime, sizeof(mtime), "%c", localtime(&(sb.st_mtime)));
109 time(&now);
110 printf(" (Last modified: %s, %.f seconds ago)\n", mtime, difftime(now, sb.st_mtime));
128 printf("Loaded i3 config:\n");
129 print_config_path(loaded_config_file_name, "main");
130 IncludedFile *file;
131 TAILQ_FOREACH (file, &included_files, files) {
132 print_config_path(file->path, "included");
111133 }
112134 }
113135
4141
4242 static bool threshold_exceeded(uint32_t x1, uint32_t y1,
4343 uint32_t x2, uint32_t y2) {
44 const uint32_t threshold = 9;
44 /* The threshold is about the height of one window decoration. */
45 const uint32_t threshold = logical_px(15);
4546 return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) > threshold * threshold;
4647 }
4748
277277 */
278278 void ewmh_update_sticky(xcb_window_t window, bool sticky) {
279279 if (sticky) {
280 DLOG("Setting _NET_WM_STATE_STICKY for window = %d.\n", window);
280 DLOG("Setting _NET_WM_STATE_STICKY for window = %08x.\n", window);
281281 xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY);
282282 } else {
283 DLOG("Removing _NET_WM_STATE_STICKY for window = %d.\n", window);
283 DLOG("Removing _NET_WM_STATE_STICKY for window = %08x.\n", window);
284284 xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY);
285285 }
286286 }
291291 */
292292 void ewmh_update_focused(xcb_window_t window, bool is_focused) {
293293 if (is_focused) {
294 DLOG("Setting _NET_WM_STATE_FOCUSED for window = %d.\n", window);
294 DLOG("Setting _NET_WM_STATE_FOCUSED for window = %08x.\n", window);
295295 xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
296296 } else {
297 DLOG("Removing _NET_WM_STATE_FOCUSED for window = %d.\n", window);
297 DLOG("Removing _NET_WM_STATE_FOCUSED for window = %08x.\n", window);
298298 xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
299299 }
300300 }
252252 }
253253 /* Consider the part of the focus stack of our current workspace:
254254 * [ ... S_{i-1} S_{i} S_{i+1} ... ]
255 * Where S_{x} is a container tree and the container 'con' that is beeing switched to
255 * Where S_{x} is a container tree and the container 'con' that is being switched to
256256 * floating belongs in S_{i}. The new floating container, 'nc', will have the
257257 * workspace as its parent so it needs to be placed in this stack. If C was focused
258258 * we just need to call con_focus(). Otherwise, nc must be placed before or after S_{i}.
346346 con->floating = FLOATING_USER_ON;
347347
348348 /* 4: set the border style as specified with new_float */
349 if (automatic)
350 con->border_style = config.default_floating_border;
349 if (automatic) {
350 con->border_style = con->max_user_border_style = config.default_floating_border;
351 }
351352
352353 /* Add pixels for the decoration. */
353354 Rect border_style_rect = con_border_style_rect(con);
696697 void floating_resize_window(Con *con, const bool proportional,
697698 const xcb_button_press_event_t *event) {
698699 DLOG("floating_resize_window\n");
700
701 /* Push changes before resizing, so that the window gets raised now and not
702 * after the user releases the mouse button */
703 tree_render();
699704
700705 /* corner saves the nearest corner to the original click. It contains
701706 * a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */
6969 event->response_type != response_type)
7070 continue;
7171
72 /* instead of removing a sequence number we better wait until it gets
73 * garbage collected. it may generate multiple events (there are multiple
74 * enter_notifies for one configure_request, for example). */
75 //SLIST_REMOVE(&ignore_events, event, Ignore_Event, ignore_events);
76 //free(event);
72 /* Instead of removing & freeing a sequence number we better wait until
73 * it gets garbage collected. It may generate multiple events (there
74 * are multiple enter_notifies for one configure_request, for example). */
7775 return true;
7876 }
7977
269267 * Configure requests are received when the application wants to resize windows
270268 * on their own.
271269 *
272 * We generate a synthethic configure notify event to signalize the client its
270 * We generate a synthetic configure notify event to signalize the client its
273271 * "new" position.
274272 *
275273 */
800798 } else if (event->type == A_WM_CHANGE_STATE) {
801799 /* http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.4 */
802800 if (event->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) {
803 /* For compatiblity reasons, Wine will request iconic state and cannot ensure that the WM has agreed on it;
801 /* For compatibility reasons, Wine will request iconic state and cannot ensure that the WM has agreed on it;
804802 * immediately revert to normal to avoid being stuck in a paused state. */
805 DLOG("Client has requested iconic state, rejecting. (window = %d)\n", event->window);
803 DLOG("Client has requested iconic state, rejecting. (window = %08x)\n", event->window);
806804 long data[] = {XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE};
807805 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, event->window,
808806 A_WM_STATE, A_WM_STATE, 32, 2, data);
809807 } else {
810 DLOG("Not handling WM_CHANGE_STATE request. (window = %d, state = %d)\n", event->window, event->data.data32[0]);
808 DLOG("Not handling WM_CHANGE_STATE request. (window = %08x, state = %d)\n", event->window, event->data.data32[0]);
811809 }
812810 } else if (event->type == A__NET_CURRENT_DESKTOP) {
813811 /* This request is used by pagers and bars to change the current
875873 tree_close_internal(con, KILL_WINDOW, false);
876874 tree_render();
877875 } else {
878 DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %d)\n", event->window);
876 DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %08x)\n", event->window);
879877 }
880878 } else if (event->type == A__NET_WM_MOVERESIZE) {
881879 /*
884882 */
885883 Con *con = con_by_window_id(event->window);
886884 if (!con || !con_is_floating(con)) {
887 DLOG("Couldn't find con for _NET_WM_MOVERESIZE request, or con not floating (window = %d)\n", event->window);
885 DLOG("Couldn't find con for _NET_WM_MOVERESIZE request, or con not floating (window = %08x)\n", event->window);
888886 return;
889887 }
890888 DLOG("Handling _NET_WM_MOVERESIZE request (con = %p)\n", con);
10581056 }
10591057
10601058 /*
1059 * Log FocusOut events.
1060 *
1061 */
1062 static void handle_focus_out(xcb_focus_in_event_t *event) {
1063 Con *con = con_by_window_id(event->event);
1064 const char *window_name, *mode, *detail;
1065
1066 if (con != NULL) {
1067 window_name = con->name;
1068 if (window_name == NULL) {
1069 window_name = "<unnamed con>";
1070 }
1071 } else if (event->event == root) {
1072 window_name = "<the root window>";
1073 } else {
1074 window_name = "<unknown window>";
1075 }
1076
1077 switch (event->mode) {
1078 case XCB_NOTIFY_MODE_NORMAL:
1079 mode = "Normal";
1080 break;
1081 case XCB_NOTIFY_MODE_GRAB:
1082 mode = "Grab";
1083 break;
1084 case XCB_NOTIFY_MODE_UNGRAB:
1085 mode = "Ungrab";
1086 break;
1087 case XCB_NOTIFY_MODE_WHILE_GRABBED:
1088 mode = "WhileGrabbed";
1089 break;
1090 default:
1091 mode = "<unknown>";
1092 break;
1093 }
1094
1095 switch (event->detail) {
1096 case XCB_NOTIFY_DETAIL_ANCESTOR:
1097 detail = "Ancestor";
1098 break;
1099 case XCB_NOTIFY_DETAIL_VIRTUAL:
1100 detail = "Virtual";
1101 break;
1102 case XCB_NOTIFY_DETAIL_INFERIOR:
1103 detail = "Inferior";
1104 break;
1105 case XCB_NOTIFY_DETAIL_NONLINEAR:
1106 detail = "Nonlinear";
1107 break;
1108 case XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL:
1109 detail = "NonlinearVirtual";
1110 break;
1111 case XCB_NOTIFY_DETAIL_POINTER:
1112 detail = "Pointer";
1113 break;
1114 case XCB_NOTIFY_DETAIL_POINTER_ROOT:
1115 detail = "PointerRoot";
1116 break;
1117 case XCB_NOTIFY_DETAIL_NONE:
1118 detail = "NONE";
1119 break;
1120 default:
1121 detail = "unknown";
1122 break;
1123 }
1124
1125 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);
1126 }
1127
1128 /*
10611129 * Handles ConfigureNotify events for the root window, which are generated when
10621130 * the monitor configuration changed.
10631131 *
10731141 return;
10741142 }
10751143 randr_query_outputs();
1144
1145 ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
1146 }
1147
1148 /*
1149 * Handles SelectionClear events for the root window, which are generated when
1150 * we lose ownership of a selection.
1151 */
1152 static void handle_selection_clear(xcb_selection_clear_event_t *event) {
1153 if (event->selection != wm_sn) {
1154 DLOG("SelectionClear for unknown selection %d, ignoring\n", event->selection);
1155 return;
1156 }
1157 LOG("Lost WM_Sn selection, exiting.\n");
1158 exit(EXIT_SUCCESS);
1159
1160 /* unreachable */
10761161 }
10771162
10781163 /*
10861171 }
10871172
10881173 /*
1089 * Handles the _MOTIF_WM_HINTS property of specifing window deocration settings.
1174 * Handles the WM_CLIENT_MACHINE property for assignments and criteria selection.
1175 *
1176 */
1177 static bool handle_machine_change(Con *con, xcb_get_property_reply_t *prop) {
1178 window_update_machine(con->window, prop);
1179 con = remanage_window(con);
1180 return true;
1181 }
1182
1183 /*
1184 * Handles the _MOTIF_WM_HINTS property of specifying window deocration settings.
10901185 *
10911186 */
10921187 static bool handle_motif_hints_change(Con *con, xcb_get_property_reply_t *prop) {
10931188 border_style_t motif_border_style;
1094 window_update_motif_hints(con->window, prop, &motif_border_style);
1095
1096 if (motif_border_style != con->border_style && motif_border_style != BS_NORMAL) {
1189 bool has_mwm_hints = window_update_motif_hints(con->window, prop, &motif_border_style);
1190
1191 if (has_mwm_hints && motif_border_style != con->border_style) {
10971192 DLOG("Update border style of con %p to %d\n", con, motif_border_style);
10981193 con_set_border_style(con, motif_border_style, con->current_border_width);
10991194
11741269 return true;
11751270 }
11761271
1272 static bool handle_windowicon_change(Con *con, xcb_get_property_reply_t *prop) {
1273 window_update_icon(con->window, prop);
1274
1275 x_push_changes(croot);
1276
1277 return true;
1278 }
1279
11771280 /* Returns false if the event could not be processed (e.g. the window could not
11781281 * be found), true otherwise */
11791282 typedef bool (*cb_property_handler_t)(Con *con, xcb_get_property_reply_t *property);
11961299 {0, UINT_MAX, handle_strut_partial_change},
11971300 {0, UINT_MAX, handle_window_type},
11981301 {0, UINT_MAX, handle_i3_floating},
1199 {0, 5 * sizeof(uint64_t), handle_motif_hints_change}};
1302 {0, 128, handle_machine_change},
1303 {0, 5 * sizeof(uint64_t), handle_motif_hints_change},
1304 {0, UINT_MAX, handle_windowicon_change}};
12001305 #define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
12011306
12021307 /*
12181323 property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL;
12191324 property_handlers[9].atom = A__NET_WM_WINDOW_TYPE;
12201325 property_handlers[10].atom = A_I3_FLOATING_WINDOW;
1221 property_handlers[11].atom = A__MOTIF_WM_HINTS;
1326 property_handlers[11].atom = XCB_ATOM_WM_CLIENT_MACHINE;
1327 property_handlers[12].atom = A__MOTIF_WM_HINTS;
1328 property_handlers[13].atom = A__NET_WM_ICON;
12221329 }
12231330
12241331 static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
12361343 }
12371344
12381345 if (handler == NULL) {
1239 //DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom);
1346 /* DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom); */
12401347 return;
12411348 }
12421349
13941501 handle_focus_in((xcb_focus_in_event_t *)event);
13951502 break;
13961503
1504 case XCB_FOCUS_OUT:
1505 handle_focus_out((xcb_focus_out_event_t *)event);
1506 break;
1507
13971508 case XCB_PROPERTY_NOTIFY: {
13981509 xcb_property_notify_event_t *e = (xcb_property_notify_event_t *)event;
13991510 last_timestamp = e->time;
14051516 handle_configure_notify((xcb_configure_notify_event_t *)event);
14061517 break;
14071518
1519 case XCB_SELECTION_CLEAR:
1520 handle_selection_clear((xcb_selection_clear_event_t *)event);
1521 break;
1522
14081523 default:
1409 //DLOG("Unhandled event of type %d\n", type);
1410 break;
1411 }
1412 }
1524 /* DLOG("Unhandled event of type %d\n", type); */
1525 break;
1526 }
1527 }
2525 char *current_socketpath = NULL;
2626
2727 TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
28
29 /*
30 * Puts the given socket file descriptor into non-blocking mode or dies if
31 * setting O_NONBLOCK failed. Non-blocking sockets are a good idea for our
32 * IPC model because we should by no means block the window manager.
33 *
34 */
35 static void set_nonblock(int sockfd) {
36 int flags = fcntl(sockfd, F_GETFL, 0);
37 if (flags & O_NONBLOCK) {
38 return;
39 }
40 flags |= O_NONBLOCK;
41 if (fcntl(sockfd, F_SETFL, flags) < 0)
42 err(-1, "Could not set O_NONBLOCK");
43 }
4428
4529 static void ipc_client_timeout(EV_P_ ev_timer *w, int revents);
4630 static void ipc_socket_writeable_cb(EV_P_ struct ev_io *w, int revents);
250234 ystr(name);
251235 y(map_open);
252236 ystr("x");
253 y(integer, r.x);
237 y(integer, (int32_t)r.x);
254238 ystr("y");
255 y(integer, r.y);
239 y(integer, (int32_t)r.y);
256240 ystr("width");
257241 y(integer, r.width);
258242 ystr("height");
535519 ystr(con->title_format);
536520 }
537521
522 ystr("window_icon_padding");
523 y(integer, con->window_icon_padding);
524
538525 if (con->type == CT_WORKSPACE) {
539526 ystr("num");
540527 y(integer, con->num);
596583 DUMP_PROPERTY("class", class_class);
597584 DUMP_PROPERTY("instance", class_instance);
598585 DUMP_PROPERTY("window_role", role);
586 DUMP_PROPERTY("machine", machine);
599587
600588 if (con->window->name != NULL) {
601589 ystr("title");
685673 DUMP_REGEX(instance);
686674 DUMP_REGEX(window_role);
687675 DUMP_REGEX(title);
676 DUMP_REGEX(machine);
688677
689678 #undef DUMP_REGEX
690679 y(map_close);
10821071 ystr("loaded_config_file_name");
10831072 ystr(current_configpath);
10841073
1074 ystr("included_config_file_names");
1075 y(array_open);
1076 IncludedFile *file;
1077 TAILQ_FOREACH (file, &included_files, files) {
1078 if (file == TAILQ_FIRST(&included_files)) {
1079 /* Skip the first file, which is current_configpath. */
1080 continue;
1081 }
1082 ystr(file->path);
1083 }
1084 y(array_close);
10851085 y(map_close);
10861086
10871087 const unsigned char *payload;
12641264 y(map_open);
12651265
12661266 ystr("config");
1267 ystr(current_config);
1267 IncludedFile *file = TAILQ_FIRST(&included_files);
1268 ystr(file->raw_contents);
1269
1270 ystr("included_configs");
1271 y(array_open);
1272 TAILQ_FOREACH (file, &included_files, files) {
1273 y(map_open);
1274 ystr("path");
1275 ystr(file->path);
1276 ystr("raw_contents");
1277 ystr(file->raw_contents);
1278 ystr("variable_replaced_contents");
1279 ystr(file->variable_replaced_contents);
1280 y(map_close);
1281 }
1282 y(array_close);
12681283
12691284 y(map_close);
12701285
15521567 }
15531568
15541569 /*
1555 * Creates the UNIX domain socket at the given path, sets it to non-blocking
1556 * mode, bind()s and listen()s on it.
1557 *
1558 */
1559 int ipc_create_socket(const char *filename) {
1560 int sockfd;
1561
1562 FREE(current_socketpath);
1563
1564 char *resolved = resolve_tilde(filename);
1565 DLOG("Creating IPC-socket at %s\n", resolved);
1566 char *copy = sstrdup(resolved);
1567 const char *dir = dirname(copy);
1568 if (!path_exists(dir))
1569 mkdirp(dir, DEFAULT_DIR_MODE);
1570 free(copy);
1571
1572 /* Unlink the unix domain socket before */
1573 unlink(resolved);
1574
1575 if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
1576 perror("socket()");
1577 free(resolved);
1578 return -1;
1579 }
1580
1581 (void)fcntl(sockfd, F_SETFD, FD_CLOEXEC);
1582
1583 struct sockaddr_un addr;
1584 memset(&addr, 0, sizeof(struct sockaddr_un));
1585 addr.sun_family = AF_LOCAL;
1586 strncpy(addr.sun_path, resolved, sizeof(addr.sun_path) - 1);
1587 if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
1588 perror("bind()");
1589 free(resolved);
1590 return -1;
1591 }
1592
1593 set_nonblock(sockfd);
1594
1595 if (listen(sockfd, 5) < 0) {
1596 perror("listen()");
1597 free(resolved);
1598 return -1;
1599 }
1600
1601 current_socketpath = resolved;
1602 return sockfd;
1603 }
1604
1605 /*
16061570 * Generates a json workspace event. Returns a dynamically allocated yajl
16071571 * generator. Free with yajl_gen_free().
16081572 */
289289 } else if (strcasecmp(last_key, "title") == 0) {
290290 current_swallow->title = regex_new(sval);
291291 swallow_is_empty = false;
292 } else if (strcasecmp(last_key, "machine") == 0) {
293 current_swallow->machine = regex_new(sval);
294 swallow_is_empty = false;
292295 } else {
293296 ELOG("swallow key %s unknown\n", last_key);
294297 }
455458
456459 if (strcasecmp(last_key, "current_border_width") == 0)
457460 json_node->current_border_width = val;
461
462 if (strcasecmp(last_key, "window_icon_padding") == 0) {
463 json_node->window_icon_padding = val;
464 }
458465
459466 if (strcasecmp(last_key, "depth") == 0)
460467 json_node->depth = val;
1111 #include "all.h"
1212 #include "shmlog.h"
1313
14 #include <ev.h>
15 #include <libgen.h>
16 #include <sys/socket.h>
17 #include <sys/un.h>
1418 #include <errno.h>
1519 #include <fcntl.h>
1620 #include <stdarg.h>
2226 #include <sys/stat.h>
2327 #include <sys/time.h>
2428 #include <unistd.h>
25 #if !defined(__OpenBSD__)
26 #include <pthread.h>
27 #endif
2829
2930 #if defined(__APPLE__)
3031 #include <sys/sysctl.h>
5960 /* Size (in bytes) of physical memory */
6061 static long long physical_mem_bytes;
6162
63 typedef struct log_client {
64 int fd;
65
66 TAILQ_ENTRY(log_client)
67 clients;
68 } log_client;
69
70 TAILQ_HEAD(log_client_head, log_client)
71 log_clients = TAILQ_HEAD_INITIALIZER(log_clients);
72
73 void log_broadcast_to_clients(const char *message, size_t len);
74
6275 /*
6376 * Writes the offsets for the next write and for the last wrap to the
6477 * shmlog_header.
123136 * For 512 MiB of RAM this will lead to a 5 MiB log buffer.
124137 * At the moment (2011-12-10), no testcase leads to an i3 log
125138 * of more than ~ 600 KiB. */
126 logbuffer_size = min(physical_mem_bytes * 0.01, shmlog_size);
139 logbuffer_size = shmlog_size;
140 if (physical_mem_bytes * 0.01 < (long long)shmlog_size) {
141 logbuffer_size = physical_mem_bytes * 0.01;
142 }
143
127144 #if defined(__FreeBSD__)
128145 sasprintf(&shmlogname, "/tmp/i3-log-%d", getpid());
129146 #else
159176 memset(logbuffer, '\0', logbuffer_size);
160177
161178 header = (i3_shmlog_header *)logbuffer;
162
163 #if !defined(__OpenBSD__)
164 pthread_condattr_t cond_attr;
165 pthread_condattr_init(&cond_attr);
166 if (pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED) != 0)
167 fprintf(stderr, "pthread_condattr_setpshared() failed, i3-dump-log -f will not work!\n");
168 pthread_cond_init(&(header->condvar), &cond_attr);
169 #endif
170179
171180 logwalk = logbuffer + sizeof(i3_shmlog_header);
172181 loglastwrap = logbuffer + logbuffer_size;
282291
283292 store_log_markers();
284293
285 #if !defined(__OpenBSD__)
286 /* Wake up all (i3-dump-log) processes waiting for condvar. */
287 pthread_cond_broadcast(&(header->condvar));
288 #endif
289
290294 if (print)
291295 fwrite(message, len, 1, stdout);
296
297 log_broadcast_to_clients(message, len);
292298 }
293299 }
294300
369375 rmdir(errorfilename);
370376 }
371377 }
378
379 char *current_log_stream_socket_path = NULL;
380
381 /*
382 * Handler for activity on the listening socket, meaning that a new client
383 * has just connected and we should accept() them. Sets up the event handler
384 * for activity on the new connection and inserts the file descriptor into
385 * the list of log clients.
386 *
387 */
388 void log_new_client(EV_P_ struct ev_io *w, int revents) {
389 struct sockaddr_un peer;
390 socklen_t len = sizeof(struct sockaddr_un);
391 int fd;
392 if ((fd = accept(w->fd, (struct sockaddr *)&peer, &len)) < 0) {
393 if (errno != EINTR) {
394 perror("accept()");
395 }
396 return;
397 }
398
399 /* Close this file descriptor on exec() */
400 (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
401
402 set_nonblock(fd);
403
404 log_client *client = scalloc(1, sizeof(log_client));
405 client->fd = fd;
406 TAILQ_INSERT_TAIL(&log_clients, client, clients);
407
408 DLOG("log: new client connected on fd %d\n", fd);
409 }
410
411 void log_broadcast_to_clients(const char *message, size_t len) {
412 log_client *current = TAILQ_FIRST(&log_clients);
413 while (current != TAILQ_END(&log_clients)) {
414 /* XXX: In case slow connections turn out to be a problem here
415 * (unlikely as long as i3-dump-log is the only consumer), introduce
416 * buffering, similar to the IPC interface. */
417 ssize_t n = writeall(current->fd, message, len);
418 log_client *previous = current;
419 current = TAILQ_NEXT(current, clients);
420 if (n < 0) {
421 TAILQ_REMOVE(&log_clients, previous, clients);
422 free(previous);
423 }
424 }
425 }
2323 #include <sys/types.h>
2424 #include <sys/un.h>
2525 #include <unistd.h>
26 #include <xcb/xcb_atom.h>
27 #include <xcb/xinerama.h>
28 #include <xcb/bigreq.h>
2629
2730 #ifdef I3_ASAN_ENABLED
2831 #include <sanitizer/lsan_interface.h>
6164
6265 xcb_screen_t *root_screen;
6366 xcb_window_t root;
67
68 xcb_window_t wm_sn_selection_owner;
69 xcb_atom_t wm_sn;
6470
6571 /* Color depth, visual id and colormap to use when creating windows and
6672 * pixmaps. Will use 32 bit depth and an appropriate visual, if available,
180186 }
181187 ipc_shutdown(SHUTDOWN_REASON_EXIT, -1);
182188 unlink(config.ipc_socket_path);
189 if (current_log_stream_socket_path != NULL) {
190 unlink(current_log_stream_socket_path);
191 }
183192 xcb_disconnect(conn);
184193
185194 /* If a nagbar is active, kill it */
278287 char *fake_outputs = NULL;
279288 bool disable_signalhandler = false;
280289 bool only_check_config = false;
290 bool replace_wm = false;
281291 static struct option long_options[] = {
282292 {"no-autostart", no_argument, 0, 'a'},
283293 {"config", required_argument, 0, 'c'},
300310 {"fake_outputs", required_argument, 0, 0},
301311 {"fake-outputs", required_argument, 0, 0},
302312 {"force-old-config-parser-v4.4-only", no_argument, 0, 0},
313 {"replace", no_argument, 0, 'r'},
303314 {0, 0, 0, 0}};
304315 int option_index = 0, opt;
305316
324335
325336 start_argv = argv;
326337
327 while ((opt = getopt_long(argc, argv, "c:CvmaL:hld:V", long_options, &option_index)) != -1) {
338 while ((opt = getopt_long(argc, argv, "c:CvmaL:hld:Vr", long_options, &option_index)) != -1) {
328339 switch (opt) {
329340 case 'a':
330341 LOG("Autostart disabled using -a\n");
361372 break;
362373 case 'l':
363374 /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */
375 break;
376 case 'r':
377 replace_wm = true;
364378 break;
365379 case 0:
366380 if (strcmp(long_options[option_index].name, "force-xinerama") == 0 ||
442456 "\tThe default is %d bytes.\n",
443457 shmlog_size);
444458 fprintf(stderr, "\n");
459 fprintf(stderr, "\t--replace\n"
460 "\tReplace an existing window manager.\n");
461 fprintf(stderr, "\n");
445462 fprintf(stderr, "If you pass plain text arguments, i3 will interpret them as a command\n"
446463 "to send to a currently running i3 (like i3-msg). This allows you to\n"
447464 "use nice and logical commands, such as:\n"
569586 root_screen = xcb_aux_get_screen(conn, conn_screen);
570587 root = root_screen->root;
571588
589 /* Prefetch X11 extensions that we are interested in. */
590 xcb_prefetch_extension_data(conn, &xcb_xkb_id);
591 xcb_prefetch_extension_data(conn, &xcb_shape_id);
592 /* BIG-REQUESTS is used by libxcb internally. */
593 xcb_prefetch_extension_data(conn, &xcb_big_requests_id);
594 if (force_xinerama) {
595 xcb_prefetch_extension_data(conn, &xcb_xinerama_id);
596 } else {
597 xcb_prefetch_extension_data(conn, &xcb_randr_id);
598 }
599
600 /* Prepare for us to get a current timestamp as recommended by ICCCM */
601 xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){XCB_EVENT_MASK_PROPERTY_CHANGE});
602 xcb_change_property(conn, XCB_PROP_MODE_APPEND, root, XCB_ATOM_SUPERSCRIPT_X, XCB_ATOM_CARDINAL, 32, 0, "");
603
572604 /* Place requests for the atoms we need as soon as possible */
573605 #define xmacro(atom) \
574606 xcb_intern_atom_cookie_t atom##_cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
598630 visual_type = get_visualtype(root_screen);
599631 }
600632
633 xcb_prefetch_maximum_request_length(conn);
634
601635 init_dpi();
602636
603637 DLOG("root_depth = %d, visual_id = 0x%08x.\n", root_depth, visual_type->visual_id);
607641
608642 xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root);
609643 xcb_query_pointer_cookie_t pointercookie = xcb_query_pointer(conn, root);
644
645 /* Get the PropertyNotify event we caused above */
646 xcb_flush(conn);
647 {
648 xcb_generic_event_t *event;
649 DLOG("waiting for PropertyNotify event\n");
650 while ((event = xcb_wait_for_event(conn)) != NULL) {
651 if (event->response_type == XCB_PROPERTY_NOTIFY) {
652 last_timestamp = ((xcb_property_notify_event_t *)event)->time;
653 free(event);
654 break;
655 }
656 free(event);
657 }
658 DLOG("got timestamp %d\n", last_timestamp);
659 }
610660
611661 /* Setup NetWM atoms */
612662 #define xmacro(name) \
632682 else
633683 config.ipc_socket_path = sstrdup(config.ipc_socket_path);
634684 }
685 /* Create the UNIX domain socket for IPC */
686 int ipc_socket = create_socket(config.ipc_socket_path, &current_socketpath);
687 if (ipc_socket == -1) {
688 die("Could not create the IPC socket: %s", config.ipc_socket_path);
689 }
635690
636691 if (config.force_xinerama) {
637692 force_xinerama = true;
693 }
694
695 /* Acquire the WM_Sn selection. */
696 {
697 /* Get the WM_Sn atom */
698 char *atom_name = xcb_atom_name_by_screen("WM", conn_screen);
699 wm_sn_selection_owner = xcb_generate_id(conn);
700
701 if (atom_name == NULL) {
702 ELOG("xcb_atom_name_by_screen(\"WM\", %d) failed, exiting\n", conn_screen);
703 return 1;
704 }
705
706 xcb_intern_atom_reply_t *atom_reply;
707 atom_reply = xcb_intern_atom_reply(conn,
708 xcb_intern_atom_unchecked(conn,
709 0,
710 strlen(atom_name),
711 atom_name),
712 NULL);
713 free(atom_name);
714 if (atom_reply == NULL) {
715 ELOG("Failed to intern the WM_Sn atom, exiting\n");
716 return 1;
717 }
718 wm_sn = atom_reply->atom;
719 free(atom_reply);
720
721 /* Check if the selection is already owned */
722 xcb_get_selection_owner_reply_t *selection_reply =
723 xcb_get_selection_owner_reply(conn,
724 xcb_get_selection_owner(conn, wm_sn),
725 NULL);
726 if (selection_reply && selection_reply->owner != XCB_NONE && !replace_wm) {
727 ELOG("Another window manager is already running (WM_Sn is owned)");
728 return 1;
729 }
730
731 /* Become the selection owner */
732 xcb_create_window(conn,
733 root_screen->root_depth,
734 wm_sn_selection_owner, /* window id */
735 root_screen->root, /* parent */
736 -1, -1, 1, 1, /* geometry */
737 0, /* border width */
738 XCB_WINDOW_CLASS_INPUT_OUTPUT,
739 root_screen->root_visual,
740 0, NULL);
741 xcb_change_property(conn,
742 XCB_PROP_MODE_REPLACE,
743 wm_sn_selection_owner,
744 XCB_ATOM_WM_CLASS,
745 XCB_ATOM_STRING,
746 8,
747 (strlen("i3-WM_Sn") + 1) * 2,
748 "i3-WM_Sn\0i3-WM_Sn\0");
749
750 xcb_set_selection_owner(conn, wm_sn_selection_owner, wm_sn, last_timestamp);
751
752 if (selection_reply && selection_reply->owner != XCB_NONE) {
753 unsigned int usleep_time = 100000; /* 0.1 seconds */
754 int check_rounds = 150; /* Wait for a maximum of 15 seconds */
755 xcb_get_geometry_reply_t *geom_reply = NULL;
756
757 DLOG("waiting for old WM_Sn selection owner to exit");
758 do {
759 free(geom_reply);
760 usleep(usleep_time);
761 if (check_rounds-- == 0) {
762 ELOG("The old window manager is not exiting");
763 return 1;
764 }
765 geom_reply = xcb_get_geometry_reply(conn,
766 xcb_get_geometry(conn, selection_reply->owner),
767 NULL);
768 } while (geom_reply != NULL);
769 }
770 free(selection_reply);
771
772 /* Announce that we are the new owner */
773 /* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
774 * In order to properly initialize these bytes, we allocate 32 bytes even
775 * though we only need less for an xcb_client_message_event_t */
776 union {
777 xcb_client_message_event_t message;
778 char storage[32];
779 } event;
780 memset(&event, 0, sizeof(event));
781 event.message.response_type = XCB_CLIENT_MESSAGE;
782 event.message.window = root_screen->root;
783 event.message.format = 32;
784 event.message.type = A_MANAGER;
785 event.message.data.data32[0] = last_timestamp;
786 event.message.data.data32[1] = wm_sn;
787 event.message.data.data32[2] = wm_sn_selection_owner;
788
789 xcb_send_event(conn, 0, root_screen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY, event.storage);
638790 }
639791
640792 xcb_void_cookie_t cookie;
662814 xcursor_set_root_cursor(XCURSOR_CURSOR_POINTER);
663815
664816 const xcb_query_extension_reply_t *extreply;
665 xcb_prefetch_extension_data(conn, &xcb_xkb_id);
666 xcb_prefetch_extension_data(conn, &xcb_shape_id);
667
668817 extreply = xcb_get_extension_data(conn, &xcb_xkb_id);
669818 xkb_supported = extreply->present;
670819 if (!extreply->present) {
843992
844993 tree_render();
845994
846 /* Create the UNIX domain socket for IPC */
847 int ipc_socket = ipc_create_socket(config.ipc_socket_path);
848 if (ipc_socket == -1) {
849 ELOG("Could not create the IPC socket, IPC disabled\n");
995 /* Listen to the IPC socket for clients */
996 struct ev_io *ipc_io = scalloc(1, sizeof(struct ev_io));
997 ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
998 ev_io_start(main_loop, ipc_io);
999
1000 /* Chose a file name in /tmp/ based on the PID */
1001 char *log_stream_socket_path = get_process_filename("log-stream-socket");
1002 int log_socket = create_socket(log_stream_socket_path, &current_log_stream_socket_path);
1003 free(log_stream_socket_path);
1004 if (log_socket == -1) {
1005 ELOG("Could not create the log socket, i3-dump-log -f will not work\n");
8501006 } else {
851 struct ev_io *ipc_io = scalloc(1, sizeof(struct ev_io));
852 ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ);
853 ev_io_start(main_loop, ipc_io);
854 }
855
856 /* Also handle the UNIX domain sockets passed via socket activation. The
857 * parameter 1 means "remove the environment variables", we don’t want to
858 * pass these to child processes. */
1007 struct ev_io *log_io = scalloc(1, sizeof(struct ev_io));
1008 ev_io_init(log_io, log_new_client, log_socket, EV_READ);
1009 ev_io_start(main_loop, log_io);
1010 }
1011
1012 /* Also handle the UNIX domain sockets passed via socket
1013 * activation. The parameter 0 means "do not remove the
1014 * environment variables", we need to be able to reexec. */
8591015 listen_fds = sd_listen_fds(0);
8601016 if (listen_fds < 0)
8611017 ELOG("socket activation: Error in sd_listen_fds\n");
9491105 xcb_ungrab_server(conn);
9501106
9511107 if (autostart) {
952 LOG("This is not an in-place restart, copying root window contents to a pixmap\n");
1108 /* When the root's window background is set to NONE, that might mean
1109 * that old content stays visible when a window is closed. That has
1110 * unpleasant effect of "my terminal (does not seem to) close!".
1111 *
1112 * There does not seem to be an easy way to query for this problem, so
1113 * we test for it: Open & close a window and check if the background is
1114 * redrawn or the window contents stay visible.
1115 */
1116 LOG("This is not an in-place restart, checking if a wallpaper is set.\n");
1117
9531118 xcb_screen_t *root = xcb_aux_get_screen(conn, conn_screen);
954 uint16_t width = root->width_in_pixels;
955 uint16_t height = root->height_in_pixels;
956 xcb_pixmap_t pixmap = xcb_generate_id(conn);
957 xcb_gcontext_t gc = xcb_generate_id(conn);
958
959 xcb_create_pixmap(conn, root->root_depth, pixmap, root->root, width, height);
960
961 xcb_create_gc(conn, gc, root->root,
962 XCB_GC_FUNCTION | XCB_GC_PLANE_MASK | XCB_GC_FILL_STYLE | XCB_GC_SUBWINDOW_MODE,
963 (uint32_t[]){XCB_GX_COPY, ~0, XCB_FILL_STYLE_SOLID, XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS});
964
965 xcb_copy_area(conn, root->root, pixmap, gc, 0, 0, 0, 0, width, height);
966 xcb_change_window_attributes(conn, root->root, XCB_CW_BACK_PIXMAP, (uint32_t[]){pixmap});
967 xcb_flush(conn);
968 xcb_free_gc(conn, gc);
969 xcb_free_pixmap(conn, pixmap);
1119 if (is_background_set(conn, root)) {
1120 LOG("A wallpaper is set, so no screenshot is necessary.\n");
1121 } else {
1122 LOG("No wallpaper set, copying root window contents to a pixmap\n");
1123 set_screenshot_as_wallpaper(conn, root);
1124 }
9701125 }
9711126
9721127 #if defined(__OpenBSD__)
10401195 * when calling exit() */
10411196 atexit(i3_exit);
10421197
1198 sd_notify(1, "READY=1");
10431199 ev_loop(main_loop, 0);
10441200 }
115115 utf8_title_cookie, title_cookie,
116116 class_cookie, leader_cookie, transient_cookie,
117117 role_cookie, startup_id_cookie, wm_hints_cookie,
118 wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie, wm_desktop_cookie;
118 wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie, wm_desktop_cookie,
119 wm_machine_cookie;
120
121 xcb_get_property_cookie_t wm_icon_cookie;
119122
120123 geomc = xcb_get_geometry(conn, d);
121124
122 /* Check if the window is mapped (it could be not mapped when intializing and
125 /* Check if the window is mapped (it could be not mapped when initializing and
123126 calling manage_window() for every window) */
124127 if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
125128 DLOG("Could not get attributes\n");
188191 motif_wm_hints_cookie = GET_PROPERTY(A__MOTIF_WM_HINTS, 5 * sizeof(uint64_t));
189192 wm_user_time_cookie = GET_PROPERTY(A__NET_WM_USER_TIME, UINT32_MAX);
190193 wm_desktop_cookie = GET_PROPERTY(A__NET_WM_DESKTOP, UINT32_MAX);
194 wm_machine_cookie = GET_PROPERTY(XCB_ATOM_WM_CLIENT_MACHINE, UINT32_MAX);
195 wm_icon_cookie = GET_PROPERTY(A__NET_WM_ICON, UINT32_MAX);
191196
192197 i3Window *cwindow = scalloc(1, sizeof(i3Window));
193198 cwindow->id = window;
201206 window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL));
202207 window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL));
203208 window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL));
209 window_update_icon(cwindow, xcb_get_property_reply(conn, wm_icon_cookie, NULL));
204210 window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL));
205211 window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
206212 window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
207213 window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL));
208214 bool urgency_hint;
209215 window_update_hints(cwindow, xcb_get_property_reply(conn, wm_hints_cookie, NULL), &urgency_hint);
210 border_style_t motif_border_style = BS_NORMAL;
211 window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
216 border_style_t motif_border_style;
217 bool has_mwm_hints = window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
212218 window_update_normal_hints(cwindow, xcb_get_property_reply(conn, wm_normal_hints_cookie, NULL), geom);
219 window_update_machine(cwindow, xcb_get_property_reply(conn, wm_machine_cookie, NULL));
213220 xcb_get_property_reply_t *type_reply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
214221 xcb_get_property_reply_t *state_reply = xcb_get_property_reply(conn, state_cookie, NULL);
215222
477484 (cwindow->leader != XCB_NONE &&
478485 cwindow->leader != cwindow->id &&
479486 con_by_window_id(cwindow->leader) != NULL)) {
480 LOG("This window is transient for another window, setting floating\n");
487 DLOG("This window is transient for another window, setting floating\n");
481488 want_floating = true;
482489
483490 if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN &&
484491 fs != NULL) {
485 LOG("There is a fullscreen window, leaving fullscreen mode\n");
492 DLOG("There is a fullscreen window, leaving fullscreen mode\n");
486493 con_toggle_fullscreen(fs, CF_OUTPUT);
487494 } else if (config.popup_during_fullscreen == PDF_SMART &&
488495 fs != NULL &&
489496 fs->window != NULL) {
490 i3Window *transient_win = cwindow;
491 while (transient_win != NULL &&
492 transient_win->transient_for != XCB_NONE) {
493 if (transient_win->transient_for == fs->window->id) {
494 LOG("This floating window belongs to the fullscreen window (popup_during_fullscreen == smart)\n");
495 set_focus = true;
496 break;
497 }
498 Con *next_transient = con_by_window_id(transient_win->transient_for);
499 if (next_transient == NULL)
500 break;
501 /* Some clients (e.g. x11-ssh-askpass) actually set
502 * WM_TRANSIENT_FOR to their own window id, so break instead of
503 * looping endlessly. */
504 if (transient_win == next_transient->window)
505 break;
506 transient_win = next_transient->window;
507 }
497 set_focus = con_find_transient_for_window(nc, fs->window->id);
508498 }
509499 }
510500
520510 if (nc->geometry.width == 0)
521511 nc->geometry = (Rect){geom->x, geom->y, geom->width, geom->height};
522512
523 if (motif_border_style != BS_NORMAL) {
513 if (want_floating) {
514 DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height);
515 if (floating_enable(nc, true)) {
516 nc->floating = FLOATING_AUTO_ON;
517 }
518 }
519
520 if (has_mwm_hints) {
524521 DLOG("MOTIF_WM_HINTS specifies decorations (border_style = %d)\n", motif_border_style);
525522 if (want_floating) {
526523 con_set_border_style(nc, motif_border_style, config.default_floating_border_width);
527524 } else {
528525 con_set_border_style(nc, motif_border_style, config.default_border_width);
529 }
530 }
531
532 if (want_floating) {
533 DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height);
534 /* automatically set the border to the default value if a motif border
535 * was not specified */
536 bool automatic_border = (motif_border_style == BS_NORMAL);
537
538 if (floating_enable(nc, automatic_border)) {
539 nc->floating = FLOATING_AUTO_ON;
540526 }
541527 }
542528
4646 match->instance == NULL &&
4747 match->window_role == NULL &&
4848 match->workspace == NULL &&
49 match->machine == NULL &&
4950 match->urgent == U_DONTCHECK &&
5051 match->id == XCB_NONE &&
5152 match->window_type == UINT32_MAX &&
5253 match->con_id == NULL &&
5354 match->dock == M_NODOCK &&
54 match->window_mode == WM_ANY);
55 match->window_mode == WM_ANY &&
56 match->match_all_windows == false);
5557 }
5658
5759 /*
129131 }
130132 }
131133
134 CHECK_WINDOW_FIELD(machine, machine, str);
135
132136 Con *con = NULL;
133137 if (match->urgent == U_LATEST) {
134138 /* if the window isn't urgent, no sense in searching */
255259
256260 LOG("window_mode matches\n");
257261 }
262
263 /* NOTE: See the comment regarding 'all' in match_parse_property()
264 * for an explanation of why match_all_windows isn't explicitly
265 * checked. */
258266
259267 return true;
260268 }
272280 regex_free(match->mark);
273281 regex_free(match->window_role);
274282 regex_free(match->workspace);
283 regex_free(match->machine);
275284 }
276285
277286 /*
389398 return;
390399 }
391400
401 if (strcmp(ctype, "machine") == 0) {
402 regex_free(match->machine);
403 match->machine = regex_new(cvalue);
404 return;
405 }
406
392407 if (strcmp(ctype, "tiling") == 0) {
393408 match->window_mode = WM_TILING;
394409 return;
427442 return;
428443 }
429444
445 /* match_matches_window() only checks negatively, so match_all_windows
446 * won't actually be used there, but that's OK because if no negative
447 * match is found (e.g. because of a more restrictive criterion) the
448 * return value of match_matches_window() is true.
449 * Setting it here only serves to cause match_is_empty() to return false,
450 * otherwise empty criteria rules apply, and that's not what we want. */
451 if (strcmp(ctype, "all") == 0) {
452 match->match_all_windows = true;
453 return;
454 }
455
430456 ELOG("Unknown criterion: %s\n", ctype);
431457 }
227227 * the focused container, con, is now a child of ws. To work around this
228228 * and still produce the correct workspace focus events (see
229229 * 517-regress-move-direction-ipc.t) we need to temporarily set focused
230 * to the old workspace. */
230 * to the old workspace.
231 *
232 * The following happen:
233 * 1. Focus con to push it on the top of the focus stack in its new
234 * workspace
235 * 2. Set focused to the old workspace to force workspace_show to
236 * execute
237 * 3. workspace_show will descend focus and target our con for
238 * focusing. This also ensures that the mouse warps correctly.
239 * See: #3518. */
240 con_focus(con);
231241 focused = old_ws;
232242 workspace_show(ws);
233 con_focus(con);
234243 }
235244
236245 /* force re-painting the indicators */
319328 } else {
320329 TAILQ_SWAP(con, swap, &(swap->parent->nodes_head), nodes);
321330 }
331
332 /* redraw parents to ensure all parent split container titles are updated correctly */
333 con_force_split_parents_redraw(con);
322334
323335 ipc_send_window_event("move", con);
324336 return;
664664
665665 new->primary = monitor_info->primary;
666666
667 new->changed =
668 update_if_necessary(&(new->rect.x), monitor_info->x) |
669 update_if_necessary(&(new->rect.y), monitor_info->y) |
670 update_if_necessary(&(new->rect.width), monitor_info->width) |
671 update_if_necessary(&(new->rect.height), monitor_info->height);
667 const bool update_x = update_if_necessary(&(new->rect.x), monitor_info->x);
668 const bool update_y = update_if_necessary(&(new->rect.y), monitor_info->y);
669 const bool update_w = update_if_necessary(&(new->rect.width), monitor_info->width);
670 const bool update_h = update_if_necessary(&(new->rect.height), monitor_info->height);
671
672 new->changed = update_x || update_y || update_w || update_h;
672673
673674 DLOG("name %s, x %d, y %d, width %d px, height %d px, width %d mm, height %d mm, primary %d, automatic %d\n",
674675 name,
742743 return;
743744 }
744745
745 bool updated = update_if_necessary(&(new->rect.x), crtc->x) |
746 update_if_necessary(&(new->rect.y), crtc->y) |
747 update_if_necessary(&(new->rect.width), crtc->width) |
748 update_if_necessary(&(new->rect.height), crtc->height);
746 const bool update_x = update_if_necessary(&(new->rect.x), crtc->x);
747 const bool update_y = update_if_necessary(&(new->rect.y), crtc->y);
748 const bool update_w = update_if_necessary(&(new->rect.width), crtc->width);
749 const bool update_h = update_if_necessary(&(new->rect.height), crtc->height);
750 const bool updated = update_x || update_y || update_w || update_h;
749751 free(crtc);
750752 new->active = (new->rect.width != 0 && new->rect.height != 0);
751753 if (!new->active) {
942944 uint32_t width = min(other->rect.width, output->rect.width);
943945 uint32_t height = min(other->rect.height, output->rect.height);
944946
945 if (update_if_necessary(&(output->rect.width), width) |
946 update_if_necessary(&(output->rect.height), height))
947 const bool update_w = update_if_necessary(&(output->rect.width), width);
948 const bool update_h = update_if_necessary(&(output->rect.height), height);
949 if (update_w || update_h) {
947950 output->changed = true;
951 }
948952
949953 update_if_necessary(&(other->rect.width), width);
950954 update_if_necessary(&(other->rect.height), height);
1919 *
2020 */
2121 struct regex *regex_new(const char *pattern) {
22 const char *error;
23 int errorcode, offset;
22 int errorcode;
23 PCRE2_SIZE offset;
2424
2525 struct regex *re = scalloc(1, sizeof(struct regex));
2626 re->pattern = sstrdup(pattern);
27 int options = PCRE_UTF8;
27 uint32_t options = PCRE2_UTF;
2828 /* We use PCRE_UCP so that \B, \b, \D, \d, \S, \s, \W, \w and some POSIX
2929 * character classes play nicely with Unicode */
30 options |= PCRE_UCP;
31 while (!(re->regex = pcre_compile2(pattern, options, &errorcode, &error, &offset, NULL))) {
32 /* If the error is that PCRE was not compiled with UTF-8 support we
33 * disable it and try again */
34 if (errorcode == 32) {
35 options &= ~PCRE_UTF8;
36 continue;
37 }
38 ELOG("PCRE regular expression compilation failed at %d: %s\n",
39 offset, error);
30 options |= PCRE2_UCP;
31 if (!(re->regex = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, options, &errorcode, &offset, NULL))) {
32 PCRE2_UCHAR buffer[256];
33 pcre2_get_error_message(errorcode, buffer, sizeof(buffer));
34 ELOG("PCRE regular expression compilation failed at %lu: %s\n",
35 offset, buffer);
4036 regex_free(re);
4137 return NULL;
42 }
43 re->extra = pcre_study(re->regex, 0, &error);
44 /* If an error happened, we print the error message, but continue.
45 * Studying the regular expression leads to faster matching, but it’s not
46 * absolutely necessary. */
47 if (error) {
48 ELOG("PCRE regular expression studying failed: %s\n", error);
4938 }
5039 return re;
5140 }
5948 return;
6049 FREE(regex->pattern);
6150 FREE(regex->regex);
62 FREE(regex->extra);
6351 FREE(regex);
6452 }
6553
7058 *
7159 */
7260 bool regex_matches(struct regex *regex, const char *input) {
61 pcre2_match_data *match_data;
7362 int rc;
63
64 match_data = pcre2_match_data_create_from_pattern(regex->regex, NULL);
7465
7566 /* We use strlen() because pcre_exec() expects the length of the input
7667 * string in bytes */
77 if ((rc = pcre_exec(regex->regex, regex->extra, input, strlen(input), 0, 0, NULL, 0)) == 0) {
68 rc = pcre2_match(regex->regex, (PCRE2_SPTR)input, strlen(input), 0, 0, match_data, NULL);
69 pcre2_match_data_free(match_data);
70 if (rc > 0) {
7871 LOG("Regular expression \"%s\" matches \"%s\"\n",
7972 regex->pattern, input);
8073 return true;
8174 }
8275
83 if (rc == PCRE_ERROR_NOMATCH) {
76 if (rc == PCRE2_ERROR_NOMATCH) {
8477 LOG("Regular expression \"%s\" does not match \"%s\"\n",
8578 regex->pattern, input);
8679 return false;
254254 }
255255
256256 Con *floating_child = con_descend_focused(child);
257 Con *transient_con = floating_child;
258 bool is_transient_for = false;
259 while (transient_con != NULL &&
260 transient_con->window != NULL &&
261 transient_con->window->transient_for != XCB_NONE) {
262 DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, fullscreen_id = 0x%08x\n",
263 transient_con->window->id, transient_con->window->transient_for, fullscreen->window->id);
264 if (transient_con->window->transient_for == fullscreen->window->id) {
265 is_transient_for = true;
266 break;
267 }
268 Con *next_transient = con_by_window_id(transient_con->window->transient_for);
269 if (next_transient == NULL)
270 break;
271 /* Some clients (e.g. x11-ssh-askpass) actually set
272 * WM_TRANSIENT_FOR to their own window id, so break instead of
273 * looping endlessly. */
274 if (transient_con == next_transient)
275 break;
276 transient_con = next_transient;
277 }
278
279 if (!is_transient_for)
280 continue;
281 else {
257 if (con_find_transient_for_window(floating_child, fullscreen->window->id)) {
282258 DLOG("Rendering floating child even though in fullscreen mode: "
283259 "floating->transient_for (0x%08x) --> fullscreen->id (0x%08x)\n",
284260 floating_child->window->transient_for, fullscreen->window->id);
261 } else {
262 continue;
285263 }
286264 }
287265 DLOG("floating child at (%d,%d) with %d x %d\n",
133133 draw_util_clear_surface(&(state->surface), background);
134134
135135 // TODO: make i3font functions per-connection, at least these two for now…?
136 xcb_flush(restore_conn);
137136 xcb_aux_sync(restore_conn);
138137
139138 Match *swallows;
152151 APPEND_REGEX(instance);
153152 APPEND_REGEX(window_role);
154153 APPEND_REGEX(title);
154 APPEND_REGEX(machine);
155155
156156 if (serialized == NULL) {
157157 DLOG("This swallows specification is not serializable?!\n");
178178 int y = (state->rect.height / 2) - (config.font.height / 2);
179179 draw_util_text(line, &(state->surface), foreground, background, x, y, text_width);
180180 i3string_free(line);
181 xcb_flush(restore_conn);
182181 xcb_aux_sync(restore_conn);
183182 }
184183
0 /*
1 * vim:ts=4:sw=4:expandtab
2 *
3 * i3 - an improved dynamic tiling window manager
4 * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
5 *
6 * tiling_drag.c: Reposition tiled windows by dragging.
7 *
8 */
9 #include "all.h"
10 static xcb_window_t create_drop_indicator(Rect rect);
11
12 /*
13 * Includes decoration (container title) to the container's rect. This way we
14 * can find the correct drop target if the mouse is on a container's
15 * decoration.
16 *
17 */
18 static Rect con_rect_plus_deco_height(Con *con) {
19 Rect rect = con->rect;
20 rect.height += con->deco_rect.height;
21 if (rect.y < con->deco_rect.height) {
22 rect.y = 0;
23 } else {
24 rect.y -= con->deco_rect.height;
25 }
26 return rect;
27 }
28
29 static bool is_tiling_drop_target(Con *con) {
30 if (!con_has_managed_window(con) ||
31 con_is_floating(con) ||
32 con_is_hidden(con)) {
33 return false;
34 }
35 Con *ws = con_get_workspace(con);
36 if (con_is_internal(ws)) {
37 /* Skip containers on i3-internal containers like the scratchpad, which are
38 technically visible on their pseudo-output. */
39 return false;
40 }
41 if (!workspace_is_visible(ws)) {
42 return false;
43 }
44 Con *fs = con_get_fullscreen_covering_ws(ws);
45 if (fs != NULL && fs != con) {
46 /* Workspace is visible, but con is not visible because some other
47 container is in fullscreen. */
48 return false;
49 }
50 return true;
51 }
52
53 /*
54 * Returns whether there currently are any drop targets.
55 * Used to only initiate a drag when there is something to drop onto.
56 *
57 */
58 bool has_drop_targets(void) {
59 int drop_targets = 0;
60 Con *con;
61 TAILQ_FOREACH (con, &all_cons, all_cons) {
62 if (!is_tiling_drop_target(con)) {
63 continue;
64 }
65 drop_targets++;
66 }
67
68 /* In addition to tiling containers themselves, an visible but empty
69 * workspace (in a multi-monitor scenario) also is a drop target. */
70 Con *output;
71 TAILQ_FOREACH (output, &(croot->focus_head), focused) {
72 if (con_is_internal(output)) {
73 continue;
74 }
75 Con *visible_ws = NULL;
76 GREP_FIRST(visible_ws, output_get_content(output), workspace_is_visible(child));
77 if (visible_ws != NULL && con_num_children(visible_ws) == 0) {
78 drop_targets++;
79 }
80 }
81
82 return drop_targets > 1;
83 }
84
85 /*
86 * Return an appropriate target at given coordinates.
87 *
88 */
89 static Con *find_drop_target(uint32_t x, uint32_t y) {
90 Con *con;
91 TAILQ_FOREACH (con, &all_cons, all_cons) {
92 Rect rect = con_rect_plus_deco_height(con);
93 if (!rect_contains(rect, x, y) ||
94 !is_tiling_drop_target(con)) {
95 continue;
96 }
97 Con *ws = con_get_workspace(con);
98 Con *fs = con_get_fullscreen_covering_ws(ws);
99 return fs ? fs : con;
100 }
101
102 /* Couldn't find leaf container, get a workspace. */
103 Output *output = get_output_containing(x, y);
104 if (!output) {
105 return NULL;
106 }
107 Con *content = output_get_content(output->con);
108 /* Still descend because you can drag to the bar on an non-empty workspace. */
109 return con_descend_tiling_focused(content);
110 }
111
112 typedef enum { DT_SIBLING,
113 DT_CENTER,
114 DT_PARENT
115 } drop_type_t;
116
117 struct callback_params {
118 xcb_window_t *indicator;
119 Con **target;
120 direction_t *direction;
121 drop_type_t *drop_type;
122 };
123
124 static Rect adjust_rect(Rect rect, direction_t direction, uint32_t threshold) {
125 switch (direction) {
126 case D_LEFT:
127 rect.width = threshold;
128 break;
129 case D_UP:
130 rect.height = threshold;
131 break;
132 case D_RIGHT:
133 rect.x += (rect.width - threshold);
134 rect.width = threshold;
135 break;
136 case D_DOWN:
137 rect.y += (rect.height - threshold);
138 rect.height = threshold;
139 break;
140 }
141 return rect;
142 }
143
144 static bool con_on_side_of_parent(Con *con, direction_t direction) {
145 const orientation_t orientation = orientation_from_direction(direction);
146 direction_t reverse_direction;
147 switch (direction) {
148 case D_LEFT:
149 reverse_direction = D_RIGHT;
150 break;
151 case D_RIGHT:
152 reverse_direction = D_LEFT;
153 break;
154 case D_UP:
155 reverse_direction = D_DOWN;
156 break;
157 case D_DOWN:
158 reverse_direction = D_UP;
159 break;
160 }
161 return (con_orientation(con->parent) != orientation ||
162 con->parent->layout == L_STACKED || con->parent->layout == L_TABBED ||
163 con_descend_direction(con->parent, reverse_direction) == con);
164 }
165
166 /*
167 * The callback that is executed on every mouse move while dragging. On each
168 * invocation we determine the drop target and the direction in which to insert
169 * the dragged container. The indicator window is updated to show the new
170 * position of the dragged container. The target container and direction are
171 * passed out using the callback params.
172 *
173 */
174 DRAGGING_CB(drag_callback) {
175 /* 30% of the container (minus the parent indicator) is used to drop the
176 * dragged container as a sibling to the target */
177 const double sibling_indicator_percent_of_rect = 0.3;
178 /* Use the base decoration height and add a few pixels. This makes the
179 * outer indicator generally thin but at least thick enough to cover
180 * container titles */
181 const double parent_indicator_max_size = render_deco_height() + logical_px(5);
182
183 Con *target = find_drop_target(new_x, new_y);
184 if (target == NULL) {
185 return;
186 }
187
188 Rect rect = con_rect_plus_deco_height(target);
189
190 direction_t direction = 0;
191 drop_type_t drop_type = DT_CENTER;
192 bool draw_window = true;
193 const struct callback_params *params = extra;
194
195 if (target->type == CT_WORKSPACE) {
196 goto create_indicator;
197 }
198
199 /* Define the thresholds in pixels. The drop type depends on the cursor
200 * position. */
201 const uint32_t min_rect_dimension = min(rect.width, rect.height);
202 const uint32_t sibling_indicator_size = max(logical_px(2), (uint32_t)(sibling_indicator_percent_of_rect * min_rect_dimension));
203 const uint32_t parent_indicator_size = min(
204 parent_indicator_max_size,
205 /* For small containers, start where the sibling indicator finishes.
206 * This is always at least 1 pixel. We use min() to not override the
207 * sibling indicator: */
208 sibling_indicator_size - 1);
209
210 /* Find which edge the cursor is closer to. */
211 const uint32_t d_left = new_x - rect.x;
212 const uint32_t d_top = new_y - rect.y;
213 const uint32_t d_right = rect.x + rect.width - new_x;
214 const uint32_t d_bottom = rect.y + rect.height - new_y;
215 const uint32_t d_min = min(min(d_left, d_right), min(d_top, d_bottom));
216 /* And move the container towards that direction. */
217 if (d_left == d_min) {
218 direction = D_LEFT;
219 } else if (d_top == d_min) {
220 direction = D_UP;
221 } else if (d_right == d_min) {
222 direction = D_RIGHT;
223 } else if (d_bottom == d_min) {
224 direction = D_DOWN;
225 } else {
226 /* Keep the compiler happy */
227 ELOG("min() is broken\n");
228 assert(false);
229 }
230 const bool target_parent = (d_min < parent_indicator_size &&
231 con_on_side_of_parent(target, direction));
232 const bool target_sibling = (d_min < sibling_indicator_size);
233 drop_type = target_parent ? DT_PARENT : (target_sibling ? DT_SIBLING : DT_CENTER);
234
235 /* target == con makes sense only when we are moving away from target's parent. */
236 if (drop_type != DT_PARENT && target == con) {
237 draw_window = false;
238 xcb_destroy_window(conn, *(params->indicator));
239 *(params->indicator) = 0;
240 goto create_indicator;
241 }
242
243 switch (drop_type) {
244 case DT_PARENT:
245 while (target->parent->type != CT_WORKSPACE && con_on_side_of_parent(target->parent, direction)) {
246 target = target->parent;
247 }
248 rect = adjust_rect(target->parent->rect, direction, parent_indicator_size);
249 break;
250 case DT_CENTER:
251 rect = target->rect;
252 rect.x += sibling_indicator_size;
253 rect.y += sibling_indicator_size;
254 rect.width -= sibling_indicator_size * 2;
255 rect.height -= sibling_indicator_size * 2;
256 break;
257 case DT_SIBLING:
258 rect = adjust_rect(target->rect, direction, sibling_indicator_size);
259 break;
260 }
261
262 create_indicator:
263 if (draw_window) {
264 if (*(params->indicator) == 0) {
265 *(params->indicator) = create_drop_indicator(rect);
266 } else {
267 const uint32_t values[4] = {rect.x, rect.y, rect.width, rect.height};
268 const uint32_t mask = XCB_CONFIG_WINDOW_X |
269 XCB_CONFIG_WINDOW_Y |
270 XCB_CONFIG_WINDOW_WIDTH |
271 XCB_CONFIG_WINDOW_HEIGHT;
272 xcb_configure_window(conn, *(params->indicator), mask, values);
273 }
274 }
275 x_mask_event_mask(~XCB_EVENT_MASK_ENTER_WINDOW);
276 xcb_flush(conn);
277
278 *(params->target) = target;
279 *(params->direction) = direction;
280 *(params->drop_type) = drop_type;
281 }
282
283 /*
284 * Returns a new drop indicator window with the given initial coordinates.
285 *
286 */
287 static xcb_window_t create_drop_indicator(Rect rect) {
288 uint32_t mask = 0;
289 uint32_t values[2];
290
291 mask = XCB_CW_BACK_PIXEL;
292 values[0] = config.client.focused.indicator.colorpixel;
293
294 mask |= XCB_CW_OVERRIDE_REDIRECT;
295 values[1] = 1;
296
297 xcb_window_t indicator = create_window(conn, rect, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT,
298 XCB_WINDOW_CLASS_INPUT_OUTPUT, XCURSOR_CURSOR_MOVE, false, mask, values);
299 /* Change the window class to "i3-drag", so that it can be matched in a
300 * compositor configuration. Note that the class needs to be changed before
301 * mapping the window. */
302 xcb_change_property(conn,
303 XCB_PROP_MODE_REPLACE,
304 indicator,
305 XCB_ATOM_WM_CLASS,
306 XCB_ATOM_STRING,
307 8,
308 (strlen("i3-drag") + 1) * 2,
309 "i3-drag\0i3-drag\0");
310 xcb_map_window(conn, indicator);
311 xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, indicator);
312
313 return indicator;
314 }
315
316 /*
317 * Initiates a mouse drag operation on a tiled window.
318 *
319 */
320 void tiling_drag(Con *con, xcb_button_press_event_t *event, bool use_threshold) {
321 DLOG("Start dragging tiled container: con = %p\n", con);
322 bool set_focus = (con == focused);
323 bool set_fs = con->fullscreen_mode != CF_NONE;
324
325 /* Don't change focus while dragging. */
326 x_mask_event_mask(~XCB_EVENT_MASK_ENTER_WINDOW);
327 xcb_flush(conn);
328
329 /* Indicate drop location while dragging. This blocks until the drag is completed. */
330 Con *target = NULL;
331 direction_t direction;
332 drop_type_t drop_type;
333 xcb_window_t indicator = 0;
334 const struct callback_params params = {&indicator, &target, &direction, &drop_type};
335
336 drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, XCURSOR_CURSOR_MOVE, use_threshold, drag_callback, &params);
337
338 /* Dragging is done. We don't need the indicator window any more. */
339 xcb_destroy_window(conn, indicator);
340
341 if (drag_result == DRAG_REVERT ||
342 target == NULL ||
343 (target == con && drop_type != DT_PARENT) ||
344 !con_exists(target)) {
345 DLOG("drop aborted\n");
346 return;
347 }
348
349 const orientation_t orientation = orientation_from_direction(direction);
350 const position_t position = position_from_direction(direction);
351 const layout_t layout = orientation == VERT ? L_SPLITV : L_SPLITH;
352 con_disable_fullscreen(con);
353 switch (drop_type) {
354 case DT_CENTER:
355 /* Also handles workspaces.*/
356 DLOG("drop to center of %p\n", target);
357 con_move_to_target(con, target);
358 break;
359 case DT_SIBLING:
360 DLOG("drop %s %p\n", position_to_string(position), target);
361 if (con_orientation(target->parent) != orientation) {
362 /* If con and target are the only children of the same parent, we can just change
363 * the parent's layout manually and then move con to the correct position.
364 * tree_split checks for a parent with only one child so it would create a new
365 * parent with the new layout. */
366 if (con->parent == target->parent && con_num_children(target->parent) == 2) {
367 target->parent->layout = layout;
368 } else {
369 tree_split(target, orientation);
370 }
371 }
372
373 insert_con_into(con, target, position);
374
375 ipc_send_window_event("move", con);
376 break;
377 case DT_PARENT: {
378 const bool parent_tabbed_or_stacked = (target->parent->layout == L_TABBED || target->parent->layout == L_STACKED);
379 DLOG("drop %s (%s) of %s%p\n",
380 direction_to_string(direction),
381 position_to_string(position),
382 parent_tabbed_or_stacked ? "tabbed/stacked " : "",
383 target);
384 if (parent_tabbed_or_stacked) {
385 /* When dealing with tabbed/stacked the target can be in the
386 * middle of the container. Thus, after a directional move, con
387 * will still be bound to the tabbed/stacked parent. */
388 if (position == BEFORE) {
389 target = TAILQ_FIRST(&(target->parent->nodes_head));
390 } else {
391 target = TAILQ_LAST(&(target->parent->nodes_head), nodes_head);
392 }
393 }
394 if (con != target) {
395 insert_con_into(con, target, position);
396 }
397 /* tree_move can change the focus */
398 Con *old_focus = focused;
399 tree_move(con, direction);
400 if (focused != old_focus) {
401 con_activate(old_focus);
402 }
403 break;
404 }
405 }
406 /* Warning: target might not exist anymore */
407 target = NULL;
408
409 /* Manage fullscreen status. */
410 if (set_focus || set_fs) {
411 Con *fs = con_get_fullscreen_covering_ws(con_get_workspace(con));
412 if (fs == con) {
413 ELOG("dragged container somehow got fullscreen again.\n");
414 assert(false);
415 } else if (fs && set_focus && set_fs) {
416 /* con was focused & fullscreen, disable other fullscreen container. */
417 con_disable_fullscreen(fs);
418 } else if (fs) {
419 /* con was not focused, prefer other fullscreen container. */
420 set_fs = set_focus = false;
421 } else if (!set_focus) {
422 /* con was not focused. If it was fullscreen and we are moving it to the focused
423 * workspace we must focus it. */
424 set_focus = (set_fs && con_get_workspace(focused) == con_get_workspace(con));
425 }
426 }
427 if (set_fs) {
428 con_enable_fullscreen(con, CF_OUTPUT);
429 }
430 if (set_focus) {
431 workspace_show(con_get_workspace(con));
432 con_focus(con);
433 }
434 tree_render();
435 }
176176 }
177177
178178 /*
179 * Checks if the given path exists by calling stat().
180 *
181 */
182 bool path_exists(const char *path) {
183 struct stat buf;
184 return (stat(path, &buf) == 0);
185 }
186
187 /*
188179 * Goes through the list of arguments (for exec()) and add/replace the given option,
189180 * including the option name, its argument, and the option character.
190181 */
484475 return position == BEFORE ? D_UP : D_DOWN;
485476 }
486477 }
478
479 /*
480 * Converts direction to a string representation.
481 *
482 */
483 const char *direction_to_string(direction_t direction) {
484 switch (direction) {
485 case D_LEFT:
486 return "left";
487 case D_RIGHT:
488 return "right";
489 case D_UP:
490 return "up";
491 case D_DOWN:
492 return "down";
493 }
494 return "invalid";
495 }
496
497 /*
498 * Converts position to a string representation.
499 *
500 */
501 const char *position_to_string(position_t position) {
502 switch (position) {
503 case BEFORE:
504 return "before";
505 case AFTER:
506 return "after";
507 }
508 return "invalid";
509 }
1717 void window_free(i3Window *win) {
1818 FREE(win->class_class);
1919 FREE(win->class_instance);
20 FREE(win->role);
21 FREE(win->machine);
2022 i3string_free(win->name);
23 cairo_surface_destroy(win->icon);
2124 FREE(win->ran_assignments);
2225 FREE(win);
2326 }
411414 * it is still in use by popular widget toolkits such as GTK+ and Java AWT.
412415 *
413416 */
414 void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style) {
415 /* This implementation simply mirrors Gnome's Metacity. Official
416 * documentation of this hint is nowhere to be found.
417 * For more information see:
418 * https://people.gnome.org/~tthurman/docs/metacity/xprops_8h-source.html
419 * https://stackoverflow.com/questions/13787553/detect-if-a-x11-window-has-decorations
417 bool window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style) {
418 /* See `man VendorShell' for more info, `XmNmwmDecorations' section:
419 * https://linux.die.net/man/3/vendorshell
420 * The following constants are adapted from <Xm/MwmUtil.h>.
420421 */
421422 #define MWM_HINTS_FLAGS_FIELD 0
422423 #define MWM_HINTS_DECORATIONS_FIELD 2
431432
432433 if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
433434 FREE(prop);
434 return;
435 return false;
435436 }
436437
437438 /* The property consists of an array of 5 uint32_t's. The first value is a
457458 }
458459
459460 FREE(prop);
461 return true;
460462
461463 #undef MWM_HINTS_FLAGS_FIELD
462464 #undef MWM_HINTS_DECORATIONS_FIELD
465467 #undef MWM_DECOR_BORDER
466468 #undef MWM_DECOR_TITLE
467469 }
470
471 /*
472 * Updates the WM_CLIENT_MACHINE
473 *
474 */
475 void window_update_machine(i3Window *win, xcb_get_property_reply_t *prop) {
476 if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
477 DLOG("WM_CLIENT_MACHINE not set.\n");
478 FREE(prop);
479 return;
480 }
481
482 FREE(win->machine);
483 win->machine = sstrndup((char *)xcb_get_property_value(prop), xcb_get_property_value_length(prop));
484 LOG("WM_CLIENT_MACHINE changed to \"%s\"\n", win->machine);
485
486 free(prop);
487 }
488
489 void window_update_icon(i3Window *win, xcb_get_property_reply_t *prop) {
490 uint32_t *data = NULL;
491 uint32_t width, height;
492 uint64_t len = 0;
493 const uint32_t pref_size = (uint32_t)(render_deco_height() - logical_px(2));
494
495 if (!prop || prop->type != XCB_ATOM_CARDINAL || prop->format != 32) {
496 DLOG("_NET_WM_ICON is not set\n");
497 FREE(prop);
498 return;
499 }
500
501 uint32_t prop_value_len = xcb_get_property_value_length(prop);
502 uint32_t *prop_value = (uint32_t *)xcb_get_property_value(prop);
503
504 /* Find an icon matching the preferred size.
505 * If there is no such icon, take the smallest icon having at least
506 * the preferred size.
507 * If all icons are smaller than the preferred size, chose the largest.
508 */
509 while (prop_value_len > (sizeof(uint32_t) * 2) && prop_value &&
510 prop_value[0] && prop_value[1]) {
511 const uint32_t cur_width = prop_value[0];
512 const uint32_t cur_height = prop_value[1];
513 /* Check that the property is as long as it should be (in bytes),
514 handling integer overflow. "+2" to handle the width and height
515 fields. */
516 const uint64_t cur_len = cur_width * (uint64_t)cur_height;
517 const uint64_t expected_len = (cur_len + 2) * 4;
518
519 if (expected_len > prop_value_len) {
520 break;
521 }
522
523 DLOG("Found _NET_WM_ICON of size: (%d,%d)\n", cur_width, cur_height);
524
525 const bool at_least_preferred_size = (cur_width >= pref_size &&
526 cur_height >= pref_size);
527 const bool smaller_than_current = (cur_width < width ||
528 cur_height < height);
529 const bool larger_than_current = (cur_width > width ||
530 cur_height > height);
531 const bool not_yet_at_preferred = (width < pref_size ||
532 height < pref_size);
533 if (len == 0 ||
534 (at_least_preferred_size &&
535 (smaller_than_current || not_yet_at_preferred)) ||
536 (!at_least_preferred_size &&
537 not_yet_at_preferred &&
538 larger_than_current)) {
539 len = cur_len;
540 width = cur_width;
541 height = cur_height;
542 data = prop_value;
543 }
544
545 if (width == pref_size && height == pref_size) {
546 break;
547 }
548
549 /* Find pointer to next icon in the reply. */
550 prop_value_len -= expected_len;
551 prop_value = (uint32_t *)(((uint8_t *)prop_value) + expected_len);
552 }
553
554 if (!data) {
555 DLOG("Could not get _NET_WM_ICON\n");
556 FREE(prop);
557 return;
558 }
559
560 DLOG("Using icon of size (%d,%d) (preferred size: %d)\n",
561 width, height, pref_size);
562
563 win->name_x_changed = true; /* trigger a redraw */
564
565 uint32_t *icon = smalloc(len * 4);
566
567 for (uint64_t i = 0; i < len; i++) {
568 uint8_t r, g, b, a;
569 const uint32_t pixel = data[2 + i];
570 a = (pixel >> 24) & 0xff;
571 r = (pixel >> 16) & 0xff;
572 g = (pixel >> 8) & 0xff;
573 b = (pixel >> 0) & 0xff;
574
575 /* Cairo uses premultiplied alpha */
576 r = (r * a) / 0xff;
577 g = (g * a) / 0xff;
578 b = (b * a) / 0xff;
579
580 icon[i] = ((uint32_t)a << 24) | (r << 16) | (g << 8) | b;
581 }
582
583 if (win->icon != NULL) {
584 cairo_surface_destroy(win->icon);
585 }
586 win->icon = cairo_image_surface_create_for_data(
587 (unsigned char *)icon,
588 CAIRO_FORMAT_ARGB32,
589 width,
590 height,
591 width * 4);
592 static cairo_user_data_key_t free_data;
593 cairo_surface_set_user_data(win->icon, &free_data, icon, free);
594
595 FREE(prop);
596 }
138138
139139 /* We set workspace->num to the number if this workspace’s name begins with
140140 * a positive number. Otherwise it’s a named ws and num will be 1. */
141 const long parsed_num = ws_name_to_number(num);
141 const int parsed_num = ws_name_to_number(num);
142142
143143 struct Workspace_Assignment *assignment;
144144 TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
253253 */
254254 Con *create_workspace_on_output(Output *output, Con *content) {
255255 /* add a workspace to this output */
256 char *name;
257256 bool exists = true;
258257 Con *ws = con_new(NULL, NULL);
259258 ws->type = CT_WORKSPACE;
269268 continue;
270269 }
271270
272 exists = (get_existing_workspace_by_name(target_name) != NULL);
271 const int num = ws_name_to_number(target_name);
272 exists = (num == -1)
273 ? get_existing_workspace_by_name(target_name)
274 : get_existing_workspace_by_num(num);
273275 if (!exists) {
274276 ws->name = sstrdup(target_name);
275277 /* Set ->num to the number of the workspace, if the name actually
276278 * is a number or starts with a number */
277 ws->num = ws_name_to_number(ws->name);
278 LOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
279 ws->num = num;
280 DLOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
279281
280282 break;
281283 }
305307
306308 con_attach(ws, content, false);
307309
310 char *name;
308311 sasprintf(&name, "[i3 con] workspace %s", ws->name);
309312 x_set_name(ws, name);
310313 free(name);
320323
321324 /*
322325 * Returns true if the workspace is currently visible. Especially important for
323 * multi-monitor environments, as they can have multiple currenlty active
326 * multi-monitor environments, as they can have multiple currently active
324327 * workspaces.
325328 *
326329 */
327330 bool workspace_is_visible(Con *ws) {
328331 Con *output = con_get_output(ws);
329 if (output == NULL)
332 if (output == NULL) {
330333 return false;
334 }
331335 Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
332 LOG("workspace visible? fs = %p, ws = %p\n", fs, ws);
333336 return (fs == ws);
334337 }
335338
10111014 bool used_assignment = false;
10121015 struct Workspace_Assignment *assignment;
10131016 TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
1014 bool attached;
1015 int num;
10161017 if (!output_triggers_assignment(current_output, assignment)) {
10171018 continue;
10181019 }
10191020 /* check if this workspace's name or num is already attached to the tree */
1020 num = ws_name_to_number(assignment->name);
1021 attached = ((num == -1) ? get_existing_workspace_by_name(assignment->name) : get_existing_workspace_by_num(num)) != NULL;
1021 const int num = ws_name_to_number(assignment->name);
1022 const bool attached = (num == -1)
1023 ? get_existing_workspace_by_name(assignment->name)
1024 : get_existing_workspace_by_num(num);
10221025 if (attached) {
10231026 continue;
10241027 }
489489 struct deco_render_params *p = scalloc(1, sizeof(struct deco_render_params));
490490
491491 /* find out which colors to use */
492 if (con->urgent)
492 if (con->urgent) {
493493 p->color = &config.client.urgent;
494 else if (con == focused || con_inside_focused(con))
494 } else if (con == focused || con_inside_focused(con)) {
495495 p->color = &config.client.focused;
496 else if (con == TAILQ_FIRST(&(parent->focus_head)))
497 p->color = &config.client.focused_inactive;
498 else
496 } else if (con == TAILQ_FIRST(&(parent->focus_head))) {
497 if (config.client.got_focused_tab_title && !leaf && con_descend_focused(con) == focused) {
498 /* Stacked/tabbed parent of focused container */
499 p->color = &config.client.focused_tab_title;
500 } else {
501 p->color = &config.client.focused_inactive;
502 }
503 } else {
499504 p->color = &config.client.unfocused;
505 }
500506
501507 p->border_style = con_border_style(con);
502508
536542
537543 /* 2: draw the client.background, but only for the parts around the window_rect */
538544 if (con->window != NULL) {
545 /* Clear visible windows before beginning to draw */
546 draw_util_clear_surface(&(con->frame_buffer), (color_t){.red = 0.0, .green = 0.0, .blue = 0.0});
547
539548 /* top area */
540549 draw_util_rectangle(&(con->frame_buffer), config.client.background,
541550 0, 0, r->width, w->y);
608617 /* 5: draw title border */
609618 x_draw_title_border(con, p);
610619
611 /* 6: draw the title */
620 /* 6: draw the icon and title */
612621 int text_offset_y = (con->deco_rect.height - config.font.height) / 2;
613622
623 struct Window *win = con->window;
624
625 const int deco_width = (int)con->deco_rect.width;
614626 const int title_padding = logical_px(2);
615 const int deco_width = (int)con->deco_rect.width;
627
616628 int mark_width = 0;
617629 if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) {
618630 char *formatted_mark = sstrdup("");
651663 }
652664
653665 i3String *title = NULL;
654 struct Window *win = con->window;
655666 if (win == NULL) {
656667 if (con->title_format == NULL) {
657668 char *_title;
671682 goto copy_pixmaps;
672683 }
673684
685 /* icon_padding is applied horizontally only, the icon will always use all
686 * available vertical space. */
687 int icon_size = max(0, con->deco_rect.height - logical_px(2));
688 int icon_padding = logical_px(max(1, con->window_icon_padding));
689 int total_icon_space = icon_size + 2 * icon_padding;
690 const bool has_icon = (con->window_icon_padding > -1) && win && win->icon && (total_icon_space < deco_width);
691 if (!has_icon) {
692 icon_size = icon_padding = total_icon_space = 0;
693 }
694 /* Determine x offsets according to title alignment */
695 int icon_offset_x;
674696 int title_offset_x;
675697 switch (config.title_align) {
676698 case ALIGN_LEFT:
677 /* (pad)[text ](pad)[mark + its pad) */
678 title_offset_x = title_padding;
699 /* (pad)[(pad)(icon)(pad)][text ](pad)[mark + its pad)
700 * ^ ^--- title_offset_x
701 * ^--- icon_offset_x */
702 icon_offset_x = icon_padding;
703 title_offset_x = title_padding + total_icon_space;
679704 break;
680705 case ALIGN_CENTER:
681 /* (pad)[ text ](pad)[mark + its pad)
682 * To center the text inside its allocated space, the surface
683 * between the brackets, we use the formula
684 * (surface_width - predict_text_width) / 2
685 * where surface_width = deco_width - 2 * pad - mark_width
686 * so, offset = pad + (surface_width - predict_text_width) / 2 =
687 * = … = (deco_width - mark_width - predict_text_width) / 2 */
688 title_offset_x = max(title_padding, (deco_width - mark_width - predict_text_width(title)) / 2);
706 /* (pad)[ ][(pad)(icon)(pad)][text ](pad)[mark + its pad)
707 * ^ ^--- title_offset_x
708 * ^--- icon_offset_x
709 * Text should come right after the icon (+padding). We calculate
710 * the offset for the icon (white space in the title) by dividing
711 * by two the total available area. That's the decoration width
712 * minus the elements that come after icon_offset_x (icon, its
713 * padding, text, marks). */
714 icon_offset_x = max(icon_padding, (deco_width - icon_padding - icon_size - predict_text_width(title) - title_padding - mark_width) / 2);
715 title_offset_x = max(title_padding, icon_offset_x + icon_padding + icon_size);
689716 break;
690717 case ALIGN_RIGHT:
691 /* [mark + its pad](pad)[ text](pad) */
692 title_offset_x = max(title_padding + mark_width, deco_width - title_padding - predict_text_width(title));
718 /* [mark + its pad](pad)[ text][(pad)(icon)(pad)](pad)
719 * ^ ^--- icon_offset_x
720 * ^--- title_offset_x */
721 title_offset_x = max(title_padding + mark_width, deco_width - title_padding - predict_text_width(title) - total_icon_space);
722 /* Make sure the icon does not escape title boundaries */
723 icon_offset_x = min(deco_width - icon_size - icon_padding - title_padding, title_offset_x + predict_text_width(title) + icon_padding);
693724 break;
694725 }
695726
697728 p->color->text, p->color->background,
698729 con->deco_rect.x + title_offset_x,
699730 con->deco_rect.y + text_offset_y,
700 deco_width - mark_width - 2 * title_padding);
731 deco_width - mark_width - 2 * title_padding - total_icon_space);
732 if (has_icon) {
733 draw_util_image(
734 win->icon,
735 &(parent->frame_buffer),
736 con->deco_rect.x + icon_offset_x,
737 con->deco_rect.y + logical_px(1),
738 icon_size,
739 icon_size);
740 }
701741
702742 if (win == NULL || con->title_format != NULL) {
703743 I3STRING_FREE(title);
840880 con_state *state;
841881 Rect rect = con->rect;
842882
843 //DLOG("Pushing changes for node %p / %s\n", con, con->name);
844883 state = state_for_frame(con->frame.id);
845884
846885 if (state->name != NULL) {
953992 win_depth = con->window->depth;
954993
955994 /* Ensure we have valid dimensions for our surface. */
956 // TODO This is probably a bug in the condition above as we should never enter this path
957 // for height == 0. Also, we should probably handle width == 0 the same way.
995 /* TODO: This is probably a bug in the condition above as we should
996 * never enter this path for height == 0. Also, we should probably
997 * handle width == 0 the same way. */
958998 int width = MAX((int32_t)rect.width, 1);
959999 int height = MAX((int32_t)rect.height, 1);
9601000
9611001 xcb_create_pixmap(conn, win_depth, con->frame_buffer.id, con->frame.id, width, height);
9621002 draw_util_surface_init(conn, &(con->frame_buffer), con->frame_buffer.id,
9631003 get_visualtype_by_id(get_visualid_by_depth(win_depth)), width, height);
1004 draw_util_clear_surface(&(con->frame_buffer), (color_t){.red = 0.0, .green = 0.0, .blue = 0.0});
9641005
9651006 /* For the graphics context, we disable GraphicsExposure events.
9661007 * Those will be sent when a CopyArea request cannot be fulfilled
9731014 con->pixmap_recreated = true;
9741015
9751016 /* Don’t render the decoration for windows inside a stack which are
976 * not visible right now */
977 // TODO Should this work the same way for L_TABBED?
1017 * not visible right now
1018 * TODO: Should this work the same way for L_TABBED? */
9781019 if (!con->parent ||
9791020 con->parent->layout != L_STACKED ||
9801021 TAILQ_FIRST(&(con->parent->focus_head)) == con)
10851126 Con *current;
10861127 con_state *state;
10871128
1088 //DLOG("Pushing changes (with unmaps) for node %p / %s\n", con, con->name);
10891129 state = state_for_frame(con->frame.id);
10901130
10911131 /* map/unmap if map state changed, also ensure that the child window
11611201 }
11621202
11631203 DLOG("-- PUSHING WINDOW STACK --\n");
1164 //DLOG("Disabling EnterNotify\n");
11651204 /* We need to keep SubstructureRedirect around, otherwise clients can send
11661205 * ConfigureWindow requests and get them applied directly instead of having
11671206 * them become ConfigureRequests that i3 handles. */
11701209 if (state->mapped)
11711210 xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
11721211 }
1173 //DLOG("Done, EnterNotify disabled\n");
11741212 bool order_changed = false;
11751213 bool stacking_changed = false;
11761214
12001238 if (con_has_managed_window(state->con))
12011239 memcpy(walk++, &(state->con->window->id), sizeof(xcb_window_t));
12021240
1203 //DLOG("stack: 0x%08x\n", state->id);
12041241 con_state *prev = CIRCLEQ_PREV(state, state);
12051242 con_state *old_prev = CIRCLEQ_PREV(state, old_state);
12061243 if (prev != old_prev)
12071244 order_changed = true;
12081245 if ((state->initial || order_changed) && prev != CIRCLEQ_END(&state_head)) {
12091246 stacking_changed = true;
1210 //DLOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id);
12111247 uint32_t mask = 0;
12121248 mask |= XCB_CONFIG_WINDOW_SIBLING;
12131249 mask |= XCB_CONFIG_WINDOW_STACK_MODE;
12601296 warp_to = NULL;
12611297 }
12621298
1263 //DLOG("Re-enabling EnterNotify\n");
12641299 values[0] = FRAME_EVENT_MASK;
12651300 CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) {
12661301 if (state->mapped)
12671302 xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values);
12681303 }
1269 //DLOG("Done, EnterNotify re-enabled\n");
12701304
12711305 x_deco_recurse(con);
12721306
13521386 CIRCLEQ_REMOVE(&old_state_head, state, old_state);
13531387 CIRCLEQ_INSERT_TAIL(&old_state_head, state, old_state);
13541388 }
1355 //CIRCLEQ_FOREACH(state, &old_state_head, old_state) {
1356 // DLOG("old stack: 0x%08x\n", state->id);
1357 //}
13581389
13591390 xcb_flush(conn);
13601391 }
13671398 void x_raise_con(Con *con) {
13681399 con_state *state;
13691400 state = state_for_frame(con->frame.id);
1370 //DLOG("raising in new stack: %p / %s / %s / xid %08x\n", con, con->name, con->window ? con->window->name_json : "", state->id);
13711401
13721402 CIRCLEQ_REMOVE(&state_head, state, state);
13731403 CIRCLEQ_INSERT_HEAD(&state_head, state, state);
14171447 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_PID, XCB_ATOM_CARDINAL, 32, 1, &pid);
14181448 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8,
14191449 strlen(current_configpath), current_configpath);
1450 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_LOG_STREAM_SOCKET_PATH, A_UTF8_STRING, 8,
1451 strlen(current_log_stream_socket_path), current_log_stream_socket_path);
14201452 update_shmlog_atom();
14211453 }
14221454
432432 bound = true;
433433 /* Let the user know bind() was successful, so that they know the
434434 * error messages can be disregarded. */
435 fprintf(stderr, "Successfuly bound to %s\n", addr.sun_path);
435 fprintf(stderr, "Successfully bound to %s\n", addr.sun_path);
436436 sun_path = sstrdup(addr.sun_path);
437437 break;
438438 }
4141 exit_forcefully
4242 workspace_exists
4343 focused_ws
44 focused_output
4445 get_socket_path
4546 launch_with_config
4647 get_i3_log
409410
410411 cmd 'focus output fake-1';
411412 cmd 'workspace 1';
412 is(get_output_for_workspace('1', 'fake-0', 'Workspace 1 in output fake-0');
413 is(get_output_for_workspace('1'), 'fake-0', 'Workspace 1 in output fake-0');
413414
414415 =cut
415416 sub get_output_for_workspace {
418419 my $tree = $i3->get_tree->recv;
419420 my @outputs = @{$tree->{nodes}};
420421
421 foreach (grep { not $_->{name} =~ /^__/ } @outputs) {
422 my $output = $_->{name};
423 foreach (grep { $_->{name} =~ "content" } @{$_->{nodes}}) {
424 return $output if $_->{nodes}[0]->{name} =~ $ws_name;
422 for my $output (@outputs) {
423 next if $output->{name} eq '__i3';
424 # get the first CT_CON of each output
425 my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
426 if (grep {$_->{name} eq $ws_name} @{$content->{nodes}}){
427 return $output->{name};
425428 }
426429 }
427430 }
661664 (scalar grep { $_ eq $name } @{get_workspace_names()}) > 0;
662665 }
663666
664 =head2 focused_ws
665
666 Returns the name of the currently focused workspace.
667
668 my $ws = focused_ws;
669 is($ws, '1', 'i3 starts on workspace 1');
670
671 =cut
672 sub focused_ws {
667 =head2 focused_output
668
669 Returns the name of the currently focused output.
670
671 is(focused_output, 'fake-0', 'i3 starts on output 0');
672
673 =cut
674 sub _focused_output {
673675 my $i3 = i3(get_socket_path());
674676 my $tree = $i3->get_tree->recv;
675677 my $focused = $tree->{focus}->[0];
676678 my $output = first { $_->{id} == $focused } @{$tree->{nodes}};
679 return $output;
680 }
681
682 sub focused_output {
683 return _focused_output->{name}
684 }
685
686 =head2 focused_ws
687
688 Returns the name of the currently focused workspace.
689
690 my $ws = focused_ws;
691 is($ws, '1', 'i3 starts on workspace 1');
692
693 =cut
694
695 sub focused_ws {
696 my $output = _focused_output;
677697 my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
678698 my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}};
679699 return $first->{name}
777797 my ($pid, $socketpath) = @_;
778798 $socketpath ||= get_socket_path();
779799
800 $SIG{CHLD} = undef;
801
780802 my $exited = 0;
781803 eval {
782804 say "Exiting i3 cleanly...";
814836 sub exit_forcefully {
815837 my ($pid, $signal) = @_;
816838 $signal ||= 'TERM';
839
840 $SIG{CHLD} = undef;
817841
818842 # Send the given signal to the i3 instance and wait for up to 10s
819843 # for it to terminate.
938962 return ${^CHILD_ERROR_NATIVE};
939963 }
940964
965 $SIG{CHLD} = sub {
966 # don't change $! and $? outside handler
967 local ($!, $?);
968
969 my $child = waitpid -1, POSIX::WNOHANG;
970 warn "SIGCHLD, waitpid() = $child";
971 if ($child == $i3_pid) {
972 warn "i3 died, exiting!";
973 exit 1;
974 }
975 };
976
941977 # force update of the cached socket path in lib/i3test
942978 # as soon as i3 has started
943979 $cv->cb(sub { get_socket_path(0) });
9691005 # Sync in case not all windows are managed by i3 just yet.
9701006 sync_with_i3;
9711007 cmd '[title=".*"] kill';
1008 # Sync to make sure x_window_kill() calls have taken effect.
1009 sync_with_i3;
9721010 }
9731011
9741012 =head2 events_for($subscribecb, [ $rettype ], [ $eventcbs ])
7272 workspace_layout => 'default',
7373 current_border_width => -1,
7474 marks => $ignore,
75 window_icon_padding => -1,
7576 };
7677
7778 # a shallow copy is sufficient, since we only ignore values at the root
1313 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
1414 # (unless you are already familiar with Perl)
1515 #
16 # Tests whether we can switch to a non-existant workspace
16 # Tests whether we can switch to a non-existent workspace
1717 # (necessary for further tests)
1818 #
1919 use List::Util qw(first);
266266 like($output->[3], qr|^bindsym Mod1\+f resize grow down 23 px$|, 'resize bottom changed');
267267
268268 #####################################################################
269 # also resizing, but with indention this time
269 # also resizing, but with indentation this time
270270 #####################################################################
271271
272272 like($output->[4], qr|^bindsym Mod1\+f resize grow left 10 px$|, 'resize left changed');
5353 'move workspace foobar; ' .
5454 'move workspace torrent; ' .
5555 'move workspace to output LVDS1; ' .
56 'move to output LVDS1 DVI1; ' .
5657 'move workspace 3: foobar; ' .
5758 'move workspace "3: foobar"; ' .
5859 'move workspace "3: foobar, baz"; '),
6162 "cmd_move_con_to_workspace_name(3, (null))\n" .
6263 "cmd_move_con_to_workspace_name(foobar, (null))\n" .
6364 "cmd_move_con_to_workspace_name(torrent, (null))\n" .
64 "cmd_move_workspace_to_output(LVDS1)\n" .
65 "cmd_move_con_to_output(LVDS1, 1)\n" .
66 "cmd_move_con_to_output(NULL, 1)\n" .
67 "cmd_move_con_to_output(LVDS1, 0)\n" .
68 "cmd_move_con_to_output(DVI1, 0)\n" .
69 "cmd_move_con_to_output(NULL, 0)\n" .
6570 "cmd_move_con_to_workspace_name(3: foobar, (null))\n" .
6671 "cmd_move_con_to_workspace_name(3: foobar, (null))\n" .
6772 "cmd_move_con_to_workspace_name(3: foobar, baz, (null))",
170175 scratchpad
171176 swap
172177 title_format
178 title_window_icon
173179 mode
174180 bar
175181 gaps
475475 ################################################################################
476476
477477 $config = <<'EOT';
478 client.focused #4c7899 #285577 #ffffff #2e9ef4 #b34d4c
479 client.focused_inactive #333333 #5f676a #ffffff #484e50
480 client.unfocused #333333 #222222 #888888 #292d2e
481 client.urgent #2f343a #900000 #ffffff #900000 #c00000
482 client.placeholder #000000 #0c0c0c #ffffff #000000
478 client.focused #4c7899 #285577 #ffffff #2e9ef4 #b34d4c
479 client.focused_inactive #333333 #5f676a #ffffff #484e50
480 client.focused_tab_title #444444 #555555 #ffffff
481 client.unfocused #333333 #222222 #888888 #292d2e
482 client.urgent #2f343a #900000 #ffffff #900000 #c00000
483 client.placeholder #000000 #0c0c0c #ffffff #000000
483484 EOT
484485
485486 $expected = <<'EOT';
486487 cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4, #b34d4c)
487488 cfg_color(client.focused_inactive, #333333, #5f676a, #ffffff, #484e50, NULL)
489 cfg_color(client.focused_tab_title, #444444, #555555, #ffffff, NULL, NULL)
488490 cfg_color(client.unfocused, #333333, #222222, #888888, #292d2e, NULL)
489491 cfg_color(client.urgent, #2f343a, #900000, #ffffff, #900000, #c00000)
490492 cfg_color(client.placeholder, #000000, #0c0c0c, #ffffff, #000000, NULL)
505507
506508 my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '#', '" . join("', '", 'set ', 'set ', qw(
507509 set_from_resource
510 include
508511 bindsym
509512 bindcode
510513 bind
548551 ipc_kill_timeout
549552 restart_state
550553 popup_during_fullscreen
554 tiling_drag
551555 exec_always
552556 exec
553557 client.background
554558 client.focused_inactive
559 client.focused_tab_title
555560 client.focused
556561 client.unfocused
557562 client.urgent
7070 cmd 'mark left';
7171
7272 #
73 # get_marks replys an array of marks, whose order is undefined,
73 # get_marks replies an array of marks, whose order is undefined,
7474 # so we use sort to be able to compare the output
7575 #
7676
1717 # i3 config file (v4)
1818 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
1919
20 # fake-1 under fake-0 to not interfere with left/right wraping
20 # fake-1 under fake-0 to not interfere with left/right wrapping
2121 fake-outputs 1024x768+0+0,1024x768+0+1024
2222 workspace X output fake-1
2323 EOT
5555 my $focus = AnyEvent->condvar;
5656
5757 my @events = events_for(
58 sub { cmd $cmd },
58 sub {
59 cmd $cmd;
60 # Sync to make sure x_window_kill() calls have taken effect.
61 sync_with_i3;
62 },
5963 'window');
6064
6165 is(scalar @events, 1, 'Received 1 event');
6161 font \
6262 -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
6363
64 # Use line contiuation with too many lines (>4096 characters).
64 # Use line continuation with too many lines (>4096 characters).
6565 # This config is invalid. Use it to ensure no buffer overflow.
6666 bindsym Mod1+b \
6767 0001-This is a very very very very very very very very very very very very very very very very very long cmd \
4040 is_num_children($ws_bottom_left, 0, 'no containers on the lower left workspace');
4141 is_num_children($ws_bottom_right, 1, 'one container on the lower right workspace');
4242
43 # Also test with multiple explicit outputs
44 cmd '[class="moveme"] move window to output fake-1 fake-2';
45
46 is_num_children($ws_top_left, 0, 'no containers on the upper left workspace');
47 is_num_children($ws_top_right, 1, 'one container on the upper right workspace');
48 is_num_children($ws_bottom_left, 1, 'one container on the lower left workspace');
49 is_num_children($ws_bottom_right, 0, 'no containers on the lower right workspace');
50
4351 done_testing;
2020 #
2121 # Ticket: #2318
2222 # Bug still in: 4.12-46-g2123888
23 use i3test;
23 use i3test i3_autostart => 0;
24
25 my $pid = launch_with_config('-default');
2426
2527 # We cannot use events_for in this test as we cannot send events after
2628 # issuing the restart/shutdown command.
5860 }
5961 })->recv;
6062
61 cmd 'exit';
63 exit_gracefully($pid);
6264
6365 $e = $cv->recv;
6466
1313 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
1414 # (unless you are already familiar with Perl)
1515 #
16 # Verify that the corrent focus stack order is preserved after various
16 # Verify that the current focus stack order is preserved after various
1717 # operations.
1818 use i3test i3_config => <<EOT;
1919 # i3 config file (v4)
4242 * | C |
4343 * +-------+
4444 *
45 * - Zone A is completly opaque.
45 * - Zone A is completely opaque.
4646 * - Zone B is clickable through (input shape).
47 * - Zone C is completly transparent (bounding shape).
47 * - Zone C is completely transparent (bounding shape).
4848 */
4949 void set_shape(long window_id) {
5050 xcb_rectangle_t bounding_rectangle = { 0, 0, 100, 50 };
2121 # i3 config file (v4)
2222 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
2323
24 # fake-1 under fake-0 to not interfere with left/right wraping
24 # fake-1 under fake-0 to not interfere with left/right wrapping
2525 fake-outputs 1024x768+0+0,1024x768+0+1024
2626 workspace X output fake-1
2727 EOT
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Verifies i3 does not crash when using layout default.
17 # Ticket: #4408
18 # Bug still in: 4.19.2-83-gc8158875
19 use i3test;
20
21 cmd 'layout default';
22 fresh_workspace;
23
24 done_testing;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Verifies the include directive.
17
18 use File::Temp qw(tempfile tempdir);
19 use File::Basename qw(basename);
20 use i3test i3_autostart => 0;
21
22 # starts i3 with the given config, opens a window, returns its border style
23 sub launch_get_border {
24 my ($config) = @_;
25
26 my $pid = launch_with_config($config);
27
28 my $i3 = i3(get_socket_path(0));
29 my $tmp = fresh_workspace;
30
31 my $window = open_window(name => 'special title');
32
33 my @content = @{get_ws_content($tmp)};
34 cmp_ok(@content, '==', 1, 'one node on this workspace now');
35 my $border = $content[0]->{border};
36
37 exit_gracefully($pid);
38
39 return $border;
40 }
41
42 #####################################################################
43 # test thet windows get the default border
44 #####################################################################
45
46 my $config = <<EOT;
47 # i3 config file (v4)
48 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
49 EOT
50
51 is(launch_get_border($config), 'normal', 'normal border');
52
53 #####################################################################
54 # now use a variable and for_window
55 #####################################################################
56
57 my ($fh, $filename) = tempfile(UNLINK => 1);
58 my $varconfig = <<'EOT';
59 set $vartest special title
60 for_window [title="$vartest"] border none
61 EOT
62 print $fh $varconfig;
63 $fh->flush;
64
65 $config = <<EOT;
66 # i3 config file (v4)
67 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
68
69 include $filename
70 EOT
71
72 is(launch_get_border($config), 'none', 'no border');
73
74 ################################################################################
75 # nested includes
76 ################################################################################
77
78 my ($indirectfh, $indirectfilename) = tempfile(UNLINK => 1);
79 print $indirectfh <<EOT;
80 include $filename
81 EOT
82 $indirectfh->flush;
83
84 $config = <<EOT;
85 # i3 config file (v4)
86 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
87
88 include $indirectfilename
89 EOT
90
91 is(launch_get_border($config), 'none', 'no border');
92
93 ################################################################################
94 # nested includes with relative paths
95 ################################################################################
96
97 my $relative = basename($filename);
98 my ($indirectfh2, $indirectfilename2) = tempfile(UNLINK => 1);
99 print $indirectfh2 <<EOT;
100 include $relative
101 EOT
102 $indirectfh2->flush;
103
104 $config = <<EOT;
105 # i3 config file (v4)
106 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
107
108 include $indirectfilename2
109 EOT
110
111 is(launch_get_border($config), 'none', 'no border');
112
113 ################################################################################
114 # command substitution
115 ################################################################################
116
117 $config = <<EOT;
118 # i3 config file (v4)
119 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
120
121 include `echo $filename`
122 EOT
123
124 is(launch_get_border($config), 'none', 'no border');
125
126 ################################################################################
127 # failing command substitution
128 ################################################################################
129
130 $config = <<'EOT';
131 # i3 config file (v4)
132 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
133
134 include i3-`false`.conf
135
136 set $vartest special title
137 for_window [title="$vartest"] border none
138 EOT
139
140 is(launch_get_border($config), 'none', 'no border');
141
142 ################################################################################
143 # permission denied
144 ################################################################################
145
146 my ($permissiondeniedfh, $permissiondenied) = tempfile(UNLINK => 1);
147 $permissiondeniedfh->flush;
148 my $mode = 0055;
149 chmod($mode, $permissiondenied);
150
151 $config = <<EOT;
152 # i3 config file (v4)
153 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
154
155 include $permissiondenied
156 include $filename
157 EOT
158
159 is(launch_get_border($config), 'none', 'no border');
160
161 ################################################################################
162 # dangling symlink
163 ################################################################################
164
165 my ($danglingfh, $dangling) = tempfile(UNLINK => 1);
166 unlink($dangling);
167 symlink("/dangling", $dangling);
168
169 $config = <<EOT;
170 # i3 config file (v4)
171 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
172
173 include $dangling
174 set \$vartest special title
175 for_window [title="\$vartest"] border none
176 EOT
177
178 is(launch_get_border($config), 'none', 'no border');
179
180 ################################################################################
181 # variables defined in the main file and used in the included file
182 ################################################################################
183
184 my ($varfh, $var) = tempfile(UNLINK => 1);
185 print $varfh <<'EOT';
186 for_window [title="$vartest"] border none
187
188 EOT
189 $varfh->flush;
190
191 $config = <<EOT;
192 # i3 config file (v4)
193 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
194
195 set \$vartest special title
196 include $var
197 EOT
198
199 is(launch_get_border($config), 'none', 'no border');
200
201 SKIP: {
202 skip "not implemented";
203
204 ################################################################################
205 # variables defined in the included file and used in the main file
206 ################################################################################
207
208 ($varfh, $var) = tempfile(UNLINK => 1);
209 print $varfh <<'EOT';
210 set $vartest special title
211 EOT
212 $varfh->flush;
213
214 $config = <<EOT;
215 # i3 config file (v4)
216 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
217
218 include $var
219 for_window [title="\$vartest"] border none
220 EOT
221
222 is(launch_get_border($config), 'none', 'no border');
223 }
224
225 ################################################################################
226 # workspace names are loaded in the correct order (before reorder_bindings)
227 ################################################################################
228
229 # The included config can be empty, the issue lies with calling parse_file
230 # multiple times.
231 my ($wsfh, $ws) = tempfile(UNLINK => 1);
232 $wsfh->flush;
233
234 $config = <<EOT;
235 # i3 config file (v4)
236 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
237
238 bindsym 1 workspace 1: eggs
239 bindsym Mod4+Shift+1 workspace 11: tomatoes
240
241 include $var
242 EOT
243
244 # starts i3 with the given config, opens a window, returns its border style
245 sub launch_get_workspace_name {
246 my ($config) = @_;
247
248 my $pid = launch_with_config($config);
249
250 my $i3 = i3(get_socket_path(0));
251 my $name = $i3->get_workspaces->recv->[0]->{name};
252
253 exit_gracefully($pid);
254
255 return $name;
256 }
257
258 is(launch_get_workspace_name($config), '1: eggs', 'workspace name');
259
260 ################################################################################
261 # loop prevention
262 ################################################################################
263
264 my ($loopfh1, $loopname1) = tempfile(UNLINK => 1);
265 my ($loopfh2, $loopname2) = tempfile(UNLINK => 1);
266
267 print $loopfh1 <<EOT;
268 include $loopname2
269 EOT
270 $loopfh1->flush;
271
272 print $loopfh2 <<EOT;
273 include $loopname1
274 EOT
275 $loopfh2->flush;
276
277 $config = <<EOT;
278 # i3 config file (v4)
279 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
280
281 # loop
282 include $loopname1
283
284 set \$vartest special title
285 for_window [title="\$vartest"] border none
286 EOT
287
288 is(launch_get_border($config), 'none', 'no border');
289
290 ################################################################################
291 # Verify the GET_VERSION IPC reply contains all included files
292 ################################################################################
293
294 $config = <<EOT;
295 # i3 config file (v4)
296 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
297
298 include $indirectfilename2
299 EOT
300
301 my $pid = launch_with_config($config);
302
303 my $i3 = i3(get_socket_path(0));
304 my $version = $i3->get_version()->recv;
305 my $included = $version->{included_config_file_names};
306
307 is_deeply($included, [ $indirectfilename2, $filename ], 'included config file names correct');
308
309 exit_gracefully($pid);
310
311 ################################################################################
312 # Verify the GET_CONFIG IPC reply returns the top-level config
313 ################################################################################
314
315 my $tmpdir = tempdir(CLEANUP => 1);
316 my $socketpath = $tmpdir . "/config.sock";
317 ok(! -e $socketpath, "$socketpath does not exist yet");
318
319 my ($indirectfh3, $indirectfilename3) = tempfile(UNLINK => 1);
320 my $indirectconfig = <<EOT;
321 for_window [title="\$vartest"] border none
322 include $relative
323 EOT
324 print $indirectfh3 $indirectconfig;
325 $indirectfh3->flush;
326
327 $config = <<EOT;
328 # i3 config file (v4)
329 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
330
331 set \$vartest special title
332
333 include $indirectfilename3
334
335 ipc-socket $socketpath
336 EOT
337
338 my $pid = launch_with_config($config, dont_add_socket_path => 1, dont_create_temp_dir => 1);
339
340 my $i3 = i3(get_socket_path(0));
341 my $config_reply = $i3->get_config()->recv;
342
343 is($config_reply->{config}, $config, 'GET_CONFIG returns the top-level config file');
344
345 my $included = $config_reply->{included_configs};
346 is(scalar @{$included}, 3, 'included_configs contains all 3 files');
347 is($included->[0]->{raw_contents}, $config, 'included_configs->[0]->{raw_contents} contains top-level config');
348 is($included->[1]->{raw_contents}, $indirectconfig, 'included_configs->[1]->{raw_contents} contains indirect config');
349 is($included->[2]->{raw_contents}, $varconfig, 'included_configs->[2]->{raw_contents} contains variable config');
350
351 my $indirect_replaced_config = <<EOT;
352 for_window [title="special title"] border none
353 include $relative
354 EOT
355 is($included->[1]->{variable_replaced_contents}, $indirect_replaced_config, 'included_configs->[1]->{variable_replaced_contents} contains config with variables replaced');
356
357 exit_gracefully($pid);
358
359
360 done_testing;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Verifies title_window_icon behavior.
17 use i3test i3_autostart => 0;
18
19 sub window_icon_padding {
20 my ($ws) = @_;
21 my ($nodes, $focus) = get_ws_content($ws);
22 ok(@{$nodes} == 1, 'precisely one container on workspace');
23 return $nodes->[0]->{'window_icon_padding'};
24 }
25
26 my $config = <<"EOT";
27 # i3 config file (v4)
28 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
29 EOT
30 my $pid = launch_with_config($config);
31
32 my $tmp = fresh_workspace;
33
34 cmd 'open';
35 is(window_icon_padding($tmp), -1, 'window_icon_padding defaults to -1');
36
37 cmd 'title_window_icon on';
38 isnt(window_icon_padding($tmp), -1, 'window_icon_padding no longer -1');
39
40 cmd 'title_window_icon toggle';
41 is(window_icon_padding($tmp), -1, 'window_icon_padding back to -1');
42
43 cmd 'title_window_icon toggle';
44 isnt(window_icon_padding($tmp), -1, 'window_icon_padding no longer -1 again');
45
46 cmd 'title_window_icon off';
47 is(window_icon_padding($tmp), -1, 'window_icon_padding back to -1');
48
49 cmd 'title_window_icon padding 3px';
50 is(window_icon_padding($tmp), 3, 'window_icon_padding set to 3');
51
52 cmd 'title_window_icon toggle';
53 ok(window_icon_padding($tmp) < 0, 'window_icon_padding toggled off');
54
55 cmd 'title_window_icon toggle';
56 is(window_icon_padding($tmp), 3, 'window_icon_padding toggled back to 3');
57
58 cmd 'title_window_icon toggle 5px';
59 ok(window_icon_padding($tmp) < 0, 'window_icon_padding toggled off');
60
61 cmd 'title_window_icon toggle 5px';
62 is(window_icon_padding($tmp), 5, 'window_icon_padding toggled on to 5px');
63
64 cmd 'title_window_icon toggle 5px';
65 ok(window_icon_padding($tmp) < 0, 'window_icon_padding toggled off');
66
67 cmd 'title_window_icon toggle 4px';
68 is(window_icon_padding($tmp), 4, 'window_icon_padding toggled on to 4px');
69
70 exit_gracefully($pid);
71
72 ################################################################################
73 # Verify title_window_icon can be used with for_window as expected
74 ################################################################################
75
76 $config = <<"EOT";
77 # i3 config file (v4)
78 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
79
80 for_window [class=".*"] title_window_icon padding 3px
81 EOT
82 $pid = launch_with_config($config);
83
84 $tmp = fresh_workspace;
85
86 open_window;
87 is(window_icon_padding($tmp), 3, 'window_icon_padding set to 3');
88
89 exit_gracefully($pid);
90
91 done_testing;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Tests all kinds of matching methods
17 #
18 use i3test;
19
20 my $tmp = fresh_workspace;
21
22 ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
23
24 # Open a new window
25 my $window = open_window;
26 my $content = get_ws_content($tmp);
27 ok(@{$content} == 1, 'window mapped');
28 my $win = $content->[0];
29
30 ######################################################################
31 # check that simple matching works.
32 ######################################################################
33 cmd '[all] kill';
34
35 sync_with_i3;
36
37 is_num_children($tmp, 0, 'window killed');
38
39 ######################################################################
40 # check that simple matching against multiple windows works.
41 ######################################################################
42
43 $tmp = fresh_workspace;
44
45 my $left = open_window;
46 ok($left->mapped, 'left window mapped');
47
48 my $right = open_window;
49 ok($right->mapped, 'right window mapped');
50
51 # two windows should be here
52 is_num_children($tmp, 2, 'two windows opened');
53
54 cmd '[all] kill';
55
56 sync_with_i3;
57
58 is_num_children($tmp, 0, 'two windows killed');
59
60 ######################################################################
61 # check that multiple criteria work are checked with a logical AND,
62 # not a logical OR (that is, matching is not cancelled after the first
63 # criterion matches).
64 ######################################################################
65
66 $tmp = fresh_workspace;
67
68 my $left = open_window(name => 'left');
69 ok($left->mapped, 'left window mapped');
70
71 my $right = open_window(name => 'right');
72 ok($right->mapped, 'right window mapped');
73
74 # two windows should be here
75 is_num_children($tmp, 2, 'two windows opened');
76
77 cmd '[all title="left"] kill';
78
79 sync_with_i3;
80
81 is_num_children($tmp, 1, 'one window still there');
82
83 cmd '[all] kill';
84
85 sync_with_i3;
86
87 is_num_children($tmp, 0, 'all windows killed');
88
89 done_testing;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Test that commands with more than 10 non-identified words doesn't works
17 # 10 is the magic number chosen for the stack size which is why it's used here
18 # Ticket: #2968
19 # Bug still in: 4.19.2-103-gfc65ca36
20 use i3test;
21
22 ######################################################################
23 # 1) run a long command
24 ######################################################################
25
26 my $i3 = i3(get_socket_path());
27 my $tmp = fresh_workspace;
28
29 my $floatwin = open_floating_window;
30
31
32 my ($absolute_before, $top_before) = $floatwin->rect;
33
34 cmd 'move window container to window container to window container to left';
35
36 sync_with_i3;
37
38 my ($absolute, $top) = $floatwin->rect;
39
40 is($absolute->x, ($absolute_before->x - 10), 'moved 10 px to the left');
41 is($absolute->y, $absolute_before->y, 'y not changed');
42 is($absolute->width, $absolute_before->width, 'width not changed');
43 is($absolute->height, $absolute_before->height, 'height not changed');
44
45 done_testing;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Test dragging containers.
17
18 my ($width, $height) = (1000, 500);
19
20 my $config = <<"EOT";
21 # i3 config file (v4)
22 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
23
24 focus_follows_mouse no
25 floating_modifier Mod1
26
27 # 2 side by side outputs
28 fake-outputs ${width}x${height}+0+0P,${width}x${height}+${width}+0
29
30 bar {
31 output primary
32 }
33 EOT
34 use i3test i3_autostart => 0;
35 use i3test::XTEST;
36 my $pid = launch_with_config($config);
37
38 sub start_drag {
39 my ($pos_x, $pos_y) = @_;
40 die "Drag outside of bounds!" unless $pos_x < $width * 2 && $pos_y < $height;
41
42 $x->root->warp_pointer($pos_x, $pos_y);
43 sync_with_i3;
44
45 xtest_key_press(64); # Alt_L
46 xtest_button_press(1, $pos_x, $pos_y);
47 xtest_sync_with_i3;
48 }
49
50 sub end_drag {
51 my ($pos_x, $pos_y) = @_;
52 die "Drag outside of bounds!" unless $pos_x < $width * 2 && $pos_y < $height;
53
54 $x->root->warp_pointer($pos_x, $pos_y);
55 sync_with_i3;
56
57 xtest_button_release(1, $pos_x, $pos_y);
58 xtest_key_release(64); # Alt_L
59 xtest_sync_with_i3;
60 }
61
62 my ($ws1, $ws2);
63 my ($A, $B, $tmp);
64 my ($A_id, $B_id);
65
66 sub move_subtest {
67 my ($cb, $win) = @_;
68
69 my @events = events_for($cb, 'window');
70 my @move = grep { $_->{change} eq 'move' } @events;
71
72 is(scalar @move, 1, 'Received 1 window::move event');
73 is($move[0]->{container}->{window}, $A->{id}, "window id matches");
74 }
75
76 ###############################################################################
77 # Drag floating container onto an empty workspace.
78 ###############################################################################
79
80 $ws2 = fresh_workspace(output => 1);
81 $ws1 = fresh_workspace(output => 0);
82 $A = open_floating_window(rect => [ 30, 30, 50, 50 ]);
83
84 start_drag(40, 40);
85 end_drag(1050, 50);
86
87 is($x->input_focus, $A->id, 'Floating window moved to the right workspace');
88 is($ws2, focused_ws, 'Empty workspace focused after floating window dragged to it');
89
90 ###############################################################################
91 # Drag tiling container onto an empty workspace.
92 ###############################################################################
93
94 subtest "Draging tiling container onto an empty workspace produces move event", \&move_subtest,
95 sub {
96
97 $ws2 = fresh_workspace(output => 1);
98 $ws1 = fresh_workspace(output => 0);
99 $A = open_window;
100
101 start_drag(50, 50);
102 end_drag(1050, 50);
103
104 is($x->input_focus, $A->id, 'Tiling window moved to the right workspace');
105 is($ws2, focused_ws, 'Empty workspace focused after tiling window dragged to it');
106
107 };
108
109 ###############################################################################
110 # Drag tiling container onto a container that closes before the drag is
111 # complete.
112 ###############################################################################
113
114 $ws1 = fresh_workspace(output => 0);
115 $A = open_window;
116 open_window;
117
118 start_drag(600, 300); # Start dragging the second window.
119
120 # Try to place it on the first window.
121 $x->root->warp_pointer(50, 50);
122 sync_with_i3;
123
124 cmd '[id=' . $A->id . '] kill';
125 sync_with_i3;
126 end_drag(50, 50);
127
128 is(@{get_ws_content($ws1)}, 1, 'One container left in ws1');
129
130 ###############################################################################
131 # Drag tiling container onto a tiling container on an other workspace.
132 ###############################################################################
133
134 subtest "Draging tiling container onto a tiling container on an other workspace produces move event", \&move_subtest,
135 sub {
136
137 $ws2 = fresh_workspace(output => 1);
138 open_window;
139 $B_id = get_focused($ws2);
140 $ws1 = fresh_workspace(output => 0);
141 $A = open_window;
142 $A_id = get_focused($ws1);
143
144 start_drag(50, 50);
145 end_drag(1500, 250); # Center of right output, inner region.
146
147 is($ws2, focused_ws, 'Workspace focused after tiling window dragged to it');
148 $ws2 = get_ws($ws2);
149 is($ws2->{focus}[0], $A_id, 'A focused first, dragged container kept focus');
150 is($ws2->{focus}[1], $B_id, 'B focused second');
151
152 };
153
154 ###############################################################################
155 # Drag tiling container onto a floating container on an other workspace.
156 ###############################################################################
157
158 subtest "Draging tiling container onto a floating container on an other workspace produces move event", \&move_subtest,
159 sub {
160
161 $ws2 = fresh_workspace(output => 1);
162 open_floating_window;
163 $B_id = get_focused($ws2);
164 $ws1 = fresh_workspace(output => 0);
165 $A = open_window;
166 $A_id = get_focused($ws1);
167
168 start_drag(50, 50);
169 end_drag(1500, 250);
170
171 is($ws2, focused_ws, 'Workspace with one floating container focused after tiling window dragged to it');
172 $ws2 = get_ws($ws2);
173 is($ws2->{focus}[0], $A_id, 'A focused first, dragged container kept focus');
174 is($ws2->{floating_nodes}[0]->{nodes}[0]->{id}, $B_id, 'B exists & floating');
175
176 };
177
178 ###############################################################################
179 # Drag tiling container onto a bar.
180 ###############################################################################
181
182 subtest "Draging tiling container onto a bar produces move event", \&move_subtest,
183 sub {
184
185 $ws1 = fresh_workspace(output => 0);
186 open_window;
187 $B_id = get_focused($ws1);
188 $ws2 = fresh_workspace(output => 1);
189 $A = open_window;
190 $A_id = get_focused($ws2);
191
192 start_drag(1500, 250);
193 end_drag(1, 498); # Bar on bottom of left output.
194
195 is($ws1, focused_ws, 'Workspace focused after tiling window dragged to its bar');
196 $ws1 = get_ws($ws1);
197 is($ws1->{focus}[0], $A_id, 'B focused first, dragged container kept focus');
198 is($ws1->{focus}[1], $B_id, 'A focused second');
199
200 };
201
202 ###############################################################################
203 # Drag an unfocused tiling container onto it's self.
204 ###############################################################################
205
206 $ws1 = fresh_workspace(output => 0);
207 open_window;
208 $A_id = get_focused($ws1);
209 open_window;
210 $B_id = get_focused($ws1);
211
212 start_drag(50, 50);
213 end_drag(450, 450);
214
215 $ws1 = get_ws($ws1);
216 is($ws1->{focus}[0], $B_id, 'B focused first, kept focus');
217 is($ws1->{focus}[1], $A_id, 'A focused second, unfocused dragged container didn\'t gain focus');
218
219 ###############################################################################
220 # Drag an unfocused tiling container onto an occupied workspace.
221 ###############################################################################
222
223 subtest "Draging unfocused tiling container onto an occupied workspace produces move event", \&move_subtest,
224 sub {
225
226 $ws1 = fresh_workspace(output => 0);
227 $A = open_window;
228 $A_id = get_focused($ws1);
229 $ws2 = fresh_workspace(output => 1);
230 open_window;
231 $B_id = get_focused($ws2);
232
233 start_drag(50, 50);
234 end_drag(1500, 250); # Center of right output, inner region.
235
236 is($ws2, focused_ws, 'Workspace remained focused after dragging unfocused container');
237 $ws2 = get_ws($ws2);
238 is($ws2->{focus}[0], $B_id, 'B focused first, kept focus');
239 is($ws2->{focus}[1], $A_id, 'A focused second, unfocused container didn\'t steal focus');
240
241 };
242
243 ###############################################################################
244 # Drag fullscreen container onto window in same workspace.
245 ###############################################################################
246
247 $ws1 = fresh_workspace(output => 0);
248 open_window;
249 $A = open_window;
250 cmd 'fullscreen enable';
251
252 start_drag(900, 100); # Second window
253 end_drag(50, 50); # To first window
254
255 is($ws1, focused_ws, 'Workspace remained focused after dragging fullscreen container');
256 is_num_fullscreen($ws1, 1, 'Container still fullscreened');
257 is($x->input_focus, $A->id, 'Fullscreen container still focused');
258
259 ###############################################################################
260 # Drag unfocused fullscreen container onto window in other workspace.
261 ###############################################################################
262
263 subtest "Draging unfocused fullscreen container onto window in other workspace produces move event", \&move_subtest,
264 sub {
265
266 $ws1 = fresh_workspace(output => 0);
267 $A = open_window;
268 cmd 'fullscreen enable';
269 $ws2 = fresh_workspace(output => 1);
270 open_window;
271 open_window;
272
273 start_drag(900, 100);
274 end_drag(1000 + 500 * 0.15 + 10, 200); # left of leftmost window
275
276 is($ws2, focused_ws, 'Workspace still focused after dragging fullscreen container to it');
277 is_num_fullscreen($ws1, 0, 'No fullscreen container in first workspace');
278 is_num_fullscreen($ws2, 1, 'Moved container still fullscreened');
279 is($x->input_focus, $A->id, 'Fullscreen container now focused');
280 $ws2 = get_ws($ws2);
281 is($ws2->{nodes}->[0]->{window}, $A->id, 'Fullscreen container now leftmost window in second workspace');
282
283 };
284
285 ###############################################################################
286 # Drag unfocused fullscreen container onto left outter region of window in
287 # other workspace. The container shouldn't end up in $ws2 because it was
288 # dragged onto the outter region of the leftmost window. We must also check
289 # that the focus remains on the other window.
290 ###############################################################################
291
292 subtest "Draging unfocused fullscreen container onto left outter region of window in other workspace produces move event", \&move_subtest,
293 sub {
294
295 $ws1 = fresh_workspace(output => 0);
296 open_window for (1..3);
297 $A = open_window;
298 $tmp = get_focused($ws1);
299 cmd 'fullscreen enable';
300 $ws2 = fresh_workspace(output => 1);
301 $B = open_window;
302
303 start_drag(990, 100); # rightmost of $ws1
304 end_drag(1004, 100); # outter region of window of $ws2
305
306 is($ws2, focused_ws, 'Workspace still focused after dragging fullscreen container to it');
307 is_num_fullscreen($ws1, 1, 'Fullscreen container still in first workspace');
308 is_num_fullscreen($ws2, 0, 'No fullscreen container in second workspace');
309 is($x->input_focus, $B->id, 'Window of second workspace still has focus');
310 is(get_focused($ws1), $tmp, 'Fullscreen container still focused in first workspace');
311 $ws1 = get_ws($ws1);
312 is($ws1->{nodes}->[3]->{window}, $A->id, 'Fullscreen container still rightmost window in first workspace');
313
314 };
315
316 exit_gracefully($pid);
317
318 ###############################################################################
319
320 done_testing;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Test that i3 does not get stuck in an endless loop between two windows that
17 # set transient_for for each other.
18 # Ticket: #4404
19 # Bug still in: 4.20-69-g43e805a00
20 #
21 use i3test i3_config => <<EOT;
22 # i3 config file (v4)
23 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
24
25 popup_during_fullscreen smart;
26 EOT
27
28 my $fs = open_window;
29 cmd 'fullscreen enable';
30
31 my $w1 = open_window({ dont_map => 1 });
32 my $w2 = open_window({ dont_map => 1 });
33
34 $w1->transient_for($w2);
35 $w2->transient_for($w1);
36 $w1->map;
37 $w2->map;
38
39 does_i3_live;
40
41 done_testing;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Verifies that bar config blocks get the i3-wide font configured,
17 # regardless of where the font is configured in the config file
18 # (before or after the bar config blocks).
19 # Ticket: #5031
20 # Bug still in: 4.20-105-g4db383e4
21 use i3test i3_config => <<'EOT';
22 # i3 config file (v4)
23
24 bar {
25 # no font directive here, no i3-wide font configured (yet)
26 }
27
28 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
29 EOT
30
31 my $i3 = i3(get_socket_path(0));
32 my $bars = $i3->get_bar_config()->recv;
33
34 my $bar_id = shift @$bars;
35 my $bar_config = $i3->get_bar_config($bar_id)->recv;
36 is($bar_config->{font}, 'fixed', 'font ok');
37
38 done_testing;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Verifies that any trailing whitespace in strings (including in
17 # “bar { output <output> }” in particular) is stripped.
18 # Ticket: #5064
19 # Bug still in: 4.20-105-g4db383e4
20 use i3test i3_autostart => 0;
21
22 # Test with a single output.
23
24 my $config = <<EOT;
25 # i3 config file (v4)
26 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
27
28 bar {
29 output anything
30 }
31 EOT
32
33 my $pid = launch_with_config($config);
34
35 my $i3 = i3(get_socket_path(0));
36 my $bars = $i3->get_bar_config()->recv;
37 is(@$bars, 1, 'one bar configured');
38 my $bar_id = shift @$bars;
39
40 my $bar_config = $i3->get_bar_config($bar_id)->recv;
41 is_deeply($bar_config->{outputs}, [ 'anything' ], 'outputs do not have trailing whitespace');
42
43 exit_gracefully($pid);
44
45 # Test with multiple outputs for a single bar.
46
47 $config = <<EOT;
48 # i3 config file (v4)
49 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
50
51 bar {
52 output nospace
53 output singlespace
54 }
55 EOT
56
57 $pid = launch_with_config($config);
58
59 $i3 = i3(get_socket_path(0));
60 $bars = $i3->get_bar_config()->recv;
61 is(@$bars, 1, 'one bar configured');
62 $bar_id = shift @$bars;
63
64 $bar_config = $i3->get_bar_config($bar_id)->recv;
65 is_deeply($bar_config->{outputs}, [ 'nospace', 'singlespace' ], 'outputs do not have trailing whitespace');
66
67 exit_gracefully($pid);
68
69
70 done_testing;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Verifies that i3-dmenu-desktop correctly parses Exec= lines in .desktop files
17 # and sends the command to i3 for execution.
18 # Ticket: #5152, #5156
19 # Bug still in: 4.21-17-g389d555d
20 use i3test;
21 use i3test::Util qw(slurp);
22 use File::Temp qw(tempfile tempdir);
23 use POSIX qw(mkfifo);
24 use JSON::XS qw(decode_json);
25
26 my $desktopdir = tempdir(CLEANUP => 1);
27
28 $ENV{XDG_DATA_DIRS} = "$desktopdir";
29
30 mkdir("$desktopdir/applications");
31
32 # Create an i3-msg executable that dumps command line flags to a FIFO
33 my $tmpdir = tempdir(CLEANUP => 1);
34
35 $ENV{PATH} = "$tmpdir:" . $ENV{PATH};
36
37 mkfifo("$tmpdir/fifo", 0600) or BAIL_OUT "Could not create FIFO: $!";
38
39 open(my $i3msg_dump, '>', "$tmpdir/i3-msg");
40 say $i3msg_dump <<EOT;
41 #!/usr/bin/env perl
42 use strict;
43 use warnings;
44 use JSON::XS qw(encode_json);
45 open(my \$f, '>', "$tmpdir/fifo");
46 say \$f encode_json(\\\@ARGV);
47 close(\$f);
48 EOT
49 close($i3msg_dump);
50 chmod 0755, "$tmpdir/i3-msg";
51
52 my $testcnt = 0;
53 sub verify_exec {
54 my ($execline, $want_arg) = @_;
55
56 $testcnt++;
57
58 open(my $desktop, '>', "$desktopdir/applications/desktop$testcnt.desktop");
59 say $desktop <<EOT;
60 [Desktop Entry]
61 Name=i3-testsuite-$testcnt
62 Type=Application
63 Exec=$execline
64 EOT
65 close($desktop);
66
67 # complete-run.pl arranges for $PATH to be set up such that the
68 # i3-dmenu-desktop version we execute is the one from the build directory.
69 my $exit = system("i3-dmenu-desktop --dmenu 'echo i3-testsuite-$testcnt' &");
70 if ($exit != 0) {
71 die "failed to run i3-dmenu-desktop";
72 }
73
74 chomp($want_arg); # trim trailing newline
75 my $got_args = decode_json(slurp("$tmpdir/fifo"));
76 is_deeply($got_args, [ $want_arg ], 'i3-dmenu-desktop executed command as expected');
77 }
78
79 # recommended number of backslashes by the spec, not ambiguous
80 my $exec_1 = <<'EOS';
81 echo "hello \\$PWD \\"and\\" more"
82 EOS
83 my $want_1 = <<'EOS';
84 exec "echo \"hello \\$PWD \\\"and\\\" more\""
85 EOS
86 verify_exec($exec_1, $want_1);
87
88 # permitted, but ambiguous
89 my $exec_2 = <<'EOS';
90 echo "hello \$PWD \"and\" more"
91 EOS
92 my $want_2 = <<'EOS';
93 exec "echo \"hello \\$PWD \\\"and\\\" more\""
94 EOS
95 verify_exec($exec_2, $want_2);
96
97 # electrum
98 my $exec_3 = <<'EOS';
99 sh -c "PATH=\"\\$HOME/.local/bin:\\$PATH\"; electrum %u"
100 EOS
101 my $want_3 = <<'EOS';
102 exec "sh -c \"PATH=\\\"\\$HOME/.local/bin:\\$PATH\\\"; electrum \""
103 EOS
104 verify_exec($exec_3, $want_3);
105
106 # complicated emacsclient command
107 my $exec_4 = <<'EOS';
108 sh -c "if [ -n \\"\\$*\\" ]; then exec emacsclient --alternate-editor= --display=\\"\\$DISPLAY\\" \\"\\$@\\"; else exec emacsclient --alternate-editor= --create-frame; fi" placeholder %F
109 EOS
110 my $want_4 = <<'EOS';
111 exec "sh -c \"if [ -n \\\"\\$*\\\" ]; then exec emacsclient --alternate-editor= --display=\\\"\\$DISPLAY\\\" \\\"\\$@\\\"; else exec emacsclient --alternate-editor= --create-frame; fi\" placeholder "
112 EOS
113 verify_exec($exec_4, $want_4);
114
115 # permitted, but unusual to quote the first arg
116 my $exec_5 = <<'EOS';
117 "electrum" arg
118 EOS
119 my $want_5 = <<'EOS';
120 exec "\"electrum\" arg"
121 EOS
122 verify_exec($exec_5, $want_5);
123
124 done_testing;
2929 ################################################################################
3030 # use 'focus output' and verify that focus gets changed appropriately
3131 ################################################################################
32
33 sub focused_output {
34 my $tree = $i3->get_tree->recv;
35 my $focused = $tree->{focus}->[0];
36 my $output = first { $_->{id} == $focused } @{$tree->{nodes}};
37 return $output->{name};
38 }
3932
4033 sync_with_i3;
4134 $x->root->warp_pointer(0, 0);
148148 # Ensure that focusing right/left works in the expected order.
149149 ############################################################################
150150
151 sub get_focused_output {
152 my $tree = i3(get_socket_path())->get_tree->recv;
153 my ($focused_id) = @{$tree->{focus}};
154 my ($output) = grep { $_->{id} == $focused_id } @{$tree->{nodes}};
155 return $output->{name};
156 }
157
158 is(get_focused_output(), 'fake-0', 'focus on fake-0');
151 is(focused_output, 'fake-0', 'focus on fake-0');
159152
160153 cmd 'focus output right';
161 is(get_focused_output(), 'fake-1', 'focus on fake-1');
154 is(focused_output, 'fake-1', 'focus on fake-1');
162155
163156 cmd 'focus output right';
164 is(get_focused_output(), 'fake-2', 'focus on fake-2');
157 is(focused_output, 'fake-2', 'focus on fake-2');
165158
166159 cmd 'focus output left';
167 is(get_focused_output(), 'fake-1', 'focus on fake-1');
160 is(focused_output, 'fake-1', 'focus on fake-1');
168161
169162 cmd 'focus output left';
170 is(get_focused_output(), 'fake-0', 'focus on fake-0');
163 is(focused_output, 'fake-0', 'focus on fake-0');
171164
172165 cmd 'focus output left';
173 is(get_focused_output(), 'fake-2', 'focus on fake-2 (wrapping)');
166 is(focused_output, 'fake-2', 'focus on fake-2 (wrapping)');
174167
175168 cmd 'focus output right';
176 is(get_focused_output(), 'fake-0', 'focus on fake-0 (wrapping)');
169 is(focused_output, 'fake-0', 'focus on fake-0 (wrapping)');
177170
178171 exit_gracefully($pid);
179172
5454
5555 # Check that the windows are on the correct output
5656 is_deeply(scalar $win_on_first_output->rect, $orig_rect1, "first window spans the first output");
57 is_deeply(scalar $win_on_second_output->rect, $orig_rect2, "second window spans the sencond output");
57 is_deeply(scalar $win_on_second_output->rect, $orig_rect2, "second window spans the second output");
5858
5959 # Check that both windows remained fullscreen
6060 my $tree = i3(get_socket_path())->get_tree->recv;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Test using multiple outputs for 'move workspace to output …'
17 # Ticket: #4337
18 use i3test i3_config => <<EOT;
19 # i3 config file (v4)
20 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
21
22 fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+0+768,1024x768+1024+768
23 EOT
24
25 # Test setup: 4 outputs 2 marked windows
26
27 open_window;
28 cmd 'mark aa, move to workspace 1, workspace 1';
29 open_window;
30 cmd 'mark ab, move to workspace 3';
31
32 sub is_ws {
33 my $ws_num = shift;
34 my $out_num = shift;
35 my $msg = shift;
36
37 local $Test::Builder::Level = $Test::Builder::Level + 1;
38 is(get_output_for_workspace("$ws_num"), "fake-$out_num", "Workspace $ws_num -> $out_num: $msg");
39 }
40
41 ###############################################################################
42 # Test moving workspace to same output
43 # See issue #4691
44 ###############################################################################
45 is_ws(1, 0, 'sanity check');
46
47 my $reply = cmd '[con_mark=aa] move workspace to output fake-0';
48 is_ws(1, 0, 'workspace did not move');
49 ok($reply->[0]->{success}, 'reply success');
50
51 ###############################################################################
52 # Test using "next" special keyword
53 ###############################################################################
54
55 is_ws(1, 0, 'sanity check');
56 is_ws(3, 2, 'sanity check');
57
58 for (my $i = 1; $i < 9; $i++) {
59 cmd '[con_mark=a] move workspace to output next';
60 my $out1 = $i % 4;
61 my $out3 = ($i + 2) % 4;
62
63 is_ws(1, $out1, 'move workspace to next');
64 is_ws(3, $out3, 'move workspace to next');
65 }
66
67 ###############################################################################
68 # Same as above but explicitly type all the outputs
69 ###############################################################################
70
71 is_ws(1, 0, 'sanity check');
72 is_ws(3, 2, 'sanity check');
73
74 for (my $i = 1; $i < 10; $i++) {
75 cmd '[con_mark=a] move workspace to output fake-0 fake-1 fake-2 fake-3';
76 my $out1 = $i % 4;
77 my $out3 = ($i + 2) % 4;
78
79 is_ws(1, $out1, 'cycle through explicit outputs');
80 is_ws(3, $out3, 'cycle through explicit outputs');
81 }
82
83 ###############################################################################
84 # Use a subset of the outputs plus some non-existing outputs
85 ###############################################################################
86
87 cmd '[con_mark=aa] move workspace to output fake-1';
88 cmd '[con_mark=ab] move workspace to output fake-1';
89 is_ws(1, 1, 'start from fake-1 which is not included in output list');
90 is_ws(3, 1, 'start from fake-1 which is not included in output list');
91
92 my @order = (0, 3, 2);
93 for (my $i = 0; $i < 10; $i++) {
94 cmd '[con_mark=a] move workspace to output doesnotexist fake-0 alsodoesnotexist fake-3 fake-2';
95
96 my $out = $order[$i % 3];
97 is_ws(1, $out, 'cycle through shuffled outputs');
98 is_ws(3, $out, 'cycle through shuffled outputs');
99
100 }
101
102 done_testing;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Test using multiple output for 'focus output …'
17 # Ticket: #4619
18 use i3test i3_config => <<EOT;
19 # i3 config file (v4)
20 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
21
22 fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+0+768,1024x768+1024+768
23 EOT
24
25 ###############################################################################
26 # Test using "next" special keyword
27 ###############################################################################
28
29 is(focused_output, "fake-0", 'sanity check');
30
31 for (my $i = 1; $i < 9; $i++) {
32 cmd 'focus output next';
33
34 my $out = $i % 4;
35 is(focused_output, "fake-$out", 'focus output next cycle');
36 }
37
38 ###############################################################################
39 # Same as above but explicitly type all the outputs
40 ###############################################################################
41
42 is(focused_output, "fake-0", 'sanity check');
43
44 for (my $i = 1; $i < 10; $i++) {
45 cmd 'focus output fake-0 fake-1 fake-2 fake-3';
46
47 my $out = $i % 4;
48 is(focused_output, "fake-$out", 'focus output next cycle');
49 }
50
51 ###############################################################################
52 # Use a subset of the outputs plus some non-existing outputs
53 ###############################################################################
54
55 cmd 'focus output fake-1';
56 is(focused_output, "fake-1", 'start from fake-1 which is not included in output list');
57
58 my @order = (0, 3, 2);
59 for (my $i = 0; $i < 10; $i++) {
60 cmd 'focus output doesnotexist fake-0 alsodoesnotexist fake-3 fake-2';
61
62 my $out = $order[$i % 3];
63 is(focused_output, "fake-$out", 'focus output next cycle');
64 }
65
66 done_testing;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Tests whether our WM registration is done with the correct WM_S0 selection.
4
5 use i3test;
6
7 my $x = X11::XCB::Connection->new;
8 my $reply = $x->get_selection_owner($x->atom(name => 'WM_S0')->id);
9 ok($reply, "registration successful");
10 done_testing;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Test that i3 doesn't crash if the binding command is empty.
17 # Ticket: #5000
18
19 use i3test i3_autostart => 0;
20
21 my $config = <<EOT;
22 # i3 config file (v4)
23 bindsym X
24 EOT
25
26 my $pid = launch_with_config($config);
27 does_i3_live;
28
29 exit_gracefully($pid);
30 done_testing;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Test that explicitly defined default mode doesn't cause segfault.
17 # Ticket: #5006
18 # Bug still in: 4.20-96-gce2665ca
19
20 use i3test i3_autostart => 0;
21
22 my $config = <<EOT;
23 # i3 config file (v4)
24 mode "default" {
25 bindsym X resize
26 }
27 EOT
28
29 my $pid = launch_with_config($config);
30 does_i3_live;
31
32 exit_gracefully($pid);
33 done_testing;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Test that i3 doesn't crash if the config contains nested variables.
17 # Ticket: #5002
18
19 use i3test i3_autostart => 0;
20
21 #######################################################################
22 # Test calloc crash
23 #######################################################################
24
25 my $config = <<'EOT';
26 # i3 config file (v4)
27 set $long_variable_name_with_short_value 1
28 set $$long_variable_name_with_short_value 2
29 set $$$long_variable_name_with_short_value 3
30 EOT
31
32 my $pid = launch_with_config($config);
33
34 # ==2108678==ERROR: AddressSanitizer: requested allocation size 0xffffffffffffffe1 (0x7e8 after adjustments for alignment, red zones etc.) exceeds maximum supported size of 0x10000000000 (thread T0)
35 # #0 0x7feaa9cbf411 in __interceptor_calloc /usr/src/debug/gcc/libsanitizer/asan/asan_malloc_linux.cpp:77
36 # #1 0x560867c0c13d in scalloc ../libi3/safewrappers.c:29
37 # #2 0x560867b7bd63 in parse_file ../src/config_parser.c:1030
38 # #3 0x560867b6b1b8 in load_configuration ../src/config.c:261
39 # #4 0x560867baf9cf in main ../src/main.c:677
40 # #5 0x7feaa95b52cf (/usr/lib/libc.so.6+0x232cf)
41
42 does_i3_live;
43
44 exit_gracefully($pid);
45
46
47 #######################################################################
48 # Test buffer overflow
49 #######################################################################
50
51 $config = <<'EOT';
52 # i3 config file (v4)
53 set $x 1
54 set $$x 2
55 EOT
56
57 $pid = launch_with_config($config);
58
59 # ==2110007==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6070000014f2 at pc 0x558590f680ba bp 0x7ffced72b760 sp 0x7ffced72b750
60 # WRITE of size 1 at 0x6070000014f2 thread T0
61 # #0 0x558590f680b9 in parse_file ../src/config_parser.c:1051
62 # #1 0x558590f571b8 in load_configuration ../src/config.c:261
63 # #2 0x558590f9b9cf in main ../src/main.c:677
64 # #3 0x7f81c61c82cf (/usr/lib/libc.so.6+0x232cf)
65 # #4 0x7f81c61c8389 in __libc_start_main (/usr/lib/libc.so.6+0x23389)
66 # #5 0x558590f0bd54 in _start ../sysdeps/x86_64/start.S:115
67
68 # 0x6070000014f2 is located 0 bytes to the right of 66-byte region [0x6070000014b0,0x6070000014f2)
69 # allocated by thread T0 here:
70 # #0 0x7f81c68bf411 in __interceptor_calloc /usr/src/debug/gcc/libsanitizer/asan/asan_malloc_linux.cpp:77
71 # #1 0x558590ff813d in scalloc ../libi3/safewrappers.c:29
72 # #2 0x558590f67d63 in parse_file ../src/config_parser.c:1030
73 # #3 0x558590f571b8 in load_configuration ../src/config.c:261
74 # #4 0x558590f9b9cf in main ../src/main.c:677
75 # #5 0x7f81c61c82cf (/usr/lib/libc.so.6+0x232cf)
76
77 does_i3_live;
78
79 exit_gracefully($pid);
80 done_testing;
0 #!perl
1 # vim:ts=4:sw=4:expandtab
2 #
3 # Please read the following documents before working on tests:
4 # • https://build.i3wm.org/docs/testsuite.html
5 # (or docs/testsuite)
6 #
7 # • https://build.i3wm.org/docs/lib-i3test.html
8 # (alternatively: perldoc ./testcases/lib/i3test.pm)
9 #
10 # • https://build.i3wm.org/docs/ipc.html
11 # (or docs/ipc)
12 #
13 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
14 # (unless you are already familiar with Perl)
15 #
16 # Test that setting and unsetting motif hints updates window decorations
17 # accordingly, respecting user configuration.
18 # Ticket: #3678
19 # Ticket: #5149
20 # Bug still in: 4.21
21 use List::Util qw(first);
22 use i3test i3_autostart => 0;
23 use X11::XCB qw(:all);
24
25 my $use_floating;
26 sub subtest_with_config {
27 my ($style, $cb) = @_;
28 my $some_other_style = $style eq "normal" ? "pixel" : "normal";
29
30 subtest 'with tiling', sub {
31 my $config = <<EOT;
32 # i3 config file (v4)
33 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
34
35 default_border $style
36 default_floating_border $some_other_style
37 EOT
38 my $pid = launch_with_config($config);
39 $use_floating = 0;
40 $cb->();
41 exit_gracefully($pid);
42 };
43
44 subtest 'with floating', sub {
45 my $config = <<EOT;
46 # i3 config file (v4)
47 font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
48
49 default_border $some_other_style
50 default_floating_border $style
51 EOT
52 my $pid = launch_with_config($config);
53 $use_floating = 1;
54 $cb->();
55 exit_gracefully($pid);
56 };
57 }
58
59 sub _change_motif_property {
60 my ($window, $value) = @_;
61 $x->change_property(
62 PROP_MODE_REPLACE,
63 $window->id,
64 $x->atom(name => '_MOTIF_WM_HINTS')->id,
65 $x->atom(name => 'CARDINAL')->id,
66 32, 5,
67 pack('L5', 2, 0, $value, 0, 0),
68 );
69 }
70
71 sub open_window_with_motifs {
72 my $value = shift;
73
74 # we don't need other windows anymore, simplifies get_border_style
75 kill_all_windows;
76
77 my $open = \&open_window;
78 if ($use_floating) {
79 $open = \&open_floating_window;
80 }
81
82 my $window = $open->(
83 before_map => sub {
84 my ($window) = @_;
85 _change_motif_property($window, $value);
86 },
87 );
88
89 sync_with_i3;
90
91 return $window;
92 }
93
94 my $window;
95 sub change_motif_property {
96 my $value = shift;
97 _change_motif_property($window, $value);
98 sync_with_i3;
99 }
100
101 sub get_border_style {
102 if ($use_floating) {
103 my @floating = @{get_ws(focused_ws)->{floating_nodes}};
104 return $floating[0]->{nodes}[0]->{border};
105 }
106
107 return @{get_ws(focused_ws)->{nodes}}[0]->{border};
108 }
109
110 sub is_border_style {
111 my ($expected, $extra_msg) = @_;
112 my $msg = "border style $expected";
113 if (defined $extra_msg) {
114 $msg = "$msg: $extra_msg";
115 }
116
117 local $Test::Builder::Level = $Test::Builder::Level + 1;
118 is(get_border_style($window), $expected, $msg);
119 }
120
121 ###############################################################################
122 subtest 'with default_border normal', \&subtest_with_config, 'normal',
123 sub {
124 $window = open_window_with_motifs(0);
125 is_border_style('none');
126
127 $window = open_window_with_motifs(1 << 0);
128 is_border_style('normal');
129
130 $window = open_window_with_motifs(1 << 1);
131 is_border_style('pixel');
132
133 $window = open_window_with_motifs(1 << 3);
134 is_border_style('normal');
135
136 cmd 'border pixel';
137 is_border_style('pixel', 'set by user');
138
139 change_motif_property(0);
140 is_border_style('none');
141
142 change_motif_property(1);
143 is_border_style('pixel', 'because of user maximum=pixel');
144
145 cmd 'border none';
146 is_border_style('none', 'set by user');
147
148 change_motif_property(0);
149 is_border_style('none');
150
151 change_motif_property(1);
152 is_border_style('none', 'because of user maximum=none');
153 };
154
155 subtest 'with default_border pixel', \&subtest_with_config, 'pixel',
156 sub {
157 $window = open_window_with_motifs(0);
158 is_border_style('none');
159
160 $window = open_window_with_motifs(1 << 0);
161 is_border_style('pixel');
162
163 $window = open_window_with_motifs(1 << 1);
164 is_border_style('pixel');
165
166 $window = open_window_with_motifs(1 << 3);
167 is_border_style('pixel');
168
169 cmd 'border normal';
170 is_border_style('normal', 'set by user');
171
172 change_motif_property(0);
173 is_border_style('none');
174
175 change_motif_property(1);
176 is_border_style('normal', 'because of user maximum=normal');
177 };
178
179 subtest 'with default_border none', \&subtest_with_config, 'none',
180 sub {
181 $window = open_window_with_motifs(0);
182 is_border_style('none');
183
184 $window = open_window_with_motifs(1 << 0);
185 is_border_style('none');
186
187 $window = open_window_with_motifs(1 << 1);
188 is_border_style('none');
189
190 $window = open_window_with_motifs(1 << 3);
191 is_border_style('none');
192
193 cmd 'border pixel';
194 is_border_style('pixel', 'set by user');
195
196 change_motif_property(0);
197 is_border_style('none');
198
199 change_motif_property(1);
200 is_border_style('pixel', 'because of user maximum=pixel');
201 };
202
203 done_testing;
+0
-36
travis/bintray-autobuild-debian.json less more
0 {
1 "package": {
2 "name": "i3-wm",
3 "repo": "i3-autobuild",
4 "subject": "i3"
5 },
6
7 "version": {
8 "name": "%version%",
9 "desc": "TODO",
10 "gpgSign": false
11 },
12
13 "files": [
14 {
15 "includePattern": "distbuild/deb/debian-amd64/(.*\\.deb)$",
16 "matrixParams": {
17 "deb_distribution": "sid",
18 "deb_component": "main",
19 "deb_architecture": "amd64"
20 },
21 "uploadPattern": "$1"
22 },
23 {
24 "includePattern": "distbuild/deb/debian-i386/(.*\\.deb)$",
25 "matrixParams": {
26 "deb_distribution": "sid",
27 "deb_component": "main",
28 "deb_architecture": "i386"
29 },
30 "uploadPattern": "$1"
31 }
32 ],
33
34 "publish": true
35 }
+0
-36
travis/bintray-autobuild-ubuntu.json less more
0 {
1 "package": {
2 "name": "i3-wm",
3 "repo": "i3-autobuild-ubuntu",
4 "subject": "i3"
5 },
6
7 "version": {
8 "name": "%version%",
9 "desc": "TODO",
10 "gpgSign": false
11 },
12
13 "files": [
14 {
15 "includePattern": "distbuild/deb/ubuntu-amd64/(.*\\.deb)$",
16 "matrixParams": {
17 "deb_distribution": "bionic",
18 "deb_component": "main",
19 "deb_architecture": "amd64"
20 },
21 "uploadPattern": "$1"
22 },
23 {
24 "includePattern": "distbuild/deb/ubuntu-i386/(.*\\.deb)$",
25 "matrixParams": {
26 "deb_distribution": "bionic",
27 "deb_component": "main",
28 "deb_architecture": "i386"
29 },
30 "uploadPattern": "$1"
31 }
32 ],
33
34 "publish": true
35 }
+0
-6
travis/check-formatting.sh less more
0 #!/bin/sh
1
2 set -e
3 set -x
4
5 clang-format-9 -i $(find . -name "*.[ch]" | tr '\n' ' ') && git diff --exit-code || (echo 'Code was not formatted using clang-format!'; false)
1010 use v5.10;
1111 use autodie;
1212 use lib 'testcases/lib';
13 use lib '/usr/share/lintian/lib';
1314 use i3test::Util qw(slurp);
1415 use Lintian::Spelling qw(check_spelling);
1516
2021 my $profile = Lintian::Profile->new;
2122 $profile->load('debian', ['/usr/share/lintian']);
2223
23 Lintian::Data->set_vendor($profile);
24
2524 my $exitcode = 0;
2625
2726 # Whitelist for spelling errors in manpages, in case the spell checker has
2827 # false-positives.
29 my $binary_spelling_exceptions = {
30 #'exmaple' => 1, # Example for how to add entries to this whitelist.
31 'betwen' => 1, # asan_flags.inc contains this spelling error.
32 };
28 my $binary_spelling_exceptions = [
29 #'exmaple', # Example for how to add entries to this whitelist.
30 'betwen', # asan_flags.inc contains this spelling error.
31 'dissassemble', # https://reviews.llvm.org/D93902
32 'oT', # lintian finds this in build/i3bar when built with clang?!
33 ];
3334 my @binaries = qw(
3435 build/i3
3536 build/i3-config-wizard
4041 build/i3bar
4142 );
4243 for my $binary (@binaries) {
43 check_spelling(slurp($binary), $binary_spelling_exceptions, sub {
44 check_spelling($profile->data, slurp($binary), $binary_spelling_exceptions, sub {
4445 my ($current, $fixed) = @_;
4546 say STDERR qq|Binary "$binary" contains a spelling error: "$current" should be "$fixed"|;
4647 $exitcode = 1;
4950
5051 # Whitelist for spelling errors in manpages, in case the spell checker has
5152 # false-positives.
52 my $manpage_spelling_exceptions = {
53 };
53 my $manpage_spelling_exceptions = [
54 ];
5455
5556 for my $name (glob('build/man/*.1')) {
5657 for my $line (split(/\n/, slurp($name))) {
5758 next if $line =~ /^\.\\\"/o;
58 check_spelling($line, $manpage_spelling_exceptions, sub {
59 check_spelling($profile->data, $line, $manpage_spelling_exceptions, sub {
5960 my ($current, $fixed) = @_;
6061 say STDERR qq|Manpage "$name" contains a spelling error: "$current" should be "$fixed"|;
6162 $exitcode = 1;
+0
-39
travis/cleanup-bintray.pl less more
0 #!/usr/bin/env perl
1 # vim:ts=4:sw=4:expandtab
2
3 use strict;
4 use warnings;
5 use Data::Dumper;
6 use HTTP::Tiny; # in core since v5.13.9
7 use JSON::PP; # in core since v5.13.9
8 use MIME::Base64; # in core since v5.7
9 use v5.13;
10
11 my $repo = shift;
12
13 my $auth = $ENV{'BINTRAY_USER'} . ':' . $ENV{'BINTRAY_KEY'};
14 die "BINTRAY_USER and/or BINTRAY_KEY environment variables not set" if $auth eq ':';
15 # TODO(stapelberg): switch to putting $auth into the URL once perl-modules ≥
16 # 5.20 is available on travis (Ubuntu Wily or newer).
17 my $auth_header = 'Basic ' . MIME::Base64::encode_base64($auth, "");
18 my $apiurl = 'https://api.bintray.com/packages/i3/' . $repo . '/i3-wm';
19 my $client = HTTP::Tiny->new(
20 verify_SSL => 1,
21 default_headers => {
22 'authorization' => $auth_header,
23 });
24 my $resp = $client->get($apiurl);
25 die "Getting versions failed: HTTP status $resp->{status} (content: $resp->{content})" unless $resp->{success};
26 my $decoded = decode_json($resp->{content});
27 my @versions = reverse sort {
28 (system("/usr/bin/dpkg", "--compare-versions", "$a", "gt", "$b") == 0) ? 1 : -1
29 } @{$decoded->{versions}};
30
31 # Keep the most recent 5 versions.
32 splice(@versions, 0, 5);
33
34 for my $version (@versions) {
35 say "Deleting old version $version";
36 $resp = $client->request('DELETE', "$apiurl/versions/$version");
37 die "Deletion of version $version failed: HTTP status $resp->{status} (content: $resp->{content})" unless $resp->{success};
38 }
88 cp -r deb/COPY-DOCS build.i3wm.org/docs
99 cd build.i3wm.org
1010 echo build.i3wm.org > CNAME
11 # Disallow search engine indexing for build.i3wm.org: users should find the
12 # release version instead, and only developers should use build.i3wm.org.
13 echo 'User-Agent: *' > robots.txt
14 echo 'Disallow: /' >> robots.txt
1115 git init
1216
1317 git config user.name "Travis CI"
+0
-6
travis/prep-bintray.sh less more
0 #!/bin/sh
1
2 set -e
3 set -x
4
5 sed -i "s,%version%,$(git describe --tags),g" travis/bintray-autobuild-*.json
0 #!/bin/sh
1
2 set -e
3
4 for fn in distbuild/deb/debian-amd64/*.deb distbuild/deb/debian-i386/*.deb
5 do
6 echo "pushing $fn to balto"
7 curl \
8 --header "Authorization: Bearer ${BALTO_TOKEN}" \
9 --form "package=@${fn}" \
10 --form distribution=all \
11 https://i3.baltorepo.com/i3/i3-autobuild/upload/
12 done
13
14 for fn in distbuild/deb/ubuntu-amd64/*.deb distbuild/deb/ubuntu-i386/*.deb
15 do
16 echo "pushing $fn to balto"
17 curl \
18 --header "Authorization: Bearer ${BALTO_TOKEN}" \
19 --form "package=@${fn}" \
20 --form distribution=all \
21 https://i3.baltorepo.com/i3/i3-autobuild-ubuntu/upload/
22 done
11 # Returns true if Debian/Ubuntu packages should be skipped because this CI run
22 # was triggered by a pull request.
33
4 # Verify BINTRAY_USER is present (only set on github.com/i3/i3),
4 # Verify BALTO_TOKEN is present (only set on github.com/i3/i3),
55 # otherwise the CI run was triggered by a pull request.
66 # Verify CC=gcc so that we only build packages once for each commit,
77 # not twice (with gcc and clang).
8 if [ ! -z "$BINTRAY_USER" ] && [ "$CC" = "gcc" ]
8 if [ ! -z "$BALTO_TOKEN" ] && [ "$CC" = "gcc" ]
99 then
1010 exit 1
1111 fi
1212 # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
1313
1414 # Install mk-build-deps (for installing the i3 build dependencies),
15 # clang and clang-format-9 (for checking formatting and building with clang),
1615 # lintian (for checking spelling errors),
1716 RUN linux32 apt-get update && \
1817 DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
1918 dpkg-dev devscripts git equivs \
20 build-essential clang clang-format-9 \
19 build-essential clang \
2120 lintian && \
2221 rm -rf /var/lib/apt/lists/*
2322
2625 RUN linux32 apt-get update && \
2726 DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \
2827 rm -rf /var/lib/apt/lists/*
28
29 # The user outside of Docker (GitHub Actions CI runner) and inside of Docker
30 # (root) are different, and newer versions of git error out in that scenario.
31 # To fix this, explicitly configure /usr/src/i3 as a safe directory:
32 RUN git config --global --add safe.directory /usr/src/i3
1212 # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
1313
1414 # Install mk-build-deps (for installing the i3 build dependencies),
15 # clang and clang-format-9 (for checking formatting and building with clang),
1615 # lintian (for checking spelling errors),
1716 RUN linux32 apt-get update && \
1817 DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
1918 dpkg-dev devscripts git equivs \
20 build-essential clang clang-format-9 \
19 build-essential clang \
2120 lintian && \
2221 rm -rf /var/lib/apt/lists/*
2322
2625 RUN linux32 apt-get update && \
2726 DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \
2827 rm -rf /var/lib/apt/lists/*
28
29 # The user outside of Docker (GitHub Actions CI runner) and inside of Docker
30 # (root) are different, and newer versions of git error out in that scenario.
31 # To fix this, explicitly configure /usr/src/i3 as a safe directory:
32 RUN git config --global --add safe.directory /usr/src/i3
1212 # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
1313
1414 # Install mk-build-deps (for installing the i3 build dependencies),
15 # clang and clang-format-9 (for checking formatting and building with clang),
1615 # lintian (for checking spelling errors),
1716 # test suite dependencies (for running tests)
1817 RUN apt-get update && \
1918 DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
2019 dpkg-dev devscripts git equivs \
21 build-essential clang clang-format-9 \
20 build-essential clang \
2221 lintian && \
2322 rm -rf /var/lib/apt/lists/*
2423
2726 RUN apt-get update && \
2827 DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \
2928 rm -rf /var/lib/apt/lists/*
29
30 # The user outside of Docker (GitHub Actions CI runner) and inside of Docker
31 # (root) are different, and newer versions of git error out in that scenario.
32 # To fix this, explicitly configure /usr/src/i3 as a safe directory:
33 RUN git config --global --add safe.directory /usr/src/i3
1010 # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
1111
1212 # Install mk-build-deps (for installing the i3 build dependencies),
13 # clang and clang-format-9 (for checking formatting and building with clang),
1413 # lintian (for checking spelling errors),
1514 # test suite dependencies (for running tests)
1615 RUN apt-get update && \
1716 DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
1817 dpkg-dev devscripts git equivs \
19 build-essential clang clang-format-9 \
18 build-essential clang \
2019 lintian \
2120 libmodule-install-perl libanyevent-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libinline-c-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libipc-run-perl libxcb-xtest0-dev libx11-xcb-perl libjson-xs-perl x11-xserver-utils && \
2221 rm -rf /var/lib/apt/lists/*
2726 RUN apt-get update && \
2827 DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \
2928 rm -rf /var/lib/apt/lists/*
29
30 # The user outside of Docker (GitHub Actions CI runner) and inside of Docker
31 # (root) are different, and newer versions of git error out in that scenario.
32 # To fix this, explicitly configure /usr/src/i3 as a safe directory:
33 RUN git config --global --add safe.directory /usr/src/i3