Merge branch 'next' into stable
This commit is contained in:
commit
533b76378a
16
.github/CONTRIBUTING.md
vendored
16
.github/CONTRIBUTING.md
vendored
@ -4,7 +4,7 @@
|
||||
|
||||
Note that bug reports and feature requests for related projects should be filed in the corresponding repositories for [i3status](https://github.com/i3/i3status) and [i3lock](https://github.com/i3/i3lock).
|
||||
|
||||
## i3 bug reports and feature requests
|
||||
## i3 bug reports
|
||||
|
||||
1. Read the [debugging instructions](https://i3wm.org/docs/debugging.html).
|
||||
2. Make sure you include a link to your logfile in your report (section 3).
|
||||
@ -18,6 +18,20 @@ Note that bug reports and feature requests for related projects should be filed
|
||||
encountered the issue you are about to report while using a compositor,
|
||||
please try reproducing it without a compositor.
|
||||
|
||||
## i3 feature requests
|
||||
|
||||
1. Read the [project goals](https://i3wm.org) on the website and make sure that
|
||||
they are compatible with the feature you want to suggest.
|
||||
2. We are generally happy with the current feature set of i3 and instead focus
|
||||
on maintenance such as stability and fixing bugs. New features will rarely
|
||||
be considered if they require additional configuration and/or commands, or
|
||||
if they add significant complexity (either through the exposed configuration
|
||||
or mental complexity) to the project.
|
||||
3. Explain in detail what problem the feature addresses and why existing
|
||||
features fall short.
|
||||
4. Consider whether the feature could instead be implemented using the
|
||||
[IPC](https://i3wm.org/docs/ipc.html) or other external tooling.
|
||||
|
||||
## Pull requests
|
||||
|
||||
* Before sending a pull request for new features, please check with us that the
|
||||
|
7
.github/ISSUE_TEMPLATE.md
vendored
7
.github/ISSUE_TEMPLATE.md
vendored
@ -3,7 +3,12 @@ PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATI
|
||||
-->
|
||||
|
||||
## I'm submitting a…
|
||||
<!-- Please check one of the following options with "x" -->
|
||||
<!--
|
||||
Check one of the following options with "x".
|
||||
|
||||
Please note that at this point we focus on maintaining i3 and fixing bugs, and will rarely consider features which require further configuration or significant complexity.
|
||||
In such cases you should consider and present specific benefits derived from adding this feature such that it can be weighed against the cost of additional complexity and maintenance.
|
||||
-->
|
||||
<pre>
|
||||
[ ] Bug
|
||||
[ ] Feature Request
|
||||
|
4
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
4
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
contact_links:
|
||||
- name: Ask a question or request support for using i3
|
||||
url: https://github.com/i3/i3/discussions/new
|
||||
about: Ask a question or request support for using i3
|
11
.github/ISSUE_TEMPLATE/feature_request.md
vendored
11
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -8,7 +8,7 @@ PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATI
|
||||
-->
|
||||
|
||||
## I'm submitting a…
|
||||
<!-- Please check one of the following options with "x" -->
|
||||
<!-- Check one of the following options with "x" -->
|
||||
<pre>
|
||||
[ ] Bug
|
||||
[x] Feature Request
|
||||
@ -28,6 +28,15 @@ Describe the desired behavior you expect after mitigation of the issue,
|
||||
e.g., »The window left next to the current window should be focused.«
|
||||
-->
|
||||
|
||||
## Impact
|
||||
<!--
|
||||
Please note that at this point we focus on maintaining i3 and fixing bugs, and will rarely consider features which require further configuration or significant complexity.
|
||||
In such cases you should consider and present specific benefits derived from adding this feature such that it can be weighed against the cost of additional complexity and maintenance.
|
||||
-->
|
||||
<pre>
|
||||
[ ] This feature requires new configuration and/or commands
|
||||
</pre>
|
||||
|
||||
## Environment
|
||||
<!--
|
||||
Please include your exact i3 version.
|
||||
|
94
.github/workflows/main.yml
vendored
Normal file
94
.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
name: GitHub Actions
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ next, master, actions ]
|
||||
pull_request:
|
||||
branches: [ next ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: build and test
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [gcc, clang]
|
||||
env:
|
||||
CC: ${{ matrix.compiler }}
|
||||
DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
|
||||
DOCKER_EMAIL: ${{ secrets.DOCKER_EMAIL }}
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
BALTO_TOKEN: ${{ secrets.BALTO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: git fetch --prune --unshallow
|
||||
- name: construct container name
|
||||
run: |
|
||||
echo "BASENAME=i3wm/travis-base:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base.Dockerfile)" >> $GITHUB_ENV
|
||||
echo "BASENAME_386=i3wm/travis-base-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-386.Dockerfile)" >> $GITHUB_ENV
|
||||
echo "BASENAME_UBUNTU=i3wm/travis-base-ubuntu:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu.Dockerfile)" >> $GITHUB_ENV
|
||||
echo "BASENAME_UBUNTU_386=i3wm/travis-base-ubuntu-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu-386.Dockerfile)" >> $GITHUB_ENV
|
||||
- name: fetch or build Docker container
|
||||
run: |
|
||||
docker pull ${{ env.BASENAME }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME }} travis/travis-base.Dockerfile
|
||||
- name: fetch or build extra Docker containers
|
||||
run: |
|
||||
echo "::group::Ubuntu amd64"
|
||||
./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_UBUNTU }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU }} travis/travis-base-ubuntu.Dockerfile
|
||||
echo "::endgroup::"
|
||||
echo "::group::Debian i386"
|
||||
./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_386 }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_386 }} travis/travis-base-386.Dockerfile
|
||||
echo "::endgroup::"
|
||||
echo "::group::Ubuntu i386"
|
||||
./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_UBUNTU_386 }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU_386 }} travis/travis-base-ubuntu-386.Dockerfile
|
||||
echo "::endgroup::"
|
||||
- name: verify safe wrapper functions are used
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-safe-wrappers.sh
|
||||
- name: verify code formatting
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-formatting.sh
|
||||
- name: build i3
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf build; mkdir -p build && cd build && CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror -fno-common" meson .. -Ddocs=true -Dmans=true -Db_sanitize=address && ninja -v'
|
||||
- name: check spelling
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-spelling.pl
|
||||
- name: run i3 tests
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} ./travis/run-tests.sh
|
||||
- name: Archive test logs
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: test-logs
|
||||
path: build/testsuite-*
|
||||
if: ${{ failure() }}
|
||||
- name: build dist tarball
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf distbuild; mkdir distbuild && cd distbuild && meson .. -Ddocs=true -Dmans=true && ninja -v dist'
|
||||
- name: build Debian packages
|
||||
run: |
|
||||
echo "::group::Debian amd64"
|
||||
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/debian-build.sh deb/debian-amd64/DIST
|
||||
echo "::endgroup::"
|
||||
echo "::group::Ubuntu amd64"
|
||||
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_UBUNTU }} ./travis/debian-build.sh deb/ubuntu-amd64/DIST
|
||||
echo "::endgroup::"
|
||||
echo "::group::Debian i386"
|
||||
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_386 }} linux32 ./travis/debian-build.sh deb/debian-i386/DIST
|
||||
echo "::endgroup::"
|
||||
echo "::group::Ubuntu i386"
|
||||
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_UBUNTU_386 }} linux32 ./travis/debian-build.sh deb/ubuntu-i386/DIST
|
||||
echo "::endgroup::"
|
||||
- name: push Debian packages to balto
|
||||
run: |
|
||||
./travis/skip-pkg.sh || travis/push-balto.sh
|
||||
- name: build docs
|
||||
run: |
|
||||
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/docs.sh
|
||||
- name: push docs to GitHub pages
|
||||
run: |
|
||||
./travis/skip-pkg.sh || travis/deploy-github-pages.sh
|
71
.travis.yml
71
.travis.yml
@ -1,71 +0,0 @@
|
||||
dist: trusty
|
||||
services:
|
||||
- docker
|
||||
language: c
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
# For https support in HTTP::Tiny.
|
||||
- libio-socket-ssl-perl
|
||||
env:
|
||||
global:
|
||||
- BASENAME="i3wm/travis-base:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base.Dockerfile)"
|
||||
- BASENAME_386="i3wm/travis-base-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-386.Dockerfile)"
|
||||
- BASENAME_UBUNTU="i3wm/travis-base-ubuntu:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu.Dockerfile)"
|
||||
- BASENAME_UBUNTU_386="i3wm/travis-base-ubuntu-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu-386.Dockerfile)"
|
||||
- secure: "B5IICA8MPx/FKaB50rTPqL8P1NU+Q0yuWl+lElL4+a9xSyLikfm3NzUPHoVwx8lNw2AVK6br7p0OmF7vMFjqAgrgc1cajTtEae5uFRKNUrWLpXM046YgNEYLLIHsQOjInxE+S4O6EFVzsUqsu8aeo2Xhq4sm4iUocG7e5isYgYo=" # DOCKER_PASS
|
||||
- secure: "EIvrq8PG7lRjidppG0RCv4F0X4GP3DT9F5+ixVuGPfhK/hZT3jYC2AVY9G+NnUcXVwQEpW92rlqpftQ/qZ13FoyWokC8ZyoyD06fr5FPCfoFF3OczZwAJzZYkObI/hE9+/hXcylx/Os6N4INd2My1ntGk3JPsWL9riopod5EjSg=" # DOCKER_EMAIL
|
||||
- secure: "hvhBunS4xXTgnIOsk/BPT7I7FrJhvVwCSt5PfxxvMqNaztOJI9BuK7ZrZ5Cy38KyHwlh3VHAH5AaCygJcPauoSQCV3bpnlbaWn3ruq2F0Q697Q5uNf73liXzyUqb9/Zvfvge4y4WWOhP5tVz1C6ZBe/NfhU7pqKLMA+6ads+99c=" # DOCKER_USER
|
||||
- secure: "uJuuefmnJUuEH15ZD8xQilibx7EeBvMHBLoIZ8bgGHeleEImBD0XbD1ypvhYJKpviOmw5BkZmc9bVO8DGDEHYbSlIa2xDlF6vGrwgCEaxcMIhOAhv+dW9C/maJVieLOEPM01/fK2qdKESZaLvlopkWmxZwDyMObI9L7AMW9zQD8=" # BINTRAY_USER
|
||||
- secure: "L3aPSNLySPXtWCW+xf8h/AAdquwNgxyTQpYOwexJmTPav82Qx8uQlp1yJkUmt+a+FLZDFfQeMivaHq0311RvuQVmkAJx49DjaddrwqOJut2UPsoVDn1WeuAcSHIXOq/0H+zgFMr/PGY0HXIsw1mTMhgheGJNqg09BvYWROCEAcA=" # BINTRAY_KEY
|
||||
- secure: "sBMVn4C/WRWgoAytEFGx4CC5O55Q63h02AcuBnb1jXcBm0RenoBpzUPtxSseJwDPUA1o/UkuEDDjm3PosT5NF+dvED01VDFMsPVE11K0u6+avYy3jYXqyUEDW3G2o6Wo/2aqNjmd++8jskBdS9+Cx9gaFbgxfzSp0Yfu3oJm/4c=" # GH_TOKEN
|
||||
install:
|
||||
- if [ -a .git/shallow ]; then git fetch --unshallow; fi
|
||||
- docker pull ${BASENAME} || ./travis/docker-build-and-push.sh ${BASENAME} travis/travis-base.Dockerfile
|
||||
- ./travis/skip-pkg.sh || docker pull ${BASENAME_UBUNTU} || ./travis/docker-build-and-push.sh ${BASENAME_UBUNTU} travis/travis-base-ubuntu.Dockerfile
|
||||
- ./travis/skip-pkg.sh || docker pull ${BASENAME_386} || ./travis/docker-build-and-push.sh ${BASENAME_386} travis/travis-base-386.Dockerfile
|
||||
- ./travis/skip-pkg.sh || docker pull ${BASENAME_UBUNTU_386} || ./travis/docker-build-and-push.sh ${BASENAME_UBUNTU_386} travis/travis-base-ubuntu-386.Dockerfile
|
||||
script:
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-safe-wrappers.sh
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-formatting.sh
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} /bin/sh -c 'rm -rf build; mkdir -p build && cd build && CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror -fno-common" meson .. -Ddocs=true -Dmans=true -Db_sanitize=address && ninja -v'
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-spelling.pl
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} ./travis/run-tests.sh
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} /bin/sh -c 'rm -rf distbuild; mkdir distbuild && cd distbuild && meson .. -Ddocs=true -Dmans=true && ninja -v dist'
|
||||
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/debian-build.sh deb/debian-amd64/DIST
|
||||
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_UBUNTU} ./travis/debian-build.sh deb/ubuntu-amd64/DIST
|
||||
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_386} linux32 ./travis/debian-build.sh deb/debian-i386/DIST
|
||||
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_UBUNTU_386} linux32 ./travis/debian-build.sh deb/ubuntu-i386/DIST
|
||||
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/docs.sh
|
||||
- ./travis/skip-pkg.sh || travis/prep-bintray.sh
|
||||
|
||||
deploy:
|
||||
- provider: bintray
|
||||
file: travis/bintray-autobuild-debian.json
|
||||
user: $BINTRAY_USER
|
||||
key: $BINTRAY_KEY
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch: next
|
||||
condition: $CC = gcc
|
||||
- provider: bintray
|
||||
file: travis/bintray-autobuild-ubuntu.json
|
||||
user: $BINTRAY_USER
|
||||
key: $BINTRAY_KEY
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch: next
|
||||
condition: $CC = gcc
|
||||
- provider: script
|
||||
script: travis/deploy-github-pages.sh
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch: next
|
||||
condition: $CC = gcc
|
||||
|
||||
after_deploy:
|
||||
- travis/cleanup-bintray.pl i3-autobuild
|
||||
- travis/cleanup-bintray.pl i3-autobuild-ubuntu
|
@ -1,7 +1,7 @@
|
||||
 i3: A tiling window manager
