diff --git a/.clang-format b/.clang-format index 4a15e88e..97f3e47a 100644 --- a/.clang-format +++ b/.clang-format @@ -1,13 +1,14 @@ -BasedOnStyle: google +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false -AllowShortFunctionsOnASingleLine: None -AllowShortBlocksOnASingleLine: false AlwaysBreakBeforeMultilineStrings: false -IndentWidth: 4 -PointerBindsToType: false +BasedOnStyle: google ColumnLimit: 0 -SpaceBeforeParens: ControlStatements -SortIncludes: false ForEachMacros: [ TAILQ_FOREACH, TAILQ_FOREACH_REVERSE, SLIST_FOREACH, CIRCLEQ_FOREACH, CIRCLEQ_FOREACH_REVERSE, NODES_FOREACH, NODES_FOREACH_REVERSE, FOREACH_NONINTERNAL] +IndentWidth: 4 +InsertBraces: true +PointerBindsToType: false +SortIncludes: false +SpaceBeforeParens: ControlStatements TypenameMacros: [ SLIST_HEAD, SLIST_ENTRY, LIST_HEAD, LIST_ENTRY, SIMPLEQ_HEAD, SIMPLEQ_ENTRY, TAILQ_HEAD, TAILQ_ENTRY, CIRCLEQ_HEAD, CIRCLEQ_ENTRY ] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index a6df0a95..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve ---- - - - -## I'm submitting a… - -
-[x] Bug
-[ ] Feature Request
-[ ] Documentation Request
-[ ] Other (Please describe in detail)
-
- -## Current Behavior - - -## Expected Behavior - - -## Reproduction Instructions - - -## Environment - -Output of `i3 --moreversion 2>&-`: -
-i3 version: 
-
- - -
Config file
-
-
- - -
-Logfile URL:
-
- - -
-- Linux Distribution & Version:
-- Are you using a compositor (e.g., xcompmgr or compton):
-
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100755 index 00000000..734ab55b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,107 @@ +name: Bug Report +description: Create a report to help us improve. +labels: [bug] +body: + - type: checkboxes + id: terms + attributes: + label: Welcome + options: + - label: Yes, I'm using the latest major release or the current development version. These are the only supported versions. + required: true + - label: Yes, I've searched similar issues and discussions on GitHub and didn't find any. + required: true + + - type: textarea + id: current + attributes: + label: Current Behavior + placeholder: |- + Describe the current behavior, + e.g., »When pressing Alt+j (focus left), the window above the current window is focused.« + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected Behavior + placeholder: |- + Describe the desired behavior you expect after mitigation of the issue, + e.g., »The window left next to the current window should be focused.« + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Reproduction Instructions + placeholder: |- + Please provide detailed instructions on how the bug can be reproduced. + E.g., »Open three windows in a V[A H[B C]] layout on a new workspace« + validations: + required: true + + - type: textarea + id: version + attributes: + label: i3 version + description: |- + Paste the output of + ``` + i3 --moreversion 2>&- + ``` + render: text + validations: + required: true + + - type: textarea + id: config + attributes: + label: Config file + description: |- + Please include your (complete) i3 config with which the issue occurs. + + If you would like to help debugging the issue, please try to reduce the config such that it is as close to the default config as possible while still reproducing the issue. This can help us bisect the root cause. + render: text + validations: + required: true + + - type: input + id: distro + attributes: + label: Linux distribution & Version + validations: + required: true + + - type: dropdown + id: compositor + attributes: + label: Are you using a compositor? + description: |- + Try running + ```shell + pidof picom + pidof compton + ``` + If any IDs show up, you are running a compositor + options: + - I don't know + - I am sure I don't run any compositor + - picom + - compton + - Other + validations: + required: true + + - type: input + id: verbose-output + attributes: + label: Logfile + description: |- + Providing the URL to a logfile can help us trace the root cause of an issue much quicker. You can learn how to generate the logfile here: + https://i3wm.org/docs/debugging.html + + Providing the logfile is optional. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 02251489..d35cc581 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,7 @@ contact_links: + - name: Userguide + url: https://i3wm.org/docs/userguide.html + about: i3 User’s Guide - name: Ask a question or request support for using i3 url: https://github.com/i3/i3/discussions/new about: Ask a question or request support for using i3 diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index c41d1cac..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project ---- - - - -## I'm submitting a… - -
-[ ] Bug
-[x] Feature Request
-[ ] Documentation Request
-[ ] Other (Please describe in detail)
-
- -## Current Behavior - - -## Desired Behavior - - -## Impact - -
-[ ] This feature requires new configuration and/or commands
-
- -## Environment - -Output of `i3 --moreversion 2>&-`: -
-i3 version: 
-
- - -
-- Linux Distribution & Version:
-- Are you using a compositor (e.g., xcompmgr or compton):
-
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100755 index 00000000..78ec54c1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,57 @@ +name: Feature request +description: Suggest an idea for this project +labels: [enhancement] +body: + - type: checkboxes + id: terms + attributes: + label: Welcome + options: + - label: Yes, I've searched similar issues and discussions on GitHub and didn't find any. + required: true + + - type: checkboxes + id: impact + attributes: + label: Impact + description: |- + Please note that at this point we focus on maintaining i3 and fixing bugs, and will rarely consider features which require further configuration or significant complexity. + In such cases you should consider and present specific benefits derived from adding this feature such that it can be weighed against the cost of additional complexity and maintenance. + Keep in mind that i3 provides a powerful way to interact with it through its IPC interface: https://i3wm.org/docs/ipc.html. + options: + - label: This feature requires new configuration and/or commands + required: false + + + - type: textarea + id: current + attributes: + label: Current Behavior + placeholder: |- + Describe the current behavior, + e.g., »When pressing Alt+j (focus left), the window above the current window is focused.« + validations: + required: true + + - type: textarea + id: desired + attributes: + label: Desired Behavior + placeholder: |- + Describe the desired behavior you expect after mitigation of the issue, + e.g., »The window left next to the current window should be focused.« + validations: + required: true + + - type: textarea + id: version + attributes: + label: i3 version + description: |- + Paste the output of + ``` + i3 --moreversion 2>&- + ``` + render: text + validations: + required: true diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5e460a63..9e651eca 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,34 +20,36 @@ jobs: DOCKER_EMAIL: ${{ secrets.DOCKER_EMAIL }} DOCKER_USER: ${{ secrets.DOCKER_USER }} GH_TOKEN: ${{ secrets.GH_TOKEN }} - BALTO_TOKEN: ${{ secrets.BALTO_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - 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' && matrix.compiler == 'gcc' 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' + CFLAGS="-Wformat -Wformat-security -Wall -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror -fno-common -D_FORTIFY_SOURCE=3" + if [ "${{ matrix.compiler }}" = "gcc" ]; then + CFLAGS="$CFLAGS -Wsuggest-attribute=pure -Wsuggest-attribute=const -Wsuggest-attribute=format" + fi + export CFLAGS + docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC -e CFLAGS ${{ env.BASENAME }} /bin/sh -c 'rm -rf build; mkdir -p build && cd build && meson setup .. -Ddocs=true -Dmans=true -Db_sanitize="address,undefined" --buildtype=debugoptimized && ninja -v' + - name: Upload docs html for manual inspection + uses: actions/upload-artifact@v4 + with: + name: i3-docs + path: | + build/*.html + if: matrix.compiler == 'gcc' - name: check spelling run: | docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-spelling.pl @@ -55,48 +57,42 @@ jobs: run: | docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} ./travis/run-tests.sh - name: Archive test logs - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: test-logs path: build/testsuite-* if: ${{ failure() }} - name: build dist tarball run: | - docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf distbuild; mkdir distbuild && cd distbuild && meson .. -Ddocs=true -Dmans=true && meson dist --no-tests' + 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' && matrix.compiler == 'gcc' 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 + docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_UBUNTU }} ./travis/debian-build.sh deb/ubuntu-amd64/DIST echo "::endgroup::" - echo "::group::Debian i386" - ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_386 }} linux32 ./travis/debian-build.sh deb/debian-i386/DIST - echo "::endgroup::" - echo "::group::Ubuntu i386" - ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_UBUNTU_386 }} linux32 ./travis/debian-build.sh deb/ubuntu-i386/DIST - echo "::endgroup::" - - name: push Debian packages to balto - run: | - ./travis/skip-pkg.sh || travis/push-balto.sh - name: build docs + if: github.ref == 'refs/heads/next' && matrix.compiler == 'gcc' 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' && matrix.compiler == 'gcc' 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@v3 + - uses: actions/checkout@v4 - name: check & print release notes run: ./release-notes/generator.pl - name: Install dependencies run: | - sudo apt-get install -y clang-format-12 + sudo apt-get install -y clang-format-20 - name: Check formatting - run: clang-format-12 --dry-run --Werror $(git ls-files '*.c' '*.h') + run: clang-format-20 --dry-run --Werror $(git ls-files '*.c' '*.h') - name: Verify safe wrapper functions are used run: ./travis/check-safe-wrappers.sh diff --git a/.gitignore b/.gitignore index 6ee317db..65debf08 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,3 @@ LAST_VERSION # it is up to you to arrange for it to be ignored by git, # e.g. by listing your directory in .git/info/exclude. /build - diff --git a/AnyEvent-I3/Changes b/AnyEvent-I3/Changes index d763437f..d9fe55a2 100644 --- a/AnyEvent-I3/Changes +++ b/AnyEvent-I3/Changes @@ -1,5 +1,14 @@ Revision history for AnyEvent-I3 +0.19 2024-04-09 + + * use Carp for errors (includes stacktraces) + * introduce (preferred) RUN_COMMAND spelling + * migrate tooling to ExtUtils::MakeMaker + * implement the tick event + * introduce the sync IPC command + * introduce the GET_BINDING_STATE IPC command + 0.18 2017-08-19 * support the GET_CONFIG command diff --git a/AnyEvent-I3/lib/AnyEvent/I3.pm b/AnyEvent-I3/lib/AnyEvent/I3.pm index 1f4e5bd3..2d749d9e 100644 --- a/AnyEvent-I3/lib/AnyEvent/I3.pm +++ b/AnyEvent-I3/lib/AnyEvent/I3.pm @@ -8,7 +8,6 @@ use AnyEvent::Handle; use AnyEvent::Socket; use AnyEvent; use Encode; -use Scalar::Util qw(tainted); use Carp; =head1 NAME @@ -17,11 +16,11 @@ AnyEvent::I3 - communicate with the i3 window manager =cut -our $VERSION = '0.18'; +our $VERSION = '0.19'; =head1 VERSION -Version 0.18 +Version 0.19 =head1 SYNOPSIS @@ -132,35 +131,10 @@ sub i3 { AnyEvent::I3->new(@_) } -# Calls i3, even when running in taint mode. sub _call_i3 { my ($args) = @_; - my $path_tainted = tainted($ENV{PATH}); - # This effectively circumvents taint mode checking for $ENV{PATH}. We - # do this because users might specify PATH explicitly to call i3 in a - # custom location (think ~/.bin/). - (local $ENV{PATH}) = ($ENV{PATH} =~ /(.*)/); - - # In taint mode, we also need to remove all relative directories from - # PATH (like . or ../bin). We only do this in taint mode and warn the - # user, since this might break a real-world use case for some people. - if ($path_tainted) { - my @dirs = split /:/, $ENV{PATH}; - my @filtered = grep !/^\./, @dirs; - if (scalar @dirs != scalar @filtered) { - $ENV{PATH} = join ':', @filtered; - warn qq|Removed relative directories from PATH because you | . - qq|are running Perl with taint mode enabled. Remove -T | . - qq|to be able to use relative directories in PATH. | . - qq|New PATH is "$ENV{PATH}"|; - } - } - # Otherwise the qx() operator wont work: - delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; chomp(my $result = qx(i3 $args)); - # Circumventing taint mode again: the socket can be anywhere on the - # system and that’s okay. if ($result =~ /^([^\0]+)$/) { return $1; } @@ -182,21 +156,21 @@ instance on the current DISPLAY which is almost always what you want. sub new { my ($class, $path) = @_; - $path = _call_i3('--get-socketpath') unless $path; - - # This is the old default path (v3.*). This fallback line can be removed in - # a year from now. -- Michael, 2012-07-09 - $path ||= '~/.i3/ipc.sock'; + # We have I3SOCK now + $path ||= $ENV{I3SOCK}; + $path ||= _call_i3('--get-socketpath'); # Check if we need to resolve ~ if ($path =~ /~/) { - # We use getpwuid() instead of $ENV{HOME} because the latter is tainted - # and thus produces warnings when running tests with perl -T - my $home = (getpwuid($<))[7]; + my $home = $ENV{HOME}; confess "Could not get home directory" unless $home and -d $home; $path =~ s/~/$home/g; } + if(!-S $path) { + die "$path is not a socket", $/; + } + bless { path => $path } => $class; } @@ -315,6 +289,11 @@ sub subscribe { # Register callbacks for each message type for my $key (keys %{$callbacks}) { + if (!exists $events{$key}) { + warn "Could not subscribe to event type '$key'." . + " Supported events are " . join(" ", sort keys %events), $/; + next; + } my $type = $events{$key}; $self->{callbacks}->{$type} = $callbacks->{$key}; } diff --git a/AnyEvent-I3/t/00-load.t b/AnyEvent-I3/t/00-load.t index 4bf6151e..210ab153 100644 --- a/AnyEvent-I3/t/00-load.t +++ b/AnyEvent-I3/t/00-load.t @@ -1,4 +1,4 @@ -#!perl -T +#!perl use Test::More tests => 1; diff --git a/AnyEvent-I3/t/01-workspaces.t b/AnyEvent-I3/t/01-workspaces.t index f3206d89..8b12f111 100644 --- a/AnyEvent-I3/t/01-workspaces.t +++ b/AnyEvent-I3/t/01-workspaces.t @@ -1,4 +1,4 @@ -#!perl -T +#!perl # vim:ts=4:sw=4:expandtab use Test::More tests => 3; diff --git a/AnyEvent-I3/t/02-sugar.t b/AnyEvent-I3/t/02-sugar.t index a3e2cc79..4811be44 100644 --- a/AnyEvent-I3/t/02-sugar.t +++ b/AnyEvent-I3/t/02-sugar.t @@ -1,4 +1,4 @@ -#!perl -T +#!perl # vim:ts=4:sw=4:expandtab use Test::More tests => 3; diff --git a/AnyEvent-I3/t/boilerplate.t b/AnyEvent-I3/t/boilerplate.t index effb65b6..f4024158 100644 --- a/AnyEvent-I3/t/boilerplate.t +++ b/AnyEvent-I3/t/boilerplate.t @@ -1,4 +1,4 @@ -#!perl -T +#!perl use strict; use warnings; diff --git a/AnyEvent-I3/t/manifest.t b/AnyEvent-I3/t/manifest.t index 45eb83fd..2dcbd43c 100644 --- a/AnyEvent-I3/t/manifest.t +++ b/AnyEvent-I3/t/manifest.t @@ -1,4 +1,4 @@ -#!perl -T +#!perl use strict; use warnings; diff --git a/AnyEvent-I3/t/pod.t b/AnyEvent-I3/t/pod.t index ee8b18ad..b4791d4e 100644 --- a/AnyEvent-I3/t/pod.t +++ b/AnyEvent-I3/t/pod.t @@ -1,4 +1,4 @@ -#!perl -T +#!perl use strict; use warnings; diff --git a/PACKAGE-MAINTAINER b/PACKAGE-MAINTAINER index c5a7bea6..3995c59c 100644 --- a/PACKAGE-MAINTAINER +++ b/PACKAGE-MAINTAINER @@ -27,7 +27,7 @@ https://mesonbuild.com/Quick-guide.html#compiling-a-meson-project In case you’re unfamiliar: $ mkdir -p build && cd build - $ meson .. + $ meson setup $ ninja Please make sure that i3-migrate-config-to-v4 and i3-config-wizard are diff --git a/RELEASE-NOTES-4.22 b/RELEASE-NOTES-4.22 deleted file mode 100644 index 36db5456..00000000 --- a/RELEASE-NOTES-4.22 +++ /dev/null @@ -1,55 +0,0 @@ - - ┌──────────────────────────────┐ - │ 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 diff --git a/RELEASE-NOTES-4.25 b/RELEASE-NOTES-4.25 new file mode 100644 index 00000000..c65b9254 --- /dev/null +++ b/RELEASE-NOTES-4.25 @@ -0,0 +1,49 @@ + + ┌──────────────────────────────┐ + │ Release notes for i3 v4.25 │ + └──────────────────────────────┘ + +This is i3 v4.25. This version is considered stable. All users of i3 are +strongly encouraged to upgrade. + +Most changes in this release cycle have been “behind the scenes”, +without an observable effect for users. For example, we switched +to clang-format-20, fixed a number of (benign) memory issues, +fixed a few flaky tests and maintained our GitHub Actions setup, +e.g. for the shutdown of baltocdn (our old host for nightly builds). + +We also investigated shipping an i3.service systemd user unit, +but could not make that work without breaking existing setups. +If distribution maintainers (or anyone) has advice, please share +(after reading up!) over in https://github.com/i3/i3/issues/5186 + + ┌────────────────────────────┐ + │ Changes in i3 v4.25 │ + └────────────────────────────┘ + + • Set _NET_FRAME_EXTENTS according to the actual decoration size. + This improves compatibility with picom and other software. + • The command parser is now reentrant, which fixes a few + advanced for_window usages (e.g. multiple criteria). + + ┌────────────────────────────┐ + │ Bugfixes │ + └────────────────────────────┘ + + • fix paragraph separators cutting off window titles + • fix crash when a container parent is focused + and a tiling drag causes it to be killed + • fix crash when using for_window [...] reload + • fix append_layout when containers use a mark + • randr: fix memleak and use-after-free + + ┌────────────────────────────┐ + │ Thanks! │ + └────────────────────────────┘ + +Thanks for testing, bugfixes, discussions and everything I forgot go out to: + + Dmitry, Emeric Planet, FedGuy699, Garrett Marcinak, Michele Piazzai, + Orestis Floros, Sergey Vlasov, Vladimir Panteleev, algonell + +-- Michael Stapelberg, 2025-12-19 diff --git a/contrib/banner.svg b/contrib/banner.svg index af82021b..74881803 100644 --- a/contrib/banner.svg +++ b/contrib/banner.svg @@ -319,7 +319,7 @@ steckdenis - Logo for I3, an improved dynamic tiling window manager: http://i3.zekjur.net/ + Logo for i3 - an improved tiling window manager: http://i3.zekjur.net/ diff --git a/contrib/sticker_stickma_black.svg b/contrib/sticker_stickma_black.svg index fced8dd9..18c3299a 100644 --- a/contrib/sticker_stickma_black.svg +++ b/contrib/sticker_stickma_black.svg @@ -329,7 +329,7 @@ steckdenis - Logo for I3, an improved dynamic tiling window manager: http://i3.zekjur.net/ + Logo for i3 - an improved tiling window manager: http://i3.zekjur.net/ diff --git a/debian/changelog b/debian/changelog index 980224d0..12ecb36f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,20 @@ +i3-wm (4.24-1) unstable; urgency=medium + + * New upstream release. + + -- Michael Stapelberg Wed, 06 Nov 2024 18:34:06 +0100 + +i3-wm (4.23-1) unstable; urgency=medium + + * New upstream release. + + -- Michael Stapelberg Sun, 29 Oct 2023 15:42:11 +0100 + i3-wm (4.22-1) unstable; urgency=medium * New upstream release. - -- Michael Stapelberg Mon, 02 Jan 2023 09:34:02 +0100 + -- Michael Stapelberg Mon, 02 Jan 2023 09:46:22 +0100 i3-wm (4.21.2-1) unstable; urgency=medium diff --git a/debian/control b/debian/control index 6ddc64e6..67e38ff9 100644 --- a/debian/control +++ b/debian/control @@ -44,7 +44,7 @@ Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} Provides: x-window-manager Recommends: xfonts-base, fonts-dejavu-core, libanyevent-i3-perl (>= 0.12), libjson-xs-perl, rxvt-unicode | x-terminal-emulator -Description: improved dynamic tiling window manager +Description: improved tiling window manager Key features of i3 are good documentation, reasonable defaults (changeable in a simple configuration file) and good multi-monitor support. The user interface is designed for power users and emphasizes keyboard usage. i3 uses diff --git a/docs/NoName-2009-03-12/i3.tex b/docs/NoName-2009-03-12/i3.tex index 4ca05f24..5cc92311 100644 --- a/docs/NoName-2009-03-12/i3.tex +++ b/docs/NoName-2009-03-12/i3.tex @@ -29,7 +29,7 @@ add_ignore_event, xcb_intern_atom, xcb_intern_atom_reply, fprintf, printf, free, load_configuration,% XInternAtom, exit, strlen}} }{} -\title{i3 - an improved dynamic tiling window manager} +\title{i3 - an improved tiling window manager} \author{sECuRE beim NoName e.V.\\ ~\\ powered by \LaTeX, of course} diff --git a/docs/debugging b/docs/debugging index 562a11f2..cfc260cc 100644 --- a/docs/debugging +++ b/docs/debugging @@ -147,16 +147,17 @@ After pressing "b" in the crash dialog, you will get a file called id (PID) and the second one is incremented each time you generate a backtrace, starting at 0. -== Sending bug reports/debugging on IRC +In Linux, if the backtrace just says +No stack.+, that's because gdb does not +have necessary permissions to attach to a running process. You can fix that by +running from a terminal (you can open a new tty, e.g. with ctrl-alt-F2): -When sending bug reports, please attach the *whole* log file. Even if you think -you found the section which clearly highlights the problem, additional -information might be necessary to completely diagnose the problem. +--------------------------------------------------------------------- +echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope +--------------------------------------------------------------------- -When debugging with us in IRC, be prepared to use a so-called nopaste service -such as https://pastebin.com because pasting large amounts of text in IRC -sometimes leads to incomplete lines (servers have line length limitations) or -flood kicks. +Afterwards, try re-generating the stack trace. Note that this setting re-sets +after reboot, see more info at +https://www.kernel.org/doc/Documentation/security/Yama.txt. == Debugging i3bar diff --git a/docs/hacking-howto b/docs/hacking-howto index c6dd6fa4..d8c95ca4 100644 --- a/docs/hacking-howto +++ b/docs/hacking-howto @@ -14,7 +14,7 @@ you find necessary, please do not hesitate to contact me.

++++ This document is not 100% up to date. Specifically, everything up to and -including <> has been updated recently. The rest might contain +including <> has been updated recently. The rest might contain outdated information. ++++

@@ -28,36 +28,24 @@ https://mesonbuild.com/[The Meson Build system]; see https://mesonbuild.com/Quick-guide.html#compiling-a-meson-project[Quickstart Guide → Compiling a Meson project]. In case you’re unfamiliar: - $ mkdir -p build && cd build - $ meson .. - $ ninja + mkdir -p build + meson setup build + meson compile -C build === Build system features -* We use the +AX_ENABLE_BUILDDIR+ macro to enforce builds happening in a separate - directory. This is a prerequisite for the +AX_EXTEND_SRCDIR+ macro and building - in a separate directory is common practice anyway. In case this causes any - trouble when packaging i3 for your distribution, please open an issue. +* +ninja test+ runs the i3 testsuite. See docs/testsuite for details. -* +make check+ runs the i3 testsuite. See docs/testsuite for details. +* +meson dist+ builds a release tarball and runs tests on the result. -* +make distcheck+ (runs testsuite on +make dist+ result, tiny bit quicker - feedback cycle than waiting for the travis build to catch the issue). +* +meson -Ddocs=true -Dmans=true+ will enable the options to build docs and + manpages. These options require additional dependencies that are normally not + required for users who just want to build i3. -* +make uninstall+ (occasionally requested by users who compile from source) - -* +make+ will build manpages/docs by default if the tools are installed. - Conversely, manpages/docs are not tried to be built for users who don’t want - to install all these dependencies to get started hacking on i3. Manpages and - docs can be disabled with the +--disable-mans++ and ++--disable-docs++ - configure options respectively. - -* non-release builds will enable address sanitizer by default. Use the - +--disable-sanitizers+ configure option to turn off all sanitizers, and see - +--help+ for available sanitizers. - -* Coverage reports are now generated using +make check-code-coverage+, which - requires specifying +--enable-code-coverage+ when calling configure. +* +meson -Db_sanitize=address+ will enable the address sanitizer which is + disabled by default. A summary of memory leaks will be printed on program + exit. This can include false-positives. For other options of the +b_sanitize+ + flag see https://mesonbuild.com/Builtin-options.html. == Pull requests @@ -341,30 +329,26 @@ ensure that the operating system on which i3 is compiled has all the expected features, i3 comes with +include/queue.h+. On BSD systems, you can use +man queue(3)+. On Linux, you have to use google (or read the source). -The lists used are +SLIST+ (single linked lists), +CIRCLEQ+ (circular -queues) and +TAILQ+ (tail queues). Usually, only forward traversal is necessary, -so an +SLIST+ works fine. If inserting elements at arbitrary positions or at -the end of a list is necessary, a +TAILQ+ is used instead. However, for the -windows inside a container, a +CIRCLEQ+ is necessary to go from the currently -selected window to the window above/below. +The lists used are +SLIST+ (single linked lists), +CIRCLEQ+ (circular queues) +and +TAILQ+ (tail queues). Usually, +TAILQ+ is used which allows inserting +elements at arbitrary positions or at the end of the list. If only forward +traversal is necessary, an +SLIST+ can be used. +CIRCLEQ+ is used just to +manage the X11 state of each window. -== Naming conventions +[[startup]] +== Startup (src/main.c, main()) -There is a row of standard variables used in many events. The following names -should be chosen for those: - - * +conn+ is the xcb_connection_t - * +event+ is the event of the particular type - * +con+ names a container - * +current+ is a loop variable when using +TAILQ_FOREACH+ etc. - -== Startup (src/mainx.c, main()) +Among other things, the main() function does the following: * Establish the xcb connection + * Load the i3 config * Check for XKB extension on the separate X connection, load Xcursor - * Check for RandR screens (with a fall-back to Xinerama) + * Set up EWMH hints * Grab the keycodes for which bindings exist + * Check for XRandR screens * Manage all existing windows + * Exec configured startup processes + * Start i3bar if configured * Enter the event loop == Keybindings diff --git a/docs/i3bar-workspace-protocol b/docs/i3bar-workspace-protocol new file mode 100644 index 00000000..009b2a1d --- /dev/null +++ b/docs/i3bar-workspace-protocol @@ -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 monitor. +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 EVENT; 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 EVENT; 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 EVENT; 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 EVENT; 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 EVENT; do i3-msg -t get_workspaces; done; +} | jq --unbuffered -c '[ .[] | .name |= . + " foo" ]' +------------------------------ diff --git a/docs/ipc b/docs/ipc index 8d8bdfa5..de9eecd3 100644 --- a/docs/ipc +++ b/docs/ipc @@ -10,15 +10,22 @@ workspace bar. The method of choice for IPC in our case is a unix socket because it has very little overhead on both sides and is usually available without headaches in -most languages. In the default configuration file, the ipc-socket gets created -in +/tmp/i3-%u.XXXXXX/ipc-socket.%p+ where +%u+ is your UNIX username, +%p+ is -the PID of i3 and XXXXXX is a string of random characters from the portable -filename character set (see mkdtemp(3)). You can get the socketpath from i3 by -executing +i3 --get-socketpath+, which will print the path to the standard -output (plus a newline). +most languages. +By default i3 will set the path of the IPC socket based on: -All i3 utilities, like +i3-msg+ and +i3-input+ will read the +I3_SOCKET_PATH+ -X11 property, stored on the X11 root window. +1. The +ipc-socket+ configuration directive if it is used +2. The +I3SOCK+ environmental variable if it is set +3. +$XDG_RUNTIME_DIR/i3/ipc-socket.%p+ if the directory is available where +%p+ + is the PID of i3 and XXXXXX is a string of random characters +4. +/tmp/i3-%u.XXXXXX/ipc-socket.%p+ where +%u+ is your UNIX username + +You can get the socketpath from i3 by executing +i3 --get-socketpath+, which +will print the path to the standard output (plus a newline) or by reading the ++I3SOCK+ environmental variable. + +All i3 utilities, like +i3-msg+ and +i3-input+ will determine the path of the +IPC socket from the +I3SOCK+ environmental variable if it is set or the ++I3_SOCKET_PATH+ X11 property, stored on the X11 root window. [WARNING] .Use an existing library! @@ -997,9 +1004,18 @@ if ($is_event) { === workspace event This event consists of a single serialized map containing a property -+change (string)+ which indicates the type of the change ("focus", "init", -"empty", "urgent", "reload", "rename", "restored", "move"). A -+current (object)+ property will be present with the affected workspace ++change (string)+ which indicates the type of the change. + +* +empty+ – the workspace has become empty +* +focus+ – the workspace has received input focus +* +init+ – the workspace has been created +* +move+ – the workspace has been moved to a different output +* +reload+ – i3 config has been reloaded +* +rename+ – the workspace's name has changed +* +restored+ – the workspace's layout has changed to a previously saved layout +* +urgent+ – the workspace has become urgent or lost its urgent status + +A +current (object)+ property will be present with the affected workspace whenever the type of event affects a workspace (otherwise, it will be +null+). When the change is "focus", an +old (object)+ property will be present with the diff --git a/docs/testsuite b/docs/testsuite index ec87429c..22e96ab1 100644 --- a/docs/testsuite +++ b/docs/testsuite @@ -36,8 +36,8 @@ that, but it will also be useful for every future change. Apart from this document, you should also have a look at: -1. The "Modern Perl" book, which can be found at - http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +1. The "Modern Perl" book: + https://i3wm.org/downloads/modern_perl_a4.pdf 2. The latest Perl documentation of the "i3test" (general testcase setup) and "i3test::Test" (additional test instructions) modules: https://build.i3wm.org/docs/lib-i3test.html respectively @@ -76,30 +76,20 @@ used to install the testsuite. Many users prefer to use the more modern The tests additionally require +Xephyr(1)+ to run a nested X server. Install +xserver-xephyr+ on Debian or +xorg-server-xephyr+ on Arch Linux. -.Installing testsuite dependencies using cpanminus (preferred) +.Installing testsuite dependencies using cpanminus -------------------------------------------------------------------------------- -$ cd ~/i3/testcases -$ sudo apt-get install cpanminus -$ sudo cpanm . +# Install testsuite system-level dependencies. Xvfb is optional but recommended. +$ sudo apt-get install xcb-proto cpanminus xvfb xserver-xephyr +# Install dependencies in ~/perl5 local library +$ cpanm --local-lib=~/perl5 local::lib App::cpanminus Module::Install +# Activate the local library +$ eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib) +$ cd ~/i3/testcases/ +$ cpanm . $ cd ~/i3/AnyEvent-I3 -$ sudo cpanm Module::Install -$ sudo cpanm . +$ cpanm . -------------------------------------------------------------------------------- -If you don’t want to use cpanminus for some reason, the same works with cpan: - -.Installing testsuite dependencies using cpan --------------------------------------------------------------------------------- -$ cd ~/i3/testcases -$ sudo cpan . -$ cd ~/i3/AnyEvent-I3 -$ sudo cpan Module::Install -$ sudo cpan . --------------------------------------------------------------------------------- - -In case you don’t have root permissions, you can also install into your home -directory, see https://michael.stapelberg.de/cpan/ - === Mechanisms ==== Script: complete-run @@ -119,48 +109,57 @@ tests are run under Xvfb. --------------------------------------- $ cd ~/i3 -$ mkdir -p build && cd build +$ mkdir -p build -$ meson .. +$ meson setup build +$ cd build -$ ninja +$ meson compile # output omitted because it is very long -$ cd testcases - $ ./complete-run.pl # output omitted because it is very long All tests successful. Files=78, Tests=734, 27 wallclock secs ( 0.38 usr 0.48 sys + 17.65 cusr 3.21 csys = 21.72 CPU) Result: PASS -$ ./complete-run.pl t/04-floating.t -[:3] i3 startup: took 0.07s, status = 1 -[:3] Running t/04-floating.t with logfile testsuite-2011-09-24-16-06-04-4.0.2-226-g1eb011a/i3-log-for-04-floating.t -[:3] t/04-floating.t finished -[:3] killing i3 -output for t/04-floating.t: -ok 1 - use X11::XCB::Window; -ok 2 - The object isa X11::XCB::Window -ok 3 - Window is mapped -ok 4 - i3 raised the width to 75 -ok 5 - i3 raised the height to 50 -ok 6 - i3 did not map it to (0x0) -ok 7 - The object isa X11::XCB::Window -ok 8 - i3 let the width at 80 -ok 9 - i3 let the height at 90 -ok 10 - i3 mapped it to x=1 -ok 11 - i3 mapped it to y=18 -ok 12 - The object isa X11::XCB::Window -ok 13 - i3 let the width at 80 -ok 14 - i3 let the height at 90 -1..14 +$ ./complete-run.pl t/005-floating.t +Running tests under Xvfb display :99 +Starting 1 Xephyr instances, starting at :100... + +Rough time estimate for this run: 9.65 seconds + +Writing logfile to 'testsuite-2024-05-01-21-33-45-4.23-28-g5834b7e8/complete-run.log'... +[:100] i3/testcases/t/005-floating.t: finished +completed 0 of 1 tests All tests successful. -Files=1, Tests=14, 0 wallclock secs ( 0.01 usr 0.00 sys + 0.19 cusr 0.03 csys = 0.23 CPU) +Files=1, Tests=13, 0 wallclock secs ( 0.00 usr + 0.00 sys = 0.00 CPU) Result: PASS -$ less latest/i3-log-for-04-floating.t +The slowest tests are: + i3/testcases/t/005-floating.t with 0.07 seconds + +Test output: +[:100] i3/testcases/t/005-floating.t: starting +[:100] i3/testcases/t/005-floating.t: finished +output for i3/testcases/t/005-floating.t: +ok 1 - An object of class 'X11::XCB::Window' isa 'X11::XCB::Window' +ok 2 - Window is mapped +ok 3 - i3 raised the width to 75 +ok 4 - i3 raised the height to 50 +ok 5 - i3 did not map it to (0x0) +ok 6 - An object of class 'X11::XCB::Window' isa 'X11::XCB::Window' +ok 7 - i3 let the width at 80 +ok 8 - i3 let the height at 90 +ok 9 - i3 mapped it to x=20 +ok 10 - i3 mapped it to y=20 +ok 11 - An object of class 'X11::XCB::Window' isa 'X11::XCB::Window' +ok 12 - i3 let the width at 80 +ok 13 - i3 let the height at 90 +1..13 + +$ less latest/i3-log-for-005-floating.t ---------------------------------------- If your attempt to run the tests with a bare call to ./complete-run.pl fails, try this: @@ -172,37 +171,34 @@ $ ./complete-run.pl --parallel=1 --keep-xserver-output This will show the output of Xephyr, which is the X server implementation we use for testing. -===== make command: +make check+ -Make check runs the i3 testsuite. -You can still use ./testcases/complete-run.pl to get the interactive progress output. +===== ninja command: +ninja test+ ++ninja test+ runs the i3 testsuite. +You can still use ./complete-run.pl to get the interactive progress output. -.Example invocation of +make check+ +.Example invocation of +ninja test+ --------------------------------------- $ cd ~/i3 -$ mkdir -p build && cd build +$ mkdir -p build -$ meson .. +$ meson setup build +$ cd build -$ ninja -# output omitted because it is very long +$ ninja test +[1/102] Generating config.h with a custom command +[1/2] Running all tests. +1/1 complete-run OK 34.39s -$ make check -# output omitted because it is very long -PASS: testcases/complete-run.pl -============================================================================ -Testsuite summary for i3 4.13 -============================================================================ -# TOTAL: 1 -# PASS: 1 -# SKIP: 0 -# XFAIL: 0 -# FAIL: 0 -# XPASS: 0 -# ERROR: 0 -============================================================================ +Ok: 1 +Expected Fail: 0 +Fail: 0 +Unexpected Pass: 0 +Skipped: 0 +Timeout: 0 -$ less test-suite.log +Full log written to i3/build/meson-logs/testlog.txt + +$ less latest/complete-run.log ---------------------------------------- ==== Coverage testing diff --git a/docs/userguide b/docs/userguide index 120e4b90..171afda4 100644 --- a/docs/userguide +++ b/docs/userguide @@ -202,7 +202,8 @@ Floating windows are always on top of tiling windows. Since i3 4.21, it's possible to drag tiling containers using the mouse. The drag can be initiated either by dragging the window's titlebar or by pressing the <> and dragging the container while holding the -left-click button. +left-click button. See the <> option for configuring which +action triggers the tiling drag. Once the drag is initiated and the cursor has left the original container, drop indicators are created according to the position of the cursor relatively to @@ -215,6 +216,10 @@ Drop on container:: This happens when the mouse is relatively near the center of a container. If the mouse is released, the result is exactly as if you had run the +move container to mark+ command. See <>. + If the swap modifier is pressed before initiating the drag (+tiling_drag + swap_modifier+ set to Shift by default), the containers are swapped + instead. In that case, the result is exactly as if you had run the +swap + container with mark+ command. See <>. Drop as sibling:: This happens when the mouse is relatively near the edge of a container. If the mouse is released, the dragged container will become a sibling of the @@ -317,7 +322,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 @@ -353,15 +358,6 @@ keyboard layout. To start the wizard, use the command +i3-config-wizard+. Please note that you must not have +~/.i3/config+, otherwise the wizard will exit. -Since i3 4.0, a new configuration format is used. i3 will try to automatically -detect the format version of a config file based on a few different keywords, -but if you want to make sure that your config is read with the new format, -include the following line in your config file: - ---------------------- -# i3 config file (v4) ---------------------- - [[include]] === Include directive @@ -511,8 +507,8 @@ your bindings in the same physical location on the keyboard, use keycodes. If you don’t switch layouts, and want a clean and simple config file, use keysyms. -Some tools (such as +import+ or +xdotool+) might be unable to run upon a -KeyPress event, because the keyboard/pointer is still grabbed. For these +Some tools (such as +xdotool+) might be unable to run upon a +KeyPress event, because the keyboard is still grabbed. For these situations, the +--release+ flag can be used, which will execute the command after the keys have been released. @@ -742,8 +738,11 @@ This option determines which border style *new* windows will have. The default +normal+. Note that default_floating_border applies only to windows which are starting out as floating windows, e.g., dialog windows, but not windows that are floated later on. -Setting border style to +pixel+ eliminates title bars. The border style +normal+ allows you to -adjust edge border width while keeping your title bar. +Setting border style to +pixel+ eliminates title bars in split layouts. The border style ++normal+ allows you to adjust edge border width while keeping your title bar. + +The title bar is always visible in stacking and tabbed layouts, and this cannot be changed +through configuration. *Syntax*: --------------------------------------------- @@ -788,6 +787,10 @@ 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+. +[[_smart_borders]] ++hide_edge_borders+ has replaced the old +smart_borders+ syntax. Use the former +instead of the latter. + *Syntax*: ----------------------------------------------- hide_edge_borders none|vertical|horizontal|both|smart|smart_no_gaps @@ -798,27 +801,6 @@ hide_edge_borders none|vertical|horizontal|both|smart|smart_no_gaps 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) @@ -1242,19 +1224,21 @@ mouse_warping none When you are in fullscreen mode, some applications still open popup windows (take Xpdf for example). This is because these applications might not be aware that they are in fullscreen mode (they do not check the corresponding hint). -There are three things which are possible to do in this situation: +i3 supports four options for this situation: -1. Display the popup if it belongs to the fullscreen application only. This is - the default and should be reasonable behavior for most users. -2. Just ignore the popup (don’t map it). This won’t interrupt you while you are - in fullscreen. However, some apps might react badly to this (deadlock until - you go out of fullscreen). -3. Leave fullscreen mode. +1. +smart+: Display the popup if it belongs to the fullscreen application only. + This is the default and should be reasonable behavior for most users. +2. +ignore+: Just ignore the popup (don’t map it). This won’t interrupt you + while you are in fullscreen. However, some apps might react badly to this + (deadlock until you go out of fullscreen). +3. +leave_fullscreen+: Leave fullscreen mode. +4. +all+: Since i3 4.24: Display all floating windows regardless to which + application they belong to. *Syntax*: ------------------------------------------------------ -popup_during_fullscreen smart|ignore|leave_fullscreen ------------------------------------------------------ +--------------------------------------------------------- +popup_during_fullscreen smart|ignore|leave_fullscreen|all +--------------------------------------------------------- *Example*: ------------------------------ @@ -1438,10 +1422,18 @@ bindsym Mod1+F fullscreen toggle You can configure how to initiate the tiling drag feature (see <>). +The default is +modifier+. + +Since i3 4.24, you can configure a modifier key which, when pressed, will swap +instead of moving containers when dropping directly onto another container. +Defaults to +Shift+. Note that you have to be pressing both the floating +modifier and the swap modifier before the drag is initiated. + *Syntax*: -------------------------------- tiling_drag off tiling_drag modifier|titlebar [modifier|titlebar] +tiling_drag swap_modifier -------------------------------- *Examples*: @@ -1454,6 +1446,14 @@ tiling_drag modifier titlebar # Disable tiling drag altogether tiling_drag off + +# Use Control to swap containers +tiling_drag swap_modifier Control + +# Setting the swap_modifier to be the same key as the floating modifier will +# always swap without the need to hold two keys +floating_modifier Mod4 +tiling_drag swap_modifier Mod4 -------------------------------- [[gaps]] @@ -1611,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 <> for the statusline option and +https://i3wm.org/docs/i3bar-workspace-protocol.html for the detailed protocol. + +*Syntax*: +------------------------ +workspace_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 @@ -2174,6 +2198,10 @@ bindsym $mod+x [class="(?i)firefox"] kill # kill only the About dialog from Firefox bindsym $mod+x [class="Firefox" window_role="About"] kill +# kill all windows except for Firefox and Gnome Terminal. +# case-insensitive and uses negative lookaheads, supported by PCRE +bindsym $mod+x [class="^(?i)(?!firefox)(?!gnome-terminal).*"] kill + # enable floating mode and move container to workspace 4 for_window [class="^evil-app$"] floating enable, move container to workspace 4 @@ -2397,6 +2425,9 @@ available: :: Sets focus to the container that matches the specified criteria. See <>. +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:: @@ -2423,6 +2454,7 @@ output:: *Syntax*: ---------------------------------------------- focus + focus workspace focus left|right|down|up focus parent|child|floating|tiling|mode_toggle focus next|prev [sibling] @@ -2434,6 +2466,10 @@ focus output left|right|down|up|current|primary|nonprimary|next| [outpu # 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 @@ -2447,7 +2483,7 @@ bindsym $mod+u focus parent bindsym $mod+g focus mode_toggle # Focus the next output (effectively toggles when you only have two outputs) -bindsym $mod+x move workspace to output next +bindsym $mod+x focus output next # Focus the output right to the current one bindsym $mod+x focus output right @@ -2462,7 +2498,7 @@ bindsym $mod+x focus output primary 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: @@ -2519,6 +2555,7 @@ bindsym $mod+c move absolute position center bindsym $mod+m move position mouse ------------------------------------------------------- +[[swapping_containers]] === Swapping containers Two containers can be swapped (i.e., move to each other's position) by using diff --git a/generate-command-parser.pl b/generate-command-parser.pl index 17728736..42f2da15 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -1,7 +1,7 @@ #!/usr/bin/env perl # vim:ts=4:sw=4:expandtab # -# i3 - an improved dynamic tiling window manager +# i3 - an improved tiling window manager # © 2009 Michael Stapelberg and contributors (see also: LICENSE) # # generate-command-parser.pl: script to generate parts of the command parser @@ -147,11 +147,12 @@ for my $state (@keys) { $next_state ||= 'INITIAL'; my $fmt = $cmd; # Replace the references to identified literals (like $workspace) with - # calls to get_string(). Also replaces state names (like FOR_WINDOW) - # with their ID (useful for cfg_criteria_init(FOR_WINDOW) e.g.). + # calls to parser_get_string(). Also replaces state names (like + # FOR_WINDOW) with their ID (useful for cfg_criteria_init(FOR_WINDOW) + # e.g.). $cmd =~ s/$_/$statenum{$_}/g for @keys; - $cmd =~ s/\$([a-z_]+)/get_string(stack, "$1")/g; - $cmd =~ s/\&([a-z_]+)/get_long(stack, "$1")/g; + $cmd =~ s/\$([a-z_]+)/parser_get_string(stack, "$1")/g; + $cmd =~ s/\&([a-z_]+)/parser_get_long(stack, "$1")/g; # For debugging/testing, we print the call using printf() and thus need # to generate a format string. The format uses %d for s, # literal numbers or state IDs and %s for NULL, s and literal diff --git a/i3-config-wizard/main.c b/i3-config-wizard/main.c index c5ec071e..5d08ca21 100644 --- a/i3-config-wizard/main.c +++ b/i3-config-wizard/main.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * i3-config-wizard: Program to convert configs using keycodes to configs using @@ -75,9 +75,9 @@ xcb_visualtype_t *visual_type = NULL; #define WIN_HEIGHT (15 * font.height + TEXT_PADDING) #define col_x(col) \ - (((col)-1) * char_width + TEXT_PADDING) + (((col) - 1) * char_width + TEXT_PADDING) #define row_y(row) \ - (((row)-1) * font.height + TEXT_PADDING) + (((row) - 1) * font.height + TEXT_PADDING) enum { STEP_WELCOME, STEP_GENERATE } current_step = STEP_WELCOME; @@ -157,8 +157,9 @@ static struct stack_entry stack[10]; static void push_string(const char *identifier, const char *str) { for (int c = 0; c < 10; c++) { if (stack[c].identifier != NULL && - strcmp(stack[c].identifier, identifier) != 0) + strcmp(stack[c].identifier, identifier) != 0) { continue; + } if (stack[c].identifier == NULL) { /* Found a free slot, let’s store it here. */ stack[c].identifier = identifier; @@ -184,8 +185,9 @@ static void push_string(const char *identifier, const char *str) { static void push_long(const char *identifier, long num) { for (int c = 0; c < 10; c++) { - if (stack[c].identifier != NULL) + if (stack[c].identifier != NULL) { continue; + } /* Found a free slot, let’s store it here. */ stack[c].identifier = identifier; stack[c].val.num = num; @@ -204,18 +206,21 @@ static void push_long(const char *identifier, long num) { static const char *get_string(const char *identifier) { for (int c = 0; c < 10; c++) { - if (stack[c].identifier == NULL) + if (stack[c].identifier == NULL) { break; - if (strcmp(identifier, stack[c].identifier) == 0) + } + if (strcmp(identifier, stack[c].identifier) == 0) { return stack[c].val.str; + } } return NULL; } static void clear_stack(void) { for (int c = 0; c < 10; c++) { - if (stack[c].type == STACK_STR) + if (stack[c].type == STACK_STR) { free(stack[c].val.str); + } stack[c].identifier = NULL; stack[c].val.str = NULL; stack[c].val.num = 0; @@ -233,11 +238,13 @@ static bool keysym_used_on_other_key(KeySym sym, xcb_keycode_t except_keycode) { max_keycode = xcb_get_setup(conn)->max_keycode; for (i = min_keycode; i && i <= max_keycode; i++) { - if (i == except_keycode) + if (i == except_keycode) { continue; + } for (int level = 0; level < 4; level++) { - if (xcb_key_symbols_get_keysym(keysyms, i, level) != sym) + if (xcb_key_symbols_get_keysym(keysyms, i, level) != sym) { continue; + } return true; } } @@ -269,22 +276,27 @@ static char *next_state(const cmdp_token *token) { * qwerty (yes, that happens quite often). */ const xkb_keysym_t *syms; int num = xkb_keymap_key_get_syms_by_level(xkb_keymap, keycode, 0, 0, &syms); - if (num == 0) + if (num == 0) { errx(1, "xkb_keymap_key_get_syms_by_level returned no symbols for keycode %d", keycode); - if (!keysym_used_on_other_key(syms[0], keycode)) + } + if (!keysym_used_on_other_key(syms[0], keycode)) { level = 0; + } } const xkb_keysym_t *syms; int num = xkb_keymap_key_get_syms_by_level(xkb_keymap, keycode, 0, level, &syms); - if (num == 0) + if (num == 0) { errx(1, "xkb_keymap_key_get_syms_by_level returned no symbols for keycode %d", keycode); - if (num > 1) + } + if (num > 1) { printf("xkb_keymap_key_get_syms_by_level (keycode = %d) returned %d symbolsinstead of 1, using only the first one.\n", keycode, num); + } char str[4096]; - if (xkb_keysym_get_name(syms[0], str, sizeof(str)) == -1) + if (xkb_keysym_get_name(syms[0], str, sizeof(str)) == -1) { errx(EXIT_FAILURE, "xkb_keysym_get_name(%u) failed", syms[0]); + } const char *release = get_string("release"); char *res; char *modrep = (modifiers == NULL ? sstrdup("") : sstrdup(modifiers)); @@ -303,8 +315,9 @@ static char *next_state(const cmdp_token *token) { /* See if we are jumping back to a state in which we were in previously * (statelist contains INITIAL) and just move statelist_idx accordingly. */ for (int i = 0; i < statelist_idx; i++) { - if (statelist[i] != _next_state) + if (statelist[i] != _next_state) { continue; + } statelist_idx = i + 1; return NULL; } @@ -329,8 +342,9 @@ static char *rewrite_binding(const char *input) { while ((size_t)(walk - input) <= len) { /* Skip whitespace before every token, newlines are relevant since they * separate configuration directives. */ - while ((*walk == ' ' || *walk == '\t') && *walk != '\0') + while ((*walk == ' ' || *walk == '\t') && *walk != '\0') { walk++; + } cmdp_token_ptr *ptr = &(tokens[state]); for (c = 0; c < ptr->n; c++) { @@ -339,11 +353,13 @@ static char *rewrite_binding(const char *input) { /* A literal. */ if (token->name[0] == '\'') { if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) { - if (token->identifier != NULL) + if (token->identifier != NULL) { push_string(token->identifier, token->name + 1); + } walk += strlen(token->name) - 1; - if ((result = next_state(token)) != NULL) + if ((result = next_state(token)) != NULL) { return result; + } break; } continue; @@ -355,20 +371,24 @@ static char *rewrite_binding(const char *input) { errno = 0; long int num = strtol(walk, &end, 10); if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) || - (errno != 0 && num == 0)) + (errno != 0 && num == 0)) { continue; + } /* No valid numbers found */ - if (end == walk) + if (end == walk) { continue; + } - if (token->identifier != NULL) + if (token->identifier != NULL) { push_long(token->identifier, num); + } /* Set walk to the first non-number character */ walk = end; - if ((result = next_state(token)) != NULL) + if ((result = next_state(token)) != NULL) { return result; + } break; } @@ -379,12 +399,14 @@ static char *rewrite_binding(const char *input) { if (*walk == '"') { beginning++; walk++; - while (*walk != '\0' && (*walk != '"' || *(walk - 1) == '\\')) + while (*walk != '\0' && (*walk != '"' || *(walk - 1) == '\\')) { walk++; + } } else { if (token->name[0] == 's') { - while (*walk != '\0' && *walk != '\r' && *walk != '\n') + while (*walk != '\0' && *walk != '\r' && *walk != '\n') { walk++; + } } else { /* For a word, the delimiters are white space (' ' or * '\t'), closing square bracket (]), comma (,) and @@ -392,8 +414,9 @@ static char *rewrite_binding(const char *input) { while (*walk != ' ' && *walk != '\t' && *walk != ']' && *walk != ',' && *walk != ';' && *walk != '\r' && - *walk != '\n' && *walk != '\0') + *walk != '\n' && *walk != '\0') { walk++; + } } } if (walk != beginning) { @@ -406,27 +429,32 @@ static char *rewrite_binding(const char *input) { /* We only handle escaped double quotes to not break * backwards compatibility with people using \w in * regular expressions etc. */ - if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"') + if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"') { inpos++; + } str[outpos] = beginning[inpos]; } - if (token->identifier) + if (token->identifier) { push_string(token->identifier, str); + } free(str); /* If we are at the end of a quoted string, skip the ending * double quote. */ - if (*walk == '"') + if (*walk == '"') { walk++; - if ((result = next_state(token)) != NULL) + } + if ((result = next_state(token)) != NULL) { return result; + } break; } } if (strcmp(token->name, "end") == 0) { if (*walk == '\0' || *walk == '\n' || *walk == '\r') { - if ((result = next_state(token)) != NULL) + if ((result = next_state(token)) != NULL) { return result; + } /* To make sure we start with an appropriate matching data * structure for commands which do *not* specify any * criteria, we re-initialize the criteria system after @@ -513,17 +541,19 @@ static int handle_expose(void) { txt(13, 10, "to abort", white, black); /* the not-selected modifier */ - if (modifier == MOD_Mod4) + if (modifier == MOD_Mod4) { txt(5, 5, "", white, black); - else + } else { txt(5, 4, "", white, black); + } /* the selected modifier */ set_font(&bold_font); - if (modifier == MOD_Mod4) + if (modifier == MOD_Mod4) { txt(2, 4, "-> ", white, black); - else + } else { txt(2, 5, "-> ", white, black); + } set_font(&font); txt(4, 9, "", green, black); @@ -562,8 +592,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press strlen("i3: generate config"), "i3: generate config"); xcb_flush(conn); - } else + } else { finish(); + } } /* Swap between modifiers when up or down is pressed. */ @@ -573,8 +604,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press } /* cancel any time */ - if (sym == XK_Escape) + if (sym == XK_Escape) { exit(0); + } /* Check if this is Mod1 or Mod4. The modmap contains Shift, Lock, Control, * Mod1, Mod2, Mod3, Mod4, Mod5 (in that order) */ @@ -583,8 +615,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press int mask = 3; for (int i = 0; i < modmap_reply->keycodes_per_modifier; i++) { xcb_keycode_t code = modmap[(mask * modmap_reply->keycodes_per_modifier) + i]; - if (code == XCB_NONE) + if (code == XCB_NONE) { continue; + } printf("Modifier keycode for Mod1: 0x%02x\n", code); if (code == event->detail) { modifier = MOD_Mod1; @@ -596,8 +629,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press mask = 6; for (int i = 0; i < modmap_reply->keycodes_per_modifier; i++) { xcb_keycode_t code = modmap[(mask * modmap_reply->keycodes_per_modifier) + i]; - if (code == XCB_NONE) + if (code == XCB_NONE) { continue; + } printf("Modifier keycode for Mod4: 0x%02x\n", code); if (code == event->detail) { modifier = MOD_Mod4; @@ -614,11 +648,13 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press * */ static void handle_button_press(xcb_button_press_event_t *event) { - if (current_step != STEP_GENERATE) + if (current_step != STEP_GENERATE) { return; + } - if (event->event_x < col_x(5) || event->event_x > col_x(10)) + if (event->event_x < col_x(5) || event->event_x > col_x(10)) { return; + } if (event->event_y >= row_y(4) && event->event_y <= (row_y(4) + font.height)) { modifier = MOD_Mod4; @@ -640,20 +676,24 @@ static void finish(void) { struct xkb_context *xkb_context; - if ((xkb_context = xkb_context_new(0)) == NULL) + if ((xkb_context = xkb_context_new(0)) == NULL) { errx(1, "could not create xkbcommon context"); + } int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn); - if ((xkb_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) + if ((xkb_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) { errx(1, "xkb_x11_keymap_new_from_device failed"); + } FILE *kc_config = fopen(SYSCONFDIR "/i3/config.keycodes", "r"); - if (kc_config == NULL) + if (kc_config == NULL) { err(1, "Could not open input file \"%s\"", SYSCONFDIR "/i3/config.keycodes"); + } FILE *ks_config = fopen(config_path, "w"); - if (ks_config == NULL) + if (ks_config == NULL) { err(1, "Could not open output config file \"%s\"", config_path); + } free(config_path); char *line = NULL; @@ -684,8 +724,9 @@ static void finish(void) { #endif /* skip the warning block at the beginning of the input file */ if (head_of_file && - strncmp("# WARNING", line, strlen("# WARNING")) == 0) + strncmp("# WARNING", line, strlen("# WARNING")) == 0) { continue; + } head_of_file = false; @@ -699,10 +740,11 @@ static void finish(void) { /* Set the modifier the user chose */ if (strncmp(walk, "set $mod ", strlen("set $mod ")) == 0) { - if (modifier == MOD_Mod1) + if (modifier == MOD_Mod1) { fputs("set $mod Mod1\n", ks_config); - else + } else { fputs("set $mod Mod4\n", ks_config); + } continue; } @@ -767,12 +809,13 @@ int main(int argc, char *argv[]) { return 0; case 'm': headless_run = true; - if (strcmp(optarg, "alt") == 0) + if (strcmp(optarg, "alt") == 0) { modifier = MOD_Mod1; - else if (strcmp(optarg, "win") == 0) + } else if (strcmp(optarg, "win") == 0) { modifier = MOD_Mod4; - else + } else { err(EXIT_FAILURE, "Invalid modifier key %s", optarg); + } break; case 'h': printf("i3-config-wizard " I3_VERSION "\n"); @@ -789,8 +832,9 @@ int main(int argc, char *argv[]) { } /* Always write to $XDG_CONFIG_HOME/i3/config by default. */ - if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) + if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) { xdg_config_home = "~/.config"; + } xdg_config_home = resolve_tilde(xdg_config_home); sasprintf(&config_path, "%s/i3/config", xdg_config_home); @@ -799,9 +843,11 @@ int main(int argc, char *argv[]) { char *config_dir; struct stat stbuf; sasprintf(&config_dir, "%s/i3", xdg_config_home); - if (stat(config_dir, &stbuf) != 0) - if (mkdirp(config_dir, DEFAULT_DIR_MODE) != 0) + if (stat(config_dir, &stbuf) != 0) { + if (mkdirp(config_dir, DEFAULT_DIR_MODE) != 0) { err(EXIT_FAILURE, "mkdirp(%s) failed", config_dir); + } + } free(config_dir); free(xdg_config_home); @@ -815,8 +861,9 @@ int main(int argc, char *argv[]) { int screen; if ((conn = xcb_connect(NULL, &screen)) == NULL || - xcb_connection_has_error(conn)) + xcb_connection_has_error(conn)) { errx(1, "Cannot open display"); + } if (xkb_x11_setup_xkb_extension(conn, XKB_X11_MIN_MAJOR_XKB_VERSION, @@ -825,8 +872,9 @@ int main(int argc, char *argv[]) { NULL, NULL, &xkb_base_event, - &xkb_base_error) != 1) + &xkb_base_error) != 1) { errx(EXIT_FAILURE, "Could not setup XKB extension."); + } keysyms = xcb_key_symbols_alloc(conn); xcb_get_modifier_mapping_cookie_t modmap_cookie; @@ -852,8 +900,9 @@ int main(int argc, char *argv[]) { root_screen = xcb_aux_get_screen(conn, screen); root = root_screen->root; - if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL))) + if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL))) { errx(EXIT_FAILURE, "Could not get modifier mapping"); + } xcb_numlock_mask = get_mod_mask_for(XCB_NUM_LOCK, symbols, modmap_reply); diff --git a/i3-dump-log/main.c b/i3-dump-log/main.c index 0ce22264..35872ccd 100644 --- a/i3-dump-log/main.c +++ b/i3-dump-log/main.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * i3-dump-log/main.c: Dumps the i3 SHM log to stdout. @@ -39,8 +39,9 @@ static int ipcfd = -1; static void disable_shmlog(void) { const char *disablecmd = "debuglog off; shmlog off"; if (ipc_send_message(ipcfd, strlen(disablecmd), - I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)disablecmd) != 0) + I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)disablecmd) != 0) { err(EXIT_FAILURE, "IPC send"); + } /* Ensure the command was sent by waiting for the reply: */ uint32_t reply_length = 0; @@ -53,8 +54,9 @@ static void disable_shmlog(void) { } static int check_for_wrap(void) { - if (wrap_count == header->wrap_count) + if (wrap_count == header->wrap_count) { return 0; + } /* The log wrapped. Print the remaining content and reset walk to the top * of the log. */ @@ -94,8 +96,7 @@ int main(int argc, char *argv[]) { {"follow", no_argument, 0, 'f'}, #endif {"help", no_argument, 0, 'h'}, - {0, 0, 0, 0} - }; + {0, 0, 0, 0}}; #if !defined(__OpenBSD__) char *options_string = "s:vfVh"; @@ -148,8 +149,9 @@ int main(int argc, char *argv[]) { ipcfd = ipc_connect(NULL); const char *enablecmd = "debuglog on; shmlog 5242880"; if (ipc_send_message(ipcfd, strlen(enablecmd), - I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)enablecmd) != 0) + I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)enablecmd) != 0) { err(EXIT_FAILURE, "IPC send"); + } /* By the time we receive a reply, I3_SHMLOG_PATH is set: */ uint32_t reply_length = 0; uint8_t *reply = NULL; @@ -175,8 +177,9 @@ int main(int argc, char *argv[]) { } } - if (*shmname == '\0') + if (*shmname == '\0') { errx(EXIT_FAILURE, "Cannot dump log: SHM logging is disabled in i3."); + } struct stat statbuf; @@ -211,8 +214,9 @@ int main(int argc, char *argv[]) { /* In case there was a write to the buffer already, skip the first * old line, it very likely is mangled. Not a problem, though, the log * is chatty enough to have plenty lines left. */ - while (*walk != '\n') + while (*walk != '\n') { walk++; + } walk++; } diff --git a/i3-input/keysym2ucs.c b/i3-input/keysym2ucs.c index 80375099..7de7a37f 100644 --- a/i3-input/keysym2ucs.c +++ b/i3-input/keysym2ucs.c @@ -822,21 +822,23 @@ long keysym2ucs(xcb_keysym_t keysym) { /* first check for Latin-1 characters (1:1 mapping) */ if ((keysym >= 0x0020 && keysym <= 0x007e) || - (keysym >= 0x00a0 && keysym <= 0x00ff)) + (keysym >= 0x00a0 && keysym <= 0x00ff)) { return keysym; + } /* also check for directly encoded 24-bit UCS characters */ - if ((keysym & 0xff000000) == 0x01000000) + if ((keysym & 0xff000000) == 0x01000000) { return keysym & 0x00ffffff; + } /* binary search in table */ while (max >= min) { mid = (min + max) / 2; - if (keysymtab[mid].keysym < keysym) + if (keysymtab[mid].keysym < keysym) { min = mid + 1; - else if (keysymtab[mid].keysym > keysym) + } else if (keysymtab[mid].keysym > keysym) { max = mid - 1; - else { + } else { /* found it */ return keysymtab[mid].ucs; } diff --git a/i3-input/main.c b/i3-input/main.c index ef9e0701..2ea94142 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * i3-input/main.c: Utility which lets the user input commands and sends them @@ -102,7 +102,7 @@ static uint8_t *concat_strings(char **glyphs, int max) { walk += strlen(glyphs[c]); } } - printf("output = %s\n", output); + printf("output = %s\n", (char *)output); return output; } @@ -111,7 +111,7 @@ static uint8_t *concat_strings(char **glyphs, int max) { * be called from the code with event == NULL or from X with event != NULL. * */ -static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) { +static int handle_expose(xcb_connection_t *conn) { printf("expose!\n"); color_t border_color = draw_util_hex_to_color("#FF0000"); @@ -163,29 +163,32 @@ static void finish_input(void) { char *command = (char *)concat_strings(glyphs_utf8, input_position); /* count the occurrences of %s in the string */ - int c; - int len = strlen(format); - int cnt = 0; - for (c = 0; c < (len - 1); c++) - if (format[c] == '%' && format[c + 1] == 's') + const size_t len = strlen(format); + size_t cnt = 0; + for (size_t c = 0; c < (len - 1); c++) { + if (format[c] == '%' && format[c + 1] == 's') { cnt++; - printf("occurrences = %d\n", cnt); + } + } + printf("occurrences = %zu\n", cnt); /* allocate space for the output */ - int inputlen = strlen(command); - char *full = scalloc(strlen(format) - (2 * cnt) /* format without all %s */ - + (inputlen * cnt) /* replaced %s */ - + 1, /* trailing NUL */ - 1); + const size_t input_len = strlen(command); + const size_t full_len = MAX(input_len, /* avoid compiler warning */ + strlen(format) - (2 * cnt) /* format without all %s */ + + (input_len * cnt) /* replaced %s */ + + 1 /* trailing NUL */ + ); + char *full = scalloc(full_len, 1); char *dest = full; - for (c = 0; c < len; c++) { - /* if this is not % or it is % but without a following 's', - * just copy the character */ - if (format[c] != '%' || (c == (len - 1)) || format[c + 1] != 's') + for (size_t c = 0; c < len; c++) { + /* if this is not % or it is % but without a following 's', just copy + * the character */ + if (format[c] != '%' || (c == (len - 1)) || format[c + 1] != 's') { *(dest++) = format[c]; - else { - strncat(dest, command, inputlen); - dest += inputlen; + } else { + strncat(dest, command, input_len); + dest += input_len; /* skip the following 's' of '%s' */ c++; } @@ -223,8 +226,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press /* If modeswitch is currently active, we need to look in group 2 or 3, * respectively. */ - if (modeswitch_active) + if (modeswitch_active) { col += 2; + } xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, col); if (sym == XK_Mode_switch) { @@ -233,17 +237,19 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press return 1; } - if (sym == XK_Return) + if (sym == XK_Return) { finish_input(); + } if (sym == XK_BackSpace) { - if (input_position == 0) + if (input_position == 0) { return 1; + } input_position--; free(glyphs_utf8[input_position]); - handle_expose(NULL, conn, NULL); + handle_expose(conn); return 1; } if (sym == XK_Escape) { @@ -259,8 +265,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym)); printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym)); - if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym)) + if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym)) { return 1; + } printf("sym = %c (%d)\n", sym, sym); @@ -284,10 +291,11 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press glyphs_utf8[input_position] = out; input_position++; - if (input_position == limit) + if (input_position == limit) { finish_input(); + } - handle_expose(NULL, conn, NULL); + handle_expose(conn); return 1; } @@ -399,7 +407,7 @@ int main(int argc, char *argv[]) { socket_path = sstrdup(optarg); break; case 'v': - printf("i3-input " I3_VERSION); + printf("i3-input " I3_VERSION "\n"); return EXIT_OK; case 'p': /* This option is deprecated, but will still work in i3 v4.1, 4.2 and 4.3 */ @@ -439,8 +447,9 @@ int main(int argc, char *argv[]) { int screen; conn = xcb_connect(NULL, &screen); - if (!conn || xcb_connection_has_error(conn)) + if (!conn || xcb_connection_has_error(conn)) { die("Cannot open display"); + } sockfd = ipc_connect(socket_path); @@ -453,8 +462,9 @@ int main(int argc, char *argv[]) { font = load_font(pattern ? pattern : "pango:monospace 8", true); set_font(&font); - if (prompt != NULL) + if (prompt != NULL) { prompt_offset = predict_text_width(prompt); + } const xcb_rectangle_t win_pos = get_window_position(); @@ -508,11 +518,12 @@ int main(int argc, char *argv[]) { while ((event = xcb_wait_for_event(conn)) != NULL) { if (event->response_type == 0) { fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence); + free(event); continue; } /* Strip off the highest bit (set if the event is generated) */ - int type = (event->response_type & 0x7F); + const int type = (event->response_type & 0x7F); switch (type) { case XCB_KEY_PRESS: @@ -525,9 +536,10 @@ int main(int argc, char *argv[]) { case XCB_EXPOSE: if (((xcb_expose_event_t *)event)->count == 0) { - handle_expose(NULL, conn, (xcb_expose_event_t *)event); + handle_expose(conn); } - + break; + default: break; } diff --git a/i3-msg/main.c b/i3-msg/main.c index 239ac46f..38f7e022 100644 --- a/i3-msg/main.c +++ b/i3-msg/main.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * i3-msg/main.c: Utility which sends messages to a running i3-instance using @@ -61,22 +61,24 @@ static int exit_code = 0; static reply_t last_reply; static int reply_boolean_cb(void *params, int val) { - if (strcmp(last_key, "success") == 0) + if (strcmp(last_key, "success") == 0) { last_reply.success = val; + } return 1; } static int reply_string_cb(void *params, const unsigned char *val, size_t len) { char *str = sstrndup((const char *)val, len); - if (strcmp(last_key, "error") == 0) + if (strcmp(last_key, "error") == 0) { last_reply.error = str; - else if (strcmp(last_key, "input") == 0) + } else if (strcmp(last_key, "input") == 0) { last_reply.input = str; - else if (strcmp(last_key, "errorposition") == 0) + } else if (strcmp(last_key, "errorposition") == 0) { last_reply.errorposition = str; - else + } else { free(str); + } return 1; } @@ -146,10 +148,6 @@ static yajl_callbacks config_callbacks = { }; int main(int argc, char *argv[]) { -#if defined(__OpenBSD__) - if (pledge("stdio rpath unix", NULL) == -1) - err(EXIT_FAILURE, "pledge"); -#endif char *socket_path = NULL; int o, option_index = 0; uint32_t message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND; @@ -244,12 +242,14 @@ int main(int argc, char *argv[]) { optind++; } - if (!payload) + if (!payload) { payload = sstrdup(""); + } int sockfd = ipc_connect(socket_path); - if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t *)payload) == -1) + if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t *)payload) == -1) { err(EXIT_FAILURE, "IPC: write()"); + } free(payload); uint32_t reply_length; @@ -257,12 +257,14 @@ int main(int argc, char *argv[]) { uint8_t *reply; int ret; if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) { - if (ret == -1) + if (ret == -1) { err(EXIT_FAILURE, "IPC: read()"); + } exit(1); } - if (reply_type != message_type) + if (reply_type != message_type) { errx(EXIT_FAILURE, "IPC: Received reply of type %d but expected %d", reply_type, message_type); + } /* For the reply of commands, have a look if that command was successful. * If not, nicely format the error message. */ if (reply_type == I3_IPC_REPLY_TYPE_COMMAND) { @@ -303,8 +305,9 @@ int main(int argc, char *argv[]) { do { free(reply); if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) { - if (ret == -1) + if (ret == -1) { err(EXIT_FAILURE, "IPC: read()"); + } exit(1); } diff --git a/i3-nagbar/main.c b/i3-nagbar/main.c index 7d9c0901..a191f4b3 100644 --- a/i3-nagbar/main.c +++ b/i3-nagbar/main.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * i3-nagbar is a utility which displays a nag message, for example in the case @@ -118,7 +118,7 @@ static void start_application(const char *command) { if (fork() == 0) { /* This is the child */ execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL); - /* not reached */ + err(EXIT_FAILURE, "execl return"); /* only reached on error */ } exit(0); } @@ -126,9 +126,11 @@ static void start_application(const char *command) { } static button_t *get_button_at(int16_t x, int16_t y) { - for (int c = 0; c < buttoncnt; c++) - if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width)) + for (int c = 0; c < buttoncnt; c++) { + if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width)) { return &buttons[c]; + } + } return NULL; } @@ -148,11 +150,13 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve printf("button released on x = %d, y = %d\n", event->event_x, event->event_y); /* If the user hits the close button, we exit(0) */ - if (event->event_x >= btn_close.x && event->event_x < btn_close.x + btn_close.width) + if (event->event_x >= btn_close.x && event->event_x < btn_close.x + btn_close.width) { exit(0); + } button_t *button = get_button_at(event->event_x, event->event_y); - if (!button) + if (!button) { return; + } /* We need to create a custom script containing our actual command * since not every terminal emulator which is contained in @@ -238,7 +242,7 @@ static int button_draw(button_t *button, int position) { * be called from the code with event == NULL or from X with event != NULL. * */ -static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) { +static int handle_expose(xcb_connection_t *conn) { /* draw background */ draw_util_clear_surface(&bar, color_background); /* draw message */ @@ -460,8 +464,9 @@ int main(int argc, char *argv[]) { buttons[buttoncnt].action); buttoncnt++; printf("now %d buttons\n", buttoncnt); - if (optind < argc) + if (optind < argc) { optind++; + } break; } } @@ -470,8 +475,9 @@ int main(int argc, char *argv[]) { int screens; if ((conn = xcb_connect(NULL, &screens)) == NULL || - xcb_connection_has_error(conn)) + xcb_connection_has_error(conn)) { die("Cannot open display"); + } /* Place requests for the atoms we need as soon as possible */ #define xmacro(atom) \ @@ -497,7 +503,7 @@ int main(int argc, char *argv[]) { } else { /* Yellowish theme for warnings */ color_button_background = draw_util_hex_to_color("#ffc100"); - color_background = draw_util_hex_to_color("#ffa8000"); + color_background = draw_util_hex_to_color("#ffa800"); color_text = draw_util_hex_to_color("#000000"); color_border = draw_util_hex_to_color("#ab7100"); color_border_bottom = draw_util_hex_to_color("#ab7100"); @@ -507,11 +513,6 @@ int main(int argc, char *argv[]) { font = load_font(pattern, true); set_font(&font); -#if defined(__OpenBSD__) - if (pledge("stdio rpath wpath cpath getpw proc exec", NULL) == -1) - err(EXIT_FAILURE, "pledge"); -#endif - /* Default values if we cannot determine the preferred window position. */ xcb_rectangle_t win_pos = (xcb_rectangle_t){50, 50, 500, font.height + 2 * MSG_PADDING + BAR_BORDER}; if (position_on_primary) { @@ -632,7 +633,7 @@ int main(int argc, char *argv[]) { switch (type) { case XCB_EXPOSE: if (((xcb_expose_event_t *)event)->count == 0) { - handle_expose(conn, (xcb_expose_event_t *)event); + handle_expose(conn); } break; diff --git a/i3-sensible-terminal b/i3-sensible-terminal index bee303f8..01b0c180 100755 --- a/i3-sensible-terminal +++ b/i3-sensible-terminal @@ -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 wezterm; 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 rio; do if command -v "$terminal" > /dev/null 2>&1; then exec "$terminal" "$@" fi diff --git a/i3bar/.gitignore b/i3bar/.gitignore index 6aad070b..f15fdbe5 100644 --- a/i3bar/.gitignore +++ b/i3bar/.gitignore @@ -1,4 +1,3 @@ i3bar -*.o core doc/i3bar.1 diff --git a/i3bar/include/child.h b/i3bar/include/child.h index ae523bc0..e77b51e3 100644 --- a/i3bar/include/child.h +++ b/i3bar/include/child.h @@ -11,7 +11,7 @@ #include -#include +#include #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); diff --git a/i3bar/include/common.h b/i3bar/include/common.h index e0f2e7e4..9c13d02f 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -46,6 +46,9 @@ struct status_block { i3String *full_text; i3String *short_text; + bool use_short; + uint32_t render_length; + char *color; char *background; char *border; diff --git a/i3bar/include/configuration.h b/i3bar/include/configuration.h index 24079c5d..c9bae7c3 100644 --- a/i3bar/include/configuration.h +++ b/i3bar/include/configuration.h @@ -62,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; @@ -79,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. diff --git a/i3bar/include/mode.h b/i3bar/include/mode.h index e8e4296d..4646b9f4 100644 --- a/i3bar/include/mode.h +++ b/i3bar/include/mode.h @@ -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); diff --git a/i3bar/include/outputs.h b/i3bar/include/outputs.h index 4685e51e..dfe145fe 100644 --- a/i3bar/include/outputs.h +++ b/i3bar/include/outputs.h @@ -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 @@ -65,8 +65,6 @@ struct i3_output { surface_t statusline_buffer; /* How much of statusline_buffer's horizontal space was used on last statusline render. */ int statusline_width; - /* Whether statusline block short texts where used on last statusline render. */ - bool statusline_short_text; /* The actual window on which we draw. */ surface_t bar; diff --git a/i3bar/include/util.h b/i3bar/include/util.h index 1f563611..bb135c9e 100644 --- a/i3bar/include/util.h +++ b/i3bar/include/util.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ diff --git a/i3bar/include/workspaces.h b/i3bar/include/workspaces.h index ff61450c..6c8e7145 100644 --- a/i3bar/include/workspaces.h +++ b/i3bar/include/workspaces.h @@ -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 */ diff --git a/i3bar/include/xcb.h b/i3bar/include/xcb.h index 0e3ca22d..fa274d01 100644 --- a/i3bar/include/xcb.h +++ b/i3bar/include/xcb.h @@ -12,7 +12,6 @@ #include #include -//#include "outputs.h" #define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0 #define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1 diff --git a/i3bar/src/child.c b/i3bar/src/child.c index df4c6601..740912f0 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -8,8 +8,10 @@ * */ #include "common.h" +#include "queue.h" #include "yajl_utils.h" +#include /* isspace */ #include #include #include @@ -20,6 +22,7 @@ #include #include #include +#include #include #include @@ -27,14 +30,30 @@ #include /* 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 +146,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 +154,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)); } /* @@ -173,10 +197,11 @@ static int stdin_start_map(void *context) { memset(&(ctx->block), '\0', sizeof(struct status_block)); /* Default width of the separator block. */ - if (config.separator_symbol == NULL) + if (config.separator_symbol == NULL) { ctx->block.sep_block_width = logical_px(9); - else + } else { ctx->block.sep_block_width = logical_px(8) + separator_symbol_width; + } /* By default we draw all four borders if a border is set. */ ctx->block.border_top = 1; @@ -190,7 +215,7 @@ static int stdin_start_map(void *context) { static int stdin_map_key(void *context, const unsigned char *key, size_t len) { parser_ctx *ctx = context; FREE(ctx->last_map_key); - sasprintf(&(ctx->last_map_key), "%.*s", len, key); + sasprintf(&(ctx->last_map_key), "%.*s", (int)len, key); return 1; } @@ -229,15 +254,15 @@ static int stdin_string(void *context, const unsigned char *val, size_t len) { return 1; } if (strcasecmp(ctx->last_map_key, "color") == 0) { - sasprintf(&(ctx->block.color), "%.*s", len, val); + sasprintf(&(ctx->block.color), "%.*s", (int)len, val); return 1; } if (strcasecmp(ctx->last_map_key, "background") == 0) { - sasprintf(&(ctx->block.background), "%.*s", len, val); + sasprintf(&(ctx->block.background), "%.*s", (int)len, val); return 1; } if (strcasecmp(ctx->last_map_key, "border") == 0) { - sasprintf(&(ctx->block.border), "%.*s", len, val); + sasprintf(&(ctx->block.border), "%.*s", (int)len, val); return 1; } if (strcasecmp(ctx->last_map_key, "markup") == 0) { @@ -255,15 +280,15 @@ static int stdin_string(void *context, const unsigned char *val, size_t len) { return 1; } if (strcasecmp(ctx->last_map_key, "min_width") == 0) { - sasprintf(&(ctx->block.min_width_str), "%.*s", len, val); + sasprintf(&(ctx->block.min_width_str), "%.*s", (int)len, val); return 1; } if (strcasecmp(ctx->last_map_key, "name") == 0) { - sasprintf(&(ctx->block.name), "%.*s", len, val); + sasprintf(&(ctx->block.name), "%.*s", (int)len, val); return 1; } if (strcasecmp(ctx->last_map_key, "instance") == 0) { - sasprintf(&(ctx->block.instance), "%.*s", len, val); + sasprintf(&(ctx->block.instance), "%.*s", (int)len, val); return 1; } @@ -315,10 +340,12 @@ static int stdin_end_map(void *context) { memcpy(new_block, &(ctx->block), sizeof(struct status_block)); /* Ensure we have a full_text set, so that when it is missing (or null), * i3bar doesn’t crash and the user gets an annoying message. */ - if (!new_block->full_text) + if (!new_block->full_text) { new_block->full_text = i3string_from_utf8("SPEC VIOLATION: full_text is NULL!"); - if (new_block->urgent) + } + if (new_block->urgent) { ctx->has_urgent = true; + } if (new_block->min_width_str) { i3String *text = i3string_from_utf8(new_block->min_width_str); @@ -329,10 +356,13 @@ static int stdin_end_map(void *context) { i3string_set_markup(new_block->full_text, new_block->pango_markup); - if (new_block->short_text != NULL) + new_block->use_short = false; + if (new_block->short_text != NULL) { i3string_set_markup(new_block->short_text, new_block->pango_markup); + } TAILQ_INSERT_TAIL(&statusline_buffer, new_block, blocks); + return 1; } @@ -362,15 +392,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 +418,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; } @@ -423,8 +452,9 @@ static bool read_json_input(unsigned char *input, int length) { char *message = (char *)yajl_get_error(parser, 0, input, length); /* strip the newline yajl adds to the error message */ - if (message[strlen(message) - 1] == '\n') + if (message[strlen(message) - 1] == '\n') { message[strlen(message) - 1] = '\0'; + } fprintf(stderr, "[i3bar] Could not parse JSON input (code = %d, message = %s): %.*s\n", status, message, length, input); @@ -443,13 +473,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 +494,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 +521,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 +657,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); + __attribute__((format(printf, 1, 2))) 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; @@ -529,13 +703,14 @@ static void child_write_output(void) { yajl_gen_get_buf(gen, &output, &size); n = writeall(child_stdin, output, size); - if (n != -1) + if (n != -1) { n = writeall(child_stdin, "\n", 1); + } 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 +718,42 @@ 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); + err(EXIT_FAILURE, "execl return"); /* only reached on error */ +} + +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 +764,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 +780,76 @@ 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); + } + /* 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 +858,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; } @@ -662,20 +881,27 @@ void send_block_clicked(int button, const char *name, const char *instance, int ystr("modifiers"); yajl_gen_array_open(gen); - if (mods & XCB_MOD_MASK_SHIFT) + if (mods & XCB_MOD_MASK_SHIFT) { ystr("Shift"); - if (mods & XCB_MOD_MASK_CONTROL) + } + if (mods & XCB_MOD_MASK_CONTROL) { ystr("Control"); - if (mods & XCB_MOD_MASK_1) + } + if (mods & XCB_MOD_MASK_1) { ystr("Mod1"); - if (mods & XCB_MOD_MASK_2) + } + if (mods & XCB_MOD_MASK_2) { ystr("Mod2"); - if (mods & XCB_MOD_MASK_3) + } + if (mods & XCB_MOD_MASK_3) { ystr("Mod3"); - if (mods & XCB_MOD_MASK_4) + } + if (mods & XCB_MOD_MASK_4) { ystr("Mod4"); - if (mods & XCB_MOD_MASK_5) + } + if (mods & XCB_MOD_MASK_5) { ystr("Mod5"); + } yajl_gen_array_close(gen); ystr("x"); @@ -706,35 +932,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 +1018,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 +1040,5 @@ void cont_child(void) { * */ bool child_want_click_events(void) { - return child.click_events; + return status_child.click_events; } diff --git a/i3bar/src/config.c b/i3bar/src/config.c index ccea937d..65fa9ba9 100644 --- a/i3bar/src/config.c +++ b/i3bar/src/config.c @@ -29,7 +29,7 @@ static bool parsing_padding; */ static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { FREE(cur_key); - sasprintf(&(cur_key), "%.*s", keyLen, keyVal); + sasprintf(&(cur_key), "%.*s", (int)keyLen, keyVal); if (strcmp(cur_key, "bindings") == 0) { parsing_bindings = true; @@ -76,8 +76,9 @@ static int config_null_cb(void *params_) { static int config_string_cb(void *params_, const unsigned char *val, size_t _len) { int len = (int)_len; /* The id and socket_path are ignored, we already know them. */ - if (!strcmp(cur_key, "id") || !strcmp(cur_key, "socket_path")) + if (!strcmp(cur_key, "id") || !strcmp(cur_key, "socket_path")) { return 1; + } if (parsing_bindings) { if (strcmp(cur_key, "command") == 0) { @@ -188,11 +189,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); @@ -396,16 +403,15 @@ static yajl_callbacks outputs_callbacks = { }; /* - * 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) { @@ -418,6 +424,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); } @@ -427,16 +438,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); } diff --git a/i3bar/src/ipc.c b/i3bar/src/ipc.c index 06ddf9b5..35a17aed 100644 --- a/i3bar/src/ipc.c +++ b/i3bar/src/ipc.c @@ -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,14 +206,15 @@ 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) + if (found_id == NULL) { return; + } /* reconfigure the bar based on the current outputs */ i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL); @@ -201,10 +225,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 +240,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); } @@ -249,7 +283,7 @@ static void got_data(struct ev_loop *loop, ev_io *watcher, int events) { * we have to expect */ uint32_t rec = 0; while (rec < header_len) { - int n = read(fd, header + rec, header_len - rec); + const int n = read(fd, header + rec, header_len - rec); if (n == -1) { ELOG("read() failed: %s\n", strerror(errno)); exit(EXIT_FAILURE); @@ -258,10 +292,11 @@ static void got_data(struct ev_loop *loop, ev_io *watcher, int events) { /* EOF received. Since i3 will restart i3bar instances as appropriate, * we exit here. */ DLOG("EOF received, exiting...\n"); + clean_xcb(); + free(header); #ifdef I3_ASAN_ENABLED __lsan_do_leak_check(); #endif - clean_xcb(); exit(EXIT_SUCCESS); } rec += n; @@ -284,7 +319,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 +339,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 +376,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 +413,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\" ]"); } } diff --git a/i3bar/src/main.c b/i3bar/src/main.c index 4e93bb02..ce5257bf 100644 --- a/i3bar/src/main.c +++ b/i3bar/src/main.c @@ -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(); diff --git a/i3bar/src/mode.c b/i3bar/src/mode.c index 13d02110..0eb8a17f 100644 --- a/i3bar/src/mode.c +++ b/i3bar/src/mode.c @@ -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; @@ -31,7 +30,7 @@ static int mode_string_cb(void *params_, const unsigned char *val, size_t len) { struct mode_json_params *params = (struct mode_json_params *)params_; if (!strcmp(params->cur_key, "change")) { - sasprintf(&(params->name), "%.*s", len, val); + sasprintf(&(params->name), "%.*s", (int)len, val); FREE(params->cur_key); return 1; } @@ -68,7 +67,7 @@ static int mode_boolean_cb(void *params_, int val) { static int mode_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { struct mode_json_params *params = (struct mode_json_params *)params_; FREE(params->cur_key); - sasprintf(&(params->cur_key), "%.*s", keyLen, keyVal); + sasprintf(&(params->cur_key), "%.*s", (int)keyLen, keyVal); return 1; } @@ -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) { @@ -129,8 +119,9 @@ void parse_mode_json(char *json) { } /* We don't want to indicate default binding mode */ - if (strcmp("default", i3string_as_utf8(params.mode->name)) == 0) + if (strcmp("default", i3string_as_utf8(params.mode->name)) == 0) { I3STRING_FREE(params.mode->name); + } /* Set the new binding mode */ set_current_mode(&binding); diff --git a/i3bar/src/outputs.c b/i3bar/src/outputs.c index 168f3eef..f855ba7c 100644 --- a/i3bar/src/outputs.c +++ b/i3bar/src/outputs.c @@ -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; }; @@ -108,14 +106,15 @@ static int outputs_string_cb(void *params_, const unsigned char *val, size_t len if (!strcmp(params->cur_key, "current_workspace")) { char *copy = NULL; - sasprintf(©, "%.*s", len, val); + sasprintf(©, "%.*s", (int)len, val); char *end; errno = 0; long parsed_num = strtol(copy, &end, 10); if (errno == 0 && - (end && *end == '\0')) + (end && *end == '\0')) { params->outputs_walk->ws = parsed_num; + } FREE(copy); FREE(params->cur_key); @@ -126,7 +125,7 @@ static int outputs_string_cb(void *params_, const unsigned char *val, size_t len return 0; } - sasprintf(&(params->outputs_walk->name), "%.*s", len, val); + sasprintf(&(params->outputs_walk->name), "%.*s", (int)len, val); FREE(params->cur_key); return 1; @@ -148,7 +147,6 @@ static int outputs_start_map_cb(void *params_) { new_output->visible = false; new_output->ws = 0, new_output->statusline_width = 0; - new_output->statusline_short_text = false; memset(&new_output->rect, 0, sizeof(rect)); memset(&new_output->bar, 0, sizeof(surface_t)); memset(&new_output->buffer, 0, sizeof(surface_t)); @@ -237,7 +235,7 @@ static int outputs_end_map_cb(void *params_) { static int outputs_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { struct outputs_json_params *params = (struct outputs_json_params *)params_; FREE(params->cur_key); - sasprintf(&(params->cur_key), "%.*s", keyLen, keyVal); + sasprintf(&(params->cur_key), "%.*s", (int)keyLen, keyVal); return 1; } @@ -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; } } diff --git a/i3bar/src/parse_json_header.c b/i3bar/src/parse_json_header.c index c74a62fe..b4490574 100644 --- a/i3bar/src/parse_json_header.c +++ b/i3bar/src/parse_json_header.c @@ -106,11 +106,13 @@ void parse_json_header(i3bar_child *child, const unsigned char *buffer, int leng yajl_status state = yajl_parse(handle, buffer, length); if (state != yajl_status_ok) { child_init(child); - if (consumed != NULL) + if (consumed != NULL) { *consumed = 0; + } } else { - if (consumed != NULL) + if (consumed != NULL) { *consumed = yajl_get_bytes_consumed(handle); + } } yajl_free(handle); diff --git a/i3bar/src/workspaces.c b/i3bar/src/workspaces.c index bd56f5d0..26abf452 100644 --- a/i3bar/src/workspaces.c +++ b/i3bar/src/workspaces.c @@ -19,15 +19,15 @@ struct workspaces_json_params { struct ws_head *workspaces; i3_ws *workspaces_walk; char *cur_key; - char *json; + bool parsing_rect; }; /* * Parse a boolean value (visible, focused, urgent) * */ -static int workspaces_boolean_cb(void *params_, int val) { - struct workspaces_json_params *params = (struct workspaces_json_params *)params_; +static int workspaces_boolean_cb(void *params_, const int val) { + struct workspaces_json_params *params = params_; if (!strcmp(params->cur_key, "visible")) { params->workspaces_walk->visible = val; @@ -56,8 +56,8 @@ static int workspaces_boolean_cb(void *params_, int val) { * Parse an integer (num or the rect) * */ -static int workspaces_integer_cb(void *params_, long long val) { - struct workspaces_json_params *params = (struct workspaces_json_params *)params_; +static int workspaces_integer_cb(void *params_, const long long val) { + struct workspaces_json_params *params = params_; if (!strcmp(params->cur_key, "id")) { params->workspaces_walk->id = val; @@ -71,26 +71,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; } @@ -103,8 +100,8 @@ static int workspaces_integer_cb(void *params_, long long val) { * Parse a string (name, output) * */ -static int workspaces_string_cb(void *params_, const unsigned char *val, size_t len) { - struct workspaces_json_params *params = (struct workspaces_json_params *)params_; +static int workspaces_string_cb(void *params_, const unsigned char *val, const size_t len) { + struct workspaces_json_params *params = params_; if (!strcmp(params->cur_key, "name")) { const char *ws_name = (const char *)val; @@ -120,14 +117,15 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t size_t offset = strspn(ws_name, ws_num); /* Also strip off the conventional ws name delimiter */ - if (offset && ws_name[offset] == ':') + if (offset && ws_name[offset] == ':') { offset += 1; + } if (config.strip_ws_numbers) { /* Offset may be equal to length, in which case display the number */ - params->workspaces_walk->name = (offset < len - ? i3string_from_markup_with_length(ws_name + offset, len - offset) - : i3string_from_markup(ws_num)); + params->workspaces_walk->name = offset < len + ? i3string_from_markup_with_length(ws_name + offset, len - offset) + : i3string_from_markup(ws_num); } else { params->workspaces_walk->name = i3string_from_markup(ws_num); } @@ -153,18 +151,18 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t if (!strcmp(params->cur_key, "output")) { /* We add the ws to the TAILQ of the output, it belongs to */ char *output_name = NULL; - sasprintf(&output_name, "%.*s", len, val); + sasprintf(&output_name, "%.*s", (int)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); } FREE(output_name); + FREE(params->cur_key); + return 1; } @@ -172,28 +170,52 @@ 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; + struct workspaces_json_params *params = params_; 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->parsing_rect = false; + } else { + params->parsing_rect = true; + } + + return 1; +} + +static int workspaces_end_map_cb(void *params_) { + struct workspaces_json_params *params = params_; + + if (params->parsing_rect) { + params->parsing_rect = false; return 1; } + i3_ws *ws = params->workspaces_walk; + if (!ws || ws->output) { + return 1; /* workspace already assigned to output */ + } + + if (!ws->name || SLIST_EMPTY(outputs)) { /* Invalid state */ + I3STRING_FREE(ws->name); + FREE(ws->canonical_name); + FREE(params->workspaces_walk); + return 1; + } + + /* Handle no output case */ + 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; } @@ -203,56 +225,55 @@ static int workspaces_start_map_cb(void *params_) { * Essentially we just save it in the parsing state * */ -static int workspaces_map_key_cb(void *params_, const unsigned char *keyVal, size_t keyLen) { - struct workspaces_json_params *params = (struct workspaces_json_params *)params_; +static int workspaces_map_key_cb(void *params_, const unsigned char *keyVal, const size_t keyLen) { + struct workspaces_json_params *params = params_; FREE(params->cur_key); - sasprintf(&(params->cur_key), "%.*s", keyLen, keyVal); + sasprintf(¶ms->cur_key, "%.*s", (int)keyLen, keyVal); return 1; } -/* A datastructure to pass all these callbacks to yajl */ +/* A data structure to pass all these callbacks to yajl */ static yajl_callbacks workspaces_callbacks = { .yajl_boolean = workspaces_boolean_cb, .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, const 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}; + const yajl_handle handle = yajl_alloc(&workspaces_callbacks, NULL, ¶ms); + const 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", (char *)err, (char *)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 +282,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); diff --git a/i3bar/src/xcb.c b/i3bar/src/xcb.c index 0cda125c..2ae7fcde 100644 --- a/i3bar/src/xcb.c +++ b/i3bar/src/xcb.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -138,8 +139,9 @@ int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) { } static uint32_t get_sep_offset(struct status_block *block) { - if (!block->no_separator && block->sep_block_width > 0) + if (!block->no_separator && block->sep_block_width > 0) { return block->sep_block_width / 2 + block->sep_block_width % 2; + } return 0; } @@ -147,12 +149,14 @@ static int get_tray_width(struct tc_head *trayclients) { trayclient *trayclient; int tray_width = 0; TAILQ_FOREACH_REVERSE (trayclient, trayclients, tc_head, tailq) { - if (!trayclient->mapped) + if (!trayclient->mapped) { continue; + } tray_width += icon_size + logical_px(config.tray_padding); } - if (tray_width > 0) + if (tray_width > 0) { tray_width += logical_px(tray_loff_px); + } return tray_width; } @@ -165,8 +169,9 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b color_t bar_bg = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg); uint32_t sep_offset = get_sep_offset(block); - if (TAILQ_NEXT(block, blocks) == NULL || sep_offset == 0) + if (TAILQ_NEXT(block, blocks) == NULL || sep_offset == 0) { return; + } uint32_t center_x = x - sep_offset; if (config.separator_symbol == NULL) { @@ -184,50 +189,103 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b } } -static uint32_t predict_statusline_length(bool use_short_text) { +static void predict_block_length(struct status_block *block) { + i3String *text = block->full_text; + struct status_block_render_desc *render = &block->full_render; + if (block->use_short && block->short_text != NULL) { + text = block->short_text; + render = &block->short_render; + } + + if (i3string_get_num_bytes(text) == 0) { + block->render_length = 0; + return; + } + + render->width = predict_text_width(text); + if (block->border) { + render->width += logical_px(block->border_left + block->border_right); + } + + /* Compute offset and append for text alignment in min_width. */ + if (block->min_width <= render->width) { + render->x_offset = 0; + render->x_append = 0; + } else { + uint32_t padding_width = block->min_width - render->width; + switch (block->align) { + case ALIGN_LEFT: + render->x_append = padding_width; + break; + case ALIGN_RIGHT: + render->x_offset = padding_width; + break; + case ALIGN_CENTER: + render->x_offset = padding_width / 2; + render->x_append = padding_width / 2 + padding_width % 2; + break; + } + } + + block->render_length = render->width + render->x_offset + render->x_append; +} + +static uint32_t predict_statusline_length(void) { uint32_t width = 0; struct status_block *block; TAILQ_FOREACH (block, &statusline_head, blocks) { - i3String *text = block->full_text; - struct status_block_render_desc *render = &block->full_render; - if (use_short_text && block->short_text != NULL) { - text = block->short_text; - render = &block->short_render; - } - - if (i3string_get_num_bytes(text) == 0) + predict_block_length(block); + uint32_t block_width = block->render_length; + if (block_width == 0) { continue; - - render->width = predict_text_width(text); - if (block->border) - render->width += logical_px(block->border_left + block->border_right); - - /* Compute offset and append for text alignment in min_width. */ - if (block->min_width <= render->width) { - render->x_offset = 0; - render->x_append = 0; - } else { - uint32_t padding_width = block->min_width - render->width; - switch (block->align) { - case ALIGN_LEFT: - render->x_append = padding_width; - break; - case ALIGN_RIGHT: - render->x_offset = padding_width; - break; - case ALIGN_CENTER: - render->x_offset = padding_width / 2; - render->x_append = padding_width / 2 + padding_width % 2; - break; - } } - width += render->width + render->x_offset + render->x_append; + width += block_width; /* If this is not the last block, add some pixels for a separator. */ - if (TAILQ_NEXT(block, blocks) != NULL) + if (TAILQ_NEXT(block, blocks) != NULL) { width += block->sep_block_width; + } + } + + return width; +} + +static uint32_t switch_block_to_short(struct status_block *block) { + /* Skip blocks that have no short form or are already in short form */ + if (block->short_text == NULL || block->use_short) { + return 0; + } + uint32_t full = block->render_length; + block->use_short = true; + predict_block_length(block); + return full - block->render_length; +} + +static uint32_t adjust_statusline_length(uint32_t max_length) { + uint32_t width = predict_statusline_length(); + + /* Progressively switch the blocks to short mode */ + struct status_block *block; + TAILQ_FOREACH (block, &statusline_head, blocks) { + if (width < max_length) { + break; + } + width -= switch_block_to_short(block); + + /* Provide support for representing a single logical block using multiple + * JSON blocks: if one block is shortened, ensure that all other blocks + * with the same name are also shortened such that the entire logical block uses + * the short form text. */ + if (block->name) { + struct status_block *other; + TAILQ_FOREACH (other, &statusline_head, blocks) { + if (other->name && !strcmp(other->name, block->name)) { + width -= switch_block_to_short(other); + } + } + } } return width; @@ -236,7 +294,7 @@ static uint32_t predict_statusline_length(bool use_short_text) { /* * Redraws the statusline to the output's statusline_buffer */ -static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_colors, bool use_short_text) { +static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_colors) { struct status_block *block; color_t bar_color = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg); @@ -254,13 +312,14 @@ static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focu TAILQ_FOREACH (block, &statusline_head, blocks) { i3String *text = block->full_text; struct status_block_render_desc *render = &block->full_render; - if (use_short_text && block->short_text != NULL) { + if (block->use_short && block->short_text != NULL) { text = block->short_text; render = &block->short_render; } - if (i3string_get_num_bytes(text) == 0) + if (i3string_get_num_bytes(text) == 0) { continue; + } color_t fg_color; if (block->urgent) { @@ -284,10 +343,12 @@ static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focu border_color = colors.urgent_ws_border; bg_color = colors.urgent_ws_bg; } else { - if (block->border) + if (block->border) { border_color = draw_util_hex_to_color(block->border); - if (block->background) + } + if (block->background) { bg_color = draw_util_hex_to_color(block->background); + } } /* Draw the border. */ @@ -334,7 +395,7 @@ static void hide_bars(void) { } xcb_unmap_window(xcb_connection, walk->bar.id); } - stop_child(); + stop_children(); } /* @@ -351,7 +412,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) { @@ -363,10 +424,11 @@ static void unhide_bars(void) { XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_STACK_MODE; values[0] = walk->rect.x; - if (config.position == POS_TOP) + if (config.position == POS_TOP) { values[1] = walk->rect.y; - else + } else { values[1] = walk->rect.y + walk->rect.h - bar_height; + } values[2] = walk->rect.w; values[3] = bar_height; values[4] = XCB_STACK_MODE_ABOVE; @@ -434,8 +496,9 @@ void init_colors(const struct xcb_color_strings_t *new_colors) { static bool execute_custom_command(xcb_keycode_t input_code, bool event_is_release) { binding_t *binding; TAILQ_FOREACH (binding, &(config.bindings), bindings) { - if ((binding->input_code != input_code) || (binding->release != event_is_release)) + if ((binding->input_code != input_code) || (binding->release != event_is_release)) { continue; + } i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command); return true; @@ -454,7 +517,7 @@ static void child_handle_button(xcb_button_press_event_t *event, i3_output *outp TAILQ_FOREACH (block, &statusline_head, blocks) { i3String *text; struct status_block_render_desc *render; - if (output->statusline_short_text && block->short_text != NULL) { + if (block->use_short && block->short_text != NULL) { text = block->short_text; render = &block->short_render; } else { @@ -500,6 +563,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=%lu] 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- @@ -526,19 +632,27 @@ static void handle_button(xcb_button_press_event_t *event) { /* During button release events, only check for custom commands. */ const bool event_is_release = (event->response_type & ~0x80) == XCB_BUTTON_RELEASE; - int32_t x = event->event_x >= 0 ? event->event_x : 0; + const int x = (event->event_x >= 0 ? event->event_x : 0) - logical_px(config.padding.x); + if (x < 0) { + /* Ignore clicks in padding */ + return; + } + int workspace_width = 0; i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk; TAILQ_FOREACH (ws_walk, walk->workspaces, tailq) { int w = predict_button_width(ws_walk->name_width); - if (x >= workspace_width && x <= workspace_width + w) + if (x >= workspace_width && x <= workspace_width + w) { clicked_ws = ws_walk; - if (ws_walk->visible) + } + if (ws_walk->visible) { cur_ws = ws_walk; + } workspace_width += w; - if (TAILQ_NEXT(ws_walk, tailq) != NULL) + if (TAILQ_NEXT(ws_walk, tailq) != NULL) { workspace_width += logical_px(ws_spacing_px); + } } if (child_want_click_events() && x > workspace_width) { @@ -583,8 +697,9 @@ static void handle_button(xcb_button_press_event_t *event) { * If there is no more workspace, don’t even send the workspace * command, otherwise (with workspace auto_back_and_forth) we’d end * up on the wrong workspace. */ - if (cur_ws == TAILQ_FIRST(walk->workspaces)) + if (cur_ws == TAILQ_FIRST(walk->workspaces)) { return; + } cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq); break; @@ -594,8 +709,9 @@ static void handle_button(xcb_button_press_event_t *event) { * If there is no more workspace, don’t even send the workspace * command, otherwise (with workspace auto_back_and_forth) we’d end * up on the wrong workspace. */ - if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head)) + if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head)) { return; + } cur_ws = TAILQ_NEXT(cur_ws, tailq); break; @@ -606,51 +722,23 @@ static void handle_button(xcb_button_press_event_t *event) { * workspace if it is not already focused */ if (cur_ws == NULL) { TAILQ_FOREACH (cur_ws, walk->workspaces, tailq) { - if (cur_ws->visible && !cur_ws->focused) + if (cur_ws->visible && !cur_ws->focused) { break; + } } } /* if there is nothing to focus, we are done */ - if (cur_ws == NULL) + if (cur_ws == NULL) { return; + } break; default: 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 +762,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(); } } @@ -824,12 +912,10 @@ static void handle_client_message(xcb_client_message_event_t *event) { DLOG("_NET_SYSTEM_TRAY_OPCODE received\n"); /* event->data.data32[0] is the timestamp */ uint32_t op = event->data.data32[1]; - uint32_t mask; uint32_t values[2]; if (op == SYSTEM_TRAY_REQUEST_DOCK) { - xcb_window_t client = event->data.data32[2]; - - mask = XCB_CW_EVENT_MASK; + const xcb_window_t client = event->data.data32[2]; + uint32_t mask = XCB_CW_EVENT_MASK; /* Needed to get the most recent value of XEMBED_MAPPED. */ values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE; @@ -872,8 +958,9 @@ static void handle_client_message(xcb_client_message_event_t *event) { DLOG("xembed flags = %d\n", xembed[1]); map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED); xe_version = xembed[0]; - if (xe_version > 1) + if (xe_version > 1) { xe_version = 1; + } free(xembedr); } else { ELOG("Window %08x violates the XEMBED protocol, _XEMBED_INFO not set\n", client); @@ -891,8 +978,9 @@ static void handle_client_message(xcb_client_message_event_t *event) { output_for_tray->bar.id, output_for_tray->rect.w - icon_size - logical_px(config.tray_padding), logical_px(config.tray_padding)); - if (xcb_request_failed(rcookie, "Could not reparent window. Maybe it is using an incorrect depth/visual?")) + if (xcb_request_failed(rcookie, "Could not reparent window. Maybe it is using an incorrect depth/visual?")) { return; + } /* We reconfigure the window to use a reasonable size. The systray * specification explicitly says: @@ -1091,17 +1179,20 @@ static void handle_configuration_change(xcb_window_t window) { trayclient *trayclient; i3_output *output; SLIST_FOREACH (output, outputs, slist) { - if (!output->active) + if (!output->active) { continue; + } int clients = 0; TAILQ_FOREACH_REVERSE (trayclient, output->trayclients, tc_head, tailq) { - if (!trayclient->mapped) + if (!trayclient->mapped) { continue; + } clients++; - if (trayclient->win != window) + if (trayclient->win != window) { continue; + } xcb_rectangle_t rect; rect.x = output->rect.w - (clients * (icon_size + logical_px(config.tray_padding))); @@ -1348,8 +1439,9 @@ static void deregister_xkb_keyevents(void) { * */ void init_xcb_late(char *fontname) { - if (fontname == NULL) + if (fontname == NULL) { fontname = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1"; + } /* Load the font */ font = load_font(fontname, true); @@ -1377,13 +1469,15 @@ void init_xcb_late(char *fontname) { bar_height = default_px + padding_scaled; icon_size = bar_height - 2 * logical_px(config.tray_padding); - if (config.separator_symbol) + if (config.separator_symbol) { separator_symbol_width = predict_text_width(config.separator_symbol); + } xcb_flush(xcb_connection); - if (config.hide_on_modifier == M_HIDE) + if (config.hide_on_modifier == M_HIDE) { register_xkb_keyevents(); + } } /* @@ -1419,11 +1513,15 @@ static void send_tray_clientmessage(void) { static void init_tray(void) { DLOG("Initializing system tray functionality\n"); /* request the tray manager atom for the X11 display we are running on */ - char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11]; - snprintf(atomname, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", screen); + /* The following line cannot use strlen as that makes compilation fail with + * some versions of clang (-Wgnu-folding-constant): */ + const size_t systray_len = strlen("_NET_SYSTEM_TRAY_S") + 11; + char atomname[systray_len]; + snprintf(atomname, systray_len, "_NET_SYSTEM_TRAY_S%d", screen); xcb_intern_atom_cookie_t tray_cookie; - if (tray_reply == NULL) + if (tray_reply == NULL) { tray_cookie = xcb_intern_atom(xcb_connection, 0, strlen(atomname), atomname); + } /* tray support: we need a window to own the selection */ selwin = xcb_generate_id(xcb_connection); @@ -1590,8 +1688,9 @@ void get_atoms(void) { * */ void kick_tray_clients(i3_output *output) { - if (TAILQ_EMPTY(output->trayclients)) + if (TAILQ_EMPTY(output->trayclients)) { return; + } trayclient *trayclient; while (!TAILQ_EMPTY(output->trayclients)) { @@ -1830,6 +1929,7 @@ void reconfig_windows(bool redraw_bars) { 8, len, class); + free(class); char *name; sasprintf(&name, "i3bar for output %s", walk->name); @@ -1886,10 +1986,11 @@ void reconfig_windows(bool redraw_bars) { XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_STACK_MODE; values[0] = walk->rect.x; - if (config.position == POS_TOP) + if (config.position == POS_TOP) { values[1] = walk->rect.y; - else + } else { values[1] = walk->rect.y + walk->rect.h - bar_height; + } values[2] = walk->rect.w; values[3] = bar_height; values[4] = XCB_STACK_MODE_ABOVE; @@ -1945,10 +2046,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) { @@ -2012,9 +2113,6 @@ static void draw_button(surface_t *surface, color_t fg_color, color_t bg_color, void draw_bars(bool unhide) { DLOG("Drawing bars...\n"); - uint32_t full_statusline_width = predict_statusline_length(false); - uint32_t short_statusline_width = predict_statusline_length(true); - i3_output *outputs_walk; SLIST_FOREACH (outputs_walk, outputs, slist) { int workspace_width = logical_px(config.padding.x); @@ -2065,8 +2163,9 @@ void draw_bars(bool unhide) { workspace_width, w, ws_walk->name_width, ws_walk->name); workspace_width += w; - if (TAILQ_NEXT(ws_walk, tailq) != NULL) + if (TAILQ_NEXT(ws_walk, tailq) != NULL) { workspace_width += logical_px(ws_spacing_px); + } } } @@ -2088,27 +2187,28 @@ void draw_bars(bool unhide) { uint32_t hoff = logical_px(((workspace_width > 0) + (tray_width > 0)) * sb_hoff_px); uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - hoff; uint32_t clip_left = 0; - uint32_t statusline_width = full_statusline_width; - bool use_short_text = false; + + /* Reset short mode between outputs */ + struct status_block *block; + TAILQ_FOREACH (block, &statusline_head, blocks) { + block->use_short = false; + } + + uint32_t statusline_width = adjust_statusline_length(max_statusline_width); if (statusline_width > max_statusline_width) { - statusline_width = short_statusline_width; - use_short_text = true; - if (statusline_width > max_statusline_width) { - clip_left = statusline_width - max_statusline_width; - } + clip_left = statusline_width - max_statusline_width; } 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_statusline(outputs_walk, clip_left, use_focus_colors); draw_util_copy_surface(&outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0, x_dest, 0, visible_statusline_width, (int16_t)bar_height); outputs_walk->statusline_width = statusline_width; - outputs_walk->statusline_short_text = use_short_text; } } diff --git a/include/all.h b/include/all.h index 0d4dbc4e..1306ef23 100644 --- a/include/all.h +++ b/include/all.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * This header file includes all relevant files of i3 and the most often used @@ -28,9 +28,10 @@ #include #include -#include #include #include +#include +#include #include "libi3.h" #include "data.h" diff --git a/include/assignments.h b/include/assignments.h index a4dc766b..67b5d16f 100644 --- a/include/assignments.h +++ b/include/assignments.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * assignments.c: Assignments for specific windows (for_window). diff --git a/include/bindings.h b/include/bindings.h index df3c32a5..b78a00fc 100644 --- a/include/bindings.h +++ b/include/bindings.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * bindings.h: Functions for configuring, finding, and running bindings. diff --git a/include/click.h b/include/click.h index 898f1870..7380c4f5 100644 --- a/include/click.h +++ b/include/click.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * click.c: Button press (mouse click) events. diff --git a/include/commands.h b/include/commands.h index 2ae2643c..6a522681 100644 --- a/include/commands.h +++ b/include/commands.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * commands.c: all command functions (see commands_parser.c) @@ -90,7 +90,7 @@ void cmd_nop(I3_CMD, const char *comment); * Implementation of 'append_layout '. * */ -void cmd_append_layout(I3_CMD, const char *path); +void cmd_append_layout(I3_CMD, const char *cpath); /** * Implementation of 'workspace next|prev|next_on_output|prev_on_output'. @@ -102,7 +102,7 @@ void cmd_workspace(I3_CMD, const char *which); * Implementation of 'workspace [--no-auto-back-and-forth] number ' * */ -void cmd_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth); +void cmd_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth_str); /** * Implementation of 'workspace back_and_forth'. @@ -114,7 +114,7 @@ void cmd_workspace_back_and_forth(I3_CMD); * Implementation of 'workspace [--no-auto-back-and-forth] ' * */ -void cmd_workspace_name(I3_CMD, const char *name, const char *no_auto_back_and_forth); +void cmd_workspace_name(I3_CMD, const char *name, const char *no_auto_back_and_forth_str); /** * Implementation of 'mark [--add|--replace] [--toggle] ' @@ -174,13 +174,13 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command); * Implementation of 'focus left|right|up|down'. * */ -void cmd_focus_direction(I3_CMD, const char *direction); +void cmd_focus_direction(I3_CMD, const char *direction_str); /** * Implementation of 'focus next|prev sibling' * */ -void cmd_focus_sibling(I3_CMD, const char *direction); +void cmd_focus_sibling(I3_CMD, const char *direction_str); /** * Implementation of 'focus tiling|floating|mode_toggle'. @@ -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]'. diff --git a/include/commands_parser.h b/include/commands_parser.h index 7e1c5203..e2557eac 100644 --- a/include/commands_parser.h +++ b/include/commands_parser.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * commands.c: all command functions (see commands_parser.c) @@ -12,6 +12,18 @@ #include #include +#include "parser_util.h" +#include "queue.h" + +/** + * Helper data structure for an operation window (window on which the operation + * will be performed). Used to build the TAILQ owindows. + * + */ +typedef struct owindow { + Con *con; + TAILQ_ENTRY(owindow) owindows; +} owindow; /** * Holds an intermediate representation of the result of a call to any command. @@ -19,6 +31,9 @@ * internally use this struct when calling cmd_floating and cmd_border. */ struct CommandResultIR { + /* The parser context this command is executing in. */ + struct cmd_parser_ctx *ctx; + /* The JSON generator to append a reply to (may be NULL). */ yajl_gen json_gen; @@ -35,6 +50,28 @@ struct CommandResultIR { bool needs_tree_render; }; +/* Define the owindows head structure here so it's complete */ +TAILQ_HEAD(owindows_head, owindow); + +/** + * Context structure for the command parser, making it re-entrant. + */ +struct cmd_parser_ctx { + int state; + Match current_match; + + /* The (small) stack where identified literals are stored during the parsing + * of a single command (like $workspace). */ + struct stack stack; + + /* List of operation windows (windows on which operations will be performed). + * Used to build the TAILQ owindows. */ + struct owindows_head owindows; + + struct CommandResultIR subcommand_output; + struct CommandResultIR command_output; +}; + typedef struct CommandResult CommandResult; /** diff --git a/include/con.h b/include/con.h index e1bb6813..63107137 100644 --- a/include/con.h +++ b/include/con.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * con.c: Functions which deal with containers directly (creating containers, @@ -83,6 +83,22 @@ bool con_is_split(Con *con); */ bool con_is_hidden(Con *con); +/** + * Returns true if the container is maximized in the given orientation. + * + * If the container is floating or fullscreen, it is not considered maximized. + * Otherwise, it is maximized if it doesn't share space with any other + * container in the given orientation. For example, if a workspace contains + * a single splitv container with three children, none of them are considered + * vertically maximized, but they are all considered horizontally maximized. + * + * Passing "maximized" hints to the application can help it make the right + * choices about how to draw its borders. See discussion in + * https://github.com/i3/i3/pull/2380. + * + */ +bool con_is_maximized(Con *con, orientation_t orientation); + /** * Returns whether the container or any of its children is sticky. * diff --git a/include/config_directives.h b/include/config_directives.h index 600226e9..fd384c0c 100644 --- a/include/config_directives.h +++ b/include/config_directives.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * config_directives.h: all config storing functions (see config_parser.c) @@ -17,7 +17,7 @@ * A utility function to convert a string containing the group and modifiers to * the corresponding bit mask. */ -i3_event_state_mask_t event_state_from_str(const char *str); +i3_event_state_mask_t event_state_from_str(const char *str) __attribute__((__pure__)); /** The beginning of the prototype for every cfg_ function. */ #define I3_CFG Match *current_match, struct ConfigResultIR *result @@ -43,7 +43,7 @@ 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(gaps, const char *workspace, const char *scope, 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); @@ -69,6 +69,7 @@ CFGFUN(no_focus); CFGFUN(ipc_socket, const char *path); CFGFUN(ipc_kill_timeout, const long timeout_ms); CFGFUN(tiling_drag, const char *value); +CFGFUN(tiling_drag_swap_modifier, const char *modifiers); 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); @@ -78,7 +79,7 @@ CFGFUN(default_border, const char *windowtype, const char *border, const long wi CFGFUN(workspace, const char *workspace, const char *output); 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); -CFGFUN(enter_mode, const char *pango_markup, const char *mode); +CFGFUN(enter_mode, const char *pango_markup, const char *modename); 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); CFGFUN(bar_font, const char *font); @@ -102,9 +103,10 @@ CFGFUN(bar_i3bar_command, const char *i3bar_command); CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text); CFGFUN(bar_socket_path, const char *socket_path); CFGFUN(bar_tray_output, const char *output); -CFGFUN(bar_tray_padding, const long spacing_px); +CFGFUN(bar_tray_padding, const long padding_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); diff --git a/include/config_parser.h b/include/config_parser.h index 82c57090..9bc924f2 100644 --- a/include/config_parser.h +++ b/include/config_parser.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * config_parser.h: config parser-related definitions @@ -13,29 +13,13 @@ #include +#include "parser_util.h" + SLIST_HEAD(variables_head, Variable); extern pid_t config_error_nagbar_pid; -struct stack_entry { - /* Just a pointer, not dynamically allocated. */ - const char *identifier; - enum { - STACK_STR = 0, - STACK_LONG = 1, - } type; - union { - char *str; - long num; - } val; -}; - -struct stack { - struct stack_entry stack[10]; -}; - struct parser_ctx { bool use_nagbar; - bool assume_v4; int state; Match current_match; diff --git a/include/configuration.h b/include/configuration.h index 99f4b64e..c062ae4c 100644 --- a/include/configuration.h +++ b/include/configuration.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * include/configuration.h: Contains all structs/variables for the configurable @@ -227,6 +227,9 @@ struct Config { /** The modifier which needs to be pressed in combination with your mouse * buttons to do things with floating windows (move, resize) */ uint32_t floating_modifier; + /** The modifier which needs to be pressed in combination with the floating + * modifier and your mouse buttons to swap containers during tiling drag */ + uint32_t swap_modifier; /** Maximum and minimum dimensions of a floating window */ int32_t floating_maximum_width; @@ -262,6 +265,9 @@ struct Config { /* just ignore the popup, that is, don’t map it */ PDF_IGNORE = 2, + + /* display all floating windows */ + PDF_ALL = 3, } popup_during_fullscreen; /* The number of currently parsed barconfigs */ @@ -272,9 +278,6 @@ struct Config { /* 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; }; @@ -335,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; diff --git a/include/data.h b/include/data.h index 483aecab..f448d263 100644 --- a/include/data.h +++ b/include/data.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * include/data.h: This file defines all data structures used by i3 @@ -81,10 +81,6 @@ 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; diff --git a/include/display_version.h b/include/display_version.h index 6996038e..9dba20ec 100644 --- a/include/display_version.h +++ b/include/display_version.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * display_version.c: displays the running i3 version, runs as part of diff --git a/include/drag.h b/include/drag.h index 2027f934..93a7c4f0 100644 --- a/include/drag.h +++ b/include/drag.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * drag.c: click and drag. diff --git a/include/ewmh.h b/include/ewmh.h index f616eb84..a4250e72 100644 --- a/include/ewmh.h +++ b/include/ewmh.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * ewmh.c: Get/set certain EWMH properties easily. diff --git a/include/fake_outputs.h b/include/fake_outputs.h index 27a0d41a..25e42fc7 100644 --- a/include/fake_outputs.h +++ b/include/fake_outputs.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * Faking outputs is useful in pathological situations (like network X servers diff --git a/include/floating.h b/include/floating.h index 612874fc..781bb130 100644 --- a/include/floating.h +++ b/include/floating.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * floating.c: Floating windows. diff --git a/include/gaps.h b/include/gaps.h index cb4d0093..50bff3da 100644 --- a/include/gaps.h +++ b/include/gaps.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ diff --git a/include/handlers.h b/include/handlers.h index 81012e7b..18416935 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * handlers.c: Small handlers for various events (keypresses, focus changes, diff --git a/include/i3-atoms_NET_SUPPORTED.xmacro.h b/include/i3-atoms_NET_SUPPORTED.xmacro.h index b491da98..90d03c20 100644 --- a/include/i3-atoms_NET_SUPPORTED.xmacro.h +++ b/include/i3-atoms_NET_SUPPORTED.xmacro.h @@ -11,6 +11,8 @@ xmacro(_NET_WM_STATE_DEMANDS_ATTENTION) \ xmacro(_NET_WM_STATE_MODAL) \ xmacro(_NET_WM_STATE_HIDDEN) \ xmacro(_NET_WM_STATE_FOCUSED) \ +xmacro(_NET_WM_STATE_MAXIMIZED_VERT) \ +xmacro(_NET_WM_STATE_MAXIMIZED_HORZ) \ xmacro(_NET_WM_STATE) \ xmacro(_NET_WM_WINDOW_TYPE) \ xmacro(_NET_WM_WINDOW_TYPE_NORMAL) \ diff --git a/include/i3.h b/include/i3.h index 4e2eb5c0..40a848df 100644 --- a/include/i3.h +++ b/include/i3.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * i3.h: global variables that are used all over i3. @@ -9,23 +9,16 @@ */ #pragma once -#include - -#include #include #include #include -#include - -#include #define SN_API_NOT_YET_FROZEN 1 #include #include "queue.h" #include "data.h" -#include "xcb.h" /** Git commit identifier, from version.c */ extern const char *i3_version; @@ -75,5 +68,4 @@ extern xcb_colormap_t colormap; extern bool xkb_supported, shape_supported; extern xcb_window_t root; extern struct ev_loop *main_loop; -extern bool only_check_config; extern bool force_xinerama; diff --git a/include/i3/ipc.h b/include/i3/ipc.h index 187640cd..50fd155c 100644 --- a/include/i3/ipc.h +++ b/include/i3/ipc.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * This public header defines the different constants and message types to use diff --git a/include/ipc.h b/include/ipc.h index e85e6081..de5a6945 100644 --- a/include/ipc.h +++ b/include/ipc.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * ipc.c: UNIX domain socket IPC (initialization, client handling, protocol). diff --git a/include/key_press.h b/include/key_press.h index 8f23854b..a3bd8943 100644 --- a/include/key_press.h +++ b/include/key_press.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * key_press.c: key press handler diff --git a/include/libi3.h b/include/libi3.h index 005167c7..0fba2739 100644 --- a/include/libi3.h +++ b/include/libi3.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * libi3: contains functions which are used by i3 *and* accompanying tools such @@ -158,7 +158,7 @@ char *sstrndup(const char *str, size_t size); * there is no more memory available) * */ -int sasprintf(char **strp, const char *fmt, ...); +int sasprintf(char **strp, const char *fmt, ...) __attribute__((format(printf, 2, 3))); /** * Wrapper around correct write which returns -1 (meaning that @@ -186,7 +186,7 @@ ssize_t swrite(int fd, const void *buf, size_t count); * Like strcasecmp but considers the case where either string is NULL. * */ -int strcasecmp_nullable(const char *a, const char *b); +int strcasecmp_nullable(const char *a, const char *b) __attribute__((pure)); /** * Build an i3String from an UTF-8 encoded string. @@ -240,12 +240,10 @@ void i3string_free(i3String *str); * to prevent accidentally using freed memory. * */ -#define I3STRING_FREE(str) \ - do { \ - if (str != NULL) { \ - i3string_free(str); \ - str = NULL; \ - } \ +#define I3STRING_FREE(str) \ + do { \ + i3string_free(str); \ + str = NULL; \ } while (0) /** @@ -506,7 +504,7 @@ void init_dpi(void); * This function returns the value of the DPI setting. * */ -long get_dpi_value(void); +long get_dpi_value(void) __attribute__((pure)); /** * Convert a logical amount of pixels (e.g. 2 pixels on a “standard” 96 DPI @@ -514,7 +512,7 @@ long get_dpi_value(void); * screen, e.g. 5 pixels on a 227 DPI MacBook Pro 13" Retina screen. * */ -int logical_px(const int logical); +int logical_px(int logical) __attribute__((pure)); /** * This function resolves ~ in pathnames. @@ -572,6 +570,7 @@ typedef struct surface_t { /* A classic XCB graphics context. */ xcb_gcontext_t gc; + bool owns_gc; int width; int height; @@ -686,3 +685,9 @@ bool is_background_set(xcb_connection_t *conn, xcb_screen_t *screen); * */ bool boolstr(const char *str); + +/** + * Get depth of visual specified by visualid + * + */ +uint16_t get_visual_depth(xcb_visualid_t visual_id); diff --git a/include/load_layout.h b/include/load_layout.h index 9205800f..fc2d7a2d 100644 --- a/include/load_layout.h +++ b/include/load_layout.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * load_layout.c: Restore (parts of) the layout, for example after an inplace diff --git a/include/log.h b/include/log.h index 3a52c64d..6830584c 100644 --- a/include/log.h +++ b/include/log.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * log.c: Logging functions. diff --git a/include/main.h b/include/main.h index 1f213cce..83645eec 100644 --- a/include/main.h +++ b/include/main.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * main.c: Initialization, main loop @@ -10,6 +10,9 @@ #pragma once #include +#include + +extern locale_t numericC; /** * Enable or disable the main X11 event handling function. diff --git a/include/manage.h b/include/manage.h index 22fbe527..547914a8 100644 --- a/include/manage.h +++ b/include/manage.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * manage.c: Initially managing new windows (or existing ones on restart). diff --git a/include/match.h b/include/match.h index 043c3a8f..5584dd11 100644 --- a/include/match.h +++ b/include/match.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * A "match" is a data structure which acts like a mask or expression to match diff --git a/include/move.h b/include/move.h index 830488b0..1002ba49 100644 --- a/include/move.h +++ b/include/move.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * move.c: Moving containers into some direction. diff --git a/include/output.h b/include/output.h index a2ad97b0..06331afc 100644 --- a/include/output.h +++ b/include/output.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * output.c: Output (monitor) related functions. @@ -31,8 +31,10 @@ Output *get_output_from_string(Output *current_output, const char *output_str); char *output_primary_name(Output *output); /** - * Returns the output for the given con. - * + * Retrieves the output for a given container. Never returns NULL. + * There is an assertion that _will_ fail if the container is inside an + * internal workspace. Use con_is_internal() if needed before calling this + * function. */ Output *get_output_for_con(Con *con); diff --git a/include/parser_util.h b/include/parser_util.h new file mode 100644 index 00000000..5f7f3d68 --- /dev/null +++ b/include/parser_util.h @@ -0,0 +1,59 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved tiling window manager + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) + * + * parser_util.h: utility functions for the config and commands parser + * + */ +#pragma once + +struct stack_entry { + /* Just a pointer, not dynamically allocated. */ + const char *identifier; + enum { + STACK_STR = 0, + STACK_LONG = 1, + } type; + union { + char *str; + long num; + } val; +}; + +struct stack { + struct stack_entry stack[10]; +}; + +/** + * Pushes a string (identified by 'identifier') on the stack. + * If a string with the same identifier is already on the stack, the new + * string will be appended, separated by a comma. + * + */ +void parser_push_string(struct stack *stack, const char *identifier, const char *str); + +/** + * Pushes a long (identified by 'identifier') on the stack. + * + */ +void parser_push_long(struct stack *stack, const char *identifier, long num); + +/** + * Returns the string with the given identifier. + * + */ +const char *parser_get_string(const struct stack *stack, const char *identifier); + +/** + * Returns the long with the given identifier. + * + */ +long parser_get_long(const struct stack *stack, const char *identifier); + +/** + * Clears the stack. + * + */ +void parser_clear_stack(struct stack *stack); diff --git a/include/queue.h b/include/queue.h index 9b410449..1cff6467 100644 --- a/include/queue.h +++ b/include/queue.h @@ -96,7 +96,7 @@ } #define SLIST_HEAD_INITIALIZER(head) \ - { NULL } + {NULL} #define SLIST_ENTRY(type) \ struct { \ @@ -174,7 +174,7 @@ } #define LIST_HEAD_INITIALIZER(head) \ - { NULL } + {NULL} #define LIST_ENTRY(type) \ struct { \ @@ -256,7 +256,7 @@ } #define SIMPLEQ_HEAD_INITIALIZER(head) \ - { NULL, &(head).sqh_first } + {NULL, &(head).sqh_first} #define SIMPLEQ_ENTRY(type) \ struct { \ @@ -322,7 +322,7 @@ } #define TAILQ_HEAD_INITIALIZER(head) \ - { NULL, &(head).tqh_first } + {NULL, &(head).tqh_first} #define TAILQ_ENTRY(type) \ struct { \ @@ -447,9 +447,7 @@ #define CIRCLEQ_HEAD_INITIALIZER(head) \ { \ - CIRCLEQ_END(&head) \ - , CIRCLEQ_END(&head) \ - } + CIRCLEQ_END(&head), CIRCLEQ_END(&head)} #define CIRCLEQ_ENTRY(type) \ struct { \ diff --git a/include/randr.h b/include/randr.h index 6fd7ea99..71429035 100644 --- a/include/randr.h +++ b/include/randr.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * For more information on RandR, please see the X.org RandR specification at diff --git a/include/regex.h b/include/regex.h index abda3cd7..7f359c0c 100644 --- a/include/regex.h +++ b/include/regex.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * regex.c: Interface to libPCRE (perl compatible regular expressions). @@ -35,4 +35,4 @@ void regex_free(struct regex *regex); * be visible without debug logging. * */ -bool regex_matches(struct regex *regex, const char *input); +bool regex_matches(const struct regex *regex, const char *input); diff --git a/include/render.h b/include/render.h index 03751c01..8d61f01c 100644 --- a/include/render.h +++ b/include/render.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * render.c: Renders (determines position/sizes) the layout tree, updating the diff --git a/include/resize.h b/include/resize.h index 5439fab5..972f9f1d 100644 --- a/include/resize.h +++ b/include/resize.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * resize.c: Interactive resizing. diff --git a/include/restore_layout.h b/include/restore_layout.h index 98b257d9..ae0fc9da 100644 --- a/include/restore_layout.h +++ b/include/restore_layout.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * restore_layout.c: Everything for restored containers that is not pure state diff --git a/include/scratchpad.h b/include/scratchpad.h index b24ffc08..4310d8fe 100644 --- a/include/scratchpad.h +++ b/include/scratchpad.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * scratchpad.c: Scratchpad functions (TODO: more description) diff --git a/include/shmlog.h b/include/shmlog.h index a30852e7..1a4212a6 100644 --- a/include/shmlog.h +++ b/include/shmlog.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * The format of the shmlog data structure which i3 development versions use by diff --git a/include/sighandler.h b/include/sighandler.h index 2cc20cd2..cef74bce 100644 --- a/include/sighandler.h +++ b/include/sighandler.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ diff --git a/include/startup.h b/include/startup.h index 0001a77d..a2b52c58 100644 --- a/include/startup.h +++ b/include/startup.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * startup.c: Startup notification code. Ensures a startup notification context @@ -56,7 +56,7 @@ void startup_sequence_rename_workspace(const char *old_name, const char *new_nam * Gets the stored startup sequence for the _NET_STARTUP_ID of a given window. * */ -struct Startup_Sequence *startup_sequence_get(i3Window *cwindow, +struct Startup_Sequence *startup_sequence_get(const i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply, bool ignore_mapped_leader); /** @@ -68,10 +68,10 @@ struct Startup_Sequence *startup_sequence_get(i3Window *cwindow, * Returns NULL otherwise. * */ -char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply); +char *startup_workspace_for_window(const i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply); /** * Deletes the startup sequence for a window if it exists. * */ -void startup_sequence_delete_by_window(i3Window *win); +void startup_sequence_delete_by_window(const i3Window *win); diff --git a/include/sync.h b/include/sync.h index e726f99e..b211ff29 100644 --- a/include/sync.h +++ b/include/sync.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * sync.c: i3 sync protocol: https://i3wm.org/docs/testsuite.html#i3_sync diff --git a/include/tiling_drag.h b/include/tiling_drag.h index 3091b734..1c292513 100644 --- a/include/tiling_drag.h +++ b/include/tiling_drag.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * tiling_drag.h: Reposition tiled windows by dragging. @@ -9,8 +9,6 @@ */ #pragma once -#include "all.h" - /** * Tiling drag initiation modes. */ diff --git a/include/tree.h b/include/tree.h index 0b758d53..e0f8d867 100644 --- a/include/tree.h +++ b/include/tree.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * tree.c: Everything that primarily modifies the layout tree data structure. diff --git a/include/util.h b/include/util.h index 8525b6d9..70fe56b8 100644 --- a/include/util.h +++ b/include/util.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * util.c: Utility functions, which can be useful everywhere within i3 (see @@ -71,7 +71,7 @@ Rect rect_sanitize_dimensions(Rect rect); * Returns true if the name consists of only digits. * */ -__attribute__((pure)) bool name_is_digits(const char *name); +bool name_is_digits(const char *name) __attribute__((__pure__)); /** * Set 'out' to the layout_t value for the given layout. The function @@ -170,28 +170,28 @@ ssize_t slurp(const char *path, char **buf); * Convert a direction to its corresponding orientation. * */ -orientation_t orientation_from_direction(direction_t direction); +orientation_t orientation_from_direction(direction_t direction) __attribute__((__const__)); /** * Convert a direction to its corresponding position. * */ -position_t position_from_direction(direction_t direction); +position_t position_from_direction(direction_t direction) __attribute__((__const__)); /** * Convert orientation and position to the corresponding direction. * */ -direction_t direction_from_orientation_position(orientation_t orientation, position_t position); +direction_t direction_from_orientation_position(orientation_t orientation, position_t position) __attribute__((__const__)); /** * Converts direction to a string representation. * */ -const char *direction_to_string(direction_t direction); +const char *direction_to_string(direction_t direction) __attribute__((__const__)); /** * Converts position to a string representation. * */ -const char *position_to_string(position_t position); +const char *position_to_string(position_t position) __attribute__((__const__)); diff --git a/include/window.h b/include/window.h index 7b43ab1f..49ef13ce 100644 --- a/include/window.h +++ b/include/window.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * window.c: Updates window attributes (X11 hints/properties). diff --git a/include/workspace.h b/include/workspace.h index fe6d9f88..0b8304cf 100644 --- a/include/workspace.h +++ b/include/workspace.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * workspace.c: Modifying workspaces, accessing them, moving containers to diff --git a/include/x.h b/include/x.h index d01709ed..8d8039a2 100644 --- a/include/x.h +++ b/include/x.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * x.c: Interface to X11, transfers our in-memory state to X11 (see also @@ -101,7 +101,7 @@ void x_push_changes(Con *con); * next call to x_push_changes() will make the change visible in X11. * */ -void x_raise_con(Con *con); +void x_raise_con(const Con *con); /** * Sets the WM_NAME property (so, no UTF8, but used only for debugging anyways) diff --git a/include/xcb.h b/include/xcb.h index ba4ff2f3..d469d8ef 100644 --- a/include/xcb.h +++ b/include/xcb.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * xcb.c: Helper functions for easier usage of XCB @@ -31,7 +31,7 @@ /** The XCB_CW_EVENT_MASK for its frame */ #define FRAME_EVENT_MASK (XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */ \ - XCB_EVENT_MASK_BUTTON_RELEASE | \ + XCB_EVENT_MASK_BUTTON_RELEASE | \ XCB_EVENT_MASK_POINTER_MOTION | /* …mouse is moved */ \ XCB_EVENT_MASK_EXPOSURE | /* …our window needs to be redrawn */ \ XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* …the frame gets destroyed */ \ @@ -98,12 +98,6 @@ xcb_atom_t xcb_get_preferred_window_type(xcb_get_property_reply_t *reply); */ bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom); -/** - * Get depth of visual specified by visualid - * - */ -uint16_t get_visual_depth(xcb_visualid_t visual_id); - /** * Get visual type specified by visualid * diff --git a/include/xcursor.h b/include/xcursor.h index ad33d506..4c9c304d 100644 --- a/include/xcursor.h +++ b/include/xcursor.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * xcursor.c: libXcursor support for themed cursors. diff --git a/include/xinerama.h b/include/xinerama.h index 52a5db33..e3fe67fc 100644 --- a/include/xinerama.h +++ b/include/xinerama.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * This is LEGACY code (we support RandR, which can do much more than diff --git a/include/yajl_utils.h b/include/yajl_utils.h index 6ab1ff1c..808925b0 100644 --- a/include/yajl_utils.h +++ b/include/yajl_utils.h @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * yajl_utils.h diff --git a/libi3/boolstr.c b/libi3/boolstr.c index 0fa417dd..a97f0af7 100644 --- a/libi3/boolstr.c +++ b/libi3/boolstr.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ diff --git a/libi3/create_socket.c b/libi3/create_socket.c index d476f43a..263ef7b3 100644 --- a/libi3/create_socket.c +++ b/libi3/create_socket.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ diff --git a/libi3/dpi.c b/libi3/dpi.c index dec38bc8..9841ec35 100644 --- a/libi3/dpi.c +++ b/libi3/dpi.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -45,7 +45,7 @@ void init_dpi(void) { char *endptr; double in_dpi = strtod(resource, &endptr); - if (in_dpi == HUGE_VAL || dpi < 0 || *endptr != '\0' || endptr == resource) { + if (in_dpi == HUGE_VAL || in_dpi < 0 || *endptr != '\0' || endptr == resource) { ELOG("Xft.dpi = %s is an invalid number and couldn't be parsed.\n", resource); dpi = 0; goto init_dpi_end; @@ -95,7 +95,8 @@ int logical_px(const int logical) { * systems to 96 dpi in order to get the behavior they expect/are used to, * but since we can easily detect this case in code, let’s do it for them. */ - if ((dpi / 96.0) < 1.25) + if ((dpi / 96.0) < 1.25) { return logical; + } return ceil((dpi / 96.0) * logical); } diff --git a/libi3/draw_util.c b/libi3/draw_util.c index 903e3536..c6555b4a 100644 --- a/libi3/draw_util.c +++ b/libi3/draw_util.c @@ -12,7 +12,6 @@ #include #include -#include /* The default visual_type to use if none is specified when creating the surface. Must be defined globally. */ extern xcb_visualtype_t *visual_type; @@ -28,6 +27,78 @@ static bool surface_initialized(surface_t *surface) { return true; } +/* + * Get a GC for the given depth. The given drawable must have this depth. + * + * Per the X11 protocol manual for "CreateGC": + * > The gcontext can be used with any destination drawable having the same root + * > and depth as the specified drawable; + */ +static xcb_gcontext_t get_gc(xcb_connection_t *conn, uint8_t depth, xcb_drawable_t drawable, bool *should_free) { + static struct { + uint8_t depth; + xcb_gcontext_t gc; + } gc_cache[2] = { + 0, + }; + + size_t index = 0; + bool cache = false; + + *should_free = false; + for (; index < sizeof(gc_cache) / sizeof(gc_cache[0]); index++) { + if (gc_cache[index].depth == depth) { + return gc_cache[index].gc; + } + if (gc_cache[index].depth == 0) { + cache = true; + break; + } + } + + xcb_gcontext_t gc = xcb_generate_id(conn); + /* The drawable is only used to get the root and depth, thus the GC is not + * tied to the drawable and it can be re-used with different drawables. */ + xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(conn, gc, drawable, 0, NULL); + + xcb_generic_error_t *error = xcb_request_check(conn, gc_cookie); + if (error != NULL) { + ELOG("Could not create graphical context. Error code: %d. Please report this bug.\n", error->error_code); + free(error); + return gc; + } + + if (cache) { + gc_cache[index].depth = depth; + gc_cache[index].gc = gc; + } else { + *should_free = true; + } + + return gc; +} + +/* + * Get depth of visual specified by visualid + * + */ +uint16_t get_visual_depth(const xcb_visualid_t visual_id) { + xcb_depth_iterator_t depth_iter; + + depth_iter = xcb_screen_allowed_depths_iterator(root_screen); + for (; depth_iter.rem; xcb_depth_next(&depth_iter)) { + xcb_visualtype_iterator_t visual_iter; + + visual_iter = xcb_depth_visuals_iterator(depth_iter.data); + for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) { + if (visual_id == visual_iter.data->visual_id) { + return depth_iter.data->depth; + } + } + } + return 0; +} + /* * Initialize the surface to represent the given drawable. * @@ -38,18 +109,11 @@ void draw_util_surface_init(xcb_connection_t *conn, surface_t *surface, xcb_draw surface->width = width; surface->height = height; - if (visual == NULL) + if (visual == NULL) { visual = visual_type; - - surface->gc = xcb_generate_id(conn); - xcb_void_cookie_t gc_cookie = xcb_create_gc_checked(conn, surface->gc, surface->id, 0, NULL); - - xcb_generic_error_t *error = xcb_request_check(conn, gc_cookie); - if (error != NULL) { - ELOG("Could not create graphical context. Error code: %d. Please report this bug.\n", error->error_code); - free(error); } + surface->gc = get_gc(conn, get_visual_depth(visual->visual_id), drawable, &surface->owns_gc); surface->surface = cairo_xcb_surface_create(conn, surface->id, visual, width, height); surface->cr = cairo_create(surface->surface); } @@ -68,11 +132,9 @@ void draw_util_surface_free(xcb_connection_t *conn, surface_t *surface) { status, cairo_status_to_string(status)); } - /* NOTE: This function is also called on uninitialised surface_t instances. - * The x11 error from xcb_free_gc(conn, XCB_NONE) is silently ignored - * elsewhere. - */ - xcb_free_gc(conn, surface->gc); + if (surface->owns_gc) { + xcb_free_gc(conn, surface->gc); + } cairo_surface_destroy(surface->surface); cairo_destroy(surface->cr); diff --git a/libi3/fake_configure_notify.c b/libi3/fake_configure_notify.c index 5d87c3da..38e1bff5 100644 --- a/libi3/fake_configure_notify.c +++ b/libi3/fake_configure_notify.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ diff --git a/libi3/font.c b/libi3/font.c index 10abad05..e2d0e357 100644 --- a/libi3/font.c +++ b/libi3/font.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -95,11 +95,13 @@ static void draw_text_pango(const char *text, size_t text_len, pango_layout_set_width(layout, max_width * PANGO_SCALE); pango_layout_set_wrap(layout, PANGO_WRAP_CHAR); pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + pango_layout_set_single_paragraph_mode(layout, true); - if (pango_markup) + if (pango_markup) { pango_layout_set_markup(layout, text, text_len); - else + } else { pango_layout_set_text(layout, text, text_len); + } /* Do the drawing */ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); @@ -131,10 +133,11 @@ static int predict_text_width_pango(const char *text, size_t text_len, bool pang gint width; pango_layout_set_font_description(layout, savedFont->specific.pango_desc); - if (pango_markup) + if (pango_markup) { pango_layout_set_markup(layout, text, text_len); - else + } else { pango_layout_set_text(layout, text, text_len); + } pango_cairo_update_layout(cr, layout); pango_layout_get_pixel_size(layout, &width, NULL); @@ -214,10 +217,11 @@ i3Font load_font(const char *pattern, const bool fallback) { info_cookie = xcb_query_font(conn, font.specific.xcb.id); free(error); - if ((error = xcb_request_check(conn, font_cookie)) != NULL) + if ((error = xcb_request_check(conn, font_cookie)) != NULL) { errx(EXIT_FAILURE, "Could open neither requested font nor fallbacks " "(fixed or -misc-*): X11 error %d", error->error_code); + } } } free(error); @@ -226,14 +230,16 @@ i3Font load_font(const char *pattern, const bool fallback) { LOG("Using X font %s\n", pattern); /* Get information (height/name) for this font */ - if (!(font.specific.xcb.info = xcb_query_font_reply(conn, info_cookie, NULL))) + if (!(font.specific.xcb.info = xcb_query_font_reply(conn, info_cookie, NULL))) { errx(EXIT_FAILURE, "Could not load font \"%s\"", pattern); + } /* Get the font table, if possible */ - if (xcb_query_font_char_infos_length(font.specific.xcb.info) == 0) + if (xcb_query_font_char_infos_length(font.specific.xcb.info) == 0) { font.specific.xcb.table = NULL; - else + } else { font.specific.xcb.table = xcb_query_font_char_infos(font.specific.xcb.info); + } /* Calculate the font height */ font.height = font.specific.xcb.info->font_ascent + font.specific.xcb.info->font_descent; @@ -258,8 +264,9 @@ void set_font(i3Font *font) { */ void free_font(void) { /* if there is no saved font, simply return */ - if (savedFont == NULL) + if (savedFont == NULL) { return; + } free(savedFont->pattern); switch (savedFont->type) { @@ -340,8 +347,9 @@ static void draw_text_xcb(const xcb_char2b_t *text, size_t text_len, xcb_drawabl text_len -= chunk_size; /* Check if we're done */ - if (text_len == 0) + if (text_len == 0) { break; + } /* Advance pos_x based on the predicted text width */ x += predict_text_width_xcb(chunk, chunk_size); @@ -405,8 +413,9 @@ static int xcb_query_text_width(const xcb_char2b_t *text, size_t text_len) { } static int predict_text_width_xcb(const xcb_char2b_t *input, size_t text_len) { - if (text_len == 0) + if (text_len == 0) { return 0; + } int width; if (savedFont->specific.xcb.table == NULL) { @@ -427,8 +436,9 @@ static int predict_text_width_xcb(const xcb_char2b_t *input, size_t text_len) { if (row < font_info->min_byte1 || row > font_info->max_byte1 || col < font_info->min_char_or_byte2 || - col > font_info->max_char_or_byte2) + col > font_info->max_char_or_byte2) { continue; + } /* Don't you ask me, how this one works… (Merovius) */ info = &font_table[((row - font_info->min_byte1) * diff --git a/libi3/format_placeholders.c b/libi3/format_placeholders.c index 71870a7b..9542a254 100644 --- a/libi3/format_placeholders.c +++ b/libi3/format_placeholders.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -19,16 +19,18 @@ * */ char *format_placeholders(char *format, placeholder_t *placeholders, int num) { - if (format == NULL) + if (format == NULL) { return NULL; + } /* We have to first iterate over the string to see how much buffer space * we need to allocate. */ int buffer_len = strlen(format) + 1; for (char *walk = format; *walk != '\0'; walk++) { for (int i = 0; i < num; i++) { - if (!CS_STARTS_WITH(walk, placeholders[i].name)) + if (!CS_STARTS_WITH(walk, placeholders[i].name)) { continue; + } buffer_len = buffer_len - strlen(placeholders[i].name) + strlen(placeholders[i].value); walk += strlen(placeholders[i].name) - 1; @@ -57,8 +59,9 @@ char *format_placeholders(char *format, placeholder_t *placeholders, int num) { break; } - if (!matched) + if (!matched) { *(outwalk++) = *walk; + } } *outwalk = '\0'; diff --git a/libi3/get_colorpixel.c b/libi3/get_colorpixel.c index 45e47725..e147b793 100644 --- a/libi3/get_colorpixel.c +++ b/libi3/get_colorpixel.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -50,17 +50,18 @@ uint32_t get_colorpixel(const char *hex) { /* 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 (a << 24) | (r << 16 | g << 8 | b); + return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; } /* Lookup this colorpixel in the cache */ struct Colorpixel *colorpixel; SLIST_FOREACH (colorpixel, &(colorpixels), colorpixels) { - if (strcmp(colorpixel->hex, hex) == 0) + if (strcmp(colorpixel->hex, hex) == 0) { return colorpixel->pixel; + } } -#define RGB_8_TO_16(i) (65535 * ((i)&0xFF) / 255) +#define RGB_8_TO_16(i) (65535 * ((i) & 0xFF) / 255) int r16 = RGB_8_TO_16(r); int g16 = RGB_8_TO_16(g); int b16 = RGB_8_TO_16(b); diff --git a/libi3/get_config_path.c b/libi3/get_config_path.c index 1f2b4c05..c401d741 100644 --- a/libi3/get_config_path.c +++ b/libi3/get_config_path.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ diff --git a/libi3/get_exe_path.c b/libi3/get_exe_path.c index 3b46ef82..9f489f93 100644 --- a/libi3/get_exe_path.c +++ b/libi3/get_exe_path.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -83,8 +83,9 @@ char *get_exe_path(const char *argv0) { const char *component; char *str = path; while (1) { - if ((component = strtok(str, ":")) == NULL) + if ((component = strtok(str, ":")) == NULL) { break; + } str = NULL; free(destpath); sasprintf(&destpath, "%s/%s", component, argv0); diff --git a/libi3/get_mod_mask.c b/libi3/get_mod_mask.c index 92af456d..f74378f9 100644 --- a/libi3/get_mod_mask.c +++ b/libi3/get_mod_mask.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -29,8 +29,9 @@ uint32_t aio_get_mod_mask_for(uint32_t keysym, xcb_key_symbols_t *symbols) { /* Get the current modifier mapping (this is blocking!) */ cookie = xcb_get_modifier_mapping(conn); - if (!(modmap_r = xcb_get_modifier_mapping_reply(conn, cookie, NULL))) + if (!(modmap_r = xcb_get_modifier_mapping_reply(conn, cookie, NULL))) { return 0; + } uint32_t result = get_mod_mask_for(keysym, symbols, modmap_r); free(modmap_r); @@ -53,25 +54,28 @@ uint32_t get_mod_mask_for(uint32_t keysym, modmap = xcb_get_modifier_mapping_keycodes(modmap_reply); /* Get the list of keycodes for the given symbol */ - if (!(codes = xcb_key_symbols_get_keycode(symbols, keysym))) + if (!(codes = xcb_key_symbols_get_keycode(symbols, keysym))) { return 0; + } /* Loop through all modifiers (Mod1-Mod5, Shift, Control, Lock) */ - for (int mod = 0; mod < 8; mod++) + for (int mod = 0; mod < 8; mod++) { for (int j = 0; j < modmap_reply->keycodes_per_modifier; j++) { /* Store the current keycode (for modifier 'mod') */ mod_code = modmap[(mod * modmap_reply->keycodes_per_modifier) + j]; /* Check if that keycode is in the list of previously resolved * keycodes for our symbol. If so, return the modifier mask. */ for (xcb_keycode_t *code = codes; *code; code++) { - if (*code != mod_code) + if (*code != mod_code) { continue; + } free(codes); /* This corresponds to the XCB_MOD_MASK_* constants */ return (1 << mod); } } + } return 0; } diff --git a/libi3/get_process_filename.c b/libi3/get_process_filename.c index d29f8db1..0eb2b95b 100644 --- a/libi3/get_process_filename.c +++ b/libi3/get_process_filename.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ diff --git a/libi3/get_visualtype.c b/libi3/get_visualtype.c index ccf266db..ce4b2495 100644 --- a/libi3/get_visualtype.c +++ b/libi3/get_visualtype.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -20,8 +20,9 @@ xcb_visualtype_t *get_visualtype(xcb_screen_t *screen) { for (visual_iter = xcb_depth_visuals_iterator(depth_iter.data); visual_iter.rem; xcb_visualtype_next(&visual_iter)) { - if (screen->root_visual == visual_iter.data->visual_id) + if (screen->root_visual == visual_iter.data->visual_id) { return visual_iter.data; + } } } return NULL; diff --git a/libi3/ipc_connect.c b/libi3/ipc_connect.c index 5da9f129..c6b945a6 100644 --- a/libi3/ipc_connect.c +++ b/libi3/ipc_connect.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -37,7 +37,7 @@ int ipc_connect(const char *socket_path) { } if (path == NULL) { - path = sstrdup("/tmp/i3-ipc.sock"); + err(EXIT_FAILURE, "Could not determine i3 socket path"); } int sockfd = ipc_connect_impl(path); @@ -55,8 +55,9 @@ int ipc_connect(const char *socket_path) { */ int ipc_connect_impl(const char *socket_path) { int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); - if (sockfd == -1) + if (sockfd == -1) { err(EXIT_FAILURE, "Could not create socket"); + } (void)fcntl(sockfd, F_SETFD, FD_CLOEXEC); diff --git a/libi3/ipc_recv_message.c b/libi3/ipc_recv_message.c index 516405b0..92a8cc35 100644 --- a/libi3/ipc_recv_message.c +++ b/libi3/ipc_recv_message.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -36,8 +36,9 @@ int ipc_recv_message(int sockfd, uint32_t *message_type, uint32_t read_bytes = 0; while (read_bytes < to_read) { int n = read(sockfd, msg + read_bytes, to_read - read_bytes); - if (n == -1) + if (n == -1) { return -1; + } if (n == 0) { if (read_bytes == 0) { return -2; @@ -60,8 +61,9 @@ int ipc_recv_message(int sockfd, uint32_t *message_type, walk += strlen(I3_IPC_MAGIC); memcpy(reply_length, walk, sizeof(uint32_t)); walk += sizeof(uint32_t); - if (message_type != NULL) + if (message_type != NULL) { memcpy(message_type, walk, sizeof(uint32_t)); + } *reply = smalloc(*reply_length); @@ -69,8 +71,9 @@ int ipc_recv_message(int sockfd, uint32_t *message_type, while (read_bytes < *reply_length) { const int n = read(sockfd, *reply + read_bytes, *reply_length - read_bytes); if (n == -1) { - if (errno == EINTR || errno == EAGAIN) + if (errno == EINTR || errno == EAGAIN) { continue; + } return -1; } if (n == 0) { diff --git a/libi3/ipc_send_message.c b/libi3/ipc_send_message.c index 4faeea7f..801d250d 100644 --- a/libi3/ipc_send_message.c +++ b/libi3/ipc_send_message.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -26,11 +26,13 @@ int ipc_send_message(int sockfd, const uint32_t message_size, .size = message_size, .type = message_type}; - if (writeall(sockfd, ((void *)&header), sizeof(i3_ipc_header_t)) == -1) + if (writeall(sockfd, ((void *)&header), sizeof(i3_ipc_header_t)) == -1) { return -1; + } - if (writeall(sockfd, payload, message_size) == -1) + if (writeall(sockfd, payload, message_size) == -1) { return -1; + } return 0; } diff --git a/libi3/is_background_set.c b/libi3/is_background_set.c index b3b6e6a6..319e52fc 100644 --- a/libi3/is_background_set.c +++ b/libi3/is_background_set.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ diff --git a/libi3/is_debug_build.c b/libi3/is_debug_build.c index 9458f75b..9228b3d2 100644 --- a/libi3/is_debug_build.c +++ b/libi3/is_debug_build.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ diff --git a/libi3/mkdirp.c b/libi3/mkdirp.c index d29bb95b..14016fa9 100644 --- a/libi3/mkdirp.c +++ b/libi3/mkdirp.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -18,8 +18,9 @@ * */ int mkdirp(const char *path, mode_t mode) { - if (mkdir(path, mode) == 0) + if (mkdir(path, mode) == 0) { return 0; + } if (errno == EEXIST) { struct stat st; /* Check that the named file actually is a directory. */ @@ -38,8 +39,9 @@ int mkdirp(const char *path, mode_t mode) { } char *copy = sstrdup(path); /* strip trailing slashes, if any */ - while (copy[strlen(copy) - 1] == '/') + while (copy[strlen(copy) - 1] == '/') { copy[strlen(copy) - 1] = '\0'; + } char *sep = strrchr(copy, '/'); if (sep == NULL) { @@ -48,8 +50,9 @@ int mkdirp(const char *path, mode_t mode) { } *sep = '\0'; int result = -1; - if (mkdirp(copy, mode) == 0) + if (mkdirp(copy, mode) == 0) { result = mkdirp(path, mode); + } free(copy); return result; diff --git a/libi3/path_exists.c b/libi3/path_exists.c index 5451c3bd..553b1cb0 100644 --- a/libi3/path_exists.c +++ b/libi3/path_exists.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ diff --git a/libi3/resolve_tilde.c b/libi3/resolve_tilde.c index 6dbf132f..c1109dda 100644 --- a/libi3/resolve_tilde.c +++ b/libi3/resolve_tilde.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -28,9 +28,9 @@ char *resolve_tilde(const char *path) { int res = glob(head, GLOB_TILDE, NULL, &globbuf); free(head); /* no match, or many wildcard matches are bad */ - if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1) + if (res == GLOB_NOMATCH || globbuf.gl_pathc != 1) { result = sstrdup(path); - else if (res != 0) { + } else if (res != 0) { err(EXIT_FAILURE, "glob() failed"); } else { head = globbuf.gl_pathv[0]; diff --git a/libi3/root_atom_contents.c b/libi3/root_atom_contents.c index 6feb31bc..d3d23819 100644 --- a/libi3/root_atom_contents.c +++ b/libi3/root_atom_contents.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -86,7 +86,8 @@ out: out_atom: free(atom_reply); out_conn: - if (provided_conn == NULL) + if (provided_conn == NULL) { xcb_disconnect(conn); + } return content; } diff --git a/libi3/safewrappers.c b/libi3/safewrappers.c index 767a0f05..086f7245 100644 --- a/libi3/safewrappers.c +++ b/libi3/safewrappers.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -20,36 +20,41 @@ */ void *smalloc(size_t size) { void *result = malloc(size); - if (result == NULL) + if (result == NULL) { err(EXIT_FAILURE, "malloc(%zd)", size); + } return result; } void *scalloc(size_t num, size_t size) { void *result = calloc(num, size); - if (result == NULL) + if (result == NULL) { err(EXIT_FAILURE, "calloc(%zd, %zd)", num, size); + } return result; } void *srealloc(void *ptr, size_t size) { void *result = realloc(ptr, size); - if (result == NULL && size > 0) + if (result == NULL && size > 0) { err(EXIT_FAILURE, "realloc(%zd)", size); + } return result; } char *sstrdup(const char *str) { char *result = strdup(str); - if (result == NULL) + if (result == NULL) { err(EXIT_FAILURE, "strdup()"); + } return result; } char *sstrndup(const char *str, size_t size) { char *result = strndup(str, size); - if (result == NULL) + if (result == NULL) { err(EXIT_FAILURE, "strndup()"); + } return result; } @@ -58,8 +63,9 @@ int sasprintf(char **strp, const char *fmt, ...) { int result; va_start(args, fmt); - if ((result = vasprintf(strp, fmt, args)) == -1) + if ((result = vasprintf(strp, fmt, args)) == -1) { err(EXIT_FAILURE, "asprintf(%s)", fmt); + } va_end(args); return result; } @@ -70,8 +76,9 @@ ssize_t writeall(int fd, const void *buf, size_t count) { while (written < count) { const ssize_t n = write(fd, ((char *)buf) + written, count - written); if (n == -1) { - if (errno == EINTR || errno == EAGAIN) + if (errno == EINTR || errno == EAGAIN) { continue; + } return n; } written += (size_t)n; @@ -103,10 +110,11 @@ ssize_t swrite(int fd, const void *buf, size_t count) { ssize_t n; n = writeall(fd, buf, count); - if (n == -1) + if (n == -1) { err(EXIT_FAILURE, "Failed to write %d", fd); - else + } else { return n; + } } /* diff --git a/libi3/screenshot_wallpaper.c b/libi3/screenshot_wallpaper.c index 2c115a9e..28a23852 100644 --- a/libi3/screenshot_wallpaper.c +++ b/libi3/screenshot_wallpaper.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ diff --git a/libi3/string.c b/libi3/string.c index da18c550..4d280a87 100644 --- a/libi3/string.c +++ b/libi3/string.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * string.c: Define an i3String type to automagically handle UTF-8/UCS-2 @@ -113,23 +113,27 @@ i3String *i3string_copy(i3String *str) { * */ void i3string_free(i3String *str) { - if (str == NULL) + if (str == NULL) { return; + } free(str->utf8); free(str->ucs2); free(str); } static void i3string_ensure_utf8(i3String *str) { - if (str->utf8 != NULL) + if (str->utf8 != NULL) { return; - if ((str->utf8 = convert_ucs2_to_utf8(str->ucs2, str->num_glyphs)) != NULL) + } + if ((str->utf8 = convert_ucs2_to_utf8(str->ucs2, str->num_glyphs)) != NULL) { str->num_bytes = strlen(str->utf8); + } } static void i3string_ensure_ucs2(i3String *str) { - if (str->ucs2 != NULL) + if (str->ucs2 != NULL) { return; + } str->ucs2 = convert_utf8_to_ucs2(str->utf8, &str->num_glyphs); } diff --git a/libi3/strndup.c b/libi3/strndup.c index 8911732c..dc491b4d 100644 --- a/libi3/strndup.c +++ b/libi3/strndup.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -20,8 +20,9 @@ char *strndup(const char *str, size_t n) { size_t len; char *copy; - for (len = 0; len < n && str[len]; len++) + for (len = 0; len < n && str[len]; len++) { continue; + } copy = smalloc(len + 1); memcpy(copy, str, len); diff --git a/libi3/ucs2_conversion.c b/libi3/ucs2_conversion.c index c7467239..6eb51f44 100644 --- a/libi3/ucs2_conversion.c +++ b/libi3/ucs2_conversion.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -32,8 +32,9 @@ char *convert_ucs2_to_utf8(xcb_char2b_t *text, size_t num_glyphs) { if (utf8_conversion_descriptor == (iconv_t)-1) { /* Get a new conversion descriptor */ utf8_conversion_descriptor = iconv_open("UTF-8", "UCS-2BE"); - if (utf8_conversion_descriptor == (iconv_t)-1) + if (utf8_conversion_descriptor == (iconv_t)-1) { err(EXIT_FAILURE, "Error opening the conversion context"); + } } else { /* Reset the existing conversion descriptor */ iconv(utf8_conversion_descriptor, NULL, NULL, NULL, NULL); diff --git a/logo.svg b/logo.svg index 6f87d0c5..4324e31b 100644 --- a/logo.svg +++ b/logo.svg @@ -312,7 +312,7 @@ steckdenis - Logo for I3, an improved dynamic tiling window manager: http://i3wm.org/ + Logo for i3 - an improved tiling window manager: http://i3wm.org/ diff --git a/man/i3.man b/man/i3.man index 1974f0b9..e37aaaec 100644 --- a/man/i3.man +++ b/man/i3.man @@ -5,7 +5,7 @@ v4.3, September 2012 == NAME -i3 - an improved dynamic, tiling window manager +i3 - an improved tiling window manager == SYNOPSIS @@ -317,32 +317,25 @@ xsetroot -solid "#333333" # Enable core dumps in case something goes wrong ulimit -c unlimited -# Start i3 and log to ~/.i3/logfile -echo "Starting at $(date)" >> ~/.i3/logfile -exec /usr/bin/i3 -V -d all >> ~/.i3/logfile +# Start i3 and log to ~/.local/share/i3/log +mkdir -p ~/.local/share/i3 +echo "Starting at $(date)" >> ~/.local/share/i3/log +exec /usr/bin/i3 -V -d all >> ~/.local/share/i3/log ------------------------------------------------------------- == ENVIRONMENT === I3SOCK -This variable overwrites the IPC socket path (placed in -/tmp/i3-%u.XXXXXX/ipc-socket.%p by default, where %u is replaced with your UNIX -username, %p is replaced with i3’s PID and XXXXXX is a string of random -characters from the portable filename character set (see mkdtemp(3))). The IPC -socket is used by external programs like i3-msg(1) or i3bar(1). - -== TODO - -There is still lot of work to do. Please check our bugtracker for up-to-date -information about tasks which are still not finished. +This variable overwrites the IPC socket path (see userguide for default +location). The IPC socket is used by external programs like i3-msg(1), i3bar(1) +or user-made scripts. == SEE ALSO You should have a copy of the userguide (featuring nice screenshots/graphics which is why this is not integrated into this manpage), the debugging guide, -and the "how to hack" guide. If you are building from source, run: - +make -C docs+ +and the "how to hack" guide. You can also access these documents online at https://i3wm.org/ diff --git a/man/i3bar.man b/man/i3bar.man index 479e10fc..761748f3 100644 --- a/man/i3bar.man +++ b/man/i3bar.man @@ -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 diff --git a/meson.build b/meson.build index b272c271..afff3305 100644 --- a/meson.build +++ b/meson.build @@ -6,17 +6,17 @@ project( 'i3', 'c', - version: '4.22', + version: '4.25', 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', ], @@ -269,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', @@ -401,6 +403,7 @@ i3srcs = [ 'src/match.c', 'src/move.c', 'src/output.c', + 'src/parser_util.c', 'src/randr.c', 'src/regex.c', 'src/render.c', @@ -683,6 +686,7 @@ executable( 'test.commands_parser', [ 'src/commands_parser.c', + 'src/parser_util.c', command_parser, ], include_directories: inc, @@ -695,6 +699,7 @@ executable( 'test.config_parser', [ 'src/config_parser.c', + 'src/parser_util.c', config_parser, ], include_directories: inc, diff --git a/meson/meson-dist-script b/meson/meson-dist-script index 47d9ce36..a86dd90c 100755 --- a/meson/meson-dist-script +++ b/meson/meson-dist-script @@ -17,6 +17,7 @@ rm -rf \ docs/slides-2012-03-16/ \ testcases/.gitignore \ travis/ \ + release-notes/ \ .clang-format \ .editorconfig \ i3bar/.gitignore \ @@ -28,7 +29,7 @@ rm -rf \ mkdir build cd build -meson .. -Dprefix=/usr -Ddocs=true -Dmans=true +meson setup -Dprefix=/usr -Ddocs=true -Dmans=true ninja cp *.1 ../man/ cp *.html ../docs/ diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index 336a8d42..c7bc3bc1 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -1,6 +1,6 @@ # vim:ts=2:sw=2:expandtab # -# i3 - an improved dynamic tiling window manager +# i3 - an improved tiling window manager # © 2009 Michael Stapelberg and contributors (see also: LICENSE) # # parser-specs/commands.spec: Specification file for generate-command-parser.pl @@ -186,6 +186,7 @@ state WORKSPACE_NUMBER: # focus output # focus tiling|floating|mode_toggle # focus parent|child +# focus workspace # focus state FOCUS: direction = 'left', 'right', 'up', 'down' @@ -198,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' diff --git a/parser-specs/config.spec b/parser-specs/config.spec index 52bd3212..d1528374 100644 --- a/parser-specs/config.spec +++ b/parser-specs/config.spec @@ -1,6 +1,6 @@ # vim:ts=2:sw=2:expandtab # -# i3 - an improved dynamic tiling window manager +# i3 - an improved tiling window manager # © 2009 Michael Stapelberg and contributors (see also: LICENSE) # # parser-specs/config.spec: Specification file for generate-command-parser.pl @@ -140,6 +140,15 @@ state FLOATING_MODIFIER: end -> call cfg_floating_modifier($modifiers) +# tiling_drag swap_modifier +state TILING_DRAG_SWAP_MODIFIER: + modifiers = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Shift', 'Control', 'Ctrl' + -> + '+' + -> + end + -> call cfg_tiling_drag_swap_modifier($modifiers) + # default_orientation state DEFAULT_ORIENTATION: orientation = 'horizontal', 'vertical', 'auto' @@ -366,7 +375,7 @@ state RESTART_STATE: # popup_during_fullscreen state POPUP_DURING_FULLSCREEN: - value = 'ignore', 'leave_fullscreen', 'smart' + value = 'ignore', 'leave_fullscreen', 'all', 'smart' -> call cfg_popup_during_fullscreen($value) state TILING_DRAG_MODE: @@ -378,6 +387,8 @@ state TILING_DRAG_MODE: state TILING_DRAG: off = '0', 'no', 'false', 'off', 'disable', 'inactive' -> call cfg_tiling_drag($off) + swap_modifier = 'swap_modifier' + -> TILING_DRAG_SWAP_MODIFIER value = 'modifier', 'titlebar' -> TILING_DRAG_MODE @@ -528,6 +539,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 @@ -567,6 +579,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 diff --git a/pseudo-doc.doxygen b/pseudo-doc.doxygen deleted file mode 100644 index 60f8e8ed..00000000 --- a/pseudo-doc.doxygen +++ /dev/null @@ -1,148 +0,0 @@ -# Doxyfile 1.5.6 -# -# You can use this file with doxygen to create a pseudo-documentation -# automatically from source. doxygen-comments are not used very extensively -# in i3, mostly for the reason that it clutters the source code and has no -# real use (doxygen’s output looks really ugly). -# -# So, if you want to use it, here you go. This is however not a supported -# document, and I recommend you have a look at the docs/ folder or at -# https://i3wm.org/ for more, real documentation. -# - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -DOXYFILE_ENCODING = UTF-8 -PROJECT_NAME = i3 -PROJECT_NUMBER = -OUTPUT_DIRECTORY = pseudo-doc -OUTPUT_LANGUAGE = English -BRIEF_MEMBER_DESC = YES -REPEAT_BRIEF = YES -ALWAYS_DETAILED_SEC = YES -FULL_PATH_NAMES = YES -SHORT_NAMES = YES -JAVADOC_AUTOBRIEF = YES -TAB_SIZE = 8 -OPTIMIZE_OUTPUT_FOR_C = YES - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -EXTRACT_ALL = YES -EXTRACT_PRIVATE = NO -EXTRACT_STATIC = YES -EXTRACT_LOCAL_CLASSES = YES -CASE_SENSE_NAMES = YES -SHOW_INCLUDE_FILES = YES -SORT_MEMBER_DOCS = YES -SORT_BRIEF_DOCS = NO -SORT_GROUP_NAMES = NO -SORT_BY_SCOPE_NAME = NO -SHOW_USED_FILES = YES -SHOW_FILES = YES - -#--------------------------------------------------------------------------- -# configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -QUIET = NO -WARNINGS = YES -WARN_IF_UNDOCUMENTED = YES -WARN_IF_DOC_ERROR = YES -WARN_NO_PARAMDOC = NO -WARN_FORMAT = "$file:$line: $text" -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# configuration options related to the input files -#--------------------------------------------------------------------------- - -INPUT = src include -INPUT_ENCODING = UTF-8 -RECURSIVE = NO - -#--------------------------------------------------------------------------- -# configuration options related to source browsing -#--------------------------------------------------------------------------- - -SOURCE_BROWSER = YES -INLINE_SOURCES = NO - -STRIP_CODE_COMMENTS = YES - -REFERENCED_BY_RELATION = YES -REFERENCES_RELATION = YES -REFERENCES_LINK_SOURCE = YES - -#--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -ALPHABETICAL_INDEX = NO -COLS_IN_ALPHA_INDEX = 5 - -#--------------------------------------------------------------------------- -# configuration options related to the HTML output -#--------------------------------------------------------------------------- - -GENERATE_HTML = YES -HTML_OUTPUT = html -HTML_FILE_EXTENSION = .html -HTML_ALIGN_MEMBERS = YES -HTML_DYNAMIC_SECTIONS = NO -FORMULA_FONTSIZE = 10 - -GENERATE_LATEX = NO -GENERATE_RTF = NO -GENERATE_MAN = NO -GENERATE_XML = NO -GENERATE_AUTOGEN_DEF = NO -GENERATE_PERLMOD = NO - -ENABLE_PREPROCESSING = YES -MACRO_EXPANSION = NO -EXPAND_ONLY_PREDEF = NO -SEARCH_INCLUDES = YES -SKIP_FUNCTION_MACROS = YES - -#--------------------------------------------------------------------------- -# Configuration::additions related to external references -#--------------------------------------------------------------------------- - -PERL_PATH = /usr/bin/perl - -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- - -CLASS_DIAGRAMS = YES -HAVE_DOT = YES -DOT_FONTNAME = FreeSans -DOT_FONTPATH = -CLASS_GRAPH = YES -COLLABORATION_GRAPH = YES -GROUP_GRAPHS = YES -UML_LOOK = NO -INCLUDE_GRAPH = YES -INCLUDED_BY_GRAPH = YES -CALL_GRAPH = YES -CALLER_GRAPH = NO -GRAPHICAL_HIERARCHY = YES -DIRECTORY_GRAPH = YES -DOT_IMAGE_FORMAT = png -DOT_GRAPH_MAX_NODES = 50 -MAX_DOT_GRAPH_DEPTH = 0 -DOT_TRANSPARENT = YES -DOT_MULTI_TARGETS = NO -GENERATE_LEGEND = YES -DOT_CLEANUP = YES - -#--------------------------------------------------------------------------- -# Configuration::additions related to the search engine -#--------------------------------------------------------------------------- - -SEARCHENGINE = NO diff --git a/release-notes/bugfixes/1-i3bar-padding-click b/release-notes/bugfixes/1-i3bar-padding-click new file mode 100644 index 00000000..2b5302fe --- /dev/null +++ b/release-notes/bugfixes/1-i3bar-padding-click @@ -0,0 +1 @@ +i3bar: fix clicks when horizontal padding is used diff --git a/release-notes/bugfixes/1-motif b/release-notes/bugfixes/1-motif deleted file mode 100644 index 793d8f36..00000000 --- a/release-notes/bugfixes/1-motif +++ /dev/null @@ -1 +0,0 @@ -Fix compliance to _MOTIF_WM_HINTS spec when all decorations are set diff --git a/release-notes/bugfixes/1-workspace_auto_back_and_forth-focus-workspace b/release-notes/bugfixes/1-workspace_auto_back_and_forth-focus-workspace new file mode 100644 index 00000000..2690e75f --- /dev/null +++ b/release-notes/bugfixes/1-workspace_auto_back_and_forth-focus-workspace @@ -0,0 +1 @@ +consider workspace_auto_back_and_forth in focus workspace diff --git a/release-notes/bugfixes/10-tiling-drag-focused-parent b/release-notes/bugfixes/10-tiling-drag-focused-parent new file mode 100644 index 00000000..fa142cc1 --- /dev/null +++ b/release-notes/bugfixes/10-tiling-drag-focused-parent @@ -0,0 +1 @@ +fix crash when a container parent is focused and a tiling drag causes it to be killed diff --git a/release-notes/bugfixes/2-gaps-order b/release-notes/bugfixes/2-gaps-order deleted file mode 100644 index 8c3c78df..00000000 --- a/release-notes/bugfixes/2-gaps-order +++ /dev/null @@ -1 +0,0 @@ -gaps: workspace gaps assignments are no longer order-dependent diff --git a/release-notes/bugfixes/2-numbered-workspace-prev-next b/release-notes/bugfixes/2-numbered-workspace-prev-next new file mode 100644 index 00000000..bd0704fa --- /dev/null +++ b/release-notes/bugfixes/2-numbered-workspace-prev-next @@ -0,0 +1 @@ +do not skip identically numbered workspaces when moving to next/prev diff --git a/release-notes/bugfixes/3-floating-from-tiling-from b/release-notes/bugfixes/3-floating-from-tiling-from deleted file mode 100644 index a29b5102..00000000 --- a/release-notes/bugfixes/3-floating-from-tiling-from +++ /dev/null @@ -1 +0,0 @@ -The floating_from and tiling_from criteria now also work in commands diff --git a/release-notes/bugfixes/3-numbered-workspace-order b/release-notes/bugfixes/3-numbered-workspace-order new file mode 100644 index 00000000..399f202d --- /dev/null +++ b/release-notes/bugfixes/3-numbered-workspace-order @@ -0,0 +1 @@ +make order of numbered workspace consistent with non-numbered diff --git a/release-notes/bugfixes/4-crash-focus-output b/release-notes/bugfixes/4-crash-focus-output new file mode 100644 index 00000000..fba6eb12 --- /dev/null +++ b/release-notes/bugfixes/4-crash-focus-output @@ -0,0 +1 @@ +fix crash with focus output and command criteria matching scratchpad window diff --git a/release-notes/bugfixes/5-reload-match-criteria b/release-notes/bugfixes/5-reload-match-criteria new file mode 100644 index 00000000..fd466d7b --- /dev/null +++ b/release-notes/bugfixes/5-reload-match-criteria @@ -0,0 +1 @@ +fix crash when reloading config with invalid criteria diff --git a/release-notes/bugfixes/6-for_window-reload b/release-notes/bugfixes/6-for_window-reload new file mode 100644 index 00000000..37933d30 --- /dev/null +++ b/release-notes/bugfixes/6-for_window-reload @@ -0,0 +1 @@ +fix crash when using for_window [...] reload diff --git a/release-notes/bugfixes/6337-paragraph-separators-cutting-off-titles b/release-notes/bugfixes/6337-paragraph-separators-cutting-off-titles new file mode 100644 index 00000000..11d3fac5 --- /dev/null +++ b/release-notes/bugfixes/6337-paragraph-separators-cutting-off-titles @@ -0,0 +1 @@ +fix paragraph separators cutting off titles diff --git a/release-notes/bugfixes/99-errorlog b/release-notes/bugfixes/99-errorlog new file mode 100644 index 00000000..c647e53e --- /dev/null +++ b/release-notes/bugfixes/99-errorlog @@ -0,0 +1 @@ +fix error log related crash diff --git a/release-notes/changes/1-bar-padding b/release-notes/changes/1-bar-padding deleted file mode 100644 index 212e9d1a..00000000 --- a/release-notes/changes/1-bar-padding +++ /dev/null @@ -1 +0,0 @@ -i3bar: bar { padding } config directive now implemented (supports bar { height } from i3-gaps) diff --git a/release-notes/changes/1-grab-pointer b/release-notes/changes/1-grab-pointer new file mode 100644 index 00000000..99166644 --- /dev/null +++ b/release-notes/changes/1-grab-pointer @@ -0,0 +1 @@ +do not grab pointer when executing bindings diff --git a/release-notes/changes/1-swap-drag b/release-notes/changes/1-swap-drag new file mode 100644 index 00000000..b3bab9db --- /dev/null +++ b/release-notes/changes/1-swap-drag @@ -0,0 +1 @@ +swap containers with the mouse diff --git a/release-notes/changes/2-migration-script b/release-notes/changes/2-migration-script new file mode 100644 index 00000000..3b54ab22 --- /dev/null +++ b/release-notes/changes/2-migration-script @@ -0,0 +1 @@ +disable automatic v3-to-v4 migration script diff --git a/release-notes/changes/2-net-wrm-state-maximized b/release-notes/changes/2-net-wrm-state-maximized new file mode 100644 index 00000000..1a3268c5 --- /dev/null +++ b/release-notes/changes/2-net-wrm-state-maximized @@ -0,0 +1 @@ +pass _NET_WM_STATE_MAXIMIZED_{HORZ, VERT} to tiled windows diff --git a/release-notes/changes/2-text-alpha-channel b/release-notes/changes/2-text-alpha-channel deleted file mode 100644 index 2ac7cb46..00000000 --- a/release-notes/changes/2-text-alpha-channel +++ /dev/null @@ -1 +0,0 @@ -colors now support an optional alpha value at the end (#rrggbbaa) diff --git a/release-notes/changes/3-deprecate-smart-borders b/release-notes/changes/3-deprecate-smart-borders new file mode 100644 index 00000000..1c00d014 --- /dev/null +++ b/release-notes/changes/3-deprecate-smart-borders @@ -0,0 +1 @@ +deprecate smart_borders in favour of hide_edge_borders smart/smart_no_gaps diff --git a/release-notes/changes/3-nonprimary b/release-notes/changes/3-nonprimary deleted file mode 100644 index f27bba76..00000000 --- a/release-notes/changes/3-nonprimary +++ /dev/null @@ -1 +0,0 @@ -Support nonprimary keyword for outputs diff --git a/release-notes/changes/3-redundant-containers b/release-notes/changes/3-redundant-containers new file mode 100644 index 00000000..cd6ab9f3 --- /dev/null +++ b/release-notes/changes/3-redundant-containers @@ -0,0 +1 @@ +avoid creating redundant containers when switching between tabbed/stacked and split layouts diff --git a/release-notes/changes/4-mode-in-binding-event b/release-notes/changes/4-mode-in-binding-event deleted file mode 100644 index e06014d3..00000000 --- a/release-notes/changes/4-mode-in-binding-event +++ /dev/null @@ -1 +0,0 @@ -add "mode" field in binding event diff --git a/release-notes/changes/4-shrink-i3bar-blocks b/release-notes/changes/4-shrink-i3bar-blocks new file mode 100644 index 00000000..81a490c0 --- /dev/null +++ b/release-notes/changes/4-shrink-i3bar-blocks @@ -0,0 +1 @@ +i3bar: use short-form text on a per-block basis diff --git a/release-notes/changes/5-_NET_WM_WINDOW_TYPE_NOTIFICATION b/release-notes/changes/5-_NET_WM_WINDOW_TYPE_NOTIFICATION new file mode 100644 index 00000000..8dd68707 --- /dev/null +++ b/release-notes/changes/5-_NET_WM_WINDOW_TYPE_NOTIFICATION @@ -0,0 +1 @@ +float notification windows by default diff --git a/release-notes/changes/5-set-_NET_FRAME_EXTENTS b/release-notes/changes/5-set-_NET_FRAME_EXTENTS new file mode 100644 index 00000000..0d40253a --- /dev/null +++ b/release-notes/changes/5-set-_NET_FRAME_EXTENTS @@ -0,0 +1 @@ +Set _NET_FRAME_EXTENTS according to the actual decoration size diff --git a/release-notes/changes/6-popup_during_fullscreen-all b/release-notes/changes/6-popup_during_fullscreen-all new file mode 100644 index 00000000..3c82685f --- /dev/null +++ b/release-notes/changes/6-popup_during_fullscreen-all @@ -0,0 +1 @@ +Add popup_during_fullscreen all option diff --git a/release-notes/changes/7-fullscreen-maximized b/release-notes/changes/7-fullscreen-maximized new file mode 100644 index 00000000..d77b9906 --- /dev/null +++ b/release-notes/changes/7-fullscreen-maximized @@ -0,0 +1 @@ +mark fullscreen windows as maximized diff --git a/release-notes/changes/9-multiple-_NET_WM_STATE b/release-notes/changes/9-multiple-_NET_WM_STATE new file mode 100644 index 00000000..1593056c --- /dev/null +++ b/release-notes/changes/9-multiple-_NET_WM_STATE @@ -0,0 +1 @@ +support multiple _NET_WM_STATE changes in one ClientMessage diff --git a/release.sh b/release.sh index 587b99ee..23bba533 100755 --- a/release.sh +++ b/release.sh @@ -1,10 +1,10 @@ -#!/bin/zsh +#!/usr/bin/env zsh # This script is used to prepare a new release of i3. set -eu -export RELEASE_VERSION="4.21" -export PREVIOUS_VERSION="4.20.1" +export RELEASE_VERSION="4.22" +export PREVIOUS_VERSION="4.21.1" export RELEASE_BRANCH="next" if [ ! -e "../i3.github.io" ] @@ -67,7 +67,7 @@ git commit -a -m "release i3 ${RELEASE_VERSION}" git tag "${RELEASE_VERSION}" -m "release i3 ${RELEASE_VERSION}" --sign --local-user=0x4AC8EE1D mkdir build -(cd build && meson .. && ninja dist) +(cd build && meson setup && ninja dist) cp build/meson-dist/i3-${RELEASE_VERSION}.tar.xz . echo "Differences in the release tarball file lists:" @@ -152,7 +152,7 @@ git checkout ${RELEASE_BRANCH} cd ${TMPDIR} git clone --quiet ${STARTDIR}/../i3.github.io -cd i3.github.io +cd i3.github.io/content mkdir docs/${PREVIOUS_VERSION} tar cf - '--exclude=[0-9]\.[0-9e]*' docs | tar xf - --strip-components=1 -C docs/${PREVIOUS_VERSION} @@ -163,10 +163,10 @@ cp ${TMPDIR}/i3/i3-${RELEASE_VERSION}.tar.xz* downloads/ 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,

Documentation for i3 v[^<]*

,

Documentation for i3 v${RELEASE_VERSION}

,g" docs/index.html -sed -i "s,\(span class=\"version\">\)[^<]*\(\),\1${RELEASE_VERSION}\2,g" index.html +sed -i "s,

Documentation for i3 v[^<]*

,

Documentation for i3 v${RELEASE_VERSION}

,g" docs/_index.html +sed -i "s,\(span class=\"version\">\)[^<]*\(\),\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,,\n \n ${RELEASE_VERSION}\n i3-${RELEASE_VERSION}.tar.xz\n $(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')\n signature\n $(date +'%Y-%m-%d')\n release notes\n \n,g" downloads/index.html +sed -i "s,,\n \n ${RELEASE_VERSION}\n i3-${RELEASE_VERSION}.tar.xz\n $(LC_ALL=en_US.UTF-8 ls -lh downloads/i3-${RELEASE_VERSION}.tar.xz | awk -F " " {'print $5'} | sed 's/K$/ KiB/g' | sed 's/M$/ MiB/g')\n signature\n $(date +'%Y-%m-%d')\n release notes\n \n,g" downloads/index.html git commit -a -m "add ${RELEASE_VERSION} release" diff --git a/share/xsessions/i3.desktop b/share/xsessions/i3.desktop index 11a64e26..df33160c 100644 --- a/share/xsessions/i3.desktop +++ b/share/xsessions/i3.desktop @@ -3,7 +3,7 @@ Name=i3 Comment=improved dynamic tiling window manager Exec=i3 TryExec=i3 -Type=Application +Type=XSession X-LightDM-DesktopName=i3 DesktopNames=i3 Keywords=tiling;wm;windowmanager;window;manager; diff --git a/src/assignments.c b/src/assignments.c index 8e2ee883..b7034ac0 100644 --- a/src/assignments.c +++ b/src/assignments.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * assignments.c: Assignments for specific windows (for_window). @@ -18,25 +18,29 @@ void run_assignments(i3Window *window) { DLOG("Checking if any assignments match this window\n"); bool needs_tree_render = false; + const Assignment *old_first_assignment = TAILQ_FIRST(&assignments); /* Check if any assignments match */ Assignment *current; TAILQ_FOREACH (current, &assignments, assignments) { - if (current->type != A_COMMAND || !match_matches_window(&(current->match), window)) + if (current->type != A_COMMAND || !match_matches_window(&(current->match), window)) { continue; + } bool skip = false; for (uint32_t c = 0; c < window->nr_assignments; c++) { - if (window->ran_assignments[c] != current) + if (window->ran_assignments[c] != current) { continue; + } DLOG("This assignment already ran for the given window, not executing it again.\n"); skip = true; break; } - if (skip) + if (skip) { continue; + } /* Store that we ran this assignment to not execute it again. We have * to do this before running the actual command to prevent infinite @@ -51,15 +55,25 @@ void run_assignments(i3Window *window) { CommandResult *result = parse_command(full_command, NULL, NULL); free(full_command); - if (result->needs_tree_render) + if (result->needs_tree_render) { needs_tree_render = true; + } command_result_free(result); + + /* Prevent crash: if the assigned command included a reload, the + * assignments array was re-initialized, which will lead to a SEGFAULT + * if we continue. + */ + if (old_first_assignment != TAILQ_FIRST(&assignments)) { + break; + } } /* If any of the commands required re-rendering, we will do that now. */ - if (needs_tree_render) + if (needs_tree_render) { tree_render(); + } } /* @@ -71,8 +85,9 @@ Assignment *assignment_for(i3Window *window, int type) { TAILQ_FOREACH (assignment, &assignments, assignments) { if ((type != A_ANY && (assignment->type & type) == 0) || - !match_matches_window(&(assignment->match), window)) + !match_matches_window(&(assignment->match), window)) { continue; + } DLOG("got a matching assignment\n"); return assignment; } diff --git a/src/bindings.c b/src/bindings.c index 4cb916fa..909fe2c3 100644 --- a/src/bindings.c +++ b/src/bindings.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * bindings.c: Functions for configuring, finding and, running bindings. @@ -86,18 +86,23 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch new_binding->command = sstrdup(command); new_binding->event_state_mask = event_state_from_str(modifiers); int group_bits_set = 0; - if ((new_binding->event_state_mask >> 16) & I3_XKB_GROUP_MASK_1) + if ((new_binding->event_state_mask >> 16) & I3_XKB_GROUP_MASK_1) { group_bits_set++; - if ((new_binding->event_state_mask >> 16) & I3_XKB_GROUP_MASK_2) + } + if ((new_binding->event_state_mask >> 16) & I3_XKB_GROUP_MASK_2) { group_bits_set++; - if ((new_binding->event_state_mask >> 16) & I3_XKB_GROUP_MASK_3) + } + if ((new_binding->event_state_mask >> 16) & I3_XKB_GROUP_MASK_3) { group_bits_set++; - if ((new_binding->event_state_mask >> 16) & I3_XKB_GROUP_MASK_4) + } + if ((new_binding->event_state_mask >> 16) & I3_XKB_GROUP_MASK_4) { group_bits_set++; - if (group_bits_set > 1) + } + if (group_bits_set > 1) { ELOG("Keybinding has more than one Group specified, but your X server is always in precisely one group. The keybinding can never trigger.\n"); + } - struct Mode *mode = mode_from_name(modename, pango_markup); + const struct Mode *mode = mode_from_name(modename, pango_markup); TAILQ_INSERT_TAIL(mode->bindings, new_binding, bindings); TAILQ_INIT(&(new_binding->keycodes_head)); @@ -107,8 +112,9 @@ Binding *configure_binding(const char *bindtype, const char *modifiers, const ch static bool binding_in_current_group(const Binding *bind) { /* If no bits are set, the binding should be installed in every group. */ - if ((bind->event_state_mask >> 16) == I3_XKB_GROUP_MASK_ANY) + if ((bind->event_state_mask >> 16) == I3_XKB_GROUP_MASK_ANY) { return true; + } switch (xkb_current_group) { case XCB_XKB_GROUP_1: return ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_1); @@ -126,9 +132,9 @@ static bool binding_in_current_group(const Binding *bind) { static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint32_t keycode) { /* Grab the key in all combinations */ -#define GRAB_KEY(modifier) \ - do { \ - xcb_grab_key(conn, 0, root, modifier, keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); \ +#define GRAB_KEY(modifier) \ + do { \ + xcb_grab_key(conn, 0, root, modifier, keycode, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); \ } while (0) const int mods = (bind->event_state_mask & 0xFFFF); DLOG("Binding %p Grabbing keycode %d with event state mask 0x%x (mods 0x%x)\n", @@ -149,11 +155,13 @@ static void grab_keycode_for_binding(xcb_connection_t *conn, Binding *bind, uint void grab_all_keys(xcb_connection_t *conn) { Binding *bind; TAILQ_FOREACH (bind, bindings, bindings) { - if (bind->input_type != B_KEYBOARD) + if (bind->input_type != B_KEYBOARD) { continue; + } - if (!binding_in_current_group(bind)) + if (!binding_in_current_group(bind)) { continue; + } /* The easy case: the user specified a keycode directly. */ if (bind->keycode > 0) { @@ -166,7 +174,7 @@ void grab_all_keys(xcb_connection_t *conn) { const int keycode = binding_keycode->keycode; const int mods = (binding_keycode->modifiers & 0xFFFF); DLOG("Binding %p Grabbing keycode %d with mods %d\n", bind, keycode, mods); - xcb_grab_key(conn, 0, root, mods, keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); + xcb_grab_key(conn, 0, root, mods, keycode, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); } } } @@ -182,8 +190,9 @@ void regrab_all_buttons(xcb_connection_t *conn) { Con *con; TAILQ_FOREACH (con, &all_cons, all_cons) { - if (con->window == NULL) + if (con->window == NULL) { continue; + } xcb_ungrab_button(conn, XCB_BUTTON_INDEX_ANY, con->window->id, XCB_BUTTON_MASK_ANY); xcb_grab_buttons(conn, con->window->id, buttons); @@ -206,10 +215,12 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas /* On a press event, we first reset all B_UPON_KEYRELEASE_IGNORE_MODS * bindings back to B_UPON_KEYRELEASE */ TAILQ_FOREACH (bind, bindings, bindings) { - if (bind->input_type != input_type) + if (bind->input_type != input_type) { continue; - if (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS) + } + if (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS) { bind->release = B_UPON_KEYRELEASE; + } } } @@ -232,7 +243,7 @@ static Binding *get_binding(i3_event_state_mask_t state_filtered, bool is_releas * keycode */ bool found_keycode = false; if (input_type == B_KEYBOARD && bind->symbol != NULL) { - xcb_keycode_t input_keycode = (xcb_keycode_t)input_code; + const xcb_keycode_t input_keycode = (xcb_keycode_t)input_code; struct Binding_Keycode *binding_keycode; TAILQ_FOREACH (binding_keycode, &(bind->keycodes_head), keycodes) { const uint32_t modifiers_mask = (binding_keycode->modifiers & 0x0000FFFF); @@ -386,18 +397,22 @@ static void add_keycode_if_matches(struct xkb_keymap *keymap, xkb_keycode_t key, /* Check if Shift was specified, and try resolving the symbol without * shift, so that “bindsym $mod+Shift+a nop” actually works. */ const xkb_layout_index_t layout = xkb_state_key_get_layout(resolving->xkb_state, key); - if (layout == XKB_LAYOUT_INVALID) + if (layout == XKB_LAYOUT_INVALID) { return; - if (xkb_state_key_get_level(resolving->xkb_state, key, layout) > 1) + } + if (xkb_state_key_get_level(resolving->xkb_state, key, layout) > 1) { return; + } /* Skip the Shift fallback for keypad keys, otherwise one cannot bind * KP_1 independent of KP_End. */ - if (sym >= XKB_KEY_KP_Space && sym <= XKB_KEY_KP_Equal) + if (sym >= XKB_KEY_KP_Space && sym <= XKB_KEY_KP_Equal) { return; + } numlock_state = resolving->xkb_state_numlock_no_shift; sym = xkb_state_key_get_one_sym(resolving->xkb_state_no_shift, key); - if (sym != resolving->keysym) + if (sym != resolving->keysym) { return; + } } Binding *bind = resolving->bind; @@ -460,12 +475,13 @@ void translate_keysyms(void) { } xkb_layout_index_t group = XCB_XKB_GROUP_1; - if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_2) + if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_2) { group = XCB_XKB_GROUP_2; - else if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_3) + } else if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_3) { group = XCB_XKB_GROUP_3; - else if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_4) + } else if ((bind->event_state_mask >> 16) & I3_XKB_GROUP_MASK_4) { group = XCB_XKB_GROUP_4; + } DLOG("Binding %p group = %d, event_state_mask = %d, &2 = %s, &3 = %s, &4 = %s\n", bind, @@ -583,14 +599,17 @@ void translate_keysyms(void) { /* check for duplicate bindings */ Binding *check; TAILQ_FOREACH (check, bindings, bindings) { - if (check == bind) + if (check == bind) { continue; - if (check->symbol != NULL) + } + if (check->symbol != NULL) { continue; + } if (check->keycode != binding_keycode->keycode || check->event_state_mask != binding_keycode->modifiers || - check->release != bind->release) + check->release != bind->release) { continue; + } has_errors = true; ELOG("Duplicate keybinding in config file:\n keysym = %s, keycode = %d, state_mask = 0x%x\n", bind->symbol, check->keycode, bind->event_state_mask); } @@ -623,21 +642,24 @@ void switch_mode(const char *new_mode) { DLOG("Switching to mode %s\n", new_mode); SLIST_FOREACH (mode, &modes, modes) { - if (strcmp(mode->name, new_mode) != 0) + if (strcmp(mode->name, new_mode) != 0) { continue; + } ungrab_all_keys(conn); bindings = mode->bindings; current_binding_mode = mode->name; translate_keysyms(); grab_all_keys(conn); + regrab_all_buttons(conn); /* Reset all B_UPON_KEYRELEASE_IGNORE_MODS bindings to avoid possibly * activating one of them. */ Binding *bind; TAILQ_FOREACH (bind, bindings, bindings) { - if (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS) + if (bind->release == B_UPON_KEYRELEASE_IGNORE_MODS) { bind->release = B_UPON_KEYRELEASE; + } } char *event_msg; @@ -658,11 +680,11 @@ static int reorder_binding_cmp(const void *a, const void *b) { Binding *second = *((Binding **)b); if (first->event_state_mask < second->event_state_mask) { return 1; - } else if (first->event_state_mask == second->event_state_mask) { - return 0; - } else { - return -1; } + if (first->event_state_mask == second->event_state_mask) { + return 0; + } + return -1; } static void reorder_bindings_of_mode(struct Mode *mode) { @@ -712,8 +734,9 @@ void reorder_bindings(void) { SLIST_FOREACH (mode, &modes, modes) { const bool current_mode = (mode->bindings == bindings); reorder_bindings_of_mode(mode); - if (current_mode) + if (current_mode) { bindings = mode->bindings; + } } } @@ -790,10 +813,12 @@ void check_for_duplicate_bindings(struct context *context) { static Binding *binding_copy(Binding *bind) { Binding *ret = smalloc(sizeof(Binding)); *ret = *bind; - if (bind->symbol != NULL) + if (bind->symbol != NULL) { ret->symbol = sstrdup(bind->symbol); - if (bind->command != NULL) + } + if (bind->command != NULL) { ret->command = sstrdup(bind->command); + } TAILQ_INIT(&(ret->keycodes_head)); struct Binding_Keycode *binding_keycode; TAILQ_FOREACH (binding_keycode, &(bind->keycodes_head), keycodes) { @@ -837,21 +862,23 @@ CommandResult *run_binding(Binding *bind, Con *con) { /* We need to copy the binding and command since “reload” may be part of * the command, and then the memory that bind points to may not contain the * same data anymore. */ - if (con == NULL) + if (con == NULL) { command = sstrdup(bind->command); - else + } else { 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. */ - const char *modename = current_binding_mode; + char *modename = sstrdup(current_binding_mode); CommandResult *result = parse_command(command, NULL, NULL); free(command); - if (result->needs_tree_render) + if (result->needs_tree_render) { tree_render(); + } if (result->parse_error) { char *pageraction; @@ -873,25 +900,24 @@ CommandResult *run_binding(Binding *bind, Con *con) { } ipc_send_binding_event("run", bind_cp, modename); + FREE(modename); binding_free(bind_cp); return result; } static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) { - xcb_intern_atom_reply_t *atom_reply; size_t content_max_words = 256; - atom_reply = xcb_intern_atom_reply( + xcb_intern_atom_reply_t *atom_reply = xcb_intern_atom_reply( conn, xcb_intern_atom(conn, 0, strlen("_XKB_RULES_NAMES"), "_XKB_RULES_NAMES"), NULL); - if (atom_reply == NULL) + if (atom_reply == NULL) { return -1; + } - xcb_get_property_cookie_t prop_cookie; - xcb_get_property_reply_t *prop_reply; - prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, - XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words); - prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); + xcb_get_property_cookie_t prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom, + XCB_GET_PROPERTY_TYPE_ANY, 0, content_max_words); + xcb_get_property_reply_t *prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL); if (prop_reply == NULL) { free(atom_reply); return -1; @@ -916,7 +942,7 @@ static int fill_rmlvo_from_root(struct xkb_rule_names *xkb_names) { return -1; } - const char *walk = (const char *)xcb_get_property_value(prop_reply); + const char *walk = xcb_get_property_value(prop_reply); int remaining = xcb_get_property_value_length(prop_reply); for (int i = 0; i < 5 && remaining > 0; i++) { const int len = strnlen(walk, remaining); @@ -1007,7 +1033,7 @@ bool load_keymap(void) { int *bindings_get_buttons_to_grab(void) { /* Let's make the reasonable assumption that there's no more than 25 * buttons. */ - int num_max = 25; + const int num_max = 25; int buffer[num_max]; int num = 0; @@ -1019,12 +1045,14 @@ int *bindings_get_buttons_to_grab(void) { Binding *bind; TAILQ_FOREACH (bind, bindings, bindings) { - if (num + 1 == num_max) + if (num + 1 == num_max) { break; + } /* We are only interested in whole window mouse bindings. */ - if (bind->input_type != B_MOUSE || !bind->whole_window) + if (bind->input_type != B_MOUSE || !bind->whole_window) { continue; + } long button; if (!parse_long(bind->symbol + (sizeof("button") - 1), &button, 10)) { @@ -1033,12 +1061,17 @@ int *bindings_get_buttons_to_grab(void) { } /* Avoid duplicates. */ + bool exists = false; for (int i = 0; i < num; i++) { - if (buffer[i] == button) - continue; + if (buffer[i] == button) { + exists = true; + break; + } } - buffer[num++] = button; + if (!exists) { + buffer[num++] = button; + } } buffer[num++] = 0; diff --git a/src/click.c b/src/click.c index a5e50632..f3388dbe 100644 --- a/src/click.c +++ b/src/click.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * click.c: Button press (mouse click) events. @@ -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); @@ -92,23 +95,27 @@ static bool floating_mod_on_tiled_client(Con *con, xcb_button_press_event_t *eve if (to_right < to_left && to_right < to_top && - to_right < to_bottom) + to_right < to_bottom) { return tiling_resize_for_border(con, BORDER_RIGHT, event, false); + } if (to_left < to_right && to_left < to_top && - to_left < to_bottom) + to_left < to_bottom) { return tiling_resize_for_border(con, BORDER_LEFT, event, false); + } if (to_top < to_right && to_top < to_left && - to_top < to_bottom) + to_top < to_bottom) { return tiling_resize_for_border(con, BORDER_TOP, event, false); + } if (to_bottom < to_right && to_bottom < to_left && - to_bottom < to_top) + to_bottom < to_top) { return tiling_resize_for_border(con, BORDER_BOTTOM, event, false); + } return false; } @@ -125,18 +132,25 @@ static bool tiling_resize(Con *con, xcb_button_press_event_t *event, const click DLOG("checks for right >= %d\n", con->window_rect.x + con->window_rect.width); if (dest == CLICK_DECORATION) { return tiling_resize_for_border(con, BORDER_TOP, event, use_threshold); + } else if (dest == CLICK_BORDER) { + if (event->event_y >= 0 && event->event_y <= (int32_t)bsr.y && + event->event_x >= (int32_t)bsr.x && event->event_x <= (int32_t)(con->rect.width + bsr.width)) { + return tiling_resize_for_border(con, BORDER_TOP, event, false); + } + } + if (event->event_x >= 0 && event->event_x <= (int32_t)bsr.x && + event->event_y >= (int32_t)bsr.y && event->event_y <= (int32_t)(con->rect.height + bsr.height)) { + return tiling_resize_for_border(con, BORDER_LEFT, event, false); } - if (event->event_x >= 0 && event->event_x <= (int32_t)bsr.x && - event->event_y >= (int32_t)bsr.y && event->event_y <= (int32_t)(con->rect.height + bsr.height)) - return tiling_resize_for_border(con, BORDER_LEFT, event, false); - if (event->event_x >= (int32_t)(con->window_rect.x + con->window_rect.width) && - event->event_y >= (int32_t)bsr.y && event->event_y <= (int32_t)(con->rect.height + bsr.height)) + event->event_y >= (int32_t)bsr.y && event->event_y <= (int32_t)(con->rect.height + bsr.height)) { return tiling_resize_for_border(con, BORDER_RIGHT, event, false); + } - if (event->event_y >= (int32_t)(con->window_rect.y + con->window_rect.height)) + if (event->event_y >= (int32_t)(con->window_rect.y + con->window_rect.height)) { return tiling_resize_for_border(con, BORDER_BOTTOM, event, false); + } return false; } @@ -152,7 +166,10 @@ static void allow_replay_pointer(xcb_timestamp_t time) { * functions for resizing/dragging. * */ -static void route_click(Con *con, xcb_button_press_event_t *event, const bool mod_pressed, const click_destination_t dest) { +static void route_click(Con *con, xcb_button_press_event_t *event, const click_destination_t dest) { + const uint32_t mod = (config.floating_modifier & 0xFFFF); + const bool mod_pressed = (mod != 0 && (event->state & mod) == mod); + DLOG("--> click properties: mod = %d, destination = %d\n", mod_pressed, dest); DLOG("--> OUTCOME = %p\n", con); DLOG("type = %d, name = %s\n", con->type, con->name); @@ -215,6 +232,9 @@ static void route_click(Con *con, xcb_button_press_event_t *event, const bool mo /* 1: see if the user scrolled on the decoration of a stacked/tabbed con */ if (in_stacked && dest == CLICK_DECORATION && is_scroll) { DLOG("Scrolling on a window decoration\n"); + /* Correctly move workspace focus first, see: #5472 */ + workspace_show(ws); + /* Use the focused child of the tabbed / stacked container, not the * container the user scrolled on. */ Con *current = TAILQ_FIRST(&(con->parent->focus_head)); @@ -358,11 +378,8 @@ void handle_button_press(xcb_button_press_event_t *event) { last_timestamp = event->time; - const uint32_t mod = (config.floating_modifier & 0xFFFF); - const bool mod_pressed = (mod != 0 && (event->state & mod) == mod); - DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail); if ((con = con_by_window_id(event->event))) { - route_click(con, event, mod_pressed, CLICK_INSIDE); + route_click(con, event, CLICK_INSIDE); return; } @@ -381,13 +398,14 @@ void handle_button_press(xcb_button_press_event_t *event) { /* If the root window is clicked, find the relevant output from the * click coordinates and focus the output's active workspace. */ if (event->event == root && event->response_type == XCB_BUTTON_PRESS) { - Con *output, *ws; + Con *output; TAILQ_FOREACH (output, &(croot->nodes_head), nodes) { if (con_is_internal(output) || - !rect_contains(output->rect, event->event_x, event->event_y)) + !rect_contains(output->rect, event->event_x, event->event_y)) { continue; + } - ws = TAILQ_FIRST(&(output_get_content(output)->focus_head)); + Con *ws = TAILQ_FIRST(&(output_get_content(output)->focus_head)); if (ws != con_get_workspace(focused)) { workspace_show(ws); tree_render(); @@ -406,25 +424,26 @@ void handle_button_press(xcb_button_press_event_t *event) { /* Check if the click was on the decoration of a child */ if (con->window != NULL) { if (rect_contains(con->deco_rect, event->event_x, event->event_y)) { - route_click(con, event, mod_pressed, CLICK_DECORATION); + route_click(con, event, 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)) + if (!rect_contains(child->deco_rect, event->event_x, event->event_y)) { continue; + } - route_click(child, event, mod_pressed, CLICK_DECORATION); + route_click(child, event, CLICK_DECORATION); return; } } if (event->child != XCB_NONE) { DLOG("event->child not XCB_NONE, so this is an event which originated from a click into the application, but the application did not handle it.\n"); - route_click(con, event, mod_pressed, CLICK_INSIDE); + route_click(con, event, CLICK_INSIDE); return; } - route_click(con, event, mod_pressed, CLICK_BORDER); + route_click(con, event, CLICK_BORDER); } diff --git a/src/commands.c b/src/commands.c index 8a87877c..b421abbd 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * commands.c: all command functions (see commands_parser.c) @@ -16,7 +16,7 @@ // Macros to make the YAJL API a bit easier to use. #define y(x, ...) (cmd_output->json_gen != NULL ? yajl_gen_##x(cmd_output->json_gen, ##__VA_ARGS__) : 0) -#define ystr(str) (cmd_output->json_gen != NULL ? yajl_gen_string(cmd_output->json_gen, (unsigned char *)str, strlen(str)) : 0) +#define ystr(str) (cmd_output->json_gen != NULL ? yajl_gen_string(cmd_output->json_gen, (unsigned char *)(str), strlen((str))) : 0) #define ysuccess(success) \ do { \ if (cmd_output->json_gen != NULL) { \ @@ -61,15 +61,15 @@ HANDLE_INVALID_MATCH; \ \ if (match_is_empty(current_match)) { \ - while (!TAILQ_EMPTY(&owindows)) { \ - owindow *ow = TAILQ_FIRST(&owindows); \ - TAILQ_REMOVE(&owindows, ow, owindows); \ + while (!TAILQ_EMPTY(&OWINDOWS)) { \ + owindow *ow = TAILQ_FIRST(&OWINDOWS); \ + TAILQ_REMOVE(&OWINDOWS, ow, owindows); \ free(ow); \ } \ owindow *ow = smalloc(sizeof(owindow)); \ ow->con = focused; \ - TAILQ_INIT(&owindows); \ - TAILQ_INSERT_TAIL(&owindows, ow, owindows); \ + TAILQ_INIT(&OWINDOWS); \ + TAILQ_INSERT_TAIL(&OWINDOWS, ow, owindows); \ } \ } while (0) @@ -81,11 +81,12 @@ * */ static bool maybe_back_and_forth(struct CommandResultIR *cmd_output, const char *name) { - Con *ws = con_get_workspace(focused); + const Con *ws = con_get_workspace(focused); /* If we switched to a different workspace, do nothing */ - if (strcmp(ws->name, name) != 0) + if (strcmp(ws->name, name) != 0) { return false; + } DLOG("This workspace is already focused.\n"); if (config.workspace_auto_back_and_forth) { @@ -100,15 +101,13 @@ static bool maybe_back_and_forth(struct CommandResultIR *cmd_output, const char * forth is enabled, in which case the back_and_forth workspace is returned. */ static Con *maybe_auto_back_and_forth_workspace(Con *workspace) { - Con *current, *baf; - - if (!config.workspace_auto_back_and_forth) + if (!config.workspace_auto_back_and_forth) { return workspace; + } - current = con_get_workspace(focused); - + const Con *current = con_get_workspace(focused); if (current == workspace) { - baf = workspace_back_and_forth_get(); + Con *baf = workspace_back_and_forth_get(); if (baf != NULL) { DLOG("Substituting workspace with back_and_forth, as it is focused.\n"); return baf; @@ -122,19 +121,8 @@ static Con *maybe_auto_back_and_forth_workspace(Con *workspace) { * Criteria functions. ******************************************************************************/ -/* - * Helper data structure for an operation window (window on which the operation - * will be performed). Used to build the TAILQ owindows. - * - */ -typedef struct owindow { - Con *con; - TAILQ_ENTRY(owindow) owindows; -} owindow; - -typedef TAILQ_HEAD(owindows_head, owindow) owindows_head; - -static owindows_head owindows; +/* Macro to access owindows from the command output context */ +#define OWINDOWS (cmd_output->ctx->owindows) /* * Initializes the specified 'Match' data structure and the initial state of @@ -142,24 +130,9 @@ static owindows_head owindows; * */ void cmd_criteria_init(I3_CMD) { - Con *con; - owindow *ow; - DLOG("Initializing criteria, current_match = %p\n", current_match); match_free(current_match); match_init(current_match); - while (!TAILQ_EMPTY(&owindows)) { - ow = TAILQ_FIRST(&owindows); - TAILQ_REMOVE(&owindows, ow, owindows); - free(ow); - } - TAILQ_INIT(&owindows); - /* copy all_cons */ - TAILQ_FOREACH (con, &all_cons, all_cons) { - ow = smalloc(sizeof(owindow)); - ow->con = con; - TAILQ_INSERT_TAIL(&owindows, ow, owindows); - } } /* @@ -168,21 +141,20 @@ void cmd_criteria_init(I3_CMD) { * */ void cmd_criteria_match_windows(I3_CMD) { - owindow *next, *current; - DLOG("match specification finished, matching...\n"); - /* copy the old list head to iterate through it and start with a fresh - * list which will contain only matching windows */ - struct owindows_head old = owindows; - TAILQ_INIT(&owindows); - for (next = TAILQ_FIRST(&old); next != TAILQ_END(&old);) { - /* make a copy of the next pointer and advance the pointer to the - * next element as we are going to invalidate the element’s - * next/prev pointers by calling TAILQ_INSERT_TAIL later */ - current = next; - next = TAILQ_NEXT(next, owindows); - DLOG("checking if con %p / %s matches\n", current->con, current->con->name); + /* Clear old queue */ + while (!TAILQ_EMPTY(&OWINDOWS)) { + owindow *ow = TAILQ_FIRST(&OWINDOWS); + TAILQ_REMOVE(&OWINDOWS, ow, owindows); + free(ow); + } + TAILQ_INIT(&OWINDOWS); + + /* Go through all cons and find matches */ + Con *con; + TAILQ_FOREACH (con, &all_cons, all_cons) { + DLOG("checking if con %p / %s matches\n", con, con->name); /* We use this flag to prevent matching on window-less containers if * only window-specific criteria were specified. */ @@ -191,23 +163,23 @@ void cmd_criteria_match_windows(I3_CMD) { if (current_match->con_id != NULL) { accept_match = true; - if (current_match->con_id == current->con) { + if (current_match->con_id == con) { DLOG("con_id matched.\n"); } else { DLOG("con_id does not match.\n"); - FREE(current); continue; } } - if (current_match->mark != NULL && !TAILQ_EMPTY(&(current->con->marks_head))) { + if (current_match->mark != NULL && !TAILQ_EMPTY(&(con->marks_head))) { accept_match = true; bool matched_by_mark = false; mark_t *mark; - TAILQ_FOREACH (mark, &(current->con->marks_head), marks) { - if (!regex_matches(current_match->mark, mark->name)) + TAILQ_FOREACH (mark, &(con->marks_head), marks) { + if (!regex_matches(current_match->mark, mark->name)) { continue; + } DLOG("match by mark\n"); matched_by_mark = true; @@ -216,33 +188,27 @@ void cmd_criteria_match_windows(I3_CMD) { if (!matched_by_mark) { DLOG("mark does not match.\n"); - FREE(current); continue; } } - if (current->con->window != NULL) { - if (match_matches_window(current_match, current->con->window)) { + if (con->window != NULL) { + if (match_matches_window(current_match, con->window)) { DLOG("matches window!\n"); accept_match = true; } else { DLOG("doesn't match\n"); - FREE(current); continue; } } if (accept_match) { - TAILQ_INSERT_TAIL(&owindows, current, owindows); - } else { - FREE(current); - continue; + DLOG("matching: %p / %s\n", con, con->name); + owindow *ow = smalloc(sizeof(owindow)); + ow->con = con; + TAILQ_INSERT_TAIL(&OWINDOWS, ow, owindows); } } - - TAILQ_FOREACH (current, &owindows, owindows) { - DLOG("matching: %p / %s\n", current->con, current->con->name); - } } /* @@ -254,9 +220,9 @@ void cmd_criteria_add(I3_CMD, const char *ctype, const char *cvalue) { match_parse_property(current_match, ctype, cvalue); } -static void move_matches_to_workspace(Con *ws) { +static void move_matches_to_workspace(const struct owindows_head *owindows, Con *ws) { owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, owindows, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); con_move_to_workspace(current->con, ws, true, false, false); } @@ -265,17 +231,17 @@ static void move_matches_to_workspace(Con *ws) { #define CHECK_MOVE_CON_TO_WORKSPACE \ do { \ HANDLE_EMPTY_MATCH; \ - if (TAILQ_EMPTY(&owindows)) { \ + if (TAILQ_EMPTY(&OWINDOWS)) { \ yerror("Nothing to move: specified criteria don't match any window"); \ return; \ } else { \ bool found = false; \ - owindow *current = TAILQ_FIRST(&owindows); \ + owindow *current = TAILQ_FIRST(&OWINDOWS); \ while (current) { \ owindow *next = TAILQ_NEXT(current, owindows); \ \ if (current->con->type == CT_WORKSPACE && !con_has_children(current->con)) { \ - TAILQ_REMOVE(&owindows, current, owindows); \ + TAILQ_REMOVE(&OWINDOWS, current, owindows); \ } else { \ found = true; \ } \ @@ -301,22 +267,22 @@ void cmd_move_con_to_workspace(I3_CMD, const char *which) { /* get the workspace */ Con *ws; - if (strcmp(which, "next") == 0) + if (strcmp(which, "next") == 0) { ws = workspace_next(); - else if (strcmp(which, "prev") == 0) + } else if (strcmp(which, "prev") == 0) { ws = workspace_prev(); - else if (strcmp(which, "next_on_output") == 0) + } else if (strcmp(which, "next_on_output") == 0) { ws = workspace_next_on_output(); - else if (strcmp(which, "prev_on_output") == 0) + } else if (strcmp(which, "prev_on_output") == 0) { ws = workspace_prev_on_output(); - else if (strcmp(which, "current") == 0) + } else if (strcmp(which, "current") == 0) { ws = con_get_workspace(focused); - else { + } else { yerror("BUG: called with which=%s", which); return; } - move_matches_to_workspace(ws); + move_matches_to_workspace(&OWINDOWS, ws); cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply @@ -336,7 +302,7 @@ void cmd_move_con_to_workspace_back_and_forth(I3_CMD) { HANDLE_EMPTY_MATCH; - move_matches_to_workspace(ws); + move_matches_to_workspace(&OWINDOWS, ws); cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply @@ -363,7 +329,7 @@ void cmd_move_con_to_workspace_name(I3_CMD, const char *name, const char *no_aut ws = maybe_auto_back_and_forth_workspace(ws); } - move_matches_to_workspace(ws); + move_matches_to_workspace(&OWINDOWS, ws); cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply @@ -379,7 +345,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_ LOG("should move window to workspace %s\n", which); - long parsed_num = ws_name_to_number(which); + const long parsed_num = ws_name_to_number(which); if (parsed_num == -1) { LOG("Could not parse initial part of \"%s\" as a number.\n", which); yerror("Could not parse number \"%s\"", which); @@ -395,7 +361,7 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_ ws = maybe_auto_back_and_forth_workspace(ws); } - move_matches_to_workspace(ws); + move_matches_to_workspace(&OWINDOWS, ws); cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply @@ -409,21 +375,23 @@ void cmd_move_con_to_workspace_number(I3_CMD, const char *which, const char *no_ static direction_t parse_direction(const char *str) { if (strcmp(str, "left") == 0) { return D_LEFT; - } else if (strcmp(str, "right") == 0) { - return D_RIGHT; - } else if (strcmp(str, "up") == 0) { - return D_UP; - } else if (strcmp(str, "down") == 0) { - return D_DOWN; - } else { - ELOG("Invalid direction. This is a parser bug.\n"); - assert(false); } + if (strcmp(str, "right") == 0) { + return D_RIGHT; + } + if (strcmp(str, "up") == 0) { + return D_UP; + } + if (strcmp(str, "down") == 0) { + return D_DOWN; + } + ELOG("Invalid direction. This is a parser bug.\n"); + assert(false); } static void cmd_resize_floating(I3_CMD, const char *direction_str, Con *floating_con, int px) { - Rect old_rect = floating_con->rect; - Con *focused_con = con_descend_focused(floating_con); + const Rect old_rect = floating_con->rect; + const Con *focused_con = con_descend_focused(floating_con); direction_t direction; if (strcmp(direction_str, "height") == 0) { @@ -433,7 +401,7 @@ static void cmd_resize_floating(I3_CMD, const char *direction_str, Con *floating } else { direction = parse_direction(direction_str); } - orientation_t orientation = orientation_from_direction(direction); + const orientation_t orientation = orientation_from_direction(direction); /* ensure that resize will take place even if pixel increment is smaller than * height increment or width increment. @@ -480,12 +448,12 @@ static void cmd_resize_floating(I3_CMD, const char *direction_str, Con *floating } } -static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *direction, int px, int ppt) { +static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *direction, int px, const int ppt) { Con *second = NULL; Con *first = current; - direction_t search_direction = parse_direction(direction); + const direction_t search_direction = parse_direction(direction); - bool res = resize_find_tiling_participants(&first, &second, search_direction, false); + const bool res = resize_find_tiling_participants(&first, &second, search_direction, false); if (!res) { yerror("No second container found in this direction."); return false; @@ -499,34 +467,34 @@ static bool cmd_resize_tiling_direction(I3_CMD, Con *current, const char *direct return resize_neighboring_cons(first, second, px, ppt); } -static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *direction, int px, double ppt) { +static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *direction, const int px, double ppt) { LOG("width/height resize\n"); /* get the appropriate current container (skip stacked/tabbed cons) */ Con *dummy = NULL; - direction_t search_direction = (strcmp(direction, "width") == 0 ? D_LEFT : D_DOWN); - bool search_result = resize_find_tiling_participants(¤t, &dummy, search_direction, true); + const direction_t search_direction = (strcmp(direction, "width") == 0 ? D_LEFT : D_DOWN); + const bool search_result = resize_find_tiling_participants(¤t, &dummy, search_direction, true); if (search_result == false) { yerror("Failed to find appropriate tiling containers for resize operation"); return false; } /* get the default percentage */ - int children = con_num_children(current->parent); + const int children = con_num_children(current->parent); LOG("ins. %d children\n", children); - double percentage = 1.0 / children; + const double percentage = 1.0 / children; LOG("default percentage = %f\n", percentage); /* Ensure all the other children have a percentage set. */ Con *child; TAILQ_FOREACH (child, &(current->parent->nodes_head), nodes) { LOG("child->percent = %f (child %p)\n", child->percent, child); - if (child->percent == 0.0) + if (child->percent == 0.0) { child->percent = percentage; + } } double new_current_percent; - double subtract_percent; if (ppt != 0.0) { new_current_percent = current->percent + ppt; } else { @@ -534,7 +502,7 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *dir ppt = (double)px / (double)con_rect_size_in_orientation(current->parent); new_current_percent = current->percent + ppt; } - subtract_percent = ppt / (children - 1); + const double subtract_percent = ppt / (children - 1); if (ppt < 0.0 && new_current_percent < percent_for_1px(current)) { yerror("Not resizing, container would end with less than 1px"); return false; @@ -559,8 +527,9 @@ static bool cmd_resize_tiling_width_height(I3_CMD, Con *current, const char *dir LOG("current->percent after = %f\n", current->percent); TAILQ_FOREACH (child, &(current->parent->nodes_head), nodes) { - if (child == current) + if (child == current) { continue; + } child->percent -= subtract_percent; LOG("child->percent after (%p) = %f\n", child, child->percent); } @@ -582,7 +551,7 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px, HANDLE_EMPTY_MATCH; owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { /* Don't handle dock windows (issue #1201) */ if (current->con->window && current->con->window->dock) { DLOG("This is a dock window. Not resizing (con = %p)\n)", current->con); @@ -618,7 +587,7 @@ void cmd_resize(I3_CMD, const char *way, const char *direction, long resize_px, ysuccess(true); } -static bool resize_set_tiling(I3_CMD, Con *target, orientation_t resize_orientation, bool is_ppt, long target_size) { +static bool resize_set_tiling(I3_CMD, Con *target, const orientation_t resize_orientation, const bool is_ppt, const long target_size) { direction_t search_direction; char *mode; if (resize_orientation == HORIZ) { @@ -662,10 +631,10 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c owindow *current; bool success = true; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { Con *floating_con; if ((floating_con = con_inside_floating(current->con))) { - Con *output = con_get_output(floating_con); + const Con *output = con_get_output(floating_con); if (cwidth == 0) { cwidth = floating_con->rect.width; } else if (mode_width && strcmp(mode_width, "ppt") == 0) { @@ -684,12 +653,12 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c } if (cwidth > 0) { - bool is_ppt = mode_width && strcmp(mode_width, "ppt") == 0; + const bool is_ppt = mode_width && strcmp(mode_width, "ppt") == 0; success &= resize_set_tiling(current_match, cmd_output, current->con, HORIZ, is_ppt, cwidth); } if (cheight > 0) { - bool is_ppt = mode_height && strcmp(mode_height, "ppt") == 0; + const bool is_ppt = mode_height && strcmp(mode_height, "ppt") == 0; success &= resize_set_tiling(current_match, cmd_output, current->con, VERT, is_ppt, cheight); } @@ -700,7 +669,7 @@ void cmd_resize_set(I3_CMD, long cwidth, const char *mode_width, long cheight, c ysuccess(success); } -static int border_width_from_style(border_style_t border_style, long border_width, Con *con) { +static int border_width_from_style(const border_style_t border_style, const long border_width, Con *con) { if (border_style == BS_NONE) { return 0; } @@ -724,13 +693,13 @@ static int border_width_from_style(border_style_t border_style, long border_widt * Implementation of 'border normal|pixel []', 'border none|1pixel|toggle'. * */ -void cmd_border(I3_CMD, const char *border_style_str, long border_width) { +void cmd_border(I3_CMD, const char *border_style_str, const long border_width) { DLOG("border style should be changed to %s with border width %ld\n", border_style_str, border_width); owindow *current; HANDLE_EMPTY_MATCH; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); border_style_t border_style; @@ -792,7 +761,7 @@ void cmd_append_layout(I3_CMD, const char *cpath) { goto out; } - json_content_t content = json_determine_content(buf, len); + const json_content_t content = json_determine_content(buf, len); LOG("JSON content = %d\n", content); if (content == JSON_CONTENT_UNKNOWN) { ELOG("Could not determine the contents of \"%s\", not loading.\n", path); @@ -808,14 +777,15 @@ void cmd_append_layout(I3_CMD, const char *cpath) { * container must not have any children (by definition). * Note that we explicitly check for workspaces, since they are okay for * this purpose, but con_accepts_window() returns false for workspaces. */ - while (parent->type != CT_WORKSPACE && !con_accepts_window(parent)) + while (parent->type != CT_WORKSPACE && !con_accepts_window(parent)) { parent = parent->parent; + } } DLOG("Appending to parent=%p instead of focused=%p\n", parent, focused); char *errormsg = NULL; tree_append_json(parent, buf, len, &errormsg); if (errormsg != NULL) { - yerror(errormsg); + yerror("%s", errormsg); free(errormsg); /* Note that we continue executing since tree_append_json() has * side-effects — user-provided layouts can be partly valid, partly @@ -835,8 +805,9 @@ void cmd_append_layout(I3_CMD, const char *cpath) { restore_open_placeholder_windows(parent); - if (content == JSON_CONTENT_WORKSPACE) + if (content == JSON_CONTENT_WORKSPACE) { ipc_send_workspace_event("restored", parent, NULL); + } cmd_output->needs_tree_render = true; out: @@ -844,6 +815,13 @@ out: free(buf); } +static void disable_global_fullscreen(void) { + Con *fs = con_get_fullscreen_con(croot, CF_GLOBAL); + if (fs) { + con_disable_fullscreen(fs); + } +} + /* * Implementation of 'workspace next|prev|next_on_output|prev_on_output'. * @@ -853,20 +831,17 @@ void cmd_workspace(I3_CMD, const char *which) { DLOG("which=%s\n", which); - if (con_get_fullscreen_con(croot, CF_GLOBAL)) { - yerror("Cannot switch workspace while in global fullscreen"); - return; - } + disable_global_fullscreen(); - if (strcmp(which, "next") == 0) + if (strcmp(which, "next") == 0) { ws = workspace_next(); - else if (strcmp(which, "prev") == 0) + } else if (strcmp(which, "prev") == 0) { ws = workspace_prev(); - else if (strcmp(which, "next_on_output") == 0) + } else if (strcmp(which, "next_on_output") == 0) { ws = workspace_next_on_output(); - else if (strcmp(which, "prev_on_output") == 0) + } else if (strcmp(which, "prev_on_output") == 0) { ws = workspace_prev_on_output(); - else { + } else { yerror("BUG: called with which=%s", which); return; } @@ -882,15 +857,12 @@ void cmd_workspace(I3_CMD, const char *which) { * Implementation of 'workspace [--no-auto-back-and-forth] number ' * */ -void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_and_forth) { - const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL); +void cmd_workspace_number(I3_CMD, const char *which, const char *no_auto_back_and_forth_str) { + const bool no_auto_back_and_forth = (no_auto_back_and_forth_str != NULL); - if (con_get_fullscreen_con(croot, CF_GLOBAL)) { - yerror("Cannot switch workspace while in global fullscreen"); - return; - } + disable_global_fullscreen(); - long parsed_num = ws_name_to_number(which); + const long parsed_num = ws_name_to_number(which); if (parsed_num == -1) { yerror("Could not parse initial part of \"%s\" as a number.", which); return; @@ -920,10 +892,7 @@ void cmd_workspace_number(I3_CMD, const char *which, const char *_no_auto_back_a * */ void cmd_workspace_back_and_forth(I3_CMD) { - if (con_get_fullscreen_con(croot, CF_GLOBAL)) { - yerror("Cannot switch workspace while in global fullscreen"); - return; - } + disable_global_fullscreen(); workspace_back_and_forth(); @@ -936,18 +905,15 @@ void cmd_workspace_back_and_forth(I3_CMD) { * Implementation of 'workspace [--no-auto-back-and-forth] ' * */ -void cmd_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_forth) { - const bool no_auto_back_and_forth = (_no_auto_back_and_forth != NULL); +void cmd_workspace_name(I3_CMD, const char *name, const char *no_auto_back_and_forth_str) { + const bool no_auto_back_and_forth = (no_auto_back_and_forth_str != NULL); if (strncasecmp(name, "__", strlen("__")) == 0) { yerror("You cannot switch to the i3-internal workspaces (\"%s\").", name); return; } - if (con_get_fullscreen_con(croot, CF_GLOBAL)) { - yerror("Cannot switch workspace while in global fullscreen"); - return; - } + disable_global_fullscreen(); DLOG("should switch to workspace %s\n", name); if (!no_auto_back_and_forth && maybe_back_and_forth(cmd_output, name)) { @@ -968,21 +934,21 @@ void cmd_workspace_name(I3_CMD, const char *name, const char *_no_auto_back_and_ void cmd_mark(I3_CMD, const char *mark, const char *mode, const char *toggle) { HANDLE_EMPTY_MATCH; - owindow *current = TAILQ_FIRST(&owindows); + const owindow *current = TAILQ_FIRST(&OWINDOWS); if (current == NULL) { yerror("Given criteria don't match a window"); return; } /* Marks must be unique, i.e., no two windows must have the same mark. */ - if (current != TAILQ_LAST(&owindows, owindows_head)) { + if (current != TAILQ_LAST(&OWINDOWS, owindows_head)) { yerror("A mark must not be put onto more than one window"); return; } DLOG("matching: %p / %s\n", current->con, current->con->name); - mark_mode_t mark_mode = (mode == NULL || strcmp(mode, "--replace") == 0) ? MM_REPLACE : MM_ADD; + const mark_mode_t mark_mode = (mode == NULL || strcmp(mode, "--replace") == 0) ? MM_REPLACE : MM_ADD; if (toggle != NULL) { con_mark_toggle(current->con, mark, mark_mode); } else { @@ -1003,7 +969,7 @@ void cmd_unmark(I3_CMD, const char *mark) { con_unmark(NULL, mark); } else { owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { con_unmark(current->con, mark); } } @@ -1052,10 +1018,9 @@ static void user_output_names_add(user_output_names_head *list, const char *name user_output_name *co = scalloc(sizeof(user_output_name), 1); co->name = sstrdup(name); TAILQ_INSERT_TAIL(list, co, user_output_names); - return; } -static Output *user_output_names_find_next(user_output_names_head *names, Output *current_output) { +static Output *user_output_names_find_next(const user_output_names_head *names, Output *current_output) { Output *target_output = NULL; user_output_name *uo; TAILQ_FOREACH (uo, names, user_output_names) { @@ -1091,9 +1056,8 @@ static Output *user_output_names_find_next(user_output_names_head *names, Output } static void user_output_names_free(user_output_names_head *names) { - user_output_name *uo; while (!TAILQ_EMPTY(names)) { - uo = TAILQ_FIRST(names); + user_output_name *uo = TAILQ_FIRST(names); free(uo->name); TAILQ_REMOVE(names, uo, user_output_names); free(uo); @@ -1104,7 +1068,7 @@ static void user_output_names_free(user_output_names_head *names) { * Implementation of 'move [window|container|workspace] [to] output '. * */ -void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) { +void cmd_move_con_to_output(I3_CMD, const char *name, const bool move_workspace) { /* Initialize a data structure that is used to save multiple user-specified * output names since this function is called multiple types for each * command call. */ @@ -1124,7 +1088,7 @@ void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace) { bool success = false; owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { Con *ws = con_get_workspace(current->con); if (con_is_internal(ws)) { continue; @@ -1162,7 +1126,7 @@ void cmd_move_con_to_mark(I3_CMD, const char *mark) { bool result = true; owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { DLOG("moving matched window %p / %s to mark \"%s\"\n", current->con, current->con->name, mark); result &= con_move_to_mark(current->con, mark); } @@ -1176,13 +1140,13 @@ void cmd_move_con_to_mark(I3_CMD, const char *mark) { * */ void cmd_floating(I3_CMD, const char *floating_mode) { - owindow *current; - + assert(floating_mode != NULL); /* Compiler complains */ DLOG("floating_mode=%s\n", floating_mode); HANDLE_EMPTY_MATCH; - TAILQ_FOREACH (current, &owindows, owindows) { + owindow *current; + TAILQ_FOREACH (current, &OWINDOWS, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); if (strcmp(floating_mode, "toggle") == 0) { DLOG("should toggle mode\n"); @@ -1195,6 +1159,7 @@ void cmd_floating(I3_CMD, const char *floating_mode) { floating_disable(current->con); } } + run_assignments(current->con->window); } cmd_output->needs_tree_render = true; @@ -1211,7 +1176,7 @@ void cmd_split(I3_CMD, const char *direction) { owindow *current; LOG("splitting in direction %c\n", direction[0]); - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { if (con_is_docked(current->con)) { ELOG("Cannot split a docked container, skipping.\n"); continue; @@ -1246,17 +1211,18 @@ void cmd_split(I3_CMD, const char *direction) { * */ void cmd_kill(I3_CMD, const char *kill_mode_str) { - if (kill_mode_str == NULL) + if (kill_mode_str == NULL) { kill_mode_str = "window"; + } DLOG("kill_mode=%s\n", kill_mode_str); int kill_mode; - if (strcmp(kill_mode_str, "window") == 0) + if (strcmp(kill_mode_str, "window") == 0) { kill_mode = KILL_WINDOW; - else if (strcmp(kill_mode_str, "client") == 0) + } else if (strcmp(kill_mode_str, "client") == 0) { kill_mode = KILL_CLIENT; - else { + } else { yerror("BUG: called with kill_mode=%s", kill_mode_str); return; } @@ -1264,7 +1230,7 @@ void cmd_kill(I3_CMD, const char *kill_mode_str) { HANDLE_EMPTY_MATCH; owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { con_close(current->con, kill_mode); } @@ -1278,13 +1244,13 @@ void cmd_kill(I3_CMD, const char *kill_mode_str) { * */ void cmd_exec(I3_CMD, const char *nosn, const char *command) { - bool no_startup_id = (nosn != NULL); + const bool no_startup_id = (nosn != NULL); HANDLE_EMPTY_MATCH; int count = 0; owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { count++; } @@ -1294,7 +1260,7 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command) { count); } - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { DLOG("should execute %s, no_startup_id = %d\n", command, no_startup_id); start_application(command, no_startup_id); } @@ -1306,7 +1272,7 @@ void cmd_exec(I3_CMD, const char *nosn, const char *command) { do { \ int count = 0; \ owindow *current; \ - TAILQ_FOREACH (current, &owindows, owindows) { \ + TAILQ_FOREACH (current, &OWINDOWS, owindows) { \ count++; \ } \ \ @@ -1325,7 +1291,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) { @@ -1338,13 +1304,13 @@ void cmd_focus_direction(I3_CMD, const char *direction_str) { } owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { Con *ws = con_get_workspace(current->con); if (!ws || con_is_internal(ws)) { continue; } if (auto_direction) { - orientation_t o = con_orientation(current->con->parent); + const orientation_t o = con_orientation(current->con->parent); direction = direction_from_orientation_position(o, position); } tree_next(current->con, direction); @@ -1365,7 +1331,7 @@ void cmd_focus_sibling(I3_CMD, const char *direction_str) { const position_t direction = (STARTS_WITH(direction_str, "prev")) ? BEFORE : AFTER; owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { Con *ws = con_get_workspace(current->con); if (!ws || con_is_internal(ws)) { continue; @@ -1406,13 +1372,14 @@ void cmd_focus_window_mode(I3_CMD, const char *window_mode) { to_floating = false; } - Con *ws = con_get_workspace(focused); + const Con *ws = con_get_workspace(focused); Con *current; bool success = false; TAILQ_FOREACH (current, &(ws->focus_head), focused) { if ((to_floating && current->type != CT_FLOATING_CON) || - (!to_floating && current->type == CT_FLOATING_CON)) + (!to_floating && current->type == CT_FLOATING_CON)) { continue; + } con_activate_unblock(con_descend_focused(current)); success = true; @@ -1439,16 +1406,18 @@ void cmd_focus_level(I3_CMD, const char *level) { * focused container won't escape the fullscreen container. */ if (strcmp(level, "parent") == 0) { if (focused && focused->parent) { - if (con_fullscreen_permits_focusing(focused->parent)) + if (con_fullscreen_permits_focusing(focused->parent)) { success = level_up(); - else + } else { ELOG("'focus parent': Currently in fullscreen, not going up\n"); + } } } /* Focusing a child should always be allowed. */ - else + else { success = level_down(); + } cmd_output->needs_tree_render = success; // XXX: default reply for now, make this a better reply @@ -1459,7 +1428,7 @@ void cmd_focus_level(I3_CMD, const char *level) { * Implementation of 'focus'. * */ -void cmd_focus(I3_CMD) { +void cmd_focus(I3_CMD, const bool focus_workspace) { DLOG("current_match = %p\n", current_match); if (match_is_empty(current_match)) { @@ -1468,24 +1437,25 @@ void cmd_focus(I3_CMD) { yerror("You have to specify which window/container should be focused"); return; - } else if (TAILQ_EMPTY(&owindows)) { + } else if (TAILQ_EMPTY(&OWINDOWS)) { yerror("No window matches given criteria"); return; } CMD_FOCUS_WARN_CHILDREN; - Con *__i3_scratch = workspace_get("__i3_scratch"); + const Con *__i3_scratch = workspace_get("__i3_scratch"); owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { 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 @@ -1493,8 +1463,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(maybe_auto_back_and_forth_workspace(ws)); + } else { + LOG("focusing %p / %s\n", current->con, current->con->name); + con_activate_unblock(current->con); + } } cmd_output->needs_tree_render = true; @@ -1507,13 +1484,13 @@ void cmd_focus(I3_CMD) { * */ void cmd_fullscreen(I3_CMD, const char *action, const char *fullscreen_mode) { - fullscreen_mode_t mode = strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT; + const fullscreen_mode_t mode = strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT; DLOG("%s fullscreen, mode = %s\n", action, fullscreen_mode); owindow *current; HANDLE_EMPTY_MATCH; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); if (strcmp(action, "toggle") == 0) { con_toggle_fullscreen(current->con, mode); @@ -1538,7 +1515,7 @@ void cmd_sticky(I3_CMD, const char *action) { HANDLE_EMPTY_MATCH; owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { if (current->con->window == NULL) { ELOG("only containers holding a window can be made sticky, skipping con = %p\n", current->con); continue; @@ -1546,12 +1523,13 @@ void cmd_sticky(I3_CMD, const char *action) { DLOG("setting sticky for container = %p / %s\n", current->con, current->con->name); bool sticky = false; - if (strcmp(action, "enable") == 0) + if (strcmp(action, "enable") == 0) { sticky = true; - else if (strcmp(action, "disable") == 0) + } else if (strcmp(action, "disable") == 0) { sticky = false; - else if (strcmp(action, "toggle") == 0) + } else if (strcmp(action, "toggle") == 0) { sticky = !current->con->sticky; + } current->con->sticky = sticky; ewmh_update_sticky(current->con->window->id, sticky); @@ -1571,21 +1549,20 @@ void cmd_sticky(I3_CMD, const char *action) { * Implementation of 'move [ [px|ppt]]'. * */ -void cmd_move_direction(I3_CMD, const char *direction_str, long amount, const char *mode) { +void cmd_move_direction(I3_CMD, const char *direction_str, const long amount, const char *mode) { owindow *current; HANDLE_EMPTY_MATCH; - Con *initially_focused = focused; - direction_t direction = parse_direction(direction_str); + const direction_t direction = parse_direction(direction_str); const bool is_ppt = mode && strcmp(mode, "ppt") == 0; DLOG("moving in direction %s, %ld %s\n", direction_str, amount, mode); - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { if (con_is_floating(current->con)) { DLOG("floating move with %ld %s\n", amount, mode); Rect newrect = current->con->parent->rect; - Con *output = con_get_output(current->con); + const Con *output = con_get_output(current->con); switch (direction) { case D_LEFT: @@ -1609,12 +1586,6 @@ void cmd_move_direction(I3_CMD, const char *direction_str, long amount, const ch } } - /* The move command should not disturb focus. con_exists is called because - * tree_move calls tree_flatten. */ - if (focused != initially_focused && con_exists(initially_focused)) { - con_activate(initially_focused); - } - // XXX: default reply for now, make this a better reply ysuccess(true); } @@ -1635,7 +1606,7 @@ void cmd_layout(I3_CMD, const char *layout_str) { DLOG("changing layout to %s (%d)\n", layout_str, layout); owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { if (con_is_docked(current->con)) { ELOG("cannot change layout of a docked container, skipping it.\n"); continue; @@ -1655,18 +1626,18 @@ void cmd_layout(I3_CMD, const char *layout_str) { * */ void cmd_layout_toggle(I3_CMD, const char *toggle_mode) { - owindow *current; - - if (toggle_mode == NULL) + if (toggle_mode == NULL) { toggle_mode = "default"; + } DLOG("toggling layout (mode = %s)\n", toggle_mode); /* check if the match is empty, not if the result is empty */ - if (match_is_empty(current_match)) + if (match_is_empty(current_match)) { con_toggle_layout(focused, toggle_mode); - else { - TAILQ_FOREACH (current, &owindows, owindows) { + } else { + owindow *current; + TAILQ_FOREACH (current, &OWINDOWS, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); con_toggle_layout(current->con, toggle_mode); } @@ -1788,13 +1759,26 @@ void cmd_focus_output(I3_CMD, const char *name) { HANDLE_EMPTY_MATCH; - if (TAILQ_EMPTY(&owindows)) { + if (TAILQ_EMPTY(&OWINDOWS)) { ysuccess(true); return; } - Output *current_output = get_output_for_con(TAILQ_FIRST(&owindows)->con); - Output *target_output = user_output_names_find_next(&names, current_output); + /* Command criteria need to work for focus output left|right|up|down. + * We need to avoid using internal workspaces with get_output_for_con, so + * we go through all matched windows until we find a non-internal one. If + * there is no match, fall back to the focused one. */ + owindow *current; + Con *con = focused; + TAILQ_FOREACH (current, &OWINDOWS, owindows) { + if (!con_is_internal(con_get_workspace(current->con))) { + con = current->con; + break; + } + } + + Output *current_output = get_output_for_con(con); + const Output *target_output = user_output_names_find_next(&names, current_output); user_output_names_free(&names); bool success = false; if (target_output) { @@ -1823,13 +1807,13 @@ void cmd_focus_output(I3_CMD, const char *name) { * Implementation of 'move [window|container] [to] [absolute] position [ [px|ppt] [px|ppt]] * */ -void cmd_move_window_to_position(I3_CMD, long x, const char *mode_x, long y, const char *mode_y) { +void cmd_move_window_to_position(I3_CMD, const long x, const char *mode_x, const long y, const char *mode_y) { bool has_error = false; owindow *current; HANDLE_EMPTY_MATCH; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { if (!con_is_floating(current->con)) { ELOG("Cannot change position. The window/container is not floating\n"); @@ -1842,7 +1826,7 @@ void cmd_move_window_to_position(I3_CMD, long x, const char *mode_x, long y, con } Rect newrect = current->con->parent->rect; - Con *output = con_get_output(current->con); + const Con *output = con_get_output(current->con); newrect.x = mode_x && strcmp(mode_x, "ppt") == 0 ? output->rect.width * ((double)x / 100.0) : x; newrect.y = mode_y && strcmp(mode_y, "ppt") == 0 ? output->rect.height * ((double)y / 100.0) : y; @@ -1854,8 +1838,9 @@ void cmd_move_window_to_position(I3_CMD, long x, const char *mode_x, long y, con } } - if (!has_error) + if (!has_error) { ysuccess(true); + } } /* @@ -1867,7 +1852,7 @@ void cmd_move_window_to_center(I3_CMD, const char *method) { HANDLE_EMPTY_MATCH; owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { Con *floating_con = con_inside_floating(current->con); if (floating_con == NULL) { ELOG("con %p / %s is not floating, cannot move it to the center.\n", @@ -1898,8 +1883,9 @@ void cmd_move_window_to_center(I3_CMD, const char *method) { } // XXX: default reply for now, make this a better reply - if (!has_error) + if (!has_error) { ysuccess(true); + } } /* @@ -1910,7 +1896,7 @@ void cmd_move_window_to_mouse(I3_CMD) { HANDLE_EMPTY_MATCH; owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { Con *floating_con = con_inside_floating(current->con); if (floating_con == NULL) { DLOG("con %p / %s is not floating, cannot move it to the mouse position.\n", @@ -1936,7 +1922,7 @@ void cmd_move_scratchpad(I3_CMD) { HANDLE_EMPTY_MATCH; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); scratchpad_move(current->con); } @@ -1952,13 +1938,13 @@ void cmd_move_scratchpad(I3_CMD) { */ void cmd_scratchpad_show(I3_CMD) { DLOG("should show scratchpad window\n"); - owindow *current; bool result = false; if (match_is_empty(current_match)) { result = scratchpad_show(NULL); } else { - TAILQ_FOREACH (current, &owindows, owindows) { + owindow *current; + TAILQ_FOREACH (current, &OWINDOWS, owindows) { DLOG("matching: %p / %s\n", current->con, current->con->name); result |= scratchpad_show(current->con); } @@ -1976,7 +1962,7 @@ void cmd_scratchpad_show(I3_CMD) { void cmd_swap(I3_CMD, const char *mode, const char *arg) { HANDLE_EMPTY_MATCH; - owindow *match = TAILQ_FIRST(&owindows); + owindow *match = TAILQ_FIRST(&OWINDOWS); if (match == NULL) { yerror("No match found for swapping."); return; @@ -2015,12 +2001,12 @@ void cmd_swap(I3_CMD, const char *mode, const char *arg) { return; } - if (match != TAILQ_LAST(&owindows, owindows_head)) { + if (match != TAILQ_LAST(&OWINDOWS, owindows_head)) { LOG("More than one container matched the swap command, only using the first one."); } DLOG("Swapping %p with %p.\n", match->con, con); - bool result = con_swap(match->con, con); + const bool result = con_swap(match->con, con); cmd_output->needs_tree_render = true; // XXX: default reply for now, make this a better reply @@ -2036,7 +2022,7 @@ void cmd_title_format(I3_CMD, const char *format) { HANDLE_EMPTY_MATCH; owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { DLOG("setting title_format for %p / %s\n", current->con, current->con->name); FREE(current->con->title_format); @@ -2087,7 +2073,7 @@ void cmd_title_window_icon(I3_CMD, const char *enable, int padding) { HANDLE_EMPTY_MATCH; owindow *current; - TAILQ_FOREACH (current, &owindows, owindows) { + TAILQ_FOREACH (current, &OWINDOWS, owindows) { if (is_toggle) { const int current_padding = current->con->window_icon_padding; if (padding > 0) { @@ -2147,7 +2133,7 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { return; } - Con *check_dest = get_existing_workspace_by_name(new_name); + const Con *check_dest = get_existing_workspace_by_name(new_name); /* If check_dest == workspace, the user might be changing the case of the * workspace, or it might just be a no-op. */ @@ -2167,7 +2153,7 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { /* By re-attaching, the sort order will be correct afterwards. */ Con *previously_focused = focused; - Con *previously_focused_content = focused->type == CT_WORKSPACE ? focused->parent : NULL; + const Con *previously_focused_content = focused->type == CT_WORKSPACE ? focused->parent : NULL; Con *parent = workspace->parent; con_detach(workspace); con_attach(workspace, parent, false); @@ -2185,9 +2171,9 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { * Instead, we loop through the available workspaces and only focus * previously_focused if we still find it. */ if (previously_focused_content) { - Con *workspace = NULL; - GREP_FIRST(workspace, previously_focused_content, child == previously_focused); - can_restore_focus &= (workspace != NULL); + const Con *ws = NULL; + GREP_FIRST(ws, previously_focused_content, child == previously_focused); + can_restore_focus &= (ws != NULL); } if (can_restore_focus) { @@ -2219,15 +2205,15 @@ void cmd_rename_workspace(I3_CMD, const char *old_name, const char *new_name) { void cmd_bar_mode(I3_CMD, const char *bar_mode, const char *bar_id) { int mode = M_DOCK; bool toggle = false; - if (strcmp(bar_mode, "dock") == 0) + if (strcmp(bar_mode, "dock") == 0) { mode = M_DOCK; - else if (strcmp(bar_mode, "hide") == 0) + } else if (strcmp(bar_mode, "hide") == 0) { mode = M_HIDE; - else if (strcmp(bar_mode, "invisible") == 0) + } else if (strcmp(bar_mode, "invisible") == 0) { mode = M_INVISIBLE; - else if (strcmp(bar_mode, "toggle") == 0) + } else if (strcmp(bar_mode, "toggle") == 0) { toggle = true; - else { + } else { ELOG("Unknown bar mode \"%s\", this is a mismatch between code and parser spec.\n", bar_mode); assert(false); } @@ -2278,13 +2264,13 @@ void cmd_bar_mode(I3_CMD, const char *bar_mode, const char *bar_id) { void cmd_bar_hidden_state(I3_CMD, const char *bar_hidden_state, const char *bar_id) { int hidden_state = S_SHOW; bool toggle = false; - if (strcmp(bar_hidden_state, "hide") == 0) + if (strcmp(bar_hidden_state, "hide") == 0) { hidden_state = S_HIDE; - else if (strcmp(bar_hidden_state, "show") == 0) + } else if (strcmp(bar_hidden_state, "show") == 0) { hidden_state = S_SHOW; - else if (strcmp(bar_hidden_state, "toggle") == 0) + } else if (strcmp(bar_hidden_state, "toggle") == 0) { toggle = true; - else { + } else { ELOG("Unknown bar state \"%s\", this is a mismatch between code and parser spec.\n", bar_hidden_state); assert(false); } @@ -2333,14 +2319,14 @@ void cmd_bar_hidden_state(I3_CMD, const char *bar_hidden_state, const char *bar_ * */ void cmd_shmlog(I3_CMD, const char *argument) { - if (!strcmp(argument, "toggle")) + if (!strcmp(argument, "toggle")) { /* Toggle shm log, if size is not 0. If it is 0, set it to default. */ shmlog_size = shmlog_size ? -shmlog_size : default_shmlog_size; - else if (!strcmp(argument, "on")) + } else if (!strcmp(argument, "on")) { shmlog_size = default_shmlog_size; - else if (!strcmp(argument, "off")) + } else if (!strcmp(argument, "off")) { shmlog_size = 0; - else { + } else { long new_size = 0; if (!parse_long(argument, &new_size, 0)) { yerror("Failed to parse %s into a shmlog size.", argument); @@ -2365,7 +2351,7 @@ void cmd_shmlog(I3_CMD, const char *argument) { * */ void cmd_debuglog(I3_CMD, const char *argument) { - bool logging = get_debug_logging(); + const bool logging = get_debug_logging(); if (!strcmp(argument, "toggle")) { LOG("%s debug logging\n", logging ? "Disabling" : "Enabling"); set_debug_logging(!logging); @@ -2402,7 +2388,7 @@ static int *gaps_right(gaps_t *gaps) { typedef int *(*gap_accessor)(gaps_t *); -static bool gaps_update(gap_accessor get, const char *scope, const char *mode, int pixels) { +static bool gaps_update(const gap_accessor get, const char *scope, const char *mode, const int pixels) { DLOG("gaps_update(scope=%s, mode=%s, pixels=%d)\n", scope, mode, pixels); Con *workspace = con_get_workspace(focused); @@ -2414,11 +2400,11 @@ static bool gaps_update(gap_accessor get, const char *scope, const char *mode, i DLOG("global_gap_size=%d, current_value=%d\n", global_gap_size, current_value); bool reset = false; - if (strcmp(mode, "plus") == 0) + if (strcmp(mode, "plus") == 0) { current_value += pixels; - else if (strcmp(mode, "minus") == 0) + } else if (strcmp(mode, "minus") == 0) { current_value -= pixels; - else if (strcmp(mode, "set") == 0) { + } else if (strcmp(mode, "set") == 0) { current_value = pixels; reset = true; } else if (strcmp(mode, "toggle") == 0) { @@ -2449,7 +2435,7 @@ static bool gaps_update(gap_accessor get, const char *scope, const char *mode, i Con *output = NULL; TAILQ_FOREACH (output, &(croot->nodes_head), nodes) { Con *cur_ws = NULL; - Con *content = output_get_content(output); + const 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); @@ -2487,7 +2473,7 @@ static bool gaps_update(gap_accessor get, const char *scope, const char *mode, i * */ void cmd_gaps(I3_CMD, const char *type, const char *scope, const char *mode, const char *value) { - int pixels = logical_px(atoi(value)); + const int pixels = logical_px(atoi(value)); if (!strcmp(type, "inner")) { if (!gaps_update(gaps_inner, scope, mode, pixels)) { diff --git a/src/commands_parser.c b/src/commands_parser.c index 7cdf6c68..c355f2a8 100644 --- a/src/commands_parser.c +++ b/src/commands_parser.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * commands_parser.c: hand-written parser to parse commands (commands are what @@ -24,10 +24,11 @@ * */ #include "all.h" +#include "parser_util.h" -// Macros to make the YAJL API a bit easier to use. -#define y(x, ...) (command_output.json_gen != NULL ? yajl_gen_##x(command_output.json_gen, ##__VA_ARGS__) : 0) -#define ystr(str) (command_output.json_gen != NULL ? yajl_gen_string(command_output.json_gen, (unsigned char *)str, strlen(str)) : 0) +/* Macros to make the YAJL API a bit easier to use. */ +#define y(x, ...) (cmd_ctx.command_output.json_gen != NULL ? yajl_gen_##x(cmd_ctx.command_output.json_gen, ##__VA_ARGS__) : 0) +#define ystr(str) (cmd_ctx.command_output.json_gen != NULL ? yajl_gen_string(cmd_ctx.command_output.json_gen, (unsigned char *)str, strlen(str)) : 0) /******************************************************************************* * The data structures used for parsing. Essentially the current state and a @@ -56,121 +57,32 @@ typedef struct tokenptr { #include "GENERATED_command_tokens.h" -/* - * Pushes a string (identified by 'identifier') on the stack. We simply use a - * single array, since the number of entries we have to store is very small. - * - */ -static void push_string(struct stack *stack, const char *identifier, char *str) { - for (int c = 0; c < 10; c++) { - if (stack->stack[c].identifier != NULL) - continue; - /* Found a free slot, let’s store it here. */ - stack->stack[c].identifier = identifier; - stack->stack[c].val.str = str; - stack->stack[c].type = STACK_STR; - return; - } - - /* When we arrive here, the stack is full. This should not happen and - * means there’s either a bug in this parser or the specification - * contains a command with more than 10 identified tokens. */ - fprintf(stderr, "BUG: commands_parser stack full. This means either a bug " - "in the code, or a new command which contains more than " - "10 identified tokens.\n"); - exit(EXIT_FAILURE); -} - -// TODO move to a common util -static void push_long(struct stack *stack, const char *identifier, long num) { - for (int c = 0; c < 10; c++) { - if (stack->stack[c].identifier != NULL) { - continue; - } - - stack->stack[c].identifier = identifier; - stack->stack[c].val.num = num; - stack->stack[c].type = STACK_LONG; - return; - } - - /* When we arrive here, the stack is full. This should not happen and - * means there’s either a bug in this parser or the specification - * contains a command with more than 10 identified tokens. */ - fprintf(stderr, "BUG: commands_parser stack full. This means either a bug " - "in the code, or a new command which contains more than " - "10 identified tokens.\n"); - exit(EXIT_FAILURE); -} - -// TODO move to a common util -static const char *get_string(struct stack *stack, const char *identifier) { - for (int c = 0; c < 10; c++) { - if (stack->stack[c].identifier == NULL) - break; - if (strcmp(identifier, stack->stack[c].identifier) == 0) - return stack->stack[c].val.str; - } - return NULL; -} - -// TODO move to a common util -static long get_long(struct stack *stack, const char *identifier) { - for (int c = 0; c < 10; c++) { - if (stack->stack[c].identifier == NULL) - break; - if (strcmp(identifier, stack->stack[c].identifier) == 0) - return stack->stack[c].val.num; - } - - return 0; -} - -// TODO move to a common util -static void clear_stack(struct stack *stack) { - for (int c = 0; c < 10; c++) { - if (stack->stack[c].type == STACK_STR) - free(stack->stack[c].val.str); - stack->stack[c].identifier = NULL; - stack->stack[c].val.str = NULL; - stack->stack[c].val.num = 0; - } -} - /******************************************************************************* * The parser itself. ******************************************************************************/ -static cmdp_state state; -static Match current_match; -/******************************************************************************* - * The (small) stack where identified literals are stored during the parsing - * of a single command (like $workspace). - ******************************************************************************/ -static struct stack stack; -static struct CommandResultIR subcommand_output; -static struct CommandResultIR command_output; - #include "GENERATED_command_call.h" -static void next_state(const cmdp_token *token) { +static void next_state(const cmdp_token *token, struct cmd_parser_ctx *cmd_ctx) { if (token->next_state == __CALL) { - subcommand_output.json_gen = command_output.json_gen; - subcommand_output.client = command_output.client; - subcommand_output.needs_tree_render = false; - GENERATED_call(¤t_match, &stack, token->extra.call_identifier, &subcommand_output); - state = subcommand_output.next_state; + cmd_ctx->subcommand_output.ctx = cmd_ctx; + cmd_ctx->subcommand_output.json_gen = cmd_ctx->command_output.json_gen; + cmd_ctx->subcommand_output.client = cmd_ctx->command_output.client; + cmd_ctx->subcommand_output.needs_tree_render = false; + GENERATED_call(&cmd_ctx->current_match, &cmd_ctx->stack, token->extra.call_identifier, &cmd_ctx->subcommand_output); + cmd_ctx->state = cmd_ctx->subcommand_output.next_state; /* If any subcommand requires a tree_render(), we need to make the * whole parser result request a tree_render(). */ - if (subcommand_output.needs_tree_render) - command_output.needs_tree_render = true; - clear_stack(&stack); + if (cmd_ctx->subcommand_output.needs_tree_render) { + cmd_ctx->command_output.needs_tree_render = true; + } + parser_clear_stack(&cmd_ctx->stack); return; } - state = token->next_state; - if (state == INITIAL) { - clear_stack(&stack); + cmd_ctx->state = token->next_state; + if (cmd_ctx->state == INITIAL) { + parser_clear_stack(&cmd_ctx->stack); } } @@ -180,15 +92,17 @@ static void next_state(const cmdp_token *token) { * workspace commands. * */ -char *parse_string(const char **walk, bool as_word) { +char *parse_string(const char **walk, const bool as_word) { const char *beginning = *walk; /* Handle quoted strings (or words). */ if (**walk == '"') { beginning++; (*walk)++; - for (; **walk != '\0' && **walk != '"'; (*walk)++) - if (**walk == '\\' && *(*walk + 1) != '\0') + for (; **walk != '\0' && **walk != '"'; (*walk)++) { + if (**walk == '\\' && *(*walk + 1) != '\0') { (*walk)++; + } + } } else { if (!as_word) { /* For a string (starting with 's'), the delimiters are @@ -197,8 +111,9 @@ char *parse_string(const char **walk, bool as_word) { * end a command. */ while (**walk != ';' && **walk != ',' && **walk != '\0' && **walk != '\r' && - **walk != '\n') + **walk != '\n') { (*walk)++; + } } else { /* For a word, the delimiters are white space (' ' or * '\t'), closing square bracket (]), comma (,) and @@ -206,12 +121,14 @@ char *parse_string(const char **walk, bool as_word) { while (**walk != ' ' && **walk != '\t' && **walk != ']' && **walk != ',' && **walk != ';' && **walk != '\r' && - **walk != '\n' && **walk != '\0') + **walk != '\n' && **walk != '\0') { (*walk)++; + } } } - if (*walk == beginning) + if (*walk == beginning) { return NULL; + } char *str = scalloc(*walk - beginning + 1, 1); /* We copy manually to handle escaping of characters. */ @@ -222,8 +139,9 @@ char *parse_string(const char **walk, bool as_word) { /* We only handle escaped double quotes and backslashes to not break * backwards compatibility with people using \w in regular expressions * etc. */ - if (beginning[inpos] == '\\' && (beginning[inpos + 1] == '"' || beginning[inpos + 1] == '\\')) + if (beginning[inpos] == '\\' && (beginning[inpos + 1] == '"' || beginning[inpos + 1] == '\\')) { inpos++; + } str[outpos] = beginning[inpos]; } @@ -239,16 +157,19 @@ char *parse_string(const char **walk, bool as_word) { */ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client) { DLOG("COMMAND: *%.4000s*\n", input); - state = INITIAL; + struct cmd_parser_ctx cmd_ctx = {0}; + + cmd_ctx.state = INITIAL; CommandResult *result = scalloc(1, sizeof(CommandResult)); - command_output.client = client; + cmd_ctx.command_output.ctx = &cmd_ctx; + cmd_ctx.command_output.client = client; /* A YAJL JSON generator used for formatting replies. */ - command_output.json_gen = gen; + cmd_ctx.command_output.json_gen = gen; y(array_open); - command_output.needs_tree_render = false; + cmd_ctx.command_output.needs_tree_render = false; const char *walk = input; const size_t len = strlen(input); @@ -258,7 +179,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client // TODO: make this testable #ifndef TEST_PARSER - cmd_criteria_init(¤t_match, &subcommand_output); + cmd_criteria_init(&cmd_ctx.current_match, &cmd_ctx.subcommand_output); #endif /* The "<=" operator is intentional: We also handle the terminating 0-byte @@ -267,10 +188,11 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client /* skip whitespace and newlines before every token */ while ((*walk == ' ' || *walk == '\t' || *walk == '\r' || *walk == '\n') && - *walk != '\0') + *walk != '\0') { walk++; + } - cmdp_token_ptr *ptr = &(tokens[state]); + const cmdp_token_ptr *ptr = &(tokens[cmd_ctx.state]); token_handled = false; for (c = 0; c < ptr->n; c++) { token = &(ptr->array[c]); @@ -279,10 +201,10 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client if (token->name[0] == '\'') { if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) { if (token->identifier != NULL) { - push_string(&stack, token->identifier, sstrdup(token->name + 1)); + parser_push_string(&cmd_ctx.stack, token->identifier, token->name + 1); } walk += strlen(token->name) - 1; - next_state(token); + next_state(token, &cmd_ctx); token_handled = true; break; } @@ -293,22 +215,24 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client /* Handle numbers. We only accept decimal numbers for now. */ char *end = NULL; errno = 0; - long int num = strtol(walk, &end, 10); + const long int num = strtol(walk, &end, 10); if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) || - (errno != 0 && num == 0)) + (errno != 0 && num == 0)) { continue; + } /* No valid numbers found */ - if (end == walk) + if (end == walk) { continue; + } if (token->identifier != NULL) { - push_long(&stack, token->identifier, num); + parser_push_long(&cmd_ctx.stack, token->identifier, num); } /* Set walk to the first non-number character */ walk = end; - next_state(token); + next_state(token, &cmd_ctx); token_handled = true; break; } @@ -318,13 +242,15 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client char *str = parse_string(&walk, (token->name[0] != 's')); if (str != NULL) { if (token->identifier) { - push_string(&stack, token->identifier, str); + parser_push_string(&cmd_ctx.stack, token->identifier, str); } + free(str); /* If we are at the end of a quoted string, skip the ending * double quote. */ - if (*walk == '"') + if (*walk == '"') { walk++; - next_state(token); + } + next_state(token, &cmd_ctx); token_handled = true; break; } @@ -332,7 +258,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client if (strcmp(token->name, "end") == 0) { if (*walk == '\0' || *walk == ',' || *walk == ';') { - next_state(token); + next_state(token, &cmd_ctx); token_handled = true; /* To make sure we start with an appropriate matching * datastructure for commands which do *not* specify any @@ -340,8 +266,9 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client * every command. */ // TODO: make this testable #ifndef TEST_PARSER - if (*walk == '\0' || *walk == ';') - cmd_criteria_init(¤t_match, &subcommand_output); + if (*walk == '\0' || *walk == ';') { + cmd_criteria_init(&cmd_ctx.current_match, &cmd_ctx.subcommand_output); + } #endif walk++; break; @@ -353,8 +280,9 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client /* Figure out how much memory we will need to fill in the names of * all tokens afterwards. */ int tokenlen = 0; - for (c = 0; c < ptr->n; c++) + for (c = 0; c < ptr->n; c++) { tokenlen += strlen(ptr->array[c].name) + strlen("'', "); + } /* Build up a decent error message. We include the problem, the * full input, and underline the position where the parser @@ -392,8 +320,9 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client /* Contains the same amount of characters as 'input' has, but with * the unparsable part highlighted using ^ characters. */ char *position = smalloc(len + 1); - for (const char *copywalk = input; *copywalk != '\0'; copywalk++) + for (const char *copywalk = input; *copywalk != '\0'; copywalk++) { position[(copywalk - input)] = (copywalk >= walk ? '^' : ' '); + } position[len] = '\0'; ELOG("%s\n", errormessage); @@ -421,14 +350,20 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client y(map_close); free(position); - clear_stack(&stack); + parser_clear_stack(&cmd_ctx.stack); break; } } y(array_close); - result->needs_tree_render = command_output.needs_tree_render; + result->needs_tree_render = cmd_ctx.command_output.needs_tree_render; + /* Clean up owindows entries */ + while (!TAILQ_EMPTY(&cmd_ctx.owindows)) { + struct owindow *ow = TAILQ_FIRST(&cmd_ctx.owindows); + TAILQ_REMOVE(&cmd_ctx.owindows, ow, owindows); + free(ow); + } return result; } @@ -436,8 +371,9 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client * Frees a CommandResult */ void command_result_free(CommandResult *result) { - if (result == NULL) + if (result == NULL) { return; + } FREE(result->error_message); FREE(result); diff --git a/src/con.c b/src/con.c index 5ecc2700..b9b38e24 100644 --- a/src/con.c +++ b/src/con.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * con.c: Functions which deal with containers directly (creating containers, @@ -57,8 +57,9 @@ Con *con_new_skeleton(Con *parent, i3Window *window) { TAILQ_INIT(&(new->swallow_head)); TAILQ_INIT(&(new->marks_head)); - if (parent != NULL) + if (parent != NULL) { con_attach(new, parent, false); + } return new; } @@ -115,18 +116,19 @@ static void _con_attach(Con *con, Con *parent, Con *previous, bool ignore_focus) /* we need to insert the container at the beginning */ TAILQ_INSERT_HEAD(nodes_head, con, nodes); } else { - while (current->num != -1 && con->num > current->num) { + while (current->num != -1 && con->num >= current->num) { current = TAILQ_NEXT(current, nodes); if (current == TAILQ_END(nodes_head)) { current = NULL; break; } } - /* we need to insert con after current, if current is not NULL */ - if (current) + /* we need to insert con before current, if current is not NULL */ + if (current) { TAILQ_INSERT_BEFORE(current, con, nodes); - else + } else { TAILQ_INSERT_TAIL(nodes_head, con, nodes); + } } } goto add_to_focus_head; @@ -163,8 +165,9 @@ static void _con_attach(Con *con, Con *parent, Con *previous, bool ignore_focus) if (!ignore_focus) { /* Get the first tiling container in focus stack */ TAILQ_FOREACH (loop, &(parent->focus_head), focused) { - if (loop->type == CT_FLOATING_CON) + if (loop->type == CT_FLOATING_CON) { continue; + } current = loop; break; } @@ -197,8 +200,9 @@ static void _con_attach(Con *con, Con *parent, Con *previous, bool ignore_focus) if (current != NULL && parent->type != CT_OUTPUT) { DLOG("Inserting con = %p after con %p\n", con, current); TAILQ_INSERT_AFTER(nodes_head, current, con, nodes); - } else + } else { TAILQ_INSERT_TAIL(nodes_head, con, nodes); + } } add_to_focus_head: @@ -251,8 +255,9 @@ void con_focus(Con *con) { /* 2: exchange the position of the container in focus stack of the parent all the way up */ TAILQ_REMOVE(&(con->parent->focus_head), con, focused); TAILQ_INSERT_HEAD(&(con->parent->focus_head), con, focused); - if (con->parent->parent != NULL) + if (con->parent->parent != NULL) { con_focus(con->parent); + } focused = con; /* We can't blindly reset non-leaf containers since they might have @@ -340,12 +345,11 @@ void con_close(Con *con, kill_window_t kill_window) { if (con->type == CT_WORKSPACE) { DLOG("con = %p is a workspace, closing all children instead.\n", con); - Con *child, *nextchild; - for (child = TAILQ_FIRST(&(con->focus_head)); child;) { - nextchild = TAILQ_NEXT(child, focused); + for (Con *child = TAILQ_FIRST(&(con->focus_head)); child;) { + Con *next_child = TAILQ_NEXT(child, focused); DLOG("killing child = %p.\n", child); tree_close_internal(child, kill_window, false); - child = nextchild; + child = next_child; } return; @@ -383,8 +387,9 @@ bool con_has_children(Con *con) { * */ bool con_is_split(Con *con) { - if (con_is_leaf(con)) + if (con_is_leaf(con)) { return false; + } switch (con->layout) { case L_DOCKAREA: @@ -409,8 +414,9 @@ bool con_is_hidden(Con *con) { while (current != NULL && current->type != CT_WORKSPACE) { Con *parent = current->parent; if (parent != NULL && (parent->layout == L_TABBED || parent->layout == L_STACKED)) { - if (TAILQ_FIRST(&(parent->focus_head)) != current) + if (TAILQ_FIRST(&(parent->focus_head)) != current) { return true; + } } current = parent; @@ -419,18 +425,79 @@ bool con_is_hidden(Con *con) { return false; } +/* + * Returns true if the container is maximized in the given orientation. + * + * If the container is floating, it is not considered maximized. Otherwise, it + * is maximized if it doesn't share space with any other container in the given + * orientation. For example, if a workspace contains a single splitv container + * with three children, none of them are considered vertically maximized, but + * they are all considered horizontally maximized. + * + * Passing "maximized" hints to the application can help it make the right + * choices about how to draw its borders. See discussion in + * https://github.com/i3/i3/pull/2380. + */ +bool con_is_maximized(Con *con, orientation_t orientation) { + /* Fullscreen containers are considered maximized. */ + if (con->fullscreen_mode != CF_NONE) { + return true; + } + + /* Look up the container layout which corresponds to the given + * orientation. */ + layout_t layout; + switch (orientation) { + case HORIZ: + layout = L_SPLITH; + break; + case VERT: + layout = L_SPLITV; + break; + default: + assert(false); + } + + /* Go through all parents, stopping once we reach the workspace node. */ + Con *current = con; + while (true) { + Con *parent = current->parent; + if (parent == NULL || current->type == CT_WORKSPACE) { + /* We are done searching. We found no reason that the container + * should not be considered maximized. */ + return true; + } + + if (parent->layout == layout && con_num_children(parent) > 1) { + /* The parent has a split in the indicated direction, which + * means none of its children are maximized in that direction. */ + return false; + } + + /* Floating containers and their children are not considered + * maximized. */ + if (parent->type == CT_FLOATING_CON) { + return false; + } + + current = parent; + } +} + /* * Returns whether the container or any of its children is sticky. * */ bool con_is_sticky(Con *con) { - if (con->sticky) + if (con->sticky) { return true; + } Con *child; TAILQ_FOREACH (child, &(con->nodes_head), nodes) { - if (con_is_sticky(child)) + if (con_is_sticky(child)) { return true; + } } return false; @@ -443,8 +510,9 @@ bool con_is_sticky(Con *con) { */ bool con_accepts_window(Con *con) { /* 1: workspaces never accept direct windows */ - if (con->type == CT_WORKSPACE) + if (con->type == CT_WORKSPACE) { return false; + } if (con_is_split(con)) { DLOG("container %p does not accept windows, it is a split container.\n", con); @@ -462,8 +530,9 @@ bool con_accepts_window(Con *con) { */ Con *con_get_output(Con *con) { Con *result = con; - while (result != NULL && result->type != CT_OUTPUT) + while (result != NULL && result->type != CT_OUTPUT) { result = result->parent; + } /* We must be able to get an output because focus can never be set higher * in the tree (root node cannot be focused). */ assert(result != NULL); @@ -476,8 +545,9 @@ Con *con_get_output(Con *con) { */ Con *con_get_workspace(Con *con) { Con *result = con; - while (result != NULL && result->type != CT_WORKSPACE) + while (result != NULL && result->type != CT_WORKSPACE) { result = result->parent; + } return result; } @@ -489,8 +559,9 @@ Con *con_get_workspace(Con *con) { Con *con_parent_with_orientation(Con *con, orientation_t orientation) { DLOG("Searching for parent of Con %p with orientation %d\n", con, orientation); Con *parent = con->parent; - if (parent->type == CT_FLOATING_CON) + if (parent->type == CT_FLOATING_CON) { return NULL; + } while (con_orientation(parent) != orientation) { DLOG("Need to go one level further up\n"); parent = parent->parent; @@ -498,10 +569,12 @@ Con *con_parent_with_orientation(Con *con, orientation_t orientation) { if (parent && (parent->type == CT_FLOATING_CON || parent->type == CT_OUTPUT || - (parent->parent && parent->parent->type == CT_OUTPUT))) + (parent->parent && parent->parent->type == CT_OUTPUT))) { parent = NULL; - if (parent == NULL) + } + if (parent == NULL) { break; + } } DLOG("Result: %p\n", parent); return parent; @@ -523,8 +596,6 @@ struct bfs_entry { * */ Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode) { - Con *current, *child; - /* TODO: is breadth-first-search really appropriate? (check as soon as * fullscreen levels and fullscreen for containers is implemented) */ TAILQ_HEAD(bfs_head, bfs_entry) bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head); @@ -534,7 +605,7 @@ Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode) { while (!TAILQ_EMPTY(&bfs_head)) { entry = TAILQ_FIRST(&bfs_head); - current = entry->con; + Con *current = entry->con; if (current != con && current->fullscreen_mode == fullscreen_mode) { /* empty the queue */ while (!TAILQ_EMPTY(&bfs_head)) { @@ -548,6 +619,7 @@ Con *con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode) { TAILQ_REMOVE(&bfs_head, entry, entries); free(entry); + Con *child; TAILQ_FOREACH (child, &(current->nodes_head), nodes) { entry = smalloc(sizeof(struct bfs_entry)); entry->con = child; @@ -603,11 +675,13 @@ bool con_is_floating(Con *con) { * */ bool con_is_docked(Con *con) { - if (con->parent == NULL) + if (con->parent == NULL) { return false; + } - if (con->parent->type == CT_DOCKAREA) + if (con->parent->type == CT_DOCKAREA) { return true; + } return con_is_docked(con->parent); } @@ -622,14 +696,17 @@ Con *con_inside_floating(Con *con) { return NULL; } - if (con->type == CT_FLOATING_CON) + if (con->type == CT_FLOATING_CON) { return con; + } - if (con->floating >= FLOATING_AUTO_ON) + if (con->floating >= FLOATING_AUTO_ON) { return con->parent; + } - if (con->type == CT_WORKSPACE || con->type == CT_OUTPUT) + if (con->type == CT_WORKSPACE || con->type == CT_OUTPUT) { return NULL; + } return con_inside_floating(con->parent); } @@ -639,10 +716,12 @@ Con *con_inside_floating(Con *con) { * */ bool con_inside_focused(Con *con) { - if (con == focused) + if (con == focused) { return true; - if (!con->parent) + } + if (!con->parent) { return false; + } return con_inside_focused(con->parent); } @@ -726,8 +805,9 @@ Con *con_by_frame_id(xcb_window_t frame) { Con *con_by_mark(const char *mark) { Con *con; TAILQ_FOREACH (con, &all_cons, all_cons) { - if (con_has_mark(con, mark)) + if (con_has_mark(con, mark)) { return con; + } } return NULL; @@ -775,8 +855,9 @@ bool con_find_transient_for_window(Con *start, xcb_window_t target) { bool con_has_mark(Con *con, const char *mark) { mark_t *current; TAILQ_FOREACH (current, &(con->marks_head), marks) { - if (strcmp(current->name, mark) == 0) + if (strcmp(current->name, mark) == 0) { return true; + } } return false; @@ -811,9 +892,8 @@ void con_mark(Con *con, const char *mark, mark_mode_t mode) { if (mode == MM_REPLACE) { DLOG("Removing all existing marks on con = %p.\n", con); - mark_t *current; while (!TAILQ_EMPTY(&(con->marks_head))) { - current = TAILQ_FIRST(&(con->marks_head)); + const mark_t *current = TAILQ_FIRST(&(con->marks_head)); con_unmark(con, current->name); } } @@ -838,15 +918,16 @@ void con_unmark(Con *con, const char *name) { if (name == NULL) { DLOG("Unmarking all containers.\n"); TAILQ_FOREACH (current, &all_cons, all_cons) { - if (con != NULL && current != con) + if (con != NULL && current != con) { continue; + } - if (TAILQ_EMPTY(&(current->marks_head))) + if (TAILQ_EMPTY(&(current->marks_head))) { continue; + } - mark_t *mark; while (!TAILQ_EMPTY(&(current->marks_head))) { - mark = TAILQ_FIRST(&(current->marks_head)); + mark_t *mark = TAILQ_FIRST(&(current->marks_head)); FREE(mark->name); TAILQ_REMOVE(&(current->marks_head), mark, marks); FREE(mark); @@ -869,8 +950,9 @@ void con_unmark(Con *con, const char *name) { mark_t *mark; TAILQ_FOREACH (mark, &(current->marks_head), marks) { - if (strcmp(mark->name, name) != 0) + if (strcmp(mark->name, name) != 0) { continue; + } FREE(mark->name); TAILQ_REMOVE(&(current->marks_head), mark, marks); @@ -893,28 +975,34 @@ Con *con_for_window(Con *con, i3Window *window, Match **store_match) { TAILQ_FOREACH (child, &(con->nodes_head), nodes) { TAILQ_FOREACH (match, &(child->swallow_head), matches) { - if (!match_matches_window(match, window)) + if (!match_matches_window(match, window)) { continue; - if (store_match != NULL) + } + if (store_match != NULL) { *store_match = match; + } return child; } Con *result = con_for_window(child, window, store_match); - if (result != NULL) + if (result != NULL) { return result; + } } TAILQ_FOREACH (child, &(con->floating_head), floating_windows) { TAILQ_FOREACH (match, &(child->swallow_head), matches) { - if (!match_matches_window(match, window)) + if (!match_matches_window(match, window)) { continue; - if (store_match != NULL) + } + if (store_match != NULL) { *store_match = match; + } return child; } Con *result = con_for_window(child, window, store_match); - if (result != NULL) + if (result != NULL) { return result; + } } return NULL; @@ -997,18 +1085,21 @@ int con_num_children(Con *con) { * this will return 2 instead of 1. */ int con_num_visible_children(Con *con) { - if (con == NULL) + if (con == NULL) { return 0; + } int children = 0; Con *current = NULL; TAILQ_FOREACH (current, &(con->nodes_head), nodes) { /* Visible leaf nodes are a child. */ - if (!con_is_hidden(current) && con_is_leaf(current)) + if (!con_is_hidden(current) && con_is_leaf(current)) { children++; + } /* All other containers need to be recursed. */ - else + else { children += con_num_visible_children(current); + } } return children; @@ -1019,11 +1110,13 @@ int con_num_visible_children(Con *con) { * */ int con_num_windows(Con *con) { - if (con == NULL) + if (con == NULL) { return 0; + } - if (con_has_managed_window(con)) + if (con_has_managed_window(con)) { return 1; + } int num = 0; Con *current = NULL; @@ -1100,10 +1193,11 @@ void con_toggle_fullscreen(Con *con, int fullscreen_mode) { DLOG("toggling fullscreen for %p / %s\n", con, con->name); - if (con->fullscreen_mode == CF_NONE) + if (con->fullscreen_mode == CF_NONE) { con_enable_fullscreen(con, fullscreen_mode); - else + } else { con_disable_fullscreen(con); + } } /* @@ -1123,8 +1217,9 @@ static void con_set_fullscreen_mode(Con *con, fullscreen_mode_t fullscreen_mode) /* update _NET_WM_STATE if this container has a window */ /* TODO: when a window is assigned to a container which is already * fullscreened, this state needs to be pushed to the client, too */ - if (con->window == NULL) + if (con->window == NULL) { return; + } if (con->fullscreen_mode != CF_NONE) { DLOG("Setting _NET_WM_STATE_FULLSCREEN for con = %p / window = %d.\n", con, con->window->id); @@ -1154,10 +1249,11 @@ void con_enable_fullscreen(Con *con, fullscreen_mode_t fullscreen_mode) { assert(fullscreen_mode == CF_GLOBAL || fullscreen_mode == CF_OUTPUT); - if (fullscreen_mode == CF_GLOBAL) + if (fullscreen_mode == CF_GLOBAL) { DLOG("enabling global fullscreen for %p / %s\n", con, con->name); - else + } else { DLOG("enabling fullscreen for %p / %s\n", con, con->name); + } if (con->fullscreen_mode == fullscreen_mode) { DLOG("fullscreen already enabled for %p / %s\n", con, con->name); @@ -1168,21 +1264,25 @@ void con_enable_fullscreen(Con *con, fullscreen_mode_t fullscreen_mode) { /* Disable any fullscreen container that would conflict the new one. */ Con *fullscreen = con_get_fullscreen_con(croot, CF_GLOBAL); - if (fullscreen == NULL) + if (fullscreen == NULL) { fullscreen = con_get_fullscreen_con(con_ws, CF_OUTPUT); - if (fullscreen != NULL) + } + if (fullscreen != NULL) { con_disable_fullscreen(fullscreen); + } /* Set focus to new fullscreen container. Unless in global fullscreen mode * and on another workspace restore focus afterwards. * Switch to the container’s workspace if mode is global. */ Con *cur_ws = con_get_workspace(focused); Con *old_focused = focused; - if (fullscreen_mode == CF_GLOBAL && cur_ws != con_ws) + if (fullscreen_mode == CF_GLOBAL && cur_ws != con_ws) { workspace_show(con_ws); + } con_activate(con); - if (fullscreen_mode != CF_GLOBAL && cur_ws != con_ws) + if (fullscreen_mode != CF_GLOBAL && cur_ws != con_ws) { con_activate(old_focused); + } con_set_fullscreen_mode(con, fullscreen_mode); } @@ -1227,15 +1327,15 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi if (con->type == CT_WORKSPACE) { /* Re-parent all of the old workspace's floating windows. */ - Con *child; while (!TAILQ_EMPTY(&(source_ws->floating_head))) { - child = TAILQ_FIRST(&(source_ws->floating_head)); + Con *child = TAILQ_FIRST(&(source_ws->floating_head)); con_move_to_workspace(child, target_ws, true, true, false); } /* If there are no non-floating children, ignore the workspace. */ - if (con_is_leaf(con)) + if (con_is_leaf(con)) { return false; + } con = workspace_encapsulate(con); if (con == NULL) { @@ -1290,8 +1390,9 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi * to the coordinate space of the correct output */ if (fix_coordinates && con->type == CT_FLOATING_CON) { floating_fix_coordinates(con, &(source_output->rect), &(dest_output->rect)); - } else + } else { DLOG("Not fixing coordinates, fix_coordinates flag = %d\n", fix_coordinates); + } } /* If moving a fullscreen container and the destination already has a @@ -1358,16 +1459,18 @@ static bool _con_move_to_con(Con *con, Con *target, bool behind_focused, bool fi /* Set focus only if con was on current workspace before moving. * Otherwise we would give focus to some window on different workspace. */ - if (focus_next) + if (focus_next) { con_activate(con_descend_focused(focus_next)); + } /* 8. If anything within the container is associated with a startup sequence, * delete it so child windows won't be created on the old workspace. */ if (!con_is_leaf(con)) { Con *child; TAILQ_FOREACH (child, &(con->nodes_head), nodes) { - if (!child->window) + if (!child->window) { continue; + } startup_sequence_delete_by_window(child->window); } } @@ -1589,8 +1692,9 @@ Con *con_next_focused(Con *con) { */ Con *con_descend_focused(Con *con) { Con *next = con; - while (next != focused && !TAILQ_EMPTY(&(next->focus_head))) + while (next != focused && !TAILQ_EMPTY(&(next->focus_head))) { next = TAILQ_FIRST(&(next->focus_head)); + } return next; } @@ -1606,13 +1710,15 @@ Con *con_descend_tiling_focused(Con *con) { Con *next = con; Con *before; Con *child; - if (next == focused) + if (next == focused) { return next; + } do { before = next; TAILQ_FOREACH (child, &(next->focus_head), focused) { - if (child->type == CT_FLOATING_CON) + if (child->type == CT_FLOATING_CON) { continue; + } next = child; break; @@ -1637,10 +1743,11 @@ Con *con_descend_direction(Con *con, direction_t direction) { if (orientation == HORIZ) { /* If the direction is horizontal, we can use either the first * (D_RIGHT) or the last con (D_LEFT) */ - if (direction == D_RIGHT) + if (direction == D_RIGHT) { most = TAILQ_FIRST(&(con->nodes_head)); - else + } else { most = TAILQ_LAST(&(con->nodes_head), nodes_head); + } } else if (orientation == VERT) { /* Wrong orientation. We use the last focused con. Within that con, * we recurse to chose the left/right con or at least the last @@ -1662,10 +1769,11 @@ Con *con_descend_direction(Con *con, direction_t direction) { if (orientation == VERT) { /* If the direction is vertical, we can use either the first * (D_DOWN) or the last con (D_UP) */ - if (direction == D_UP) + if (direction == D_UP) { most = TAILQ_LAST(&(con->nodes_head), nodes_head); - else + } else { most = TAILQ_FIRST(&(con->nodes_head)); + } } else if (orientation == HORIZ) { /* Wrong orientation. We use the last focused con. Within that con, * we recurse to chose the top/bottom con or at least the last @@ -1683,8 +1791,9 @@ Con *con_descend_direction(Con *con, direction_t direction) { } } - if (!most) + if (!most) { return con; + } return con_descend_direction(most, direction); } @@ -1710,12 +1819,11 @@ bool con_draw_decoration_into_frame(Con *con) { } 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) || + if ((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)) + if (!con_is_floating(con)) { return (Rect){0, 0, 0, 0}; + } } adjacent_t borders_to_hide = ADJ_NONE; @@ -1732,8 +1840,9 @@ static Rect con_border_style_rect_without_title(Con *con) { DLOG("Effective border width is set to: %d\n", border_width); /* Shortcut to avoid calling con_adjacent_borders() on dock containers. */ int border_style = con_border_style(con); - if (border_style == BS_NONE) + if (border_style == BS_NONE) { return (Rect){0, 0, 0, 0}; + } if (border_style == BS_NORMAL) { result = (Rect){border_width, 0, -(2 * border_width), -(border_width)}; } else { @@ -1789,18 +1898,23 @@ adjacent_t con_adjacent_borders(Con *con) { adjacent_t result = ADJ_NONE; /* Floating windows are never adjacent to any other window, so don’t hide their border(s). This prevents bug #998. */ - if (con_is_floating(con)) + if (con_is_floating(con)) { return result; + } Con *workspace = con_get_workspace(con); - if (con->rect.x == workspace->rect.x) + if (con->rect.x == workspace->rect.x) { result |= ADJ_LEFT_SCREEN_EDGE; - if (con->rect.x + con->rect.width == workspace->rect.x + workspace->rect.width) + } + if (con->rect.x + con->rect.width == workspace->rect.x + workspace->rect.width) { result |= ADJ_RIGHT_SCREEN_EDGE; - if (con->rect.y == workspace->rect.y) + } + if (con->rect.y == workspace->rect.y) { result |= ADJ_UPPER_SCREEN_EDGE; - if (con->rect.y + con->rect.height == workspace->rect.y + workspace->rect.height) + } + if (con->rect.y + con->rect.height == workspace->rect.y + workspace->rect.height) { result |= ADJ_LOWER_SCREEN_EDGE; + } return result; } @@ -1887,14 +2001,16 @@ void con_set_layout(Con *con, layout_t layout) { /* Users can focus workspaces, but not any higher in the hierarchy. * Focus on the workspace is a special case, since in every other case, the * user means "change the layout of the parent split container". */ - if (con->type != CT_WORKSPACE) + if (con->type != CT_WORKSPACE) { con = con->parent; + } /* We fill in last_split_layout when switching to a different layout * since there are many places in the code that don’t use * con_set_layout(). */ - if (con->layout == L_SPLITH || con->layout == L_SPLITV) + if (con->layout == L_SPLITH || con->layout == L_SPLITV) { con->last_split_layout = con->layout; + } /* When the container type is CT_WORKSPACE, the user wants to change the * whole workspace into stacked/tabbed mode. To do this and still allow @@ -1922,9 +2038,8 @@ void con_set_layout(Con *con, layout_t layout) { Con **focus_order = get_focus_order(con); DLOG("Moving cons\n"); - Con *child; while (!TAILQ_EMPTY(&(con->nodes_head))) { - child = TAILQ_FIRST(&(con->nodes_head)); + Con *child = TAILQ_FIRST(&(con->nodes_head)); con_detach(child); con_attach(child, new, true); } @@ -1942,6 +2057,31 @@ void con_set_layout(Con *con, layout_t layout) { } } + if (con->type != CT_WORKSPACE && con->parent->type != CT_WORKSPACE && + con_num_children(con) == 1 && con_num_children(con->parent) == 1) { + /* Special case: Avoid creating redundant containers (#3001): + * split h / v (tree_split()) will avoid creating new containers when + * the target container is already a single child in L_SPLITH / + * L_SPLITV. However, if the layout is tabbed / stacked, a new split is + * created. This means, however, that when the user continuously + * switches between split h/v and tabbed / stacked, an endless series + * of 1-child containers will be created. Since a single level of split + * containers on top of tabbed / stacked containers are useful, we want + * to avoid this situation here. + * Example of past behaviour: S[V[w]] -> S[S[w]] -> S[S[V[w]]] -> … + * Example of desired behaviour: S[V[w]] -> S[w] -> S[v[w]] -> … + * Therefore, when both the current & parent containers have a single + * child, we just close the redundant middle container and proceed with + * the parent. */ + Con *parent = con->parent; + Con *child = TAILQ_FIRST(&(con->nodes_head)); + con_detach(child); + con_attach(child, parent, true); + parent->last_split_layout = con->last_split_layout; + tree_close_internal(con, DONT_KILL_WINDOW, true); + con = parent; + } + if (layout == L_DEFAULT) { /* Special case: the layout formerly known as "default" (in combination * with an orientation). Since we switched to splith/splitv layouts, @@ -1950,8 +2090,9 @@ void con_set_layout(Con *con, layout_t layout) { * splitv) in order to still do the same thing. */ con->layout = con->last_split_layout; /* In case last_split_layout was not initialized… */ - if (con->layout == L_DEFAULT) + if (con->layout == L_DEFAULT) { con->layout = L_SPLITH; + } } else { con->layout = layout; } @@ -1970,8 +2111,9 @@ void con_toggle_layout(Con *con, const char *toggle_mode) { /* Users can focus workspaces, but not any higher in the hierarchy. * Focus on the workspace is a special case, since in every other case, the * user means "change the layout of the parent split container". */ - if (con->type != CT_WORKSPACE) + if (con->type != CT_WORKSPACE) { parent = con->parent; + } DLOG("con_toggle_layout(%p, %s), parent = %p\n", con, toggle_mode, parent); const char delim[] = " "; @@ -2029,22 +2171,24 @@ void con_toggle_layout(Con *con, const char *toggle_mode) { con_set_layout(con, new_layout); } } else if (strcasecmp(toggle_mode, "all") == 0 || strcasecmp(toggle_mode, "default") == 0) { - if (parent->layout == L_STACKED) + if (parent->layout == L_STACKED) { con_set_layout(con, L_TABBED); - else if (parent->layout == L_TABBED) { - if (strcasecmp(toggle_mode, "all") == 0) + } else if (parent->layout == L_TABBED) { + if (strcasecmp(toggle_mode, "all") == 0) { con_set_layout(con, L_SPLITH); - else + } else { con_set_layout(con, parent->last_split_layout); + } } else if (parent->layout == L_SPLITH || parent->layout == L_SPLITV) { if (strcasecmp(toggle_mode, "all") == 0) { /* When toggling through all modes, we toggle between * splith/splitv, whereas normally we just directly jump to * stacked. */ - if (parent->layout == L_SPLITH) + if (parent->layout == L_SPLITH) { con_set_layout(con, L_SPLITV); - else + } else { con_set_layout(con, L_STACKED); + } } else { con_set_layout(con, L_STACKED); } @@ -2098,7 +2242,6 @@ static void con_on_remove_child(Con *con) { if (children == 0) { DLOG("Container empty, closing\n"); tree_close_internal(con, DONT_KILL_WINDOW, false); - return; } } @@ -2183,13 +2326,15 @@ Rect con_minimum_size(Con *con) { */ bool con_fullscreen_permits_focusing(Con *con) { /* No focus, no problem. */ - if (!focused) + if (!focused) { return true; + } /* Find the first fullscreen ascendent. */ Con *fs = focused; - while (fs && fs->fullscreen_mode == CF_NONE) + while (fs && fs->fullscreen_mode == CF_NONE) { fs = fs->parent; + } /* fs must be non-NULL since the workspace con doesn’t have CF_NONE and * there always has to be a workspace con in the hierarchy. */ @@ -2197,12 +2342,14 @@ bool con_fullscreen_permits_focusing(Con *con) { /* The most common case is we hit the workspace level. In this * situation, changing focus is also harmless. */ assert(fs->fullscreen_mode != CF_NONE); - if (fs->type == CT_WORKSPACE) + if (fs->type == CT_WORKSPACE) { return true; + } /* Allow it if the container itself is the fullscreen container. */ - if (con == fs) + if (con == fs) { return true; + } /* If fullscreen is per-output, the focus being in a different workspace is * sufficient to guarantee that change won't leave fullscreen in bad shape. */ @@ -2224,14 +2371,16 @@ bool con_fullscreen_permits_focusing(Con *con) { bool con_has_urgent_child(Con *con) { Con *child; - if (con_is_leaf(con)) + if (con_is_leaf(con)) { return con->urgent; + } /* We are not interested in floating windows since they can only be * attached to a workspace → nodes_head instead of focus_head */ TAILQ_FOREACH (child, &(con->nodes_head), nodes) { - if (con_has_urgent_child(child)) + if (con_has_urgent_child(child)) { return true; + } } return false; @@ -2249,8 +2398,9 @@ void con_update_parents_urgency(Con *con) { * hierarchy than the workspace level. Unfortunately, since the content * container has type == CT_CON, that’s not easy to verify in the loop * below, so we need another condition to catch that case: */ - if (con->type == CT_WORKSPACE) + if (con->type == CT_WORKSPACE) { return; + } bool new_urgency_value = con->urgent; while (parent && parent->type != CT_WORKSPACE && parent->type != CT_DOCKAREA) { @@ -2259,8 +2409,9 @@ void con_update_parents_urgency(Con *con) { } else { /* We can only reset the urgency when the parent * has no other urgent children */ - if (!con_has_urgent_child(parent)) + if (!con_has_urgent_child(parent)) { parent->urgent = false; + } } parent = parent->parent; } @@ -2280,8 +2431,9 @@ void con_set_urgency(Con *con, bool urgent) { if (con->urgency_timer == NULL) { con->urgent = urgent; - } else + } else { DLOG("Discarding urgency WM_HINT because timer is running\n"); + } if (con->window) { if (con->urgent) { @@ -2297,8 +2449,9 @@ void con_set_urgency(Con *con, bool urgent) { Con *ws; /* Set the urgency flag on the workspace, if a workspace could be found * (for dock clients, that is not the case). */ - if ((ws = con_get_workspace(con)) != NULL) + if ((ws = con_get_workspace(con)) != NULL) { workspace_update_urgent_flag(ws); + } if (con->urgent != old_urgent) { LOG("Urgency flag changed to %d\n", con->urgent); @@ -2322,28 +2475,30 @@ char *con_get_tree_representation(Con *con) { /* end of recursion */ if (con_is_leaf(con)) { - if (!con->window) + if (!con->window) { return sstrdup("nowin"); + } - if (!con->window->class_instance) + if (!con->window->class_instance) { return sstrdup("noinstance"); + } return sstrdup(con->window->class_instance); } char *buf; /* 1) add the Layout type to buf */ - if (con->layout == L_DEFAULT) + if (con->layout == L_DEFAULT) { buf = sstrdup("D["); - else if (con->layout == L_SPLITV) + } else if (con->layout == L_SPLITV) { buf = sstrdup("V["); - else if (con->layout == L_SPLITH) + } else if (con->layout == L_SPLITH) { buf = sstrdup("H["); - else if (con->layout == L_TABBED) + } else if (con->layout == L_TABBED) { buf = sstrdup("T["); - else if (con->layout == L_STACKED) + } else if (con->layout == L_STACKED) { buf = sstrdup("S["); - else { + } else { ELOG("BUG: Code not updated to account for new layout type\n"); assert(false); } diff --git a/src/config.c b/src/config.c index f06a3f8d..884052fd 100644 --- a/src/config.c +++ b/src/config.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * config.c: Configuration file (calling the parser (src/config_parser.c) with @@ -11,8 +11,6 @@ #include "all.h" #include -#include - #include char *current_configpath = NULL; @@ -42,9 +40,8 @@ static void free_configuration(void) { /* First ungrab the keys */ ungrab_all_keys(conn); - struct Mode *mode; while (!SLIST_EMPTY(&modes)) { - mode = SLIST_FIRST(&modes); + struct Mode *mode = SLIST_FIRST(&modes); FREE(mode->name); /* Clear the old binding list */ @@ -60,13 +57,14 @@ static void free_configuration(void) { } while (!TAILQ_EMPTY(&assignments)) { - struct Assignment *assign = TAILQ_FIRST(&assignments); - if (assign->type == A_TO_WORKSPACE || assign->type == A_TO_WORKSPACE_NUMBER) + Assignment *assign = TAILQ_FIRST(&assignments); + if (assign->type == A_TO_WORKSPACE || assign->type == A_TO_WORKSPACE_NUMBER) { FREE(assign->dest.workspace); - else if (assign->type == A_COMMAND) + } else if (assign->type == A_COMMAND) { FREE(assign->dest.command); - else if (assign->type == A_TO_OUTPUT) + } else if (assign->type == A_TO_OUTPUT) { FREE(assign->dest.output); + } match_free(&(assign->match)); TAILQ_REMOVE(&assignments, assign, assignments); FREE(assign); @@ -81,12 +79,12 @@ static void free_configuration(void) { } /* Clear bar configs */ - Barconfig *barconfig; while (!TAILQ_EMPTY(&barconfigs)) { - barconfig = TAILQ_FIRST(&barconfigs); + Barconfig *barconfig = TAILQ_FIRST(&barconfigs); FREE(barconfig->id); - for (int c = 0; c < barconfig->num_outputs; c++) + for (int c = 0; c < barconfig->num_outputs; c++) { free(barconfig->outputs[c]); + } while (!TAILQ_EMPTY(&(barconfig->bar_bindings))) { struct Barbinding *binding = TAILQ_FIRST(&(barconfig->bar_bindings)); @@ -105,6 +103,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); @@ -223,12 +222,14 @@ bool load_configuration(const char *override_configpath, config_load_t load_type config.gaps.left = 0; /* Set default urgency reset delay to 500ms */ - if (config.workspace_urgency_timer == 0) + if (config.workspace_urgency_timer == 0) { config.workspace_urgency_timer = 0.5; + } config.focus_wrapping = FOCUS_WRAPPING_ON; config.tiling_drag = TILING_DRAG_MODIFIER; + config.swap_modifier = XCB_KEY_BUT_MASK_SHIFT; FREE(current_configpath); current_configpath = get_config_path(override_configpath, true); @@ -258,11 +259,9 @@ bool load_configuration(const char *override_configpath, config_load_t load_type TAILQ_INSERT_TAIL(&included_files, file, files); LOG("Parsing configfile %s\n", resolved_path); - struct stack stack; - memset(&stack, '\0', sizeof(struct stack)); + struct stack stack = {0}; struct parser_ctx ctx = { .use_nagbar = (load_type != C_VALIDATE), - .assume_v4 = false, .stack = &stack, }; SLIST_INIT(&(ctx.variables)); @@ -282,8 +281,8 @@ bool load_configuration(const char *override_configpath, config_load_t load_type } /* Make bar config blocks without a configured font use the i3-wide font. */ - Barconfig *current; if (load_type != C_VALIDATE) { + Barconfig *current; TAILQ_FOREACH (current, &barconfigs, configs) { if (current->font != NULL) { continue; diff --git a/src/config_directives.c b/src/config_directives.c index 9077fe98..52ab6e14 100644 --- a/src/config_directives.c +++ b/src/config_directives.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * config_directives.c: all config storing functions (see config_parser.c) @@ -52,14 +52,9 @@ CFGFUN(include, const char *pattern) { file->path = sstrdup(resolved_path); TAILQ_INSERT_TAIL(&included_files, file, files); - struct stack stack; - memset(&stack, '\0', sizeof(struct stack)); + struct stack stack = {0}; struct parser_ctx ctx = { .use_nagbar = result->ctx->use_nagbar, - /* The include mechanism was added in v4, so we can skip the - * auto-detection and get rid of the risk of detecting the wrong - * version in potentially very short include fragments: */ - .assume_v4 = true, .stack = &stack, .variables = result->ctx->variables, }; @@ -83,8 +78,8 @@ CFGFUN(include, const char *pattern) { default: /* missing case statement */ assert(false); - break; } + result->ctx->variables = ctx.variables; /* In case head was modified */ } wordfree(&p); } @@ -100,7 +95,7 @@ static int criteria_next_state; * commands.c for matching target windows of a command. * */ -CFGFUN(criteria_init, int _state) { +CFGFUN(criteria_init, const int _state) { criteria_next_state = _state; DLOG("Initializing criteria, current_match = %p, state = %d\n", current_match, _state); @@ -133,33 +128,45 @@ i3_event_state_mask_t event_state_from_str(const char *str) { /* It might be better to use strtok() here, but the simpler strstr() should * do for now. */ i3_event_state_mask_t result = 0; - if (str == NULL) + if (str == NULL) { return result; - if (strstr(str, "Mod1") != NULL) + } + if (strstr(str, "Mod1") != NULL) { result |= XCB_KEY_BUT_MASK_MOD_1; - if (strstr(str, "Mod2") != NULL) + } + if (strstr(str, "Mod2") != NULL) { result |= XCB_KEY_BUT_MASK_MOD_2; - if (strstr(str, "Mod3") != NULL) + } + if (strstr(str, "Mod3") != NULL) { result |= XCB_KEY_BUT_MASK_MOD_3; - if (strstr(str, "Mod4") != NULL) + } + if (strstr(str, "Mod4") != NULL) { result |= XCB_KEY_BUT_MASK_MOD_4; - if (strstr(str, "Mod5") != NULL) + } + if (strstr(str, "Mod5") != NULL) { result |= XCB_KEY_BUT_MASK_MOD_5; + } if (strstr(str, "Control") != NULL || - strstr(str, "Ctrl") != NULL) + strstr(str, "Ctrl") != NULL) { result |= XCB_KEY_BUT_MASK_CONTROL; - if (strstr(str, "Shift") != NULL) + } + if (strstr(str, "Shift") != NULL) { result |= XCB_KEY_BUT_MASK_SHIFT; + } - if (strstr(str, "Group1") != NULL) + if (strstr(str, "Group1") != NULL) { result |= (I3_XKB_GROUP_MASK_1 << 16); + } if (strstr(str, "Group2") != NULL || - strstr(str, "Mode_switch") != NULL) + strstr(str, "Mode_switch") != NULL) { result |= (I3_XKB_GROUP_MASK_2 << 16); - if (strstr(str, "Group3") != NULL) + } + if (strstr(str, "Group3") != NULL) { result |= (I3_XKB_GROUP_MASK_3 << 16); - if (strstr(str, "Group4") != NULL) + } + if (strstr(str, "Group4") != NULL) { result |= (I3_XKB_GROUP_MASK_4 << 16); + } return result; } @@ -219,6 +226,10 @@ CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *comman } CFGFUN(for_window, const char *command) { + if (current_match->error != NULL) { + ELOG("match has error: %s\n", current_match->error); + return; + } if (match_is_empty(current_match)) { ELOG("Match is empty, ignoring this for_window statement\n"); return; @@ -231,7 +242,7 @@ 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) { +static void apply_gaps(gaps_t *gaps, const gaps_mask_t mask, const int value) { if (gaps == NULL) { return; } @@ -283,19 +294,26 @@ static void create_gaps_assignment(const char *workspace, const gaps_mask_t mask static gaps_mask_t gaps_scope_to_mask(const char *scope) { if (!strcmp(scope, "inner")) { return GAPS_INNER; - } else if (!strcmp(scope, "outer")) { + } + if (!strcmp(scope, "outer")) { return GAPS_OUTER; - } else if (!strcmp(scope, "vertical")) { + } + if (!strcmp(scope, "vertical")) { return GAPS_VERTICAL; - } else if (!strcmp(scope, "horizontal")) { + } + if (!strcmp(scope, "horizontal")) { return GAPS_HORIZONTAL; - } else if (!strcmp(scope, "top")) { + } + if (!strcmp(scope, "top")) { return GAPS_TOP; - } else if (!strcmp(scope, "right")) { + } + if (!strcmp(scope, "right")) { return GAPS_RIGHT; - } else if (!strcmp(scope, "bottom")) { + } + if (!strcmp(scope, "bottom")) { return GAPS_BOTTOM; - } else if (!strcmp(scope, "left")) { + } + if (!strcmp(scope, "left")) { return GAPS_LEFT; } ELOG("Invalid command, cannot process scope %s", scope); @@ -303,8 +321,8 @@ static gaps_mask_t gaps_scope_to_mask(const char *scope) { } 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); + const int pixels = logical_px(value); + const gaps_mask_t mask = gaps_scope_to_mask(scope); if (workspace == NULL) { apply_gaps(&config.gaps, mask, pixels); @@ -314,17 +332,25 @@ CFGFUN(gaps, const char *workspace, const char *scope, const long value) { } 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; + if (!strcmp(enable, "no_gaps")) { + config.hide_edge_borders = HEBM_SMART_NO_GAPS; + } else if (boolstr(enable)) { + if (config.hide_edge_borders == HEBM_NONE) { + /* Only enable this if hide_edge_borders is at the default value as it otherwise takes precedence */ + config.hide_edge_borders = HEBM_SMART; + } else { + ELOG("Both hide_edge_borders and smart_borders was used. " + "Ignoring smart_borders as it is deprecated.\n"); + } + } } CFGFUN(smart_gaps, const char *enable) { - if (!strcmp(enable, "inverse_outer")) + if (!strcmp(enable, "inverse_outer")) { config.smart_gaps = SMART_GAPS_INVERSE_OUTER; - else + } else { config.smart_gaps = boolstr(enable) ? SMART_GAPS_ON : SMART_GAPS_OFF; + } } CFGFUN(floating_minimum_size, const long width, const long height) { @@ -341,23 +367,29 @@ CFGFUN(floating_modifier, const char *modifiers) { config.floating_modifier = event_state_from_str(modifiers); } +CFGFUN(tiling_drag_swap_modifier, const char *modifiers) { + config.swap_modifier = event_state_from_str(modifiers); +} + CFGFUN(default_orientation, const char *orientation) { - if (strcmp(orientation, "horizontal") == 0) + if (strcmp(orientation, "horizontal") == 0) { config.default_orientation = HORIZ; - else if (strcmp(orientation, "vertical") == 0) + } else if (strcmp(orientation, "vertical") == 0) { config.default_orientation = VERT; - else + } else { config.default_orientation = NO_ORIENTATION; + } } CFGFUN(workspace_layout, const char *layout) { - if (strcmp(layout, "default") == 0) + if (strcmp(layout, "default") == 0) { config.default_layout = L_DEFAULT; - else if (strcmp(layout, "stacking") == 0 || - strcmp(layout, "stacked") == 0) + } else if (strcmp(layout, "stacking") == 0 || + strcmp(layout, "stacked") == 0) { config.default_layout = L_STACKED; - else + } else { config.default_layout = L_TABBED; + } } CFGFUN(default_border, const char *windowtype, const char *border, const long width) { @@ -393,22 +425,23 @@ CFGFUN(default_border, const char *windowtype, const char *border, const long wi } CFGFUN(hide_edge_borders, const char *borders) { - if (strcmp(borders, "smart_no_gaps") == 0) + if (strcmp(borders, "smart_no_gaps") == 0) { config.hide_edge_borders = HEBM_SMART_NO_GAPS; - else if (strcmp(borders, "smart") == 0) + } else if (strcmp(borders, "smart") == 0) { config.hide_edge_borders = HEBM_SMART; - else if (strcmp(borders, "vertical") == 0) + } else if (strcmp(borders, "vertical") == 0) { config.hide_edge_borders = HEBM_VERTICAL; - else if (strcmp(borders, "horizontal") == 0) + } else if (strcmp(borders, "horizontal") == 0) { config.hide_edge_borders = HEBM_HORIZONTAL; - else if (strcmp(borders, "both") == 0) + } else if (strcmp(borders, "both") == 0) { config.hide_edge_borders = HEBM_BOTH; - else if (strcmp(borders, "none") == 0) + } else if (strcmp(borders, "none") == 0) { config.hide_edge_borders = HEBM_NONE; - else if (boolstr(borders)) + } else if (boolstr(borders)) { config.hide_edge_borders = HEBM_VERTICAL; - else + } else { config.hide_edge_borders = HEBM_NONE; + } } CFGFUN(focus_follows_mouse, const char *value) { @@ -416,10 +449,11 @@ CFGFUN(focus_follows_mouse, const char *value) { } CFGFUN(mouse_warping, const char *value) { - if (strcmp(value, "none") == 0) + if (strcmp(value, "none") == 0) { config.mouse_warping = POINTER_WARPING_NONE; - else if (strcmp(value, "output") == 0) + } else if (strcmp(value, "output") == 0) { config.mouse_warping = POINTER_WARPING_OUTPUT; + } } CFGFUN(force_xinerama, const char *value) { @@ -469,15 +503,15 @@ CFGFUN(force_display_urgency_hint, const long duration_ms) { } CFGFUN(focus_on_window_activation, const char *mode) { - if (strcmp(mode, "smart") == 0) + if (strcmp(mode, "smart") == 0) { config.focus_on_window_activation = FOWA_SMART; - else if (strcmp(mode, "urgent") == 0) + } else if (strcmp(mode, "urgent") == 0) { config.focus_on_window_activation = FOWA_URGENT; - else if (strcmp(mode, "focus") == 0) + } else if (strcmp(mode, "focus") == 0) { config.focus_on_window_activation = FOWA_FOCUS; - else if (strcmp(mode, "none") == 0) + } else if (strcmp(mode, "none") == 0) { config.focus_on_window_activation = FOWA_NONE; - else { + } else { ELOG("Unknown focus_on_window_activation mode \"%s\", ignoring it.\n", mode); return; } @@ -507,9 +541,9 @@ CFGFUN(workspace, const char *workspace, const char *output) { struct Workspace_Assignment *assignment; /* When a new workspace line is encountered, for the first output word, - * $workspace from the config.spec is non-NULL. Afterwards, the parser calls - * clear_stack() because of the call line. Thus, we have to preserve the - * workspace string. */ + * $workspace from the config.spec is non-NULL. Afterward, the parser calls + * parser_clear_stack() because of the call line. Thus, we have to preserve + * the workspace string. */ if (workspace) { FREE(current_workspace); @@ -555,6 +589,8 @@ CFGFUN(popup_during_fullscreen, const char *value) { config.popup_during_fullscreen = PDF_IGNORE; } else if (strcmp(value, "leave_fullscreen") == 0) { config.popup_during_fullscreen = PDF_LEAVE_FULLSCREEN; + } else if (strcmp(value, "all") == 0) { + config.popup_during_fullscreen = PDF_ALL; } else { config.popup_during_fullscreen = PDF_SMART; } @@ -601,6 +637,10 @@ CFGFUN(color, const char *colorclass, const char *border, const char *background } CFGFUN(assign_output, const char *output) { + if (current_match->error != NULL) { + ELOG("match has error: %s\n", current_match->error); + return; + } if (match_is_empty(current_match)) { ELOG("Match is empty, ignoring this assignment\n"); return; @@ -619,7 +659,11 @@ CFGFUN(assign_output, const char *output) { TAILQ_INSERT_TAIL(&assignments, assignment, assignments); } -CFGFUN(assign, const char *workspace, bool is_number) { +CFGFUN(assign, const char *workspace, const bool is_number) { + if (current_match->error != NULL) { + ELOG("match has error: %s\n", current_match->error); + return; + } if (match_is_empty(current_match)) { ELOG("Match is empty, ignoring this assignment\n"); return; @@ -644,6 +688,10 @@ CFGFUN(assign, const char *workspace, bool is_number) { } CFGFUN(no_focus) { + if (current_match->error != NULL) { + ELOG("match has error: %s\n", current_match->error); + return; + } if (match_is_empty(current_match)) { ELOG("Match is empty, ignoring this assignment\n"); return; @@ -703,7 +751,7 @@ CFGFUN(bar_id, const char *bar_id) { } CFGFUN(bar_output, const char *output) { - int new_outputs = current_bar->num_outputs + 1; + const int new_outputs = current_bar->num_outputs + 1; current_bar->outputs = srealloc(current_bar->outputs, sizeof(char *) * new_outputs); current_bar->outputs[current_bar->num_outputs] = sstrdup(output); current_bar->num_outputs = new_outputs; @@ -767,7 +815,7 @@ static void bar_configure_binding(const char *button, const char *release, const return; } - int input_code = atoi(button + strlen("button")); + const int input_code = atoi(button + strlen("button")); if (input_code < 1) { ELOG("Button \"%s\" does not seem to be in format 'buttonX'.\n", button); return; @@ -790,12 +838,12 @@ static void bar_configure_binding(const char *button, const char *release, const } CFGFUN(bar_wheel_up_cmd, const char *command) { - ELOG("'wheel_up_cmd' is deprecated. Please us 'bindsym button4 %s' instead.\n", command); + ELOG("'wheel_up_cmd' is deprecated. Please use 'bindsym button4 %s' instead.\n", command); bar_configure_binding("button4", NULL, command); } CFGFUN(bar_wheel_down_cmd, const char *command) { - ELOG("'wheel_down_cmd' is deprecated. Please us 'bindsym button5 %s' instead.\n", command); + ELOG("'wheel_down_cmd' is deprecated. Please use 'bindsym button5 %s' instead.\n", command); bar_configure_binding("button5", NULL, command); } @@ -854,18 +902,19 @@ CFGFUN(bar_tray_padding, const long padding_px) { } CFGFUN(bar_color_single, const char *colorclass, const char *color) { - if (strcmp(colorclass, "background") == 0) + if (strcmp(colorclass, "background") == 0) { current_bar->colors.background = sstrdup(color); - else if (strcmp(colorclass, "separator") == 0) + } else if (strcmp(colorclass, "separator") == 0) { current_bar->colors.separator = sstrdup(color); - else if (strcmp(colorclass, "statusline") == 0) + } else if (strcmp(colorclass, "statusline") == 0) { current_bar->colors.statusline = sstrdup(color); - else if (strcmp(colorclass, "focused_background") == 0) + } else if (strcmp(colorclass, "focused_background") == 0) { current_bar->colors.focused_background = sstrdup(color); - else if (strcmp(colorclass, "focused_separator") == 0) + } else if (strcmp(colorclass, "focused_separator") == 0) { current_bar->colors.focused_separator = sstrdup(color); - else + } else { current_bar->colors.focused_statusline = sstrdup(color); + } } CFGFUN(bar_status_command, const char *command) { @@ -873,6 +922,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); } @@ -894,7 +948,7 @@ CFGFUN(bar_strip_workspace_name, const char *value) { } CFGFUN(bar_start) { - current_bar = scalloc(1, sizeof(struct Barconfig)); + current_bar = scalloc(1, sizeof(Barconfig)); TAILQ_INIT(&(current_bar->bar_bindings)); TAILQ_INIT(&(current_bar->tray_outputs)); current_bar->tray_padding = 2; @@ -904,8 +958,9 @@ CFGFUN(bar_start) { CFGFUN(bar_finish) { DLOG("\t new bar configuration finished, saving.\n"); /* Generate a unique ID for this bar if not already configured */ - if (current_bar->id == NULL) + if (current_bar->id == NULL) { sasprintf(¤t_bar->id, "bar-%d", config.number_barconfigs); + } config.number_barconfigs++; diff --git a/src/config_parser.c b/src/config_parser.c index baf5848a..c417aae4 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -1,10 +1,10 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * - * config_parser.c: hand-written parser to parse configuration directives. + * config_parser.c: handwritten parser to parse configuration directives. * * See also src/commands_parser.c for rationale on why we use a custom parser. * @@ -72,90 +72,6 @@ typedef struct tokenptr { #include "GENERATED_config_tokens.h" -/* - * Pushes a string (identified by 'identifier') on the stack. We simply use a - * single array, since the number of entries we have to store is very small. - * - */ -static void push_string(struct stack *ctx, const char *identifier, const char *str) { - for (int c = 0; c < 10; c++) { - if (ctx->stack[c].identifier != NULL && - strcmp(ctx->stack[c].identifier, identifier) != 0) - continue; - if (ctx->stack[c].identifier == NULL) { - /* Found a free slot, let’s store it here. */ - ctx->stack[c].identifier = identifier; - ctx->stack[c].val.str = sstrdup(str); - ctx->stack[c].type = STACK_STR; - } else { - /* Append the value. */ - char *prev = ctx->stack[c].val.str; - sasprintf(&(ctx->stack[c].val.str), "%s,%s", prev, str); - free(prev); - } - return; - } - - /* When we arrive here, the stack is full. This should not happen and - * means there’s either a bug in this parser or the specification - * contains a command with more than 10 identified tokens. */ - fprintf(stderr, "BUG: config_parser stack full. This means either a bug " - "in the code, or a new command which contains more than " - "10 identified tokens.\n"); - exit(EXIT_FAILURE); -} - -static void push_long(struct stack *ctx, const char *identifier, long num) { - for (int c = 0; c < 10; c++) { - if (ctx->stack[c].identifier != NULL) { - continue; - } - /* Found a free slot, let’s store it here. */ - ctx->stack[c].identifier = identifier; - ctx->stack[c].val.num = num; - ctx->stack[c].type = STACK_LONG; - return; - } - - /* When we arrive here, the stack is full. This should not happen and - * means there’s either a bug in this parser or the specification - * contains a command with more than 10 identified tokens. */ - fprintf(stderr, "BUG: config_parser stack full. This means either a bug " - "in the code, or a new command which contains more than " - "10 identified tokens.\n"); - exit(EXIT_FAILURE); -} - -static const char *get_string(struct stack *ctx, const char *identifier) { - for (int c = 0; c < 10; c++) { - if (ctx->stack[c].identifier == NULL) - break; - if (strcmp(identifier, ctx->stack[c].identifier) == 0) - return ctx->stack[c].val.str; - } - return NULL; -} - -static long get_long(struct stack *ctx, const char *identifier) { - for (int c = 0; c < 10; c++) { - if (ctx->stack[c].identifier == NULL) - break; - if (strcmp(identifier, ctx->stack[c].identifier) == 0) - return ctx->stack[c].val.num; - } - return 0; -} - -static void clear_stack(struct stack *ctx) { - for (int c = 0; c < 10; c++) { - if (ctx->stack[c].type == STACK_STR) - free(ctx->stack[c].val.str); - ctx->stack[c].identifier = NULL; - ctx->stack[c].val.str = NULL; - ctx->stack[c].val.num = 0; - } -} - /******************************************************************************* * The parser itself. ******************************************************************************/ @@ -174,12 +90,12 @@ static void next_state(const cmdp_token *token, struct parser_ctx *ctx) { ctx->has_errors = true; } _next_state = subcommand_output.next_state; - clear_stack(ctx->stack); + parser_clear_stack(ctx->stack); } ctx->state = _next_state; if (ctx->state == INITIAL) { - clear_stack(ctx->stack); + parser_clear_stack(ctx->stack); } /* See if we are jumping back to a state in which we were in previously @@ -218,8 +134,9 @@ static const char *start_of_line(const char *walk, const char *beginning) { static char *single_line(const char *start) { char *result = sstrdup(start); char *end = strchr(result, '\n'); - if (end != NULL) + if (end != NULL) { *end = '\0'; + } return result; } @@ -229,7 +146,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte const char *dumpwalk = input; int linecnt = 1; while (*dumpwalk != '\0') { - char *next_nl = strchr(dumpwalk, '\n'); + const char *next_nl = strchr(dumpwalk, '\n'); if (next_nl != NULL) { DLOG("CONFIG(line %3d): %.*s\n", linecnt, (int)(next_nl - dumpwalk), dumpwalk); dumpwalk = next_nl + 1; @@ -264,10 +181,11 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte while ((size_t)(walk - input) <= len) { /* Skip whitespace before every token, newlines are relevant since they * separate configuration directives. */ - while ((*walk == ' ' || *walk == '\t') && *walk != '\0') + while ((*walk == ' ' || *walk == '\t') && *walk != '\0') { walk++; + } - cmdp_token_ptr *ptr = &(tokens[ctx->state]); + const cmdp_token_ptr *ptr = &(tokens[ctx->state]); token_handled = false; for (c = 0; c < ptr->n; c++) { token = &(ptr->array[c]); @@ -276,7 +194,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte if (token->name[0] == '\'') { if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) { if (token->identifier != NULL) { - push_string(ctx->stack, token->identifier, token->name + 1); + parser_push_string(ctx->stack, token->identifier, token->name + 1); } walk += strlen(token->name) - 1; next_state(token, ctx); @@ -290,17 +208,19 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte /* Handle numbers. We only accept decimal numbers for now. */ char *end = NULL; errno = 0; - long int num = strtol(walk, &end, 10); + const long int num = strtol(walk, &end, 10); if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) || - (errno != 0 && num == 0)) + (errno != 0 && num == 0)) { continue; + } /* No valid numbers found */ - if (end == walk) + if (end == walk) { continue; + } if (token->identifier != NULL) { - push_long(ctx->stack, token->identifier, num); + parser_push_long(ctx->stack, token->identifier, num); } /* Set walk to the first non-number character */ @@ -317,12 +237,14 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte if (*walk == '"') { beginning++; walk++; - while (*walk != '\0' && (*walk != '"' || *(walk - 1) == '\\')) + while (*walk != '\0' && (*walk != '"' || *(walk - 1) == '\\')) { walk++; + } } else { if (token->name[0] == 's') { - while (*walk != '\0' && *walk != '\r' && *walk != '\n') + while (*walk != '\0' && *walk != '\r' && *walk != '\n') { walk++; + } } else { /* For a word, the delimiters are white space (' ' or * '\t'), closing square bracket (]), comma (,) and @@ -330,8 +252,9 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte while (*walk != ' ' && *walk != '\t' && *walk != ']' && *walk != ',' && *walk != ';' && *walk != '\r' && - *walk != '\n' && *walk != '\0') + *walk != '\n' && *walk != '\0') { walk++; + } } } if (walk != beginning) { @@ -344,18 +267,20 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte /* We only handle escaped double quotes to not break * backwards compatibility with people using \w in * regular expressions etc. */ - if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"') + if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"') { inpos++; + } str[outpos] = beginning[inpos]; } if (token->identifier) { - push_string(ctx->stack, token->identifier, str); + parser_push_string(ctx->stack, token->identifier, str); } free(str); /* If we are at the end of a quoted string, skip the ending * double quote. */ - if (*walk == '"') + if (*walk == '"') { walk++; + } next_state(token, ctx); token_handled = true; break; @@ -363,8 +288,9 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte } if (strcmp(token->name, "line") == 0) { - while (*walk != '\0' && *walk != '\n' && *walk != '\r') + while (*walk != '\0' && *walk != '\n' && *walk != '\r') { walk++; + } next_state(token, ctx); token_handled = true; linecnt++; @@ -392,10 +318,11 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte if (!token_handled) { /* Figure out how much memory we will need to fill in the names of - * all tokens afterwards. */ + * all tokens afterward. */ int tokenlen = 0; - for (c = 0; c < ptr->n; c++) + for (c = 0; c < ptr->n; c++) { tokenlen += strlen(ptr->array[c].name) + strlen("'', "); + } /* Build up a decent error message. We include the problem, the * full input, and underline the position where the parser @@ -415,8 +342,9 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte } else { /* Skip error tokens in error messages, they are used * internally only and might confuse users. */ - if (strcmp(token->name, "error") == 0) + if (strcmp(token->name, "error") == 0) { continue; + } /* Any other token is copied to the error message enclosed * with angle brackets. */ *tokenwalk++ = '<'; @@ -443,8 +371,9 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte const char *copywalk; for (copywalk = error_line; *copywalk != '\n' && *copywalk != '\r' && *copywalk != '\0'; - copywalk++) + copywalk++) { position[(copywalk - error_line)] = (copywalk >= walk ? '^' : (*copywalk == '\t' ? '\t' : ' ')); + } position[(copywalk - error_line)] = '\0'; ELOG("CONFIG: %s\n", errormessage); @@ -469,7 +398,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte free(error_copy); /* Print context lines *after* the error, if any. */ for (int i = 0; i < 2; i++) { - char *error_line_end = strchr(error_line, '\n'); + const char *error_line_end = strchr(error_line, '\n'); if (error_line_end != NULL && *(error_line_end + 1) != '\0') { error_line = error_line_end + 1; error_copy = single_line(error_line); @@ -481,22 +410,24 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte context->has_errors = true; /* Skip the rest of this line, but continue parsing. */ - while ((size_t)(walk - input) <= len && *walk != '\n') + while ((size_t)(walk - input) <= len && *walk != '\n') { walk++; + } free(position); free(errormessage); - clear_stack(ctx->stack); + parser_clear_stack(ctx->stack); /* To figure out in which state to go (e.g. MODE or INITIAL), * we find the nearest state which contains an token * and follow that one. */ bool error_token_found = false; for (int i = ctx->statelist_idx - 1; (i >= 0) && !error_token_found; i--) { - cmdp_token_ptr *errptr = &(tokens[ctx->statelist[i]]); + const cmdp_token_ptr *errptr = &(tokens[ctx->statelist[i]]); for (int j = 0; j < errptr->n; j++) { - if (strcmp(errptr->array[j].name, "error") != 0) + if (strcmp(errptr->array[j].name, "error") != 0) { continue; + } next_state(&(errptr->array[j]), ctx); error_token_found = true; break; @@ -560,7 +491,6 @@ int main(int argc, char *argv[]) { memset(&stack, '\0', sizeof(struct stack)); struct parser_ctx ctx = { .use_nagbar = false, - .assume_v4 = false, .stack = &stack, }; SLIST_INIT(&(ctx.variables)); @@ -571,179 +501,10 @@ int main(int argc, char *argv[]) { #else -/* - * Goes through each line of buf (separated by \n) and checks for statements / - * commands which only occur in i3 v4 configuration files. If it finds any, it - * returns version 4, otherwise it returns version 3. - * - */ -static int detect_version(char *buf) { - char *walk = buf; - char *line = buf; - while (*walk != '\0') { - if (*walk != '\n') { - walk++; - continue; - } - - /* check for some v4-only statements */ - if (strncasecmp(line, "bindcode", strlen("bindcode")) == 0 || - strncasecmp(line, "include", strlen("include")) == 0 || - strncasecmp(line, "force_focus_wrapping", strlen("force_focus_wrapping")) == 0 || - strncasecmp(line, "# i3 config file (v4)", strlen("# i3 config file (v4)")) == 0 || - strncasecmp(line, "workspace_layout", strlen("workspace_layout")) == 0) { - LOG("deciding for version 4 due to this line: %.*s\n", (int)(walk - line), line); - return 4; - } - - /* if this is a bind statement, we can check the command */ - if (strncasecmp(line, "bind", strlen("bind")) == 0) { - char *bind = strchr(line, ' '); - if (bind == NULL) - goto next; - while ((*bind == ' ' || *bind == '\t') && *bind != '\0') - bind++; - if (*bind == '\0') - goto next; - if ((bind = strchr(bind, ' ')) == NULL) - goto next; - while ((*bind == ' ' || *bind == '\t') && *bind != '\0') - bind++; - if (*bind == '\0') - goto next; - if (strncasecmp(bind, "layout", strlen("layout")) == 0 || - strncasecmp(bind, "floating", strlen("floating")) == 0 || - strncasecmp(bind, "workspace", strlen("workspace")) == 0 || - strncasecmp(bind, "focus left", strlen("focus left")) == 0 || - strncasecmp(bind, "focus right", strlen("focus right")) == 0 || - strncasecmp(bind, "focus up", strlen("focus up")) == 0 || - strncasecmp(bind, "focus down", strlen("focus down")) == 0 || - strncasecmp(bind, "border normal", strlen("border normal")) == 0 || - strncasecmp(bind, "border 1pixel", strlen("border 1pixel")) == 0 || - strncasecmp(bind, "border pixel", strlen("border pixel")) == 0 || - strncasecmp(bind, "border borderless", strlen("border borderless")) == 0 || - strncasecmp(bind, "--no-startup-id", strlen("--no-startup-id")) == 0 || - strncasecmp(bind, "bar", strlen("bar")) == 0) { - LOG("deciding for version 4 due to this line: %.*s\n", (int)(walk - line), line); - return 4; - } - } - - next: - /* advance to the next line */ - walk++; - line = walk; - } - - return 3; -} - -/* - * Calls i3-migrate-config-to-v4 to migrate a configuration file (input - * buffer). - * - * Returns the converted config file or NULL if there was an error (for - * example the script could not be found in $PATH or the i3 executable’s - * directory). - * - */ -static char *migrate_config(char *input, off_t size) { - int writepipe[2]; - int readpipe[2]; - - if (pipe(writepipe) != 0 || - pipe(readpipe) != 0) { - warn("migrate_config: Could not create pipes"); - return NULL; - } - - pid_t pid = fork(); - if (pid == -1) { - warn("Could not fork()"); - return NULL; - } - - /* child */ - if (pid == 0) { - /* close writing end of writepipe, connect reading side to stdin */ - close(writepipe[1]); - dup2(writepipe[0], 0); - - /* close reading end of readpipe, connect writing side to stdout */ - close(readpipe[0]); - dup2(readpipe[1], 1); - - static char *argv[] = { - NULL, /* will be replaced by the executable path */ - NULL}; - exec_i3_utility("i3-migrate-config-to-v4", argv); - } - - /* parent */ - - /* close reading end of the writepipe (connected to the script’s stdin) */ - close(writepipe[0]); - - /* write the whole config file to the pipe, the script will read everything - * immediately */ - if (writeall(writepipe[1], input, size) == -1) { - warn("Could not write to pipe"); - return NULL; - } - close(writepipe[1]); - - /* close writing end of the readpipe (connected to the script’s stdout) */ - close(readpipe[1]); - - /* read the script’s output */ - int conv_size = 65535; - char *converted = scalloc(conv_size, 1); - int read_bytes = 0, ret; - do { - if (read_bytes == conv_size) { - conv_size += 65535; - converted = srealloc(converted, conv_size); - } - ret = read(readpipe[0], converted + read_bytes, conv_size - read_bytes); - if (ret == -1) { - warn("Cannot read from pipe"); - FREE(converted); - return NULL; - } - read_bytes += ret; - } while (ret > 0); - - /* get the returncode */ - int status; - wait(&status); - if (!WIFEXITED(status)) { - fprintf(stderr, "Child did not terminate normally, using old config file (will lead to broken behaviour)\n"); - FREE(converted); - return NULL; - } - - int returncode = WEXITSTATUS(status); - if (returncode != 0) { - fprintf(stderr, "Migration process exit code was != 0\n"); - if (returncode == 2) { - fprintf(stderr, "could not start the migration script\n"); - /* TODO: script was not found. tell the user to fix their system or create a v4 config */ - } else if (returncode == 1) { - fprintf(stderr, "This already was a v4 config. Please add the following line to your config file:\n"); - fprintf(stderr, "# i3 config file (v4)\n"); - /* TODO: nag the user with a message to include a hint for i3 in their config file */ - } - FREE(converted); - return NULL; - } - - return converted; -} - /** * Launch nagbar to indicate errors in the configuration file. */ -void start_config_error_nagbar(const char *configpath, bool has_errors) { +void start_config_error_nagbar(const char *configpath, const bool has_errors) { char *editaction, *pageraction; sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", configpath); sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename); @@ -793,8 +554,9 @@ static void upsert_variable(struct variables_head *variables, char *key, char *v /* ensure that the correct variable is matched in case of one being * the prefix of another */ SLIST_FOREACH (test, variables, variables) { - if (strlen(new->key) >= strlen(test->key)) + if (strlen(new->key) >= strlen(test->key)) { break; + } loc = test; } @@ -805,7 +567,7 @@ static void upsert_variable(struct variables_head *variables, char *key, char *v } } -static char *get_resource(char *name) { +static char *get_resource(const char *name) { if (conn == NULL) { return NULL; } @@ -835,9 +597,8 @@ static char *get_resource(char *name) { * */ void free_variables(struct parser_ctx *ctx) { - struct Variable *current; while (!SLIST_EMPTY(&(ctx->variables))) { - current = SLIST_FIRST(&(ctx->variables)); + struct Variable *current = SLIST_FIRST(&(ctx->variables)); FREE(current->key); FREE(current->value); SLIST_REMOVE_HEAD(&(ctx->variables), variables); @@ -845,59 +606,62 @@ void free_variables(struct parser_ctx *ctx) { } } -/* - * Parses the given file by first replacing the variables, then calling - * parse_config and possibly launching i3-nagbar. - * - */ -parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFile *included_file) { - int fd; - struct stat stbuf; - char *buf; - FILE *fstr; - char buffer[4096], key[512], value[4096], *continuation = NULL; - - char *old_dir = getcwd(NULL, 0); +static bool try_chdir(const char *path) { + bool result = true; char *dir = NULL; /* dirname(3) might modify the buffer, so make a copy: */ - char *dirbuf = sstrdup(f); + char *dirbuf = sstrdup(path); if ((dir = dirname(dirbuf)) != NULL) { LOG("Changing working directory to config file directory %s\n", dir); if (chdir(dir) == -1) { ELOG("chdir(%s) failed: %s\n", dir, strerror(errno)); - return PARSE_FILE_FAILED; + result = false; } } - free(dirbuf); + FREE(dirbuf); + return result; +} +static parse_file_result_t parse_file_inner(struct parser_ctx *ctx, const char *f, IncludedFile *included_file) { + if (!try_chdir(f)) { + return PARSE_FILE_FAILED; + } + + int fd; if ((fd = open(f, O_RDONLY)) == -1) { return PARSE_FILE_FAILED; } + struct stat stbuf; if (fstat(fd, &stbuf) == -1) { + close(fd); return PARSE_FILE_FAILED; } - buf = scalloc(stbuf.st_size + 1, 1); - + FILE *fstr; if ((fstr = fdopen(fd, "r")) == NULL) { return PARSE_FILE_FAILED; } included_file->raw_contents = scalloc(stbuf.st_size + 1, 1); if ((ssize_t)fread(included_file->raw_contents, 1, stbuf.st_size, fstr) != stbuf.st_size) { + fclose(fstr); return PARSE_FILE_FAILED; } rewind(fstr); + char buffer[4096], key[512], value[4096], *continuation = NULL; bool invalid_sets = false; - + char *buf = scalloc(stbuf.st_size + 1, 1); while (!feof(fstr)) { - if (!continuation) + if (!continuation) { continuation = buffer; + } if (fgets(continuation, sizeof(buffer) - (continuation - buffer), fstr) == NULL) { - if (feof(fstr)) + if (feof(fstr)) { break; + } + fclose(fstr); return PARSE_FILE_FAILED; } if (buffer[strlen(buffer) - 1] != '\n' && !feof(fstr)) { @@ -919,7 +683,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) { @@ -980,7 +744,7 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFi continue; } } - fclose(fstr); + fclose(fstr); /* No need to close(fd) */ if (database != NULL) { xcb_xrm_database_free(database); @@ -990,22 +754,21 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFi /* For every custom variable, see how often it occurs in the file and * how much extra bytes it requires when replaced. */ - struct Variable *current, *nearest; + struct Variable *current; int extra_bytes = 0; /* We need to copy the buffer because we need to invalidate the * variables (otherwise we will count them twice, which is bad when * 'extra' is negative) */ char *bufcopy = sstrdup(buf); SLIST_FOREACH (current, &(ctx->variables), variables) { - int extra = (strlen(current->value) - strlen(current->key)); - char *next; - for (next = bufcopy; + const int extra = (strlen(current->value) - strlen(current->key)); + for (char *next = bufcopy; next < (bufcopy + stbuf.st_size) && (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); + const char *end = next + strlen(current->key); while (next < end) { *next++ = ' '; } @@ -1016,19 +779,20 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFi /* Then, allocate a new buffer and copy the file over to the new one, * but replace occurrences of our variables */ - char *walk = buf, *destwalk; + const char *walk = buf; char *new = scalloc(stbuf.st_size + extra_bytes + 1, 1); - destwalk = new; + char *destwalk = new; while (walk < (buf + stbuf.st_size)) { /* Find the next variable */ SLIST_FOREACH (current, &(ctx->variables), variables) { current->next_match = strcasestr(walk, current->key); } - nearest = NULL; + const struct Variable *nearest = NULL; int distance = stbuf.st_size; SLIST_FOREACH (current, &(ctx->variables), variables) { - if (current->next_match == NULL) + if (current->next_match == NULL) { continue; + } if ((current->next_match - walk) < distance) { distance = (current->next_match - walk); nearest = current; @@ -1043,43 +807,12 @@ 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); } } - /* analyze the string to find out whether this is an old config file (3.x) - * or a new config file (4.x). If it’s old, we run the converter script. */ - int version = 4; - if (!ctx->assume_v4) { - version = detect_version(buf); - } - if (version == 3) { - /* We need to convert this v3 configuration */ - char *converted = migrate_config(new, strlen(new)); - if (converted != NULL) { - ELOG("\n"); - ELOG("****************************************************************\n"); - ELOG("NOTE: Automatically converted configuration file from v3 to v4.\n"); - ELOG("\n"); - ELOG("Please convert your config file to v4. You can use this command:\n"); - ELOG(" mv %s %s.O\n", f, f); - ELOG(" i3-migrate-config-to-v4 %s.O > %s\n", f, f); - ELOG("****************************************************************\n"); - ELOG("\n"); - free(new); - new = converted; - } else { - LOG("\n"); - LOG("**********************************************************************\n"); - LOG("ERROR: Could not convert config file. Maybe i3-migrate-config-to-v4\n"); - LOG("was not correctly installed on your system?\n"); - LOG("**********************************************************************\n"); - LOG("\n"); - } - } - included_file->variable_replaced_contents = sstrdup(new); struct context *context = scalloc(1, sizeof(struct context)); @@ -1093,9 +826,6 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFi if (ctx->use_nagbar && (context->has_errors || context->has_warnings || invalid_sets)) { ELOG("FYI: You are using i3 version %s\n", i3_version); - if (version == 3) - ELOG("Please convert your configfile first, then fix any remaining errors (see above).\n"); - start_config_error_nagbar(f, context->has_errors || invalid_sets); } @@ -1106,15 +836,28 @@ parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFi free(new); free(buf); - if (chdir(old_dir) == -1) { - ELOG("chdir(%s) failed: %s\n", old_dir, strerror(errno)); - return PARSE_FILE_FAILED; - } - free(old_dir); if (has_errors) { return PARSE_FILE_CONFIG_ERRORS; } return PARSE_FILE_SUCCESS; } +/* + * Parses the given file by first replacing the variables, then calling + * parse_config and possibly launching i3-nagbar. + * + */ +parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFile *included_file) { + char *old_dir = getcwd(NULL, 0); + + parse_file_result_t result = parse_file_inner(ctx, f, included_file); + + if (chdir(old_dir) == -1) { + ELOG("chdir(%s) failed: %s\n", old_dir, strerror(errno)); + result = PARSE_FILE_FAILED; + } + free(old_dir); + return result; +} + #endif diff --git a/src/display_version.c b/src/display_version.c index bfdbc8d6..f5cf74bf 100644 --- a/src/display_version.c +++ b/src/display_version.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * display_version.c: displays the running i3 version, runs as part of @@ -100,27 +100,31 @@ void display_running_version(void) { int sockfd = ipc_connect(NULL); if (ipc_send_message(sockfd, 0, I3_IPC_MESSAGE_TYPE_GET_VERSION, - (uint8_t *)"") == -1) + (uint8_t *)"") == -1) { err(EXIT_FAILURE, "IPC: write()"); + } uint32_t reply_length; uint32_t reply_type; uint8_t *reply; int ret; if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) { - if (ret == -1) + if (ret == -1) { err(EXIT_FAILURE, "IPC: read()"); + } exit(EXIT_FAILURE); } - if (reply_type != I3_IPC_MESSAGE_TYPE_GET_VERSION) + if (reply_type != I3_IPC_MESSAGE_TYPE_GET_VERSION) { errx(EXIT_FAILURE, "Got reply type %d, but expected %d (GET_VERSION)", reply_type, I3_IPC_MESSAGE_TYPE_GET_VERSION); + } yajl_handle handle = yajl_alloc(&version_callbacks, NULL, NULL); - yajl_status state = yajl_parse(handle, (const unsigned char *)reply, (int)reply_length); - if (state != yajl_status_ok) + yajl_status state = yajl_parse(handle, reply, (int)reply_length); + if (state != yajl_status_ok) { errx(EXIT_FAILURE, "Could not parse my own reply. That's weird. reply is %.*s", (int)reply_length, reply); + } printf("\r\x1b[K"); printf("Running i3 version: %s (pid %s)\n", human_readable_version, pid_from_atom); @@ -146,8 +150,9 @@ void display_running_version(void) { destpath_size = destpath_size * 2; destpath = srealloc(destpath, destpath_size); } - if (linksize == -1) + if (linksize == -1) { err(EXIT_FAILURE, "readlink(%s)", exepath); + } /* readlink() does not NULL-terminate strings, so we have to. */ destpath[linksize] = '\0'; @@ -162,16 +167,18 @@ void display_running_version(void) { destpath_size = destpath_size * 2; destpath = srealloc(destpath, destpath_size); } - if (linksize == -1) + if (linksize == -1) { err(EXIT_FAILURE, "readlink(%s)", exepath); + } /* readlink() does not NULL-terminate strings, so we have to. */ destpath[linksize] = '\0'; /* Check if "(deleted)" is the readlink result. If so, the running version * does not match the file on disk. */ - if (strstr(destpath, "(deleted)") != NULL) + if (strstr(destpath, "(deleted)") != NULL) { printf("RUNNING BINARY DIFFERENT FROM BINARY ON DISK!\n"); + } /* Since readlink() might put a "(deleted)" somewhere in the buffer and * stripping that out seems hackish and ugly, we read the process’s argv[0] @@ -180,10 +187,12 @@ void display_running_version(void) { sasprintf(&exepath, "/proc/%s/cmdline", pid_from_atom); int fd; - if ((fd = open(exepath, O_RDONLY)) == -1) + if ((fd = open(exepath, O_RDONLY)) == -1) { err(EXIT_FAILURE, "open(%s)", exepath); - if (read(fd, destpath, sizeof(destpath)) == -1) + } + if (read(fd, destpath, sizeof(destpath)) == -1) { err(EXIT_FAILURE, "read(%s)", exepath); + } close(fd); printf("The i3 binary you are running: %s\n", destpath); diff --git a/src/drag.c b/src/drag.c index 582dbb17..de29aa2e 100644 --- a/src/drag.c +++ b/src/drag.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * drag.c: click and drag. @@ -103,8 +103,9 @@ static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) { break; } - if (last_motion_notify != (xcb_motion_notify_event_t *)event) + if (last_motion_notify != (xcb_motion_notify_event_t *)event) { free(event); + } if (dragloop->result != DRAGGING) { ev_break(EV_A_ EVBREAK_ONE); @@ -154,7 +155,7 @@ static bool drain_drag_events(EV_P, struct drag_x11_cb *dragloop) { } static void xcb_drag_prepare_cb(EV_P_ ev_prepare *w, int revents) { - struct drag_x11_cb *dragloop = (struct drag_x11_cb *)w->data; + struct drag_x11_cb *dragloop = w->data; while (!drain_drag_events(EV_A, dragloop)) { /* repeatedly drain events: draining might produce additional ones */ } @@ -176,22 +177,21 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, xcb_window_t confine_to, int cursor, bool use_threshold, callback_t callback, const void *extra) { - xcb_cursor_t xcursor = cursor ? xcursor_get_cursor(cursor) : XCB_NONE; + const xcb_cursor_t xcursor = cursor ? xcursor_get_cursor(cursor) : XCB_NONE; /* Grab the pointer */ - xcb_grab_pointer_cookie_t cookie; xcb_grab_pointer_reply_t *reply; xcb_generic_error_t *error; - cookie = xcb_grab_pointer(conn, - false, /* get all pointer events specified by the following mask */ - root, /* grab the root window */ - XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */ - XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */ - XCB_GRAB_MODE_ASYNC, /* keyboard mode */ - confine_to, /* confine_to = in which window should the cursor stay */ - use_threshold ? XCB_NONE : xcursor, /* possibly display a special cursor */ - XCB_CURRENT_TIME); + xcb_grab_pointer_cookie_t cookie = xcb_grab_pointer(conn, + false, /* get all pointer events specified by the following mask */ + root, /* grab the root window */ + XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, /* which events to let through */ + XCB_GRAB_MODE_ASYNC, /* pointer events should continue as normal */ + XCB_GRAB_MODE_ASYNC, /* keyboard mode */ + confine_to, /* confine_to = in which window should the cursor stay */ + use_threshold ? XCB_NONE : xcursor, /* possibly display a special cursor */ + XCB_CURRENT_TIME); if ((reply = xcb_grab_pointer_reply(conn, cookie, &error)) == NULL) { ELOG("Could not grab pointer (error_code = %d)\n", error->error_code); @@ -202,15 +202,14 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, free(reply); /* Grab the keyboard */ - xcb_grab_keyboard_cookie_t keyb_cookie; xcb_grab_keyboard_reply_t *keyb_reply; - keyb_cookie = xcb_grab_keyboard(conn, - false, /* get all keyboard events */ - root, /* grab the root window */ - XCB_CURRENT_TIME, - XCB_GRAB_MODE_ASYNC, /* continue processing pointer events as normal */ - XCB_GRAB_MODE_ASYNC /* keyboard mode */ + xcb_grab_keyboard_cookie_t keyb_cookie = xcb_grab_keyboard(conn, + false, /* get all keyboard events */ + root, /* grab the root window */ + XCB_CURRENT_TIME, + XCB_GRAB_MODE_ASYNC, /* continue processing pointer events as normal */ + XCB_GRAB_MODE_ASYNC /* keyboard mode */ ); if ((keyb_reply = xcb_grab_keyboard_reply(conn, keyb_cookie, &error)) == NULL) { @@ -233,8 +232,9 @@ drag_result_t drag_pointer(Con *con, const xcb_button_press_event_t *event, .extra = extra, }; ev_prepare *prepare = &loop.prepare; - if (con) + if (con) { loop.old_rect = con->rect; + } ev_prepare_init(prepare, xcb_drag_prepare_cb); prepare->data = &loop; main_set_x11_cb(false); diff --git a/src/ewmh.c b/src/ewmh.c index ba91093d..3929e7c3 100644 --- a/src/ewmh.c +++ b/src/ewmh.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * ewmh.c: Get/set certain EWMH properties easily. @@ -138,8 +138,9 @@ static void ewmh_update_wm_desktop_recursively(Con *con, const uint32_t desktop) } } - if (!con_has_managed_window(con)) + if (!con_has_managed_window(con)) { return; + } uint32_t wm_desktop = desktop; /* Sticky windows are only actually sticky when they are floating or inside @@ -160,8 +161,9 @@ static void ewmh_update_wm_desktop_recursively(Con *con, const uint32_t desktop) } /* If this is the cached value, we don't need to do anything. */ - if (con->window->wm_desktop == wm_desktop) + if (con->window->wm_desktop == wm_desktop) { return; + } con->window->wm_desktop = wm_desktop; const xcb_window_t window = con->window->id; @@ -351,8 +353,9 @@ void ewmh_setup_hints(void) { * */ Con *ewmh_get_workspace_by_index(uint32_t idx) { - if (idx == NET_WM_DESKTOP_NONE) + if (idx == NET_WM_DESKTOP_NONE) { return NULL; + } uint32_t current_index = 0; diff --git a/src/fake_outputs.c b/src/fake_outputs.c index 6b68ef44..7aaca375 100644 --- a/src/fake_outputs.c +++ b/src/fake_outputs.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * Faking outputs is useful in pathological situations (like network X servers @@ -46,8 +46,9 @@ void fake_outputs_init(const char *output_spec) { primary = true; walk++; } - if (*walk == ',') + if (*walk == ',') { walk++; /* Skip delimiter */ + } DLOG("Parsed output as width = %u, height = %u at (%u, %u)%s\n", width, height, x, y, primary ? " (primary)" : ""); @@ -71,10 +72,11 @@ void fake_outputs_init(const char *output_spec) { new_output->rect.width = width; new_output->rect.height = height; /* We always treat the screen at 0x0 as the primary screen */ - if (new_output->rect.x == 0 && new_output->rect.y == 0) + if (new_output->rect.x == 0 && new_output->rect.y == 0) { TAILQ_INSERT_HEAD(&outputs, new_output, outputs); - else + } else { TAILQ_INSERT_TAIL(&outputs, new_output, outputs); + } output_init_con(new_output); init_ws_for_output(new_output); num_screens++; diff --git a/src/floating.c b/src/floating.c index f52f27bc..d6f3682d 100644 --- a/src/floating.c +++ b/src/floating.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * floating.c: Floating windows. @@ -20,8 +20,9 @@ * */ static Rect total_outputs_dimensions(void) { - if (TAILQ_EMPTY(&outputs)) + if (TAILQ_EMPTY(&outputs)) { return (Rect){0, 0, root_screen->width_in_pixels, root_screen->height_in_pixels}; + } Output *output; /* Use Rect to encapsulate dimensions, ignoring x/y */ @@ -76,7 +77,6 @@ void floating_check_size(Con *floating_con, bool prefer_height) { /* Define reasonable minimal and maximal sizes for floating windows */ const int floating_sane_min_height = 50; const int floating_sane_min_width = 75; - Rect floating_sane_max_dimensions; Con *focused_con = con_descend_focused(floating_con); DLOG("deco_rect.height = %d\n", focused_con->deco_rect.height); @@ -147,7 +147,7 @@ void floating_check_size(Con *floating_con, bool prefer_height) { * this case according to the ICCCM. */ double width = floating_con->rect.width - window->base_width - border_rect.width; double height = floating_con->rect.height - window->base_height - border_rect.height; - const double ar = (double)width / (double)height; + const double ar = width / height; double new_ar = -1; if (min_ar > 0 && ar < min_ar) { new_ar = min_ar; @@ -207,7 +207,7 @@ void floating_check_size(Con *floating_con, bool prefer_height) { /* Unless user requests otherwise (-1), ensure width/height do not exceed * configured maxima or, if unconfigured, limit to combined width of all * outputs */ - floating_sane_max_dimensions = total_outputs_dimensions(); + Rect floating_sane_max_dimensions = total_outputs_dimensions(); if (config.floating_maximum_height != -1) { floating_con->rect.height -= border_rect.height; if (config.floating_maximum_height == 0) { @@ -407,8 +407,9 @@ bool floating_enable(Con *con, bool automatic) { /* render the cons to get initial window_rect correct */ render_con(nc); - if (set_focus) + if (set_focus) { con_activate(con); + } floating_set_hint_atom(nc, true); ipc_send_window_event("floating", con); @@ -556,10 +557,12 @@ void floating_move_to_pointer(Con *con) { /* Correct target coordinates to be in-bounds. */ x = MAX(x, (int32_t)output->rect.x); y = MAX(y, (int32_t)output->rect.y); - if (x + con->rect.width > output->rect.x + output->rect.width) + if (x + con->rect.width > output->rect.x + output->rect.width) { x = output->rect.x + output->rect.width - con->rect.width; - if (y + con->rect.height > output->rect.y + output->rect.height) + } + if (y + con->rect.height > output->rect.y + output->rect.height) { y = output->rect.y + output->rect.height - con->rect.height; + } /* Update container's coordinates to position it correctly. */ floating_reposition(con, (Rect){x, y, con->rect.width, con->rect.height}); @@ -575,8 +578,9 @@ DRAGGING_CB(drag_window_callback) { xcb_flush(conn); /* Check if we cross workspace boundaries while moving */ - if (!floating_maybe_reassign_ws(con)) + if (!floating_maybe_reassign_ws(con)) { return; + } /* Ensure not to warp the pointer while dragging */ x_set_warp_to(NULL); tree_render(); @@ -612,8 +616,9 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event, bool } /* If this is a scratchpad window, don't auto center it from now on. */ - if (con->scratchpad_state == SCRATCHPAD_FRESH) + if (con->scratchpad_state == SCRATCHPAD_FRESH) { con->scratchpad_state = SCRATCHPAD_CHANGED; + } tree_render(); } @@ -643,15 +648,17 @@ DRAGGING_CB(resize_window_callback) { /* First guess: We resize by exactly the amount the mouse moved, * taking into account in which corner the client was grabbed */ - if (corner & BORDER_LEFT) + if (corner & BORDER_LEFT) { dest_width = old_rect->width - (new_x - event->root_x); - else + } else { dest_width = old_rect->width + (new_x - event->root_x); + } - if (corner & BORDER_TOP) + if (corner & BORDER_TOP) { dest_height = old_rect->height - (new_y - event->root_y); - else + } else { dest_height = old_rect->height + (new_y - event->root_y); + } /* User wants to keep proportions, so we may have to adjust our values */ if (params->proportional) { @@ -666,11 +673,13 @@ DRAGGING_CB(resize_window_callback) { /* If not the lower right corner is grabbed, we must also reposition * the client by exactly the amount we resized it */ - if (corner & BORDER_LEFT) + if (corner & BORDER_LEFT) { dest_x = old_rect->x + (old_rect->width - con->rect.width); + } - if (corner & BORDER_TOP) + if (corner & BORDER_TOP) { dest_y = old_rect->y + (old_rect->height - con->rect.height); + } con->rect.x = dest_x; con->rect.y = dest_y; @@ -697,10 +706,11 @@ void floating_resize_window(Con *con, const bool proportional, * a bitmask of the nearest borders (BORDER_LEFT, BORDER_RIGHT, …) */ border_t corner = 0; - if (event->event_x <= (int16_t)(con->rect.width / 2)) + if (event->event_x <= (int16_t)(con->rect.width / 2)) { corner |= BORDER_LEFT; - else + } else { corner |= BORDER_RIGHT; + } int cursor = 0; if (event->event_y <= (int16_t)(con->rect.height / 2)) { @@ -724,12 +734,14 @@ void floating_resize_window(Con *con, const bool proportional, } /* If the user cancels, undo the resize */ - if (drag_result == DRAG_REVERT) + if (drag_result == DRAG_REVERT) { floating_reposition(con, initial_rect); + } /* If this is a scratchpad window, don't auto center it from now on. */ - if (con->scratchpad_state == SCRATCHPAD_FRESH) + if (con->scratchpad_state == SCRATCHPAD_FRESH) { con->scratchpad_state = SCRATCHPAD_CHANGED; + } } /* @@ -752,8 +764,9 @@ bool floating_reposition(Con *con, Rect newrect) { floating_maybe_reassign_ws(con); /* If this is a scratchpad window, don't auto center it from now on. */ - if (con->scratchpad_state == SCRATCHPAD_FRESH) + if (con->scratchpad_state == SCRATCHPAD_FRESH) { con->scratchpad_state = SCRATCHPAD_CHANGED; + } tree_render(); return true; @@ -779,16 +792,19 @@ void floating_resize(Con *floating_con, uint32_t x, uint32_t y) { bool prefer_height = (rect->width == x); rect->width = x; rect->height = y; - if (wi) + if (wi) { rect->width += (wi - 1 - rect->width) % wi; - if (hi) + } + if (hi) { rect->height += (hi - 1 - rect->height) % hi; + } floating_check_size(floating_con, prefer_height); /* If this is a scratchpad window, don't auto center it from now on. */ - if (floating_con->scratchpad_state == SCRATCHPAD_FRESH) + if (floating_con->scratchpad_state == SCRATCHPAD_FRESH) { floating_con->scratchpad_state = SCRATCHPAD_CHANGED; + } } /* diff --git a/src/gaps.c b/src/gaps.c index 5d29faa9..9b4bf806 100644 --- a/src/gaps.c +++ b/src/gaps.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * gaps.c: gaps logic: whether to display gaps at all, and how big @@ -15,16 +15,18 @@ */ gaps_t calculate_effective_gaps(Con *con) { Con *workspace = con_get_workspace(con); - if (workspace == NULL) + 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) + 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), @@ -90,22 +92,26 @@ bool gaps_should_inset_con(Con *con, int children) { 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) + 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) + 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) + if (!found_neighbor) { return false; + } /* If we have an adjacent container and nothing is fullscreen, we consider it. */ - if (fullscreen == NULL) + 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); diff --git a/src/handlers.c b/src/handlers.c index 17968a43..384005b9 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * handlers.c: Small handlers for various events (keypresses, focus changes, @@ -58,17 +58,20 @@ bool event_is_ignored(const int sequence, const int response_type) { event = SLIST_NEXT(event, ignore_events); SLIST_REMOVE(&ignore_events, save, Ignore_Event, ignore_events); free(save); - } else + } else { event = SLIST_NEXT(event, ignore_events); + } } SLIST_FOREACH (event, &ignore_events, ignore_events) { - if (event->sequence != sequence) + if (event->sequence != sequence) { continue; + } if (event->response_type != -1 && - event->response_type != response_type) + event->response_type != response_type) { continue; + } /* Instead of removing & freeing a sequence number we better wait until * it gets garbage collected. It may generate multiple events (there @@ -89,8 +92,9 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { Output *output; /* If the user disable focus follows mouse, we have nothing to do here */ - if (config.disable_focus_follows_mouse) + if (config.disable_focus_follows_mouse) { return; + } if ((output = get_output_containing(x, y)) == NULL) { ELOG("ERROR: No such screen\n"); @@ -111,8 +115,9 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { con_focus(next); /* If the focus changed, we re-render to get updated decorations */ - if (old_focused != focused) + if (old_focused != focused) { tree_render(); + } } /* @@ -167,19 +172,22 @@ static void handle_enter_notify(xcb_enter_notify_event_t *event) { } } - if (config.disable_focus_follows_mouse) + if (config.disable_focus_follows_mouse) { return; + } /* if this container is already focused, there is nothing to do. */ - if (con == focused) + if (con == focused) { return; + } /* Get the currently focused workspace to check if the focus change also * involves changing workspaces. If so, we need to call workspace_show() to * correctly update state and send the IPC event. */ Con *ws = con_get_workspace(con); - if (ws != con_get_workspace(focused)) + if (ws != con_get_workspace(focused)) { workspace_show(ws); + } focused_id = XCB_NONE; con_focus(con_descend_focused(con)); @@ -197,8 +205,9 @@ static void handle_motion_notify(xcb_motion_notify_event_t *event) { /* Skip events where the pointer was over a child window, we are only * interested in events on the root window. */ - if (event->child != XCB_NONE) + if (event->child != XCB_NONE) { return; + } Con *con; if ((con = con_by_frame_id(event->event)) == NULL) { @@ -207,18 +216,21 @@ static void handle_motion_notify(xcb_motion_notify_event_t *event) { return; } - if (config.disable_focus_follows_mouse) + if (config.disable_focus_follows_mouse) { return; + } - if (con->layout != L_DEFAULT && con->layout != L_SPLITV && con->layout != L_SPLITH) + if (con->layout != L_DEFAULT && con->layout != L_SPLITV && con->layout != L_SPLITH) { return; + } /* see over which rect the user is */ 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) + if (TAILQ_FIRST(&(con->parent->focus_head)) == con) { return; + } con_focus(con); x_push_changes(croot); @@ -227,12 +239,14 @@ static void handle_motion_notify(xcb_motion_notify_event_t *event) { } 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)) + if (!rect_contains(current->deco_rect, event->event_x, event->event_y)) { continue; + } /* We found the rect, let’s see if this window is focused */ - if (TAILQ_FIRST(&(con->focus_head)) == current) + if (TAILQ_FIRST(&(con->focus_head)) == current) { return; + } con_focus(current); x_push_changes(croot); @@ -248,8 +262,9 @@ static void handle_motion_notify(xcb_motion_notify_event_t *event) { */ static void handle_mapping_notify(xcb_mapping_notify_event_t *event) { if (event->request != XCB_MAPPING_KEYBOARD && - event->request != XCB_MAPPING_MODIFIER) + event->request != XCB_MAPPING_MODIFIER) { return; + } DLOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n"); xcb_refresh_keyboard_mapping(keysyms, event); @@ -265,10 +280,8 @@ static void handle_mapping_notify(xcb_mapping_notify_event_t *event) { * A new window appeared on the screen (=was mapped), so let’s manage it. * */ -static void handle_map_request(xcb_map_request_event_t *event) { - xcb_get_window_attributes_cookie_t cookie; - - cookie = xcb_get_window_attributes_unchecked(conn, event->window); +static void handle_map_request(const xcb_map_request_event_t *event) { + xcb_get_window_attributes_cookie_t cookie = xcb_get_window_attributes_unchecked(conn, event->window); DLOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence); add_ignore_event(event->sequence, -1); @@ -422,6 +435,7 @@ static void handle_configure_request(xcb_configure_request_event_t *event) { } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(workspace))) { DLOG("Marking con = %p urgent\n", con); con_set_urgency(con, true); + con = remanage_window(con); tree_render(); } else { DLOG("Ignoring request for con = %p.\n", con); @@ -478,8 +492,9 @@ static void handle_unmap_notify_event(xcb_unmap_notify_event_t *event) { return; } - if (con->ignore_unmap > 0) + if (con->ignore_unmap > 0) { con->ignore_unmap--; + } /* See the end of this function. */ cookie = xcb_get_input_focus(conn); DLOG("ignore_unmap = %d for frame of container %p\n", con->ignore_unmap, con); @@ -548,12 +563,14 @@ static void handle_destroy_notify_event(xcb_destroy_notify_event_t *event) { } static bool window_name_changed(i3Window *window, char *old_name) { - if ((old_name == NULL) && (window->name == NULL)) + if ((old_name == NULL) && (window->name == NULL)) { return false; + } /* Either the old or the new one is NULL, but not both. */ - if ((old_name == NULL) ^ (window->name == NULL)) + if ((old_name == NULL) ^ (window->name == NULL)) { return true; + } return (strcmp(old_name, i3string_as_utf8(window->name)) != 0); } @@ -571,8 +588,9 @@ static bool handle_windowname_change(Con *con, xcb_get_property_reply_t *prop) { x_push_changes(croot); - if (window_name_changed(con->window, old_name)) + if (window_name_changed(con->window, old_name)) { ipc_send_window_event("title", con); + } FREE(old_name); @@ -593,8 +611,9 @@ static bool handle_windowname_change_legacy(Con *con, xcb_get_property_reply_t * x_push_changes(croot); - if (window_name_changed(con->window, old_name)) + if (window_name_changed(con->window, old_name)) { ipc_send_window_event("title", con); + } FREE(old_name); @@ -608,7 +627,7 @@ static bool handle_windowname_change_legacy(Con *con, xcb_get_property_reply_t * static bool handle_windowrole_change(Con *con, xcb_get_property_reply_t *prop) { window_update_role(con->window, prop); - con = remanage_window(con); + remanage_window(con); return true; } @@ -652,6 +671,60 @@ static void handle_expose_event(xcb_expose_event_t *event) { #define _NET_MOVERESIZE_WINDOW_WIDTH (1 << 10) #define _NET_MOVERESIZE_WINDOW_HEIGHT (1 << 11) +static void handle_net_wm_state_change(Con *con, uint32_t change, uint32_t atom) { + if (atom == 0) { + return; + } + + const char *debug_change = (change == _NET_WM_STATE_REMOVE ? "remove" : (change == _NET_WM_STATE_ADD ? "add" : "toggle")); + + if (atom == A__NET_WM_STATE_FULLSCREEN) { + DLOG("Received a client message to %s _NET_WM_STATE_FULLSCREEN.\n", debug_change); + + /* Check if the fullscreen state should be toggled */ + if (change == _NET_WM_STATE_TOGGLE || + (con->fullscreen_mode != CF_NONE && change == _NET_WM_STATE_REMOVE) || + (con->fullscreen_mode == CF_NONE && change == _NET_WM_STATE_ADD)) { + DLOG("toggling fullscreen\n"); + con_toggle_fullscreen(con, CF_OUTPUT); + } + } else if (atom == A__NET_WM_STATE_DEMANDS_ATTENTION) { + DLOG("Received a client message to %s _NET_WM_STATE_DEMANDS_ATTENTION.\n", debug_change); + + /* Check if the urgent flag must be set or not */ + if (change == _NET_WM_STATE_ADD) { + con_set_urgency(con, true); + con = remanage_window(con); + } else if (change == _NET_WM_STATE_REMOVE) { + con_set_urgency(con, false); + con = remanage_window(con); + } else if (change == _NET_WM_STATE_TOGGLE) { + con_set_urgency(con, !con->urgent); + con = remanage_window(con); + } + } else if (atom == A__NET_WM_STATE_STICKY) { + DLOG("Received a client message to %s _NET_WM_STATE_STICKY.\n", debug_change); + + if (change == _NET_WM_STATE_ADD) { + con->sticky = true; + } else if (change == _NET_WM_STATE_REMOVE) { + con->sticky = false; + } else if (change == _NET_WM_STATE_TOGGLE) { + con->sticky = !con->sticky; + } + + DLOG("New sticky status for con = %p is %i.\n", con, con->sticky); + ewmh_update_sticky(con->window->id, con->sticky); + output_push_sticky_windows(focused); + ewmh_update_wm_desktop(); + } else { + DLOG("Unknown atom in ClientMessage to %s type %u\n", debug_change, atom); + return; + } + + tree_render(); +} + /* * Handle client messages (EWMH) * @@ -659,16 +732,14 @@ static void handle_expose_event(xcb_expose_event_t *event) { static void handle_client_message(xcb_client_message_event_t *event) { /* If this is a startup notification ClientMessage, the library will handle * it and call our monitor_event() callback. */ - if (sn_xcb_display_process_event(sndisplay, (xcb_generic_event_t *)event)) + if (sn_xcb_display_process_event(sndisplay, (xcb_generic_event_t *)event)) { return; + } LOG("ClientMessage for window 0x%08x\n", event->window); if (event->type == A__NET_WM_STATE) { - if (event->format != 32 || - (event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN && - event->data.data32[1] != A__NET_WM_STATE_DEMANDS_ATTENTION && - event->data.data32[1] != A__NET_WM_STATE_STICKY)) { - DLOG("Unknown atom in clientmessage of type %d\n", event->data.data32[1]); + if (event->format != 32) { + DLOG("Unknown format %d in ClientMessage\n", event->format); return; } @@ -678,44 +749,13 @@ static void handle_client_message(xcb_client_message_event_t *event) { return; } - if (event->data.data32[1] == A__NET_WM_STATE_FULLSCREEN) { - /* Check if the fullscreen state should be toggled */ - if ((con->fullscreen_mode != CF_NONE && - (event->data.data32[0] == _NET_WM_STATE_REMOVE || - event->data.data32[0] == _NET_WM_STATE_TOGGLE)) || - (con->fullscreen_mode == CF_NONE && - (event->data.data32[0] == _NET_WM_STATE_ADD || - event->data.data32[0] == _NET_WM_STATE_TOGGLE))) { - DLOG("toggling fullscreen\n"); - con_toggle_fullscreen(con, CF_OUTPUT); - } - } else if (event->data.data32[1] == A__NET_WM_STATE_DEMANDS_ATTENTION) { - /* Check if the urgent flag must be set or not */ - if (event->data.data32[0] == _NET_WM_STATE_ADD) - con_set_urgency(con, true); - else if (event->data.data32[0] == _NET_WM_STATE_REMOVE) - con_set_urgency(con, false); - else if (event->data.data32[0] == _NET_WM_STATE_TOGGLE) - con_set_urgency(con, !con->urgent); - } else if (event->data.data32[1] == A__NET_WM_STATE_STICKY) { - DLOG("Received a client message to modify _NET_WM_STATE_STICKY.\n"); - if (event->data.data32[0] == _NET_WM_STATE_ADD) - con->sticky = true; - else if (event->data.data32[0] == _NET_WM_STATE_REMOVE) - con->sticky = false; - else if (event->data.data32[0] == _NET_WM_STATE_TOGGLE) - con->sticky = !con->sticky; - - DLOG("New sticky status for con = %p is %i.\n", con, con->sticky); - ewmh_update_sticky(con->window->id, con->sticky); - output_push_sticky_windows(focused); - ewmh_update_wm_desktop(); + for (size_t i = 0; i < sizeof(event->data.data32) / sizeof(event->data.data32[0]) - 1; i++) { + handle_net_wm_state_change(con, event->data.data32[0], event->data.data32[i + 1]); } - - tree_render(); } else if (event->type == A__NET_ACTIVE_WINDOW) { - if (event->format != 32) + if (event->format != 32) { return; + } DLOG("_NET_ACTIVE_WINDOW: Window 0x%08x should be activated\n", event->window); @@ -763,8 +803,10 @@ static void handle_client_message(xcb_client_message_event_t *event) { } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) { DLOG("Marking con = %p urgent\n", con); con_set_urgency(con, true); - } else + con = remanage_window(con); + } else { DLOG("Ignoring request for con = %p.\n", con); + } } tree_render(); @@ -851,6 +893,7 @@ static void handle_client_message(xcb_client_message_event_t *event) { con->sticky = true; ewmh_update_sticky(con->window->id, true); output_push_sticky_windows(focused); + run_assignments(con->window); } } else { Con *ws = ewmh_get_workspace_by_index(index); @@ -874,8 +917,9 @@ static void handle_client_message(xcb_client_message_event_t *event) { if (con) { DLOG("Handling _NET_CLOSE_WINDOW request (con = %p)\n", con); - if (event->data.data32[0]) + if (event->data.data32[0]) { last_timestamp = event->data.data32[0]; + } tree_close_internal(con, KILL_WINDOW, false); tree_render(); @@ -982,6 +1026,7 @@ static bool handle_hints(Con *con, xcb_get_property_reply_t *reply) { bool urgency_hint; window_update_hints(con->window, reply, &urgency_hint); con_set_urgency(con, urgency_hint); + remanage_window(con); tree_render(); return true; } @@ -1025,8 +1070,9 @@ static void handle_focus_in(xcb_focus_in_event_t *event) { } Con *con; - if ((con = con_by_window_id(event->event)) == NULL || con->window == NULL) + if ((con = con_by_window_id(event->event)) == NULL || con->window == NULL) { return; + } DLOG("That is con %p / %s\n", con, con->name); if (event->mode == XCB_NOTIFY_MODE_GRAB || @@ -1257,25 +1303,6 @@ static bool handle_strut_partial_change(Con *con, xcb_get_property_reply_t *prop return true; } -/* - * Handles the _I3_FLOATING_WINDOW property to properly run assignments for - * floating window changes. - * - * This is needed to correctly run the assignments after changes in floating - * windows which are triggered by user commands (floating enable | disable). In - * that case, we can't call run_assignments because it will modify the parser - * state when it needs to parse the user-specified action, breaking the parser - * state for the original command. - * - */ -static bool handle_i3_floating(Con *con, xcb_get_property_reply_t *prop) { - DLOG("floating change for con %p\n", con); - - remanage_window(con); - - return true; -} - static bool handle_windowicon_change(Con *con, xcb_get_property_reply_t *prop) { window_update_icon(con->window, prop); @@ -1305,7 +1332,6 @@ static struct property_handler_t property_handlers[] = { {0, 128, handle_class_change}, {0, UINT_MAX, handle_strut_partial_change}, {0, UINT_MAX, handle_window_type}, - {0, UINT_MAX, handle_i3_floating}, {0, 128, handle_machine_change}, {0, 5 * sizeof(uint64_t), handle_motif_hints_change}, {0, UINT_MAX, handle_windowicon_change}}; @@ -1329,10 +1355,9 @@ void property_handlers_init(void) { property_handlers[7].atom = XCB_ATOM_WM_CLASS; property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL; property_handlers[9].atom = A__NET_WM_WINDOW_TYPE; - property_handlers[10].atom = A_I3_FLOATING_WINDOW; - property_handlers[11].atom = XCB_ATOM_WM_CLIENT_MACHINE; - property_handlers[12].atom = A__MOTIF_WM_HINTS; - property_handlers[13].atom = A__NET_WM_ICON; + property_handlers[10].atom = XCB_ATOM_WM_CLIENT_MACHINE; + property_handlers[11].atom = A__MOTIF_WM_HINTS; + property_handlers[12].atom = A__NET_WM_ICON; } static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) { @@ -1342,8 +1367,9 @@ static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) Con *con; for (size_t c = 0; c < NUM_HANDLERS; c++) { - if (property_handlers[c].atom != atom) + if (property_handlers[c].atom != atom) { continue; + } handler = &property_handlers[c]; break; @@ -1370,8 +1396,9 @@ static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) } /* the handler will free() the reply unless it returns false */ - if (!handler->cb(con, propr)) + if (!handler->cb(con, propr)) { FREE(propr); + } } /* @@ -1380,8 +1407,9 @@ static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) * */ void handle_event(int type, xcb_generic_event_t *event) { - if (type != XCB_MOTION_NOTIFY) + if (type != XCB_MOTION_NOTIFY) { DLOG("event type %d, xkb_base %d\n", type, xkb_base); + } if (randr_base > -1 && type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { @@ -1397,8 +1425,9 @@ void handle_event(int type, xcb_generic_event_t *event) { DLOG("xkb new keyboard notify, sequence %d, time %d\n", state->sequence, state->time); xcb_key_symbols_free(keysyms); keysyms = xcb_key_symbols_alloc(conn); - if (((xcb_xkb_new_keyboard_notify_event_t *)event)->changed & XCB_XKB_NKN_DETAIL_KEYCODES) + if (((xcb_xkb_new_keyboard_notify_event_t *)event)->changed & XCB_XKB_NKN_DETAIL_KEYCODES) { (void)load_keymap(); + } ungrab_all_keys(conn); translate_keysyms(); grab_all_keys(conn); @@ -1417,8 +1446,9 @@ void handle_event(int type, xcb_generic_event_t *event) { } } else if (state->xkbType == XCB_XKB_STATE_NOTIFY) { DLOG("xkb state group = %d\n", state->group); - if (xkb_current_group == state->group) + if (xkb_current_group == state->group) { return; + } xkb_current_group = state->group; ungrab_all_keys(conn); grab_all_keys(conn); diff --git a/src/ipc.c b/src/ipc.c index f69ba2ae..4ba0cb5c 100644 --- a/src/ipc.c +++ b/src/ipc.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * ipc.c: UNIX domain socket IPC (initialization, client handling, protocol). @@ -28,7 +28,7 @@ char *current_socketpath = NULL; TAILQ_HEAD(ipc_client_head, ipc_client) all_clients = TAILQ_HEAD_INITIALIZER(all_clients); static void ipc_client_timeout(EV_P_ ev_timer *w, int revents); -static void ipc_socket_writeable_cb(EV_P_ struct ev_io *w, int revents); +static void ipc_socket_writeable_cb(EV_P_ ev_io *w, int revents); static ev_tstamp kill_timeout = 10.0; @@ -66,7 +66,7 @@ static void ipc_push_pending(ipc_client *client) { ev_io_start(main_loop, client->write_callback); if (!client->timeout) { - struct ev_timer *timeout = scalloc(1, sizeof(struct ev_timer)); + ev_timer *timeout = scalloc(1, sizeof(struct ev_timer)); ev_timer_init(timeout, ipc_client_timeout, kill_timeout, 0.); timeout->data = client; client->timeout = timeout; @@ -192,9 +192,8 @@ static void ipc_send_shutdown_event(shutdown_reason_t reason) { void ipc_shutdown(shutdown_reason_t reason, int exempt_fd) { ipc_send_shutdown_event(reason); - ipc_client *current; while (!TAILQ_EMPTY(&all_clients)) { - current = TAILQ_FIRST(&all_clients); + ipc_client *current = TAILQ_FIRST(&all_clients); if (current->fd != exempt_fd) { shutdown(current->fd, SHUT_RDWR); } @@ -216,8 +215,9 @@ IPC_HANDLER(run_command) { CommandResult *result = parse_command(command, gen, client); free(command); - if (result->needs_tree_render) + if (result->needs_tree_render) { tree_render(); + } command_result_free(result); @@ -338,10 +338,11 @@ static void dump_binding(yajl_gen gen, Binding *bind) { ystr((const char *)(bind->input_type == B_KEYBOARD ? "keyboard" : "mouse")); ystr("symbol"); - if (bind->symbol == NULL) + if (bind->symbol == NULL) { y(null); - else + } else { ystr(bind->symbol); + } ystr("command"); ystr(bind->command); @@ -357,7 +358,7 @@ static void dump_binding(yajl_gen gen, Binding *bind) { y(map_close); } -void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { +void dump_node(yajl_gen gen, Con *con, bool inplace_restart) { y(map_open); ystr("id"); y(integer, (uintptr_t)con); @@ -386,13 +387,14 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { /* provided for backwards compatibility only. */ ystr("orientation"); - if (!con_is_split(con)) + if (!con_is_split(con)) { ystr("none"); - else { - if (con_orientation(con) == HORIZ) + } else { + if (con_orientation(con) == HORIZ) { ystr("horizontal"); - else + } else { ystr("vertical"); + } } ystr("scratchpad_state"); @@ -409,10 +411,11 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { } ystr("percent"); - if (con->percent == 0.0) + if (con->percent == 0.0) { y(null); - else + } else { y(double, con->percent); + } ystr("urgent"); y(bool, con->urgent); @@ -516,12 +519,13 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { dump_rect(gen, "geometry", con->geometry); ystr("name"); - if (con->window && con->window->name) + if (con->window && con->window->name) { ystr(i3string_as_utf8(con->window->name)); - else if (con->name != NULL) + } else if (con->name != NULL) { ystr(con->name); - else + } else { y(null); + } if (con->title_format != NULL) { ystr("title_format"); @@ -539,10 +543,11 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { } ystr("window"); - if (con->window) + if (con->window) { y(integer, con->window->id); - else + } else { y(null); + } ystr("window_type"); if (con->window) { @@ -571,8 +576,9 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { } else { ystr("unknown"); } - } else + } else { y(null); + } if (con->window && !inplace_restart) { /* Window properties are useless to preserve when restarting because @@ -600,10 +606,11 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { } ystr("transient_for"); - if (con->window->transient_for == XCB_NONE) + if (con->window->transient_for == XCB_NONE) { y(null); - else + } else { y(integer, con->window->transient_for); + } y(map_close); } @@ -660,8 +667,9 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { TAILQ_FOREACH (match, &(con->swallow_head), matches) { /* We will generate a new restart_mode match specification after this * loop, so skip this one. */ - if (match->restart_mode) + if (match->restart_mode) { continue; + } y(map_open); if (match->dock != M_DONTCHECK) { ystr("dock"); @@ -714,8 +722,9 @@ void dump_node(yajl_gen gen, struct Con *con, bool inplace_restart) { } static void dump_bar_bindings(yajl_gen gen, Barconfig *config) { - if (TAILQ_EMPTY(&(config->bar_bindings))) + if (TAILQ_EMPTY(&(config->bar_bindings))) { return; + } ystr("bindings"); y(array_open); @@ -739,7 +748,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); @@ -821,12 +830,14 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { dump_bar_bindings(gen, config); ystr("position"); - if (config->position == P_BOTTOM) + if (config->position == P_BOTTOM) { ystr("bottom"); - else + } else { ystr("top"); + } YSTR_IF_SET(status_command); + YSTR_IF_SET(workspace_command); YSTR_IF_SET(font); if (config->bar_height) { @@ -898,10 +909,10 @@ static void dump_bar_config(yajl_gen gen, Barconfig *config) { } IPC_HANDLER(tree) { - setlocale(LC_NUMERIC, "C"); + locale_t prev_locale = uselocale(numericC); yajl_gen gen = ygenalloc(); dump_node(gen, croot, false); - setlocale(LC_NUMERIC, ""); + uselocale(prev_locale); const unsigned char *payload; ylength length; @@ -924,8 +935,9 @@ IPC_HANDLER(get_workspaces) { Con *output; TAILQ_FOREACH (output, &(croot->nodes_head), nodes) { - if (con_is_internal(output)) + if (con_is_internal(output)) { continue; + } Con *ws; TAILQ_FOREACH (ws, &(output_get_content(output)->nodes_head), nodes) { assert(ws->type == CT_WORKSPACE); @@ -1014,10 +1026,11 @@ IPC_HANDLER(get_outputs) { ystr("current_workspace"); Con *ws = NULL; - if (output->con && (ws = con_get_fullscreen_con(output->con, CF_OUTPUT))) + if (output->con && (ws = con_get_fullscreen_con(output->con, CF_OUTPUT))) { ystr(ws->name); - else + } else { y(null); + } y(map_close); } @@ -1136,8 +1149,9 @@ IPC_HANDLER(get_bar_config) { LOG("IPC: looking for config for bar ID \"%s\"\n", bar_id); Barconfig *current, *config = NULL; TAILQ_FOREACH (current, &barconfigs, configs) { - if (strcmp(current->id, bar_id) != 0) + if (strcmp(current->id, bar_id) != 0) { continue; + } config = current; break; @@ -1220,20 +1234,15 @@ static int add_subscription(void *extra, const unsigned char *s, * */ IPC_HANDLER(subscribe) { - yajl_handle p; - yajl_status stat; - /* Setup the JSON parser */ static yajl_callbacks callbacks = { .yajl_string = add_subscription, }; - p = yalloc(&callbacks, (void *)client); - stat = yajl_parse(p, (const unsigned char *)message, message_size); + const yajl_handle p = yalloc(&callbacks, client); + const yajl_status stat = yajl_parse(p, message, message_size); if (stat != yajl_status_ok) { - unsigned char *err; - err = yajl_get_error(p, true, (const unsigned char *)message, - message_size); + unsigned char *err = yajl_get_error(p, true, message, message_size); ELOG("YAJL parse error: %s\n", err); yajl_free_error(p, err); @@ -1356,24 +1365,18 @@ static int _sync_json_int(void *extra, long long val) { } IPC_HANDLER(sync) { - yajl_handle p; - yajl_status stat; - /* Setup the JSON parser */ static yajl_callbacks callbacks = { .yajl_map_key = _sync_json_key, .yajl_integer = _sync_json_int, }; - struct sync_state state; - memset(&state, '\0', sizeof(struct sync_state)); - p = yalloc(&callbacks, (void *)&state); - stat = yajl_parse(p, (const unsigned char *)message, message_size); + struct sync_state state = {0}; + yajl_handle p = yalloc(&callbacks, &state); + yajl_status stat = yajl_parse(p, message, message_size); FREE(state.last_key); if (stat != yajl_status_ok) { - unsigned char *err; - err = yajl_get_error(p, true, (const unsigned char *)message, - message_size); + unsigned char *err = yajl_get_error(p, true, message, message_size); ELOG("YAJL parse error: %s\n", err); yajl_free_error(p, err); @@ -1436,11 +1439,11 @@ handler_t handlers[13] = { * at the moment. * */ -static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { +static void ipc_receive_message(EV_P_ ev_io *w, int revents) { uint32_t message_type; uint32_t message_length; uint8_t *message = NULL; - ipc_client *client = (ipc_client *)w->data; + ipc_client *client = w->data; assert(client->fd == w->fd); int ret = ipc_recv_message(w->fd, &message_type, &message_length, &message); @@ -1459,9 +1462,9 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { return; } - if (message_type >= (sizeof(handlers) / sizeof(handler_t))) + if (message_type >= (sizeof(handlers) / sizeof(handler_t))) { DLOG("Unhandled message type: %d\n", message_type); - else { + } else { handler_t h = handlers[message_type]; h(client, message, 0, message_length, message_type); } @@ -1472,7 +1475,7 @@ static void ipc_receive_message(EV_P_ struct ev_io *w, int revents) { static void ipc_client_timeout(EV_P_ ev_timer *w, int revents) { /* No need to be polite and check for writeability, the other callback would * have been called by now. */ - ipc_client *client = (ipc_client *)w->data; + ipc_client *client = w->data; char *cmdline = NULL; #if defined(__linux__) && defined(SO_PEERCRED) @@ -1532,7 +1535,7 @@ static void ipc_socket_writeable_cb(EV_P_ ev_io *w, int revents) { * the list of clients. * */ -void ipc_new_client(EV_P_ struct ev_io *w, int revents) { +void ipc_new_client(EV_P_ ev_io *w, int revents) { struct sockaddr_un peer; socklen_t len = sizeof(struct sockaddr_un); int fd; @@ -1563,12 +1566,12 @@ ipc_client *ipc_new_client_on_fd(EV_P_ int fd) { ipc_client *client = scalloc(1, sizeof(ipc_client)); client->fd = fd; - client->read_callback = scalloc(1, sizeof(struct ev_io)); + client->read_callback = scalloc(1, sizeof(ev_io)); client->read_callback->data = client; ev_io_init(client->read_callback, ipc_receive_message, fd, EV_READ); ev_io_start(EV_A_ client->read_callback); - client->write_callback = scalloc(1, sizeof(struct ev_io)); + client->write_callback = scalloc(1, sizeof(ev_io)); client->write_callback->data = client; ev_io_init(client->write_callback, ipc_socket_writeable_cb, fd, EV_WRITE); @@ -1582,7 +1585,7 @@ ipc_client *ipc_new_client_on_fd(EV_P_ int fd) { * generator. Free with yajl_gen_free(). */ yajl_gen ipc_marshal_workspace_event(const char *change, Con *current, Con *old) { - setlocale(LC_NUMERIC, "C"); + locale_t prev_locale = uselocale(numericC); yajl_gen gen = ygenalloc(); y(map_open); @@ -1591,20 +1594,22 @@ yajl_gen ipc_marshal_workspace_event(const char *change, Con *current, Con *old) ystr(change); ystr("current"); - if (current == NULL) + if (current == NULL) { y(null); - else + } else { dump_node(gen, current, false); + } ystr("old"); - if (old == NULL) + if (old == NULL) { y(null); - else + } else { dump_node(gen, old, false); + } y(map_close); - setlocale(LC_NUMERIC, ""); + uselocale(prev_locale); return gen; } @@ -1634,7 +1639,7 @@ void ipc_send_window_event(const char *property, Con *con) { DLOG("Issue IPC window %s event (con = %p, window = 0x%08x)\n", property, con, (con->window ? con->window->id : XCB_WINDOW_NONE)); - setlocale(LC_NUMERIC, "C"); + locale_t prev_locale = uselocale(numericC); yajl_gen gen = ygenalloc(); y(map_open); @@ -1653,7 +1658,7 @@ void ipc_send_window_event(const char *property, Con *con) { ipc_send_event("window", I3_IPC_EVENT_WINDOW, (const char *)payload); y(free); - setlocale(LC_NUMERIC, ""); + uselocale(prev_locale); } /* @@ -1661,7 +1666,7 @@ void ipc_send_window_event(const char *property, Con *con) { */ void ipc_send_barconfig_update_event(Barconfig *barconfig) { DLOG("Issue barconfig_update event for id = %s\n", barconfig->id); - setlocale(LC_NUMERIC, "C"); + locale_t prev_locale = uselocale(numericC); yajl_gen gen = ygenalloc(); dump_bar_config(gen, barconfig); @@ -1672,7 +1677,7 @@ void ipc_send_barconfig_update_event(Barconfig *barconfig) { ipc_send_event("barconfig_update", I3_IPC_EVENT_BARCONFIG_UPDATE, (const char *)payload); y(free); - setlocale(LC_NUMERIC, ""); + uselocale(prev_locale); } /* @@ -1681,7 +1686,7 @@ void ipc_send_barconfig_update_event(Barconfig *barconfig) { 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"); + locale_t prev_locale = uselocale(numericC); yajl_gen gen = ygenalloc(); @@ -1709,7 +1714,7 @@ void ipc_send_binding_event(const char *event_type, Binding *bind, const char *m ipc_send_event("binding", I3_IPC_EVENT_BINDING, (const char *)payload); y(free); - setlocale(LC_NUMERIC, ""); + uselocale(prev_locale); } /* diff --git a/src/key_press.c b/src/key_press.c index d16174f8..715ba30a 100644 --- a/src/key_press.c +++ b/src/key_press.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * key_press.c: key press handler @@ -25,8 +25,9 @@ void handle_key_press(xcb_key_press_event_t *event) { Binding *bind = get_binding_from_xcb_event((xcb_generic_event_t *)event); /* if we couldn't find a binding, we are done */ - if (bind == NULL) + if (bind == NULL) { return; + } CommandResult *result = run_binding(bind, NULL); command_result_free(result); diff --git a/src/load_layout.c b/src/load_layout.c index 053649d0..8f4ec9fb 100644 --- a/src/load_layout.c +++ b/src/load_layout.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * load_layout.c: Restore (parts of) the layout, for example after an inplace @@ -29,7 +29,7 @@ static bool parsing_window_rect; static bool parsing_geometry; static bool parsing_focus; static bool parsing_marks; -struct Match *current_swallow; +Match *current_swallow; static bool swallow_is_empty; static int num_marks; /* We need to save each container that needs to be marked if we want to support @@ -40,7 +40,7 @@ static int num_marks; struct pending_marks { char *mark; Con *con_to_be_marked; -} * marks; +} *marks; /* This list is used for reordering the focus stack after parsing the 'focus' * array. */ @@ -233,8 +233,9 @@ static int json_end_array(void *ctx) { LOG("focus (reverse) %d\n", mapping->old_id); Con *con; TAILQ_FOREACH (con, &(json_node->focus_head), focused) { - if (con->old_id != mapping->old_id) + if (con->old_id != mapping->old_id) { continue; + } LOG("got it! %p\n", con); /* Move this entry to the top of the focus list. */ TAILQ_REMOVE(&(json_node->focus_head), con, focused); @@ -257,29 +258,37 @@ static int json_key(void *ctx, const unsigned char *val, size_t len) { FREE(last_key); last_key = scalloc(len + 1, 1); memcpy(last_key, val, len); - if (strcasecmp(last_key, "swallows") == 0) + if (strcasecmp(last_key, "swallows") == 0) { parsing_swallows = true; + } - if (strcasecmp(last_key, "gaps") == 0) + if (strcasecmp(last_key, "gaps") == 0) { parsing_gaps = true; + } - if (strcasecmp(last_key, "rect") == 0) + if (strcasecmp(last_key, "rect") == 0) { parsing_rect = true; + } - if (strcasecmp(last_key, "actual_deco_rect") == 0) + if (strcasecmp(last_key, "actual_deco_rect") == 0) { parsing_actual_deco_rect = true; + } - if (strcasecmp(last_key, "deco_rect") == 0) + if (strcasecmp(last_key, "deco_rect") == 0) { parsing_deco_rect = true; + } - if (strcasecmp(last_key, "window_rect") == 0) + if (strcasecmp(last_key, "window_rect") == 0) { parsing_window_rect = true; + } - if (strcasecmp(last_key, "geometry") == 0) + if (strcasecmp(last_key, "geometry") == 0) { parsing_geometry = true; + } - if (strcasecmp(last_key, "focus") == 0) + if (strcasecmp(last_key, "focus") == 0) { parsing_focus = true; + } if (strcasecmp(last_key, "marks") == 0) { num_marks = 0; @@ -293,7 +302,7 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { LOG("string: %.*s for key %s\n", (int)len, val, last_key); if (parsing_swallows) { char *sval; - sasprintf(&sval, "%.*s", len, val); + sasprintf(&sval, "%.*s", (int)len, val); if (strcasecmp(last_key, "class") == 0) { current_swallow->class = regex_new(sval); swallow_is_empty = false; @@ -318,7 +327,7 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { sasprintf(&mark, "%.*s", (int)len, val); marks = srealloc(marks, (++num_marks) * sizeof(struct pending_marks)); - marks[num_marks - 1].mark = sstrdup(mark); + marks[num_marks - 1].mark = mark; marks[num_marks - 1].con_to_be_marked = json_node; } else { if (strcasecmp(last_key, "name") == 0) { @@ -341,88 +350,94 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); if (strcasecmp(buf, "none") == 0 || - strcasecmp(buf, "horizontal") == 0) + strcasecmp(buf, "horizontal") == 0) { json_node->last_split_layout = L_SPLITH; - else if (strcasecmp(buf, "vertical") == 0) + } else if (strcasecmp(buf, "vertical") == 0) { json_node->last_split_layout = L_SPLITV; - else + } else { LOG("Unhandled orientation: %s\n", buf); + } free(buf); } else if (strcasecmp(last_key, "border") == 0) { char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); - if (strcasecmp(buf, "none") == 0) - json_node->border_style = BS_NONE; - else if (strcasecmp(buf, "1pixel") == 0) { - json_node->border_style = BS_PIXEL; + if (strcasecmp(buf, "none") == 0) { + json_node->max_user_border_style = json_node->border_style = BS_NONE; + } else if (strcasecmp(buf, "1pixel") == 0) { + json_node->max_user_border_style = json_node->border_style = BS_PIXEL; json_node->current_border_width = 1; - } else if (strcasecmp(buf, "pixel") == 0) - json_node->border_style = BS_PIXEL; - else if (strcasecmp(buf, "normal") == 0) - json_node->border_style = BS_NORMAL; - else + } else if (strcasecmp(buf, "pixel") == 0) { + json_node->max_user_border_style = json_node->border_style = BS_PIXEL; + } else if (strcasecmp(buf, "normal") == 0) { + json_node->max_user_border_style = json_node->border_style = BS_NORMAL; + } else { LOG("Unhandled \"border\": %s\n", buf); + } free(buf); } else if (strcasecmp(last_key, "type") == 0) { char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); - if (strcasecmp(buf, "root") == 0) + if (strcasecmp(buf, "root") == 0) { json_node->type = CT_ROOT; - else if (strcasecmp(buf, "output") == 0) + } else if (strcasecmp(buf, "output") == 0) { json_node->type = CT_OUTPUT; - else if (strcasecmp(buf, "con") == 0) + } else if (strcasecmp(buf, "con") == 0) { json_node->type = CT_CON; - else if (strcasecmp(buf, "floating_con") == 0) + } else if (strcasecmp(buf, "floating_con") == 0) { json_node->type = CT_FLOATING_CON; - else if (strcasecmp(buf, "workspace") == 0) + } else if (strcasecmp(buf, "workspace") == 0) { json_node->type = CT_WORKSPACE; - else if (strcasecmp(buf, "dockarea") == 0) + } else if (strcasecmp(buf, "dockarea") == 0) { json_node->type = CT_DOCKAREA; - else + } else { LOG("Unhandled \"type\": %s\n", buf); + } free(buf); } else if (strcasecmp(last_key, "layout") == 0) { char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); - if (strcasecmp(buf, "default") == 0) + if (strcasecmp(buf, "default") == 0) { /* This set above when we read "orientation". */ json_node->layout = json_node->last_split_layout; - else if (strcasecmp(buf, "stacked") == 0) + } else if (strcasecmp(buf, "stacked") == 0) { json_node->layout = L_STACKED; - else if (strcasecmp(buf, "tabbed") == 0) + } else if (strcasecmp(buf, "tabbed") == 0) { json_node->layout = L_TABBED; - else if (strcasecmp(buf, "dockarea") == 0) + } else if (strcasecmp(buf, "dockarea") == 0) { json_node->layout = L_DOCKAREA; - else if (strcasecmp(buf, "output") == 0) + } else if (strcasecmp(buf, "output") == 0) { json_node->layout = L_OUTPUT; - else if (strcasecmp(buf, "splith") == 0) + } else if (strcasecmp(buf, "splith") == 0) { json_node->layout = L_SPLITH; - else if (strcasecmp(buf, "splitv") == 0) + } else if (strcasecmp(buf, "splitv") == 0) { json_node->layout = L_SPLITV; - else + } else { LOG("Unhandled \"layout\": %s\n", buf); + } free(buf); } else if (strcasecmp(last_key, "workspace_layout") == 0) { char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); - if (strcasecmp(buf, "default") == 0) + if (strcasecmp(buf, "default") == 0) { json_node->workspace_layout = L_DEFAULT; - else if (strcasecmp(buf, "stacked") == 0) + } else if (strcasecmp(buf, "stacked") == 0) { json_node->workspace_layout = L_STACKED; - else if (strcasecmp(buf, "tabbed") == 0) + } else if (strcasecmp(buf, "tabbed") == 0) { json_node->workspace_layout = L_TABBED; - else + } else { LOG("Unhandled \"workspace_layout\": %s\n", buf); + } free(buf); } else if (strcasecmp(last_key, "last_split_layout") == 0) { char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); - if (strcasecmp(buf, "splith") == 0) + if (strcasecmp(buf, "splith") == 0) { json_node->last_split_layout = L_SPLITH; - else if (strcasecmp(buf, "splitv") == 0) + } else if (strcasecmp(buf, "splitv") == 0) { json_node->last_split_layout = L_SPLITV; - else + } else { LOG("Unhandled \"last_splitlayout\": %s\n", buf); + } free(buf); } else if (strcasecmp(last_key, "mark") == 0) { DLOG("Found deprecated key \"mark\".\n"); @@ -434,24 +449,26 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { } else if (strcasecmp(last_key, "floating") == 0) { char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); - if (strcasecmp(buf, "auto_off") == 0) + if (strcasecmp(buf, "auto_off") == 0) { json_node->floating = FLOATING_AUTO_OFF; - else if (strcasecmp(buf, "auto_on") == 0) + } else if (strcasecmp(buf, "auto_on") == 0) { json_node->floating = FLOATING_AUTO_ON; - else if (strcasecmp(buf, "user_off") == 0) + } else if (strcasecmp(buf, "user_off") == 0) { json_node->floating = FLOATING_USER_OFF; - else if (strcasecmp(buf, "user_on") == 0) + } else if (strcasecmp(buf, "user_on") == 0) { json_node->floating = FLOATING_USER_ON; + } free(buf); } else if (strcasecmp(last_key, "scratchpad_state") == 0) { char *buf = NULL; sasprintf(&buf, "%.*s", (int)len, val); - if (strcasecmp(buf, "none") == 0) + if (strcasecmp(buf, "none") == 0) { json_node->scratchpad_state = SCRATCHPAD_NONE; - else if (strcasecmp(buf, "fresh") == 0) + } else if (strcasecmp(buf, "fresh") == 0) { json_node->scratchpad_state = SCRATCHPAD_FRESH; - else if (strcasecmp(buf, "changed") == 0) + } else if (strcasecmp(buf, "changed") == 0) { json_node->scratchpad_state = SCRATCHPAD_CHANGED; + } free(buf); } else if (strcasecmp(last_key, "previous_workspace_name") == 0) { FREE(previous_workspace_name); @@ -464,27 +481,33 @@ static int json_string(void *ctx, const unsigned char *val, size_t len) { static int json_int(void *ctx, long long val) { LOG("int %lld for key %s\n", val, last_key); /* For backwards compatibility with i3 < 4.8 */ - if (strcasecmp(last_key, "type") == 0) + if (strcasecmp(last_key, "type") == 0) { json_node->type = val; + } - if (strcasecmp(last_key, "fullscreen_mode") == 0) + if (strcasecmp(last_key, "fullscreen_mode") == 0) { json_node->fullscreen_mode = val; + } - if (strcasecmp(last_key, "num") == 0) + if (strcasecmp(last_key, "num") == 0) { json_node->num = val; + } - if (strcasecmp(last_key, "current_border_width") == 0) + if (strcasecmp(last_key, "current_border_width") == 0) { json_node->current_border_width = val; + } if (strcasecmp(last_key, "window_icon_padding") == 0) { json_node->window_icon_padding = val; } - if (strcasecmp(last_key, "depth") == 0) + if (strcasecmp(last_key, "depth") == 0) { json_node->depth = val; + } - if (!parsing_swallows && strcasecmp(last_key, "id") == 0) + if (!parsing_swallows && strcasecmp(last_key, "id") == 0) { json_node->old_id = val; + } if (parsing_focus) { struct focus_mapping *focus_mapping = scalloc(1, sizeof(struct focus_mapping)); @@ -494,22 +517,24 @@ static int json_int(void *ctx, long long val) { if (parsing_rect || parsing_window_rect || parsing_geometry) { Rect *r; - if (parsing_rect) + if (parsing_rect) { r = &(json_node->rect); - else if (parsing_window_rect) + } else if (parsing_window_rect) { r = &(json_node->window_rect); - else + } else { r = &(json_node->geometry); - if (strcasecmp(last_key, "x") == 0) + } + if (strcasecmp(last_key, "x") == 0) { r->x = val; - else if (strcasecmp(last_key, "y") == 0) + } else if (strcasecmp(last_key, "y") == 0) { r->y = val; - else if (strcasecmp(last_key, "width") == 0) + } else if (strcasecmp(last_key, "width") == 0) { r->width = val; - else if (strcasecmp(last_key, "height") == 0) + } else if (strcasecmp(last_key, "height") == 0) { r->height = val; - else + } else { ELOG("WARNING: unknown key %s in rect\n", last_key); + } DLOG("rect now: (%d, %d, %d, %d)\n", r->x, r->y, r->width, r->height); } @@ -528,16 +553,17 @@ static int json_int(void *ctx, long long val) { } } if (parsing_gaps) { - if (strcasecmp(last_key, "inner") == 0) + if (strcasecmp(last_key, "inner") == 0) { json_node->gaps.inner = val; - else if (strcasecmp(last_key, "top") == 0) + } else if (strcasecmp(last_key, "top") == 0) { json_node->gaps.top = val; - else if (strcasecmp(last_key, "right") == 0) + } else if (strcasecmp(last_key, "right") == 0) { json_node->gaps.right = val; - else if (strcasecmp(last_key, "bottom") == 0) + } else if (strcasecmp(last_key, "bottom") == 0) { json_node->gaps.bottom = val; - else if (strcasecmp(last_key, "left") == 0) + } else if (strcasecmp(last_key, "left") == 0) { json_node->gaps.left = val; + } } return 1; @@ -549,8 +575,9 @@ static int json_bool(void *ctx, int val) { to_focus = json_node; } - if (strcasecmp(last_key, "sticky") == 0) + if (strcasecmp(last_key, "sticky") == 0) { json_node->sticky = val; + } if (parsing_swallows) { if (strcasecmp(last_key, "restart_mode") == 0) { @@ -584,12 +611,14 @@ static int json_determine_content_shallower(void *ctx) { } static int json_determine_content_string(void *ctx, const unsigned char *val, size_t len) { - if (strcasecmp(last_key, "type") != 0 || content_level > 1) + if (strcasecmp(last_key, "type") != 0 || content_level > 1) { return 1; + } DLOG("string = %.*s, last_key = %s\n", (int)len, val, last_key); - if (strncasecmp((const char *)val, "workspace", len) == 0) + if (strncasecmp((const char *)val, "workspace", len) == 0) { content_result = JSON_CONTENT_WORKSPACE; + } return 0; } @@ -605,14 +634,14 @@ bool json_validate(const char *buf, const size_t len) { /* Allow multiple values, i.e. multiple nodes to attach */ yajl_config(hand, yajl_allow_multiple_values, true); - setlocale(LC_NUMERIC, "C"); + locale_t prev_locale = uselocale(numericC); if (yajl_parse(hand, (const unsigned char *)buf, len) != yajl_status_ok) { unsigned char *str = yajl_get_error(hand, 1, (const unsigned char *)buf, len); ELOG("JSON parsing error: %s\n", str); yajl_free_error(hand, str); valid = false; } - setlocale(LC_NUMERIC, ""); + uselocale(prev_locale); yajl_complete_parse(hand); yajl_free(hand); @@ -642,7 +671,7 @@ json_content_t json_determine_content(const char *buf, const size_t len) { yajl_config(hand, yajl_allow_comments, true); /* Allow multiple values, i.e. multiple nodes to attach */ yajl_config(hand, yajl_allow_multiple_values, true); - setlocale(LC_NUMERIC, "C"); + locale_t prev_locale = uselocale(numericC); const yajl_status stat = yajl_parse(hand, (const unsigned char *)buf, len); if (stat != yajl_status_ok && stat != yajl_status_client_canceled) { unsigned char *str = yajl_get_error(hand, 1, (const unsigned char *)buf, len); @@ -650,7 +679,7 @@ json_content_t json_determine_content(const char *buf, const size_t len) { yajl_free_error(hand, str); } - setlocale(LC_NUMERIC, ""); + uselocale(prev_locale); yajl_complete_parse(hand); yajl_free(hand); @@ -696,13 +725,14 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm parsing_geometry = false; parsing_focus = false; parsing_marks = false; - setlocale(LC_NUMERIC, "C"); + locale_t prev_locale = uselocale(numericC); const yajl_status stat = yajl_parse(hand, (const unsigned char *)buf, len); if (stat != yajl_status_ok) { unsigned char *str = yajl_get_error(hand, 1, (const unsigned char *)buf, len); ELOG("JSON parsing error: %s\n", str); - if (errormsg != NULL) + if (errormsg != NULL) { *errormsg = sstrdup((const char *)str); + } yajl_free_error(hand, str); while (incomplete-- > 0) { Con *parent = json_node->parent; @@ -720,7 +750,7 @@ void tree_append_json(Con *con, const char *buf, const size_t len, char **errorm * next time. */ con_fix_percent(con); - setlocale(LC_NUMERIC, ""); + uselocale(prev_locale); yajl_complete_parse(hand); yajl_free(hand); diff --git a/src/log.c b/src/log.c index 010d2a53..61489fc5 100644 --- a/src/log.c +++ b/src/log.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * log.c: Logging functions. @@ -94,9 +94,9 @@ static void store_log_markers(void) { */ void init_logging(void) { if (!errorfilename) { - if (!(errorfilename = get_process_filename("errorlog"))) + if (!(errorfilename = get_process_filename("errorlog"))) { fprintf(stderr, "Could not initialize errorlog\n"); - else { + } else { errorfile = fopen(errorfilename, "w"); if (!errorfile) { fprintf(stderr, "Could not initialize errorlog on %s: %s\n", @@ -121,10 +121,11 @@ void init_logging(void) { /* Start SHM logging if shmlog_size is > 0. shmlog_size is SHMLOG_SIZE by * default on development versions, and 0 on release versions. If it is * not > 0, the user has turned it off, so let's close the logbuffer. */ - if (shmlog_size > 0 && logbuffer == NULL) + if (shmlog_size > 0 && logbuffer == NULL) { open_logbuffer(); - else if (shmlog_size <= 0 && logbuffer) + } else if (shmlog_size <= 0 && logbuffer) { close_logbuffer(); + } atexit(purge_zerobyte_logfile); } @@ -228,7 +229,7 @@ void set_debug_logging(const bool _debug_logging) { * This is to be called by *LOG() which includes filename/linenumber/function. * */ -static void vlog(const bool print, const char *fmt, va_list args) { +__attribute__((format(printf, 2, 0))) static void vlog(const bool print, const char *fmt, va_list args) { /* Precisely one page to not consume too much memory but to hold enough * data to be useful. */ static char message[4096]; @@ -292,8 +293,9 @@ static void vlog(const bool print, const char *fmt, va_list args) { store_log_markers(); - if (print) + if (print) { fwrite(message, len, 1, stdout); + } log_broadcast_to_clients(message, len); } @@ -307,8 +309,9 @@ static void vlog(const bool print, const char *fmt, va_list args) { void verboselog(char *fmt, ...) { va_list args; - if (!logbuffer && !verbose) + if (!logbuffer && !verbose) { return; + } va_start(args, fmt); vlog(verbose, fmt, args); @@ -327,6 +330,9 @@ void errorlog(char *fmt, ...) { va_end(args); /* also log to the error logfile, if opened */ + if (!errorfile) { + return; + } va_start(args, fmt); vfprintf(errorfile, fmt, args); fflush(errorfile); @@ -342,8 +348,9 @@ void errorlog(char *fmt, ...) { void debuglog(char *fmt, ...) { va_list args; - if (!logbuffer && !(debug_logging)) + if (!logbuffer && !(debug_logging)) { return; + } va_start(args, fmt); vlog(debug_logging, fmt, args); @@ -359,15 +366,18 @@ void purge_zerobyte_logfile(void) { struct stat st; char *slash; - if (!errorfilename) + if (!errorfilename) { return; + } /* don't delete the log file if it contains something */ - if ((stat(errorfilename, &st)) == -1 || st.st_size > 0) + if ((stat(errorfilename, &st)) == -1 || st.st_size > 0) { return; + } - if (unlink(errorfilename) == -1) + if (unlink(errorfilename) == -1) { return; + } if ((slash = strrchr(errorfilename, '/')) != NULL) { *slash = '\0'; @@ -386,7 +396,7 @@ char *current_log_stream_socket_path = NULL; * the list of log clients. * */ -void log_new_client(EV_P_ struct ev_io *w, int revents) { +void log_new_client(EV_P_ ev_io *w, int revents) { struct sockaddr_un peer; socklen_t len = sizeof(struct sockaddr_un); int fd; diff --git a/src/main.c b/src/main.c index 6d6b9c9c..acbd180d 100644 --- a/src/main.c +++ b/src/main.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * main.c: Initialization, main loop @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -37,6 +36,8 @@ #include "i3-atoms_NET_SUPPORTED.xmacro.h" #include "i3-atoms_rest.xmacro.h" +locale_t numericC; + /* The original value of RLIMIT_CORE when i3 was started. We need to restore * this before starting any other process, since we set RLIMIT_CORE to * RLIM_INFINITY for i3 debugging versions. */ @@ -47,7 +48,7 @@ int listen_fds; /* We keep the xcb_prepare watcher around to be able to enable and disable it * temporarily for drag_pointer(). */ -static struct ev_prepare *xcb_prepare; +static ev_prepare *xcb_prepare; char **start_argv; @@ -117,7 +118,7 @@ I3_REST_ATOMS_XMACRO * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop * */ -static void xcb_got_event(EV_P_ struct ev_io *w, int revents) { +static void xcb_got_event(EV_P_ ev_io *w, int revents) { /* empty, because xcb_prepare_cb are used */ } @@ -134,9 +135,9 @@ static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) { while ((event = xcb_poll_for_event(conn)) != NULL) { if (event->response_type == 0) { - if (event_is_ignored(event->sequence, 0)) + if (event_is_ignored(event->sequence, 0)) { DLOG("Expected X11 Error received for sequence %x\n", event->sequence); - else { + } else { xcb_generic_error_t *error = (xcb_generic_error_t *)event; DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n", error->sequence, error->error_code); @@ -146,8 +147,7 @@ static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) { } /* Strip off the highest bit (set if the event is generated) */ - int type = (event->response_type & 0x7F); - + const int type = (event->response_type & 0x7F); handle_event(type, event); free(event); @@ -238,8 +238,8 @@ static void handle_term_signal(struct ev_loop *loop, ev_signal *signal, int reve * */ static void setup_term_handlers(void) { - static struct ev_signal signal_watchers[6]; - size_t num_watchers = sizeof(signal_watchers) / sizeof(signal_watchers[0]); + static ev_signal signal_watchers[6]; + const size_t num_watchers = sizeof(signal_watchers) / sizeof(signal_watchers[0]); /* We have to rely on libev functionality here and should not use * sigaction handlers because we need to invoke the exit handlers @@ -252,7 +252,7 @@ static void setup_term_handlers(void) { ev_signal_init(&signal_watchers[2], handle_term_signal, SIGALRM); ev_signal_init(&signal_watchers[3], handle_term_signal, SIGTERM); ev_signal_init(&signal_watchers[4], handle_term_signal, SIGUSR1); - ev_signal_init(&signal_watchers[5], handle_term_signal, SIGUSR1); + ev_signal_init(&signal_watchers[5], handle_term_signal, SIGUSR2); for (size_t i = 0; i < num_watchers; i++) { ev_signal_start(main_loop, &signal_watchers[i]); /* The signal handlers should not block ev_run from returning @@ -316,14 +316,16 @@ int main(int argc, char *argv[]) { int option_index = 0, opt; setlocale(LC_ALL, ""); + numericC = newlocale(LC_NUMERIC_MASK, "C", 0); /* Get the RLIMIT_CORE limit at startup time to restore this before * starting processes. */ getrlimit(RLIMIT_CORE, &original_rlimit_core); /* Disable output buffering to make redirects in .xsession actually useful for debugging */ - if (!isatty(fileno(stdout))) + if (!isatty(fileno(stdout))) { setbuf(stdout, NULL); + } srand(time(NULL)); @@ -506,20 +508,22 @@ int main(int argc, char *argv[]) { } int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); - if (sockfd == -1) + if (sockfd == -1) { err(EXIT_FAILURE, "Could not create socket"); + } - struct sockaddr_un addr; - memset(&addr, 0, sizeof(struct sockaddr_un)); + struct sockaddr_un addr = {0}; addr.sun_family = AF_LOCAL; strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); FREE(socket_path); - if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) + if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) { err(EXIT_FAILURE, "Could not connect to i3"); + } if (ipc_send_message(sockfd, strlen(payload), I3_IPC_MESSAGE_TYPE_RUN_COMMAND, - (uint8_t *)payload) == -1) + (uint8_t *)payload) == -1) { err(EXIT_FAILURE, "IPC: write()"); + } FREE(payload); uint32_t reply_length; @@ -527,13 +531,15 @@ int main(int argc, char *argv[]) { uint8_t *reply; int ret; if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) { - if (ret == -1) + if (ret == -1) { err(EXIT_FAILURE, "IPC: read()"); + } return 1; } - if (reply_type != I3_IPC_REPLY_TYPE_COMMAND) + if (reply_type != I3_IPC_REPLY_TYPE_COMMAND) { errx(EXIT_FAILURE, "IPC: received reply of type %d but expected %d (COMMAND)", reply_type, I3_IPC_REPLY_TYPE_COMMAND); - printf("%.*s\n", reply_length, reply); + } + printf("%.*s\n", reply_length, (char *)reply); FREE(reply); return 0; } @@ -545,44 +551,51 @@ int main(int argc, char *argv[]) { if (is_debug_build()) { struct rlimit limit = {RLIM_INFINITY, RLIM_INFINITY}; setrlimit(RLIMIT_CORE, &limit); + LOG("CORE DUMPS: You are running a development version of i3, so coredumps were automatically enabled (ulimit -c unlimited).\n"); +#ifdef __linux__ /* The following code is helpful, but not required. We thus don’t pay * much attention to error handling, non-linux or other edge cases. */ - LOG("CORE DUMPS: You are running a development version of i3, so coredumps were automatically enabled (ulimit -c unlimited).\n"); - size_t cwd_size = 1024; - char *cwd = smalloc(cwd_size); - char *cwd_ret; - while ((cwd_ret = getcwd(cwd, cwd_size)) == NULL && errno == ERANGE) { - cwd_size = cwd_size * 2; - cwd = srealloc(cwd, cwd_size); - } - if (cwd_ret != NULL) + char *cwd = getcwd(NULL, 0); + if (cwd != NULL) { LOG("CORE DUMPS: Your current working directory is \"%s\".\n", cwd); + free(cwd); + } + const size_t buffer_size = 1024; + char *buffer = scalloc(buffer_size, sizeof(char)); + int patternfd; if ((patternfd = open("/proc/sys/kernel/core_pattern", O_RDONLY)) >= 0) { - memset(cwd, '\0', cwd_size); - if (read(patternfd, cwd, cwd_size) > 0) - /* a trailing newline is included in cwd */ - LOG("CORE DUMPS: Your core_pattern is: %s", cwd); + if (read(patternfd, buffer, buffer_size - 1) > 0) { + /* a trailing newline is included in buffer */ + LOG("CORE DUMPS: Your core_pattern is: %s", buffer); + } close(patternfd); } - free(cwd); + free(buffer); +#endif } - LOG("i3 %s starting\n", i3_version); + LOG("i3 %s starting\n", _i3_version); conn = xcb_connect(NULL, &conn_screen); - if (xcb_connection_has_error(conn)) + if (xcb_connection_has_error(conn)) { errx(EXIT_FAILURE, "Cannot open display"); + } sndisplay = sn_xcb_display_new(conn, NULL, NULL); /* Initialize the libev event loop. This needs to be done before loading * the config file because the parser will install an ev_child watcher - * for the nagbar when config errors are found. */ + * for the nagbar when config errors are found. + * + * Main loop must be ev's default loop because (at the moment of writing) + * only the default loop can handle ev_child events and reap zombies + * (the start_application routine relies on that too). */ main_loop = EV_DEFAULT; - if (main_loop == NULL) + if (main_loop == NULL) { die("Could not initialize libev. Bad LIBEV_FLAGS?\n"); + } root_screen = xcb_aux_get_screen(conn, conn_screen); root = root_screen->root; @@ -678,10 +691,11 @@ int main(int argc, char *argv[]) { if (config.ipc_socket_path == NULL) { /* Fall back to a file name in /tmp/ based on the PID */ - if ((config.ipc_socket_path = getenv("I3SOCK")) == NULL) + if ((config.ipc_socket_path = getenv("I3SOCK")) == NULL) { config.ipc_socket_path = get_process_filename("ipc-socket"); - else + } else { config.ipc_socket_path = sstrdup(config.ipc_socket_path); + } } /* Create the UNIX domain socket for IPC */ int ipc_socket = create_socket(config.ipc_socket_path, ¤t_socketpath); @@ -751,12 +765,12 @@ int main(int argc, char *argv[]) { xcb_set_selection_owner(conn, wm_sn_selection_owner, wm_sn, last_timestamp); if (selection_reply && selection_reply->owner != XCB_NONE) { - unsigned int usleep_time = 100000; /* 0.1 seconds */ - int check_rounds = 150; /* Wait for a maximum of 15 seconds */ + int check_rounds = 150; /* Wait for a maximum of 15 seconds */ xcb_get_geometry_reply_t *geom_reply = NULL; DLOG("waiting for old WM_Sn selection owner to exit"); do { + const unsigned int usleep_time = 100000; free(geom_reply); usleep(usleep_time); if (check_rounds-- == 0) { @@ -777,8 +791,7 @@ int main(int argc, char *argv[]) { union { xcb_client_message_event_t message; char storage[32]; - } event; - memset(&event, 0, sizeof(event)); + } event = {0}; event.message.response_type = XCB_CLIENT_MESSAGE; event.message.window = root_screen->root; event.message.format = 32; @@ -903,8 +916,9 @@ int main(int argc, char *argv[]) { xcb_numlock_mask = aio_get_mod_mask_for(XCB_NUM_LOCK, keysyms); - if (!load_keymap()) + if (!load_keymap()) { die("Could not load keymap\n"); + } translate_keysyms(); grab_all_keys(conn); @@ -921,14 +935,16 @@ int main(int argc, char *argv[]) { rmdir(dir); } } - if (needs_tree_init) + if (needs_tree_init) { tree_init(greply); + } free(greply); /* Setup fake outputs for testing */ - if (fake_outputs == NULL && config.fake_outputs != NULL) + if (fake_outputs == NULL && config.fake_outputs != NULL) { fake_outputs = config.fake_outputs; + } if (fake_outputs != NULL) { fake_outputs_init(fake_outputs); @@ -953,8 +969,9 @@ int main(int argc, char *argv[]) { TAILQ_FOREACH (con, &(croot->nodes_head), nodes) { Output *output; TAILQ_FOREACH (output, &outputs, outputs) { - if (output->active || strcmp(con->name, output_primary_name(output)) != 0) + if (output->active || strcmp(con->name, output_primary_name(output)) != 0) { continue; + } /* This will correctly correlate the output with its content * container. We need to make the connection to properly @@ -973,28 +990,28 @@ int main(int argc, char *argv[]) { scratchpad_fix_resolution(); - xcb_query_pointer_reply_t *pointerreply; Output *output = NULL; - if (!(pointerreply = xcb_query_pointer_reply(conn, pointercookie, NULL))) { + xcb_query_pointer_reply_t *pointer_reply = xcb_query_pointer_reply(conn, pointercookie, NULL); + if (!pointer_reply) { ELOG("Could not query pointer position, using first screen\n"); } else { - DLOG("Pointer at %d, %d\n", pointerreply->root_x, pointerreply->root_y); - output = get_output_containing(pointerreply->root_x, pointerreply->root_y); + DLOG("Pointer at %d, %d\n", pointer_reply->root_x, pointer_reply->root_y); + output = get_output_containing(pointer_reply->root_x, pointer_reply->root_y); if (!output) { ELOG("ERROR: No screen at (%d, %d), starting on the first screen\n", - pointerreply->root_x, pointerreply->root_y); + pointer_reply->root_x, pointer_reply->root_y); } } if (!output) { output = get_first_output(); } con_activate(con_descend_focused(output_get_content(output->con))); - free(pointerreply); + free(pointer_reply); tree_render(); /* Listen to the IPC socket for clients */ - struct ev_io *ipc_io = scalloc(1, sizeof(struct ev_io)); + ev_io *ipc_io = scalloc(1, sizeof(struct ev_io)); ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ); ev_io_start(main_loop, ipc_io); @@ -1002,10 +1019,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); + 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(ev_io)); ev_io_init(log_io, log_new_client, log_socket, EV_READ); ev_io_start(main_loop, log_io); } @@ -1013,12 +1031,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. */ + 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 +1052,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(ev_io)); + ev_io_init(socket_ipc_io, ipc_new_client, fd, EV_READ); + ev_io_start(main_loop, socket_ipc_io); } } @@ -1056,8 +1075,8 @@ int main(int argc, char *argv[]) { /* Set the ewmh desktop properties. */ ewmh_update_desktop_properties(); - struct ev_io *xcb_watcher = scalloc(1, sizeof(struct ev_io)); - xcb_prepare = scalloc(1, sizeof(struct ev_prepare)); + ev_io *xcb_watcher = scalloc(1, sizeof(struct ev_io)); + xcb_prepare = scalloc(1, sizeof(ev_prepare)); ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); ev_io_start(main_loop, xcb_watcher); @@ -1091,13 +1110,14 @@ int main(int argc, char *argv[]) { } /* Strip off the highest bit (set if the event is generated) */ - int type = (event->response_type & 0x7F); + const int type = (event->response_type & 0x7F); /* We still need to handle MapRequests which are sent in the * timespan starting from when we register as a window manager and * this piece of code which drops events. */ - if (type == XCB_MAP_REQUEST) + if (type == XCB_MAP_REQUEST) { handle_event(type, event); + } free(event); } @@ -1125,14 +1145,9 @@ int main(int argc, char *argv[]) { } } -#if defined(__OpenBSD__) - if (pledge("stdio rpath wpath cpath proc exec unix", NULL) == -1) - err(EXIT_FAILURE, "pledge"); -#endif - - if (!disable_signalhandler) + if (!disable_signalhandler) { setup_signal_handler(); - else { + } else { struct sigaction action; action.sa_sigaction = handle_core_signal; @@ -1144,8 +1159,9 @@ int main(int argc, char *argv[]) { sigaction(SIGILL, &action, NULL) == -1 || sigaction(SIGABRT, &action, NULL) == -1 || sigaction(SIGFPE, &action, NULL) == -1 || - sigaction(SIGSEGV, &action, NULL) == -1) + sigaction(SIGSEGV, &action, NULL) == -1) { ELOG("Could not setup signal handler.\n"); + } } setup_term_handlers(); @@ -1196,6 +1212,23 @@ int main(int argc, char *argv[]) { * when calling exit() */ atexit(i3_exit); + /* There might be children who died before we initialized the event loop, + * e.g., when restarting i3 (see #5756). + * To not carry zombie children around, raise the signal to invite libev to + * reap them. + * + * Note that there is no race condition between raising the signal below and + * entering the event loop later: the signal is just to notify libev that + * zombies might already be there. Actual reaping will take place in the + * event loop anyway. */ + (void)raise(SIGCHLD); + sd_notify(1, "READY=1"); 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); } diff --git a/src/manage.c b/src/manage.c index a7de243e..3347d590 100644 --- a/src/manage.c +++ b/src/manage.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * manage.c: Initially managing new windows (or existing ones on restart). @@ -43,25 +43,25 @@ static void _remove_matches(Con *con) { */ void manage_existing_windows(xcb_window_t root) { xcb_query_tree_reply_t *reply; - int i, len; - xcb_window_t *children; - xcb_get_window_attributes_cookie_t *cookies; /* Get the tree of windows whose parent is the root window (= all) */ - if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL) + if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL) { return; + } - len = xcb_query_tree_children_length(reply); - cookies = smalloc(len * sizeof(*cookies)); + int len = xcb_query_tree_children_length(reply); + xcb_get_window_attributes_cookie_t *cookies = smalloc(len * sizeof(*cookies)); /* Request the window attributes for every window */ - children = xcb_query_tree_children(reply); - for (i = 0; i < len; ++i) + xcb_window_t *children = xcb_query_tree_children(reply); + for (int i = 0; i < len; ++i) { cookies[i] = xcb_get_window_attributes(conn, children[i]); + } /* Call manage_window with the attributes for every window */ - for (i = 0; i < len; ++i) + for (int i = 0; i < len; ++i) { manage_window(children[i], cookies[i], true); + } free(reply); free(cookies); @@ -304,14 +304,16 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki nc = con_descend_tiling_focused(assigned_ws); DLOG("focused on ws %s: %p / %s\n", assigned_ws->name, nc, nc->name); - if (nc->type == CT_WORKSPACE) + if (nc->type == CT_WORKSPACE) { nc = tree_open_con(nc, cwindow); - else + } else { nc = tree_open_con(nc->parent, cwindow); + } /* set the urgency hint on the window if the workspace is not visible */ - if (!workspace_is_visible(assigned_ws)) + if (!workspace_is_visible(assigned_ws)) { urgency_hint = true; + } } else if (cwindow->wm_desktop != NET_WM_DESKTOP_NONE && cwindow->wm_desktop != NET_WM_DESKTOP_ALL && (wm_desktop_ws = ewmh_get_workspace_by_index(cwindow->wm_desktop)) != NULL) { @@ -323,27 +325,30 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki wm_desktop_ws, wm_desktop_ws->name, cwindow->wm_desktop); nc = con_descend_tiling_focused(wm_desktop_ws); - if (nc->type == CT_WORKSPACE) + if (nc->type == CT_WORKSPACE) { nc = tree_open_con(nc, cwindow); - else + } else { nc = tree_open_con(nc->parent, cwindow); + } } else if (startup_ws) { /* If it was started on a specific workspace, we want to open it there. */ DLOG("Using workspace on which this application was started (%s)\n", startup_ws); nc = con_descend_tiling_focused(workspace_get(startup_ws)); DLOG("focused on ws %s: %p / %s\n", startup_ws, nc, nc->name); - if (nc->type == CT_WORKSPACE) + if (nc->type == CT_WORKSPACE) { nc = tree_open_con(nc, cwindow); - else + } else { nc = tree_open_con(nc->parent, cwindow); + } } else { /* If not, insert it at the currently focused position */ if (focused->type == CT_CON && con_accepts_window(focused)) { LOG("using current container, focused = %p, focused->name = %s\n", focused, focused->name); nc = focused; - } else + } else { nc = tree_open_con(NULL, cwindow); + } } if ((assignment = assignment_for(cwindow, A_TO_OUTPUT))) { @@ -407,8 +412,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki * of an output, move the window to that output. This is * needed e.g. for LibreOffice Impress multi-monitor * presentations to work out of the box. */ - if (output != NULL) + if (output != NULL) { con_move_to_output(nc, output, false); + } con_toggle_fullscreen(nc, CF_OUTPUT); } fs = NULL; @@ -457,6 +463,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (xcb_reply_contains_atom(type_reply, A__NET_WM_WINDOW_TYPE_DIALOG) || xcb_reply_contains_atom(type_reply, A__NET_WM_WINDOW_TYPE_UTILITY) || xcb_reply_contains_atom(type_reply, A__NET_WM_WINDOW_TYPE_TOOLBAR) || + xcb_reply_contains_atom(type_reply, A__NET_WM_WINDOW_TYPE_NOTIFICATION) || xcb_reply_contains_atom(type_reply, A__NET_WM_WINDOW_TYPE_SPLASH) || xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_MODAL) || (cwindow->max_width > 0 && cwindow->max_height > 0 && @@ -466,8 +473,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki want_floating = true; } - if (xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_STICKY)) + if (xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_STICKY)) { nc->sticky = true; + } /* We ignore the hint for an internal workspace because windows in the * scratchpad also have this value, but upon restarting i3 we don't want @@ -491,7 +499,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki if (config.popup_during_fullscreen == PDF_LEAVE_FULLSCREEN && fs != NULL) { DLOG("There is a fullscreen window, leaving fullscreen mode\n"); - con_toggle_fullscreen(fs, CF_OUTPUT); + con_disable_fullscreen(fs); } else if (config.popup_during_fullscreen == PDF_SMART && fs != NULL && fs->window != NULL) { @@ -500,16 +508,22 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki } /* dock clients cannot be floating, that makes no sense */ - if (cwindow->dock) + if (cwindow->dock) { want_floating = false; + } /* Store the requested geometry. The width/height gets raised to at least * 75x50 when entering floating mode, which is the minimum size for a * window to be useful (smaller windows are usually overlays/toolbars/… * which are not managed by the wm anyways). We store the original geometry * here because it’s used for dock clients. */ - if (nc->geometry.width == 0) + if (nc->geometry.width == 0) { nc->geometry = (Rect){geom->x, geom->y, geom->width, geom->height}; + } + + if (config.popup_during_fullscreen == PDF_ALL && want_floating && fs != NULL) { + set_focus = true; + } if (want_floating) { DLOG("geometry = %d x %d\n", nc->geometry.width, nc->geometry.height); @@ -633,15 +647,13 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki xcb_discard_reply(conn, wm_user_time_cookie.sequence); } - if (set_focus) { - /* Even if the client doesn't want focus, we still need to focus the - * container to not break focus workflows. Our handling towards X will - * take care of not setting the input focus. However, one exception to - * this are clients using the globally active input model which we - * don't want to focus at all. */ - if (nc->window->doesnt_accept_focus && !nc->window->needs_take_focus) { - set_focus = false; - } + /* Even if the client doesn't want focus, we still need to focus the + * container to not break focus workflows. Our handling towards X will take + * care of not setting the input focus. However, one exception to this are + * clients using the globally active input model which we don't want to + * focus at all. */ + if (nc->window->doesnt_accept_focus && !nc->window->needs_take_focus) { + set_focus = false; } /* Defer setting focus after the 'new' event has been sent to ensure the @@ -678,27 +690,34 @@ out: free(attr); } +static Con *placeholder_for_con(Con *con) { + /* Make sure this windows hasn't already been swallowed. */ + if (con->window->swallowed) { + return NULL; + } + Match *match; + Con *nc = con_for_window(croot, con->window, &match); + if (nc == NULL || nc->window == NULL || nc->window == con->window) { + return NULL; + } + /* Make sure the placeholder that wants to swallow this window didn't spawn + * after the window to follow current behavior: adding a placeholder won't + * swallow windows currently managed. */ + if (nc->window->managed_since > con->window->managed_since) { + return NULL; + } + return nc; +} + /* * Remanages a window: performs a swallow check and runs assignments. * Returns con for the window regardless if it updated. * */ Con *remanage_window(Con *con) { - /* Make sure this windows hasn't already been swallowed. */ - if (con->window->swallowed) { - run_assignments(con->window); - return con; - } - Match *match; - Con *nc = con_for_window(croot, con->window, &match); - if (nc == NULL || nc->window == NULL || nc->window == con->window) { - run_assignments(con->window); - return con; - } - /* Make sure the placeholder that wants to swallow this window didn't spawn - * after the window to follow current behavior: adding a placeholder won't - * swallow windows currently managed. */ - if (nc->window->managed_since > con->window->managed_since) { + Con *nc = placeholder_for_con(con); + if (!nc) { + /* The con is not updated, just run assignments */ run_assignments(con->window); return con; } diff --git a/src/match.c b/src/match.c index 34314e25..3acb88a7 100644 --- a/src/match.c +++ b/src/match.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * A "match" is a data structure which acts like a mask or expression to match @@ -80,6 +80,7 @@ void match_copy(Match *dest, Match *src) { DUPLICATE_REGEX(instance); DUPLICATE_REGEX(window_role); DUPLICATE_REGEX(workspace); + DUPLICATE_REGEX(machine); } /* @@ -167,12 +168,14 @@ bool match_matches_window(Match *match, i3Window *window) { } if (match->workspace != NULL) { - if ((con = con_by_window_id(window->id)) == NULL) + if ((con = con_by_window_id(window->id)) == NULL) { return false; + } Con *ws = con_get_workspace(con); - if (ws == NULL) + if (ws == NULL) { return false; + } if (strcmp(match->workspace->pattern, "__focused__") == 0 && strcmp(ws->name, con_get_workspace(focused)->name) == 0) { @@ -198,8 +201,9 @@ bool match_matches_window(Match *match, i3Window *window) { } if (match->mark != NULL) { - if ((con = con_by_window_id(window->id)) == NULL) + if ((con = con_by_window_id(window->id)) == NULL) { return false; + } bool matched = false; mark_t *mark; diff --git a/src/move.c b/src/move.c index 1a161647..036f65ec 100644 --- a/src/move.c +++ b/src/move.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * move.c: Moving containers into some direction. @@ -241,6 +241,7 @@ static void move_to_output_directed(Con *con, direction_t direction) { con_focus(con); focused = old_ws; workspace_show(ws); + con_focus(con); } /* force re-painting the indicators */ @@ -292,6 +293,7 @@ void tree_move(Con *con, direction_t direction) { if (con_is_floating(con)) { /* this is a floating con, we just disable floating */ floating_disable(con); + run_assignments(con->window); return; } if (con_inside_floating(con)) { @@ -354,8 +356,9 @@ void tree_move(Con *con, direction_t direction) { /* This is the container *above* 'con' (an ancestor of con) which is inside * 'same_orientation' */ Con *above = con; - while (above->parent != same_orientation) + while (above->parent != same_orientation) { above = above->parent; + } /* Enforce the fullscreen focus restrictions. */ if (!con_fullscreen_permits_focusing(above->parent)) { diff --git a/src/output.c b/src/output.c index 1c22e1cf..a1d6aafa 100644 --- a/src/output.c +++ b/src/output.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * output.c: Output (monitor) related functions. @@ -33,13 +33,17 @@ Con *output_get_content(Con *output) { Output *get_output_from_string(Output *current_output, const char *output_str) { if (strcasecmp(output_str, "current") == 0) { return get_output_for_con(focused); - } else if (strcasecmp(output_str, "left") == 0) { + } + if (strcasecmp(output_str, "left") == 0) { return get_output_next_wrap(D_LEFT, current_output); - } else if (strcasecmp(output_str, "right") == 0) { + } + if (strcasecmp(output_str, "right") == 0) { return get_output_next_wrap(D_RIGHT, current_output); - } else if (strcasecmp(output_str, "up") == 0) { + } + if (strcasecmp(output_str, "up") == 0) { return get_output_next_wrap(D_UP, current_output); - } else if (strcasecmp(output_str, "down") == 0) { + } + if (strcasecmp(output_str, "down") == 0) { return get_output_next_wrap(D_DOWN, current_output); } @@ -54,6 +58,12 @@ char *output_primary_name(Output *output) { return SLIST_FIRST(&output->names_head)->name; } +/* + * Retrieves the output for a given container. Never returns NULL. + * There is an assertion that _will_ fail if the container is inside an + * internal workspace. Use con_is_internal() if needed before calling this + * function. + */ Output *get_output_for_con(Con *con) { Con *output_con = con_get_output(con); Output *output = get_output_by_name(output_con->name, true); @@ -77,13 +87,13 @@ Output *get_output_for_con(Con *con) { void output_push_sticky_windows(Con *old_focus) { Con *output; TAILQ_FOREACH (output, &(croot->focus_head), focused) { - Con *workspace, *visible_ws = NULL; + Con *visible_ws = NULL; GREP_FIRST(visible_ws, output_get_content(output), workspace_is_visible(child)); /* We use this loop instead of TAILQ_FOREACH to avoid problems if the * sticky window was the last window on that workspace as moving it in * this case will close the workspace. */ - for (workspace = TAILQ_FIRST(&(output_get_content(output)->focus_head)); + for (Con *workspace = TAILQ_FIRST(&(output_get_content(output)->focus_head)); workspace != TAILQ_END(&(output_get_content(output)->focus_head));) { Con *current_ws = workspace; workspace = TAILQ_NEXT(workspace, focused); @@ -91,8 +101,7 @@ void output_push_sticky_windows(Con *old_focus) { /* Since moving the windows actually removes them from the list of * floating windows on this workspace, here too we need to use * another loop than TAILQ_FOREACH. */ - Con *child; - for (child = TAILQ_FIRST(&(current_ws->focus_head)); + for (Con *child = TAILQ_FIRST(&(current_ws->focus_head)); child != TAILQ_END(&(current_ws->focus_head));) { Con *current = child; child = TAILQ_NEXT(child, focused); diff --git a/src/parser_util.c b/src/parser_util.c new file mode 100644 index 00000000..a0fd1e10 --- /dev/null +++ b/src/parser_util.c @@ -0,0 +1,101 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved tiling window manager + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) + * + * parser_util.c: utility functions for the config and commands parser + * + */ +#include "all.h" +#include "parser_util.h" + +/* + * Pushes a string (identified by 'identifier') on the stack. We simply use a + * single array, since the number of entries we have to store is very small. + * + */ +void parser_push_string(struct stack *stack, const char *identifier, const char *str) { + for (int c = 0; c < 10; c++) { + if (stack->stack[c].identifier != NULL && + strcmp(stack->stack[c].identifier, identifier) != 0) { + continue; + } + if (stack->stack[c].identifier == NULL) { + /* Found a free slot, let's store it here. */ + stack->stack[c].identifier = identifier; + stack->stack[c].val.str = sstrdup(str); + stack->stack[c].type = STACK_STR; + } else { + /* Append the value. */ + char *prev = stack->stack[c].val.str; + sasprintf(&stack->stack[c].val.str, "%s,%s", prev, str); + free(prev); + } + return; + } + + /* When we arrive here, the stack is full. This should not happen and + * means there's either a bug in this parser or the specification + * contains a command with more than 10 identified tokens. */ + fprintf(stderr, "BUG: parser stack full. This means either a bug " + "in the code, or a new command which contains more than " + "10 identified tokens.\n"); + exit(EXIT_FAILURE); +} + +void parser_push_long(struct stack *stack, const char *identifier, const long num) { + for (int c = 0; c < 10; c++) { + if (stack->stack[c].identifier != NULL) { + continue; + } + /* Found a free slot, let's store it here. */ + stack->stack[c].identifier = identifier; + stack->stack[c].val.num = num; + stack->stack[c].type = STACK_LONG; + return; + } + + /* When we arrive here, the stack is full. This should not happen and + * means there's either a bug in this parser or the specification + * contains a command with more than 10 identified tokens. */ + fprintf(stderr, "BUG: parser stack full. This means either a bug " + "in the code, or a new command which contains more than " + "10 identified tokens.\n"); + exit(EXIT_FAILURE); +} + +const char *parser_get_string(const struct stack *stack, const char *identifier) { + for (int c = 0; c < 10; c++) { + if (stack->stack[c].identifier == NULL) { + break; + } + if (strcmp(identifier, stack->stack[c].identifier) == 0) { + return stack->stack[c].val.str; + } + } + return NULL; +} + +long parser_get_long(const struct stack *stack, const char *identifier) { + for (int c = 0; c < 10; c++) { + if (stack->stack[c].identifier == NULL) { + break; + } + if (strcmp(identifier, stack->stack[c].identifier) == 0) { + return stack->stack[c].val.num; + } + } + return 0; +} + +void parser_clear_stack(struct stack *stack) { + for (int c = 0; c < 10; c++) { + if (stack->stack[c].type == STACK_STR) { + free(stack->stack[c].val.str); + } + stack->stack[c].identifier = NULL; + stack->stack[c].val.str = NULL; + stack->stack[c].val.num = 0; + } +} diff --git a/src/randr.c b/src/randr.c index fb733205..26ee13db 100644 --- a/src/randr.c +++ b/src/randr.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * For more information on RandR, please see the X.org RandR specification at @@ -106,8 +106,9 @@ static bool any_randr_output_active(void) { Output *output; TAILQ_FOREACH (output, &outputs, outputs) { - if (output != root_output && !output->to_be_disabled && output->active) + if (output != root_output && !output->to_be_disabled && output->active) { return true; + } } return false; @@ -121,13 +122,15 @@ static bool any_randr_output_active(void) { Output *get_output_containing(unsigned int x, unsigned int y) { Output *output; TAILQ_FOREACH (output, &outputs, outputs) { - if (!output->active) + if (!output->active) { continue; + } DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n", x, y, output->rect.x, output->rect.y, output->rect.width, output->rect.height); if (x >= output->rect.x && x < (output->rect.x + output->rect.width) && - y >= output->rect.y && y < (output->rect.y + output->rect.height)) + y >= output->rect.y && y < (output->rect.y + output->rect.height)) { return output; + } } return NULL; @@ -155,14 +158,16 @@ Output *get_output_from_rect(Rect rect) { Output *get_output_with_dimensions(Rect rect) { Output *output; TAILQ_FOREACH (output, &outputs, outputs) { - if (!output->active) + if (!output->active) { continue; + } DLOG("comparing x=%d y=%d %dx%d with x=%d and y=%d %dx%d\n", rect.x, rect.y, rect.width, rect.height, output->rect.x, output->rect.y, output->rect.width, output->rect.height); if (rect.x == output->rect.x && rect.width == output->rect.width && - rect.y == output->rect.y && rect.height == output->rect.height) + rect.y == output->rect.y && rect.height == output->rect.height) { return output; + } } return NULL; @@ -177,23 +182,28 @@ Output *get_output_with_dimensions(Rect rect) { */ Output *output_containing_rect(Rect rect) { Output *output; - int lx = rect.x, uy = rect.y; - int rx = rect.x + rect.width, by = rect.y + rect.height; - long max_area = 0; + const int lx = rect.x; + const int uy = rect.y; + const int rx = rect.x + rect.width; + const int by = rect.y + rect.height; Output *result = NULL; TAILQ_FOREACH (output, &outputs, outputs) { - if (!output->active) + if (!output->active) { continue; - int lx_o = (int)output->rect.x, uy_o = (int)output->rect.y; - int rx_o = (int)(output->rect.x + output->rect.width), by_o = (int)(output->rect.y + output->rect.height); + } + const int lx_o = (int)output->rect.x; + const int uy_o = (int)output->rect.y; + const int rx_o = (int)(output->rect.x + output->rect.width); + const int by_o = (int)(output->rect.y + output->rect.height); DLOG("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n", rect.x, rect.y, output->rect.x, output->rect.y, output->rect.width, output->rect.height); - int left = max(lx, lx_o); - int right = min(rx, rx_o); - int bottom = min(by, by_o); - int top = max(uy, uy_o); + const int left = max(lx, lx_o); + const int right = min(rx, rx_o); + const int bottom = min(by, by_o); + const int top = max(uy, uy_o); if (left < right && bottom > top) { - long area = (right - left) * (bottom - top); + const long max_area = 0; + const long area = (right - left) * (bottom - top); if (area > max_area) { result = output; } @@ -217,18 +227,20 @@ Output *get_output_next_wrap(direction_t direction, Output *current) { /* If no output can be found, wrap */ if (!best) { direction_t opposite; - if (direction == D_RIGHT) + if (direction == D_RIGHT) { opposite = D_LEFT; - else if (direction == D_LEFT) + } else if (direction == D_LEFT) { opposite = D_RIGHT; - else if (direction == D_DOWN) + } else if (direction == D_DOWN) { opposite = D_UP; - else + } else { opposite = D_DOWN; + } best = get_output_next(opposite, current, FARTHEST_OUTPUT); } - if (!best) + if (!best) { best = current; + } DLOG("current = %s, best = %s\n", output_primary_name(current), output_primary_name(best)); return best; } @@ -244,33 +256,34 @@ Output *get_output_next_wrap(direction_t direction, Output *current) { * specified (note that “current” counts as such an output). * */ -Output *get_output_next(direction_t direction, Output *current, output_close_far_t close_far) { - Rect *cur = &(current->rect), - *other; - Output *output, - *best = NULL; +Output *get_output_next(const direction_t direction, Output *current, output_close_far_t close_far) { + const Rect *cur = &(current->rect); + Output *output, *best = NULL; TAILQ_FOREACH (output, &outputs, outputs) { - if (!output->active) + if (!output->active) { continue; + } - other = &(output->rect); - + const Rect *other = &(output->rect); if ((direction == D_RIGHT && other->x > cur->x) || (direction == D_LEFT && other->x < cur->x)) { /* Skip the output when it doesn’t overlap the other one’s y * coordinate at all. */ if ((other->y + other->height) <= cur->y || - (cur->y + cur->height) <= other->y) + (cur->y + cur->height) <= other->y) { continue; + } } else if ((direction == D_DOWN && other->y > cur->y) || (direction == D_UP && other->y < cur->y)) { /* Skip the output when it doesn’t overlap the other one’s x * coordinate at all. */ if ((other->x + other->width) <= cur->x || - (cur->x + cur->width) <= other->x) + (cur->x + cur->width) <= other->x) { continue; - } else + } + } else { continue; + } /* No candidate yet? Start with this one. */ if (!best) { @@ -340,8 +353,9 @@ void output_init_con(Output *output) { /* Search for a Con with that name directly below the root node. There * might be one from a restored layout. */ TAILQ_FOREACH (current, &(croot->nodes_head), nodes) { - if (strcmp(current->name, output_primary_name(output)) != 0) + if (strcmp(current->name, output_primary_name(output)) != 0) { continue; + } con = current; reused = true; @@ -526,10 +540,10 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { assert(output->con != NULL); output->con->rect = output->rect; - Con *content, *workspace, *child; + Con *workspace, *child; /* Point content to the container of the workspaces */ - content = output_get_content(output->con); + const Con *content = output_get_content(output->con); /* Fix the position of all floating windows on this output. * The 'rect' of each workspace will be updated in src/render.c. */ @@ -546,14 +560,16 @@ static void output_change_mode(xcb_connection_t *conn, Output *output) { TAILQ_FOREACH (workspace, &(content->nodes_head), nodes) { /* Workspaces with more than one child are left untouched because * we do not want to change an existing layout. */ - if (con_num_children(workspace) > 1) + if (con_num_children(workspace) > 1) { continue; + } workspace->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH; DLOG("Setting workspace [%d,%s]'s layout to %d.\n", workspace->num, workspace->name, workspace->layout); if ((child = TAILQ_FIRST(&(workspace->nodes_head)))) { - if (child->layout == L_SPLITV || child->layout == L_SPLITH) + if (child->layout == L_SPLITV || child->layout == L_SPLITH) { child->layout = workspace->layout; + } DLOG("Setting child [%d,%s]'s layout to %d.\n", child->num, child->name, child->layout); } } @@ -645,9 +661,8 @@ static bool randr_query_outputs_15(void) { struct output_name *output_name = scalloc(1, sizeof(struct output_name)); output_name->name = sstrdup(oname); SLIST_INSERT_HEAD(&new->names_head, output_name, names); - } else { - free(oname); } + free(oname); } FREE(info); } @@ -731,21 +746,21 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, * position/size) */ if (output->crtc == XCB_NONE) { if (!existing) { - if (new->primary) + if (new->primary) { TAILQ_INSERT_HEAD(&outputs, new, outputs); - else + } else { TAILQ_INSERT_TAIL(&outputs, new, outputs); - } else if (new->active) + } + } else if (new->active) { new->to_be_disabled = true; + } return; } - xcb_randr_get_crtc_info_cookie_t icookie; - icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts); + const xcb_randr_get_crtc_info_cookie_t icookie = xcb_randr_get_crtc_info(conn, output->crtc, cts); if ((crtc = xcb_randr_get_crtc_info_reply(conn, icookie, NULL)) == NULL) { DLOG("Skipping output %s: could not get CRTC (%p)\n", output_primary_name(new), crtc); - free(new); return; } @@ -769,10 +784,11 @@ static void handle_output(xcb_connection_t *conn, xcb_randr_output_t id, * need to insert the new output or we are done. */ if (!updated || !existing) { if (!existing) { - if (new->primary) + if (new->primary) { TAILQ_INSERT_HEAD(&outputs, new, outputs); - else + } else { TAILQ_INSERT_TAIL(&outputs, new, outputs); + } } return; } @@ -788,15 +804,14 @@ static void randr_query_outputs_14(void) { DLOG("Querying outputs using RandR ≤ 1.4\n"); /* Get screen resources (primary output, crtcs, outputs, modes) */ - xcb_randr_get_screen_resources_current_cookie_t rcookie; - rcookie = xcb_randr_get_screen_resources_current(conn, root); - xcb_randr_get_output_primary_cookie_t pcookie; - pcookie = xcb_randr_get_output_primary(conn, root); + const xcb_randr_get_screen_resources_current_cookie_t rcookie = xcb_randr_get_screen_resources_current(conn, root); + const xcb_randr_get_output_primary_cookie_t pcookie = xcb_randr_get_output_primary(conn, root); - if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL) + if ((primary = xcb_randr_get_output_primary_reply(conn, pcookie, NULL)) == NULL) { ELOG("Could not get RandR primary output\n"); - else + } else { DLOG("primary output is %08x\n", primary->output); + } xcb_randr_get_screen_resources_current_reply_t *res = xcb_randr_get_screen_resources_current_reply(conn, rcookie, NULL); @@ -816,15 +831,17 @@ static void randr_query_outputs_14(void) { /* Request information for each output */ xcb_randr_get_output_info_cookie_t ocookie[len]; - for (int i = 0; i < len; i++) + for (int i = 0; i < len; i++) { ocookie[i] = xcb_randr_get_output_info(conn, randr_outputs[i], cts); + } /* Loop through all outputs available for this X11 screen */ for (int i = 0; i < len; i++) { xcb_randr_get_output_info_reply_t *output; - if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) + if ((output = xcb_randr_get_output_info_reply(conn, ocookie[i], NULL)) == NULL) { continue; + } handle_output(conn, randr_outputs[i], output, cts, res); free(output); @@ -850,10 +867,9 @@ static void move_content(Con *con) { /* 2: iterate through workspaces and re-assign them, fixing the coordinates * of floating containers as we go */ - Con *current; - Con *old_content = output_get_content(con); + const Con *old_content = output_get_content(con); while (!TAILQ_EMPTY(&(old_content->nodes_head))) { - current = TAILQ_FIRST(&(old_content->nodes_head)); + Con *current = TAILQ_FIRST(&(old_content->nodes_head)); if (current != next && TAILQ_EMPTY(&(current->focus_head))) { /* the workspace is empty and not focused, get rid of it */ DLOG("Getting rid of current = %p / %s (empty, unfocused)\n", current, current->name); @@ -885,12 +901,10 @@ static void move_content(Con *con) { continue; } DLOG("Handling dock con %p\n", child); - Con *dock; while (!TAILQ_EMPTY(&(child->nodes_head))) { - dock = TAILQ_FIRST(&(child->nodes_head)); - Con *nc; Match *match; - nc = con_for_window(first, dock->window, &match); + Con *dock = TAILQ_FIRST(&(child->nodes_head)); + Con *nc = con_for_window(first, dock->window, &match); DLOG("Moving dock client %p to nc %p\n", dock, nc); con_detach(dock); DLOG("Re-attaching\n"); @@ -910,8 +924,6 @@ static void move_content(Con *con) { * */ void randr_query_outputs(void) { - Output *output, *other; - if (!randr_query_outputs_15()) { randr_query_outputs_14(); } @@ -929,21 +941,25 @@ void randr_query_outputs(void) { /* Check for clones, disable the clones and reduce the mode to the * lowest common mode */ + Output *output; TAILQ_FOREACH (output, &outputs, outputs) { - if (!output->active || output->to_be_disabled) + if (!output->active || output->to_be_disabled) { continue; + } DLOG("output %p / %s, position (%d, %d), checking for clones\n", output, output_primary_name(output), output->rect.x, output->rect.y); - for (other = output; + for (Output *other = output; other != TAILQ_END(&outputs); other = TAILQ_NEXT(other, outputs)) { - if (other == output || !other->active || other->to_be_disabled) + if (other == output || !other->active || other->to_be_disabled) { continue; + } if (other->rect.x != output->rect.x || - other->rect.y != output->rect.y) + other->rect.y != output->rect.y) { continue; + } DLOG("output %p has the same position, its mode = %d x %d\n", other, other->rect.width, other->rect.height); @@ -985,8 +1001,7 @@ void randr_query_outputs(void) { * those mentioned #3767 e.g. when a CT_OUTPUT is created from an in-place * restart's layout but the output is disabled by a randr query happening * at the same time. */ - Con *con; - for (con = TAILQ_FIRST(&(croot->nodes_head)); con;) { + for (Con *con = TAILQ_FIRST(&(croot->nodes_head)); con;) { Con *next = TAILQ_NEXT(con, nodes); if (!con_is_internal(con) && get_output_by_name(con->name, true) == NULL) { DLOG("No output %s found, moving its old content to first output\n", con->name); @@ -1010,19 +1025,22 @@ void randr_query_outputs(void) { /* Just go through each active output and assign one workspace */ TAILQ_FOREACH (output, &outputs, outputs) { - if (!output->active) + if (!output->active) { continue; + } Con *content = output_get_content(output->con); - if (!TAILQ_EMPTY(&(content->nodes_head))) + if (!TAILQ_EMPTY(&(content->nodes_head))) { continue; + } DLOG("Should add ws for output %s\n", output_primary_name(output)); init_ws_for_output(output); } /* Focus the primary screen, if possible */ TAILQ_FOREACH (output, &outputs, outputs) { - if (!output->primary || !output->con) + if (!output->primary || !output->con) { continue; + } DLOG("Focusing primary output %s\n", output_primary_name(output)); Con *content = output_get_content(output->con); @@ -1070,13 +1088,11 @@ static void fallback_to_root_output(void) { * */ void randr_init(int *event_base, const bool disable_randr15) { - const xcb_query_extension_reply_t *extreply; - root_output = create_root_output(conn); TAILQ_INSERT_TAIL(&outputs, root_output, outputs); - extreply = xcb_get_extension_data(conn, &xcb_randr_id); - if (!extreply->present) { + const xcb_query_extension_reply_t *extension_reply = xcb_get_extension_data(conn, &xcb_randr_id); + if (!extension_reply->present) { DLOG("RandR is not present, activating root output.\n"); fallback_to_root_output(); return; @@ -1101,8 +1117,9 @@ void randr_init(int *event_base, const bool disable_randr15) { randr_query_outputs(); - if (event_base != NULL) - *event_base = extreply->first_event; + if (event_base != NULL) { + *event_base = extension_reply->first_event; + } xcb_randr_select_input(conn, root, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE | diff --git a/src/regex.c b/src/regex.c index 66ae5113..47901748 100644 --- a/src/regex.c +++ b/src/regex.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * regex.c: Interface to libPCRE (perl compatible regular expressions). @@ -32,8 +32,8 @@ struct regex *regex_new(const char *pattern) { if (!(re->regex = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, options, &errorcode, &offset, NULL))) { PCRE2_UCHAR buffer[256]; pcre2_get_error_message(errorcode, buffer, sizeof(buffer)); - ELOG("PCRE regular expression compilation failed at %lu: %s\n", - offset, buffer); + ELOG("PCRE regular expression compilation failed at %zu: %s\n", + (size_t)offset, buffer); regex_free(re); return NULL; } @@ -45,8 +45,9 @@ struct regex *regex_new(const char *pattern) { * */ void regex_free(struct regex *regex) { - if (!regex) + if (!regex) { return; + } FREE(regex->pattern); FREE(regex->regex); FREE(regex); @@ -58,15 +59,12 @@ void regex_free(struct regex *regex) { * be visible without debug logging. * */ -bool regex_matches(struct regex *regex, const char *input) { - pcre2_match_data *match_data; - int rc; - - match_data = pcre2_match_data_create_from_pattern(regex->regex, NULL); +bool regex_matches(const struct regex *regex, const char *input) { + pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex->regex, NULL); /* We use strlen() because pcre_exec() expects the length of the input * string in bytes */ - rc = pcre2_match(regex->regex, (PCRE2_SPTR)input, strlen(input), 0, 0, match_data, NULL); + const int rc = pcre2_match(regex->regex, (PCRE2_SPTR)input, strlen(input), 0, 0, match_data, NULL); pcre2_match_data_free(match_data); if (rc > 0) { LOG("Regular expression \"%s\" matches \"%s\"\n", diff --git a/src/render.c b/src/render.c index b9fa3903..939fe948 100644 --- a/src/render.c +++ b/src/render.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * render.c: Renders (determines position/sizes) the layout tree, updating the @@ -26,8 +26,9 @@ static void render_con_dockarea(Con *con, Con *child, render_params *p); */ int render_deco_height(void) { int deco_height = config.font.height + 4; - if (config.font.height & 0x01) + if (config.font.height & 0x01) { ++deco_height; + } return deco_height; } @@ -84,7 +85,6 @@ void render_con(Con *con) { params.y = con->rect.y; } - int i = 0; con->mapped = true; /* if this container contains a window, set the coordinates */ @@ -151,12 +151,14 @@ void render_con(Con *con) { if (con->layout == L_OUTPUT) { /* Skip i3-internal outputs */ - if (con_is_internal(con)) + if (con_is_internal(con)) { goto free_params; + } render_output(con); } else if (con->type == CT_ROOT) { render_root(con, fullscreen); } else { + int i = 0; Con *child; TAILQ_FOREACH (child, &(con->nodes_head), nodes) { assert(params.children > 0); @@ -205,12 +207,13 @@ void render_con(Con *con) { render_con(child); } - if (params.children != 1) + if (params.children != 1) { /* Raise the stack con itself. This will put the stack * decoration on top of every stack window. That way, when a * new window is opened in the stack, the old window will not * obscure part of the decoration (it’s unmapped afterwards). */ x_raise_con(con); + } } } @@ -228,9 +231,9 @@ static int *precalculate_sizes(Con *con, render_params *p) { Con *child; int i = 0, assigned = 0; - int total = con_rect_size_in_orientation(con); + const int total = con_rect_size_in_orientation(con); TAILQ_FOREACH (child, &(con->nodes_head), nodes) { - double percentage = child->percent > 0.0 ? child->percent : 1.0 / p->children; + const double percentage = child->percent > 0.0 ? child->percent : 1.0 / p->children; assigned += sizes[i++] = lround(percentage * total); } assert(assigned == total || @@ -247,6 +250,26 @@ static int *precalculate_sizes(Con *con, render_params *p) { return sizes; } +static bool fullscreen_blocks_floating_render(Con *fullscreen, Con *floating) { + if (fullscreen == NULL) { + return false; + } + /* Don’t render floating windows when there is a fullscreen window on that + * workspace. Necessary to make floating fullscreen work correctly (ticket + * #564). Exception to the above rule: popup_during_fullscreen smart|all. */ + switch (config.popup_during_fullscreen) { + case PDF_LEAVE_FULLSCREEN: + case PDF_IGNORE: + return true; + case PDF_SMART: + return fullscreen->window == NULL || + !con_find_transient_for_window(con_descend_focused(floating), fullscreen->window->id); + case PDF_ALL: + return con_has_parent(fullscreen, floating); + } + return false; /* not reachable */ +} + static void render_root(Con *con, Con *fullscreen) { Con *output; if (!fullscreen) { @@ -261,8 +284,9 @@ static void render_root(Con *con, Con *fullscreen) { * windows/containers so that they overlap on another output. */ DLOG("Rendering floating windows:\n"); TAILQ_FOREACH (output, &(con->nodes_head), nodes) { - if (con_is_internal(output)) + if (con_is_internal(output)) { continue; + } /* Get the active workspace of that output */ Con *content = output_get_content(output); if (!content || TAILQ_EMPTY(&(content->focus_head))) { @@ -273,24 +297,8 @@ static void render_root(Con *con, Con *fullscreen) { Con *fullscreen = con_get_fullscreen_covering_ws(workspace); Con *child; TAILQ_FOREACH (child, &(workspace->floating_head), floating_windows) { - if (fullscreen != NULL) { - /* Don’t render floating windows when there is a fullscreen - * window on that workspace. Necessary to make floating - * fullscreen work correctly (ticket #564). Exception to the - * above rule: smart popup_during_fullscreen handling (popups - * belonging to the fullscreen app will be rendered). */ - if (config.popup_during_fullscreen != PDF_SMART || fullscreen->window == NULL) { - continue; - } - - Con *floating_child = con_descend_focused(child); - if (con_find_transient_for_window(floating_child, fullscreen->window->id)) { - DLOG("Rendering floating child even though in fullscreen mode: " - "floating->transient_for (0x%08x) --> fullscreen->id (0x%08x)\n", - floating_child->window->transient_for, fullscreen->window->id); - } else { - continue; - } + if (fullscreen_blocks_floating_render(fullscreen, child)) { + continue; } DLOG("floating child at (%d,%d) with %d x %d\n", child->rect.x, child->rect.y, child->rect.width, child->rect.height); @@ -352,8 +360,9 @@ static void render_output(Con *con) { /* First pass: determine the height of all CT_DOCKAREAs (the sum of their * children) and figure out how many pixels we have left for the rest */ TAILQ_FOREACH (child, &(con->nodes_head), nodes) { - if (child->type != CT_DOCKAREA) + if (child->type != CT_DOCKAREA) { continue; + } child->rect.height = 0; TAILQ_FOREACH (dockchild, &(child->nodes_head), nodes) { diff --git a/src/resize.c b/src/resize.c index 3b90f3aa..43207fd6 100644 --- a/src/resize.c +++ b/src/resize.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * resize.c: Interactive resizing. @@ -50,15 +50,17 @@ DRAGGING_CB(resize_callback) { if (params->orientation == HORIZ) { /* Check if the new coordinates are within screen boundaries */ if (new_x > (output->rect.x + output->rect.width - 25) || - new_x < (output->rect.x + 25)) + new_x < (output->rect.x + 25)) { return; + } *(params->new_position) = new_x; xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_X, params->new_position); } else { if (new_y > (output->rect.y + output->rect.height - 25) || - new_y < (output->rect.y + 25)) + new_y < (output->rect.y + 25)) { return; + } *(params->new_position) = new_y; xcb_configure_window(conn, params->helpwin, XCB_CONFIG_WINDOW_Y, params->new_position); diff --git a/src/restore_layout.c b/src/restore_layout.c index 6f35d165..ba8199d9 100644 --- a/src/restore_layout.c +++ b/src/restore_layout.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * restore_layout.c: Everything for restored containers that is not pure state @@ -37,13 +37,13 @@ static TAILQ_HEAD(state_head, placeholder_state) state_head = static xcb_connection_t *restore_conn; -static struct ev_io *xcb_watcher; -static struct ev_prepare *xcb_prepare; +static ev_io *xcb_watcher; +static ev_prepare *xcb_prepare; static void restore_handle_event(int type, xcb_generic_event_t *event); /* Documentation for these functions can be found in src/main.c, starting at xcb_got_event */ -static void restore_xcb_got_event(EV_P_ struct ev_io *w, int revents) { +static void restore_xcb_got_event(EV_P_ ev_io *w, int revents) { } static void restore_xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) { @@ -89,9 +89,8 @@ void restore_connect(void) { ev_io_stop(main_loop, xcb_watcher); ev_prepare_stop(main_loop, xcb_prepare); - placeholder_state *state; while (!TAILQ_EMPTY(&state_head)) { - state = TAILQ_FIRST(&state_head); + placeholder_state *state = TAILQ_FIRST(&state_head); TAILQ_REMOVE(&state_head, state, state); free(state); } @@ -117,8 +116,8 @@ void restore_connect(void) { errx(EXIT_FAILURE, "Cannot open display"); } - xcb_watcher = scalloc(1, sizeof(struct ev_io)); - xcb_prepare = scalloc(1, sizeof(struct ev_prepare)); + xcb_watcher = scalloc(1, sizeof(ev_io)); + xcb_prepare = scalloc(1, sizeof(ev_prepare)); ev_io_init(xcb_watcher, restore_xcb_got_event, xcb_get_file_descriptor(restore_conn), EV_READ); ev_io_start(main_loop, xcb_watcher); @@ -144,7 +143,9 @@ static void update_placeholder_contents(placeholder_state *state) { #define APPEND_REGEX(re_name) \ do { \ if (swallows->re_name != NULL) { \ + char *old_serialized = serialized; \ sasprintf(&serialized, "%s%s" #re_name "=\"%s\"", (serialized ? serialized : "["), (serialized ? " " : ""), swallows->re_name->pattern); \ + free(old_serialized); \ } \ } while (0) @@ -159,7 +160,9 @@ static void update_placeholder_contents(placeholder_state *state) { continue; } + char *old_serialized = serialized; sasprintf(&serialized, "%s]", serialized); + free(old_serialized); DLOG("con %p (placeholder 0x%08x) line %d: %s\n", state->con, state->window, n, serialized); i3String *str = i3string_from_utf8(serialized); @@ -208,9 +211,10 @@ static void open_placeholder_window(Con *con) { /* Set the same name as was stored in the layout file. While perhaps * slightly confusing in the first instant, this brings additional * clarity to which placeholder is waiting for which actual window. */ - if (con->name != NULL) + if (con->name != NULL) { xcb_change_property(restore_conn, XCB_PROP_MODE_REPLACE, placeholder, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen(con->name), con->name); + } DLOG("Created placeholder window 0x%08x for leaf container %p / %s\n", placeholder, con, con->name); @@ -269,8 +273,9 @@ void restore_open_placeholder_windows(Con *parent) { bool restore_kill_placeholder(xcb_window_t placeholder) { placeholder_state *state; TAILQ_FOREACH (state, &state_head, state) { - if (state->window != placeholder) + if (state->window != placeholder) { continue; + } xcb_destroy_window(restore_conn, state->window); draw_util_surface_free(restore_conn, &(state->surface)); @@ -287,8 +292,9 @@ bool restore_kill_placeholder(xcb_window_t placeholder) { static void expose_event(xcb_expose_event_t *event) { placeholder_state *state; TAILQ_FOREACH (state, &state_head, state) { - if (state->window != event->window) + if (state->window != event->window) { continue; + } DLOG("refreshing window 0x%08x contents (con %p)\n", state->window, state->con); @@ -309,8 +315,9 @@ static void expose_event(xcb_expose_event_t *event) { static void configure_notify(xcb_configure_notify_event_t *event) { placeholder_state *state; TAILQ_FOREACH (state, &state_head, state) { - if (state->window != event->window) + if (state->window != event->window) { continue; + } DLOG("ConfigureNotify: window 0x%08x has now width=%d, height=%d (con %p)\n", state->window, event->width, event->height, state->con); diff --git a/src/scratchpad.c b/src/scratchpad.c index 24cde612..db2e195f 100644 --- a/src/scratchpad.c +++ b/src/scratchpad.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * scratchpad.c: Moving windows to the scratchpad and making them visible again. @@ -21,8 +21,7 @@ void scratchpad_move(Con *con) { LOG("'move scratchpad' used on a workspace \"%s\". Calling it " "recursively on all windows on this workspace.\n", con->name); - Con *current; - current = TAILQ_FIRST(&(con->focus_head)); + Con *current = TAILQ_FIRST(&(con->focus_head)); while (current) { Con *next = TAILQ_NEXT(current, focused); scratchpad_move(current); @@ -101,8 +100,9 @@ bool scratchpad_show(Con *con) { /* If the current con or any of its parents are in fullscreen mode, we * first need to disable it before showing the scratchpad con. */ Con *fs = focused; - while (fs && fs->fullscreen_mode == CF_NONE) + while (fs && fs->fullscreen_mode == CF_NONE) { fs = fs->parent; + } if (fs && fs->type != CT_WORKSPACE) { con_toggle_fullscreen(fs, CF_OUTPUT); @@ -215,8 +215,9 @@ bool scratchpad_show(Con *con) { * */ static int _gcd(const int m, const int n) { - if (n == 0) + if (n == 0) { return m; + } return _gcd(n, (m % n)); } @@ -254,8 +255,9 @@ void scratchpad_fix_resolution(void) { int new_width = -1, new_height = -1; TAILQ_FOREACH (output, &(croot->nodes_head), nodes) { - if (output == __i3_output) + if (output == __i3_output) { continue; + } DLOG("output %s's resolution: (%d, %d) %d x %d\n", output->name, output->rect.x, output->rect.y, output->rect.width, output->rect.height); diff --git a/src/sd-daemon.c b/src/sd-daemon.c index 84761025..85b0c34d 100644 --- a/src/sd-daemon.c +++ b/src/sd-daemon.c @@ -44,8 +44,8 @@ #include #include -int sd_listen_fds(int unset_environment) { - int r, fd; +int sd_listen_fds(const int unset_environment) { + int r; const char *e; char *p = NULL; unsigned long l; @@ -92,7 +92,7 @@ int sd_listen_fds(int unset_environment) { goto finish; } - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int)l; fd++) { + for (int fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int)l; fd++) { int flags; if ((flags = fcntl(fd, F_GETFD)) < 0) { @@ -100,8 +100,9 @@ int sd_listen_fds(int unset_environment) { goto finish; } - if (flags & FD_CLOEXEC) + if (flags & FD_CLOEXEC) { continue; + } if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) { r = -errno; @@ -123,23 +124,26 @@ finish: int sd_is_fifo(int fd, const char *path) { struct stat st_fd; - if (fd < 0) + if (fd < 0) { return -EINVAL; + } memset(&st_fd, 0, sizeof(st_fd)); - if (fstat(fd, &st_fd) < 0) + if (fstat(fd, &st_fd) < 0) { return -errno; + } - if (!S_ISFIFO(st_fd.st_mode)) + if (!S_ISFIFO(st_fd.st_mode)) { return 0; + } if (path) { - struct stat st_path; + struct stat st_path = {0}; - memset(&st_path, 0, sizeof(st_path)); if (stat(path, &st_path) < 0) { - if (errno == ENOENT || errno == ENOTDIR) + if (errno == ENOENT || errno == ENOTDIR) { return 0; + } return -errno; } @@ -154,41 +158,50 @@ int sd_is_fifo(int fd, const char *path) { static int sd_is_socket_internal(int fd, int type, int listening) { struct stat st_fd; - if (fd < 0 || type < 0) + if (fd < 0 || type < 0) { return -EINVAL; + } - if (fstat(fd, &st_fd) < 0) + if (fstat(fd, &st_fd) < 0) { return -errno; + } - if (!S_ISSOCK(st_fd.st_mode)) + if (!S_ISSOCK(st_fd.st_mode)) { return 0; + } if (type != 0) { int other_type = 0; socklen_t l = sizeof(other_type); - if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0) + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0) { return -errno; + } - if (l != sizeof(other_type)) + if (l != sizeof(other_type)) { return -EINVAL; + } - if (other_type != type) + if (other_type != type) { return 0; + } } if (listening >= 0) { int accepting = 0; socklen_t l = sizeof(accepting); - if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0) + if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0) { return -errno; + } - if (l != sizeof(accepting)) + if (l != sizeof(accepting)) { return -EINVAL; + } - if (!accepting != !listening) + if (!accepting != !listening) { return 0; + } } return 1; @@ -205,11 +218,13 @@ union sockaddr_union { int sd_is_socket(int fd, int family, int type, int listening) { int r; - if (family < 0) + if (family < 0) { return -EINVAL; + } - if ((r = sd_is_socket_internal(fd, type, listening)) <= 0) + if ((r = sd_is_socket_internal(fd, type, listening)) <= 0) { return r; + } if (family > 0) { union sockaddr_union sockaddr; @@ -218,11 +233,13 @@ int sd_is_socket(int fd, int family, int type, int listening) { memset(&sockaddr, 0, sizeof(sockaddr)); l = sizeof(sockaddr); - if (getsockname(fd, &sockaddr.sa, &l) < 0) + if (getsockname(fd, &sockaddr.sa, &l) < 0) { return -errno; + } - if (l < sizeof(sa_family_t)) + if (l < sizeof(sa_family_t)) { return -EINVAL; + } return sockaddr.sa.sa_family == family; } @@ -235,38 +252,47 @@ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port socklen_t l; int r; - if (family != 0 && family != AF_INET && family != AF_INET6) + if (family != 0 && family != AF_INET && family != AF_INET6) { return -EINVAL; + } - if ((r = sd_is_socket_internal(fd, type, listening)) <= 0) + if ((r = sd_is_socket_internal(fd, type, listening)) <= 0) { return r; + } memset(&sockaddr, 0, sizeof(sockaddr)); l = sizeof(sockaddr); - if (getsockname(fd, &sockaddr.sa, &l) < 0) + if (getsockname(fd, &sockaddr.sa, &l) < 0) { return -errno; + } - if (l < sizeof(sa_family_t)) + if (l < sizeof(sa_family_t)) { return -EINVAL; + } if (sockaddr.sa.sa_family != AF_INET && - sockaddr.sa.sa_family != AF_INET6) + sockaddr.sa.sa_family != AF_INET6) { return 0; + } - if (family > 0) - if (sockaddr.sa.sa_family != family) + if (family > 0) { + if (sockaddr.sa.sa_family != family) { return 0; + } + } if (port > 0) { if (sockaddr.sa.sa_family == AF_INET) { - if (l < sizeof(struct sockaddr_in)) + if (l < sizeof(struct sockaddr_in)) { return -EINVAL; + } return htons(port) == sockaddr.in4.sin_port; } else { - if (l < sizeof(struct sockaddr_in6)) + if (l < sizeof(struct sockaddr_in6)) { return -EINVAL; + } return htons(port) == sockaddr.in6.sin6_port; } @@ -280,37 +306,44 @@ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t socklen_t l; int r; - if ((r = sd_is_socket_internal(fd, type, listening)) <= 0) + if ((r = sd_is_socket_internal(fd, type, listening)) <= 0) { return r; + } memset(&sockaddr, 0, sizeof(sockaddr)); l = sizeof(sockaddr); - if (getsockname(fd, &sockaddr.sa, &l) < 0) + if (getsockname(fd, &sockaddr.sa, &l) < 0) { return -errno; + } - if (l < sizeof(sa_family_t)) + if (l < sizeof(sa_family_t)) { return -EINVAL; + } - if (sockaddr.sa.sa_family != AF_UNIX) + if (sockaddr.sa.sa_family != AF_UNIX) { return 0; + } if (path) { - if (length <= 0) + if (length <= 0) { length = strlen(path); + } - if (length <= 0) + if (length <= 0) { /* Unnamed socket */ return l == offsetof(struct sockaddr_un, sun_path); + } - if (path[0]) + if (path[0]) { /* Normal path socket */ return (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) && memcmp(path, sockaddr.un.sun_path, length + 1) == 0; - else + } else { /* Abstract namespace socket */ return (l == offsetof(struct sockaddr_un, sun_path) + length) && memcmp(path, sockaddr.un.sun_path, length) == 0; + } } return 1; @@ -331,8 +364,9 @@ int sd_notify(int unset_environment, const char *state) { goto finish; } - if (!(e = getenv("NOTIFY_SOCKET"))) + if (!(e = getenv("NOTIFY_SOCKET"))) { return 0; + } /* Must be an abstract socket, or an absolute path */ if ((e[0] != '@' && e[0] != '/') || e[1] == 0) { @@ -340,6 +374,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,10 +386,11 @@ 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] == '@') + if (sockaddr.un.sun_path[0] == '@') { sockaddr.un.sun_path[0] = 0; + } memset(&iovec, 0, sizeof(iovec)); iovec.iov_base = (char *)state; @@ -360,8 +400,9 @@ int sd_notify(int unset_environment, const char *state) { msghdr.msg_name = &sockaddr; msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(e); - if (msghdr.msg_namelen > sizeof(struct sockaddr_un)) + if (msghdr.msg_namelen > sizeof(struct sockaddr_un)) { msghdr.msg_namelen = sizeof(struct sockaddr_un); + } msghdr.msg_iov = &iovec; msghdr.msg_iovlen = 1; @@ -374,11 +415,13 @@ int sd_notify(int unset_environment, const char *state) { r = 1; finish: - if (unset_environment) + if (unset_environment) { unsetenv("NOTIFY_SOCKET"); + } - if (fd >= 0) + if (fd >= 0) { close(fd); + } return r; #endif @@ -390,14 +433,14 @@ int sd_notifyf(int unset_environment, const char *format, ...) { #else va_list ap; char *p = NULL; - int r; va_start(ap, format); - r = vasprintf(&p, format, ap); + int r = vasprintf(&p, format, ap); va_end(ap); - if (r < 0 || !p) + if (r < 0 || !p) { return -ENOMEM; + } r = sd_notify(unset_environment, p); free(p); @@ -415,11 +458,13 @@ int sd_booted(void) { /* We simply test whether the systemd cgroup hierarchy is mounted */ - if (lstat("/sys/fs/cgroup", &a) < 0) + if (lstat("/sys/fs/cgroup", &a) < 0) { return 0; + } - if (lstat("/sys/fs/cgroup/systemd", &b) < 0) + if (lstat("/sys/fs/cgroup/systemd", &b) < 0) { return 0; + } return a.st_dev != b.st_dev; #endif diff --git a/src/sighandler.c b/src/sighandler.c index 2be69c31..b1be7d9c 100644 --- a/src/sighandler.c +++ b/src/sighandler.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * */ @@ -48,8 +48,9 @@ static int margin = 4; */ static int sighandler_backtrace(void) { char *tmpdir = getenv("TMPDIR"); - if (tmpdir == NULL) + if (tmpdir == NULL) { tmpdir = "/tmp"; + } pid_t pid_parent = getpid(); @@ -111,8 +112,7 @@ static int sighandler_backtrace(void) { "-ex", "quit", NULL}; execvp(args[0], args); - DLOG("Failed to exec GDB\n"); - exit(EXIT_FAILURE); + err(EXIT_FAILURE, "execvp(gdb)"); } int status = 0; @@ -343,6 +343,7 @@ void setup_signal_handler(void) { sigaction(SIGILL, &action, NULL) == -1 || sigaction(SIGABRT, &action, NULL) == -1 || sigaction(SIGFPE, &action, NULL) == -1 || - sigaction(SIGSEGV, &action, NULL) == -1) + sigaction(SIGSEGV, &action, NULL) == -1) { ELOG("Could not setup signal handler.\n"); + } } diff --git a/src/startup.c b/src/startup.c index 9b0576e8..08377f8b 100644 --- a/src/startup.c +++ b/src/startup.c @@ -1,11 +1,11 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * startup.c: Startup notification code. Ensures a startup notification context - * is setup when launching applications. We store the current + * is set up when launching applications. We store the current * workspace to open windows in that startup notification context on * the appropriate workspace. * @@ -37,8 +37,9 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) { struct Startup_Sequence *current, *sequence = NULL; TAILQ_FOREACH (current, &startup_sequences, sequences) { - if (strcmp(current->id, id) != 0) + if (strcmp(current->id, id) != 0) { continue; + } sequence = current; break; @@ -70,15 +71,14 @@ static void startup_timeout(EV_P_ ev_timer *w, int revents) { * */ static int _prune_startup_sequences(void) { - time_t current_time = time(NULL); + const time_t current_time = time(NULL); int active_sequences = 0; /* Traverse the list and delete everything which was marked for deletion 30 * seconds ago or earlier. */ - struct Startup_Sequence *current, *next; - for (next = TAILQ_FIRST(&startup_sequences); + for (struct Startup_Sequence *next = TAILQ_FIRST(&startup_sequences); next != TAILQ_END(&startup_sequences);) { - current = next; + struct Startup_Sequence *current = next; next = TAILQ_NEXT(next, sequences); if (current->delete_at == 0) { @@ -86,8 +86,9 @@ static int _prune_startup_sequences(void) { continue; } - if (current_time <= current->delete_at) + if (current_time <= current->delete_at) { continue; + } startup_sequence_delete(current); } @@ -118,10 +119,9 @@ void startup_sequence_delete(struct Startup_Sequence *sequence) { } /* - * Starts the given application by passing it through a shell. We use double - * fork to avoid zombie processes. As the started application’s parent exits - * (immediately), the application is reparented to init (process-id 1), which - * correctly handles children, so we don’t have to do it :-). + * Starts the given application by passing it through a shell. Zombie processes + * will be collected by ev in the default loop, we don't have to manually + * deal with it. * * The shell used to start applications is the system's bourne shell (i.e., * /bin/sh). @@ -143,13 +143,14 @@ void start_application(const char *command, bool no_startup_id) { * spaces in the command), since we don’t want the parameters. */ char *first_word = sstrdup(command); char *space = strchr(first_word, ' '); - if (space) + if (space) { *space = '\0'; + } sn_launcher_context_initiate(context, "i3", first_word, last_timestamp); free(first_word); /* Trigger a timeout after 60 seconds */ - struct ev_timer *timeout = scalloc(1, sizeof(struct ev_timer)); + ev_timer *timeout = scalloc(1, sizeof(struct ev_timer)); ev_timer_init(timeout, startup_timeout, 60.0, 0.); timeout->data = context; ev_timer_start(main_loop, timeout); @@ -173,7 +174,8 @@ void start_application(const char *command, bool no_startup_id) { LOG("executing: %s\n", command); if (fork() == 0) { - /* Child process */ + /* Child process. + * It will be reaped by ev, even though there is no corresponding ev_child */ setsid(); setrlimit(RLIMIT_CORE, &original_rlimit_core); /* Close all socket activation file descriptors explicitly, we disabled @@ -186,18 +188,15 @@ void start_application(const char *command, bool no_startup_id) { unsetenv("LISTEN_PID"); unsetenv("LISTEN_FDS"); signal(SIGPIPE, SIG_DFL); - if (fork() == 0) { - /* Setup the environment variable(s) */ - if (!no_startup_id) - sn_launcher_context_setup_child_process(context); - setenv("I3SOCK", current_socketpath, 1); - - execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL); - /* not reached */ + /* Setup the environment variable(s) */ + if (!no_startup_id) { + sn_launcher_context_setup_child_process(context); } - _exit(EXIT_SUCCESS); + setenv("I3SOCK", current_socketpath, 1); + + execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, NULL); + err(EXIT_FAILURE, "execl return"); /* only reached on error */ } - wait(0); if (!no_startup_id) { /* Change the pointer of the root window to indicate progress */ @@ -210,16 +209,15 @@ void start_application(const char *command, bool no_startup_id) { * */ void startup_monitor_event(SnMonitorEvent *event, void *userdata) { - SnStartupSequence *snsequence; - - snsequence = sn_monitor_event_get_startup_sequence(event); + SnStartupSequence *sn_startup_sequence = sn_monitor_event_get_startup_sequence(event); /* Get the corresponding internal startup sequence */ - const char *id = sn_startup_sequence_get_id(snsequence); + const char *id = sn_startup_sequence_get_id(sn_startup_sequence); struct Startup_Sequence *current, *sequence = NULL; TAILQ_FOREACH (current, &startup_sequences, sequences) { - if (strcmp(current->id, id) != 0) + if (strcmp(current->id, id) != 0) { continue; + } sequence = current; break; @@ -232,7 +230,7 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) { switch (sn_monitor_event_get_type(event)) { case SN_MONITOR_EVENT_COMPLETED: - DLOG("startup sequence %s completed\n", sn_startup_sequence_get_id(snsequence)); + DLOG("startup sequence %s completed\n", sn_startup_sequence_get_id(sn_startup_sequence)); /* Mark the given sequence for deletion in 30 seconds. */ time_t current_time = time(NULL); @@ -259,8 +257,9 @@ void startup_monitor_event(SnMonitorEvent *event, void *userdata) { void startup_sequence_rename_workspace(const char *old_name, const char *new_name) { struct Startup_Sequence *current; TAILQ_FOREACH (current, &startup_sequences, sequences) { - if (strcmp(current->workspace, old_name) != 0) + if (strcmp(current->workspace, old_name) != 0) { continue; + } DLOG("Renaming workspace \"%s\" to \"%s\" in startup sequence %s.\n", old_name, new_name, current->id); free(current->workspace); @@ -272,15 +271,16 @@ void startup_sequence_rename_workspace(const char *old_name, const char *new_nam * Gets the stored startup sequence for the _NET_STARTUP_ID of a given window. * */ -struct Startup_Sequence *startup_sequence_get(i3Window *cwindow, +struct Startup_Sequence *startup_sequence_get(const i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply, bool ignore_mapped_leader) { /* The _NET_STARTUP_ID is only needed during this function, so we get it * here and don’t save it in the 'cwindow'. */ if (startup_id_reply == NULL || xcb_get_property_value_length(startup_id_reply) == 0) { FREE(startup_id_reply); DLOG("No _NET_STARTUP_ID set on window 0x%08x\n", cwindow->id); - if (cwindow->leader == XCB_NONE) + if (cwindow->leader == XCB_NONE) { return NULL; + } /* This is a special case that causes the leader's startup sequence * to only be returned if it has never been mapped, useful primarily @@ -296,10 +296,8 @@ struct Startup_Sequence *startup_sequence_get(i3Window *cwindow, DLOG("Checking leader window 0x%08x\n", cwindow->leader); - xcb_get_property_cookie_t cookie; - - cookie = xcb_get_property(conn, false, cwindow->leader, - A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512); + const xcb_get_property_cookie_t cookie = xcb_get_property(conn, false, cwindow->leader, + A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512); startup_id_reply = xcb_get_property_reply(conn, cookie, NULL); if (startup_id_reply == NULL || @@ -315,8 +313,9 @@ struct Startup_Sequence *startup_sequence_get(i3Window *cwindow, (char *)xcb_get_property_value(startup_id_reply)); struct Startup_Sequence *current, *sequence = NULL; TAILQ_FOREACH (current, &startup_sequences, sequences) { - if (strcmp(current->id, startup_id) != 0) + if (strcmp(current->id, startup_id) != 0) { continue; + } sequence = current; break; @@ -344,10 +343,11 @@ struct Startup_Sequence *startup_sequence_get(i3Window *cwindow, * Returns NULL otherwise. * */ -char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply) { +char *startup_workspace_for_window(const i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply) { struct Startup_Sequence *sequence = startup_sequence_get(cwindow, startup_id_reply, false); - if (sequence == NULL) + if (sequence == NULL) { return NULL; + } /* If the startup sequence's time span has elapsed, delete it. */ time_t current_time = time(NULL); @@ -364,16 +364,12 @@ char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t * * Deletes the startup sequence for a window if it exists. * */ -void startup_sequence_delete_by_window(i3Window *win) { - struct Startup_Sequence *sequence; - xcb_get_property_cookie_t cookie; - xcb_get_property_reply_t *startup_id_reply; +void startup_sequence_delete_by_window(const i3Window *win) { + const xcb_get_property_cookie_t cookie = xcb_get_property(conn, false, win->id, A__NET_STARTUP_ID, + XCB_GET_PROPERTY_TYPE_ANY, 0, 512); + xcb_get_property_reply_t *startup_id_reply = xcb_get_property_reply(conn, cookie, NULL); - cookie = xcb_get_property(conn, false, win->id, A__NET_STARTUP_ID, - XCB_GET_PROPERTY_TYPE_ANY, 0, 512); - startup_id_reply = xcb_get_property_reply(conn, cookie, NULL); - - sequence = startup_sequence_get(win, startup_id_reply, true); + struct Startup_Sequence *sequence = startup_sequence_get(win, startup_id_reply, true); if (sequence != NULL) { startup_sequence_delete(sequence); } diff --git a/src/sync.c b/src/sync.c index dafbc355..b7f858d0 100644 --- a/src/sync.c +++ b/src/sync.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * sync.c: i3 sync protocol: https://i3wm.org/docs/testsuite.html#i3_sync diff --git a/src/tiling_drag.c b/src/tiling_drag.c index e4babe3b..6992f93a 100644 --- a/src/tiling_drag.c +++ b/src/tiling_drag.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * tiling_drag.c: Reposition tiled windows by dragging. @@ -338,7 +338,15 @@ void tiling_drag(Con *con, xcb_button_press_event_t *event, bool use_threshold) case DT_CENTER: /* Also handles workspaces.*/ DLOG("drop to center of %p\n", target); - con_move_to_target(con, target); + const uint32_t mod = (config.swap_modifier & 0xFFFF); + const bool swap_pressed = (mod != 0 && (event->state & mod) == mod); + if (swap_pressed) { + if (!con_swap(con, target)) { + return; + } + } else { + con_move_to_target(con, target); + } break; case DT_SIBLING: DLOG("drop %s %p\n", position_to_string(position), target); @@ -381,7 +389,7 @@ void tiling_drag(Con *con, xcb_button_press_event_t *event, bool use_threshold) /* tree_move can change the focus */ Con *old_focus = focused; tree_move(con, direction); - if (focused != old_focus) { + if (focused != old_focus && con_exists(old_focus)) { con_activate(old_focus); } break; diff --git a/src/tree.c b/src/tree.c index cd4fca44..2ceb0688 100644 --- a/src/tree.c +++ b/src/tree.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * tree.c: Everything that primarily modifies the layout tree data structure. @@ -9,8 +9,8 @@ */ #include "all.h" -struct Con *croot; -struct Con *focused; +Con *croot; +Con *focused; struct all_cons_head all_cons = TAILQ_HEAD_INITIALIZER(all_cons); @@ -161,8 +161,9 @@ Con *tree_open_con(Con *con, i3Window *window) { * workspace. */ if (con->type == CT_FLOATING_CON) { con = con_descend_tiling_focused(con->parent); - if (con->type != CT_WORKSPACE) + if (con->type != CT_WORKSPACE) { con = con->parent; + } } DLOG("con = %p\n", con); } @@ -199,17 +200,16 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par } DLOG("closing %p, kill_window = %d\n", con, kill_window); - Con *child, *nextchild; bool abort_kill = false; /* We cannot use TAILQ_FOREACH because the children get deleted * in their parent’s nodes_head */ - for (child = TAILQ_FIRST(&(con->nodes_head)); child;) { - nextchild = TAILQ_NEXT(child, nodes); + for (Con *child = TAILQ_FIRST(&(con->nodes_head)); child;) { + Con *next_child = TAILQ_NEXT(child, nodes); DLOG("killing child=%p\n", child); if (!tree_close_internal(child, kill_window, true)) { abort_kill = true; } - child = nextchild; + child = next_child; } if (abort_kill) { @@ -221,42 +221,40 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par if (kill_window != DONT_KILL_WINDOW) { x_window_kill(con->window->id, kill_window); return false; - } else { - xcb_void_cookie_t cookie; - /* Ignore any further events by clearing the event mask, - * unmap the window, - * then reparent it to the root window. */ - xcb_change_window_attributes(conn, con->window->id, - XCB_CW_EVENT_MASK, (uint32_t[]){XCB_NONE}); - xcb_unmap_window(conn, con->window->id); - cookie = xcb_reparent_window(conn, con->window->id, root, con->rect.x, con->rect.y); - - /* Ignore X11 errors for the ReparentWindow request. - * X11 Errors are returned when the window was already destroyed */ - add_ignore_event(cookie.sequence, 0); - - /* We are no longer handling this window, thus set WM_STATE to - * WM_STATE_WITHDRAWN (see ICCCM 4.1.3.1) */ - long data[] = {XCB_ICCCM_WM_STATE_WITHDRAWN, XCB_NONE}; - cookie = xcb_change_property(conn, XCB_PROP_MODE_REPLACE, - con->window->id, A_WM_STATE, A_WM_STATE, 32, 2, data); - - /* Remove the window from the save set. All windows in the save set - * will be mapped when i3 closes its connection (e.g. when - * restarting). This is not what we want, since some apps keep - * unmapped windows around and don’t expect them to suddenly be - * mapped. See https://bugs.i3wm.org/1617 */ - xcb_change_save_set(conn, XCB_SET_MODE_DELETE, con->window->id); - - /* Stop receiving ShapeNotify events. */ - if (shape_supported) { - xcb_shape_select_input(conn, con->window->id, false); - } - - /* Ignore X11 errors for the ReparentWindow request. - * X11 Errors are returned when the window was already destroyed */ - add_ignore_event(cookie.sequence, 0); } + /* Ignore any further events by clearing the event mask, + * unmap the window, + * then reparent it to the root window. */ + xcb_change_window_attributes(conn, con->window->id, + XCB_CW_EVENT_MASK, (uint32_t[]){XCB_NONE}); + xcb_unmap_window(conn, con->window->id); + xcb_void_cookie_t cookie = xcb_reparent_window(conn, con->window->id, root, con->rect.x, con->rect.y); + + /* Ignore X11 errors for the ReparentWindow request. + * X11 Errors are returned when the window was already destroyed */ + add_ignore_event(cookie.sequence, 0); + + /* We are no longer handling this window, thus set WM_STATE to + * WM_STATE_WITHDRAWN (see ICCCM 4.1.3.1) */ + long data[] = {XCB_ICCCM_WM_STATE_WITHDRAWN, XCB_NONE}; + cookie = xcb_change_property(conn, XCB_PROP_MODE_REPLACE, + con->window->id, A_WM_STATE, A_WM_STATE, 32, 2, data); + + /* Remove the window from the save set. All windows in the save set + * will be mapped when i3 closes its connection (e.g. when + * restarting). This is not what we want, since some apps keep + * unmapped windows around and don’t expect them to suddenly be + * mapped. See https://bugs.i3wm.org/1617 */ + xcb_change_save_set(conn, XCB_SET_MODE_DELETE, con->window->id); + + /* Stop receiving ShapeNotify events. */ + if (shape_supported) { + xcb_shape_select_input(conn, con->window->id, false); + } + + /* Ignore X11 errors for the ReparentWindow request. + * X11 Errors are returned when the window was already destroyed */ + add_ignore_event(cookie.sequence, 0); ipc_send_window_event("close", con); window_free(con->window); con->window = NULL; @@ -293,8 +291,9 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par * Rendering has to be avoided when dont_kill_parent is set (when * tree_close_internal calls itself recursively) because the tree is in a * non-renderable state during that time. */ - if (!dont_kill_parent) + if (!dont_kill_parent) { tree_render(); + } /* kill the X11 part of this container */ x_con_kill(con); @@ -313,8 +312,9 @@ bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_par } /* check if the parent container is empty now and close it */ - if (!dont_kill_parent) + if (!dont_kill_parent) { CALL(parent, on_remove_child); + } return true; } @@ -419,8 +419,9 @@ bool level_down(void) { if (child == TAILQ_END(&(next->focus_head))) { DLOG("cannot go down\n"); return false; - } else + } else { next = TAILQ_FIRST(&(next->focus_head)); + } } con_activate(next); @@ -449,8 +450,9 @@ static void mark_unmapped(Con *con) { * */ void tree_render(void) { - if (croot == NULL) + if (croot == NULL) { return; + } DLOG("-- BEGIN RENDERING --\n"); /* Reset map state for all nodes in tree */ @@ -659,13 +661,15 @@ void tree_flatten(Con *con) { /* We only consider normal containers without windows */ if (con->type != CT_CON || parent->layout == L_OUTPUT || /* con == "content" */ - con->window != NULL) + con->window != NULL) { goto recurse; + } /* Ensure it got only one child */ child = TAILQ_FIRST(&(con->nodes_head)); - if (child == NULL || TAILQ_NEXT(child, nodes) != NULL) + if (child == NULL || TAILQ_NEXT(child, nodes) != NULL) { goto recurse; + } DLOG("child = %p, con = %p, parent = %p\n", child, con, parent); @@ -676,8 +680,9 @@ void tree_flatten(Con *con) { (con->layout != L_SPLITH && con->layout != L_SPLITV) || (child->layout != L_SPLITH && child->layout != L_SPLITV) || con_orientation(con) == con_orientation(child) || - con_orientation(child) != con_orientation(parent)) + con_orientation(child) != con_orientation(parent)) { goto recurse; + } DLOG("Alright, I have to flatten this situation now. Stay calm.\n"); /* 1: save focus */ diff --git a/src/util.c b/src/util.c index 7b148614..c0609987 100644 --- a/src/util.c +++ b/src/util.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * util.c: Utility functions, which can be useful everywhere within i3 (see @@ -21,42 +21,42 @@ #include #endif -int min(int a, int b) { +__attribute__((__const__)) int min(const int a, const int b) { return (a < b ? a : b); } -int max(int a, int b) { +__attribute__((__const__)) int max(const int a, const int b) { return (a > b ? a : b); } -bool rect_contains(Rect rect, uint32_t x, uint32_t y) { +__attribute__((__const__)) bool rect_contains(const Rect rect, const uint32_t x, const uint32_t y) { return (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y && y <= (rect.y + rect.height)); } -Rect rect_add(Rect a, Rect b) { +__attribute__((__const__)) Rect rect_add(const Rect a, const Rect b) { return (Rect){a.x + b.x, a.y + b.y, a.width + b.width, a.height + b.height}; } -Rect rect_sub(Rect a, Rect b) { +__attribute__((__const__)) Rect rect_sub(const Rect a, const Rect b) { return (Rect){a.x - b.x, a.y - b.y, a.width - b.width, a.height - b.height}; } -Rect rect_sanitize_dimensions(Rect rect) { +__attribute__((__const__)) Rect rect_sanitize_dimensions(Rect rect) { rect.width = (int32_t)rect.width <= 0 ? 1 : rect.width; rect.height = (int32_t)rect.height <= 0 ? 1 : rect.height; return rect; } -bool rect_equals(Rect a, Rect b) { +__attribute__((__const__)) bool rect_equals(const Rect a, const Rect b) { return a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height; } @@ -66,9 +66,11 @@ bool rect_equals(Rect a, Rect b) { */ __attribute__((pure)) bool name_is_digits(const char *name) { /* positive integers and zero are interpreted as numbers */ - for (size_t i = 0; i < strlen(name); i++) - if (!isdigit(name[i])) + for (size_t i = 0; i < strlen(name); i++) { + if (!isdigit(name[i])) { return false; + } + } return true; } @@ -83,17 +85,21 @@ bool layout_from_name(const char *layout_str, layout_t *out) { if (strcmp(layout_str, "default") == 0) { *out = L_DEFAULT; return true; - } else if (strcasecmp(layout_str, "stacked") == 0 || - strcasecmp(layout_str, "stacking") == 0) { + } + if (strcasecmp(layout_str, "stacked") == 0 || + strcasecmp(layout_str, "stacking") == 0) { *out = L_STACKED; return true; - } else if (strcasecmp(layout_str, "tabbed") == 0) { + } + if (strcasecmp(layout_str, "tabbed") == 0) { *out = L_TABBED; return true; - } else if (strcasecmp(layout_str, "splitv") == 0) { + } + if (strcasecmp(layout_str, "splitv") == 0) { *out = L_SPLITV; return true; - } else if (strcasecmp(layout_str, "splith") == 0) { + } + if (strcasecmp(layout_str, "splith") == 0) { *out = L_SPLITH; return true; } @@ -145,19 +151,19 @@ bool update_if_necessary(uint32_t *destination, const uint32_t new_value) { * */ void exec_i3_utility(char *name, char *argv[]) { - /* start the migration script, search PATH first */ - char *migratepath = name; - argv[0] = migratepath; - execvp(migratepath, argv); + /* start the utility, search PATH first */ + char *binary = name; + argv[0] = binary; + execvp(binary, argv); - /* if the script is not in path, maybe the user installed to a strange + /* if the utility is not in path, maybe the user installed to a strange * location and runs the i3 binary with an absolute path. We use * argv[0]’s dirname */ char *pathbuf = sstrdup(start_argv[0]); char *dir = dirname(pathbuf); - sasprintf(&migratepath, "%s/%s", dir, name); - argv[0] = migratepath; - execvp(migratepath, argv); + sasprintf(&binary, "%s/%s", dir, name); + argv[0] = binary; + execvp(binary, argv); #if defined(__linux__) /* on linux, we have one more fall-back: dirname(/proc/self/exe) */ @@ -167,9 +173,9 @@ void exec_i3_utility(char *name, char *argv[]) { _exit(EXIT_FAILURE); } dir = dirname(buffer); - sasprintf(&migratepath, "%s/%s", dir, name); - argv[0] = migratepath; - execvp(migratepath, argv); + sasprintf(&binary, "%s/%s", dir, name); + argv[0] = binary; + execvp(binary, argv); #endif warn("Could not start %s", name); @@ -182,8 +188,8 @@ void exec_i3_utility(char *name, char *argv[]) { */ static char **add_argument(char **original, char *opt_char, char *opt_arg, char *opt_name) { int num_args; - for (num_args = 0; original[num_args] != NULL; num_args++) - ; + for (num_args = 0; original[num_args] != NULL; num_args++) { + } char **result = scalloc(num_args + 3, sizeof(char *)); /* copy the arguments, but skip the ones we'll replace */ @@ -196,8 +202,9 @@ static char **add_argument(char **original, char *opt_char, char *opt_arg, char } if (!strcmp(original[i], opt_char) || (opt_name && !strcmp(original[i], opt_name))) { - if (opt_arg) + if (opt_arg) { skip_next = true; + } continue; } result[write_index++] = original[i]; @@ -214,12 +221,12 @@ static char **add_argument(char **original, char *opt_char, char *opt_arg, char #define ystr(str) yajl_gen_string(gen, (unsigned char *)str, strlen(str)) static char *store_restart_layout(void) { - setlocale(LC_NUMERIC, "C"); + locale_t prev_locale = uselocale(numericC); yajl_gen gen = yajl_gen_alloc(NULL); dump_node(gen, croot, true); - setlocale(LC_NUMERIC, ""); + uselocale(prev_locale); const unsigned char *payload; size_t length; @@ -230,8 +237,9 @@ static char *store_restart_layout(void) { char *filename; if (config.restart_state_path == NULL) { filename = get_process_filename("restart-state"); - if (!filename) + if (!filename) { return NULL; + } } else { filename = resolve_tilde(config.restart_state_path); } @@ -241,8 +249,9 @@ static char *store_restart_layout(void) { char *filenamecopy = sstrdup(filename); char *base = dirname(filenamecopy); DLOG("Creating \"%s\" for storing the restart layout\n", base); - if (mkdirp(base, DEFAULT_DIR_MODE) != 0) + if (mkdirp(base, DEFAULT_DIR_MODE) != 0) { ELOG("Could not create \"%s\" for storing the restart layout, layout will be lost.\n", base); + } free(filenamecopy); int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); @@ -310,8 +319,9 @@ void i3_restart(bool forget_layout) { * */ char *pango_escape_markup(char *input) { - if (!font_is_pango()) + if (!font_is_pango()) { return input; + } char *escaped = g_markup_escape_text(input, -1); FREE(input); @@ -364,8 +374,9 @@ void start_nagbar(pid_t *nagbar_pid, char *argv[]) { } /* child */ - if (*nagbar_pid == 0) + if (*nagbar_pid == 0) { exec_i3_utility("i3-nagbar", argv); + } DLOG("Starting i3-nagbar with PID %d\n", *nagbar_pid); @@ -385,14 +396,17 @@ void start_nagbar(pid_t *nagbar_pid, char *argv[]) { * */ void kill_nagbar(pid_t nagbar_pid, bool wait_for_it) { - if (nagbar_pid == -1) + if (nagbar_pid == -1) { return; + } - if (kill(nagbar_pid, SIGTERM) == -1) + if (kill(nagbar_pid, SIGTERM) == -1) { warn("kill(configerror_nagbar) failed"); + } - if (!wait_for_it) + if (!wait_for_it) { return; + } /* When restarting, we don’t enter the ev main loop anymore and after the * exec(), our old pid is no longer watched. So, ev won’t handle SIGCHLD diff --git a/src/version.c b/src/version.c index 1244711a..5878a10b 100644 --- a/src/version.c +++ b/src/version.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * Stores the latest Git commit identifier so that it can be linked into i3 diff --git a/src/window.c b/src/window.c index eeff0741..0106c6c4 100644 --- a/src/window.c +++ b/src/window.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * window.c: Updates window attributes (X11 hints/properties). @@ -49,10 +49,11 @@ void window_update_class(i3Window *win, xcb_get_property_reply_t *prop) { FREE(win->class_class); win->class_instance = sstrndup(new_class, prop_length); - if (class_class_index < prop_length) + if (class_class_index < prop_length) { win->class_class = sstrndup(new_class + class_class_index, prop_length - class_class_index); - else + } else { win->class_class = NULL; + } LOG("WM_CLASS changed to %s (instance), %s (class)\n", win->class_instance, win->class_class); @@ -375,8 +376,9 @@ bool window_update_normal_hints(i3Window *win, xcb_get_property_reply_t *reply, * */ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *urgency_hint) { - if (urgency_hint != NULL) + if (urgency_hint != NULL) { *urgency_hint = false; + } if (prop == NULL || xcb_get_property_value_length(prop) == 0) { DLOG("WM_HINTS not set.\n"); @@ -397,8 +399,9 @@ void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *ur LOG("WM_HINTS.input changed to \"%d\"\n", hints.input); } - if (urgency_hint != NULL) + if (urgency_hint != NULL) { *urgency_hint = (xcb_icccm_wm_hints_get_urgency(&hints) != 0); + } free(prop); } @@ -437,13 +440,14 @@ static border_style_t border_style_from_motif_value(uint32_t value) { } 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; } + if (value & MWM_DECOR_TITLE) { + return BS_NORMAL; + } + if (value & MWM_DECOR_BORDER) { + return BS_PIXEL; + } + return BS_NONE; } /* @@ -478,7 +482,7 @@ bool window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, bo * (64-bits long on amd64 for example). On the other hand, * xcb_get_property_value() behaves strictly according to documentation, * i.e. returns 32-bit data fields. */ - uint32_t *motif_hints = (uint32_t *)xcb_get_property_value(prop); + uint32_t *motif_hints = xcb_get_property_value(prop); if (motif_hints[MWM_HINTS_FLAGS_FIELD] & MWM_HINTS_DECORATIONS) { *motif_border_style = border_style_from_motif_value(motif_hints[MWM_HINTS_DECORATIONS_FIELD]); @@ -516,7 +520,7 @@ void window_update_machine(i3Window *win, xcb_get_property_reply_t *prop) { void window_update_icon(i3Window *win, xcb_get_property_reply_t *prop) { uint32_t *data = NULL; - uint32_t width, height; + uint32_t width = 0, height = 0; uint64_t len = 0; const uint32_t pref_size = (uint32_t)(render_deco_height() - logical_px(2)); @@ -527,7 +531,7 @@ void window_update_icon(i3Window *win, xcb_get_property_reply_t *prop) { } uint32_t prop_value_len = xcb_get_property_value_length(prop); - uint32_t *prop_value = (uint32_t *)xcb_get_property_value(prop); + uint32_t *prop_value = xcb_get_property_value(prop); /* Find an icon matching the preferred size. * If there is no such icon, take the smallest icon having at least @@ -593,12 +597,11 @@ void window_update_icon(i3Window *win, xcb_get_property_reply_t *prop) { uint32_t *icon = smalloc(len * 4); for (uint64_t i = 0; i < len; i++) { - uint8_t r, g, b, a; const uint32_t pixel = data[2 + i]; - a = (pixel >> 24) & 0xff; - r = (pixel >> 16) & 0xff; - g = (pixel >> 8) & 0xff; - b = (pixel >> 0) & 0xff; + const uint8_t a = (pixel >> 24) & 0xff; + uint8_t r = (pixel >> 16) & 0xff; + uint8_t g = (pixel >> 8) & 0xff; + uint8_t b = (pixel >> 0) & 0xff; /* Cairo uses premultiplied alpha */ r = (r * a) / 0xff; diff --git a/src/workspace.c b/src/workspace.c index 1bf1225c..4b7889ca 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * workspace.c: Modifying workspaces, accessing them, moving containers to @@ -190,12 +190,14 @@ void extract_workspace_names_from_bindings(void) { TAILQ_FOREACH (bind, bindings, bindings) { DLOG("binding with command %s\n", bind->command); if (strlen(bind->command) < strlen("workspace ") || - strncasecmp(bind->command, "workspace", strlen("workspace")) != 0) + strncasecmp(bind->command, "workspace", strlen("workspace")) != 0) { continue; + } DLOG("relevant command = %s\n", bind->command); const char *target = bind->command + strlen("workspace "); - while (*target == ' ' || *target == '\t') + while (*target == ' ' || *target == '\t') { target++; + } /* We check if this is the workspace * next/prev/next_on_output/prev_on_output/back_and_forth command. * Beware: The workspace names "next", "prev", "next_on_output", @@ -206,21 +208,25 @@ void extract_workspace_names_from_bindings(void) { strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 || strncasecmp(target, "prev_on_output", strlen("prev_on_output")) == 0 || strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 || - strncasecmp(target, "current", strlen("current")) == 0) + strncasecmp(target, "current", strlen("current")) == 0) { continue; + } if (strncasecmp(target, "--no-auto-back-and-forth", strlen("--no-auto-back-and-forth")) == 0) { target += strlen("--no-auto-back-and-forth"); - while (*target == ' ' || *target == '\t') + while (*target == ' ' || *target == '\t') { target++; + } } if (strncasecmp(target, "number", strlen("number")) == 0) { target += strlen("number"); - while (*target == ' ' || *target == '\t') + while (*target == ' ' || *target == '\t') { target++; + } } char *target_name = parse_string(&target, false); - if (target_name == NULL) + if (target_name == NULL) { continue; + } if (strncasecmp(target_name, "__", strlen("__")) == 0) { LOG("Cannot create workspace \"%s\". Names starting with __ are i3-internal.\n", target); free(target_name); @@ -331,24 +337,28 @@ static Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) { if (current != exclude && current->sticky_group != NULL && current->window != NULL && - strcmp(current->sticky_group, sticky_group) == 0) + strcmp(current->sticky_group, sticky_group) == 0) { return current; + } Con *recurse = _get_sticky(current, sticky_group, exclude); - if (recurse != NULL) + if (recurse != NULL) { return recurse; + } } TAILQ_FOREACH (current, &(con->floating_head), floating_windows) { if (current != exclude && current->sticky_group != NULL && current->window != NULL && - strcmp(current->sticky_group, sticky_group) == 0) + strcmp(current->sticky_group, sticky_group) == 0) { return current; + } Con *recurse = _get_sticky(current, sticky_group, exclude); - if (recurse != NULL) + if (recurse != NULL) { return recurse; + } } return NULL; @@ -429,14 +439,16 @@ void workspace_show(Con *workspace) { Con *current, *old = NULL; /* safe-guard against showing i3-internal workspaces like __i3_scratch */ - if (con_is_internal(workspace)) + if (con_is_internal(workspace)) { return; + } /* disable fullscreen for the other workspaces and get the workspace we are * currently on. */ TAILQ_FOREACH (current, &(workspace->parent->nodes_head), nodes) { - if (current->fullscreen_mode == CF_OUTPUT) + if (current->fullscreen_mode == CF_OUTPUT) { old = current; + } current->fullscreen_mode = CF_NONE; } @@ -492,7 +504,7 @@ void workspace_show(Con *workspace) { if (focused->urgency_timer == NULL) { DLOG("Deferring reset of urgency flag of con %p on newly shown workspace %p\n", focused, workspace); - focused->urgency_timer = scalloc(1, sizeof(struct ev_timer)); + focused->urgency_timer = scalloc(1, sizeof(ev_timer)); /* use a repeating timer to allow for easy resets */ ev_timer_init(focused->urgency_timer, workspace_defer_update_urgent_hint_cb, config.workspace_urgency_timer, config.workspace_urgency_timer); @@ -503,8 +515,9 @@ void workspace_show(Con *workspace) { focused, workspace); ev_timer_again(main_loop, focused->urgency_timer); } - } else + } else { con_focus(next); + } ipc_send_workspace_event("focus", workspace, current); @@ -572,20 +585,25 @@ Con *workspace_next(void) { if (current->num == -1) { /* If currently a named workspace, find next named workspace. */ - if ((next = TAILQ_NEXT(current, nodes)) != NULL) + if ((next = TAILQ_NEXT(current, nodes)) != NULL) { return next; + } bool found_current = false; TAILQ_FOREACH (output, &(croot->nodes_head), nodes) { /* Skip outputs starting with __, they are internal. */ - if (con_is_internal(output)) + if (con_is_internal(output)) { continue; + } NODES_FOREACH (output_get_content(output)) { - if (child->type != CT_WORKSPACE) + if (child->type != CT_WORKSPACE) { continue; - if (!first) + } + if (!first) { first = child; - if (!first_opposite || (child->num != -1 && child->num < first_opposite->num)) + } + if (!first_opposite || (child->num != -1 && child->num < first_opposite->num)) { first_opposite = child; + } if (child == current) { found_current = true; } else if (child->num == -1 && found_current) { @@ -596,30 +614,46 @@ Con *workspace_next(void) { } } else { /* If currently a numbered workspace, find next numbered workspace. */ + bool found_current = false; TAILQ_FOREACH (output, &(croot->nodes_head), nodes) { /* Skip outputs starting with __, they are internal. */ - if (con_is_internal(output)) + if (con_is_internal(output)) { continue; + } NODES_FOREACH (output_get_content(output)) { - if (child->type != CT_WORKSPACE) + if (child->type != CT_WORKSPACE) { continue; - if (!first || (child->num != -1 && child->num < first->num)) + } + if (!first || (child->num != -1 && child->num < first->num)) { first = child; - if (!first_opposite && child->num == -1) + } + if (!first_opposite && child->num == -1) { first_opposite = child; - if (child->num == -1) + } + if (child->num == -1) { break; + } /* Need to check child against current and next because we are * traversing multiple lists and thus are not guaranteed the * relative order between the list of workspaces. */ - if (current->num < child->num && (!next || child->num < next->num)) + if (current->num < child->num && (!next || child->num < next->num)) { next = child; + } + + /* If two workspaces have the same number, but different names + * (eg '5:a', '5:b') then just take the next one. */ + if (child == current) { + found_current = true; + } else if (found_current && current->num == child->num) { + return child; + } } } } - if (!next) + if (!next) { next = first_opposite ? first_opposite : first; + } return next; } @@ -636,21 +670,26 @@ Con *workspace_prev(void) { if (current->num == -1) { /* If named workspace, find previous named workspace. */ prev = TAILQ_PREV(current, nodes_head, nodes); - if (prev && prev->num != -1) + if (prev && prev->num != -1) { prev = NULL; + } if (!prev) { bool found_current = false; TAILQ_FOREACH_REVERSE (output, &(croot->nodes_head), nodes_head, nodes) { /* Skip outputs starting with __, they are internal. */ - if (con_is_internal(output)) + if (con_is_internal(output)) { continue; + } NODES_FOREACH_REVERSE (output_get_content(output)) { - if (child->type != CT_WORKSPACE) + if (child->type != CT_WORKSPACE) { continue; - if (!last) + } + if (!last) { last = child; - if (!first_opposite || (child->num != -1 && child->num > first_opposite->num)) + } + if (!first_opposite || (child->num != -1 && child->num > first_opposite->num)) { first_opposite = child; + } if (child == current) { found_current = true; } else if (child->num == -1 && found_current) { @@ -662,30 +701,46 @@ Con *workspace_prev(void) { } } else { /* If numbered workspace, find previous numbered workspace. */ + bool found_current = false; TAILQ_FOREACH_REVERSE (output, &(croot->nodes_head), nodes_head, nodes) { /* Skip outputs starting with __, they are internal. */ - if (con_is_internal(output)) + if (con_is_internal(output)) { continue; + } NODES_FOREACH_REVERSE (output_get_content(output)) { - if (child->type != CT_WORKSPACE) + if (child->type != CT_WORKSPACE) { continue; - if (!last || (child->num != -1 && last->num < child->num)) + } + if (!last || (child->num != -1 && last->num < child->num)) { last = child; - if (!first_opposite && child->num == -1) + } + if (!first_opposite && child->num == -1) { first_opposite = child; - if (child->num == -1) + } + if (child->num == -1) { continue; + } /* Need to check child against current and previous because we * are traversing multiple lists and thus are not guaranteed * the relative order between the list of workspaces. */ - if (current->num > child->num && (!prev || child->num > prev->num)) + if (current->num > child->num && (!prev || child->num > prev->num)) { prev = child; + } + + /* If two workspaces have the same number, but different names + * (eg '5:a', '5:b') then just take the previous one. */ + if (child == current) { + found_current = true; + } else if (found_current && current->num == child->num) { + return child; + } } } } - if (!prev) + if (!prev) { prev = first_opposite ? first_opposite : last; + } return prev; } @@ -704,16 +759,28 @@ Con *workspace_next_on_output(void) { next = TAILQ_NEXT(current, nodes); } else { /* If currently a numbered workspace, find next numbered workspace. */ + bool found_current = false; NODES_FOREACH (output_get_content(output)) { - if (child->type != CT_WORKSPACE) + if (child->type != CT_WORKSPACE) { continue; - if (child->num == -1) + } + if (child->num == -1) { break; + } /* Need to check child against current and next because we are * traversing multiple lists and thus are not guaranteed the * relative order between the list of workspaces. */ - if (current->num < child->num && (!next || child->num < next->num)) + if (current->num < child->num && (!next || child->num < next->num)) { next = child; + } + + /* If two workspaces have the same number, but different names + * (eg '5:a', '5:b') then just take the next one. */ + if (child == current) { + found_current = true; + } else if (found_current && current->num == child->num) { + return child; + } } } @@ -721,8 +788,9 @@ Con *workspace_next_on_output(void) { if (!next) { bool found_current = false; NODES_FOREACH (output_get_content(output)) { - if (child->type != CT_WORKSPACE) + if (child->type != CT_WORKSPACE) { continue; + } if (child == current) { found_current = true; } else if (child->num == -1 && (current->num != -1 || found_current)) { @@ -735,10 +803,12 @@ Con *workspace_next_on_output(void) { /* Find first workspace. */ if (!next) { NODES_FOREACH (output_get_content(output)) { - if (child->type != CT_WORKSPACE) + if (child->type != CT_WORKSPACE) { continue; - if (!next || (child->num != -1 && child->num < next->num)) + } + if (!next || (child->num != -1 && child->num < next->num)) { next = child; + } } } workspace_next_on_output_end: @@ -758,18 +828,30 @@ Con *workspace_prev_on_output(void) { if (current->num == -1) { /* If named workspace, find previous named workspace. */ prev = TAILQ_PREV(current, nodes_head, nodes); - if (prev && prev->num != -1) + if (prev && prev->num != -1) { prev = NULL; + } } else { /* If numbered workspace, find previous numbered workspace. */ + bool found_current = false; NODES_FOREACH_REVERSE (output_get_content(output)) { - if (child->type != CT_WORKSPACE || child->num == -1) + if (child->type != CT_WORKSPACE || child->num == -1) { continue; + } /* Need to check child against current and previous because we * are traversing multiple lists and thus are not guaranteed * the relative order between the list of workspaces. */ - if (current->num > child->num && (!prev || child->num > prev->num)) + if (current->num > child->num && (!prev || child->num > prev->num)) { prev = child; + } + + /* If two workspaces have the same number, but different names + * (eg '5:a', '5:b') then just take the previous one. */ + if (child == current) { + found_current = true; + } else if (found_current && current->num == child->num) { + return child; + } } } @@ -777,8 +859,9 @@ Con *workspace_prev_on_output(void) { if (!prev) { bool found_current = false; NODES_FOREACH_REVERSE (output_get_content(output)) { - if (child->type != CT_WORKSPACE) + if (child->type != CT_WORKSPACE) { continue; + } if (child == current) { found_current = true; } else if (child->num == -1 && (current->num != -1 || found_current)) { @@ -791,10 +874,12 @@ Con *workspace_prev_on_output(void) { /* Find last workspace. */ if (!prev) { NODES_FOREACH_REVERSE (output_get_content(output)) { - if (child->type != CT_WORKSPACE) + if (child->type != CT_WORKSPACE) { continue; - if (!prev || child->num > prev->num) + } + if (!prev || child->num > prev->num) { prev = child; + } } } @@ -855,8 +940,9 @@ void workspace_update_urgent_flag(Con *ws) { ws->urgent = get_urgency_flag(ws); DLOG("Workspace urgency flag changed from %d to %d\n", old_flag, ws->urgent); - if (old_flag != ws->urgent) + if (old_flag != ws->urgent) { ipc_send_workspace_event("urgent", ws, NULL); + } } /* @@ -953,9 +1039,8 @@ Con *workspace_encapsulate(Con *ws) { DLOG("Moving children of workspace %p / %s into container %p\n", ws, ws->name, new); - Con *child; while (!TAILQ_EMPTY(&(ws->nodes_head))) { - child = TAILQ_FIRST(&(ws->nodes_head)); + Con *child = TAILQ_FIRST(&(ws->nodes_head)); con_detach(child); con_attach(child, new, true); } diff --git a/src/x.c b/src/x.c index 9720e833..87ee05c2 100644 --- a/src/x.c +++ b/src/x.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * x.c: Interface to X11, transfers our in-memory state to X11 (see also @@ -41,6 +41,8 @@ typedef struct con_state { bool unmap_now; bool child_mapped; bool is_hidden; + bool is_maximized_vert; + bool is_maximized_horz; /* The con for which this state is. */ Con *con; @@ -177,7 +179,7 @@ void x_con_init(Con *con) { (strlen("i3-frame") + 1) * 2, "i3-frame\0i3-frame\0"); - struct con_state *state = scalloc(1, sizeof(struct con_state)); + con_state *state = scalloc(1, sizeof(struct con_state)); state->id = con->frame.id; state->mapped = false; state->initial = true; @@ -195,7 +197,7 @@ void x_con_init(Con *con) { * */ void x_reinit(Con *con) { - struct con_state *state; + con_state *state; if ((state = state_for_frame(con->frame.id)) == NULL) { ELOG("window state not found\n"); @@ -215,7 +217,7 @@ void x_reinit(Con *con) { * */ void x_reparent_child(Con *con, Con *old) { - struct con_state *state; + con_state *state; if ((state = state_for_frame(con->frame.id)) == NULL) { ELOG("window state for con not found\n"); return; @@ -230,7 +232,7 @@ void x_reparent_child(Con *con, Con *old) { * */ void x_move_win(Con *src, Con *dest) { - struct con_state *state_src, *state_dest; + con_state *state_src, *state_dest; if ((state_src = state_for_frame(src->frame.id)) == NULL) { ELOG("window state for src not found\n"); @@ -252,8 +254,6 @@ void x_move_win(Con *src, Con *dest) { } static void _x_con_kill(Con *con) { - con_state *state; - if (con->colormap != XCB_NONE) { xcb_free_colormap(conn, con->colormap); } @@ -262,7 +262,7 @@ static void _x_con_kill(Con *con) { draw_util_surface_free(conn, &(con->frame_buffer)); xcb_free_pixmap(conn, con->frame_buffer.id); con->frame_buffer.id = XCB_NONE; - state = state_for_frame(con->frame.id); + con_state *state = state_for_frame(con->frame.id); CIRCLEQ_REMOVE(&state_head, state, state); CIRCLEQ_REMOVE(&old_state_head, state, old_state); TAILQ_REMOVE(&initial_mapping_head, state, initial_mapping_order); @@ -301,18 +301,20 @@ void x_con_reframe(Con *con) { * */ bool window_supports_protocol(xcb_window_t window, xcb_atom_t atom) { - xcb_get_property_cookie_t cookie; xcb_icccm_get_wm_protocols_reply_t protocols; bool result = false; - cookie = xcb_icccm_get_wm_protocols(conn, window, A_WM_PROTOCOLS); - if (xcb_icccm_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1) + xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_protocols(conn, window, A_WM_PROTOCOLS); + if (xcb_icccm_get_wm_protocols_reply(conn, cookie, &protocols, NULL) != 1) { return false; + } /* Check if the client’s protocols have the requested atom set */ - for (uint32_t i = 0; i < protocols.atoms_len; i++) - if (protocols.atoms[i] == atom) + for (uint32_t i = 0; i < protocols.atoms_len; i++) { + if (protocols.atoms[i] == atom) { result = true; + } + } xcb_icccm_get_wm_protocols_reply_wipe(&protocols); @@ -471,18 +473,21 @@ void x_draw_decoration(Con *con) { parent->layout != L_TABBED) || parent->type == CT_OUTPUT || parent->type == CT_DOCKAREA || - con->type == CT_FLOATING_CON) + con->type == CT_FLOATING_CON) { return; + } /* Skip containers whose height is 0 (for example empty dockareas) */ - if (con->rect.height == 0) + if (con->rect.height == 0) { return; + } /* Skip containers whose pixmap has not yet been created (can happen when * decoration rendering happens recursively for a window for which * x_push_node() was not yet called) */ - if (leaf && con->frame_buffer.id == XCB_NONE) + if (leaf && con->frame_buffer.id == XCB_NONE) { return; + } /* 1: build deco_params and compare with cache */ struct deco_render_params *p = scalloc(1, sizeof(struct deco_render_params)); @@ -532,8 +537,9 @@ void x_draw_decoration(Con *con) { FREE(con->deco_render_params); con->deco_render_params = p; - if (con->window != NULL && con->window->name_x_changed) + if (con->window != NULL && con->window->name_x_changed) { con->window->name_x_changed = false; + } parent->pixmap_recreated = false; con->pixmap_recreated = false; @@ -602,8 +608,9 @@ void x_draw_decoration(Con *con) { /* If the parent hasn't been set up yet, skip the decoration rendering * for now. */ - if (dest_surface->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 @@ -614,8 +621,9 @@ void x_draw_decoration(Con *con) { /* if this is a borderless/1pixel window, we don’t need to render the * decoration. */ - if (p->border_style != BS_NORMAL) + if (p->border_style != BS_NORMAL || con->deco_rect.width == 0 || con->deco_rect.height == 0) { goto copy_pixmaps; + } /* 4: paint the bar */ DLOG("con->deco_rect = (x=%d, y=%d, w=%d, h=%d) for con->name=%s\n", @@ -641,8 +649,9 @@ void x_draw_decoration(Con *con) { mark_t *mark; TAILQ_FOREACH (mark, &(con->marks_head), marks) { - if (mark->name[0] == '_') + if (mark->name[0] == '_') { continue; + } had_visible_mark = true; char *buf; @@ -731,6 +740,9 @@ 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, dest_surface, @@ -764,12 +776,12 @@ copy_pixmaps: * */ void x_deco_recurse(Con *con) { - Con *current; bool leaf = TAILQ_EMPTY(&(con->nodes_head)) && TAILQ_EMPTY(&(con->floating_head)); - con_state *state = state_for_frame(con->frame.id); + const con_state *state = state_for_frame(con->frame.id); if (!leaf) { + Con *current; TAILQ_FOREACH (current, &(con->nodes_head), nodes) { x_deco_recurse(current); } @@ -784,8 +796,9 @@ void x_deco_recurse(Con *con) { } if ((con->type != CT_ROOT && con->type != CT_OUTPUT) && - (!leaf || con->mapped)) + (!leaf || con->mapped)) { x_draw_decoration(con); + } } /* @@ -799,8 +812,9 @@ static void set_hidden_state(Con *con) { con_state *state = state_for_frame(con->frame.id); bool should_be_hidden = con_is_hidden(con); - if (should_be_hidden == state->is_hidden) + if (should_be_hidden == state->is_hidden) { return; + } if (should_be_hidden) { DLOG("setting _NET_WM_STATE_HIDDEN for con = %p\n", con); @@ -813,6 +827,44 @@ static void set_hidden_state(Con *con) { state->is_hidden = should_be_hidden; } +/* + * Sets or removes _NET_WM_STATE_MAXIMIZE_{HORZ, VERT} on con + * + */ +static void set_maximized_state(Con *con) { + if (!con->window) { + return; + } + + con_state *state = state_for_frame(con->frame.id); + + const bool con_maximized_horz = con_is_maximized(con, HORIZ); + if (con_maximized_horz != state->is_maximized_horz) { + DLOG("setting _NET_WM_STATE_MAXIMIZED_HORZ for con %p(%s) to %d\n", con, con->name, con_maximized_horz); + + if (con_maximized_horz) { + xcb_add_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_MAXIMIZED_HORZ); + } else { + xcb_remove_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_MAXIMIZED_HORZ); + } + + state->is_maximized_horz = con_maximized_horz; + } + + const bool con_maximized_vert = con_is_maximized(con, VERT); + if (con_maximized_vert != state->is_maximized_vert) { + DLOG("setting _NET_WM_STATE_MAXIMIZED_VERT for con %p(%s) to %d\n", con, con->name, con_maximized_vert); + + if (con_maximized_vert) { + xcb_add_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_MAXIMIZED_VERT); + } else { + xcb_remove_property_atom(conn, con->window->id, A__NET_WM_STATE, A__NET_WM_STATE_MAXIMIZED_VERT); + } + + state->is_maximized_vert = con_maximized_vert; + } +} + /* * Set the container frame shape as the union of the window shape and the * shape of the frame borders. @@ -851,7 +903,7 @@ static void set_shape_state(Con *con, bool need_reshape) { return; } - struct con_state *state; + con_state *state; if ((state = state_for_frame(con->frame.id)) == NULL) { ELOG("window state for con %p not found\n", con); return; @@ -886,10 +938,9 @@ static void set_shape_state(Con *con, bool need_reshape) { */ void x_push_node(Con *con) { Con *current; - con_state *state; Rect rect = con->rect; - state = state_for_frame(con->frame.id); + con_state *state = state_for_frame(con->frame.id); if (state->name != NULL) { DLOG("pushing name %s for con %p\n", state->name, con); @@ -911,8 +962,9 @@ void x_push_node(Con *con) { } } rect.height = max_y + max_height; - if (rect.height == 0) + if (rect.height == 0) { con->mapped = false; + } } else if (con->window == NULL) { /* not a stacked or tabbed split container */ con->mapped = false; @@ -969,8 +1021,9 @@ void x_push_node(Con *con) { /* 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 * large, causing an XCB_ALLOC error. */ - if (con->type == CT_ROOT || con->type == CT_OUTPUT) + if (con->type == CT_ROOT || con->type == CT_OUTPUT) { is_pixmap_needed = false; + } bool fake_notify = false; /* Set new position if rect changed (and if height > 0) or if the pixmap @@ -1001,8 +1054,9 @@ void x_push_node(Con *con) { } uint16_t win_depth = root_depth; - if (con->window) + if (con->window) { win_depth = con->window->depth; + } /* Ensure we have valid dimensions for our surface. */ /* TODO: This is probably a bug in the condition above as we should @@ -1032,11 +1086,12 @@ void x_push_node(Con *con) { * TODO: Should this work the same way for L_TABBED? */ if (!con->parent || con->parent->layout != L_STACKED || - TAILQ_FIRST(&(con->parent->focus_head)) == con) + TAILQ_FIRST(&(con->parent->focus_head)) == con) { /* Render the decoration now to make the correct decoration visible * from the very first moment. Later calls will be cached, so this * doesn’t hurt performance. */ x_deco_recurse(con); + } } DLOG("setting rect (%d, %d, %d, %d)\n", rect.x, rect.y, rect.width, rect.height); @@ -1055,7 +1110,7 @@ void x_push_node(Con *con) { fake_notify = true; } - /* dito, but for child windows */ + /* ditto, but for child windows */ if (con->window != NULL && !rect_equals(state->window_rect, con->window_rect)) { DLOG("setting window rect (%d, %d, %d, %d)\n", @@ -1067,6 +1122,24 @@ void x_push_node(Con *con) { set_shape_state(con, need_reshape); + /* Set _NET_FRAME_EXTENTS according to the actual decoration size. */ + if (con != NULL && con->window != NULL) { + Rect bsr = con_border_style_rect(con); + Rect r = { + bsr.x, /* left */ + 0 - bsr.width - bsr.x, /* right */ + bsr.y, /* top */ + 0 - bsr.height - bsr.y /* bottom */ + }; + xcb_change_property( + conn, + XCB_PROP_MODE_REPLACE, + con->window->id, + A__NET_FRAME_EXTENTS, + XCB_ATOM_CARDINAL, 32, 4, + &r); + } + /* Map if map state changed, also ensure that the child window * is changed if we are mapped and there is a new, unmapped child window. * Unmaps are handled in x_push_node_unmaps(). */ @@ -1118,6 +1191,7 @@ void x_push_node(Con *con) { } set_hidden_state(con); + set_maximized_state(con); /* Handle all children and floating windows of this node. We recurse * in focus order to display the focused client in a stack first when @@ -1138,15 +1212,12 @@ void x_push_node(Con *con) { */ static void x_push_node_unmaps(Con *con) { Con *current; - con_state *state; - - state = state_for_frame(con->frame.id); + con_state *state = state_for_frame(con->frame.id); /* map/unmap if map state changed, also ensure that the child window * is changed if we are mapped *and* in initial state (meaning the * container was empty before, but now got a child) */ if (state->unmap_now) { - xcb_void_cookie_t cookie; if (con->window != NULL) { /* Set WM_STATE_WITHDRAWN, it seems like Java apps need it */ long data[] = {XCB_ICCCM_WM_STATE_WITHDRAWN, XCB_NONE}; @@ -1154,7 +1225,7 @@ static void x_push_node_unmaps(Con *con) { A_WM_STATE, A_WM_STATE, 32, 2, data); } - cookie = xcb_unmap_window(conn, con->frame.id); + const xcb_void_cookie_t cookie = xcb_unmap_window(conn, con->frame.id); DLOG("unmapping container %p / %s (serial %d)\n", con, con->name, cookie.sequence); /* we need to increase ignore_unmap for this container (if it * contains a window) and for every window "under" this one which @@ -1182,13 +1253,15 @@ static void x_push_node_unmaps(Con *con) { * TODO: Remove once #1185 has been fixed */ static bool is_con_attached(Con *con) { - if (con->parent == NULL) + if (con->parent == NULL) { return false; + } Con *current; TAILQ_FOREACH (current, &(con->parent->nodes_head), nodes) { - if (current == con) + if (current == con) { return true; + } } return false; @@ -1220,8 +1293,9 @@ void x_push_changes(Con *con) { * them become ConfigureRequests that i3 handles. */ uint32_t values[1] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT}; CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) { - if (state->mapped) + if (state->mapped) { xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); + } } bool order_changed = false; bool stacking_changed = false; @@ -1249,13 +1323,15 @@ void x_push_changes(Con *con) { /* X11 correctly represents the stack if we push it from bottom to top */ CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) { - if (con_has_managed_window(state->con)) + if (con_has_managed_window(state->con)) { memcpy(walk++, &(state->con->window->id), sizeof(xcb_window_t)); + } con_state *prev = CIRCLEQ_PREV(state, state); con_state *old_prev = CIRCLEQ_PREV(state, old_state); - if (prev != old_prev) + if (prev != old_prev) { order_changed = true; + } if ((state->initial || order_changed) && prev != CIRCLEQ_END(&state_head)) { stacking_changed = true; uint32_t mask = 0; @@ -1278,8 +1354,9 @@ void x_push_changes(Con *con) { /* reorder by initial mapping */ TAILQ_FOREACH (state, &initial_mapping_head, initial_mapping_order) { - if (con_has_managed_window(state->con)) + if (con_has_managed_window(state->con)) { *walk++ = state->con->window->id; + } } ewmh_update_client_list(client_list_windows, client_list_count); @@ -1312,15 +1389,17 @@ void x_push_changes(Con *con) { values[0] = FRAME_EVENT_MASK; CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) { - if (state->mapped) + if (state->mapped) { xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); + } } x_deco_recurse(con); xcb_window_t to_focus = focused->frame.id; - if (focused->window != NULL) + if (focused->window != NULL) { to_focus = focused->window->id; + } if (focused_id != to_focus) { if (!focused->mapped) { @@ -1337,8 +1416,9 @@ void x_push_changes(Con *con) { change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused); - if (to_focus != last_focused && is_con_attached(focused)) + if (to_focus != last_focused && is_con_attached(focused)) { ipc_send_window_event("focus", focused); + } } else { DLOG("Updating focus (focused: %p / %s) to X11 window 0x%08x\n", focused, focused->name, to_focus); /* We remove XCB_EVENT_MASK_FOCUS_CHANGE from the event mask to get @@ -1356,8 +1436,9 @@ void x_push_changes(Con *con) { change_ewmh_focus((con_has_managed_window(focused) ? focused->window->id : XCB_WINDOW_NONE), last_focused); - if (to_focus != XCB_NONE && to_focus != last_focused && focused->window != NULL && is_con_attached(focused)) + if (to_focus != XCB_NONE && to_focus != last_focused && focused->window != NULL && is_con_attached(focused)) { ipc_send_window_event("focus", focused); + } } focused_id = last_focused = to_focus; @@ -1387,8 +1468,9 @@ void x_push_changes(Con *con) { * EnterNotify event. */ values[0] = FRAME_EVENT_MASK & ~XCB_EVENT_MASK_ENTER_WINDOW; CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) { - if (!state->unmap_now) + if (!state->unmap_now) { continue; + } xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); } @@ -1409,10 +1491,8 @@ void x_push_changes(Con *con) { * next call to x_push_changes() will make the change visible in X11. * */ -void x_raise_con(Con *con) { - con_state *state; - state = state_for_frame(con->frame.id); - +void x_raise_con(const Con *con) { + con_state *state = state_for_frame(con->frame.id); CIRCLEQ_REMOVE(&state_head, state, state); CIRCLEQ_INSERT_HEAD(&state_head, state, state); } @@ -1424,8 +1504,7 @@ void x_raise_con(Con *con) { * */ void x_set_name(Con *con, const char *name) { - struct con_state *state; - + con_state *state; if ((state = state_for_frame(con->frame.id)) == NULL) { ELOG("window state not found\n"); return; @@ -1472,8 +1551,9 @@ void x_set_i3_atoms(void) { * */ void x_set_warp_to(Rect *rect) { - if (config.mouse_warping != POINTER_WARPING_NONE) + if (config.mouse_warping != POINTER_WARPING_NONE) { warp_to = rect; + } } /* @@ -1487,8 +1567,9 @@ void x_mask_event_mask(uint32_t mask) { con_state *state; CIRCLEQ_FOREACH_REVERSE (state, &state_head, state) { - if (state->mapped) + if (state->mapped) { xcb_change_window_attributes(conn, state->id, XCB_CW_EVENT_MASK, values); + } } } @@ -1496,8 +1577,7 @@ void x_mask_event_mask(uint32_t mask) { * Enables or disables nonrectangular shape of the container frame. */ void x_set_shape(Con *con, xcb_shape_sk_t kind, bool enable) { - struct con_state *state; - if ((state = state_for_frame(con->frame.id)) == NULL) { + if (state_for_frame(con->frame.id) == NULL) { ELOG("window state for con %p not found\n", con); return; } diff --git a/src/xcb.c b/src/xcb.c index 5258dcc2..20bd0daa 100644 --- a/src/xcb.c +++ b/src/xcb.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * xcb.c: Helper functions for easier usage of XCB @@ -49,8 +49,9 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, xcb_change_window_attributes(conn, result, XCB_CW_CURSOR, cursor_values); /* Map the window (= make it visible) */ - if (map) + if (map) { xcb_map_window(conn, result); + } return result; } @@ -62,8 +63,9 @@ xcb_window_t create_window(xcb_connection_t *conn, Rect dims, */ void fake_absolute_configure_notify(Con *con) { xcb_rectangle_t absolute; - if (con->window == NULL) + if (con->window == NULL) { return; + } absolute.x = con->rect.x + con->window_rect.x; absolute.y = con->rect.y + con->window_rect.y; @@ -102,14 +104,13 @@ void send_take_focus(xcb_window_t window, xcb_timestamp_t timestamp) { * Configures the given window to have the size/position specified by given rect * */ -void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) { - xcb_void_cookie_t cookie; - cookie = xcb_configure_window(conn, window, - XCB_CONFIG_WINDOW_X | - XCB_CONFIG_WINDOW_Y | - XCB_CONFIG_WINDOW_WIDTH | - XCB_CONFIG_WINDOW_HEIGHT, - &(r.x)); +void xcb_set_window_rect(xcb_connection_t *conn, const xcb_window_t window, Rect r) { + xcb_void_cookie_t cookie = xcb_configure_window(conn, window, + XCB_CONFIG_WINDOW_X | + XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT, + &(r.x)); /* ignore events which are generated because we configured a window */ add_ignore_event(cookie.sequence, -1); } @@ -119,12 +120,14 @@ void xcb_set_window_rect(xcb_connection_t *conn, xcb_window_t window, Rect r) { * */ xcb_atom_t xcb_get_preferred_window_type(xcb_get_property_reply_t *reply) { - if (reply == NULL || xcb_get_property_value_length(reply) == 0) + if (reply == NULL || xcb_get_property_value_length(reply) == 0) { return XCB_NONE; + } xcb_atom_t *atoms; - if ((atoms = xcb_get_property_value(reply)) == NULL) + if ((atoms = xcb_get_property_value(reply)) == NULL) { return XCB_NONE; + } for (int i = 0; i < xcb_get_property_value_length(reply) / (reply->format / 8); i++) { if (atoms[i] == A__NET_WM_WINDOW_TYPE_NORMAL || @@ -149,39 +152,22 @@ xcb_atom_t xcb_get_preferred_window_type(xcb_get_property_reply_t *reply) { * */ bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom) { - if (prop == NULL || xcb_get_property_value_length(prop) == 0) + if (prop == NULL || xcb_get_property_value_length(prop) == 0) { return false; + } xcb_atom_t *atoms; - if ((atoms = xcb_get_property_value(prop)) == NULL) + if ((atoms = xcb_get_property_value(prop)) == NULL) { return false; + } - for (int i = 0; i < xcb_get_property_value_length(prop) / (prop->format / 8); i++) - if (atoms[i] == atom) + for (int i = 0; i < xcb_get_property_value_length(prop) / (prop->format / 8); i++) { + if (atoms[i] == atom) { return true; - - return false; -} - -/* - * Get depth of visual specified by visualid - * - */ -uint16_t get_visual_depth(xcb_visualid_t visual_id) { - xcb_depth_iterator_t depth_iter; - - depth_iter = xcb_screen_allowed_depths_iterator(root_screen); - for (; depth_iter.rem; xcb_depth_next(&depth_iter)) { - xcb_visualtype_iterator_t visual_iter; - - visual_iter = xcb_depth_visuals_iterator(depth_iter.data); - for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) { - if (visual_id == visual_iter.data->visual_id) { - return depth_iter.data->depth; - } } } - return 0; + + return false; } /* @@ -214,14 +200,14 @@ xcb_visualid_t get_visualid_by_depth(uint16_t depth) { depth_iter = xcb_screen_allowed_depths_iterator(root_screen); for (; depth_iter.rem; xcb_depth_next(&depth_iter)) { - if (depth_iter.data->depth != depth) + if (depth_iter.data->depth != depth) { continue; + } - xcb_visualtype_iterator_t visual_iter; - - visual_iter = xcb_depth_visuals_iterator(depth_iter.data); - if (!visual_iter.rem) + xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator(depth_iter.data); + if (!visual_iter.rem) { continue; + } return visual_iter.data->visual_id; } return 0; @@ -248,8 +234,9 @@ void xcb_remove_property_atom(xcb_connection_t *conn, xcb_window_t window, xcb_a xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, xcb_get_property(conn, false, window, property, XCB_GET_PROPERTY_TYPE_ANY, 0, 4096), NULL); - if (reply == NULL || xcb_get_property_value_length(reply) == 0) + if (reply == NULL || xcb_get_property_value_length(reply) == 0) { goto release_grab; + } xcb_atom_t *atoms = xcb_get_property_value(reply); if (atoms == NULL) { goto release_grab; @@ -260,8 +247,9 @@ void xcb_remove_property_atom(xcb_connection_t *conn, xcb_window_t window, xcb_a const int current_size = xcb_get_property_value_length(reply) / (reply->format / 8); xcb_atom_t values[current_size]; for (int i = 0; i < current_size; i++) { - if (atoms[i] != atom) + if (atoms[i] != atom) { values[num++] = atoms[i]; + } } xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, property, XCB_ATOM_ATOM, 32, num, values); diff --git a/src/xcursor.c b/src/xcursor.c index cb98399f..1e5b72ab 100644 --- a/src/xcursor.c +++ b/src/xcursor.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * xcursor.c: xcursor support for themed cursors. diff --git a/src/xinerama.c b/src/xinerama.c index ffaaa009..404fea07 100644 --- a/src/xinerama.c +++ b/src/xinerama.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * This is LEGACY code (we support RandR, which can do much more than @@ -36,15 +36,12 @@ static Output *get_screen_at(unsigned int x, unsigned int y) { * */ static void query_screens(xcb_connection_t *conn) { - xcb_xinerama_query_screens_reply_t *reply; - xcb_xinerama_screen_info_t *screen_info; - - reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); + xcb_xinerama_query_screens_reply_t *reply = xcb_xinerama_query_screens_reply(conn, xcb_xinerama_query_screens_unchecked(conn), NULL); if (!reply) { ELOG("Couldn't get Xinerama screens\n"); return; } - screen_info = xcb_xinerama_query_screens_screen_info(reply); + const xcb_xinerama_screen_info_t *screen_info = xcb_xinerama_query_screens_screen_info(reply); int screens = xcb_xinerama_query_screens_screen_info_length(reply); for (int screen = 0; screen < screens; screen++) { @@ -68,10 +65,11 @@ static void query_screens(xcb_connection_t *conn) { s->rect.width = screen_info[screen].width; s->rect.height = screen_info[screen].height; /* We always treat the screen at 0x0 as the primary screen */ - if (s->rect.x == 0 && s->rect.y == 0) + if (s->rect.x == 0 && s->rect.y == 0) { TAILQ_INSERT_HEAD(&outputs, s, outputs); - else + } else { TAILQ_INSERT_TAIL(&outputs, s, outputs); + } output_init_con(s); init_ws_for_output(s); num_screens++; @@ -113,14 +111,14 @@ void xinerama_init(void) { DLOG("Xinerama extension not found, using root output.\n"); use_root_output(conn); } else { - xcb_xinerama_is_active_reply_t *reply; - reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL); + xcb_xinerama_is_active_reply_t *reply = xcb_xinerama_is_active_reply(conn, xcb_xinerama_is_active(conn), NULL); if (reply == NULL || !reply->state) { DLOG("Xinerama is not active (in your X-Server), using root output.\n"); use_root_output(conn); - } else + } else { query_screens(conn); + } FREE(reply); } diff --git a/testcases/complete-run.pl.in b/testcases/complete-run.pl.in index 00f8e609..1757cff2 100755 --- a/testcases/complete-run.pl.in +++ b/testcases/complete-run.pl.in @@ -106,6 +106,8 @@ $ENV{PATH} = join(':', '@abs_top_builddir@', '@abs_top_srcdir@', $ENV{PATH}); +# Make sure we don't re-use the existing (developer's) i3 connection +delete $ENV{I3SOCK}; qx(Xephyr -help 2>&1); die "Xephyr was not found in your path. Please install Xephyr (xserver-xephyr on Debian)." if $?; @@ -203,7 +205,7 @@ for my $display (@displays) { # Read previous timing information, if available. We will be able to roughly # predict the test duration and schedule a good order for the tests. my $timingsjson = slurp('.last_run_timings.json') if -e '.last_run_timings.json'; -%timings = %{decode_json($timingsjson)} if length($timingsjson) > 0; +%timings = %{decode_json($timingsjson)} if length($timingsjson // '') > 0; # Re-order the files so that those which took the longest time in the previous # run will be started at the beginning to not delay the whole run longer than @@ -377,7 +379,7 @@ sub take_job { } # count lines before stripping eof-marker otherwise we might - # end up with for (1 .. 0) { } which would effectivly skip the loop + # end up with for (1 .. 0) { } which would effectively skip the loop my $lines = $buf =~ tr/\n//; my $t_eof = $buf =~ s/^$TestWorker::EOF$//m; diff --git a/testcases/inject_randr1.5.c b/testcases/inject_randr1.5.c index 75cbf835..e96f95a3 100644 --- a/testcases/inject_randr1.5.c +++ b/testcases/inject_randr1.5.c @@ -1,7 +1,7 @@ /* * vim:ts=4:sw=4:expandtab * - * i3 - an improved dynamic tiling window manager + * i3 - an improved tiling window manager * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * inject_randr1.5.c: An X11 proxy which interprets RandR 1.5 GetMonitors @@ -54,7 +54,7 @@ static struct injected_reply getmonitors_reply = {NULL, 0}; static struct injected_reply getoutputinfo_reply = {NULL, 0}; /* END RandR 1.5 specific */ -#define XCB_PAD(i) (-(i)&3) +#define XCB_PAD(i) (-(i) & 3) struct connstate { /* clientw is a libev watcher for the connection which we accept()ed. */ diff --git a/testcases/lib/i3test.pm.in b/testcases/lib/i3test.pm.in index 6d73afca..fbca9e86 100644 --- a/testcases/lib/i3test.pm.in +++ b/testcases/lib/i3test.pm.in @@ -53,7 +53,7 @@ our @EXPORT = qw( kill_all_windows events_for listen_for_binding - is_net_wm_state_focused + net_wm_state_contains cmp_tree ); @@ -173,6 +173,9 @@ use Time::HiRes qw(sleep); use i3test::Test; __ $tester->BAIL_OUT("$@") if $@; + require feature; + require strict; + require warnings; feature->import(":5.10"); strict->import; warnings->import; @@ -897,7 +900,6 @@ tests which test specific config file directives. use i3test i3_autostart => 0; my $config = <atom(name => '_NET_WM_STATE_FOCUSED'); + my $atom = $x->atom(name => $atom_name); my $cookie = $x->get_property( 0, $window->{id}, diff --git a/testcases/new-test b/testcases/new-test index e0b2e13f..cc8bd279 100755 --- a/testcases/new-test +++ b/testcases/new-test @@ -53,7 +53,7 @@ my $header = <<'EOF'; # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # TODO: Description of this file. @@ -84,7 +84,6 @@ if ($multi_monitor) { use i3test i3_autostart => 0; my $config = <input_focus, $window->id, 'fullscreen window still focused'); ################################################################################ -# Verify that changing workspace while in global fullscreen does not work. +# Verify that changing workspace while in global fullscreen disables fullscreen +# first. +# See #2974 ################################################################################ $tmp = fresh_workspace; @@ -197,11 +199,11 @@ is(focused_ws(), $tmp, 'workspace selected'); $other = get_unused_workspace; cmd "workspace $other"; -is($x->input_focus, $window->id, 'window still focused'); -is(focused_ws(), $tmp, 'workspace still selected'); +isnt($x->input_focus, $window->id, 'window not focused anymore'); +is(focused_ws(), $other, 'workspace switched'); +is_num_fullscreen($other, 0, 'no fullscreen windows'); +is_num_fullscreen($tmp, 0, 'no fullscreen windows'); -# leave global fullscreen so that is does not interfere with the other tests -$window->fullscreen(0); sync_with_i3; ################################################################################ diff --git a/testcases/t/101-focus.t b/testcases/t/101-focus.t index 1e87a544..0784979b 100644 --- a/testcases/t/101-focus.t +++ b/testcases/t/101-focus.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/102-dock.t b/testcases/t/102-dock.t index 60352d6d..1b513cb4 100644 --- a/testcases/t/102-dock.t +++ b/testcases/t/102-dock.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/104-focus-stack.t b/testcases/t/104-focus-stack.t index d2193368..7624a13e 100644 --- a/testcases/t/104-focus-stack.t +++ b/testcases/t/104-focus-stack.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Checks if the focus is correctly restored, when creating a floating client diff --git a/testcases/t/111-goto.t b/testcases/t/111-goto.t index ade653d1..8617be47 100644 --- a/testcases/t/111-goto.t +++ b/testcases/t/111-goto.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/112-floating-resize.t b/testcases/t/112-floating-resize.t index 947ca3b6..d393cac1 100644 --- a/testcases/t/112-floating-resize.t +++ b/testcases/t/112-floating-resize.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/113-urgent.t b/testcases/t/113-urgent.t index 0de90193..ec26eb0e 100644 --- a/testcases/t/113-urgent.t +++ b/testcases/t/113-urgent.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) use i3test i3_autostart => 0; @@ -47,9 +47,10 @@ sub set_urgency { } my $config = <{window} == $win1->id } @content; ok(!$win1_info->{urgent}, 'win1 window is not marked urgent after focusing'); +############################################################################## +# +############################################################################## + $tmp = fresh_workspace; + $win1 = open_window(wm_class => 'special'); + $win2 = open_window; + is($x->input_focus, $win2->id, 'second window has focus'); + + cmd 'nop hello'; + set_urgency($win1, 1, $type); + sync_with_i3; + is($x->input_focus, $win1->id, 'first window got focus'); + ############################################################################## exit_gracefully($pid); diff --git a/testcases/t/114-client-leader.t b/testcases/t/114-client-leader.t index 3ecc644d..7826f028 100644 --- a/testcases/t/114-client-leader.t +++ b/testcases/t/114-client-leader.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/115-ipc-workspaces.t b/testcases/t/115-ipc-workspaces.t index b0c4354e..d47e525c 100644 --- a/testcases/t/115-ipc-workspaces.t +++ b/testcases/t/115-ipc-workspaces.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/116-nestedcons.t b/testcases/t/116-nestedcons.t index 7b15c5f0..a945b034 100644 --- a/testcases/t/116-nestedcons.t +++ b/testcases/t/116-nestedcons.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) use i3test; diff --git a/testcases/t/117-workspace.t b/testcases/t/117-workspace.t index 6345ae68..f9283a6f 100644 --- a/testcases/t/117-workspace.t +++ b/testcases/t/117-workspace.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests whether we can switch to a non-existent workspace diff --git a/testcases/t/118-openkill.t b/testcases/t/118-openkill.t index 08b5cda9..ad82c18b 100644 --- a/testcases/t/118-openkill.t +++ b/testcases/t/118-openkill.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests whether opening an empty container and killing it again works diff --git a/testcases/t/119-match.t b/testcases/t/119-match.t index 65c41f1f..33a8d580 100644 --- a/testcases/t/119-match.t +++ b/testcases/t/119-match.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests all kinds of matching methods diff --git a/testcases/t/120-multiple-cmds.t b/testcases/t/120-multiple-cmds.t index 114d7129..7d3a5fb6 100644 --- a/testcases/t/120-multiple-cmds.t +++ b/testcases/t/120-multiple-cmds.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests multiple commands (using ';') and multiple operations (using ',') diff --git a/testcases/t/121-next-prev.t b/testcases/t/121-next-prev.t index 72c52c9a..758ab10a 100644 --- a/testcases/t/121-next-prev.t +++ b/testcases/t/121-next-prev.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests focus switching (next/prev) diff --git a/testcases/t/122-split.t b/testcases/t/122-split.t index fe93880c..66f26ce6 100644 --- a/testcases/t/122-split.t +++ b/testcases/t/122-split.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests splitting diff --git a/testcases/t/124-move.t b/testcases/t/124-move.t index b4a8ca3e..27d56ef4 100644 --- a/testcases/t/124-move.t +++ b/testcases/t/124-move.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests moving. Basically, there are four different code-paths: diff --git a/testcases/t/126-regress-close.t b/testcases/t/126-regress-close.t index bdb31df2..3e02602c 100644 --- a/testcases/t/126-regress-close.t +++ b/testcases/t/126-regress-close.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Regression: closing of floating clients did crash i3 when closing the diff --git a/testcases/t/127-regress-floating-parent.t b/testcases/t/127-regress-floating-parent.t index ebacd3e5..3571fb85 100644 --- a/testcases/t/127-regress-floating-parent.t +++ b/testcases/t/127-regress-floating-parent.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Regression: make a container floating, kill its parent, make it tiling again diff --git a/testcases/t/128-open-order.t b/testcases/t/128-open-order.t index b0ab321f..1e52d77b 100644 --- a/testcases/t/128-open-order.t +++ b/testcases/t/128-open-order.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Check if new containers are opened after the currently focused one instead diff --git a/testcases/t/129-focus-after-close.t b/testcases/t/129-focus-after-close.t index 8bfa0140..fe498b9c 100644 --- a/testcases/t/129-focus-after-close.t +++ b/testcases/t/129-focus-after-close.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Check if the focus is correctly restored after closing windows. diff --git a/testcases/t/130-close-empty-split.t b/testcases/t/130-close-empty-split.t index 2e3cb794..62b49ed5 100644 --- a/testcases/t/130-close-empty-split.t +++ b/testcases/t/130-close-empty-split.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Check if empty split containers are automatically closed. diff --git a/testcases/t/131-stacking-order.t b/testcases/t/131-stacking-order.t index 3d89e0f3..7e0c7768 100644 --- a/testcases/t/131-stacking-order.t +++ b/testcases/t/131-stacking-order.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Check if stacking containers can be used independently of diff --git a/testcases/t/132-move-workspace.t b/testcases/t/132-move-workspace.t index c7721c3d..bcfdaa7e 100644 --- a/testcases/t/132-move-workspace.t +++ b/testcases/t/132-move-workspace.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Checks if the 'move [window/container] to workspace' command works correctly diff --git a/testcases/t/133-size-hints.t b/testcases/t/133-size-hints.t index a16c5399..ceb8ffca 100644 --- a/testcases/t/133-size-hints.t +++ b/testcases/t/133-size-hints.t @@ -11,13 +11,12 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Checks if size hints are interpreted correctly. # use i3test i3_config => < <{focused}, 'con is focused'); @@ -92,7 +91,7 @@ subtest 'Window with WM_TAKE_FOCUS and without InputHint', sub { $window->map; ok(!recv_take_focus($window), 'did not receive ClientMessage'); - ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set'); + ok(net_wm_state_contains($window, '_NET_WM_STATE_FOCUSED'), '_NET_WM_STATE_FOCUSED set'); my ($nodes) = get_ws_content($ws); my $con = shift @$nodes; @@ -114,7 +113,7 @@ subtest 'Window with WM_TAKE_FOCUS and unspecified InputHint', sub { my $window = open_window({ protocols => [ $take_focus ] }); ok(!recv_take_focus($window), 'did not receive ClientMessage'); - ok(is_net_wm_state_focused($window), '_NET_WM_STATE_FOCUSED set'); + ok(net_wm_state_contains($window, '_NET_WM_STATE_FOCUSED'), '_NET_WM_STATE_FOCUSED set'); my ($nodes) = get_ws_content($ws); my $con = shift @$nodes; diff --git a/testcases/t/159-socketpaths.t b/testcases/t/159-socketpaths.t index e73239ad..ebe576cb 100644 --- a/testcases/t/159-socketpaths.t +++ b/testcases/t/159-socketpaths.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests if the various ipc_socket_path options are correctly handled @@ -27,7 +27,6 @@ use v5.10; ##################################################################### my $config = < 0; @@ -20,7 +20,6 @@ use X11::XCB qw(PROP_MODE_REPLACE); my (@nodes); my $config = <<'EOT'; -# i3 config file (v4) font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 # test 1, test 2 diff --git a/testcases/t/166-assign.t b/testcases/t/166-assign.t index 249bbf87..d31bb534 100644 --- a/testcases/t/166-assign.t +++ b/testcases/t/166-assign.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests if assignments work @@ -64,7 +64,6 @@ sub test_workspace_assignment { ##################################################################### my $config = <destroy; ##################################################################### $config = < 0; ##################################################################### my $config = < 0; ##################################################################### my $config = < 0; ############################################################## my $config = < 0; ##################################################################### my $config = < 0; my $config = < 0; ##################################################################### my $config = < < 1); print $fh < < <) -ERROR: CONFIG: Line 3: font foobar -ERROR: CONFIG: Line 4: -ERROR: CONFIG: Line 5: unknown qux +ERROR: CONFIG: Line 1: font foobar +ERROR: CONFIG: Line 2: +ERROR: CONFIG: Line 3: unknown qux ERROR: CONFIG: ^^^^^^^^^^^ -ERROR: CONFIG: Line 6: -ERROR: CONFIG: Line 7: # yay +ERROR: CONFIG: Line 4: +ERROR: CONFIG: Line 5: # yay EOT $expected = $expected_head . $expected_all_tokens . $expected_tail; @@ -776,7 +774,7 @@ EOT $expected = <<'EOT'; cfg_bar_start() cfg_bar_output(LVDS-1) -ERROR: CONFIG: Expected one of these tokens: , '#', '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', 'height', 'padding', 'colors', '}' +ERROR: CONFIG: Expected one of these tokens: , '#', '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 ) ERROR: CONFIG: Line 1: bar { ERROR: CONFIG: Line 2: output LVDS-1 diff --git a/testcases/t/202-scratchpad-criteria.t b/testcases/t/202-scratchpad-criteria.t index 9d86be94..3489d5e9 100644 --- a/testcases/t/202-scratchpad-criteria.t +++ b/testcases/t/202-scratchpad-criteria.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Verifies that using criteria to address scratchpad windows works. diff --git a/testcases/t/203-regress-assign-and-move.t b/testcases/t/203-regress-assign-and-move.t index c70ce4ce..7f0d86b6 100644 --- a/testcases/t/203-regress-assign-and-move.t +++ b/testcases/t/203-regress-assign-and-move.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Verifies that you can assign a window _and_ use for_window with a move @@ -21,7 +21,6 @@ use i3test i3_autostart => 0; my $config = < <[0]->{success}, 0, 'command was unsuccessful'); is($result->[0]->{error}, 'A mark must not be put onto more than one window', 'correct error is returned'); is_deeply(get_mark_for_window_on_workspace($tmp, $first), [], 'first container is not marked'); -is_deeply(get_mark_for_window_on_workspace($tmp, $second), [], 'second containr is not marked'); +is_deeply(get_mark_for_window_on_workspace($tmp, $second), [], 'second container is not marked'); ############################################################## diff --git a/testcases/t/211-regress-urgency-assign.t b/testcases/t/211-regress-urgency-assign.t index 5bcc7460..a4acd063 100644 --- a/testcases/t/211-regress-urgency-assign.t +++ b/testcases/t/211-regress-urgency-assign.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Verifies that windows are properly recognized as urgent when they start up @@ -21,7 +21,6 @@ # Ticket: #1086 # Bug still in: 4.6-62-g7098ef6 use i3test i3_config => < 0; use X11::XCB qw(PROP_MODE_REPLACE); my $config = < < < 0; ##################################################################### my $config = < 1); my ($outfh, $outname) = tempfile('/tmp/i3-socket.XXXXXX', UNLINK => 1); $config = < 1); my $config_path = get_config_path(); open(my $configfh, '>', $config_path); say $configfh < < <<'EOT'; -# i3 config file (v4) font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 for_window [class="^special_kill$"] kill diff --git a/testcases/t/234-ewmh-desktop-names.t b/testcases/t/234-ewmh-desktop-names.t index 246a0a46..bd792902 100644 --- a/testcases/t/234-ewmh-desktop-names.t +++ b/testcases/t/234-ewmh-desktop-names.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Test that the EWMH specified property _NET_DESKTOP_NAMES is updated properly diff --git a/testcases/t/235-check-config-no-x.t b/testcases/t/235-check-config-no-x.t index dce70894..f3f7c5e2 100644 --- a/testcases/t/235-check-config-no-x.t +++ b/testcases/t/235-check-config-no-x.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Check whether the -C option works without a display and doesn't @@ -38,7 +38,6 @@ sub check_config { ################################################################################ $cfg = < <{mode}, 'default', 'Event for binding to enter new mode is atributed to default mode'); + is($events[0]->{mode}, 'default', 'Event for binding to enter new mode is attributed 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'); diff --git a/testcases/t/239-net-close-window-request.t b/testcases/t/239-net-close-window-request.t index 763da681..73063e6f 100644 --- a/testcases/t/239-net-close-window-request.t +++ b/testcases/t/239-net-close-window-request.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Test _NET_CLOSE_WINDOW requests to close a window. diff --git a/testcases/t/240-focus-on-window-activation.t b/testcases/t/240-focus-on-window-activation.t index efcd5ca4..b5897c58 100644 --- a/testcases/t/240-focus-on-window-activation.t +++ b/testcases/t/240-focus-on-window-activation.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests for the focus_on_window_activation directive @@ -49,7 +49,6 @@ sub get_urgency_for_window_on_workspace { ##################################################################### $config = < < <root->rect; ########################################################################## $config = < < " command. # Ticket: #1727 # Bug still in: 4.10.2-1-gc0dbc5d use i3test i3_config => < < < < < < 0; use X11::XCB qw(PROP_MODE_REPLACE); my $config = < < < < < <atom(name => '_NET_WM_STATE_HIDDEN'); - my ($con) = @_; - my $cookie = $x->get_property( - 0, - $con->{id}, - $x->atom(name => '_NET_WM_STATE')->id, - GET_PROPERTY_TYPE_ANY, - 0, - 4096 - ); - - my $reply = $x->get_property_reply($cookie->{sequence}); - my $len = $reply->{length}; - return 0 if $len == 0; - - my @atoms = unpack("L$len", $reply->{value}); - for (my $i = 0; $i < $len; $i++) { - return 1 if $atoms[$i] == $atom->id; - } - - return 0; + return net_wm_state_contains($con, '_NET_WM_STATE_HIDDEN'); } my ($tabA, $tabB, $tabC, $subtabA, $subtabB, $windowA, $windowB); diff --git a/testcases/t/284-ewmh-visible-name.t b/testcases/t/284-ewmh-visible-name.t index c83b019a..f2072b5b 100644 --- a/testcases/t/284-ewmh-visible-name.t +++ b/testcases/t/284-ewmh-visible-name.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests that _NET_WM_VISIBLE_NAME is set correctly. diff --git a/testcases/t/285-sticky.t b/testcases/t/285-sticky.t index 8dfe9aea..b601be53 100644 --- a/testcases/t/285-sticky.t +++ b/testcases/t/285-sticky.t @@ -11,13 +11,12 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests sticky windows. # Ticket: #1455 use i3test i3_config => < < 0; ##################################################################### my $config = <atleast_version('xcb-xkb', '1.11'); my $config = < 0; my $config = < < < < < 0; use i3test::XTEST; +my $pid = launch_with_config('-default'); + sub scroll_down { # button5 = scroll down xtest_button_press(5, 3, 3); @@ -81,4 +83,38 @@ cmd '[id=' . $outside->id . '] focus'; scroll_up; is($x->input_focus, $first->id, 'Scrolling from outside the tabbed container works'); +exit_gracefully($pid); + +############################################################################### +# Test that focus changes workspace correctly with 'focus_follows_mouse no' +# See issue #5472. +############################################################################### +$pid = launch_with_config(< 0); +$first = open_window; +cmd 'layout tabbed'; +is($x->input_focus, $first->id, 'sanity check: window focused'); +open_window; + +my $ws2 = fresh_workspace(output => 1); +ok(get_ws($ws2)->{focused}, 'sanity check: second workspace focused'); + +# Decoration of top left window. +$x->root->warp_pointer(3, 3); + +my @events = events_for( sub { scroll_up }, 'workspace'); +is($x->input_focus, $first->id, 'window focused'); +is(scalar @events, 1, 'Received 1 workspace event'); +is($events[0]->{change}, 'focus', 'Event has change = focus'); +is($events[0]->{current}->{name}, $ws1, 'new == ws1'); +is($events[0]->{old}->{name}, $ws2, 'old == ws2'); + +exit_gracefully($pid); + done_testing; diff --git a/testcases/t/298-ipc-misbehaving-connection.t b/testcases/t/298-ipc-misbehaving-connection.t index d53ee92d..8e8c1640 100644 --- a/testcases/t/298-ipc-misbehaving-connection.t +++ b/testcases/t/298-ipc-misbehaving-connection.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Test that i3 will not hang if a connected client stops reading from its @@ -19,7 +19,6 @@ # Ticket: #2999 # Bug still in: 4.15-180-g715cea61 use i3test i3_config => < < 0; my $config = <<"EOT"; -# i3 config file (v4) font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 fake-outputs 1024x768+0+0,1024x768+1024+0,1024x768+1024+768,1024x768+0+768 diff --git a/testcases/t/309-crash-move-parent.t b/testcases/t/309-crash-move-parent.t index eb58251b..423af0f2 100644 --- a/testcases/t/309-crash-move-parent.t +++ b/testcases/t/309-crash-move-parent.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Test that moving a container that is to be flattened does not crash i3 diff --git a/testcases/t/310-client-message-sticky.t b/testcases/t/310-client-message-sticky.t index 0e7d8b7c..f815d6b1 100644 --- a/testcases/t/310-client-message-sticky.t +++ b/testcases/t/310-client-message-sticky.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Verify that _NET_WM_DESKTOP sticky requests do not conflict with dock diff --git a/testcases/t/311-get-binding-modes.t b/testcases/t/311-get-binding-modes.t index 3a00f695..74bbdd42 100644 --- a/testcases/t/311-get-binding-modes.t +++ b/testcases/t/311-get-binding-modes.t @@ -11,14 +11,13 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Verifies the GET_BINDING_MODE IPC command # Ticket: #3892 # Bug still in: 4.18-318-g50160eb1 use i3test i3_config => <flush; $config = <flush; $config = <flush; $config = <flush; $config = <flush; $config = < 1); $wsfh->flush; $config = <flush; $config = <flush; $config = <root->warp_pointer($pos_x, $pos_y); sync_with_i3; - xtest_key_press(64); # Alt_L + xtest_key_press(64); # Alt_L xtest_button_press(1, $pos_x, $pos_y); xtest_sync_with_i3; } @@ -56,7 +55,7 @@ sub end_drag { sync_with_i3; xtest_button_release(1, $pos_x, $pos_y); - xtest_key_release(64); # Alt_L + xtest_key_release(64); # Alt_L xtest_sync_with_i3; } @@ -92,7 +91,7 @@ is($ws2, focused_ws, 'Empty workspace focused after floating window dragged to i # Drag tiling container onto an empty workspace. ############################################################################### -subtest "Draging tiling container onto an empty workspace produces move event", \&move_subtest, +subtest "Dragging tiling container onto an empty workspace produces move event", \&move_subtest, sub { $ws2 = fresh_workspace(output => 1); @@ -104,6 +103,30 @@ end_drag(1050, 50); is($x->input_focus, $A->id, 'Tiling window moved to the right workspace'); is($ws2, focused_ws, 'Empty workspace focused after tiling window dragged to it'); +is(@{get_ws_content($ws1)}, 0, 'No container left in ws1'); +is(@{get_ws_content($ws2)}, 1, 'One container in ws2'); + +}; + +############################################################################### +# Swap-drag tiling container onto an empty workspace. +############################################################################### + +subtest "Swap tiling container with an empty workspace does nothing", sub { + +$ws2 = fresh_workspace(output => 1); +$ws1 = fresh_workspace(output => 0); +$A = open_window; + +xtest_key_press(50); # Shift +start_drag(50, 50); +end_drag(1050, 50); +xtest_key_release(50); # Shift + +is($x->input_focus, $A->id, 'Tiling window still focused'); +is($ws1, focused_ws, 'Same workspace focused'); +is(@{get_ws_content($ws1)}, 1, 'One container still in ws1'); +is(@{get_ws_content($ws2)}, 0, 'No container in ws2'); }; @@ -132,7 +155,7 @@ is(@{get_ws_content($ws1)}, 1, 'One container left in ws1'); # Drag tiling container onto a tiling container on an other workspace. ############################################################################### -subtest "Draging tiling container onto a tiling container on an other workspace produces move event", \&move_subtest, +subtest "Dragging tiling container onto a tiling container on an other workspace produces move event", \&move_subtest, sub { $ws2 = fresh_workspace(output => 1); @@ -152,11 +175,37 @@ is($ws2->{focus}[1], $B_id, 'B focused second'); }; +############################################################################### +# Swap-drag tiling container onto a tiling container on an other workspace. +############################################################################### + +subtest "Swap tiling container with a tiling container on an other workspace produces move event", sub { + +$ws2 = fresh_workspace(output => 1); +open_window; +$B_id = get_focused($ws2); +$ws1 = fresh_workspace(output => 0); +$A = open_window; +$A_id = get_focused($ws1); + +xtest_key_press(50); # Shift +start_drag(50, 50); +end_drag(1500, 250); # Center of right output, inner region. +xtest_key_release(50); # Shift + +is($ws2, focused_ws, 'Workspace focused after tiling window dragged to it'); +$ws2 = get_ws($ws2); +is($ws2->{focus}[0], $A_id, 'A focused first, dragged container kept focus'); +$ws1 = get_ws($ws1); +is($ws1->{focus}[0], $B_id, 'B now in first workspace'); + +}; + ############################################################################### # Drag tiling container onto a floating container on an other workspace. ############################################################################### -subtest "Draging tiling container onto a floating container on an other workspace produces move event", \&move_subtest, +subtest "Dragging tiling container onto a floating container on an other workspace produces move event", \&move_subtest, sub { $ws2 = fresh_workspace(output => 1); @@ -180,7 +229,7 @@ is($ws2->{floating_nodes}[0]->{nodes}[0]->{id}, $B_id, 'B exists & floating'); # Drag tiling container onto a bar. ############################################################################### -subtest "Draging tiling container onto a bar produces move event", \&move_subtest, +subtest "Dragging tiling container onto a bar produces move event", \&move_subtest, sub { $ws1 = fresh_workspace(output => 0); @@ -221,7 +270,7 @@ is($ws1->{focus}[1], $A_id, 'A focused second, unfocused dragged container didn\ # Drag an unfocused tiling container onto an occupied workspace. ############################################################################### -subtest "Draging unfocused tiling container onto an occupied workspace produces move event", \&move_subtest, +subtest "Dragging unfocused tiling container onto an occupied workspace produces move event", \&move_subtest, sub { $ws1 = fresh_workspace(output => 0); @@ -261,7 +310,7 @@ is($x->input_focus, $A->id, 'Fullscreen container still focused'); # Drag unfocused fullscreen container onto window in other workspace. ############################################################################### -subtest "Draging unfocused fullscreen container onto window in other workspace produces move event", \&move_subtest, +subtest "Dragging unfocused fullscreen container onto window in other workspace produces move event", \&move_subtest, sub { $ws1 = fresh_workspace(output => 0); @@ -284,13 +333,13 @@ is($ws2->{nodes}->[0]->{window}, $A->id, 'Fullscreen container now leftmost wind }; ############################################################################### -# Drag unfocused fullscreen container onto left outter region of window in +# Drag unfocused fullscreen container onto left outer region of window in # other workspace. The container shouldn't end up in $ws2 because it was -# dragged onto the outter region of the leftmost window. We must also check +# dragged onto the outer region of the leftmost window. We must also check # that the focus remains on the other window. ############################################################################### -subtest "Draging unfocused fullscreen container onto left outter region of window in other workspace produces move event", \&move_subtest, +subtest "Dragging unfocused fullscreen container onto left outer region of window in other workspace produces move event", \&move_subtest, sub { $ws1 = fresh_workspace(output => 0); @@ -302,7 +351,7 @@ $ws2 = fresh_workspace(output => 1); $B = open_window; start_drag(990, 100); # rightmost of $ws1 -end_drag(1004, 100); # outter region of window of $ws2 +end_drag(1004, 100); # outer region of window of $ws2 is($ws2, focused_ws, 'Workspace still focused after dragging fullscreen container to it'); is_num_fullscreen($ws1, 1, 'Fullscreen container still in first workspace'); diff --git a/testcases/t/316-transient-for-loop.t b/testcases/t/316-transient-for-loop.t index 336a8d8d..3c7b7a7a 100644 --- a/testcases/t/316-transient-for-loop.t +++ b/testcases/t/316-transient-for-loop.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Test that i3 does not get stuck in an endless loop between two windows that @@ -20,7 +20,6 @@ # Bug still in: 4.20-69-g43e805a00 # use i3test i3_config => < <<'EOT'; +bar { + # no font directive here, no i3-wide font configured (yet) +} + +# NOTE: iso99887 is invalid font specification, so it should always fallback +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso99887-9 +EOT + +my $i3 = i3(get_socket_path(0)); +my $bars = $i3->get_bar_config()->recv; + +my $bar_id = shift @$bars; +my $bar_config = $i3->get_bar_config($bar_id)->recv; + +# This should fallback to 'fixed' due to nonexistent font set in config +is($bar_config->{font}, 'fixed', 'font fallback ok'); + +done_testing; diff --git a/testcases/t/317-bar-config-font-order.t b/testcases/t/317-bar-config-font-order.t index 3c1fdcdd..13f5643f 100644 --- a/testcases/t/317-bar-config-font-order.t +++ b/testcases/t/317-bar-config-font-order.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Verifies that bar config blocks get the i3-wide font configured, @@ -20,8 +20,6 @@ # Ticket: #5031 # Bug still in: 4.20-105-g4db383e4 use i3test i3_config => <<'EOT'; -# i3 config file (v4) - bar { # no font directive here, no i3-wide font configured (yet) } @@ -34,6 +32,12 @@ my $bars = $i3->get_bar_config()->recv; my $bar_id = shift @$bars; my $bar_config = $i3->get_bar_config($bar_id)->recv; -is($bar_config->{font}, 'fixed', 'font ok'); + +# This should either load the font specified, or fallback to 'fixed' +my %valid_fonts = map {; $_ => 1 } qw( + -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + fixed +); +is($valid_fonts{ $bar_config->{font} }, 1, 'font ok'); done_testing; diff --git a/testcases/t/317-bar-output-trailing-space.t b/testcases/t/317-bar-output-trailing-space.t index 540d30c9..f1def339 100644 --- a/testcases/t/317-bar-output-trailing-space.t +++ b/testcases/t/317-bar-output-trailing-space.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Verifies that any trailing whitespace in strings (including in @@ -23,7 +23,6 @@ use i3test i3_autostart => 0; # Test with a single output. my $config = < 1); @@ -39,7 +40,7 @@ mkfifo("$tmpdir/fifo", 0600) or BAIL_OUT "Could not create FIFO: $!"; open(my $i3msg_dump, '>', "$tmpdir/i3-msg"); say $i3msg_dump < 0; use i3test::Util qw(slurp); my $config = <get_version()->recv; open(my $configfh, '>', $version->{'loaded_config_file_name'}); say $configfh < <input_focus, $window->id, $msg); +} + +# Leftmost window is focused on button presses that have no binding +my $L = open_window; +my $A = open_window(wm_class => 'mark_A'); +my $B = open_window(wm_class => 'mark_B'); +is_focus($B, 'sanity check'); +is_focus(open_window, 'sanity check, other window'); + +button(1, $A, 'button 1 binding'); +button(1, $A, 'button 1 binding, again'); +button(2, $B, 'button 2 binding'); +button(1, $A, 'button 1 binding'); +button(3, $L, 'button 3, no binding'); + +# Test modes, see #4539 +# Unfortunately, grabbing / ungrabbing doesn't seem to work correctly in xvfb +# so we can't really test this. + +my $C = open_window(wm_class => 'mark_C'); +my $D = open_window(wm_class => 'mark_D'); + +button(4, $B, 'button 4 binding outside mode'); +button(5, $L, 'button 5 no binding outside mode'); + +cmd 'mode testmode'; +button(4, $C, 'button 4 binding inside mode'); +button(5, $D, 'button 5 binding inside mode'); + +cmd 'mode default'; +button(4, $B, 'button 4 binding outside mode'); +button(5, $L, 'button 5 no binding outside mode'); + +done_testing; diff --git a/testcases/t/321-crash-criteria-scratchpad.t b/testcases/t/321-crash-criteria-scratchpad.t new file mode 100644 index 00000000..e7a59206 --- /dev/null +++ b/testcases/t/321-crash-criteria-scratchpad.t @@ -0,0 +1,46 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • https://i3wm.org/downloads/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verify that i3 does not crash when command criteria that match a scratchpad +# window are used with the focus output command or other commands +# Ticket: #6076 +# Bug still in: 4.23-43-g822477cb +use i3test; + +my $ws = fresh_workspace; +my $win = open_window; +cmd "move scratchpad"; + +sub cmd_on_w { + local $Test::Builder::Level = $Test::Builder::Level + 1; + my $c = shift; + subtest "$c" => sub { + my $result = cmd '[id="' . $win->id . '"] ' . $c; + is($result->[0]->{success}, 1, "command succeeded"); + is(@{get_ws($ws)->{floating_nodes}}, 0, 'no floating windows on workspace'); + is(@{get_ws($ws)->{nodes}}, 0, 'no nodes on workspace'); + } +} + +cmd_on_w 'nop'; +cmd_on_w 'focus output left'; +cmd_on_w 'focus left'; +cmd_on_w 'floating disable'; +cmd_on_w 'floating enable'; + +does_i3_live; + +done_testing; diff --git a/testcases/t/322-match-error-crash.t b/testcases/t/322-match-error-crash.t new file mode 100644 index 00000000..24c02e20 --- /dev/null +++ b/testcases/t/322-match-error-crash.t @@ -0,0 +1,33 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • https://i3wm.org/downloads/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Verify i3 does not crash when reloading configuration with invalid match +# criteria. +# Ticket: #6141 +# Bug still in: 4.23-47-gbe840af4 +use i3test i3_config => < 0; +use X11::XCB qw(:all); + +my $pid = launch_with_config('-default'); + +sub net_frame_extents { + my ($window) = @_; + + # Ensure the previous i3 command has reached X11. + sync_with_i3; + + my $cookie = $x->get_property( + 0, + $window->{id}, + $x->atom(name => '_NET_FRAME_EXTENTS')->id, + GET_PROPERTY_TYPE_ANY, + 0, + 4 + ); + + my $reply = $x->get_property_reply($cookie->{sequence}); + my $len = $reply->{length}; + return [] if $len == 0; + + return unpack("L$len", $reply->{value}); +} + +sub is_net_frame_extents { + my ($window, $expect, $msg) = @_; + $msg //= ""; + $msg = "frame extents $msg"; + $msg =~ s/\s+$//; + my @extents = net_frame_extents($window); + is_deeply(\@extents, $expect, "$msg: got: @extents want: @$expect"); +} + +subtest 'basic border styles' => sub { + my $w = open_window; + cmd 'border normal 3'; + is_net_frame_extents($w, [3, 3, 18, 3], "normal border with 3px width"); + + cmd 'border pixel 1'; + is_net_frame_extents($w, [1, 1, 1, 1], "pixel border with 1px width"); + + cmd 'border pixel 5'; + is_net_frame_extents($w, [5, 5, 5, 5], "pixel border with 5px width"); + + open_window; + is_net_frame_extents($w, [5, 5, 5, 5], "other window does not affect"); + cmd 'kill'; + + cmd 'border normal 0'; + is_net_frame_extents($w, [0, 0, 18, 0], "normal border with 0px width"); + + cmd 'border none'; + is_net_frame_extents($w, [0, 0, 0, 0], "no border"); +}; + +subtest 'multiple windows in different layouts' => sub { + fresh_workspace; + + my $w1 = open_window; + my $w2 = open_window; + my $w3 = open_window; + + cmd 'border normal 2'; + is_net_frame_extents($w1, [2, 2, 18, 2], "window 1 in splith layout with normal border"); + is_net_frame_extents($w2, [2, 2, 18, 2], "window 2 in splith layout with normal border"); + is_net_frame_extents($w3, [2, 2, 18, 2], "window 3 in splith layout with normal border"); + + cmd 'layout stacking'; + is_net_frame_extents($w1, [2, 2, 0, 2], "window 1 in stacking layout"); + is_net_frame_extents($w2, [2, 2, 0, 2], "window 2 in stacking layout"); + is_net_frame_extents($w3, [2, 2, 0, 2], "window 3 in stacking layout"); + + cmd 'layout tabbed'; + is_net_frame_extents($w1, [2, 2, 0, 2], "window 1 in tabbed layout"); + is_net_frame_extents($w2, [2, 2, 0, 2], "window 2 in tabbed layout"); + is_net_frame_extents($w3, [2, 2, 0, 2], "window 3 in tabbed layout"); + + cmd 'layout splitv'; + is_net_frame_extents($w1, [2, 2, 18, 2], "window 1 in splitv layout"); + is_net_frame_extents($w2, [2, 2, 18, 2], "window 2 in splitv layout"); + is_net_frame_extents($w3, [2, 2, 18, 2], "window 3 in splitv layout"); +}; + +sub launch_with_hide_edge_borders { + my ($value) = @_; + my $config = < sub { + launch_with_hide_edge_borders('none'); + my $w = open_window; + cmd 'border normal 3'; + is_net_frame_extents($w, [3, 3, 18, 3], "window with normal borders (hide_edge_borders none)"); + + launch_with_hide_edge_borders('vertical'); + $w = open_window; + cmd 'border normal 3'; + is_net_frame_extents($w, [0, 0, 18, 3], "window with hidden vertical borders"); + + launch_with_hide_edge_borders('horizontal'); + $w = open_window; + cmd 'border normal 3'; + is_net_frame_extents($w, [3, 3, 18, 0], "window with hidden horizontal borders"); + cmd 'border pixel 3'; + is_net_frame_extents($w, [3, 3, 0, 0], "window with hidden horizontal borders"); + + launch_with_hide_edge_borders('both'); + $w = open_window; + cmd 'border normal 3'; + is_net_frame_extents($w, [0, 0, 18, 0], "window with all edge borders hidden"); + cmd 'border pixel 3'; + is_net_frame_extents($w, [0, 0, 0, 0], "window with all edge borders hidden"); + + launch_with_hide_edge_borders('smart'); + $w = open_window; + cmd 'border normal 3'; + is_net_frame_extents($w, [0, 0, 18, 0], "window with smart borders (single window)"); + cmd 'border pixel 3'; + is_net_frame_extents($w, [0, 0, 0, 0], "window with smart borders (single window)"); + + my $w2 = open_window; + cmd 'border normal 5'; + is_net_frame_extents($w, [3, 3, 3, 3], "first window with smart borders (multiple windows)"); + is_net_frame_extents($w2, [5, 5, 18, 5], "second window with smart borders (multiple windows)"); + + exit_gracefully($pid); + launch_with_config('-default'); +}; + +subtest 'floating windows' => sub { + fresh_workspace; + my $w = open_window; + cmd 'border normal 4'; + is_net_frame_extents($w, [4, 4, 18, 4], "tiling window with normal border"); + + cmd 'floating enable'; + is_net_frame_extents($w, [4, 4, 18, 4], "floating window with normal border"); + + cmd 'border pixel 2'; + is_net_frame_extents($w, [2, 2, 2, 2], "floating window with pixel border"); + + cmd 'border none'; + is_net_frame_extents($w, [0, 0, 0, 0], "floating window with no border"); +}; + +done_testing; diff --git a/testcases/t/324-for-window-reload-crash.t b/testcases/t/324-for-window-reload-crash.t new file mode 100644 index 00000000..b69f4806 --- /dev/null +++ b/testcases/t/324-for-window-reload-crash.t @@ -0,0 +1,33 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • https://i3wm.org/downloads/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# This test ensures that i3 does not crash when a for_window rule triggers a +# 'reload' command. +# Bug still in: 4.24-12-gab6a75a6 + +use i3test i3_config => <<'EOT'; +font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1 + +for_window [class="special"] reload +EOT + +my $window = open_window( + wm_class => 'special', +); + +does_i3_live; + +done_testing; diff --git a/testcases/t/325-layout-percent-and-marks.t b/testcases/t/325-layout-percent-and-marks.t new file mode 100644 index 00000000..e02d57b4 --- /dev/null +++ b/testcases/t/325-layout-percent-and-marks.t @@ -0,0 +1,61 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Tests that container percentages are correctly preserved when restoring +# layouts containing marks. +# See: https://github.com/i3/i3/issues/6391 +# +use i3test; +use File::Temp qw(tempfile); +use IO::Handle; + +################################################################################ +# Test 1: Layout with marks should preserve percent values +################################################################################ + +my $ws = fresh_workspace; + +my @content = @{get_ws_content($ws)}; +is(@content, 0, 'no nodes on the new workspace yet'); + +my ($fh, $filename) = tempfile(UNLINK => 1); +print $fh <<'EOT'; +{ + "layout": "splith", + "nodes": [ + { + "percent": 0.2, + "marks": ["left_mark"], + "swallows": [ + { "class": "^left$" } + ] + }, + { + "percent": 0.8, + "swallows": [ + { "class": "^right$" } + ] + } + ] +} +EOT +$fh->flush; +cmd "append_layout $filename"; +close($fh); + +does_i3_live; + +@content = @{get_ws_content($ws)}; +is(@content, 1, 'one node on the workspace now'); + +my @nodes = @{$content[0]->{nodes}}; +is(@nodes, 2, 'split container has two children'); + +cmp_float($nodes[0]->{percent}, 0.2, 'first container (with mark) got 20%'); +cmp_float($nodes[1]->{percent}, 0.8, 'second container got 80%'); + +my @marks = @{$nodes[0]->{marks}}; +is(@marks, 1, 'first container has one mark'); +is($marks[0], 'left_mark', 'mark is correctly applied'); + +done_testing; diff --git a/testcases/t/500-multi-monitor.t b/testcases/t/500-multi-monitor.t index 8d2c6496..540d54b3 100644 --- a/testcases/t/500-multi-monitor.t +++ b/testcases/t/500-multi-monitor.t @@ -11,14 +11,13 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests that the provided X-Server to the t/5??-*.t tests is actually providing # multiple monitors. # use i3test i3_config => < < < < to [output] ' command works # use List::Util qw(first); use i3test i3_config => < < < < < < < < < <' command will move containers across outputs. # use i3test i3_config => <` properly sends the workspace focus @@ -19,7 +19,6 @@ # # Bug still in: 4.6-195-g34232b8 use i3test i3_config => < < < < < < <' when moving containers across @@ -20,7 +20,6 @@ # Bug still in: 4.10.1-40-g0ad097e use List::Util qw(first); use i3test i3_config => < < < < numerical sort +# numbered w/ names -> numerical sort. Workspaces with the same number but +# different names sort by output, followed by creation time on each output. # named -> sort by creation time ################################################################################ cmd 'workspace 1'; @@ -94,6 +100,12 @@ assert_next('5'); assert_next('6'); assert_next('7'); +assert_next('8:a'); +assert_next('8:b'); +assert_next('8:c'); +assert_next('8:d'); +assert_next('8:e'); + assert_next('B'); assert_next('F'); assert_next('C'); @@ -112,6 +124,12 @@ assert_prev('C'); assert_prev('F'); assert_prev('B'); +assert_prev('8:e'); +assert_prev('8:d'); +assert_prev('8:c'); +assert_prev('8:b'); +assert_prev('8:a'); + assert_prev('7'); assert_prev('6'); assert_prev('5'); diff --git a/testcases/t/529-net-wm-desktop.t b/testcases/t/529-net-wm-desktop.t index f1da2980..e6b0a573 100644 --- a/testcases/t/529-net-wm-desktop.t +++ b/testcases/t/529-net-wm-desktop.t @@ -11,13 +11,12 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests for _NET_WM_DESKTOP. # Ticket: #2153 use i3test i3_config => < < < < < numerical sort +# numbered w/ names -> numerical sort. Workspaces with the same number but +# different names sort by output, followed by creation time on each output. # named -> sort by creation time ################################################################################ cmd 'workspace 1'; @@ -94,6 +100,12 @@ assert_next('5'); assert_next('6'); assert_next('7'); +assert_next('8:a'); +assert_next('8:b'); +assert_next('8:c'); +assert_next('8:d'); +assert_next('8:e'); + assert_next('B'); assert_next('F'); assert_next('C'); @@ -112,6 +124,12 @@ assert_prev('C'); assert_prev('F'); assert_prev('B'); +assert_prev('8:e'); +assert_prev('8:d'); +assert_prev('8:c'); +assert_prev('8:b'); +assert_prev('8:a'); + assert_prev('7'); assert_prev('6'); assert_prev('5'); diff --git a/testcases/t/536-net-wm-desktop_mm.t b/testcases/t/536-net-wm-desktop_mm.t index 3e4c9747..168d4040 100644 --- a/testcases/t/536-net-wm-desktop_mm.t +++ b/testcases/t/536-net-wm-desktop_mm.t @@ -11,13 +11,12 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests for _NET_WM_DESKTOP. # Ticket: #2153 use i3test i3_config => < < < 0; +my $config = <get_bar_config()->recv; is(@$bars, 1, 'one bar configured'); @@ -36,5 +38,28 @@ my $bar_id = shift @$bars; my $bar_config = i3->get_bar_config($bar_id)->recv; is_deeply($bar_config->{outputs}, [ "primary" ], 'bar_config output is primary'); +exit_gracefully($pid); + +# Same but for "nonprimary" + +$config = <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->{outputs}, [ "nonprimary" ], 'bar_config output is nonprimary'); +exit_gracefully($pid); done_testing; diff --git a/testcases/t/539-disable_focus_wrapping.t b/testcases/t/539-disable_focus_wrapping.t index 8d2e8472..586a3f20 100644 --- a/testcases/t/539-disable_focus_wrapping.t +++ b/testcases/t/539-disable_focus_wrapping.t @@ -11,7 +11,7 @@ # • http://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Tests that focus does not wrap when focus_wrapping is disabled in @@ -19,7 +19,6 @@ # Ticket: #2352 # Bug still in: 4.14-72-g6411130c use i3test i3_config => < 0; my $config = < < < 0; my $config = < 0; my $config = < 0; ####################################################################### my $config = <<'EOT'; -# i3 config file (v4) set $long_variable_name_with_short_value 1 set $$long_variable_name_with_short_value 2 set $$$long_variable_name_with_short_value 3 @@ -50,7 +49,6 @@ exit_gracefully($pid); ####################################################################### $config = <<'EOT'; -# i3 config file (v4) set $x 1 set $$x 2 EOT diff --git a/testcases/t/548-motif-hints.t b/testcases/t/548-motif-hints.t index 9fe890cf..04a29a10 100644 --- a/testcases/t/548-motif-hints.t +++ b/testcases/t/548-motif-hints.t @@ -11,17 +11,19 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Test that setting and unsetting motif hints updates window decorations # accordingly, respecting user configuration. # Ticket: #3678 # Ticket: #5149 +# Ticket: #5438 # Bug still in: 4.21 +use File::Temp qw(tempfile); use List::Util qw(first); -use i3test i3_autostart => 0; use X11::XCB qw(:all); +use i3test i3_autostart => 0; my $use_floating; sub subtest_with_config { @@ -30,7 +32,6 @@ sub subtest_with_config { subtest 'with tiling', sub { my $config = <( + %args, before_map => sub { my ($window) = @_; _change_motif_property($window, $value); @@ -117,7 +120,7 @@ sub is_border_style { } local $Test::Builder::Level = $Test::Builder::Level + 1; - is(get_border_style($window), $expected, $msg); + is(get_border_style, $expected, $msg); } ############################################################################### @@ -202,4 +205,43 @@ change_motif_property(1); is_border_style('pixel', 'because of user maximum=pixel'); }; +############################################################################### +# Test with append_layout +# See #5438 +############################################################################### + +$use_floating = 0; + +my $config = < 1); +print $fh <<'EOT'; +{ + "nodes": [ + { + "border": "none", + "swallows": [ + { + "class": "^Special$" + } + ], + "type": "con" + } + ], + "type": "con" +} +EOT +$fh->flush; +cmd "append_layout $filename"; + +# can't use get_border_style because append_layout creates a parent container +is(@{get_ws(focused_ws)->{nodes}}[0]->{nodes}[0]->{border}, 'none', 'placeholder has border style none'); + +$window = open_window_with_motifs(1, wm_class => 'Special', instance => 'Special', kill_all => 0); +is(@{get_ws(focused_ws)->{nodes}}[0]->{nodes}[0]->{border}, 'none', 'window has border style none'); + done_testing; diff --git a/testcases/t/549-focus-wrapping-gaps.t b/testcases/t/549-focus-wrapping-gaps.t index 135bea8c..e4d2f014 100644 --- a/testcases/t/549-focus-wrapping-gaps.t +++ b/testcases/t/549-focus-wrapping-gaps.t @@ -11,7 +11,7 @@ # • https://build.i3wm.org/docs/ipc.html # (or docs/ipc) # -# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# • https://i3wm.org/downloads/modern_perl_a4.pdf # (unless you are already familiar with Perl) # # Ensure focus wrapping works with negative gaps @@ -20,7 +20,6 @@ use i3test i3_autostart => 0; my $config = < 0; + +my $config = < 'win1'); +switch_and_check('nop', '1', 'sanity check'); + +cmd 'workspace "other workspace"'; +my $win2 = open_window(wm_class => 'win2'); +switch_and_check('nop', 'other workspace', 'sanity check'); + +switch_and_check('[class=win1] focus workspace', '1', 'class criterion'); +switch_and_check('[class=win2] focus workspace', 'other workspace', 'class criterion'); +switch_and_check('[class=win2] focus workspace', 'other workspace', 'repeat class criterion'); + +switch_and_check("[id=$win1->{id}] focus workspace", '1', 'window id criterion'); +switch_and_check("[id=$win1->{id}] focus workspace", '1', 'repeat window id criterion'); +switch_and_check("[id=$win2->{id}] focus workspace", 'other workspace', 'window id criterion'); + +kill_all_windows; +exit_gracefully($pid); + + +##################################################################### +# Test Back and forth +# See #5744 +##################################################################### +my $config = < 'win1'); +switch_and_check('nop', '1', 'sanity check'); + +cmd 'workspace "other workspace"'; +my $win2 = open_window(wm_class => 'win2'); +switch_and_check('nop', 'other workspace', 'sanity check'); + +switch_and_check('[class=win1] focus workspace', '1', 'class criterion'); +switch_and_check('[class=win2] focus workspace', 'other workspace', 'class criterion'); +switch_and_check('[class=win2] focus workspace', '1', 'repeat class criterion'); + +cmd 'workspace 3'; + +switch_and_check("[id=$win1->{id}] focus workspace", '1', 'window id criterion'); +switch_and_check("[id=$win1->{id}] focus workspace", '3', 'repeat window id criterion'); +switch_and_check("[id=$win2->{id}] focus workspace", 'other workspace', 'window id criterion'); +switch_and_check("[id=$win2->{id}] focus workspace", '3', 'window id criterion, back-and-forth'); + +exit_gracefully($pid); + +done_testing; diff --git a/testcases/t/550-split-redundant-containers.t b/testcases/t/550-split-redundant-containers.t new file mode 100644 index 00000000..3dfef399 --- /dev/null +++ b/testcases/t/550-split-redundant-containers.t @@ -0,0 +1,82 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Test that splitting and stacked/tabbed layouts do not create redundant +# containers. +# Ticket: #3001 +# Bug still in: 4.22-24-ga5da4d54 +use i3test; + +cmp_tree( + msg => 'toggling between split h/v', + layout_before => 'H[a*]', + layout_after => 'V[a*]', + cb => sub { + cmd 'split v, split h, split v'; + }); +cmp_tree( + msg => 'toggling between tabbed/stacked', + layout_before => 'H[a*]', + layout_after => 'S[a*]', + cb => sub { + cmd 'layout tabbed, layout stacked'; + }); +cmp_tree( + msg => 'split h to v and then tabbed', + layout_before => 'H[a*]', + layout_after => 'T[a*]', + cb => sub { + cmd 'split v, layout tabbed'; + }); +cmp_tree( + msg => 'repeat tabbed layout', + layout_before => 'H[a*]', + layout_after => 'T[a*]', + cb => sub { + cmd 'layout tabbed' for 1..5; + }); +cmp_tree( + msg => 'split v inside tabbed', + layout_before => 'H[a*]', + layout_after => 'T[V[a*]]', + cb => sub { + cmd 'layout tabbed, split v'; + }); +cmp_tree( + msg => 'split v inside tabbed and back to just tabbed', + layout_before => 'H[a*]', + layout_after => 'T[a*]', + cb => sub { + cmd 'layout tabbed, split v, layout tabbed'; + }); +cmp_tree( + msg => 'toggle split v inside tabbed', + layout_before => 'H[a*]', + layout_after => 'T[V[a*]]', + cb => sub { + cmd 'layout tabbed, split v, layout tabbed, split v'; + }); +cmp_tree( + msg => 'tabbed with 2 nodes inside other tabbed', + layout_before => 'T[a*]', + layout_after => 'T[T[a b*]]', + cb => sub { + cmd 'split v'; + open_window(wm_class => "b", name => "b"); + cmd 'layout tabbed'; + }); + +done_testing; diff --git a/testcases/t/551-net-wm-state-maximized.t b/testcases/t/551-net-wm-state-maximized.t new file mode 100644 index 00000000..fcd2a067 --- /dev/null +++ b/testcases/t/551-net-wm-state-maximized.t @@ -0,0 +1,128 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • http://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • http://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • http://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • https://i3wm.org/downloads/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Tests for setting and removing the _NET_WM_STATE_MAXIMIZED_VERT and +# _NET_WM_STATE_MAXIMIZED_HORZ atoms. +use i3test; +use X11::XCB qw(:all); + +sub maximized_vert { + my ($window) = @_; + return net_wm_state_contains($window, '_NET_WM_STATE_MAXIMIZED_VERT'); +} + +sub maximized_horz { + my ($window) = @_; + return net_wm_state_contains($window, '_NET_WM_STATE_MAXIMIZED_HORZ'); +} + +# Returns true if the given window is maximized in both directions. +sub maximized_both { + my ($window) = @_; + return maximized_vert($window) && maximized_horz($window); +} + +# Returns true if the given window is maximized in neither direction. +sub maximized_neither { + my ($window) = @_; + return !maximized_vert($window) && !maximized_horz($window); +} + +my ($winA, $winB, $winC); +fresh_workspace; + +$winA = open_window; +ok(maximized_both($winA), 'if there is just one window, it is maximized'); + +subtest 'two windows in default layout', sub { + $winB = open_window; + ok(maximized_vert($winA), 'vertically maximized'); + ok(maximized_vert($winB), 'vertically maximized'); + ok(!maximized_horz($winA), 'not horizontally maximized'); + ok(!maximized_horz($winB), 'not horizontally maximized'); + cmd 'kill'; +}; + +cmd 'fullscreen enable'; +ok(maximized_both($winA), 'fullscreen windows are maximized'); + +cmd 'fullscreen disable'; +ok(maximized_both($winA), 'disabling fullscreen retains maximized'); + +cmd 'floating enable'; +ok(maximized_neither($winA), 'floating windows are not maximized'); + +cmd 'floating disable'; +ok(maximized_both($winA), 'disabling floating sets maximized to true again'); + +# Open a second window. +$winB = open_window; + +# Windows in stacked or tabbed containers are considered maximized. +subtest 'stacking layout', sub { + cmd 'layout stacking'; + ok(maximized_both($winA), 'A maximized'); + ok(maximized_both($winB), 'B maximized'); +}; + +subtest 'tabbed layout', sub { + cmd 'layout tabbed'; + ok(maximized_both($winA), 'A maximized'); + ok(maximized_both($winB), 'B maximized'); +}; + +# Arrange the two windows with a vertical split. +subtest 'vertical split', sub { + cmd 'layout splitv'; + ok(!maximized_vert($winA), 'A not maximized vertically'); + ok(!maximized_vert($winB), 'B not maximized vertically'); + ok(maximized_horz($winA), 'A maximized horizontally'); + ok(maximized_horz($winB), 'B maximized horizontally'); +}; + +# Arrange the two windows with a horizontal split. +subtest 'horizontal split', sub { + cmd 'layout splith'; + ok(maximized_vert($winA), 'A maximized vertically'); + ok(maximized_vert($winB), 'B maximized vertically'); + ok(!maximized_horz($winA), 'A not maximized horizontally'); + ok(!maximized_horz($winB), 'B not maximized horizontally'); +}; + +# Add a vertical split within the horizontal split, and open a third window. +subtest 'vertical split within the horizontal split', sub { + cmd 'split vertical'; + $winC = open_window; + ok(maximized_vert($winA), 'maximized vertically'); + ok(!maximized_vert($winB), 'B not maximized vertically'); + ok(!maximized_vert($winC), 'C not maximized vertically'); + ok(!maximized_horz($winA), 'A not maximized horizontally'); + ok(!maximized_horz($winB), 'B not maximized horizontally'); + ok(!maximized_horz($winC), 'C not maximized horizontally'); +}; + +# Change the vertical split container to a tabbed container. +subtest 'tabbed container within horizontal split', sub { + cmd 'layout tabbed'; + ok(maximized_vert($winA), 'A maximized vertically'); + ok(maximized_vert($winB), 'B maximized vertically'); + ok(maximized_vert($winC), 'C maximized vertically'); + ok(!maximized_horz($winA), 'A not maximized horizontally'); + ok(!maximized_horz($winB), 'B not maximized horizontally'); + ok(!maximized_horz($winC), 'C not maximized horizontally'); +}; + +done_testing; diff --git a/testcases/t/552-net-wm-state-multiple-changes.t b/testcases/t/552-net-wm-state-multiple-changes.t new file mode 100644 index 00000000..136ca1af --- /dev/null +++ b/testcases/t/552-net-wm-state-multiple-changes.t @@ -0,0 +1,63 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • https://i3wm.org/downloads/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Test that i3 supports setting multiple _NET_WM_STATE changes in one ClientMessage. +# Bug still in: 4.23-21-g6a530de2 +use i3test; + +my $_NET_WM_STATE_REMOVE = 0; +my $_NET_WM_STATE_ADD = 1; +my $_NET_WM_STATE_TOGGLE = 2; +sub send_event { + my ($win, $add) = @_; + my $msg = pack "CCSLLLLLL", + X11::XCB::CLIENT_MESSAGE, # response_type + 32, # format + 0, # sequence + $win->id, # window + $x->atom(name => '_NET_WM_STATE')->id, # message type + ($add ? $_NET_WM_STATE_ADD : $_NET_WM_STATE_REMOVE), # data32[0] + $x->atom(name => '_NET_WM_STATE_FULLSCREEN')->id, # data32[1] + $x->atom(name => '_NET_WM_STATE_STICKY')->id, # data32[2] + 0, # data32[3] + 0; # data32[4] + + $x->send_event(0, $x->get_root_window(), X11::XCB::EVENT_MASK_SUBSTRUCTURE_REDIRECT, $msg); + sync_with_i3; +} + +my $ws1 = fresh_workspace; +my $win = open_floating_window; + +# Nothing to remove +send_event($win, 0); +is_num_fullscreen($ws1, 0, 'no fullscreen window'); + +# Enable +send_event($win, 1); +is_num_fullscreen($ws1, 1, 'one fullscreen window'); +my $ws2 = fresh_workspace; +is_num_fullscreen($ws2, 1, 'sticky fullscreen window in second workspace'); + +# Disable +send_event($win, 0); +is_num_fullscreen($ws1, 0, 'no fullscreen windows'); +is_num_fullscreen($ws2, 0, 'no fullscreen windows'); +cmd "workspace $ws1"; +is(@{get_ws($ws1)->{floating_nodes}}, 0, 'No floating (sticky) window in first workspace'); +is(@{get_ws($ws2)->{floating_nodes}}, 1, 'One floating (non-sticky) window in second workspace'); + +done_testing; diff --git a/testcases/t/553-popup_during_fullscreen.t b/testcases/t/553-popup_during_fullscreen.t new file mode 100644 index 00000000..441375f5 --- /dev/null +++ b/testcases/t/553-popup_during_fullscreen.t @@ -0,0 +1,150 @@ +#!perl +# vim:ts=4:sw=4:expandtab +# +# Please read the following documents before working on tests: +# • https://build.i3wm.org/docs/testsuite.html +# (or docs/testsuite) +# +# • https://build.i3wm.org/docs/lib-i3test.html +# (alternatively: perldoc ./testcases/lib/i3test.pm) +# +# • https://build.i3wm.org/docs/ipc.html +# (or docs/ipc) +# +# • https://i3wm.org/downloads/modern_perl_a4.pdf +# (unless you are already familiar with Perl) +# +# Test popup_during_fullscreen option +use i3test i3_autostart => 0; +use i3test::XTEST; + +sub setup { + kill_all_windows; + + my $ws = fresh_workspace; + my $w1 = open_window; + cmd 'fullscreen'; + is_num_fullscreen($ws, 1, 'sanity check: one fullscreen window'); + return ($ws, $w1); +} + +sub open_transient_for { + my $for = shift; + my $w = open_window({ dont_map => 1, rect => [ 30, 30, 50, 50 ] }); + $w->transient_for($for); + $w->map; + sync_with_i3; + return $w; +} + +sub open_without_map_wait { + my $w = open_window({ dont_map => 1 }); + $w->map; + sync_with_i3; + return $w; +} + +################################################################################ +# Test popup_during_fullscreen ignore +################################################################################ + +my $config = <input_focus, $w1->id, 'fullscreen window still focused'); + +open_without_map_wait; +is_num_fullscreen($ws, 1, 'still one fullscren window'); +is($x->input_focus, $w1->id, 'fullscreen window focused'); + +exit_gracefully($pid); + +################################################################################ +# Test popup_during_fullscreen leave_fullscreen +################################################################################ + +$config = <input_focus, $w1->id, 'fullscreen window focused'); + +# Fullscreen stays when regular windows open +$w1->fullscreen(1); +open_without_map_wait; +is_num_fullscreen($ws, 1, 'still one fullscreen window'); +is($x->input_focus, $w1->id, 'fullscreen window focused'); + + +exit_gracefully($pid); + +################################################################################ +# Test popup_during_fullscreen smart +################################################################################ + +$config = <input_focus, $w2->id, 'popup focused'); + +# Fullscreen stays when regular windows open +open_without_map_wait; +is_num_fullscreen($ws, 1, 'still one fullscreen window'); +is($x->input_focus, $w2->id, 'popup still focused'); + + +exit_gracefully($pid); + +################################################################################ +# Test popup_during_fullscreen all +# See #6062 +################################################################################ + +$config = <input_focus, $w2->id, 'popup focused'); + +# Fullscreen stays when regular windows open +$w1->fullscreen(1); +open_without_map_wait; +is_num_fullscreen($ws, 1, 'still one fullscreen window'); + +exit_gracefully($pid); + +done_testing; diff --git a/travis/push-balto.sh b/travis/push-balto.sh deleted file mode 100755 index ffa5ee15..00000000 --- a/travis/push-balto.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh - -set -e - -for fn in distbuild/deb/debian-amd64/*.deb distbuild/deb/debian-i386/*.deb -do - echo "pushing $fn to balto" - curl \ - --header "Authorization: Bearer ${BALTO_TOKEN}" \ - --form "package=@${fn}" \ - --form distribution=all \ - https://i3.baltorepo.com/i3/i3-autobuild/upload/ -done - -for fn in distbuild/deb/ubuntu-amd64/*.deb distbuild/deb/ubuntu-i386/*.deb -do - echo "pushing $fn to balto" - curl \ - --header "Authorization: Bearer ${BALTO_TOKEN}" \ - --form "package=@${fn}" \ - --form distribution=all \ - https://i3.baltorepo.com/i3/i3-autobuild-ubuntu/upload/ -done diff --git a/travis/run-tests.sh b/travis/run-tests.sh index 4cc70479..56f8afbe 100755 --- a/travis/run-tests.sh +++ b/travis/run-tests.sh @@ -1,29 +1,9 @@ #!/bin/sh -set -e -set -x +set -euxo pipefail cd build -# TODO: remove this workaround once https://bugs.debian.org/836723 is fixed -# Found at https://llvm.org/bugs/show_bug.cgi?id=27310#c8: -if [ "$CC" = "clang" ] -then - cat >fixasan.c < /etc/dpkg/dpkg.cfg.d/docker-apt-speedup -# Paper over occasional network flakiness of some mirrors. -RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry - -# NOTE: I tried exclusively using gce_debian_mirror.storage.googleapis.com -# instead of httpredir.debian.org, but the results (Fetched 123 MB in 36s (3357 -# kB/s)) are not any better than httpredir.debian.org (Fetched 123 MB in 34s -# (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. - -# Install mk-build-deps (for installing the i3 build dependencies), -# lintian (for checking spelling errors), -RUN linux32 apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - dpkg-dev devscripts git equivs \ - build-essential clang \ - lintian && \ - rm -rf /var/lib/apt/lists/* - -# Install i3 build dependencies. -COPY debian/control /usr/src/i3-debian-packaging/control -RUN linux32 apt-get update && \ - DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \ - rm -rf /var/lib/apt/lists/* - -# The user outside of Docker (GitHub Actions CI runner) and inside of Docker -# (root) are different, and newer versions of git error out in that scenario. -# To fix this, explicitly configure /usr/src/i3 as a safe directory: -RUN git config --global --add safe.directory /usr/src/i3 diff --git a/travis/travis-base-ubuntu-386.Dockerfile b/travis/travis-base-ubuntu-386.Dockerfile deleted file mode 100644 index 4a41fe3e..00000000 --- a/travis/travis-base-ubuntu-386.Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -# vim:ft=Dockerfile -# Same as travis-base.Dockerfile, but without the test suite dependencies since -# we only build Debian packages on Ubuntu i386, we don’t run the tests. -FROM i386/ubuntu:bionic - -RUN echo force-unsafe-io > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup -# Paper over occasional network flakiness of some mirrors. -RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry - -# NOTE: I tried exclusively using gce_debian_mirror.storage.googleapis.com -# instead of httpredir.debian.org, but the results (Fetched 123 MB in 36s (3357 -# kB/s)) are not any better than httpredir.debian.org (Fetched 123 MB in 34s -# (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now. - -# Install mk-build-deps (for installing the i3 build dependencies), -# lintian (for checking spelling errors), -RUN linux32 apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - dpkg-dev devscripts git equivs \ - build-essential clang \ - lintian && \ - rm -rf /var/lib/apt/lists/* - -# Install i3 build dependencies. -COPY debian/control /usr/src/i3-debian-packaging/control -RUN linux32 apt-get update && \ - DEBIAN_FRONTEND=noninteractive mk-build-deps --install --remove --tool 'apt-get --no-install-recommends -y' /usr/src/i3-debian-packaging/control && \ - rm -rf /var/lib/apt/lists/* - -# The user outside of Docker (GitHub Actions CI runner) and inside of Docker -# (root) are different, and newer versions of git error out in that scenario. -# To fix this, explicitly configure /usr/src/i3 as a safe directory: -RUN git config --global --add safe.directory /usr/src/i3 diff --git a/travis/travis-base-ubuntu.Dockerfile b/travis/travis-base-ubuntu.Dockerfile index 8523b92c..ef631ce4 100644 --- a/travis/travis-base-ubuntu.Dockerfile +++ b/travis/travis-base-ubuntu.Dockerfile @@ -1,7 +1,7 @@ # vim:ft=Dockerfile # Same as travis-base.Dockerfile, but without the test suite dependencies since # we only build Debian packages on Ubuntu, we don’t run the tests. -FROM ubuntu:bionic +FROM ubuntu:24.04 RUN echo force-unsafe-io > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup # Paper over occasional network flakiness of some mirrors. diff --git a/travis/travis-base.Dockerfile b/travis/travis-base.Dockerfile index 52ae0656..3733a9b8 100644 --- a/travis/travis-base.Dockerfile +++ b/travis/travis-base.Dockerfile @@ -16,7 +16,7 @@ RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ dpkg-dev devscripts git equivs \ - build-essential clang \ + build-essential clang libclang-rt-dev \ lintian \ libmodule-install-perl libanyevent-perl libextutils-pkgconfig-perl xcb-proto cpanminus xvfb xserver-xephyr xauth libinline-perl libinline-c-perl libxml-simple-perl libmouse-perl libmousex-nativetraits-perl libextutils-depends-perl perl libtest-deep-perl libtest-exception-perl libxml-parser-perl libtest-simple-perl libtest-fatal-perl libdata-dump-perl libtest-differences-perl libxml-tokeparser-perl libipc-run-perl libxcb-xtest0-dev libx11-xcb-perl libjson-xs-perl x11-xserver-utils && \ rm -rf /var/lib/apt/lists/*