Compare commits
141 Commits
fa9481cdc6
...
3056caf6e3
Author | SHA1 | Date | |
---|---|---|---|
3056caf6e3 | |||
3dff3d8b13 | |||
|
a5da4d54f3 | ||
|
3702960a87 | ||
|
6911c116e7 | ||
|
bfbe73f665 | ||
|
0f64420281 | ||
|
26990d90f2 | ||
|
ba1f40f45f | ||
|
c52f13900d | ||
|
8d64937054 | ||
|
9c8746c00f | ||
|
fa25afedd2 | ||
|
46de32eedd | ||
|
944a262688 | ||
|
aaee2b3eae | ||
|
dfb3850989 | ||
|
90d7b9769c | ||
|
16f83396b4 | ||
|
8fe28d1a95 | ||
|
d06e87eb8d | ||
|
3e184daf29 | ||
|
7d3a3ae0fb | ||
|
6984dff01a | ||
|
47b2caa116 | ||
|
57f984ae67 | ||
|
b85da284a7 | ||
|
ab6f1fd160 | ||
|
ed690c7ba0 | ||
|
d5c8319b6c | ||
|
1786b13f0d | ||
|
fd95a47183 | ||
|
30131ed697 | ||
|
a1e4b44955 | ||
|
029cb8af19 | ||
|
60c3fedb73 | ||
|
96614a2f32 | ||
|
1ba0eaca22 | ||
|
2ac6180b90 | ||
|
170a322cc2 | ||
|
d130126204 | ||
|
2b236955bd | ||
|
be27a2f50d | ||
|
804bca3a9a | ||
|
14795c303c | ||
|
e6a28b9475 | ||
|
69e13d7821 | ||
|
2a91514a31 | ||
|
9dcf37b428 | ||
|
9e3a9e8225 | ||
|
c8fd8eff21 | ||
|
6fe625f469 | ||
|
d26ddcbfe5 | ||
|
6e6af01b7a | ||
|
a59423df81 | ||
|
2bfa06b7df | ||
|
9c9b110ed1 | ||
|
588cc4c79c | ||
|
4d0323fa9e | ||
|
3f400b8ad0 | ||
|
a5791b2e64 | ||
|
b2c696f680 | ||
|
b82b3e85da | ||
|
6b658f88be | ||
|
5b0f848a40 | ||
|
9ac027234b | ||
|
2fbb36b95f | ||
|
b825dc124a | ||
|
0b89d4b2a7 | ||
|
327bca26d8 | ||
|
c45342e74f | ||
|
62eb0033b1 | ||
|
a68eb3a71e | ||
|
080c73d1a4 | ||
|
1f53ae4614 | ||
|
23bc304477 | ||
|
c6bfd05276 | ||
|
85252a3bd1 | ||
|
9ffcc51183 | ||
|
39afa033e4 | ||
|
3b9d70af41 | ||
|
c55b52a7cc | ||
|
131b0c5b3d | ||
|
aa876585e8 | ||
|
f1754e12c0 | ||
|
e12d2f6a1d | ||
|
b88ca36a5a | ||
|
7abd58abf2 | ||
|
d62183a2b8 | ||
|
9d6a8735eb | ||
|
decc37eba1 | ||
|
3f58d51ec6 | ||
|
304e815ed4 | ||
|
0af2bac9ed | ||
|
5e4ed2fc75 | ||
|
de3fc07123 | ||
|
b18b80ca40 | ||
|
5e759ed424 | ||
|
941229ee62 | ||
|
55d400b17d | ||
|
2ba393f084 | ||
|
6479cb7deb | ||
|
8128774386 | ||
|
a6c86fd794 | ||
|
4f3d4c26f6 | ||
|
c5dc0d8c93 | ||
|
06e31ece8f | ||
|
812ec43d46 | ||
|
8ec41334ec | ||
|
f6097d4a37 | ||
|
eddced6b45 | ||
|
8e9b29419f | ||
|
016d0b5f07 | ||
|
4ab34d5042 | ||
|
28671a622b | ||
|
3e434ba0ce | ||
|
6ae232a323 | ||
|
5caf49323c | ||
|
12cdf435aa | ||
|
d7b9a45ff3 | ||
|
0967021858 | ||
|
f0856c285c | ||
|
ac95dffd6b | ||
|
2bdcae8149 | ||
|
c0ef3caec8 | ||
|
d7f4707e05 | ||
|
5bc4280a48 | ||
|
8ade46bdf0 | ||
|
227c1538be | ||
|
516d442e9a | ||
|
8252144cc3 | ||
|
0ac5e248f2 | ||
|
5ce8e3241b | ||
|
e48b9aa284 | ||
|
f795c2c8da | ||
|
4b5ead023e | ||
|
ac368e7916 | ||
|
6fb58eb841 | ||
|
4db383e430 | ||
|
e9c63d3001 | ||
|
b242bcebcf |
45
.github/workflows/main.yml
vendored
45
.github/workflows/main.yml
vendored
@ -23,31 +23,22 @@ jobs:
|
||||
BALTO_TOKEN: ${{ secrets.BALTO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- run: git fetch --prune --unshallow
|
||||
- name: construct container name
|
||||
run: |
|
||||
echo "BASENAME=i3wm/travis-base:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base.Dockerfile)" >> $GITHUB_ENV
|
||||
echo "BASENAME_386=i3wm/travis-base-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-386.Dockerfile)" >> $GITHUB_ENV
|
||||
echo "BASENAME_UBUNTU=i3wm/travis-base-ubuntu:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu.Dockerfile)" >> $GITHUB_ENV
|
||||
echo "BASENAME_UBUNTU_386=i3wm/travis-base-ubuntu-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu-386.Dockerfile)" >> $GITHUB_ENV
|
||||
- name: fetch or build Docker container
|
||||
run: |
|
||||
docker pull ${{ env.BASENAME }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME }} travis/travis-base.Dockerfile
|
||||
- name: fetch or build extra Docker containers
|
||||
if: github.ref == 'refs/heads/next'
|
||||
run: |
|
||||
echo "::group::Ubuntu amd64"
|
||||
./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_UBUNTU }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU }} travis/travis-base-ubuntu.Dockerfile
|
||||
echo "::endgroup::"
|
||||
echo "::group::Debian i386"
|
||||
./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_386 }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_386 }} travis/travis-base-386.Dockerfile
|
||||
echo "::endgroup::"
|
||||
echo "::group::Ubuntu i386"
|
||||
./travis/skip-pkg.sh || docker pull ${{ env.BASENAME_UBUNTU_386 }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU_386 }} travis/travis-base-ubuntu-386.Dockerfile
|
||||
echo "::endgroup::"
|
||||
docker pull ${{ env.BASENAME_UBUNTU }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU }} travis/travis-base-ubuntu.Dockerfile
|
||||
- name: build i3
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf build; mkdir -p build && cd build && CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror -fno-common" meson .. -Ddocs=true -Dmans=true -Db_sanitize=address && ninja -v'
|
||||
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 -D_FORTIFY_SOURCE=3" meson setup .. -Ddocs=true -Dmans=true -Db_sanitize=address --buildtype=debugoptimized && ninja -v'
|
||||
- name: check spelling
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-spelling.pl
|
||||
@ -62,41 +53,39 @@ jobs:
|
||||
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'
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf distbuild; mkdir distbuild && cd distbuild && meson setup .. -Ddocs=true -Dmans=true && meson dist --no-tests'
|
||||
- name: build Debian packages
|
||||
if: github.ref == 'refs/heads/next'
|
||||
run: |
|
||||
echo "::group::Debian amd64"
|
||||
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/debian-build.sh deb/debian-amd64/DIST
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/debian-build.sh deb/debian-amd64/DIST
|
||||
echo "::endgroup::"
|
||||
echo "::group::Ubuntu amd64"
|
||||
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_UBUNTU }} ./travis/debian-build.sh deb/ubuntu-amd64/DIST
|
||||
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
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_UBUNTU }} ./travis/debian-build.sh deb/ubuntu-amd64/DIST
|
||||
echo "::endgroup::"
|
||||
- name: push Debian packages to balto
|
||||
if: github.ref == 'refs/heads/next'
|
||||
run: |
|
||||
./travis/skip-pkg.sh || travis/push-balto.sh
|
||||
travis/push-balto.sh
|
||||
- name: build docs
|
||||
if: github.ref == 'refs/heads/next'
|
||||
run: |
|
||||
./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/docs.sh
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/docs.sh
|
||||
- name: push docs to GitHub pages
|
||||
if: github.ref == 'refs/heads/next'
|
||||
run: |
|
||||
./travis/skip-pkg.sh || travis/deploy-github-pages.sh
|
||||
travis/deploy-github-pages.sh
|
||||
formatting:
|
||||
name: Check formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: check & print release notes
|
||||
run: ./release-notes/generator.pl
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get install -y clang-format-10
|
||||
sudo apt-get install -y clang-format-12
|
||||
- name: Check formatting
|
||||
run: clang-format-10 --dry-run --Werror $(git ls-files '*.c' '*.h')
|
||||
run: clang-format-12 --dry-run --Werror $(git ls-files '*.c' '*.h')
|
||||
- name: Verify safe wrapper functions are used
|
||||
run: ./travis/check-safe-wrappers.sh
|
||||
|
@ -1,27 +0,0 @@
|
||||
|
||||
┌──────────────────────────────┐
|
||||
│ Release notes for i3 v4.20.1 │
|
||||
└──────────────────────────────┘
|
||||
|
||||
This is i3 v4.20.1. This version is considered stable. All users of i3 are
|
||||
strongly encouraged to upgrade.
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Bugfixes │
|
||||
└────────────────────────────┘
|
||||
|
||||
• i3bar: fix crash with multiple monitors
|
||||
• xmlto: fix broken .TH line by extending title length
|
||||
• i3-msg: fix --raw short form (-r) in manpage
|
||||
• libi3: add missing sys/stat.h header
|
||||
• use getcwd(NULL, 0) instead of GNU extension get_current_dir_name()
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Thanks! │
|
||||
└────────────────────────────┘
|
||||
|
||||
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
|
||||
|
||||
rvalieris, Jakob Haufe, lycurgus, Baptiste Daroussin
|
||||
|
||||
-- Michael Stapelberg, 2021-11-03
|
55
RELEASE-NOTES-4.22
Normal file
55
RELEASE-NOTES-4.22
Normal file
@ -0,0 +1,55 @@
|
||||
|
||||
┌──────────────────────────────┐
|
||||
│ Release notes for i3 v4.22 │
|
||||
└──────────────────────────────┘
|
||||
|
||||
This is i3 v4.22. This version is considered stable. All users of i3 are
|
||||
strongly encouraged to upgrade.
|
||||
|
||||
The biggest change in this release is the merge of the i3-gaps fork.
|
||||
The i3-gaps fork was the most popular fork of i3, adding the option to
|
||||
show gaps between tiled windows and/or the screen edges.
|
||||
|
||||
See https://i3wm.org/docs/userguide.html#gaps for more details.
|
||||
|
||||
Instead of maintaining two versions of i3 (both upstream and downstream,
|
||||
meaning in Linux distributions and other package collections),
|
||||
we concluded it would be better for everyone to merge this feature.
|
||||
|
||||
For users of i3: gaps are off by default, so there is no change in behavior.
|
||||
For users of i3-gaps: the configuration is compatible, so you can switch
|
||||
to i3 v4.22 or newer, without any changes in behavior.
|
||||
|
||||
Thanks to Ingo Bürk for maintaining i3-gaps for many years,
|
||||
for becoming a core i3 maintainer and for helping make this merge possible!
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Changes in i3 v4.22 │
|
||||
└────────────────────────────┘
|
||||
|
||||
• i3bar: bar { padding } config directive now implemented (supports bar { height } from i3-gaps)
|
||||
• i3-dmenu-desktop: allow more than one --entry-type with the --show-duplicates flag
|
||||
• You can now enable gaps using the gaps config directive and/or command
|
||||
• colors now support an optional alpha value at the end (#rrggbbaa)
|
||||
• the hide_edge_borders option now supports the smart_no_gaps keyword
|
||||
• Support nonprimary keyword for outputs
|
||||
• add "mode" field in binding event
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Bugfixes │
|
||||
└────────────────────────────┘
|
||||
|
||||
• gaps: workspace gaps assignments are no longer order-dependent
|
||||
• Fix compliance to _MOTIF_WM_HINTS spec when all decorations are set
|
||||
• The floating_from and tiling_from criteria now also work in commands
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Thanks! │
|
||||
└────────────────────────────┘
|
||||
|
||||
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
|
||||
|
||||
bodea, Demian, Erich Heine, Ingo Bürk, Matias Goldfeld, Orestis Floros,
|
||||
Tudor Brindus
|
||||
|
||||
-- Michael Stapelberg, 2023-01-02
|
24
debian/changelog
vendored
24
debian/changelog
vendored
@ -1,3 +1,27 @@
|
||||
i3-wm (4.22-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Mon, 02 Jan 2023 09:46:22 +0100
|
||||
|
||||
i3-wm (4.21.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Mon, 24 Oct 2022 21:22:36 +0200
|
||||
|
||||
i3-wm (4.21.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Mon, 24 Oct 2022 21:22:36 +0200
|
||||
|
||||
i3-wm (4.21-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Wed, 21 Sep 2022 18:14:14 +0200
|
||||
|
||||
i3-wm (4.20.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
BIN
docs/gaps1920.png
Normal file
BIN
docs/gaps1920.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
@ -564,9 +564,7 @@ split container.
|
||||
==== Default layout
|
||||
|
||||
In default layout, containers are placed horizontally or vertically next to
|
||||
each other (depending on the +con->orientation+). If a child is a leaf node (as
|
||||
opposed to a split container) and has border style "normal", appropriate space
|
||||
will be reserved for its window decoration.
|
||||
each other (depending on the +con->orientation+).
|
||||
|
||||
==== Stacked layout
|
||||
|
||||
|
184
docs/i3bar-workspace-protocol
Normal file
184
docs/i3bar-workspace-protocol
Normal file
@ -0,0 +1,184 @@
|
||||
i3bar workspace buttons protocol
|
||||
================================
|
||||
|
||||
This document explains the protocol in which i3bar expects input for
|
||||
configuring workspace buttons. This feature is available since i3 version 4.23.
|
||||
|
||||
The program defined by the +workspace_command+ configuration option for i3bar can
|
||||
modify the workspace buttons displayed by i3bar. The command should constantly
|
||||
print in its standard output a stream of messages following the protocol
|
||||
defined in this page.
|
||||
|
||||
If you are looking for the status line protocol instead, see https://i3wm.org/docs/i3bar-protocol.html.
|
||||
|
||||
== The protocol
|
||||
|
||||
Each message should be a newline-delimited JSON array. The array is in the same
|
||||
format as the +GET_WORKSPACES+ ipc event, see
|
||||
https://i3wm.org/docs/ipc.html#_workspaces_reply.
|
||||
|
||||
As an example, this is the output of the +i3-msg -t get_workspaces+ command:
|
||||
------------------------------
|
||||
[
|
||||
{
|
||||
"id": 94131549984064,
|
||||
"num": 1,
|
||||
"name": "1",
|
||||
"visible": false,
|
||||
"focused": false,
|
||||
"output": "HDMI-A-0",
|
||||
"urgent": false
|
||||
},
|
||||
{
|
||||
"id": 94131550477584,
|
||||
"num": 2,
|
||||
"name": "2",
|
||||
"visible": true,
|
||||
"focused": true,
|
||||
"output": "HDMI-A-0",
|
||||
"urgent": false
|
||||
},
|
||||
{
|
||||
"id": 94131550452704,
|
||||
"num": 3,
|
||||
"name": "3:some workspace",
|
||||
"visible": false,
|
||||
"focused": false,
|
||||
"output": "HDMI-A-0",
|
||||
"urgent": false
|
||||
}
|
||||
]
|
||||
------------------------------
|
||||
|
||||
Please note that this example was pretty printed for human consumption, with
|
||||
the +"rect"+ field removed. Workspace button commands should output each array
|
||||
in one line.
|
||||
|
||||
Each element in the array represents a workspace. i3bar creates one workspace
|
||||
button for each element in the array. The order of these buttons is the same as
|
||||
the order of the elements in the array.
|
||||
|
||||
In general, we recommend subscribing to the +workspace+ and +output+
|
||||
https://i3wm.org/docs/ipc.html#_workspace_event[events],
|
||||
fetching the current workspace information with +GET_WORKSPACES+, modifying the
|
||||
JSON array in the response according to your needs and then printing it to the
|
||||
standard output. However, you are free to build a new message from the ground
|
||||
up.
|
||||
|
||||
=== Workspace objects in detail
|
||||
|
||||
The documentation of +GET_WORKSPACES+ should be sufficient to understand the
|
||||
meaning of each property but here we provide extra notes for each property and
|
||||
its meaning with respect to i3bar.
|
||||
|
||||
All properties but +name+ are optional.
|
||||
|
||||
id (integer)::
|
||||
If it is included it will be used to switch to that workspace when you
|
||||
click the corresponding button. If it's not provided, the +name+ will be
|
||||
used. You can use the +id+ field to present workspaces under a modified
|
||||
name.
|
||||
num (integer)::
|
||||
The only use of a workspace's number is if the +strip_workspace_numbers+
|
||||
setting is enabled.
|
||||
name (string)::
|
||||
The only required property. If an +id+ is provided you can freely change
|
||||
the +name+ as you wish, effectively renaming the buttons of i3bar.
|
||||
visible (boolean)::
|
||||
Defaults to +false+ if not included. +focused+ takes precedence over it,
|
||||
however +visible+ is important for more than one monitors.
|
||||
focused (boolean)::
|
||||
Defaults to +false+ if not included. Generally, exactly one of the
|
||||
workspaces should be +focused+. If not, no button will have the
|
||||
+focused_workspace+ color.
|
||||
urgent (boolean)::
|
||||
Defaults to +false+ if not included.
|
||||
rect (map)::
|
||||
Not used by i3bar but will be ignored.
|
||||
output (string)::
|
||||
Defaults to the primary output if not included.
|
||||
|
||||
== Examples
|
||||
|
||||
These example scripts require the https://stedolan.github.io/jq/[jq] utility to
|
||||
be installed but otherwise just use the standard +i3-msg+ utility included with
|
||||
i3. However, you can write your own scripts in your preferred language, with
|
||||
the help of one of the
|
||||
https://i3wm.org/docs/ipc.html#_see_also_existing_libraries[pre-existing i3
|
||||
libraries]
|
||||
|
||||
=== Base configuration
|
||||
|
||||
------------------------------
|
||||
bar {
|
||||
…
|
||||
workspace_command /path/to/your/script.sh
|
||||
…
|
||||
}
|
||||
------------------------------
|
||||
|
||||
=== Re-create the default behaviour of i3bar
|
||||
|
||||
Not very useful by itself but this will be the basic building block of all the
|
||||
following scripts. This one does not require +jq+.
|
||||
|
||||
------------------------------
|
||||
#!/bin/sh
|
||||
i3-msg -t subscribe -m '["workspace", "output"]' | {
|
||||
# Initially print the current workspaces before we receive any events. This
|
||||
# avoids having an empty bar when starting up.
|
||||
i3-msg -t get_workspaces;
|
||||
# Then, while we receive events, update the workspace information.
|
||||
while read; do i3-msg -t get_workspaces; done;
|
||||
}
|
||||
------------------------------
|
||||
|
||||
=== Hide workspace named +foo+ unless if it is focused.
|
||||
|
||||
------------------------------
|
||||
#!/bin/sh
|
||||
i3-msg -t subscribe -m '["workspace", "output"]' | {
|
||||
i3-msg -t get_workspaces;
|
||||
while read; do i3-msg -t get_workspaces; done;
|
||||
} | jq --unbuffered -c '[ .[] | select(.name != "foo" or .focused) ]'
|
||||
------------------------------
|
||||
|
||||
Important! Make sure you use the +--unbuffered+ flag with +jq+, otherwise you
|
||||
might not get the changes in real-time but whenever they are flushed, which
|
||||
might mean that you are getting an empty bar until enough events are written.
|
||||
|
||||
=== Show empty workspaces +foo+ and +bar+ on LVDS1 even if they do not exist at the moment.
|
||||
|
||||
------------------------------
|
||||
#!/bin/sh
|
||||
i3-msg -t subscribe -m '["workspace", "output"]' | {
|
||||
i3-msg -t get_workspaces;
|
||||
while read; do i3-msg -t get_workspaces; done;
|
||||
} | jq --unbuffered -c '
|
||||
def fake_ws(name): {
|
||||
name: name,
|
||||
output: "LVDS1",
|
||||
};
|
||||
. + [ fake_ws("foo"), fake_ws("bar") ] | unique_by(.name)
|
||||
'
|
||||
------------------------------
|
||||
|
||||
=== Sort workspaces in reverse alphanumeric order
|
||||
|
||||
------------------------------
|
||||
#!/bin/sh
|
||||
i3-msg -t subscribe -m '["workspace", "output"]' | {
|
||||
i3-msg -t get_workspaces;
|
||||
while read; do i3-msg -t get_workspaces; done;
|
||||
} | jq --unbuffered -c 'sort_by(.name) | reverse'
|
||||
------------------------------
|
||||
|
||||
=== Append "foo" to the name of each workspace
|
||||
|
||||
------------------------------
|
||||
#!/bin/sh
|
||||
i3-msg -t subscribe -m '["workspace", "output"]' | {
|
||||
i3-msg -t get_workspaces;
|
||||
while read; do i3-msg -t get_workspaces; done;
|
||||
} | jq --unbuffered -c '[ .[] | .name |= . + " foo" ]'
|
||||
------------------------------
|
13
docs/ipc
13
docs/ipc
@ -410,6 +410,14 @@ deco_rect (map)::
|
||||
The coordinates of the *window decoration* inside its container. These
|
||||
coordinates are relative to the container and do not include the actual
|
||||
client window.
|
||||
actual_deco_rect (map)::
|
||||
See +deco_rect+. i3 v4.22 changed the way title bars are rendered. Before
|
||||
i3 v4.22, the deco_rect was always relative to the parent coordinates.
|
||||
Starting with i3 v4.22, this remains true for tabbed/stacked containers
|
||||
(actual_deco_rect is identical to deco_rect), but for normal-border leaf
|
||||
containers within vertical/horizontal split containers, actual_deco_rect
|
||||
is relative to the container itself. For more background, see
|
||||
https://github.com/i3/i3/issues/1966
|
||||
geometry (map)::
|
||||
The original geometry the window specified when i3 mapped it. Used when
|
||||
switching a window to floating mode, for example.
|
||||
@ -440,6 +448,9 @@ focus (array of integer)::
|
||||
order. Traversing the tree by following the first entry in this array
|
||||
will result in eventually reaching the one node with +focused+ set to
|
||||
true.
|
||||
sticky (bool)::
|
||||
Whether this window is "sticky". If it is also floating, this window will
|
||||
be present on all workspaces on the same output.
|
||||
fullscreen_mode (integer)::
|
||||
Whether this container is in fullscreen state or not.
|
||||
Possible values are
|
||||
@ -1088,6 +1099,8 @@ binding that ran a command because of user input. The +change (string)+ field
|
||||
indicates what sort of binding event was triggered (right now it will always be
|
||||
+"run"+ but may be expanded in the future).
|
||||
|
||||
The +mode (string)+ field contains the name of the mode the binding was run in.
|
||||
|
||||
The +binding (object)+ field contains details about the binding that was run:
|
||||
|
||||
command (string)::
|
||||
|
288
docs/userguide
288
docs/userguide
@ -196,6 +196,7 @@ provided by the i3 https://github.com/i3/i3/blob/next/etc/config.keycodes[defaul
|
||||
|
||||
Floating windows are always on top of tiling windows.
|
||||
|
||||
[[tiling_drag]]
|
||||
=== Moving tiling containers with the mouse
|
||||
|
||||
Since i3 4.21, it's possible to drag tiling containers using the mouse. The
|
||||
@ -316,7 +317,7 @@ single workspace on which you open three terminal windows. All these terminal
|
||||
windows are directly attached to one node inside i3’s layout tree, the
|
||||
workspace node. By default, the workspace node’s orientation is +horizontal+.
|
||||
|
||||
Now you move one of these terminals down (+$mod+Shift+j+ by default). The
|
||||
Now you move one of these terminals down (+$mod+Shift+k+ by default). The
|
||||
workspace node’s orientation will be changed to +vertical+. The terminal window
|
||||
you moved down is directly attached to the workspace and appears on the bottom
|
||||
of the screen. A new (horizontal) container was created to accommodate the
|
||||
@ -777,14 +778,19 @@ default_border pixel 3
|
||||
=== Hiding borders adjacent to the screen edges
|
||||
|
||||
You can hide container borders adjacent to the screen edges using
|
||||
+hide_edge_borders+. This is useful if you are using scrollbars, or do not want
|
||||
to waste even two pixels in displayspace. The "smart" setting hides borders on
|
||||
workspaces with only one window visible, but keeps them on workspaces with
|
||||
multiple windows visible. Default is none.
|
||||
+hide_edge_borders+ (the default is +none+). Hiding borders is useful if you are
|
||||
using scrollbars, or do not want to waste even two pixels in displayspace.
|
||||
|
||||
The "smart" setting hides borders on workspaces with only one window visible,
|
||||
but keeps them on workspaces with multiple windows visible.
|
||||
|
||||
The "smart_no_gaps" setting hides edge-specific borders of a container if the
|
||||
container is the only container on its workspace and the gaps to the screen edge
|
||||
are +0+.
|
||||
|
||||
*Syntax*:
|
||||
-----------------------------------------------
|
||||
hide_edge_borders none|vertical|horizontal|both|smart
|
||||
hide_edge_borders none|vertical|horizontal|both|smart|smart_no_gaps
|
||||
-----------------------------------------------
|
||||
|
||||
*Example*:
|
||||
@ -792,6 +798,27 @@ hide_edge_borders none|vertical|horizontal|both|smart
|
||||
hide_edge_borders vertical
|
||||
----------------------
|
||||
|
||||
[[_smart_borders]]
|
||||
=== Smart borders
|
||||
|
||||
Smart borders will draw borders on windows only if there is more than one window
|
||||
in a workspace. This feature can also be enabled only if the gap size between
|
||||
window and screen edge is +0+.
|
||||
|
||||
*Syntax*:
|
||||
-----------------------------------------------
|
||||
smart_borders on|off|no_gaps
|
||||
-----------------------------------------------
|
||||
|
||||
*Example*:
|
||||
----------------------
|
||||
# Activate smart borders (always)
|
||||
smart_borders on
|
||||
|
||||
# Activate smart borders (only when there are effectively no gaps)
|
||||
smart_borders no_gaps
|
||||
----------------------
|
||||
|
||||
[[for_window]]
|
||||
=== Arbitrary commands for specific windows (for_window)
|
||||
|
||||
@ -938,7 +965,7 @@ considered.
|
||||
*Syntax*:
|
||||
------------------------------------------------------------
|
||||
assign <criteria> [→] [workspace] [number] <workspace>
|
||||
assign <criteria> [→] output left|right|up|down|primary|<output>
|
||||
assign <criteria> [→] output left|right|up|down|primary|nonprimary|<output>
|
||||
------------------------------------------------------------
|
||||
|
||||
*Examples*:
|
||||
@ -970,6 +997,9 @@ assign [class="^URxvt$"] → output right
|
||||
|
||||
# Assign urxvt to the primary output
|
||||
assign [class="^URxvt$"] → output primary
|
||||
|
||||
# Assign urxvt to the first non-primary output
|
||||
assign [class="^URxvt$"] → output nonprimary
|
||||
----------------------
|
||||
|
||||
Note that you might not have a primary output configured yet. To do so, run:
|
||||
@ -1117,7 +1147,8 @@ client.background::
|
||||
which do not cover the whole area of this window expose the color. Note
|
||||
that this colorclass only takes a single color.
|
||||
|
||||
Colors are in HTML hex format (#rrggbb), see the following example:
|
||||
Colors are in HTML hex format (#rrggbb, optionally #rrggbbaa), see the following
|
||||
example:
|
||||
|
||||
*Examples (default colors)*:
|
||||
----------------------------------------------------------------------
|
||||
@ -1402,6 +1433,103 @@ fullscreen toggle
|
||||
bindsym Mod1+F fullscreen toggle
|
||||
-------------------
|
||||
|
||||
[[config_tiling_drag]]
|
||||
=== Tiling drag
|
||||
|
||||
You can configure how to initiate the tiling drag feature (see <<tiling_drag>>).
|
||||
|
||||
*Syntax*:
|
||||
--------------------------------
|
||||
tiling_drag off
|
||||
tiling_drag modifier|titlebar [modifier|titlebar]
|
||||
--------------------------------
|
||||
|
||||
*Examples*:
|
||||
--------------------------------
|
||||
# Only initiate a tiling drag when the modifier is held:
|
||||
tiling_drag modifier
|
||||
|
||||
# Initiate a tiling drag on either titlebar click or held modifier:
|
||||
tiling_drag modifier titlebar
|
||||
|
||||
# Disable tiling drag altogether
|
||||
tiling_drag off
|
||||
--------------------------------
|
||||
|
||||
[[gaps]]
|
||||
=== Gaps
|
||||
|
||||
Since i3 4.22, you can configure window gaps.
|
||||
“Gaps” are added spacing between windows (or split containers) and to the screen edges:
|
||||
|
||||
image::gaps1920.png["Screenshot of i3 with gaps enabled (10 px inner gaps, 20 px outer gaps)",title="Gaps enabled (10 px inner gaps, 20 px outer gaps)"]
|
||||
|
||||
You can configure two different kind of gaps:
|
||||
|
||||
1. Inner gaps are space between two adjacent windows (or split containers).
|
||||
2. Outer gaps are space along the screen edges. You can configure each side
|
||||
(left, right, top, bottom) separately.
|
||||
|
||||
If you are familiar with HTML and CSS, you can think of inner gaps as `padding`,
|
||||
and of outer gaps as `margin`, applied to a `<div>` around your window or split
|
||||
container.
|
||||
|
||||
Note that outer gaps are added to the inner gaps, meaning the total gap size
|
||||
between a screen edge and a window (or split container) will be the sum of outer
|
||||
and inner gaps.
|
||||
|
||||
You can define gaps either globally or per workspace using the following
|
||||
syntax.
|
||||
|
||||
*Syntax*:
|
||||
-----------------------
|
||||
# Inner gaps for all windows: space between two adjacent windows (or split containers).
|
||||
gaps inner <gap_size>[px]
|
||||
|
||||
# Outer gaps for all windows: space along the screen edges.
|
||||
gaps outer|horizontal|vertical|top|left|bottom|right <gap_size>[px]
|
||||
|
||||
# Inner and outer gaps for all windows on a specific workspace.
|
||||
# <ws> can be a workspace number or name.
|
||||
workspace <ws> gaps inner <gap_size>[px]
|
||||
workspace <ws> gaps outer|horizontal|vertical|top|left|bottom|right <gap_size>[px]
|
||||
|
||||
# Enabling “Smart Gaps” means no gaps will be shown when there is
|
||||
# precisely one window or split container on the workspace.
|
||||
#
|
||||
# inverse_outer only enables outer gaps when there is exactly one
|
||||
# window or split container on the workspace.
|
||||
smart_gaps on|off|inverse_outer
|
||||
-----------------------
|
||||
|
||||
Outer gaps can be configured for each side individually with the `top`, `left`,
|
||||
`bottom` and `right` directive. `horizontal` and `vertical` are shortcuts for
|
||||
`left`/`right` and `top`/`bottom`, respectively.
|
||||
|
||||
*Example*:
|
||||
-------------------------------------------------
|
||||
# Configure 5px of space between windows and to the screen edges.
|
||||
gaps inner 5px
|
||||
|
||||
# Configure an additional 5px of extra space to the screen edges,
|
||||
# for a total gap of 10px to the screen edges, and 5px between windows.
|
||||
gaps outer 5px
|
||||
|
||||
# Overwrite gaps to 0, I need all the space I can get on workspace 3.
|
||||
workspace 3 gaps inner 0
|
||||
workspace 3 gaps outer 0
|
||||
|
||||
# Only enable outer gaps when there is exactly one window or split container on the workspace.
|
||||
smart_gaps inverse_outer
|
||||
-------------------------------------------------
|
||||
|
||||
Tip: Gaps can additionally be changed at runtime with the `gaps` command, see
|
||||
<<changing_gaps>>.
|
||||
|
||||
Tip: You can find an
|
||||
https://github.com/Airblader/i3/wiki/Example-Configuration[example
|
||||
configuration] that uses modes to illustrate various gap configurations.
|
||||
|
||||
== Configuring i3bar
|
||||
|
||||
The bar at the bottom of your monitor is drawn by a separate process called
|
||||
@ -1483,6 +1611,30 @@ bar {
|
||||
}
|
||||
-------------------------------------------------
|
||||
|
||||
[[workspace_command]]
|
||||
=== Workspace buttons command
|
||||
|
||||
Since i3 4.23, i3bar can run a program and use its +stdout+ output to define
|
||||
the workspace buttons displayed on the left hand side of the bar. With this
|
||||
feature, you can, for example, rename the buttons of workspaces, hide specific
|
||||
workspaces, always show a workspace button even if the workspace does not exist
|
||||
or change the order of the buttons.
|
||||
|
||||
Also see <<status_command>> for the statusline option and
|
||||
https://i3wm.org/docs/i3bar-workspace-protocol.html for the detailed protocol.
|
||||
|
||||
*Syntax*:
|
||||
------------------------
|
||||
workspace_command <command>
|
||||
------------------------
|
||||
|
||||
*Example*:
|
||||
-------------------------------------------------
|
||||
bar {
|
||||
workspace_command /path/to/script.sh
|
||||
}
|
||||
-------------------------------------------------
|
||||
|
||||
=== Display mode
|
||||
|
||||
You can either have i3bar be visible permanently at one edge of the screen
|
||||
@ -1521,7 +1673,7 @@ the windows key). The default value for the hidden_state is hide.
|
||||
mode dock|hide|invisible
|
||||
hidden_state hide|show
|
||||
modifier <Modifier>|none
|
||||
------------------------
|
||||
-------------------------
|
||||
|
||||
*Example*:
|
||||
----------------
|
||||
@ -1691,7 +1843,7 @@ tray_output none|primary|<output>
|
||||
---------------------------------
|
||||
|
||||
*Example*:
|
||||
-------------------------
|
||||
----------------------------------
|
||||
# disable system tray
|
||||
bar {
|
||||
tray_output none
|
||||
@ -1706,7 +1858,7 @@ bar {
|
||||
bar {
|
||||
tray_output HDMI2
|
||||
}
|
||||
-------------------------
|
||||
----------------------------------
|
||||
|
||||
Note that you might not have a primary output configured yet. To do so, run:
|
||||
-------------------------
|
||||
@ -1730,10 +1882,10 @@ tray_padding <px> [px]
|
||||
-------------------------
|
||||
|
||||
*Example*:
|
||||
-------------------------
|
||||
---------------------------
|
||||
# Obey Fitts's law
|
||||
tray_padding 0
|
||||
-------------------------
|
||||
---------------------------
|
||||
|
||||
=== Font
|
||||
|
||||
@ -1871,8 +2023,8 @@ bar {
|
||||
|
||||
=== Colors
|
||||
|
||||
As with i3, colors are in HTML hex format (#rrggbb). The following colors can
|
||||
be configured at the moment:
|
||||
As with i3, colors are in HTML hex format (#rrggbb, optionally #rrggbbaa). The
|
||||
following colors can be configured at the moment:
|
||||
|
||||
background::
|
||||
Background color of the bar.
|
||||
@ -1956,6 +2108,49 @@ while +#000000FF+ will be a fully opaque black (the same as +#000000+).
|
||||
Please note that due to the way the tray specification works, enabling this
|
||||
flag will cause all tray icons to have a transparent background.
|
||||
|
||||
[[i3bar_padding]]
|
||||
=== Padding
|
||||
|
||||
To make i3bar higher (without increasing the font size), and/or add padding to
|
||||
the left and right side of i3bar, you can use the +padding+ directive:
|
||||
|
||||
*Syntax*:
|
||||
--------------------------------------
|
||||
bar {
|
||||
# 2px left/right and 2px top/bottom padding
|
||||
padding 2px
|
||||
|
||||
# 2px top/bottom padding, no left/right padding
|
||||
padding 2px 0
|
||||
|
||||
# 2px top padding, no left/right padding, 4px bottom padding
|
||||
padding 2px 0 4px
|
||||
|
||||
# four value syntax
|
||||
padding top[px] right[px] bottom[px] left[px]
|
||||
}
|
||||
--------------------------------------
|
||||
|
||||
*Examples*:
|
||||
--------------------------------------
|
||||
bar {
|
||||
# 2px left/right and 2px top/bottom padding
|
||||
padding 2px
|
||||
|
||||
# 2px top/bottom padding, no left/right padding
|
||||
padding 2px 0
|
||||
|
||||
# 2px top padding, no left/right padding, 4px bottom padding
|
||||
padding 2px 0 4px
|
||||
|
||||
# 2px top padding, 6px right padding, 4px bottom padding, 1px left padding
|
||||
padding 2px 6px 4px 1px
|
||||
}
|
||||
--------------------------------------
|
||||
|
||||
Note: As a convenience for users who migrate from i3-gaps to i3, the +height+
|
||||
directive from i3-gaps is supported by i3, but should be changed to +padding+.
|
||||
|
||||
[[list_of_commands]]
|
||||
== List of commands
|
||||
|
||||
@ -2226,6 +2421,9 @@ available:
|
||||
<criteria>::
|
||||
Sets focus to the container that matches the specified criteria.
|
||||
See <<command_criteria>>.
|
||||
workspace::
|
||||
Sets focus to the workspace that contains the container that matches the
|
||||
specified criteria.
|
||||
left|right|up|down::
|
||||
Sets focus to the nearest container in the given direction.
|
||||
parent::
|
||||
@ -2252,10 +2450,11 @@ output::
|
||||
*Syntax*:
|
||||
----------------------------------------------
|
||||
<criteria> focus
|
||||
<criteria> focus workspace
|
||||
focus left|right|down|up
|
||||
focus parent|child|floating|tiling|mode_toggle
|
||||
focus next|prev [sibling]
|
||||
focus output left|right|down|up|current|primary|next|<output1> [output2]…
|
||||
focus output left|right|down|up|current|primary|nonprimary|next|<output1> [output2]…
|
||||
----------------------------------------------
|
||||
|
||||
*Examples*:
|
||||
@ -2263,6 +2462,10 @@ focus output left|right|down|up|current|primary|next|<output1> [output2]…
|
||||
# Focus firefox
|
||||
bindsym $mod+F1 [class="Firefox"] focus
|
||||
|
||||
# Focus the workspace where firefox is, without necessarily focusing firefox
|
||||
# itself.
|
||||
bindsym $mod+x [class="Firefox"] focus workspace
|
||||
|
||||
# Focus container on the left, bottom, top, right
|
||||
bindsym $mod+j focus left
|
||||
bindsym $mod+k focus down
|
||||
@ -2287,8 +2490,11 @@ bindsym $mod+x focus output HDMI-2
|
||||
# Focus the primary output
|
||||
bindsym $mod+x focus output primary
|
||||
|
||||
# Cycle focus through non-primary outputs
|
||||
bindsym $mod+x focus output nonprimary
|
||||
|
||||
# Cycle focus between outputs VGA1 and LVDS1 but not DVI0
|
||||
bindsym $mod+x move workspace to output VGA1 LVDS1
|
||||
bindsym $mod+x focus output VGA1 LVDS1
|
||||
-------------------------------------------------
|
||||
|
||||
Note that you might not have a primary output configured yet. To do so, run:
|
||||
@ -2550,9 +2756,9 @@ To move a container to another RandR output (addressed by names like +LVDS1+ or
|
||||
+right+, +up+ or +down+), there are two commands:
|
||||
|
||||
*Syntax*:
|
||||
------------------------------------------------------------
|
||||
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]…
|
||||
-------------------------------------------------------------------------------------
|
||||
move container to output left|right|down|up|current|primary|nonprimary|next|<output1> [output2]…
|
||||
move workspace to output left|right|down|up|current|primary|nonprimary|next|<output1> [output2]…
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
*Examples*:
|
||||
@ -2774,10 +2980,10 @@ 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|toggle>
|
||||
title_window_icon <padding|toggle> <px>
|
||||
------------------------------
|
||||
----------------------------------------
|
||||
|
||||
*Examples*:
|
||||
-------------------------------------------------------------------------------------
|
||||
@ -2978,6 +3184,35 @@ bindsym $mod+b bar mode hide bar-1
|
||||
bindsym $mod+Shift+b bar mode invisible bar-1
|
||||
------------------------------------------------
|
||||
|
||||
[[changing_gaps]]
|
||||
=== Changing gaps
|
||||
|
||||
Gaps can be modified at runtime with the following command syntax:
|
||||
|
||||
*Syntax*:
|
||||
-----------------------------------------------
|
||||
# Inner gaps: space between two adjacent windows (or split containers).
|
||||
gaps inner current|all set|plus|minus|toggle <gap_size_in_px>
|
||||
# Outer gaps: space along the screen edges.
|
||||
gaps outer|horizontal|vertical|top|right|bottom|left current|all set|plus|minus|toggle <gap_size_in_px>
|
||||
-----------------------------------------------
|
||||
|
||||
With `current` or `all` you can change gaps either for only the currently
|
||||
focused or all currently existing workspaces (note that this does not affect the
|
||||
global configuration itself).
|
||||
|
||||
*Examples*:
|
||||
----------------------------------------------
|
||||
gaps inner all set 20
|
||||
gaps outer current plus 5
|
||||
gaps horizontal current plus 40
|
||||
gaps outer current toggle 60
|
||||
|
||||
for_window [class="vlc"] gaps inner 0, gaps outer 0
|
||||
----------------------------------------------
|
||||
|
||||
To change the gaps for all windows, see the <<gaps>> configuration directive.
|
||||
|
||||
[[multi_monitor]]
|
||||
== Multiple monitors
|
||||
|
||||
@ -3011,7 +3246,8 @@ To help you get going if you have never used multiple monitors before, here is
|
||||
a short overview of the xrandr options which will probably be of interest to
|
||||
you. It is always useful to get an overview of the current screen configuration.
|
||||
Just run "xrandr" and you will get an output like the following:
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-------------------------------------------------------------------------------------
|
||||
$ xrandr
|
||||
Screen 0: minimum 320 x 200, current 1280 x 800, maximum 8192 x 8192
|
||||
VGA1 disconnected (normal left inverted right x axis y axis)
|
||||
@ -3024,7 +3260,7 @@ LVDS1 connected 1280x800+0+0 (normal left inverted right x axis y axis) 261mm x
|
||||
720x400 85.0
|
||||
640x400 85.1
|
||||
640x350 85.1
|
||||
--------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
Several things are important here: You can see that +LVDS1+ is connected (of
|
||||
course, it is the internal flat panel) but +VGA1+ is not. If you have a monitor
|
||||
@ -3036,12 +3272,15 @@ combined resolution of your monitors. By default, it is usually too low and has
|
||||
to be increased by editing +/etc/X11/xorg.conf+.
|
||||
|
||||
So, say you connected VGA1 and want to use it as an additional screen:
|
||||
|
||||
-------------------------------------------
|
||||
xrandr --output VGA1 --auto --left-of LVDS1
|
||||
-------------------------------------------
|
||||
|
||||
This command makes xrandr try to find the native resolution of the device
|
||||
connected to +VGA1+ and configures it to the left of your internal flat panel.
|
||||
When running "xrandr" again, the output looks like this:
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
$ xrandr
|
||||
Screen 0: minimum 320 x 200, current 2560 x 1024, maximum 8192 x 8192
|
||||
@ -3064,6 +3303,7 @@ LVDS1 connected 1280x800+1280+0 (normal left inverted right x axis y axis) 261mm
|
||||
640x400 85.1
|
||||
640x350 85.1
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Please note that i3 uses exactly the same API as xrandr does, so it will see
|
||||
only what you can see in xrandr.
|
||||
|
||||
|
@ -49,6 +49,10 @@ set $right semicolon
|
||||
# use Mouse+Mod1 to drag floating windows to their wanted position
|
||||
floating_modifier Mod1
|
||||
|
||||
# move tiling windows via drag & drop by left-clicking into the title bar,
|
||||
# or left-clicking anywhere into the window while holding the floating modifier.
|
||||
tiling_drag modifier titlebar
|
||||
|
||||
# start a terminal
|
||||
bindsym Mod1+Return exec i3-sensible-terminal
|
||||
|
||||
|
@ -43,6 +43,10 @@ bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOU
|
||||
# Use Mouse+$mod to drag floating windows to their wanted position
|
||||
floating_modifier $mod
|
||||
|
||||
# move tiling windows via drag & drop by left-clicking into the title bar,
|
||||
# or left-clicking anywhere into the window while holding the floating modifier.
|
||||
tiling_drag modifier titlebar
|
||||
|
||||
# start a terminal
|
||||
bindcode $mod+36 exec i3-sensible-terminal
|
||||
|
||||
|
@ -46,9 +46,11 @@ sub slurp {
|
||||
|
||||
my @entry_types;
|
||||
my $dmenu_cmd = 'dmenu -i';
|
||||
my $show_duplicates;
|
||||
my $result = GetOptions(
|
||||
'dmenu=s' => \$dmenu_cmd,
|
||||
'entry-type=s' => \@entry_types,
|
||||
'show-duplicates' => \$show_duplicates,
|
||||
'version' => sub {
|
||||
say "dmenu-desktop 1.5 © 2012 Michael Stapelberg";
|
||||
exit 0;
|
||||
@ -292,7 +294,7 @@ for my $app (keys %apps) {
|
||||
}
|
||||
|
||||
$choices{$name} = $app;
|
||||
next;
|
||||
next unless $show_duplicates;
|
||||
}
|
||||
|
||||
if ((scalar grep { $_ eq 'command' } @entry_types) > 0) {
|
||||
@ -327,7 +329,7 @@ for my $app (keys %apps) {
|
||||
if (!(scalar grep { $_ eq lc(basename($command)) } @keys) > 0) {
|
||||
$choices{basename($command)} = $app;
|
||||
}
|
||||
next;
|
||||
next unless $show_duplicates;
|
||||
}
|
||||
|
||||
if ((scalar grep { $_ eq 'filename' } @entry_types) > 0) {
|
||||
@ -366,9 +368,14 @@ binmode $dmenu_in, ':utf8';
|
||||
binmode $dmenu_out, ':utf8';
|
||||
|
||||
# Feed dmenu the possible choices.
|
||||
# Since the process might have already exited, we ignore SIGPIPE.
|
||||
$SIG{PIPE} = 'IGNORE';
|
||||
|
||||
say $dmenu_in $_ for sort keys %choices;
|
||||
close($dmenu_in);
|
||||
|
||||
$SIG{PIPE} = 'DEFAULT';
|
||||
|
||||
waitpid($pid, 0);
|
||||
my $status = ($? >> 8);
|
||||
|
||||
@ -413,7 +420,7 @@ my $exec = $app->{Exec};
|
||||
my $location = $app->{_Location};
|
||||
|
||||
# Quote as described by “The Exec key”:
|
||||
# https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
|
||||
# https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s07.html
|
||||
sub quote {
|
||||
my ($str) = @_;
|
||||
$str =~ s/("|`|\$|\\)/\\$1/g;
|
||||
@ -425,6 +432,17 @@ $choice = quote($choice);
|
||||
$location = quote($location);
|
||||
$name = quote($name);
|
||||
|
||||
# https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s07.html:
|
||||
#
|
||||
# Note that the general escape rule for values of type string states that the
|
||||
# backslash character can be escaped as ("\\") as well and that this escape rule
|
||||
# is applied before the quoting rule. As such, to unambiguously represent a
|
||||
# literal backslash character in a quoted argument in a desktop entry file
|
||||
# requires the use of four successive backslash characters ("\\\\"). Likewise, a
|
||||
# literal dollar sign in a quoted argument in a desktop entry file is
|
||||
# unambiguously represented with ("\\$").
|
||||
$exec =~ s/\\\\/\\/g;
|
||||
|
||||
# Remove deprecated field codes, as the spec dictates.
|
||||
$exec =~ s/%[dDnNvm]//g;
|
||||
|
||||
@ -481,9 +499,10 @@ EOT
|
||||
# starts with a double quote ("), everything is parsed as-is until the next
|
||||
# double quote which is NOT preceded by a backslash (\).
|
||||
#
|
||||
# Therefore, we escape all double quotes (") by replacing them with \"
|
||||
$exec =~ s/\\"/\\\\\\"/g;
|
||||
$exec =~ s/([^\\])"/$1\\"/g;
|
||||
# Therefore, we escape all double quotes (") by replacing them with \".
|
||||
# To not change the meaning of any double quote, backslashes need to be
|
||||
# escaped as well.
|
||||
$exec =~ s/(["\\])/\\$1/g;
|
||||
|
||||
if (exists($app->{StartupNotify}) && !$app->{StartupNotify}) {
|
||||
$nosn = '--no-startup-id';
|
||||
@ -501,7 +520,7 @@ system('i3-msg', $cmd) == 0 or die "Could not launch i3-msg: $?";
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=name]
|
||||
i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=name] [--show-duplicates]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
@ -552,11 +571,15 @@ version of dmenu.
|
||||
|
||||
Display the (localized) "Name" (type = name), the command (type = command) or
|
||||
the (*.desktop) filename (type = filename) in dmenu. This option can be
|
||||
specified multiple times.
|
||||
specified multiple times. Duplicates will be removed, see '--show-duplicates'.
|
||||
|
||||
Examples are "GNU Image Manipulation Program" (type = name), "gimp" (type =
|
||||
command), and "libreoffice-writer" (type = filename).
|
||||
|
||||
=item B<--show-duplicates>
|
||||
|
||||
Show duplicates if '--entry-type' is specified multiple times.
|
||||
|
||||
=back
|
||||
|
||||
=head1 VERSION
|
||||
|
@ -8,6 +8,11 @@
|
||||
# Distributions/packagers can enhance this script with a
|
||||
# distribution-specific mechanism to find the preferred pager.
|
||||
|
||||
# The less -E and -F options exit immediately for short files, strip if present.
|
||||
case "$LESS" in
|
||||
*[EF]*) LESS=`echo "$LESS" | tr -d EF`
|
||||
esac
|
||||
|
||||
# Hopefully one of these is installed (no flamewars about preference please!):
|
||||
# We don't use 'more' because it will exit if the file is too short.
|
||||
# Worst case scenario we'll open the file in your editor.
|
||||
|
@ -14,7 +14,7 @@
|
||||
# 2. Distribution-specific mechanisms come next, e.g. x-terminal-emulator
|
||||
# 3. The terminal emulator with best accessibility comes first.
|
||||
# 4. No order is guaranteed/desired for the remaining terminal emulators.
|
||||
for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper; do
|
||||
for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper wezterm; do
|
||||
if command -v "$terminal" > /dev/null 2>&1; then
|
||||
exec "$terminal" "$@"
|
||||
fi
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <ev.h>
|
||||
|
||||
#define STDIN_CHUNK_SIZE 1024
|
||||
|
||||
@ -40,6 +40,18 @@ typedef struct {
|
||||
*/
|
||||
bool click_events;
|
||||
bool click_events_init;
|
||||
|
||||
/**
|
||||
* stdin- and SIGCHLD-watchers
|
||||
*/
|
||||
ev_io *stdin_io;
|
||||
ev_child *child_sig;
|
||||
int stdin_fd;
|
||||
|
||||
/**
|
||||
* Line read from child that did not include a newline character.
|
||||
*/
|
||||
char *pending_line;
|
||||
} i3bar_child;
|
||||
|
||||
/*
|
||||
@ -50,36 +62,66 @@ void clear_statusline(struct statusline_head *head, bool free_resources);
|
||||
|
||||
/*
|
||||
* Start a child process with the specified command and reroute stdin.
|
||||
* We actually start a $SHELL to execute the command so we don't have to care
|
||||
* about arguments and such
|
||||
* We actually start a shell to execute the command so we don't have to care
|
||||
* about arguments and such.
|
||||
*
|
||||
* If `command' is NULL, such as in the case when no `status_command' is given
|
||||
* in the bar config, no child will be started.
|
||||
*
|
||||
*/
|
||||
void start_child(char *command);
|
||||
|
||||
/*
|
||||
* Same as start_child but starts the configured client that manages workspace
|
||||
* buttons.
|
||||
*
|
||||
*/
|
||||
void start_ws_child(char *command);
|
||||
|
||||
/*
|
||||
* Returns true if the status child process is alive.
|
||||
*
|
||||
*/
|
||||
bool status_child_is_alive(void);
|
||||
|
||||
/*
|
||||
* Returns true if the workspace child process is alive.
|
||||
*
|
||||
*/
|
||||
bool ws_child_is_alive(void);
|
||||
|
||||
/*
|
||||
* kill()s the child process (if any). Called when exit()ing.
|
||||
*
|
||||
*/
|
||||
void kill_child_at_exit(void);
|
||||
void kill_children_at_exit(void);
|
||||
|
||||
/*
|
||||
* kill()s the child process (if any) and closes and
|
||||
* free()s the stdin- and SIGCHLD-watchers
|
||||
* kill()s the child process (if any) and closes and free()s the stdin- and
|
||||
* SIGCHLD-watchers
|
||||
*
|
||||
*/
|
||||
void kill_child(void);
|
||||
|
||||
/*
|
||||
* kill()s the workspace child process (if any) and closes and free()s the
|
||||
* stdin- and SIGCHLD-watchers.
|
||||
* Similar to kill_child.
|
||||
*
|
||||
*/
|
||||
void kill_ws_child(void);
|
||||
|
||||
/*
|
||||
* Sends a SIGSTOP to the child process (if existent)
|
||||
*
|
||||
*/
|
||||
void stop_child(void);
|
||||
void stop_children(void);
|
||||
|
||||
/*
|
||||
* Sends a SIGCONT to the child process (if existent)
|
||||
*
|
||||
*/
|
||||
void cont_child(void);
|
||||
void cont_children(void);
|
||||
|
||||
/*
|
||||
* Whether or not the child want click events
|
||||
@ -92,3 +134,14 @@ bool child_want_click_events(void);
|
||||
*
|
||||
*/
|
||||
void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int out_x, int out_y, int width, int height, int mods);
|
||||
|
||||
/*
|
||||
* When workspace_command is enabled this function is used to re-parse the
|
||||
* latest received JSON from the client.
|
||||
*/
|
||||
void repeat_last_ws_json(void);
|
||||
|
||||
/*
|
||||
* Replaces the workspace buttons with an error message.
|
||||
*/
|
||||
void set_workspace_button_error(const char *message);
|
||||
|
@ -38,11 +38,21 @@ typedef struct tray_output_t {
|
||||
TAILQ_ENTRY(tray_output_t) tray_outputs;
|
||||
} tray_output_t;
|
||||
|
||||
/* Matches i3/include/data.h */
|
||||
struct Rect {
|
||||
uint32_t x;
|
||||
uint32_t y;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
};
|
||||
|
||||
typedef struct config_t {
|
||||
uint32_t modifier;
|
||||
TAILQ_HEAD(bindings_head, binding_t) bindings;
|
||||
position_t position;
|
||||
bool verbose;
|
||||
uint32_t bar_height;
|
||||
struct Rect padding;
|
||||
bool transparency;
|
||||
struct xcb_color_strings_t colors;
|
||||
bool disable_binding_mode_indicator;
|
||||
@ -52,6 +62,7 @@ typedef struct config_t {
|
||||
bool strip_ws_name;
|
||||
char *bar_id;
|
||||
char *command;
|
||||
char *workspace_command;
|
||||
char *fontname;
|
||||
i3String *separator_symbol;
|
||||
TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs;
|
||||
@ -69,17 +80,17 @@ typedef struct config_t {
|
||||
extern config_t config;
|
||||
|
||||
/**
|
||||
* Start parsing the received bar configuration JSON string
|
||||
* Parse the received bar configuration JSON string
|
||||
*
|
||||
*/
|
||||
void parse_config_json(char *json);
|
||||
void parse_config_json(const unsigned char *json, size_t size);
|
||||
|
||||
/**
|
||||
* Start parsing the received bar configuration list. The only usecase right
|
||||
* now is to automatically get the first bar id.
|
||||
* Parse the received bar configuration list. The only usecase right now is to
|
||||
* automatically get the first bar id.
|
||||
*
|
||||
*/
|
||||
void parse_get_first_i3bar_config(char *json);
|
||||
void parse_get_first_i3bar_config(const unsigned char *json, size_t size);
|
||||
|
||||
/**
|
||||
* free()s the color strings as soon as they are not needed anymore.
|
||||
|
@ -24,7 +24,7 @@ struct mode {
|
||||
typedef struct mode mode;
|
||||
|
||||
/*
|
||||
* Start parsing the received JSON string
|
||||
* Parse the received JSON string
|
||||
*
|
||||
*/
|
||||
void parse_mode_json(char *json);
|
||||
void parse_mode_json(const unsigned char *json, size_t size);
|
||||
|
@ -22,10 +22,10 @@ SLIST_HEAD(outputs_head, i3_output);
|
||||
extern struct outputs_head* outputs;
|
||||
|
||||
/*
|
||||
* Start parsing the received JSON string
|
||||
* Parse the received JSON string
|
||||
*
|
||||
*/
|
||||
void parse_outputs_json(char* json);
|
||||
void parse_outputs_json(const unsigned char* json, size_t size);
|
||||
|
||||
/*
|
||||
* Initiate the outputs list
|
||||
|
@ -18,10 +18,10 @@ typedef struct i3_ws i3_ws;
|
||||
TAILQ_HEAD(ws_head, i3_ws);
|
||||
|
||||
/*
|
||||
* Start parsing the received JSON string
|
||||
* Parse the received JSON string
|
||||
*
|
||||
*/
|
||||
void parse_workspaces_json(char *json);
|
||||
void parse_workspaces_json(const unsigned char *json, size_t size);
|
||||
|
||||
/*
|
||||
* free() all workspace data structures
|
||||
@ -38,7 +38,6 @@ struct i3_ws {
|
||||
bool visible; /* If the ws is currently visible on an output */
|
||||
bool focused; /* If the ws is currently focused */
|
||||
bool urgent; /* If the urgent hint of the ws is set */
|
||||
rect rect; /* The rect of the ws (not used (yet)) */
|
||||
struct i3_output *output; /* The current output of the ws */
|
||||
|
||||
TAILQ_ENTRY(i3_ws) tailq; /* Pointer for the TAILQ-Macro */
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "common.h"
|
||||
#include "yajl_utils.h"
|
||||
|
||||
#include <ctype.h> /* isspace */
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <ev.h>
|
||||
@ -27,14 +28,30 @@
|
||||
#include <yajl/yajl_parse.h>
|
||||
|
||||
/* Global variables for child_*() */
|
||||
i3bar_child child = {0};
|
||||
#define DLOG_CHILD DLOG("%s: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
|
||||
__func__, (long)child.pid, child.stopped, child.stop_signal, child.cont_signal, child.click_events, child.click_events_init)
|
||||
i3bar_child status_child = {0};
|
||||
i3bar_child ws_child = {0};
|
||||
|
||||
/* stdin- and SIGCHLD-watchers */
|
||||
ev_io *stdin_io;
|
||||
int stdin_fd;
|
||||
ev_child *child_sig;
|
||||
#define DLOG_CHILD(c) \
|
||||
do { \
|
||||
if ((c).pid == 0) { \
|
||||
DLOG("%s: child pid = 0\n", __func__); \
|
||||
} else if ((c).pid == status_child.pid) { \
|
||||
DLOG("%s: status_command: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
|
||||
__func__, (long)(c).pid, (c).stopped, (c).stop_signal, (c).cont_signal, (c).click_events, (c).click_events_init); \
|
||||
} else if ((c).pid == ws_child.pid) { \
|
||||
DLOG("%s: workspace_command: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
|
||||
__func__, (long)(c).pid, (c).stopped, (c).stop_signal, (c).cont_signal, (c).click_events, (c).click_events_init); \
|
||||
} else { \
|
||||
ELOG("%s: unknown child, this should never happen " \
|
||||
"pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
|
||||
__func__, (long)(c).pid, (c).stopped, (c).stop_signal, (c).cont_signal, (c).click_events, (c).click_events_init); \
|
||||
} \
|
||||
} while (0)
|
||||
#define DLOG_CHILDREN \
|
||||
do { \
|
||||
DLOG_CHILD(status_child); \
|
||||
DLOG_CHILD(ws_child); \
|
||||
} while (0)
|
||||
|
||||
/* JSON parser for stdin */
|
||||
yajl_handle parser;
|
||||
@ -127,7 +144,7 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha
|
||||
TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks);
|
||||
|
||||
finish:
|
||||
FREE(message);
|
||||
free(message);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
@ -135,22 +152,27 @@ finish:
|
||||
* Stop and free() the stdin- and SIGCHLD-watchers
|
||||
*
|
||||
*/
|
||||
static void cleanup(void) {
|
||||
if (stdin_io != NULL) {
|
||||
ev_io_stop(main_loop, stdin_io);
|
||||
FREE(stdin_io);
|
||||
close(stdin_fd);
|
||||
stdin_fd = 0;
|
||||
close(child_stdin);
|
||||
child_stdin = 0;
|
||||
static void cleanup(i3bar_child *c) {
|
||||
DLOG_CHILD(*c);
|
||||
|
||||
if (c->stdin_io != NULL) {
|
||||
ev_io_stop(main_loop, c->stdin_io);
|
||||
FREE(c->stdin_io);
|
||||
|
||||
if (c->pid == status_child.pid) {
|
||||
close(child_stdin);
|
||||
child_stdin = 0;
|
||||
}
|
||||
close(c->stdin_fd);
|
||||
}
|
||||
|
||||
if (child_sig != NULL) {
|
||||
ev_child_stop(main_loop, child_sig);
|
||||
FREE(child_sig);
|
||||
if (c->child_sig != NULL) {
|
||||
ev_child_stop(main_loop, c->child_sig);
|
||||
FREE(c->child_sig);
|
||||
}
|
||||
|
||||
memset(&child, 0, sizeof(i3bar_child));
|
||||
FREE(c->pending_line);
|
||||
memset(c, 0, sizeof(i3bar_child));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -362,15 +384,13 @@ static int stdin_end_array(void *context) {
|
||||
* Returns NULL on EOF.
|
||||
*
|
||||
*/
|
||||
static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
|
||||
int fd = watcher->fd;
|
||||
int n = 0;
|
||||
static unsigned char *get_buffer(int fd, int *ret_buffer_len) {
|
||||
int rec = 0;
|
||||
int buffer_len = STDIN_CHUNK_SIZE;
|
||||
unsigned char *buffer = smalloc(buffer_len + 1);
|
||||
buffer[0] = '\0';
|
||||
while (1) {
|
||||
n = read(fd, buffer + rec, buffer_len - rec);
|
||||
const ssize_t n = read(fd, buffer + rec, buffer_len - rec);
|
||||
if (n == -1) {
|
||||
if (errno == EAGAIN) {
|
||||
/* finish up */
|
||||
@ -390,10 +410,11 @@ static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
|
||||
|
||||
if (rec == buffer_len) {
|
||||
buffer_len += STDIN_CHUNK_SIZE;
|
||||
buffer = srealloc(buffer, buffer_len);
|
||||
buffer = srealloc(buffer, buffer_len + 1);
|
||||
}
|
||||
}
|
||||
if (*buffer == '\0') {
|
||||
buffer[rec] = '\0';
|
||||
if (buffer[0] == '\0') {
|
||||
FREE(buffer);
|
||||
rec = -1;
|
||||
}
|
||||
@ -443,13 +464,14 @@ static bool read_json_input(unsigned char *input, int length) {
|
||||
* in statusline
|
||||
*
|
||||
*/
|
||||
static void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
|
||||
static void stdin_io_cb(int fd) {
|
||||
int rec;
|
||||
unsigned char *buffer = get_buffer(watcher, &rec);
|
||||
if (buffer == NULL)
|
||||
unsigned char *buffer = get_buffer(fd, &rec);
|
||||
if (buffer == NULL) {
|
||||
return;
|
||||
}
|
||||
bool has_urgent = false;
|
||||
if (child.version > 0) {
|
||||
if (status_child.version > 0) {
|
||||
has_urgent = read_json_input(buffer, rec);
|
||||
} else {
|
||||
read_flat_input((char *)buffer, rec);
|
||||
@ -463,22 +485,23 @@ static void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
|
||||
* whether this is JSON or plain text
|
||||
*
|
||||
*/
|
||||
static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
|
||||
static void stdin_io_first_line_cb(int fd) {
|
||||
int rec;
|
||||
unsigned char *buffer = get_buffer(watcher, &rec);
|
||||
if (buffer == NULL)
|
||||
unsigned char *buffer = get_buffer(fd, &rec);
|
||||
if (buffer == NULL) {
|
||||
return;
|
||||
}
|
||||
DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
|
||||
/* Detect whether this is JSON or plain text. */
|
||||
unsigned int consumed = 0;
|
||||
/* At the moment, we don’t care for the version. This might change
|
||||
* in the future, but for now, we just discard it. */
|
||||
parse_json_header(&child, buffer, rec, &consumed);
|
||||
if (child.version > 0) {
|
||||
/* If hide-on-modifier is set, we start of by sending the
|
||||
* child a SIGSTOP, because the bars aren't mapped at start */
|
||||
parse_json_header(&status_child, buffer, rec, &consumed);
|
||||
if (status_child.version > 0) {
|
||||
/* If hide-on-modifier is set, we start of by sending the status_child
|
||||
* a SIGSTOP, because the bars aren't mapped at start */
|
||||
if (config.hide_on_modifier) {
|
||||
stop_child();
|
||||
stop_children();
|
||||
}
|
||||
draw_bars(read_json_input(buffer + consumed, rec - consumed));
|
||||
} else {
|
||||
@ -489,9 +512,133 @@ static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int rev
|
||||
read_flat_input((char *)buffer, rec);
|
||||
}
|
||||
free(buffer);
|
||||
ev_io_stop(main_loop, stdin_io);
|
||||
ev_io_init(stdin_io, &stdin_io_cb, stdin_fd, EV_READ);
|
||||
ev_io_start(main_loop, stdin_io);
|
||||
}
|
||||
|
||||
static bool isempty(char *s) {
|
||||
while (*s != '\0') {
|
||||
if (!isspace(*s)) {
|
||||
return false;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static char *append_string(const char *previous, const char *str) {
|
||||
if (previous != NULL) {
|
||||
char *result;
|
||||
sasprintf(&result, "%s%s", previous, str);
|
||||
return result;
|
||||
}
|
||||
return sstrdup(str);
|
||||
}
|
||||
|
||||
static char *ws_last_json;
|
||||
|
||||
static void ws_stdin_io_cb(int fd) {
|
||||
int rec;
|
||||
unsigned char *buffer = get_buffer(fd, &rec);
|
||||
if (buffer == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
gchar **strings = g_strsplit((const char *)buffer, "\n", 0);
|
||||
for (int idx = 0; strings[idx] != NULL; idx++) {
|
||||
if (ws_child.pending_line == NULL && isempty(strings[idx])) {
|
||||
/* In the normal case where the buffer ends with '\n', the last
|
||||
* string should be empty */
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strings[idx + 1] == NULL) {
|
||||
/* This is the last string but it is not empty, meaning that we have
|
||||
* read data that is incomplete, save it for later. */
|
||||
char *new = append_string(ws_child.pending_line, strings[idx]);
|
||||
free(ws_child.pending_line);
|
||||
ws_child.pending_line = new;
|
||||
continue;
|
||||
}
|
||||
|
||||
free(ws_last_json);
|
||||
ws_last_json = append_string(ws_child.pending_line, strings[idx]);
|
||||
FREE(ws_child.pending_line);
|
||||
|
||||
parse_workspaces_json((const unsigned char *)ws_last_json, strlen(ws_last_json));
|
||||
}
|
||||
|
||||
g_strfreev(strings);
|
||||
free(buffer);
|
||||
|
||||
draw_bars(false);
|
||||
}
|
||||
|
||||
static void common_stdin_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
|
||||
if (watcher == status_child.stdin_io) {
|
||||
if (status_child.version == (uint32_t)-1) {
|
||||
stdin_io_first_line_cb(watcher->fd);
|
||||
} else {
|
||||
stdin_io_cb(watcher->fd);
|
||||
}
|
||||
} else if (watcher == ws_child.stdin_io) {
|
||||
ws_stdin_io_cb(watcher->fd);
|
||||
} else {
|
||||
ELOG("Got callback for unknown watcher fd=%d\n", watcher->fd);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* When workspace_command is enabled this function is used to re-parse the
|
||||
* latest received JSON from the client.
|
||||
*/
|
||||
void repeat_last_ws_json(void) {
|
||||
if (ws_last_json) {
|
||||
DLOG("Repeating last workspace JSON\n");
|
||||
parse_workspaces_json((const unsigned char *)ws_last_json, strlen(ws_last_json));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Wrapper around set_workspace_button_error to mimic the call of
|
||||
* set_statusline_error.
|
||||
*/
|
||||
__attribute__((format(printf, 1, 2))) static void set_workspace_button_error_f(const char *format, ...) {
|
||||
char *message;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
if (vasprintf(&message, format, args) == -1) {
|
||||
goto finish;
|
||||
}
|
||||
|
||||
set_workspace_button_error(message);
|
||||
|
||||
finish:
|
||||
free(message);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/*
|
||||
* Replaces the workspace buttons with an error message.
|
||||
*/
|
||||
void set_workspace_button_error(const char *message) {
|
||||
free_workspaces();
|
||||
|
||||
char *name = NULL;
|
||||
sasprintf(&name, "Error: %s", message);
|
||||
|
||||
i3_output *output;
|
||||
SLIST_FOREACH (output, outputs, slist) {
|
||||
i3_ws *fake_ws = scalloc(1, sizeof(i3_ws));
|
||||
/* Don't set the canonical_name field to make this workspace unfocusable. */
|
||||
fake_ws->name = i3string_from_utf8(name);
|
||||
fake_ws->name_width = predict_text_width(fake_ws->name);
|
||||
fake_ws->num = -1;
|
||||
fake_ws->urgent = fake_ws->visible = true;
|
||||
fake_ws->output = output;
|
||||
|
||||
TAILQ_INSERT_TAIL(output->workspaces, fake_ws, tailq);
|
||||
}
|
||||
|
||||
free(name);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -501,27 +648,45 @@ static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int rev
|
||||
*
|
||||
*/
|
||||
static void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
|
||||
int exit_status = WEXITSTATUS(watcher->rstatus);
|
||||
const int exit_status = WEXITSTATUS(watcher->rstatus);
|
||||
|
||||
ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
|
||||
child.pid,
|
||||
watcher->pid,
|
||||
exit_status);
|
||||
|
||||
void (*error_function_pointer)(const char *, ...) = NULL;
|
||||
const char *command_type = "";
|
||||
i3bar_child *c = NULL;
|
||||
if (watcher->pid == status_child.pid) {
|
||||
command_type = "status_command";
|
||||
error_function_pointer = set_statusline_error;
|
||||
c = &status_child;
|
||||
} else if (watcher->pid == ws_child.pid) {
|
||||
command_type = "workspace_command";
|
||||
error_function_pointer = set_workspace_button_error_f;
|
||||
c = &ws_child;
|
||||
} else {
|
||||
ELOG("Unknown child pid, this should never happen\n");
|
||||
return;
|
||||
}
|
||||
DLOG_CHILD(*c);
|
||||
|
||||
/* this error is most likely caused by a user giving a nonexecutable or
|
||||
* nonexistent file, so we will handle those cases separately. */
|
||||
if (exit_status == 126)
|
||||
set_statusline_error("status_command is not executable (exit %d)", exit_status);
|
||||
else if (exit_status == 127)
|
||||
set_statusline_error("status_command not found or is missing a library dependency (exit %d)", exit_status);
|
||||
else
|
||||
set_statusline_error("status_command process exited unexpectedly (exit %d)", exit_status);
|
||||
if (exit_status == 126) {
|
||||
error_function_pointer("%s is not executable (exit %d)", command_type, exit_status);
|
||||
} else if (exit_status == 127) {
|
||||
error_function_pointer("%s not found or is missing a library dependency (exit %d)", command_type, exit_status);
|
||||
} else {
|
||||
error_function_pointer("%s process exited unexpectedly (exit %d)", command_type, exit_status);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
cleanup(c);
|
||||
draw_bars(false);
|
||||
}
|
||||
|
||||
static void child_write_output(void) {
|
||||
if (child.click_events) {
|
||||
if (status_child.click_events) {
|
||||
const unsigned char *output;
|
||||
size_t size;
|
||||
ssize_t n;
|
||||
@ -535,7 +700,7 @@ static void child_write_output(void) {
|
||||
yajl_gen_clear(gen);
|
||||
|
||||
if (n == -1) {
|
||||
child.click_events = false;
|
||||
status_child.click_events = false;
|
||||
kill_child();
|
||||
set_statusline_error("child_write_output failed");
|
||||
draw_bars(false);
|
||||
@ -543,6 +708,41 @@ static void child_write_output(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static pid_t sfork(void) {
|
||||
const pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
ELOG("Couldn't fork(): %s\n", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
static void spipe(int pipedes[2]) {
|
||||
if (pipe(pipedes) == -1) {
|
||||
err(EXIT_FAILURE, "pipe(pipe_in)");
|
||||
}
|
||||
}
|
||||
|
||||
static void exec_shell(char *command) {
|
||||
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char *)NULL);
|
||||
}
|
||||
|
||||
static void setup_child_cb(i3bar_child *child) {
|
||||
/* We set O_NONBLOCK because blocking is evil in event-driven software */
|
||||
fcntl(child->stdin_fd, F_SETFL, O_NONBLOCK);
|
||||
|
||||
child->stdin_io = smalloc(sizeof(ev_io));
|
||||
ev_io_init(child->stdin_io, &common_stdin_cb, child->stdin_fd, EV_READ);
|
||||
ev_io_start(main_loop, child->stdin_io);
|
||||
|
||||
/* We must cleanup, if the child unexpectedly terminates */
|
||||
child->child_sig = smalloc(sizeof(ev_child));
|
||||
ev_child_init(child->child_sig, &child_sig_cb, child->pid, 0);
|
||||
ev_child_start(main_loop, child->child_sig);
|
||||
|
||||
DLOG_CHILD(*child);
|
||||
}
|
||||
|
||||
/*
|
||||
* Start a child process with the specified command and reroute stdin.
|
||||
* We actually start a shell to execute the command so we don't have to care
|
||||
@ -553,8 +753,9 @@ static void child_write_output(void) {
|
||||
*
|
||||
*/
|
||||
void start_child(char *command) {
|
||||
if (command == NULL)
|
||||
if (command == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Allocate a yajl parser which will be used to parse stdin. */
|
||||
static yajl_callbacks callbacks = {
|
||||
@ -568,69 +769,77 @@ void start_child(char *command) {
|
||||
.yajl_end_array = stdin_end_array,
|
||||
};
|
||||
parser = yajl_alloc(&callbacks, NULL, &parser_context);
|
||||
|
||||
gen = yajl_gen_alloc(NULL);
|
||||
|
||||
int pipe_in[2]; /* pipe we read from */
|
||||
int pipe_out[2]; /* pipe we write to */
|
||||
spipe(pipe_in);
|
||||
spipe(pipe_out);
|
||||
|
||||
if (pipe(pipe_in) == -1)
|
||||
err(EXIT_FAILURE, "pipe(pipe_in)");
|
||||
if (pipe(pipe_out) == -1)
|
||||
err(EXIT_FAILURE, "pipe(pipe_out)");
|
||||
status_child.pid = sfork();
|
||||
if (status_child.pid == 0) {
|
||||
/* Child-process. Reroute streams and start shell */
|
||||
close(pipe_in[0]);
|
||||
close(pipe_out[1]);
|
||||
|
||||
child.pid = fork();
|
||||
switch (child.pid) {
|
||||
case -1:
|
||||
ELOG("Couldn't fork(): %s\n", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
case 0:
|
||||
/* Child-process. Reroute streams and start shell */
|
||||
dup2(pipe_in[1], STDOUT_FILENO);
|
||||
dup2(pipe_out[0], STDIN_FILENO);
|
||||
|
||||
close(pipe_in[0]);
|
||||
close(pipe_out[1]);
|
||||
setpgid(status_child.pid, 0);
|
||||
exec_shell(command);
|
||||
return;
|
||||
}
|
||||
/* Parent-process. Reroute streams */
|
||||
close(pipe_in[1]);
|
||||
close(pipe_out[0]);
|
||||
|
||||
dup2(pipe_in[1], STDOUT_FILENO);
|
||||
dup2(pipe_out[0], STDIN_FILENO);
|
||||
status_child.stdin_fd = pipe_in[0];
|
||||
child_stdin = pipe_out[1];
|
||||
status_child.version = -1;
|
||||
|
||||
setpgid(child.pid, 0);
|
||||
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char *)NULL);
|
||||
return;
|
||||
default:
|
||||
/* Parent-process. Reroute streams */
|
||||
setup_child_cb(&status_child);
|
||||
}
|
||||
|
||||
close(pipe_in[1]);
|
||||
close(pipe_out[0]);
|
||||
|
||||
stdin_fd = pipe_in[0];
|
||||
child_stdin = pipe_out[1];
|
||||
|
||||
break;
|
||||
/*
|
||||
* Same as start_child but starts the configured client that manages workspace
|
||||
* buttons.
|
||||
*
|
||||
*/
|
||||
void start_ws_child(char *command) {
|
||||
if (command == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* We set O_NONBLOCK because blocking is evil in event-driven software */
|
||||
fcntl(stdin_fd, F_SETFL, O_NONBLOCK);
|
||||
ws_child.stop_signal = SIGSTOP;
|
||||
ws_child.cont_signal = SIGCONT;
|
||||
|
||||
stdin_io = smalloc(sizeof(ev_io));
|
||||
ev_io_init(stdin_io, &stdin_io_first_line_cb, stdin_fd, EV_READ);
|
||||
ev_io_start(main_loop, stdin_io);
|
||||
int pipe_in[2]; /* pipe we read from */
|
||||
spipe(pipe_in);
|
||||
|
||||
/* We must cleanup, if the child unexpectedly terminates */
|
||||
child_sig = smalloc(sizeof(ev_child));
|
||||
ev_child_init(child_sig, &child_sig_cb, child.pid, 0);
|
||||
ev_child_start(main_loop, child_sig);
|
||||
ws_child.pid = sfork();
|
||||
if (ws_child.pid == 0) {
|
||||
/* Child-process. Reroute streams and start shell */
|
||||
close(pipe_in[0]);
|
||||
dup2(pipe_in[1], STDOUT_FILENO);
|
||||
|
||||
atexit(kill_child_at_exit);
|
||||
DLOG_CHILD;
|
||||
setpgid(ws_child.pid, 0);
|
||||
exec_shell(command);
|
||||
return;
|
||||
}
|
||||
/* Parent-process. Reroute streams */
|
||||
close(pipe_in[1]);
|
||||
ws_child.stdin_fd = pipe_in[0];
|
||||
|
||||
setup_child_cb(&ws_child);
|
||||
}
|
||||
|
||||
static void child_click_events_initialize(void) {
|
||||
DLOG_CHILD;
|
||||
DLOG_CHILD(status_child);
|
||||
|
||||
if (!child.click_events_init) {
|
||||
if (!status_child.click_events_init) {
|
||||
yajl_gen_array_open(gen);
|
||||
child_write_output();
|
||||
child.click_events_init = true;
|
||||
status_child.click_events_init = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -639,7 +848,7 @@ static void child_click_events_initialize(void) {
|
||||
*
|
||||
*/
|
||||
void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int out_x, int out_y, int width, int height, int mods) {
|
||||
if (!child.click_events) {
|
||||
if (!status_child.click_events) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -706,35 +915,85 @@ void send_block_clicked(int button, const char *name, const char *instance, int
|
||||
child_write_output();
|
||||
}
|
||||
|
||||
static bool is_alive(i3bar_child *c) {
|
||||
return c->pid > 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the status child process is alive.
|
||||
*
|
||||
*/
|
||||
bool status_child_is_alive(void) {
|
||||
return is_alive(&status_child);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the workspace child process is alive.
|
||||
*
|
||||
*/
|
||||
bool ws_child_is_alive(void) {
|
||||
return is_alive(&ws_child);
|
||||
}
|
||||
|
||||
/*
|
||||
* kill()s the child process (if any). Called when exit()ing.
|
||||
*
|
||||
*/
|
||||
void kill_child_at_exit(void) {
|
||||
DLOG_CHILD;
|
||||
void kill_children_at_exit(void) {
|
||||
DLOG_CHILDREN;
|
||||
cont_children();
|
||||
|
||||
if (child.pid > 0) {
|
||||
if (child.cont_signal > 0 && child.stopped)
|
||||
killpg(child.pid, child.cont_signal);
|
||||
killpg(child.pid, SIGTERM);
|
||||
if (is_alive(&status_child)) {
|
||||
killpg(status_child.pid, SIGTERM);
|
||||
}
|
||||
if (is_alive(&ws_child)) {
|
||||
killpg(ws_child.pid, SIGTERM);
|
||||
}
|
||||
}
|
||||
|
||||
static void cont_child(i3bar_child *c) {
|
||||
if (is_alive(c) && c->cont_signal > 0 && c->stopped) {
|
||||
c->stopped = false;
|
||||
killpg(c->pid, c->cont_signal);
|
||||
}
|
||||
}
|
||||
|
||||
static void kill_and_wait(i3bar_child *c) {
|
||||
DLOG_CHILD(*c);
|
||||
if (!is_alive(c)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cont_child(c);
|
||||
killpg(c->pid, SIGTERM);
|
||||
int status;
|
||||
waitpid(c->pid, &status, 0);
|
||||
cleanup(c);
|
||||
}
|
||||
|
||||
/*
|
||||
* kill()s the child process (if existent) and closes and
|
||||
* free()s the stdin- and SIGCHLD-watchers
|
||||
* kill()s the child process (if any) and closes and free()s the stdin- and
|
||||
* SIGCHLD-watchers
|
||||
*
|
||||
*/
|
||||
void kill_child(void) {
|
||||
DLOG_CHILD;
|
||||
kill_and_wait(&status_child);
|
||||
}
|
||||
|
||||
if (child.pid > 0) {
|
||||
if (child.cont_signal > 0 && child.stopped)
|
||||
killpg(child.pid, child.cont_signal);
|
||||
killpg(child.pid, SIGTERM);
|
||||
int status;
|
||||
waitpid(child.pid, &status, 0);
|
||||
cleanup();
|
||||
/*
|
||||
* kill()s the workspace child process (if any) and closes and free()s the
|
||||
* stdin- and SIGCHLD-watchers.
|
||||
* Similar to kill_child.
|
||||
*
|
||||
*/
|
||||
void kill_ws_child(void) {
|
||||
kill_and_wait(&ws_child);
|
||||
}
|
||||
|
||||
static void stop_child(i3bar_child *c) {
|
||||
if (c->stop_signal > 0 && !c->stopped) {
|
||||
c->stopped = true;
|
||||
killpg(c->pid, c->stop_signal);
|
||||
}
|
||||
}
|
||||
|
||||
@ -742,26 +1001,21 @@ void kill_child(void) {
|
||||
* Sends a SIGSTOP to the child process (if existent)
|
||||
*
|
||||
*/
|
||||
void stop_child(void) {
|
||||
DLOG_CHILD;
|
||||
|
||||
if (child.stop_signal > 0 && !child.stopped) {
|
||||
child.stopped = true;
|
||||
killpg(child.pid, child.stop_signal);
|
||||
}
|
||||
void stop_children(void) {
|
||||
DLOG_CHILDREN;
|
||||
stop_child(&status_child);
|
||||
stop_child(&ws_child);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends a SIGCONT to the child process (if existent)
|
||||
*
|
||||
*/
|
||||
void cont_child(void) {
|
||||
DLOG_CHILD;
|
||||
void cont_children(void) {
|
||||
DLOG_CHILDREN;
|
||||
|
||||
if (child.cont_signal > 0 && child.stopped) {
|
||||
child.stopped = false;
|
||||
killpg(child.pid, child.cont_signal);
|
||||
}
|
||||
cont_child(&status_child);
|
||||
cont_child(&ws_child);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -769,5 +1023,5 @@ void cont_child(void) {
|
||||
*
|
||||
*/
|
||||
bool child_want_click_events(void) {
|
||||
return child.click_events;
|
||||
return status_child.click_events;
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ config_t config;
|
||||
static char *cur_key;
|
||||
static bool parsing_bindings;
|
||||
static bool parsing_tray_outputs;
|
||||
static bool parsing_padding;
|
||||
|
||||
/*
|
||||
* Parse a key.
|
||||
@ -38,12 +39,17 @@ static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t
|
||||
parsing_tray_outputs = true;
|
||||
}
|
||||
|
||||
if (strcmp(cur_key, "padding") == 0) {
|
||||
parsing_padding = true;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int config_end_array_cb(void *params_) {
|
||||
parsing_bindings = false;
|
||||
parsing_tray_outputs = false;
|
||||
parsing_padding = false;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -182,11 +188,17 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "status_command")) {
|
||||
DLOG("command = %.*s\n", len, val);
|
||||
DLOG("status_command = %.*s\n", len, val);
|
||||
sasprintf(&config.command, "%.*s", len, val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "workspace_command")) {
|
||||
DLOG("workspace_command = %.*s\n", len, val);
|
||||
sasprintf(&config.workspace_command, "%.*s", len, val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "font")) {
|
||||
DLOG("font = %.*s\n", len, val);
|
||||
FREE(config.fontname);
|
||||
@ -329,6 +341,35 @@ static int config_integer_cb(void *params_, long long val) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (parsing_padding) {
|
||||
if (strcmp(cur_key, "x") == 0) {
|
||||
DLOG("padding.x = %lld\n", val);
|
||||
config.padding.x = (uint32_t)val;
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(cur_key, "y") == 0) {
|
||||
DLOG("padding.y = %lld\n", val);
|
||||
config.padding.y = (uint32_t)val;
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(cur_key, "width") == 0) {
|
||||
DLOG("padding.width = %lld\n", val);
|
||||
config.padding.width = (uint32_t)val;
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(cur_key, "height") == 0) {
|
||||
DLOG("padding.height = %lld\n", val);
|
||||
config.padding.height = (uint32_t)val;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "bar_height")) {
|
||||
DLOG("bar_height = %lld\n", val);
|
||||
config.bar_height = (uint32_t)val;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "tray_padding")) {
|
||||
DLOG("tray_padding = %lld\n", val);
|
||||
config.tray_padding = val;
|
||||
@ -353,24 +394,23 @@ static int config_integer_cb(void *params_, long long val) {
|
||||
/* A datastructure to pass all these callbacks to yajl */
|
||||
static yajl_callbacks outputs_callbacks = {
|
||||
.yajl_null = config_null_cb,
|
||||
.yajl_boolean = config_boolean_cb,
|
||||
.yajl_integer = config_integer_cb,
|
||||
.yajl_boolean = config_boolean_cb,
|
||||
.yajl_string = config_string_cb,
|
||||
.yajl_end_array = config_end_array_cb,
|
||||
.yajl_map_key = config_map_key_cb,
|
||||
};
|
||||
|
||||
/*
|
||||
* Start parsing the received bar configuration JSON string
|
||||
* Parse the received bar configuration JSON string
|
||||
*
|
||||
*/
|
||||
void parse_config_json(char *json) {
|
||||
yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
|
||||
|
||||
void parse_config_json(const unsigned char *json, size_t size) {
|
||||
TAILQ_INIT(&(config.bindings));
|
||||
TAILQ_INIT(&(config.tray_outputs));
|
||||
|
||||
yajl_status state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
|
||||
yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
|
||||
yajl_status state = yajl_parse(handle, json, size);
|
||||
|
||||
/* FIXME: Proper error handling for JSON parsing */
|
||||
switch (state) {
|
||||
@ -383,6 +423,11 @@ void parse_config_json(char *json) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (config.disable_ws && config.workspace_command) {
|
||||
ELOG("You have specified 'workspace_buttons no'. Your 'workspace_command %s' will be ignored.\n", config.workspace_command);
|
||||
FREE(config.workspace_command);
|
||||
}
|
||||
|
||||
yajl_free(handle);
|
||||
}
|
||||
|
||||
@ -392,16 +437,16 @@ static int i3bar_config_string_cb(void *params_, const unsigned char *val, size_
|
||||
}
|
||||
|
||||
/*
|
||||
* Start parsing the received bar configuration list. The only usecase right
|
||||
* now is to automatically get the first bar id.
|
||||
* Parse the received bar configuration list. The only usecase right now is to
|
||||
* automatically get the first bar id.
|
||||
*
|
||||
*/
|
||||
void parse_get_first_i3bar_config(char *json) {
|
||||
void parse_get_first_i3bar_config(const unsigned char *json, size_t size) {
|
||||
yajl_callbacks configs_callbacks = {
|
||||
.yajl_string = i3bar_config_string_cb,
|
||||
};
|
||||
yajl_handle handle = yajl_alloc(&configs_callbacks, NULL, NULL);
|
||||
yajl_parse(handle, (const unsigned char *)json, strlen(json));
|
||||
yajl_parse(handle, json, size);
|
||||
yajl_free(handle);
|
||||
}
|
||||
|
||||
|
@ -24,14 +24,24 @@ ev_io *i3_connection;
|
||||
|
||||
const char *sock_path;
|
||||
|
||||
typedef void (*handler_t)(char *);
|
||||
typedef void (*handler_t)(const unsigned char *, size_t);
|
||||
|
||||
/*
|
||||
* Returns true when i3bar is configured to read workspace information from i3
|
||||
* via JSON over the i3 IPC interface, as opposed to reading workspace
|
||||
* information from the workspace_command via JSON over stdout.
|
||||
*
|
||||
*/
|
||||
static bool i3_provides_workspaces(void) {
|
||||
return !config.disable_ws && config.workspace_command == NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called, when we get a reply to a command from i3.
|
||||
* Since i3 does not give us much feedback on commands, we do not much
|
||||
*
|
||||
*/
|
||||
static void got_command_reply(char *reply) {
|
||||
static void got_command_reply(const unsigned char *reply, size_t size) {
|
||||
/* TODO: Error handling for command replies */
|
||||
}
|
||||
|
||||
@ -39,9 +49,9 @@ static void got_command_reply(char *reply) {
|
||||
* Called, when we get a reply with workspaces data
|
||||
*
|
||||
*/
|
||||
static void got_workspace_reply(char *reply) {
|
||||
static void got_workspace_reply(const unsigned char *reply, size_t size) {
|
||||
DLOG("Got workspace data!\n");
|
||||
parse_workspaces_json(reply);
|
||||
parse_workspaces_json(reply, size);
|
||||
draw_bars(false);
|
||||
}
|
||||
|
||||
@ -50,7 +60,7 @@ static void got_workspace_reply(char *reply) {
|
||||
* Since i3 does not give us much feedback on commands, we do not much
|
||||
*
|
||||
*/
|
||||
static void got_subscribe_reply(char *reply) {
|
||||
static void got_subscribe_reply(const unsigned char *reply, size_t size) {
|
||||
DLOG("Got subscribe reply: %s\n", reply);
|
||||
/* TODO: Error handling for subscribe commands */
|
||||
}
|
||||
@ -59,12 +69,12 @@ static void got_subscribe_reply(char *reply) {
|
||||
* Called, when we get a reply with outputs data
|
||||
*
|
||||
*/
|
||||
static void got_output_reply(char *reply) {
|
||||
static void got_output_reply(const unsigned char *reply, size_t size) {
|
||||
DLOG("Clearing old output configuration...\n");
|
||||
free_outputs();
|
||||
|
||||
DLOG("Parsing outputs JSON...\n");
|
||||
parse_outputs_json(reply);
|
||||
parse_outputs_json(reply, size);
|
||||
DLOG("Reconfiguring windows...\n");
|
||||
reconfig_windows(false);
|
||||
|
||||
@ -73,8 +83,19 @@ static void got_output_reply(char *reply) {
|
||||
kick_tray_clients(o_walk);
|
||||
}
|
||||
|
||||
if (!config.disable_ws) {
|
||||
if (i3_provides_workspaces()) {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
||||
} else if (config.workspace_command) {
|
||||
/* Communication with the workspace child is one-way. Since we called
|
||||
* free_outputs() and free_workspaces() we have lost our workspace
|
||||
* information which will result in no workspace buttons. A
|
||||
* well-behaving client should be subscribed to output events as well
|
||||
* and re-send the output information to i3bar. Even in that case
|
||||
* though there is a race condition where the child can send the new
|
||||
* workspace information after the output change before i3bar receives
|
||||
* the output event from i3. For this reason, we re-parse the latest
|
||||
* received JSON. */
|
||||
repeat_last_ws_json();
|
||||
}
|
||||
|
||||
draw_bars(false);
|
||||
@ -84,10 +105,10 @@ static void got_output_reply(char *reply) {
|
||||
* Called when we get the configuration for our bar instance
|
||||
*
|
||||
*/
|
||||
static void got_bar_config(char *reply) {
|
||||
static void got_bar_config(const unsigned char *reply, size_t size) {
|
||||
if (!config.bar_id) {
|
||||
DLOG("Received bar list \"%s\"\n", reply);
|
||||
parse_get_first_i3bar_config(reply);
|
||||
parse_get_first_i3bar_config(reply, size);
|
||||
|
||||
if (!config.bar_id) {
|
||||
ELOG("No bar configuration found, please configure a bar block in your i3 config file.\n");
|
||||
@ -106,13 +127,14 @@ static void got_bar_config(char *reply) {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
|
||||
|
||||
free_colors(&(config.colors));
|
||||
parse_config_json(reply);
|
||||
parse_config_json(reply, size);
|
||||
|
||||
/* Now we can actually use 'config', so let's subscribe to the appropriate
|
||||
* events and request the workspaces if necessary. */
|
||||
subscribe_events();
|
||||
if (!config.disable_ws)
|
||||
if (i3_provides_workspaces()) {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
||||
}
|
||||
|
||||
/* Initialize the rest of XCB */
|
||||
init_xcb_late(config.fontname);
|
||||
@ -121,6 +143,7 @@ static void got_bar_config(char *reply) {
|
||||
init_colors(&(config.colors));
|
||||
|
||||
start_child(config.command);
|
||||
start_ws_child(config.workspace_command);
|
||||
}
|
||||
|
||||
/* Data structure to easily call the reply handlers later */
|
||||
@ -143,7 +166,7 @@ handler_t reply_handlers[] = {
|
||||
* Called, when a workspace event arrives (i.e. the user changed the workspace)
|
||||
*
|
||||
*/
|
||||
static void got_workspace_event(char *event) {
|
||||
static void got_workspace_event(const unsigned char *event, size_t size) {
|
||||
DLOG("Got workspace event!\n");
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
||||
}
|
||||
@ -152,7 +175,7 @@ static void got_workspace_event(char *event) {
|
||||
* Called, when an output event arrives (i.e. the screen configuration changed)
|
||||
*
|
||||
*/
|
||||
static void got_output_event(char *event) {
|
||||
static void got_output_event(const unsigned char *event, size_t size) {
|
||||
DLOG("Got output event!\n");
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
|
||||
}
|
||||
@ -161,9 +184,9 @@ static void got_output_event(char *event) {
|
||||
* Called, when a mode event arrives (i3 changed binding mode).
|
||||
*
|
||||
*/
|
||||
static void got_mode_event(char *event) {
|
||||
static void got_mode_event(const unsigned char *event, size_t size) {
|
||||
DLOG("Got mode event!\n");
|
||||
parse_mode_json(event);
|
||||
parse_mode_json(event, size);
|
||||
draw_bars(false);
|
||||
}
|
||||
|
||||
@ -183,11 +206,11 @@ static bool strings_differ(char *a, char *b) {
|
||||
* Called, when a barconfig_update event arrives (i.e. i3 changed the bar hidden_state or mode)
|
||||
*
|
||||
*/
|
||||
static void got_bar_config_update(char *event) {
|
||||
static void got_bar_config_update(const unsigned char *event, size_t size) {
|
||||
/* check whether this affect this bar instance by checking the bar_id */
|
||||
char *expected_id;
|
||||
sasprintf(&expected_id, "\"id\":\"%s\"", config.bar_id);
|
||||
char *found_id = strstr(event, expected_id);
|
||||
char *found_id = strstr((const char *)event, expected_id);
|
||||
FREE(expected_id);
|
||||
if (found_id == NULL)
|
||||
return;
|
||||
@ -201,10 +224,12 @@ static void got_bar_config_update(char *event) {
|
||||
DLOG("Received bar config update \"%s\"\n", event);
|
||||
|
||||
char *old_command = config.command;
|
||||
char *old_workspace_command = config.workspace_command;
|
||||
config.command = NULL;
|
||||
config.workspace_command = NULL;
|
||||
bar_display_mode_t old_mode = config.hide_on_modifier;
|
||||
|
||||
parse_config_json(event);
|
||||
parse_config_json(event, size);
|
||||
if (old_mode != config.hide_on_modifier) {
|
||||
reconfig_windows(true);
|
||||
}
|
||||
@ -214,13 +239,21 @@ static void got_bar_config_update(char *event) {
|
||||
init_colors(&(config.colors));
|
||||
|
||||
/* restart status command process */
|
||||
if (strings_differ(old_command, config.command)) {
|
||||
if (!status_child_is_alive() || strings_differ(old_command, config.command)) {
|
||||
kill_child();
|
||||
clear_statusline(&statusline_head, true);
|
||||
start_child(config.command);
|
||||
}
|
||||
free(old_command);
|
||||
|
||||
/* restart workspace command process */
|
||||
if (!ws_child_is_alive() || strings_differ(old_workspace_command, config.workspace_command)) {
|
||||
free_workspaces();
|
||||
kill_ws_child();
|
||||
start_ws_child(config.workspace_command);
|
||||
}
|
||||
free(old_workspace_command);
|
||||
|
||||
draw_bars(false);
|
||||
}
|
||||
|
||||
@ -284,7 +317,7 @@ static void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
|
||||
|
||||
/* Now that we know, what to expect, we can start read()ing the rest
|
||||
* of the message */
|
||||
char *buffer = smalloc(size + 1);
|
||||
unsigned char *buffer = smalloc(size + 1);
|
||||
rec = 0;
|
||||
|
||||
while (rec < size) {
|
||||
@ -304,10 +337,11 @@ static void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
|
||||
/* And call the callback (indexed by the type) */
|
||||
if (type & (1UL << 31)) {
|
||||
type ^= 1UL << 31;
|
||||
event_handlers[type](buffer);
|
||||
event_handlers[type](buffer, size);
|
||||
} else {
|
||||
if (reply_handlers[type])
|
||||
reply_handlers[type](buffer);
|
||||
if (reply_handlers[type]) {
|
||||
reply_handlers[type](buffer, size);
|
||||
}
|
||||
}
|
||||
|
||||
FREE(header);
|
||||
@ -340,8 +374,9 @@ int i3_send_msg(uint32_t type, const char *payload) {
|
||||
memcpy(walk, &type, sizeof(uint32_t));
|
||||
walk += sizeof(uint32_t);
|
||||
|
||||
if (payload != NULL)
|
||||
strncpy(walk, payload, len);
|
||||
if (payload != NULL) {
|
||||
memcpy(walk, payload, len);
|
||||
}
|
||||
|
||||
swrite(i3_connection->fd, buffer, to_write);
|
||||
|
||||
@ -376,9 +411,9 @@ void destroy_connection(void) {
|
||||
*
|
||||
*/
|
||||
void subscribe_events(void) {
|
||||
if (config.disable_ws) {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\", \"barconfig_update\" ]");
|
||||
} else {
|
||||
if (i3_provides_workspaces()) {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\", \"barconfig_update\" ]");
|
||||
} else {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\", \"barconfig_update\" ]");
|
||||
}
|
||||
}
|
||||
|
@ -185,12 +185,12 @@ int main(int argc, char **argv) {
|
||||
ev_signal_start(main_loop, sig_int);
|
||||
ev_signal_start(main_loop, sig_hup);
|
||||
|
||||
atexit(kill_children_at_exit);
|
||||
|
||||
/* From here on everything should run smooth for itself, just start listening for
|
||||
* events. We stop simply stop the event loop, when we are finished */
|
||||
ev_loop(main_loop, 0);
|
||||
|
||||
kill_child();
|
||||
|
||||
clean_xcb();
|
||||
ev_default_destroy();
|
||||
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
/* A datatype to pass through the callbacks to save the state */
|
||||
struct mode_json_params {
|
||||
char *json;
|
||||
char *cur_key;
|
||||
char *name;
|
||||
bool pango_markup;
|
||||
@ -96,26 +95,17 @@ static yajl_callbacks mode_callbacks = {
|
||||
};
|
||||
|
||||
/*
|
||||
* Start parsing the received JSON string
|
||||
* Parse the received JSON string
|
||||
*
|
||||
*/
|
||||
void parse_mode_json(char *json) {
|
||||
/* FIXME: Fasciliate stream processing, i.e. allow starting to interpret
|
||||
* JSON in chunks */
|
||||
void parse_mode_json(const unsigned char *json, size_t size) {
|
||||
struct mode_json_params params;
|
||||
|
||||
mode binding;
|
||||
|
||||
params.cur_key = NULL;
|
||||
params.json = json;
|
||||
params.mode = &binding;
|
||||
|
||||
yajl_handle handle;
|
||||
yajl_status state;
|
||||
|
||||
handle = yajl_alloc(&mode_callbacks, NULL, (void *)¶ms);
|
||||
|
||||
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
|
||||
yajl_handle handle = yajl_alloc(&mode_callbacks, NULL, (void *)¶ms);
|
||||
yajl_status state = yajl_parse(handle, json, size);
|
||||
|
||||
/* FIXME: Proper error handling for JSON parsing */
|
||||
switch (state) {
|
||||
|
@ -18,10 +18,8 @@
|
||||
|
||||
/* A datatype to pass through the callbacks to save the state */
|
||||
struct outputs_json_params {
|
||||
struct outputs_head *outputs;
|
||||
i3_output *outputs_walk;
|
||||
char *cur_key;
|
||||
char *json;
|
||||
bool in_rect;
|
||||
};
|
||||
|
||||
@ -263,21 +261,17 @@ void init_outputs(void) {
|
||||
}
|
||||
|
||||
/*
|
||||
* Start parsing the received JSON string
|
||||
* Parse the received JSON string
|
||||
*
|
||||
*/
|
||||
void parse_outputs_json(char *json) {
|
||||
void parse_outputs_json(const unsigned char *json, size_t size) {
|
||||
struct outputs_json_params params;
|
||||
params.outputs_walk = NULL;
|
||||
params.cur_key = NULL;
|
||||
params.json = json;
|
||||
params.in_rect = false;
|
||||
|
||||
yajl_handle handle;
|
||||
yajl_status state;
|
||||
handle = yajl_alloc(&outputs_callbacks, NULL, (void *)¶ms);
|
||||
|
||||
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
|
||||
yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, (void *)¶ms);
|
||||
yajl_status state = yajl_parse(handle, json, size);
|
||||
|
||||
/* FIXME: Proper errorhandling for JSON-parsing */
|
||||
switch (state) {
|
||||
@ -291,6 +285,7 @@ void parse_outputs_json(char *json) {
|
||||
}
|
||||
|
||||
yajl_free(handle);
|
||||
free(params.cur_key);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -319,12 +314,14 @@ void free_outputs(void) {
|
||||
*
|
||||
*/
|
||||
i3_output *get_output_by_name(char *name) {
|
||||
i3_output *walk;
|
||||
if (name == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
const bool is_primary = !strcasecmp(name, "primary");
|
||||
|
||||
i3_output *walk;
|
||||
SLIST_FOREACH (walk, outputs, slist) {
|
||||
if (!strcmp(walk->name, name)) {
|
||||
if ((is_primary && walk->primary) || !strcmp(walk->name, name)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,8 @@ struct workspaces_json_params {
|
||||
struct ws_head *workspaces;
|
||||
i3_ws *workspaces_walk;
|
||||
char *cur_key;
|
||||
char *json;
|
||||
bool need_output;
|
||||
bool parsing_rect;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -71,26 +72,23 @@ static int workspaces_integer_cb(void *params_, long long val) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* rect is unused, so we don't bother to save it */
|
||||
if (!strcmp(params->cur_key, "x")) {
|
||||
params->workspaces_walk->rect.x = (int)val;
|
||||
FREE(params->cur_key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(params->cur_key, "y")) {
|
||||
params->workspaces_walk->rect.y = (int)val;
|
||||
FREE(params->cur_key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(params->cur_key, "width")) {
|
||||
params->workspaces_walk->rect.w = (int)val;
|
||||
FREE(params->cur_key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(params->cur_key, "height")) {
|
||||
params->workspaces_walk->rect.h = (int)val;
|
||||
FREE(params->cur_key);
|
||||
return 1;
|
||||
}
|
||||
@ -156,15 +154,16 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
|
||||
sasprintf(&output_name, "%.*s", len, val);
|
||||
|
||||
i3_output *target = get_output_by_name(output_name);
|
||||
i3_ws *ws = params->workspaces_walk;
|
||||
if (target != NULL) {
|
||||
params->workspaces_walk->output = target;
|
||||
|
||||
TAILQ_INSERT_TAIL(params->workspaces_walk->output->workspaces,
|
||||
params->workspaces_walk,
|
||||
tailq);
|
||||
ws->output = target;
|
||||
TAILQ_INSERT_TAIL(ws->output->workspaces, ws, tailq);
|
||||
}
|
||||
|
||||
params->need_output = false;
|
||||
FREE(output_name);
|
||||
FREE(params->cur_key);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -172,28 +171,42 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
|
||||
}
|
||||
|
||||
/*
|
||||
* We hit the start of a JSON map (rect or a new output)
|
||||
* We hit the start of a JSON map (rect or a new workspace)
|
||||
*
|
||||
*/
|
||||
static int workspaces_start_map_cb(void *params_) {
|
||||
struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
|
||||
|
||||
i3_ws *new_workspace = NULL;
|
||||
|
||||
if (params->cur_key == NULL) {
|
||||
new_workspace = smalloc(sizeof(i3_ws));
|
||||
i3_ws *new_workspace = scalloc(1, sizeof(i3_ws));
|
||||
new_workspace->num = -1;
|
||||
new_workspace->name = NULL;
|
||||
new_workspace->visible = 0;
|
||||
new_workspace->focused = 0;
|
||||
new_workspace->urgent = 0;
|
||||
memset(&new_workspace->rect, 0, sizeof(rect));
|
||||
new_workspace->output = NULL;
|
||||
|
||||
params->workspaces_walk = new_workspace;
|
||||
params->need_output = true;
|
||||
params->parsing_rect = false;
|
||||
} else {
|
||||
params->parsing_rect = true;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int workspaces_end_map_cb(void *params_) {
|
||||
struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
|
||||
i3_ws *ws = params->workspaces_walk;
|
||||
const bool parsing_rect = params->parsing_rect;
|
||||
params->parsing_rect = false;
|
||||
|
||||
if (parsing_rect || !ws || !ws->name || !params->need_output) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
ws->output = get_output_by_name("primary");
|
||||
if (ws->output == NULL) {
|
||||
ws->output = SLIST_FIRST(outputs);
|
||||
}
|
||||
TAILQ_INSERT_TAIL(ws->output->workspaces, ws, tailq);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -216,43 +229,42 @@ static yajl_callbacks workspaces_callbacks = {
|
||||
.yajl_integer = workspaces_integer_cb,
|
||||
.yajl_string = workspaces_string_cb,
|
||||
.yajl_start_map = workspaces_start_map_cb,
|
||||
.yajl_end_map = workspaces_end_map_cb,
|
||||
.yajl_map_key = workspaces_map_key_cb,
|
||||
};
|
||||
|
||||
/*
|
||||
* Start parsing the received JSON string
|
||||
* Parse the received JSON string
|
||||
*
|
||||
*/
|
||||
void parse_workspaces_json(char *json) {
|
||||
/* FIXME: Fasciliate stream processing, i.e. allow starting to interpret
|
||||
* JSON in chunks */
|
||||
struct workspaces_json_params params;
|
||||
|
||||
void parse_workspaces_json(const unsigned char *json, size_t size) {
|
||||
free_workspaces();
|
||||
|
||||
params.workspaces_walk = NULL;
|
||||
params.cur_key = NULL;
|
||||
params.json = json;
|
||||
|
||||
yajl_handle handle;
|
||||
yajl_status state;
|
||||
handle = yajl_alloc(&workspaces_callbacks, NULL, (void *)¶ms);
|
||||
|
||||
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
|
||||
struct workspaces_json_params params = {0};
|
||||
yajl_handle handle = yajl_alloc(&workspaces_callbacks, NULL, (void *)¶ms);
|
||||
yajl_status state = yajl_parse(handle, json, size);
|
||||
|
||||
/* FIXME: Proper error handling for JSON parsing */
|
||||
switch (state) {
|
||||
case yajl_status_ok:
|
||||
break;
|
||||
case yajl_status_client_canceled:
|
||||
case yajl_status_error:
|
||||
ELOG("Could not parse workspaces reply!\n");
|
||||
exit(EXIT_FAILURE);
|
||||
case yajl_status_error: {
|
||||
unsigned char *err = yajl_get_error(handle, 1, json, size);
|
||||
ELOG("Could not parse workspaces reply, error:\n%s\njson:---%s---\n", err, json);
|
||||
yajl_free_error(handle, err);
|
||||
|
||||
if (config.workspace_command) {
|
||||
kill_ws_child();
|
||||
set_workspace_button_error("Could not parse workspace_command's JSON");
|
||||
} else {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
yajl_free(handle);
|
||||
|
||||
FREE(params.cur_key);
|
||||
}
|
||||
|
||||
@ -261,14 +273,14 @@ void parse_workspaces_json(char *json) {
|
||||
*
|
||||
*/
|
||||
void free_workspaces(void) {
|
||||
i3_output *outputs_walk;
|
||||
if (outputs == NULL) {
|
||||
return;
|
||||
}
|
||||
i3_ws *ws_walk;
|
||||
|
||||
i3_output *outputs_walk;
|
||||
SLIST_FOREACH (outputs_walk, outputs, slist) {
|
||||
if (outputs_walk->workspaces != NULL && !TAILQ_EMPTY(outputs_walk->workspaces)) {
|
||||
i3_ws *ws_walk;
|
||||
TAILQ_FOREACH (ws_walk, outputs_walk->workspaces, tailq) {
|
||||
I3STRING_FREE(ws_walk->name);
|
||||
FREE(ws_walk->canonical_name);
|
||||
|
121
i3bar/src/xcb.c
121
i3bar/src/xcb.c
@ -59,7 +59,7 @@ xcb_visualtype_t *visual_type;
|
||||
uint8_t depth;
|
||||
xcb_colormap_t colormap;
|
||||
|
||||
/* Overall height of the bar (based on font size) */
|
||||
/* Overall height of the bar */
|
||||
int bar_height;
|
||||
|
||||
/* These are only relevant for XKB, which we only need for grabbing modifiers */
|
||||
@ -180,7 +180,7 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b
|
||||
/* Draw a custom separator. */
|
||||
uint32_t separator_x = MAX(x - block->sep_block_width, center_x - separator_symbol_width / 2);
|
||||
draw_util_text(config.separator_symbol, &output->statusline_buffer, sep_fg, bar_bg,
|
||||
separator_x, logical_px(ws_voff_px), x - separator_x);
|
||||
separator_x, bar_height / 2 - font.height / 2, x - separator_x);
|
||||
}
|
||||
}
|
||||
|
||||
@ -334,7 +334,7 @@ static void hide_bars(void) {
|
||||
}
|
||||
xcb_unmap_window(xcb_connection, walk->bar.id);
|
||||
}
|
||||
stop_child();
|
||||
stop_children();
|
||||
}
|
||||
|
||||
/*
|
||||
@ -351,7 +351,7 @@ static void unhide_bars(void) {
|
||||
uint32_t mask;
|
||||
uint32_t values[5];
|
||||
|
||||
cont_child();
|
||||
cont_children();
|
||||
|
||||
SLIST_FOREACH (walk, outputs, slist) {
|
||||
if (walk->bar.id == XCB_NONE) {
|
||||
@ -500,6 +500,49 @@ static int predict_button_width(int name_width) {
|
||||
logical_px(config.ws_min_width));
|
||||
}
|
||||
|
||||
static char *quote_workspace_name(const char *in) {
|
||||
/* To properly handle workspace names with double quotes in them, we need
|
||||
* to escape the double quotes. We allocate a large enough buffer (twice
|
||||
* the unescaped size is always enough), then we copy character by
|
||||
* character. */
|
||||
const size_t namelen = strlen(in);
|
||||
const size_t len = namelen + strlen("workspace \"\"") + 1;
|
||||
char *out = scalloc(2 * len, 1);
|
||||
memcpy(out, "workspace \"", strlen("workspace \""));
|
||||
size_t inpos, outpos;
|
||||
for (inpos = 0, outpos = strlen("workspace \"");
|
||||
inpos < namelen;
|
||||
inpos++, outpos++) {
|
||||
if (in[inpos] == '"' || in[inpos] == '\\') {
|
||||
out[outpos] = '\\';
|
||||
outpos++;
|
||||
}
|
||||
out[outpos] = in[inpos];
|
||||
}
|
||||
out[outpos] = '"';
|
||||
return out;
|
||||
}
|
||||
|
||||
static void focus_workspace(i3_ws *ws) {
|
||||
char *buffer = NULL;
|
||||
if (ws->id != 0) {
|
||||
/* Workspace ID has higher precedence since the workspace_command is
|
||||
* allowed to change workspace names as long as it provides a valid ID. */
|
||||
sasprintf(&buffer, "[con_id=%lld] focus workspace", ws->id);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ws->canonical_name == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
buffer = quote_workspace_name(ws->canonical_name);
|
||||
|
||||
done:
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, buffer);
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a button press event (i.e. a mouse click on one of our bars).
|
||||
* We determine, whether the click occurred on a workspace button or if the scroll-
|
||||
@ -620,37 +663,7 @@ static void handle_button(xcb_button_press_event_t *event) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* To properly handle workspace names with double quotes in them, we need
|
||||
* to escape the double quotes. Unfortunately, that’s rather ugly in C: We
|
||||
* first count the number of double quotes, then we allocate a large enough
|
||||
* buffer, then we copy character by character. */
|
||||
int num_quotes = 0;
|
||||
size_t namelen = 0;
|
||||
const char *utf8_name = cur_ws->canonical_name;
|
||||
for (const char *walk = utf8_name; *walk != '\0'; walk++) {
|
||||
if (*walk == '"' || *walk == '\\')
|
||||
num_quotes++;
|
||||
/* While we’re looping through the name anyway, we can save one
|
||||
* strlen(). */
|
||||
namelen++;
|
||||
}
|
||||
|
||||
const size_t len = namelen + strlen("workspace \"\"") + 1;
|
||||
char *buffer = scalloc(len + num_quotes, 1);
|
||||
memcpy(buffer, "workspace \"", strlen("workspace \""));
|
||||
size_t inpos, outpos;
|
||||
for (inpos = 0, outpos = strlen("workspace \"");
|
||||
inpos < namelen;
|
||||
inpos++, outpos++) {
|
||||
if (utf8_name[inpos] == '"' || utf8_name[inpos] == '\\') {
|
||||
buffer[outpos] = '\\';
|
||||
outpos++;
|
||||
}
|
||||
buffer[outpos] = utf8_name[inpos];
|
||||
}
|
||||
buffer[outpos] = '"';
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, buffer);
|
||||
free(buffer);
|
||||
focus_workspace(cur_ws);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -674,9 +687,9 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
|
||||
}
|
||||
|
||||
if (num_visible == 0) {
|
||||
stop_child();
|
||||
stop_children();
|
||||
} else {
|
||||
cont_child();
|
||||
cont_children();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1354,8 +1367,27 @@ void init_xcb_late(char *fontname) {
|
||||
/* Load the font */
|
||||
font = load_font(fontname, true);
|
||||
set_font(&font);
|
||||
DLOG("Calculated font height: %d\n", font.height);
|
||||
bar_height = font.height + 2 * logical_px(ws_voff_px);
|
||||
DLOG("Calculated font-height: %d\n", font.height);
|
||||
|
||||
/*
|
||||
* If the bar height was explicitly set (but padding was not set), use
|
||||
* it. Otherwise, calculate it based on the font size.
|
||||
*/
|
||||
const int default_px = font.height + 2 * logical_px(ws_voff_px);
|
||||
int padding_scaled =
|
||||
logical_px(config.padding.y) +
|
||||
logical_px(config.padding.height);
|
||||
if (config.bar_height > 0 &&
|
||||
config.padding.x == 0 &&
|
||||
config.padding.y == 0 &&
|
||||
config.padding.width == 0 &&
|
||||
config.padding.height == 0) {
|
||||
padding_scaled = config.bar_height - default_px;
|
||||
DLOG("setting padding_scaled=%d based on bar_height=%d\n", padding_scaled, config.bar_height);
|
||||
} else {
|
||||
DLOG("padding: x=%d, y=%d -> padding_scaled=%d\n", config.padding.x, config.padding.y, padding_scaled);
|
||||
}
|
||||
bar_height = default_px + padding_scaled;
|
||||
icon_size = bar_height - 2 * logical_px(config.tray_padding);
|
||||
|
||||
if (config.separator_symbol)
|
||||
@ -1926,10 +1958,10 @@ void reconfig_windows(bool redraw_bars) {
|
||||
/* Unmap the window, and draw it again when in dock mode */
|
||||
umap_cookie = xcb_unmap_window_checked(xcb_connection, walk->bar.id);
|
||||
if (config.hide_on_modifier == M_DOCK) {
|
||||
cont_child();
|
||||
cont_children();
|
||||
map_cookie = xcb_map_window_checked(xcb_connection, walk->bar.id);
|
||||
} else {
|
||||
stop_child();
|
||||
stop_children();
|
||||
}
|
||||
|
||||
if (config.hide_on_modifier == M_HIDE) {
|
||||
@ -1973,7 +2005,7 @@ void reconfig_windows(bool redraw_bars) {
|
||||
*/
|
||||
static void draw_button(surface_t *surface, color_t fg_color, color_t bg_color, color_t border_color,
|
||||
int x, int width, int text_width, i3String *text) {
|
||||
int height = font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1);
|
||||
int height = bar_height - 2 * logical_px(1);
|
||||
|
||||
/* Draw the border of the button. */
|
||||
draw_util_rectangle(surface, border_color, x, logical_px(1), width, height);
|
||||
@ -1983,7 +2015,7 @@ static void draw_button(surface_t *surface, color_t fg_color, color_t bg_color,
|
||||
width - 2 * logical_px(1), height - 2 * logical_px(1));
|
||||
|
||||
draw_util_text(text, surface, fg_color, bg_color, x + (width - text_width) / 2,
|
||||
logical_px(ws_voff_px), text_width);
|
||||
bar_height / 2 - font.height / 2, text_width);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1998,7 +2030,7 @@ void draw_bars(bool unhide) {
|
||||
|
||||
i3_output *outputs_walk;
|
||||
SLIST_FOREACH (outputs_walk, outputs, slist) {
|
||||
int workspace_width = 0;
|
||||
int workspace_width = logical_px(config.padding.x);
|
||||
|
||||
if (!outputs_walk->active) {
|
||||
DLOG("Output %s inactive, skipping...\n", outputs_walk->name);
|
||||
@ -2082,6 +2114,7 @@ void draw_bars(bool unhide) {
|
||||
|
||||
int16_t visible_statusline_width = MIN(statusline_width, max_statusline_width);
|
||||
int x_dest = outputs_walk->rect.w - tray_width - logical_px((tray_width > 0) * sb_hoff_px) - visible_statusline_width;
|
||||
x_dest -= logical_px(config.padding.width);
|
||||
|
||||
draw_statusline(outputs_walk, clip_left, use_focus_colors, use_short_text);
|
||||
draw_util_copy_surface(&outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0,
|
||||
|
@ -46,6 +46,7 @@
|
||||
#include "click.h"
|
||||
#include "key_press.h"
|
||||
#include "floating.h"
|
||||
#include "gaps.h"
|
||||
#include "drag.h"
|
||||
#include "configuration.h"
|
||||
#include "handlers.h"
|
||||
|
@ -198,7 +198,7 @@ void cmd_focus_level(I3_CMD, const char *level);
|
||||
* Implementation of 'focus'.
|
||||
*
|
||||
*/
|
||||
void cmd_focus(I3_CMD);
|
||||
void cmd_focus(I3_CMD, bool focus_workspace);
|
||||
|
||||
/**
|
||||
* Implementation of 'fullscreen [enable|disable|toggle] [global]'.
|
||||
@ -332,6 +332,12 @@ void cmd_shmlog(I3_CMD, const char *argument);
|
||||
*/
|
||||
void cmd_debuglog(I3_CMD, const char *argument);
|
||||
|
||||
/**
|
||||
* Implementation of 'gaps inner|outer|top|right|bottom|left|horizontal|vertical current|all set|plus|minus|toggle <px>'
|
||||
*
|
||||
*/
|
||||
void cmd_gaps(I3_CMD, const char *type, const char *scope, const char *mode, const char *value);
|
||||
|
||||
/**
|
||||
* Implementation of 'title_window_icon <yes|no|toggle>' and 'title_window_icon <padding|toggle> <px>'
|
||||
*
|
||||
|
@ -420,6 +420,14 @@ Con *con_descend_tiling_focused(Con *con);
|
||||
*/
|
||||
Con *con_descend_direction(Con *con, direction_t direction);
|
||||
|
||||
/**
|
||||
* Returns whether the window decoration (title bar) should be drawn into the
|
||||
* X11 frame window of this container (default) or into the X11 frame window of
|
||||
* the parent container (for stacked/tabbed containers).
|
||||
*
|
||||
*/
|
||||
bool con_draw_decoration_into_frame(Con *con);
|
||||
|
||||
/**
|
||||
* Returns a "relative" Rect which contains the amount of pixels that need to
|
||||
* be added to the original Rect to get the final position (obviously the
|
||||
@ -451,7 +459,7 @@ int con_border_style(Con *con);
|
||||
* floating window.
|
||||
*
|
||||
*/
|
||||
void con_set_border_style(Con *con, int border_style, int border_width);
|
||||
void con_set_border_style(Con *con, border_style_t border_style, int border_width);
|
||||
|
||||
/**
|
||||
* This function changes the layout of a given container. Use it to handle
|
||||
@ -557,3 +565,9 @@ uint32_t con_rect_size_in_orientation(Con *con);
|
||||
*
|
||||
*/
|
||||
void con_merge_into(Con *old, Con *new);
|
||||
|
||||
/**
|
||||
* Returns true if the container is within any stacked/tabbed split container.
|
||||
*
|
||||
*/
|
||||
bool con_inside_stacked_or_tabbed(Con *con);
|
||||
|
@ -43,11 +43,15 @@ CFGFUN(include, const char *pattern);
|
||||
CFGFUN(font, const char *font);
|
||||
CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command);
|
||||
CFGFUN(for_window, const char *command);
|
||||
CFGFUN(gaps, const char *workspace, const char *type, const long value);
|
||||
CFGFUN(smart_borders, const char *enable);
|
||||
CFGFUN(smart_gaps, const char *enable);
|
||||
CFGFUN(floating_minimum_size, const long width, const long height);
|
||||
CFGFUN(floating_maximum_size, const long width, const long height);
|
||||
CFGFUN(default_orientation, const char *orientation);
|
||||
CFGFUN(workspace_layout, const char *layout);
|
||||
CFGFUN(workspace_back_and_forth, const char *value);
|
||||
CFGFUN(empty_workspaces, const char *value);
|
||||
CFGFUN(focus_follows_mouse, const char *value);
|
||||
CFGFUN(mouse_warping, const char *value);
|
||||
CFGFUN(focus_wrapping, const char *value);
|
||||
@ -65,6 +69,7 @@ CFGFUN(assign, const char *workspace, bool is_number);
|
||||
CFGFUN(no_focus);
|
||||
CFGFUN(ipc_socket, const char *path);
|
||||
CFGFUN(ipc_kill_timeout, const long timeout_ms);
|
||||
CFGFUN(tiling_drag, const char *value);
|
||||
CFGFUN(restart_state, const char *path);
|
||||
CFGFUN(popup_during_fullscreen, const char *value);
|
||||
CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border);
|
||||
@ -84,6 +89,11 @@ CFGFUN(bar_hidden_state, const char *hidden_state);
|
||||
CFGFUN(bar_id, const char *bar_id);
|
||||
CFGFUN(bar_output, const char *output);
|
||||
CFGFUN(bar_verbose, const char *verbose);
|
||||
CFGFUN(bar_height, const long height);
|
||||
CFGFUN(bar_padding_one, const long all);
|
||||
CFGFUN(bar_padding_two, const long top_and_bottom, const long right_and_left);
|
||||
CFGFUN(bar_padding_three, const long top, const long right_and_left, const long bottom);
|
||||
CFGFUN(bar_padding_four, const long top, const long right, const long bottom, const long left);
|
||||
CFGFUN(bar_modifier, const char *modifiers);
|
||||
CFGFUN(bar_wheel_up_cmd, const char *command);
|
||||
CFGFUN(bar_wheel_down_cmd, const char *command);
|
||||
@ -96,6 +106,7 @@ CFGFUN(bar_tray_output, const char *output);
|
||||
CFGFUN(bar_tray_padding, const long spacing_px);
|
||||
CFGFUN(bar_color_single, const char *colorclass, const char *color);
|
||||
CFGFUN(bar_status_command, const char *command);
|
||||
CFGFUN(bar_workspace_command, const char *command);
|
||||
CFGFUN(bar_binding_mode_indicator, const char *value);
|
||||
CFGFUN(bar_workspace_buttons, const char *value);
|
||||
CFGFUN(bar_workspace_min_width, const long width);
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include "queue.h"
|
||||
#include "i3.h"
|
||||
#include "tiling_drag.h"
|
||||
|
||||
typedef struct IncludedFile IncludedFile;
|
||||
typedef struct Config Config;
|
||||
@ -118,6 +119,9 @@ struct Config {
|
||||
/** Default orientation for new containers */
|
||||
int default_orientation;
|
||||
|
||||
/** Init empty workspaces on startup */
|
||||
bool empty_workspaces;
|
||||
|
||||
/** By default, focus follows mouse. If the user explicitly wants to
|
||||
* turn this off (and instead rely only on the keyboard for changing
|
||||
* focus), we allow them to do this with this relatively special option.
|
||||
@ -265,6 +269,17 @@ struct Config {
|
||||
|
||||
/* The number of currently parsed barconfigs */
|
||||
int number_barconfigs;
|
||||
|
||||
tiling_drag_t tiling_drag;
|
||||
|
||||
/* Gap sizes */
|
||||
gaps_t gaps;
|
||||
|
||||
/* Should single containers on a workspace receive a border? */
|
||||
smart_borders_t smart_borders;
|
||||
|
||||
/* Disable gaps if there is only one container on the workspace */
|
||||
smart_gaps_t smart_gaps;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -323,6 +338,10 @@ struct Barconfig {
|
||||
* Will be passed to the shell. */
|
||||
char *status_command;
|
||||
|
||||
/** Command that should be run to get the workspace buttons. Will be passed
|
||||
* to the shell. */
|
||||
char *workspace_command;
|
||||
|
||||
/** Font specification for all text rendered on the bar. */
|
||||
char *font;
|
||||
|
||||
@ -352,6 +371,11 @@ struct Barconfig {
|
||||
/** Enable verbose mode? Useful for debugging purposes. */
|
||||
bool verbose;
|
||||
|
||||
/** Defines the height of the bar in pixels. */
|
||||
uint32_t bar_height;
|
||||
|
||||
struct Rect padding;
|
||||
|
||||
struct bar_colors {
|
||||
char *background;
|
||||
char *statusline;
|
||||
|
@ -47,6 +47,7 @@ typedef struct Con Con;
|
||||
typedef struct Match Match;
|
||||
typedef struct Assignment Assignment;
|
||||
typedef struct Window i3Window;
|
||||
typedef struct gaps_t gaps_t;
|
||||
typedef struct mark_t mark_t;
|
||||
|
||||
/******************************************************************************
|
||||
@ -61,9 +62,11 @@ typedef enum { NO_ORIENTATION = 0,
|
||||
VERT } orientation_t;
|
||||
typedef enum { BEFORE,
|
||||
AFTER } position_t;
|
||||
typedef enum { BS_NORMAL = 0,
|
||||
BS_NONE = 1,
|
||||
BS_PIXEL = 2 } border_style_t;
|
||||
typedef enum {
|
||||
BS_NONE = 0,
|
||||
BS_PIXEL = 1,
|
||||
BS_NORMAL = 2,
|
||||
} border_style_t;
|
||||
|
||||
/** parameter to specify whether tree_close_internal() and x_window_kill() should kill
|
||||
* only this specific window or the whole X11 client */
|
||||
@ -78,11 +81,20 @@ typedef enum { ADJ_NONE = 0,
|
||||
ADJ_UPPER_SCREEN_EDGE = (1 << 2),
|
||||
ADJ_LOWER_SCREEN_EDGE = (1 << 4) } adjacent_t;
|
||||
|
||||
typedef enum { SMART_BORDERS_OFF,
|
||||
SMART_BORDERS_ON,
|
||||
SMART_BORDERS_NO_GAPS } smart_borders_t;
|
||||
|
||||
typedef enum { SMART_GAPS_OFF,
|
||||
SMART_GAPS_ON,
|
||||
SMART_GAPS_INVERSE_OUTER } smart_gaps_t;
|
||||
|
||||
typedef enum { HEBM_NONE = ADJ_NONE,
|
||||
HEBM_VERTICAL = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE,
|
||||
HEBM_HORIZONTAL = ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE,
|
||||
HEBM_BOTH = HEBM_VERTICAL | HEBM_HORIZONTAL,
|
||||
HEBM_SMART = (1 << 5) } hide_edge_borders_mode_t;
|
||||
HEBM_SMART = (1 << 5),
|
||||
HEBM_SMART_NO_GAPS = (1 << 6) } hide_edge_borders_mode_t;
|
||||
|
||||
typedef enum { MM_REPLACE,
|
||||
MM_ADD } mark_mode_t;
|
||||
@ -135,6 +147,25 @@ typedef enum {
|
||||
POINTER_WARPING_NONE = 1
|
||||
} warping_t;
|
||||
|
||||
struct gaps_t {
|
||||
int inner;
|
||||
int top;
|
||||
int right;
|
||||
int bottom;
|
||||
int left;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
GAPS_INNER = (1 << 0),
|
||||
GAPS_TOP = (1 << 1),
|
||||
GAPS_RIGHT = (1 << 2),
|
||||
GAPS_BOTTOM = (1 << 3),
|
||||
GAPS_LEFT = (1 << 4),
|
||||
GAPS_VERTICAL = (GAPS_TOP | GAPS_BOTTOM),
|
||||
GAPS_HORIZONTAL = (GAPS_RIGHT | GAPS_LEFT),
|
||||
GAPS_OUTER = (GAPS_VERTICAL | GAPS_HORIZONTAL),
|
||||
} gaps_mask_t;
|
||||
|
||||
/**
|
||||
* Focus wrapping modes.
|
||||
*/
|
||||
@ -202,12 +233,14 @@ struct deco_render_params {
|
||||
};
|
||||
|
||||
/**
|
||||
* Stores which workspace (by name or number) goes to which output.
|
||||
* Stores which workspace (by name or number) goes to which output and its gaps config.
|
||||
*
|
||||
*/
|
||||
struct Workspace_Assignment {
|
||||
char *name;
|
||||
char *output;
|
||||
gaps_t gaps;
|
||||
gaps_mask_t gaps_mask;
|
||||
|
||||
TAILQ_ENTRY(Workspace_Assignment) ws_assignments;
|
||||
};
|
||||
@ -643,6 +676,9 @@ struct Con {
|
||||
* workspace is not a named workspace (for named workspaces, num == -1) */
|
||||
int num;
|
||||
|
||||
/** Only applicable for containers of type CT_WORKSPACE. */
|
||||
gaps_t gaps;
|
||||
|
||||
struct Con *parent;
|
||||
|
||||
/* The position and size for this con. These coordinates are absolute. Note
|
||||
@ -721,7 +757,16 @@ struct Con {
|
||||
* layout in workspace_layout and creates a new split container with that
|
||||
* layout whenever a new container is attached to the workspace. */
|
||||
layout_t layout, last_split_layout, workspace_layout;
|
||||
|
||||
border_style_t border_style;
|
||||
/* When the border style of a con changes because of motif hints, we don't
|
||||
* want to set more decoration that the user wants. The user's preference is determined by these:
|
||||
* 1. For new tiling windows, as set by `default_border`
|
||||
* 2. For new floating windows, as set by `default_floating_border`
|
||||
* 3. For all windows that the user runs the `border` command, whatever is
|
||||
* the result of that command for that window. */
|
||||
border_style_t max_user_border_style;
|
||||
|
||||
/** floating? (= not in tiling layout) This cannot be simply a bool
|
||||
* because we want to keep track of whether the status was set by the
|
||||
* application (by setting _NET_WM_WINDOW_TYPE appropriately) or by the
|
||||
|
41
include/gaps.h
Normal file
41
include/gaps.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "data.h"
|
||||
|
||||
/**
|
||||
* Calculates the effective gap sizes for a container.
|
||||
*/
|
||||
gaps_t calculate_effective_gaps(Con *con);
|
||||
|
||||
/*
|
||||
* Decides whether the container should be inset.
|
||||
*/
|
||||
bool gaps_should_inset_con(Con *con, int children);
|
||||
|
||||
/*
|
||||
* Returns whether the given container has an adjacent container in the
|
||||
* specified direction. In other words, this returns true if and only if
|
||||
* the container is not touching the edge of the screen in that direction.
|
||||
*/
|
||||
bool gaps_has_adjacent_container(Con *con, direction_t direction);
|
||||
|
||||
/**
|
||||
* Returns the configured gaps for this workspace based on the workspace name,
|
||||
* number, and configured workspace gap assignments.
|
||||
*/
|
||||
gaps_t gaps_for_workspace(Con *ws);
|
||||
|
||||
/**
|
||||
* Re-applies all workspace gap assignments to existing workspaces after
|
||||
* reloading the configuration file.
|
||||
*
|
||||
*/
|
||||
void gaps_reapply_workspace_assignments(void);
|
@ -133,7 +133,7 @@ void ipc_send_barconfig_update_event(Barconfig *barconfig);
|
||||
/**
|
||||
* For the binding events, we send the serialized binding struct.
|
||||
*/
|
||||
void ipc_send_binding_event(const char *event_type, Binding *bind);
|
||||
void ipc_send_binding_event(const char *event_type, Binding *bind, const char *modename);
|
||||
|
||||
/**
|
||||
* Set the maximum duration that we allow for a connection with an unwriteable
|
||||
|
@ -9,8 +9,27 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "all.h"
|
||||
|
||||
/**
|
||||
* Tiling drag initiation modes.
|
||||
*/
|
||||
typedef enum {
|
||||
TILING_DRAG_OFF = 0,
|
||||
TILING_DRAG_MODIFIER = 1,
|
||||
TILING_DRAG_TITLEBAR = 2,
|
||||
TILING_DRAG_MODIFIER_OR_TITLEBAR = 3
|
||||
} tiling_drag_t;
|
||||
|
||||
/**
|
||||
* Returns whether there currently are any drop targets.
|
||||
* Used to only initiate a drag when there is something to drop onto.
|
||||
*
|
||||
*/
|
||||
bool has_drop_targets(void);
|
||||
|
||||
/**
|
||||
* Initiates a mouse drag operation on a tiled window.
|
||||
*
|
||||
*/
|
||||
void tiling_drag(Con *con, xcb_button_press_event_t *event);
|
||||
void tiling_drag(Con *con, xcb_button_press_event_t *event, bool use_threshold);
|
||||
|
@ -94,7 +94,7 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
|
||||
* it is still in use by popular widget toolkits such as GTK+ and Java AWT.
|
||||
*
|
||||
*/
|
||||
void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
|
||||
bool window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style);
|
||||
|
||||
/**
|
||||
* Updates the WM_CLIENT_MACHINE
|
||||
|
@ -186,3 +186,9 @@ Con *workspace_encapsulate(Con *ws);
|
||||
*
|
||||
*/
|
||||
void workspace_move_to_output(Con *ws, Output *output);
|
||||
|
||||
/*
|
||||
* Create all workspaces specified in the config bindings.
|
||||
*
|
||||
*/
|
||||
void workspace_init(void);
|
||||
|
@ -20,13 +20,13 @@ extern xcb_visualtype_t *visual_type;
|
||||
/* Forward declarations */
|
||||
static void draw_util_set_source_color(surface_t *surface, color_t color);
|
||||
|
||||
#define RETURN_UNLESS_SURFACE_INITIALIZED(surface) \
|
||||
do { \
|
||||
if ((surface)->id == XCB_NONE) { \
|
||||
ELOG("Surface %p is not initialized, skipping drawing.\n", surface); \
|
||||
return; \
|
||||
} \
|
||||
} while (0)
|
||||
static bool surface_initialized(surface_t *surface) {
|
||||
if (surface->id == XCB_NONE) {
|
||||
ELOG("Surface %p is not initialized, skipping drawing.\n", surface);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the surface to represent the given drawable.
|
||||
@ -131,7 +131,9 @@ color_t draw_util_hex_to_color(const char *color) {
|
||||
*
|
||||
*/
|
||||
static void draw_util_set_source_color(surface_t *surface, color_t color) {
|
||||
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
|
||||
if (!surface_initialized(surface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cairo_set_source_rgba(surface->cr, color.red, color.green, color.blue, color.alpha);
|
||||
}
|
||||
@ -143,7 +145,9 @@ static void draw_util_set_source_color(surface_t *surface, color_t 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) {
|
||||
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
|
||||
if (!surface_initialized(surface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Flush any changes before we draw the text as this might use XCB directly. */
|
||||
CAIRO_SURFACE_FLUSH(surface->surface);
|
||||
@ -162,7 +166,9 @@ void draw_util_text(i3String *text, surface_t *surface, color_t fg_color, color_
|
||||
*
|
||||
*/
|
||||
void draw_util_image(cairo_surface_t *image, surface_t *surface, int x, int y, int width, int height) {
|
||||
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
|
||||
if (!surface_initialized(surface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cairo_save(surface->cr);
|
||||
|
||||
@ -186,7 +192,9 @@ void draw_util_image(cairo_surface_t *image, surface_t *surface, int x, int y, i
|
||||
*
|
||||
*/
|
||||
void draw_util_rectangle(surface_t *surface, color_t color, double x, double y, double w, double h) {
|
||||
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
|
||||
if (!surface_initialized(surface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cairo_save(surface->cr);
|
||||
|
||||
@ -211,7 +219,9 @@ void draw_util_rectangle(surface_t *surface, color_t color, double x, double y,
|
||||
*
|
||||
*/
|
||||
void draw_util_clear_surface(surface_t *surface, color_t color) {
|
||||
RETURN_UNLESS_SURFACE_INITIALIZED(surface);
|
||||
if (!surface_initialized(surface)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cairo_save(surface->cr);
|
||||
|
||||
@ -236,8 +246,10 @@ 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) {
|
||||
RETURN_UNLESS_SURFACE_INITIALIZED(src);
|
||||
RETURN_UNLESS_SURFACE_INITIALIZED(dest);
|
||||
if (!surface_initialized(src) ||
|
||||
!surface_initialized(dest)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cairo_save(dest->cr);
|
||||
|
||||
|
@ -21,6 +21,7 @@ static xcb_visualtype_t *root_visual_type;
|
||||
static double pango_font_red;
|
||||
static double pango_font_green;
|
||||
static double pango_font_blue;
|
||||
static double pango_font_alpha;
|
||||
|
||||
static PangoLayout *create_layout_with_dpi(cairo_t *cr) {
|
||||
PangoLayout *layout;
|
||||
@ -102,7 +103,7 @@ static void draw_text_pango(const char *text, size_t text_len,
|
||||
|
||||
/* Do the drawing */
|
||||
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
|
||||
cairo_set_source_rgb(cr, pango_font_red, pango_font_green, pango_font_blue);
|
||||
cairo_set_source_rgba(cr, pango_font_red, pango_font_green, pango_font_blue, pango_font_alpha);
|
||||
pango_cairo_update_layout(cr, layout);
|
||||
pango_layout_get_pixel_size(layout, NULL, &height);
|
||||
/* Center the piece of text vertically. */
|
||||
@ -303,6 +304,7 @@ void set_font_colors(xcb_gcontext_t gc, color_t foreground, color_t background)
|
||||
pango_font_red = foreground.red;
|
||||
pango_font_green = foreground.green;
|
||||
pango_font_blue = foreground.blue;
|
||||
pango_font_alpha = foreground.alpha;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -30,17 +30,27 @@ SLIST_HEAD(colorpixel_head, Colorpixel) colorpixels;
|
||||
*
|
||||
*/
|
||||
uint32_t get_colorpixel(const char *hex) {
|
||||
char strgroups[3][3] = {
|
||||
char alpha[2];
|
||||
if (strlen(hex) == strlen("#rrggbbaa")) {
|
||||
alpha[0] = hex[7];
|
||||
alpha[1] = hex[8];
|
||||
} else {
|
||||
alpha[0] = alpha[1] = 'F';
|
||||
}
|
||||
|
||||
char strgroups[4][3] = {
|
||||
{hex[1], hex[2], '\0'},
|
||||
{hex[3], hex[4], '\0'},
|
||||
{hex[5], hex[6], '\0'}};
|
||||
{hex[5], hex[6], '\0'},
|
||||
{alpha[0], alpha[1], '\0'}};
|
||||
uint8_t r = strtol(strgroups[0], NULL, 16);
|
||||
uint8_t g = strtol(strgroups[1], NULL, 16);
|
||||
uint8_t b = strtol(strgroups[2], NULL, 16);
|
||||
uint8_t a = strtol(strgroups[3], NULL, 16);
|
||||
|
||||
/* Shortcut: if our screen is true color, no need to do a roundtrip to X11 */
|
||||
if (root_screen == NULL || root_screen->root_depth == 24 || root_screen->root_depth == 32) {
|
||||
return (0xFFUL << 24) | (r << 16 | g << 8 | b);
|
||||
return (a << 24) | (r << 16 | g << 8 | b);
|
||||
}
|
||||
|
||||
/* Lookup this colorpixel in the cache */
|
||||
|
@ -35,7 +35,7 @@ used.
|
||||
|
||||
== DESCRIPTION
|
||||
|
||||
i3-config-wizard is started by i3 in its default config, unless ~/.i3/config
|
||||
i3-config-wizard is started by i3 in its default config, unless \~/.i3/config
|
||||
exists. i3-config-wizard creates a keysym based i3 config file (based on
|
||||
/etc/i3/config.keycodes) in ~/.i3/config.
|
||||
|
||||
|
@ -39,7 +39,7 @@ Display the <prompt> string in front of user input text field.
|
||||
The prompt string is not included in the user input/command.
|
||||
|
||||
-f <font>::
|
||||
Use the specified X11 core font (use +xfontsel+ to chose a font).
|
||||
Use the specified X11 core font (use +xfontsel+ to choose a font).
|
||||
|
||||
-v::
|
||||
Show version and exit.
|
||||
|
@ -46,9 +46,12 @@ Be verbose.
|
||||
workspace switching buttons and a statusline generated by i3status(1) or
|
||||
similar. It is automatically invoked (and configured through) i3.
|
||||
|
||||
i3bar supports colors via a JSON protocol starting from v4.2, see
|
||||
i3bar supports using a JSON protocol for setting the status line, see
|
||||
https://i3wm.org/docs/i3bar-protocol.html
|
||||
|
||||
Since i3 4.23, i3bar supports another JSON protocol for setting workspace
|
||||
buttons. See https://i3wm.org/docs/i3bar-workspace-protocol.html.
|
||||
|
||||
== ENVIRONMENT
|
||||
|
||||
=== I3SOCK
|
||||
|
14
meson.build
14
meson.build
@ -6,17 +6,17 @@
|
||||
project(
|
||||
'i3',
|
||||
'c',
|
||||
version: '4.20.1',
|
||||
version: '4.22',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
'warning_level=1', # enable all warnings (-Wall)
|
||||
# TODO(https://github.com/i3/i3/issues/4087): switch to
|
||||
# 'buildtype=debugoptimized',
|
||||
],
|
||||
# Ubuntu 18.04 (supported until 2023) has meson 0.45.
|
||||
# Ubuntu 20.04 (supported until 2025) has meson 0.53.
|
||||
# We can revisit our minimum supported meson version
|
||||
# if it turns out to be too hard to maintain.
|
||||
meson_version: '>=0.45.0',
|
||||
meson_version: '>=0.47.0',
|
||||
)
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
@ -86,6 +86,7 @@ if get_option('docs')
|
||||
'docs/wsbar',
|
||||
'docs/testsuite',
|
||||
'docs/i3bar-protocol',
|
||||
'docs/i3bar-workspace-protocol',
|
||||
'docs/layout-saving',
|
||||
]
|
||||
foreach m : doc_toc_inputs
|
||||
@ -125,7 +126,7 @@ if get_option('docs')
|
||||
endforeach
|
||||
|
||||
else
|
||||
if run_command('[', '-f', 'docs/hacking-howto.html', ']').returncode() == 0
|
||||
if run_command('[', '-f', 'docs/hacking-howto.html', ']', check: false).returncode() == 0
|
||||
install_data(
|
||||
[
|
||||
'docs/hacking-howto.html',
|
||||
@ -135,6 +136,7 @@ else
|
||||
'docs/wsbar.html',
|
||||
'docs/testsuite.html',
|
||||
'docs/i3bar-protocol.html',
|
||||
'docs/i3bar-workspace-protocol.html',
|
||||
'docs/layout-saving.html',
|
||||
'docs/debugging.html',
|
||||
],
|
||||
@ -166,6 +168,7 @@ install_data(
|
||||
'docs/refcard_style.css',
|
||||
'docs/logo-30.png',
|
||||
'docs/layout-saving-1.png',
|
||||
'docs/gaps1920.png',
|
||||
],
|
||||
install_dir: docdir,
|
||||
)
|
||||
@ -268,7 +271,7 @@ if get_option('mans')
|
||||
endforeach
|
||||
|
||||
else
|
||||
if run_command('[', '-f', 'man/i3.1', ']').returncode() == 0
|
||||
if run_command('[', '-f', 'man/i3.1', ']', check: false).returncode() == 0
|
||||
install_data(
|
||||
[
|
||||
'man/i3.1',
|
||||
@ -389,6 +392,7 @@ i3srcs = [
|
||||
'src/ewmh.c',
|
||||
'src/fake_outputs.c',
|
||||
'src/floating.c',
|
||||
'src/gaps.c',
|
||||
'src/handlers.c',
|
||||
'src/ipc.c',
|
||||
'src/key_press.c',
|
||||
|
@ -43,6 +43,7 @@ state INITIAL:
|
||||
'title_window_icon' -> TITLE_WINDOW_ICON
|
||||
'mode' -> MODE
|
||||
'bar' -> BAR
|
||||
'gaps' -> GAPS
|
||||
|
||||
state CRITERIA:
|
||||
ctype = 'class' -> CRITERION
|
||||
@ -56,6 +57,8 @@ state CRITERIA:
|
||||
ctype = 'urgent' -> CRITERION
|
||||
ctype = 'workspace' -> CRITERION
|
||||
ctype = 'machine' -> CRITERION
|
||||
ctype = 'floating_from' -> CRITERION_FROM
|
||||
ctype = 'tiling_from' -> CRITERION_FROM
|
||||
ctype = 'tiling', 'floating', 'all'
|
||||
-> call cmd_criteria_add($ctype, NULL); CRITERIA
|
||||
']' -> call cmd_criteria_match_windows(); INITIAL
|
||||
@ -63,6 +66,22 @@ state CRITERIA:
|
||||
state CRITERION:
|
||||
'=' -> CRITERION_STR
|
||||
|
||||
state CRITERION_FROM:
|
||||
'=' -> CRITERION_FROM_STR_START
|
||||
|
||||
state CRITERION_FROM_STR_START:
|
||||
'"' -> CRITERION_FROM_STR
|
||||
kind = 'auto', 'user'
|
||||
-> call cmd_criteria_add($ctype, $kind); CRITERIA
|
||||
|
||||
state CRITERION_FROM_STR:
|
||||
kind = 'auto', 'user'
|
||||
-> CRITERION_FROM_STR_END
|
||||
|
||||
state CRITERION_FROM_STR_END:
|
||||
'"'
|
||||
-> call cmd_criteria_add($ctype, $kind); CRITERIA
|
||||
|
||||
state CRITERION_STR:
|
||||
cvalue = word
|
||||
-> call cmd_criteria_add($ctype, $cvalue); CRITERIA
|
||||
@ -101,6 +120,29 @@ state BORDER_WIDTH:
|
||||
border_width = number
|
||||
-> call cmd_border($border_style, &border_width)
|
||||
|
||||
# gaps inner|outer|horizontal|vertical|top|right|bottom|left [current] [set|plus|minus|toggle] <px>
|
||||
state GAPS:
|
||||
type = 'inner', 'outer', 'horizontal', 'vertical', 'top', 'right', 'bottom', 'left'
|
||||
-> GAPS_WITH_TYPE
|
||||
|
||||
state GAPS_WITH_TYPE:
|
||||
scope = 'current', 'all'
|
||||
-> GAPS_WITH_SCOPE
|
||||
|
||||
state GAPS_WITH_SCOPE:
|
||||
mode = 'plus', 'minus', 'set', 'toggle'
|
||||
-> GAPS_WITH_MODE
|
||||
|
||||
state GAPS_WITH_MODE:
|
||||
value = word
|
||||
-> GAPS_END
|
||||
|
||||
state GAPS_END:
|
||||
'px'
|
||||
->
|
||||
end
|
||||
-> call cmd_gaps($type, $scope, $mode, $value)
|
||||
|
||||
# layout default|stacked|stacking|tabbed|splitv|splith
|
||||
# layout toggle [split|all]
|
||||
state LAYOUT:
|
||||
@ -144,6 +186,7 @@ state WORKSPACE_NUMBER:
|
||||
# focus output <output>
|
||||
# focus tiling|floating|mode_toggle
|
||||
# focus parent|child
|
||||
# focus workspace
|
||||
# focus
|
||||
state FOCUS:
|
||||
direction = 'left', 'right', 'up', 'down'
|
||||
@ -156,8 +199,10 @@ state FOCUS:
|
||||
-> call cmd_focus_window_mode($window_mode)
|
||||
level = 'parent', 'child'
|
||||
-> call cmd_focus_level($level)
|
||||
workspace = 'workspace'
|
||||
-> call cmd_focus(1)
|
||||
end
|
||||
-> call cmd_focus()
|
||||
-> call cmd_focus(0)
|
||||
|
||||
state FOCUS_AUTO:
|
||||
'sibling'
|
||||
|
@ -25,6 +25,9 @@ state INITIAL:
|
||||
'bar' -> BARBRACE
|
||||
'font' -> FONT
|
||||
'mode' -> MODENAME
|
||||
'gaps' -> GAPS
|
||||
'smart_borders' -> SMART_BORDERS
|
||||
'smart_gaps' -> SMART_GAPS
|
||||
'floating_minimum_size' -> FLOATING_MINIMUM_SIZE_WIDTH
|
||||
'floating_maximum_size' -> FLOATING_MAXIMUM_SIZE_WIDTH
|
||||
'floating_modifier' -> FLOATING_MODIFIER
|
||||
@ -53,6 +56,7 @@ state INITIAL:
|
||||
'ipc_kill_timeout' -> IPC_KILL_TIMEOUT
|
||||
'restart_state' -> RESTART_STATE
|
||||
'popup_during_fullscreen' -> POPUP_DURING_FULLSCREEN
|
||||
'tiling_drag' -> TILING_DRAG
|
||||
exectype = 'exec_always', 'exec' -> EXEC
|
||||
colorclass = 'client.background'
|
||||
-> COLOR_SINGLE
|
||||
@ -64,6 +68,38 @@ state IGNORE_LINE:
|
||||
line
|
||||
-> INITIAL
|
||||
|
||||
# gaps inner|outer|horizontal|vertical|top|right|bottom|left <gap_size>[px]
|
||||
state GAPS:
|
||||
scope = 'inner', 'outer', 'horizontal', 'vertical', 'top', 'right', 'bottom', 'left'
|
||||
-> GAPS_WITH_SCOPE
|
||||
|
||||
state GAPS_WITH_SCOPE:
|
||||
value = number
|
||||
-> GAPS_END
|
||||
|
||||
state GAPS_END:
|
||||
'px'
|
||||
->
|
||||
end
|
||||
-> call cfg_gaps($workspace, $scope, &value)
|
||||
|
||||
# smart_borders true|false
|
||||
# smart_borders no_gaps
|
||||
state SMART_BORDERS:
|
||||
enabled = '1', 'yes', 'true', 'on', 'enable', 'active'
|
||||
-> call cfg_smart_borders($enabled)
|
||||
enabled = 'no_gaps'
|
||||
-> call cfg_smart_borders($enabled)
|
||||
|
||||
# smart_gaps on|off|inverse_outer
|
||||
state SMART_GAPS:
|
||||
enabled = '1', 'yes', 'true', 'on', 'enable', 'active'
|
||||
-> call cfg_smart_gaps($enabled)
|
||||
enabled = '0', 'no', 'false', 'off', 'disable', 'inactive'
|
||||
-> call cfg_smart_gaps($enabled)
|
||||
enabled = 'inverse_outer'
|
||||
-> call cfg_smart_gaps($enabled)
|
||||
|
||||
# include <pattern>
|
||||
state INCLUDE:
|
||||
pattern = string
|
||||
@ -134,10 +170,10 @@ state DEFAULT_BORDER_PIXELS_PX:
|
||||
end
|
||||
-> call cfg_default_border($windowtype, $border, &width)
|
||||
|
||||
# hide_edge_borders <none|vertical|horizontal|both|smart>
|
||||
# hide_edge_borders <none|vertical|horizontal|both|smart|smart_no_gaps>
|
||||
# also hide_edge_borders <bool> for compatibility
|
||||
state HIDE_EDGE_BORDERS:
|
||||
hide_borders = 'none', 'vertical', 'horizontal', 'both', 'smart'
|
||||
hide_borders = 'none', 'vertical', 'horizontal', 'both', 'smart_no_gaps', 'smart'
|
||||
-> call cfg_hide_edge_borders($hide_borders)
|
||||
hide_borders = '1', 'yes', 'true', 'on', 'enable', 'active'
|
||||
-> call cfg_hide_edge_borders($hide_borders)
|
||||
@ -296,13 +332,16 @@ state FOCUS_ON_WINDOW_ACTIVATION:
|
||||
-> call cfg_focus_on_window_activation($mode)
|
||||
|
||||
# workspace <workspace> output <output>
|
||||
# workspace <workspace> gaps inner|outer <px>
|
||||
state WORKSPACE:
|
||||
workspace = word
|
||||
-> WORKSPACE_OUTPUT
|
||||
-> WORKSPACE_COMMAND
|
||||
|
||||
state WORKSPACE_OUTPUT:
|
||||
state WORKSPACE_COMMAND:
|
||||
'output'
|
||||
-> WORKSPACE_OUTPUT_WORD
|
||||
'gaps'
|
||||
-> GAPS
|
||||
|
||||
state WORKSPACE_OUTPUT_WORD:
|
||||
output = word
|
||||
@ -330,6 +369,18 @@ state POPUP_DURING_FULLSCREEN:
|
||||
value = 'ignore', 'leave_fullscreen', 'smart'
|
||||
-> call cfg_popup_during_fullscreen($value)
|
||||
|
||||
state TILING_DRAG_MODE:
|
||||
value = 'modifier', 'titlebar'
|
||||
->
|
||||
end
|
||||
-> call cfg_tiling_drag($value)
|
||||
|
||||
state TILING_DRAG:
|
||||
off = '0', 'no', 'false', 'off', 'disable', 'inactive'
|
||||
-> call cfg_tiling_drag($off)
|
||||
value = 'modifier', 'titlebar'
|
||||
-> TILING_DRAG_MODE
|
||||
|
||||
# client.background <hexcolor>
|
||||
state COLOR_SINGLE:
|
||||
color = word
|
||||
@ -477,6 +528,7 @@ state BAR:
|
||||
'set' -> BAR_IGNORE_LINE
|
||||
'i3bar_command' -> BAR_BAR_COMMAND
|
||||
'status_command' -> BAR_STATUS_COMMAND
|
||||
'workspace_command' -> BAR_WORKSPACE_COMMAND
|
||||
'socket_path' -> BAR_SOCKET_PATH
|
||||
'mode' -> BAR_MODE
|
||||
'hidden_state' -> BAR_HIDDEN_STATE
|
||||
@ -497,6 +549,8 @@ state BAR:
|
||||
'strip_workspace_numbers' -> BAR_STRIP_WORKSPACE_NUMBERS
|
||||
'strip_workspace_name' -> BAR_STRIP_WORKSPACE_NAME
|
||||
'verbose' -> BAR_VERBOSE
|
||||
'height' -> BAR_HEIGHT
|
||||
'padding' -> BAR_PADDING
|
||||
'colors' -> BAR_COLORS_BRACE
|
||||
'}'
|
||||
-> call cfg_bar_finish(); INITIAL
|
||||
@ -514,6 +568,10 @@ state BAR_STATUS_COMMAND:
|
||||
command = string
|
||||
-> call cfg_bar_status_command($command); BAR
|
||||
|
||||
state BAR_WORKSPACE_COMMAND:
|
||||
command = string
|
||||
-> call cfg_bar_workspace_command($command); BAR
|
||||
|
||||
state BAR_SOCKET_PATH:
|
||||
path = string
|
||||
-> call cfg_bar_socket_path($path); BAR
|
||||
@ -565,7 +623,7 @@ state BAR_POSITION:
|
||||
-> call cfg_bar_position($position); BAR
|
||||
|
||||
state BAR_OUTPUT:
|
||||
output = string
|
||||
output = word
|
||||
-> call cfg_bar_output($output); BAR
|
||||
|
||||
state BAR_TRAY_OUTPUT:
|
||||
@ -620,6 +678,44 @@ state BAR_VERBOSE:
|
||||
value = word
|
||||
-> call cfg_bar_verbose($value); BAR
|
||||
|
||||
state BAR_HEIGHT:
|
||||
value = number
|
||||
-> call cfg_bar_height(&value); BAR
|
||||
|
||||
state BAR_PADDING:
|
||||
top_or_all = number
|
||||
-> BAR_PADDING_TOP
|
||||
|
||||
state BAR_PADDING_TOP:
|
||||
'px'
|
||||
->
|
||||
right_or_right_and_left = number
|
||||
-> BAR_PADDING_RIGHT
|
||||
end
|
||||
-> call cfg_bar_padding_one(&top_or_all); BAR
|
||||
|
||||
state BAR_PADDING_RIGHT:
|
||||
'px'
|
||||
->
|
||||
bottom = number
|
||||
-> BAR_PADDING_BOTTOM
|
||||
end
|
||||
-> call cfg_bar_padding_two(&top_or_all, &right_or_right_and_left); BAR
|
||||
|
||||
state BAR_PADDING_BOTTOM:
|
||||
'px'
|
||||
->
|
||||
left = number
|
||||
-> BAR_PADDING_LEFT
|
||||
end
|
||||
-> call cfg_bar_padding_three(&top_or_all, &right_or_right_and_left, &bottom); BAR
|
||||
|
||||
state BAR_PADDING_LEFT:
|
||||
'px'
|
||||
->
|
||||
end
|
||||
-> call cfg_bar_padding_four(&top_or_all, &right_or_right_and_left, &bottom, &left); BAR
|
||||
|
||||
state BAR_COLORS_BRACE:
|
||||
end
|
||||
->
|
||||
|
1
release-notes/bugfixes/1-i3bar-nonprimary
Normal file
1
release-notes/bugfixes/1-i3bar-nonprimary
Normal file
@ -0,0 +1 @@
|
||||
fix regression with i3bar's output nonprimary
|
@ -1 +0,0 @@
|
||||
Do not replace existing IPC socket on start
|
@ -1 +0,0 @@
|
||||
fix focus when moving container between outputs with mouse warp and focus_follows_mouse
|
@ -1 +0,0 @@
|
||||
Fix endless loop with transient_for windows
|
@ -1 +0,0 @@
|
||||
fix wrong failed reply on move workspace to output
|
@ -1 +0,0 @@
|
||||
changed WM registration selection from WM_S_S<screen> to WM_S<screen>
|
@ -1 +0,0 @@
|
||||
avoid graphics artifacts when changing the layout tree by initializing surfaces to all black
|
@ -1 +0,0 @@
|
||||
Fix segfault if command in bindsym is empty
|
@ -1 +0,0 @@
|
||||
update parent split con titles when child con swaps position with another child con
|
@ -1 +0,0 @@
|
||||
Refuse to start without valid IPC socket
|
1
release-notes/changes/1-workspace_command
Normal file
1
release-notes/changes/1-workspace_command
Normal file
@ -0,0 +1 @@
|
||||
add workspace_command option in i3bar
|
@ -1 +0,0 @@
|
||||
Add client.focused_tab_title color option
|
1
release-notes/changes/2-focus-workspace
Normal file
1
release-notes/changes/2-focus-workspace
Normal file
@ -0,0 +1 @@
|
||||
add "focus workspace" command
|
@ -1 +0,0 @@
|
||||
Add support for multiple outputs in focus command
|
@ -1 +0,0 @@
|
||||
Allow moving tiling windows with the mouse
|
@ -1 +0,0 @@
|
||||
Add title_window_icon toggle
|
19
release.sh
19
release.sh
@ -3,8 +3,8 @@
|
||||
|
||||
set -eu
|
||||
|
||||
export RELEASE_VERSION="4.20"
|
||||
export PREVIOUS_VERSION="4.19.2"
|
||||
export RELEASE_VERSION="4.22"
|
||||
export PREVIOUS_VERSION="4.21.1"
|
||||
export RELEASE_BRANCH="next"
|
||||
|
||||
if [ ! -e "../i3.github.io" ]
|
||||
@ -20,6 +20,12 @@ then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git diff-files --quiet --exit-code debian/changelog
|
||||
then
|
||||
echo "Expected debian/changelog to be changed (containing the changelog for ${RELEASE_VERSION})."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -e "RELEASE-NOTES-${RELEASE_VERSION}" ]
|
||||
then
|
||||
echo "RELEASE-NOTES-${RELEASE_VERSION} not found. Here is the output from the generator:"
|
||||
@ -108,7 +114,7 @@ cp "${STARTDIR}/debian/changelog" i3/debian/changelog
|
||||
|
||||
cat > ${TMPDIR}/Dockerfile <<EOT
|
||||
FROM debian:sid
|
||||
RUN sed -i 's,^deb \(.*\),deb \1\ndeb-src \1,g' /etc/apt/sources.list
|
||||
RUN echo deb-src http://deb.debian.org/debian sid main > /etc/apt/sources.list
|
||||
RUN apt-get update && apt-get install -y dpkg-dev devscripts
|
||||
COPY i3/i3-${RELEASE_VERSION}.tar.xz /usr/src/i3-wm_${RELEASE_VERSION}.orig.tar.xz
|
||||
WORKDIR /usr/src/
|
||||
@ -158,7 +164,7 @@ git add downloads/i3-${RELEASE_VERSION}.tar.xz*
|
||||
cp ${TMPDIR}/i3/RELEASE-NOTES-${RELEASE_VERSION} downloads/RELEASE-NOTES-${RELEASE_VERSION}.txt
|
||||
git add downloads/RELEASE-NOTES-${RELEASE_VERSION}.txt
|
||||
sed -i "s,<h2>Documentation for i3 v[^<]*</h2>,<h2>Documentation for i3 v${RELEASE_VERSION}</h2>,g" docs/index.html
|
||||
sed -i "s,<span style=\"margin-left: 2em; color: #c0c0c0\">[^<]*</span>,<span style=\"margin-left: 2em; color: #c0c0c0\">${RELEASE_VERSION}</span>,g" index.html
|
||||
sed -i "s,\(span class=\"version\">\)[^<]*\(</span>\),\1${RELEASE_VERSION}\2,g" index.html
|
||||
sed -i "s,The current stable version is .*$,The current stable version is ${RELEASE_VERSION}.,g" downloads/index.html
|
||||
sed -i "s,<tbody>,<tbody>\n <tr>\n <td>${RELEASE_VERSION}</td>\n <td><a href=\"/downloads/i3-${RELEASE_VERSION}.tar.xz\">i3-${RELEASE_VERSION}.tar.xz</a></td>\n <td>$(LC_ALL=en_US.UTF-8 ls -lh ../i3/i3-${RELEASE_VERSION}.tar.xz | awk -F " " {'print $5'} | sed 's/K$/ KiB/g' | sed 's/M$/ MiB/g')</td>\n <td><a href=\"/downloads/i3-${RELEASE_VERSION}.tar.xz.asc\">signature</a></td>\n <td>$(date +'%Y-%m-%d')</td>\n <td><a href=\"/downloads/RELEASE-NOTES-${RELEASE_VERSION}.txt\">release notes</a></td>\n </tr>\n,g" downloads/index.html
|
||||
|
||||
@ -184,7 +190,7 @@ git commit -a -m "update docs for ${RELEASE_VERSION}"
|
||||
|
||||
git remote remove origin
|
||||
git remote add origin git@github.com:i3/i3.github.io.git
|
||||
git config --add remote.origin.push "+refs/heads/master:refs/heads/master"
|
||||
git config --add remote.origin.push "+refs/heads/main:refs/heads/main"
|
||||
|
||||
################################################################################
|
||||
# Section 4: prepare release announcement email
|
||||
@ -220,9 +226,6 @@ echo ""
|
||||
echo " cd ${TMPDIR}/i3.github.io"
|
||||
echo " git push"
|
||||
echo ""
|
||||
echo " cd ${TMPDIR}/debian"
|
||||
echo " dput"
|
||||
echo ""
|
||||
echo " cd ${TMPDIR}"
|
||||
echo " sendmail -t < email.txt"
|
||||
echo ""
|
||||
|
@ -843,6 +843,10 @@ CommandResult *run_binding(Binding *bind, Con *con) {
|
||||
sasprintf(&command, "[con_id=\"%p\"] %s", con, bind->command);
|
||||
|
||||
Binding *bind_cp = binding_copy(bind);
|
||||
/* The "mode" command might change the current mode, so back it up to
|
||||
* correctly produce an event later. */
|
||||
char *modename = sstrdup(current_binding_mode);
|
||||
|
||||
CommandResult *result = parse_command(command, NULL, NULL);
|
||||
free(command);
|
||||
|
||||
@ -868,7 +872,8 @@ CommandResult *run_binding(Binding *bind, Con *con) {
|
||||
free(pageraction);
|
||||
}
|
||||
|
||||
ipc_send_binding_event("run", bind_cp);
|
||||
ipc_send_binding_event("run", bind_cp, modename);
|
||||
FREE(modename);
|
||||
binding_free(bind_cp);
|
||||
|
||||
return result;
|
||||
|
85
src/click.c
85
src/click.c
@ -39,6 +39,9 @@ static bool tiling_resize_for_border(Con *con, border_t border, xcb_button_press
|
||||
case BORDER_BOTTOM:
|
||||
search_direction = D_DOWN;
|
||||
break;
|
||||
default:
|
||||
ELOG("BUG: invalid border value %d\n", border);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool res = resize_find_tiling_participants(&first, &second, search_direction, false);
|
||||
@ -141,6 +144,12 @@ static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click
|
||||
return false;
|
||||
}
|
||||
|
||||
static void allow_replay_pointer(xcb_timestamp_t time) {
|
||||
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, time);
|
||||
xcb_flush(conn);
|
||||
tree_render();
|
||||
}
|
||||
|
||||
/*
|
||||
* Being called by handle_button_press, this function calls the appropriate
|
||||
* functions for resizing/dragging.
|
||||
@ -152,8 +161,10 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
||||
DLOG("type = %d, name = %s\n", con->type, con->name);
|
||||
|
||||
/* don’t handle dockarea cons, they must not be focused */
|
||||
if (con->parent->type == CT_DOCKAREA)
|
||||
goto done;
|
||||
if (con->parent->type == CT_DOCKAREA) {
|
||||
allow_replay_pointer(event->time);
|
||||
return;
|
||||
}
|
||||
|
||||
/* if the user has bound an action to this click, it should override the
|
||||
* default behavior. */
|
||||
@ -173,7 +184,8 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
||||
|
||||
/* There is no default behavior for button release events so we are done. */
|
||||
if (event->response_type == XCB_BUTTON_RELEASE) {
|
||||
goto done;
|
||||
allow_replay_pointer(event->time);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Any click in a workspace should focus that workspace. If the
|
||||
@ -184,8 +196,10 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
||||
|
||||
if (!ws) {
|
||||
ws = TAILQ_FIRST(&(output_get_content(con_get_output(con))->focus_head));
|
||||
if (!ws)
|
||||
goto done;
|
||||
if (!ws) {
|
||||
allow_replay_pointer(event->time);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* get the floating con */
|
||||
@ -212,13 +226,19 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
||||
Con *next = get_tree_next_sibling(current, direction);
|
||||
con_activate(con_descend_focused(next ? next : current));
|
||||
|
||||
goto done;
|
||||
allow_replay_pointer(event->time);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 2: floating modifier pressed, initiate a drag */
|
||||
if (mod_pressed && event->detail == XCB_BUTTON_INDEX_1 && !floatingcon) {
|
||||
tiling_drag(con, event);
|
||||
goto done;
|
||||
if (mod_pressed && is_left_click && !floatingcon &&
|
||||
(config.tiling_drag == TILING_DRAG_MODIFIER ||
|
||||
config.tiling_drag == TILING_DRAG_MODIFIER_OR_TITLEBAR) &&
|
||||
has_drop_targets()) {
|
||||
const bool use_threshold = !mod_pressed;
|
||||
tiling_drag(con, event, use_threshold);
|
||||
allow_replay_pointer(event->time);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 3: focus this con or one of its children. */
|
||||
@ -262,8 +282,10 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
||||
is_left_or_right_click) {
|
||||
/* try tiling resize, but continue if it doesn’t work */
|
||||
DLOG("tiling resize with fallback\n");
|
||||
if (tiling_resize(con, event, dest, dest == CLICK_DECORATION && !was_focused))
|
||||
goto done;
|
||||
if (tiling_resize(con, event, dest, dest == CLICK_DECORATION && !was_focused)) {
|
||||
allow_replay_pointer(event->time);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (dest == CLICK_DECORATION && is_right_click) {
|
||||
@ -285,13 +307,20 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
||||
return;
|
||||
}
|
||||
|
||||
goto done;
|
||||
allow_replay_pointer(event->time);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 8: floating modifier pressed, initiate a drag */
|
||||
if ((mod_pressed || dest == CLICK_DECORATION) && event->detail == XCB_BUTTON_INDEX_1) {
|
||||
tiling_drag(con, event);
|
||||
goto done;
|
||||
/* 8: floating modifier pressed, or click in titlebar, initiate a drag */
|
||||
if (is_left_click &&
|
||||
((config.tiling_drag == TILING_DRAG_TITLEBAR && dest == CLICK_DECORATION) ||
|
||||
(config.tiling_drag == TILING_DRAG_MODIFIER_OR_TITLEBAR &&
|
||||
(mod_pressed || dest == CLICK_DECORATION))) &&
|
||||
has_drop_targets()) {
|
||||
allow_replay_pointer(event->time);
|
||||
const bool use_threshold = !mod_pressed;
|
||||
tiling_drag(con, event, use_threshold);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 9: floating modifier pressed, initiate a resize */
|
||||
@ -312,10 +341,7 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo
|
||||
tiling_resize(con, event, dest, dest == CLICK_DECORATION && !was_focused);
|
||||
}
|
||||
|
||||
done:
|
||||
xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time);
|
||||
xcb_flush(conn);
|
||||
tree_render();
|
||||
allow_replay_pointer(event->time);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -381,13 +407,20 @@ void handle_button_press(xcb_button_press_event_t *event) {
|
||||
}
|
||||
|
||||
/* Check if the click was on the decoration of a child */
|
||||
Con *child;
|
||||
TAILQ_FOREACH_REVERSE (child, &(con->nodes_head), nodes_head, nodes) {
|
||||
if (!rect_contains(child->deco_rect, event->event_x, event->event_y))
|
||||
continue;
|
||||
if (con->window != NULL) {
|
||||
if (rect_contains(con->deco_rect, event->event_x, event->event_y)) {
|
||||
route_click(con, event, mod_pressed, CLICK_DECORATION);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Con *child;
|
||||
TAILQ_FOREACH_REVERSE (child, &(con->nodes_head), nodes_head, nodes) {
|
||||
if (!rect_contains(child->deco_rect, event->event_x, event->event_y))
|
||||
continue;
|
||||
|
||||
route_click(child, event, mod_pressed, CLICK_DECORATION);
|
||||
return;
|
||||
route_click(child, event, mod_pressed, CLICK_DECORATION);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (event->child != XCB_NONE) {
|
||||
|
201
src/commands.c
201
src/commands.c
@ -747,6 +747,8 @@ void cmd_border(I3_CMD, const char *border_style_str, long border_width) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* User changed the border */
|
||||
current->con->max_user_border_style = border_style;
|
||||
const int con_border_width = border_width_from_style(border_style, border_width, current->con);
|
||||
con_set_border_style(current->con, border_style, con_border_width);
|
||||
}
|
||||
@ -1030,11 +1032,16 @@ typedef struct user_output_name {
|
||||
typedef TAILQ_HEAD(user_output_names_head, user_output_name) user_output_names_head;
|
||||
|
||||
static void user_output_names_add(user_output_names_head *list, const char *name) {
|
||||
if (strcmp(name, "next") == 0) {
|
||||
/* "next" here works like a wildcard: It "expands" to all available
|
||||
* outputs. */
|
||||
const bool get_non_primary = (strcasecmp("nonprimary", name) == 0);
|
||||
if (get_non_primary || strcmp(name, "next") == 0) {
|
||||
/* "next" (or "nonprimary") here work like a wildcard: It "expands" to
|
||||
* all available (or non-primary) outputs. */
|
||||
Output *output;
|
||||
TAILQ_FOREACH (output, &outputs, outputs) {
|
||||
if (get_non_primary && output->primary) {
|
||||
continue;
|
||||
}
|
||||
|
||||
user_output_name *co = scalloc(sizeof(user_output_name), 1);
|
||||
co->name = sstrdup(output_primary_name(output));
|
||||
TAILQ_INSERT_TAIL(list, co, user_output_names);
|
||||
@ -1318,7 +1325,7 @@ void cmd_focus_direction(I3_CMD, const char *direction_str) {
|
||||
HANDLE_EMPTY_MATCH;
|
||||
CMD_FOCUS_WARN_CHILDREN;
|
||||
|
||||
direction_t direction;
|
||||
direction_t direction = D_LEFT;
|
||||
position_t position;
|
||||
bool auto_direction = true;
|
||||
if (strcmp(direction_str, "prev") == 0) {
|
||||
@ -1452,7 +1459,7 @@ void cmd_focus_level(I3_CMD, const char *level) {
|
||||
* Implementation of 'focus'.
|
||||
*
|
||||
*/
|
||||
void cmd_focus(I3_CMD) {
|
||||
void cmd_focus(I3_CMD, bool focus_workspace) {
|
||||
DLOG("current_match = %p\n", current_match);
|
||||
|
||||
if (match_is_empty(current_match)) {
|
||||
@ -1474,11 +1481,12 @@ void cmd_focus(I3_CMD) {
|
||||
Con *ws = con_get_workspace(current->con);
|
||||
/* If no workspace could be found, this was a dock window.
|
||||
* Just skip it, you cannot focus dock windows. */
|
||||
if (!ws)
|
||||
if (!ws) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* In case this is a scratchpad window, call scratchpad_show(). */
|
||||
if (ws == __i3_scratch) {
|
||||
if (ws == __i3_scratch && !focus_workspace) {
|
||||
scratchpad_show(current->con);
|
||||
/* While for the normal focus case we can change focus multiple
|
||||
* times and only a single window ends up focused, we could show
|
||||
@ -1486,8 +1494,15 @@ void cmd_focus(I3_CMD) {
|
||||
break;
|
||||
}
|
||||
|
||||
LOG("focusing %p / %s\n", current->con, current->con->name);
|
||||
con_activate_unblock(current->con);
|
||||
if (focus_workspace) {
|
||||
/* Show the workspace of the matched container, without necessarily
|
||||
* focusing it. */
|
||||
LOG("focusing workspace %p / %s - %p / %s\n", current->con, current->con->name, ws, ws->name);
|
||||
workspace_show(ws);
|
||||
} else {
|
||||
LOG("focusing %p / %s\n", current->con, current->con->name);
|
||||
con_activate_unblock(current->con);
|
||||
}
|
||||
}
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
@ -2372,3 +2387,171 @@ void cmd_debuglog(I3_CMD, const char *argument) {
|
||||
// XXX: default reply for now, make this a better reply
|
||||
ysuccess(true);
|
||||
}
|
||||
|
||||
static int *gaps_inner(gaps_t *gaps) {
|
||||
return &(gaps->inner);
|
||||
}
|
||||
|
||||
static int *gaps_top(gaps_t *gaps) {
|
||||
return &(gaps->top);
|
||||
}
|
||||
|
||||
static int *gaps_left(gaps_t *gaps) {
|
||||
return &(gaps->left);
|
||||
}
|
||||
|
||||
static int *gaps_bottom(gaps_t *gaps) {
|
||||
return &(gaps->bottom);
|
||||
}
|
||||
|
||||
static int *gaps_right(gaps_t *gaps) {
|
||||
return &(gaps->right);
|
||||
}
|
||||
|
||||
typedef int *(*gap_accessor)(gaps_t *);
|
||||
|
||||
static bool gaps_update(gap_accessor get, const char *scope, const char *mode, int pixels) {
|
||||
DLOG("gaps_update(scope=%s, mode=%s, pixels=%d)\n", scope, mode, pixels);
|
||||
Con *workspace = con_get_workspace(focused);
|
||||
|
||||
const int global_gap_size = *get(&(config.gaps));
|
||||
int current_value = global_gap_size;
|
||||
if (strcmp(scope, "current") == 0) {
|
||||
current_value += *get(&(workspace->gaps));
|
||||
}
|
||||
DLOG("global_gap_size=%d, current_value=%d\n", global_gap_size, current_value);
|
||||
|
||||
bool reset = false;
|
||||
if (strcmp(mode, "plus") == 0)
|
||||
current_value += pixels;
|
||||
else if (strcmp(mode, "minus") == 0)
|
||||
current_value -= pixels;
|
||||
else if (strcmp(mode, "set") == 0) {
|
||||
current_value = pixels;
|
||||
reset = true;
|
||||
} else if (strcmp(mode, "toggle") == 0) {
|
||||
current_value = !current_value * pixels;
|
||||
reset = true;
|
||||
} else {
|
||||
ELOG("Invalid mode %s when changing gaps", mode);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* See https://github.com/Airblader/i3/issues/262 */
|
||||
int min_value = 0;
|
||||
const bool is_outer = get(&(config.gaps)) != gaps_inner(&(config.gaps));
|
||||
if (is_outer) {
|
||||
/* Outer gaps can compensate inner gaps. */
|
||||
if (strcmp(scope, "all") == 0) {
|
||||
min_value = -config.gaps.inner;
|
||||
} else {
|
||||
min_value = -config.gaps.inner - workspace->gaps.inner;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_value < min_value) {
|
||||
current_value = min_value;
|
||||
}
|
||||
|
||||
if (strcmp(scope, "all") == 0) {
|
||||
Con *output = NULL;
|
||||
TAILQ_FOREACH (output, &(croot->nodes_head), nodes) {
|
||||
Con *cur_ws = NULL;
|
||||
Con *content = output_get_content(output);
|
||||
TAILQ_FOREACH (cur_ws, &(content->nodes_head), nodes) {
|
||||
int *gaps_value = get(&(cur_ws->gaps));
|
||||
DLOG("current gaps_value = %d\n", *gaps_value);
|
||||
|
||||
if (reset) {
|
||||
*gaps_value = 0;
|
||||
} else {
|
||||
int max_compensate = 0;
|
||||
if (is_outer) {
|
||||
max_compensate = config.gaps.inner;
|
||||
}
|
||||
if (*gaps_value + current_value + max_compensate < 0) {
|
||||
/* Enforce new per-workspace gap size minimum value (in case
|
||||
current_value is smaller than before): the workspace can at most
|
||||
have a negative gap size of -current_value - max_compensate. */
|
||||
*gaps_value = -current_value - max_compensate;
|
||||
}
|
||||
}
|
||||
DLOG("gaps_value after fix = %d\n", *gaps_value);
|
||||
}
|
||||
}
|
||||
|
||||
*get(&(config.gaps)) = current_value;
|
||||
DLOG("global gaps value after fix = %d\n", *get(&(config.gaps)));
|
||||
} else {
|
||||
int *gaps_value = get(&(workspace->gaps));
|
||||
*gaps_value = current_value - global_gap_size;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of 'gaps inner|outer|top|right|bottom|left|horizontal|vertical current|all set|plus|minus|toggle <px>'
|
||||
*
|
||||
*/
|
||||
void cmd_gaps(I3_CMD, const char *type, const char *scope, const char *mode, const char *value) {
|
||||
int pixels = logical_px(atoi(value));
|
||||
|
||||
if (!strcmp(type, "inner")) {
|
||||
if (!gaps_update(gaps_inner, scope, mode, pixels)) {
|
||||
goto error;
|
||||
}
|
||||
/* Update all workspaces with a no-op change (plus 0) so that the
|
||||
* minimum value is re-calculated and applied as a side effect. */
|
||||
if (!gaps_update(gaps_top, "all", "plus", 0) ||
|
||||
!gaps_update(gaps_bottom, "all", "plus", 0) ||
|
||||
!gaps_update(gaps_right, "all", "plus", 0) ||
|
||||
!gaps_update(gaps_left, "all", "plus", 0)) {
|
||||
goto error;
|
||||
}
|
||||
} else if (!strcmp(type, "outer")) {
|
||||
if (!gaps_update(gaps_top, scope, mode, pixels) ||
|
||||
!gaps_update(gaps_bottom, scope, mode, pixels) ||
|
||||
!gaps_update(gaps_right, scope, mode, pixels) ||
|
||||
!gaps_update(gaps_left, scope, mode, pixels)) {
|
||||
goto error;
|
||||
}
|
||||
} else if (!strcmp(type, "vertical")) {
|
||||
if (!gaps_update(gaps_top, scope, mode, pixels) ||
|
||||
!gaps_update(gaps_bottom, scope, mode, pixels)) {
|
||||
goto error;
|
||||
}
|
||||
} else if (!strcmp(type, "horizontal")) {
|
||||
if (!gaps_update(gaps_right, scope, mode, pixels) ||
|
||||
!gaps_update(gaps_left, scope, mode, pixels)) {
|
||||
goto error;
|
||||
}
|
||||
} else if (!strcmp(type, "top")) {
|
||||
if (!gaps_update(gaps_top, scope, mode, pixels)) {
|
||||
goto error;
|
||||
}
|
||||
} else if (!strcmp(type, "bottom")) {
|
||||
if (!gaps_update(gaps_bottom, scope, mode, pixels)) {
|
||||
goto error;
|
||||
}
|
||||
} else if (!strcmp(type, "right")) {
|
||||
if (!gaps_update(gaps_right, scope, mode, pixels)) {
|
||||
goto error;
|
||||
}
|
||||
} else if (!strcmp(type, "left")) {
|
||||
if (!gaps_update(gaps_left, scope, mode, pixels)) {
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
ELOG("Invalid type %s when changing gaps", type);
|
||||
goto error;
|
||||
}
|
||||
|
||||
cmd_output->needs_tree_render = true;
|
||||
// XXX: default reply for now, make this a better reply
|
||||
ysuccess(true);
|
||||
return;
|
||||
|
||||
error:
|
||||
ysuccess(false);
|
||||
}
|
||||
|
109
src/con.c
109
src/con.c
@ -41,7 +41,7 @@ Con *con_new_skeleton(Con *parent, i3Window *window) {
|
||||
TAILQ_INSERT_TAIL(&all_cons, new, all_cons);
|
||||
new->type = CT_CON;
|
||||
new->window = window;
|
||||
new->border_style = config.default_border;
|
||||
new->border_style = new->max_user_border_style = config.default_border;
|
||||
new->current_border_width = -1;
|
||||
new->window_icon_padding = -1;
|
||||
if (window) {
|
||||
@ -618,7 +618,10 @@ bool con_is_docked(Con *con) {
|
||||
*
|
||||
*/
|
||||
Con *con_inside_floating(Con *con) {
|
||||
assert(con != NULL);
|
||||
if (con == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (con->type == CT_FLOATING_CON)
|
||||
return con;
|
||||
|
||||
@ -1685,17 +1688,34 @@ Con *con_descend_direction(Con *con, direction_t direction) {
|
||||
return con_descend_direction(most, direction);
|
||||
}
|
||||
|
||||
static bool has_outer_gaps(gaps_t gaps) {
|
||||
return gaps.top > 0 ||
|
||||
gaps.right > 0 ||
|
||||
gaps.bottom > 0 ||
|
||||
gaps.left > 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a "relative" Rect which contains the amount of pixels that need to
|
||||
* be added to the original Rect to get the final position (obviously the
|
||||
* amount of pixels for normal, 1pixel and borderless are different).
|
||||
* Returns whether the window decoration (title bar) should be drawn into the
|
||||
* X11 frame window of this container (default) or into the X11 frame window of
|
||||
* the parent container (for stacked/tabbed containers).
|
||||
*
|
||||
*/
|
||||
Rect con_border_style_rect(Con *con) {
|
||||
if (config.hide_edge_borders == HEBM_SMART && con_num_visible_children(con_get_workspace(con)) <= 1) {
|
||||
if (!con_is_floating(con)) {
|
||||
bool con_draw_decoration_into_frame(Con *con) {
|
||||
return con_is_leaf(con) &&
|
||||
con_border_style(con) == BS_NORMAL &&
|
||||
(con->parent == NULL ||
|
||||
(con->parent->layout != L_TABBED &&
|
||||
con->parent->layout != L_STACKED));
|
||||
}
|
||||
|
||||
static Rect con_border_style_rect_without_title(Con *con) {
|
||||
if ((config.smart_borders == SMART_BORDERS_ON && con_num_visible_children(con_get_workspace(con)) <= 1) ||
|
||||
(config.smart_borders == SMART_BORDERS_NO_GAPS && !has_outer_gaps(calculate_effective_gaps(con))) ||
|
||||
(config.hide_edge_borders == HEBM_SMART && con_num_visible_children(con_get_workspace(con)) <= 1) ||
|
||||
(config.hide_edge_borders == HEBM_SMART_NO_GAPS && con_num_visible_children(con_get_workspace(con)) <= 1 && !has_outer_gaps(calculate_effective_gaps(con)))) {
|
||||
if (!con_is_floating(con))
|
||||
return (Rect){0, 0, 0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
adjacent_t borders_to_hide = ADJ_NONE;
|
||||
@ -1720,7 +1740,13 @@ Rect con_border_style_rect(Con *con) {
|
||||
result = (Rect){border_width, border_width, -(2 * border_width), -(2 * border_width)};
|
||||
}
|
||||
|
||||
borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
|
||||
/* If hide_edge_borders is set to no_gaps and it did not pass the no border check, show all borders */
|
||||
if (config.hide_edge_borders == HEBM_SMART_NO_GAPS) {
|
||||
borders_to_hide = con_adjacent_borders(con) & HEBM_NONE;
|
||||
} else {
|
||||
borders_to_hide = con_adjacent_borders(con) & config.hide_edge_borders;
|
||||
}
|
||||
|
||||
if (borders_to_hide & ADJ_LEFT_SCREEN_EDGE) {
|
||||
result.x -= border_width;
|
||||
result.width += border_width;
|
||||
@ -1738,6 +1764,23 @@ Rect con_border_style_rect(Con *con) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a "relative" Rect which contains the amount of pixels that need to
|
||||
* be added to the original Rect to get the final position (obviously the
|
||||
* amount of pixels for normal, 1pixel and borderless are different).
|
||||
*
|
||||
*/
|
||||
Rect con_border_style_rect(Con *con) {
|
||||
Rect result = con_border_style_rect_without_title(con);
|
||||
if (con_border_style(con) == BS_NORMAL &&
|
||||
con_draw_decoration_into_frame(con)) {
|
||||
const int deco_height = render_deco_height();
|
||||
result.y += deco_height;
|
||||
result.height -= deco_height;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns adjacent borders of the window. We need this if hide_edge_borders is
|
||||
* enabled.
|
||||
@ -1777,14 +1820,19 @@ int con_border_style(Con *con) {
|
||||
return BS_NONE;
|
||||
}
|
||||
|
||||
if (con->parent->layout == L_STACKED)
|
||||
return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL);
|
||||
if (con->parent != NULL) {
|
||||
if (con->parent->layout == L_STACKED) {
|
||||
return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL);
|
||||
}
|
||||
|
||||
if (con->parent->layout == L_TABBED && con->border_style != BS_NORMAL)
|
||||
return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL);
|
||||
if (con->parent->layout == L_TABBED && con->border_style != BS_NORMAL) {
|
||||
return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL);
|
||||
}
|
||||
|
||||
if (con->parent->type == CT_DOCKAREA)
|
||||
return BS_NONE;
|
||||
if (con->parent->type == CT_DOCKAREA) {
|
||||
return BS_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
return con->border_style;
|
||||
}
|
||||
@ -1794,7 +1842,11 @@ int con_border_style(Con *con) {
|
||||
* floating window.
|
||||
*
|
||||
*/
|
||||
void con_set_border_style(Con *con, int border_style, int border_width) {
|
||||
void con_set_border_style(Con *con, border_style_t border_style, int border_width) {
|
||||
if (border_style > con->max_user_border_style) {
|
||||
border_style = con->max_user_border_style;
|
||||
}
|
||||
|
||||
/* Handle the simple case: non-floating containerns */
|
||||
if (!con_is_floating(con)) {
|
||||
con->border_style = border_style;
|
||||
@ -1807,27 +1859,19 @@ void con_set_border_style(Con *con, int border_style, int border_width) {
|
||||
* con->rect represent the absolute position of the window (same for
|
||||
* parent). Then, we change the border style and subtract the new border
|
||||
* pixels. For the parent, we do the same also for the decoration. */
|
||||
DLOG("This is a floating container\n");
|
||||
|
||||
Con *parent = con->parent;
|
||||
Rect bsr = con_border_style_rect(con);
|
||||
int deco_height = (con->border_style == BS_NORMAL ? render_deco_height() : 0);
|
||||
|
||||
con->rect = rect_add(con->rect, bsr);
|
||||
parent->rect = rect_add(parent->rect, bsr);
|
||||
parent->rect.y += deco_height;
|
||||
parent->rect.height -= deco_height;
|
||||
|
||||
/* Change the border style, get new border/decoration values. */
|
||||
con->border_style = border_style;
|
||||
con->current_border_width = border_width;
|
||||
bsr = con_border_style_rect(con);
|
||||
deco_height = (con->border_style == BS_NORMAL ? render_deco_height() : 0);
|
||||
|
||||
con->rect = rect_sub(con->rect, bsr);
|
||||
parent->rect = rect_sub(parent->rect, bsr);
|
||||
parent->rect.y -= deco_height;
|
||||
parent->rect.height += deco_height;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2538,3 +2582,18 @@ void con_merge_into(Con *old, Con *new) {
|
||||
|
||||
tree_close_internal(old, DONT_KILL_WINDOW, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the container is within any stacked/tabbed split container.
|
||||
*
|
||||
*/
|
||||
bool con_inside_stacked_or_tabbed(Con *con) {
|
||||
if (con->parent == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (con->parent->layout == L_STACKED ||
|
||||
con->parent->layout == L_TABBED) {
|
||||
return true;
|
||||
}
|
||||
return con_inside_stacked_or_tabbed(con->parent);
|
||||
}
|
||||
|
26
src/config.c
26
src/config.c
@ -105,6 +105,7 @@ static void free_configuration(void) {
|
||||
FREE(barconfig->outputs);
|
||||
FREE(barconfig->socket_path);
|
||||
FREE(barconfig->status_command);
|
||||
FREE(barconfig->workspace_command);
|
||||
FREE(barconfig->i3bar_command);
|
||||
FREE(barconfig->font);
|
||||
FREE(barconfig->colors.background);
|
||||
@ -209,6 +210,8 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
|
||||
|
||||
config.show_marks = true;
|
||||
|
||||
config.empty_workspaces = false;
|
||||
|
||||
config.default_border = BS_NORMAL;
|
||||
config.default_floating_border = BS_NORMAL;
|
||||
config.default_border_width = logical_px(2);
|
||||
@ -216,12 +219,20 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
|
||||
/* Set default_orientation to NO_ORIENTATION for auto orientation. */
|
||||
config.default_orientation = NO_ORIENTATION;
|
||||
|
||||
config.gaps.inner = 0;
|
||||
config.gaps.top = 0;
|
||||
config.gaps.right = 0;
|
||||
config.gaps.bottom = 0;
|
||||
config.gaps.left = 0;
|
||||
|
||||
/* Set default urgency reset delay to 500ms */
|
||||
if (config.workspace_urgency_timer == 0)
|
||||
config.workspace_urgency_timer = 0.5;
|
||||
|
||||
config.focus_wrapping = FOCUS_WRAPPING_ON;
|
||||
|
||||
config.tiling_drag = TILING_DRAG_MODIFIER;
|
||||
|
||||
FREE(current_configpath);
|
||||
current_configpath = get_config_path(override_configpath, true);
|
||||
if (current_configpath == NULL) {
|
||||
@ -273,15 +284,26 @@ bool load_configuration(const char *override_configpath, config_load_t load_type
|
||||
set_font(&config.font);
|
||||
}
|
||||
|
||||
/* Make bar config blocks without a configured font use the i3-wide font. */
|
||||
Barconfig *current;
|
||||
if (load_type != C_VALIDATE) {
|
||||
TAILQ_FOREACH (current, &barconfigs, configs) {
|
||||
if (current->font != NULL) {
|
||||
continue;
|
||||
}
|
||||
current->font = sstrdup(config.font.pattern);
|
||||
}
|
||||
}
|
||||
|
||||
if (load_type == C_RELOAD) {
|
||||
translate_keysyms();
|
||||
grab_all_keys(conn);
|
||||
regrab_all_buttons(conn);
|
||||
gaps_reapply_workspace_assignments();
|
||||
|
||||
/* Redraw the currently visible decorations on reload, so that the
|
||||
* possibly new drawing parameters changed. */
|
||||
x_deco_recurse(croot);
|
||||
xcb_flush(conn);
|
||||
tree_render();
|
||||
}
|
||||
|
||||
return result == 0;
|
||||
|
@ -163,15 +163,9 @@ i3_event_state_mask_t event_state_from_str(const char *str) {
|
||||
return result;
|
||||
}
|
||||
|
||||
static char *font_pattern;
|
||||
|
||||
CFGFUN(font, const char *font) {
|
||||
config.font = load_font(font, true);
|
||||
set_font(&config.font);
|
||||
|
||||
/* Save the font pattern for using it as bar font later on */
|
||||
FREE(font_pattern);
|
||||
font_pattern = sstrdup(font);
|
||||
}
|
||||
|
||||
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
|
||||
@ -186,6 +180,11 @@ static char *current_mode;
|
||||
static bool current_mode_pango_markup;
|
||||
|
||||
CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command) {
|
||||
if (current_mode == NULL) {
|
||||
/* When using an invalid mode name, e.g. “default” */
|
||||
return;
|
||||
}
|
||||
|
||||
configure_binding(bindtype, modifiers, key, release, border, whole_window, exclude_titlebar, command, current_mode, current_mode_pango_markup);
|
||||
}
|
||||
|
||||
@ -232,6 +231,102 @@ CFGFUN(for_window, const char *command) {
|
||||
TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
|
||||
}
|
||||
|
||||
static void apply_gaps(gaps_t *gaps, gaps_mask_t mask, int value) {
|
||||
if (gaps == NULL) {
|
||||
return;
|
||||
}
|
||||
if (mask & GAPS_INNER) {
|
||||
gaps->inner = value;
|
||||
}
|
||||
if (mask & GAPS_TOP) {
|
||||
gaps->top = value;
|
||||
}
|
||||
if (mask & GAPS_RIGHT) {
|
||||
gaps->right = value;
|
||||
}
|
||||
if (mask & GAPS_BOTTOM) {
|
||||
gaps->bottom = value;
|
||||
}
|
||||
if (mask & GAPS_LEFT) {
|
||||
gaps->left = value;
|
||||
}
|
||||
}
|
||||
|
||||
static void create_gaps_assignment(const char *workspace, const gaps_mask_t mask, const int pixels) {
|
||||
if (mask == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
DLOG("Setting gaps for workspace %s", workspace);
|
||||
|
||||
bool found = false;
|
||||
struct Workspace_Assignment *assignment;
|
||||
TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
|
||||
if (strcasecmp(assignment->name, workspace) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Assignment does not yet exist, let's create it. */
|
||||
if (!found) {
|
||||
assignment = scalloc(1, sizeof(struct Workspace_Assignment));
|
||||
assignment->name = sstrdup(workspace);
|
||||
assignment->output = NULL;
|
||||
TAILQ_INSERT_TAIL(&ws_assignments, assignment, ws_assignments);
|
||||
}
|
||||
|
||||
assignment->gaps_mask |= mask;
|
||||
apply_gaps(&assignment->gaps, mask, pixels);
|
||||
}
|
||||
|
||||
static gaps_mask_t gaps_scope_to_mask(const char *scope) {
|
||||
if (!strcmp(scope, "inner")) {
|
||||
return GAPS_INNER;
|
||||
} else if (!strcmp(scope, "outer")) {
|
||||
return GAPS_OUTER;
|
||||
} else if (!strcmp(scope, "vertical")) {
|
||||
return GAPS_VERTICAL;
|
||||
} else if (!strcmp(scope, "horizontal")) {
|
||||
return GAPS_HORIZONTAL;
|
||||
} else if (!strcmp(scope, "top")) {
|
||||
return GAPS_TOP;
|
||||
} else if (!strcmp(scope, "right")) {
|
||||
return GAPS_RIGHT;
|
||||
} else if (!strcmp(scope, "bottom")) {
|
||||
return GAPS_BOTTOM;
|
||||
} else if (!strcmp(scope, "left")) {
|
||||
return GAPS_LEFT;
|
||||
}
|
||||
ELOG("Invalid command, cannot process scope %s", scope);
|
||||
return 0;
|
||||
}
|
||||
|
||||
CFGFUN(gaps, const char *workspace, const char *scope, const long value) {
|
||||
int pixels = logical_px(value);
|
||||
gaps_mask_t mask = gaps_scope_to_mask(scope);
|
||||
|
||||
if (workspace == NULL) {
|
||||
apply_gaps(&config.gaps, mask, pixels);
|
||||
} else {
|
||||
create_gaps_assignment(workspace, mask, pixels);
|
||||
}
|
||||
}
|
||||
|
||||
CFGFUN(smart_borders, const char *enable) {
|
||||
if (!strcmp(enable, "no_gaps"))
|
||||
config.smart_borders = SMART_BORDERS_NO_GAPS;
|
||||
else
|
||||
config.smart_borders = boolstr(enable) ? SMART_BORDERS_ON : SMART_BORDERS_OFF;
|
||||
}
|
||||
|
||||
CFGFUN(smart_gaps, const char *enable) {
|
||||
if (!strcmp(enable, "inverse_outer"))
|
||||
config.smart_gaps = SMART_GAPS_INVERSE_OUTER;
|
||||
else
|
||||
config.smart_gaps = boolstr(enable) ? SMART_GAPS_ON : SMART_GAPS_OFF;
|
||||
}
|
||||
|
||||
CFGFUN(floating_minimum_size, const long width, const long height) {
|
||||
config.floating_minimum_width = width;
|
||||
config.floating_minimum_height = height;
|
||||
@ -298,7 +393,9 @@ CFGFUN(default_border, const char *windowtype, const char *border, const long wi
|
||||
}
|
||||
|
||||
CFGFUN(hide_edge_borders, const char *borders) {
|
||||
if (strcmp(borders, "smart") == 0)
|
||||
if (strcmp(borders, "smart_no_gaps") == 0)
|
||||
config.hide_edge_borders = HEBM_SMART_NO_GAPS;
|
||||
else if (strcmp(borders, "smart") == 0)
|
||||
config.hide_edge_borders = HEBM_SMART;
|
||||
else if (strcmp(borders, "vertical") == 0)
|
||||
config.hide_edge_borders = HEBM_VERTICAL;
|
||||
@ -314,6 +411,10 @@ CFGFUN(hide_edge_borders, const char *borders) {
|
||||
config.hide_edge_borders = HEBM_NONE;
|
||||
}
|
||||
|
||||
CFGFUN(empty_workspaces, const char *value) {
|
||||
config.empty_workspaces = boolstr(value);
|
||||
}
|
||||
|
||||
CFGFUN(focus_follows_mouse, const char *value) {
|
||||
config.disable_focus_follows_mouse = !boolstr(value);
|
||||
}
|
||||
@ -418,9 +519,11 @@ CFGFUN(workspace, const char *workspace, const char *output) {
|
||||
|
||||
TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
|
||||
if (strcasecmp(assignment->name, workspace) == 0) {
|
||||
ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
|
||||
workspace);
|
||||
return;
|
||||
if (assignment->output != NULL) {
|
||||
ELOG("You have a duplicate workspace assignment for workspace \"%s\"\n",
|
||||
workspace);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -561,6 +664,20 @@ CFGFUN(ipc_kill_timeout, const long timeout_ms) {
|
||||
ipc_set_kill_timeout(timeout_ms / 1000.0);
|
||||
}
|
||||
|
||||
CFGFUN(tiling_drag, const char *value) {
|
||||
if (strcmp(value, "modifier") == 0) {
|
||||
config.tiling_drag = TILING_DRAG_MODIFIER;
|
||||
} else if (strcmp(value, "titlebar") == 0) {
|
||||
config.tiling_drag = TILING_DRAG_TITLEBAR;
|
||||
} else if (strcmp(value, "modifier,titlebar") == 0 ||
|
||||
strcmp(value, "titlebar,modifier") == 0) {
|
||||
/* Switch the above to strtok() or similar if we ever grow more options */
|
||||
config.tiling_drag = TILING_DRAG_MODIFIER_OR_TITLEBAR;
|
||||
} else {
|
||||
config.tiling_drag = TILING_DRAG_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Bar configuration (i3bar)
|
||||
******************************************************************************/
|
||||
@ -600,6 +717,50 @@ CFGFUN(bar_verbose, const char *verbose) {
|
||||
current_bar->verbose = boolstr(verbose);
|
||||
}
|
||||
|
||||
CFGFUN(bar_height, const long height) {
|
||||
current_bar->bar_height = (uint32_t)height;
|
||||
}
|
||||
|
||||
static void dlog_padding(void) {
|
||||
DLOG("padding now: x=%d, y=%d, w=%d, h=%d\n",
|
||||
current_bar->padding.x,
|
||||
current_bar->padding.y,
|
||||
current_bar->padding.width,
|
||||
current_bar->padding.height);
|
||||
}
|
||||
|
||||
CFGFUN(bar_padding_one, const long all) {
|
||||
current_bar->padding.x = (uint32_t)all;
|
||||
current_bar->padding.y = (uint32_t)all;
|
||||
current_bar->padding.width = (uint32_t)all;
|
||||
current_bar->padding.height = (uint32_t)all;
|
||||
dlog_padding();
|
||||
}
|
||||
|
||||
CFGFUN(bar_padding_two, const long top_and_bottom, const long right_and_left) {
|
||||
current_bar->padding.x = (uint32_t)right_and_left;
|
||||
current_bar->padding.y = (uint32_t)top_and_bottom;
|
||||
current_bar->padding.width = (uint32_t)right_and_left;
|
||||
current_bar->padding.height = (uint32_t)top_and_bottom;
|
||||
dlog_padding();
|
||||
}
|
||||
|
||||
CFGFUN(bar_padding_three, const long top, const long right_and_left, const long bottom) {
|
||||
current_bar->padding.x = (uint32_t)right_and_left;
|
||||
current_bar->padding.y = (uint32_t)top;
|
||||
current_bar->padding.width = (uint32_t)right_and_left;
|
||||
current_bar->padding.height = (uint32_t)bottom;
|
||||
dlog_padding();
|
||||
}
|
||||
|
||||
CFGFUN(bar_padding_four, const long top, const long right, const long bottom, const long left) {
|
||||
current_bar->padding.x = (uint32_t)left;
|
||||
current_bar->padding.y = (uint32_t)top;
|
||||
current_bar->padding.width = (uint32_t)right;
|
||||
current_bar->padding.height = (uint32_t)bottom;
|
||||
dlog_padding();
|
||||
}
|
||||
|
||||
CFGFUN(bar_modifier, const char *modifiers) {
|
||||
current_bar->modifier = modifiers ? event_state_from_str(modifiers) : XCB_NONE;
|
||||
}
|
||||
@ -716,6 +877,11 @@ CFGFUN(bar_status_command, const char *command) {
|
||||
current_bar->status_command = sstrdup(command);
|
||||
}
|
||||
|
||||
CFGFUN(bar_workspace_command, const char *command) {
|
||||
FREE(current_bar->workspace_command);
|
||||
current_bar->workspace_command = sstrdup(command);
|
||||
}
|
||||
|
||||
CFGFUN(bar_binding_mode_indicator, const char *value) {
|
||||
current_bar->hide_binding_mode_indicator = !boolstr(value);
|
||||
}
|
||||
@ -752,10 +918,6 @@ CFGFUN(bar_finish) {
|
||||
|
||||
config.number_barconfigs++;
|
||||
|
||||
/* If no font was explicitly set, we use the i3 font as default */
|
||||
if (current_bar->font == NULL && font_pattern != NULL)
|
||||
current_bar->font = sstrdup(font_pattern);
|
||||
|
||||
TAILQ_INSERT_TAIL(&barconfigs, current_bar, configs);
|
||||
/* Simply reset the pointer, but don't free the resources. */
|
||||
current_bar = NULL;
|
||||
|
@ -919,7 +919,7 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFi
|
||||
continuation = NULL;
|
||||
}
|
||||
|
||||
strncpy(buf + strlen(buf), buffer, strlen(buffer) + 1);
|
||||
strcpy(buf + strlen(buf), buffer);
|
||||
|
||||
/* Skip comments and empty lines. */
|
||||
if (skip_line || comment) {
|
||||
@ -1001,9 +1001,14 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFi
|
||||
char *next;
|
||||
for (next = bufcopy;
|
||||
next < (bufcopy + stbuf.st_size) &&
|
||||
(next = strcasestr(next, current->key)) != NULL;
|
||||
next += strlen(current->key)) {
|
||||
*next = '_';
|
||||
(next = strcasestr(next, current->key)) != NULL;) {
|
||||
/* We need to invalidate variables completely (otherwise we may count
|
||||
* the same variable more than once, thus causing buffer overflow or
|
||||
* allocation failure) with spaces (variable names cannot contain spaces) */
|
||||
char *end = next + strlen(current->key);
|
||||
while (next < end) {
|
||||
*next++ = ' ';
|
||||
}
|
||||
extra_bytes += extra;
|
||||
}
|
||||
}
|
||||
@ -1038,7 +1043,7 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFi
|
||||
} else {
|
||||
/* Copy until the next variable, then copy its value */
|
||||
strncpy(destwalk, walk, distance);
|
||||
strncpy(destwalk + distance, nearest->value, strlen(nearest->value));
|
||||
strcpy(destwalk + distance, nearest->value);
|
||||
walk += distance + strlen(nearest->key);
|
||||
destwalk += distance + strlen(nearest->value);
|
||||
}
|
||||
|
@ -42,7 +42,8 @@ struct drag_x11_cb {
|
||||
|
||||
static bool threshold_exceeded(uint32_t x1, uint32_t y1,
|
||||
uint32_t x2, uint32_t y2) {
|
||||
const uint32_t threshold = 9;
|
||||
/* The threshold is about the height of one window decoration. */
|
||||
const uint32_t threshold = logical_px(15);
|
||||
return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) > threshold * threshold;
|
||||
}
|
||||
|
||||
|
@ -79,16 +79,22 @@ void floating_check_size(Con *floating_con, bool prefer_height) {
|
||||
Rect floating_sane_max_dimensions;
|
||||
Con *focused_con = con_descend_focused(floating_con);
|
||||
|
||||
DLOG("deco_rect.height = %d\n", focused_con->deco_rect.height);
|
||||
Rect border_rect = con_border_style_rect(focused_con);
|
||||
/* We have to do the opposite calculations that render_con() do
|
||||
* to get the exact size we want. */
|
||||
border_rect.width = -border_rect.width;
|
||||
border_rect.width += 2 * focused_con->border_width;
|
||||
border_rect.height = -border_rect.height;
|
||||
|
||||
/* undo x11 border */
|
||||
border_rect.width += 2 * focused_con->border_width;
|
||||
border_rect.height += 2 * focused_con->border_width;
|
||||
if (con_border_style(focused_con) == BS_NORMAL) {
|
||||
border_rect.height += render_deco_height();
|
||||
}
|
||||
|
||||
DLOG("floating_check_size, want min width %d, min height %d, border extra: w=%d, h=%d\n",
|
||||
floating_sane_min_width,
|
||||
floating_sane_min_height,
|
||||
border_rect.width,
|
||||
border_rect.height);
|
||||
|
||||
i3Window *window = focused_con->window;
|
||||
if (window != NULL) {
|
||||
@ -319,9 +325,6 @@ bool floating_enable(Con *con, bool automatic) {
|
||||
x_set_name(nc, name);
|
||||
free(name);
|
||||
|
||||
/* find the height for the decorations */
|
||||
int deco_height = render_deco_height();
|
||||
|
||||
DLOG("Original rect: (%d, %d) with %d x %d\n", con->rect.x, con->rect.y, con->rect.width, con->rect.height);
|
||||
DLOG("Geometry = (%d, %d) with %d x %d\n", con->geometry.x, con->geometry.y, con->geometry.width, con->geometry.height);
|
||||
nc->rect = con->geometry;
|
||||
@ -347,19 +350,15 @@ bool floating_enable(Con *con, bool automatic) {
|
||||
con->floating = FLOATING_USER_ON;
|
||||
|
||||
/* 4: set the border style as specified with new_float */
|
||||
if (automatic)
|
||||
con->border_style = config.default_floating_border;
|
||||
if (automatic) {
|
||||
con->border_style = con->max_user_border_style = config.default_floating_border;
|
||||
}
|
||||
|
||||
/* Add pixels for the decoration. */
|
||||
Rect border_style_rect = con_border_style_rect(con);
|
||||
Rect bsr = con_border_style_rect(con);
|
||||
|
||||
nc->rect.height -= border_style_rect.height;
|
||||
nc->rect.width -= border_style_rect.width;
|
||||
|
||||
/* Add some more pixels for the title bar */
|
||||
if (con_border_style(con) == BS_NORMAL) {
|
||||
nc->rect.height += deco_height;
|
||||
}
|
||||
nc->rect.height -= bsr.height;
|
||||
nc->rect.width -= bsr.width;
|
||||
|
||||
/* Honor the X11 border */
|
||||
nc->rect.height += con->border_width * 2;
|
||||
@ -405,14 +404,6 @@ bool floating_enable(Con *con, bool automatic) {
|
||||
|
||||
DLOG("Floating rect: (%d, %d) with %d x %d\n", nc->rect.x, nc->rect.y, nc->rect.width, nc->rect.height);
|
||||
|
||||
/* 5: Subtract the deco_height in order to make the floating window appear
|
||||
* at precisely the position it specified in its original geometry (which
|
||||
* is what applications might remember). */
|
||||
deco_height = (con->border_style == BS_NORMAL ? render_deco_height() : 0);
|
||||
nc->rect.y -= deco_height;
|
||||
|
||||
DLOG("Corrected y = %d (deco_height = %d)\n", nc->rect.y, deco_height);
|
||||
|
||||
/* render the cons to get initial window_rect correct */
|
||||
render_con(nc);
|
||||
|
||||
@ -698,6 +689,10 @@ void floating_resize_window(Con *con, const bool proportional,
|
||||
const xcb_button_press_event_t *event) {
|
||||
DLOG("floating_resize_window\n");
|
||||
|
||||
/* Push changes before resizing, so that the window gets raised now and not
|
||||
* after the user releases the mouse button */
|
||||
tree_render();
|
||||
|
||||
/* corner saves the nearest corner to the original click. It contains
|
||||
* a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */
|
||||
border_t corner = 0;
|
||||
|
169
src/gaps.c
Normal file
169
src/gaps.c
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* gaps.c: gaps logic: whether to display gaps at all, and how big
|
||||
* they should be.
|
||||
*
|
||||
*/
|
||||
#include "all.h"
|
||||
|
||||
/**
|
||||
* Calculates the effective gap sizes for a container.
|
||||
*/
|
||||
gaps_t calculate_effective_gaps(Con *con) {
|
||||
Con *workspace = con_get_workspace(con);
|
||||
if (workspace == NULL)
|
||||
return (gaps_t){0, 0, 0, 0, 0};
|
||||
|
||||
bool one_child = con_num_visible_children(workspace) <= 1 ||
|
||||
(con_num_children(workspace) == 1 &&
|
||||
(TAILQ_FIRST(&(workspace->nodes_head))->layout == L_TABBED ||
|
||||
TAILQ_FIRST(&(workspace->nodes_head))->layout == L_STACKED));
|
||||
|
||||
if (config.smart_gaps == SMART_GAPS_ON && one_child)
|
||||
return (gaps_t){0, 0, 0, 0, 0};
|
||||
|
||||
gaps_t gaps = {
|
||||
.inner = (workspace->gaps.inner + config.gaps.inner),
|
||||
.top = 0,
|
||||
.right = 0,
|
||||
.bottom = 0,
|
||||
.left = 0};
|
||||
|
||||
if (config.smart_gaps != SMART_GAPS_INVERSE_OUTER || one_child) {
|
||||
gaps.top = workspace->gaps.top + config.gaps.top;
|
||||
gaps.right = workspace->gaps.right + config.gaps.right;
|
||||
gaps.bottom = workspace->gaps.bottom + config.gaps.bottom;
|
||||
gaps.left = workspace->gaps.left + config.gaps.left;
|
||||
}
|
||||
|
||||
return gaps;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decides whether the container should be inset.
|
||||
*/
|
||||
bool gaps_should_inset_con(Con *con, int children) {
|
||||
/* No parent? None of the conditionals below can be true. */
|
||||
if (con->parent == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Floating split containers should never have gaps inside them. */
|
||||
if (con_inside_floating(con)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool leaf_or_stacked_tabbed =
|
||||
con_is_leaf(con) ||
|
||||
(con->layout == L_STACKED || con->layout == L_TABBED);
|
||||
|
||||
/* Inset direct children of the workspace that are leaf containers or
|
||||
stacked/tabbed containers. */
|
||||
if (leaf_or_stacked_tabbed &&
|
||||
con->parent->type == CT_WORKSPACE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Inset direct children of vertical or horizontal split containers at any
|
||||
depth in the tree. Do not inset as soon as any parent is a stacked or
|
||||
tabbed container, to avoid double insets. */
|
||||
if (leaf_or_stacked_tabbed &&
|
||||
!con_inside_stacked_or_tabbed(con) &&
|
||||
con->parent->type == CT_CON &&
|
||||
(con->parent->layout == L_SPLITH ||
|
||||
con->parent->layout == L_SPLITV)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns whether the given container has an adjacent container in the
|
||||
* specified direction. In other words, this returns true if and only if
|
||||
* the container is not touching the edge of the screen in that direction.
|
||||
*/
|
||||
bool gaps_has_adjacent_container(Con *con, direction_t direction) {
|
||||
Con *workspace = con_get_workspace(con);
|
||||
Con *fullscreen = con_get_fullscreen_con(workspace, CF_GLOBAL);
|
||||
if (fullscreen == NULL)
|
||||
fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
|
||||
|
||||
/* If this container is fullscreen by itself, there's no adjacent container. */
|
||||
if (con == fullscreen)
|
||||
return false;
|
||||
|
||||
Con *first = con;
|
||||
Con *second = NULL;
|
||||
bool found_neighbor = resize_find_tiling_participants(&first, &second, direction, false);
|
||||
if (!found_neighbor)
|
||||
return false;
|
||||
|
||||
/* If we have an adjacent container and nothing is fullscreen, we consider it. */
|
||||
if (fullscreen == NULL)
|
||||
return true;
|
||||
|
||||
/* For fullscreen containers, only consider the adjacent container if it is also fullscreen. */
|
||||
return con_has_parent(con, fullscreen) && con_has_parent(second, fullscreen);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the configured gaps for this workspace based on the workspace name,
|
||||
* number, and configured workspace gap assignments.
|
||||
*/
|
||||
gaps_t gaps_for_workspace(Con *ws) {
|
||||
gaps_t gaps = (gaps_t){0, 0, 0, 0, 0};
|
||||
gaps_mask_t mask = 0;
|
||||
struct Workspace_Assignment *assignment;
|
||||
TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
|
||||
if (strcmp(assignment->name, ws->name) == 0) {
|
||||
gaps = assignment->gaps;
|
||||
mask = assignment->gaps_mask;
|
||||
break;
|
||||
} else if (ws->num != -1 && name_is_digits(assignment->name) && ws_name_to_number(assignment->name) == ws->num) {
|
||||
gaps = assignment->gaps;
|
||||
mask = assignment->gaps_mask;
|
||||
}
|
||||
}
|
||||
if (mask == 0) {
|
||||
return gaps;
|
||||
}
|
||||
|
||||
if (mask & GAPS_INNER) {
|
||||
gaps.inner -= config.gaps.inner;
|
||||
}
|
||||
if (mask & GAPS_TOP) {
|
||||
gaps.top -= config.gaps.top;
|
||||
}
|
||||
if (mask & GAPS_RIGHT) {
|
||||
gaps.right -= config.gaps.right;
|
||||
}
|
||||
if (mask & GAPS_BOTTOM) {
|
||||
gaps.bottom -= config.gaps.bottom;
|
||||
}
|
||||
if (mask & GAPS_LEFT) {
|
||||
gaps.left -= config.gaps.left;
|
||||
}
|
||||
|
||||
return gaps;
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-applies all workspace gap assignments to existing workspaces after
|
||||
* reloading the configuration file.
|
||||
*
|
||||
*/
|
||||
void gaps_reapply_workspace_assignments(void) {
|
||||
Con *output, *workspace = NULL;
|
||||
TAILQ_FOREACH (output, &(croot->nodes_head), nodes) {
|
||||
Con *content = output_get_content(output);
|
||||
TAILQ_FOREACH (workspace, &(content->nodes_head), nodes) {
|
||||
DLOG("updating gap assignments for workspace %s\n", workspace->name);
|
||||
workspace->gaps = gaps_for_workspace(workspace);
|
||||
}
|
||||
}
|
||||
}
|
@ -214,18 +214,30 @@ static void handle_motion_notify(xcb_motion_notify_event_t *event) {
|
||||
return;
|
||||
|
||||
/* see over which rect the user is */
|
||||
Con *current;
|
||||
TAILQ_FOREACH_REVERSE (current, &(con->nodes_head), nodes_head, nodes) {
|
||||
if (!rect_contains(current->deco_rect, event->event_x, event->event_y))
|
||||
continue;
|
||||
if (con->window != NULL) {
|
||||
if (rect_contains(con->deco_rect, event->event_x, event->event_y)) {
|
||||
/* We found the rect, let’s see if this window is focused */
|
||||
if (TAILQ_FIRST(&(con->parent->focus_head)) == con)
|
||||
return;
|
||||
|
||||
/* We found the rect, let’s see if this window is focused */
|
||||
if (TAILQ_FIRST(&(con->focus_head)) == current)
|
||||
con_focus(con);
|
||||
x_push_changes(croot);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Con *current;
|
||||
TAILQ_FOREACH_REVERSE (current, &(con->nodes_head), nodes_head, nodes) {
|
||||
if (!rect_contains(current->deco_rect, event->event_x, event->event_y))
|
||||
continue;
|
||||
|
||||
con_focus(current);
|
||||
x_push_changes(croot);
|
||||
return;
|
||||
/* We found the rect, let’s see if this window is focused */
|
||||
if (TAILQ_FIRST(&(con->focus_head)) == current)
|
||||
return;
|
||||
|
||||
con_focus(current);
|
||||
x_push_changes(croot);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -318,15 +330,9 @@ static void handle_configure_request(xcb_configure_request_event_t *event) {
|
||||
Con *fullscreen = con_get_fullscreen_covering_ws(workspace);
|
||||
|
||||
if (fullscreen != con && con_is_floating(con) && con_is_leaf(con)) {
|
||||
/* find the height for the decorations */
|
||||
int deco_height = con->deco_rect.height;
|
||||
/* we actually need to apply the size/position changes to the *parent*
|
||||
* container */
|
||||
Rect bsr = con_border_style_rect(con);
|
||||
if (con->border_style == BS_NORMAL) {
|
||||
bsr.y += deco_height;
|
||||
bsr.height -= deco_height;
|
||||
}
|
||||
Con *floatingcon = con->parent;
|
||||
Rect newrect = floatingcon->rect;
|
||||
|
||||
@ -1187,9 +1193,9 @@ static bool handle_machine_change(Con *con, xcb_get_property_reply_t *prop) {
|
||||
*/
|
||||
static bool handle_motif_hints_change(Con *con, xcb_get_property_reply_t *prop) {
|
||||
border_style_t motif_border_style;
|
||||
window_update_motif_hints(con->window, prop, &motif_border_style);
|
||||
bool has_mwm_hints = window_update_motif_hints(con->window, prop, &motif_border_style);
|
||||
|
||||
if (motif_border_style != con->border_style && motif_border_style != BS_NORMAL) {
|
||||
if (has_mwm_hints && motif_border_style != con->border_style) {
|
||||
DLOG("Update border style of con %p to %d\n", con, motif_border_style);
|
||||
con_set_border_style(con, motif_border_style, con->current_border_width);
|
||||
|
||||
|
53
src/ipc.c
53
src/ipc.c
@ -245,6 +245,28 @@ static void dump_rect(yajl_gen gen, const char *name, Rect r) {
|
||||
y(map_close);
|
||||
}
|
||||
|
||||
static void dump_gaps(yajl_gen gen, const char *name, gaps_t gaps) {
|
||||
ystr(name);
|
||||
y(map_open);
|
||||
ystr("inner");
|
||||
y(integer, gaps.inner);
|
||||
|
||||
// TODO: the i3ipc Python modules recognize gaps, but only inner/outer
|
||||
// This is currently here to preserve compatibility with that
|
||||
ystr("outer");
|
||||
y(integer, gaps.top);
|
||||
|
||||
ystr("top");
|
||||
y(integer, gaps.top);
|
||||
ystr("right");
|
||||
y(integer, gaps.right);
|
||||
ystr("bottom");
|
||||
y(integer, gaps.bottom);
|
||||
ystr("left");
|
||||
y(integer, gaps.left);
|
||||
y(map_close);
|
||||
}
|
||||
|
||||
static void dump_event_state_mask(yajl_gen gen, Binding *bind) {
|
||||
y(array_open);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
@ -481,7 +503,15 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
|
||||
y(integer, con->current_border_width);
|
||||
|
||||
dump_rect(gen, "rect", con->rect);
|
||||
dump_rect(gen, "deco_rect", con->deco_rect);
|
||||
if (con_draw_decoration_into_frame(con)) {
|
||||
Rect simulated_deco_rect = con->deco_rect;
|
||||
simulated_deco_rect.x = con->rect.x - con->parent->rect.x;
|
||||
simulated_deco_rect.y = con->rect.y - con->parent->rect.y;
|
||||
dump_rect(gen, "deco_rect", simulated_deco_rect);
|
||||
dump_rect(gen, "actual_deco_rect", con->deco_rect);
|
||||
} else {
|
||||
dump_rect(gen, "deco_rect", con->deco_rect);
|
||||
}
|
||||
dump_rect(gen, "window_rect", con->window_rect);
|
||||
dump_rect(gen, "geometry", con->geometry);
|
||||
|
||||
@ -504,6 +534,8 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) {
|
||||
if (con->type == CT_WORKSPACE) {
|
||||
ystr("num");
|
||||
y(integer, con->num);
|
||||
|
||||
dump_gaps(gen, "gaps", con->gaps);
|
||||
}
|
||||
|
||||
ystr("window");
|
||||
@ -707,7 +739,7 @@ static void dump_bar_bindings(yajl_gen gen, Barconfig *config) {
|
||||
|
||||
static char *canonicalize_output_name(char *name) {
|
||||
/* Do not canonicalize special output names. */
|
||||
if (strcasecmp(name, "primary") == 0) {
|
||||
if (strcasecmp(name, "primary") == 0 || strcasecmp(name, "nonprimary") == 0) {
|
||||
return name;
|
||||
}
|
||||
Output *output = get_output_by_name(name, false);
|
||||
@ -795,8 +827,16 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) {
|
||||
ystr("top");
|
||||
|
||||
YSTR_IF_SET(status_command);
|
||||
YSTR_IF_SET(workspace_command);
|
||||
YSTR_IF_SET(font);
|
||||
|
||||
if (config->bar_height) {
|
||||
ystr("bar_height");
|
||||
y(integer, config->bar_height);
|
||||
}
|
||||
|
||||
dump_rect(gen, "padding", config->padding);
|
||||
|
||||
if (config->separator_symbol) {
|
||||
ystr("separator_symbol");
|
||||
ystr(config->separator_symbol);
|
||||
@ -1639,7 +1679,7 @@ void ipc_send_barconfig_update_event(Barconfig *barconfig) {
|
||||
/*
|
||||
* For the binding events, we send the serialized binding struct.
|
||||
*/
|
||||
void ipc_send_binding_event(const char *event_type, Binding *bind) {
|
||||
void ipc_send_binding_event(const char *event_type, Binding *bind, const char *modename) {
|
||||
DLOG("Issue IPC binding %s event (sym = %s, code = %d)\n", event_type, bind->symbol, bind->keycode);
|
||||
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
@ -1651,6 +1691,13 @@ void ipc_send_binding_event(const char *event_type, Binding *bind) {
|
||||
ystr("change");
|
||||
ystr(event_type);
|
||||
|
||||
ystr("mode");
|
||||
if (modename == NULL) {
|
||||
ystr("default");
|
||||
} else {
|
||||
ystr(modename);
|
||||
}
|
||||
|
||||
ystr("binding");
|
||||
dump_binding(gen, bind);
|
||||
|
||||
|
@ -20,8 +20,10 @@ static char *last_key;
|
||||
static int incomplete;
|
||||
static Con *json_node;
|
||||
static Con *to_focus;
|
||||
static bool parsing_gaps;
|
||||
static bool parsing_swallows;
|
||||
static bool parsing_rect;
|
||||
static bool parsing_actual_deco_rect;
|
||||
static bool parsing_deco_rect;
|
||||
static bool parsing_window_rect;
|
||||
static bool parsing_geometry;
|
||||
@ -60,7 +62,12 @@ static int json_start_map(void *ctx) {
|
||||
TAILQ_INSERT_TAIL(&(json_node->swallow_head), current_swallow, matches);
|
||||
swallow_is_empty = true;
|
||||
} else {
|
||||
if (!parsing_rect && !parsing_deco_rect && !parsing_window_rect && !parsing_geometry) {
|
||||
if (!parsing_rect &&
|
||||
!parsing_actual_deco_rect &&
|
||||
!parsing_deco_rect &&
|
||||
!parsing_window_rect &&
|
||||
!parsing_geometry &&
|
||||
!parsing_gaps) {
|
||||
if (last_key && strcasecmp(last_key, "floating_nodes") == 0) {
|
||||
DLOG("New floating_node\n");
|
||||
Con *ws = con_get_workspace(json_node);
|
||||
@ -84,7 +91,13 @@ static int json_start_map(void *ctx) {
|
||||
|
||||
static int json_end_map(void *ctx) {
|
||||
LOG("end of map\n");
|
||||
if (!parsing_swallows && !parsing_rect && !parsing_deco_rect && !parsing_window_rect && !parsing_geometry) {
|
||||
if (!parsing_swallows &&
|
||||
!parsing_rect &&
|
||||
!parsing_actual_deco_rect &&
|
||||
!parsing_deco_rect &&
|
||||
!parsing_window_rect &&
|
||||
!parsing_geometry &&
|
||||
!parsing_gaps) {
|
||||
/* Set a few default values to simplify manually crafted layout files. */
|
||||
if (json_node->layout == L_DEFAULT) {
|
||||
DLOG("Setting layout = L_SPLITH\n");
|
||||
@ -192,7 +205,9 @@ static int json_end_map(void *ctx) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
parsing_gaps = false;
|
||||
parsing_rect = false;
|
||||
parsing_actual_deco_rect = false;
|
||||
parsing_deco_rect = false;
|
||||
parsing_window_rect = false;
|
||||
parsing_geometry = false;
|
||||
@ -245,9 +260,15 @@ static int json_key(void *ctx, const unsigned char *val, size_t len) {
|
||||
if (strcasecmp(last_key, "swallows") == 0)
|
||||
parsing_swallows = true;
|
||||
|
||||
if (strcasecmp(last_key, "gaps") == 0)
|
||||
parsing_gaps = true;
|
||||
|
||||
if (strcasecmp(last_key, "rect") == 0)
|
||||
parsing_rect = true;
|
||||
|
||||
if (strcasecmp(last_key, "actual_deco_rect") == 0)
|
||||
parsing_actual_deco_rect = true;
|
||||
|
||||
if (strcasecmp(last_key, "deco_rect") == 0)
|
||||
parsing_deco_rect = true;
|
||||
|
||||
@ -506,6 +527,18 @@ static int json_int(void *ctx, long long val) {
|
||||
swallow_is_empty = false;
|
||||
}
|
||||
}
|
||||
if (parsing_gaps) {
|
||||
if (strcasecmp(last_key, "inner") == 0)
|
||||
json_node->gaps.inner = val;
|
||||
else if (strcasecmp(last_key, "top") == 0)
|
||||
json_node->gaps.top = val;
|
||||
else if (strcasecmp(last_key, "right") == 0)
|
||||
json_node->gaps.right = val;
|
||||
else if (strcasecmp(last_key, "bottom") == 0)
|
||||
json_node->gaps.bottom = val;
|
||||
else if (strcasecmp(last_key, "left") == 0)
|
||||
json_node->gaps.left = val;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
@ -653,9 +686,11 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm
|
||||
yajl_config(hand, yajl_dont_validate_strings, true);
|
||||
json_node = con;
|
||||
to_focus = NULL;
|
||||
parsing_gaps = false;
|
||||
incomplete = 0;
|
||||
parsing_swallows = false;
|
||||
parsing_rect = false;
|
||||
parsing_actual_deco_rect = false;
|
||||
parsing_deco_rect = false;
|
||||
parsing_window_rect = false;
|
||||
parsing_geometry = false;
|
||||
|
@ -137,7 +137,11 @@ void open_logbuffer(void) {
|
||||
* For 512 MiB of RAM this will lead to a 5 MiB log buffer.
|
||||
* At the moment (2011-12-10), no testcase leads to an i3 log
|
||||
* of more than ~ 600 KiB. */
|
||||
logbuffer_size = min(physical_mem_bytes * 0.01, shmlog_size);
|
||||
logbuffer_size = shmlog_size;
|
||||
if (physical_mem_bytes * 0.01 < (long long)shmlog_size) {
|
||||
logbuffer_size = physical_mem_bytes * 0.01;
|
||||
}
|
||||
|
||||
#if defined(__FreeBSD__)
|
||||
sasprintf(&shmlogname, "/tmp/i3-log-%d", getpid());
|
||||
#else
|
||||
|
27
src/main.c
27
src/main.c
@ -1002,10 +1002,11 @@ int main(int argc, char *argv[]) {
|
||||
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);
|
||||
struct ev_io *log_io = NULL;
|
||||
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));
|
||||
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);
|
||||
}
|
||||
@ -1013,12 +1014,13 @@ int main(int argc, char *argv[]) {
|
||||
/* 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. */
|
||||
struct ev_io *socket_ipc_io = NULL;
|
||||
listen_fds = sd_listen_fds(0);
|
||||
if (listen_fds < 0)
|
||||
if (listen_fds < 0) {
|
||||
ELOG("socket activation: Error in sd_listen_fds\n");
|
||||
else if (listen_fds == 0)
|
||||
} else if (listen_fds == 0) {
|
||||
DLOG("socket activation: no sockets passed\n");
|
||||
else {
|
||||
} else {
|
||||
int flags;
|
||||
for (int fd = SD_LISTEN_FDS_START;
|
||||
fd < (SD_LISTEN_FDS_START + listen_fds);
|
||||
@ -1033,9 +1035,9 @@ int main(int argc, char *argv[]) {
|
||||
ELOG("Could not disable FD_CLOEXEC on fd %d\n", fd);
|
||||
}
|
||||
|
||||
struct ev_io *ipc_io = scalloc(1, sizeof(struct ev_io));
|
||||
ev_io_init(ipc_io, ipc_new_client, fd, EV_READ);
|
||||
ev_io_start(main_loop, ipc_io);
|
||||
socket_ipc_io = scalloc(1, sizeof(struct ev_io));
|
||||
ev_io_init(socket_ipc_io, ipc_new_client, fd, EV_READ);
|
||||
ev_io_start(main_loop, socket_ipc_io);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1197,5 +1199,16 @@ int main(int argc, char *argv[]) {
|
||||
atexit(i3_exit);
|
||||
|
||||
sd_notify(1, "READY=1");
|
||||
|
||||
if (config.empty_workspaces) {
|
||||
workspace_init();
|
||||
}
|
||||
|
||||
ev_loop(main_loop, 0);
|
||||
|
||||
/* Free these heap allocations just to satisfy LeakSanitizer. */
|
||||
FREE(ipc_io);
|
||||
FREE(socket_ipc_io);
|
||||
FREE(log_io);
|
||||
FREE(xcb_watcher);
|
||||
}
|
||||
|
24
src/manage.c
24
src/manage.c
@ -214,8 +214,8 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||
window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL));
|
||||
bool urgency_hint;
|
||||
window_update_hints(cwindow, xcb_get_property_reply(conn, wm_hints_cookie, NULL), &urgency_hint);
|
||||
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);
|
||||
border_style_t motif_border_style;
|
||||
bool has_mwm_hints = 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);
|
||||
@ -511,7 +511,14 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||
if (nc->geometry.width == 0)
|
||||
nc->geometry = (Rect){geom->x, geom->y, geom->width, geom->height};
|
||||
|
||||
if (motif_border_style != BS_NORMAL) {
|
||||
if (want_floating) {
|
||||
DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height);
|
||||
if (floating_enable(nc, true)) {
|
||||
nc->floating = FLOATING_AUTO_ON;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_mwm_hints) {
|
||||
DLOG("MOTIF_WM_HINTS specifies decorations (border_style = %d)\n", motif_border_style);
|
||||
if (want_floating) {
|
||||
con_set_border_style(nc, motif_border_style, config.default_floating_border_width);
|
||||
@ -520,17 +527,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||
}
|
||||
}
|
||||
|
||||
if (want_floating) {
|
||||
DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height);
|
||||
/* automatically set the border to the default value if a motif border
|
||||
* was not specified */
|
||||
bool automatic_border = (motif_border_style == BS_NORMAL);
|
||||
|
||||
if (floating_enable(nc, automatic_border)) {
|
||||
nc->floating = FLOATING_AUTO_ON;
|
||||
}
|
||||
}
|
||||
|
||||
/* explicitly set the border width to the default */
|
||||
if (nc->current_border_width == -1) {
|
||||
nc->current_border_width = (want_floating ? config.default_floating_border_width : config.default_border_width);
|
||||
|
@ -48,8 +48,10 @@ static Output *get_output_by_id(xcb_randr_output_t id) {
|
||||
*
|
||||
*/
|
||||
Output *get_output_by_name(const char *name, const bool require_active) {
|
||||
const bool get_primary = (strcasecmp("primary", name) == 0);
|
||||
const bool get_non_primary = (strcasecmp("nonprimary", name) == 0);
|
||||
|
||||
Output *output;
|
||||
bool get_primary = (strcasecmp("primary", name) == 0);
|
||||
TAILQ_FOREACH (output, &outputs, outputs) {
|
||||
if (require_active && !output->active) {
|
||||
continue;
|
||||
@ -57,6 +59,9 @@ Output *get_output_by_name(const char *name, const bool require_active) {
|
||||
if (output->primary && get_primary) {
|
||||
return output;
|
||||
}
|
||||
if (!output->primary && get_non_primary) {
|
||||
return output;
|
||||
}
|
||||
struct output_name *output_name;
|
||||
SLIST_FOREACH (output_name, &output->names_head, names) {
|
||||
if (strcasecmp(output_name->name, name) == 0) {
|
||||
|
79
src/render.c
79
src/render.c
@ -49,6 +49,41 @@ void render_con(Con *con) {
|
||||
DLOG("Rendering node %p / %s / layout %d / children %d\n", con, con->name,
|
||||
con->layout, params.children);
|
||||
|
||||
if (con->type == CT_WORKSPACE) {
|
||||
gaps_t gaps = calculate_effective_gaps(con);
|
||||
Rect inset = (Rect){
|
||||
gaps.left,
|
||||
gaps.top,
|
||||
-(gaps.left + gaps.right),
|
||||
-(gaps.top + gaps.bottom),
|
||||
};
|
||||
con->rect = rect_add(con->rect, inset);
|
||||
params.rect = rect_add(params.rect, inset);
|
||||
params.x += gaps.left;
|
||||
params.y += gaps.top;
|
||||
}
|
||||
|
||||
if (gaps_should_inset_con(con, params.children)) {
|
||||
gaps_t gaps = calculate_effective_gaps(con);
|
||||
Rect inset = (Rect){
|
||||
gaps_has_adjacent_container(con, D_LEFT) ? gaps.inner / 2 : gaps.inner,
|
||||
gaps_has_adjacent_container(con, D_UP) ? gaps.inner / 2 : gaps.inner,
|
||||
gaps_has_adjacent_container(con, D_RIGHT) ? -(gaps.inner / 2) : -gaps.inner,
|
||||
gaps_has_adjacent_container(con, D_DOWN) ? -(gaps.inner / 2) : -gaps.inner,
|
||||
};
|
||||
inset.width -= inset.x;
|
||||
inset.height -= inset.y;
|
||||
|
||||
if (con->fullscreen_mode == CF_NONE) {
|
||||
params.rect = rect_add(params.rect, inset);
|
||||
con->rect = rect_add(con->rect, inset);
|
||||
}
|
||||
inset.height = 0;
|
||||
|
||||
params.x = con->rect.x;
|
||||
params.y = con->rect.y;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
con->mapped = true;
|
||||
|
||||
@ -56,17 +91,27 @@ void render_con(Con *con) {
|
||||
if (con->window) {
|
||||
/* depending on the border style, the rect of the child window
|
||||
* needs to be smaller */
|
||||
Rect *inset = &(con->window_rect);
|
||||
*inset = (Rect){0, 0, con->rect.width, con->rect.height};
|
||||
Rect inset = (Rect){
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = con->rect.width,
|
||||
.height = con->rect.height,
|
||||
};
|
||||
if (con->fullscreen_mode == CF_NONE) {
|
||||
*inset = rect_add(*inset, con_border_style_rect(con));
|
||||
DLOG("deco_rect.height = %d\n", con->deco_rect.height);
|
||||
Rect bsr = con_border_style_rect(con);
|
||||
DLOG("bsr at %dx%d with size %dx%d\n",
|
||||
bsr.x, bsr.y, bsr.width, bsr.height);
|
||||
|
||||
inset = rect_add(inset, bsr);
|
||||
}
|
||||
|
||||
/* Obey x11 border */
|
||||
inset->width -= (2 * con->border_width);
|
||||
inset->height -= (2 * con->border_width);
|
||||
inset.width -= (2 * con->border_width);
|
||||
inset.height -= (2 * con->border_width);
|
||||
|
||||
*inset = rect_sanitize_dimensions(*inset);
|
||||
inset = rect_sanitize_dimensions(inset);
|
||||
con->window_rect = inset;
|
||||
|
||||
/* NB: We used to respect resize increment size hints for tiling
|
||||
* windows up until commit 0db93d9 here. However, since all terminal
|
||||
@ -74,7 +119,8 @@ void render_con(Con *con) {
|
||||
* can (by providing their fake-transparency or background color), this
|
||||
* code was removed. See also https://bugs.i3wm.org/540 */
|
||||
|
||||
DLOG("child will be at %dx%d with size %dx%d\n", inset->x, inset->y, inset->width, inset->height);
|
||||
DLOG("child will be at %dx%d with size %dx%d\n",
|
||||
inset.x, inset.y, inset.width, inset.height);
|
||||
}
|
||||
|
||||
/* Check for fullscreen nodes */
|
||||
@ -131,6 +177,18 @@ void render_con(Con *con) {
|
||||
child->rect.x, child->rect.y, child->rect.width, child->rect.height);
|
||||
x_raise_con(child);
|
||||
render_con(child);
|
||||
|
||||
/* render_con_split() sets the deco_rect width based on the rect
|
||||
* width, but the render_con() call updates the rect width by
|
||||
* applying gaps, so we need to update deco_rect. */
|
||||
if (con->layout == L_SPLITH || con->layout == L_SPLITV) {
|
||||
if (con_is_leaf(child)) {
|
||||
if (child->border_style == BS_NORMAL) {
|
||||
child->deco_rect.width = child->rect.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
@ -353,11 +411,8 @@ static void render_con_split(Con *con, Con *child, render_params *p, int i) {
|
||||
if (con_is_leaf(child)) {
|
||||
if (child->border_style == BS_NORMAL) {
|
||||
/* TODO: make a function for relative coords? */
|
||||
child->deco_rect.x = child->rect.x - con->rect.x;
|
||||
child->deco_rect.y = child->rect.y - con->rect.y;
|
||||
|
||||
child->rect.y += p->deco_height;
|
||||
child->rect.height -= p->deco_height;
|
||||
child->deco_rect.x = 0;
|
||||
child->deco_rect.y = 0;
|
||||
|
||||
child->deco_rect.width = child->rect.width;
|
||||
child->deco_rect.height = p->deco_height;
|
||||
|
27
src/resize.c
27
src/resize.c
@ -173,6 +173,8 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
|
||||
bool use_threshold) {
|
||||
Con *output = con_get_output(first);
|
||||
DLOG("x = %d, width = %d\n", output->rect.x, output->rect.width);
|
||||
DLOG("first = %p / %s\n", first, first->name);
|
||||
DLOG("second = %p / %s\n", second, second->name);
|
||||
|
||||
x_mask_event_mask(~XCB_EVENT_MASK_ENTER_WINDOW);
|
||||
xcb_flush(conn);
|
||||
@ -197,14 +199,31 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
|
||||
Rect helprect;
|
||||
helprect.x = second->rect.x;
|
||||
helprect.y = second->rect.y;
|
||||
/* Resizes might happen between a split container and a leaf
|
||||
* container. Because gaps happen *within* a split container, we need to
|
||||
* work with (any) leaf window inside the split, so descend focused. */
|
||||
Con *ffirst = con_descend_focused(first);
|
||||
Con *fsecond = con_descend_focused(second);
|
||||
if (orientation == HORIZ) {
|
||||
helprect.width = logical_px(2);
|
||||
helprect.height = second->rect.height;
|
||||
initial_position = second->rect.x;
|
||||
const uint32_t ffirst_right = ffirst->rect.x + ffirst->rect.width;
|
||||
const uint32_t gap = (fsecond->rect.x - ffirst_right);
|
||||
const uint32_t middle = fsecond->rect.x - (gap / 2);
|
||||
DLOG("ffirst->rect = {.x = %u, .width = %u}\n", ffirst->rect.x, ffirst->rect.width);
|
||||
DLOG("fsecond->rect = {.x = %u, .width = %u}\n", fsecond->rect.x, fsecond->rect.width);
|
||||
DLOG("gap = %u, middle = %u\n", gap, middle);
|
||||
initial_position = middle;
|
||||
} else {
|
||||
helprect.width = second->rect.width;
|
||||
helprect.height = logical_px(2);
|
||||
initial_position = second->rect.y;
|
||||
const uint32_t ffirst_bottom = ffirst->rect.y + ffirst->rect.height;
|
||||
const uint32_t gap = (fsecond->rect.y - ffirst_bottom);
|
||||
const uint32_t middle = fsecond->rect.y - (gap / 2);
|
||||
DLOG("ffirst->rect = {.y = %u, .height = %u}\n", ffirst->rect.y, ffirst->rect.height);
|
||||
DLOG("fsecond->rect = {.y = %u, .height = %u}\n", fsecond->rect.y, fsecond->rect.height);
|
||||
DLOG("gap = %u, middle = %u\n", gap, middle);
|
||||
initial_position = middle;
|
||||
}
|
||||
|
||||
mask = XCB_CW_BACK_PIXEL;
|
||||
@ -220,10 +239,10 @@ void resize_graphical_handler(Con *first, Con *second, orientation_t orientation
|
||||
xcb_map_window(conn, helpwin);
|
||||
if (orientation == HORIZ) {
|
||||
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
|
||||
second->rect.x, event->root_y);
|
||||
initial_position, event->root_y);
|
||||
} else {
|
||||
xcb_warp_pointer(conn, XCB_NONE, event->root, 0, 0, 0, 0,
|
||||
event->root_x, second->rect.y);
|
||||
event->root_x, initial_position);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -340,6 +340,11 @@ int sd_notify(int unset_environment, const char *state) {
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (strlen(e) > sizeof(sockaddr.un.sun_path)) {
|
||||
r = -EINVAL;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if ((fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0)) < 0) {
|
||||
r = -errno;
|
||||
goto finish;
|
||||
@ -347,7 +352,7 @@ int sd_notify(int unset_environment, const char *state) {
|
||||
|
||||
memset(&sockaddr, 0, sizeof(sockaddr));
|
||||
sockaddr.sa.sa_family = AF_UNIX;
|
||||
strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path));
|
||||
strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path) - 1);
|
||||
|
||||
if (sockaddr.un.sun_path[0] == '@')
|
||||
sockaddr.un.sun_path[0] = 0;
|
||||
|
@ -10,17 +10,60 @@
|
||||
#include "all.h"
|
||||
static xcb_window_t create_drop_indicator(Rect rect);
|
||||
|
||||
static bool is_tiling_drop_target(Con *con) {
|
||||
if (!con_has_managed_window(con) ||
|
||||
con_is_floating(con) ||
|
||||
con_is_hidden(con)) {
|
||||
return false;
|
||||
}
|
||||
Con *ws = con_get_workspace(con);
|
||||
if (con_is_internal(ws)) {
|
||||
/* Skip containers on i3-internal containers like the scratchpad, which are
|
||||
technically visible on their pseudo-output. */
|
||||
return false;
|
||||
}
|
||||
if (!workspace_is_visible(ws)) {
|
||||
return false;
|
||||
}
|
||||
Con *fs = con_get_fullscreen_covering_ws(ws);
|
||||
if (fs != NULL && fs != con) {
|
||||
/* Workspace is visible, but con is not visible because some other
|
||||
container is in fullscreen. */
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Includes decoration (container title) to the container's rect. This way we
|
||||
* can find the correct drop target if the mouse is on a container's
|
||||
* decoration.
|
||||
* Returns whether there currently are any drop targets.
|
||||
* Used to only initiate a drag when there is something to drop onto.
|
||||
*
|
||||
*/
|
||||
static Rect con_rect_plus_deco_height(Con *con) {
|
||||
Rect rect = con->rect;
|
||||
rect.height += con->deco_rect.height;
|
||||
rect.y -= con->deco_rect.height;
|
||||
return rect;
|
||||
bool has_drop_targets(void) {
|
||||
int drop_targets = 0;
|
||||
Con *con;
|
||||
TAILQ_FOREACH (con, &all_cons, all_cons) {
|
||||
if (!is_tiling_drop_target(con)) {
|
||||
continue;
|
||||
}
|
||||
drop_targets++;
|
||||
}
|
||||
|
||||
/* In addition to tiling containers themselves, an visible but empty
|
||||
* workspace (in a multi-monitor scenario) also is a drop target. */
|
||||
Con *output;
|
||||
TAILQ_FOREACH (output, &(croot->focus_head), focused) {
|
||||
if (con_is_internal(output)) {
|
||||
continue;
|
||||
}
|
||||
Con *visible_ws = NULL;
|
||||
GREP_FIRST(visible_ws, output_get_content(output), workspace_is_visible(child));
|
||||
if (visible_ws != NULL && con_num_children(visible_ws) == 0) {
|
||||
drop_targets++;
|
||||
}
|
||||
}
|
||||
|
||||
return drop_targets > 1;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -30,19 +73,14 @@ static Rect con_rect_plus_deco_height(Con *con) {
|
||||
static Con *find_drop_target(uint32_t x, uint32_t y) {
|
||||
Con *con;
|
||||
TAILQ_FOREACH (con, &all_cons, all_cons) {
|
||||
Rect rect = con_rect_plus_deco_height(con);
|
||||
|
||||
if (rect_contains(rect, x, y) &&
|
||||
con_has_managed_window(con) &&
|
||||
!con_is_floating(con) &&
|
||||
!con_is_hidden(con)) {
|
||||
Con *ws = con_get_workspace(con);
|
||||
if (!workspace_is_visible(ws)) {
|
||||
continue;
|
||||
}
|
||||
Con *fs = con_get_fullscreen_covering_ws(ws);
|
||||
return fs ? fs : con;
|
||||
Rect rect = con->rect;
|
||||
if (!rect_contains(rect, x, y) ||
|
||||
!is_tiling_drop_target(con)) {
|
||||
continue;
|
||||
}
|
||||
Con *ws = con_get_workspace(con);
|
||||
Con *fs = con_get_fullscreen_covering_ws(ws);
|
||||
return fs ? fs : con;
|
||||
}
|
||||
|
||||
/* Couldn't find leaf container, get a workspace. */
|
||||
@ -131,7 +169,7 @@ DRAGGING_CB(drag_callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rect rect = con_rect_plus_deco_height(target);
|
||||
Rect rect = target->rect;
|
||||
|
||||
direction_t direction = 0;
|
||||
drop_type_t drop_type = DT_CENTER;
|
||||
@ -263,7 +301,7 @@ static xcb_window_t create_drop_indicator(Rect rect) {
|
||||
* Initiates a mouse drag operation on a tiled window.
|
||||
*
|
||||
*/
|
||||
void tiling_drag(Con *con, xcb_button_press_event_t *event) {
|
||||
void tiling_drag(Con *con, xcb_button_press_event_t *event, bool use_threshold) {
|
||||
DLOG("Start dragging tiled container: con = %p\n", con);
|
||||
bool set_focus = (con == focused);
|
||||
bool set_fs = con->fullscreen_mode != CF_NONE;
|
||||
@ -279,7 +317,7 @@ void tiling_drag(Con *con, xcb_button_press_event_t *event) {
|
||||
xcb_window_t indicator = 0;
|
||||
const struct callback_params params = {&indicator, &target, &direction, &drop_type};
|
||||
|
||||
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, BORDER_TOP, XCURSOR_CURSOR_MOVE, drag_callback, ¶ms);
|
||||
drag_result_t drag_result = drag_pointer(con, event, XCB_NONE, XCURSOR_CURSOR_MOVE, use_threshold, drag_callback, ¶ms);
|
||||
|
||||
/* Dragging is done. We don't need the indicator window any more. */
|
||||
xcb_destroy_window(conn, indicator);
|
||||
@ -374,6 +412,7 @@ void tiling_drag(Con *con, xcb_button_press_event_t *event) {
|
||||
con_enable_fullscreen(con, CF_OUTPUT);
|
||||
}
|
||||
if (set_focus) {
|
||||
workspace_show(con_get_workspace(con));
|
||||
con_focus(con);
|
||||
}
|
||||
tree_render();
|
||||
|
@ -470,7 +470,11 @@ static Con *get_tree_next_workspace(Con *con, direction_t direction) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Output *current_output = get_output_containing(con->rect.x, con->rect.y);
|
||||
// Use the center of the container instead of the left/top edges, to make
|
||||
// this work with negative gaps. See https://github.com/i3/i3/issues/5293
|
||||
const uint32_t x = con->rect.x + (con->rect.width / 2);
|
||||
const uint32_t y = con->rect.y + (con->rect.height / 2);
|
||||
Output *current_output = get_output_containing(x, y);
|
||||
if (!current_output) {
|
||||
return NULL;
|
||||
}
|
||||
|
86
src/window.c
86
src/window.c
@ -403,6 +403,49 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
|
||||
free(prop);
|
||||
}
|
||||
|
||||
/* See `man VendorShell' for more info, `XmNmwmDecorations' section:
|
||||
* https://linux.die.net/man/3/vendorshell
|
||||
* The following constants are adapted from <Xm/MwmUtil.h>.
|
||||
*/
|
||||
#define MWM_HINTS_FLAGS_FIELD 0
|
||||
#define MWM_HINTS_DECORATIONS_FIELD 2
|
||||
|
||||
#define MWM_HINTS_DECORATIONS (1 << 1)
|
||||
#define MWM_DECOR_ALL (1 << 0)
|
||||
#define MWM_DECOR_BORDER (1 << 1)
|
||||
#define MWM_DECOR_TITLE (1 << 3)
|
||||
|
||||
static border_style_t border_style_from_motif_value(uint32_t value) {
|
||||
if (value & MWM_DECOR_ALL) {
|
||||
/* If this value is set, all other flags set are exclusive:
|
||||
* MWM_DECOR_ALL
|
||||
* All decorations except those specified by other flag bits that are set
|
||||
*
|
||||
* We support these cases:
|
||||
* - No exceptions -> BS_NORMAL
|
||||
* - Title and no border (ignored) -> BS_NORMAL
|
||||
* - No title and no border -> BS_NONE
|
||||
* - No title and border -> BS_PIXEL
|
||||
* */
|
||||
|
||||
if (value & MWM_DECOR_TITLE) {
|
||||
if (value & MWM_DECOR_BORDER) {
|
||||
return BS_NONE;
|
||||
}
|
||||
|
||||
return BS_PIXEL;
|
||||
}
|
||||
|
||||
return BS_NORMAL;
|
||||
} else if (value & MWM_DECOR_TITLE) {
|
||||
return BS_NORMAL;
|
||||
} else if (value & MWM_DECOR_BORDER) {
|
||||
return BS_PIXEL;
|
||||
} else {
|
||||
return BS_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates the MOTIF_WM_HINTS. The container's border style should be set to
|
||||
* `motif_border_style' if border style is not BS_NORMAL.
|
||||
@ -415,27 +458,15 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur
|
||||
* it is still in use by popular widget toolkits such as GTK+ and Java AWT.
|
||||
*
|
||||
*/
|
||||
void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style) {
|
||||
/* This implementation simply mirrors Gnome's Metacity. Official
|
||||
* documentation of this hint is nowhere to be found.
|
||||
* For more information see:
|
||||
* https://people.gnome.org/~tthurman/docs/metacity/xprops_8h-source.html
|
||||
* https://stackoverflow.com/questions/13787553/detect-if-a-x11-window-has-decorations
|
||||
*/
|
||||
#define MWM_HINTS_FLAGS_FIELD 0
|
||||
#define MWM_HINTS_DECORATIONS_FIELD 2
|
||||
bool window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style) {
|
||||
if (prop == NULL) {
|
||||
return false;
|
||||
}
|
||||
assert(motif_border_style != NULL);
|
||||
|
||||
#define MWM_HINTS_DECORATIONS (1 << 1)
|
||||
#define MWM_DECOR_ALL (1 << 0)
|
||||
#define MWM_DECOR_BORDER (1 << 1)
|
||||
#define MWM_DECOR_TITLE (1 << 3)
|
||||
|
||||
if (motif_border_style != NULL)
|
||||
*motif_border_style = BS_NORMAL;
|
||||
|
||||
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
|
||||
if (xcb_get_property_value_length(prop) == 0) {
|
||||
FREE(prop);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The property consists of an array of 5 uint32_t's. The first value is a
|
||||
@ -449,18 +480,14 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
|
||||
* i.e. returns 32-bit data fields. */
|
||||
uint32_t *motif_hints = (uint32_t *)xcb_get_property_value(prop);
|
||||
|
||||
if (motif_border_style != NULL &&
|
||||
motif_hints[MWM_HINTS_FLAGS_FIELD] & MWM_HINTS_DECORATIONS) {
|
||||
if (motif_hints[MWM_HINTS_DECORATIONS_FIELD] & MWM_DECOR_ALL ||
|
||||
motif_hints[MWM_HINTS_DECORATIONS_FIELD] & MWM_DECOR_TITLE)
|
||||
*motif_border_style = BS_NORMAL;
|
||||
else if (motif_hints[MWM_HINTS_DECORATIONS_FIELD] & MWM_DECOR_BORDER)
|
||||
*motif_border_style = BS_PIXEL;
|
||||
else
|
||||
*motif_border_style = BS_NONE;
|
||||
if (motif_hints[MWM_HINTS_FLAGS_FIELD] & MWM_HINTS_DECORATIONS) {
|
||||
*motif_border_style = border_style_from_motif_value(motif_hints[MWM_HINTS_DECORATIONS_FIELD]);
|
||||
FREE(prop);
|
||||
return true;
|
||||
}
|
||||
|
||||
FREE(prop);
|
||||
return false;
|
||||
}
|
||||
|
||||
#undef MWM_HINTS_FLAGS_FIELD
|
||||
#undef MWM_HINTS_DECORATIONS_FIELD
|
||||
@ -468,7 +495,6 @@ void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo
|
||||
#undef MWM_DECOR_ALL
|
||||
#undef MWM_DECOR_BORDER
|
||||
#undef MWM_DECOR_TITLE
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates the WM_CLIENT_MACHINE
|
||||
|
@ -85,6 +85,10 @@ Con *get_assigned_output(const char *name, long parsed_num) {
|
||||
Con *output = NULL;
|
||||
struct Workspace_Assignment *assignment;
|
||||
TAILQ_FOREACH (assignment, &ws_assignments, ws_assignments) {
|
||||
if (assignment->output == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name && strcmp(assignment->name, name) == 0) {
|
||||
DLOG("Found workspace name=\"%s\" assignment to output \"%s\"\n",
|
||||
name, assignment->output);
|
||||
@ -156,6 +160,7 @@ Con *workspace_get(const char *num) {
|
||||
workspace->workspace_layout = config.default_layout;
|
||||
workspace->num = parsed_num;
|
||||
workspace->type = CT_WORKSPACE;
|
||||
workspace->gaps = gaps_for_workspace(workspace);
|
||||
|
||||
con_attach(workspace, output_get_content(output), false);
|
||||
_workspace_apply_default_orientation(workspace);
|
||||
@ -281,6 +286,7 @@ Con *create_workspace_on_output(Output *output, Con *content) {
|
||||
ws->num = c;
|
||||
sasprintf(&(ws->name), "%d", c);
|
||||
}
|
||||
|
||||
con_attach(ws, content, false);
|
||||
|
||||
char *name;
|
||||
@ -288,6 +294,8 @@ Con *create_workspace_on_output(Output *output, Con *content) {
|
||||
x_set_name(ws, name);
|
||||
free(name);
|
||||
|
||||
ws->gaps = gaps_for_workspace(ws);
|
||||
|
||||
ws->fullscreen_mode = CF_OUTPUT;
|
||||
|
||||
ws->workspace_layout = config.default_layout;
|
||||
@ -506,8 +514,9 @@ void workspace_show(Con *workspace) {
|
||||
* client, which will clear the urgency flag too early. Also, there is no
|
||||
* way for con_focus() to know about when to clear urgency immediately and
|
||||
* when to defer it. */
|
||||
if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
|
||||
/* check if this workspace is currently visible */
|
||||
|
||||
if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))
|
||||
&& !config.empty_workspaces) {
|
||||
if (!workspace_is_visible(old)) {
|
||||
LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
|
||||
yajl_gen gen = ipc_marshal_workspace_event("empty", old, NULL);
|
||||
@ -520,7 +529,6 @@ void workspace_show(Con *workspace) {
|
||||
|
||||
y(free);
|
||||
|
||||
/* Avoid calling output_push_sticky_windows later with a freed container. */
|
||||
if (old == old_focus) {
|
||||
old_focus = NULL;
|
||||
}
|
||||
@ -1065,3 +1073,19 @@ void workspace_move_to_output(Con *ws, Output *output) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void workspace_init(void)
|
||||
{
|
||||
int i = 0;
|
||||
char *c = binding_workspace_names[0];
|
||||
|
||||
|
||||
while (c != NULL) {
|
||||
DLOG("bwn: %s\n", c);
|
||||
workspace_get(c);
|
||||
i++;
|
||||
c = binding_workspace_names[i];
|
||||
}
|
||||
|
||||
//workspace_show_by_name(binding_workspace_names[0]);
|
||||
}
|
||||
|
62
src/x.c
62
src/x.c
@ -355,29 +355,27 @@ void x_window_kill(xcb_window_t window, kill_window_t kill_window) {
|
||||
free(event);
|
||||
}
|
||||
|
||||
static void x_draw_title_border(Con *con, struct deco_render_params *p) {
|
||||
assert(con->parent != NULL);
|
||||
|
||||
static void x_draw_title_border(Con *con, struct deco_render_params *p, surface_t *dest_surface) {
|
||||
Rect *dr = &(con->deco_rect);
|
||||
|
||||
/* Left */
|
||||
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
|
||||
draw_util_rectangle(dest_surface, p->color->border,
|
||||
dr->x, dr->y, 1, dr->height);
|
||||
|
||||
/* Right */
|
||||
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
|
||||
draw_util_rectangle(dest_surface, p->color->border,
|
||||
dr->x + dr->width - 1, dr->y, 1, dr->height);
|
||||
|
||||
/* Top */
|
||||
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
|
||||
draw_util_rectangle(dest_surface, p->color->border,
|
||||
dr->x, dr->y, dr->width, 1);
|
||||
|
||||
/* Bottom */
|
||||
draw_util_rectangle(&(con->parent->frame_buffer), p->color->border,
|
||||
draw_util_rectangle(dest_surface, p->color->border,
|
||||
dr->x, dr->y + dr->height - 1, dr->width, 1);
|
||||
}
|
||||
|
||||
static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p) {
|
||||
static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p, surface_t *dest_surface) {
|
||||
assert(con->parent != NULL);
|
||||
|
||||
Rect *dr = &(con->deco_rect);
|
||||
@ -389,7 +387,7 @@ static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p
|
||||
/* We actually only redraw the far right two pixels as that is the
|
||||
* distance we keep from the edge (not the entire border width).
|
||||
* Redrawing the entire border would cause text to be cut off. */
|
||||
draw_util_rectangle(&(con->parent->frame_buffer), p->color->background,
|
||||
draw_util_rectangle(dest_surface, p->color->background,
|
||||
dr->x + dr->width - 2 * logical_px(1),
|
||||
dr->y,
|
||||
2 * logical_px(1),
|
||||
@ -397,7 +395,7 @@ static void x_draw_decoration_after_title(Con *con, struct deco_render_params *p
|
||||
}
|
||||
|
||||
/* Redraw the border. */
|
||||
x_draw_title_border(con, p);
|
||||
x_draw_title_border(con, p, dest_surface);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -593,16 +591,24 @@ void x_draw_decoration(Con *con) {
|
||||
}
|
||||
}
|
||||
|
||||
surface_t *dest_surface = &(parent->frame_buffer);
|
||||
if (con_draw_decoration_into_frame(con)) {
|
||||
DLOG("using con->frame_buffer (for con->name=%s) as dest_surface\n", con->name);
|
||||
dest_surface = &(con->frame_buffer);
|
||||
} else {
|
||||
DLOG("sticking to parent->frame_buffer = %p\n", dest_surface);
|
||||
}
|
||||
DLOG("dest_surface %p is %d x %d (id=0x%08x)\n", dest_surface, dest_surface->width, dest_surface->height, dest_surface->id);
|
||||
|
||||
/* If the parent hasn't been set up yet, skip the decoration rendering
|
||||
* for now. */
|
||||
if (parent->frame_buffer.id == XCB_NONE)
|
||||
if (dest_surface->id == XCB_NONE)
|
||||
goto copy_pixmaps;
|
||||
|
||||
/* For the first child, we clear the parent pixmap to ensure there's no
|
||||
* garbage left on there. This is important to avoid tearing when using
|
||||
* transparency. */
|
||||
if (con == TAILQ_FIRST(&(con->parent->nodes_head))) {
|
||||
draw_util_clear_surface(&(con->parent->frame_buffer), COLOR_TRANSPARENT);
|
||||
FREE(con->parent->deco_render_params);
|
||||
}
|
||||
|
||||
@ -612,11 +618,13 @@ void x_draw_decoration(Con *con) {
|
||||
goto copy_pixmaps;
|
||||
|
||||
/* 4: paint the bar */
|
||||
draw_util_rectangle(&(parent->frame_buffer), p->color->background,
|
||||
DLOG("con->deco_rect = (x=%d, y=%d, w=%d, h=%d) for con->name=%s\n",
|
||||
con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height, con->name);
|
||||
draw_util_rectangle(dest_surface, p->color->background,
|
||||
con->deco_rect.x, con->deco_rect.y, con->deco_rect.width, con->deco_rect.height);
|
||||
|
||||
/* 5: draw title border */
|
||||
x_draw_title_border(con, p);
|
||||
x_draw_title_border(con, p, dest_surface);
|
||||
|
||||
/* 6: draw the icon and title */
|
||||
int text_offset_y = (con->deco_rect.height - config.font.height) / 2;
|
||||
@ -651,7 +659,7 @@ void x_draw_decoration(Con *con) {
|
||||
? title_padding
|
||||
: deco_width - mark_width - title_padding;
|
||||
|
||||
draw_util_text(mark, &(parent->frame_buffer),
|
||||
draw_util_text(mark, dest_surface,
|
||||
p->color->text, p->color->background,
|
||||
con->deco_rect.x + mark_offset_x,
|
||||
con->deco_rect.y + text_offset_y, mark_width);
|
||||
@ -723,9 +731,12 @@ void x_draw_decoration(Con *con) {
|
||||
/* 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;
|
||||
default:
|
||||
ELOG("BUG: invalid config.title_align value %d\n", config.title_align);
|
||||
return;
|
||||
}
|
||||
|
||||
draw_util_text(title, &(parent->frame_buffer),
|
||||
draw_util_text(title, dest_surface,
|
||||
p->color->text, p->color->background,
|
||||
con->deco_rect.x + title_offset_x,
|
||||
con->deco_rect.y + text_offset_y,
|
||||
@ -733,7 +744,7 @@ void x_draw_decoration(Con *con) {
|
||||
if (has_icon) {
|
||||
draw_util_image(
|
||||
win->icon,
|
||||
&(parent->frame_buffer),
|
||||
dest_surface,
|
||||
con->deco_rect.x + icon_offset_x,
|
||||
con->deco_rect.y + logical_px(1),
|
||||
icon_size,
|
||||
@ -744,7 +755,7 @@ void x_draw_decoration(Con *con) {
|
||||
I3STRING_FREE(title);
|
||||
}
|
||||
|
||||
x_draw_decoration_after_title(con, p);
|
||||
x_draw_decoration_after_title(con, p, dest_surface);
|
||||
copy_pixmaps:
|
||||
draw_util_copy_surface(&(con->frame_buffer), &(con->frame), 0, 0, 0, 0, con->rect.width, con->rect.height);
|
||||
}
|
||||
@ -891,7 +902,7 @@ void x_push_node(Con *con) {
|
||||
FREE(state->name);
|
||||
}
|
||||
|
||||
if (con->window == NULL) {
|
||||
if (con->window == NULL && (con->layout == L_STACKED || con->layout == L_TABBED)) {
|
||||
/* Calculate the height of all window decorations which will be drawn on to
|
||||
* this frame. */
|
||||
uint32_t max_y = 0, max_height = 0;
|
||||
@ -905,6 +916,9 @@ void x_push_node(Con *con) {
|
||||
rect.height = max_y + max_height;
|
||||
if (rect.height == 0)
|
||||
con->mapped = false;
|
||||
} else if (con->window == NULL) {
|
||||
/* not a stacked or tabbed split container */
|
||||
con->mapped = false;
|
||||
}
|
||||
|
||||
bool need_reshape = false;
|
||||
@ -949,10 +963,11 @@ void x_push_node(Con *con) {
|
||||
|
||||
/* The pixmap of a borderless leaf container will not be used except
|
||||
* for the titlebar in a stack or tabs (issue #1013). */
|
||||
bool is_pixmap_needed = (con->border_style != BS_NONE ||
|
||||
!con_is_leaf(con) ||
|
||||
con->parent->layout == L_STACKED ||
|
||||
con->parent->layout == L_TABBED);
|
||||
bool is_pixmap_needed = ((con_is_leaf(con) && con_border_style(con) != BS_NONE) ||
|
||||
con->layout == L_STACKED ||
|
||||
con->layout == L_TABBED);
|
||||
DLOG("Con %p (layout %d), is_pixmap_needed = %s, rect.height = %d\n",
|
||||
con, con->layout, is_pixmap_needed ? "yes" : "no", con->rect.height);
|
||||
|
||||
/* The root con and output cons will never require a pixmap. In particular for the
|
||||
* __i3 output, this will likely not work anyway because it might be ridiculously
|
||||
@ -999,6 +1014,7 @@ void x_push_node(Con *con) {
|
||||
int width = MAX((int32_t)rect.width, 1);
|
||||
int height = MAX((int32_t)rect.height, 1);
|
||||
|
||||
DLOG("creating %d x %d pixmap for con %p (con->frame_buffer.id = (pixmap_t)0x%08x) (con->frame.id (drawable_t)0x%08x)\n", width, height, con, con->frame_buffer.id, con->frame.id);
|
||||
xcb_create_pixmap(conn, win_depth, con->frame_buffer.id, con->frame.id, width, height);
|
||||
draw_util_surface_init(conn, &(con->frame_buffer), con->frame_buffer.id,
|
||||
get_visualtype_by_id(get_visualid_by_depth(win_depth)), width, height);
|
||||
|
@ -798,6 +798,8 @@ sub exit_gracefully {
|
||||
my ($pid, $socketpath) = @_;
|
||||
$socketpath ||= get_socket_path();
|
||||
|
||||
$SIG{CHLD} = undef;
|
||||
|
||||
my $exited = 0;
|
||||
eval {
|
||||
say "Exiting i3 cleanly...";
|
||||
@ -836,6 +838,8 @@ sub exit_forcefully {
|
||||
my ($pid, $signal) = @_;
|
||||
$signal ||= 'TERM';
|
||||
|
||||
$SIG{CHLD} = undef;
|
||||
|
||||
# Send the given signal to the i3 instance and wait for up to 10s
|
||||
# for it to terminate.
|
||||
kill($signal, $pid)
|
||||
@ -959,6 +963,18 @@ sub launch_with_config {
|
||||
return ${^CHILD_ERROR_NATIVE};
|
||||
}
|
||||
|
||||
$SIG{CHLD} = sub {
|
||||
# don't change $! and $? outside handler
|
||||
local ($!, $?);
|
||||
|
||||
my $child = waitpid -1, POSIX::WNOHANG;
|
||||
warn "SIGCHLD, waitpid() = $child";
|
||||
if ($child == $i3_pid) {
|
||||
warn "i3 died, exiting!";
|
||||
exit 1;
|
||||
}
|
||||
};
|
||||
|
||||
# force update of the cached socket path in lib/i3test
|
||||
# as soon as i3 has started
|
||||
$cv->cb(sub { get_socket_path(0) });
|
||||
|
@ -326,6 +326,42 @@ is_num_children('left-bottom', 2, 'two children on left-bottom');
|
||||
kill_all_windows;
|
||||
exit_gracefully($pid);
|
||||
|
||||
#####################################################################
|
||||
# Test assignments to primary / nonprimary outputs
|
||||
#####################################################################
|
||||
$config = <<'EOT';
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
fake-outputs 1024x768+0+0P,1024x768+1024+0
|
||||
|
||||
workspace primary output fake-0
|
||||
workspace nonprimary output fake-1
|
||||
|
||||
assign [class="current"] → output current
|
||||
assign [class="^primary$"] → output primary
|
||||
assign [class="nonprimary"] → output nonprimary
|
||||
EOT
|
||||
|
||||
$pid = launch_with_config($config);
|
||||
|
||||
cmd 'workspace primary';
|
||||
open_special(wm_class => 'current');
|
||||
sync_with_i3;
|
||||
is_num_children('primary', 1, 'one window in current workspace');
|
||||
|
||||
open_special(wm_class => 'nonprimary');
|
||||
sync_with_i3;
|
||||
is_num_children('nonprimary', 1, 'one child on nonprimary');
|
||||
|
||||
cmd 'workspace nonprimary';
|
||||
open_special(wm_class => 'primary');
|
||||
sync_with_i3;
|
||||
is_num_children('primary', 2, 'two children on primary');
|
||||
|
||||
kill_all_windows;
|
||||
exit_gracefully($pid);
|
||||
|
||||
#####################################################################
|
||||
# regression test: dock clients with floating assignments should not crash
|
||||
# (instead, nothing should happen - dock clients can’t float)
|
||||
|
@ -121,6 +121,12 @@ bar {
|
||||
urgent_workspace #2f343a #900000 #ffffff
|
||||
binding_mode #abc123 #123abc #ababab
|
||||
}
|
||||
|
||||
# old syntax for compatibility with i3-gaps
|
||||
height 50
|
||||
# new syntax: top right bottom left
|
||||
# y width height x
|
||||
padding 4px 2px 4px 2px
|
||||
}
|
||||
EOT
|
||||
|
||||
@ -145,6 +151,14 @@ is_deeply($bar_config->{tray_outputs}, [ 'LVDS1', 'HDMI2' ], 'tray_output ok');
|
||||
is($bar_config->{tray_padding}, 0, 'tray_padding ok');
|
||||
is($bar_config->{font}, 'Terminus', 'font ok');
|
||||
is($bar_config->{socket_path}, '/tmp/foobar', 'socket_path ok');
|
||||
is($bar_config->{bar_height}, 50, 'bar_height ok');
|
||||
is_deeply($bar_config->{padding},
|
||||
{
|
||||
x => 2,
|
||||
y => 4,
|
||||
width => 2,
|
||||
height => 4,
|
||||
}, 'padding ok');
|
||||
is_deeply($bar_config->{colors},
|
||||
{
|
||||
background => '#ff0000',
|
||||
@ -171,6 +185,105 @@ is_deeply($bar_config->{colors},
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
#####################################################################
|
||||
# validate one-value padding directive
|
||||
#####################################################################
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
bar {
|
||||
# all padding is 25px
|
||||
padding 25px
|
||||
}
|
||||
EOT
|
||||
|
||||
$pid = launch_with_config($config);
|
||||
|
||||
$i3 = i3(get_socket_path(0));
|
||||
$bars = $i3->get_bar_config()->recv;
|
||||
is(@$bars, 1, 'one bar configured');
|
||||
|
||||
$bar_id = shift @$bars;
|
||||
|
||||
$bar_config = $i3->get_bar_config($bar_id)->recv;
|
||||
is_deeply($bar_config->{padding},
|
||||
{
|
||||
x => 25,
|
||||
y => 25,
|
||||
width => 25,
|
||||
height => 25,
|
||||
}, 'padding ok');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
#####################################################################
|
||||
# validate two-value padding directive
|
||||
#####################################################################
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
bar {
|
||||
# top and bottom padding is 25px, right and left padding is 50px
|
||||
padding 25px 50px
|
||||
}
|
||||
EOT
|
||||
|
||||
$pid = launch_with_config($config);
|
||||
|
||||
$i3 = i3(get_socket_path(0));
|
||||
$bars = $i3->get_bar_config()->recv;
|
||||
is(@$bars, 1, 'one bar configured');
|
||||
|
||||
$bar_id = shift @$bars;
|
||||
|
||||
$bar_config = $i3->get_bar_config($bar_id)->recv;
|
||||
is_deeply($bar_config->{padding},
|
||||
{
|
||||
x => 50,
|
||||
y => 25,
|
||||
width => 50,
|
||||
height => 25,
|
||||
}, 'padding ok');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
#####################################################################
|
||||
# validate three-value padding directive
|
||||
#####################################################################
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
bar {
|
||||
# top padding is 25px, right and left padding is 50px, bottom padding is 75px
|
||||
padding 25px 50px 75px
|
||||
}
|
||||
EOT
|
||||
|
||||
$pid = launch_with_config($config);
|
||||
|
||||
$i3 = i3(get_socket_path(0));
|
||||
$bars = $i3->get_bar_config()->recv;
|
||||
is(@$bars, 1, 'one bar configured');
|
||||
|
||||
$bar_id = shift @$bars;
|
||||
|
||||
$bar_config = $i3->get_bar_config($bar_id)->recv;
|
||||
is_deeply($bar_config->{padding},
|
||||
{
|
||||
x => 50,
|
||||
y => 25,
|
||||
width => 50,
|
||||
height => 75,
|
||||
}, 'padding ok');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
#####################################################################
|
||||
# ensure that multiple bars get different IDs
|
||||
#####################################################################
|
||||
|
@ -106,30 +106,35 @@ is(parser_calls('resize shrink left 25 px or 33 ppt;'),
|
||||
|
||||
is(parser_calls('[con_mark=yay] focus'),
|
||||
"cmd_criteria_add(con_mark, yay)\n" .
|
||||
"cmd_focus()",
|
||||
"cmd_focus(0)",
|
||||
'criteria focus ok');
|
||||
|
||||
is(parser_calls('[con_mark=yay] focus workspace'),
|
||||
"cmd_criteria_add(con_mark, yay)\n" .
|
||||
"cmd_focus(1)",
|
||||
'criteria focus workspace ok');
|
||||
|
||||
is(parser_calls("[con_mark=yay con_mark=bar] focus"),
|
||||
"cmd_criteria_add(con_mark, yay)\n" .
|
||||
"cmd_criteria_add(con_mark, bar)\n" .
|
||||
"cmd_focus()",
|
||||
"cmd_focus(0)",
|
||||
'criteria focus ok');
|
||||
|
||||
is(parser_calls("[con_mark=yay\tcon_mark=bar] focus"),
|
||||
"cmd_criteria_add(con_mark, yay)\n" .
|
||||
"cmd_criteria_add(con_mark, bar)\n" .
|
||||
"cmd_focus()",
|
||||
"cmd_focus(0)",
|
||||
'criteria focus ok');
|
||||
|
||||
is(parser_calls("[con_mark=yay\tcon_mark=bar]\tfocus"),
|
||||
"cmd_criteria_add(con_mark, yay)\n" .
|
||||
"cmd_criteria_add(con_mark, bar)\n" .
|
||||
"cmd_focus()",
|
||||
"cmd_focus(0)",
|
||||
'criteria focus ok');
|
||||
|
||||
is(parser_calls('[con_mark="yay"] focus'),
|
||||
"cmd_criteria_add(con_mark, yay)\n" .
|
||||
"cmd_focus()",
|
||||
"cmd_focus(0)",
|
||||
'quoted criteria focus ok');
|
||||
|
||||
# Make sure trailing whitespace is stripped off: While this is not an issue for
|
||||
@ -179,6 +184,7 @@ is(parser_calls('unknown_literal'),
|
||||
title_window_icon
|
||||
mode
|
||||
bar
|
||||
gaps
|
||||
)) . "'\n" .
|
||||
"ERROR: Your command: unknown_literal\n" .
|
||||
"ERROR: ^^^^^^^^^^^^^^^",
|
||||
|
@ -515,6 +515,9 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
|
||||
bar
|
||||
font
|
||||
mode
|
||||
gaps
|
||||
smart_borders
|
||||
smart_gaps
|
||||
floating_minimum_size
|
||||
floating_maximum_size
|
||||
floating_modifier
|
||||
@ -549,6 +552,7 @@ my $expected_all_tokens = "ERROR: CONFIG: Expected one of these tokens: <end>, '
|
||||
ipc_kill_timeout
|
||||
restart_state
|
||||
popup_during_fullscreen
|
||||
tiling_drag
|
||||
exec_always
|
||||
exec
|
||||
client.background
|
||||
@ -580,7 +584,7 @@ client.focused #4c7899 #285577 #ffffff #2e9ef4
|
||||
EOT
|
||||
|
||||
$expected = <<'EOT';
|
||||
ERROR: CONFIG: Expected one of these tokens: 'none', 'vertical', 'horizontal', 'both', 'smart', '1', 'yes', 'true', 'on', 'enable', 'active'
|
||||
ERROR: CONFIG: Expected one of these tokens: 'none', 'vertical', 'horizontal', 'both', 'smart_no_gaps', 'smart', '1', 'yes', 'true', 'on', 'enable', 'active'
|
||||
ERROR: CONFIG: (in file <stdin>)
|
||||
ERROR: CONFIG: Line 1: hide_edge_borders FOOBAR
|
||||
ERROR: CONFIG: ^^^^^^
|
||||
@ -772,7 +776,7 @@ EOT
|
||||
$expected = <<'EOT';
|
||||
cfg_bar_start()
|
||||
cfg_bar_output(LVDS-1)
|
||||
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'workspace_min_width', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'colors', '}'
|
||||
ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'workspace_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'wheel_up_cmd', 'wheel_down_cmd', 'bindsym', 'position', 'output', 'tray_output', 'tray_padding', 'font', 'separator_symbol', 'binding_mode_indicator', 'workspace_buttons', 'workspace_min_width', 'strip_workspace_numbers', 'strip_workspace_name', 'verbose', 'height', 'padding', 'colors', '}'
|
||||
ERROR: CONFIG: (in file <stdin>)
|
||||
ERROR: CONFIG: Line 1: bar {
|
||||
ERROR: CONFIG: Line 2: output LVDS-1
|
||||
|
@ -16,6 +16,7 @@
|
||||
#
|
||||
# Test that the binding event works properly
|
||||
# Ticket: #1210
|
||||
# Ticket: #5323
|
||||
use i3test i3_autostart => 0;
|
||||
|
||||
my $keysym = 't';
|
||||
@ -28,6 +29,12 @@ my $config = <<EOT;
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
bindsym $binding_symbol $command
|
||||
|
||||
bindsym m mode some-mode
|
||||
mode some-mode {
|
||||
bindsym a nop
|
||||
bindsym b mode default
|
||||
}
|
||||
EOT
|
||||
|
||||
SKIP: {
|
||||
@ -46,12 +53,13 @@ SKIP: {
|
||||
qx(xdotool key $binding_symbol);
|
||||
},
|
||||
'binding');
|
||||
|
||||
is(scalar @events, 1, 'Received 1 event');
|
||||
|
||||
is($events[0]->{change}, 'run',
|
||||
'the `change` field should indicate this binding has run');
|
||||
|
||||
is($events[0]->{mode}, 'default', 'the `mode` field should be "default"');
|
||||
|
||||
ok($events[0]->{binding},
|
||||
'the `binding` field should be a hash that contains information about the binding');
|
||||
|
||||
@ -69,6 +77,21 @@ SKIP: {
|
||||
is($events[0]->{binding}->{input_code}, 0,
|
||||
'the input_code should be the specified code if the key was bound with bindcode, and otherwise zero');
|
||||
|
||||
# Test that the mode field is correct
|
||||
# See #5323.
|
||||
@events = events_for(
|
||||
sub {
|
||||
qx(xdotool key m);
|
||||
qx(xdotool key a);
|
||||
qx(xdotool key b);
|
||||
},
|
||||
'binding');
|
||||
is(scalar @events, 3, 'Received 3 events');
|
||||
|
||||
is($events[0]->{mode}, 'default', 'Event for binding to enter new mode is atributed to default mode');
|
||||
is($events[1]->{mode}, 'some-mode', 'Event for binding while in mode is attributed to the non-default mode');
|
||||
is($events[2]->{mode}, 'some-mode', 'Event for binding exiting mode is attributed to the non-default mode');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ $target = get_focused($ws);
|
||||
$A = $cons[0];
|
||||
$C = $cons[1]->{nodes}[1];
|
||||
|
||||
$y = $C->{rect}->{y} - 0.5 * $C->{deco_rect}->{height};
|
||||
$y = $C->{rect}->{y} + 0.5 * $C->{deco_rect}->{height};
|
||||
|
||||
# make sure that B is the focus head of its parent
|
||||
cmd '[id="' . $B->{id} . '"] focus';
|
||||
|
@ -14,7 +14,10 @@
|
||||
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||
# (unless you are already familiar with Perl)
|
||||
#
|
||||
use i3test i3_config => <<EOT;
|
||||
use i3test i3_autostart => 0;
|
||||
use X11::XCB qw(PROP_MODE_REPLACE);
|
||||
|
||||
my $config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
for_window [tiling] mark --add tiling
|
||||
@ -26,7 +29,8 @@ for_window [floating_from="auto"] mark --add floating_auto
|
||||
for_window [tiling_from="user"] mark --add tiling_user
|
||||
for_window [floating_from="user"] mark --add floating_user
|
||||
EOT
|
||||
use X11::XCB qw(PROP_MODE_REPLACE);
|
||||
|
||||
my $pid = launch_with_config($config);
|
||||
|
||||
##############################################################
|
||||
# Check that the auto tiling / floating criteria work.
|
||||
@ -83,4 +87,45 @@ is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'B', 'floating_user' ], "Only 'float
|
||||
|
||||
##############################################################
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
################################################################################
|
||||
# Verify floating_from/tiling_from works as command criterion (issue #5258).
|
||||
################################################################################
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
for_window [tiling] mark --add tiling
|
||||
for_window [floating] mark --add floating
|
||||
EOT
|
||||
|
||||
$pid = launch_with_config($config);
|
||||
|
||||
$tmp = fresh_workspace;
|
||||
$A = open_window;
|
||||
$B = open_floating_window;
|
||||
|
||||
@nodes = @{get_ws($tmp)->{nodes}};
|
||||
cmp_ok(@nodes, '==', 1, 'one tiling container on this workspace');
|
||||
is_deeply($nodes[0]->{marks}, [ 'tiling' ], "mark set for 'tiling' criterion");
|
||||
|
||||
@nodes = @{get_ws($tmp)->{floating_nodes}};
|
||||
cmp_ok(@nodes, '==', 1, 'one floating container on this workspace');
|
||||
is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'floating' ], "mark set for 'floating' criterion");
|
||||
|
||||
cmd '[tiling_from="auto" con_mark="tiling"] mark --add tiling_auto';
|
||||
cmd '[floating_from="auto" con_mark="floating"] mark --add floating_auto';
|
||||
|
||||
@nodes = @{get_ws($tmp)->{nodes}};
|
||||
cmp_ok(@nodes, '==', 1, 'one tiling container on this workspace');
|
||||
is_deeply($nodes[0]->{marks}, [ 'tiling', 'tiling_auto' ], "mark set for 'tiling' criterion");
|
||||
|
||||
@nodes = @{get_ws($tmp)->{floating_nodes}};
|
||||
cmp_ok(@nodes, '==', 1, 'one floating container on this workspace');
|
||||
is_deeply($nodes[0]->{nodes}[0]->{marks}, [ 'floating', 'floating_auto' ], "mark set for 'floating' criterion");
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
done_testing;
|
||||
|
@ -160,4 +160,69 @@ is($tilewindow2->rect->width, $tiled[1]->{rect}->{width} - 2*2, 'second tiled bo
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
#####################################################################
|
||||
# 5: check that the borders are visible on a workspace with one tiled
|
||||
# window and edge gaps
|
||||
#####################################################################
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
new_window pixel 2
|
||||
new_float pixel 2
|
||||
gaps outer 5
|
||||
hide_edge_borders smart_no_gaps
|
||||
EOT
|
||||
|
||||
$pid = launch_with_config($config);
|
||||
|
||||
$tmp = fresh_workspace;
|
||||
|
||||
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
|
||||
|
||||
$tilewindow = open_window;
|
||||
|
||||
$wscontent = get_ws($tmp);
|
||||
|
||||
@tiled = @{$wscontent->{nodes}};
|
||||
ok(@tiled == 1, 'one tiled container opened');
|
||||
is($tiled[0]->{current_border_width}, 2, 'tiled current border width set to 2');
|
||||
is($tilewindow->rect->width, $tiled[0]->{rect}->{width} - 2*2, 'single tiled border width 2');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
#####################################################################
|
||||
# 5: check that the borders are hidden on a workspace with one tiled
|
||||
# window with no gaps
|
||||
#####################################################################
|
||||
|
||||
$config = <<EOT;
|
||||
# i3 config file (v4)
|
||||
font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
|
||||
|
||||
new_window pixel 2
|
||||
new_float pixel 2
|
||||
hide_edge_borders smart_no_gaps
|
||||
EOT
|
||||
|
||||
$pid = launch_with_config($config);
|
||||
|
||||
$tmp = fresh_workspace;
|
||||
|
||||
ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
|
||||
|
||||
$tilewindow = open_window;
|
||||
|
||||
$wscontent = get_ws($tmp);
|
||||
|
||||
@tiled = @{$wscontent->{nodes}};
|
||||
ok(@tiled == 1, 'one tiled container opened');
|
||||
is($tiled[0]->{current_border_width}, 2, 'tiled current border width set to 2');
|
||||
is($tilewindow->rect->width, $tiled[0]->{rect}->{width} - 2*0, 'single tiled border width 0');
|
||||
|
||||
exit_gracefully($pid);
|
||||
|
||||
|
||||
|
||||
done_testing;
|
||||
|
@ -21,7 +21,9 @@
|
||||
#
|
||||
# Ticket: #2318
|
||||
# Bug still in: 4.12-46-g2123888
|
||||
use i3test;
|
||||
use i3test i3_autostart => 0;
|
||||
|
||||
my $pid = launch_with_config('-default');
|
||||
|
||||
# We cannot use events_for in this test as we cannot send events after
|
||||
# issuing the restart/shutdown command.
|
||||
@ -59,7 +61,7 @@ $i3->subscribe({
|
||||
}
|
||||
})->recv;
|
||||
|
||||
cmd 'exit';
|
||||
exit_gracefully($pid);
|
||||
|
||||
$e = $cv->recv;
|
||||
|
||||
|
@ -86,6 +86,8 @@ workspace 5 output fake-0
|
||||
workspace 5:xxx output fake-1
|
||||
workspace 6:xxx output fake-0
|
||||
workspace 6 output fake-1
|
||||
workspace 7 output nonprimary primary
|
||||
workspace 8 output doesnotexist primary
|
||||
EOT
|
||||
|
||||
$pid = launch_with_config($config);
|
||||
@ -98,7 +100,9 @@ do_test('5', 'fake-0', 'Numbered assignment ok');
|
||||
do_test('5:xxx', 'fake-1', 'Named assignment overrides number');
|
||||
do_test('6', 'fake-1', 'Numbered assignment ok');
|
||||
do_test('6:xxx', 'fake-0', 'Named assignment overrides number');
|
||||
do_test('7', 'fake-2', 'Numbered initialization for fake-2');
|
||||
do_test('7', 'fake-1', 'Non-primary output');
|
||||
do_test('8', 'fake-0', 'Primary output');
|
||||
do_test('9', 'fake-2', 'Numbered initialization for fake-2');
|
||||
|
||||
cmd 'focus output fake-0, workspace foo';
|
||||
check_output('foo', 'fake-0', 'Workspace with only non-existing assigned outputs opened in current output');
|
||||
|
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