|
||||
=====================================================
|
||||
|
||||
[](https://travis-ci.org/i3/i3)
|
||||
[](https://github.com/i3/i3/actions/workflows/main.yml)
|
||||
[](https://github.com/i3/i3/issues)
|
||||
[](https://github.com/i3/i3/pulls)
|
||||
|
||||
|
@ -1,25 +0,0 @@
|
||||
|
||||
┌──────────────────────────────┐
|
||||
│ Release notes for i3 v4.19.2 │
|
||||
└──────────────────────────────┘
|
||||
|
||||
This is i3 v4.19. This version is considered stable. All users of i3 are
|
||||
strongly encouraged to upgrade.
|
||||
|
||||
This is a bugfix release for v4.19
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Bugfixes │
|
||||
└────────────────────────────┘
|
||||
|
||||
• fix release tarball version number and debug log settings
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Thanks! │
|
||||
└────────────────────────────┘
|
||||
|
||||
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
|
||||
|
||||
Stuart Henderson
|
||||
|
||||
-- Michael Stapelberg, 2021-02-27
|
93
RELEASE-NOTES-4.20
Normal file
93
RELEASE-NOTES-4.20
Normal file
@ -0,0 +1,93 @@
|
||||
|
||||
┌──────────────────────────────┐
|
||||
│ Release notes for i3 v4.20 │
|
||||
└──────────────────────────────┘
|
||||
|
||||
This is i3 v4.20. This version is considered stable. All users of i3 are
|
||||
strongly encouraged to upgrade.
|
||||
|
||||
Two long-awaited features have been added:
|
||||
|
||||
1. You can now use an “include” directive in your i3 config:
|
||||
https://i3wm.org/docs/userguide.html#include
|
||||
|
||||
2. You can now enable showing window icons in window titlebars:
|
||||
https://i3wm.org/docs/userguide.html#title_window_icon
|
||||
|
||||
In case you notice any issues regarding your background/wallpaper, note:
|
||||
|
||||
Some login managers (e.g. gdm) start the X11 server with the -background none
|
||||
flag. When this flag is set, a background needs to be explicitly set later in
|
||||
the X11 session, otherwise stale copies of closed windows remain visible on
|
||||
the X11 root window (symptom looks like “my terminal window is not closing”).
|
||||
|
||||
i3 works around this situation by setting a screenshot as background when
|
||||
starting. Any background you set before starting i3 (e.g. in your Xsession) or
|
||||
after starting i3 (e.g. via exec statements in the i3 config) will be visible.
|
||||
|
||||
A downside of this workaround is that if you have any windows already open in
|
||||
your X11 session, those will be part of the screenshot.
|
||||
|
||||
To fix this issue, starting in v4.20, i3 detects whether the -background none
|
||||
option is enabled and only then sets a screenshot as background.
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Changes in i3 v4.20 │
|
||||
└────────────────────────────┘
|
||||
|
||||
• default config: use dex for XDG autostart
|
||||
• docs/ipc: document scratchpad_state
|
||||
• ipc: the GET_CONFIG request now returns all included files and their details
|
||||
• i3-nagbar: position on focused monitor by default
|
||||
• i3-nagbar: add option to position on primary monitor
|
||||
• i3bar: use first bar config by default
|
||||
• i3-dmenu-desktop: ignore duplicate files and directories (fixes crash on NixOS)
|
||||
• i3-dump-log -f now uses UNIX sockets instead of pthreads. The UNIX socket approach
|
||||
should be more reliable and also more portable.
|
||||
• When clicking on a tab, focus its child (like when scrolling), or (if
|
||||
already focused), focus the tab container (alternatingly).
|
||||
• Implement the include config directive:
|
||||
https://i3wm.org/docs/userguide.html#include
|
||||
• Implement optionally showing window icons in titlebar:
|
||||
https://i3wm.org/docs/userguide.html#title_window_icon
|
||||
• Allow for_window to match against WM_CLIENT_MACHINE
|
||||
• Add %machine placeholder (WM_CLIENT_MACHINE) to title_format
|
||||
• 'move container|workspace to output': toggle a workspace (or container)
|
||||
between multiple outputs when multiple output names specified.
|
||||
• Add 'move container|workspace to output next'
|
||||
• Add 'all' window matching criterion
|
||||
• Acquire the WM_Sn selection when starting as required by ICCCM
|
||||
• Add --replace command line argument to replace an existing WM
|
||||
• Notify systemd when i3 is ready, allowing other services in a systemd user session
|
||||
to use i3 as a dependency
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Bugfixes │
|
||||
└────────────────────────────┘
|
||||
|
||||
• i3bar: properly close file descriptors
|
||||
• i3bar: properly restart status command after config change
|
||||
• i3bar: exit with 1 when a wrong command line argument is used
|
||||
• ipc: return proper signed int for container positions: negative values were
|
||||
returned as large 32 bits integers
|
||||
• when initializing new outputs, avoid duplicating workspace numbers
|
||||
• fix workspaces not moving to assigned output after output becomes available
|
||||
• fix duplicate bindcode after i3-config-wizard
|
||||
• fix commented-out rofi call in default i3 config
|
||||
• clear pixmap before drawing to prevent visual garbage
|
||||
• fix crash with "layout default"
|
||||
• send an "output" event on XRandR 1.5 monitor configuration change
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Thanks! │
|
||||
└────────────────────────────┘
|
||||
|
||||
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
|
||||
|
||||
ajakk, Albert Safin, Anaël Beutot, Antoine Martin, Dmitri Goutnik, ekarpp,
|
||||
Imran Virani, Ingo Bürk, Isaac Garzon, Ivan Milov, Jay Khandkar, j-jzk, Ken
|
||||
Gilmer, Kjetil Torgrim Homme, lbonn, Michael Stapelberg, Orestis Floros, Ralph
|
||||
Gutkowski, Romuald Brunet, tomty89, Tristan Giles, Tudor Brindus, Uli
|
||||
Schlachter, Vincent Bernat, Vladimir Panteleev
|
||||
|
||||
-- Michael Stapelberg, 2021-10-19
|
6
debian/changelog
vendored
6
debian/changelog
vendored
@ -1,3 +1,9 @@
|
||||
i3-wm (4.19.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Sat, 27 Feb 2021 10:32:17 +0100
|
||||
|
||||
i3-wm (4.19-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
@ -400,8 +400,8 @@ the correct state.
|
||||
Then, it looks through all bindings and gets the one which matches the received
|
||||
event.
|
||||
|
||||
The bound command is parsed by the cmdparse lexer/parser, see +parse_cmd+ in
|
||||
+src/cmdparse.y+.
|
||||
The bound command is parsed by the i3 parser, see +parse_command+ in
|
||||
+src/commands_parser.c+.
|
||||
|
||||
== Manage windows (src/main.c, manage_window() and reparent_window())
|
||||
|
||||
|
@ -83,7 +83,7 @@ $parser->html_header_after_title(
|
||||
<ul id="nav">
|
||||
<li><a style="border-bottom: 2px solid #fff" href="/docs">Docs</a></li>
|
||||
<li><a href="/screenshots">Screens</a></li>
|
||||
<li><a href="https://www.reddit.com/r/i3wm/">FAQ</a></li>
|
||||
<li><a href="https://www.github.com/i3/i3/discussions">Get Help</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
<li><a href="https://bugs.i3wm.org/">Bugs</a></li>
|
||||
</ul>
|
||||
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 20 KiB |
BIN
docs/i3-sync.dia
BIN
docs/i3-sync.dia
Binary file not shown.
BIN
docs/i3-sync.png
BIN
docs/i3-sync.png
Binary file not shown.
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 13 KiB |
@ -191,7 +191,7 @@ separator_block_width::
|
||||
is 9 pixels), since the separator line is drawn in the middle.
|
||||
markup::
|
||||
A string that indicates how the text of the block should be parsed. Set to
|
||||
+"pango"+ to use https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup].
|
||||
+"pango"+ to use https://developer.gnome.org/pango/1.46/[Pango markup].
|
||||
Set to +"none"+ to not use any markup (default). Pango markup only works
|
||||
if you use a pango font.
|
||||
|
||||
|
235
docs/ipc
235
docs/ipc
@ -14,7 +14,8 @@ most languages. In the default configuration file, the ipc-socket gets created
|
||||
in +/tmp/i3-%u.XXXXXX/ipc-socket.%p+ where +%u+ is your UNIX username, +%p+ is
|
||||
the PID of i3 and XXXXXX is a string of random characters from the portable
|
||||
filename character set (see mkdtemp(3)). You can get the socketpath from i3 by
|
||||
calling +i3 --get-socketpath+.
|
||||
executing +i3 --get-socketpath+, which will print the path to the standard
|
||||
output (plus a newline).
|
||||
|
||||
All i3 utilities, like +i3-msg+ and +i3-input+ will read the +I3_SOCKET_PATH+
|
||||
X11 property, stored on the X11 root window.
|
||||
@ -39,12 +40,12 @@ my $sock = IO::Socket::UNIX->new(Peer => $path);
|
||||
|
||||
== Sending messages to i3
|
||||
|
||||
To send a message to i3, you have to format in the binary message format which
|
||||
i3 expects. This format specifies a magic string in the beginning to ensure
|
||||
the integrity of messages (to prevent follow-up errors). Following the magic
|
||||
string comes the length of the payload of the message as 32-bit integer, and
|
||||
the type of the message as 32-bit integer (the integers are not converted, so
|
||||
they are in native byte order).
|
||||
To send a message to i3, you have to format it in the binary message format
|
||||
which i3 expects. This format specifies a magic string in the beginning to
|
||||
ensure the integrity of messages (to prevent follow-up errors). Following the
|
||||
magic string comes the length of the payload of the message as a 32-bit
|
||||
integer, and the type of the message as a 32-bit integer (the integers are not
|
||||
converted, so they are in native byte order).
|
||||
|
||||
The magic string currently is "i3-ipc" and will only be changed when a change
|
||||
in the IPC API is done which breaks compatibility (we hope that we don’t need
|
||||
@ -96,18 +97,30 @@ $sock->write(format_ipc_command("exit"));
|
||||
|
||||
== Receiving replies from i3
|
||||
|
||||
Replies from i3 usually consist of a simple string (the length of the string
|
||||
is the message_length, so you can consider them length-prefixed) which in turn
|
||||
contain the JSON serialization of a data structure. For example, the
|
||||
GET_WORKSPACES message returns an array of workspaces (each workspace is a map
|
||||
with certain attributes).
|
||||
Each message sent to i3 will cause exactly one reply to be sent in return. The
|
||||
order of the sent replies will always correspond to the order of the sent
|
||||
requests. The only exception to this is <<events>>, which (once subscribed to)
|
||||
may be sent at any time (though never in the middle of another event or reply).
|
||||
|
||||
=== Reply format
|
||||
It is generally safe to send several messages to i3 without first waiting for a
|
||||
reply for each one (pipelining) -- though, note that depending on the language /
|
||||
network library you use, writing to the socket without also reading from it may
|
||||
cause a deadlock due to the socket buffers getting full.
|
||||
|
||||
The reply format is identical to the normal message format. There also is
|
||||
the magic string, then the message length, then the message type and the
|
||||
payload.
|
||||
|
||||
The payload of replies from i3 usually consists of a simple string (the length
|
||||
of the string is the message_length, so you can consider them length-prefixed),
|
||||
which in turn contain the JSON serialization of a data structure. For example,
|
||||
the GET_WORKSPACES message returns an array of workspaces (each workspace is a
|
||||
map with certain attributes).
|
||||
|
||||
Replies currently have a 1:1 correspondence to messages, with the message type
|
||||
of the reply corresponding to the message type of the message which caused the
|
||||
reply to be sent.
|
||||
|
||||
The following reply types are implemented:
|
||||
|
||||
COMMAND (0)::
|
||||
@ -127,16 +140,30 @@ BAR_CONFIG (6)::
|
||||
VERSION (7)::
|
||||
Reply to the GET_VERSION message.
|
||||
BINDING_MODES (8)::
|
||||
Reply to the GET_BINDING_MODES message.
|
||||
Reply to the GET_BINDING_MODES message.
|
||||
GET_CONFIG (9)::
|
||||
Reply to the GET_CONFIG message.
|
||||
TICK (10)::
|
||||
Reply to the SEND_TICK message.
|
||||
SYNC (11)::
|
||||
Reply to the SYNC message.
|
||||
GET_BINDING_STATE (12)::
|
||||
Reply to the GET_BINDING_STATE message.
|
||||
|
||||
== Messages and replies
|
||||
|
||||
[[_command_reply]]
|
||||
=== COMMAND reply
|
||||
=== RUN_COMMAND / COMMAND
|
||||
|
||||
Run the payload as an https://i3wm.org/docs/userguide.html#list_of_commands[i3
|
||||
command] (like the commands you can bind to keys).
|
||||
|
||||
*Message:*
|
||||
|
||||
The message payload is the string containing the command to execute. There is
|
||||
no JSON encoding or trailing newline.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of a list of serialized maps for each command that was
|
||||
parsed. Each has the property +success (bool)+ and may also include a
|
||||
@ -170,7 +197,15 @@ When the specified command cannot be parsed, `success` will be false and
|
||||
-------------------
|
||||
|
||||
[[_workspaces_reply]]
|
||||
=== WORKSPACES reply
|
||||
=== GET_WORKSPACES / WORKSPACES
|
||||
|
||||
Get the list of current workspaces.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of a serialized list of workspaces. Each workspace has the
|
||||
following properties:
|
||||
@ -234,7 +269,16 @@ output (string)::
|
||||
-------------------
|
||||
|
||||
[[_subscribe_reply]]
|
||||
=== SUBSCRIBE reply
|
||||
=== SUBSCRIBE
|
||||
|
||||
Subscribe this IPC connection to the event types specified in the message
|
||||
payload. See <<events>>.
|
||||
|
||||
*Message:*
|
||||
|
||||
A JSON-encoded array of event types to subscribe to.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of a single serialized map. The only property is
|
||||
+success (bool)+, indicating whether the subscription was successful (the
|
||||
@ -246,7 +290,15 @@ default) or whether a JSON parse error occurred.
|
||||
-------------------
|
||||
|
||||
[[_outputs_reply]]
|
||||
=== OUTPUTS reply
|
||||
=== GET_OUTPUTS / OUTPUTS
|
||||
|
||||
Get the list of current outputs.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of a serialized list of outputs. Each output has the
|
||||
following properties:
|
||||
@ -257,7 +309,7 @@ active (boolean)::
|
||||
Whether this output is currently active (has a valid mode).
|
||||
primary (boolean)::
|
||||
Whether this output is currently the primary output.
|
||||
current_workspace (string)::
|
||||
current_workspace (string or null)::
|
||||
The name of the current workspace that is visible on this output. +null+ if
|
||||
the output is not active.
|
||||
rect (map)::
|
||||
@ -293,7 +345,15 @@ rect (map)::
|
||||
-------------------
|
||||
|
||||
[[_tree_reply]]
|
||||
=== TREE reply
|
||||
=== GET_TREE / TREE
|
||||
|
||||
Get the i3 layout tree.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of a serialized tree. Each node in the tree (representing
|
||||
one container) has at least the properties listed below. While the nodes might
|
||||
@ -329,7 +389,7 @@ orientation (string)::
|
||||
"vertical".
|
||||
THIS FIELD IS OBSOLETE. It is still present, but your code should not
|
||||
use it. Instead, rely on the layout field.
|
||||
percent (float)::
|
||||
percent (float or null)::
|
||||
The percentage which this container takes in its parent. A value of
|
||||
+null+ means that the percent property does not make sense for this
|
||||
container, for example for the root container.
|
||||
@ -353,14 +413,15 @@ deco_rect (map)::
|
||||
geometry (map)::
|
||||
The original geometry the window specified when i3 mapped it. Used when
|
||||
switching a window to floating mode, for example.
|
||||
window (integer)::
|
||||
window (integer or null)::
|
||||
The X11 window ID of the *actual client window* inside this container.
|
||||
This field is set to null for split containers or otherwise empty
|
||||
This field is set to +null+ for split containers or otherwise empty
|
||||
containers. This ID corresponds to what xwininfo(1) and other
|
||||
X11-related tools display (usually in hex).
|
||||
window_properties (map)::
|
||||
This optional field contains all available X11 window properties from the
|
||||
following list: *title*, *instance*, *class*, *window_role* and *transient_for*.
|
||||
following list: *title*, *instance*, *class*, *window_role*, *machine*
|
||||
and *transient_for*.
|
||||
window_type (string)::
|
||||
The window type (_NET_WM_WINDOW_TYPE). Possible values are undefined, normal,
|
||||
dialog, utility, toolbar, splash, menu, dropdown_menu, popup_menu, tooltip and
|
||||
@ -386,12 +447,18 @@ fullscreen_mode (integer)::
|
||||
+1+ (fullscreened on output) or
|
||||
+2+ (fullscreened globally).
|
||||
Note that all workspaces are considered fullscreened on their respective output.
|
||||
|
||||
floating (string)::
|
||||
Floating state of container.
|
||||
Can be either "auto_on", "auto_off", "user_on" or "user_off"
|
||||
nodes (array of node)::
|
||||
The tiling (i.e. non-floating) child containers of this node.
|
||||
floating_nodes (array of node)::
|
||||
The floating child containers of this node. Only non-empty on nodes with
|
||||
type +workspace+.
|
||||
scratchpad_state (string)::
|
||||
Whether the window is not in the scratchpad ("none"), freshly moved to
|
||||
the scratchpad but not yet resized ("fresh") or moved to the scratchpad
|
||||
and resized ("changed").
|
||||
|
||||
Please note that in the following example, I have left out some keys/values
|
||||
which are not relevant for the type of the node. Otherwise, the example would
|
||||
@ -532,7 +599,15 @@ JSON dump:
|
||||
-----------------------
|
||||
|
||||
[[_marks_reply]]
|
||||
=== MARKS reply
|
||||
=== GET_MARKS / MARKS
|
||||
|
||||
Gets the names of all currently set marks.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of a single array of strings for each container that has a
|
||||
mark. A mark can only be set on one container, so the array is unique.
|
||||
@ -541,7 +616,15 @@ The order of that array is undefined.
|
||||
If no window has a mark the response will be the empty array [].
|
||||
|
||||
[[_bar_config_reply]]
|
||||
=== BAR_CONFIG reply
|
||||
=== GET_BAR_CONFIG / BAR_CONFIG
|
||||
|
||||
Gets the specified bar configuration or the names of all bar configurations if payload is empty.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload, or the ID of the bar whose configuration to retrieve.
|
||||
|
||||
*Reply:*
|
||||
|
||||
This can be used by third-party workspace bars (especially i3bar, but others
|
||||
are free to implement compatible alternatives) to get the +bar+ block
|
||||
@ -641,7 +724,15 @@ binding_mode_text/binding_mode_bg/binding_mode_border::
|
||||
--------------
|
||||
|
||||
[[_version_reply]]
|
||||
=== VERSION reply
|
||||
=== GET_VERSION / VERSION
|
||||
|
||||
Gets the i3 version.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of a single JSON dictionary with the following keys:
|
||||
|
||||
@ -674,7 +765,15 @@ loaded_config_file_name (string)::
|
||||
-------------------
|
||||
|
||||
[[_binding_modes_reply]]
|
||||
=== BINDING_MODES reply
|
||||
=== GET_BINDING_MODES / BINDING_MODES
|
||||
|
||||
Gets the names of all currently configured binding modes.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of an array of all currently configured binding modes.
|
||||
|
||||
@ -684,18 +783,66 @@ The reply consists of an array of all currently configured binding modes.
|
||||
---------------------
|
||||
|
||||
[[_config_reply]]
|
||||
=== CONFIG reply
|
||||
=== GET_CONFIG / CONFIG
|
||||
|
||||
The config reply is a map which currently only contains the "config" member,
|
||||
which is a string containing the config file as loaded by i3 most recently.
|
||||
Returns the last loaded i3 config.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The config reply is a map which contains the following fields:
|
||||
|
||||
config (string)::
|
||||
The top-level config file contents that i3 has loaded most recently.
|
||||
This field is kept for backwards compatibility. See +included_configs+
|
||||
instead.
|
||||
included_configs (array of maps)::
|
||||
i3 adds one entry to this array for each config file it loads, in
|
||||
order. The first entry’s +raw_contents+ are identical to the +config+
|
||||
field.
|
||||
|
||||
Each +included_configs+ entry contains the following fields
|
||||
|
||||
path (string)::
|
||||
Absolute path name to the config file that i3 loaded.
|
||||
raw_contents (string)::
|
||||
The raw contents of the file as i3 read them.
|
||||
variable_replaced_contents (string)::
|
||||
The contents of the file after i3 replaced all variables. This is useful
|
||||
for debugging variable replacement.
|
||||
|
||||
*Example:*
|
||||
-------------------
|
||||
{ "config": "font pango:monospace 8\nbindsym Mod4+q exit\n" }
|
||||
{
|
||||
"config": "include font.cfg\n",
|
||||
"included_configs": [
|
||||
{
|
||||
"path": "/home/michael/configfiles/i3/config",
|
||||
"raw_contents": "include font.cfg\n",
|
||||
"variable_replaced_contents": "include font.cfg\n"
|
||||
},
|
||||
{
|
||||
"path": "/home/michael/configfiles/i3/font.cfg",
|
||||
"raw_contents": "set $font pango:monospace 8\nfont $font",
|
||||
"variable_replaced_contents": "set pango:monospace 8 pango:monospace 8\nfont pango:monospace 8\n"
|
||||
}
|
||||
],
|
||||
}
|
||||
-------------------
|
||||
|
||||
[[_tick_reply]]
|
||||
=== TICK reply
|
||||
=== SEND_TICK / TICK
|
||||
|
||||
Sends a tick event with the specified payload.
|
||||
|
||||
*Message:*
|
||||
|
||||
The payload of the tick event to send to IPC event listeners.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply is a map containing the "success" member. After the reply was
|
||||
received, the tick event has been written to all IPC connections which subscribe
|
||||
@ -709,7 +856,15 @@ events generated prior to the +SEND_TICK+ message (happened-before relation).
|
||||
-------------------
|
||||
|
||||
[[_sync_reply]]
|
||||
=== SYNC reply
|
||||
=== SYNC
|
||||
|
||||
Sends an i3 sync event with the specified random value to the specified window.
|
||||
|
||||
*Message:*
|
||||
|
||||
A JSON-encoded map with the properties "rnd" and "window" (both integer).
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply is a map containing the "success" member. After the reply was
|
||||
received, the https://i3wm.org/docs/testsuite.html#i3_sync[i3 sync message] was
|
||||
@ -721,7 +876,15 @@ responded to.
|
||||
-------------------
|
||||
|
||||
[[_binding_state_reply]]
|
||||
=== GET_BINDING_STATE reply
|
||||
=== GET_BINDING_STATE
|
||||
|
||||
Request the current binding state, i.e. the currently active binding mode name.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The binding_state reply is a map which currently only contains the "name"
|
||||
member, which is the name of the currently active binding mode as a string.
|
||||
|
@ -185,9 +185,9 @@ Therefore, if you just start Emacs via dmenu, it will not get swallowed by that
|
||||
container. Only if you start Emacs with the proper instance name (+emacs24
|
||||
--name notmuch+), it will get swallowed.
|
||||
|
||||
You can match on "class", "instance", "window_role" and "title". All values are
|
||||
case-sensitive regular expressions (PCRE). Use +xprop(1)+ and click into a
|
||||
window to see its properties:
|
||||
You can match on "class", "instance", "window_role", "title" and "machine". All
|
||||
values are case-sensitive regular expressions (PCRE). Use +xprop(1)+ and click
|
||||
into a window to see its properties:
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
$ xprop
|
||||
|
@ -481,7 +481,7 @@ an i3 crash resulting in the testcase being unable to communicate with i3 via
|
||||
IPC anymore.
|
||||
|
||||
[[i3_sync]]
|
||||
== Appendix A: The i3 sync protocol
|
||||
== Appendix A: The I3_SYNC protocol
|
||||
|
||||
Consider the following situation: You open two windows in your testcase, then
|
||||
you use +focus left+ and want to verify that the X11 focus has been updated
|
||||
@ -499,9 +499,9 @@ is($x->input_focus, $left->id, 'left window focused');
|
||||
However, the test fails. Sometimes. Apparently, there is a race condition in
|
||||
your test. If you think about it, this is because you are using two different
|
||||
pieces of software: You tell i3 to update focus, i3 confirms that, and then you
|
||||
ask X11 to give you the current focus. There is a certain time i3 needs to
|
||||
update the X11 state. If the testcase gets CPU time before X11 processed i3's
|
||||
requests, the test will fail.
|
||||
ask X11 to give you the current focus. There is a certain time that the X11
|
||||
server needs to process the requests from i3. If the testcase's request for the
|
||||
input focus is processed before i3's requests, the test will fail.
|
||||
|
||||
image::i3-sync.png["Diagram of the race condition", title="Diagram of the race condition"]
|
||||
|
||||
@ -531,10 +531,10 @@ less robust.
|
||||
|
||||
The real solution for this problem is a mechanism which I call "the i3 sync
|
||||
protocol". The idea is to send a request (which does not modify state) via X11
|
||||
to i3 which will then be answered. Due to the request's position in the event
|
||||
queue (*after* all previous events), you can be sure that by the time you
|
||||
receive the reply, all other events have been dealt with by i3 (and, more
|
||||
importantly, X11).
|
||||
to i3 which will then be answered, again via X11. Because this answer is
|
||||
generated via an X11 request, it will be sent to the X11 server *after* all
|
||||
previous requests. Thus, you can be sure that by the time you receive the reply,
|
||||
all other events have been dealt with by i3 (and, more importantly, X11).
|
||||
|
||||
image::i3-sync-working.png["Diagram of the i3 sync solution", title="Diagram of the i3 sync solution"]
|
||||
|
||||
@ -569,7 +569,35 @@ i3 will then reply with the same ClientMessage, sent to the window specified in
|
||||
request. You should use a random value in +data[1]+ and check that you received
|
||||
the same one when getting the reply.
|
||||
|
||||
== Appendix B: Socket activation
|
||||
== Appendix B: The sync IPC command
|
||||
|
||||
The above I3_SYNC protocol allows to synchronise with i3. However, it is not
|
||||
enough for tests that also involve i3bar: There might still be messages from
|
||||
i3bar in-flight even after synchronising with i3. Thus, there also exists a sync
|
||||
IPC command, that is however not meant to be used directly. Instead, i3bar uses
|
||||
it for implementing the I3_SYNC protocol.
|
||||
|
||||
The intended usage works like this:
|
||||
|
||||
1. You send an I3_SYNC message to i3bar's window. See <<i3_sync>>.
|
||||
2. i3bar sends a SYNC IPC command to i3 with payload
|
||||
+{"window":your-window-here,"rnd":your-random-value}+.
|
||||
3. i3 reacts to this IPC command as if it received an I3_SYNC request via X11.
|
||||
|
||||
This protocol is used, for example, in t/525-i3bar-mouse-bindings.t: A mouse
|
||||
button press on i3bar is triggered. i3bar reacts to this by sending IPC commands
|
||||
to i3.
|
||||
|
||||
The necessary synchronisation is achieved by sending an I3_SYNC event to i3bar:
|
||||
Because i3bar reacts with a sync IPC command to i3, all previous IPC commands from
|
||||
i3bar will be handled first. Because i3 reacts via X11, all previous X11
|
||||
requests from i3 will be handled by the X11 server first.
|
||||
|
||||
The actual test also has to sync with i3 first due to how X11 handling works.
|
||||
For more details, refer to the documentation for +XAllowEvents+ with mode
|
||||
+ReplayPointer+.
|
||||
|
||||
== Appendix C: Socket activation
|
||||
|
||||
Socket activation is a mechanism which was made popular by systemd, an init
|
||||
replacement. It basically describes creating a listening socket before starting
|
||||
|
194
docs/userguide
194
docs/userguide
@ -3,9 +3,9 @@ i3 User’s Guide
|
||||
Michael Stapelberg <michael@i3wm.org>
|
||||
|
||||
This document contains all the information you need to configure and use the i3
|
||||
window manager. If it does not, please check https://www.reddit.com/r/i3wm/
|
||||
first, then contact us on IRC (preferred) or post your question(s) on the
|
||||
mailing list.
|
||||
window manager. If it does not you can https://i3wm.org/contact/[contact us] on
|
||||
https://github.com/i3/i3/discussions[GitHub Discussions], IRC, or the mailing
|
||||
list.
|
||||
|
||||
== Default keybindings
|
||||
|
||||
@ -53,12 +53,23 @@ existing window (rotated displays).
|
||||
|
||||
image:two_terminals.png[Two terminals]
|
||||
|
||||
To move the focus between the two terminals, you can use the direction keys
|
||||
which you might know from the editor +vi+. However, in i3, your homerow is used
|
||||
for these keys (in +vi+, the keys are shifted to the left by one for
|
||||
compatibility with most keyboard layouts). Therefore, +$mod+j+ is left, +$mod+k+
|
||||
is down, +$mod+l+ is up and `$mod+;` is right. So, to switch between the
|
||||
terminals, use +$mod+k+ or +$mod+l+. Of course, you can also use the arrow keys.
|
||||
To move the focus between the two terminals, you can use the arrow keys. For
|
||||
convenience, the arrows are also available directly on the
|
||||
https://en.wikipedia.org/wiki/Touch_typing[keyboard’s home row] underneath your
|
||||
right hand:
|
||||
|
||||
|===
|
||||
| `$mod+j` | left
|
||||
| `$mod+k` | down
|
||||
| `$mod+l` | up
|
||||
| `$mod+;` | right
|
||||
|===
|
||||
|
||||
Note that this differs by one key from the popular text editor `vi`, which was
|
||||
https://twitter.com/hillelogram/status/1326600125569961991[developed on an
|
||||
ADM-3A terminal and therefore uses `hjkl` instead of `jkl;`] -- i3’s default is
|
||||
meant to require minimal finger movement, but some `vi` users change their i3
|
||||
config for consistency.
|
||||
|
||||
At the moment, your workspace is split (it contains two terminals) in a
|
||||
specific direction (horizontal by default). Every window can be split
|
||||
@ -67,7 +78,7 @@ horizontally or vertically again, just like the workspace. The terminology is
|
||||
or browser) and "split container" for containers that consist of one or more
|
||||
windows.
|
||||
|
||||
TODO: picture of the tree
|
||||
//TODO: picture of the tree
|
||||
|
||||
To split a window vertically, press +$mod+v+ before you create the new window.
|
||||
To split it horizontally, press +$mod+h+.
|
||||
@ -308,6 +319,90 @@ include the following line in your config file:
|
||||
# i3 config file (v4)
|
||||
---------------------
|
||||
|
||||
[[include]]
|
||||
=== Include directive
|
||||
|
||||
Since i3 v4.20, it is possible to include other configuration files from your i3
|
||||
configuration.
|
||||
|
||||
*Syntax*:
|
||||
-----------------
|
||||
include <pattern>
|
||||
-----------------
|
||||
|
||||
i3 expands `pattern` using shell-like word expansion, specifically using the
|
||||
https://manpages.debian.org/wordexp.3[`wordexp(3)` C standard library function].
|
||||
|
||||
*Examples*:
|
||||
--------------------------------------------------------------------------------
|
||||
# Tilde expands to the user’s home directory:
|
||||
include ~/.config/i3/assignments.conf
|
||||
|
||||
# Environment variables are expanded:
|
||||
include $HOME/.config/i3/assignments.conf
|
||||
|
||||
# Wildcards are expanded:
|
||||
include ~/.config/i3/config.d/*.conf
|
||||
|
||||
# Command substitution:
|
||||
include ~/.config/i3/`hostname`.conf
|
||||
|
||||
# i3 loads each path only once, so including the i3 config will not result
|
||||
# in an endless loop, but in an error:
|
||||
include ~/.config/i3/config
|
||||
|
||||
# i3 changes the working directory while parsing a config file
|
||||
# so that relative paths are interpreted relative to the directory
|
||||
# of the config file that contains the path:
|
||||
include assignments.conf
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
If a specified file cannot be read, for example because of a lack of file
|
||||
permissions, or because of a dangling symlink, i3 will report an error and
|
||||
continue processing your remaining configuration.
|
||||
|
||||
To list all loaded configuration files, run `i3 --moreversion`:
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
% i3 --moreversion
|
||||
Binary i3 version: 4.19.2-87-gfcae64f7+ © 2009 Michael Stapelberg and contributors
|
||||
Running i3 version: 4.19.2-87-gfcae64f7+ (pid 963940)
|
||||
Loaded i3 config:
|
||||
/tmp/i3.cfg (main) (last modified: 2021-05-13T16:42:31 CEST, 463 seconds ago)
|
||||
/tmp/included.cfg (included) (last modified: 2021-05-13T16:42:43 CEST, 451 seconds ago)
|
||||
/tmp/another.cfg (included) (last modified: 2021-05-13T16:42:46 CEST, 448 seconds ago)
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Variables are shared between all config files, but beware of the following limitation:
|
||||
|
||||
* You can define a variable and use it within an included file.
|
||||
* You cannot use (in the parent file) a variable that was defined within an included file.
|
||||
|
||||
This is a technical limitation: variable expansion happens in a separate stage
|
||||
before parsing include directives.
|
||||
|
||||
Conceptually, included files can only add to the configuration, not undo the
|
||||
effects of already-processed configuration. For example, you can only add new
|
||||
key bindings, not overwrite or remove existing key bindings. This means:
|
||||
|
||||
* The `include` directive is suitable for organizing large configurations into
|
||||
separate files, possibly selecting files based on conditionals.
|
||||
|
||||
* The `include` directive is not suitable for expressing “use the default
|
||||
configuration with the following changes”. For that case, we still recommend
|
||||
copying and modifying the default config.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Implementation-wise, i3 does not currently construct one big configuration from
|
||||
all `include` directives. Instead, i3’s config file parser interprets all
|
||||
configuration directives in its `parse_file()` function. When processing an
|
||||
`include` configuration directive, the parser recursively calls `parse_file()`.
|
||||
|
||||
This means the evaluation order of files forms a tree, or one could say i3 uses
|
||||
depth-first traversal.
|
||||
====
|
||||
|
||||
=== Comments
|
||||
|
||||
It is possible and recommended to use comments in your configuration file to
|
||||
@ -597,9 +692,10 @@ Default is +left+
|
||||
title_align left|center|right
|
||||
---------------------------------------------
|
||||
|
||||
[[default_border]]
|
||||
=== Default border style for new windows
|
||||
|
||||
This option determines which border style new windows will have. The default is
|
||||
This option determines which border style *new* windows will have. The default is
|
||||
+normal+. Note that default_floating_border applies only to windows which are starting out as
|
||||
floating windows, e.g., dialog windows, but not windows that are floated later on.
|
||||
|
||||
@ -1863,12 +1959,17 @@ bindsym $mod+x [class="Firefox" window_role="About"] kill
|
||||
# enable floating mode and move container to workspace 4
|
||||
for_window [class="^evil-app$"] floating enable, move container to workspace 4
|
||||
|
||||
# enable window icons for all windows with extra horizontal padding of 1px
|
||||
for_window [all] title_window_icon padding 1px
|
||||
|
||||
# move all floating windows to the scratchpad
|
||||
bindsym $mod+x [floating] move scratchpad
|
||||
------------------------------------
|
||||
|
||||
The criteria which are currently implemented are:
|
||||
|
||||
all::
|
||||
Matches all windows. This criterion requires no value.
|
||||
class::
|
||||
Compares the window class (the second part of WM_CLASS). Use the
|
||||
special value +\_\_focused__+ to match all windows having the same window
|
||||
@ -1885,6 +1986,10 @@ window_type::
|
||||
Compare the window type (_NET_WM_WINDOW_TYPE). Possible values are
|
||||
+normal+, +dialog+, +utility+, +toolbar+, +splash+, +menu+, +dropdown_menu+,
|
||||
+popup_menu+, +tooltip+ and +notification+.
|
||||
machine::
|
||||
Compares the name of the machine the client window is running on
|
||||
(WM_CLIENT_MACHINE). Usually, it is equal to the hostname of the local
|
||||
machine, but it may differ if remote X11 apps are used.
|
||||
id::
|
||||
Compares the X11 window ID, which you can get via +xwininfo+ for example.
|
||||
title::
|
||||
@ -1922,9 +2027,9 @@ tiling_from::
|
||||
tiling are matched. With "user", only windows that the user made tiling
|
||||
are matched.
|
||||
|
||||
The criteria +class+, +instance+, +role+, +title+, +workspace+ and +mark+ are
|
||||
actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for
|
||||
information on how to use them.
|
||||
The criteria +class+, +instance+, +role+, +title+, +workspace+, +machine+ and
|
||||
+mark+ are actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc
|
||||
perlre+ for information on how to use them.
|
||||
|
||||
[[exec]]
|
||||
=== Executing applications (exec)
|
||||
@ -2264,9 +2369,8 @@ exhausting numbered ones and looks for numbered ones after exhausting named ones
|
||||
See <<move_to_outputs>> for how to move a container/workspace to a different
|
||||
RandR output.
|
||||
|
||||
Workspace names are parsed as
|
||||
https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup]
|
||||
by i3bar.
|
||||
Workspace names are parsed as https://developer.gnome.org/pango/1.46/[Pango
|
||||
markup] by i3bar.
|
||||
|
||||
[[back_and_forth]]
|
||||
To switch back to the previously focused workspace, use +workspace
|
||||
@ -2391,15 +2495,18 @@ To move a container to another RandR output (addressed by names like +LVDS1+ or
|
||||
|
||||
*Syntax*:
|
||||
------------------------------------------------------------
|
||||
move container to output left|right|down|up|current|primary|<output>
|
||||
move workspace to output left|right|down|up|current|primary|<output>
|
||||
------------------------------------------------------------
|
||||
move container to output left|right|down|up|current|primary|next|<output1> [output2]…
|
||||
move workspace to output left|right|down|up|current|primary|next|<output1> [output2]…
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
*Examples*:
|
||||
--------------------------------------------------------
|
||||
# Move the current workspace to the next output
|
||||
# (effectively toggles when you only have two outputs)
|
||||
bindsym $mod+x move workspace to output right
|
||||
bindsym $mod+x move workspace to output next
|
||||
|
||||
# Cycle this workspace between outputs VGA1 and LVDS1 but not DVI0
|
||||
bindsym $mod+x move workspace to output VGA1 LVDS1
|
||||
|
||||
# Put this window on the presentation output.
|
||||
bindsym $mod+x move container to output VGA1
|
||||
@ -2408,6 +2515,11 @@ bindsym $mod+x move container to output VGA1
|
||||
bindsym $mod+x move container to output primary
|
||||
--------------------------------------------------------
|
||||
|
||||
If you specify more than one output, the container/workspace is cycled through
|
||||
them: If it is already in one of the outputs of the list, it will move to the
|
||||
next output in the list. If it is in an output not in the list, it will move to
|
||||
the first specified output. Non-existing outputs are skipped.
|
||||
|
||||
Note that you might not have a primary output configured yet. To do so, run:
|
||||
-------------------------
|
||||
xrandr --output <output> --primary
|
||||
@ -2558,9 +2670,8 @@ unmark irssi
|
||||
|
||||
By default, i3 will simply print the X11 window title. Using +title_format+,
|
||||
this can be customized by setting the format to the desired output. This
|
||||
directive supports
|
||||
https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup]
|
||||
and the following placeholders which will be replaced:
|
||||
directive supports https://developer.gnome.org/pango/1.46/[Pango markup] and the
|
||||
following placeholders which will be replaced:
|
||||
|
||||
+%title+::
|
||||
For normal windows, this is the X11 window title (_NET_WM_NAME or WM_NAME
|
||||
@ -2573,6 +2684,9 @@ and the following placeholders which will be replaced:
|
||||
+%instance+::
|
||||
The X11 window instance (first part of WM_CLASS). This corresponds to the
|
||||
+instance+ criterion, see <<command_criteria>>.
|
||||
+%machine+::
|
||||
The X11 name of the machine (WM_CLIENT_MACHINE). This corresponds to the
|
||||
+machine+ criterion, see <<command_criteria>>.
|
||||
|
||||
Using the <<for_window>> directive, you can set the title format for any window
|
||||
based on <<command_criteria>>.
|
||||
@ -2594,6 +2708,32 @@ for_window [class=".*"] title_format "<b>%title</b>"
|
||||
for_window [class="(?i)firefox"] title_format "<span foreground='red'>%title</span>"
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
[[title_window_icon]]
|
||||
=== Window title icon
|
||||
|
||||
By default, i3 does not display the window icon in the title bar.
|
||||
|
||||
Starting with i3 v4.20, you can optionally enable window icons either for
|
||||
specific windows or for all windows (using the <<for_window>> directive).
|
||||
|
||||
*Syntax*:
|
||||
-----------------------------
|
||||
title_window_icon <yes|no>
|
||||
title_window_icon padding <px>
|
||||
------------------------------
|
||||
|
||||
*Examples*:
|
||||
-------------------------------------------------------------------------------------
|
||||
# show the window icon for the focused window to make it stand out
|
||||
bindsym $mod+p title_window_icon on
|
||||
|
||||
# enable window icons for all windows
|
||||
for_window [all] title_window_icon on
|
||||
|
||||
# enable window icons for all windows with extra horizontal padding
|
||||
for_window [all] title_window_icon padding 3px
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
=== Changing border style
|
||||
|
||||
To change the border of the current client, you can use +border normal+ to use the normal
|
||||
@ -2623,10 +2763,16 @@ border 1pixel
|
||||
bindsym $mod+t border normal 0
|
||||
# use no window title and a thick border
|
||||
bindsym $mod+y border pixel 3
|
||||
# use window title *and* a thick border
|
||||
bindsym $mod+y border normal 3
|
||||
# use neither window title nor border
|
||||
bindsym $mod+u border none
|
||||
# no border on VLC
|
||||
for_window [class="vlc"] border none
|
||||
----------------------------------------------
|
||||
|
||||
To change the default for all windows, see the directive <<default_border>>.
|
||||
|
||||
[[shmlog]]
|
||||
=== Enabling shared memory logging
|
||||
|
||||
|
@ -17,6 +17,10 @@ font pango:monospace 8
|
||||
# text rendering and scalability on retina/hidpi displays (thanks to pango).
|
||||
#font pango:DejaVu Sans Mono 8
|
||||
|
||||
# Start XDG autostart .desktop files using dex. See also
|
||||
# https://wiki.archlinux.org/index.php/XDG_Autostart
|
||||
exec --no-startup-id dex --autostart --environment i3
|
||||
|
||||
# The combination of xss-lock, nm-applet and pactl is a popular choice, so
|
||||
# they are included here as an example. Modify as you see fit.
|
||||
|
||||
|
@ -18,6 +18,10 @@ font pango:monospace 8
|
||||
# text rendering and scalability on retina/hidpi displays (thanks to pango).
|
||||
#font pango:DejaVu Sans Mono 8
|
||||
|
||||
# Start XDG autostart .desktop files using dex. See also
|
||||
# https://wiki.archlinux.org/index.php/XDG_Autostart
|
||||
exec --no-startup-id dex --autostart --environment i3
|
||||
|
||||
# The combination of xss-lock, nm-applet and pactl is a popular choice, so
|
||||
# they are included here as an example. Modify as you see fit.
|
||||
|
||||
|
@ -133,7 +133,7 @@ close($enumfh);
|
||||
open(my $callfh, '>', "GENERATED_${prefix}_call.h");
|
||||
my $resultname = uc(substr($prefix, 0, 1)) . substr($prefix, 1) . 'ResultIR';
|
||||
say $callfh '#pragma once';
|
||||
say $callfh "static void GENERATED_call(const int call_identifier, struct $resultname *result) {";
|
||||
say $callfh "static void GENERATED_call(Match *current_match, struct stack *stack, const int call_identifier, struct $resultname *result) {";
|
||||
say $callfh ' switch (call_identifier) {';
|
||||
my $call_id = 0;
|
||||
for my $state (@keys) {
|
||||
@ -150,8 +150,8 @@ for my $state (@keys) {
|
||||
# calls to get_string(). Also replaces state names (like FOR_WINDOW)
|
||||
# with their ID (useful for cfg_criteria_init(FOR_WINDOW) e.g.).
|
||||
$cmd =~ s/$_/$statenum{$_}/g for @keys;
|
||||
$cmd =~ s/\$([a-z_]+)/get_string("$1")/g;
|
||||
$cmd =~ s/\&([a-z_]+)/get_long("$1")/g;
|
||||
$cmd =~ s/\$([a-z_]+)/get_string(stack, "$1")/g;
|
||||
$cmd =~ s/\&([a-z_]+)/get_long(stack, "$1")/g;
|
||||
# For debugging/testing, we print the call using printf() and thus need
|
||||
# to generate a format string. The format uses %d for <number>s,
|
||||
# literal numbers or state IDs and %s for NULL, <string>s and literal
|
||||
@ -175,9 +175,9 @@ for my $state (@keys) {
|
||||
say $callfh '#ifndef TEST_PARSER';
|
||||
my $real_cmd = $cmd;
|
||||
if ($real_cmd =~ /\(\)/) {
|
||||
$real_cmd =~ s/\(/(¤t_match, result/;
|
||||
$real_cmd =~ s/\(/(current_match, result/;
|
||||
} else {
|
||||
$real_cmd =~ s/\(/(¤t_match, result, /;
|
||||
$real_cmd =~ s/\(/(current_match, result, /;
|
||||
}
|
||||
say $callfh " $real_cmd;";
|
||||
say $callfh '#else';
|
||||
|
@ -154,6 +154,8 @@ find(
|
||||
},
|
||||
no_chdir => 1,
|
||||
follow_fast => 1,
|
||||
# Ignore any duplicate files and directories and proceed normally:
|
||||
follow_skip => 2,
|
||||
},
|
||||
@searchdirs
|
||||
);
|
||||
|
@ -25,10 +25,10 @@
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#if !defined(__OpenBSD__)
|
||||
static uint32_t offset_next_write;
|
||||
#endif
|
||||
static uint32_t wrap_count;
|
||||
|
||||
static i3_shmlog_header *header;
|
||||
@ -36,12 +36,6 @@ static char *logbuffer,
|
||||
*walk;
|
||||
static int ipcfd = -1;
|
||||
|
||||
static volatile bool interrupted = false;
|
||||
|
||||
static void sighandler(int signal) {
|
||||
interrupted = true;
|
||||
}
|
||||
|
||||
static void disable_shmlog(void) {
|
||||
const char *disablecmd = "debuglog off; shmlog off";
|
||||
if (ipc_send_message(ipcfd, strlen(disablecmd),
|
||||
@ -188,22 +182,25 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
/* NB: While we must never write, we need O_RDWR for the pthread condvar. */
|
||||
int logbuffer_shm = shm_open(shmname, O_RDWR, 0);
|
||||
if (logbuffer_shm == -1)
|
||||
if (logbuffer_shm == -1) {
|
||||
err(EXIT_FAILURE, "Could not shm_open SHM segment for the i3 log (%s)", shmname);
|
||||
}
|
||||
|
||||
if (fstat(logbuffer_shm, &statbuf) != 0)
|
||||
if (fstat(logbuffer_shm, &statbuf) != 0) {
|
||||
err(EXIT_FAILURE, "stat(%s)", shmname);
|
||||
}
|
||||
|
||||
/* NB: While we must never write, we need PROT_WRITE for the pthread condvar. */
|
||||
logbuffer = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, logbuffer_shm, 0);
|
||||
if (logbuffer == MAP_FAILED)
|
||||
logbuffer = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, logbuffer_shm, 0);
|
||||
if (logbuffer == MAP_FAILED) {
|
||||
err(EXIT_FAILURE, "Could not mmap SHM segment for the i3 log");
|
||||
}
|
||||
|
||||
header = (i3_shmlog_header *)logbuffer;
|
||||
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("next_write = %d, last_wrap = %d, logbuffer_size = %d, shmname = %s\n",
|
||||
header->offset_next_write, header->offset_last_wrap, header->size, shmname);
|
||||
}
|
||||
free(shmname);
|
||||
walk = logbuffer + header->offset_next_write;
|
||||
|
||||
@ -233,25 +230,38 @@ int main(int argc, char *argv[]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Handle SIGINT gracefully to invoke atexit handlers, if any. */
|
||||
struct sigaction action;
|
||||
action.sa_handler = sighandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = 0;
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
char *log_stream_socket_path = root_atom_contents("I3_LOG_STREAM_SOCKET_PATH", NULL, 0);
|
||||
if (log_stream_socket_path == NULL) {
|
||||
errx(EXIT_FAILURE, "could not determine i3 log stream socket path: possible i3-dump-log and i3 version mismatch");
|
||||
}
|
||||
|
||||
/* Since pthread_cond_wait() expects a mutex, we need to provide one.
|
||||
* To not lock i3 (that’s bad, mhkay?) we just define one outside of
|
||||
* the shared memory. */
|
||||
pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_mutex_lock(&dummy_mutex);
|
||||
while (!interrupted) {
|
||||
pthread_cond_wait(&(header->condvar), &dummy_mutex);
|
||||
/* If this was not a spurious wakeup, print the new lines. */
|
||||
if (header->offset_next_write != offset_next_write) {
|
||||
offset_next_write = header->offset_next_write;
|
||||
print_till_end();
|
||||
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
|
||||
if (sockfd == -1) {
|
||||
err(EXIT_FAILURE, "Could not create socket");
|
||||
}
|
||||
|
||||
(void)fcntl(sockfd, F_SETFD, FD_CLOEXEC);
|
||||
|
||||
struct sockaddr_un addr;
|
||||
memset(&addr, 0, sizeof(struct sockaddr_un));
|
||||
addr.sun_family = AF_LOCAL;
|
||||
strncpy(addr.sun_path, log_stream_socket_path, sizeof(addr.sun_path) - 1);
|
||||
if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
|
||||
err(EXIT_FAILURE, "Could not connect to i3 on socket %s", log_stream_socket_path);
|
||||
}
|
||||
|
||||
/* Same size as the buffer used in log.c vlog(): */
|
||||
char buf[4096];
|
||||
for (;;) {
|
||||
const int n = read(sockfd, buf, sizeof(buf));
|
||||
if (n == -1) {
|
||||
err(EXIT_FAILURE, "read(log-stream-socket):");
|
||||
}
|
||||
if (n == 0) {
|
||||
exit(0); /* i3 closed the socket */
|
||||
}
|
||||
buf[n] = '\0';
|
||||
swrite(STDOUT_FILENO, buf, n);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -156,6 +156,7 @@ int main(int argc, char *argv[]) {
|
||||
char *payload = NULL;
|
||||
bool quiet = false;
|
||||
bool monitor = false;
|
||||
bool raw_reply = false;
|
||||
|
||||
static struct option long_options[] = {
|
||||
{"socket", required_argument, 0, 's'},
|
||||
@ -164,9 +165,10 @@ int main(int argc, char *argv[]) {
|
||||
{"quiet", no_argument, 0, 'q'},
|
||||
{"monitor", no_argument, 0, 'm'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"raw", no_argument, 0, 'r'},
|
||||
{0, 0, 0, 0}};
|
||||
|
||||
char *options_string = "s:t:vhqm";
|
||||
char *options_string = "s:t:vhqmr";
|
||||
|
||||
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
|
||||
if (o == 's') {
|
||||
@ -217,6 +219,8 @@ int main(int argc, char *argv[]) {
|
||||
return 0;
|
||||
} else if (o == '?') {
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (o == 'r') {
|
||||
raw_reply = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,32 +266,38 @@ int main(int argc, char *argv[]) {
|
||||
/* For the reply of commands, have a look if that command was successful.
|
||||
* If not, nicely format the error message. */
|
||||
if (reply_type == I3_IPC_REPLY_TYPE_COMMAND) {
|
||||
yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
|
||||
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
|
||||
yajl_free(handle);
|
||||
if (!raw_reply) {
|
||||
yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
|
||||
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
|
||||
yajl_free(handle);
|
||||
|
||||
switch (state) {
|
||||
case yajl_status_ok:
|
||||
break;
|
||||
case yajl_status_client_canceled:
|
||||
case yajl_status_error:
|
||||
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
|
||||
switch (state) {
|
||||
case yajl_status_ok:
|
||||
break;
|
||||
case yajl_status_client_canceled:
|
||||
case yajl_status_error:
|
||||
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!quiet) {
|
||||
if (!quiet || raw_reply) {
|
||||
printf("%.*s\n", reply_length, reply);
|
||||
}
|
||||
} else if (reply_type == I3_IPC_REPLY_TYPE_CONFIG) {
|
||||
yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
|
||||
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
|
||||
yajl_free(handle);
|
||||
if (raw_reply) {
|
||||
printf("%.*s\n", reply_length, reply);
|
||||
} else {
|
||||
yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
|
||||
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
|
||||
yajl_free(handle);
|
||||
|
||||
switch (state) {
|
||||
case yajl_status_ok:
|
||||
break;
|
||||
case yajl_status_client_canceled:
|
||||
case yajl_status_error:
|
||||
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
|
||||
switch (state) {
|
||||
case yajl_status_ok:
|
||||
break;
|
||||
case yajl_status_client_canceled:
|
||||
case yajl_status_error:
|
||||
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
|
||||
}
|
||||
}
|
||||
} else if (reply_type == I3_IPC_REPLY_TYPE_SUBSCRIBE) {
|
||||
do {
|
||||
|
@ -266,13 +266,9 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the position and size the i3-nagbar window should use.
|
||||
* This will be the primary output or a fallback if it cannot be determined.
|
||||
* Tries to position the rectangle on the primary output.
|
||||
*/
|
||||
static xcb_rectangle_t get_window_position(void) {
|
||||
/* Default values if we cannot determine the primary output or its CRTC info. */
|
||||
xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + 2 * MSG_PADDING + BAR_BORDER};
|
||||
|
||||
static void set_window_position_primary(xcb_rectangle_t *result) {
|
||||
xcb_randr_get_screen_resources_current_cookie_t rcookie = xcb_randr_get_screen_resources_current(conn, root);
|
||||
xcb_randr_get_output_primary_cookie_t pcookie = xcb_randr_get_output_primary(conn, root);
|
||||
|
||||
@ -314,14 +310,61 @@ static xcb_rectangle_t get_window_position(void) {
|
||||
goto free_resources;
|
||||
}
|
||||
|
||||
result.x = crtc->x;
|
||||
result.y = crtc->y;
|
||||
result->x = crtc->x;
|
||||
result->y = crtc->y;
|
||||
goto free_resources;
|
||||
|
||||
free_resources:
|
||||
free(res);
|
||||
free(primary);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to position the rectangle on the output with input focus.
|
||||
* If unsuccessful, try to position on primary output.
|
||||
*/
|
||||
static void set_window_position_focus(xcb_rectangle_t *result) {
|
||||
bool success = false;
|
||||
xcb_get_input_focus_reply_t *input_focus = NULL;
|
||||
xcb_get_geometry_reply_t *geometry = NULL;
|
||||
xcb_translate_coordinates_reply_t *coordinates = NULL;
|
||||
|
||||
/* To avoid the input window disappearing while determining its position */
|
||||
xcb_grab_server(conn);
|
||||
|
||||
input_focus = xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL);
|
||||
if (input_focus == NULL || input_focus->focus == XCB_NONE) {
|
||||
LOG("Failed to receive the current input focus or no window has the input focus right now.\n");
|
||||
goto free_resources;
|
||||
}
|
||||
|
||||
geometry = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, input_focus->focus), NULL);
|
||||
if (geometry == NULL) {
|
||||
LOG("Failed to received window geometry.\n");
|
||||
goto free_resources;
|
||||
}
|
||||
|
||||
coordinates = xcb_translate_coordinates_reply(
|
||||
conn, xcb_translate_coordinates(conn, input_focus->focus, root, geometry->x, geometry->y), NULL);
|
||||
if (coordinates == NULL) {
|
||||
LOG("Failed to translate coordinates.\n");
|
||||
goto free_resources;
|
||||
}
|
||||
|
||||
LOG("Found current focus at x = %i / y = %i.\n", coordinates->dst_x, coordinates->dst_y);
|
||||
result->x = coordinates->dst_x;
|
||||
result->y = coordinates->dst_y;
|
||||
success = true;
|
||||
|
||||
free_resources:
|
||||
xcb_ungrab_server(conn);
|
||||
free(input_focus);
|
||||
free(coordinates);
|
||||
free(geometry);
|
||||
if (!success) {
|
||||
LOG("Could not position on focused output, trying to position on primary output.\n");
|
||||
set_window_position_primary(result);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@ -360,6 +403,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
argv0 = argv[0];
|
||||
|
||||
bool position_on_primary = false;
|
||||
char *pattern = sstrdup("pango:monospace 8");
|
||||
int o, option_index = 0;
|
||||
enum { TYPE_ERROR = 0,
|
||||
@ -373,9 +417,10 @@ int main(int argc, char *argv[]) {
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"message", required_argument, 0, 'm'},
|
||||
{"type", required_argument, 0, 't'},
|
||||
{"primary", no_argument, 0, 'p'},
|
||||
{0, 0, 0, 0}};
|
||||
|
||||
char *options_string = "b:B:f:m:t:vh";
|
||||
char *options_string = "b:B:f:m:t:vhp";
|
||||
|
||||
prompt = i3string_from_utf8("Please do not run this program.");
|
||||
|
||||
@ -399,8 +444,11 @@ int main(int argc, char *argv[]) {
|
||||
case 'h':
|
||||
free(pattern);
|
||||
printf("i3-nagbar " I3_VERSION "\n");
|
||||
printf("i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
|
||||
printf("i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v] [-p]\n");
|
||||
return 0;
|
||||
case 'p':
|
||||
position_on_primary = true;
|
||||
break;
|
||||
case 'b':
|
||||
case 'B':
|
||||
buttons = srealloc(buttons, sizeof(button_t) * (buttoncnt + 1));
|
||||
@ -464,7 +512,13 @@ int main(int argc, char *argv[]) {
|
||||
err(EXIT_FAILURE, "pledge");
|
||||
#endif
|
||||
|
||||
xcb_rectangle_t win_pos = get_window_position();
|
||||
/* Default values if we cannot determine the preferred window position. */
|
||||
xcb_rectangle_t win_pos = (xcb_rectangle_t){50, 50, 500, font.height + 2 * MSG_PADDING + BAR_BORDER};
|
||||
if (position_on_primary) {
|
||||
set_window_position_primary(&win_pos);
|
||||
} else {
|
||||
set_window_position_focus(&win_pos);
|
||||
}
|
||||
|
||||
xcb_cursor_context_t *cursor_ctx;
|
||||
if (xcb_cursor_context_new(conn, root_screen, &cursor_ctx) < 0) {
|
||||
|
@ -8,7 +8,13 @@
|
||||
# We welcome patches that add distribution-specific mechanisms to find the
|
||||
# preferred terminal emulator. On Debian, there is the x-terminal-emulator
|
||||
# symlink for example.
|
||||
for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper; do
|
||||
#
|
||||
# Invariants:
|
||||
# 1. $TERMINAL must come first
|
||||
# 2. Distribution-specific mechanisms come next, e.g. x-terminal-emulator
|
||||
# 3. The terminal emulator with best accessibility comes first.
|
||||
# 4. No order is guaranteed/desired for the remaining terminal emulators.
|
||||
for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper; do
|
||||
if command -v "$terminal" > /dev/null 2>&1; then
|
||||
exec "$terminal" "$@"
|
||||
fi
|
||||
|
@ -42,6 +42,12 @@ typedef struct {
|
||||
bool click_events_init;
|
||||
} i3bar_child;
|
||||
|
||||
/*
|
||||
* Remove all blocks from the given statusline.
|
||||
* If free_resources is set, the fields of each status block will be free'd.
|
||||
*/
|
||||
void clear_statusline(struct statusline_head *head, bool free_resources);
|
||||
|
||||
/*
|
||||
* Start a child process with the specified command and reroute stdin.
|
||||
* We actually start a $SHELL to execute the command so we don't have to care
|
||||
|
@ -74,6 +74,13 @@ extern config_t config;
|
||||
*/
|
||||
void parse_config_json(char *json);
|
||||
|
||||
/**
|
||||
* Start parsing the received bar configuration list. The only usecase right
|
||||
* now is to automatically get the first bar id.
|
||||
*
|
||||
*/
|
||||
void parse_get_first_i3bar_config(char *json);
|
||||
|
||||
/**
|
||||
* free()s the color strings as soon as they are not needed anymore.
|
||||
*
|
||||
|
@ -18,7 +18,7 @@
|
||||
* socket_path must be a valid path to the ipc_socket of i3
|
||||
*
|
||||
*/
|
||||
int init_connection(const char *socket_path);
|
||||
void init_connection(const char *socket_path);
|
||||
|
||||
/*
|
||||
* Destroy the connection to i3.
|
||||
|
@ -27,7 +27,7 @@
|
||||
#include <yajl/yajl_parse.h>
|
||||
|
||||
/* Global variables for child_*() */
|
||||
i3bar_child child;
|
||||
i3bar_child child = {0};
|
||||
#define DLOG_CHILD DLOG("%s: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
|
||||
__func__, (long)child.pid, child.stopped, child.stop_signal, child.cont_signal, child.click_events, child.click_events_init)
|
||||
|
||||
@ -66,7 +66,7 @@ int child_stdin;
|
||||
* Remove all blocks from the given statusline.
|
||||
* If free_resources is set, the fields of each status block will be free'd.
|
||||
*/
|
||||
static void clear_statusline(struct statusline_head *head, bool free_resources) {
|
||||
void clear_statusline(struct statusline_head *head, bool free_resources) {
|
||||
struct status_block *first;
|
||||
while (!TAILQ_EMPTY(head)) {
|
||||
first = TAILQ_FIRST(head);
|
||||
@ -139,6 +139,10 @@ static void cleanup(void) {
|
||||
if (stdin_io != NULL) {
|
||||
ev_io_stop(main_loop, stdin_io);
|
||||
FREE(stdin_io);
|
||||
close(stdin_fd);
|
||||
stdin_fd = 0;
|
||||
close(child_stdin);
|
||||
child_stdin = 0;
|
||||
}
|
||||
|
||||
if (child_sig != NULL) {
|
||||
|
@ -184,7 +184,6 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
|
||||
|
||||
if (!strcmp(cur_key, "status_command")) {
|
||||
DLOG("command = %.*s\n", len, val);
|
||||
FREE(config.command);
|
||||
sasprintf(&config.command, "%.*s", len, val);
|
||||
return 1;
|
||||
}
|
||||
@ -367,14 +366,12 @@ static yajl_callbacks outputs_callbacks = {
|
||||
*
|
||||
*/
|
||||
void parse_config_json(char *json) {
|
||||
yajl_handle handle;
|
||||
yajl_status state;
|
||||
handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
|
||||
yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
|
||||
|
||||
TAILQ_INIT(&(config.bindings));
|
||||
TAILQ_INIT(&(config.tray_outputs));
|
||||
|
||||
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
|
||||
yajl_status state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
|
||||
|
||||
/* FIXME: Proper error handling for JSON parsing */
|
||||
switch (state) {
|
||||
@ -390,6 +387,25 @@ void parse_config_json(char *json) {
|
||||
yajl_free(handle);
|
||||
}
|
||||
|
||||
static int i3bar_config_string_cb(void *params_, const unsigned char *val, size_t _len) {
|
||||
sasprintf(&config.bar_id, "%.*s", (int)_len, val);
|
||||
return 0; /* Stop parsing */
|
||||
}
|
||||
|
||||
/*
|
||||
* Start parsing the received bar configuration list. The only usecase right
|
||||
* now is to automatically get the first bar id.
|
||||
*
|
||||
*/
|
||||
void parse_get_first_i3bar_config(char *json) {
|
||||
yajl_callbacks configs_callbacks = {
|
||||
.yajl_string = i3bar_config_string_cb,
|
||||
};
|
||||
yajl_handle handle = yajl_alloc(&configs_callbacks, NULL, NULL);
|
||||
yajl_parse(handle, (const unsigned char *)json, strlen(json));
|
||||
yajl_free(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* free()s the color strings as soon as they are not needed anymore.
|
||||
*
|
||||
|
@ -85,6 +85,20 @@ static void got_output_reply(char *reply) {
|
||||
*
|
||||
*/
|
||||
static void got_bar_config(char *reply) {
|
||||
if (!config.bar_id) {
|
||||
DLOG("Received bar list \"%s\"\n", reply);
|
||||
parse_get_first_i3bar_config(reply);
|
||||
|
||||
if (!config.bar_id) {
|
||||
ELOG("No bar configuration found, please configure a bar block in your i3 config file.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
LOG("Using first bar config: %s. Use --bar_id to manually select a different bar configuration.\n", config.bar_id);
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id);
|
||||
return;
|
||||
}
|
||||
|
||||
DLOG("Received bar config \"%s\"\n", reply);
|
||||
/* We initiate the main function by requesting infos about the outputs and
|
||||
* workspaces. Everything else (creating the bars, showing the right workspace-
|
||||
@ -141,9 +155,6 @@ static void got_workspace_event(char *event) {
|
||||
static void got_output_event(char *event) {
|
||||
DLOG("Got output event!\n");
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
|
||||
if (!config.disable_ws) {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -156,6 +167,15 @@ static void got_mode_event(char *event) {
|
||||
draw_bars(false);
|
||||
}
|
||||
|
||||
static bool strings_differ(char *a, char *b) {
|
||||
const bool a_null = (a == NULL);
|
||||
const bool b_null = (b == NULL);
|
||||
if (a_null != b_null) {
|
||||
return true;
|
||||
}
|
||||
return strcmp(a, b) != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called, when a barconfig_update event arrives (i.e. i3 changed the bar hidden_state or mode)
|
||||
*
|
||||
@ -176,8 +196,11 @@ static void got_bar_config_update(char *event) {
|
||||
|
||||
/* update the configuration with the received settings */
|
||||
DLOG("Received bar config update \"%s\"\n", event);
|
||||
char *old_command = config.command ? sstrdup(config.command) : NULL;
|
||||
|
||||
char *old_command = config.command;
|
||||
config.command = NULL;
|
||||
bar_display_mode_t old_mode = config.hide_on_modifier;
|
||||
|
||||
parse_config_json(event);
|
||||
if (old_mode != config.hide_on_modifier) {
|
||||
reconfig_windows(true);
|
||||
@ -188,8 +211,9 @@ static void got_bar_config_update(char *event) {
|
||||
init_colors(&(config.colors));
|
||||
|
||||
/* restart status command process */
|
||||
if (old_command && strcmp(old_command, config.command) != 0) {
|
||||
if (strings_differ(old_command, config.command)) {
|
||||
kill_child();
|
||||
clear_statusline(&statusline_head, true);
|
||||
start_child(config.command);
|
||||
}
|
||||
free(old_command);
|
||||
@ -328,13 +352,12 @@ int i3_send_msg(uint32_t type, const char *payload) {
|
||||
* socket_path must be a valid path to the ipc_socket of i3
|
||||
*
|
||||
*/
|
||||
int init_connection(const char *socket_path) {
|
||||
void init_connection(const char *socket_path) {
|
||||
sock_path = socket_path;
|
||||
int sockfd = ipc_connect(socket_path);
|
||||
i3_connection = smalloc(sizeof(ev_io));
|
||||
ev_io_init(i3_connection, &got_data, sockfd, EV_READ);
|
||||
ev_io_start(main_loop, i3_connection);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -56,9 +56,9 @@ static char *expand_path(char *path) {
|
||||
}
|
||||
|
||||
static void print_usage(char *elf_name) {
|
||||
printf("Usage: %s -b bar_id [-s sock_path] [-t] [-h] [-v]\n", elf_name);
|
||||
printf("Usage: %s [-b bar_id] [-s sock_path] [-t] [-h] [-v] [-V]\n", elf_name);
|
||||
printf("\n");
|
||||
printf("-b, --bar_id <bar_id>\tBar ID for which to get the configuration\n");
|
||||
printf("-b, --bar_id <bar_id>\tBar ID for which to get the configuration, defaults to the first bar from the i3 config\n");
|
||||
printf("-s, --socket <sock_path>\tConnect to i3 via <sock_path>\n");
|
||||
printf("-t, --transparency Enable transparency (RGBA colors)\n");
|
||||
printf("-h, --help Display this help message and exit\n");
|
||||
@ -128,17 +128,12 @@ int main(int argc, char **argv) {
|
||||
break;
|
||||
default:
|
||||
print_usage(argv[0]);
|
||||
exit(EXIT_SUCCESS);
|
||||
exit(EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.bar_id) {
|
||||
/* TODO: maybe we want -f which will automatically ask i3 for the first
|
||||
* configured bar (and error out if there are too many)? */
|
||||
ELOG("No bar_id passed. Please let i3 start i3bar or specify --bar_id\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
LOG("i3bar version " I3_VERSION "\n");
|
||||
|
||||
main_loop = ev_default_loop(0); /* needed in init_xcb_early */
|
||||
char *atom_sock_path = init_xcb_early();
|
||||
@ -166,10 +161,13 @@ int main(int argc, char **argv) {
|
||||
init_dpi();
|
||||
|
||||
init_outputs();
|
||||
if (init_connection(socket_path)) {
|
||||
/* Request the bar configuration. When it arrives, we fill the config array. */
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id);
|
||||
}
|
||||
|
||||
init_connection(socket_path);
|
||||
/* Request the bar configuration. When it arrives, we fill the config
|
||||
* array. In case that config.bar_id is empty, we will receive a list of
|
||||
* available configs and then request the configuration for the first bar.
|
||||
* See got_bar_config for more. */
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id);
|
||||
free(socket_path);
|
||||
|
||||
/* We listen to SIGTERM/QUIT/INT and try to exit cleanly, by stopping the main loop.
|
||||
|
@ -1532,7 +1532,6 @@ void clean_xcb(void) {
|
||||
free_font();
|
||||
|
||||
xcb_free_cursor(xcb_connection, cursor);
|
||||
xcb_flush(xcb_connection);
|
||||
xcb_aux_sync(xcb_connection);
|
||||
xcb_disconnect(xcb_connection);
|
||||
|
||||
|
@ -56,7 +56,6 @@
|
||||
#include "render.h"
|
||||
#include "window.h"
|
||||
#include "match.h"
|
||||
#include "cmdparse.h"
|
||||
#include "xcursor.h"
|
||||
#include "resize.h"
|
||||
#include "sighandler.h"
|
||||
|
@ -1,14 +0,0 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* cmdparse.y: the parser for commands you send to i3 (or bind on keys)
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <config.h>
|
||||
|
||||
char *parse_cmd(const char *new);
|
@ -138,7 +138,7 @@ void cmd_mode(I3_CMD, const char *mode);
|
||||
* Implementation of 'move [window|container] [to] output <str>'.
|
||||
*
|
||||
*/
|
||||
void cmd_move_con_to_output(I3_CMD, const char *name);
|
||||
void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace);
|
||||
|
||||
/**
|
||||
* Implementation of 'move [window|container] [to] mark <str>'.
|
||||
@ -152,12 +152,6 @@ void cmd_move_con_to_mark(I3_CMD, const char *mark);
|
||||
*/
|
||||
void cmd_floating(I3_CMD, const char *floating_mode);
|
||||
|
||||
/**
|
||||
* Implementation of 'move workspace to [output] <str>'.
|
||||
*
|
||||
*/
|
||||
void cmd_move_workspace_to_output(I3_CMD, const char *name);
|
||||
|
||||
/**
|
||||
* Implementation of 'split v|h|t|vertical|horizontal|toggle'.
|
||||
*
|
||||
@ -337,3 +331,9 @@ void cmd_shmlog(I3_CMD, const char *argument);
|
||||
*
|
||||
*/
|
||||
void cmd_debuglog(I3_CMD, const char *argument);
|
||||
|
||||
/**
|
||||
* Implementation of 'title_window_icon <yes|no>' and 'title_window_icon padding <px>'
|
||||
*
|
||||
*/
|
||||
void cmd_title_window_icon(I3_CMD, const char *enable, int padding);
|
||||
|
@ -39,6 +39,7 @@ CFGFUN(criteria_init, int _state);
|
||||
CFGFUN(criteria_add, const char *ctype, const char *cvalue);
|
||||
CFGFUN(criteria_pop_state);
|
||||
|
||||
CFGFUN(include, const char *pattern);
|
||||
CFGFUN(font, const char *font);
|
||||
CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command);
|
||||
CFGFUN(for_window, const char *command);
|
||||
|
@ -16,6 +16,50 @@
|
||||
SLIST_HEAD(variables_head, Variable);
|
||||
extern pid_t config_error_nagbar_pid;
|
||||
|
||||
struct stack_entry {
|
||||
/* Just a pointer, not dynamically allocated. */
|
||||
const char *identifier;
|
||||
enum {
|
||||
STACK_STR = 0,
|
||||
STACK_LONG = 1,
|
||||
} type;
|
||||
union {
|
||||
char *str;
|
||||
long num;
|
||||
} val;
|
||||
};
|
||||
|
||||
struct stack {
|
||||
struct stack_entry stack[10];
|
||||
};
|
||||
|
||||
struct parser_ctx {
|
||||
bool use_nagbar;
|
||||
bool assume_v4;
|
||||
|
||||
int state;
|
||||
Match current_match;
|
||||
|
||||
/* A list which contains the states that lead to the current state, e.g.
|
||||
* INITIAL, WORKSPACE_LAYOUT.
|
||||
* When jumping back to INITIAL, statelist_idx will simply be set to 1
|
||||
* (likewise for other states, e.g. MODE or BAR).
|
||||
* This list is used to process the nearest error token. */
|
||||
int statelist[10];
|
||||
/* NB: statelist_idx points to where the next entry will be inserted */
|
||||
int statelist_idx;
|
||||
|
||||
/*******************************************************************************
|
||||
* The (small) stack where identified literals are stored during the parsing
|
||||
* of a single config directive (like $workspace).
|
||||
******************************************************************************/
|
||||
struct stack *stack;
|
||||
|
||||
struct variables_head variables;
|
||||
|
||||
bool has_errors;
|
||||
};
|
||||
|
||||
/**
|
||||
* An intermediate reprsentation of the result of a parse_config call.
|
||||
* Currently unused, but the JSON output will be useful in the future when we
|
||||
@ -23,22 +67,34 @@ extern pid_t config_error_nagbar_pid;
|
||||
*
|
||||
*/
|
||||
struct ConfigResultIR {
|
||||
/* The JSON generator to append a reply to. */
|
||||
yajl_gen json_gen;
|
||||
struct parser_ctx *ctx;
|
||||
|
||||
/* The next state to transition to. Passed to the function so that we can
|
||||
* determine the next state as a result of a function call, like
|
||||
* cfg_criteria_pop_state() does. */
|
||||
int next_state;
|
||||
};
|
||||
|
||||
struct ConfigResultIR *parse_config(const char *input, struct context *context);
|
||||
/* Whether any error happened while processing this config directive. */
|
||||
bool has_errors;
|
||||
};
|
||||
|
||||
/**
|
||||
* launch nagbar to indicate errors in the configuration file.
|
||||
*/
|
||||
void start_config_error_nagbar(const char *configpath, bool has_errors);
|
||||
|
||||
/**
|
||||
* Releases the memory of all variables in ctx.
|
||||
*
|
||||
*/
|
||||
void free_variables(struct parser_ctx *ctx);
|
||||
|
||||
typedef enum {
|
||||
PARSE_FILE_FAILED = -1,
|
||||
PARSE_FILE_SUCCESS = 0,
|
||||
PARSE_FILE_CONFIG_ERRORS = 1,
|
||||
} parse_file_result_t;
|
||||
|
||||
/**
|
||||
* Parses the given file by first replacing the variables, then calling
|
||||
* parse_config and launching i3-nagbar if use_nagbar is true.
|
||||
@ -47,4 +103,4 @@ void start_config_error_nagbar(const char *configpath, bool has_errors);
|
||||
* parsing.
|
||||
*
|
||||
*/
|
||||
bool parse_file(const char *f, bool use_nagbar);
|
||||
parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFile *included_file);
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "queue.h"
|
||||
#include "i3.h"
|
||||
|
||||
typedef struct IncludedFile IncludedFile;
|
||||
typedef struct Config Config;
|
||||
typedef struct Barconfig Barconfig;
|
||||
extern char *current_configpath;
|
||||
@ -22,6 +23,7 @@ extern char *current_config;
|
||||
extern Config config;
|
||||
extern SLIST_HEAD(modes_head, Mode) modes;
|
||||
extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;
|
||||
extern TAILQ_HEAD(includedfiles_head, IncludedFile) included_files;
|
||||
|
||||
/**
|
||||
* Used during the config file lexing/parsing to keep the state of the lexer
|
||||
@ -69,6 +71,18 @@ struct Variable {
|
||||
SLIST_ENTRY(Variable) variables;
|
||||
};
|
||||
|
||||
/**
|
||||
* List entry struct for an included file.
|
||||
*
|
||||
*/
|
||||
struct IncludedFile {
|
||||
char *path;
|
||||
char *raw_contents;
|
||||
char *variable_replaced_contents;
|
||||
|
||||
TAILQ_ENTRY(IncludedFile) files;
|
||||
};
|
||||
|
||||
/**
|
||||
* The configuration file can contain multiple sets of bindings. Apart from the
|
||||
* default set (name == "default"), you can specify other sets and change the
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <xcb/randr.h>
|
||||
#include <pcre.h>
|
||||
#include <sys/time.h>
|
||||
#include <cairo/cairo.h>
|
||||
|
||||
#include "queue.h"
|
||||
|
||||
@ -414,6 +415,9 @@ struct Window {
|
||||
* for_window. */
|
||||
char *role;
|
||||
|
||||
/** WM_CLIENT_MACHINE of the window */
|
||||
char *machine;
|
||||
|
||||
/** Flag to force re-rendering the decoration upon changes */
|
||||
bool name_x_changed;
|
||||
|
||||
@ -468,6 +472,9 @@ struct Window {
|
||||
double min_aspect_ratio;
|
||||
double max_aspect_ratio;
|
||||
|
||||
/** Window icon, as Cairo surface */
|
||||
cairo_surface_t *icon;
|
||||
|
||||
/** The window has a nonrectangular shape. */
|
||||
bool shaped;
|
||||
/** The window has a nonrectangular input shape. */
|
||||
@ -500,6 +507,7 @@ struct Match {
|
||||
struct regex *mark;
|
||||
struct regex *window_role;
|
||||
struct regex *workspace;
|
||||
struct regex *machine;
|
||||
xcb_atom_t window_type;
|
||||
enum {
|
||||
U_DONTCHECK = -1,
|
||||
@ -522,6 +530,7 @@ struct Match {
|
||||
WM_FLOATING_USER,
|
||||
WM_FLOATING } window_mode;
|
||||
Con *con_id;
|
||||
bool match_all_windows;
|
||||
|
||||
/* Where the window looking for a match should be inserted:
|
||||
*
|
||||
@ -652,6 +661,11 @@ struct Con {
|
||||
/** The format with which the window's name should be displayed. */
|
||||
char *title_format;
|
||||
|
||||
/** Whether the window icon should be displayed, and with what padding. -1
|
||||
* means display no window icon (default behavior), 0 means display without
|
||||
* any padding, 1 means display with 1 pixel of padding and so on. */
|
||||
int window_icon_padding;
|
||||
|
||||
/* a sticky-group is an identifier which bundles several containers to a
|
||||
* group. The contents are shared between all of them, that is they are
|
||||
* displayed on whichever of the containers is currently visible */
|
||||
|
@ -3,6 +3,7 @@
|
||||
xmacro(_NET_WM_USER_TIME) \
|
||||
xmacro(_NET_STARTUP_ID) \
|
||||
xmacro(_NET_WORKAREA) \
|
||||
xmacro(_NET_WM_ICON) \
|
||||
xmacro(WM_PROTOCOLS) \
|
||||
xmacro(WM_DELETE_WINDOW) \
|
||||
xmacro(UTF8_STRING) \
|
||||
@ -15,8 +16,10 @@ xmacro(I3_CONFIG_PATH) \
|
||||
xmacro(I3_SYNC) \
|
||||
xmacro(I3_SHMLOG_PATH) \
|
||||
xmacro(I3_PID) \
|
||||
xmacro(I3_LOG_STREAM_SOCKET_PATH) \
|
||||
xmacro(I3_FLOATING_WINDOW) \
|
||||
xmacro(_NET_REQUEST_FRAME_EXTENTS) \
|
||||
xmacro(_NET_FRAME_EXTENTS) \
|
||||
xmacro(_MOTIF_WM_HINTS) \
|
||||
xmacro(WM_CHANGE_STATE)
|
||||
xmacro(WM_CHANGE_STATE) \
|
||||
xmacro(MANAGER)
|
||||
|
@ -39,6 +39,7 @@ extern bool debug_build;
|
||||
/** The number of file descriptors passed via socket activation. */
|
||||
extern int listen_fds;
|
||||
extern int conn_screen;
|
||||
extern xcb_atom_t wm_sn;
|
||||
/**
|
||||
* The EWMH support window that is used to indicate that an EWMH-compliant
|
||||
* window manager is present. This window is created when i3 starts and
|
||||
|
@ -80,13 +80,6 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents);
|
||||
*/
|
||||
ipc_client *ipc_new_client_on_fd(EV_P_ int fd);
|
||||
|
||||
/**
|
||||
* Creates the UNIX domain socket at the given path, sets it to non-blocking
|
||||
* mode, bind()s and listen()s on it.
|
||||
*
|
||||
*/
|
||||
int ipc_create_socket(const char *filename);
|
||||
|
||||
/**
|
||||
* Sends the specified event to all IPC clients which are currently connected
|
||||
* and subscribed to this kind of event.
|
||||
|
@ -445,18 +445,13 @@ bool font_is_pango(void);
|
||||
* specified coordinates (from the top left corner of the leftmost, uppermost
|
||||
* glyph) and using the provided gc.
|
||||
*
|
||||
* The given cairo surface must refer to the specified X drawable.
|
||||
*
|
||||
* Text must be specified as an i3String.
|
||||
*
|
||||
*/
|
||||
void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc,
|
||||
xcb_visualtype_t *visual, int x, int y, int max_width);
|
||||
|
||||
/**
|
||||
* ASCII version of draw_text to print static strings.
|
||||
*
|
||||
*/
|
||||
void draw_text_ascii(const char *text, xcb_drawable_t drawable,
|
||||
xcb_gcontext_t gc, int x, int y, int max_width);
|
||||
cairo_surface_t *surface, int x, int y, int max_width);
|
||||
|
||||
/**
|
||||
* Predict the text width in pixels for the given text. Text must be
|
||||
@ -571,8 +566,6 @@ typedef struct surface_t {
|
||||
/* A classic XCB graphics context. */
|
||||
xcb_gcontext_t gc;
|
||||
|
||||
xcb_visualtype_t *visual_type;
|
||||
|
||||
int width;
|
||||
int height;
|
||||
|
||||
@ -618,6 +611,11 @@ color_t draw_util_hex_to_color(const char *color);
|
||||
*/
|
||||
void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_t bg_color, int x, int y, int max_width);
|
||||
|
||||
/**
|
||||
* Draw the given image using libi3.
|
||||
*/
|
||||
void draw_util_image(cairo_surface_t *image, surface_t *surface, int x, int y, int width, int height);
|
||||
|
||||
/**
|
||||
* Draws a filled rectangle.
|
||||
* This function is a convenience wrapper and takes care of flushing the
|
||||
@ -638,3 +636,46 @@ void draw_util_clear_surface(surface_t *surface, color_t color);
|
||||
*/
|
||||
void draw_util_copy_surface(surface_t *src, surface_t *dest, double src_x, double src_y,
|
||||
double dest_x, double dest_y, double width, double height);
|
||||
|
||||
/**
|
||||
* Puts the given socket file descriptor into non-blocking mode or dies if
|
||||
* setting O_NONBLOCK failed. Non-blocking sockets are a good idea for our
|
||||
* IPC model because we should by no means block the window manager.
|
||||
*
|
||||
*/
|
||||
void set_nonblock(int sockfd);
|
||||
|
||||
/**
|
||||
* Creates the UNIX domain socket at the given path, sets it to non-blocking
|
||||
* mode, bind()s and listen()s on it.
|
||||
*
|
||||
* The full path to the socket is stored in the char* that out_socketpath points
|
||||
* to.
|
||||
*
|
||||
*/
|
||||
int create_socket(const char *filename, char **out_socketpath);
|
||||
|
||||
/**
|
||||
* Checks if the given path exists by calling stat().
|
||||
*
|
||||
*/
|
||||
bool path_exists(const char *path);
|
||||
|
||||
/**
|
||||
* Grab a screenshot of the screen's root window and set it as the wallpaper.
|
||||
*/
|
||||
void set_screenshot_as_wallpaper(xcb_connection_t *conn, xcb_screen_t *screen);
|
||||
|
||||
/**
|
||||
* Test whether the screen's root window has a background set.
|
||||
*
|
||||
* This opens & closes a window and test whether the root window still shows the
|
||||
* content of the window.
|
||||
*/
|
||||
bool is_background_set(xcb_connection_t *conn, xcb_screen_t *screen);
|
||||
|
||||
/**
|
||||
* Reports whether str represents the enabled state (1, yes, true, …).
|
||||
*
|
||||
*/
|
||||
bool boolstr(const char *str);
|
||||
|
@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <config.h>
|
||||
#include <ev.h>
|
||||
|
||||
/* We will include libi3.h which define its own version of LOG, ELOG.
|
||||
* We want *our* version, so we undef the libi3 one. */
|
||||
@ -31,6 +32,7 @@
|
||||
extern char *errorfilename;
|
||||
extern char *shmlogname;
|
||||
extern int shmlog_size;
|
||||
extern char *current_log_stream_socket_path;
|
||||
|
||||
/**
|
||||
* Initializes logging by creating an error logfile in /tmp (or
|
||||
@ -100,3 +102,5 @@ void verboselog(char *fmt, ...)
|
||||
* failures. This function is invoked automatically when exiting.
|
||||
*/
|
||||
void purge_zerobyte_logfile(void);
|
||||
|
||||
void log_new_client(EV_P_ struct ev_io *w, int revents);
|
||||
|
@ -12,10 +12,6 @@
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#if !defined(__OpenBSD__)
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
/* Default shmlog size if not set by user. */
|
||||
extern const int default_shmlog_size;
|
||||
|
||||
@ -39,11 +35,4 @@ typedef struct i3_shmlog_header {
|
||||
* coincidentally be exactly the same as previously). Overflows can happen
|
||||
* and don’t matter — clients use an equality check (==). */
|
||||
uint32_t wrap_count;
|
||||
|
||||
#if !defined(__OpenBSD__)
|
||||
/* pthread condvar which will be broadcasted whenever there is a new
|
||||
* message in the log. i3-dump-log uses this to implement -f (follow, like
|
||||
* tail -f) in an efficient way. */
|
||||
pthread_cond_t condvar;
|
||||
#endif
|
||||
} i3_shmlog_header;
|
||||
|
@ -95,3 +95,15 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
|
||||
*
|
||||
*/
|
||||
void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
|
||||
|
||||
/**
|
||||
* Updates the WM_CLIENT_MACHINE
|
||||
*
|
||||
*/
|
||||
void window_update_machine(i3Window *win, xcb_get_property_reply_t *prop);
|
||||
|
||||
/**
|
||||
* Updates the _NET_WM_ICON
|
||||
*
|
||||
*/
|
||||
void window_update_icon(i3Window *win, xcb_get_property_reply_t *prop);
|
||||
|
25
libi3/boolstr.c
Normal file
25
libi3/boolstr.c
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
*/
|
||||
#include "libi3.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/*
|
||||
* Reports whether str represents the enabled state (1, yes, true, …).
|
||||
*
|
||||
*/
|
||||
bool boolstr(const char *str) {
|
||||
return (strcasecmp(str, "1") == 0 ||
|
||||
strcasecmp(str, "yes") == 0 ||
|
||||
strcasecmp(str, "true") == 0 ||
|
||||
strcasecmp(str, "on") == 0 ||
|
||||
strcasecmp(str, "enable") == 0 ||
|
||||
strcasecmp(str, "active") == 0);
|
||||
}
|
70
libi3/create_socket.c
Normal file
70
libi3/create_socket.c
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
*/
|
||||
#include "libi3.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <libgen.h>
|
||||
#include <err.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
/*
|
||||
* Creates the UNIX domain socket at the given path, sets it to non-blocking
|
||||
* mode, bind()s and listen()s on it.
|
||||
*
|
||||
* The full path to the socket is stored in the char* that out_socketpath points
|
||||
* to.
|
||||
*
|
||||
*/
|
||||
int create_socket(const char *filename, char **out_socketpath) {
|
||||
char *resolved = resolve_tilde(filename);
|
||||
DLOG("Creating UNIX socket at %s\n", resolved);
|
||||
char *copy = sstrdup(resolved);
|
||||
const char *dir = dirname(copy);
|
||||
if (!path_exists(dir)) {
|
||||
mkdirp(dir, DEFAULT_DIR_MODE);
|
||||
}
|
||||
free(copy);
|
||||
|
||||
/* Unlink the unix domain socket before */
|
||||
unlink(resolved);
|
||||
|
||||
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
|
||||
if (sockfd < 0) {
|
||||
perror("socket()");
|
||||
free(resolved);
|
||||
return -1;
|
||||
}
|
||||
|
||||
(void)fcntl(sockfd, F_SETFD, FD_CLOEXEC);
|
||||
|
||||
struct sockaddr_un addr;
|
||||
memset(&addr, 0, sizeof(struct sockaddr_un));
|
||||
addr.sun_family = AF_LOCAL;
|
||||
strncpy(addr.sun_path, resolved, sizeof(addr.sun_path) - 1);
|
||||
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
|
||||
perror("bind()");
|
||||
free(resolved);
|
||||
return -1;
|
||||
}
|
||||
|
||||
set_nonblock(sockfd);
|
||||
|
||||
if (listen(sockfd, 5) < 0) {
|
||||
perror("listen()");
|
||||
free(resolved);
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(*out_socketpath);
|
||||
*out_socketpath = resolved;
|
||||
return sockfd;
|
||||
}
|
@ -35,19 +35,22 @@ static void draw_util_set_source_color(surface_t *surface, color_t color);
|
||||
void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_drawable_t drawable,
|
||||
xcb_visualtype_t *visual, int width, int height) {
|
||||
surface->id = drawable;
|
||||
surface->visual_type = ((visual == NULL) ? visual_type : visual);
|
||||
surface->width = width;
|
||||
surface->height = height;
|
||||
|
||||
if (visual == NULL)
|
||||
visual = visual_type;
|
||||
|
||||
surface->gc = xcb_generate_id(conn);
|
||||
xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(conn, surface->gc, surface->id, 0, NULL);
|
||||
|
||||
xcb_generic_error_t *error = xcb_request_check(conn, gc_cookie);
|
||||
if (error != NULL) {
|
||||
ELOG("Could not create graphical context. Error code: %d. Please report this bug.\n", error->error_code);
|
||||
free(error);
|
||||
}
|
||||
|
||||
surface->surface = cairo_xcb_surface_create(conn, surface->id, surface->visual_type, width, height);
|
||||
surface->surface = cairo_xcb_surface_create(conn, surface->id, visual, width, height);
|
||||
surface->cr = cairo_create(surface->surface);
|
||||
}
|
||||
|
||||
@ -56,6 +59,19 @@ void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_draw
|
||||
*
|
||||
*/
|
||||
void draw_util_surface_free(xcb_connection_t *conn, surface_t *surface) {
|
||||
cairo_status_t status = CAIRO_STATUS_SUCCESS;
|
||||
if (surface->cr) {
|
||||
status = cairo_status(surface->cr);
|
||||
}
|
||||
if (status != CAIRO_STATUS_SUCCESS) {
|
||||
LOG("Found cairo context in an error status while freeing, error %d is %s",
|
||||
status, cairo_status_to_string(status));
|
||||
}
|
||||
|
||||
/* NOTE: This function is also called on uninitialised surface_t instances.
|
||||
* The x11 error from xcb_free_gc(conn, XCB_NONE) is silently ignored
|
||||
* elsewhere.
|
||||
*/
|
||||
xcb_free_gc(conn, surface->gc);
|
||||
cairo_surface_destroy(surface->surface);
|
||||
cairo_destroy(surface->cr);
|
||||
@ -133,12 +149,36 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_
|
||||
CAIRO_SURFACE_FLUSH(surface->surface);
|
||||
|
||||
set_font_colors(surface->gc, fg_color, bg_color);
|
||||
draw_text(text, surface->id, surface->gc, surface->visual_type, x, y, max_width);
|
||||
draw_text(text, surface->id, surface->gc, surface->surface, x, y, max_width);
|
||||
|
||||
/* Notify cairo that we (possibly) used another way to draw on the surface. */
|
||||
cairo_surface_mark_dirty(surface->surface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the given image using libi3.
|
||||
* This function is a convenience wrapper and takes care of flushing the
|
||||
* surface as well as restoring the cairo state.
|
||||
*
|
||||
*/
|
||||
void draw_util_image(cairo_surface_t *image, surface_t *surface, int x, int y, int width, int height) {
|
||||
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
|
||||
|
||||
cairo_save(surface->cr);
|
||||
|
||||
cairo_translate(surface->cr, x, y);
|
||||
|
||||
const int src_width = cairo_image_surface_get_width(image);
|
||||
const int src_height = cairo_image_surface_get_height(image);
|
||||
double scale = MIN((double)width / src_width, (double)height / src_height);
|
||||
cairo_scale(surface->cr, scale, scale);
|
||||
|
||||
cairo_set_source_surface(surface->cr, image, 0, 0);
|
||||
cairo_paint(surface->cr);
|
||||
|
||||
cairo_restore(surface->cr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Draws a filled rectangle.
|
||||
* This function is a convenience wrapper and takes care of flushing the
|
||||
|
49
libi3/font.c
49
libi3/font.c
@ -82,12 +82,10 @@ static bool load_pango_font(i3Font *font, const char *desc) {
|
||||
*
|
||||
*/
|
||||
static void draw_text_pango(const char *text, size_t text_len,
|
||||
xcb_drawable_t drawable, xcb_visualtype_t *visual, int x, int y,
|
||||
int max_width, bool pango_markup) {
|
||||
xcb_drawable_t drawable, cairo_surface_t *surface,
|
||||
int x, int y, int max_width, bool pango_markup) {
|
||||
/* Create the Pango layout */
|
||||
/* root_visual_type is cached in load_pango_font */
|
||||
cairo_surface_t *surface = cairo_xcb_surface_create(conn, drawable,
|
||||
visual, x + max_width, y + savedFont->height);
|
||||
cairo_t *cr = cairo_create(surface);
|
||||
PangoLayout *layout = create_layout_with_dpi(cr);
|
||||
gint height;
|
||||
@ -115,7 +113,6 @@ static void draw_text_pango(const char *text, size_t text_len,
|
||||
/* Free resources */
|
||||
g_object_unref(layout);
|
||||
cairo_destroy(cr);
|
||||
cairo_surface_destroy(surface);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -358,11 +355,8 @@ static void draw_text_xcb(const xcb_char2b_t *text, size_t text_len, xcb_drawabl
|
||||
*
|
||||
*/
|
||||
void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc,
|
||||
xcb_visualtype_t *visual, int x, int y, int max_width) {
|
||||
cairo_surface_t *surface, int x, int y, int max_width) {
|
||||
assert(savedFont != NULL);
|
||||
if (visual == NULL) {
|
||||
visual = root_visual_type;
|
||||
}
|
||||
|
||||
switch (savedFont->type) {
|
||||
case FONT_TYPE_NONE:
|
||||
@ -375,42 +369,7 @@ void draw_text(i3String *text, xcb_drawable_t drawable, xcb_gcontext_t gc,
|
||||
case FONT_TYPE_PANGO:
|
||||
/* Render the text using Pango */
|
||||
draw_text_pango(i3string_as_utf8(text), i3string_get_num_bytes(text),
|
||||
drawable, visual, x, y, max_width, i3string_is_markup(text));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ASCII version of draw_text to print static strings.
|
||||
*
|
||||
*/
|
||||
void draw_text_ascii(const char *text, xcb_drawable_t drawable,
|
||||
xcb_gcontext_t gc, int x, int y, int max_width) {
|
||||
assert(savedFont != NULL);
|
||||
|
||||
switch (savedFont->type) {
|
||||
case FONT_TYPE_NONE:
|
||||
/* Nothing to do */
|
||||
return;
|
||||
case FONT_TYPE_XCB: {
|
||||
size_t text_len = strlen(text);
|
||||
if (text_len > 255) {
|
||||
/* The text is too long to draw it directly to X */
|
||||
i3String *str = i3string_from_utf8(text);
|
||||
draw_text(str, drawable, gc, NULL, x, y, max_width);
|
||||
i3string_free(str);
|
||||
} else {
|
||||
/* X11 coordinates for fonts start at the baseline */
|
||||
int pos_y = y + savedFont->specific.xcb.info->font_ascent;
|
||||
|
||||
xcb_image_text_8(conn, text_len, drawable, gc, x, pos_y, text);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FONT_TYPE_PANGO:
|
||||
/* Render the text using Pango */
|
||||
draw_text_pango(text, strlen(text),
|
||||
drawable, root_visual_type, x, y, max_width, false);
|
||||
drawable, surface, x, y, max_width, i3string_is_markup(text));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -11,15 +11,6 @@
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
/*
|
||||
* Checks if the given path exists by calling stat().
|
||||
*
|
||||
*/
|
||||
static bool path_exists(const char *path) {
|
||||
struct stat buf;
|
||||
return (stat(path, &buf) == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the path of the first configuration file found. If override_configpath is
|
||||
* specified, that path is returned and saved for further calls. Otherwise,
|
||||
|
@ -27,15 +27,14 @@ char *get_process_filename(const char *prefix) {
|
||||
char *tmp;
|
||||
sasprintf(&tmp, "%s/i3", dir);
|
||||
dir = tmp;
|
||||
struct stat buf;
|
||||
if (stat(dir, &buf) != 0) {
|
||||
if (mkdir(dir, 0700) == -1) {
|
||||
warn("Could not mkdir(%s)", dir);
|
||||
errx(EXIT_FAILURE, "Check permissions of $XDG_RUNTIME_DIR = '%s'",
|
||||
getenv("XDG_RUNTIME_DIR"));
|
||||
perror("mkdir()");
|
||||
return NULL;
|
||||
}
|
||||
/* mkdirp() should prevent race between multiple i3 instances started
|
||||
* in parallel from causing problem */
|
||||
if (mkdirp(dir, 0700) == -1) {
|
||||
warn("Could not mkdirp(%s)", dir);
|
||||
errx(EXIT_FAILURE, "Check permissions of $XDG_RUNTIME_DIR = '%s'",
|
||||
getenv("XDG_RUNTIME_DIR"));
|
||||
perror("mkdirp()");
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
/* If not, we create a (secure) temp directory using the template
|
||||
|
117
libi3/is_background_set.c
Normal file
117
libi3/is_background_set.c
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
*/
|
||||
#include "libi3.h"
|
||||
|
||||
#include <xcb/xcb_aux.h>
|
||||
|
||||
/**
|
||||
* Find the region in the given window that is not covered by a mapped child
|
||||
* window.
|
||||
*/
|
||||
static cairo_region_t *unobscured_region(xcb_connection_t *conn, xcb_window_t window,
|
||||
uint16_t window_width, uint16_t window_height) {
|
||||
cairo_rectangle_int_t rectangle;
|
||||
cairo_region_t *region;
|
||||
|
||||
rectangle.x = 0;
|
||||
rectangle.y = 0;
|
||||
rectangle.width = window_width;
|
||||
rectangle.height = window_height;
|
||||
region = cairo_region_create_rectangle(&rectangle);
|
||||
|
||||
xcb_query_tree_reply_t *tree = xcb_query_tree_reply(conn, xcb_query_tree_unchecked(conn, window), NULL);
|
||||
if (!tree) {
|
||||
return region;
|
||||
}
|
||||
|
||||
/* Get information about children */
|
||||
uint16_t n_children = tree->children_len;
|
||||
xcb_window_t *children = xcb_query_tree_children(tree);
|
||||
|
||||
xcb_get_geometry_cookie_t geometries[n_children];
|
||||
xcb_get_window_attributes_cookie_t attributes[n_children];
|
||||
|
||||
for (int i = 0; i < n_children; i++) {
|
||||
geometries[i] = xcb_get_geometry_unchecked(conn, children[i]);
|
||||
attributes[i] = xcb_get_window_attributes_unchecked(conn, children[i]);
|
||||
}
|
||||
|
||||
/* Remove every visible child from the region */
|
||||
for (int i = 0; i < n_children; i++) {
|
||||
xcb_get_geometry_reply_t *geom = xcb_get_geometry_reply(conn, geometries[i], NULL);
|
||||
xcb_get_window_attributes_reply_t *attr = xcb_get_window_attributes_reply(conn, attributes[i], NULL);
|
||||
|
||||
if (geom && attr && attr->map_state == XCB_MAP_STATE_VIEWABLE) {
|
||||
rectangle.x = geom->x;
|
||||
rectangle.y = geom->y;
|
||||
rectangle.width = geom->width;
|
||||
rectangle.height = geom->height;
|
||||
cairo_region_subtract_rectangle(region, &rectangle);
|
||||
}
|
||||
|
||||
free(geom);
|
||||
free(attr);
|
||||
}
|
||||
|
||||
free(tree);
|
||||
return region;
|
||||
}
|
||||
|
||||
static void find_unobscured_pixel(xcb_connection_t *conn, xcb_window_t window,
|
||||
uint16_t window_width, uint16_t window_height,
|
||||
uint16_t *x, uint16_t *y) {
|
||||
cairo_region_t *region = unobscured_region(conn, window, window_width, window_height);
|
||||
if (cairo_region_num_rectangles(region) > 0) {
|
||||
/* Return the top left pixel of the first rectangle */
|
||||
cairo_rectangle_int_t rect;
|
||||
cairo_region_get_rectangle(region, 0, &rect);
|
||||
*x = rect.x;
|
||||
*y = rect.y;
|
||||
} else {
|
||||
/* No unobscured area found */
|
||||
*x = 0;
|
||||
*y = 0;
|
||||
}
|
||||
cairo_region_destroy(region);
|
||||
}
|
||||
|
||||
static uint32_t flicker_window_at(xcb_connection_t *conn, xcb_screen_t *screen, int16_t x, int16_t y, xcb_window_t window,
|
||||
uint32_t pixel) {
|
||||
xcb_create_window(conn, XCB_COPY_FROM_PARENT, window, screen->root, x, y, 10, 10,
|
||||
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT,
|
||||
XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT, (uint32_t[]){pixel, 1});
|
||||
xcb_map_window(conn, window);
|
||||
xcb_clear_area(conn, 0, window, 0, 0, 0, 0);
|
||||
xcb_aux_sync(conn);
|
||||
xcb_destroy_window(conn, window);
|
||||
|
||||
xcb_get_image_reply_t *img = xcb_get_image_reply(conn,
|
||||
xcb_get_image_unchecked(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, screen->root, x, y, 1, 1, ~0),
|
||||
NULL);
|
||||
uint32_t result = 0;
|
||||
if (img) {
|
||||
uint8_t *data = xcb_get_image_data(img);
|
||||
uint8_t depth = img->depth;
|
||||
for (int i = 0; i < MIN(depth, 4); i++) {
|
||||
result = (result << 8) | data[i];
|
||||
}
|
||||
free(img);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool is_background_set(xcb_connection_t *conn, xcb_screen_t *screen) {
|
||||
uint16_t x, y;
|
||||
find_unobscured_pixel(conn, screen->root, screen->width_in_pixels, screen->height_in_pixels, &x, &y);
|
||||
|
||||
xcb_window_t window = xcb_generate_id(conn);
|
||||
|
||||
uint32_t pixel1 = flicker_window_at(conn, screen, x, y, window, screen->black_pixel);
|
||||
uint32_t pixel2 = flicker_window_at(conn, screen, x, y, window, screen->white_pixel);
|
||||
return pixel1 == pixel2;
|
||||
}
|
21
libi3/nonblock.c
Normal file
21
libi3/nonblock.c
Normal file
@ -0,0 +1,21 @@
|
||||
#include "libi3.h"
|
||||
|
||||
#include <err.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
/*
|
||||
* Puts the given socket file descriptor into non-blocking mode or dies if
|
||||
* setting O_NONBLOCK failed. Non-blocking sockets are a good idea for our
|
||||
* IPC model because we should by no means block the window manager.
|
||||
*
|
||||
*/
|
||||
void set_nonblock(int sockfd) {
|
||||
int flags = fcntl(sockfd, F_GETFL, 0);
|
||||
if (flags & O_NONBLOCK) {
|
||||
return;
|
||||
}
|
||||
flags |= O_NONBLOCK;
|
||||
if (fcntl(sockfd, F_SETFL, flags) < 0) {
|
||||
err(-1, "Could not set O_NONBLOCK");
|
||||
}
|
||||
}
|
21
libi3/path_exists.c
Normal file
21
libi3/path_exists.c
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
*/
|
||||
#include "libi3.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/*
|
||||
* Checks if the given path exists by calling stat().
|
||||
*
|
||||
*/
|
||||
bool path_exists(const char *path) {
|
||||
struct stat buf;
|
||||
return (stat(path, &buf) == 0);
|
||||
}
|
27
libi3/screenshot_wallpaper.c
Normal file
27
libi3/screenshot_wallpaper.c
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
*/
|
||||
#include "libi3.h"
|
||||
|
||||
void set_screenshot_as_wallpaper(xcb_connection_t *conn, xcb_screen_t *screen) {
|
||||
uint16_t width = screen->width_in_pixels;
|
||||
uint16_t height = screen->height_in_pixels;
|
||||
xcb_pixmap_t pixmap = xcb_generate_id(conn);
|
||||
xcb_gcontext_t gc = xcb_generate_id(conn);
|
||||
|
||||
xcb_create_pixmap(conn, screen->root_depth, pixmap, screen->root, width, height);
|
||||
|
||||
xcb_create_gc(conn, gc, screen->root,
|
||||
XCB_GC_FUNCTION | XCB_GC_PLANE_MASK | XCB_GC_FILL_STYLE | XCB_GC_SUBWINDOW_MODE,
|
||||
(uint32_t[]){XCB_GX_COPY, ~0, XCB_FILL_STYLE_SOLID, XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS});
|
||||
|
||||
xcb_copy_area(conn, screen->root, pixmap, gc, 0, 0, 0, 0, width, height);
|
||||
xcb_change_window_attributes(conn, screen->root, XCB_CW_BACK_PIXMAP, (uint32_t[]){pixmap});
|
||||
xcb_free_gc(conn, gc);
|
||||
xcb_free_pixmap(conn, pixmap);
|
||||
xcb_flush(conn);
|
||||
}
|
46
logo.svg
46
logo.svg
@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
@ -13,9 +12,10 @@
|
||||
height="297mm"
|
||||
id="svg2"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.46"
|
||||
sodipodi:docname="logo_i3_linuxfr_bapt_v2.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape">
|
||||
inkscape:version="1.0.1 (c497b03c, 2020-09-10)"
|
||||
sodipodi:docname="logo.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
version="1.1">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
@ -39,10 +39,10 @@
|
||||
</linearGradient>
|
||||
<inkscape:perspective
|
||||
id="perspective3661"
|
||||
inkscape:persp3d-origin="750.50629 : 505.26732 : 1"
|
||||
inkscape:vp_z="683.5728 : 1230.5721 : 1"
|
||||
inkscape:vp_y="0 : 1946.8917 : 0"
|
||||
inkscape:vp_x="-526.84957 : 2.2065866e-13 : 0"
|
||||
inkscape:persp3d-origin="800.54004 : 538.95181 : 1"
|
||||
inkscape:vp_z="729.14432 : 1312.6102 : 1"
|
||||
inkscape:vp_y="0 : 2076.6845 : 0"
|
||||
inkscape:vp_x="-561.97287 : 2.3536924e-13 : 0"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<linearGradient
|
||||
id="linearGradient3284">
|
||||
@ -112,10 +112,10 @@
|
||||
</linearGradient>
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="-526.84957 : 2.9848654e-13 : 0"
|
||||
inkscape:vp_y="1.192088e-13 : 1946.8917 : 0"
|
||||
inkscape:vp_z="680.54236 : 1232.3792 : 1"
|
||||
inkscape:persp3d-origin="730.30325 : 937.39936 : 1"
|
||||
inkscape:vp_x="-561.97287 : 3.1838564e-13 : 0"
|
||||
inkscape:vp_y="1.2715605e-13 : 2076.6845 : 0"
|
||||
inkscape:vp_z="725.91185 : 1314.5378 : 1"
|
||||
inkscape:persp3d-origin="778.99013 : 999.89265 : 1"
|
||||
id="perspective10" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
@ -152,10 +152,10 @@
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<inkscape:perspective
|
||||
id="perspective3373"
|
||||
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 526.18109 : 1"
|
||||
inkscape:persp3d-origin="396.85039 : 374.17322 : 1"
|
||||
inkscape:vp_z="793.70078 : 561.25983 : 1"
|
||||
inkscape:vp_y="0 : 1066.6667 : 0"
|
||||
inkscape:vp_x="0 : 561.25983 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
<linearGradient
|
||||
id="linearGradient3211">
|
||||
@ -282,15 +282,17 @@
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="239.17981"
|
||||
inkscape:cy="807.75327"
|
||||
inkscape:cx="164.67981"
|
||||
inkscape:cy="285.75327"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1272"
|
||||
inkscape:window-height="950"
|
||||
inkscape:window-height="856"
|
||||
inkscape:window-x="24"
|
||||
inkscape:window-y="24" />
|
||||
inkscape:window-y="22"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
@ -495,7 +497,7 @@
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;display:inline"
|
||||
d="M 182.85714,106.6479 L 182.85714,415.21935 L 182.85714,106.6479 z"
|
||||
id="rect3246"
|
||||
id="path934"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
sodipodi:nodetypes="ccc"
|
||||
@ -515,7 +517,7 @@
|
||||
style="display:inline">
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:60;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline;opacity:0.87;color:#000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-dashoffset:0;visibility:visible;overflow:visible;enable-background:accumulate"
|
||||
d="M 232.58675,170.48705 C 227.06049,176.20076 227.20767,185.31117 232.91561,190.84339 C 232.91561,190.84339 262.59553,220.39981 299.69461,257.34451 C 307.623,265.23991 308.90149,274.72214 304.93625,288.45787 C 300.97101,302.19359 290.70796,318.30838 277.52977,331.54163 C 264.35159,344.77488 248.27973,355.10504 234.56067,359.12752 C 220.8416,363.15 211.35411,361.91106 203.42572,354.01566 C 184.92972,335.59662 168.41707,318.85373 156.40736,306.74834 C 150.40251,300.69564 145.54261,295.80887 142.03518,292.37245 C 140.28147,290.65424 138.89319,289.33126 137.69845,288.24431 C 137.10109,287.70084 136.61259,287.25572 135.77496,286.60406 C 135.35615,286.27823 134.96551,285.91185 133.67159,285.18696 C 133.02464,284.82451 132.24966,284.30398 130.06183,283.75178 C 127.87401,283.19957 121.6694,282.14423 116.27918,287.55701 C 113.07203,290.69556 111.51088,295.14955 112.05678,299.60357 C 112.60268,304.05759 115.19327,308.00272 119.06345,310.27384 C 119.7757,310.94033 120.58858,311.68637 121.88019,312.95183 C 125.15148,316.1569 129.98569,321.01459 135.96535,327.0419 C 147.92468,339.09652 164.49757,355.90001 183.10064,374.42567 C 199.28665,390.54432 222.47127,392.68834 242.66987,386.76604 C 262.86847,380.84373 281.97067,367.90254 297.93978,351.86671 C 313.90889,335.83087 326.77031,316.67487 332.60834,296.45175 C 338.44637,276.22863 336.20569,253.05315 320.01968,236.9345 C 282.92061,199.9898 253.24069,170.43338 253.24069,170.43338 C 250.52156,167.6512 246.79264,166.08723 242.90239,166.09734 C 239.01212,166.10745 235.29138,167.69077 232.58675,170.48705 L 232.58675,170.48705 z"
|
||||
d="m 232.58675,170.48705 c -5.52626,5.71371 -5.37908,14.82412 0.32886,20.35634 0,0 29.67992,29.55642 66.779,66.50112 7.92839,7.8954 9.20688,17.37763 5.24164,31.11336 -3.96524,13.73572 -14.22829,29.85051 -27.40648,43.08376 -13.17818,13.23325 -29.25004,23.56341 -42.9691,27.58589 -13.71907,4.02248 -23.20656,2.78354 -31.13495,-5.11186 -18.496,-18.41904 -35.00865,-35.16193 -47.01836,-47.26732 -6.00485,-6.0527 -10.86475,-10.93947 -14.37218,-14.37589 -1.75371,-1.71821 -3.14199,-3.04119 -4.33673,-4.12814 -0.59736,-0.54347 -1.08586,-0.98859 -1.92349,-1.64025 -0.41881,-0.32583 -0.80945,-0.69221 -2.10337,-1.4171 -0.64695,-0.36245 -1.42193,-0.88298 -3.60976,-1.43518 -2.18782,-0.55221 -8.39243,-1.60755 -13.78265,3.80523 -3.20715,3.13855 -4.7683,7.59254 -4.2224,12.04656 0.5459,4.45402 3.90276,7.82444 7.00667,10.67027 0.71225,0.66649 1.52513,1.41253 2.81674,2.67799 3.27129,3.20507 8.1055,8.06276 14.08516,14.09007 11.95933,12.05462 28.53222,28.85811 47.13529,47.38377 16.18601,16.11865 39.37063,18.26267 59.56923,12.34037 20.1986,-5.92231 39.3008,-18.8635 55.26991,-34.89933 15.96911,-16.03584 28.83053,-35.19184 34.66856,-55.41496 5.83803,-20.22312 3.59735,-43.3986 -12.58866,-59.51725 l -66.77899,-66.50112 c -2.71913,-2.78218 -6.44805,-4.34615 -10.3383,-4.33604 -3.89027,0.0101 -7.61101,1.59343 -10.31564,4.38971 zm 232.58675,170.48705 c -5.52626,5.71371 -5.37908,14.82412 0.32886,20.35634 0,0 29.67992,29.55642 66.779,66.50112 7.92839,7.8954 9.20688,17.37763 5.24164,31.11336 -3.96524,13.73572 -14.22829,29.85051 -27.40648,43.08376 -13.17818,13.23325 -29.25004,23.56341 -42.9691,27.58589 -13.71907,4.02248 -23.20656,2.78354 -31.13495,-5.11186 -18.496,-18.41904 -35.00865,-35.16193 -47.01836,-47.26732 -6.00485,-6.0527 -10.86475,-10.93947 -14.37218,-14.37589 -1.75371,-1.71821 -3.14199,-3.04119 -4.33673,-4.12814 -0.59736,-0.54347 -1.08586,-0.98859 -1.92349,-1.64025 -0.41881,-0.32583 -0.80945,-0.69221 -2.10337,-1.4171 -0.64695,-0.36245 -1.42193,-0.88298 -3.60976,-1.43518 -2.18782,-0.55221 -8.39243,-1.60755 -13.78265,3.80523 -3.20715,3.13855 -4.7683,7.59254 -4.2224,12.04656 0.5459,4.45402 3.90276,7.82444 7.00667,10.67027 0.71225,0.66649 1.52513,1.41253 2.81674,2.67799 3.27129,3.20507 8.1055,8.06276 14.08516,14.09007 11.95933,12.05462 28.53222,28.85811 47.13529,47.38377 16.18601,16.11865 39.37063,18.26267 59.56923,12.34037 20.1986,-5.92231 39.3008,-18.8635 55.26991,-34.89933 15.96911,-16.03584 28.83053,-35.19184 34.66856,-55.41496 5.83803,-20.22312 3.59735,-43.3986 -12.58866,-59.51725 l -66.77899,-66.50112 c -2.71913,-2.78218 -6.44805,-4.34615 -10.3383,-4.33604 -3.89027,0.0101 -7.61101,1.59343 -10.31564,4.38971 z"
|
||||
id="path2405" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:60;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;display:inline;opacity:0.87"
|
||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 27 KiB |
@ -9,7 +9,7 @@ i3-msg - send messages to i3 window manager
|
||||
|
||||
== SYNOPSIS
|
||||
|
||||
i3-msg [-q] [-v] [-h] [-s socket] [-t type] [message]
|
||||
i3-msg [-q] [-v] [-h] [-s socket] [-t type] [-r] [message]
|
||||
|
||||
== OPTIONS
|
||||
|
||||
@ -36,6 +36,10 @@ Instead of exiting right after receiving the first subscribed event,
|
||||
wait indefinitely for all of them. Can only be used with "-t subscribe".
|
||||
See the "subscribe" IPC message type below for details.
|
||||
|
||||
*-q, --raw*::
|
||||
Display the raw JSON reply instead of pretty-printing errors (for commands) or
|
||||
displaying the top-level config file contents (for GET_CONFIG).
|
||||
|
||||
*message*::
|
||||
Send ipc message, see below.
|
||||
|
||||
|
@ -9,7 +9,7 @@ i3-nagbar - displays an error bar on top of your screen
|
||||
|
||||
== SYNOPSIS
|
||||
|
||||
i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v]
|
||||
i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v] [-p]
|
||||
|
||||
== OPTIONS
|
||||
|
||||
@ -39,6 +39,10 @@ i3-sensible-terminal.
|
||||
Same as above, but will execute the shell commands directly, without launching a
|
||||
terminal emulator.
|
||||
|
||||
*-p, --primary*::
|
||||
Always opens the i3-nagbar on the primary monitor. By default it opens on the
|
||||
focused monitor.
|
||||
|
||||
== DESCRIPTION
|
||||
|
||||
i3-nagbar is used by i3 to tell you about errors in your configuration file
|
||||
|
@ -23,20 +23,20 @@ It tries to start one of the following (in that order):
|
||||
|
||||
* $TERMINAL (this is a non-standard variable)
|
||||
* x-terminal-emulator (only present on Debian and derivatives)
|
||||
* mate-terminal
|
||||
* gnome-terminal
|
||||
* terminator
|
||||
* xfce4-terminal
|
||||
* urxvt
|
||||
* rxvt
|
||||
* termit
|
||||
* terminator
|
||||
* Eterm
|
||||
* aterm
|
||||
* uxterm
|
||||
* xterm
|
||||
* gnome-terminal
|
||||
* roxterm
|
||||
* xfce4-terminal
|
||||
* termite
|
||||
* lxterminal
|
||||
* mate-terminal
|
||||
* terminology
|
||||
* st
|
||||
* qterminal
|
||||
|
@ -44,6 +44,9 @@ Retrieve the i3 IPC socket path from X11, print it, then exit.
|
||||
Limits the size of the i3 SHM log to <limit> bytes. Setting this to 0 disables
|
||||
SHM logging entirely. The default is 0 bytes.
|
||||
|
||||
--replace::
|
||||
Replace an existing window manager.
|
||||
|
||||
== DESCRIPTION
|
||||
|
||||
=== INTRODUCTION
|
||||
|
@ -9,7 +9,7 @@ i3bar - xcb-based status- and workspace-bar
|
||||
|
||||
== SYNOPSIS
|
||||
|
||||
*i3bar* [*-s* 'sock_path'] [*-b* 'bar_id'] [*-v*] [*-h*]
|
||||
*i3bar* [*-b* 'bar_id'] [*-s* 'sock_path'] [*-t*] [*-h*] [*-v*] [*-V*]
|
||||
|
||||
== WARNING
|
||||
|
||||
@ -25,13 +25,20 @@ You have been warned!
|
||||
Overwrites the path to the i3 IPC socket.
|
||||
|
||||
*-b, --bar_id* 'bar_id'::
|
||||
Specifies the bar ID for which to get the configuration from i3.
|
||||
Specifies the bar ID for which to get the configuration from i3. By default,
|
||||
i3bar will use the first bar block as configured in i3.
|
||||
|
||||
*-t, --transparency*::
|
||||
Enable transparency (RGBA colors)
|
||||
|
||||
*-h, --help*::
|
||||
Display a short help-message and exit
|
||||
|
||||
*-v, --version*::
|
||||
Display version number and exit.
|
||||
|
||||
*-h, --help*::
|
||||
Display a short help-message and exit
|
||||
*-V*::
|
||||
Be verbose.
|
||||
|
||||
== DESCRIPTION
|
||||
|
||||
|
24
meson.build
24
meson.build
@ -6,7 +6,7 @@
|
||||
project(
|
||||
'i3',
|
||||
'c',
|
||||
version: '4.19.2',
|
||||
version: '4.20',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
'warning_level=1', # enable all warnings (-Wall)
|
||||
@ -325,6 +325,8 @@ ev_dep = cc.find_library('ev')
|
||||
inc = include_directories('include')
|
||||
|
||||
libi3srcs = [
|
||||
'libi3/boolstr.c',
|
||||
'libi3/create_socket.c',
|
||||
'libi3/dpi.c',
|
||||
'libi3/draw_util.c',
|
||||
'libi3/fake_configure_notify.c',
|
||||
@ -341,11 +343,15 @@ libi3srcs = [
|
||||
'libi3/ipc_recv_message.c',
|
||||
'libi3/ipc_send_message.c',
|
||||
'libi3/is_debug_build.c',
|
||||
'libi3/path_exists.c',
|
||||
'libi3/resolve_tilde.c',
|
||||
'libi3/root_atom_contents.c',
|
||||
'libi3/safewrappers.c',
|
||||
'libi3/string.c',
|
||||
'libi3/ucs2_conversion.c',
|
||||
'libi3/nonblock.c',
|
||||
'libi3/screenshot_wallpaper.c',
|
||||
'libi3/is_background_set.c',
|
||||
]
|
||||
|
||||
if not cdata.get('HAVE_STRNDUP')
|
||||
@ -514,6 +520,7 @@ executable(
|
||||
'i3-config-wizard/i3-config-wizard-atoms.xmacro.h',
|
||||
'i3-config-wizard/main.c',
|
||||
'i3-config-wizard/xcb.h',
|
||||
config_parser,
|
||||
],
|
||||
install: true,
|
||||
include_directories: include_directories('include', 'i3-config-wizard'),
|
||||
@ -642,7 +649,7 @@ if get_option('docs')
|
||||
'@OUTPUT@',
|
||||
],
|
||||
install: true,
|
||||
install_dir: join_paths(get_option('datadir'), 'doc', 'i3'),
|
||||
install_dir: docdir,
|
||||
)
|
||||
|
||||
custom_target(
|
||||
@ -655,7 +662,7 @@ if get_option('docs')
|
||||
'@OUTPUT@',
|
||||
],
|
||||
install: true,
|
||||
install_dir: join_paths(get_option('datadir'), 'doc', 'i3'),
|
||||
install_dir: docdir,
|
||||
)
|
||||
endif
|
||||
|
||||
@ -669,7 +676,10 @@ executable(
|
||||
|
||||
executable(
|
||||
'test.commands_parser',
|
||||
'src/commands_parser.c',
|
||||
[
|
||||
'src/commands_parser.c',
|
||||
command_parser,
|
||||
],
|
||||
include_directories: inc,
|
||||
c_args: '-DTEST_PARSER',
|
||||
dependencies: common_deps,
|
||||
@ -678,7 +688,10 @@ executable(
|
||||
|
||||
executable(
|
||||
'test.config_parser',
|
||||
'src/config_parser.c',
|
||||
[
|
||||
'src/config_parser.c',
|
||||
config_parser,
|
||||
],
|
||||
include_directories: inc,
|
||||
c_args: '-DTEST_PARSER',
|
||||
dependencies: common_deps,
|
||||
@ -706,6 +719,7 @@ if meson.version().version_compare('>=0.46.0')
|
||||
anyevent_i3,
|
||||
i3test_pm,
|
||||
],
|
||||
timeout: 120, # Default of 30 seconds can cause timeouts on slower machines
|
||||
)
|
||||
else
|
||||
# meson < 0.46.0 does not support the depends arg in test targets.
|
||||
|
@ -40,6 +40,7 @@ state INITIAL:
|
||||
'scratchpad' -> SCRATCHPAD
|
||||
'swap' -> SWAP
|
||||
'title_format' -> TITLE_FORMAT
|
||||
'title_window_icon' -> TITLE_WINDOW_ICON
|
||||
'mode' -> MODE
|
||||
'bar' -> BAR
|
||||
|
||||
@ -54,7 +55,8 @@ state CRITERIA:
|
||||
ctype = 'title' -> CRITERION
|
||||
ctype = 'urgent' -> CRITERION
|
||||
ctype = 'workspace' -> CRITERION
|
||||
ctype = 'tiling', 'floating'
|
||||
ctype = 'machine' -> CRITERION
|
||||
ctype = 'tiling', 'floating', 'all'
|
||||
-> call cmd_criteria_add($ctype, NULL); CRITERIA
|
||||
']' -> call cmd_criteria_match_windows(); INITIAL
|
||||
|
||||
@ -384,8 +386,10 @@ state MOVE_WORKSPACE_NUMBER:
|
||||
-> call cmd_move_con_to_workspace_number($number, $no_auto_back_and_forth)
|
||||
|
||||
state MOVE_TO_OUTPUT:
|
||||
output = string
|
||||
-> call cmd_move_con_to_output($output)
|
||||
output = word
|
||||
-> call cmd_move_con_to_output($output, 0); MOVE_TO_OUTPUT
|
||||
end
|
||||
-> call cmd_move_con_to_output(NULL, 0); INITIAL
|
||||
|
||||
state MOVE_TO_MARK:
|
||||
mark = string
|
||||
@ -393,9 +397,13 @@ state MOVE_TO_MARK:
|
||||
|
||||
state MOVE_WORKSPACE_TO_OUTPUT:
|
||||
'output'
|
||||
->
|
||||
output = string
|
||||
-> call cmd_move_workspace_to_output($output)
|
||||
-> MOVE_WORKSPACE_TO_OUTPUT_WORD
|
||||
|
||||
state MOVE_WORKSPACE_TO_OUTPUT_WORD:
|
||||
output = word
|
||||
-> call cmd_move_con_to_output($output, 1); MOVE_WORKSPACE_TO_OUTPUT_WORD
|
||||
end
|
||||
-> call cmd_move_con_to_output(NULL, 1); INITIAL
|
||||
|
||||
state MOVE_TO_ABSOLUTE_POSITION:
|
||||
'position'
|
||||
@ -455,6 +463,20 @@ state TITLE_FORMAT:
|
||||
format = string
|
||||
-> call cmd_title_format($format)
|
||||
|
||||
state TITLE_WINDOW_ICON:
|
||||
'padding'
|
||||
-> TITLE_WINDOW_ICON_PADDING
|
||||
enable = '1', 'yes', 'true', 'on', 'enable', 'active', '0', 'no', 'false', 'off', 'disable', 'inactive'
|
||||
-> call cmd_title_window_icon($enable, 0)
|
||||
|
||||
state TITLE_WINDOW_ICON_PADDING:
|
||||
end
|
||||
-> call cmd_title_window_icon($enable, &padding)
|
||||
'px'
|
||||
-> call cmd_title_window_icon($enable, &padding)
|
||||
padding = number
|
||||
->
|
||||
|
||||
# bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>]
|
||||
state BAR:
|
||||
'hidden_state'
|
||||
|
@ -20,6 +20,7 @@ state INITIAL:
|
||||
'set ' -> IGNORE_LINE
|
||||
'set ' -> IGNORE_LINE
|
||||
'set_from_resource' -> IGNORE_LINE
|
||||
'include' -> INCLUDE
|
||||
bindtype = 'bindsym', 'bindcode', 'bind' -> BINDING
|
||||
'bar' -> BARBRACE
|
||||
'font' -> FONT
|
||||
@ -63,6 +64,11 @@ state IGNORE_LINE:
|
||||
line
|
||||
-> INITIAL
|
||||
|
||||
# include <pattern>
|
||||
state INCLUDE:
|
||||
pattern = string
|
||||
-> call cfg_include($pattern)
|
||||
|
||||
# floating_minimum_size <width> x <height>
|
||||
state FLOATING_MINIMUM_SIZE_WIDTH:
|
||||
width = number
|
||||
@ -191,9 +197,10 @@ state CRITERIA:
|
||||
ctype = 'title' -> CRITERION
|
||||
ctype = 'urgent' -> CRITERION
|
||||
ctype = 'workspace' -> CRITERION
|
||||
ctype = 'machine' -> CRITERION
|
||||
ctype = 'floating_from' -> CRITERION_FROM
|
||||
ctype = 'tiling_from' -> CRITERION_FROM
|
||||
ctype = 'tiling', 'floating'
|
||||
ctype = 'tiling', 'floating', 'all'
|
||||
-> call cfg_criteria_add($ctype, NULL); CRITERIA
|
||||
']'
|
||||
-> call cfg_criteria_pop_state()
|
||||
@ -393,6 +400,8 @@ state BINDCOMMAND:
|
||||
->
|
||||
command = string
|
||||
-> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)
|
||||
end
|
||||
-> call cfg_binding($bindtype, $modifiers, $key, $release, $border, $whole_window, $exclude_titlebar, $command)
|
||||
|
||||
################################################################################
|
||||
# Mode configuration
|
||||
|
@ -228,3 +228,4 @@ echo "Announce on:"
|
||||
echo " twitter"
|
||||
echo " #i3 topic"
|
||||
echo " reddit /r/i3wm (link post to changelog)"
|
||||
echo " GitHub Discussions → Announcements"
|
||||
|
@ -717,6 +717,40 @@ void reorder_bindings(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if a is a key binding for the same key as b.
|
||||
*
|
||||
*/
|
||||
static bool binding_same_key(Binding *a, Binding *b) {
|
||||
/* Check if the input types are different */
|
||||
if (a->input_type != b->input_type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check if one is using keysym while the other is using bindsym. */
|
||||
if ((a->symbol == NULL && b->symbol != NULL) ||
|
||||
(a->symbol != NULL && b->symbol == NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If a is NULL, b has to be NULL, too (see previous conditional).
|
||||
* If the keycodes differ, it can't be a duplicate. */
|
||||
if (a->symbol != NULL &&
|
||||
strcasecmp(a->symbol, b->symbol) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check if the keycodes or modifiers are different. If so, they
|
||||
* can't be duplicate */
|
||||
if (a->keycode != b->keycode ||
|
||||
a->event_state_mask != b->event_state_mask ||
|
||||
a->release != b->release) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks for duplicate key bindings (the same keycode or keysym is configured
|
||||
* more than once). If a duplicate binding is found, a message is printed to
|
||||
@ -730,31 +764,13 @@ void check_for_duplicate_bindings(struct context *context) {
|
||||
TAILQ_FOREACH (bind, bindings, bindings) {
|
||||
/* Abort when we reach the current keybinding, only check the
|
||||
* bindings before */
|
||||
if (bind == current)
|
||||
if (bind == current) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check if the input types are different */
|
||||
if (bind->input_type != current->input_type)
|
||||
continue;
|
||||
|
||||
/* Check if one is using keysym while the other is using bindsym.
|
||||
* If so, skip. */
|
||||
if ((bind->symbol == NULL && current->symbol != NULL) ||
|
||||
(bind->symbol != NULL && current->symbol == NULL))
|
||||
continue;
|
||||
|
||||
/* If bind is NULL, current has to be NULL, too (see above).
|
||||
* If the keycodes differ, it can't be a duplicate. */
|
||||
if (bind->symbol != NULL &&
|
||||
strcasecmp(bind->symbol, current->symbol) != 0)
|
||||
continue;
|
||||
|
||||
/* Check if the keycodes or modifiers are different. If so, they
|
||||
* can't be duplicate */
|
||||
if (bind->keycode != current->keycode ||
|
||||
bind->event_state_mask != current->event_state_mask ||
|
||||
bind->release != current->release)
|
||||
if (!binding_same_key(bind, current)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
context->has_errors = true;
|
||||
if (current->keycode != 0) {
|
||||
|
16
src/click.c
16
src/click.c
@ -218,8 +218,20 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 2: focus this con. */
|
||||
con_activate(con);
|
||||
/* 2: focus this con or one of its children. */
|
||||
Con *con_to_focus = con;
|
||||
if (in_stacked && dest == CLICK_DECORATION) {
|
||||
/* If the container is a tab/stacked container and the click happened
|
||||
* on a tab, switch to the tab. If the tab contents were already
|
||||
* focused, focus the tab container itself. If the tab container was
|
||||
* already focused, cycle back to focusing the tab contents. */
|
||||
if (was_focused || !con_has_parent(focused, con)) {
|
||||
while (!TAILQ_EMPTY(&(con_to_focus->focus_head))) {
|
||||
con_to_focus = TAILQ_FIRST(&(con_to_focus->focus_head));
|
||||
}
|
||||
}
|
||||
}
|
||||
con_activate(con_to_focus);
|
||||
|
||||
/* 3: For floating containers, we also want to raise them on click.
|
||||
* We will skip handling events on floating cons in fullscreen mode */
|
||||
|
174
src/commands.c
174
src/commands.c
@ -1024,23 +1024,113 @@ void cmd_mode(I3_CMD, const char *mode) {
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation of 'move [window|container] [to] output <str>'.
|
||||
* Implementation of 'move [window|container|workspace] [to] output <strings>'.
|
||||
*
|
||||
*/
|
||||
void cmd_move_con_to_output(I3_CMD, const char *name) {
|
||||
DLOG("Should move window to output \"%s\".\n", name);
|
||||
HANDLE_EMPTY_MATCH;
|
||||
void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) {
|
||||
/* Initialize a data structure that is used to save multiple user-specified
|
||||
* output names since this function is called multiple types for each
|
||||
* command call. */
|
||||
typedef struct user_output_name {
|
||||
char *name;
|
||||
TAILQ_ENTRY(user_output_name) user_output_names;
|
||||
} user_output_name;
|
||||
static TAILQ_HEAD(user_output_names_head, user_output_name) user_output_names = TAILQ_HEAD_INITIALIZER(user_output_names);
|
||||
|
||||
owindow *current;
|
||||
bool had_error = false;
|
||||
TAILQ_FOREACH (current, &owindows, owindows) {
|
||||
DLOG("matching: %p / %s\n", current->con, current->con->name);
|
||||
if (name) {
|
||||
if (strcmp(name, "next") == 0) {
|
||||
/* "next" here works like a wildcard: It "expands" to all available
|
||||
* outputs. */
|
||||
Output *output;
|
||||
TAILQ_FOREACH (output, &outputs, outputs) {
|
||||
user_output_name *co = scalloc(sizeof(user_output_name), 1);
|
||||
co->name = sstrdup(output_primary_name(output));
|
||||
TAILQ_INSERT_TAIL(&user_output_names, co, user_output_names);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
had_error |= !con_move_to_output_name(current->con, name, true);
|
||||
user_output_name *co = scalloc(sizeof(user_output_name), 1);
|
||||
co->name = sstrdup(name);
|
||||
TAILQ_INSERT_TAIL(&user_output_names, co, user_output_names);
|
||||
return;
|
||||
}
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
ysuccess(!had_error);
|
||||
HANDLE_EMPTY_MATCH;
|
||||
|
||||
if (TAILQ_EMPTY(&user_output_names)) {
|
||||
yerror("At least one output must be specified");
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
user_output_name *uo;
|
||||
owindow *current;
|
||||
TAILQ_FOREACH (current, &owindows, owindows) {
|
||||
Con *ws = con_get_workspace(current->con);
|
||||
if (con_is_internal(ws)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Output *current_output = get_output_for_con(ws);
|
||||
|
||||
Output *target_output = NULL;
|
||||
TAILQ_FOREACH (uo, &user_output_names, user_output_names) {
|
||||
if (strcasecmp(output_primary_name(current_output), uo->name) == 0) {
|
||||
/* The current output is in the user list */
|
||||
while (true) {
|
||||
/* This corrupts the outer loop but it is ok since we are
|
||||
* going to break anyway. */
|
||||
uo = TAILQ_NEXT(uo, user_output_names);
|
||||
if (!uo) {
|
||||
/* We reached the end of the list. We should use the
|
||||
* first available output that, if it exists, is
|
||||
* already saved in target_output. */
|
||||
break;
|
||||
}
|
||||
Output *out = get_output_from_string(current_output, uo->name);
|
||||
if (out) {
|
||||
DLOG("Found next target for workspace %s from user list: %s\n", ws->name, uo->name);
|
||||
target_output = out;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!target_output) {
|
||||
/* The first available output from the list is used in 2 cases:
|
||||
* 1. When we must wrap around the user list. For example, if
|
||||
* user specifies outputs A B C and C is `current_output`.
|
||||
* 2. When the current output is not in the user list. For
|
||||
* example, user specifies A B C and D is `current_output`.
|
||||
*/
|
||||
DLOG("Found first target for workspace %s from user list: %s\n", ws->name, uo->name);
|
||||
target_output = get_output_from_string(current_output, uo->name);
|
||||
}
|
||||
}
|
||||
if (target_output) {
|
||||
if (move_workspace) {
|
||||
workspace_move_to_output(ws, target_output);
|
||||
} else {
|
||||
con_move_to_output(current->con, target_output, true);
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
while (!TAILQ_EMPTY(&user_output_names)) {
|
||||
uo = TAILQ_FIRST(&user_output_names);
|
||||
free(uo->name);
|
||||
TAILQ_REMOVE(&user_output_names, uo, user_output_names);
|
||||
free(uo);
|
||||
}
|
||||
|
||||
cmd_output->needs_tree_render = success;
|
||||
if (success) {
|
||||
ysuccess(true);
|
||||
} else {
|
||||
yerror("No output matched");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1094,36 +1184,6 @@ void cmd_floating(I3_CMD, const char *floating_mode) {
|
||||
ysuccess(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation of 'move workspace to [output] <str>'.
|
||||
*
|
||||
*/
|
||||
void cmd_move_workspace_to_output(I3_CMD, const char *name) {
|
||||
DLOG("should move workspace to output %s\n", name);
|
||||
|
||||
HANDLE_EMPTY_MATCH;
|
||||
|
||||
owindow *current;
|
||||
TAILQ_FOREACH (current, &owindows, owindows) {
|
||||
Con *ws = con_get_workspace(current->con);
|
||||
if (con_is_internal(ws)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Output *current_output = get_output_for_con(ws);
|
||||
Output *target_output = get_output_from_string(current_output, name);
|
||||
if (!target_output) {
|
||||
yerror("Could not get output from string \"%s\"", name);
|
||||
return;
|
||||
}
|
||||
|
||||
workspace_move_to_output(ws, target_output);
|
||||
}
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
ysuccess(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation of 'split v|h|t|vertical|horizontal|toggle'.
|
||||
*
|
||||
@ -1661,6 +1721,9 @@ void cmd_restart(I3_CMD) {
|
||||
}
|
||||
ipc_shutdown(SHUTDOWN_REASON_RESTART, exempt_fd);
|
||||
unlink(config.ipc_socket_path);
|
||||
if (current_log_stream_socket_path != NULL) {
|
||||
unlink(current_log_stream_socket_path);
|
||||
}
|
||||
/* We need to call this manually since atexit handlers don’t get called
|
||||
* when exec()ing */
|
||||
purge_zerobyte_logfile();
|
||||
@ -1974,6 +2037,35 @@ void cmd_title_format(I3_CMD, const char *format) {
|
||||
ysuccess(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation of 'title_window_icon <yes|no>' and 'title_window_icon padding <px>'
|
||||
*
|
||||
*/
|
||||
void cmd_title_window_icon(I3_CMD, const char *enable, int padding) {
|
||||
if (enable != NULL && !boolstr(enable)) {
|
||||
padding = -1;
|
||||
}
|
||||
DLOG("setting window_icon=%d\n", padding);
|
||||
HANDLE_EMPTY_MATCH;
|
||||
|
||||
owindow *current;
|
||||
TAILQ_FOREACH (current, &owindows, owindows) {
|
||||
DLOG("setting window_icon for %p / %s\n", current->con, current->con->name);
|
||||
current->con->window_icon_padding = padding;
|
||||
|
||||
if (current->con->window != NULL) {
|
||||
/* Make sure the window title is redrawn immediately. */
|
||||
current->con->window->name_x_changed = true;
|
||||
} else {
|
||||
/* For windowless containers we also need to force the redrawing. */
|
||||
FREE(current->con->deco_render_params);
|
||||
}
|
||||
}
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
ysuccess(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation of 'rename workspace [<name>] to <name>'
|
||||
*
|
||||
|
@ -56,40 +56,19 @@ typedef struct tokenptr {
|
||||
|
||||
#include "GENERATED_command_tokens.h"
|
||||
|
||||
/*******************************************************************************
|
||||
* The (small) stack where identified literals are stored during the parsing
|
||||
* of a single command (like $workspace).
|
||||
******************************************************************************/
|
||||
|
||||
struct stack_entry {
|
||||
/* Just a pointer, not dynamically allocated. */
|
||||
const char *identifier;
|
||||
enum {
|
||||
STACK_STR = 0,
|
||||
STACK_LONG = 1,
|
||||
} type;
|
||||
union {
|
||||
char *str;
|
||||
long num;
|
||||
} val;
|
||||
};
|
||||
|
||||
/* 10 entries should be enough for everybody. */
|
||||
static struct stack_entry stack[10];
|
||||
|
||||
/*
|
||||
* Pushes a string (identified by 'identifier') on the stack. We simply use a
|
||||
* single array, since the number of entries we have to store is very small.
|
||||
*
|
||||
*/
|
||||
static void push_string(const char *identifier, char *str) {
|
||||
static void push_string(struct stack *stack, const char *identifier, char *str) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].identifier != NULL)
|
||||
if (stack->stack[c].identifier != NULL)
|
||||
continue;
|
||||
/* Found a free slot, let’s store it here. */
|
||||
stack[c].identifier = identifier;
|
||||
stack[c].val.str = str;
|
||||
stack[c].type = STACK_STR;
|
||||
stack->stack[c].identifier = identifier;
|
||||
stack->stack[c].val.str = str;
|
||||
stack->stack[c].type = STACK_STR;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -103,15 +82,15 @@ static void push_string(const char *identifier, char *str) {
|
||||
}
|
||||
|
||||
// TODO move to a common util
|
||||
static void push_long(const char *identifier, long num) {
|
||||
static void push_long(struct stack *stack, const char *identifier, long num) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].identifier != NULL) {
|
||||
if (stack->stack[c].identifier != NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
stack[c].identifier = identifier;
|
||||
stack[c].val.num = num;
|
||||
stack[c].type = STACK_LONG;
|
||||
stack->stack[c].identifier = identifier;
|
||||
stack->stack[c].val.num = num;
|
||||
stack->stack[c].type = STACK_LONG;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -125,36 +104,36 @@ static void push_long(const char *identifier, long num) {
|
||||
}
|
||||
|
||||
// TODO move to a common util
|
||||
static const char *get_string(const char *identifier) {
|
||||
static const char *get_string(struct stack *stack, const char *identifier) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].identifier == NULL)
|
||||
if (stack->stack[c].identifier == NULL)
|
||||
break;
|
||||
if (strcmp(identifier, stack[c].identifier) == 0)
|
||||
return stack[c].val.str;
|
||||
if (strcmp(identifier, stack->stack[c].identifier) == 0)
|
||||
return stack->stack[c].val.str;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// TODO move to a common util
|
||||
static long get_long(const char *identifier) {
|
||||
static long get_long(struct stack *stack, const char *identifier) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].identifier == NULL)
|
||||
if (stack->stack[c].identifier == NULL)
|
||||
break;
|
||||
if (strcmp(identifier, stack[c].identifier) == 0)
|
||||
return stack[c].val.num;
|
||||
if (strcmp(identifier, stack->stack[c].identifier) == 0)
|
||||
return stack->stack[c].val.num;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO move to a common util
|
||||
static void clear_stack(void) {
|
||||
static void clear_stack(struct stack *stack) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].type == STACK_STR)
|
||||
free(stack[c].val.str);
|
||||
stack[c].identifier = NULL;
|
||||
stack[c].val.str = NULL;
|
||||
stack[c].val.num = 0;
|
||||
if (stack->stack[c].type == STACK_STR)
|
||||
free(stack->stack[c].val.str);
|
||||
stack->stack[c].identifier = NULL;
|
||||
stack->stack[c].val.str = NULL;
|
||||
stack->stack[c].val.num = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,9 +142,12 @@ static void clear_stack(void) {
|
||||
******************************************************************************/
|
||||
|
||||
static cmdp_state state;
|
||||
#ifndef TEST_PARSER
|
||||
static Match current_match;
|
||||
#endif
|
||||
/*******************************************************************************
|
||||
* The (small) stack where identified literals are stored during the parsing
|
||||
* of a single command (like $workspace).
|
||||
******************************************************************************/
|
||||
static struct stack stack;
|
||||
static struct CommandResultIR subcommand_output;
|
||||
static struct CommandResultIR command_output;
|
||||
|
||||
@ -176,19 +158,19 @@ static void next_state(const cmdp_token *token) {
|
||||
subcommand_output.json_gen = command_output.json_gen;
|
||||
subcommand_output.client = command_output.client;
|
||||
subcommand_output.needs_tree_render = false;
|
||||
GENERATED_call(token->extra.call_identifier, &subcommand_output);
|
||||
GENERATED_call(¤t_match, &stack, token->extra.call_identifier, &subcommand_output);
|
||||
state = subcommand_output.next_state;
|
||||
/* If any subcommand requires a tree_render(), we need to make the
|
||||
* whole parser result request a tree_render(). */
|
||||
if (subcommand_output.needs_tree_render)
|
||||
command_output.needs_tree_render = true;
|
||||
clear_stack();
|
||||
clear_stack(&stack);
|
||||
return;
|
||||
}
|
||||
|
||||
state = token->next_state;
|
||||
if (state == INITIAL) {
|
||||
clear_stack();
|
||||
clear_stack(&stack);
|
||||
}
|
||||
}
|
||||
|
||||
@ -296,8 +278,9 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
|
||||
/* A literal. */
|
||||
if (token->name[0] == '\'') {
|
||||
if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
|
||||
if (token->identifier != NULL)
|
||||
push_string(token->identifier, sstrdup(token->name + 1));
|
||||
if (token->identifier != NULL) {
|
||||
push_string(&stack, token->identifier, sstrdup(token->name + 1));
|
||||
}
|
||||
walk += strlen(token->name) - 1;
|
||||
next_state(token);
|
||||
token_handled = true;
|
||||
@ -319,8 +302,9 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
|
||||
if (end == walk)
|
||||
continue;
|
||||
|
||||
if (token->identifier != NULL)
|
||||
push_long(token->identifier, num);
|
||||
if (token->identifier != NULL) {
|
||||
push_long(&stack, token->identifier, num);
|
||||
}
|
||||
|
||||
/* Set walk to the first non-number character */
|
||||
walk = end;
|
||||
@ -333,8 +317,9 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
|
||||
strcmp(token->name, "word") == 0) {
|
||||
char *str = parse_string(&walk, (token->name[0] != 's'));
|
||||
if (str != NULL) {
|
||||
if (token->identifier)
|
||||
push_string(token->identifier, str);
|
||||
if (token->identifier) {
|
||||
push_string(&stack, token->identifier, str);
|
||||
}
|
||||
/* If we are at the end of a quoted string, skip the ending
|
||||
* double quote. */
|
||||
if (*walk == '"')
|
||||
@ -436,7 +421,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
|
||||
y(map_close);
|
||||
|
||||
free(position);
|
||||
clear_stack();
|
||||
clear_stack(&stack);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
12
src/con.c
12
src/con.c
@ -43,6 +43,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) {
|
||||
new->window = window;
|
||||
new->border_style = config.default_border;
|
||||
new->current_border_width = -1;
|
||||
new->window_icon_padding = -1;
|
||||
if (window) {
|
||||
new->depth = window->depth;
|
||||
} else {
|
||||
@ -1855,9 +1856,9 @@ void con_set_layout(Con *con, layout_t layout) {
|
||||
con_attach(new, con, false);
|
||||
|
||||
tree_flatten(croot);
|
||||
con_force_split_parents_redraw(con);
|
||||
return;
|
||||
}
|
||||
con_force_split_parents_redraw(con);
|
||||
return;
|
||||
}
|
||||
|
||||
if (layout == L_DEFAULT) {
|
||||
@ -2304,20 +2305,25 @@ i3String *con_parse_title_format(Con *con) {
|
||||
char *title;
|
||||
char *class;
|
||||
char *instance;
|
||||
char *machine;
|
||||
if (win == NULL) {
|
||||
title = pango_escape_markup(con_get_tree_representation(con));
|
||||
class = sstrdup("i3-frame");
|
||||
instance = sstrdup("i3-frame");
|
||||
machine = sstrdup("");
|
||||
} else {
|
||||
title = pango_escape_markup(sstrdup((win->name == NULL) ? "" : i3string_as_utf8(win->name)));
|
||||
class = pango_escape_markup(sstrdup((win->class_class == NULL) ? "" : win->class_class));
|
||||
instance = pango_escape_markup(sstrdup((win->class_instance == NULL) ? "" : win->class_instance));
|
||||
machine = pango_escape_markup(sstrdup((win->machine == NULL) ? "" : win->machine));
|
||||
}
|
||||
|
||||
placeholder_t placeholders[] = {
|
||||
{.name = "%title", .value = title},
|
||||
{.name = "%class", .value = class},
|
||||
{.name = "%instance", .value = instance}};
|
||||
{.name = "%instance", .value = instance},
|
||||
{.name = "%machine", .value = machine},
|
||||
};
|
||||
const size_t num = sizeof(placeholders) / sizeof(placeholder_t);
|
||||
|
||||
char *formatted_str = format_placeholders(con->title_format, &placeholders[0], num);
|
||||
|
46
src/config.c
46
src/config.c
@ -10,13 +10,16 @@
|
||||
*/
|
||||
#include "all.h"
|
||||
|
||||
#include <libgen.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
char *current_configpath = NULL;
|
||||
char *current_config = NULL;
|
||||
Config config;
|
||||
struct modes_head modes;
|
||||
struct barconfig_head barconfigs = TAILQ_HEAD_INITIALIZER(barconfigs);
|
||||
struct includedfiles_head included_files = TAILQ_HEAD_INITIALIZER(included_files);
|
||||
|
||||
/*
|
||||
* Ungrabs all keys, to be called before re-grabbing the keys because of a
|
||||
@ -225,8 +228,43 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
|
||||
"$XDG_CONFIG_HOME/i3/config, ~/.i3/config, $XDG_CONFIG_DIRS/i3/config "
|
||||
"and " SYSCONFDIR "/i3/config)");
|
||||
}
|
||||
LOG("Parsing configfile %s\n", current_configpath);
|
||||
const bool result = parse_file(current_configpath, load_type != C_VALIDATE);
|
||||
|
||||
IncludedFile *file;
|
||||
while (!TAILQ_EMPTY(&included_files)) {
|
||||
file = TAILQ_FIRST(&included_files);
|
||||
FREE(file->path);
|
||||
FREE(file->raw_contents);
|
||||
FREE(file->variable_replaced_contents);
|
||||
TAILQ_REMOVE(&included_files, file, files);
|
||||
FREE(file);
|
||||
}
|
||||
|
||||
char resolved_path[PATH_MAX] = {'\0'};
|
||||
if (realpath(current_configpath, resolved_path) == NULL) {
|
||||
die("realpath(%s): %s", current_configpath, strerror(errno));
|
||||
}
|
||||
|
||||
file = scalloc(1, sizeof(IncludedFile));
|
||||
file->path = sstrdup(resolved_path);
|
||||
TAILQ_INSERT_TAIL(&included_files, file, files);
|
||||
|
||||
LOG("Parsing configfile %s\n", resolved_path);
|
||||
struct stack stack;
|
||||
memset(&stack, '\0', sizeof(struct stack));
|
||||
struct parser_ctx ctx = {
|
||||
.use_nagbar = (load_type != C_VALIDATE),
|
||||
.assume_v4 = false,
|
||||
.stack = &stack,
|
||||
};
|
||||
SLIST_INIT(&(ctx.variables));
|
||||
const int result = parse_file(&ctx, resolved_path, file);
|
||||
free_variables(&ctx);
|
||||
if (result == -1) {
|
||||
die("Could not open configuration file: %s\n", strerror(errno));
|
||||
}
|
||||
|
||||
extract_workspace_names_from_bindings();
|
||||
reorder_bindings();
|
||||
|
||||
if (config.font.type == FONT_TYPE_NONE && load_type != C_VALIDATE) {
|
||||
ELOG("You did not specify required configuration option \"font\"\n");
|
||||
@ -245,5 +283,5 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
return result;
|
||||
return result == 0;
|
||||
}
|
||||
|
@ -9,6 +9,86 @@
|
||||
*/
|
||||
#include "all.h"
|
||||
|
||||
#include <wordexp.h>
|
||||
|
||||
/*******************************************************************************
|
||||
* Include functions.
|
||||
******************************************************************************/
|
||||
|
||||
CFGFUN(include, const char *pattern) {
|
||||
DLOG("include %s\n", pattern);
|
||||
|
||||
wordexp_t p;
|
||||
const int ret = wordexp(pattern, &p, 0);
|
||||
if (ret != 0) {
|
||||
ELOG("wordexp(%s): error %d\n", pattern, ret);
|
||||
result->has_errors = true;
|
||||
return;
|
||||
}
|
||||
char **w = p.we_wordv;
|
||||
for (size_t i = 0; i < p.we_wordc; i++) {
|
||||
char resolved_path[PATH_MAX] = {'\0'};
|
||||
if (realpath(w[i], resolved_path) == NULL) {
|
||||
LOG("Skipping %s: %s\n", w[i], strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
bool skip = false;
|
||||
IncludedFile *file;
|
||||
TAILQ_FOREACH (file, &included_files, files) {
|
||||
if (strcmp(file->path, resolved_path) == 0) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skip) {
|
||||
LOG("Skipping file %s (already included)\n", resolved_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG("Including config file %s\n", resolved_path);
|
||||
|
||||
file = scalloc(1, sizeof(IncludedFile));
|
||||
file->path = sstrdup(resolved_path);
|
||||
TAILQ_INSERT_TAIL(&included_files, file, files);
|
||||
|
||||
struct stack stack;
|
||||
memset(&stack, '\0', sizeof(struct stack));
|
||||
struct parser_ctx ctx = {
|
||||
.use_nagbar = result->ctx->use_nagbar,
|
||||
/* The include mechanism was added in v4, so we can skip the
|
||||
* auto-detection and get rid of the risk of detecting the wrong
|
||||
* version in potentially very short include fragments: */
|
||||
.assume_v4 = true,
|
||||
.stack = &stack,
|
||||
.variables = result->ctx->variables,
|
||||
};
|
||||
switch (parse_file(&ctx, resolved_path, file)) {
|
||||
case PARSE_FILE_SUCCESS:
|
||||
break;
|
||||
|
||||
case PARSE_FILE_FAILED:
|
||||
ELOG("including config file %s: %s\n", resolved_path, strerror(errno));
|
||||
/* fallthrough */
|
||||
|
||||
case PARSE_FILE_CONFIG_ERRORS:
|
||||
result->has_errors = true;
|
||||
TAILQ_REMOVE(&included_files, file, files);
|
||||
FREE(file->path);
|
||||
FREE(file->raw_contents);
|
||||
FREE(file->variable_replaced_contents);
|
||||
FREE(file);
|
||||
break;
|
||||
|
||||
default:
|
||||
/* missing case statement */
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
wordfree(&p);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Criteria functions.
|
||||
******************************************************************************/
|
||||
@ -45,15 +125,6 @@ CFGFUN(criteria_add, const char *ctype, const char *cvalue) {
|
||||
* Utility functions
|
||||
******************************************************************************/
|
||||
|
||||
static bool eval_boolstr(const char *str) {
|
||||
return (strcasecmp(str, "1") == 0 ||
|
||||
strcasecmp(str, "yes") == 0 ||
|
||||
strcasecmp(str, "true") == 0 ||
|
||||
strcasecmp(str, "on") == 0 ||
|
||||
strcasecmp(str, "enable") == 0 ||
|
||||
strcasecmp(str, "active") == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* A utility function to convert a string containing the group and modifiers to
|
||||
* the corresponding bit mask.
|
||||
@ -237,14 +308,14 @@ CFGFUN(hide_edge_borders, const char *borders) {
|
||||
config.hide_edge_borders = HEBM_BOTH;
|
||||
else if (strcmp(borders, "none") == 0)
|
||||
config.hide_edge_borders = HEBM_NONE;
|
||||
else if (eval_boolstr(borders))
|
||||
else if (boolstr(borders))
|
||||
config.hide_edge_borders = HEBM_VERTICAL;
|
||||
else
|
||||
config.hide_edge_borders = HEBM_NONE;
|
||||
}
|
||||
|
||||
CFGFUN(focus_follows_mouse, const char *value) {
|
||||
config.disable_focus_follows_mouse = !eval_boolstr(value);
|
||||
config.disable_focus_follows_mouse = !boolstr(value);
|
||||
}
|
||||
|
||||
CFGFUN(mouse_warping, const char *value) {
|
||||
@ -255,11 +326,11 @@ CFGFUN(mouse_warping, const char *value) {
|
||||
}
|
||||
|
||||
CFGFUN(force_xinerama, const char *value) {
|
||||
config.force_xinerama = eval_boolstr(value);
|
||||
config.force_xinerama = boolstr(value);
|
||||
}
|
||||
|
||||
CFGFUN(disable_randr15, const char *value) {
|
||||
config.disable_randr15 = eval_boolstr(value);
|
||||
config.disable_randr15 = boolstr(value);
|
||||
}
|
||||
|
||||
CFGFUN(focus_wrapping, const char *value) {
|
||||
@ -267,7 +338,7 @@ CFGFUN(focus_wrapping, const char *value) {
|
||||
config.focus_wrapping = FOCUS_WRAPPING_FORCE;
|
||||
} else if (strcmp(value, "workspace") == 0) {
|
||||
config.focus_wrapping = FOCUS_WRAPPING_WORKSPACE;
|
||||
} else if (eval_boolstr(value)) {
|
||||
} else if (boolstr(value)) {
|
||||
config.focus_wrapping = FOCUS_WRAPPING_ON;
|
||||
} else {
|
||||
config.focus_wrapping = FOCUS_WRAPPING_OFF;
|
||||
@ -276,7 +347,7 @@ CFGFUN(focus_wrapping, const char *value) {
|
||||
|
||||
CFGFUN(force_focus_wrapping, const char *value) {
|
||||
/* Legacy syntax. */
|
||||
if (eval_boolstr(value)) {
|
||||
if (boolstr(value)) {
|
||||
config.focus_wrapping = FOCUS_WRAPPING_FORCE;
|
||||
} else {
|
||||
/* For "force_focus_wrapping off", don't enable or disable
|
||||
@ -288,7 +359,7 @@ CFGFUN(force_focus_wrapping, const char *value) {
|
||||
}
|
||||
|
||||
CFGFUN(workspace_back_and_forth, const char *value) {
|
||||
config.workspace_auto_back_and_forth = eval_boolstr(value);
|
||||
config.workspace_auto_back_and_forth = boolstr(value);
|
||||
}
|
||||
|
||||
CFGFUN(fake_outputs, const char *outputs) {
|
||||
@ -330,7 +401,7 @@ CFGFUN(title_align, const char *alignment) {
|
||||
}
|
||||
|
||||
CFGFUN(show_marks, const char *value) {
|
||||
config.show_marks = eval_boolstr(value);
|
||||
config.show_marks = boolstr(value);
|
||||
}
|
||||
|
||||
static char *current_workspace = NULL;
|
||||
@ -518,7 +589,7 @@ CFGFUN(bar_output, const char *output) {
|
||||
}
|
||||
|
||||
CFGFUN(bar_verbose, const char *verbose) {
|
||||
current_bar->verbose = eval_boolstr(verbose);
|
||||
current_bar->verbose = boolstr(verbose);
|
||||
}
|
||||
|
||||
CFGFUN(bar_modifier, const char *modifiers) {
|
||||
@ -638,11 +709,11 @@ CFGFUN(bar_status_command, const char *command) {
|
||||
}
|
||||
|
||||
CFGFUN(bar_binding_mode_indicator, const char *value) {
|
||||
current_bar->hide_binding_mode_indicator = !eval_boolstr(value);
|
||||
current_bar->hide_binding_mode_indicator = !boolstr(value);
|
||||
}
|
||||
|
||||
CFGFUN(bar_workspace_buttons, const char *value) {
|
||||
current_bar->hide_workspace_buttons = !eval_boolstr(value);
|
||||
current_bar->hide_workspace_buttons = !boolstr(value);
|
||||
}
|
||||
|
||||
CFGFUN(bar_workspace_min_width, const long width) {
|
||||
@ -650,11 +721,11 @@ CFGFUN(bar_workspace_min_width, const long width) {
|
||||
}
|
||||
|
||||
CFGFUN(bar_strip_workspace_numbers, const char *value) {
|
||||
current_bar->strip_workspace_numbers = eval_boolstr(value);
|
||||
current_bar->strip_workspace_numbers = boolstr(value);
|
||||
}
|
||||
|
||||
CFGFUN(bar_strip_workspace_name, const char *value) {
|
||||
current_bar->strip_workspace_name = eval_boolstr(value);
|
||||
current_bar->strip_workspace_name = boolstr(value);
|
||||
}
|
||||
|
||||
CFGFUN(bar_start) {
|
||||
|
@ -35,18 +35,14 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <libgen.h>
|
||||
|
||||
#include <xcb/xcb_xrm.h>
|
||||
|
||||
// Macros to make the YAJL API a bit easier to use.
|
||||
#define y(x, ...) yajl_gen_##x(command_output.json_gen, ##__VA_ARGS__)
|
||||
#define ystr(str) yajl_gen_string(command_output.json_gen, (unsigned char *)str, strlen(str))
|
||||
|
||||
xcb_xrm_database_t *database = NULL;
|
||||
|
||||
#ifndef TEST_PARSER
|
||||
pid_t config_error_nagbar_pid = -1;
|
||||
static struct context *context;
|
||||
#endif
|
||||
|
||||
/*******************************************************************************
|
||||
@ -76,46 +72,25 @@ typedef struct tokenptr {
|
||||
|
||||
#include "GENERATED_config_tokens.h"
|
||||
|
||||
/*******************************************************************************
|
||||
* The (small) stack where identified literals are stored during the parsing
|
||||
* of a single command (like $workspace).
|
||||
******************************************************************************/
|
||||
|
||||
struct stack_entry {
|
||||
/* Just a pointer, not dynamically allocated. */
|
||||
const char *identifier;
|
||||
enum {
|
||||
STACK_STR = 0,
|
||||
STACK_LONG = 1,
|
||||
} type;
|
||||
union {
|
||||
char *str;
|
||||
long num;
|
||||
} val;
|
||||
};
|
||||
|
||||
/* 10 entries should be enough for everybody. */
|
||||
static struct stack_entry stack[10];
|
||||
|
||||
/*
|
||||
* Pushes a string (identified by 'identifier') on the stack. We simply use a
|
||||
* single array, since the number of entries we have to store is very small.
|
||||
*
|
||||
*/
|
||||
static void push_string(const char *identifier, const char *str) {
|
||||
static void push_string(struct stack *ctx, const char *identifier, const char *str) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].identifier != NULL &&
|
||||
strcmp(stack[c].identifier, identifier) != 0)
|
||||
if (ctx->stack[c].identifier != NULL &&
|
||||
strcmp(ctx->stack[c].identifier, identifier) != 0)
|
||||
continue;
|
||||
if (stack[c].identifier == NULL) {
|
||||
if (ctx->stack[c].identifier == NULL) {
|
||||
/* Found a free slot, let’s store it here. */
|
||||
stack[c].identifier = identifier;
|
||||
stack[c].val.str = sstrdup(str);
|
||||
stack[c].type = STACK_STR;
|
||||
ctx->stack[c].identifier = identifier;
|
||||
ctx->stack[c].val.str = sstrdup(str);
|
||||
ctx->stack[c].type = STACK_STR;
|
||||
} else {
|
||||
/* Append the value. */
|
||||
char *prev = stack[c].val.str;
|
||||
sasprintf(&(stack[c].val.str), "%s,%s", prev, str);
|
||||
char *prev = ctx->stack[c].val.str;
|
||||
sasprintf(&(ctx->stack[c].val.str), "%s,%s", prev, str);
|
||||
free(prev);
|
||||
}
|
||||
return;
|
||||
@ -130,14 +105,15 @@ static void push_string(const char *identifier, const char *str) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static void push_long(const char *identifier, long num) {
|
||||
static void push_long(struct stack *ctx, const char *identifier, long num) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].identifier != NULL)
|
||||
if (ctx->stack[c].identifier != NULL) {
|
||||
continue;
|
||||
}
|
||||
/* Found a free slot, let’s store it here. */
|
||||
stack[c].identifier = identifier;
|
||||
stack[c].val.num = num;
|
||||
stack[c].type = STACK_LONG;
|
||||
ctx->stack[c].identifier = identifier;
|
||||
ctx->stack[c].val.num = num;
|
||||
ctx->stack[c].type = STACK_LONG;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -150,33 +126,33 @@ static void push_long(const char *identifier, long num) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static const char *get_string(const char *identifier) {
|
||||
static const char *get_string(struct stack *ctx, const char *identifier) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].identifier == NULL)
|
||||
if (ctx->stack[c].identifier == NULL)
|
||||
break;
|
||||
if (strcmp(identifier, stack[c].identifier) == 0)
|
||||
return stack[c].val.str;
|
||||
if (strcmp(identifier, ctx->stack[c].identifier) == 0)
|
||||
return ctx->stack[c].val.str;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static long get_long(const char *identifier) {
|
||||
static long get_long(struct stack *ctx, const char *identifier) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].identifier == NULL)
|
||||
if (ctx->stack[c].identifier == NULL)
|
||||
break;
|
||||
if (strcmp(identifier, stack[c].identifier) == 0)
|
||||
return stack[c].val.num;
|
||||
if (strcmp(identifier, ctx->stack[c].identifier) == 0)
|
||||
return ctx->stack[c].val.num;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void clear_stack(void) {
|
||||
static void clear_stack(struct stack *ctx) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].type == STACK_STR)
|
||||
free(stack[c].val.str);
|
||||
stack[c].identifier = NULL;
|
||||
stack[c].val.str = NULL;
|
||||
stack[c].val.num = 0;
|
||||
if (ctx->stack[c].type == STACK_STR)
|
||||
free(ctx->stack[c].val.str);
|
||||
ctx->stack[c].identifier = NULL;
|
||||
ctx->stack[c].val.str = NULL;
|
||||
ctx->stack[c].val.num = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,50 +160,42 @@ static void clear_stack(void) {
|
||||
* The parser itself.
|
||||
******************************************************************************/
|
||||
|
||||
static cmdp_state state;
|
||||
static Match current_match;
|
||||
static struct ConfigResultIR subcommand_output;
|
||||
static struct ConfigResultIR command_output;
|
||||
|
||||
/* A list which contains the states that lead to the current state, e.g.
|
||||
* INITIAL, WORKSPACE_LAYOUT.
|
||||
* When jumping back to INITIAL, statelist_idx will simply be set to 1
|
||||
* (likewise for other states, e.g. MODE or BAR).
|
||||
* This list is used to process the nearest error token. */
|
||||
static cmdp_state statelist[10] = {INITIAL};
|
||||
/* NB: statelist_idx points to where the next entry will be inserted */
|
||||
static int statelist_idx = 1;
|
||||
|
||||
#include "GENERATED_config_call.h"
|
||||
|
||||
static void next_state(const cmdp_token *token) {
|
||||
static void next_state(const cmdp_token *token, struct parser_ctx *ctx) {
|
||||
cmdp_state _next_state = token->next_state;
|
||||
|
||||
//printf("token = name %s identifier %s\n", token->name, token->identifier);
|
||||
//printf("next_state = %d\n", token->next_state);
|
||||
if (token->next_state == __CALL) {
|
||||
subcommand_output.json_gen = command_output.json_gen;
|
||||
GENERATED_call(token->extra.call_identifier, &subcommand_output);
|
||||
struct ConfigResultIR subcommand_output = {
|
||||
.ctx = ctx,
|
||||
};
|
||||
GENERATED_call(&(ctx->current_match), ctx->stack, token->extra.call_identifier, &subcommand_output);
|
||||
if (subcommand_output.has_errors) {
|
||||
ctx->has_errors = true;
|
||||
}
|
||||
_next_state = subcommand_output.next_state;
|
||||
clear_stack();
|
||||
clear_stack(ctx->stack);
|
||||
}
|
||||
|
||||
state = _next_state;
|
||||
if (state == INITIAL) {
|
||||
clear_stack();
|
||||
ctx->state = _next_state;
|
||||
if (ctx->state == INITIAL) {
|
||||
clear_stack(ctx->stack);
|
||||
}
|
||||
|
||||
/* See if we are jumping back to a state in which we were in previously
|
||||
* (statelist contains INITIAL) and just move statelist_idx accordingly. */
|
||||
for (int i = 0; i < statelist_idx; i++) {
|
||||
if (statelist[i] != _next_state)
|
||||
for (int i = 0; i < ctx->statelist_idx; i++) {
|
||||
if ((cmdp_state)(ctx->statelist[i]) != _next_state) {
|
||||
continue;
|
||||
statelist_idx = i + 1;
|
||||
}
|
||||
ctx->statelist_idx = i + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Otherwise, the state is new and we add it to the list */
|
||||
statelist[statelist_idx++] = _next_state;
|
||||
ctx->statelist[ctx->statelist_idx++] = _next_state;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -257,7 +225,7 @@ static char *single_line(const char *start) {
|
||||
return result;
|
||||
}
|
||||
|
||||
struct ConfigResultIR *parse_config(const char *input, struct context *context) {
|
||||
static void parse_config(struct parser_ctx *ctx, const char *input, struct context *context) {
|
||||
/* Dump the entire config file into the debug log. We cannot just use
|
||||
* DLOG("%s", input); because one log message must not exceed 4 KiB. */
|
||||
const char *dumpwalk = input;
|
||||
@ -273,13 +241,11 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
|
||||
}
|
||||
linecnt++;
|
||||
}
|
||||
state = INITIAL;
|
||||
statelist_idx = 1;
|
||||
|
||||
/* A YAJL JSON generator used for formatting replies. */
|
||||
command_output.json_gen = yajl_gen_alloc(NULL);
|
||||
|
||||
y(array_open);
|
||||
ctx->state = INITIAL;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ctx->statelist[i] = INITIAL;
|
||||
}
|
||||
ctx->statelist_idx = 1;
|
||||
|
||||
const char *walk = input;
|
||||
const size_t len = strlen(input);
|
||||
@ -290,7 +256,10 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
|
||||
|
||||
// TODO: make this testable
|
||||
#ifndef TEST_PARSER
|
||||
cfg_criteria_init(¤t_match, &subcommand_output, INITIAL);
|
||||
struct ConfigResultIR subcommand_output = {
|
||||
.ctx = ctx,
|
||||
};
|
||||
cfg_criteria_init(&(ctx->current_match), &subcommand_output, INITIAL);
|
||||
#endif
|
||||
|
||||
/* The "<=" operator is intentional: We also handle the terminating 0-byte
|
||||
@ -303,7 +272,7 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
|
||||
|
||||
//printf("remaining input: %s\n", walk);
|
||||
|
||||
cmdp_token_ptr *ptr = &(tokens[state]);
|
||||
cmdp_token_ptr *ptr = &(tokens[ctx->state]);
|
||||
token_handled = false;
|
||||
for (c = 0; c < ptr->n; c++) {
|
||||
token = &(ptr->array[c]);
|
||||
@ -311,10 +280,11 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
|
||||
/* A literal. */
|
||||
if (token->name[0] == '\'') {
|
||||
if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
|
||||
if (token->identifier != NULL)
|
||||
push_string(token->identifier, token->name + 1);
|
||||
if (token->identifier != NULL) {
|
||||
push_string(ctx->stack, token->identifier, token->name + 1);
|
||||
}
|
||||
walk += strlen(token->name) - 1;
|
||||
next_state(token);
|
||||
next_state(token, ctx);
|
||||
token_handled = true;
|
||||
break;
|
||||
}
|
||||
@ -334,12 +304,13 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
|
||||
if (end == walk)
|
||||
continue;
|
||||
|
||||
if (token->identifier != NULL)
|
||||
push_long(token->identifier, num);
|
||||
if (token->identifier != NULL) {
|
||||
push_long(ctx->stack, token->identifier, num);
|
||||
}
|
||||
|
||||
/* Set walk to the first non-number character */
|
||||
walk = end;
|
||||
next_state(token);
|
||||
next_state(token, ctx);
|
||||
token_handled = true;
|
||||
break;
|
||||
}
|
||||
@ -382,14 +353,15 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
|
||||
inpos++;
|
||||
str[outpos] = beginning[inpos];
|
||||
}
|
||||
if (token->identifier)
|
||||
push_string(token->identifier, str);
|
||||
if (token->identifier) {
|
||||
push_string(ctx->stack, token->identifier, str);
|
||||
}
|
||||
free(str);
|
||||
/* If we are at the end of a quoted string, skip the ending
|
||||
* double quote. */
|
||||
if (*walk == '"')
|
||||
walk++;
|
||||
next_state(token);
|
||||
next_state(token, ctx);
|
||||
token_handled = true;
|
||||
break;
|
||||
}
|
||||
@ -398,7 +370,7 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
|
||||
if (strcmp(token->name, "line") == 0) {
|
||||
while (*walk != '\0' && *walk != '\n' && *walk != '\r')
|
||||
walk++;
|
||||
next_state(token);
|
||||
next_state(token, ctx);
|
||||
token_handled = true;
|
||||
linecnt++;
|
||||
walk++;
|
||||
@ -408,7 +380,7 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
|
||||
if (strcmp(token->name, "end") == 0) {
|
||||
//printf("checking for end: *%s*\n", walk);
|
||||
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
|
||||
next_state(token);
|
||||
next_state(token, ctx);
|
||||
token_handled = true;
|
||||
/* To make sure we start with an appropriate matching
|
||||
* datastructure for commands which do *not* specify any
|
||||
@ -416,7 +388,7 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
|
||||
* every command. */
|
||||
// TODO: make this testable
|
||||
#ifndef TEST_PARSER
|
||||
cfg_criteria_init(¤t_match, &subcommand_output, INITIAL);
|
||||
cfg_criteria_init(&(ctx->current_match), &subcommand_output, INITIAL);
|
||||
#endif
|
||||
linecnt++;
|
||||
walk++;
|
||||
@ -515,41 +487,24 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
|
||||
|
||||
context->has_errors = true;
|
||||
|
||||
/* Format this error message as a JSON reply. */
|
||||
y(map_open);
|
||||
ystr("success");
|
||||
y(bool, false);
|
||||
/* We set parse_error to true to distinguish this from other
|
||||
* errors. i3-nagbar is spawned upon keypresses only for parser
|
||||
* errors. */
|
||||
ystr("parse_error");
|
||||
y(bool, true);
|
||||
ystr("error");
|
||||
ystr(errormessage);
|
||||
ystr("input");
|
||||
ystr(input);
|
||||
ystr("errorposition");
|
||||
ystr(position);
|
||||
y(map_close);
|
||||
|
||||
/* Skip the rest of this line, but continue parsing. */
|
||||
while ((size_t)(walk - input) <= len && *walk != '\n')
|
||||
walk++;
|
||||
|
||||
free(position);
|
||||
free(errormessage);
|
||||
clear_stack();
|
||||
clear_stack(ctx->stack);
|
||||
|
||||
/* To figure out in which state to go (e.g. MODE or INITIAL),
|
||||
* we find the nearest state which contains an <error> token
|
||||
* and follow that one. */
|
||||
bool error_token_found = false;
|
||||
for (int i = statelist_idx - 1; (i >= 0) && !error_token_found; i--) {
|
||||
cmdp_token_ptr *errptr = &(tokens[statelist[i]]);
|
||||
for (int i = ctx->statelist_idx - 1; (i >= 0) && !error_token_found; i--) {
|
||||
cmdp_token_ptr *errptr = &(tokens[ctx->statelist[i]]);
|
||||
for (int j = 0; j < errptr->n; j++) {
|
||||
if (strcmp(errptr->array[j].name, "error") != 0)
|
||||
continue;
|
||||
next_state(&(errptr->array[j]));
|
||||
next_state(&(errptr->array[j]), ctx);
|
||||
error_token_found = true;
|
||||
break;
|
||||
}
|
||||
@ -558,10 +513,6 @@ struct ConfigResultIR *parse_config(const char *input, struct context *context)
|
||||
assert(error_token_found);
|
||||
}
|
||||
}
|
||||
|
||||
y(array_close);
|
||||
|
||||
return &command_output;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@ -612,9 +563,17 @@ int main(int argc, char *argv[]) {
|
||||
fprintf(stderr, "Syntax: %s <command>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
struct stack stack;
|
||||
memset(&stack, '\0', sizeof(struct stack));
|
||||
struct parser_ctx ctx = {
|
||||
.use_nagbar = false,
|
||||
.assume_v4 = false,
|
||||
.stack = &stack,
|
||||
};
|
||||
SLIST_INIT(&(ctx.variables));
|
||||
struct context context;
|
||||
context.filename = "<stdin>";
|
||||
parse_config(argv[1], &context);
|
||||
parse_config(&ctx, argv[1], &context);
|
||||
}
|
||||
|
||||
#else
|
||||
@ -636,6 +595,7 @@ static int detect_version(char *buf) {
|
||||
|
||||
/* check for some v4-only statements */
|
||||
if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 ||
|
||||
strncasecmp(line, "include", strlen("include")) == 0 ||
|
||||
strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 ||
|
||||
strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 ||
|
||||
strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) {
|
||||
@ -877,34 +837,63 @@ static char *get_resource(char *name) {
|
||||
return resource;
|
||||
}
|
||||
|
||||
/*
|
||||
* Releases the memory of all variables in ctx.
|
||||
*
|
||||
*/
|
||||
void free_variables(struct parser_ctx *ctx) {
|
||||
struct Variable *current;
|
||||
while (!SLIST_EMPTY(&(ctx->variables))) {
|
||||
current = SLIST_FIRST(&(ctx->variables));
|
||||
FREE(current->key);
|
||||
FREE(current->value);
|
||||
SLIST_REMOVE_HEAD(&(ctx->variables), variables);
|
||||
FREE(current);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Parses the given file by first replacing the variables, then calling
|
||||
* parse_config and possibly launching i3-nagbar.
|
||||
*
|
||||
*/
|
||||
bool parse_file(const char *f, bool use_nagbar) {
|
||||
struct variables_head variables = SLIST_HEAD_INITIALIZER(&variables);
|
||||
parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFile *included_file) {
|
||||
int fd;
|
||||
struct stat stbuf;
|
||||
char *buf;
|
||||
FILE *fstr;
|
||||
char buffer[4096], key[512], value[4096], *continuation = NULL;
|
||||
|
||||
if ((fd = open(f, O_RDONLY)) == -1)
|
||||
die("Could not open configuration file: %s\n", strerror(errno));
|
||||
char *old_dir = get_current_dir_name();
|
||||
char *dir = NULL;
|
||||
/* dirname(3) might modify the buffer, so make a copy: */
|
||||
char *dirbuf = sstrdup(f);
|
||||
if ((dir = dirname(dirbuf)) != NULL) {
|
||||
LOG("Changing working directory to config file directory %s\n", dir);
|
||||
if (chdir(dir) == -1) {
|
||||
ELOG("chdir(%s) failed: %s\n", dir, strerror(errno));
|
||||
return PARSE_FILE_FAILED;
|
||||
}
|
||||
}
|
||||
free(dirbuf);
|
||||
|
||||
if (fstat(fd, &stbuf) == -1)
|
||||
die("Could not fstat file: %s\n", strerror(errno));
|
||||
if ((fd = open(f, O_RDONLY)) == -1) {
|
||||
return PARSE_FILE_FAILED;
|
||||
}
|
||||
|
||||
if (fstat(fd, &stbuf) == -1) {
|
||||
return PARSE_FILE_FAILED;
|
||||
}
|
||||
|
||||
buf = scalloc(stbuf.st_size + 1, 1);
|
||||
|
||||
if ((fstr = fdopen(fd, "r")) == NULL)
|
||||
die("Could not fdopen: %s\n", strerror(errno));
|
||||
if ((fstr = fdopen(fd, "r")) == NULL) {
|
||||
return PARSE_FILE_FAILED;
|
||||
}
|
||||
|
||||
FREE(current_config);
|
||||
current_config = scalloc(stbuf.st_size + 1, 1);
|
||||
if ((ssize_t)fread(current_config, 1, stbuf.st_size, fstr) != stbuf.st_size) {
|
||||
die("Could not fread: %s\n", strerror(errno));
|
||||
included_file->raw_contents = scalloc(stbuf.st_size + 1, 1);
|
||||
if ((ssize_t)fread(included_file->raw_contents, 1, stbuf.st_size, fstr) != stbuf.st_size) {
|
||||
return PARSE_FILE_FAILED;
|
||||
}
|
||||
rewind(fstr);
|
||||
|
||||
@ -916,7 +905,7 @@ bool parse_file(const char *f, bool use_nagbar) {
|
||||
if (fgets(continuation, sizeof(buffer) - (continuation - buffer), fstr) == NULL) {
|
||||
if (feof(fstr))
|
||||
break;
|
||||
die("Could not read configuration file\n");
|
||||
return PARSE_FILE_FAILED;
|
||||
}
|
||||
if (buffer[strlen(buffer) - 1] != '\n' && !feof(fstr)) {
|
||||
ELOG("Your line continuation is too long, it exceeds %zd bytes\n", sizeof(buffer));
|
||||
@ -960,7 +949,7 @@ bool parse_file(const char *f, bool use_nagbar) {
|
||||
continue;
|
||||
}
|
||||
|
||||
upsert_variable(&variables, v_key, v_value);
|
||||
upsert_variable(&(ctx->variables), v_key, v_value);
|
||||
continue;
|
||||
} else if (strcasecmp(key, "set_from_resource") == 0) {
|
||||
char res_name[512] = {'\0'};
|
||||
@ -993,7 +982,7 @@ bool parse_file(const char *f, bool use_nagbar) {
|
||||
res_value = sstrdup(fallback);
|
||||
}
|
||||
|
||||
upsert_variable(&variables, v_key, res_value);
|
||||
upsert_variable(&(ctx->variables), v_key, res_value);
|
||||
FREE(res_value);
|
||||
continue;
|
||||
}
|
||||
@ -1014,7 +1003,7 @@ bool parse_file(const char *f, bool use_nagbar) {
|
||||
* variables (otherwise we will count them twice, which is bad when
|
||||
* 'extra' is negative) */
|
||||
char *bufcopy = sstrdup(buf);
|
||||
SLIST_FOREACH (current, &variables, variables) {
|
||||
SLIST_FOREACH (current, &(ctx->variables), variables) {
|
||||
int extra = (strlen(current->value) - strlen(current->key));
|
||||
char *next;
|
||||
for (next = bufcopy;
|
||||
@ -1034,12 +1023,12 @@ bool parse_file(const char *f, bool use_nagbar) {
|
||||
destwalk = new;
|
||||
while (walk < (buf + stbuf.st_size)) {
|
||||
/* Find the next variable */
|
||||
SLIST_FOREACH (current, &variables, variables) {
|
||||
SLIST_FOREACH (current, &(ctx->variables), variables) {
|
||||
current->next_match = strcasestr(walk, current->key);
|
||||
}
|
||||
nearest = NULL;
|
||||
int distance = stbuf.st_size;
|
||||
SLIST_FOREACH (current, &variables, variables) {
|
||||
SLIST_FOREACH (current, &(ctx->variables), variables) {
|
||||
if (current->next_match == NULL)
|
||||
continue;
|
||||
if ((current->next_match - walk) < distance) {
|
||||
@ -1064,7 +1053,10 @@ bool parse_file(const char *f, bool use_nagbar) {
|
||||
|
||||
/* analyze the string to find out whether this is an old config file (3.x)
|
||||
* or a new config file (4.x). If it’s old, we run the converter script. */
|
||||
int version = detect_version(buf);
|
||||
int version = 4;
|
||||
if (!ctx->assume_v4) {
|
||||
version = detect_version(buf);
|
||||
}
|
||||
if (version == 3) {
|
||||
/* We need to convert this v3 configuration */
|
||||
char *converted = migrate_config(new, strlen(new));
|
||||
@ -1090,17 +1082,18 @@ bool parse_file(const char *f, bool use_nagbar) {
|
||||
}
|
||||
}
|
||||
|
||||
context = scalloc(1, sizeof(struct context));
|
||||
included_file->variable_replaced_contents = sstrdup(new);
|
||||
|
||||
struct context *context = scalloc(1, sizeof(struct context));
|
||||
context->filename = f;
|
||||
parse_config(ctx, new, context);
|
||||
if (ctx->has_errors) {
|
||||
context->has_errors = true;
|
||||
}
|
||||
|
||||
struct ConfigResultIR *config_output = parse_config(new, context);
|
||||
yajl_gen_free(config_output->json_gen);
|
||||
|
||||
extract_workspace_names_from_bindings();
|
||||
check_for_duplicate_bindings(context);
|
||||
reorder_bindings();
|
||||
|
||||
if (use_nagbar && (context->has_errors || context->has_warnings || invalid_sets)) {
|
||||
if (ctx->use_nagbar && (context->has_errors || context->has_warnings || invalid_sets)) {
|
||||
ELOG("FYI: You are using i3 version %s\n", i3_version);
|
||||
if (version == 3)
|
||||
ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n");
|
||||
@ -1108,22 +1101,22 @@ bool parse_file(const char *f, bool use_nagbar) {
|
||||
start_config_error_nagbar(f, context->has_errors || invalid_sets);
|
||||
}
|
||||
|
||||
bool has_errors = context->has_errors;
|
||||
const bool has_errors = context->has_errors;
|
||||
|
||||
FREE(context->line_copy);
|
||||
free(context);
|
||||
free(new);
|
||||
free(buf);
|
||||
|
||||
while (!SLIST_EMPTY(&variables)) {
|
||||
current = SLIST_FIRST(&variables);
|
||||
FREE(current->key);
|
||||
FREE(current->value);
|
||||
SLIST_REMOVE_HEAD(&variables, variables);
|
||||
FREE(current);
|
||||
if (chdir(old_dir) == -1) {
|
||||
ELOG("chdir(%s) failed: %s\n", old_dir, strerror(errno));
|
||||
return PARSE_FILE_FAILED;
|
||||
}
|
||||
|
||||
return !has_errors;
|
||||
free(old_dir);
|
||||
if (has_errors) {
|
||||
return PARSE_FILE_CONFIG_ERRORS;
|
||||
}
|
||||
return PARSE_FILE_SUCCESS;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -14,22 +14,34 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static bool human_readable_key, loaded_config_file_name_key;
|
||||
static char *human_readable_version, *loaded_config_file_name;
|
||||
static bool human_readable_key;
|
||||
static bool loaded_config_file_name_key;
|
||||
static bool included_config_file_names;
|
||||
|
||||
static char *human_readable_version;
|
||||
static char *loaded_config_file_name;
|
||||
|
||||
static int version_string(void *ctx, const unsigned char *val, size_t len) {
|
||||
if (human_readable_key)
|
||||
if (human_readable_key) {
|
||||
sasprintf(&human_readable_version, "%.*s", (int)len, val);
|
||||
if (loaded_config_file_name_key)
|
||||
}
|
||||
if (loaded_config_file_name_key) {
|
||||
sasprintf(&loaded_config_file_name, "%.*s", (int)len, val);
|
||||
}
|
||||
if (included_config_file_names) {
|
||||
IncludedFile *file = scalloc(1, sizeof(IncludedFile));
|
||||
sasprintf(&(file->path), "%.*s", (int)len, val);
|
||||
TAILQ_INSERT_TAIL(&included_files, file, files);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int version_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
|
||||
human_readable_key = (stringlen == strlen("human_readable") &&
|
||||
strncmp((const char *)stringval, "human_readable", strlen("human_readable")) == 0);
|
||||
loaded_config_file_name_key = (stringlen == strlen("loaded_config_file_name") &&
|
||||
strncmp((const char *)stringval, "loaded_config_file_name", strlen("loaded_config_file_name")) == 0);
|
||||
#define KEY_MATCHES(x) (stringlen == strlen(x) && strncmp((const char *)stringval, x, strlen(x)) == 0)
|
||||
human_readable_key = KEY_MATCHES("human_readable");
|
||||
loaded_config_file_name_key = KEY_MATCHES("loaded_config_file_name");
|
||||
included_config_file_names = KEY_MATCHES("included_config_file_names");
|
||||
#undef KEY_MATCHES
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -38,6 +50,22 @@ static yajl_callbacks version_callbacks = {
|
||||
.yajl_map_key = version_map_key,
|
||||
};
|
||||
|
||||
static void print_config_path(const char *path, const char *role) {
|
||||
struct stat sb;
|
||||
time_t now;
|
||||
char mtime[64];
|
||||
|
||||
printf(" %s (%s)", path, role);
|
||||
if (stat(path, &sb) == -1) {
|
||||
printf("\n");
|
||||
ELOG("Cannot stat config file \"%s\"\n", path);
|
||||
} else {
|
||||
strftime(mtime, sizeof(mtime), "%c", localtime(&(sb.st_mtime)));
|
||||
time(&now);
|
||||
printf(" (last modified: %s, %.f seconds ago)\n", mtime, difftime(now, sb.st_mtime));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Connects to i3 to find out the currently running version. Useful since it
|
||||
* might be different from the version compiled into this binary (maybe the
|
||||
@ -98,17 +126,11 @@ void display_running_version(void) {
|
||||
printf("Running i3 version: %s (pid %s)\n", human_readable_version, pid_from_atom);
|
||||
|
||||
if (loaded_config_file_name) {
|
||||
struct stat sb;
|
||||
time_t now;
|
||||
char mtime[64];
|
||||
printf("Loaded i3 config: %s", loaded_config_file_name);
|
||||
if (stat(loaded_config_file_name, &sb) == -1) {
|
||||
printf("\n");
|
||||
ELOG("Cannot stat config file \"%s\"\n", loaded_config_file_name);
|
||||
} else {
|
||||
strftime(mtime, sizeof(mtime), "%c", localtime(&(sb.st_mtime)));
|
||||
time(&now);
|
||||
printf(" (Last modified: %s, %.f seconds ago)\n", mtime, difftime(now, sb.st_mtime));
|
||||
printf("Loaded i3 config:\n");
|
||||
print_config_path(loaded_config_file_name, "main");
|
||||
IncludedFile *file;
|
||||
TAILQ_FOREACH (file, &included_files, files) {
|
||||
print_config_path(file->path, "included");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,10 +278,10 @@ void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows) {
|
||||
*/
|
||||
void ewmh_update_sticky(xcb_window_t window, bool sticky) {
|
||||
if (sticky) {
|
||||
DLOG("Setting _NET_WM_STATE_STICKY for window = %d.\n", window);
|
||||
DLOG("Setting _NET_WM_STATE_STICKY for window = %08x.\n", window);
|
||||
xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY);
|
||||
} else {
|
||||
DLOG("Removing _NET_WM_STATE_STICKY for window = %d.\n", window);
|
||||
DLOG("Removing _NET_WM_STATE_STICKY for window = %08x.\n", window);
|
||||
xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY);
|
||||
}
|
||||
}
|
||||
@ -292,10 +292,10 @@ void ewmh_update_sticky(xcb_window_t window, bool sticky) {
|
||||
*/
|
||||
void ewmh_update_focused(xcb_window_t window, bool is_focused) {
|
||||
if (is_focused) {
|
||||
DLOG("Setting _NET_WM_STATE_FOCUSED for window = %d.\n", window);
|
||||
DLOG("Setting _NET_WM_STATE_FOCUSED for window = %08x.\n", window);
|
||||
xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
|
||||
} else {
|
||||
DLOG("Removing _NET_WM_STATE_FOCUSED for window = %d.\n", window);
|
||||
DLOG("Removing _NET_WM_STATE_FOCUSED for window = %08x.\n", window);
|
||||
xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
|
||||
}
|
||||
}
|
||||
|
129
src/handlers.c
129
src/handlers.c
@ -803,12 +803,12 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
||||
if (event->data.data32[0] == XCB_ICCCM_WM_STATE_ICONIC) {
|
||||
/* For compatiblity reasons, Wine will request iconic state and cannot ensure that the WM has agreed on it;
|
||||
* immediately revert to normal to avoid being stuck in a paused state. */
|
||||
DLOG("Client has requested iconic state, rejecting. (window = %d)\n", event->window);
|
||||
DLOG("Client has requested iconic state, rejecting. (window = %08x)\n", event->window);
|
||||
long data[] = {XCB_ICCCM_WM_STATE_NORMAL, XCB_NONE};
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, event->window,
|
||||
A_WM_STATE, A_WM_STATE, 32, 2, data);
|
||||
} else {
|
||||
DLOG("Not handling WM_CHANGE_STATE request. (window = %d, state = %d)\n", event->window, event->data.data32[0]);
|
||||
DLOG("Not handling WM_CHANGE_STATE request. (window = %08x, state = %d)\n", event->window, event->data.data32[0]);
|
||||
}
|
||||
} else if (event->type == A__NET_CURRENT_DESKTOP) {
|
||||
/* This request is used by pagers and bars to change the current
|
||||
@ -876,7 +876,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
||||
tree_close_internal(con, KILL_WINDOW, false);
|
||||
tree_render();
|
||||
} else {
|
||||
DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %d)\n", event->window);
|
||||
DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %08x)\n", event->window);
|
||||
}
|
||||
} else if (event->type == A__NET_WM_MOVERESIZE) {
|
||||
/*
|
||||
@ -885,7 +885,7 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
||||
*/
|
||||
Con *con = con_by_window_id(event->window);
|
||||
if (!con || !con_is_floating(con)) {
|
||||
DLOG("Couldn't find con for _NET_WM_MOVERESIZE request, or con not floating (window = %d)\n", event->window);
|
||||
DLOG("Couldn't find con for _NET_WM_MOVERESIZE request, or con not floating (window = %08x)\n", event->window);
|
||||
return;
|
||||
}
|
||||
DLOG("Handling _NET_WM_MOVERESIZE request (con = %p)\n", con);
|
||||
@ -1058,6 +1058,76 @@ static void handle_focus_in(xcb_focus_in_event_t *event) {
|
||||
tree_render();
|
||||
}
|
||||
|
||||
/*
|
||||
* Log FocusOut events.
|
||||
*
|
||||
*/
|
||||
static void handle_focus_out(xcb_focus_in_event_t *event) {
|
||||
Con *con = con_by_window_id(event->event);
|
||||
const char *window_name, *mode, *detail;
|
||||
|
||||
if (con != NULL) {
|
||||
window_name = con->name;
|
||||
if (window_name == NULL) {
|
||||
window_name = "<unnamed con>";
|
||||
}
|
||||
} else if (event->event == root) {
|
||||
window_name = "<the root window>";
|
||||
} else {
|
||||
window_name = "<unknown window>";
|
||||
}
|
||||
|
||||
switch (event->mode) {
|
||||
case XCB_NOTIFY_MODE_NORMAL:
|
||||
mode = "Normal";
|
||||
break;
|
||||
case XCB_NOTIFY_MODE_GRAB:
|
||||
mode = "Grab";
|
||||
break;
|
||||
case XCB_NOTIFY_MODE_UNGRAB:
|
||||
mode = "Ungrab";
|
||||
break;
|
||||
case XCB_NOTIFY_MODE_WHILE_GRABBED:
|
||||
mode = "WhileGrabbed";
|
||||
break;
|
||||
default:
|
||||
mode = "<unknown>";
|
||||
break;
|
||||
}
|
||||
|
||||
switch (event->detail) {
|
||||
case XCB_NOTIFY_DETAIL_ANCESTOR:
|
||||
detail = "Ancestor";
|
||||
break;
|
||||
case XCB_NOTIFY_DETAIL_VIRTUAL:
|
||||
detail = "Virtual";
|
||||
break;
|
||||
case XCB_NOTIFY_DETAIL_INFERIOR:
|
||||
detail = "Inferior";
|
||||
break;
|
||||
case XCB_NOTIFY_DETAIL_NONLINEAR:
|
||||
detail = "Nonlinear";
|
||||
break;
|
||||
case XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL:
|
||||
detail = "NonlinearVirtual";
|
||||
break;
|
||||
case XCB_NOTIFY_DETAIL_POINTER:
|
||||
detail = "Pointer";
|
||||
break;
|
||||
case XCB_NOTIFY_DETAIL_POINTER_ROOT:
|
||||
detail = "PointerRoot";
|
||||
break;
|
||||
case XCB_NOTIFY_DETAIL_NONE:
|
||||
detail = "NONE";
|
||||
break;
|
||||
default:
|
||||
detail = "unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
DLOG("focus change out: window 0x%08x (con %p, %s) lost focus with detail=%s, mode=%s\n", event->event, con, window_name, detail, mode);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handles ConfigureNotify events for the root window, which are generated when
|
||||
* the monitor configuration changed.
|
||||
@ -1074,6 +1144,23 @@ static void handle_configure_notify(xcb_configure_notify_event_t *event) {
|
||||
return;
|
||||
}
|
||||
randr_query_outputs();
|
||||
|
||||
ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
|
||||
}
|
||||
|
||||
/*
|
||||
* Handles SelectionClear events for the root window, which are generated when
|
||||
* we lose ownership of a selection.
|
||||
*/
|
||||
static void handle_selection_clear(xcb_selection_clear_event_t *event) {
|
||||
if (event->selection != wm_sn) {
|
||||
DLOG("SelectionClear for unknown selection %d, ignoring\n", event->selection);
|
||||
return;
|
||||
}
|
||||
LOG("Lost WM_Sn selection, exiting.\n");
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
/* unreachable */
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1086,6 +1173,16 @@ static bool handle_class_change(Con *con, xcb_get_property_reply_t *prop) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handles the WM_CLIENT_MACHINE property for assignments and criteria selection.
|
||||
*
|
||||
*/
|
||||
static bool handle_machine_change(Con *con, xcb_get_property_reply_t *prop) {
|
||||
window_update_machine(con->window, prop);
|
||||
con = remanage_window(con);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handles the _MOTIF_WM_HINTS property of specifing window deocration settings.
|
||||
*
|
||||
@ -1175,6 +1272,14 @@ static bool handle_i3_floating(Con *con, xcb_get_property_reply_t *prop) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool handle_windowicon_change(Con *con, xcb_get_property_reply_t *prop) {
|
||||
window_update_icon(con->window, prop);
|
||||
|
||||
x_push_changes(croot);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Returns false if the event could not be processed (e.g. the window could not
|
||||
* be found), true otherwise */
|
||||
typedef bool (*cb_property_handler_t)(Con *con, xcb_get_property_reply_t *property);
|
||||
@ -1197,7 +1302,9 @@ static struct property_handler_t property_handlers[] = {
|
||||
{0, UINT_MAX, handle_strut_partial_change},
|
||||
{0, UINT_MAX, handle_window_type},
|
||||
{0, UINT_MAX, handle_i3_floating},
|
||||
{0, 5 * sizeof(uint64_t), handle_motif_hints_change}};
|
||||
{0, 128, handle_machine_change},
|
||||
{0, 5 * sizeof(uint64_t), handle_motif_hints_change},
|
||||
{0, UINT_MAX, handle_windowicon_change}};
|
||||
#define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
|
||||
|
||||
/*
|
||||
@ -1219,7 +1326,9 @@ void property_handlers_init(void) {
|
||||
property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL;
|
||||
property_handlers[9].atom = A__NET_WM_WINDOW_TYPE;
|
||||
property_handlers[10].atom = A_I3_FLOATING_WINDOW;
|
||||
property_handlers[11].atom = A__MOTIF_WM_HINTS;
|
||||
property_handlers[11].atom = XCB_ATOM_WM_CLIENT_MACHINE;
|
||||
property_handlers[12].atom = A__MOTIF_WM_HINTS;
|
||||
property_handlers[13].atom = A__NET_WM_ICON;
|
||||
}
|
||||
|
||||
static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
|
||||
@ -1395,6 +1504,10 @@ void handle_event(int type, xcb_generic_event_t *event) {
|
||||
handle_focus_in((xcb_focus_in_event_t *)event);
|
||||
break;
|
||||
|
||||
case XCB_FOCUS_OUT:
|
||||
handle_focus_out((xcb_focus_out_event_t *)event);
|
||||
break;
|
||||
|
||||
case XCB_PROPERTY_NOTIFY: {
|
||||
xcb_property_notify_event_t *e = (xcb_property_notify_event_t *)event;
|
||||
last_timestamp = e->time;
|
||||
@ -1406,6 +1519,10 @@ void handle_event(int type, xcb_generic_event_t *event) {
|
||||
handle_configure_notify((xcb_configure_notify_event_t *)event);
|
||||
break;
|
||||
|
||||
case XCB_SELECTION_CLEAR:
|
||||
handle_selection_clear((xcb_selection_clear_event_t *)event);
|
||||
break;
|
||||
|
||||
default:
|
||||
//DLOG("Unhandled event of type %d\n", type);
|
||||
break;
|
||||
|
104
src/ipc.c
104
src/ipc.c
@ -27,22 +27,6 @@ char *current_socketpath = NULL;
|
||||
|
||||
TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients);
|
||||
|
||||
/*
|
||||
* Puts the given socket file descriptor into non-blocking mode or dies if
|
||||
* setting O_NONBLOCK failed. Non-blocking sockets are a good idea for our
|
||||
* IPC model because we should by no means block the window manager.
|
||||
*
|
||||
*/
|
||||
static void set_nonblock(int sockfd) {
|
||||
int flags = fcntl(sockfd, F_GETFL, 0);
|
||||
if (flags & O_NONBLOCK) {
|
||||
return;
|
||||
}
|
||||
flags |= O_NONBLOCK;
|
||||
if (fcntl(sockfd, F_SETFL, flags) < 0)
|
||||
err(-1, "Could not set O_NONBLOCK");
|
||||
}
|
||||
|
||||
static void ipc_client_timeout(EV_P_ ev_timer *w, int revents);
|
||||
static void ipc_socket_writeable_cb(EV_P_ struct ev_io *w, int revents);
|
||||
|
||||
@ -251,9 +235,9 @@ static void dump_rect(yajl_gen gen, const char *name, Rect r) {
|
||||
ystr(name);
|
||||
y(map_open);
|
||||
ystr("x");
|
||||
y(integer, r.x);
|
||||
y(integer, (int32_t)r.x);
|
||||
ystr("y");
|
||||
y(integer, r.y);
|
||||
y(integer, (int32_t)r.y);
|
||||
ystr("width");
|
||||
y(integer, r.width);
|
||||
ystr("height");
|
||||
@ -514,6 +498,9 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
|
||||
ystr(con->title_format);
|
||||
}
|
||||
|
||||
ystr("window_icon_padding");
|
||||
y(integer, con->window_icon_padding);
|
||||
|
||||
if (con->type == CT_WORKSPACE) {
|
||||
ystr("num");
|
||||
y(integer, con->num);
|
||||
@ -573,6 +560,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
|
||||
DUMP_PROPERTY("class", class_class);
|
||||
DUMP_PROPERTY("instance", class_instance);
|
||||
DUMP_PROPERTY("window_role", role);
|
||||
DUMP_PROPERTY("machine", machine);
|
||||
|
||||
if (con->window->name != NULL) {
|
||||
ystr("title");
|
||||
@ -662,6 +650,7 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
|
||||
DUMP_REGEX(instance);
|
||||
DUMP_REGEX(window_role);
|
||||
DUMP_REGEX(title);
|
||||
DUMP_REGEX(machine);
|
||||
|
||||
#undef DUMP_REGEX
|
||||
y(map_close);
|
||||
@ -1054,6 +1043,17 @@ IPC_HANDLER(get_version) {
|
||||
ystr("loaded_config_file_name");
|
||||
ystr(current_configpath);
|
||||
|
||||
ystr("included_config_file_names");
|
||||
y(array_open);
|
||||
IncludedFile *file;
|
||||
TAILQ_FOREACH (file, &included_files, files) {
|
||||
if (file == TAILQ_FIRST(&included_files)) {
|
||||
/* Skip the first file, which is current_configpath. */
|
||||
continue;
|
||||
}
|
||||
ystr(file->path);
|
||||
}
|
||||
y(array_close);
|
||||
y(map_close);
|
||||
|
||||
const unsigned char *payload;
|
||||
@ -1236,7 +1236,22 @@ IPC_HANDLER(get_config) {
|
||||
y(map_open);
|
||||
|
||||
ystr("config");
|
||||
ystr(current_config);
|
||||
IncludedFile *file = TAILQ_FIRST(&included_files);
|
||||
ystr(file->raw_contents);
|
||||
|
||||
ystr("included_configs");
|
||||
y(array_open);
|
||||
TAILQ_FOREACH (file, &included_files, files) {
|
||||
y(map_open);
|
||||
ystr("path");
|
||||
ystr(file->path);
|
||||
ystr("raw_contents");
|
||||
ystr(file->raw_contents);
|
||||
ystr("variable_replaced_contents");
|
||||
ystr(file->variable_replaced_contents);
|
||||
y(map_close);
|
||||
}
|
||||
y(array_close);
|
||||
|
||||
y(map_close);
|
||||
|
||||
@ -1523,57 +1538,6 @@ ipc_client *ipc_new_client_on_fd(EV_P_ int fd) {
|
||||
return client;
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates the UNIX domain socket at the given path, sets it to non-blocking
|
||||
* mode, bind()s and listen()s on it.
|
||||
*
|
||||
*/
|
||||
int ipc_create_socket(const char *filename) {
|
||||
int sockfd;
|
||||
|
||||
FREE(current_socketpath);
|
||||
|
||||
char *resolved = resolve_tilde(filename);
|
||||
DLOG("Creating IPC-socket at %s\n", resolved);
|
||||
char *copy = sstrdup(resolved);
|
||||
const char *dir = dirname(copy);
|
||||
if (!path_exists(dir))
|
||||
mkdirp(dir, DEFAULT_DIR_MODE);
|
||||
free(copy);
|
||||
|
||||
/* Unlink the unix domain socket before */
|
||||
unlink(resolved);
|
||||
|
||||
if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
|
||||
perror("socket()");
|
||||
free(resolved);
|
||||
return -1;
|
||||
}
|
||||
|
||||
(void)fcntl(sockfd, F_SETFD, FD_CLOEXEC);
|
||||
|
||||
struct sockaddr_un addr;
|
||||
memset(&addr, 0, sizeof(struct sockaddr_un));
|
||||
addr.sun_family = AF_LOCAL;
|
||||
strncpy(addr.sun_path, resolved, sizeof(addr.sun_path) - 1);
|
||||
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
|
||||
perror("bind()");
|
||||
free(resolved);
|
||||
return -1;
|
||||
}
|
||||
|
||||
set_nonblock(sockfd);
|
||||
|
||||
if (listen(sockfd, 5) < 0) {
|
||||
perror("listen()");
|
||||
free(resolved);
|
||||
return -1;
|
||||
}
|
||||
|
||||
current_socketpath = resolved;
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generates a json workspace event. Returns a dynamically allocated yajl
|
||||
* generator. Free with yajl_gen_free().
|
||||
|
@ -285,6 +285,9 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) {
|
||||
} else if (strcasecmp(last_key, "title") == 0) {
|
||||
current_swallow->title = regex_new(sval);
|
||||
swallow_is_empty = false;
|
||||
} else if (strcasecmp(last_key, "machine") == 0) {
|
||||
current_swallow->machine = regex_new(sval);
|
||||
swallow_is_empty = false;
|
||||
} else {
|
||||
ELOG("swallow key %s unknown\n", last_key);
|
||||
}
|
||||
@ -452,6 +455,10 @@ static int json_int(void *ctx, long long val) {
|
||||
if (strcasecmp(last_key, "current_border_width") == 0)
|
||||
json_node->current_border_width = val;
|
||||
|
||||
if (strcasecmp(last_key, "window_icon_padding") == 0) {
|
||||
json_node->window_icon_padding = val;
|
||||
}
|
||||
|
||||
if (strcasecmp(last_key, "depth") == 0)
|
||||
json_node->depth = val;
|
||||
|
||||
|
82
src/log.c
82
src/log.c
@ -12,6 +12,10 @@
|
||||
#include "all.h"
|
||||
#include "shmlog.h"
|
||||
|
||||
#include <ev.h>
|
||||
#include <libgen.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
@ -23,9 +27,6 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#if !defined(__OpenBSD__)
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <sys/sysctl.h>
|
||||
@ -60,6 +61,18 @@ static int logbuffer_shm;
|
||||
/* Size (in bytes) of physical memory */
|
||||
static long long physical_mem_bytes;
|
||||
|
||||
typedef struct log_client {
|
||||
int fd;
|
||||
|
||||
TAILQ_ENTRY(log_client)
|
||||
clients;
|
||||
} log_client;
|
||||
|
||||
TAILQ_HEAD(log_client_head, log_client)
|
||||
log_clients = TAILQ_HEAD_INITIALIZER(log_clients);
|
||||
|
||||
void log_broadcast_to_clients(const char *message, size_t len);
|
||||
|
||||
/*
|
||||
* Writes the offsets for the next write and for the last wrap to the
|
||||
* shmlog_header.
|
||||
@ -161,14 +174,6 @@ void open_logbuffer(void) {
|
||||
|
||||
header = (i3_shmlog_header *)logbuffer;
|
||||
|
||||
#if !defined(__OpenBSD__)
|
||||
pthread_condattr_t cond_attr;
|
||||
pthread_condattr_init(&cond_attr);
|
||||
if (pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED) != 0)
|
||||
fprintf(stderr, "pthread_condattr_setpshared() failed, i3-dump-log -f will not work!\n");
|
||||
pthread_cond_init(&(header->condvar), &cond_attr);
|
||||
#endif
|
||||
|
||||
logwalk = logbuffer + sizeof(i3_shmlog_header);
|
||||
loglastwrap = logbuffer + logbuffer_size;
|
||||
store_log_markers();
|
||||
@ -283,13 +288,10 @@ static void vlog(const bool print, const char *fmt, va_list args) {
|
||||
|
||||
store_log_markers();
|
||||
|
||||
#if !defined(__OpenBSD__)
|
||||
/* Wake up all (i3-dump-log) processes waiting for condvar. */
|
||||
pthread_cond_broadcast(&(header->condvar));
|
||||
#endif
|
||||
|
||||
if (print)
|
||||
fwrite(message, len, 1, stdout);
|
||||
|
||||
log_broadcast_to_clients(message, len);
|
||||
}
|
||||
}
|
||||
|
||||
@ -370,3 +372,51 @@ void purge_zerobyte_logfile(void) {
|
||||
rmdir(errorfilename);
|
||||
}
|
||||
}
|
||||
|
||||
char *current_log_stream_socket_path = NULL;
|
||||
|
||||
/*
|
||||
* Handler for activity on the listening socket, meaning that a new client
|
||||
* has just connected and we should accept() them. Sets up the event handler
|
||||
* for activity on the new connection and inserts the file descriptor into
|
||||
* the list of log clients.
|
||||
*
|
||||
*/
|
||||
void log_new_client(EV_P_ struct ev_io *w, int revents) {
|
||||
struct sockaddr_un peer;
|
||||
socklen_t len = sizeof(struct sockaddr_un);
|
||||
int fd;
|
||||
if ((fd = accept(w->fd, (struct sockaddr *)&peer, &len)) < 0) {
|
||||
if (errno != EINTR) {
|
||||
perror("accept()");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Close this file descriptor on exec() */
|
||||
(void)fcntl(fd, F_SETFD, FD_CLOEXEC);
|
||||
|
||||
set_nonblock(fd);
|
||||
|
||||
log_client *client = scalloc(1, sizeof(log_client));
|
||||
client->fd = fd;
|
||||
TAILQ_INSERT_TAIL(&log_clients, client, clients);
|
||||
|
||||
DLOG("log: new client connected on fd %d\n", fd);
|
||||
}
|
||||
|
||||
void log_broadcast_to_clients(const char *message, size_t len) {
|
||||
log_client *current = TAILQ_FIRST(&log_clients);
|
||||
while (current != TAILQ_END(&log_clients)) {
|
||||
/* XXX: In case slow connections turn out to be a problem here
|
||||
* (unlikely as long as i3-dump-log is the only consumer), introduce
|
||||
* buffering, similar to the IPC interface. */
|
||||
ssize_t n = writeall(current->fd, message, len);
|
||||
log_client *previous = current;
|
||||
current = TAILQ_NEXT(current, clients);
|
||||
if (n < 0) {
|
||||
TAILQ_REMOVE(&log_clients, previous, clients);
|
||||
free(previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
206
src/main.c
206
src/main.c
@ -24,6 +24,9 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#include <xcb/xcb_atom.h>
|
||||
#include <xcb/xinerama.h>
|
||||
#include <xcb/bigreq.h>
|
||||
|
||||
#ifdef I3_ASAN_ENABLED
|
||||
#include <sanitizer/lsan_interface.h>
|
||||
@ -63,6 +66,9 @@ xcb_timestamp_t last_timestamp = XCB_CURRENT_TIME;
|
||||
xcb_screen_t *root_screen;
|
||||
xcb_window_t root;
|
||||
|
||||
xcb_window_t wm_sn_selection_owner;
|
||||
xcb_atom_t wm_sn;
|
||||
|
||||
/* Color depth, visual id and colormap to use when creating windows and
|
||||
* pixmaps. Will use 32 bit depth and an appropriate visual, if available,
|
||||
* otherwise the root window’s default (usually 24 bit TrueColor). */
|
||||
@ -181,6 +187,9 @@ static void i3_exit(void) {
|
||||
}
|
||||
ipc_shutdown(SHUTDOWN_REASON_EXIT, -1);
|
||||
unlink(config.ipc_socket_path);
|
||||
if (current_log_stream_socket_path != NULL) {
|
||||
unlink(current_log_stream_socket_path);
|
||||
}
|
||||
xcb_disconnect(conn);
|
||||
|
||||
/* If a nagbar is active, kill it */
|
||||
@ -279,6 +288,7 @@ int main(int argc, char *argv[]) {
|
||||
char *fake_outputs = NULL;
|
||||
bool disable_signalhandler = false;
|
||||
bool only_check_config = false;
|
||||
bool replace_wm = false;
|
||||
static struct option long_options[] = {
|
||||
{"no-autostart", no_argument, 0, 'a'},
|
||||
{"config", required_argument, 0, 'c'},
|
||||
@ -301,6 +311,7 @@ int main(int argc, char *argv[]) {
|
||||
{"fake_outputs", required_argument, 0, 0},
|
||||
{"fake-outputs", required_argument, 0, 0},
|
||||
{"force-old-config-parser-v4.4-only", no_argument, 0, 0},
|
||||
{"replace", no_argument, 0, 'r'},
|
||||
{0, 0, 0, 0}};
|
||||
int option_index = 0, opt;
|
||||
|
||||
@ -325,7 +336,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
start_argv = argv;
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "c:CvmaL:hld:V", long_options, &option_index)) != -1) {
|
||||
while ((opt = getopt_long(argc, argv, "c:CvmaL:hld:Vr", long_options, &option_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'a':
|
||||
LOG("Autostart disabled using -a\n");
|
||||
@ -363,6 +374,9 @@ int main(int argc, char *argv[]) {
|
||||
case 'l':
|
||||
/* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */
|
||||
break;
|
||||
case 'r':
|
||||
replace_wm = true;
|
||||
break;
|
||||
case 0:
|
||||
if (strcmp(long_options[option_index].name, "force-xinerama") == 0 ||
|
||||
strcmp(long_options[option_index].name, "force_xinerama") == 0) {
|
||||
@ -443,6 +457,9 @@ int main(int argc, char *argv[]) {
|
||||
"\tThe default is %d bytes.\n",
|
||||
shmlog_size);
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "\t--replace\n"
|
||||
"\tReplace an existing window manager.\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "If you pass plain text arguments, i3 will interpret them as a command\n"
|
||||
"to send to a currently running i3 (like i3-msg). This allows you to\n"
|
||||
"use nice and logical commands, such as:\n"
|
||||
@ -570,6 +587,21 @@ int main(int argc, char *argv[]) {
|
||||
root_screen = xcb_aux_get_screen(conn, conn_screen);
|
||||
root = root_screen->root;
|
||||
|
||||
/* Prefetch X11 extensions that we are interested in. */
|
||||
xcb_prefetch_extension_data(conn, &xcb_xkb_id);
|
||||
xcb_prefetch_extension_data(conn, &xcb_shape_id);
|
||||
/* BIG-REQUESTS is used by libxcb internally. */
|
||||
xcb_prefetch_extension_data(conn, &xcb_big_requests_id);
|
||||
if (force_xinerama) {
|
||||
xcb_prefetch_extension_data(conn, &xcb_xinerama_id);
|
||||
} else {
|
||||
xcb_prefetch_extension_data(conn, &xcb_randr_id);
|
||||
}
|
||||
|
||||
/* Prepare for us to get a current timestamp as recommended by ICCCM */
|
||||
xcb_change_window_attributes(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){XCB_EVENT_MASK_PROPERTY_CHANGE});
|
||||
xcb_change_property(conn, XCB_PROP_MODE_APPEND, root, XCB_ATOM_SUPERSCRIPT_X, XCB_ATOM_CARDINAL, 32, 0, "");
|
||||
|
||||
/* Place requests for the atoms we need as soon as possible */
|
||||
#define xmacro(atom) \
|
||||
xcb_intern_atom_cookie_t atom##_cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
|
||||
@ -599,6 +631,8 @@ int main(int argc, char *argv[]) {
|
||||
visual_type = get_visualtype(root_screen);
|
||||
}
|
||||
|
||||
xcb_prefetch_maximum_request_length(conn);
|
||||
|
||||
init_dpi();
|
||||
|
||||
DLOG("root_depth = %d, visual_id = 0x%08x.\n", root_depth, visual_type->visual_id);
|
||||
@ -609,6 +643,22 @@ int main(int argc, char *argv[]) {
|
||||
xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root);
|
||||
xcb_query_pointer_cookie_t pointercookie = xcb_query_pointer(conn, root);
|
||||
|
||||
/* Get the PropertyNotify event we caused above */
|
||||
xcb_flush(conn);
|
||||
{
|
||||
xcb_generic_event_t *event;
|
||||
DLOG("waiting for PropertyNotify event\n");
|
||||
while ((event = xcb_wait_for_event(conn)) != NULL) {
|
||||
if (event->response_type == XCB_PROPERTY_NOTIFY) {
|
||||
last_timestamp = ((xcb_property_notify_event_t *)event)->time;
|
||||
free(event);
|
||||
break;
|
||||
}
|
||||
free(event);
|
||||
}
|
||||
DLOG("got timestamp %d\n", last_timestamp);
|
||||
}
|
||||
|
||||
/* Setup NetWM atoms */
|
||||
#define xmacro(name) \
|
||||
do { \
|
||||
@ -638,6 +688,103 @@ int main(int argc, char *argv[]) {
|
||||
force_xinerama = true;
|
||||
}
|
||||
|
||||
/* Acquire the WM_Sn selection. */
|
||||
{
|
||||
/* Get the WM_Sn atom */
|
||||
char *atom_name = xcb_atom_name_by_screen("WM_S", conn_screen);
|
||||
wm_sn_selection_owner = xcb_generate_id(conn);
|
||||
|
||||
if (atom_name == NULL) {
|
||||
ELOG("xcb_atom_name_by_screen(\"WM_S\", %d) failed, exiting\n", conn_screen);
|
||||
return 1;
|
||||
}
|
||||
|
||||
xcb_intern_atom_reply_t *atom_reply;
|
||||
atom_reply = xcb_intern_atom_reply(conn,
|
||||
xcb_intern_atom_unchecked(conn,
|
||||
0,
|
||||
strlen(atom_name),
|
||||
atom_name),
|
||||
NULL);
|
||||
free(atom_name);
|
||||
if (atom_reply == NULL) {
|
||||
ELOG("Failed to intern the WM_Sn atom, exiting\n");
|
||||
return 1;
|
||||
}
|
||||
wm_sn = atom_reply->atom;
|
||||
free(atom_reply);
|
||||
|
||||
/* Check if the selection is already owned */
|
||||
xcb_get_selection_owner_reply_t *selection_reply =
|
||||
xcb_get_selection_owner_reply(conn,
|
||||
xcb_get_selection_owner(conn, wm_sn),
|
||||
NULL);
|
||||
if (selection_reply && selection_reply->owner != XCB_NONE && !replace_wm) {
|
||||
ELOG("Another window manager is already running (WM_Sn is owned)");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Become the selection owner */
|
||||
xcb_create_window(conn,
|
||||
root_screen->root_depth,
|
||||
wm_sn_selection_owner, /* window id */
|
||||
root_screen->root, /* parent */
|
||||
-1, -1, 1, 1, /* geometry */
|
||||
0, /* border width */
|
||||
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||
root_screen->root_visual,
|
||||
0, NULL);
|
||||
xcb_change_property(conn,
|
||||
XCB_PROP_MODE_REPLACE,
|
||||
wm_sn_selection_owner,
|
||||
XCB_ATOM_WM_CLASS,
|
||||
XCB_ATOM_STRING,
|
||||
8,
|
||||
(strlen("i3-WM_Sn") + 1) * 2,
|
||||
"i3-WM_Sn\0i3-WM_Sn\0");
|
||||
|
||||
xcb_set_selection_owner(conn, wm_sn_selection_owner, wm_sn, last_timestamp);
|
||||
|
||||
if (selection_reply && selection_reply->owner != XCB_NONE) {
|
||||
unsigned int usleep_time = 100000; /* 0.1 seconds */
|
||||
int check_rounds = 150; /* Wait for a maximum of 15 seconds */
|
||||
xcb_get_geometry_reply_t *geom_reply = NULL;
|
||||
|
||||
DLOG("waiting for old WM_Sn selection owner to exit");
|
||||
do {
|
||||
free(geom_reply);
|
||||
usleep(usleep_time);
|
||||
if (check_rounds-- == 0) {
|
||||
ELOG("The old window manager is not exiting");
|
||||
return 1;
|
||||
}
|
||||
geom_reply = xcb_get_geometry_reply(conn,
|
||||
xcb_get_geometry(conn, selection_reply->owner),
|
||||
NULL);
|
||||
} while (geom_reply != NULL);
|
||||
}
|
||||
free(selection_reply);
|
||||
|
||||
/* Announce that we are the new owner */
|
||||
/* Every X11 event is 32 bytes long. Therefore, XCB will copy 32 bytes.
|
||||
* In order to properly initialize these bytes, we allocate 32 bytes even
|
||||
* though we only need less for an xcb_client_message_event_t */
|
||||
union {
|
||||
xcb_client_message_event_t message;
|
||||
char storage[32];
|
||||
} event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
event.message.response_type = XCB_CLIENT_MESSAGE;
|
||||
event.message.window = root_screen->root;
|
||||
event.message.format = 32;
|
||||
event.message.type = A_MANAGER;
|
||||
event.message.data.data32[0] = last_timestamp;
|
||||
event.message.data.data32[1] = wm_sn;
|
||||
event.message.data.data32[2] = wm_sn_selection_owner;
|
||||
|
||||
xcb_send_event(conn, 0, root_screen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY, event.storage);
|
||||
}
|
||||
|
||||
xcb_void_cookie_t cookie;
|
||||
cookie = xcb_change_window_attributes_checked(conn, root, XCB_CW_EVENT_MASK, (uint32_t[]){ROOT_EVENT_MASK});
|
||||
xcb_generic_error_t *error = xcb_request_check(conn, cookie);
|
||||
@ -663,9 +810,6 @@ int main(int argc, char *argv[]) {
|
||||
xcursor_set_root_cursor(XCURSOR_CURSOR_POINTER);
|
||||
|
||||
const xcb_query_extension_reply_t *extreply;
|
||||
xcb_prefetch_extension_data(conn, &xcb_xkb_id);
|
||||
xcb_prefetch_extension_data(conn, &xcb_shape_id);
|
||||
|
||||
extreply = xcb_get_extension_data(conn, &xcb_xkb_id);
|
||||
xkb_supported = extreply->present;
|
||||
if (!extreply->present) {
|
||||
@ -845,7 +989,7 @@ int main(int argc, char *argv[]) {
|
||||
tree_render();
|
||||
|
||||
/* Create the UNIX domain socket for IPC */
|
||||
int ipc_socket = ipc_create_socket(config.ipc_socket_path);
|
||||
int ipc_socket = create_socket(config.ipc_socket_path, ¤t_socketpath);
|
||||
if (ipc_socket == -1) {
|
||||
ELOG("Could not create the IPC socket, IPC disabled\n");
|
||||
} else {
|
||||
@ -854,9 +998,21 @@ int main(int argc, char *argv[]) {
|
||||
ev_io_start(main_loop, ipc_io);
|
||||
}
|
||||
|
||||
/* Also handle the UNIX domain sockets passed via socket activation. The
|
||||
* parameter 1 means "remove the environment variables", we don’t want to
|
||||
* pass these to child processes. */
|
||||
/* Chose a file name in /tmp/ based on the PID */
|
||||
char *log_stream_socket_path = get_process_filename("log-stream-socket");
|
||||
int log_socket = create_socket(log_stream_socket_path, ¤t_log_stream_socket_path);
|
||||
free(log_stream_socket_path);
|
||||
if (log_socket == -1) {
|
||||
ELOG("Could not create the log socket, i3-dump-log -f will not work\n");
|
||||
} else {
|
||||
struct ev_io *log_io = scalloc(1, sizeof(struct ev_io));
|
||||
ev_io_init(log_io, log_new_client, log_socket, EV_READ);
|
||||
ev_io_start(main_loop, log_io);
|
||||
}
|
||||
|
||||
/* Also handle the UNIX domain sockets passed via socket
|
||||
* activation. The parameter 0 means "do not remove the
|
||||
* environment variables", we need to be able to reexec. */
|
||||
listen_fds = sd_listen_fds(0);
|
||||
if (listen_fds < 0)
|
||||
ELOG("socket activation: Error in sd_listen_fds\n");
|
||||
@ -950,24 +1106,23 @@ int main(int argc, char *argv[]) {
|
||||
xcb_ungrab_server(conn);
|
||||
|
||||
if (autostart) {
|
||||
LOG("This is not an in-place restart, copying root window contents to a pixmap\n");
|
||||
/* When the root's window background is set to NONE, that might mean
|
||||
* that old content stays visible when a window is closed. That has
|
||||
* unpleasant effect of "my terminal (does not seem to) close!".
|
||||
*
|
||||
* There does not seem to be an easy way to query for this problem, so
|
||||
* we test for it: Open & close a window and check if the background is
|
||||
* redrawn or the window contents stay visible.
|
||||
*/
|
||||
LOG("This is not an in-place restart, checking if a wallpaper is set.\n");
|
||||
|
||||
xcb_screen_t *root = xcb_aux_get_screen(conn, conn_screen);
|
||||
uint16_t width = root->width_in_pixels;
|
||||
uint16_t height = root->height_in_pixels;
|
||||
xcb_pixmap_t pixmap = xcb_generate_id(conn);
|
||||
xcb_gcontext_t gc = xcb_generate_id(conn);
|
||||
|
||||
xcb_create_pixmap(conn, root->root_depth, pixmap, root->root, width, height);
|
||||
|
||||
xcb_create_gc(conn, gc, root->root,
|
||||
XCB_GC_FUNCTION | XCB_GC_PLANE_MASK | XCB_GC_FILL_STYLE | XCB_GC_SUBWINDOW_MODE,
|
||||
(uint32_t[]){XCB_GX_COPY, ~0, XCB_FILL_STYLE_SOLID, XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS});
|
||||
|
||||
xcb_copy_area(conn, root->root, pixmap, gc, 0, 0, 0, 0, width, height);
|
||||
xcb_change_window_attributes(conn, root->root, XCB_CW_BACK_PIXMAP, (uint32_t[]){pixmap});
|
||||
xcb_flush(conn);
|
||||
xcb_free_gc(conn, gc);
|
||||
xcb_free_pixmap(conn, pixmap);
|
||||
if (is_background_set(conn, root)) {
|
||||
LOG("A wallpaper is set, so no screenshot is necessary.\n");
|
||||
} else {
|
||||
LOG("No wallpaper set, copying root window contents to a pixmap\n");
|
||||
set_screenshot_as_wallpaper(conn, root);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__OpenBSD__)
|
||||
@ -1041,5 +1196,6 @@ int main(int argc, char *argv[]) {
|
||||
* when calling exit() */
|
||||
atexit(i3_exit);
|
||||
|
||||
sd_notify(1, "READY=1");
|
||||
ev_loop(main_loop, 0);
|
||||
}
|
||||
|
@ -116,7 +116,10 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||
utf8_title_cookie, title_cookie,
|
||||
class_cookie, leader_cookie, transient_cookie,
|
||||
role_cookie, startup_id_cookie, wm_hints_cookie,
|
||||
wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie, wm_desktop_cookie;
|
||||
wm_normal_hints_cookie, motif_wm_hints_cookie, wm_user_time_cookie, wm_desktop_cookie,
|
||||
wm_machine_cookie;
|
||||
|
||||
xcb_get_property_cookie_t wm_icon_cookie;
|
||||
|
||||
geomc = xcb_get_geometry(conn, d);
|
||||
|
||||
@ -189,6 +192,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||
motif_wm_hints_cookie = GET_PROPERTY(A__MOTIF_WM_HINTS, 5 * sizeof(uint64_t));
|
||||
wm_user_time_cookie = GET_PROPERTY(A__NET_WM_USER_TIME, UINT32_MAX);
|
||||
wm_desktop_cookie = GET_PROPERTY(A__NET_WM_DESKTOP, UINT32_MAX);
|
||||
wm_machine_cookie = GET_PROPERTY(XCB_ATOM_WM_CLIENT_MACHINE, UINT32_MAX);
|
||||
wm_icon_cookie = GET_PROPERTY(A__NET_WM_ICON, UINT32_MAX);
|
||||
|
||||
i3Window *cwindow = scalloc(1, sizeof(i3Window));
|
||||
cwindow->id = window;
|
||||
@ -202,6 +207,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||
window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL));
|
||||
window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL));
|
||||
window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL));
|
||||
window_update_icon(cwindow, xcb_get_property_reply(conn, wm_icon_cookie, NULL));
|
||||
window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL));
|
||||
window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
|
||||
window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
|
||||
@ -211,6 +217,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||
border_style_t motif_border_style = BS_NORMAL;
|
||||
window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
|
||||
window_update_normal_hints(cwindow, xcb_get_property_reply(conn, wm_normal_hints_cookie, NULL), geom);
|
||||
window_update_machine(cwindow, xcb_get_property_reply(conn, wm_machine_cookie, NULL));
|
||||
xcb_get_property_reply_t *type_reply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
|
||||
xcb_get_property_reply_t *state_reply = xcb_get_property_reply(conn, state_cookie, NULL);
|
||||
|
||||
|
28
src/match.c
28
src/match.c
@ -47,12 +47,14 @@ bool match_is_empty(Match *match) {
|
||||
match->instance == NULL &&
|
||||
match->window_role == NULL &&
|
||||
match->workspace == NULL &&
|
||||
match->machine == NULL &&
|
||||
match->urgent == U_DONTCHECK &&
|
||||
match->id == XCB_NONE &&
|
||||
match->window_type == UINT32_MAX &&
|
||||
match->con_id == NULL &&
|
||||
match->dock == M_NODOCK &&
|
||||
match->window_mode == WM_ANY);
|
||||
match->window_mode == WM_ANY &&
|
||||
match->match_all_windows == false);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -130,6 +132,8 @@ bool match_matches_window(Match *match, i3Window *window) {
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_WINDOW_FIELD(machine, machine, str);
|
||||
|
||||
Con *con = NULL;
|
||||
if (match->urgent == U_LATEST) {
|
||||
/* if the window isn't urgent, no sense in searching */
|
||||
@ -257,6 +261,10 @@ bool match_matches_window(Match *match, i3Window *window) {
|
||||
LOG("window_mode matches\n");
|
||||
}
|
||||
|
||||
/* NOTE: See the comment regarding 'all' in match_parse_property()
|
||||
* for an explanation of why match_all_windows isn't explicitly
|
||||
* checked. */
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -273,6 +281,7 @@ void match_free(Match *match) {
|
||||
regex_free(match->mark);
|
||||
regex_free(match->window_role);
|
||||
regex_free(match->workspace);
|
||||
regex_free(match->machine);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -390,6 +399,12 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(ctype, "machine") == 0) {
|
||||
regex_free(match->machine);
|
||||
match->machine = regex_new(cvalue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(ctype, "tiling") == 0) {
|
||||
match->window_mode = WM_TILING;
|
||||
return;
|
||||
@ -428,5 +443,16 @@ void match_parse_property(Match *match, const char *ctype, const char *cvalue) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* match_matches_window() only checks negatively, so match_all_windows
|
||||
* won't actually be used there, but that's OK because if no negative
|
||||
* match is found (e.g. because of a more restrictive criterion) the
|
||||
* return value of match_matches_window() is true.
|
||||
* Setting it here only serves to cause match_is_empty() to return false,
|
||||
* otherwise empty criteria rules apply, and that's not what we want. */
|
||||
if (strcmp(ctype, "all") == 0) {
|
||||
match->match_all_windows = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ELOG("Unknown criterion: %s\n", ctype);
|
||||
}
|
||||
|
@ -134,7 +134,6 @@ static void update_placeholder_contents(placeholder_state *state) {
|
||||
draw_util_clear_surface(&(state->surface), background);
|
||||
|
||||
// TODO: make i3font functions per-connection, at least these two for now…?
|
||||
xcb_flush(restore_conn);
|
||||
xcb_aux_sync(restore_conn);
|
||||
|
||||
Match *swallows;
|
||||
@ -153,6 +152,7 @@ static void update_placeholder_contents(placeholder_state *state) {
|
||||
APPEND_REGEX(instance);
|
||||
APPEND_REGEX(window_role);
|
||||
APPEND_REGEX(title);
|
||||
APPEND_REGEX(machine);
|
||||
|
||||
if (serialized == NULL) {
|
||||
DLOG("This swallows specification is not serializable?!\n");
|
||||
@ -179,7 +179,6 @@ static void update_placeholder_contents(placeholder_state *state) {
|
||||
int y = (state->rect.height / 2) - (config.font.height / 2);
|
||||
draw_util_text(line, &(state->surface), foreground, background, x, y, text_width);
|
||||
i3string_free(line);
|
||||
xcb_flush(restore_conn);
|
||||
xcb_aux_sync(restore_conn);
|
||||
}
|
||||
|
||||
|
@ -176,15 +176,6 @@ void exec_i3_utility(char *name, char *argv[]) {
|
||||
_exit(2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if the given path exists by calling stat().
|
||||
*
|
||||
*/
|
||||
bool path_exists(const char *path) {
|
||||
struct stat buf;
|
||||
return (stat(path, &buf) == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Goes through the list of arguments (for exec()) and add/replace the given option,
|
||||
* including the option name, its argument, and the option character.
|
||||
|
130
src/window.c
130
src/window.c
@ -18,7 +18,10 @@
|
||||
void window_free(i3Window *win) {
|
||||
FREE(win->class_class);
|
||||
FREE(win->class_instance);
|
||||
FREE(win->role);
|
||||
FREE(win->machine);
|
||||
i3string_free(win->name);
|
||||
cairo_surface_destroy(win->icon);
|
||||
FREE(win->ran_assignments);
|
||||
FREE(win);
|
||||
}
|
||||
@ -466,3 +469,130 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
|
||||
#undef MWM_DECOR_BORDER
|
||||
#undef MWM_DECOR_TITLE
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates the WM_CLIENT_MACHINE
|
||||
*
|
||||
*/
|
||||
void window_update_machine(i3Window *win, xcb_get_property_reply_t *prop) {
|
||||
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||
DLOG("WM_CLIENT_MACHINE not set.\n");
|
||||
FREE(prop);
|
||||
return;
|
||||
}
|
||||
|
||||
FREE(win->machine);
|
||||
win->machine = sstrndup((char *)xcb_get_property_value(prop), xcb_get_property_value_length(prop));
|
||||
LOG("WM_CLIENT_MACHINE changed to \"%s\"\n", win->machine);
|
||||
|
||||
free(prop);
|
||||
}
|
||||
|
||||
void window_update_icon(i3Window *win, xcb_get_property_reply_t *prop) {
|
||||
uint32_t *data = NULL;
|
||||
uint32_t width, height;
|
||||
uint64_t len = 0;
|
||||
const uint32_t pref_size = (uint32_t)(render_deco_height() - logical_px(2));
|
||||
|
||||
if (!prop || prop->type != XCB_ATOM_CARDINAL || prop->format != 32) {
|
||||
DLOG("_NET_WM_ICON is not set\n");
|
||||
FREE(prop);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t prop_value_len = xcb_get_property_value_length(prop);
|
||||
uint32_t *prop_value = (uint32_t *)xcb_get_property_value(prop);
|
||||
|
||||
/* Find an icon matching the preferred size.
|
||||
* If there is no such icon, take the smallest icon having at least
|
||||
* the preferred size.
|
||||
* If all icons are smaller than the preferred size, chose the largest.
|
||||
*/
|
||||
while (prop_value_len > (sizeof(uint32_t) * 2) && prop_value &&
|
||||
prop_value[0] && prop_value[1]) {
|
||||
const uint32_t cur_width = prop_value[0];
|
||||
const uint32_t cur_height = prop_value[1];
|
||||
/* Check that the property is as long as it should be (in bytes),
|
||||
handling integer overflow. "+2" to handle the width and height
|
||||
fields. */
|
||||
const uint64_t cur_len = cur_width * (uint64_t)cur_height;
|
||||
const uint64_t expected_len = (cur_len + 2) * 4;
|
||||
|
||||
if (expected_len > prop_value_len) {
|
||||
break;
|
||||
}
|
||||
|
||||
DLOG("Found _NET_WM_ICON of size: (%d,%d)\n", cur_width, cur_height);
|
||||
|
||||
const bool at_least_preferred_size = (cur_width >= pref_size &&
|
||||
cur_height >= pref_size);
|
||||
const bool smaller_than_current = (cur_width < width ||
|
||||
cur_height < height);
|
||||
const bool larger_than_current = (cur_width > width ||
|
||||
cur_height > height);
|
||||
const bool not_yet_at_preferred = (width < pref_size ||
|
||||
height < pref_size);
|
||||
if (len == 0 ||
|
||||
(at_least_preferred_size &&
|
||||
(smaller_than_current || not_yet_at_preferred)) ||
|
||||
(!at_least_preferred_size &&
|
||||
not_yet_at_preferred &&
|
||||
larger_than_current)) {
|
||||
len = cur_len;
|
||||
width = cur_width;
|
||||
height = cur_height;
|
||||
data = prop_value;
|
||||
}
|
||||
|
||||
if (width == pref_size && height == pref_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Find pointer to next icon in the reply. */
|
||||
prop_value_len -= expected_len;
|
||||
prop_value = (uint32_t *)(((uint8_t *)prop_value) + expected_len);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
DLOG("Could not get _NET_WM_ICON\n");
|
||||
FREE(prop);
|
||||
return;
|
||||
}
|
||||
|
||||
DLOG("Using icon of size (%d,%d) (preferred size: %d)\n",
|
||||
width, height, pref_size);
|
||||
|
||||
win->name_x_changed = true; /* trigger a redraw */
|
||||
|
||||
uint32_t *icon = smalloc(len * 4);
|
||||
|
||||
for (uint64_t i = 0; i < len; i++) {
|
||||
uint8_t r, g, b, a;
|
||||
const uint32_t pixel = data[2 + i];
|
||||
a = (pixel >> 24) & 0xff;
|
||||
r = (pixel >> 16) & 0xff;
|
||||
g = (pixel >> 8) & 0xff;
|
||||
b = (pixel >> 0) & 0xff;
|
||||
|
||||
/* Cairo uses premultiplied alpha */
|
||||
r = (r * a) / 0xff;
|
||||
g = (g * a) / 0xff;
|
||||
b = (b * a) / 0xff;
|
||||
|
||||
icon[i] = ((uint32_t)a << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
if (win->icon != NULL) {
|
||||
cairo_surface_destroy(win->icon);
|
||||
}
|
||||
win->icon = cairo_image_surface_create_for_data(
|
||||
(unsigned char *)icon,
|
||||
CAIRO_FORMAT_ARGB32,
|
||||
width,
|
||||
height,
|
||||
width * 4);
|
||||
static cairo_user_data_key_t free_data;
|
||||
cairo_surface_set_user_data(win->icon, &free_data, icon, free);
|
||||
|
||||
FREE(prop);
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ Con *workspace_get(const char *num) {
|
||||
|
||||
/* We set workspace->num to the number if this workspace’s name begins with
|
||||
* a positive number. Otherwise it’s a named ws and num will be 1. */
|
||||
const long parsed_num = ws_name_to_number(num);
|
||||
const int parsed_num = ws_name_to_number(num);
|
||||
|
||||
Con *output = get_assigned_output(num, parsed_num);
|
||||
/* if an assignment is not found, we create this workspace on the current output */
|
||||
@ -238,7 +238,6 @@ void extract_workspace_names_from_bindings(void) {
|
||||
*/
|
||||
Con *create_workspace_on_output(Output *output, Con *content) {
|
||||
/* add a workspace to this output */
|
||||
char *name;
|
||||
bool exists = true;
|
||||
Con *ws = con_new(NULL, NULL);
|
||||
ws->type = CT_WORKSPACE;
|
||||
@ -254,13 +253,16 @@ Con *create_workspace_on_output(Output *output, Con *content) {
|
||||
continue;
|
||||
}
|
||||
|
||||
exists = (get_existing_workspace_by_name(target_name) != NULL);
|
||||
const int num = ws_name_to_number(target_name);
|
||||
exists = (num == -1)
|
||||
? get_existing_workspace_by_name(target_name)
|
||||
: get_existing_workspace_by_num(num);
|
||||
if (!exists) {
|
||||
ws->name = sstrdup(target_name);
|
||||
/* Set ->num to the number of the workspace, if the name actually
|
||||
* is a number or starts with a number */
|
||||
ws->num = ws_name_to_number(ws->name);
|
||||
LOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
|
||||
ws->num = num;
|
||||
DLOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
|
||||
|
||||
break;
|
||||
}
|
||||
@ -281,6 +283,7 @@ Con *create_workspace_on_output(Output *output, Con *content) {
|
||||
}
|
||||
con_attach(ws, content, false);
|
||||
|
||||
char *name;
|
||||
sasprintf(&name, "[i3 con] workspace %s", ws->name);
|
||||
x_set_name(ws, name);
|
||||
free(name);
|
||||
@ -987,14 +990,14 @@ void workspace_move_to_output(Con *ws, Output *output) {
|
||||
bool used_assignment = false;
|
||||
struct Workspace_Assignment *assignment;
|
||||
TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
|
||||
bool attached;
|
||||
int num;
|
||||
if (!output_triggers_assignment(current_output, assignment)) {
|
||||
continue;
|
||||
}
|
||||
/* check if this workspace's name or num is already attached to the tree */
|
||||
num = ws_name_to_number(assignment->name);
|
||||
attached = ((num == -1) ? get_existing_workspace_by_name(assignment->name) : get_existing_workspace_by_num(num)) != NULL;
|
||||
const int num = ws_name_to_number(assignment->name);
|
||||
const bool attached = (num == -1)
|
||||
? get_existing_workspace_by_name(assignment->name)
|
||||
: get_existing_workspace_by_num(num);
|
||||
if (attached) {
|
||||
continue;
|
||||
}
|
||||
|
68
src/x.c
68
src/x.c
@ -537,6 +537,9 @@ void x_draw_decoration(Con *con) {
|
||||
|
||||
/* 2: draw the client.background, but only for the parts around the window_rect */
|
||||
if (con->window != NULL) {
|
||||
/* Clear visible windows before beginning to draw */
|
||||
draw_util_clear_surface(&(con->frame_buffer), (color_t){.red = 0.0, .green = 0.0, .blue = 0.0});
|
||||
|
||||
/* top area */
|
||||
draw_util_rectangle(&(con->frame_buffer), config.client.background,
|
||||
0, 0, r->width, w->y);
|
||||
@ -609,11 +612,14 @@ void x_draw_decoration(Con *con) {
|
||||
/* 5: draw title border */
|
||||
x_draw_title_border(con, p);
|
||||
|
||||
/* 6: draw the title */
|
||||
/* 6: draw the icon and title */
|
||||
int text_offset_y = (con->deco_rect.height - config.font.height) / 2;
|
||||
|
||||
const int title_padding = logical_px(2);
|
||||
struct Window *win = con->window;
|
||||
|
||||
const int deco_width = (int)con->deco_rect.width;
|
||||
const int title_padding = logical_px(2);
|
||||
|
||||
int mark_width = 0;
|
||||
if (config.show_marks && !TAILQ_EMPTY(&(con->marks_head))) {
|
||||
char *formatted_mark = sstrdup("");
|
||||
@ -652,7 +658,6 @@ void x_draw_decoration(Con *con) {
|
||||
}
|
||||
|
||||
i3String *title = NULL;
|
||||
struct Window *win = con->window;
|
||||
if (win == NULL) {
|
||||
if (con->title_format == NULL) {
|
||||
char *_title;
|
||||
@ -672,25 +677,45 @@ void x_draw_decoration(Con *con) {
|
||||
goto copy_pixmaps;
|
||||
}
|
||||
|
||||
/* icon_padding is applied horizontally only, the icon will always use all
|
||||
* available vertical space. */
|
||||
int icon_size = max(0, con->deco_rect.height - logical_px(2));
|
||||
int icon_padding = logical_px(max(1, con->window_icon_padding));
|
||||
int total_icon_space = icon_size + 2 * icon_padding;
|
||||
const bool has_icon = (con->window_icon_padding > -1) && win && win->icon && (total_icon_space < deco_width);
|
||||
if (!has_icon) {
|
||||
icon_size = icon_padding = total_icon_space = 0;
|
||||
}
|
||||
/* Determine x offsets according to title alignment */
|
||||
int icon_offset_x;
|
||||
int title_offset_x;
|
||||
switch (config.title_align) {
|
||||
case ALIGN_LEFT:
|
||||
/* (pad)[text ](pad)[mark + its pad) */
|
||||
title_offset_x = title_padding;
|
||||
/* (pad)[(pad)(icon)(pad)][text ](pad)[mark + its pad)
|
||||
* ^ ^--- title_offset_x
|
||||
* ^--- icon_offset_x */
|
||||
icon_offset_x = icon_padding;
|
||||
title_offset_x = title_padding + total_icon_space;
|
||||
break;
|
||||
case ALIGN_CENTER:
|
||||
/* (pad)[ text ](pad)[mark + its pad)
|
||||
* To center the text inside its allocated space, the surface
|
||||
* between the brackets, we use the formula
|
||||
* (surface_width - predict_text_width) / 2
|
||||
* where surface_width = deco_width - 2 * pad - mark_width
|
||||
* so, offset = pad + (surface_width - predict_text_width) / 2 =
|
||||
* = … = (deco_width - mark_width - predict_text_width) / 2 */
|
||||
title_offset_x = max(title_padding, (deco_width - mark_width - predict_text_width(title)) / 2);
|
||||
/* (pad)[ ][(pad)(icon)(pad)][text ](pad)[mark + its pad)
|
||||
* ^ ^--- title_offset_x
|
||||
* ^--- icon_offset_x
|
||||
* Text should come right after the icon (+padding). We calculate
|
||||
* the offset for the icon (white space in the title) by dividing
|
||||
* by two the total available area. That's the decoration width
|
||||
* minus the elements that come after icon_offset_x (icon, its
|
||||
* padding, text, marks). */
|
||||
icon_offset_x = max(icon_padding, (deco_width - icon_padding - icon_size - predict_text_width(title) - title_padding - mark_width) / 2);
|
||||
title_offset_x = max(title_padding, icon_offset_x + icon_padding + icon_size);
|
||||
break;
|
||||
case ALIGN_RIGHT:
|
||||
/* [mark + its pad](pad)[ text](pad) */
|
||||
title_offset_x = max(title_padding + mark_width, deco_width - title_padding - predict_text_width(title));
|
||||
/* [mark + its pad](pad)[ text][(pad)(icon)(pad)](pad)
|
||||
* ^ ^--- icon_offset_x
|
||||
* ^--- title_offset_x */
|
||||
title_offset_x = max(title_padding + mark_width, deco_width - title_padding - predict_text_width(title) - total_icon_space);
|
||||
/* Make sure the icon does not escape title boundaries */
|
||||
icon_offset_x = min(deco_width - icon_size - icon_padding - title_padding, title_offset_x + predict_text_width(title) + icon_padding);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -698,7 +723,16 @@ void x_draw_decoration(Con *con) {
|
||||
p->color->text, p->color->background,
|
||||
con->deco_rect.x + title_offset_x,
|
||||
con->deco_rect.y + text_offset_y,
|
||||
deco_width - mark_width - 2 * title_padding);
|
||||
deco_width - mark_width - 2 * title_padding - total_icon_space);
|
||||
if (has_icon) {
|
||||
draw_util_image(
|
||||
win->icon,
|
||||
&(parent->frame_buffer),
|
||||
con->deco_rect.x + icon_offset_x,
|
||||
con->deco_rect.y + logical_px(1),
|
||||
icon_size,
|
||||
icon_size);
|
||||
}
|
||||
|
||||
if (win == NULL || con->title_format != NULL) {
|
||||
I3STRING_FREE(title);
|
||||
@ -1418,6 +1452,8 @@ void x_set_i3_atoms(void) {
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_PID, XCB_ATOM_CARDINAL, 32, 1, &pid);
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_CONFIG_PATH, A_UTF8_STRING, 8,
|
||||
strlen(current_configpath), current_configpath);
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A_I3_LOG_STREAM_SOCKET_PATH, A_UTF8_STRING, 8,
|
||||
strlen(current_log_stream_socket_path), current_log_stream_socket_path);
|
||||
update_shmlog_atom();
|
||||
}
|
||||
|
||||
|
@ -410,7 +410,7 @@ Returns the name of the output on which this workspace resides
|
||||
|
||||
cmd 'focus output fake-1';
|
||||
cmd 'workspace 1';
|
||||
is(get_output_for_workspace('1', 'fake-0', 'Workspace 1 in output fake-0');
|
||||
is(get_output_for_workspace('1'), 'fake-0', 'Workspace 1 in output fake-0');
|
||||
|
||||
=cut
|
||||
sub get_output_for_workspace {
|
||||
@ -419,10 +419,12 @@ sub get_output_for_workspace {
|
||||
my $tree = $i3->get_tree->recv;
|
||||
my @outputs = @{$tree->{nodes}};
|
||||
|
||||
foreach (grep { not $_->{name} =~ /^__/ } @outputs) {
|
||||
my $output = $_->{name};
|
||||
foreach (grep { $_->{name} =~ "content" } @{$_->{nodes}}) {
|
||||
return $output if $_->{nodes}[0]->{name} =~ $ws_name;
|
||||
for my $output (@outputs) {
|
||||
next if $output->{name} eq '__i3';
|
||||
# get the first CT_CON of each output
|
||||
my $content = first { $_->{type} eq 'con' } @{$output->{nodes}};
|
||||
if (grep {$_->{name} eq $ws_name} @{$content->{nodes}}){
|
||||
return $output->{name};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -73,6 +73,7 @@ my $expected = {
|
||||
workspace_layout => 'default',
|
||||
current_border_width => -1,
|
||||
marks => $ignore,
|
||||
window_icon_padding => -1,
|
||||
};
|
||||
|
||||
# a shallow copy is sufficient, since we only ignore values at the root
|
||||
|
@ -54,6 +54,7 @@ is(parser_calls(
|
||||
'move workspace foobar; ' .
|
||||
'move workspace torrent; ' .
|
||||
'move workspace to output LVDS1; ' .
|
||||
'move to output LVDS1 DVI1; ' .
|
||||
'move workspace 3: foobar; ' .
|
||||
'move workspace "3: foobar"; ' .
|
||||
'move workspace "3: foobar, baz"; '),
|
||||
@ -62,7 +63,11 @@ is(parser_calls(
|
||||
"cmd_move_con_to_workspace_name(3, (null))\n" .
|
||||
"cmd_move_con_to_workspace_name(foobar, (null))\n" .
|
||||
"cmd_move_con_to_workspace_name(torrent, (null))\n" .
|
||||
"cmd_move_workspace_to_output(LVDS1)\n" .
|
||||
"cmd_move_con_to_output(LVDS1, 1)\n" .
|
||||
"cmd_move_con_to_output(NULL, 1)\n" .
|
||||
"cmd_move_con_to_output(LVDS1, 0)\n" .
|
||||
"cmd_move_con_to_output(DVI1, 0)\n" .
|
||||
"cmd_move_con_to_output(NULL, 0)\n" .
|
||||
"cmd_move_con_to_workspace_name(3: foobar, (null))\n" .
|
||||
"cmd_move_con_to_workspace_name(3: foobar, (null))\n" .
|
||||
"cmd_move_con_to_workspace_name(3: foobar, baz, (null))",
|
||||
@ -171,6 +176,7 @@ is(parser_calls('unknown_literal'),
|
||||
scratchpad
|
||||
swap
|
||||
title_format
|
||||
title_window_icon
|
||||
mode
|
||||
bar
|
||||
)) . "'\n" .
|
||||
|
@ -506,6 +506,7 @@ EOT
|
||||
|
||||
my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '#', '" . join("', '", 'set ', 'set ', qw(
|
||||
set_from_resource
|
||||
include
|
||||
bindsym
|
||||
bindcode
|
||||
bind
|
||||
|
@ -41,4 +41,12 @@ is_num_children($ws_top_right, 1, 'one container on the upper right workspace');
|
||||
is_num_children($ws_bottom_left, 0, 'no containers on the lower left workspace');
|
||||
is_num_children($ws_bottom_right, 1, 'one container on the lower right workspace');
|
||||
|
||||
# Also test with multiple explicit outputs
|
||||
cmd '[class="moveme"] move window to output fake-1 fake-2';
|
||||
|
||||
is_num_children($ws_top_left, 0, 'no containers on the upper left workspace');
|
||||
is_num_children($ws_top_right, 1, 'one container on the upper right workspace');
|
||||
is_num_children($ws_bottom_left, 1, 'one container on the lower left workspace');
|
||||
is_num_children($ws_bottom_right, 0, 'no containers on the lower right workspace');
|
||||
|
||||
done_testing;
|
||||
|
25
testcases/t/312-regress-layout-default.t
Normal file
25
testcases/t/312-regress-layout-default.t
Normal file
@ -0,0 +1,25 @@
|
||||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
#
|
||||
# Please read the following documents before working on tests:
|
||||
# • https://build.i3wm.org/docs/testsuite.html
|
||||
# (or docs/testsuite)
|
||||
#
|
||||
# • https://build.i3wm.org/docs/lib-i3test.html
|
||||
# (alternatively: perldoc ./testcases/lib/i3test.pm)
|
||||
#
|
||||
# • https://build.i3wm.org/docs/ipc.html
|
||||
# (or docs/ipc)
|
||||
#
|
||||
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||
# (unless you are already familiar with Perl)
|
||||
#
|
||||
# Verifies i3 does not crash when using layout default.
|
||||
# Ticket: #4408
|
||||
# Bug still in: 4.19.2-83-gc8158875
|
||||
use i3test;
|
||||
|
||||
cmd 'layout default';
|
||||
fresh_workspace;
|
||||
|
||||
done_testing;
|
361
testcases/t/313-include.t
Normal file
361
testcases/t/313-include.t
Normal file
@ -0,0 +1,361 @@
|
||||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
#
|
||||
# Please read the following documents before working on tests:
|
||||
# • https://build.i3wm.org/docs/testsuite.html
|
||||
# (or docs/testsuite)
|
||||
#
|
||||
# • https://build.i3wm.org/docs/lib-i3test.html
|
||||
# (alternatively: perldoc ./testcases/lib/i3test.pm)
|
||||
#
|
||||
# • https://build.i3wm.org/docs/ipc.html
|
||||
# (or docs/ipc)
|
||||
#
|
||||
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||
# (unless you are already familiar with Perl)
|
||||
#
|
||||
# Verifies the include directive.
|
||||
|
||||
use File::Temp qw(tempfile tempdir);
|
||||
use File::Basename qw(basename);
|
||||
use i3test i3_autostart => 0;
|
||||
|
||||
# starts i3 with the given config, opens a window, returns its border style
|
||||
sub launch_get_border {
|
||||
my ($config) = @_;
|
||||
|
||||
my $pid = launch_with_config($config);
|
||||
|
||||
my $i3 = i3(get_socket_path(0));
|
||||
my $tmp = fresh_workspace;
|
||||
|
||||
my $window = open_window(name => 'special title');
|
||||
|
||||
my @content = @{get_ws_content($tmp)};
|
||||
cmp_ok(@content, '==', 1, 'one node on this workspace now');
|
||||
my $border = $content[0]->{border};
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
return $border;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# test thet windows get the default border
|
||||
#####################################################################
|
||||
|
||||
my $config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
EOT
|
||||
|
||||
is(launch_get_border($config), 'normal', 'normal border');
|
||||
|
||||
#####################################################################
|
||||
# now use a variable and for_window
|
||||
#####################################################################
|
||||
|
||||
my ($fh, $filename) = tempfile(UNLINK => 1);
|
||||
my $varconfig = <<'EOT';
|
||||
set $vartest special title
|
||||
for_window [title="$vartest"] border none
|
||||
EOT
|
||||
print $fh $varconfig;
|
||||
$fh->flush;
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
include $filename
|
||||
EOT
|
||||
|
||||
is(launch_get_border($config), 'none', 'no border');
|
||||
|
||||
################################################################################
|
||||
# nested includes
|
||||
################################################################################
|
||||
|
||||
my ($indirectfh, $indirectfilename) = tempfile(UNLINK => 1);
|
||||
print $indirectfh <<EOT;
|
||||
include $filename
|
||||
EOT
|
||||
$indirectfh->flush;
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
include $indirectfilename
|
||||
EOT
|
||||
|
||||
is(launch_get_border($config), 'none', 'no border');
|
||||
|
||||
################################################################################
|
||||
# nested includes with relative paths
|
||||
################################################################################
|
||||
|
||||
my $relative = basename($filename);
|
||||
my ($indirectfh2, $indirectfilename2) = tempfile(UNLINK => 1);
|
||||
print $indirectfh2 <<EOT;
|
||||
include $relative
|
||||
EOT
|
||||
$indirectfh2->flush;
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
include $indirectfilename2
|
||||
EOT
|
||||
|
||||
is(launch_get_border($config), 'none', 'no border');
|
||||
|
||||
################################################################################
|
||||
# command substitution
|
||||
################################################################################
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
include `echo $filename`
|
||||
EOT
|
||||
|
||||
is(launch_get_border($config), 'none', 'no border');
|
||||
|
||||
################################################################################
|
||||
# failing command substitution
|
||||
################################################################################
|
||||
|
||||
$config = <<'EOT';
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
include i3-`false`.conf
|
||||
|
||||
set $vartest special title
|
||||
for_window [title="$vartest"] border none
|
||||
EOT
|
||||
|
||||
is(launch_get_border($config), 'none', 'no border');
|
||||
|
||||
################################################################################
|
||||
# permission denied
|
||||
################################################################################
|
||||
|
||||
my ($permissiondeniedfh, $permissiondenied) = tempfile(UNLINK => 1);
|
||||
$permissiondeniedfh->flush;
|
||||
my $mode = 0055;
|
||||
chmod($mode, $permissiondenied);
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
include $permissiondenied
|
||||
include $filename
|
||||
EOT
|
||||
|
||||
is(launch_get_border($config), 'none', 'no border');
|
||||
|
||||
################################################################################
|
||||
# dangling symlink
|
||||
################################################################################
|
||||
|
||||
my ($danglingfh, $dangling) = tempfile(UNLINK => 1);
|
||||
unlink($dangling);
|
||||
symlink("/dangling", $dangling);
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
include $dangling
|
||||
set \$vartest special title
|
||||
for_window [title="\$vartest"] border none
|
||||
EOT
|
||||
|
||||
is(launch_get_border($config), 'none', 'no border');
|
||||
|
||||
################################################################################
|
||||
# variables defined in the main file and used in the included file
|
||||
################################################################################
|
||||
|
||||
my ($varfh, $var) = tempfile(UNLINK => 1);
|
||||
print $varfh <<'EOT';
|
||||
for_window [title="$vartest"] border none
|
||||
|
||||
EOT
|
||||
$varfh->flush;
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
set \$vartest special title
|
||||
include $var
|
||||
EOT
|
||||
|
||||
is(launch_get_border($config), 'none', 'no border');
|
||||
|
||||
SKIP: {
|
||||
skip "not implemented";
|
||||
|
||||
################################################################################
|
||||
# variables defined in the included file and used in the main file
|
||||
################################################################################
|
||||
|
||||
($varfh, $var) = tempfile(UNLINK => 1);
|
||||
print $varfh <<'EOT';
|
||||
set $vartest special title
|
||||
EOT
|
||||
$varfh->flush;
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
include $var
|
||||
for_window [title="\$vartest"] border none
|
||||
EOT
|
||||
|
||||
is(launch_get_border($config), 'none', 'no border');
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# workspace names are loaded in the correct order (before reorder_bindings)
|
||||
################################################################################
|
||||
|
||||
# The included config can be empty, the issue lies with calling parse_file
|
||||
# multiple times.
|
||||
my ($wsfh, $ws) = tempfile(UNLINK => 1);
|
||||
$wsfh->flush;
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
bindsym 1 workspace 1: eggs
|
||||
bindsym Mod4+Shift+1 workspace 11: tomatoes
|
||||
|
||||
include $var
|
||||
EOT
|
||||
|
||||
# starts i3 with the given config, opens a window, returns its border style
|
||||
sub launch_get_workspace_name {
|
||||
my ($config) = @_;
|
||||
|
||||
my $pid = launch_with_config($config);
|
||||
|
||||
my $i3 = i3(get_socket_path(0));
|
||||
my $name = $i3->get_workspaces->recv->[0]->{name};
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
is(launch_get_workspace_name($config), '1: eggs', 'workspace name');
|
||||
|
||||
################################################################################
|
||||
# loop prevention
|
||||
################################################################################
|
||||
|
||||
my ($loopfh1, $loopname1) = tempfile(UNLINK => 1);
|
||||
my ($loopfh2, $loopname2) = tempfile(UNLINK => 1);
|
||||
|
||||
print $loopfh1 <<EOT;
|
||||
include $loopname2
|
||||
EOT
|
||||
$loopfh1->flush;
|
||||
|
||||
print $loopfh2 <<EOT;
|
||||
include $loopname1
|
||||
EOT
|
||||
$loopfh2->flush;
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
# loop
|
||||
include $loopname1
|
||||
|
||||
set \$vartest special title
|
||||
for_window [title="\$vartest"] border none
|
||||
EOT
|
||||
|
||||
is(launch_get_border($config), 'none', 'no border');
|
||||
|
||||
################################################################################
|
||||
# Verify the GET_VERSION IPC reply contains all included files
|
||||
################################################################################
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
include $indirectfilename2
|
||||
EOT
|
||||
|
||||
my $pid = launch_with_config($config);
|
||||
|
||||
my $i3 = i3(get_socket_path(0));
|
||||
my $version = $i3->get_version()->recv;
|
||||
my $included = $version->{included_config_file_names};
|
||||
|
||||
is_deeply($included, [ $indirectfilename2, $filename ], 'included config file names correct');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
################################################################################
|
||||
# Verify the GET_CONFIG IPC reply returns the top-level config
|
||||
################################################################################
|
||||
|
||||
my $tmpdir = tempdir(CLEANUP => 1);
|
||||
my $socketpath = $tmpdir . "/config.sock";
|
||||
ok(! -e $socketpath, "$socketpath does not exist yet");
|
||||
|
||||
my ($indirectfh3, $indirectfilename3) = tempfile(UNLINK => 1);
|
||||
my $indirectconfig = <<EOT;
|
||||
for_window [title="\$vartest"] border none
|
||||
include $relative
|
||||
EOT
|
||||
print $indirectfh3 $indirectconfig;
|
||||
$indirectfh3->flush;
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
set \$vartest special title
|
||||
|
||||
include $indirectfilename3
|
||||
|
||||
ipc-socket $socketpath
|
||||
EOT
|
||||
|
||||
my $pid = launch_with_config($config, dont_add_socket_path => 1, dont_create_temp_dir => 1);
|
||||
|
||||
my $i3 = i3(get_socket_path(0));
|
||||
my $config_reply = $i3->get_config()->recv;
|
||||
|
||||
is($config_reply->{config}, $config, 'GET_CONFIG returns the top-level config file');
|
||||
|
||||
my $included = $config_reply->{included_configs};
|
||||
is(scalar @{$included}, 3, 'included_configs contains all 3 files');
|
||||
is($included->[0]->{raw_contents}, $config, 'included_configs->[0]->{raw_contents} contains top-level config');
|
||||
is($included->[1]->{raw_contents}, $indirectconfig, 'included_configs->[1]->{raw_contents} contains indirect config');
|
||||
is($included->[2]->{raw_contents}, $varconfig, 'included_configs->[2]->{raw_contents} contains variable config');
|
||||
|
||||
my $indirect_replaced_config = <<EOT;
|
||||
for_window [title="special title"] border none
|
||||
include $relative
|
||||
EOT
|
||||
is($included->[1]->{variable_replaced_contents}, $indirect_replaced_config, 'included_configs->[1]->{variable_replaced_contents} contains config with variables replaced');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
|
||||
done_testing;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user