New upstream version 4.21.1
Sophie Brun
1 year, 4 months ago
3 | 3 | |
4 | 4 | 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). |
5 | 5 | |
6 | ## i3 bug reports and feature requests | |
6 | ## i3 bug reports | |
7 | 7 | |
8 | 8 | 1. Read the [debugging instructions](https://i3wm.org/docs/debugging.html). |
9 | 9 | 2. Make sure you include a link to your logfile in your report (section 3). |
17 | 17 | encountered the issue you are about to report while using a compositor, |
18 | 18 | please try reproducing it without a compositor. |
19 | 19 | |
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 | ||
20 | 34 | ## Pull requests |
21 | 35 | |
22 | 36 | * Before sending a pull request for new features, please check with us that the |
26 | 40 | * Use `clang-format` to format your code. |
27 | 41 | * Run the [testsuite](https://i3wm.org/docs/testsuite.html) |
28 | 42 | * 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. | |
34 | 51 | |
35 | 52 | ## Finding something to do |
36 | 53 |
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 |
7 | 7 | --> |
8 | 8 | |
9 | 9 | ## I'm submitting a… |
10 | <!-- Please check one of the following options with "x" --> | |
10 | <!-- Check one of the following options with "x" --> | |
11 | 11 | <pre> |
12 | 12 | [ ] Bug |
13 | 13 | [x] Feature Request |
27 | 27 | e.g., »The window left next to the current window should be focused.« |
28 | 28 | --> |
29 | 29 | |
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 | ||
30 | 39 | ## Environment |
31 | 40 | <!-- |
32 | 41 | Please include your exact i3 version. |
5 | 5 | --> |
6 | 6 | |
7 | 7 | ## 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 | --> | |
9 | 14 | <pre> |
10 | 15 | [ ] Bug |
11 | 16 | [ ] 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 | 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) | |
1 | 1 | [![Issues](https://img.shields.io/github/issues/Airblader/i3.svg)](https://github.com/Airblader/i3/issues) |
2 | 2 | [![Forks](https://img.shields.io/github/forks/Airblader/i3.svg)](https://github.com/Airblader/i3/network) |
3 | 3 | [![Stars](https://img.shields.io/github/stars/Airblader/i3.svg)](https://github.com/Airblader/i3/stargazers) |
18 | 18 | |
19 | 19 | 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). |
20 | 20 | |
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). | |
22 | 22 | |
23 | 23 | # Features |
24 | 24 |
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 |
0 | i3-wm (4.19.1-1) unstable; urgency=medium | |
0 | i3-wm (4.21-1) unstable; urgency=medium | |
1 | ||
2 | * New upstream release. | |
3 | ||
4 | -- Michael Stapelberg <[email protected]> Wed, 21 Sep 2022 18:14:14 +0200 | |
5 | ||
6 | i3-wm (4.20.1-1) unstable; urgency=medium | |
7 | ||
8 | * New upstream release. | |
9 | ||
10 | -- Michael Stapelberg <[email protected]> Wed, 03 Nov 2021 09:22:48 +0100 | |
11 | ||
12 | i3-wm (4.20-1) unstable; urgency=medium | |
13 | ||
14 | * New upstream release. | |
15 | ||
16 | -- Michael Stapelberg <[email protected]> Sat, 27 Feb 2021 10:32:17 +0100 | |
17 | ||
18 | i3-wm (4.19.2-1) unstable; urgency=medium | |
19 | ||
20 | * New upstream release. | |
21 | ||
22 | -- Michael Stapelberg <[email protected]> Sat, 27 Feb 2021 10:32:17 +0100 | |
23 | ||
24 | i3-wm (4.19-1) unstable; urgency=medium | |
1 | 25 | |
2 | 26 | * New upstream release. |
3 | 27 | |
4 | 28 | -- Michael Stapelberg <[email protected]> Mon, 19 Oct 2020 22:48:30 +0200 |
5 | ||
6 | i3-wm (4.19-1) unstable; urgency=medium | |
7 | ||
8 | * New upstream release. | |
9 | ||
10 | -- Michael Stapelberg <[email protected]> Sun, 15 Nov 2020 18:28:11 +0100 | |
11 | 29 | |
12 | 30 | i3-wm (4.18.3-1) unstable; urgency=medium |
13 | 31 | |
613 | 631 | * Bugfix: Resize client after updating base_width/base_height |
614 | 632 | * Bugfix: Force render containers after setting the client active |
615 | 633 | * Bugfix: Fix two problems in resizing floating windows with right mouse |
616 | * Bugfix: Use more precise floating point arithmetics | |
634 | * Bugfix: Use more precise floating point arithmetic | |
617 | 635 | * Bugfix: Correctly place new windows below fullscreen windows |
618 | 636 | |
619 | 637 | -- Michael Stapelberg <[email protected]> Mon, 21 Dec 2009 22:33:02 +0100 |
21 | 21 | pkg-config, |
22 | 22 | libev-dev (>= 1:4.04), |
23 | 23 | libyajl-dev (>= 2.0.4), |
24 | libpcre3-dev (>= 1:8.10), | |
24 | libpcre2-dev, | |
25 | 25 | libstartup-notification0-dev (>= 0.10), |
26 | 26 | libcairo2-dev (>= 1.14.4), |
27 | 27 | libpango1.0-dev, |
16 | 16 | # Set -Ddocdir; the default is /usr/share/doc/i3 |
17 | 17 | dh_auto_configure -- -Ddocdir=/usr/share/doc/i3-wm -Dmans=true |
18 | 18 | |
19 | override_dh_builddeb: | |
20 | # bintray does not support xz currently. | |
21 | dh_builddeb -- -Zgzip | |
22 | ||
23 | 19 | %: |
24 | 20 | dh $@ --buildsystem=meson |
399 | 399 | Then, it looks through all bindings and gets the one which matches the received |
400 | 400 | event. |
401 | 401 | |
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+. | |
404 | 404 | |
405 | 405 | == Manage windows (src/main.c, manage_window() and reparent_window()) |
406 | 406 |
82 | 82 | <ul id="nav"> |
83 | 83 | <li><a style="border-bottom: 2px solid #fff" href="/docs">Docs</a></li> |
84 | 84 | <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> | |
86 | 86 | <li><a href="/contact">Contact</a></li> |
87 | 87 | <li><a href="https://bugs.i3wm.org/">Bugs</a></li> |
88 | 88 | </ul> |
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
190 | 190 | is 9 pixels), since the separator line is drawn in the middle. |
191 | 191 | markup:: |
192 | 192 | 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]. | |
194 | 194 | Set to +"none"+ to not use any markup (default). Pango markup only works |
195 | 195 | if you use a pango font. |
196 | 196 |
13 | 13 | in +/tmp/i3-%u.XXXXXX/ipc-socket.%p+ where +%u+ is your UNIX username, +%p+ is |
14 | 14 | the PID of i3 and XXXXXX is a string of random characters from the portable |
15 | 15 | 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). | |
17 | 18 | |
18 | 19 | All i3 utilities, like +i3-msg+ and +i3-input+ will read the +I3_SOCKET_PATH+ |
19 | 20 | X11 property, stored on the X11 root window. |
38 | 39 | |
39 | 40 | == Sending messages to i3 |
40 | 41 | |
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). | |
47 | 48 | |
48 | 49 | The magic string currently is "i3-ipc" and will only be changed when a change |
49 | 50 | in the IPC API is done which breaks compatibility (we hope that we don’t need |
95 | 96 | |
96 | 97 | == Receiving replies from i3 |
97 | 98 | |
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. | |
105 | 108 | |
106 | 109 | The reply format is identical to the normal message format. There also is |
107 | 110 | the magic string, then the message length, then the message type and the |
108 | 111 | 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. | |
109 | 122 | |
110 | 123 | The following reply types are implemented: |
111 | 124 | |
126 | 139 | VERSION (7):: |
127 | 140 | Reply to the GET_VERSION message. |
128 | 141 | BINDING_MODES (8):: |
129 | Reply to the GET_BINDING_MODES message. | |
142 | Reply to the GET_BINDING_MODES message. | |
130 | 143 | GET_CONFIG (9):: |
131 | 144 | Reply to the GET_CONFIG message. |
132 | 145 | TICK (10):: |
133 | 146 | Reply to the SEND_TICK message. |
147 | SYNC (11):: | |
148 | Reply to the SYNC message. | |
134 | 149 | GET_BINDING_STATE (12):: |
135 | 150 | Reply to the GET_BINDING_STATE message. |
136 | 151 | |
152 | == Messages and replies | |
153 | ||
137 | 154 | [[_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:* | |
139 | 166 | |
140 | 167 | The reply consists of a list of serialized maps for each command that was |
141 | 168 | parsed. Each has the property +success (bool)+ and may also include a |
169 | 196 | ------------------- |
170 | 197 | |
171 | 198 | [[_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:* | |
173 | 208 | |
174 | 209 | The reply consists of a serialized list of workspaces. Each workspace has the |
175 | 210 | following properties: |
233 | 268 | ------------------- |
234 | 269 | |
235 | 270 | [[_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:* | |
237 | 281 | |
238 | 282 | The reply consists of a single serialized map. The only property is |
239 | 283 | +success (bool)+, indicating whether the subscription was successful (the |
245 | 289 | ------------------- |
246 | 290 | |
247 | 291 | [[_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:* | |
249 | 301 | |
250 | 302 | The reply consists of a serialized list of outputs. Each output has the |
251 | 303 | following properties: |
256 | 308 | Whether this output is currently active (has a valid mode). |
257 | 309 | primary (boolean):: |
258 | 310 | Whether this output is currently the primary output. |
259 | current_workspace (string):: | |
311 | current_workspace (string or null):: | |
260 | 312 | The name of the current workspace that is visible on this output. +null+ if |
261 | 313 | the output is not active. |
262 | 314 | rect (map):: |
292 | 344 | ------------------- |
293 | 345 | |
294 | 346 | [[_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:* | |
296 | 356 | |
297 | 357 | The reply consists of a serialized tree. Each node in the tree (representing |
298 | 358 | one container) has at least the properties listed below. While the nodes might |
328 | 388 | "vertical". |
329 | 389 | THIS FIELD IS OBSOLETE. It is still present, but your code should not |
330 | 390 | use it. Instead, rely on the layout field. |
331 | percent (float):: | |
391 | percent (float or null):: | |
332 | 392 | The percentage which this container takes in its parent. A value of |
333 | 393 | +null+ means that the percent property does not make sense for this |
334 | 394 | container, for example for the root container. |
352 | 412 | geometry (map):: |
353 | 413 | The original geometry the window specified when i3 mapped it. Used when |
354 | 414 | switching a window to floating mode, for example. |
355 | window (integer):: | |
415 | window (integer or null):: | |
356 | 416 | 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 | |
358 | 418 | containers. This ID corresponds to what xwininfo(1) and other |
359 | 419 | X11-related tools display (usually in hex). |
360 | 420 | window_properties (map):: |
361 | 421 | 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*. | |
363 | 424 | 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. | |
367 | 428 | urgent (bool):: |
368 | 429 | Whether this container (window, split container, floating container or |
369 | 430 | workspace) has the urgency hint set, directly or indirectly. All parent |
378 | 439 | order. Traversing the tree by following the first entry in this array |
379 | 440 | will result in eventually reaching the one node with +focused+ set to |
380 | 441 | 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. | |
381 | 445 | fullscreen_mode (integer):: |
382 | 446 | Whether this container is in fullscreen state or not. |
383 | 447 | Possible values are |
385 | 449 | +1+ (fullscreened on output) or |
386 | 450 | +2+ (fullscreened globally). |
387 | 451 | 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" | |
389 | 455 | nodes (array of node):: |
390 | 456 | The tiling (i.e. non-floating) child containers of this node. |
391 | 457 | floating_nodes (array of node):: |
392 | 458 | The floating child containers of this node. Only non-empty on nodes with |
393 | 459 | 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"). | |
394 | 464 | |
395 | 465 | Please note that in the following example, I have left out some keys/values |
396 | 466 | which are not relevant for the type of the node. Otherwise, the example would |
531 | 601 | ----------------------- |
532 | 602 | |
533 | 603 | [[_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:* | |
535 | 613 | |
536 | 614 | The reply consists of a single array of strings for each container that has a |
537 | 615 | mark. A mark can only be set on one container, so the array is unique. |
540 | 618 | If no window has a mark the response will be the empty array []. |
541 | 619 | |
542 | 620 | [[_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:* | |
544 | 630 | |
545 | 631 | This can be used by third-party workspace bars (especially i3bar, but others |
546 | 632 | are free to implement compatible alternatives) to get the +bar+ block |
640 | 726 | -------------- |
641 | 727 | |
642 | 728 | [[_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:* | |
644 | 738 | |
645 | 739 | The reply consists of a single JSON dictionary with the following keys: |
646 | 740 | |
673 | 767 | ------------------- |
674 | 768 | |
675 | 769 | [[_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:* | |
677 | 779 | |
678 | 780 | The reply consists of an array of all currently configured binding modes. |
679 | 781 | |
683 | 785 | --------------------- |
684 | 786 | |
685 | 787 | [[_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 | } | |
694 | 836 | ------------------- |
695 | 837 | |
696 | 838 | [[_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:* | |
698 | 848 | |
699 | 849 | The reply is a map containing the "success" member. After the reply was |
700 | 850 | received, the tick event has been written to all IPC connections which subscribe |
708 | 858 | ------------------- |
709 | 859 | |
710 | 860 | [[_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:* | |
712 | 870 | |
713 | 871 | The reply is a map containing the "success" member. After the reply was |
714 | 872 | received, the https://i3wm.org/docs/testsuite.html#i3_sync[i3 sync message] was |
720 | 878 | ------------------- |
721 | 879 | |
722 | 880 | [[_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:* | |
724 | 890 | |
725 | 891 | The binding_state reply is a map which currently only contains the "name" |
726 | 892 | member, which is the name of the currently active binding mode as a string. |
184 | 184 | container. Only if you start Emacs with the proper instance name (+emacs24 |
185 | 185 | --name notmuch+), it will get swallowed. |
186 | 186 | |
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: | |
190 | 190 | |
191 | 191 | -------------------------------------------------------------------------------- |
192 | 192 | $ xprop |
3 | 3 | September 2012 |
4 | 4 | |
5 | 5 | 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 | |
8 | 8 | interest for end users. |
9 | ||
10 | 9 | |
11 | 10 | == Introduction |
12 | 11 | |
480 | 479 | IPC anymore. |
481 | 480 | |
482 | 481 | [[i3_sync]] |
483 | == Appendix A: The i3 sync protocol | |
482 | == Appendix A: The I3_SYNC protocol | |
484 | 483 | |
485 | 484 | Consider the following situation: You open two windows in your testcase, then |
486 | 485 | you use +focus left+ and want to verify that the X11 focus has been updated |
498 | 497 | However, the test fails. Sometimes. Apparently, there is a race condition in |
499 | 498 | your test. If you think about it, this is because you are using two different |
500 | 499 | 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. | |
504 | 503 | |
505 | 504 | image::i3-sync.png["Diagram of the race condition", title="Diagram of the race condition"] |
506 | 505 | |
530 | 529 | |
531 | 530 | The real solution for this problem is a mechanism which I call "the i3 sync |
532 | 531 | 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). | |
537 | 536 | |
538 | 537 | image::i3-sync-working.png["Diagram of the i3 sync solution", title="Diagram of the i3 sync solution"] |
539 | 538 | |
568 | 567 | request. You should use a random value in +data[1]+ and check that you received |
569 | 568 | the same one when getting the reply. |
570 | 569 | |
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 | |
572 | 599 | |
573 | 600 | Socket activation is a mechanism which was made popular by systemd, an init |
574 | 601 | replacement. It basically describes creating a listening socket before starting |
2 | 2 | Michael Stapelberg <[email protected]> |
3 | 3 | |
4 | 4 | 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. | |
8 | 8 | |
9 | 9 | == Default keybindings |
10 | 10 | |
52 | 52 | |
53 | 53 | image:two_terminals.png[Two terminals] |
54 | 54 | |
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. | |
61 | 72 | |
62 | 73 | At the moment, your workspace is split (it contains two terminals) in a |
63 | 74 | specific direction (horizontal by default). Every window can be split |
66 | 77 | or browser) and "split container" for containers that consist of one or more |
67 | 78 | windows. |
68 | 79 | |
69 | TODO: picture of the tree | |
80 | //TODO: picture of the tree | |
70 | 81 | |
71 | 82 | To split a window vertically, press +$mod+v+ before you create the new window. |
72 | 83 | To split it horizontally, press +$mod+h+. |
184 | 195 | |
185 | 196 | Floating windows are always on top of tiling windows. |
186 | 197 | |
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]] | |
187 | 241 | == Tree |
188 | 242 | |
189 | 243 | i3 stores all information about the X11 outputs, workspaces and layout of the |
307 | 361 | # i3 config file (v4) |
308 | 362 | --------------------- |
309 | 363 | |
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 | ||
310 | 448 | === Comments |
311 | 449 | |
312 | 450 | It is possible and recommended to use comments in your configuration file to |
442 | 580 | # The middle button over a titlebar kills the window |
443 | 581 | bindsym --release button2 kill |
444 | 582 | |
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 | |
446 | 584 | bindsym --whole-window $mod+button2 kill |
447 | 585 | |
448 | 586 | # The right button toggles floating |
596 | 734 | title_align left|center|right |
597 | 735 | --------------------------------------------- |
598 | 736 | |
737 | [[default_border]] | |
599 | 738 | === Default border style for new windows |
600 | 739 | |
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 | |
602 | 741 | +normal+. Note that default_floating_border applies only to windows which are starting out as |
603 | 742 | floating windows, e.g., dialog windows, but not windows that are floated later on. |
604 | 743 | |
944 | 1083 | workspace "2: vim" output VGA1 |
945 | 1084 | --------------------------- |
946 | 1085 | |
1086 | [[client_colors]] | |
947 | 1087 | === Changing colors |
948 | 1088 | |
949 | 1089 | You can change all colors which i3 uses to draw the window decorations. |
960 | 1100 | client.focused_inactive:: |
961 | 1101 | A client which is the focused one of its container, but it does not have |
962 | 1102 | 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. | |
963 | 1107 | client.unfocused:: |
964 | 1108 | A client which is not the focused one of its container. |
965 | 1109 | client.urgent:: |
1003 | 1147 | programs to get information from i3, such as the current workspaces |
1004 | 1148 | (to display a workspace bar), and to control i3. |
1005 | 1149 | |
1006 | The IPC socket is enabled by default and will be created in | |
1150 | By default, an IPC socket will be created in | |
1007 | 1151 | +$XDG_RUNTIME_DIR/i3/ipc-socket.%p+ if the directory is available, falling back |
1008 | 1152 | to +/tmp/i3-%u.XXXXXX/ipc-socket.%p+, where +%u+ is your UNIX username, +%p+ is |
1009 | 1153 | the PID of i3 and XXXXXX is a string of random characters from the portable |
1257 | 1401 | # this line is not continued \ |
1258 | 1402 | bindsym Mod1+F fullscreen toggle |
1259 | 1403 | ------------------- |
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 | -------------------------------- | |
1260 | 1427 | |
1261 | 1428 | == Configuring i3bar |
1262 | 1429 | |
1879 | 2046 | # enable floating mode and move container to workspace 4 |
1880 | 2047 | for_window [class="^evil-app$"] floating enable, move container to workspace 4 |
1881 | 2048 | |
2049 | # enable window icons for all windows with extra horizontal padding of 1px | |
2050 | for_window [all] title_window_icon padding 1px | |
2051 | ||
1882 | 2052 | # move all floating windows to the scratchpad |
1883 | 2053 | bindsym $mod+x [floating] move scratchpad |
1884 | 2054 | ------------------------------------ |
1885 | 2055 | |
1886 | 2056 | The criteria which are currently implemented are: |
1887 | 2057 | |
2058 | all:: | |
2059 | Matches all windows. This criterion requires no value. | |
1888 | 2060 | class:: |
1889 | 2061 | Compares the window class (the second part of WM_CLASS). Use the |
1890 | 2062 | special value +\_\_focused__+ to match all windows having the same window |
1901 | 2073 | Compare the window type (_NET_WM_WINDOW_TYPE). Possible values are |
1902 | 2074 | +normal+, +dialog+, +utility+, +toolbar+, +splash+, +menu+, +dropdown_menu+, |
1903 | 2075 | +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. | |
1904 | 2080 | id:: |
1905 | 2081 | Compares the X11 window ID, which you can get via +xwininfo+ for example. |
1906 | 2082 | title:: |
1938 | 2114 | tiling are matched. With "user", only windows that the user made tiling |
1939 | 2115 | are matched. |
1940 | 2116 | |
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. | |
1944 | 2120 | |
1945 | 2121 | [[exec]] |
1946 | 2122 | === Executing applications (exec) |
2119 | 2295 | focus left|right|down|up |
2120 | 2296 | focus parent|child|floating|tiling|mode_toggle |
2121 | 2297 | 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]… | |
2123 | 2299 | ---------------------------------------------- |
2124 | 2300 | |
2125 | 2301 | *Examples*: |
2139 | 2315 | # Focus last floating/tiling container |
2140 | 2316 | bindsym $mod+g focus mode_toggle |
2141 | 2317 | |
2318 | # Focus the next output (effectively toggles when you only have two outputs) | |
2319 | bindsym $mod+x move workspace to output next | |
2320 | ||
2142 | 2321 | # Focus the output right to the current one |
2143 | 2322 | bindsym $mod+x focus output right |
2144 | 2323 | |
2147 | 2326 | |
2148 | 2327 | # Focus the primary output |
2149 | 2328 | 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 | |
2150 | 2332 | ------------------------------------------------- |
2151 | 2333 | |
2152 | 2334 | Note that you might not have a primary output configured yet. To do so, run: |
2154 | 2336 | xrandr --output <output> --primary |
2155 | 2337 | ------------------------- |
2156 | 2338 | |
2339 | [[move_direction]] | |
2157 | 2340 | === Moving containers |
2158 | 2341 | |
2159 | 2342 | Use the +move+ command to move a container. |
2280 | 2463 | See <<move_to_outputs>> for how to move a container/workspace to a different |
2281 | 2464 | RandR output. |
2282 | 2465 | |
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. | |
2286 | 2468 | |
2287 | 2469 | [[back_and_forth]] |
2288 | 2470 | To switch back to the previously focused workspace, use +workspace |
2389 | 2571 | If a workspace does not exist, the command +workspace number "1: mail"+ will |
2390 | 2572 | create workspace "1: mail". |
2391 | 2573 | |
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 | |
2393 | 2575 | 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. | |
2395 | 2579 | |
2396 | 2580 | === Moving workspaces to a different screen |
2397 | 2581 | |
2407 | 2591 | |
2408 | 2592 | *Syntax*: |
2409 | 2593 | ------------------------------------------------------------ |
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 | ------------------------------------------------------------------------------------- | |
2413 | 2597 | |
2414 | 2598 | *Examples*: |
2415 | 2599 | -------------------------------------------------------- |
2416 | 2600 | # Move the current workspace to the next output |
2417 | 2601 | # (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 | |
2419 | 2606 | |
2420 | 2607 | # Put this window on the presentation output. |
2421 | 2608 | bindsym $mod+x move container to output VGA1 |
2423 | 2610 | # Put this window on the primary output. |
2424 | 2611 | bindsym $mod+x move container to output primary |
2425 | 2612 | -------------------------------------------------------- |
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. | |
2426 | 2618 | |
2427 | 2619 | Note that you might not have a primary output configured yet. To do so, run: |
2428 | 2620 | ------------------------- |
2429 | 2621 | xrandr --output <output> --primary |
2430 | 2622 | ------------------------- |
2431 | 2623 | |
2624 | [[move_to_mark]] | |
2432 | 2625 | === Moving containers/windows to marks |
2433 | 2626 | |
2434 | 2627 | To move a container to another container with a specific mark (see <<vim_like_marks>>), |
2574 | 2767 | |
2575 | 2768 | By default, i3 will simply print the X11 window title. Using +title_format+, |
2576 | 2769 | 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: | |
2580 | 2772 | |
2581 | 2773 | +%title+:: |
2582 | 2774 | For normal windows, this is the X11 window title (_NET_WM_NAME or WM_NAME |
2589 | 2781 | +%instance+:: |
2590 | 2782 | The X11 window instance (first part of WM_CLASS). This corresponds to the |
2591 | 2783 | +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>>. | |
2592 | 2787 | |
2593 | 2788 | Using the <<for_window>> directive, you can set the title format for any window |
2594 | 2789 | based on <<command_criteria>>. |
2608 | 2803 | |
2609 | 2804 | # print window titles of firefox windows red |
2610 | 2805 | 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 | |
2611 | 2832 | ------------------------------------------------------------------------------------- |
2612 | 2833 | |
2613 | 2834 | === Changing border style |
2639 | 2860 | bindsym $mod+t border normal 0 |
2640 | 2861 | # use no window title and a thick border |
2641 | 2862 | bindsym $mod+y border pixel 3 |
2863 | # use window title *and* a thick border | |
2864 | bindsym $mod+y border normal 3 | |
2642 | 2865 | # use neither window title nor border |
2643 | 2866 | bindsym $mod+u border none |
2867 | # no border on VLC | |
2868 | for_window [class="vlc"] border none | |
2644 | 2869 | ---------------------------------------------- |
2870 | ||
2871 | To change the default for all windows, see the directive <<default_border>>. | |
2645 | 2872 | |
2646 | 2873 | [[shmlog]] |
2647 | 2874 | === Enabling shared memory logging |
15 | 15 | # This font is widely installed, provides lots of unicode glyphs, right-to-left |
16 | 16 | # text rendering and scalability on retina/hidpi displays (thanks to pango). |
17 | 17 | #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 | |
18 | 22 | |
19 | 23 | # The combination of xss-lock, nm-applet and pactl is a popular choice, so |
20 | 24 | # they are included here as an example. Modify as you see fit. |
43 | 47 | |
44 | 48 | # use Mouse+Mod1 to drag floating windows to their wanted position |
45 | 49 | 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 | |
46 | 54 | |
47 | 55 | # start a terminal |
48 | 56 | bindsym Mod1+Return exec i3-sensible-terminal |
16 | 16 | # This font is widely installed, provides lots of unicode glyphs, right-to-left |
17 | 17 | # text rendering and scalability on retina/hidpi displays (thanks to pango). |
18 | 18 | #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 | |
19 | 23 | |
20 | 24 | # The combination of xss-lock, nm-applet and pactl is a popular choice, so |
21 | 25 | # they are included here as an example. Modify as you see fit. |
37 | 41 | |
38 | 42 | # Use Mouse+$mod to drag floating windows to their wanted position |
39 | 43 | 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 | |
40 | 48 | |
41 | 49 | # start a terminal |
42 | 50 | bindcode $mod+36 exec i3-sensible-terminal |
132 | 132 | open(my $callfh, '>', "GENERATED_${prefix}_call.h"); |
133 | 133 | my $resultname = uc(substr($prefix, 0, 1)) . substr($prefix, 1) . 'ResultIR'; |
134 | 134 | 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) {"; | |
136 | 136 | say $callfh ' switch (call_identifier) {'; |
137 | 137 | my $call_id = 0; |
138 | 138 | for my $state (@keys) { |
149 | 149 | # calls to get_string(). Also replaces state names (like FOR_WINDOW) |
150 | 150 | # with their ID (useful for cfg_criteria_init(FOR_WINDOW) e.g.). |
151 | 151 | $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; | |
154 | 154 | # For debugging/testing, we print the call using printf() and thus need |
155 | 155 | # to generate a format string. The format uses %d for <number>s, |
156 | 156 | # literal numbers or state IDs and %s for NULL, <string>s and literal |
174 | 174 | say $callfh '#ifndef TEST_PARSER'; |
175 | 175 | my $real_cmd = $cmd; |
176 | 176 | if ($real_cmd =~ /\(\)/) { |
177 | $real_cmd =~ s/\(/(¤t_match, result/; | |
177 | $real_cmd =~ s/\(/(current_match, result/; | |
178 | 178 | } else { |
179 | $real_cmd =~ s/\(/(¤t_match, result, /; | |
179 | $real_cmd =~ s/\(/(current_match, result, /; | |
180 | 180 | } |
181 | 181 | say $callfh " $real_cmd;"; |
182 | 182 | say $callfh '#else'; |
227 | 227 | ($call_identifier) = ($next_state =~ /^call ([0-9]+)$/); |
228 | 228 | $next_state = '__CALL'; |
229 | 229 | } |
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 } },|; | |
232 | 239 | } |
233 | 240 | say $tokfh '};'; |
234 | 241 | } |
330 | 330 | * separate configuration directives. */ |
331 | 331 | while ((*walk == ' ' || *walk == '\t') && *walk != '\0') |
332 | 332 | walk++; |
333 | ||
334 | //printf("remaining input: %s\n", walk); | |
335 | 333 | |
336 | 334 | cmdp_token_ptr *ptr = &(tokens[state]); |
337 | 335 | for (c = 0; c < ptr->n; c++) { |
425 | 423 | } |
426 | 424 | |
427 | 425 | if (strcmp(token->name, "end") == 0) { |
428 | //printf("checking for end: *%s*\n", walk); | |
429 | 426 | if (*walk == '\0' || *walk == '\n' || *walk == '\r') { |
430 | 427 | if ((result = next_state(token)) != NULL) |
431 | 428 | 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 | |
434 | 431 | * criteria, we re-initialize the criteria system after |
435 | 432 | * every command. */ |
436 | // TODO: make this testable | |
437 | 433 | walk++; |
438 | 434 | break; |
439 | 435 | } |
153 | 153 | }, |
154 | 154 | no_chdir => 1, |
155 | 155 | follow_fast => 1, |
156 | # Ignore any duplicate files and directories and proceed normally: | |
157 | follow_skip => 2, | |
156 | 158 | }, |
157 | 159 | @searchdirs |
158 | 160 | ); |
410 | 412 | my $location = $app->{_Location}; |
411 | 413 | |
412 | 414 | # 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 | |
414 | 416 | sub quote { |
415 | 417 | my ($str) = @_; |
416 | 418 | $str =~ s/("|`|\$|\\)/\\$1/g; |
421 | 423 | $choice = quote($choice); |
422 | 424 | $location = quote($location); |
423 | 425 | $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; | |
424 | 437 | |
425 | 438 | # Remove deprecated field codes, as the spec dictates. |
426 | 439 | $exec =~ s/%[dDnNvm]//g; |
478 | 491 | # starts with a double quote ("), everything is parsed as-is until the next |
479 | 492 | # double quote which is NOT preceded by a backslash (\). |
480 | 493 | # |
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; | |
483 | 498 | |
484 | 499 | if (exists($app->{StartupNotify}) && !$app->{StartupNotify}) { |
485 | 500 | $nosn = '--no-startup-id'; |
24 | 24 | #include <sys/mman.h> |
25 | 25 | #include <sys/stat.h> |
26 | 26 | #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 | ||
31 | 31 | static uint32_t wrap_count; |
32 | 32 | |
33 | 33 | static i3_shmlog_header *header; |
34 | 34 | static char *logbuffer, |
35 | 35 | *walk; |
36 | 36 | static int ipcfd = -1; |
37 | ||
38 | static volatile bool interrupted = false; | |
39 | ||
40 | static void sighandler(int signal) { | |
41 | interrupted = true; | |
42 | } | |
43 | 37 | |
44 | 38 | static void disable_shmlog(void) { |
45 | 39 | const char *disablecmd = "debuglog off; shmlog off"; |
187 | 181 | |
188 | 182 | /* NB: While we must never write, we need O_RDWR for the pthread condvar. */ |
189 | 183 | int logbuffer_shm = shm_open(shmname, O_RDWR, 0); |
190 | if (logbuffer_shm == -1) | |
184 | if (logbuffer_shm == -1) { | |
191 | 185 | 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) { | |
194 | 189 | 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) { | |
199 | 194 | err(EXIT_FAILURE, "Could not mmap SHM segment for the i3 log"); |
195 | } | |
200 | 196 | |
201 | 197 | header = (i3_shmlog_header *)logbuffer; |
202 | 198 | |
203 | if (verbose) | |
199 | if (verbose) { | |
204 | 200 | printf("next_write = %d, last_wrap = %d, logbuffer_size = %d, shmname = %s\n", |
205 | 201 | header->offset_next_write, header->offset_last_wrap, header->size, shmname); |
202 | } | |
206 | 203 | free(shmname); |
207 | 204 | walk = logbuffer + header->offset_next_write; |
208 | 205 | |
232 | 229 | return 0; |
233 | 230 | } |
234 | 231 | |
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); | |
254 | 264 | } |
255 | 265 | |
256 | 266 | #endif |
0 | 0 | # This list can be used to convert X11 Keysyms to Unicode 2.1 character. |
1 | 1 | # 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 | |
3 | 3 | # occurred due to using this list. |
4 | 4 | # |
5 | 5 | # The list is created by looking at the Keysym names and the Unicode data |
155 | 155 | char *payload = NULL; |
156 | 156 | bool quiet = false; |
157 | 157 | bool monitor = false; |
158 | bool raw_reply = false; | |
158 | 159 | |
159 | 160 | static struct option long_options[] = { |
160 | 161 | {"socket", required_argument, 0, 's'}, |
163 | 164 | {"quiet", no_argument, 0, 'q'}, |
164 | 165 | {"monitor", no_argument, 0, 'm'}, |
165 | 166 | {"help", no_argument, 0, 'h'}, |
167 | {"raw", no_argument, 0, 'r'}, | |
166 | 168 | {0, 0, 0, 0}}; |
167 | 169 | |
168 | char *options_string = "s:t:vhqm"; | |
170 | char *options_string = "s:t:vhqmr"; | |
169 | 171 | |
170 | 172 | while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { |
171 | 173 | if (o == 's') { |
216 | 218 | return 0; |
217 | 219 | } else if (o == '?') { |
218 | 220 | exit(EXIT_FAILURE); |
221 | } else if (o == 'r') { | |
222 | raw_reply = true; | |
219 | 223 | } |
220 | 224 | } |
221 | 225 | |
261 | 265 | /* For the reply of commands, have a look if that command was successful. |
262 | 266 | * If not, nicely format the error message. */ |
263 | 267 | 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) { | |
277 | 283 | printf("%.*s\n", reply_length, reply); |
278 | 284 | } |
279 | 285 | } 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 | } | |
290 | 300 | } |
291 | 301 | } else if (reply_type == I3_IPC_REPLY_TYPE_SUBSCRIBE) { |
292 | 302 | do { |
265 | 265 | } |
266 | 266 | |
267 | 267 | /** |
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) { | |
275 | 271 | xcb_randr_get_screen_resources_current_cookie_t rcookie = xcb_randr_get_screen_resources_current(conn, root); |
276 | 272 | xcb_randr_get_output_primary_cookie_t pcookie = xcb_randr_get_output_primary(conn, root); |
277 | 273 | |
313 | 309 | goto free_resources; |
314 | 310 | } |
315 | 311 | |
316 | result.x = crtc->x; | |
317 | result.y = crtc->y; | |
312 | result->x = crtc->x; | |
313 | result->y = crtc->y; | |
318 | 314 | goto free_resources; |
319 | 315 | |
320 | 316 | free_resources: |
321 | 317 | free(res); |
322 | 318 | 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 | } | |
324 | 367 | } |
325 | 368 | |
326 | 369 | int main(int argc, char *argv[]) { |
359 | 402 | |
360 | 403 | argv0 = argv[0]; |
361 | 404 | |
405 | bool position_on_primary = false; | |
362 | 406 | char *pattern = sstrdup("pango:monospace 8"); |
363 | 407 | int o, option_index = 0; |
364 | 408 | enum { TYPE_ERROR = 0, |
372 | 416 | {"help", no_argument, 0, 'h'}, |
373 | 417 | {"message", required_argument, 0, 'm'}, |
374 | 418 | {"type", required_argument, 0, 't'}, |
419 | {"primary", no_argument, 0, 'p'}, | |
375 | 420 | {0, 0, 0, 0}}; |
376 | 421 | |
377 | char *options_string = "b:B:f:m:t:vh"; | |
422 | char *options_string = "b:B:f:m:t:vhp"; | |
378 | 423 | |
379 | 424 | prompt = i3string_from_utf8("Please do not run this program."); |
380 | 425 | |
398 | 443 | case 'h': |
399 | 444 | free(pattern); |
400 | 445 | 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"); | |
402 | 447 | return 0; |
448 | case 'p': | |
449 | position_on_primary = true; | |
450 | break; | |
403 | 451 | case 'b': |
404 | 452 | case 'B': |
405 | 453 | buttons = srealloc(buttons, sizeof(button_t) * (buttoncnt + 1)); |
463 | 511 | err(EXIT_FAILURE, "pledge"); |
464 | 512 | #endif |
465 | 513 | |
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 | } | |
467 | 521 | |
468 | 522 | xcb_cursor_context_t *cursor_ctx; |
469 | 523 | if (xcb_cursor_context_new(conn, root_screen, &cursor_ctx) < 0) { |
7 | 7 | # Distributions/packagers can enhance this script with a |
8 | 8 | # distribution-specific mechanism to find the preferred pager. |
9 | 9 | |
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 | ||
10 | 15 | # Hopefully one of these is installed (no flamewars about preference please!): |
11 | 16 | # We don't use 'more' because it will exit if the file is too short. |
12 | 17 | # Worst case scenario we'll open the file in your editor. |
7 | 7 | # We welcome patches that add distribution-specific mechanisms to find the |
8 | 8 | # preferred terminal emulator. On Debian, there is the x-terminal-emulator |
9 | 9 | # 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 | |
11 | 17 | if command -v "$terminal" > /dev/null 2>&1; then |
12 | 18 | exec "$terminal" "$@" |
13 | 19 | fi |
42 | 42 | } i3bar_child; |
43 | 43 | |
44 | 44 | /* |
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 | /* | |
45 | 51 | * Start a child process with the specified command and reroute stdin. |
46 | 52 | * We actually start a $SHELL to execute the command so we don't have to care |
47 | 53 | * about arguments and such |
65 | 65 | uint32_t border_left; |
66 | 66 | bool pango_markup; |
67 | 67 | |
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. */ | |
69 | 69 | uint32_t sep_block_width; |
70 | 70 | |
71 | 71 | /* Continuously-updated information on how to render this status block. */ |
75 | 75 | void parse_config_json(char *json); |
76 | 76 | |
77 | 77 | /** |
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 | /** | |
78 | 85 | * free()s the color strings as soon as they are not needed anymore. |
79 | 86 | * |
80 | 87 | */ |
17 | 17 | * socket_path must be a valid path to the ipc_socket of i3 |
18 | 18 | * |
19 | 19 | */ |
20 | int init_connection(const char *socket_path); | |
20 | void init_connection(const char *socket_path); | |
21 | 21 | |
22 | 22 | /* |
23 | 23 | * Destroy the connection to i3. |
26 | 26 | #include <yajl/yajl_parse.h> |
27 | 27 | |
28 | 28 | /* Global variables for child_*() */ |
29 | i3bar_child child; | |
29 | i3bar_child child = {0}; | |
30 | 30 | #define DLOG_CHILD DLOG("%s: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \ |
31 | 31 | __func__, (long)child.pid, child.stopped, child.stop_signal, child.cont_signal, child.click_events, child.click_events_init) |
32 | 32 | |
65 | 65 | * Remove all blocks from the given statusline. |
66 | 66 | * If free_resources is set, the fields of each status block will be free'd. |
67 | 67 | */ |
68 | static void clear_statusline(struct statusline_head *head, bool free_resources) { | |
68 | void clear_statusline(struct statusline_head *head, bool free_resources) { | |
69 | 69 | struct status_block *first; |
70 | 70 | while (!TAILQ_EMPTY(head)) { |
71 | 71 | first = TAILQ_FIRST(head); |
138 | 138 | if (stdin_io != NULL) { |
139 | 139 | ev_io_stop(main_loop, stdin_io); |
140 | 140 | FREE(stdin_io); |
141 | close(stdin_fd); | |
142 | stdin_fd = 0; | |
143 | close(child_stdin); | |
144 | child_stdin = 0; | |
141 | 145 | } |
142 | 146 | |
143 | 147 | if (child_sig != NULL) { |
12 | 12 | #include <stdlib.h> |
13 | 13 | #include <string.h> |
14 | 14 | |
15 | #include <X11/Xlib.h> | |
16 | 15 | #include <yajl/yajl_parse.h> |
17 | 16 | |
18 | 17 | config_t config; |
125 | 124 | } |
126 | 125 | |
127 | 126 | if (len == strlen("shift") && !strncmp((const char *)val, "shift", strlen("shift"))) { |
128 | config.modifier = ShiftMask; | |
127 | config.modifier = XCB_MOD_MASK_SHIFT; | |
129 | 128 | return 1; |
130 | 129 | } |
131 | 130 | if (len == strlen("ctrl") && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) { |
132 | config.modifier = ControlMask; | |
131 | config.modifier = XCB_MOD_MASK_CONTROL; | |
133 | 132 | return 1; |
134 | 133 | } |
135 | 134 | if (len == strlen("Mod") + 1 && !strncmp((const char *)val, "Mod", strlen("Mod"))) { |
136 | 135 | switch (val[3]) { |
137 | 136 | case '1': |
138 | config.modifier = Mod1Mask; | |
137 | config.modifier = XCB_MOD_MASK_1; | |
139 | 138 | return 1; |
140 | 139 | case '2': |
141 | config.modifier = Mod2Mask; | |
140 | config.modifier = XCB_MOD_MASK_2; | |
142 | 141 | return 1; |
143 | 142 | case '3': |
144 | config.modifier = Mod3Mask; | |
143 | config.modifier = XCB_MOD_MASK_3; | |
145 | 144 | return 1; |
146 | 145 | case '5': |
147 | config.modifier = Mod5Mask; | |
146 | config.modifier = XCB_MOD_MASK_5; | |
148 | 147 | return 1; |
149 | 148 | } |
150 | 149 | } |
151 | 150 | |
152 | config.modifier = Mod4Mask; | |
151 | config.modifier = XCB_MOD_MASK_4; | |
153 | 152 | return 1; |
154 | 153 | } |
155 | 154 | |
183 | 182 | |
184 | 183 | if (!strcmp(cur_key, "status_command")) { |
185 | 184 | DLOG("command = %.*s\n", len, val); |
186 | FREE(config.command); | |
187 | 185 | sasprintf(&config.command, "%.*s", len, val); |
188 | 186 | return 1; |
189 | 187 | } |
372 | 370 | * |
373 | 371 | */ |
374 | 372 | 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); | |
378 | 374 | |
379 | 375 | TAILQ_INIT(&(config.bindings)); |
380 | 376 | TAILQ_INIT(&(config.tray_outputs)); |
381 | 377 | |
382 | state = yajl_parse(handle, (const unsigned char *)json, strlen(json)); | |
378 | yajl_status state = yajl_parse(handle, (const unsigned char *)json, strlen(json)); | |
383 | 379 | |
384 | 380 | /* FIXME: Proper error handling for JSON parsing */ |
385 | 381 | switch (state) { |
392 | 388 | break; |
393 | 389 | } |
394 | 390 | |
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)); | |
395 | 410 | yajl_free(handle); |
396 | 411 | } |
397 | 412 |
84 | 84 | * |
85 | 85 | */ |
86 | 86 | 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 | ||
87 | 101 | DLOG("Received bar config \"%s\"\n", reply); |
88 | 102 | /* We initiate the main function by requesting infos about the outputs and |
89 | 103 | * workspaces. Everything else (creating the bars, showing the right workspace- |
140 | 154 | static void got_output_event(char *event) { |
141 | 155 | DLOG("Got output event!\n"); |
142 | 156 | 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 | } | |
146 | 157 | } |
147 | 158 | |
148 | 159 | /* |
153 | 164 | DLOG("Got mode event!\n"); |
154 | 165 | parse_mode_json(event); |
155 | 166 | 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; | |
156 | 179 | } |
157 | 180 | |
158 | 181 | /* |
175 | 198 | |
176 | 199 | /* update the configuration with the received settings */ |
177 | 200 | 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; | |
179 | 204 | bar_display_mode_t old_mode = config.hide_on_modifier; |
205 | ||
180 | 206 | parse_config_json(event); |
181 | 207 | if (old_mode != config.hide_on_modifier) { |
182 | 208 | reconfig_windows(true); |
187 | 213 | init_colors(&(config.colors)); |
188 | 214 | |
189 | 215 | /* restart status command process */ |
190 | if (old_command && strcmp(old_command, config.command) != 0) { | |
216 | if (strings_differ(old_command, config.command)) { | |
191 | 217 | kill_child(); |
218 | clear_statusline(&statusline_head, true); | |
192 | 219 | start_child(config.command); |
193 | 220 | } |
194 | 221 | free(old_command); |
327 | 354 | * socket_path must be a valid path to the ipc_socket of i3 |
328 | 355 | * |
329 | 356 | */ |
330 | int init_connection(const char *socket_path) { | |
357 | void init_connection(const char *socket_path) { | |
331 | 358 | sock_path = socket_path; |
332 | 359 | int sockfd = ipc_connect(socket_path); |
333 | 360 | i3_connection = smalloc(sizeof(ev_io)); |
334 | 361 | ev_io_init(i3_connection, &got_data, sockfd, EV_READ); |
335 | 362 | ev_io_start(main_loop, i3_connection); |
336 | return 1; | |
337 | 363 | } |
338 | 364 | |
339 | 365 | /* |
55 | 55 | } |
56 | 56 | |
57 | 57 | 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); | |
59 | 59 | 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"); | |
61 | 61 | printf("-s, --socket <sock_path>\tConnect to i3 via <sock_path>\n"); |
62 | 62 | printf("-t, --transparency Enable transparency (RGBA colors)\n"); |
63 | 63 | printf("-h, --help Display this help message and exit\n"); |
127 | 127 | break; |
128 | 128 | default: |
129 | 129 | print_usage(argv[0]); |
130 | exit(EXIT_SUCCESS); | |
130 | exit(EXIT_FAILURE); | |
131 | 131 | break; |
132 | 132 | } |
133 | 133 | } |
134 | 134 | |
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"); | |
141 | 136 | |
142 | 137 | main_loop = ev_default_loop(0); /* needed in init_xcb_early */ |
143 | 138 | char *atom_sock_path = init_xcb_early(); |
165 | 160 | init_dpi(); |
166 | 161 | |
167 | 162 | 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); | |
172 | 170 | free(socket_path); |
173 | 171 | |
174 | 172 | /* We listen to SIGTERM/QUIT/INT and try to exit cleanly, by stopping the main loop. |
202 | 202 | if (block->border) |
203 | 203 | render->width += logical_px(block->border_left + block->border_right); |
204 | 204 | |
205 | /* Compute offset and append for text aligment in min_width. */ | |
205 | /* Compute offset and append for text alignment in min_width. */ | |
206 | 206 | if (block->min_width <= render->width) { |
207 | 207 | render->x_offset = 0; |
208 | 208 | render->x_append = 0; |
1539 | 1539 | free_font(); |
1540 | 1540 | |
1541 | 1541 | xcb_free_cursor(xcb_connection, cursor); |
1542 | xcb_flush(xcb_connection); | |
1543 | 1542 | xcb_aux_sync(xcb_connection); |
1544 | 1543 | xcb_disconnect(xcb_connection); |
1545 | 1544 |
55 | 55 | #include "render.h" |
56 | 56 | #include "window.h" |
57 | 57 | #include "match.h" |
58 | #include "cmdparse.h" | |
59 | 58 | #include "xcursor.h" |
60 | 59 | #include "resize.h" |
60 | #include "tiling_drag.h" | |
61 | 61 | #include "sighandler.h" |
62 | 62 | #include "move.h" |
63 | 63 | #include "output.h" |
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); |
137 | 137 | * Implementation of 'move [window|container] [to] output <str>'. |
138 | 138 | * |
139 | 139 | */ |
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); | |
141 | 141 | |
142 | 142 | /** |
143 | 143 | * Implementation of 'move [window|container] [to] mark <str>'. |
152 | 152 | void cmd_floating(I3_CMD, const char *floating_mode); |
153 | 153 | |
154 | 154 | /** |
155 | * Implementation of 'move workspace to [output] <str>'. | |
156 | * | |
157 | */ | |
158 | void cmd_move_workspace_to_output(I3_CMD, const char *name); | |
159 | ||
160 | /** | |
161 | 155 | * Implementation of 'split v|h|t|vertical|horizontal|toggle'. |
162 | 156 | * |
163 | 157 | */ |
342 | 336 | * |
343 | 337 | */ |
344 | 338 | 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); |
13 | 13 | #include <yajl/yajl_gen.h> |
14 | 14 | |
15 | 15 | /** |
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. | |
17 | 17 | * When calling parse_command("floating enable, border none"), the parser will |
18 | 18 | * internally use this struct when calling cmd_floating and cmd_border. |
19 | 19 | */ |
206 | 206 | * |
207 | 207 | */ |
208 | 208 | 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); | |
209 | 217 | |
210 | 218 | /** |
211 | 219 | * Returns true if and only if the given containers holds the mark. |
362 | 370 | */ |
363 | 371 | bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates); |
364 | 372 | |
373 | bool con_move_to_target(Con *con, Con *target); | |
365 | 374 | /** |
366 | 375 | * Moves the given container to the given mark. |
367 | 376 | * |
441 | 450 | * floating window. |
442 | 451 | * |
443 | 452 | */ |
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); | |
445 | 454 | |
446 | 455 | /** |
447 | 456 | * This function changes the layout of a given container. Use it to handle |
38 | 38 | CFGFUN(criteria_add, const char *ctype, const char *cvalue); |
39 | 39 | CFGFUN(criteria_pop_state); |
40 | 40 | |
41 | CFGFUN(include, const char *pattern); | |
41 | 42 | CFGFUN(font, const char *font); |
42 | 43 | CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command); |
43 | 44 | CFGFUN(for_window, const char *command); |
66 | 67 | CFGFUN(no_focus); |
67 | 68 | CFGFUN(ipc_socket, const char *path); |
68 | 69 | CFGFUN(ipc_kill_timeout, const long timeout_ms); |
70 | CFGFUN(tiling_drag, const char *value); | |
69 | 71 | CFGFUN(restart_state, const char *path); |
70 | 72 | CFGFUN(popup_during_fullscreen, const char *value); |
71 | 73 | CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border); |
15 | 15 | SLIST_HEAD(variables_head, Variable); |
16 | 16 | extern pid_t config_error_nagbar_pid; |
17 | 17 | |
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 | ||
18 | 62 | /** |
19 | * An intermediate reprsentation of the result of a parse_config call. | |
63 | * An intermediate representation of the result of a parse_config call. | |
20 | 64 | * Currently unused, but the JSON output will be useful in the future when we |
21 | 65 | * implement a config parsing IPC command. |
22 | 66 | * |
23 | 67 | */ |
24 | 68 | struct ConfigResultIR { |
25 | /* The JSON generator to append a reply to. */ | |
26 | yajl_gen json_gen; | |
69 | struct parser_ctx *ctx; | |
27 | 70 | |
28 | 71 | /* The next state to transition to. Passed to the function so that we can |
29 | 72 | * determine the next state as a result of a function call, like |
30 | 73 | * cfg_criteria_pop_state() does. */ |
31 | 74 | int next_state; |
75 | ||
76 | /* Whether any error happened while processing this config directive. */ | |
77 | bool has_errors; | |
32 | 78 | }; |
33 | ||
34 | struct ConfigResultIR *parse_config(const char *input, struct context *context); | |
35 | 79 | |
36 | 80 | /** |
37 | 81 | * launch nagbar to indicate errors in the configuration file. |
38 | 82 | */ |
39 | 83 | 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; | |
40 | 96 | |
41 | 97 | /** |
42 | 98 | * Parses the given file by first replacing the variables, then calling |
46 | 102 | * parsing. |
47 | 103 | * |
48 | 104 | */ |
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); |
13 | 13 | |
14 | 14 | #include "queue.h" |
15 | 15 | #include "i3.h" |
16 | ||
16 | #include "tiling_drag.h" | |
17 | ||
18 | typedef struct IncludedFile IncludedFile; | |
17 | 19 | typedef struct Config Config; |
18 | 20 | typedef struct Barconfig Barconfig; |
19 | 21 | extern char *current_configpath; |
21 | 23 | extern Config config; |
22 | 24 | extern SLIST_HEAD(modes_head, Mode) modes; |
23 | 25 | extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs; |
26 | extern TAILQ_HEAD(includedfiles_head, IncludedFile) included_files; | |
24 | 27 | |
25 | 28 | /** |
26 | 29 | * Used during the config file lexing/parsing to keep the state of the lexer |
66 | 69 | char *next_match; |
67 | 70 | |
68 | 71 | 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; | |
69 | 84 | }; |
70 | 85 | |
71 | 86 | /** |
223 | 238 | color_t background; |
224 | 239 | struct Colortriple focused; |
225 | 240 | struct Colortriple focused_inactive; |
241 | struct Colortriple focused_tab_title; | |
226 | 242 | struct Colortriple unfocused; |
227 | 243 | struct Colortriple urgent; |
228 | 244 | struct Colortriple placeholder; |
245 | bool got_focused_tab_title; | |
229 | 246 | } client; |
230 | 247 | struct config_bar { |
231 | 248 | struct Colortriple focused; |
249 | 266 | /* The number of currently parsed barconfigs */ |
250 | 267 | int number_barconfigs; |
251 | 268 | |
269 | tiling_drag_t tiling_drag; | |
270 | ||
252 | 271 | /* Gap sizes */ |
253 | 272 | gaps_t gaps; |
254 | 273 |
8 | 8 | */ |
9 | 9 | #pragma once |
10 | 10 | |
11 | #define PCRE2_CODE_UNIT_WIDTH 8 | |
12 | ||
11 | 13 | #define SN_API_NOT_YET_FROZEN 1 |
12 | 14 | #include <libsn/sn-launcher.h> |
13 | 15 | |
14 | 16 | #include <xcb/randr.h> |
15 | #include <pcre.h> | |
17 | #include <pcre2.h> | |
16 | 18 | #include <sys/time.h> |
19 | #include <cairo/cairo.h> | |
17 | 20 | |
18 | 21 | #include "queue.h" |
19 | 22 | |
58 | 61 | VERT } orientation_t; |
59 | 62 | typedef enum { BEFORE, |
60 | 63 | 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; | |
64 | 69 | |
65 | 70 | /** parameter to specify whether tree_close_internal() and x_window_kill() should kill |
66 | 71 | * only this specific window or the whole X11 client */ |
265 | 270 | */ |
266 | 271 | struct regex { |
267 | 272 | char *pattern; |
268 | pcre *regex; | |
269 | pcre_extra *extra; | |
273 | pcre2_code *regex; | |
270 | 274 | }; |
271 | 275 | |
272 | 276 | /** |
432 | 436 | * for_window. */ |
433 | 437 | char *role; |
434 | 438 | |
439 | /** WM_CLIENT_MACHINE of the window */ | |
440 | char *machine; | |
441 | ||
435 | 442 | /** Flag to force re-rendering the decoration upon changes */ |
436 | 443 | bool name_x_changed; |
437 | 444 | |
485 | 492 | /* aspect ratio from WM_NORMAL_HINTS (MPlayer uses this for example) */ |
486 | 493 | double min_aspect_ratio; |
487 | 494 | double max_aspect_ratio; |
495 | ||
496 | /** Window icon, as Cairo surface */ | |
497 | cairo_surface_t *icon; | |
488 | 498 | |
489 | 499 | /** The window has a nonrectangular shape. */ |
490 | 500 | bool shaped; |
518 | 528 | struct regex *mark; |
519 | 529 | struct regex *window_role; |
520 | 530 | struct regex *workspace; |
531 | struct regex *machine; | |
521 | 532 | xcb_atom_t window_type; |
522 | 533 | enum { |
523 | 534 | U_DONTCHECK = -1, |
540 | 551 | WM_FLOATING_USER, |
541 | 552 | WM_FLOATING } window_mode; |
542 | 553 | Con *con_id; |
554 | bool match_all_windows; | |
543 | 555 | |
544 | 556 | /* Where the window looking for a match should be inserted: |
545 | 557 | * |
673 | 685 | /** The format with which the window's name should be displayed. */ |
674 | 686 | char *title_format; |
675 | 687 | |
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 | ||
676 | 693 | /* a sticky-group is an identifier which bundles several containers to a |
677 | 694 | * group. The contents are shared between all of them, that is they are |
678 | 695 | * displayed on whichever of the containers is currently visible */ |
727 | 744 | * layout in workspace_layout and creates a new split container with that |
728 | 745 | * layout whenever a new container is attached to the workspace. */ |
729 | 746 | layout_t layout, last_split_layout, workspace_layout; |
747 | ||
730 | 748 | 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 | ||
731 | 757 | /** floating? (= not in tiling layout) This cannot be simply a bool |
732 | 758 | * because we want to keep track of whether the status was set by the |
733 | 759 | * application (by setting _NET_WM_WINDOW_TYPE appropriately) or by the |
13 | 13 | /** |
14 | 14 | * Connects to i3 to find out the currently running version. Useful since it |
15 | 15 | * 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). | |
17 | 17 | * |
18 | 18 | * The output looks like this: |
19 | 19 | * Running i3 version: 4.2-202-gb8e782c (2012-08-12, branch "next") (pid 14804) |
2 | 2 | xmacro(_NET_WM_USER_TIME) \ |
3 | 3 | xmacro(_NET_STARTUP_ID) \ |
4 | 4 | xmacro(_NET_WORKAREA) \ |
5 | xmacro(_NET_WM_ICON) \ | |
5 | 6 | xmacro(WM_PROTOCOLS) \ |
6 | 7 | xmacro(WM_DELETE_WINDOW) \ |
7 | 8 | xmacro(UTF8_STRING) \ |
14 | 15 | xmacro(I3_SYNC) \ |
15 | 16 | xmacro(I3_SHMLOG_PATH) \ |
16 | 17 | xmacro(I3_PID) \ |
18 | xmacro(I3_LOG_STREAM_SOCKET_PATH) \ | |
17 | 19 | xmacro(I3_FLOATING_WINDOW) \ |
18 | 20 | xmacro(_NET_REQUEST_FRAME_EXTENTS) \ |
19 | 21 | xmacro(_NET_FRAME_EXTENTS) \ |
20 | 22 | xmacro(_MOTIF_WM_HINTS) \ |
21 | xmacro(WM_CHANGE_STATE) | |
23 | xmacro(WM_CHANGE_STATE) \ | |
24 | xmacro(MANAGER) |
38 | 38 | /** The number of file descriptors passed via socket activation. */ |
39 | 39 | extern int listen_fds; |
40 | 40 | extern int conn_screen; |
41 | extern xcb_atom_t wm_sn; | |
41 | 42 | /** |
42 | 43 | * The EWMH support window that is used to indicate that an EWMH-compliant |
43 | 44 | * window manager is present. This window is created when i3 starts and |
54 | 55 | extern SnDisplay *sndisplay; |
55 | 56 | extern xcb_key_symbols_t *keysyms; |
56 | 57 | extern char **start_argv; |
57 | extern Display *xlibdpy, *xkbdpy; | |
58 | 58 | extern int xkb_current_group; |
59 | 59 | extern TAILQ_HEAD(bindings_head, Binding) *bindings; |
60 | 60 | extern const char *current_binding_mode; |
80 | 80 | ipc_client *ipc_new_client_on_fd(EV_P_ int fd); |
81 | 81 | |
82 | 82 | /** |
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 | /** | |
90 | 83 | * Sends the specified event to all IPC clients which are currently connected |
91 | 84 | * and subscribed to this kind of event. |
92 | 85 | * |
14 | 14 | #include <stdbool.h> |
15 | 15 | #include <stdarg.h> |
16 | 16 | #include <stdio.h> |
17 | #include <sys/stat.h> | |
17 | 18 | #include <xcb/xcb.h> |
18 | 19 | #include <xcb/xproto.h> |
19 | 20 | #include <xcb/xcb_keysyms.h> |
293 | 294 | int ipc_connect(const char *socket_path); |
294 | 295 | |
295 | 296 | /** |
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 | /** | |
296 | 303 | * Formats a message (payload) of the given size and type and sends it to i3 via |
297 | 304 | * the given socket file descriptor. |
298 | 305 | * |
444 | 451 | * specified coordinates (from the top left corner of the leftmost, uppermost |
445 | 452 | * glyph) and using the provided gc. |
446 | 453 | * |
454 | * The given cairo surface must refer to the specified X drawable. | |
455 | * | |
447 | 456 | * Text must be specified as an i3String. |
448 | 457 | * |
449 | 458 | */ |
450 | 459 | 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); | |
459 | 461 | |
460 | 462 | /** |
461 | 463 | * Predict the text width in pixels for the given text. Text must be |
570 | 572 | /* A classic XCB graphics context. */ |
571 | 573 | xcb_gcontext_t gc; |
572 | 574 | |
573 | xcb_visualtype_t *visual_type; | |
574 | ||
575 | 575 | int width; |
576 | 576 | int height; |
577 | 577 | |
618 | 618 | void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width); |
619 | 619 | |
620 | 620 | /** |
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 | /** | |
621 | 626 | * Draws a filled rectangle. |
622 | 627 | * This function is a convenience wrapper and takes care of flushing the |
623 | 628 | * surface as well as restoring the cairo state. |
637 | 642 | */ |
638 | 643 | void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y, |
639 | 644 | 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); |
9 | 9 | #pragma once |
10 | 10 | |
11 | 11 | #include <config.h> |
12 | #include <ev.h> | |
12 | 13 | |
13 | 14 | /* We will include libi3.h which define its own version of LOG, ELOG. |
14 | 15 | * We want *our* version, so we undef the libi3 one. */ |
30 | 31 | extern char *errorfilename; |
31 | 32 | extern char *shmlogname; |
32 | 33 | extern int shmlog_size; |
34 | extern char *current_log_stream_socket_path; | |
33 | 35 | |
34 | 36 | /** |
35 | 37 | * Initializes logging by creating an error logfile in /tmp (or |
99 | 101 | * failures. This function is invoked automatically when exiting. |
100 | 102 | */ |
101 | 103 | void purge_zerobyte_logfile(void); |
104 | ||
105 | void log_new_client(EV_P_ struct ev_io *w, int revents); |
48 | 48 | * |
49 | 49 | */ |
50 | 50 | 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); | |
57 | 51 | |
58 | 52 | /** |
59 | 53 | * (Re-)queries the outputs via RandR and stores them in the list of outputs. |
10 | 10 | #pragma once |
11 | 11 | |
12 | 12 | #include <config.h> |
13 | ||
14 | #if !defined(__OpenBSD__) | |
15 | #include <pthread.h> | |
16 | #endif | |
17 | 13 | |
18 | 14 | /* Default shmlog size if not set by user. */ |
19 | 15 | extern const int default_shmlog_size; |
38 | 34 | * coincidentally be exactly the same as previously). Overflows can happen |
39 | 35 | * and don’t matter — clients use an equality check (==). */ |
40 | 36 | 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 | |
48 | 37 | } 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); |
182 | 182 | * |
183 | 183 | */ |
184 | 184 | 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); |
93 | 93 | * it is still in use by popular widget toolkits such as GTK+ and Java AWT. |
94 | 94 | * |
95 | 95 | */ |
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); |
91 | 91 | |
92 | 92 | /** |
93 | 93 | * 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 | |
95 | 95 | * workspaces. |
96 | 96 | * |
97 | 97 | */ |
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 | } |
34 | 34 | void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_drawable_t drawable, |
35 | 35 | xcb_visualtype_t *visual, int width, int height) { |
36 | 36 | surface->id = drawable; |
37 | surface->visual_type = ((visual == NULL) ? visual_type : visual); | |
38 | 37 | surface->width = width; |
39 | 38 | surface->height = height; |
39 | ||
40 | if (visual == NULL) | |
41 | visual = visual_type; | |
40 | 42 | |
41 | 43 | surface->gc = xcb_generate_id(conn); |
42 | 44 | xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(conn, surface->gc, surface->id, 0, NULL); |
44 | 46 | xcb_generic_error_t *error = xcb_request_check(conn, gc_cookie); |
45 | 47 | if (error != NULL) { |
46 | 48 | 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); | |
50 | 53 | surface->cr = cairo_create(surface->surface); |
51 | 54 | } |
52 | 55 | |
55 | 58 | * |
56 | 59 | */ |
57 | 60 | 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 | */ | |
58 | 74 | xcb_free_gc(conn, surface->gc); |
59 | 75 | cairo_surface_destroy(surface->surface); |
60 | 76 | cairo_destroy(surface->cr); |
132 | 148 | CAIRO_SURFACE_FLUSH(surface->surface); |
133 | 149 | |
134 | 150 | 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); | |
136 | 152 | |
137 | 153 | /* Notify cairo that we (possibly) used another way to draw on the surface. */ |
138 | 154 | 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); | |
139 | 179 | } |
140 | 180 | |
141 | 181 | /* |
82 | 82 | * |
83 | 83 | */ |
84 | 84 | 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) { | |
87 | 87 | /* Create the Pango layout */ |
88 | 88 | /* 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); | |
91 | 89 | cairo_t *cr = cairo_create(surface); |
92 | 90 | PangoLayout *layout = create_layout_with_dpi(cr); |
93 | 91 | gint height; |
115 | 113 | /* Free resources */ |
116 | 114 | g_object_unref(layout); |
117 | 115 | cairo_destroy(cr); |
118 | cairo_surface_destroy(surface); | |
119 | 116 | } |
120 | 117 | |
121 | 118 | /* |
163 | 160 | font.type = FONT_TYPE_NONE; |
164 | 161 | font.pattern = NULL; |
165 | 162 | |
166 | /* No XCB connction, return early because we're just validating the | |
163 | /* No XCB connection, return early because we're just validating the | |
167 | 164 | * configuration file. */ |
168 | 165 | if (conn == NULL) { |
169 | 166 | return font; |
359 | 356 | * |
360 | 357 | */ |
361 | 358 | 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) { | |
363 | 360 | assert(savedFont != NULL); |
364 | if (visual == NULL) { | |
365 | visual = root_visual_type; | |
366 | } | |
367 | 361 | |
368 | 362 | switch (savedFont->type) { |
369 | 363 | case FONT_TYPE_NONE: |
376 | 370 | case FONT_TYPE_PANGO: |
377 | 371 | /* Render the text using Pango */ |
378 | 372 | 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)); | |
415 | 374 | return; |
416 | 375 | } |
417 | 376 | } |
9 | 9 | #include <stdlib.h> |
10 | 10 | #include <string.h> |
11 | 11 | #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 | } | |
21 | 12 | |
22 | 13 | /* |
23 | 14 | * Get the path of the first configuration file found. If override_configpath is |
26 | 26 | char *tmp; |
27 | 27 | sasprintf(&tmp, "%s/i3", dir); |
28 | 28 | 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; | |
38 | 37 | } |
39 | 38 | } else { |
40 | 39 | /* If not, we create a (secure) temp directory using the template |
12 | 12 | #include <string.h> |
13 | 13 | #include <sys/socket.h> |
14 | 14 | #include <sys/un.h> |
15 | #include <unistd.h> | |
15 | 16 | |
16 | 17 | /* |
17 | 18 | * Connects to the i3 IPC socket and returns the file descriptor for the |
38 | 39 | path = sstrdup("/tmp/i3-ipc.sock"); |
39 | 40 | } |
40 | 41 | |
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) { | |
41 | 56 | int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); |
42 | 57 | if (sockfd == -1) |
43 | 58 | err(EXIT_FAILURE, "Could not create socket"); |
47 | 62 | struct sockaddr_un addr; |
48 | 63 | memset(&addr, 0, sizeof(struct sockaddr_un)); |
49 | 64 | 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 | } | |
54 | 70 | return sockfd; |
55 | 71 | } |
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 | } |
0 | 0 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> |
1 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | |
2 | 1 | <svg |
3 | 2 | xmlns:dc="http://purl.org/dc/elements/1.1/" |
4 | 3 | xmlns:cc="http://creativecommons.org/ns#" |
12 | 11 | height="297mm" |
13 | 12 | id="svg2" |
14 | 13 | 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"> | |
18 | 18 | <defs |
19 | 19 | id="defs4"> |
20 | 20 | <linearGradient |
38 | 38 | </linearGradient> |
39 | 39 | <inkscape:perspective |
40 | 40 | 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" | |
45 | 45 | sodipodi:type="inkscape:persp3d" /> |
46 | 46 | <linearGradient |
47 | 47 | id="linearGradient3284"> |
111 | 111 | </linearGradient> |
112 | 112 | <inkscape:perspective |
113 | 113 | 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" | |
118 | 118 | id="perspective10" /> |
119 | 119 | <radialGradient |
120 | 120 | inkscape:collect="always" |
151 | 151 | gradientUnits="userSpaceOnUse" /> |
152 | 152 | <inkscape:perspective |
153 | 153 | 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" | |
158 | 158 | sodipodi:type="inkscape:persp3d" /> |
159 | 159 | <linearGradient |
160 | 160 | id="linearGradient3211"> |
281 | 281 | inkscape:pageopacity="0.0" |
282 | 282 | inkscape:pageshadow="2" |
283 | 283 | inkscape:zoom="1" |
284 | inkscape:cx="239.17981" | |
285 | inkscape:cy="807.75327" | |
284 | inkscape:cx="164.67981" | |
285 | inkscape:cy="285.75327" | |
286 | 286 | inkscape:document-units="px" |
287 | 287 | inkscape:current-layer="layer1" |
288 | 288 | showgrid="false" |
289 | 289 | inkscape:window-width="1272" |
290 | inkscape:window-height="950" | |
290 | inkscape:window-height="856" | |
291 | 291 | inkscape:window-x="24" |
292 | inkscape:window-y="24" /> | |
292 | inkscape:window-y="22" | |
293 | inkscape:document-rotation="0" | |
294 | inkscape:window-maximized="0" /> | |
293 | 295 | <metadata |
294 | 296 | id="metadata7"> |
295 | 297 | <rdf:RDF> |
494 | 496 | <path |
495 | 497 | 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" |
496 | 498 | d="M 182.85714,106.6479 L 182.85714,415.21935 L 182.85714,106.6479 z" |
497 | id="rect3246" | |
499 | id="path934" | |
498 | 500 | sodipodi:nodetypes="ccc" /> |
499 | 501 | <path |
500 | 502 | sodipodi:nodetypes="ccc" |
514 | 516 | style="display:inline"> |
515 | 517 | <path |
516 | 518 | 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" | |
518 | 520 | id="path2405" /> |
519 | 521 | <path |
520 | 522 | 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" |
34 | 34 | |
35 | 35 | == DESCRIPTION |
36 | 36 | |
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 | |
38 | 38 | exists. i3-config-wizard creates a keysym based i3 config file (based on |
39 | 39 | /etc/i3/config.keycodes) in ~/.i3/config. |
40 | 40 |
38 | 38 | The prompt string is not included in the user input/command. |
39 | 39 | |
40 | 40 | -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). | |
42 | 42 | |
43 | 43 | -v:: |
44 | 44 | Show version and exit. |
8 | 8 | |
9 | 9 | == SYNOPSIS |
10 | 10 | |
11 | i3-msg [-q] [-v] [-h] [-s socket] [-t type] [message] | |
11 | i3-msg [-q] [-v] [-h] [-s socket] [-t type] [-r] [message] | |
12 | 12 | |
13 | 13 | == OPTIONS |
14 | 14 | |
34 | 34 | Instead of exiting right after receiving the first subscribed event, |
35 | 35 | wait indefinitely for all of them. Can only be used with "-t subscribe". |
36 | 36 | 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). | |
37 | 41 | |
38 | 42 | *message*:: |
39 | 43 | Send ipc message, see below. |
8 | 8 | |
9 | 9 | == SYNOPSIS |
10 | 10 | |
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] | |
12 | 12 | |
13 | 13 | == OPTIONS |
14 | 14 | |
38 | 38 | Same as above, but will execute the shell commands directly, without launching a |
39 | 39 | terminal emulator. |
40 | 40 | |
41 | *-p, --primary*:: | |
42 | Always opens the i3-nagbar on the primary monitor. By default it opens on the | |
43 | focused monitor. | |
44 | ||
41 | 45 | == DESCRIPTION |
42 | 46 | |
43 | 47 | i3-nagbar is used by i3 to tell you about errors in your configuration file |
22 | 22 | |
23 | 23 | * $TERMINAL (this is a non-standard variable) |
24 | 24 | * x-terminal-emulator (only present on Debian and derivatives) |
25 | * mate-terminal | |
26 | * gnome-terminal | |
27 | * terminator | |
28 | * xfce4-terminal | |
25 | 29 | * urxvt |
26 | 30 | * rxvt |
27 | 31 | * termit |
28 | * terminator | |
29 | 32 | * Eterm |
30 | 33 | * aterm |
31 | 34 | * uxterm |
32 | 35 | * xterm |
33 | * gnome-terminal | |
34 | 36 | * roxterm |
35 | * xfce4-terminal | |
36 | 37 | * termite |
37 | 38 | * lxterminal |
38 | * mate-terminal | |
39 | 39 | * terminology |
40 | 40 | * st |
41 | 41 | * qterminal |
42 | 42 | --shmlog-size <limit>:: |
43 | 43 | Limits the size of the i3 SHM log to <limit> bytes. Setting this to 0 disables |
44 | 44 | SHM logging entirely. The default is 0 bytes. |
45 | ||
46 | --replace:: | |
47 | Replace an existing window manager. | |
45 | 48 | |
46 | 49 | == DESCRIPTION |
47 | 50 |
8 | 8 | |
9 | 9 | == SYNOPSIS |
10 | 10 | |
11 | *i3bar* [*-s* 'sock_path'] [*-b* 'bar_id'] [*-v*] [*-h*] | |
11 | *i3bar* [*-b* 'bar_id'] [*-s* 'sock_path'] [*-t*] [*-h*] [*-v*] [*-V*] | |
12 | 12 | |
13 | 13 | == WARNING |
14 | 14 | |
24 | 24 | Overwrites the path to the i3 IPC socket. |
25 | 25 | |
26 | 26 | *-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 | |
28 | 35 | |
29 | 36 | *-v, --version*:: |
30 | 37 | Display version number and exit. |
31 | 38 | |
32 | *-h, --help*:: | |
33 | Display a short help-message and exit | |
39 | *-V*:: | |
40 | Be verbose. | |
34 | 41 | |
35 | 42 | == DESCRIPTION |
36 | 43 |
5 | 5 | project( |
6 | 6 | 'i3', |
7 | 7 | 'c', |
8 | version: '4.19.1', | |
8 | version: '4.21.1', | |
9 | 9 | default_options: [ |
10 | 10 | 'c_std=c11', |
11 | 11 | 'warning_level=1', # enable all warnings (-Wall) |
62 | 62 | sources: vcs_tag( |
63 | 63 | input: config_h_in, |
64 | 64 | output: 'config.h', |
65 | fallback: meson.project_version() + ' (2021-02-01)', | |
65 | fallback: meson.project_version() + ' (2022-10-28)', | |
66 | 66 | ) |
67 | 67 | ) |
68 | 68 | |
228 | 228 | output: '@[email protected]', |
229 | 229 | command: [ |
230 | 230 | xmlto, |
231 | '--stringparam', | |
232 | 'man.th.title.max.length=30', | |
231 | 233 | 'man', |
232 | 234 | '-o', |
233 | 235 | '@OUTDIR@', |
313 | 315 | xkbcommon_dep = dependency('xkbcommon', method: 'pkg-config') |
314 | 316 | xkbcommon_x11_dep = dependency('xkbcommon-x11', method: 'pkg-config') |
315 | 317 | 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') | |
317 | 319 | cairo_dep = dependency('cairo', version: '>=1.14.4', method: 'pkg-config') |
318 | 320 | pangocairo_dep = dependency('pangocairo', method: 'pkg-config') |
319 | 321 | glib_dep = dependency('glib-2.0', method: 'pkg-config') |
324 | 326 | inc = include_directories('include') |
325 | 327 | |
326 | 328 | libi3srcs = [ |
329 | 'libi3/boolstr.c', | |
330 | 'libi3/create_socket.c', | |
327 | 331 | 'libi3/dpi.c', |
328 | 332 | 'libi3/draw_util.c', |
329 | 333 | 'libi3/fake_configure_notify.c', |
340 | 344 | 'libi3/ipc_recv_message.c', |
341 | 345 | 'libi3/ipc_send_message.c', |
342 | 346 | 'libi3/is_debug_build.c', |
347 | 'libi3/path_exists.c', | |
343 | 348 | 'libi3/resolve_tilde.c', |
344 | 349 | 'libi3/root_atom_contents.c', |
345 | 350 | 'libi3/safewrappers.c', |
346 | 351 | 'libi3/string.c', |
347 | 352 | 'libi3/ucs2_conversion.c', |
353 | 'libi3/nonblock.c', | |
354 | 'libi3/screenshot_wallpaper.c', | |
355 | 'libi3/is_background_set.c', | |
348 | 356 | ] |
349 | 357 | |
350 | 358 | if not cdata.get('HAVE_STRNDUP') |
400 | 408 | 'src/sighandler.c', |
401 | 409 | 'src/startup.c', |
402 | 410 | 'src/sync.c', |
411 | 'src/tiling_drag.c', | |
403 | 412 | 'src/tree.c', |
404 | 413 | 'src/util.c', |
405 | 414 | 'src/version.c', |
513 | 522 | 'i3-config-wizard/i3-config-wizard-atoms.xmacro.h', |
514 | 523 | 'i3-config-wizard/main.c', |
515 | 524 | 'i3-config-wizard/xcb.h', |
525 | config_parser, | |
516 | 526 | ], |
517 | 527 | install: true, |
518 | 528 | include_directories: include_directories('include', 'i3-config-wizard'), |
641 | 651 | '@OUTPUT@', |
642 | 652 | ], |
643 | 653 | install: true, |
644 | install_dir: join_paths(get_option('datadir'), 'doc', 'i3'), | |
654 | install_dir: docdir, | |
645 | 655 | ) |
646 | 656 | |
647 | 657 | custom_target( |
654 | 664 | '@OUTPUT@', |
655 | 665 | ], |
656 | 666 | install: true, |
657 | install_dir: join_paths(get_option('datadir'), 'doc', 'i3'), | |
667 | install_dir: docdir, | |
658 | 668 | ) |
659 | 669 | endif |
660 | 670 | |
668 | 678 | |
669 | 679 | executable( |
670 | 680 | 'test.commands_parser', |
671 | 'src/commands_parser.c', | |
681 | [ | |
682 | 'src/commands_parser.c', | |
683 | command_parser, | |
684 | ], | |
672 | 685 | include_directories: inc, |
673 | 686 | c_args: '-DTEST_PARSER', |
674 | 687 | dependencies: common_deps, |
677 | 690 | |
678 | 691 | executable( |
679 | 692 | 'test.config_parser', |
680 | 'src/config_parser.c', | |
693 | [ | |
694 | 'src/config_parser.c', | |
695 | config_parser, | |
696 | ], | |
681 | 697 | include_directories: inc, |
682 | 698 | c_args: '-DTEST_PARSER', |
683 | 699 | dependencies: common_deps, |
705 | 721 | anyevent_i3, |
706 | 722 | i3test_pm, |
707 | 723 | ], |
724 | timeout: 120, # Default of 30 seconds can cause timeouts on slower machines | |
708 | 725 | ) |
709 | 726 | else |
710 | 727 | # meson < 0.46.0 does not support the depends arg in test targets. |
39 | 39 | 'scratchpad' -> SCRATCHPAD |
40 | 40 | 'swap' -> SWAP |
41 | 41 | 'title_format' -> TITLE_FORMAT |
42 | 'title_window_icon' -> TITLE_WINDOW_ICON | |
42 | 43 | 'mode' -> MODE |
43 | 44 | 'bar' -> BAR |
44 | 45 | 'gaps' -> GAPS |
54 | 55 | ctype = 'title' -> CRITERION |
55 | 56 | ctype = 'urgent' -> CRITERION |
56 | 57 | ctype = 'workspace' -> CRITERION |
57 | ctype = 'tiling', 'floating' | |
58 | ctype = 'machine' -> CRITERION | |
59 | ctype = 'tiling', 'floating', 'all' | |
58 | 60 | -> call cmd_criteria_add($ctype, NULL); CRITERIA |
59 | 61 | ']' -> call cmd_criteria_match_windows(); INITIAL |
60 | 62 | |
181 | 183 | -> call cmd_focus_direction($direction) |
182 | 184 | |
183 | 185 | 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 | |
186 | 190 | |
187 | 191 | # kill [window|client] |
188 | 192 | state KILL: |
401 | 405 | -> call cmd_move_con_to_workspace_number($number, $no_auto_back_and_forth) |
402 | 406 | |
403 | 407 | 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 | |
406 | 412 | |
407 | 413 | state MOVE_TO_MARK: |
408 | 414 | mark = string |
410 | 416 | |
411 | 417 | state MOVE_WORKSPACE_TO_OUTPUT: |
412 | 418 | '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 | |
416 | 426 | |
417 | 427 | state MOVE_TO_ABSOLUTE_POSITION: |
418 | 428 | 'position' |
472 | 482 | format = string |
473 | 483 | -> call cmd_title_format($format) |
474 | 484 | |
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 | ||
475 | 501 | # bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>] |
476 | 502 | state BAR: |
477 | 503 | 'hidden_state' |
19 | 19 | 'set ' -> IGNORE_LINE |
20 | 20 | 'set ' -> IGNORE_LINE |
21 | 21 | 'set_from_resource' -> IGNORE_LINE |
22 | 'include' -> INCLUDE | |
22 | 23 | bindtype = 'bindsym', 'bindcode', 'bind' -> BINDING |
23 | 24 | 'bar' -> BARBRACE |
24 | 25 | 'font' -> FONT |
54 | 55 | 'ipc_kill_timeout' -> IPC_KILL_TIMEOUT |
55 | 56 | 'restart_state' -> RESTART_STATE |
56 | 57 | 'popup_during_fullscreen' -> POPUP_DURING_FULLSCREEN |
58 | 'tiling_drag' -> TILING_DRAG | |
57 | 59 | exectype = 'exec_always', 'exec' -> EXEC |
58 | 60 | colorclass = 'client.background' |
59 | 61 | -> 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' | |
61 | 63 | -> COLOR_BORDER |
62 | 64 | |
63 | 65 | # We ignore comments and 'set' lines (variables). |
86 | 88 | state SMART_GAPS: |
87 | 89 | enabled = '1', 'yes', 'true', 'on', 'enable', 'active' |
88 | 90 | -> call cfg_smart_gaps($enabled) |
91 | enabled = '0', 'no', 'false', 'off', 'disable', 'inactive' | |
92 | -> call cfg_smart_gaps($enabled) | |
89 | 93 | enabled = 'inverse_outer' |
90 | 94 | -> call cfg_smart_gaps($enabled) |
95 | ||
96 | # include <pattern> | |
97 | state INCLUDE: | |
98 | pattern = string | |
99 | -> call cfg_include($pattern) | |
91 | 100 | |
92 | 101 | # floating_minimum_size <width> x <height> |
93 | 102 | state FLOATING_MINIMUM_SIZE_WIDTH: |
217 | 226 | ctype = 'title' -> CRITERION |
218 | 227 | ctype = 'urgent' -> CRITERION |
219 | 228 | ctype = 'workspace' -> CRITERION |
229 | ctype = 'machine' -> CRITERION | |
220 | 230 | ctype = 'floating_from' -> CRITERION_FROM |
221 | 231 | ctype = 'tiling_from' -> CRITERION_FROM |
222 | ctype = 'tiling', 'floating' | |
232 | ctype = 'tiling', 'floating', 'all' | |
223 | 233 | -> call cfg_criteria_add($ctype, NULL); CRITERIA |
224 | 234 | ']' |
225 | 235 | -> call cfg_criteria_pop_state() |
351 | 361 | state POPUP_DURING_FULLSCREEN: |
352 | 362 | value = 'ignore', 'leave_fullscreen', 'smart' |
353 | 363 | -> 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 | |
354 | 376 | |
355 | 377 | # client.background <hexcolor> |
356 | 378 | state COLOR_SINGLE: |
588 | 610 | -> call cfg_bar_position($position); BAR |
589 | 611 | |
590 | 612 | state BAR_OUTPUT: |
591 | output = string | |
613 | output = word | |
592 | 614 | -> call cfg_bar_output($output); BAR |
593 | 615 | |
594 | 616 | 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 | Add title_window_icon toggle |
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; |
0 | 0 | #!/bin/zsh |
1 | 1 | # This script is used to prepare a new release of i3. |
2 | 2 | |
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" | |
5 | 7 | export RELEASE_BRANCH="next" |
6 | 8 | |
7 | 9 | if [ ! -e "../i3.github.io" ] |
19 | 21 | |
20 | 22 | if [ ! -e "RELEASE-NOTES-${RELEASE_VERSION}" ] |
21 | 23 | 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 | |
29 | 26 | exit 1 |
30 | 27 | fi |
31 | 28 | |
40 | 37 | |
41 | 38 | TMPDIR=$(mktemp -d) |
42 | 39 | 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)." | |
45 | 42 | exit 1 |
46 | 43 | fi |
47 | 44 | git clone --quiet --branch "${RELEASE_BRANCH}" https://github.com/i3/i3 |
51 | 48 | exit 1 |
52 | 49 | fi |
53 | 50 | git checkout -b release-${RELEASE_VERSION} |
51 | git rm RELEASE-NOTES-* | |
54 | 52 | cp "${STARTDIR}/RELEASE-NOTES-${RELEASE_VERSION}" "RELEASE-NOTES-${RELEASE_VERSION}" |
55 | 53 | 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 | |
58 | 59 | git commit -a -m "release i3 ${RELEASE_VERSION}" |
59 | 60 | git tag "${RELEASE_VERSION}" -m "release i3 ${RELEASE_VERSION}" --sign --local-user=0x4AC8EE1D |
60 | 61 | |
61 | 62 | mkdir build |
62 | 63 | (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 . | |
64 | 65 | |
65 | 66 | echo "Differences in the release tarball file lists:" |
66 | 67 | |
67 | 68 | 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 | |
70 | 71 | |
71 | 72 | gpg --armor -b i3-${RELEASE_VERSION}.tar.xz |
72 | 73 | |
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" | |
76 | 77 | |
77 | 78 | if [ "${RELEASE_BRANCH}" = "stable" ]; then |
78 | 79 | git checkout stable |
93 | 94 | git config --add remote.origin.push "+refs/heads/stable:refs/heads/stable" |
94 | 95 | |
95 | 96 | ################################################################################ |
96 | # Section 2: Debian packaging | |
97 | # Section 2: Debian packaging (for QA) | |
97 | 98 | ################################################################################ |
98 | 99 | |
99 | 100 | cd "${TMPDIR}" |
132 | 133 | echo "Content of resulting package’s .changes file:" |
133 | 134 | cat ${TMPDIR}/debian/*.changes |
134 | 135 | |
135 | # debsign is in devscripts, which is available in fedora and debian | |
136 | debsign --no-re-sign -k4AC8EE1D ${TMPDIR}/debian/*.changes | |
137 | ||
138 | 136 | # TODO: docker cleanup |
139 | 137 | |
140 | 138 | ################################################################################ |
175 | 173 | |
176 | 174 | (cd _docs && make) |
177 | 175 | |
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") | |
179 | 177 | do |
180 | 178 | base="$(basename $i)" |
181 | 179 | [ -e "${TMPDIR}/i3/docs/${base}" ] && cp "_docs/${base}.html" docs/ |
233 | 231 | echo "" |
234 | 232 | echo "Announce on:" |
235 | 233 | echo " twitter" |
236 | echo " google+" | |
237 | 234 | echo " #i3 topic" |
238 | echo " reddit /r/i3wm" | |
235 | echo " reddit /r/i3wm (link post to changelog)" | |
236 | echo " GitHub Discussions → Announcements" |
717 | 717 | } |
718 | 718 | |
719 | 719 | /* |
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 | /* | |
720 | 754 | * Checks for duplicate key bindings (the same keycode or keysym is configured |
721 | 755 | * more than once). If a duplicate binding is found, a message is printed to |
722 | 756 | * stderr and the has_errors variable is set to true, which will start |
729 | 763 | TAILQ_FOREACH (bind, bindings, bindings) { |
730 | 764 | /* Abort when we reach the current keybinding, only check the |
731 | 765 | * bindings before */ |
732 | if (bind == current) | |
766 | if (bind == current) { | |
733 | 767 | 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)) { | |
737 | 771 | 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 | } | |
757 | 773 | |
758 | 774 | context->has_errors = true; |
759 | 775 | if (current->keycode != 0) { |
79 | 79 | */ |
80 | 80 | static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *event) { |
81 | 81 | /* 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 | |
83 | 83 | * the position of the mouse pointer */ |
84 | 84 | int to_right = con->rect.width - event->event_x, |
85 | 85 | to_left = event->event_x, |
140 | 140 | return false; |
141 | 141 | } |
142 | 142 | |
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 | ||
143 | 149 | /* |
144 | 150 | * Being called by handle_button_press, this function calls the appropriate |
145 | 151 | * functions for resizing/dragging. |
151 | 157 | DLOG("type = %d, name = %s\n", con->type, con->name); |
152 | 158 | |
153 | 159 | /* 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 | } | |
156 | 164 | |
157 | 165 | /* if the user has bound an action to this click, it should override the |
158 | 166 | * default behavior. */ |
172 | 180 | |
173 | 181 | /* There is no default behavior for button release events so we are done. */ |
174 | 182 | if (event->response_type == XCB_BUTTON_RELEASE) { |
175 | goto done; | |
183 | allow_replay_pointer(event->time); | |
184 | return; | |
176 | 185 | } |
177 | 186 | |
178 | 187 | /* Any click in a workspace should focus that workspace. If the |
183 | 192 | |
184 | 193 | if (!ws) { |
185 | 194 | 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 | } | |
192 | 200 | |
193 | 201 | /* get the floating con */ |
194 | 202 | Con *floatingcon = con_inside_floating(con); |
214 | 222 | Con *next = get_tree_next_sibling(current, direction); |
215 | 223 | con_activate(con_descend_focused(next ? next : current)); |
216 | 224 | |
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. | |
224 | 259 | * We will skip handling events on floating cons in fullscreen mode */ |
225 | 260 | Con *fs = con_get_fullscreen_covering_ws(ws); |
226 | 261 | if (floatingcon != NULL && fs != con) { |
227 | /* 4: floating_modifier plus left mouse button drags */ | |
262 | /* 5: floating_modifier plus left mouse button drags */ | |
228 | 263 | if (mod_pressed && is_left_click) { |
229 | 264 | floating_drag_window(floatingcon, event, false); |
230 | 265 | return; |
231 | 266 | } |
232 | 267 | |
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 | |
234 | 269 | * left/right/bottom border, or a right click on the decoration. |
235 | 270 | * also try resizing (tiling) if possible */ |
236 | 271 | if (mod_pressed && is_right_click) { |
243 | 278 | is_left_or_right_click) { |
244 | 279 | /* try tiling resize, but continue if it doesn’t work */ |
245 | 280 | 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 | } | |
248 | 285 | } |
249 | 286 | |
250 | 287 | if (dest == CLICK_DECORATION && is_right_click) { |
259 | 296 | return; |
260 | 297 | } |
261 | 298 | |
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 | |
263 | 300 | * to a resize) */ |
264 | 301 | if (dest == CLICK_DECORATION && is_left_click) { |
265 | 302 | floating_drag_window(floatingcon, event, !was_focused); |
266 | 303 | return; |
267 | 304 | } |
268 | 305 | |
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 */ | |
273 | 323 | if (dest == CLICK_INSIDE && mod_pressed && is_right_click) { |
274 | 324 | if (floating_mod_on_tiled_client(con, event)) { |
275 | 325 | return; |
280 | 330 | xcb_flush(conn); |
281 | 331 | return; |
282 | 332 | } |
283 | /* 8: otherwise, check for border/decoration clicks and resize */ | |
333 | /* 10: otherwise, check for border/decoration clicks and resize */ | |
284 | 334 | if ((dest == CLICK_BORDER || dest == CLICK_DECORATION) && |
285 | 335 | is_left_or_right_click) { |
286 | 336 | DLOG("Trying to resize (tiling)\n"); |
287 | 337 | tiling_resize(con, event, dest, dest == CLICK_DECORATION && !was_focused); |
288 | 338 | } |
289 | 339 | |
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); | |
294 | 341 | } |
295 | 342 | |
296 | 343 | /* |
746 | 746 | return; |
747 | 747 | } |
748 | 748 | |
749 | /* User changed the border */ | |
750 | current->con->max_user_border_style = border_style; | |
749 | 751 | const int con_border_width = border_width_from_style(border_style, border_width, current->con); |
750 | 752 | con_set_border_style(current->con, border_style, con_border_width); |
751 | 753 | } |
1022 | 1024 | ysuccess(true); |
1023 | 1025 | } |
1024 | 1026 | |
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 | ||
1031 | 1112 | HANDLE_EMPTY_MATCH; |
1032 | 1113 | |
1114 | if (TAILQ_EMPTY(&names)) { | |
1115 | yerror("At least one output must be specified"); | |
1116 | return; | |
1117 | } | |
1118 | ||
1119 | bool success = false; | |
1033 | 1120 | owindow *current; |
1034 | bool had_error = false; | |
1035 | 1121 | 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 | } | |
1043 | 1146 | } |
1044 | 1147 | |
1045 | 1148 | /* |
1090 | 1193 | |
1091 | 1194 | cmd_output->needs_tree_render = true; |
1092 | 1195 | // 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; | |
1123 | 1196 | ysuccess(true); |
1124 | 1197 | } |
1125 | 1198 | |
1660 | 1733 | } |
1661 | 1734 | ipc_shutdown(SHUTDOWN_REASON_RESTART, exempt_fd); |
1662 | 1735 | unlink(config.ipc_socket_path); |
1736 | if (current_log_stream_socket_path != NULL) { | |
1737 | unlink(current_log_stream_socket_path); | |
1738 | } | |
1663 | 1739 | /* We need to call this manually since atexit handlers don’t get called |
1664 | 1740 | * when exec()ing */ |
1665 | 1741 | purge_zerobyte_logfile(); |
1693 | 1769 | * |
1694 | 1770 | */ |
1695 | 1771 | 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 | ||
1696 | 1783 | HANDLE_EMPTY_MATCH; |
1697 | 1784 | |
1698 | 1785 | if (TAILQ_EMPTY(&owindows)) { |
1701 | 1788 | } |
1702 | 1789 | |
1703 | 1790 | 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 | } | |
1723 | 1814 | } |
1724 | 1815 | |
1725 | 1816 | /* |
1959 | 2050 | ewmh_update_visible_name(current->con->window->id, NULL); |
1960 | 2051 | } |
1961 | 2052 | } |
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); | |
1962 | 2102 | |
1963 | 2103 | if (current->con->window != NULL) { |
1964 | 2104 | /* Make sure the window title is redrawn immediately. */ |
55 | 55 | |
56 | 56 | #include "GENERATED_command_tokens.h" |
57 | 57 | |
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 | ||
79 | 58 | /* |
80 | 59 | * Pushes a string (identified by 'identifier') on the stack. We simply use a |
81 | 60 | * single array, since the number of entries we have to store is very small. |
82 | 61 | * |
83 | 62 | */ |
84 | static void push_string(const char *identifier, char *str) { | |
63 | static void push_string(struct stack *stack, const char *identifier, char *str) { | |
85 | 64 | for (int c = 0; c < 10; c++) { |
86 | if (stack[c].identifier != NULL) | |
65 | if (stack->stack[c].identifier != NULL) | |
87 | 66 | continue; |
88 | 67 | /* 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; | |
92 | 71 | return; |
93 | 72 | } |
94 | 73 | |
102 | 81 | } |
103 | 82 | |
104 | 83 | // 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) { | |
106 | 85 | for (int c = 0; c < 10; c++) { |
107 | if (stack[c].identifier != NULL) { | |
86 | if (stack->stack[c].identifier != NULL) { | |
108 | 87 | continue; |
109 | 88 | } |
110 | 89 | |
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; | |
114 | 93 | return; |
115 | 94 | } |
116 | 95 | |
124 | 103 | } |
125 | 104 | |
126 | 105 | // 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) { | |
128 | 107 | for (int c = 0; c < 10; c++) { |
129 | if (stack[c].identifier == NULL) | |
108 | if (stack->stack[c].identifier == NULL) | |
130 | 109 | 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; | |
133 | 112 | } |
134 | 113 | return NULL; |
135 | 114 | } |
136 | 115 | |
137 | 116 | // 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) { | |
139 | 118 | for (int c = 0; c < 10; c++) { |
140 | if (stack[c].identifier == NULL) | |
119 | if (stack->stack[c].identifier == NULL) | |
141 | 120 | 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; | |
144 | 123 | } |
145 | 124 | |
146 | 125 | return 0; |
147 | 126 | } |
148 | 127 | |
149 | 128 | // TODO move to a common util |
150 | static void clear_stack(void) { | |
129 | static void clear_stack(struct stack *stack) { | |
151 | 130 | 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; | |
157 | 136 | } |
158 | 137 | } |
159 | 138 | |
162 | 141 | ******************************************************************************/ |
163 | 142 | |
164 | 143 | static cmdp_state state; |
165 | #ifndef TEST_PARSER | |
166 | 144 | 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; | |
168 | 150 | static struct CommandResultIR subcommand_output; |
169 | 151 | static struct CommandResultIR command_output; |
170 | 152 | |
175 | 157 | subcommand_output.json_gen = command_output.json_gen; |
176 | 158 | subcommand_output.client = command_output.client; |
177 | 159 | subcommand_output.needs_tree_render = false; |
178 | GENERATED_call(token->extra.call_identifier, &subcommand_output); | |
160 | GENERATED_call(¤t_match, &stack, token->extra.call_identifier, &subcommand_output); | |
179 | 161 | state = subcommand_output.next_state; |
180 | 162 | /* If any subcommand requires a tree_render(), we need to make the |
181 | 163 | * whole parser result request a tree_render(). */ |
182 | 164 | if (subcommand_output.needs_tree_render) |
183 | 165 | command_output.needs_tree_render = true; |
184 | clear_stack(); | |
166 | clear_stack(&stack); | |
185 | 167 | return; |
186 | 168 | } |
187 | 169 | |
188 | 170 | state = token->next_state; |
189 | 171 | if (state == INITIAL) { |
190 | clear_stack(); | |
172 | clear_stack(&stack); | |
191 | 173 | } |
192 | 174 | } |
193 | 175 | |
295 | 277 | /* A literal. */ |
296 | 278 | if (token->name[0] == '\'') { |
297 | 279 | 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 | } | |
300 | 283 | walk += strlen(token->name) - 1; |
301 | 284 | next_state(token); |
302 | 285 | token_handled = true; |
318 | 301 | if (end == walk) |
319 | 302 | continue; |
320 | 303 | |
321 | if (token->identifier != NULL) | |
322 | push_long(token->identifier, num); | |
304 | if (token->identifier != NULL) { | |
305 | push_long(&stack, token->identifier, num); | |
306 | } | |
323 | 307 | |
324 | 308 | /* Set walk to the first non-number character */ |
325 | 309 | walk = end; |
332 | 316 | strcmp(token->name, "word") == 0) { |
333 | 317 | char *str = parse_string(&walk, (token->name[0] != 's')); |
334 | 318 | 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 | } | |
337 | 322 | /* If we are at the end of a quoted string, skip the ending |
338 | 323 | * double quote. */ |
339 | 324 | if (*walk == '"') |
404 | 389 | free(possible_tokens); |
405 | 390 | |
406 | 391 | /* 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. */ | |
408 | 393 | char *position = smalloc(len + 1); |
409 | 394 | for (const char *copywalk = input; *copywalk != '\0'; copywalk++) |
410 | 395 | position[(copywalk - input)] = (copywalk >= walk ? '^' : ' '); |
435 | 420 | y(map_close); |
436 | 421 | |
437 | 422 | free(position); |
438 | clear_stack(); | |
423 | clear_stack(&stack); | |
439 | 424 | break; |
440 | 425 | } |
441 | 426 | } |
40 | 40 | TAILQ_INSERT_TAIL(&all_cons, new, all_cons); |
41 | 41 | new->type = CT_CON; |
42 | 42 | new->window = window; |
43 | new->border_style = config.default_border; | |
43 | new->border_style = new->max_user_border_style = config.default_border; | |
44 | 44 | new->current_border_width = -1; |
45 | new->window_icon_padding = -1; | |
45 | 46 | if (window) { |
46 | 47 | new->depth = window->depth; |
47 | 48 | } else { |
729 | 730 | } |
730 | 731 | |
731 | 732 | /* |
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 | /* | |
732 | 768 | * Returns true if and only if the given containers holds the mark. |
733 | 769 | * |
734 | 770 | */ |
850 | 886 | Con *con_for_window(Con *con, i3Window *window, Match **store_match) { |
851 | 887 | Con *child; |
852 | 888 | Match *match; |
853 | //DLOG("searching con for window %p starting at con %p\n", window, con); | |
854 | //DLOG("class == %s\n", window->class_class); | |
855 | 889 | |
856 | 890 | TAILQ_FOREACH (child, &(con->nodes_head), nodes) { |
857 | 891 | TAILQ_FOREACH (match, &(child->swallow_head), matches) { |
1010 | 1044 | Con *child; |
1011 | 1045 | int children = con_num_children(con); |
1012 | 1046 | |
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 */ | |
1015 | 1049 | double total = 0.0; |
1016 | 1050 | int children_with_percent = 0; |
1017 | 1051 | TAILQ_FOREACH (child, &(con->nodes_head), nodes) { |
1021 | 1055 | } |
1022 | 1056 | } |
1023 | 1057 | |
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 */ | |
1026 | 1060 | if (children_with_percent != children) { |
1027 | 1061 | TAILQ_FOREACH (child, &(con->nodes_head), nodes) { |
1028 | 1062 | if (child->percent <= 0.0) { |
1035 | 1069 | } |
1036 | 1070 | } |
1037 | 1071 | |
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 */ | |
1040 | 1074 | if (total == 0.0) { |
1041 | 1075 | TAILQ_FOREACH (child, &(con->nodes_head), nodes) { |
1042 | 1076 | child->percent = 1.0 / children; |
1354 | 1388 | return true; |
1355 | 1389 | } |
1356 | 1390 | |
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) { | |
1368 | 1392 | /* For target containers in the scratchpad, we just send the window to the scratchpad. */ |
1369 | 1393 | if (con_get_workspace(target) == workspace_get("__i3_scratch")) { |
1370 | 1394 | DLOG("target container is in the scratchpad, moving container to scratchpad.\n"); |
1399 | 1423 | } |
1400 | 1424 | |
1401 | 1425 | 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); | |
1402 | 1440 | } |
1403 | 1441 | |
1404 | 1442 | /* |
1770 | 1808 | * floating window. |
1771 | 1809 | * |
1772 | 1810 | */ |
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 | ||
1774 | 1816 | /* Handle the simple case: non-floating containerns */ |
1775 | 1817 | if (!con_is_floating(con)) { |
1776 | 1818 | con->border_style = border_style; |
1783 | 1825 | * con->rect represent the absolute position of the window (same for |
1784 | 1826 | * parent). Then, we change the border style and subtract the new border |
1785 | 1827 | * pixels. For the parent, we do the same also for the decoration. */ |
1786 | DLOG("This is a floating container\n"); | |
1787 | ||
1788 | 1828 | Con *parent = con->parent; |
1789 | 1829 | Rect bsr = con_border_style_rect(con); |
1790 | 1830 | int deco_height = (con->border_style == BS_NORMAL ? render_deco_height() : 0); |
1869 | 1909 | con_attach(new, con, false); |
1870 | 1910 | |
1871 | 1911 | tree_flatten(croot); |
1872 | } | |
1873 | con_force_split_parents_redraw(con); | |
1874 | return; | |
1912 | con_force_split_parents_redraw(con); | |
1913 | return; | |
1914 | } | |
1875 | 1915 | } |
1876 | 1916 | |
1877 | 1917 | if (layout == L_DEFAULT) { |
2215 | 2255 | } else |
2216 | 2256 | DLOG("Discarding urgency WM_HINT because timer is running\n"); |
2217 | 2257 | |
2218 | //CLIENT_LOG(con); | |
2219 | 2258 | if (con->window) { |
2220 | 2259 | if (con->urgent) { |
2221 | 2260 | gettimeofday(&con->window->urgent, NULL); |
2357 | 2396 | char *title; |
2358 | 2397 | char *class; |
2359 | 2398 | char *instance; |
2399 | char *machine; | |
2360 | 2400 | if (win == NULL) { |
2361 | 2401 | title = pango_escape_markup(con_get_tree_representation(con)); |
2362 | 2402 | class = sstrdup("i3-frame"); |
2363 | 2403 | instance = sstrdup("i3-frame"); |
2404 | machine = sstrdup(""); | |
2364 | 2405 | } else { |
2365 | 2406 | title = pango_escape_markup(sstrdup((win->name == NULL) ? "" : i3string_as_utf8(win->name))); |
2366 | 2407 | class = pango_escape_markup(sstrdup((win->class_class == NULL) ? "" : win->class_class)); |
2367 | 2408 | instance = pango_escape_markup(sstrdup((win->class_instance == NULL) ? "" : win->class_instance)); |
2409 | machine = pango_escape_markup(sstrdup((win->machine == NULL) ? "" : win->machine)); | |
2368 | 2410 | } |
2369 | 2411 | |
2370 | 2412 | placeholder_t placeholders[] = { |
2371 | 2413 | {.name = "%title", .value = title}, |
2372 | 2414 | {.name = "%class", .value = class}, |
2373 | {.name = "%instance", .value = instance}}; | |
2415 | {.name = "%instance", .value = instance}, | |
2416 | {.name = "%machine", .value = machine}, | |
2417 | }; | |
2374 | 2418 | const size_t num = sizeof(placeholders) / sizeof(placeholder_t); |
2375 | 2419 | |
2376 | 2420 | char *formatted_str = format_placeholders(con->title_format, &placeholders[0], num); |
9 | 9 | */ |
10 | 10 | #include "all.h" |
11 | 11 | |
12 | #include <libgen.h> | |
13 | #include <unistd.h> | |
14 | ||
12 | 15 | #include <xkbcommon/xkbcommon.h> |
13 | 16 | |
14 | 17 | char *current_configpath = NULL; |
15 | char *current_config = NULL; | |
16 | 18 | Config config; |
17 | 19 | struct modes_head modes; |
18 | 20 | struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs); |
21 | struct includedfiles_head included_files = TAILQ_HEAD_INITIALIZER(included_files); | |
19 | 22 | |
20 | 23 | /* |
21 | 24 | * Ungrabs all keys, to be called before re-grabbing the keys because of a |
193 | 196 | INIT_COLOR(config.client.focused_inactive, "#333333", "#5f676a", "#ffffff", "#484e50"); |
194 | 197 | INIT_COLOR(config.client.unfocused, "#333333", "#222222", "#888888", "#292d2e"); |
195 | 198 | INIT_COLOR(config.client.urgent, "#2f343a", "#900000", "#ffffff", "#900000"); |
199 | config.client.got_focused_tab_title = false; | |
196 | 200 | |
197 | 201 | /* border and indicator color are ignored for placeholder contents */ |
198 | 202 | INIT_COLOR(config.client.placeholder, "#000000", "#0c0c0c", "#ffffff", "#000000"); |
223 | 227 | |
224 | 228 | config.focus_wrapping = FOCUS_WRAPPING_ON; |
225 | 229 | |
230 | config.tiling_drag = TILING_DRAG_MODIFIER; | |
231 | ||
226 | 232 | FREE(current_configpath); |
227 | 233 | current_configpath = get_config_path(override_configpath, true); |
228 | 234 | if (current_configpath == NULL) { |
230 | 236 | "$XDG_CONFIG_HOME/i3/config, ~/.i3/config, $XDG_CONFIG_DIRS/i3/config " |
231 | 237 | "and " SYSCONFDIR "/i3/config)"); |
232 | 238 | } |
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(); | |
235 | 276 | |
236 | 277 | if (config.font.type == FONT_TYPE_NONE && load_type != C_VALIDATE) { |
237 | 278 | ELOG("You did not specify required configuration option \"font\"\n"); |
239 | 280 | set_font(&config.font); |
240 | 281 | } |
241 | 282 | |
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 | ||
242 | 294 | if (load_type == C_RELOAD) { |
243 | 295 | translate_keysyms(); |
244 | 296 | grab_all_keys(conn); |
250 | 302 | xcb_flush(conn); |
251 | 303 | } |
252 | 304 | |
253 | return result; | |
305 | return result == 0; | |
254 | 306 | } |
8 | 8 | */ |
9 | 9 | #include "all.h" |
10 | 10 | |
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 | ||
11 | 91 | /******************************************************************************* |
12 | 92 | * Criteria functions. |
13 | 93 | ******************************************************************************/ |
43 | 123 | /******************************************************************************* |
44 | 124 | * Utility functions |
45 | 125 | ******************************************************************************/ |
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 | } | |
55 | 126 | |
56 | 127 | /* |
57 | 128 | * A utility function to convert a string containing the group and modifiers to |
91 | 162 | return result; |
92 | 163 | } |
93 | 164 | |
94 | static char *font_pattern; | |
95 | ||
96 | 165 | CFGFUN(font, const char *font) { |
97 | 166 | config.font = load_font(font, true); |
98 | 167 | 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); | |
103 | 168 | } |
104 | 169 | |
105 | 170 | 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) { |
114 | 179 | static bool current_mode_pango_markup; |
115 | 180 | |
116 | 181 | 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 | ||
117 | 187 | configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, current_mode, current_mode_pango_markup); |
118 | 188 | } |
119 | 189 | |
304 | 374 | if (!strcmp(enable, "no_gaps")) |
305 | 375 | config.smart_borders = SMART_BORDERS_NO_GAPS; |
306 | 376 | 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; | |
308 | 378 | } |
309 | 379 | |
310 | 380 | CFGFUN(smart_gaps, const char *enable) { |
311 | 381 | if (!strcmp(enable, "inverse_outer")) |
312 | 382 | config.smart_gaps = SMART_GAPS_INVERSE_OUTER; |
313 | 383 | 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; | |
315 | 385 | } |
316 | 386 | |
317 | 387 | CFGFUN(floating_minimum_size, const long width, const long height) { |
392 | 462 | config.hide_edge_borders = HEBM_BOTH; |
393 | 463 | else if (strcmp(borders, "none") == 0) |
394 | 464 | config.hide_edge_borders = HEBM_NONE; |
395 | else if (eval_boolstr(borders)) | |
465 | else if (boolstr(borders)) | |
396 | 466 | config.hide_edge_borders = HEBM_VERTICAL; |
397 | 467 | else |
398 | 468 | config.hide_edge_borders = HEBM_NONE; |
399 | 469 | } |
400 | 470 | |
401 | 471 | CFGFUN(focus_follows_mouse, const char *value) { |
402 | config.disable_focus_follows_mouse = !eval_boolstr(value); | |
472 | config.disable_focus_follows_mouse = !boolstr(value); | |
403 | 473 | } |
404 | 474 | |
405 | 475 | CFGFUN(mouse_warping, const char *value) { |
410 | 480 | } |
411 | 481 | |
412 | 482 | CFGFUN(force_xinerama, const char *value) { |
413 | config.force_xinerama = eval_boolstr(value); | |
483 | config.force_xinerama = boolstr(value); | |
414 | 484 | } |
415 | 485 | |
416 | 486 | CFGFUN(disable_randr15, const char *value) { |
417 | config.disable_randr15 = eval_boolstr(value); | |
487 | config.disable_randr15 = boolstr(value); | |
418 | 488 | } |
419 | 489 | |
420 | 490 | CFGFUN(focus_wrapping, const char *value) { |
422 | 492 | config.focus_wrapping = FOCUS_WRAPPING_FORCE; |
423 | 493 | } else if (strcmp(value, "workspace") == 0) { |
424 | 494 | config.focus_wrapping = FOCUS_WRAPPING_WORKSPACE; |
425 | } else if (eval_boolstr(value)) { | |
495 | } else if (boolstr(value)) { | |
426 | 496 | config.focus_wrapping = FOCUS_WRAPPING_ON; |
427 | 497 | } else { |
428 | 498 | config.focus_wrapping = FOCUS_WRAPPING_OFF; |
431 | 501 | |
432 | 502 | CFGFUN(force_focus_wrapping, const char *value) { |
433 | 503 | /* Legacy syntax. */ |
434 | if (eval_boolstr(value)) { | |
504 | if (boolstr(value)) { | |
435 | 505 | config.focus_wrapping = FOCUS_WRAPPING_FORCE; |
436 | 506 | } else { |
437 | 507 | /* For "force_focus_wrapping off", don't enable or disable |
443 | 513 | } |
444 | 514 | |
445 | 515 | 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); | |
447 | 517 | } |
448 | 518 | |
449 | 519 | CFGFUN(fake_outputs, const char *outputs) { |
485 | 555 | } |
486 | 556 | |
487 | 557 | CFGFUN(show_marks, const char *value) { |
488 | config.show_marks = eval_boolstr(value); | |
558 | config.show_marks = boolstr(value); | |
489 | 559 | } |
490 | 560 | |
491 | 561 | static char *current_workspace = NULL; |
553 | 623 | } |
554 | 624 | |
555 | 625 | 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 | } \ | |
571 | 648 | } while (0) |
572 | 649 | |
573 | 650 | APPLY_COLORS(focused_inactive); |
651 | APPLY_COLORS(focused_tab_title); | |
574 | 652 | APPLY_COLORS(focused); |
575 | 653 | APPLY_COLORS(unfocused); |
576 | 654 | APPLY_COLORS(urgent); |
639 | 717 | ipc_set_kill_timeout(timeout_ms / 1000.0); |
640 | 718 | } |
641 | 719 | |
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 | ||
642 | 734 | /******************************************************************************* |
643 | 735 | * Bar configuration (i3bar) |
644 | 736 | ******************************************************************************/ |
675 | 767 | } |
676 | 768 | |
677 | 769 | CFGFUN(bar_verbose, const char *verbose) { |
678 | current_bar->verbose = eval_boolstr(verbose); | |
770 | current_bar->verbose = boolstr(verbose); | |
679 | 771 | } |
680 | 772 | |
681 | 773 | CFGFUN(bar_height, const long height) { |
799 | 891 | } |
800 | 892 | |
801 | 893 | 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); | |
803 | 895 | } |
804 | 896 | |
805 | 897 | CFGFUN(bar_workspace_buttons, const char *value) { |
806 | current_bar->hide_workspace_buttons = !eval_boolstr(value); | |
898 | current_bar->hide_workspace_buttons = !boolstr(value); | |
807 | 899 | } |
808 | 900 | |
809 | 901 | CFGFUN(bar_workspace_min_width, const long width) { |
811 | 903 | } |
812 | 904 | |
813 | 905 | 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); | |
815 | 907 | } |
816 | 908 | |
817 | 909 | 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); | |
819 | 911 | } |
820 | 912 | |
821 | 913 | CFGFUN(bar_start) { |
834 | 926 | |
835 | 927 | config.number_barconfigs++; |
836 | 928 | |
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 | ||
841 | 929 | TAILQ_INSERT_TAIL(&barconfigs, current_bar, configs); |
842 | 930 | /* Simply reset the pointer, but don't free the resources. */ |
843 | 931 | current_bar = NULL; |
34 | 34 | #include <sys/types.h> |
35 | 35 | #include <sys/wait.h> |
36 | 36 | #include <unistd.h> |
37 | #include <libgen.h> | |
37 | 38 | |
38 | 39 | #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)) | |
43 | 40 | |
44 | 41 | xcb_xrm_database_t *database = NULL; |
45 | 42 | |
46 | 43 | #ifndef TEST_PARSER |
47 | 44 | pid_t config_error_nagbar_pid = -1; |
48 | static struct context *context; | |
49 | 45 | #endif |
50 | 46 | |
51 | 47 | /******************************************************************************* |
75 | 71 | |
76 | 72 | #include "GENERATED_config_tokens.h" |
77 | 73 | |
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 | ||
99 | 74 | /* |
100 | 75 | * Pushes a string (identified by 'identifier') on the stack. We simply use a |
101 | 76 | * single array, since the number of entries we have to store is very small. |
102 | 77 | * |
103 | 78 | */ |
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) { | |
105 | 80 | 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) | |
108 | 83 | continue; |
109 | if (stack[c].identifier == NULL) { | |
84 | if (ctx->stack[c].identifier == NULL) { | |
110 | 85 | /* 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; | |
114 | 89 | } else { |
115 | 90 | /* 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); | |
118 | 93 | free(prev); |
119 | 94 | } |
120 | 95 | return; |
129 | 104 | exit(EXIT_FAILURE); |
130 | 105 | } |
131 | 106 | |
132 | static void push_long(const char *identifier, long num) { | |
107 | static void push_long(struct stack *ctx, const char *identifier, long num) { | |
133 | 108 | for (int c = 0; c < 10; c++) { |
134 | if (stack[c].identifier != NULL) | |
109 | if (ctx->stack[c].identifier != NULL) { | |
135 | 110 | continue; |
111 | } | |
136 | 112 | /* 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; | |
140 | 116 | return; |
141 | 117 | } |
142 | 118 | |
149 | 125 | exit(EXIT_FAILURE); |
150 | 126 | } |
151 | 127 | |
152 | static const char *get_string(const char *identifier) { | |
128 | static const char *get_string(struct stack *ctx, const char *identifier) { | |
153 | 129 | for (int c = 0; c < 10; c++) { |
154 | if (stack[c].identifier == NULL) | |
130 | if (ctx->stack[c].identifier == NULL) | |
155 | 131 | 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; | |
158 | 134 | } |
159 | 135 | return NULL; |
160 | 136 | } |
161 | 137 | |
162 | static long get_long(const char *identifier) { | |
138 | static long get_long(struct stack *ctx, const char *identifier) { | |
163 | 139 | for (int c = 0; c < 10; c++) { |
164 | if (stack[c].identifier == NULL) | |
140 | if (ctx->stack[c].identifier == NULL) | |
165 | 141 | 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; | |
168 | 144 | } |
169 | 145 | return 0; |
170 | 146 | } |
171 | 147 | |
172 | static void clear_stack(void) { | |
148 | static void clear_stack(struct stack *ctx) { | |
173 | 149 | 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; | |
179 | 155 | } |
180 | 156 | } |
181 | 157 | |
183 | 159 | * The parser itself. |
184 | 160 | ******************************************************************************/ |
185 | 161 | |
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 | ||
200 | 162 | #include "GENERATED_config_call.h" |
201 | 163 | |
202 | static void next_state(const cmdp_token *token) { | |
164 | static void next_state(const cmdp_token *token, struct parser_ctx *ctx) { | |
203 | 165 | cmdp_state _next_state = token->next_state; |
204 | 166 | |
205 | //printf("token = name %s identifier %s\n", token->name, token->identifier); | |
206 | //printf("next_state = %d\n", token->next_state); | |
207 | 167 | 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 | } | |
210 | 175 | _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); | |
217 | 182 | } |
218 | 183 | |
219 | 184 | /* See if we are jumping back to a state in which we were in previously |
220 | 185 | * (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) { | |
223 | 188 | continue; |
224 | statelist_idx = i + 1; | |
189 | } | |
190 | ctx->statelist_idx = i + 1; | |
225 | 191 | return; |
226 | 192 | } |
227 | 193 | |
228 | 194 | /* 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; | |
230 | 196 | } |
231 | 197 | |
232 | 198 | /* |
256 | 222 | return result; |
257 | 223 | } |
258 | 224 | |
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) { | |
260 | 226 | /* Dump the entire config file into the debug log. We cannot just use |
261 | 227 | * DLOG("%s", input); because one log message must not exceed 4 KiB. */ |
262 | 228 | const char *dumpwalk = input; |
272 | 238 | } |
273 | 239 | linecnt++; |
274 | 240 | } |
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; | |
282 | 246 | |
283 | 247 | const char *walk = input; |
284 | 248 | const size_t len = strlen(input); |
287 | 251 | bool token_handled; |
288 | 252 | linecnt = 1; |
289 | 253 | |
290 | // TODO: make this testable | |
291 | 254 | #ifndef TEST_PARSER |
292 | cfg_criteria_init(¤t_match, &subcommand_output, INITIAL); | |
255 | struct ConfigResultIR subcommand_output = { | |
256 | .ctx = ctx, | |
257 | }; | |
258 | cfg_criteria_init(&(ctx->current_match), &subcommand_output, INITIAL); | |
293 | 259 | #endif |
294 | 260 | |
295 | 261 | /* The "<=" operator is intentional: We also handle the terminating 0-byte |
300 | 266 | while ((*walk == ' ' || *walk == '\t') && *walk != '\0') |
301 | 267 | walk++; |
302 | 268 | |
303 | //printf("remaining input: %s\n", walk); | |
304 | ||
305 | cmdp_token_ptr *ptr = &(tokens[state]); | |
269 | cmdp_token_ptr *ptr = &(tokens[ctx->state]); | |
306 | 270 | token_handled = false; |
307 | 271 | for (c = 0; c < ptr->n; c++) { |
308 | 272 | token = &(ptr->array[c]); |
310 | 274 | /* A literal. */ |
311 | 275 | if (token->name[0] == '\'') { |
312 | 276 | 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 | } | |
315 | 280 | walk += strlen(token->name) - 1; |
316 | next_state(token); | |
281 | next_state(token, ctx); | |
317 | 282 | token_handled = true; |
318 | 283 | break; |
319 | 284 | } |
333 | 298 | if (end == walk) |
334 | 299 | continue; |
335 | 300 | |
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 | } | |
338 | 304 | |
339 | 305 | /* Set walk to the first non-number character */ |
340 | 306 | walk = end; |
341 | next_state(token); | |
307 | next_state(token, ctx); | |
342 | 308 | token_handled = true; |
343 | 309 | break; |
344 | 310 | } |
381 | 347 | inpos++; |
382 | 348 | str[outpos] = beginning[inpos]; |
383 | 349 | } |
384 | if (token->identifier) | |
385 | push_string(token->identifier, str); | |
350 | if (token->identifier) { | |
351 | push_string(ctx->stack, token->identifier, str); | |
352 | } | |
386 | 353 | free(str); |
387 | 354 | /* If we are at the end of a quoted string, skip the ending |
388 | 355 | * double quote. */ |
389 | 356 | if (*walk == '"') |
390 | 357 | walk++; |
391 | next_state(token); | |
358 | next_state(token, ctx); | |
392 | 359 | token_handled = true; |
393 | 360 | break; |
394 | 361 | } |
397 | 364 | if (strcmp(token->name, "line") == 0) { |
398 | 365 | while (*walk != '\0' && *walk != '\n' && *walk != '\r') |
399 | 366 | walk++; |
400 | next_state(token); | |
367 | next_state(token, ctx); | |
401 | 368 | token_handled = true; |
402 | 369 | linecnt++; |
403 | 370 | walk++; |
405 | 372 | } |
406 | 373 | |
407 | 374 | if (strcmp(token->name, "end") == 0) { |
408 | //printf("checking for end: *%s*\n", walk); | |
409 | 375 | if (*walk == '\0' || *walk == '\n' || *walk == '\r') { |
410 | next_state(token); | |
376 | next_state(token, ctx); | |
411 | 377 | token_handled = true; |
412 | 378 | /* To make sure we start with an appropriate matching |
413 | 379 | * datastructure for commands which do *not* specify any |
414 | 380 | * criteria, we re-initialize the criteria system after |
415 | 381 | * every command. */ |
416 | // TODO: make this testable | |
417 | 382 | #ifndef TEST_PARSER |
418 | cfg_criteria_init(¤t_match, &subcommand_output, INITIAL); | |
383 | cfg_criteria_init(&(ctx->current_match), &subcommand_output, INITIAL); | |
419 | 384 | #endif |
420 | 385 | linecnt++; |
421 | 386 | walk++; |
472 | 437 | const char *error_line = start_of_line(walk, input); |
473 | 438 | |
474 | 439 | /* 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. */ | |
476 | 441 | char *position = scalloc(strlen(error_line) + 1, 1); |
477 | 442 | const char *copywalk; |
478 | 443 | for (copywalk = error_line; |
514 | 479 | |
515 | 480 | context->has_errors = true; |
516 | 481 | |
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 | ||
534 | 482 | /* Skip the rest of this line, but continue parsing. */ |
535 | 483 | while ((size_t)(walk - input) <= len && *walk != '\n') |
536 | 484 | walk++; |
537 | 485 | |
538 | 486 | free(position); |
539 | 487 | free(errormessage); |
540 | clear_stack(); | |
488 | clear_stack(ctx->stack); | |
541 | 489 | |
542 | 490 | /* To figure out in which state to go (e.g. MODE or INITIAL), |
543 | 491 | * we find the nearest state which contains an <error> token |
544 | 492 | * and follow that one. */ |
545 | 493 | 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]]); | |
548 | 496 | for (int j = 0; j < errptr->n; j++) { |
549 | 497 | if (strcmp(errptr->array[j].name, "error") != 0) |
550 | 498 | continue; |
551 | next_state(&(errptr->array[j])); | |
499 | next_state(&(errptr->array[j]), ctx); | |
552 | 500 | error_token_found = true; |
553 | 501 | break; |
554 | 502 | } |
557 | 505 | assert(error_token_found); |
558 | 506 | } |
559 | 507 | } |
560 | ||
561 | y(array_close); | |
562 | ||
563 | return &command_output; | |
564 | 508 | } |
565 | 509 | |
566 | 510 | /******************************************************************************* |
611 | 555 | fprintf(stderr, "Syntax: %s <command>\n", argv[0]); |
612 | 556 | return 1; |
613 | 557 | } |
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)); | |
614 | 566 | struct context context; |
615 | 567 | context.filename = "<stdin>"; |
616 | parse_config(argv[1], &context); | |
568 | parse_config(&ctx, argv[1], &context); | |
617 | 569 | } |
618 | 570 | |
619 | 571 | #else |
635 | 587 | |
636 | 588 | /* check for some v4-only statements */ |
637 | 589 | if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 || |
590 | strncasecmp(line, "include", strlen("include")) == 0 || | |
638 | 591 | strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 || |
639 | 592 | strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 || |
640 | 593 | strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) { |
877 | 830 | } |
878 | 831 | |
879 | 832 | /* |
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 | /* | |
880 | 848 | * Parses the given file by first replacing the variables, then calling |
881 | 849 | * parse_config and possibly launching i3-nagbar. |
882 | 850 | * |
883 | 851 | */ |
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) { | |
886 | 853 | int fd; |
887 | 854 | struct stat stbuf; |
888 | 855 | char *buf; |
889 | 856 | FILE *fstr; |
890 | 857 | char buffer[4096], key[512], value[4096], *continuation = NULL; |
891 | 858 | |
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 | } | |
897 | 879 | |
898 | 880 | buf = scalloc(stbuf.st_size + 1, 1); |
899 | 881 | |
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; | |
907 | 889 | } |
908 | 890 | rewind(fstr); |
909 | 891 | |
915 | 897 | if (fgets(continuation, sizeof(buffer) - (continuation - buffer), fstr) == NULL) { |
916 | 898 | if (feof(fstr)) |
917 | 899 | break; |
918 | die("Could not read configuration file\n"); | |
900 | return PARSE_FILE_FAILED; | |
919 | 901 | } |
920 | 902 | if (buffer[strlen(buffer) - 1] != '\n' && !feof(fstr)) { |
921 | 903 | ELOG("Your line continuation is too long, it exceeds %zd bytes\n", sizeof(buffer)); |
959 | 941 | continue; |
960 | 942 | } |
961 | 943 | |
962 | upsert_variable(&variables, v_key, v_value); | |
944 | upsert_variable(&(ctx->variables), v_key, v_value); | |
963 | 945 | continue; |
964 | 946 | } else if (strcasecmp(key, "set_from_resource") == 0) { |
965 | 947 | char res_name[512] = {'\0'}; |
992 | 974 | res_value = sstrdup(fallback); |
993 | 975 | } |
994 | 976 | |
995 | upsert_variable(&variables, v_key, res_value); | |
977 | upsert_variable(&(ctx->variables), v_key, res_value); | |
996 | 978 | FREE(res_value); |
997 | 979 | continue; |
998 | 980 | } |
1013 | 995 | * variables (otherwise we will count them twice, which is bad when |
1014 | 996 | * 'extra' is negative) */ |
1015 | 997 | char *bufcopy = sstrdup(buf); |
1016 | SLIST_FOREACH (current, &variables, variables) { | |
998 | SLIST_FOREACH (current, &(ctx->variables), variables) { | |
1017 | 999 | int extra = (strlen(current->value) - strlen(current->key)); |
1018 | 1000 | char *next; |
1019 | 1001 | for (next = bufcopy; |
1020 | 1002 | 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 | } | |
1024 | 1011 | extra_bytes += extra; |
1025 | 1012 | } |
1026 | 1013 | } |
1033 | 1020 | destwalk = new; |
1034 | 1021 | while (walk < (buf + stbuf.st_size)) { |
1035 | 1022 | /* Find the next variable */ |
1036 | SLIST_FOREACH (current, &variables, variables) { | |
1023 | SLIST_FOREACH (current, &(ctx->variables), variables) { | |
1037 | 1024 | current->next_match = strcasestr(walk, current->key); |
1038 | 1025 | } |
1039 | 1026 | nearest = NULL; |
1040 | 1027 | int distance = stbuf.st_size; |
1041 | SLIST_FOREACH (current, &variables, variables) { | |
1028 | SLIST_FOREACH (current, &(ctx->variables), variables) { | |
1042 | 1029 | if (current->next_match == NULL) |
1043 | 1030 | continue; |
1044 | 1031 | if ((current->next_match - walk) < distance) { |
1063 | 1050 | |
1064 | 1051 | /* analyze the string to find out whether this is an old config file (3.x) |
1065 | 1052 | * 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 | } | |
1067 | 1057 | if (version == 3) { |
1068 | 1058 | /* We need to convert this v3 configuration */ |
1069 | 1059 | char *converted = migrate_config(new, strlen(new)); |
1089 | 1079 | } |
1090 | 1080 | } |
1091 | 1081 | |
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)); | |
1093 | 1085 | 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 | ||
1099 | 1091 | 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)) { | |
1103 | 1094 | ELOG("FYI: You are using i3 version %s\n", i3_version); |
1104 | 1095 | if (version == 3) |
1105 | 1096 | ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n"); |
1107 | 1098 | start_config_error_nagbar(f, context->has_errors || invalid_sets); |
1108 | 1099 | } |
1109 | 1100 | |
1110 | bool has_errors = context->has_errors; | |
1101 | const bool has_errors = context->has_errors; | |
1111 | 1102 | |
1112 | 1103 | FREE(context->line_copy); |
1113 | 1104 | free(context); |
1114 | 1105 | free(new); |
1115 | 1106 | free(buf); |
1116 | 1107 | |
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; | |
1126 | 1117 | } |
1127 | 1118 | |
1128 | 1119 | #endif |
13 | 13 | #include <time.h> |
14 | 14 | #include <unistd.h> |
15 | 15 | |
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; | |
18 | 22 | |
19 | 23 | static int version_string(void *ctx, const unsigned char *val, size_t len) { |
20 | if (human_readable_key) | |
24 | if (human_readable_key) { | |
21 | 25 | sasprintf(&human_readable_version, "%.*s", (int)len, val); |
22 | if (loaded_config_file_name_key) | |
26 | } | |
27 | if (loaded_config_file_name_key) { | |
23 | 28 | 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 | } | |
24 | 35 | return 1; |
25 | 36 | } |
26 | 37 | |
27 | 38 | 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 | |
32 | 44 | return 1; |
33 | 45 | } |
34 | 46 | |
37 | 49 | .yajl_map_key = version_map_key, |
38 | 50 | }; |
39 | 51 | |
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 | ||
40 | 68 | /* |
41 | 69 | * Connects to i3 to find out the currently running version. Useful since it |
42 | 70 | * 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). | |
44 | 72 | * |
45 | 73 | * The output looks like this: |
46 | 74 | * Running i3 version: 4.2-202-gb8e782c (2012-08-12, branch "next") (pid 14804) |
97 | 125 | printf("Running i3 version: %s (pid %s)\n", human_readable_version, pid_from_atom); |
98 | 126 | |
99 | 127 | 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"); | |
111 | 133 | } |
112 | 134 | } |
113 | 135 |
41 | 41 | |
42 | 42 | static bool threshold_exceeded(uint32_t x1, uint32_t y1, |
43 | 43 | 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); | |
45 | 46 | return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) > threshold * threshold; |
46 | 47 | } |
47 | 48 |
277 | 277 | */ |
278 | 278 | void ewmh_update_sticky(xcb_window_t window, bool sticky) { |
279 | 279 | 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); | |
281 | 281 | xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY); |
282 | 282 | } 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); | |
284 | 284 | xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY); |
285 | 285 | } |
286 | 286 | } |
291 | 291 | */ |
292 | 292 | void ewmh_update_focused(xcb_window_t window, bool is_focused) { |
293 | 293 | 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); | |
295 | 295 | xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED); |
296 | 296 | } 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); | |
298 | 298 | xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED); |
299 | 299 | } |
300 | 300 | } |
252 | 252 | } |
253 | 253 | /* Consider the part of the focus stack of our current workspace: |
254 | 254 | * [ ... 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 | |
256 | 256 | * floating belongs in S_{i}. The new floating container, 'nc', will have the |
257 | 257 | * workspace as its parent so it needs to be placed in this stack. If C was focused |
258 | 258 | * we just need to call con_focus(). Otherwise, nc must be placed before or after S_{i}. |
346 | 346 | con->floating = FLOATING_USER_ON; |
347 | 347 | |
348 | 348 | /* 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 | } | |
351 | 352 | |
352 | 353 | /* Add pixels for the decoration. */ |
353 | 354 | Rect border_style_rect = con_border_style_rect(con); |
696 | 697 | void floating_resize_window(Con *con, const bool proportional, |
697 | 698 | const xcb_button_press_event_t *event) { |
698 | 699 | 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(); | |
699 | 704 | |
700 | 705 | /* corner saves the nearest corner to the original click. It contains |
701 | 706 | * a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */ |
69 | 69 | event->response_type != response_type) |
70 | 70 | continue; |
71 | 71 | |
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). */ | |
77 | 75 | return true; |
78 | 76 | } |
79 | 77 | |
269 | 267 | * Configure requests are received when the application wants to resize windows |
270 | 268 | * on their own. |
271 | 269 | * |
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 | |
273 | 271 | * "new" position. |
274 | 272 | * |
275 | 273 | */ |
800 | 798 | } else if (event->type == A_WM_CHANGE_STATE) { |
801 | 799 | /* http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.4 */ |
802 | 800 | 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; | |
804 | 802 | * 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); | |
806 | 804 | long data[] = {XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE}; |
807 | 805 | xcb_change_property(conn, XCB_PROP_MODE_REPLACE, event->window, |
808 | 806 | A_WM_STATE, A_WM_STATE, 32, 2, data); |
809 | 807 | } 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]); | |
811 | 809 | } |
812 | 810 | } else if (event->type == A__NET_CURRENT_DESKTOP) { |
813 | 811 | /* This request is used by pagers and bars to change the current |
875 | 873 | tree_close_internal(con, KILL_WINDOW, false); |
876 | 874 | tree_render(); |
877 | 875 | } 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); | |
879 | 877 | } |
880 | 878 | } else if (event->type == A__NET_WM_MOVERESIZE) { |
881 | 879 | /* |
884 | 882 | */ |
885 | 883 | Con *con = con_by_window_id(event->window); |
886 | 884 | 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); | |
888 | 886 | return; |
889 | 887 | } |
890 | 888 | DLOG("Handling _NET_WM_MOVERESIZE request (con = %p)\n", con); |
1058 | 1056 | } |
1059 | 1057 | |
1060 | 1058 | /* |
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 | /* | |
1061 | 1129 | * Handles ConfigureNotify events for the root window, which are generated when |
1062 | 1130 | * the monitor configuration changed. |
1063 | 1131 | * |
1073 | 1141 | return; |
1074 | 1142 | } |
1075 | 1143 | 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 */ | |
1076 | 1161 | } |
1077 | 1162 | |
1078 | 1163 | /* |
1086 | 1171 | } |
1087 | 1172 | |
1088 | 1173 | /* |
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. | |
1090 | 1185 | * |
1091 | 1186 | */ |
1092 | 1187 | static bool handle_motif_hints_change(Con *con, xcb_get_property_reply_t *prop) { |
1093 | 1188 | 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) { | |
1097 | 1192 | DLOG("Update border style of con %p to %d\n", con, motif_border_style); |
1098 | 1193 | con_set_border_style(con, motif_border_style, con->current_border_width); |
1099 | 1194 | |
1174 | 1269 | return true; |
1175 | 1270 | } |
1176 | 1271 | |
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 | ||
1177 | 1280 | /* Returns false if the event could not be processed (e.g. the window could not |
1178 | 1281 | * be found), true otherwise */ |
1179 | 1282 | typedef bool (*cb_property_handler_t)(Con *con, xcb_get_property_reply_t *property); |
1196 | 1299 | {0, UINT_MAX, handle_strut_partial_change}, |
1197 | 1300 | {0, UINT_MAX, handle_window_type}, |
1198 | 1301 | {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}}; | |
1200 | 1305 | #define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t)) |
1201 | 1306 | |
1202 | 1307 | /* |
1218 | 1323 | property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL; |
1219 | 1324 | property_handlers[9].atom = A__NET_WM_WINDOW_TYPE; |
1220 | 1325 | 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; | |
1222 | 1329 | } |
1223 | 1330 | |
1224 | 1331 | static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) { |
1236 | 1343 | } |
1237 | 1344 | |
1238 | 1345 | 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); */ | |
1240 | 1347 | return; |
1241 | 1348 | } |
1242 | 1349 | |
1394 | 1501 | handle_focus_in((xcb_focus_in_event_t *)event); |
1395 | 1502 | break; |
1396 | 1503 | |
1504 | case XCB_FOCUS_OUT: | |
1505 | handle_focus_out((xcb_focus_out_event_t *)event); | |
1506 | break; | |
1507 | ||
1397 | 1508 | case XCB_PROPERTY_NOTIFY: { |
1398 | 1509 | xcb_property_notify_event_t *e = (xcb_property_notify_event_t *)event; |
1399 | 1510 | last_timestamp = e->time; |
1405 | 1516 | handle_configure_notify((xcb_configure_notify_event_t *)event); |
1406 | 1517 | break; |
1407 | 1518 | |
1519 | case XCB_SELECTION_CLEAR: | |
1520 | handle_selection_clear((xcb_selection_clear_event_t *)event); | |
1521 | break; | |
1522 | ||
1408 | 1523 | 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 | } |
25 | 25 | char *current_socketpath = NULL; |
26 | 26 | |
27 | 27 | 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 | } | |
44 | 28 | |
45 | 29 | static void ipc_client_timeout(EV_P_ ev_timer *w, int revents); |
46 | 30 | static void ipc_socket_writeable_cb(EV_P_ struct ev_io *w, int revents); |
250 | 234 | ystr(name); |
251 | 235 | y(map_open); |
252 | 236 | ystr("x"); |
253 | y(integer, r.x); | |
237 | y(integer, (int32_t)r.x); | |
254 | 238 | ystr("y"); |
255 | y(integer, r.y); | |
239 | y(integer, (int32_t)r.y); | |
256 | 240 | ystr("width"); |
257 | 241 | y(integer, r.width); |
258 | 242 | ystr("height"); |
535 | 519 | ystr(con->title_format); |
536 | 520 | } |
537 | 521 | |
522 | ystr("window_icon_padding"); | |
523 | y(integer, con->window_icon_padding); | |
524 | ||
538 | 525 | if (con->type == CT_WORKSPACE) { |
539 | 526 | ystr("num"); |
540 | 527 | y(integer, con->num); |
596 | 583 | DUMP_PROPERTY("class", class_class); |
597 | 584 | DUMP_PROPERTY("instance", class_instance); |
598 | 585 | DUMP_PROPERTY("window_role", role); |
586 | DUMP_PROPERTY("machine", machine); | |
599 | 587 | |
600 | 588 | if (con->window->name != NULL) { |
601 | 589 | ystr("title"); |
685 | 673 | DUMP_REGEX(instance); |
686 | 674 | DUMP_REGEX(window_role); |
687 | 675 | DUMP_REGEX(title); |
676 | DUMP_REGEX(machine); | |
688 | 677 | |
689 | 678 | #undef DUMP_REGEX |
690 | 679 | y(map_close); |
1082 | 1071 | ystr("loaded_config_file_name"); |
1083 | 1072 | ystr(current_configpath); |
1084 | 1073 | |
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); | |
1085 | 1085 | y(map_close); |
1086 | 1086 | |
1087 | 1087 | const unsigned char *payload; |
1264 | 1264 | y(map_open); |
1265 | 1265 | |
1266 | 1266 | 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); | |
1268 | 1283 | |
1269 | 1284 | y(map_close); |
1270 | 1285 | |
1552 | 1567 | } |
1553 | 1568 | |
1554 | 1569 | /* |
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 | /* | |
1606 | 1570 | * Generates a json workspace event. Returns a dynamically allocated yajl |
1607 | 1571 | * generator. Free with yajl_gen_free(). |
1608 | 1572 | */ |
289 | 289 | } else if (strcasecmp(last_key, "title") == 0) { |
290 | 290 | current_swallow->title = regex_new(sval); |
291 | 291 | swallow_is_empty = false; |
292 | } else if (strcasecmp(last_key, "machine") == 0) { | |
293 | current_swallow->machine = regex_new(sval); | |
294 | swallow_is_empty = false; | |
292 | 295 | } else { |
293 | 296 | ELOG("swallow key %s unknown\n", last_key); |
294 | 297 | } |
455 | 458 | |
456 | 459 | if (strcasecmp(last_key, "current_border_width") == 0) |
457 | 460 | json_node->current_border_width = val; |
461 | ||
462 | if (strcasecmp(last_key, "window_icon_padding") == 0) { | |
463 | json_node->window_icon_padding = val; | |
464 | } | |
458 | 465 | |
459 | 466 | if (strcasecmp(last_key, "depth") == 0) |
460 | 467 | json_node->depth = val; |
11 | 11 | #include "all.h" |
12 | 12 | #include "shmlog.h" |
13 | 13 | |
14 | #include <ev.h> | |
15 | #include <libgen.h> | |
16 | #include <sys/socket.h> | |
17 | #include <sys/un.h> | |
14 | 18 | #include <errno.h> |
15 | 19 | #include <fcntl.h> |
16 | 20 | #include <stdarg.h> |
22 | 26 | #include <sys/stat.h> |
23 | 27 | #include <sys/time.h> |
24 | 28 | #include <unistd.h> |
25 | #if !defined(__OpenBSD__) | |
26 | #include <pthread.h> | |
27 | #endif | |
28 | 29 | |
29 | 30 | #if defined(__APPLE__) |
30 | 31 | #include <sys/sysctl.h> |
59 | 60 | /* Size (in bytes) of physical memory */ |
60 | 61 | static long long physical_mem_bytes; |
61 | 62 | |
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 | ||
62 | 75 | /* |
63 | 76 | * Writes the offsets for the next write and for the last wrap to the |
64 | 77 | * shmlog_header. |
123 | 136 | * For 512 MiB of RAM this will lead to a 5 MiB log buffer. |
124 | 137 | * At the moment (2011-12-10), no testcase leads to an i3 log |
125 | 138 | * 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 | ||
127 | 144 | #if defined(__FreeBSD__) |
128 | 145 | sasprintf(&shmlogname, "/tmp/i3-log-%d", getpid()); |
129 | 146 | #else |
159 | 176 | memset(logbuffer, '\0', logbuffer_size); |
160 | 177 | |
161 | 178 | 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 | |
170 | 179 | |
171 | 180 | logwalk = logbuffer + sizeof(i3_shmlog_header); |
172 | 181 | loglastwrap = logbuffer + logbuffer_size; |
282 | 291 | |
283 | 292 | store_log_markers(); |
284 | 293 | |
285 | #if !defined(__OpenBSD__) | |
286 | /* Wake up all (i3-dump-log) processes waiting for condvar. */ | |
287 | pthread_cond_broadcast(&(header->condvar)); | |
288 | #endif | |
289 | ||
290 | 294 | if (print) |
291 | 295 | fwrite(message, len, 1, stdout); |
296 | ||
297 | log_broadcast_to_clients(message, len); | |
292 | 298 | } |
293 | 299 | } |
294 | 300 | |
369 | 375 | rmdir(errorfilename); |
370 | 376 | } |
371 | 377 | } |
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 | } |
23 | 23 | #include <sys/types.h> |
24 | 24 | #include <sys/un.h> |
25 | 25 | #include <unistd.h> |
26 | #include <xcb/xcb_atom.h> | |
27 | #include <xcb/xinerama.h> | |
28 | #include <xcb/bigreq.h> | |
26 | 29 | |
27 | 30 | #ifdef I3_ASAN_ENABLED |
28 | 31 | #include <sanitizer/lsan_interface.h> |
61 | 64 | |
62 | 65 | xcb_screen_t *root_screen; |
63 | 66 | xcb_window_t root; |
67 | ||
68 | xcb_window_t wm_sn_selection_owner; | |
69 | xcb_atom_t wm_sn; | |
64 | 70 | |
65 | 71 | /* Color depth, visual id and colormap to use when creating windows and |
66 | 72 | * pixmaps. Will use 32 bit depth and an appropriate visual, if available, |
180 | 186 | } |
181 | 187 | ipc_shutdown(SHUTDOWN_REASON_EXIT, -1); |
182 | 188 | unlink(config.ipc_socket_path); |
189 | if (current_log_stream_socket_path != NULL) { | |
190 | unlink(current_log_stream_socket_path); | |
191 | } | |
183 | 192 | xcb_disconnect(conn); |
184 | 193 | |
185 | 194 | /* If a nagbar is active, kill it */ |
278 | 287 | char *fake_outputs = NULL; |
279 | 288 | bool disable_signalhandler = false; |
280 | 289 | bool only_check_config = false; |
290 | bool replace_wm = false; | |
281 | 291 | static struct option long_options[] = { |
282 | 292 | {"no-autostart", no_argument, 0, 'a'}, |
283 | 293 | {"config", required_argument, 0, 'c'}, |
300 | 310 | {"fake_outputs", required_argument, 0, 0}, |
301 | 311 | {"fake-outputs", required_argument, 0, 0}, |
302 | 312 | {"force-old-config-parser-v4.4-only", no_argument, 0, 0}, |
313 | {"replace", no_argument, 0, 'r'}, | |
303 | 314 | {0, 0, 0, 0}}; |
304 | 315 | int option_index = 0, opt; |
305 | 316 | |
324 | 335 | |
325 | 336 | start_argv = argv; |
326 | 337 | |
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) { | |
328 | 339 | switch (opt) { |
329 | 340 | case 'a': |
330 | 341 | LOG("Autostart disabled using -a\n"); |
361 | 372 | break; |
362 | 373 | case 'l': |
363 | 374 | /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */ |
375 | break; | |
376 | case 'r': | |
377 | replace_wm = true; | |
364 | 378 | break; |
365 | 379 | case 0: |
366 | 380 | if (strcmp(long_options[option_index].name, "force-xinerama") == 0 || |
442 | 456 | "\tThe default is %d bytes.\n", |
443 | 457 | shmlog_size); |
444 | 458 | fprintf(stderr, "\n"); |
459 | fprintf(stderr, "\t--replace\n" | |
460 | "\tReplace an existing window manager.\n"); | |
461 | fprintf(stderr, "\n"); | |
445 | 462 | fprintf(stderr, "If you pass plain text arguments, i3 will interpret them as a command\n" |
446 | 463 | "to send to a currently running i3 (like i3-msg). This allows you to\n" |
447 | 464 | "use nice and logical commands, such as:\n" |
569 | 586 | root_screen = xcb_aux_get_screen(conn, conn_screen); |
570 | 587 | root = root_screen->root; |
571 | 588 | |
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 | ||
572 | 604 | /* Place requests for the atoms we need as soon as possible */ |
573 | 605 | #define xmacro(atom) \ |
574 | 606 | xcb_intern_atom_cookie_t atom##_cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom); |
598 | 630 | visual_type = get_visualtype(root_screen); |
599 | 631 | } |
600 | 632 | |
633 | xcb_prefetch_maximum_request_length(conn); | |
634 | ||
601 | 635 | init_dpi(); |
602 | 636 | |
603 | 637 | DLOG("root_depth = %d, visual_id = 0x%08x.\n", root_depth, visual_type->visual_id); |
607 | 641 | |
608 | 642 | xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root); |
609 | 643 | 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 | } | |
610 | 660 | |
611 | 661 | /* Setup NetWM atoms */ |
612 | 662 | #define xmacro(name) \ |
632 | 682 | else |
633 | 683 | config.ipc_socket_path = sstrdup(config.ipc_socket_path); |
634 | 684 | } |
685 | /* Create the UNIX domain socket for IPC */ | |
686 | int ipc_socket = create_socket(config.ipc_socket_path, ¤t_socketpath); | |
687 | if (ipc_socket == -1) { | |
688 | die("Could not create the IPC socket: %s", config.ipc_socket_path); | |
689 | } | |
635 | 690 | |
636 | 691 | if (config.force_xinerama) { |
637 | 692 | 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); | |
638 | 790 | } |
639 | 791 | |
640 | 792 | xcb_void_cookie_t cookie; |
662 | 814 | xcursor_set_root_cursor(XCURSOR_CURSOR_POINTER); |
663 | 815 | |
664 | 816 | 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 | ||
668 | 817 | extreply = xcb_get_extension_data(conn, &xcb_xkb_id); |
669 | 818 | xkb_supported = extreply->present; |
670 | 819 | if (!extreply->present) { |
843 | 992 | |
844 | 993 | tree_render(); |
845 | 994 | |
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, ¤t_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"); | |
850 | 1006 | } 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. */ | |
859 | 1015 | listen_fds = sd_listen_fds(0); |
860 | 1016 | if (listen_fds < 0) |
861 | 1017 | ELOG("socket activation: Error in sd_listen_fds\n"); |
949 | 1105 | xcb_ungrab_server(conn); |
950 | 1106 | |
951 | 1107 | 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 | ||
953 | 1118 | 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 | } | |
970 | 1125 | } |
971 | 1126 | |
972 | 1127 | #if defined(__OpenBSD__) |
1040 | 1195 | * when calling exit() */ |
1041 | 1196 | atexit(i3_exit); |
1042 | 1197 | |
1198 | sd_notify(1, "READY=1"); | |
1043 | 1199 | ev_loop(main_loop, 0); |
1044 | 1200 | } |
115 | 115 | utf8_title_cookie, title_cookie, |
116 | 116 | class_cookie, leader_cookie, transient_cookie, |
117 | 117 | 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; | |
119 | 122 | |
120 | 123 | geomc = xcb_get_geometry(conn, d); |
121 | 124 | |
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 | |
123 | 126 | calling manage_window() for every window) */ |
124 | 127 | if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) { |
125 | 128 | DLOG("Could not get attributes\n"); |
188 | 191 | motif_wm_hints_cookie = GET_PROPERTY(A__MOTIF_WM_HINTS, 5 * sizeof(uint64_t)); |
189 | 192 | wm_user_time_cookie = GET_PROPERTY(A__NET_WM_USER_TIME, UINT32_MAX); |
190 | 193 | 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); | |
191 | 196 | |
192 | 197 | i3Window *cwindow = scalloc(1, sizeof(i3Window)); |
193 | 198 | cwindow->id = window; |
201 | 206 | window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL)); |
202 | 207 | window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL)); |
203 | 208 | 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)); | |
204 | 210 | window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL)); |
205 | 211 | window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL)); |
206 | 212 | window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL)); |
207 | 213 | window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL)); |
208 | 214 | bool urgency_hint; |
209 | 215 | 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); | |
212 | 218 | 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)); | |
213 | 220 | xcb_get_property_reply_t *type_reply = xcb_get_property_reply(conn, wm_type_cookie, NULL); |
214 | 221 | xcb_get_property_reply_t *state_reply = xcb_get_property_reply(conn, state_cookie, NULL); |
215 | 222 | |
477 | 484 | (cwindow->leader != XCB_NONE && |
478 | 485 | cwindow->leader != cwindow->id && |
479 | 486 | 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"); | |
481 | 488 | want_floating = true; |
482 | 489 | |
483 | 490 | if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN && |
484 | 491 | fs != NULL) { |
485 | LOG("There is a fullscreen window, leaving fullscreen mode\n"); | |
492 | DLOG("There is a fullscreen window, leaving fullscreen mode\n"); | |
486 | 493 | con_toggle_fullscreen(fs, CF_OUTPUT); |
487 | 494 | } else if (config.popup_during_fullscreen == PDF_SMART && |
488 | 495 | fs != NULL && |
489 | 496 | 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); | |
508 | 498 | } |
509 | 499 | } |
510 | 500 | |
520 | 510 | if (nc->geometry.width == 0) |
521 | 511 | nc->geometry = (Rect){geom->x, geom->y, geom->width, geom->height}; |
522 | 512 | |
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) { | |
524 | 521 | DLOG("MOTIF_WM_HINTS specifies decorations (border_style = %d)\n", motif_border_style); |
525 | 522 | if (want_floating) { |
526 | 523 | con_set_border_style(nc, motif_border_style, config.default_floating_border_width); |
527 | 524 | } else { |
528 | 525 | 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; | |
540 | 526 | } |
541 | 527 | } |
542 | 528 |
46 | 46 | match->instance == NULL && |
47 | 47 | match->window_role == NULL && |
48 | 48 | match->workspace == NULL && |
49 | match->machine == NULL && | |
49 | 50 | match->urgent == U_DONTCHECK && |
50 | 51 | match->id == XCB_NONE && |
51 | 52 | match->window_type == UINT32_MAX && |
52 | 53 | match->con_id == NULL && |
53 | 54 | match->dock == M_NODOCK && |
54 | match->window_mode == WM_ANY); | |
55 | match->window_mode == WM_ANY && | |
56 | match->match_all_windows == false); | |
55 | 57 | } |
56 | 58 | |
57 | 59 | /* |
129 | 131 | } |
130 | 132 | } |
131 | 133 | |
134 | CHECK_WINDOW_FIELD(machine, machine, str); | |
135 | ||
132 | 136 | Con *con = NULL; |
133 | 137 | if (match->urgent == U_LATEST) { |
134 | 138 | /* if the window isn't urgent, no sense in searching */ |
255 | 259 | |
256 | 260 | LOG("window_mode matches\n"); |
257 | 261 | } |
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. */ | |
258 | 266 | |
259 | 267 | return true; |
260 | 268 | } |
272 | 280 | regex_free(match->mark); |
273 | 281 | regex_free(match->window_role); |
274 | 282 | regex_free(match->workspace); |
283 | regex_free(match->machine); | |
275 | 284 | } |
276 | 285 | |
277 | 286 | /* |
389 | 398 | return; |
390 | 399 | } |
391 | 400 | |
401 | if (strcmp(ctype, "machine") == 0) { | |
402 | regex_free(match->machine); | |
403 | match->machine = regex_new(cvalue); | |
404 | return; | |
405 | } | |
406 | ||
392 | 407 | if (strcmp(ctype, "tiling") == 0) { |
393 | 408 | match->window_mode = WM_TILING; |
394 | 409 | return; |
427 | 442 | return; |
428 | 443 | } |
429 | 444 | |
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 | ||
430 | 456 | ELOG("Unknown criterion: %s\n", ctype); |
431 | 457 | } |
227 | 227 | * the focused container, con, is now a child of ws. To work around this |
228 | 228 | * and still produce the correct workspace focus events (see |
229 | 229 | * 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); | |
231 | 241 | focused = old_ws; |
232 | 242 | workspace_show(ws); |
233 | con_focus(con); | |
234 | 243 | } |
235 | 244 | |
236 | 245 | /* force re-painting the indicators */ |
319 | 328 | } else { |
320 | 329 | TAILQ_SWAP(con, swap, &(swap->parent->nodes_head), nodes); |
321 | 330 | } |
331 | ||
332 | /* redraw parents to ensure all parent split container titles are updated correctly */ | |
333 | con_force_split_parents_redraw(con); | |
322 | 334 | |
323 | 335 | ipc_send_window_event("move", con); |
324 | 336 | return; |
664 | 664 | |
665 | 665 | new->primary = monitor_info->primary; |
666 | 666 | |
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; | |
672 | 673 | |
673 | 674 | DLOG("name %s, x %d, y %d, width %d px, height %d px, width %d mm, height %d mm, primary %d, automatic %d\n", |
674 | 675 | name, |
742 | 743 | return; |
743 | 744 | } |
744 | 745 | |
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; | |
749 | 751 | free(crtc); |
750 | 752 | new->active = (new->rect.width != 0 && new->rect.height != 0); |
751 | 753 | if (!new->active) { |
942 | 944 | uint32_t width = min(other->rect.width, output->rect.width); |
943 | 945 | uint32_t height = min(other->rect.height, output->rect.height); |
944 | 946 | |
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) { | |
947 | 950 | output->changed = true; |
951 | } | |
948 | 952 | |
949 | 953 | update_if_necessary(&(other->rect.width), width); |
950 | 954 | update_if_necessary(&(other->rect.height), height); |
19 | 19 | * |
20 | 20 | */ |
21 | 21 | struct regex *regex_new(const char *pattern) { |
22 | const char *error; | |
23 | int errorcode, offset; | |
22 | int errorcode; | |
23 | PCRE2_SIZE offset; | |
24 | 24 | |
25 | 25 | struct regex *re = scalloc(1, sizeof(struct regex)); |
26 | 26 | re->pattern = sstrdup(pattern); |
27 | int options = PCRE_UTF8; | |
27 | uint32_t options = PCRE2_UTF; | |
28 | 28 | /* We use PCRE_UCP so that \B, \b, \D, \d, \S, \s, \W, \w and some POSIX |
29 | 29 | * 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); | |
40 | 36 | regex_free(re); |
41 | 37 | 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); | |
49 | 38 | } |
50 | 39 | return re; |
51 | 40 | } |
59 | 48 | return; |
60 | 49 | FREE(regex->pattern); |
61 | 50 | FREE(regex->regex); |
62 | FREE(regex->extra); | |
63 | 51 | FREE(regex); |
64 | 52 | } |
65 | 53 | |
70 | 58 | * |
71 | 59 | */ |
72 | 60 | bool regex_matches(struct regex *regex, const char *input) { |
61 | pcre2_match_data *match_data; | |
73 | 62 | int rc; |
63 | ||
64 | match_data = pcre2_match_data_create_from_pattern(regex->regex, NULL); | |
74 | 65 | |
75 | 66 | /* We use strlen() because pcre_exec() expects the length of the input |
76 | 67 | * 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) { | |
78 | 71 | LOG("Regular expression \"%s\" matches \"%s\"\n", |
79 | 72 | regex->pattern, input); |
80 | 73 | return true; |
81 | 74 | } |
82 | 75 | |
83 | if (rc == PCRE_ERROR_NOMATCH) { | |
76 | if (rc == PCRE2_ERROR_NOMATCH) { | |
84 | 77 | LOG("Regular expression \"%s\" does not match \"%s\"\n", |
85 | 78 | regex->pattern, input); |
86 | 79 | return false; |
254 | 254 | } |
255 | 255 | |
256 | 256 | 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)) { | |
282 | 258 | DLOG("Rendering floating child even though in fullscreen mode: " |
283 | 259 | "floating->transient_for (0x%08x) --> fullscreen->id (0x%08x)\n", |
284 | 260 | floating_child->window->transient_for, fullscreen->window->id); |
261 | } else { | |
262 | continue; | |
285 | 263 | } |
286 | 264 | } |
287 | 265 | DLOG("floating child at (%d,%d) with %d x %d\n", |
133 | 133 | draw_util_clear_surface(&(state->surface), background); |
134 | 134 | |
135 | 135 | // TODO: make i3font functions per-connection, at least these two for now…? |
136 | xcb_flush(restore_conn); | |
137 | 136 | xcb_aux_sync(restore_conn); |
138 | 137 | |
139 | 138 | Match *swallows; |
152 | 151 | APPEND_REGEX(instance); |
153 | 152 | APPEND_REGEX(window_role); |
154 | 153 | APPEND_REGEX(title); |
154 | APPEND_REGEX(machine); | |
155 | 155 | |
156 | 156 | if (serialized == NULL) { |
157 | 157 | DLOG("This swallows specification is not serializable?!\n"); |
178 | 178 | int y = (state->rect.height / 2) - (config.font.height / 2); |
179 | 179 | draw_util_text(line, &(state->surface), foreground, background, x, y, text_width); |
180 | 180 | i3string_free(line); |
181 | xcb_flush(restore_conn); | |
182 | 181 | xcb_aux_sync(restore_conn); |
183 | 182 | } |
184 | 183 |
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, ¶ms); | |
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 | } |
176 | 176 | } |
177 | 177 | |
178 | 178 | /* |
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 | /* | |
188 | 179 | * Goes through the list of arguments (for exec()) and add/replace the given option, |
189 | 180 | * including the option name, its argument, and the option character. |
190 | 181 | */ |
484 | 475 | return position == BEFORE ? D_UP : D_DOWN; |
485 | 476 | } |
486 | 477 | } |
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 | } |
17 | 17 | void window_free(i3Window *win) { |
18 | 18 | FREE(win->class_class); |
19 | 19 | FREE(win->class_instance); |
20 | FREE(win->role); | |
21 | FREE(win->machine); | |
20 | 22 | i3string_free(win->name); |
23 | cairo_surface_destroy(win->icon); | |
21 | 24 | FREE(win->ran_assignments); |
22 | 25 | FREE(win); |
23 | 26 | } |
411 | 414 | * it is still in use by popular widget toolkits such as GTK+ and Java AWT. |
412 | 415 | * |
413 | 416 | */ |
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>. | |
420 | 421 | */ |
421 | 422 | #define MWM_HINTS_FLAGS_FIELD 0 |
422 | 423 | #define MWM_HINTS_DECORATIONS_FIELD 2 |
431 | 432 | |
432 | 433 | if (prop == NULL || xcb_get_property_value_length(prop) == 0) { |
433 | 434 | FREE(prop); |
434 | return; | |
435 | return false; | |
435 | 436 | } |
436 | 437 | |
437 | 438 | /* The property consists of an array of 5 uint32_t's. The first value is a |
457 | 458 | } |
458 | 459 | |
459 | 460 | FREE(prop); |
461 | return true; | |
460 | 462 | |
461 | 463 | #undef MWM_HINTS_FLAGS_FIELD |
462 | 464 | #undef MWM_HINTS_DECORATIONS_FIELD |
465 | 467 | #undef MWM_DECOR_BORDER |
466 | 468 | #undef MWM_DECOR_TITLE |
467 | 469 | } |
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 | } |
138 | 138 | |
139 | 139 | /* We set workspace->num to the number if this workspace’s name begins with |
140 | 140 | * 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); | |
142 | 142 | |
143 | 143 | struct Workspace_Assignment *assignment; |
144 | 144 | TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) { |
253 | 253 | */ |
254 | 254 | Con *create_workspace_on_output(Output *output, Con *content) { |
255 | 255 | /* add a workspace to this output */ |
256 | char *name; | |
257 | 256 | bool exists = true; |
258 | 257 | Con *ws = con_new(NULL, NULL); |
259 | 258 | ws->type = CT_WORKSPACE; |
269 | 268 | continue; |
270 | 269 | } |
271 | 270 | |
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); | |
273 | 275 | if (!exists) { |
274 | 276 | ws->name = sstrdup(target_name); |
275 | 277 | /* Set ->num to the number of the workspace, if the name actually |
276 | 278 | * 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); | |
279 | 281 | |
280 | 282 | break; |
281 | 283 | } |
305 | 307 | |
306 | 308 | con_attach(ws, content, false); |
307 | 309 | |
310 | char *name; | |
308 | 311 | sasprintf(&name, "[i3 con] workspace %s", ws->name); |
309 | 312 | x_set_name(ws, name); |
310 | 313 | free(name); |
320 | 323 | |
321 | 324 | /* |
322 | 325 | * 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 | |
324 | 327 | * workspaces. |
325 | 328 | * |
326 | 329 | */ |
327 | 330 | bool workspace_is_visible(Con *ws) { |
328 | 331 | Con *output = con_get_output(ws); |
329 | if (output == NULL) | |
332 | if (output == NULL) { | |
330 | 333 | return false; |
334 | } | |
331 | 335 | Con *fs = con_get_fullscreen_con(output, CF_OUTPUT); |
332 | LOG("workspace visible? fs = %p, ws = %p\n", fs, ws); | |
333 | 336 | return (fs == ws); |
334 | 337 | } |
335 | 338 | |
1011 | 1014 | bool used_assignment = false; |
1012 | 1015 | struct Workspace_Assignment *assignment; |
1013 | 1016 | TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) { |
1014 | bool attached; | |
1015 | int num; | |
1016 | 1017 | if (!output_triggers_assignment(current_output, assignment)) { |
1017 | 1018 | continue; |
1018 | 1019 | } |
1019 | 1020 | /* 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); | |
1022 | 1025 | if (attached) { |
1023 | 1026 | continue; |
1024 | 1027 | } |
489 | 489 | struct deco_render_params *p = scalloc(1, sizeof(struct deco_render_params)); |
490 | 490 | |
491 | 491 | /* find out which colors to use */ |
492 | if (con->urgent) | |
492 | if (con->urgent) { | |
493 | 493 | p->color = &config.client.urgent; |
494 | else if (con == focused || con_inside_focused(con)) | |
494 | } else if (con == focused || con_inside_focused(con)) { | |
495 | 495 | 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 { | |
499 | 504 | p->color = &config.client.unfocused; |
505 | } | |
500 | 506 | |
501 | 507 | p->border_style = con_border_style(con); |
502 | 508 | |
536 | 542 | |
537 | 543 | /* 2: draw the client.background, but only for the parts around the window_rect */ |
538 | 544 | 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 | ||
539 | 548 | /* top area */ |
540 | 549 | draw_util_rectangle(&(con->frame_buffer), config.client.background, |
541 | 550 | 0, 0, r->width, w->y); |
608 | 617 | /* 5: draw title border */ |
609 | 618 | x_draw_title_border(con, p); |
610 | 619 | |
611 | /* 6: draw the title */ | |
620 | /* 6: draw the icon and title */ | |
612 | 621 | int text_offset_y = (con->deco_rect.height - config.font.height) / 2; |
613 | 622 | |
623 | struct Window *win = con->window; | |
624 | ||
625 | const int deco_width = (int)con->deco_rect.width; | |
614 | 626 | const int title_padding = logical_px(2); |
615 | const int deco_width = (int)con->deco_rect.width; | |
627 | ||
616 | 628 | int mark_width = 0; |
617 | 629 | if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) { |
618 | 630 | char *formatted_mark = sstrdup(""); |
651 | 663 | } |
652 | 664 | |
653 | 665 | i3String *title = NULL; |
654 | struct Window *win = con->window; | |
655 | 666 | if (win == NULL) { |
656 | 667 | if (con->title_format == NULL) { |
657 | 668 | char *_title; |
671 | 682 | goto copy_pixmaps; |
672 | 683 | } |
673 | 684 | |
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; | |
674 | 696 | int title_offset_x; |
675 | 697 | switch (config.title_align) { |
676 | 698 | 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; | |
679 | 704 | break; |
680 | 705 | 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); | |
689 | 716 | break; |
690 | 717 | 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); | |
693 | 724 | break; |
694 | 725 | } |
695 | 726 | |
697 | 728 | p->color->text, p->color->background, |
698 | 729 | con->deco_rect.x + title_offset_x, |
699 | 730 | 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 | } | |
701 | 741 | |
702 | 742 | if (win == NULL || con->title_format != NULL) { |
703 | 743 | I3STRING_FREE(title); |
840 | 880 | con_state *state; |
841 | 881 | Rect rect = con->rect; |
842 | 882 | |
843 | //DLOG("Pushing changes for node %p / %s\n", con, con->name); | |
844 | 883 | state = state_for_frame(con->frame.id); |
845 | 884 | |
846 | 885 | if (state->name != NULL) { |
953 | 992 | win_depth = con->window->depth; |
954 | 993 | |
955 | 994 | /* 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. */ | |
958 | 998 | int width = MAX((int32_t)rect.width, 1); |
959 | 999 | int height = MAX((int32_t)rect.height, 1); |
960 | 1000 | |
961 | 1001 | xcb_create_pixmap(conn, win_depth, con->frame_buffer.id, con->frame.id, width, height); |
962 | 1002 | draw_util_surface_init(conn, &(con->frame_buffer), con->frame_buffer.id, |
963 | 1003 | 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}); | |
964 | 1005 | |
965 | 1006 | /* For the graphics context, we disable GraphicsExposure events. |
966 | 1007 | * Those will be sent when a CopyArea request cannot be fulfilled |
973 | 1014 | con->pixmap_recreated = true; |
974 | 1015 | |
975 | 1016 | /* 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? */ | |
978 | 1019 | if (!con->parent || |
979 | 1020 | con->parent->layout != L_STACKED || |
980 | 1021 | TAILQ_FIRST(&(con->parent->focus_head)) == con) |
1085 | 1126 | Con *current; |
1086 | 1127 | con_state *state; |
1087 | 1128 | |
1088 | //DLOG("Pushing changes (with unmaps) for node %p / %s\n", con, con->name); | |
1089 | 1129 | state = state_for_frame(con->frame.id); |
1090 | 1130 | |
1091 | 1131 | /* map/unmap if map state changed, also ensure that the child window |
1161 | 1201 | } |
1162 | 1202 | |
1163 | 1203 | DLOG("-- PUSHING WINDOW STACK --\n"); |
1164 | //DLOG("Disabling EnterNotify\n"); | |
1165 | 1204 | /* We need to keep SubstructureRedirect around, otherwise clients can send |
1166 | 1205 | * ConfigureWindow requests and get them applied directly instead of having |
1167 | 1206 | * them become ConfigureRequests that i3 handles. */ |
1170 | 1209 | if (state->mapped) |
1171 | 1210 | xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); |
1172 | 1211 | } |
1173 | //DLOG("Done, EnterNotify disabled\n"); | |
1174 | 1212 | bool order_changed = false; |
1175 | 1213 | bool stacking_changed = false; |
1176 | 1214 | |
1200 | 1238 | if (con_has_managed_window(state->con)) |
1201 | 1239 | memcpy(walk++, &(state->con->window->id), sizeof(xcb_window_t)); |
1202 | 1240 | |
1203 | //DLOG("stack: 0x%08x\n", state->id); | |
1204 | 1241 | con_state *prev = CIRCLEQ_PREV(state, state); |
1205 | 1242 | con_state *old_prev = CIRCLEQ_PREV(state, old_state); |
1206 | 1243 | if (prev != old_prev) |
1207 | 1244 | order_changed = true; |
1208 | 1245 | if ((state->initial || order_changed) && prev != CIRCLEQ_END(&state_head)) { |
1209 | 1246 | stacking_changed = true; |
1210 | //DLOG("Stacking 0x%08x above 0x%08x\n", prev->id, state->id); | |
1211 | 1247 | uint32_t mask = 0; |
1212 | 1248 | mask |= XCB_CONFIG_WINDOW_SIBLING; |
1213 | 1249 | mask |= XCB_CONFIG_WINDOW_STACK_MODE; |
1260 | 1296 | warp_to = NULL; |
1261 | 1297 | } |
1262 | 1298 | |
1263 | //DLOG("Re-enabling EnterNotify\n"); | |
1264 | 1299 | values[0] = FRAME_EVENT_MASK; |
1265 | 1300 | CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) { |
1266 | 1301 | if (state->mapped) |
1267 | 1302 | xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); |
1268 | 1303 | } |
1269 | //DLOG("Done, EnterNotify re-enabled\n"); | |
1270 | 1304 | |
1271 | 1305 | x_deco_recurse(con); |
1272 | 1306 | |
1352 | 1386 | CIRCLEQ_REMOVE(&old_state_head, state, old_state); |
1353 | 1387 | CIRCLEQ_INSERT_TAIL(&old_state_head, state, old_state); |
1354 | 1388 | } |
1355 | //CIRCLEQ_FOREACH(state, &old_state_head, old_state) { | |
1356 | // DLOG("old stack: 0x%08x\n", state->id); | |
1357 | //} | |
1358 | 1389 | |
1359 | 1390 | xcb_flush(conn); |
1360 | 1391 | } |
1367 | 1398 | void x_raise_con(Con *con) { |
1368 | 1399 | con_state *state; |
1369 | 1400 | 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); | |
1371 | 1401 | |
1372 | 1402 | CIRCLEQ_REMOVE(&state_head, state, state); |
1373 | 1403 | CIRCLEQ_INSERT_HEAD(&state_head, state, state); |
1417 | 1447 | xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_PID, XCB_ATOM_CARDINAL, 32, 1, &pid); |
1418 | 1448 | xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8, |
1419 | 1449 | 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); | |
1420 | 1452 | update_shmlog_atom(); |
1421 | 1453 | } |
1422 | 1454 |
432 | 432 | bound = true; |
433 | 433 | /* Let the user know bind() was successful, so that they know the |
434 | 434 | * 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); | |
436 | 436 | sun_path = sstrdup(addr.sun_path); |
437 | 437 | break; |
438 | 438 | } |
41 | 41 | exit_forcefully |
42 | 42 | workspace_exists |
43 | 43 | focused_ws |
44 | focused_output | |
44 | 45 | get_socket_path |
45 | 46 | launch_with_config |
46 | 47 | get_i3_log |
409 | 410 | |
410 | 411 | cmd 'focus output fake-1'; |
411 | 412 | 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'); | |
413 | 414 | |
414 | 415 | =cut |
415 | 416 | sub get_output_for_workspace { |
418 | 419 | my $tree = $i3->get_tree->recv; |
419 | 420 | my @outputs = @{$tree->{nodes}}; |
420 | 421 | |
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}; | |
425 | 428 | } |
426 | 429 | } |
427 | 430 | } |
661 | 664 | (scalar grep { $_ eq $name } @{get_workspace_names()}) > 0; |
662 | 665 | } |
663 | 666 | |
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 { | |
673 | 675 | my $i3 = i3(get_socket_path()); |
674 | 676 | my $tree = $i3->get_tree->recv; |
675 | 677 | my $focused = $tree->{focus}->[0]; |
676 | 678 | 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; | |
677 | 697 | my $content = first { $_->{type} eq 'con' } @{$output->{nodes}}; |
678 | 698 | my $first = first { $_->{fullscreen_mode} == 1 } @{$content->{nodes}}; |
679 | 699 | return $first->{name} |
777 | 797 | my ($pid, $socketpath) = @_; |
778 | 798 | $socketpath ||= get_socket_path(); |
779 | 799 | |
800 | $SIG{CHLD} = undef; | |
801 | ||
780 | 802 | my $exited = 0; |
781 | 803 | eval { |
782 | 804 | say "Exiting i3 cleanly..."; |
814 | 836 | sub exit_forcefully { |
815 | 837 | my ($pid, $signal) = @_; |
816 | 838 | $signal ||= 'TERM'; |
839 | ||
840 | $SIG{CHLD} = undef; | |
817 | 841 | |
818 | 842 | # Send the given signal to the i3 instance and wait for up to 10s |
819 | 843 | # for it to terminate. |
938 | 962 | return ${^CHILD_ERROR_NATIVE}; |
939 | 963 | } |
940 | 964 | |
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 | ||
941 | 977 | # force update of the cached socket path in lib/i3test |
942 | 978 | # as soon as i3 has started |
943 | 979 | $cv->cb(sub { get_socket_path(0) }); |
969 | 1005 | # Sync in case not all windows are managed by i3 just yet. |
970 | 1006 | sync_with_i3; |
971 | 1007 | cmd '[title=".*"] kill'; |
1008 | # Sync to make sure x_window_kill() calls have taken effect. | |
1009 | sync_with_i3; | |
972 | 1010 | } |
973 | 1011 | |
974 | 1012 | =head2 events_for($subscribecb, [ $rettype ], [ $eventcbs ]) |
72 | 72 | workspace_layout => 'default', |
73 | 73 | current_border_width => -1, |
74 | 74 | marks => $ignore, |
75 | window_icon_padding => -1, | |
75 | 76 | }; |
76 | 77 | |
77 | 78 | # a shallow copy is sufficient, since we only ignore values at the root |
13 | 13 | # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf |
14 | 14 | # (unless you are already familiar with Perl) |
15 | 15 | # |
16 | # Tests whether we can switch to a non-existant workspace | |
16 | # Tests whether we can switch to a non-existent workspace | |
17 | 17 | # (necessary for further tests) |
18 | 18 | # |
19 | 19 | use List::Util qw(first); |
266 | 266 | like($output->[3], qr|^bindsym Mod1\+f resize grow down 23 px$|, 'resize bottom changed'); |
267 | 267 | |
268 | 268 | ##################################################################### |
269 | # also resizing, but with indention this time | |
269 | # also resizing, but with indentation this time | |
270 | 270 | ##################################################################### |
271 | 271 | |
272 | 272 | like($output->[4], qr|^bindsym Mod1\+f resize grow left 10 px$|, 'resize left changed'); |
53 | 53 | 'move workspace foobar; ' . |
54 | 54 | 'move workspace torrent; ' . |
55 | 55 | 'move workspace to output LVDS1; ' . |
56 | 'move to output LVDS1 DVI1; ' . | |
56 | 57 | 'move workspace 3: foobar; ' . |
57 | 58 | 'move workspace "3: foobar"; ' . |
58 | 59 | 'move workspace "3: foobar, baz"; '), |
61 | 62 | "cmd_move_con_to_workspace_name(3, (null))\n" . |
62 | 63 | "cmd_move_con_to_workspace_name(foobar, (null))\n" . |
63 | 64 | "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" . | |
65 | 70 | "cmd_move_con_to_workspace_name(3: foobar, (null))\n" . |
66 | 71 | "cmd_move_con_to_workspace_name(3: foobar, (null))\n" . |
67 | 72 | "cmd_move_con_to_workspace_name(3: foobar, baz, (null))", |
170 | 175 | scratchpad |
171 | 176 | swap |
172 | 177 | title_format |
178 | title_window_icon | |
173 | 179 | mode |
174 | 180 | bar |
175 | 181 | gaps |
475 | 475 | ################################################################################ |
476 | 476 | |
477 | 477 | $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 | |
483 | 484 | EOT |
484 | 485 | |
485 | 486 | $expected = <<'EOT'; |
486 | 487 | cfg_color(client.focused, #4c7899, #285577, #ffffff, #2e9ef4, #b34d4c) |
487 | 488 | cfg_color(client.focused_inactive, #333333, #5f676a, #ffffff, #484e50, NULL) |
489 | cfg_color(client.focused_tab_title, #444444, #555555, #ffffff, NULL, NULL) | |
488 | 490 | cfg_color(client.unfocused, #333333, #222222, #888888, #292d2e, NULL) |
489 | 491 | cfg_color(client.urgent, #2f343a, #900000, #ffffff, #900000, #c00000) |
490 | 492 | cfg_color(client.placeholder, #000000, #0c0c0c, #ffffff, #000000, NULL) |
505 | 507 | |
506 | 508 | my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '#', '" . join("', '", 'set ', 'set ', qw( |
507 | 509 | set_from_resource |
510 | include | |
508 | 511 | bindsym |
509 | 512 | bindcode |
510 | 513 | bind |
548 | 551 | ipc_kill_timeout |
549 | 552 | restart_state |
550 | 553 | popup_during_fullscreen |
554 | tiling_drag | |
551 | 555 | exec_always |
552 | 556 | exec |
553 | 557 | client.background |
554 | 558 | client.focused_inactive |
559 | client.focused_tab_title | |
555 | 560 | client.focused |
556 | 561 | client.unfocused |
557 | 562 | client.urgent |
70 | 70 | cmd 'mark left'; |
71 | 71 | |
72 | 72 | # |
73 | # get_marks replys an array of marks, whose order is undefined, | |
73 | # get_marks replies an array of marks, whose order is undefined, | |
74 | 74 | # so we use sort to be able to compare the output |
75 | 75 | # |
76 | 76 |
17 | 17 | # i3 config file (v4) |
18 | 18 | font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 |
19 | 19 | |
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 | |
21 | 21 | fake-outputs 1024x768+0+0,1024x768+0+1024 |
22 | 22 | workspace X output fake-1 |
23 | 23 | EOT |
55 | 55 | my $focus = AnyEvent->condvar; |
56 | 56 | |
57 | 57 | 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 | }, | |
59 | 63 | 'window'); |
60 | 64 | |
61 | 65 | is(scalar @events, 1, 'Received 1 event'); |
61 | 61 | font \ |
62 | 62 | -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 |
63 | 63 | |
64 | # Use line contiuation with too many lines (>4096 characters). | |
64 | # Use line continuation with too many lines (>4096 characters). | |
65 | 65 | # This config is invalid. Use it to ensure no buffer overflow. |
66 | 66 | bindsym Mod1+b \ |
67 | 67 | 0001-This is a very very very very very very very very very very very very very very very very very long cmd \ |
40 | 40 | is_num_children($ws_bottom_left, 0, 'no containers on the lower left workspace'); |
41 | 41 | is_num_children($ws_bottom_right, 1, 'one container on the lower right workspace'); |
42 | 42 | |
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 | ||
43 | 51 | done_testing; |
20 | 20 | # |
21 | 21 | # Ticket: #2318 |
22 | 22 | # Bug still in: 4.12-46-g2123888 |
23 | use i3test; | |
23 | use i3test i3_autostart => 0; | |
24 | ||
25 | my $pid = launch_with_config('-default'); | |
24 | 26 | |
25 | 27 | # We cannot use events_for in this test as we cannot send events after |
26 | 28 | # issuing the restart/shutdown command. |
58 | 60 | } |
59 | 61 | })->recv; |
60 | 62 | |
61 | cmd 'exit'; | |
63 | exit_gracefully($pid); | |
62 | 64 | |
63 | 65 | $e = $cv->recv; |
64 | 66 |
13 | 13 | # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf |
14 | 14 | # (unless you are already familiar with Perl) |
15 | 15 | # |
16 | # Verify that the corrent focus stack order is preserved after various | |
16 | # Verify that the current focus stack order is preserved after various | |
17 | 17 | # operations. |
18 | 18 | use i3test i3_config => <<EOT; |
19 | 19 | # i3 config file (v4) |
42 | 42 | * | C | |
43 | 43 | * +-------+ |
44 | 44 | * |
45 | * - Zone A is completly opaque. | |
45 | * - Zone A is completely opaque. | |
46 | 46 | * - Zone B is clickable through (input shape). |
47 | * - Zone C is completly transparent (bounding shape). | |
47 | * - Zone C is completely transparent (bounding shape). | |
48 | 48 | */ |
49 | 49 | void set_shape(long window_id) { |
50 | 50 | xcb_rectangle_t bounding_rectangle = { 0, 0, 100, 50 }; |
21 | 21 | # i3 config file (v4) |
22 | 22 | font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 |
23 | 23 | |
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 | |
25 | 25 | fake-outputs 1024x768+0+0,1024x768+0+1024 |
26 | 26 | workspace X output fake-1 |
27 | 27 | 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; |
29 | 29 | ################################################################################ |
30 | 30 | # use 'focus output' and verify that focus gets changed appropriately |
31 | 31 | ################################################################################ |
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 | } | |
39 | 32 | |
40 | 33 | sync_with_i3; |
41 | 34 | $x->root->warp_pointer(0, 0); |
148 | 148 | # Ensure that focusing right/left works in the expected order. |
149 | 149 | ############################################################################ |
150 | 150 | |
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'); | |
159 | 152 | |
160 | 153 | 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'); | |
162 | 155 | |
163 | 156 | 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'); | |
165 | 158 | |
166 | 159 | 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'); | |
168 | 161 | |
169 | 162 | 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'); | |
171 | 164 | |
172 | 165 | 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)'); | |
174 | 167 | |
175 | 168 | 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)'); | |
177 | 170 | |
178 | 171 | exit_gracefully($pid); |
179 | 172 |
54 | 54 | |
55 | 55 | # Check that the windows are on the correct output |
56 | 56 | 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"); | |
58 | 58 | |
59 | 59 | # Check that both windows remained fullscreen |
60 | 60 | 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 | { | |
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 | { | |
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 | #!/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) |
10 | 10 | use v5.10; |
11 | 11 | use autodie; |
12 | 12 | use lib 'testcases/lib'; |
13 | use lib '/usr/share/lintian/lib'; | |
13 | 14 | use i3test::Util qw(slurp); |
14 | 15 | use Lintian::Spelling qw(check_spelling); |
15 | 16 | |
20 | 21 | my $profile = Lintian::Profile->new; |
21 | 22 | $profile->load('debian', ['/usr/share/lintian']); |
22 | 23 | |
23 | Lintian::Data->set_vendor($profile); | |
24 | ||
25 | 24 | my $exitcode = 0; |
26 | 25 | |
27 | 26 | # Whitelist for spelling errors in manpages, in case the spell checker has |
28 | 27 | # 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 | ]; | |
33 | 34 | my @binaries = qw( |
34 | 35 | build/i3 |
35 | 36 | build/i3-config-wizard |
40 | 41 | build/i3bar |
41 | 42 | ); |
42 | 43 | for my $binary (@binaries) { |
43 | check_spelling(slurp($binary), $binary_spelling_exceptions, sub { | |
44 | check_spelling($profile->data, slurp($binary), $binary_spelling_exceptions, sub { | |
44 | 45 | my ($current, $fixed) = @_; |
45 | 46 | say STDERR qq|Binary "$binary" contains a spelling error: "$current" should be "$fixed"|; |
46 | 47 | $exitcode = 1; |
49 | 50 | |
50 | 51 | # Whitelist for spelling errors in manpages, in case the spell checker has |
51 | 52 | # false-positives. |
52 | my $manpage_spelling_exceptions = { | |
53 | }; | |
53 | my $manpage_spelling_exceptions = [ | |
54 | ]; | |
54 | 55 | |
55 | 56 | for my $name (glob('build/man/*.1')) { |
56 | 57 | for my $line (split(/\n/, slurp($name))) { |
57 | 58 | next if $line =~ /^\.\\\"/o; |
58 | check_spelling($line, $manpage_spelling_exceptions, sub { | |
59 | check_spelling($profile->data, $line, $manpage_spelling_exceptions, sub { | |
59 | 60 | my ($current, $fixed) = @_; |
60 | 61 | say STDERR qq|Manpage "$name" contains a spelling error: "$current" should be "$fixed"|; |
61 | 62 | $exitcode = 1; |
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 | } |
8 | 8 | cp -r deb/COPY-DOCS build.i3wm.org/docs |
9 | 9 | cd build.i3wm.org |
10 | 10 | 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 | |
11 | 15 | git init |
12 | 16 | |
13 | 17 | git config user.name "Travis CI" |
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 |
1 | 1 | # Returns true if Debian/Ubuntu packages should be skipped because this CI run |
2 | 2 | # was triggered by a pull request. |
3 | 3 | |
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), | |
5 | 5 | # otherwise the CI run was triggered by a pull request. |
6 | 6 | # Verify CC=gcc so that we only build packages once for each commit, |
7 | 7 | # not twice (with gcc and clang). |
8 | if [ ! -z "$BINTRAY_USER" ] && [ "$CC" = "gcc" ] | |
8 | if [ ! -z "$BALTO_TOKEN" ] && [ "$CC" = "gcc" ] | |
9 | 9 | then |
10 | 10 | exit 1 |
11 | 11 | fi |
12 | 12 | # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. |
13 | 13 | |
14 | 14 | # Install mk-build-deps (for installing the i3 build dependencies), |
15 | # clang and clang-format-9 (for checking formatting and building with clang), | |
16 | 15 | # lintian (for checking spelling errors), |
17 | 16 | RUN linux32 apt-get update && \ |
18 | 17 | DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ |
19 | 18 | dpkg-dev devscripts git equivs \ |
20 | build-essential clang clang-format-9 \ | |
19 | build-essential clang \ | |
21 | 20 | lintian && \ |
22 | 21 | rm -rf /var/lib/apt/lists/* |
23 | 22 | |
26 | 25 | RUN linux32 apt-get update && \ |
27 | 26 | DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \ |
28 | 27 | 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 |
12 | 12 | # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. |
13 | 13 | |
14 | 14 | # Install mk-build-deps (for installing the i3 build dependencies), |
15 | # clang and clang-format-9 (for checking formatting and building with clang), | |
16 | 15 | # lintian (for checking spelling errors), |
17 | 16 | RUN linux32 apt-get update && \ |
18 | 17 | DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ |
19 | 18 | dpkg-dev devscripts git equivs \ |
20 | build-essential clang clang-format-9 \ | |
19 | build-essential clang \ | |
21 | 20 | lintian && \ |
22 | 21 | rm -rf /var/lib/apt/lists/* |
23 | 22 | |
26 | 25 | RUN linux32 apt-get update && \ |
27 | 26 | DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \ |
28 | 27 | 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 |
12 | 12 | # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. |
13 | 13 | |
14 | 14 | # Install mk-build-deps (for installing the i3 build dependencies), |
15 | # clang and clang-format-9 (for checking formatting and building with clang), | |
16 | 15 | # lintian (for checking spelling errors), |
17 | 16 | # test suite dependencies (for running tests) |
18 | 17 | RUN apt-get update && \ |
19 | 18 | DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ |
20 | 19 | dpkg-dev devscripts git equivs \ |
21 | build-essential clang clang-format-9 \ | |
20 | build-essential clang \ | |
22 | 21 | lintian && \ |
23 | 22 | rm -rf /var/lib/apt/lists/* |
24 | 23 | |
27 | 26 | RUN apt-get update && \ |
28 | 27 | DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \ |
29 | 28 | 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 |
10 | 10 | # (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. |
11 | 11 | |
12 | 12 | # Install mk-build-deps (for installing the i3 build dependencies), |
13 | # clang and clang-format-9 (for checking formatting and building with clang), | |
14 | 13 | # lintian (for checking spelling errors), |
15 | 14 | # test suite dependencies (for running tests) |
16 | 15 | RUN apt-get update && \ |
17 | 16 | DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ |
18 | 17 | dpkg-dev devscripts git equivs \ |
19 | build-essential clang clang-format-9 \ | |
18 | build-essential clang \ | |
20 | 19 | lintian \ |
21 | 20 | 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 && \ |
22 | 21 | rm -rf /var/lib/apt/lists/* |
27 | 26 | RUN apt-get update && \ |
28 | 27 | DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \ |
29 | 28 | 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 |